summary refs log tree commit diff
path: root/src/core/devices
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
parent88c227d90a6b7b388c5c85d72802a0ca8f05ed5c (diff)
New upstream version 1.35.91 upstream/1.35.91
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/adsl/nm-atm-manager.c38
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c373
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c320
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.h12
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.c48
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.h12
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c225
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.h14
-rw-r--r--src/core/devices/bluetooth/tests/nm-bt-test.c18
-rw-r--r--src/core/devices/nm-acd-manager.c497
-rw-r--r--src/core/devices/nm-acd-manager.h34
-rw-r--r--src/core/devices/nm-device-6lowpan.c34
-rw-r--r--src/core/devices/nm-device-bond.c77
-rw-r--r--src/core/devices/nm-device-bridge.c120
-rw-r--r--src/core/devices/nm-device-dummy.c29
-rw-r--r--src/core/devices/nm-device-ethernet.c641
-rw-r--r--src/core/devices/nm-device-factory.c50
-rw-r--r--src/core/devices/nm-device-factory.h40
-rw-r--r--src/core/devices/nm-device-generic.c12
-rw-r--r--src/core/devices/nm-device-infiniband.c41
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c75
-rw-r--r--src/core/devices/nm-device-macsec.c122
-rw-r--r--src/core/devices/nm-device-macvlan.c57
-rw-r--r--src/core/devices/nm-device-ppp.c288
-rw-r--r--src/core/devices/nm-device-private.h139
-rw-r--r--src/core/devices/nm-device-tun.c51
-rw-r--r--src/core/devices/nm-device-utils.c25
-rw-r--r--src/core/devices/nm-device-utils.h13
-rw-r--r--src/core/devices/nm-device-veth.c24
-rw-r--r--src/core/devices/nm-device-vlan.c84
-rw-r--r--src/core/devices/nm-device-vrf.c42
-rw-r--r--src/core/devices/nm-device-vxlan.c49
-rw-r--r--src/core/devices/nm-device-wireguard.c261
-rw-r--r--src/core/devices/nm-device-wpan.c31
-rw-r--r--src/core/devices/nm-device.c9163
-rw-r--r--src/core/devices/nm-device.h231
-rw-r--r--src/core/devices/nm-lldp-listener.c70
-rw-r--r--src/core/devices/nm-lldp-listener.h2
-rw-r--r--src/core/devices/ovs/nm-device-ovs-bridge.c28
-rw-r--r--src/core/devices/ovs/nm-device-ovs-interface.c100
-rw-r--r--src/core/devices/ovs/nm-device-ovs-port.c51
-rw-r--r--src/core/devices/ovs/nm-ovs-factory.c62
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c339
-rw-r--r--src/core/devices/ovs/nm-ovsdb.h26
-rw-r--r--src/core/devices/team/nm-device-team.c145
-rw-r--r--src/core/devices/team/nm-team-factory.c10
-rw-r--r--src/core/devices/tests/meson.build1
-rw-r--r--src/core/devices/tests/test-acd.c259
-rw-r--r--src/core/devices/tests/test-lldp.c32
-rw-r--r--src/core/devices/wifi/meson.build1
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.c1262
-rw-r--r--src/core/devices/wifi/nm-device-iwd-p2p.h36
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c462
-rw-r--r--src/core/devices/wifi/nm-device-iwd.h4
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c46
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c212
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c559
-rw-r--r--src/core/devices/wifi/nm-device-wifi.h4
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.c497
-rw-r--r--src/core/devices/wifi/nm-iwd-manager.h40
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c48
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.h14
-rw-r--r--src/core/devices/wifi/nm-wifi-common.c44
-rw-r--r--src/core/devices/wifi/nm-wifi-factory.c77
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.c128
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.h21
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c256
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.h25
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c122
-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
80 files changed, 10175 insertions, 10653 deletions
diff --git a/src/core/devices/adsl/nm-atm-manager.c b/src/core/devices/adsl/nm-atm-manager.c
index bd9ed8cc..cc8d37e6 100644
--- a/src/core/devices/adsl/nm-atm-manager.c
+++ b/src/core/devices/adsl/nm-atm-manager.c
@@ -27,7 +27,7 @@
 
 typedef struct {
     NMUdevClient *udev_client;
-    GSList *      devices;
+    GSList       *devices;
 } NMAtmManagerPrivate;
 
 typedef struct {
@@ -51,7 +51,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(
     NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_ADSL_SETTING_NAME));
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     return g_object_new(NM_TYPE_ATM_MANAGER, NULL);
 }
@@ -62,7 +62,7 @@ static gboolean
 dev_get_attrs(struct udev_device *udev_device, const char **out_path, char **out_driver)
 {
     struct udev_device *parent = NULL;
-    const char *        driver, *path;
+    const char         *driver, *path;
 
     g_return_val_if_fail(udev_device != NULL, FALSE);
     g_return_val_if_fail(out_path != NULL, FALSE);
@@ -91,7 +91,7 @@ dev_get_attrs(struct udev_device *udev_device, const char **out_path, char **out
 static void
 device_destroyed(gpointer user_data, GObject *dead)
 {
-    NMAtmManager *       self = NM_ATM_MANAGER(user_data);
+    NMAtmManager        *self = NM_ATM_MANAGER(user_data);
     NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self);
 
     priv->devices = g_slist_remove(priv->devices, dead);
@@ -101,11 +101,11 @@ static void
 adsl_add(NMAtmManager *self, struct udev_device *udev_device)
 {
     NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self);
-    const char *         ifname, *sysfs_path = NULL;
-    char *               driver         = NULL;
-    gs_free char *       atm_index_path = NULL;
+    const char          *ifname, *sysfs_path = NULL;
+    char                *driver         = NULL;
+    gs_free char        *atm_index_path = NULL;
     int                  atm_index;
-    NMDevice *           device;
+    NMDevice            *device;
 
     g_return_if_fail(udev_device != NULL);
 
@@ -153,8 +153,8 @@ static void
 adsl_remove(NMAtmManager *self, struct udev_device *udev_device)
 {
     NMAtmManagerPrivate *priv  = NM_ATM_MANAGER_GET_PRIVATE(self);
-    const char *         iface = udev_device_get_sysname(udev_device);
-    GSList *             iter;
+    const char          *iface = udev_device_get_sysname(udev_device);
+    GSList              *iter;
 
     nm_log_dbg(LOGD_PLATFORM, "(%s): removing ATM device", iface);
 
@@ -177,9 +177,9 @@ adsl_remove(NMAtmManager *self, struct udev_device *udev_device)
 static void
 start(NMDeviceFactory *factory)
 {
-    NMAtmManager *          self = NM_ATM_MANAGER(factory);
-    NMAtmManagerPrivate *   priv = NM_ATM_MANAGER_GET_PRIVATE(self);
-    struct udev_enumerate * enumerate;
+    NMAtmManager           *self = NM_ATM_MANAGER(factory);
+    NMAtmManagerPrivate    *priv = NM_ATM_MANAGER_GET_PRIVATE(self);
+    struct udev_enumerate  *enumerate;
     struct udev_list_entry *devices;
 
     enumerate = nm_udev_client_enumerate_new(priv->udev_client);
@@ -203,10 +203,10 @@ static void
 handle_uevent(NMUdevClient *client, struct udev_device *device, gpointer user_data)
 {
     NMAtmManager *self = NM_ATM_MANAGER(user_data);
-    const char *  subsys;
-    const char *  ifindex;
+    const char   *subsys;
+    const char   *ifindex;
     guint64       seqnum;
-    const char *  action;
+    const char   *action;
 
     action = udev_device_get_action(device);
 
@@ -245,9 +245,9 @@ nm_atm_manager_init(NMAtmManager *self)
 static void
 dispose(GObject *object)
 {
-    NMAtmManager *       self = NM_ATM_MANAGER(object);
+    NMAtmManager        *self = NM_ATM_MANAGER(object);
     NMAtmManagerPrivate *priv = NM_ATM_MANAGER_GET_PRIVATE(self);
-    GSList *             iter;
+    GSList              *iter;
 
     for (iter = priv->devices; iter; iter = iter->next)
         g_object_weak_unref(G_OBJECT(iter->data), device_destroyed, self);
@@ -261,7 +261,7 @@ dispose(GObject *object)
 static void
 nm_atm_manager_class_init(NMAtmManagerClass *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/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index adcf8785..79705c28 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -15,13 +15,12 @@
 #include <unistd.h>
 #include <stdlib.h>
 
-#include "nm-ip4-config.h"
 #include "devices/nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
-#include "ppp/nm-ppp-manager-call.h"
-#include "ppp/nm-ppp-status.h"
+#include "nm-manager.h"
 #include "nm-setting-adsl.h"
 #include "nm-utils.h"
+#include "ppp/nm-ppp-mgr.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceAdsl
 #include "devices/nm-device-logging.h"
@@ -34,15 +33,14 @@ typedef struct {
     guint carrier_poll_id;
     int   atm_index;
 
-    /* PPP */
-    NMPPPManager *ppp_manager;
+    NMPppMgr *ppp_mgr;
 
     /* RFC 2684 bridging (PPPoE over ATM) */
-    int   brfd;
-    int   nas_ifindex;
-    char *nas_ifname;
-    guint nas_update_id;
-    guint nas_update_count;
+    int      brfd;
+    int      nas_ifindex;
+    char    *nas_ifname;
+    GSource *nas_update_source;
+    guint    nas_update_count;
 } NMDeviceAdslPrivate;
 
 struct _NMDeviceAdsl {
@@ -72,7 +70,7 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMSettingAdsl *s_adsl;
-    const char *   protocol;
+    const char    *protocol;
 
     if (!NM_DEVICE_CLASS(nm_device_adsl_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -93,11 +91,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     NMSettingAdsl *s_adsl;
 
@@ -126,12 +124,12 @@ complete_connection(NMDevice *           device,
 static gboolean
 br2684_assign_vcc(NMDeviceAdsl *self, NMSettingAdsl *s_adsl)
 {
-    NMDeviceAdslPrivate *     priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
+    NMDeviceAdslPrivate      *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
     struct sockaddr_atmpvc    addr;
     struct atm_backend_br2684 be;
     struct atm_qos            qos;
     int                       errsv, err, bufsize = 8192;
-    const char *              encapsulation;
+    const char               *encapsulation;
     gboolean                  is_llc;
 
     g_return_val_if_fail(priv->brfd == -1, FALSE);
@@ -216,18 +214,18 @@ error:
 }
 
 static void
-link_changed_cb(NMPlatform *    platform,
+link_changed_cb(NMPlatform     *platform,
                 int             obj_type_i,
                 int             ifindex,
                 NMPlatformLink *info,
                 int             change_type_i,
-                NMDeviceAdsl *  self)
+                NMDeviceAdsl   *self)
 {
     const NMPlatformSignalChangeType change_type = change_type_i;
 
     if (change_type == NM_PLATFORM_SIGNAL_REMOVED) {
         NMDeviceAdslPrivate *priv   = NM_DEVICE_ADSL_GET_PRIVATE(self);
-        NMDevice *           device = NM_DEVICE(self);
+        NMDevice            *device = NM_DEVICE(self);
 
         /* This only gets called for PPPoE connections and "nas" interfaces */
 
@@ -245,8 +243,8 @@ static gboolean
 pppoe_vcc_config(NMDeviceAdsl *self)
 {
     NMDeviceAdslPrivate *priv   = NM_DEVICE_ADSL_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
-    NMSettingAdsl *      s_adsl;
+    NMDevice            *device = NM_DEVICE(self);
+    NMSettingAdsl       *s_adsl;
 
     s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL);
 
@@ -271,11 +269,11 @@ pppoe_vcc_config(NMDeviceAdsl *self)
 }
 
 static gboolean
-nas_update_cb(gpointer user_data)
+nas_update_timeout_cb(gpointer user_data)
 {
-    NMDeviceAdsl *       self   = NM_DEVICE_ADSL(user_data);
+    NMDeviceAdsl        *self   = NM_DEVICE_ADSL(user_data);
     NMDeviceAdslPrivate *priv   = NM_DEVICE_ADSL_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDevice            *device = NM_DEVICE(self);
 
     nm_assert(priv->nas_ifname);
 
@@ -284,46 +282,48 @@ nas_update_cb(gpointer user_data)
     nm_assert(priv->nas_ifindex <= 0);
     priv->nas_ifindex =
         nm_platform_link_get_ifindex(nm_device_get_platform(device), priv->nas_ifname);
+
+    if (priv->nas_ifindex <= 0 && priv->nas_update_count <= 10) {
+        /* Keep waiting for it to appear */
+        return G_SOURCE_CONTINUE;
+    }
+
+    nm_clear_g_source_inst(&priv->nas_update_source);
+
     if (priv->nas_ifindex <= 0) {
-        if (priv->nas_update_count <= 10) {
-            /* Keep waiting for it to appear */
-            return G_SOURCE_CONTINUE;
-        }
-        priv->nas_update_id = 0;
         _LOGW(LOGD_ADSL,
               "failed to find br2684 interface %s ifindex after timeout",
               priv->nas_ifname);
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_FAILED,
                                 NM_DEVICE_STATE_REASON_BR2684_FAILED);
-        return G_SOURCE_REMOVE;
+        return G_SOURCE_CONTINUE;
     }
 
-    priv->nas_update_id = 0;
     _LOGD(LOGD_ADSL, "using br2684 iface '%s' index %d", priv->nas_ifname, priv->nas_ifindex);
 
     if (!pppoe_vcc_config(self)) {
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_FAILED,
                                 NM_DEVICE_STATE_REASON_BR2684_FAILED);
-        return G_SOURCE_REMOVE;
+        return G_SOURCE_CONTINUE;
     }
 
     nm_device_activate_schedule_stage2_device_config(device, TRUE);
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 static gboolean
 br2684_create_iface(NMDeviceAdsl *self)
 {
-    NMDeviceAdslPrivate *   priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
+    NMDeviceAdslPrivate    *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
     struct atm_newif_br2684 ni;
     nm_auto_close int       fd = -1;
-    int                     err, errsv;
+    int                     err;
+    int                     errsv;
     guint                   num = 0;
 
-    if (nm_clear_g_source(&priv->nas_update_id))
-        nm_assert_not_reached();
+    nm_assert(!priv->nas_update_source);
 
     fd = socket(PF_ATMPVC, SOCK_DGRAM | SOCK_CLOEXEC, ATM_AAL5);
     if (fd < 0) {
@@ -358,174 +358,187 @@ br2684_create_iface(NMDeviceAdsl *self)
 
         nm_strdup_reset(&priv->nas_ifname, ni.ifname);
         _LOGD(LOGD_ADSL, "waiting for br2684 iface '%s' to appear", priv->nas_ifname);
-        priv->nas_update_count = 0;
-        priv->nas_update_id    = g_timeout_add(100, nas_update_cb, self);
+        priv->nas_update_count  = 0;
+        priv->nas_update_source = nm_g_timeout_add_source(100, nas_update_timeout_cb, self);
         return TRUE;
     }
 }
 
-static NMActStageReturn
-act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+/*****************************************************************************/
+
+static void
+_ppp_mgr_cleanup(NMDeviceAdsl *self)
 {
-    NMDeviceAdsl *       self = NM_DEVICE_ADSL(device);
     NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
-    NMSettingAdsl *      s_adsl;
-    const char *         protocol;
 
-    s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL);
-
-    g_return_val_if_fail(s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
+    nm_clear_pointer(&priv->ppp_mgr, nm_ppp_mgr_destroy);
+}
 
-    protocol = nm_setting_adsl_get_protocol(s_adsl);
-    _LOGD(LOGD_ADSL, "using ADSL protocol '%s'", protocol);
+static void
+_ppp_mgr_stage3_maybe_ready(NMDeviceAdsl *self)
+{
+    NMDevice            *device = NM_DEVICE(self);
+    NMDeviceAdslPrivate *priv   = NM_DEVICE_ADSL_GET_PRIVATE(self);
+    int                  IS_IPv4;
 
-    if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) {
-        /* PPPoA doesn't need anything special */
-        return NM_ACT_STAGE_RETURN_SUCCESS;
-    }
+    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+        const int             addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const NMPppMgrIPData *ip_data;
 
-    if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
-        /* PPPoE needs RFC2684 bridging before we can do PPP over it */
-        if (priv->nas_ifindex <= 0) {
-            if (priv->nas_update_id == 0) {
-                if (!br2684_create_iface(self)) {
-                    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED);
-                    return NM_ACT_STAGE_RETURN_FAILURE;
-                }
-            }
-            return NM_ACT_STAGE_RETURN_POSTPONE;
-        }
-        return NM_ACT_STAGE_RETURN_SUCCESS;
+        ip_data = nm_ppp_mgr_get_ip_data(priv->ppp_mgr, addr_family);
+        if (ip_data->ip_received)
+            nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, ip_data->l3cd);
     }
 
-    _LOGW(LOGD_ADSL, "unhandled ADSL protocol '%s'", protocol);
-    return NM_ACT_STAGE_RETURN_SUCCESS;
+    if (nm_ppp_mgr_get_state(priv->ppp_mgr) >= NM_PPP_MGR_STATE_HAVE_IP_CONFIG)
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
 static void
-ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+_ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data, gpointer user_data)
 {
-    NMDevice *device = NM_DEVICE(user_data);
+    NMDeviceAdsl *self   = NM_DEVICE_ADSL(user_data);
+    NMDevice     *device = NM_DEVICE(self);
+    NMDeviceState device_state;
 
-    switch (status) {
-    case NM_PPP_STATUS_DISCONNECT:
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
-        break;
-    case NM_PPP_STATUS_DEAD:
-        nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
-        break;
-    default:
-        break;
-    }
-}
+    if (callback_data->callback_type != NM_PPP_MGR_CALLBACK_TYPE_STATE_CHANGED)
+        return;
 
-static void
-ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data)
-{
-    NMDevice *device = NM_DEVICE(user_data);
+    device_state = nm_device_get_state(device);
 
-    if (!nm_device_set_ip_ifindex(device, ifindex)) {
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+    if (callback_data->data.state >= _NM_PPP_MGR_STATE_FAILED_START) {
+        if (device_state <= NM_DEVICE_STATE_ACTIVATED)
+            nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, callback_data->data.reason);
+        return;
     }
-}
 
-static void
-ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
-{
-    NMDevice *device = NM_DEVICE(user_data);
+    if (device_state < NM_DEVICE_STATE_IP_CONFIG) {
+        if (callback_data->data.state >= NM_PPP_MGR_STATE_HAVE_IFINDEX) {
+            gs_free char         *old_name = NULL;
+            gs_free_error GError *error    = NULL;
+
+            if (!nm_device_take_over_link(device, callback_data->data.ifindex, &old_name, &error)) {
+                _LOGW(LOGD_DEVICE | LOGD_PPP,
+                      "could not take control of link %d: %s",
+                      callback_data->data.ifindex,
+                      error->message);
+                _ppp_mgr_cleanup(self);
+                nm_device_state_changed(device,
+                                        NM_DEVICE_STATE_FAILED,
+                                        NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+                return;
+            }
 
-    /* Ignore PPP IP4 events that come in after initial configuration */
-    if (nm_device_activate_ip4_state_in_conf(device))
-        nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config));
+            if (old_name)
+                nm_manager_remove_device(NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_ADSL);
+
+            nm_device_activate_schedule_stage2_device_config(device, FALSE);
+        }
+        return;
+    }
+
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
+/*****************************************************************************/
+
 static NMActStageReturn
-act_stage3_ip4_config_start(NMDevice *           device,
-                            NMIP4Config **       out_config,
-                            NMDeviceStateReason *out_failure_reason)
+act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceAdsl *       self = NM_DEVICE_ADSL(device);
+    NMDeviceAdsl        *self = NM_DEVICE_ADSL(device);
     NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
-    NMSettingAdsl *      s_adsl;
-    NMActRequest *       req;
-    GError *             err = NULL;
-    const char *         ppp_iface;
-
-    req = nm_device_get_act_request(device);
 
-    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+    if (!priv->ppp_mgr) {
+        gs_free_error GError *error = NULL;
+        NMSettingAdsl        *s_adsl;
+        const char           *protocol;
+        NMActRequest         *req;
+        const char           *ppp_iface;
+
+        req = nm_device_get_act_request(device);
+        g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+        s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL);
+        g_return_val_if_fail(s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
+
+        protocol = nm_setting_adsl_get_protocol(s_adsl);
+
+        _LOGD(LOGD_ADSL, "using ADSL protocol '%s'", protocol);
+
+        if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOA)) {
+            /* PPPoA doesn't need anything special */
+        } else if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
+            /* PPPoE needs RFC2684 bridging before we can do PPP over it */
+            if (priv->nas_ifindex <= 0) {
+                if (!priv->nas_update_source) {
+                    if (!br2684_create_iface(self)) {
+                        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_BR2684_FAILED);
+                        return NM_ACT_STAGE_RETURN_FAILURE;
+                    }
+                }
+                return NM_ACT_STAGE_RETURN_POSTPONE;
+            }
+        } else
+            nm_assert(nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_IPOATM));
+
+        /* PPPoE uses the NAS interface, not the ATM interface */
+        if (nm_streq0(protocol, NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
+            nm_assert(priv->nas_ifname);
+            ppp_iface = priv->nas_ifname;
+            _LOGD(LOGD_ADSL, "starting PPPoE on br2684 interface %s", priv->nas_ifname);
+        } else {
+            ppp_iface = nm_device_get_iface(device);
+            _LOGD(LOGD_ADSL, "starting PPPoA");
+        }
 
-    s_adsl = nm_device_get_applied_setting(device, NM_TYPE_SETTING_ADSL);
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+                                             .netns         = nm_device_get_netns(device),
+                                             .parent_iface  = ppp_iface,
+                                             .callback      = _ppp_mgr_callback,
+                                             .user_data     = self,
+                                             .act_req       = req,
+                                             .ppp_username  = nm_setting_adsl_get_username(s_adsl),
+                                             .timeout_secs  = 30,
+                                             .baud_override = 0,
+                                         }),
+                                         &error);
+        if (!priv->ppp_mgr) {
+            _LOGW(LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
+            *out_failure_reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED;
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
 
-    g_return_val_if_fail(s_adsl, NM_ACT_STAGE_RETURN_FAILURE);
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
 
-    /* PPPoE uses the NAS interface, not the ATM interface */
-    if (nm_streq0(nm_setting_adsl_get_protocol(s_adsl), NM_SETTING_ADSL_PROTOCOL_PPPOE)) {
-        nm_assert(priv->nas_ifname);
-        ppp_iface = priv->nas_ifname;
+    if (nm_ppp_mgr_get_state(priv->ppp_mgr) < NM_PPP_MGR_STATE_HAVE_IFINDEX)
+        return NM_ACT_STAGE_RETURN_POSTPONE;
 
-        _LOGD(LOGD_ADSL, "starting PPPoE on br2684 interface %s", priv->nas_ifname);
-    } else {
-        ppp_iface = nm_device_get_iface(device);
-        _LOGD(LOGD_ADSL, "starting PPPoA");
-    }
+    return NM_ACT_STAGE_RETURN_SUCCESS;
+}
 
-    priv->ppp_manager = nm_ppp_manager_create(ppp_iface, &err);
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
+{
+    NMDeviceAdsl        *self = NM_DEVICE_ADSL(device);
+    NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
+    NMPppMgrState        ppp_state;
 
-    if (priv->ppp_manager) {
-        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
-                                            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));
+    if (!priv->ppp_mgr) {
+        nm_assert_not_reached();
+        return;
     }
 
-    if (!priv->ppp_manager
-        || !nm_ppp_manager_start(priv->ppp_manager,
-                                 req,
-                                 nm_setting_adsl_get_username(s_adsl),
-                                 30,
-                                 0,
-                                 &err)) {
-        _LOGW(LOGD_ADSL, "PPP failed to start: %s", err->message);
-        g_error_free(err);
+    ppp_state = nm_ppp_mgr_get_state(priv->ppp_mgr);
 
-        g_clear_object(&priv->ppp_manager);
+    nm_assert(NM_IN_SET(ppp_state, NM_PPP_MGR_STATE_HAVE_IFINDEX, NM_PPP_MGR_STATE_HAVE_IP_CONFIG));
 
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (ppp_state < NM_PPP_MGR_STATE_HAVE_IP_CONFIG) {
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING, NULL);
+        return;
     }
 
-    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);
-    return NM_ACT_STAGE_RETURN_POSTPONE;
-}
-
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
-{
-    if (addr_family == AF_INET)
-        return act_stage3_ip4_config_start(device, (NMIP4Config **) out_config, out_failure_reason);
-
-    return NM_DEVICE_CLASS(nm_device_adsl_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
 static void
@@ -533,23 +546,15 @@ adsl_cleanup(NMDeviceAdsl *self)
 {
     NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
 
-    if (priv->ppp_manager) {
-        g_signal_handlers_disconnect_by_func(priv->ppp_manager,
-                                             G_CALLBACK(ppp_state_changed),
-                                             self);
-        g_signal_handlers_disconnect_by_func(priv->ppp_manager, G_CALLBACK(ppp_ip4_config), self);
-        nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
-        g_clear_object(&priv->ppp_manager);
-    }
+    _ppp_mgr_cleanup(self);
 
     g_signal_handlers_disconnect_by_func(nm_device_get_platform(NM_DEVICE(self)),
                                          G_CALLBACK(link_changed_cb),
                                          self);
 
-    nm_close(priv->brfd);
-    priv->brfd = -1;
+    nm_clear_fd(&priv->brfd);
 
-    nm_clear_g_source(&priv->nas_update_id);
+    nm_clear_g_source_inst(&priv->nas_update_source);
 
     /* FIXME: kernel has no way of explicitly deleting the 'nasX' interface yet,
      * so it gets leaked.  It does get destroyed when it's no longer in use,
@@ -572,7 +577,7 @@ carrier_update_cb(gpointer user_data)
 {
     NMDeviceAdsl *self = NM_DEVICE_ADSL(user_data);
     int           carrier;
-    char *        path;
+    char         *path;
 
     path    = g_strdup_printf("/sys/class/atm/%s/carrier",
                            NM_ASSERT_VALID_PATH_COMPONENT(nm_device_get_iface(NM_DEVICE(self))));
@@ -627,7 +632,7 @@ nm_device_adsl_init(NMDeviceAdsl *self)
 static void
 constructed(GObject *object)
 {
-    NMDeviceAdsl *       self = NM_DEVICE_ADSL(object);
+    NMDeviceAdsl        *self = NM_DEVICE_ADSL(object);
     NMDeviceAdslPrivate *priv = NM_DEVICE_ADSL_GET_PRIVATE(self);
 
     G_OBJECT_CLASS(nm_device_adsl_parent_class)->constructed(object);
@@ -681,9 +686,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_adsl = {
 static void
 nm_device_adsl_class_init(NMDeviceAdslClass *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->constructed  = constructed;
     object_class->dispose      = dispose;
@@ -699,9 +704,9 @@ nm_device_adsl_class_init(NMDeviceAdslClass *klass)
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->complete_connection         = complete_connection;
 
-    device_class->act_stage2_config          = act_stage2_config;
-    device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
-    device_class->deactivate                 = deactivate;
+    device_class->act_stage2_config    = act_stage2_config;
+    device_class->act_stage3_ip_config = act_stage3_ip_config;
+    device_class->deactivate           = deactivate;
 
     obj_properties[PROP_ATM_INDEX] =
         g_param_spec_int(NM_DEVICE_ADSL_ATM_INDEX,
diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index 36e442ad..05f85297 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -38,7 +38,7 @@
 #define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN)
 
 typedef struct {
-    const char *            bdaddr;
+    const char             *bdaddr;
     CList                   lst_head;
     NMBluetoothCapabilities bt_type : 8;
     char                    bdaddr_data[];
@@ -46,24 +46,24 @@ typedef struct {
 
 typedef struct {
     NMSettingsConnection *sett_conn;
-    ConnDataHead *        cdata_hd;
+    ConnDataHead         *cdata_hd;
     CList                 lst;
 } ConnDataElem;
 
 typedef struct {
-    GCancellable *             ext_cancellable;
-    GCancellable *             int_cancellable;
+    GCancellable              *ext_cancellable;
+    GCancellable              *int_cancellable;
     NMBtVTableRegisterCallback callback;
     gpointer                   callback_user_data;
     gulong                     ext_cancelled_id;
 } NetworkServerRegisterReqData;
 
 typedef struct {
-    GCancellable *          ext_cancellable;
-    GCancellable *          int_cancellable;
+    GCancellable           *ext_cancellable;
+    GCancellable           *int_cancellable;
     NMBluezManagerConnectCb callback;
     gpointer                callback_user_data;
-    char *                  device_name;
+    char                   *device_name;
     gulong                  ext_cancelled_id;
     guint                   timeout_id;
     guint                   timeout_wait_connect_id;
@@ -97,16 +97,16 @@ typedef struct {
 
     struct {
         CList                         lst;
-        char *                        adapter_address;
-        NMDevice *                    device_br;
+        char                         *adapter_address;
+        NMDevice                     *device_br;
         NetworkServerRegisterReqData *r_req_data;
     } x_network_server;
 
     struct {
         NMSettingsConnection *panu_connection;
-        NMDeviceBt *          device_bt;
+        NMDeviceBt           *device_bt;
         DeviceConnectReqData *c_req_data;
-        NMBluez5DunContext *  connect_dun_context;
+        NMBluez5DunContext   *connect_dun_context;
         gulong                device_bt_signal_id;
     } x_device;
 
@@ -142,7 +142,7 @@ typedef struct {
 } BzDBusObj;
 
 typedef struct {
-    NMManager * manager;
+    NMManager  *manager;
     NMSettings *settings;
 
     GDBusConnection *dbus_connection;
@@ -194,7 +194,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(
     NM_LINK_TYPE_BNEP) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_BLUETOOTH_SETTING_NAME))
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     return g_object_new(NM_TYPE_BLUEZ_MANAGER, NULL);
 }
@@ -214,8 +214,8 @@ convert_uuids_to_capabilities(const char *const *strv)
     if (strv) {
         for (; strv[0]; strv++) {
             gs_free char *s_part1 = NULL;
-            const char *  str     = strv[0];
-            const char *  s;
+            const char   *str     = strv[0];
+            const char   *s;
 
             s = strchr(str, '-');
             if (!s)
@@ -245,8 +245,8 @@ static void     _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, cons
 static gboolean _bzobjs_network_server_is_usable(const BzDBusObj *bzobj, gboolean require_powered);
 static gboolean _bzobjs_is_dead(const BzDBusObj *bzobj);
 static gboolean _bzobjs_device_is_usable(const BzDBusObj *bzobj,
-                                         BzDBusObj **     out_adapter_bzobj,
-                                         gboolean *       out_create_panu_connection);
+                                         BzDBusObj      **out_adapter_bzobj,
+                                         gboolean        *out_create_panu_connection);
 static gboolean _bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj);
 static ConnDataHead *
 _conn_track_find_head(NMBluezManager *self, NMBluetoothCapabilities bt_type, const char *bdaddr);
@@ -293,9 +293,9 @@ _network_server_register_req_data_complete(NetworkServerRegisterReqData *r_req_d
 
 static void
 _device_connect_req_data_complete(DeviceConnectReqData *c_req_data,
-                                  NMBluezManager *      self,
-                                  const char *          device_name,
-                                  GError *              error)
+                                  NMBluezManager       *self,
+                                  const char           *device_name,
+                                  GError               *error)
 {
     nm_assert((!!device_name) != (!!error));
 
@@ -371,7 +371,7 @@ _bz_dbus_obj_free(BzDBusObj *bzobj)
 static const char *
 _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len)
 {
-    char *      buf0   = buf;
+    char       *buf0   = buf;
     const char *prefix = "";
     gboolean    device_is_usable;
     gboolean    create_panu_connection = FALSE;
@@ -591,7 +591,7 @@ static BzDBusObj *
 _bzobjs_add(NMBluezManager *self, const char *object_path)
 {
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    BzDBusObj *            bzobj;
+    BzDBusObj             *bzobj;
 
     bzobj = _bz_dbus_obj_new(self, object_path);
     if (!g_hash_table_add(priv->bzobjs, bzobj))
@@ -642,14 +642,14 @@ _bzobjs_adapter_is_usable_for_device(const BzDBusObj *bzobj)
 
 static gboolean
 _bzobjs_device_is_usable(const BzDBusObj *bzobj,
-                         BzDBusObj **     out_adapter_bzobj,
-                         gboolean *       out_create_panu_connection)
+                         BzDBusObj      **out_adapter_bzobj,
+                         gboolean        *out_create_panu_connection)
 {
-    NMBluezManager *       self;
+    NMBluezManager        *self;
     NMBluezManagerPrivate *priv;
     gboolean               usable_dun = FALSE;
     gboolean               usable_nap = FALSE;
-    BzDBusObj *            bzobj_adapter;
+    BzDBusObj             *bzobj_adapter;
     gboolean               create_panu_connection = FALSE;
 
     if (!bzobj->d_has_device_iface
@@ -805,14 +805,14 @@ _conn_track_find_elem(NMBluezManager *self, NMSettingsConnection *sett_conn)
 }
 
 static gboolean
-_conn_track_is_relevant_connection(NMConnection *           connection,
+_conn_track_is_relevant_connection(NMConnection            *connection,
                                    NMBluetoothCapabilities *out_bt_type,
-                                   const char **            out_bdaddr)
+                                   const char             **out_bdaddr)
 {
-    NMSettingBluetooth *    s_bt;
+    NMSettingBluetooth     *s_bt;
     NMBluetoothCapabilities bt_type;
-    const char *            bdaddr;
-    const char *            b_type;
+    const char             *bdaddr;
+    const char             *b_type;
 
     s_bt = nm_connection_get_setting_bluetooth(connection);
     if (!s_bt)
@@ -840,9 +840,9 @@ _conn_track_is_relevant_connection(NMConnection *           connection,
 }
 
 static gboolean
-_conn_track_is_relevant_sett_conn(NMSettingsConnection *   sett_conn,
+_conn_track_is_relevant_sett_conn(NMSettingsConnection    *sett_conn,
                                   NMBluetoothCapabilities *out_bt_type,
-                                  const char **            out_bdaddr)
+                                  const char             **out_bdaddr)
 {
     NMConnection *connection;
 
@@ -854,25 +854,25 @@ _conn_track_is_relevant_sett_conn(NMSettingsConnection *   sett_conn,
 }
 
 static gboolean
-_conn_track_is_relevant_for_sett_conn(NMSettingsConnection *  sett_conn,
+_conn_track_is_relevant_for_sett_conn(NMSettingsConnection   *sett_conn,
                                       NMBluetoothCapabilities bt_type,
-                                      const char *            bdaddr)
+                                      const char             *bdaddr)
 {
     NMBluetoothCapabilities x_bt_type;
-    const char *            x_bdaddr;
+    const char             *x_bdaddr;
 
     return bdaddr && _conn_track_is_relevant_sett_conn(sett_conn, &x_bt_type, &x_bdaddr)
            && x_bt_type == bt_type && nm_streq(x_bdaddr, bdaddr);
 }
 
 static void
-_conn_track_schedule_notify(NMBluezManager *        self,
+_conn_track_schedule_notify(NMBluezManager         *self,
                             NMBluetoothCapabilities bt_type,
-                            const char *            bdaddr)
+                            const char             *bdaddr)
 {
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     GHashTableIter         iter;
-    BzDBusObj *            bzobj;
+    BzDBusObj             *bzobj;
 
     g_hash_table_iter_init(&iter, priv->bzobjs);
     while (g_hash_table_iter_next(&iter, (gpointer *) &bzobj, NULL)) {
@@ -885,19 +885,19 @@ _conn_track_schedule_notify(NMBluezManager *        self,
 }
 
 static void
-_conn_track_update(NMBluezManager *      self,
+_conn_track_update(NMBluezManager       *self,
                    NMSettingsConnection *sett_conn,
                    gboolean              track,
-                   gboolean *            out_changed,
-                   gboolean *            out_changed_usable,
-                   ConnDataElem **       out_conn_data_elem)
-{
-    NMBluezManagerPrivate * priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    ConnDataHead *          cdata_hd;
-    ConnDataElem *          cdata_el;
-    ConnDataElem *          cdata_el_remove = NULL;
+                   gboolean             *out_changed,
+                   gboolean             *out_changed_usable,
+                   ConnDataElem        **out_conn_data_elem)
+{
+    NMBluezManagerPrivate  *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    ConnDataHead           *cdata_hd;
+    ConnDataElem           *cdata_el;
+    ConnDataElem           *cdata_el_remove = NULL;
     NMBluetoothCapabilities bt_type;
-    const char *            bdaddr;
+    const char             *bdaddr;
     gboolean                changed        = FALSE;
     gboolean                changed_usable = FALSE;
     char                    sbuf_cap[100];
@@ -946,7 +946,7 @@ _conn_track_update(NMBluezManager *      self,
 out_remove:
     if (cdata_el_remove) {
         GHashTableIter iter;
-        BzDBusObj *    bzobj;
+        BzDBusObj     *bzobj;
 
         _LOGT("connection: untrack for %s, %s: %s (%s)",
               nm_bluetooth_capability_to_string(cdata_el_remove->cdata_hd->bt_type,
@@ -989,10 +989,10 @@ cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, NMBlu
 }
 
 static void
-cp_connection_updated(NMSettings *          settings,
+cp_connection_updated(NMSettings           *settings,
                       NMSettingsConnection *sett_conn,
                       guint                 update_reason_u,
-                      NMBluezManager *      self)
+                      NMBluezManager       *self)
 {
     _conn_track_update(self, sett_conn, TRUE, NULL, NULL, NULL);
 }
@@ -1032,8 +1032,8 @@ _network_server_find_has_device(NMBluezManagerPrivate *priv, NMDevice *device)
 
 static BzDBusObj *
 _network_server_find_available(NMBluezManagerPrivate *priv,
-                               const char *           addr,
-                               NMDevice *             device_accept_busy)
+                               const char            *addr,
+                               NMDevice              *device_accept_busy)
 {
     BzDBusObj *bzobj;
 
@@ -1052,10 +1052,10 @@ _network_server_find_available(NMBluezManagerPrivate *priv,
 
 static gboolean
 _network_server_vt_is_available(const NMBtVTableNetworkServer *vtable,
-                                const char *                   addr,
-                                NMDevice *                     device_accept_busy)
+                                const char                    *addr,
+                                NMDevice                      *device_accept_busy)
 {
-    NMBluezManager *       self = _network_server_get_bluez_manager(vtable);
+    NMBluezManager        *self = _network_server_get_bluez_manager(vtable);
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
 
     return !!_network_server_find_available(priv, addr, device_accept_busy);
@@ -1064,9 +1064,9 @@ _network_server_vt_is_available(const NMBtVTableNetworkServer *vtable,
 static void
 _network_server_register_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    gs_unref_variant GVariant *ret = NULL;
-    gs_free_error GError *error    = NULL;
-    BzDBusObj *           bzobj;
+    gs_unref_variant GVariant *ret   = NULL;
+    gs_free_error GError      *error = NULL;
+    BzDBusObj                 *bzobj;
 
     ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error);
     if (!ret && nm_utils_error_is_cancelled(error))
@@ -1092,18 +1092,18 @@ _network_server_register_cancelled_cb(GCancellable *cancellable, BzDBusObj *bzob
 
 static gboolean
 _network_server_vt_register_bridge(const NMBtVTableNetworkServer *vtable,
-                                   const char *                   addr,
-                                   NMDevice *                     device,
-                                   GCancellable *                 cancellable,
+                                   const char                    *addr,
+                                   NMDevice                      *device,
+                                   GCancellable                  *cancellable,
                                    NMBtVTableRegisterCallback     callback,
                                    gpointer                       callback_user_data,
-                                   GError **                      error)
+                                   GError                       **error)
 {
-    NMBluezManager *              self = _network_server_get_bluez_manager(vtable);
-    NMBluezManagerPrivate *       priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    NMBluezManager               *self = _network_server_get_bluez_manager(vtable);
+    NMBluezManagerPrivate        *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     NetworkServerRegisterReqData *r_req_data;
-    BzDBusObj *                   bzobj;
-    const char *                  ifname;
+    BzDBusObj                    *bzobj;
+    const char                   *ifname;
 
     g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
     g_return_val_if_fail(G_IS_CANCELLABLE(cancellable), FALSE);
@@ -1174,8 +1174,8 @@ _network_server_vt_register_bridge(const NMBtVTableNetworkServer *vtable,
 static void
 _network_server_unregister_bridge_complete_on_idle_cb(gpointer user_data, GCancellable *cancellable)
 {
-    gs_free_error GError *        error  = NULL;
-    gs_free char *                reason = NULL;
+    gs_free_error GError         *error  = NULL;
+    gs_free char                 *reason = NULL;
     NetworkServerRegisterReqData *r_req_data;
 
     nm_utils_user_data_unpack(user_data, &r_req_data, &reason);
@@ -1190,9 +1190,9 @@ _network_server_unregister_bridge_complete_on_idle_cb(gpointer user_data, GCance
 static void
 _network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const char *reason)
 {
-    NMBluezManagerPrivate *    priv             = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    NMBluezManagerPrivate               *priv   = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     _nm_unused gs_unref_object NMDevice *device = NULL;
-    NetworkServerRegisterReqData *       r_req_data;
+    NetworkServerRegisterReqData        *r_req_data;
 
     nm_assert(NM_IS_DEVICE(bzobj->x_network_server.device_br));
 
@@ -1242,9 +1242,9 @@ _network_server_unregister_bridge(NMBluezManager *self, BzDBusObj *bzobj, const
 static gboolean
 _network_server_vt_unregister_bridge(const NMBtVTableNetworkServer *vtable, NMDevice *device)
 {
-    NMBluezManager *       self = _network_server_get_bluez_manager(vtable);
+    NMBluezManager        *self = _network_server_get_bluez_manager(vtable);
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    BzDBusObj *            bzobj;
+    BzDBusObj             *bzobj;
 
     g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
 
@@ -1258,7 +1258,7 @@ _network_server_vt_unregister_bridge(const NMBtVTableNetworkServer *vtable, NMDe
 static void
 _network_server_process_change(BzDBusObj *bzobj, gboolean *out_emit_device_availability_changed)
 {
-    NMBluezManager *       self = bzobj->self;
+    NMBluezManager        *self = bzobj->self;
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     gboolean               network_server_is_usable;
     gboolean               emit_device_availability_changed = FALSE;
@@ -1303,13 +1303,13 @@ _network_server_process_change(BzDBusObj *bzobj, gboolean *out_emit_device_avail
 static void
 _conn_create_panu_connection(NMBluezManager *self, BzDBusObj *bzobj)
 {
-    NMBluezManagerPrivate *priv              = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    NMBluezManagerPrivate        *priv       = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     gs_unref_object NMConnection *connection = NULL;
-    NMSettingsConnection *        added;
-    NMSetting *                   setting;
-    gs_free char *                id = NULL;
+    NMSettingsConnection         *added;
+    NMSetting                    *setting;
+    gs_free char                 *id = NULL;
     char                          uuid[37];
-    gs_free_error GError *error = NULL;
+    gs_free_error GError         *error = NULL;
 
     nm_uuid_generate_random_str_arr(uuid);
     id = g_strdup_printf(_("%s Network"), bzobj->d_device.name);
@@ -1398,7 +1398,7 @@ _device_state_changed_cb(NMDevice *device,
 static void
 _device_process_change(BzDBusObj *bzobj)
 {
-    NMBluezManager *self                       = bzobj->self;
+    NMBluezManager             *self           = bzobj->self;
     gs_unref_object NMDeviceBt *device_added   = NULL;
     gs_unref_object NMDeviceBt *device_deleted = NULL;
     gboolean                    device_is_usable;
@@ -1513,7 +1513,7 @@ static void
 _process_change_idle_all(NMBluezManager *self, gboolean *out_emit_device_availability_changed)
 {
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    BzDBusObj *            bzobj;
+    BzDBusObj             *bzobj;
 
     while (
         (bzobj =
@@ -1537,7 +1537,7 @@ _process_change_idle_all(NMBluezManager *self, gboolean *out_emit_device_availab
 static gboolean
 _process_change_idle_cb(gpointer user_data)
 {
-    NMBluezManager *       self                             = user_data;
+    NMBluezManager        *self                             = user_data;
     NMBluezManagerPrivate *priv                             = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     gboolean               emit_device_availability_changed = FALSE;
 
@@ -1622,7 +1622,7 @@ _dbus_process_changes(NMBluezManager *self, BzDBusObj *bzobj, const char *log_re
 
     if (recheck_devices_for_adapter) {
         GHashTableIter iter;
-        BzDBusObj *    bzobj2;
+        BzDBusObj     *bzobj2;
 
         /* we got a change to the availability of an adapter. We might need to recheck
          * all devices that use this adapter... */
@@ -1651,17 +1651,17 @@ _dbus_process_changes(NMBluezManager *self, BzDBusObj *bzobj, const char *log_re
                  NM_BLUEZ5_NETWORK_SERVER_INTERFACE)
 
 static gboolean
-_dbus_handle_properties_changed(NMBluezManager *   self,
-                                const char *       object_path,
-                                const char *       interface_name,
-                                GVariant *         changed_properties,
+_dbus_handle_properties_changed(NMBluezManager    *self,
+                                const char        *object_path,
+                                const char        *interface_name,
+                                GVariant          *changed_properties,
                                 const char *const *invalidated_properties,
-                                BzDBusObj **       inout_bzobj)
+                                BzDBusObj        **inout_bzobj)
 {
-    BzDBusObj *  bzobj   = NULL;
+    BzDBusObj   *bzobj   = NULL;
     gboolean     changed = FALSE;
-    const char * property_name;
-    GVariant *   property_value;
+    const char  *property_name;
+    GVariant    *property_value;
     GVariantIter iter_prop;
     gsize        i;
 
@@ -1933,14 +1933,14 @@ _dbus_handle_properties_changed(NMBluezManager *   self,
 
 static void
 _dbus_handle_interface_added(NMBluezManager *self,
-                             const char *    object_path,
-                             GVariant *      ifaces,
+                             const char     *object_path,
+                             GVariant       *ifaces,
                              gboolean        initial_get_managed_objects)
 {
-    BzDBusObj *  bzobj   = NULL;
+    BzDBusObj   *bzobj   = NULL;
     gboolean     changed = FALSE;
-    const char * interface_name;
-    GVariant *   changed_properties;
+    const char  *interface_name;
+    GVariant    *changed_properties;
     GVariantIter iter_ifaces;
 
     nm_assert(g_variant_is_of_type(ifaces, G_VARIANT_TYPE("a{sa{sv}}")));
@@ -1966,9 +1966,9 @@ _dbus_handle_interface_added(NMBluezManager *self,
 }
 
 static gboolean
-_dbus_handle_interface_removed(NMBluezManager *   self,
-                               const char *       object_path,
-                               BzDBusObj **       inout_bzobj,
+_dbus_handle_interface_removed(NMBluezManager    *self,
+                               const char        *object_path,
+                               BzDBusObj        **inout_bzobj,
                                const char *const *removed_interfaces)
 {
     gboolean   changed = FALSE;
@@ -2056,16 +2056,16 @@ _dbus_handle_interface_removed(NMBluezManager *   self,
 
 static void
 _dbus_managed_objects_changed_cb(GDBusConnection *connection,
-                                 const char *     sender_name,
-                                 const char *     arg_object_path,
-                                 const char *     interface_name,
-                                 const char *     signal_name,
-                                 GVariant *       parameters,
+                                 const char      *sender_name,
+                                 const char      *arg_object_path,
+                                 const char      *interface_name,
+                                 const char      *signal_name,
+                                 GVariant        *parameters,
                                  gpointer         user_data)
 {
-    NMBluezManager *       self  = user_data;
+    NMBluezManager        *self  = user_data;
     NMBluezManagerPrivate *priv  = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    BzDBusObj *            bzobj = NULL;
+    BzDBusObj             *bzobj = NULL;
     gboolean               changed;
 
     nm_assert(nm_streq0(interface_name, DBUS_INTERFACE_OBJECT_MANAGER));
@@ -2077,7 +2077,7 @@ _dbus_managed_objects_changed_cb(GDBusConnection *connection,
 
     if (nm_streq(signal_name, "InterfacesAdded")) {
         gs_unref_variant GVariant *interfaces_and_properties = NULL;
-        const char *               object_path;
+        const char                *object_path;
 
         if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oa{sa{sv}})")))
             return;
@@ -2090,7 +2090,7 @@ _dbus_managed_objects_changed_cb(GDBusConnection *connection,
 
     if (nm_streq(signal_name, "InterfacesRemoved")) {
         gs_free const char **interfaces = NULL;
-        const char *         object_path;
+        const char          *object_path;
 
         if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(oas)")))
             return;
@@ -2106,19 +2106,19 @@ _dbus_managed_objects_changed_cb(GDBusConnection *connection,
 
 static void
 _dbus_properties_changed_cb(GDBusConnection *connection,
-                            const char *     sender_name,
-                            const char *     object_path,
-                            const char *     signal_interface_name,
-                            const char *     signal_name,
-                            GVariant *       parameters,
+                            const char      *sender_name,
+                            const char      *object_path,
+                            const char      *signal_interface_name,
+                            const char      *signal_name,
+                            GVariant        *parameters,
                             gpointer         user_data)
 {
-    NMBluezManager *       self = user_data;
-    NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    const char *           interface_name;
+    NMBluezManager            *self = user_data;
+    NMBluezManagerPrivate     *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    const char                *interface_name;
     gs_unref_variant GVariant *changed_properties     = NULL;
-    gs_free const char **      invalidated_properties = NULL;
-    BzDBusObj *                bzobj                  = NULL;
+    gs_free const char       **invalidated_properties = NULL;
+    BzDBusObj                 *bzobj                  = NULL;
 
     if (priv->get_managed_objects_cancellable) {
         /* we still wait for the initial GetManagedObjects(). Ignore the event. */
@@ -2146,11 +2146,11 @@ _dbus_properties_changed_cb(GDBusConnection *connection,
 static void
 _dbus_get_managed_objects_cb(GVariant *result, GError *error, gpointer user_data)
 {
-    NMBluezManager *       self;
+    NMBluezManager        *self;
     NMBluezManagerPrivate *priv;
     GVariantIter           iter;
-    const char *           object_path;
-    GVariant *             ifaces;
+    const char            *object_path;
+    GVariant              *ifaces;
 
     if (!result && nm_utils_error_is_cancelled(error))
         return;
@@ -2184,7 +2184,7 @@ _cleanup_for_name_owner(NMBluezManager *self)
     NMBluezManagerPrivate *priv                             = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
     gboolean               emit_device_availability_changed = FALSE;
     GHashTableIter         iter;
-    BzDBusObj *            bzobj;
+    BzDBusObj             *bzobj;
     gboolean               first = TRUE;
 
     nm_clear_g_cancellable(&priv->get_managed_objects_cancellable);
@@ -2217,7 +2217,7 @@ static void
 name_owner_changed(NMBluezManager *self, const char *owner)
 {
     _nm_unused gs_unref_object NMBluezManager *self_keep_alive = g_object_ref(self);
-    NMBluezManagerPrivate *                    priv            = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
+    NMBluezManagerPrivate                     *priv            = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
 
     owner = nm_str_not_empty(owner);
 
@@ -2270,15 +2270,15 @@ name_owner_changed(NMBluezManager *self, const char *owner)
 
 static void
 name_owner_changed_cb(GDBusConnection *connection,
-                      const char *     sender_name,
-                      const char *     object_path,
-                      const char *     interface_name,
-                      const char *     signal_name,
-                      GVariant *       parameters,
+                      const char      *sender_name,
+                      const char      *object_path,
+                      const char      *interface_name,
+                      const char      *signal_name,
+                      GVariant        *parameters,
                       gpointer         user_data)
 {
     NMBluezManager *self = user_data;
-    const char *    new_owner;
+    const char     *new_owner;
 
     if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)")))
         return;
@@ -2321,8 +2321,8 @@ _cleanup_all(NMBluezManager *self)
 static void
 start(NMDeviceFactory *factory)
 {
-    NMBluezManager *             self;
-    NMBluezManagerPrivate *      priv;
+    NMBluezManager              *self;
+    NMBluezManagerPrivate       *priv;
     NMSettingsConnection *const *sett_conns;
     guint                        n_sett_conns;
     guint                        i;
@@ -2378,12 +2378,12 @@ start(NMDeviceFactory *factory)
 /*****************************************************************************/
 
 static void
-_connect_returned(NMBluezManager *        self,
-                  BzDBusObj *             bzobj,
+_connect_returned(NMBluezManager         *self,
+                  BzDBusObj              *bzobj,
                   NMBluetoothCapabilities bt_type,
-                  const char *            device_name,
-                  NMBluez5DunContext *    dun_context,
-                  GError *                error)
+                  const char             *device_name,
+                  NMBluez5DunContext     *dun_context,
+                  GError                 *error)
 {
     char sbuf_cap[100];
 
@@ -2449,8 +2449,8 @@ _connect_dun_notify_tty_hangup_cb(NMBluez5DunContext *context, gpointer user_dat
 
 static void
 _connect_dun_step2_cb(NMBluez5DunContext *context,
-                      const char *        rfcomm_dev,
-                      GError *            error,
+                      const char         *rfcomm_dev,
+                      GError             *error,
                       gpointer            user_data)
 {
     BzDBusObj *bzobj;
@@ -2491,10 +2491,10 @@ _connect_dun_step2_cb(NMBluez5DunContext *context,
 static void
 _connect_dun_step1_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    gs_unref_variant GVariant *ret = NULL;
-    gs_free_error GError *error    = NULL;
-    BzDBusObj *           bzobj_adapter;
-    BzDBusObj *           bzobj;
+    gs_unref_variant GVariant *ret   = NULL;
+    gs_free_error GError      *error = NULL;
+    BzDBusObj                 *bzobj_adapter;
+    BzDBusObj                 *bzobj;
 
     ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error);
 
@@ -2537,9 +2537,9 @@ static void
 _connect_nap_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
     gs_unref_variant GVariant *ret                = NULL;
-    const char *               network_iface_name = NULL;
-    gs_free_error GError *error                   = NULL;
-    BzDBusObj *           bzobj;
+    const char                *network_iface_name = NULL;
+    gs_free_error GError      *error              = NULL;
+    BzDBusObj                 *bzobj;
 
     ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source_object), res, &error);
 
@@ -2584,7 +2584,7 @@ static void
 _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason)
 {
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
-    DeviceConnectReqData * c_req_data;
+    DeviceConnectReqData  *c_req_data;
     char                   sbuf_cap[100];
     gboolean               bt_type;
 
@@ -2655,19 +2655,19 @@ _connect_disconnect(NMBluezManager *self, BzDBusObj *bzobj, const char *reason)
 }
 
 gboolean
-nm_bluez_manager_connect(NMBluezManager *        self,
-                         const char *            object_path,
+nm_bluez_manager_connect(NMBluezManager         *self,
+                         const char             *object_path,
                          NMBluetoothCapabilities connection_bt_type,
                          int                     timeout_msec,
-                         GCancellable *          cancellable,
+                         GCancellable           *cancellable,
                          NMBluezManagerConnectCb callback,
                          gpointer                callback_user_data,
-                         GError **               error)
+                         GError                **error)
 {
     gs_unref_object GCancellable *int_cancellable = NULL;
-    DeviceConnectReqData *        c_req_data;
-    NMBluezManagerPrivate *       priv;
-    BzDBusObj *                   bzobj;
+    DeviceConnectReqData         *c_req_data;
+    NMBluezManagerPrivate        *priv;
+    BzDBusObj                    *bzobj;
     char                          sbuf_cap[100];
 
     g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(self), FALSE);
@@ -2781,11 +2781,11 @@ nm_bluez_manager_disconnect(NMBluezManager *self, const char *object_path)
 /*****************************************************************************/
 
 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)
 {
     *out_ignore = TRUE;
     g_return_val_if_fail(plink->type == NM_LINK_TYPE_BNEP, NULL);
@@ -2845,7 +2845,7 @@ nm_bluez_manager_init(NMBluezManager *self)
 static void
 dispose(GObject *object)
 {
-    NMBluezManager *       self = NM_BLUEZ_MANAGER(object);
+    NMBluezManager        *self = NM_BLUEZ_MANAGER(object);
     NMBluezManagerPrivate *priv = NM_BLUEZ_MANAGER_GET_PRIVATE(self);
 
     /* FIXME(shutdown): we need a nm_device_factory_stop() hook to first unregister all
@@ -2877,7 +2877,7 @@ dispose(GObject *object)
 static void
 nm_bluez_manager_class_init(NMBluezManagerClass *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/bluetooth/nm-bluez-manager.h b/src/core/devices/bluetooth/nm-bluez-manager.h
index 04bfea7d..299f5c3b 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.h
+++ b/src/core/devices/bluetooth/nm-bluez-manager.h
@@ -24,18 +24,18 @@ GType nm_bluez_manager_get_type(void);
 typedef void (*NMBluezManagerConnectCb)(
     NMBluezManager *self,
     gboolean        is_completed /* or else is early notification with DUN path */,
-    const char *    device_name,
-    GError *        error,
+    const char     *device_name,
+    GError         *error,
     gpointer        user_data);
 
-gboolean nm_bluez_manager_connect(NMBluezManager *        self,
-                                  const char *            object_path,
+gboolean nm_bluez_manager_connect(NMBluezManager         *self,
+                                  const char             *object_path,
                                   NMBluetoothCapabilities connection_bt_type,
                                   int                     timeout_msec,
-                                  GCancellable *          cancellable,
+                                  GCancellable           *cancellable,
                                   NMBluezManagerConnectCb callback,
                                   gpointer                callback_user_data,
-                                  GError **               error);
+                                  GError                **error);
 
 void nm_bluez_manager_disconnect(NMBluezManager *self, const char *object_path);
 
diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.c b/src/core/devices/bluetooth/nm-bluez5-dun.c
index 7efbfaf4..426bab0c 100644
--- a/src/core/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.c
@@ -23,7 +23,7 @@
 /*****************************************************************************/
 
 typedef struct {
-    GCancellable *       cancellable;
+    GCancellable        *cancellable;
     NMBluez5DunConnectCb callback;
     gpointer             callback_user_data;
 
@@ -265,10 +265,10 @@ _connect_create_rfcomm(NMBluez5DunContext *context)
 static gboolean
 _connect_socket_connect_cb(int fd, GIOCondition condition, gpointer user_data)
 {
-    NMBluez5DunContext *context = user_data;
-    gs_free_error GError *error = NULL;
-    int                   errsv = 0;
-    socklen_t             slen  = sizeof(errsv);
+    NMBluez5DunContext   *context = user_data;
+    gs_free_error GError *error   = NULL;
+    int                   errsv   = 0;
+    socklen_t             slen    = sizeof(errsv);
     int                   r;
 
     nm_clear_g_source_inst(&context->cdat->source);
@@ -415,7 +415,7 @@ _connect_sdp_search_cb(uint8_t type, uint16_t status, uint8_t *rsp, size_t size,
     do {
         sdp_record_t *rec;
         int           recsize = 0;
-        sdp_list_t *  protos;
+        sdp_list_t   *protos;
 
         rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
         if (!rec)
@@ -454,8 +454,8 @@ _connect_sdp_search_cb(uint8_t type, uint16_t status, uint8_t *rsp, size_t size,
 static gboolean
 _connect_sdp_search_io_cb(int fd, GIOCondition condition, gpointer user_data)
 {
-    NMBluez5DunContext *context = user_data;
-    gs_free_error GError *error = NULL;
+    NMBluez5DunContext   *context = user_data;
+    gs_free_error GError *error   = NULL;
     int                   errsv;
 
     if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
@@ -505,8 +505,8 @@ _connect_sdp_search_io_cb(int fd, GIOCondition condition, gpointer user_data)
 static gboolean
 _connect_sdp_session_start_on_idle_cb(gpointer user_data)
 {
-    NMBluez5DunContext *context = user_data;
-    gs_free_error GError *error = NULL;
+    NMBluez5DunContext   *context = user_data;
+    gs_free_error GError *error   = NULL;
 
     nm_clear_g_source_inst(&context->cdat->source);
 
@@ -521,15 +521,15 @@ _connect_sdp_session_start_on_idle_cb(gpointer user_data)
 static gboolean
 _connect_sdp_io_cb(int fd, GIOCondition condition, gpointer user_data)
 {
-    NMBluez5DunContext *context = user_data;
-    sdp_list_t *        search;
-    sdp_list_t *        attrs;
-    uuid_t              svclass;
-    uint16_t            attr;
-    int                 errsv;
-    int                 fd_err = 0;
-    int                 r;
-    socklen_t           len     = sizeof(fd_err);
+    NMBluez5DunContext   *context = user_data;
+    sdp_list_t           *search;
+    sdp_list_t           *attrs;
+    uuid_t                svclass;
+    uint16_t              attr;
+    int                   errsv;
+    int                   fd_err = 0;
+    int                   r;
+    socklen_t             len   = sizeof(fd_err);
     gs_free_error GError *error = NULL;
 
     nm_clear_g_source_inst(&context->cdat->source);
@@ -657,17 +657,17 @@ _connect_sdp_session_start(NMBluez5DunContext *context, GError **error)
 /*****************************************************************************/
 
 gboolean
-nm_bluez5_dun_connect(const char *                 adapter,
-                      const char *                 remote,
-                      GCancellable *               cancellable,
+nm_bluez5_dun_connect(const char                  *adapter,
+                      const char                  *remote,
+                      GCancellable                *cancellable,
                       NMBluez5DunConnectCb         callback,
                       gpointer                     callback_user_data,
                       NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
                       gpointer                     notify_tty_hangup_user_data,
-                      GError **                    error)
+                      GError                     **error)
 {
     nm_auto_free_context NMBluez5DunContext *context = NULL;
-    ConnectData *                            cdat;
+    ConnectData                             *cdat;
     gsize                                    src_l;
     gsize                                    dst_l;
 
diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.h b/src/core/devices/bluetooth/nm-bluez5-dun.h
index 020d4119..72ca1380 100644
--- a/src/core/devices/bluetooth/nm-bluez5-dun.h
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.h
@@ -11,20 +11,20 @@ typedef struct _NMBluez5DunContext NMBluez5DunContext;
 #if WITH_BLUEZ5_DUN
 
 typedef void (*NMBluez5DunConnectCb)(NMBluez5DunContext *context,
-                                     const char *        rfcomm_dev,
-                                     GError *            error,
+                                     const char         *rfcomm_dev,
+                                     GError             *error,
                                      gpointer            user_data);
 
 typedef void (*NMBluez5DunNotifyTtyHangupCb)(NMBluez5DunContext *context, gpointer user_data);
 
-gboolean nm_bluez5_dun_connect(const char *                 adapter,
-                               const char *                 remote,
-                               GCancellable *               cancellable,
+gboolean nm_bluez5_dun_connect(const char                  *adapter,
+                               const char                  *remote,
+                               GCancellable                *cancellable,
                                NMBluez5DunConnectCb         callback,
                                gpointer                     callback_user_data,
                                NMBluez5DunNotifyTtyHangupCb notify_tty_hangup_cb,
                                gpointer                     notify_tty_hangup_user_data,
-                               GError **                    error);
+                               GError                     **error);
 
 void nm_bluez5_dun_disconnect(NMBluez5DunContext *context);
 
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index 3dbfbbe0..39b5c4ec 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -26,7 +26,6 @@
 #include "settings/nm-settings-connection.h"
 #include "nm-utils.h"
 #include "nm-bt-error.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 
 #include "devices/wwan/nm-modem-manager.h"
@@ -115,7 +114,7 @@ static NMBluetoothCapabilities
 get_connection_bt_type(NMConnection *connection)
 {
     NMSettingBluetooth *s_bt;
-    const char *        bt_type;
+    const char         *bt_type;
 
     s_bt = nm_connection_get_setting_bluetooth(connection);
 
@@ -133,10 +132,10 @@ get_connection_bt_type(NMConnection *connection)
 }
 
 static gboolean
-get_connection_bt_type_check(NMDeviceBt *             self,
-                             NMConnection *           connection,
+get_connection_bt_type_check(NMDeviceBt              *self,
+                             NMConnection            *connection,
                              NMBluetoothCapabilities *out_bt_type,
-                             GError **                error)
+                             GError                 **error)
 {
     NMBluetoothCapabilities bt_type;
 
@@ -170,8 +169,8 @@ get_generic_capabilities(NMDevice *device)
 static gboolean
 can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
 {
-    NMDeviceBt *            self = NM_DEVICE_BT(device);
-    NMDeviceBtPrivate *     priv = NM_DEVICE_BT_GET_PRIVATE(self);
+    NMDeviceBt             *self = NM_DEVICE_BT(device);
+    NMDeviceBtPrivate      *priv = NM_DEVICE_BT_GET_PRIVATE(self);
     NMBluetoothCapabilities bt_type;
 
     nm_assert(!specific_object || !*specific_object);
@@ -195,10 +194,10 @@ can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **speci
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
-    NMDeviceBt *        self = NM_DEVICE_BT(device);
-    NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(self);
+    NMDeviceBt         *self = NM_DEVICE_BT(device);
+    NMDeviceBtPrivate  *priv = NM_DEVICE_BT_GET_PRIVATE(self);
     NMSettingBluetooth *s_bt;
-    const char *        bdaddr;
+    const char         *bdaddr;
 
     if (!NM_DEVICE_CLASS(nm_device_bt_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -227,14 +226,14 @@ 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)
 {
-    NMDeviceBt *            self = NM_DEVICE_BT(device);
-    NMDeviceBtPrivate *     priv = NM_DEVICE_BT_GET_PRIVATE(self);
+    NMDeviceBt             *self = NM_DEVICE_BT(device);
+    NMDeviceBtPrivate      *priv = NM_DEVICE_BT_GET_PRIVATE(self);
     NMBluetoothCapabilities bt_type;
 
     if (!get_connection_bt_type_check(self, connection, &bt_type, error))
@@ -251,23 +250,23 @@ 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)
 {
-    NMDeviceBtPrivate * priv = NM_DEVICE_BT_GET_PRIVATE(device);
+    NMDeviceBtPrivate  *priv = NM_DEVICE_BT_GET_PRIVATE(device);
     NMSettingBluetooth *s_bt;
-    const char *        setting_bdaddr;
-    const char *        ctype;
+    const char         *setting_bdaddr;
+    const char         *ctype;
     gboolean            is_dun = FALSE;
     gboolean            is_pan = FALSE;
-    NMSettingGsm *      s_gsm;
-    NMSettingCdma *     s_cdma;
-    NMSettingSerial *   s_serial;
-    NMSettingPpp *      s_ppp;
-    const char *        fallback_prefix = NULL, *preferred = NULL;
+    NMSettingGsm       *s_gsm;
+    NMSettingCdma      *s_cdma;
+    NMSettingSerial    *s_serial;
+    NMSettingPpp       *s_ppp;
+    const char         *fallback_prefix = NULL, *preferred = NULL;
 
     s_gsm    = nm_connection_get_setting_gsm(connection);
     s_cdma   = nm_connection_get_setting_cdma(connection);
@@ -424,44 +423,10 @@ ppp_stats(NMModem *modem, guint i_in_bytes, guint i_out_bytes, gpointer user_dat
 static void
 ppp_failed(NMModem *modem, guint i_reason, gpointer user_data)
 {
-    NMDevice *          device = NM_DEVICE(user_data);
-    NMDeviceBt *        self   = NM_DEVICE_BT(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
@@ -481,7 +446,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);
     NMDeviceBtPrivate *priv   = NM_DEVICE_BT_GET_PRIVATE(device);
 
     g_return_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH);
@@ -498,8 +463,8 @@ modem_auth_result(NMModem *modem, GError *error, gpointer user_data)
 static void
 modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer user_data)
 {
-    NMDeviceBt *        self   = user_data;
-    NMDeviceBtPrivate * priv   = NM_DEVICE_BT_GET_PRIVATE(self);
+    NMDeviceBt         *self   = user_data;
+    NMDeviceBtPrivate  *priv   = NM_DEVICE_BT_GET_PRIVATE(self);
     NMDeviceStateReason reason = i_reason;
     NMDeviceState       state;
 
@@ -528,7 +493,7 @@ modem_prepare_result(NMModem *modem, gboolean success, guint i_reason, gpointer
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
@@ -547,22 +512,35 @@ device_state_changed(NMDevice *          device,
 }
 
 static void
-modem_ip4_config_result(NMModem *modem, NMIP4Config *config, GError *error, gpointer user_data)
+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)
 {
-    NMDeviceBt *self   = NM_DEVICE_BT(user_data);
-    NMDevice *  device = NM_DEVICE(self);
+    const int   IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDeviceBt *self    = NM_DEVICE_BT(user_data);
+    NMDevice   *device  = NM_DEVICE(self);
 
-    g_return_if_fail(nm_device_activate_ip4_state_in_conf(device) == TRUE);
+    g_return_if_fail(nm_device_devip_get_state(device, addr_family) == NM_DEVICE_IP_STATE_PENDING);
 
     if (error) {
-        _LOGW(LOGD_MB | LOGD_IP4 | LOGD_BT,
-              "retrieving IP4 configuration failed: %s",
-              error->message);
-        nm_device_ip_method_failed(device, AF_INET, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+        _LOGW(LOGD_MB | LOGD_IP4, "retrieving IP configuration failed: %s", error->message);
+        nm_device_devip_set_failed(device, addr_family, failure_reason_i);
         return;
     }
 
-    nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config));
+    if (do_auto) {
+        if (IS_IPv4)
+            nm_device_ip_method_dhcp4_start(device);
+        else
+            nm_device_ip_method_autoconf6_start(device);
+    }
+
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd);
 }
 
 static void
@@ -604,7 +582,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);
     NMDeviceState dev_state = nm_device_get_state(device);
 
     if (new_state <= NM_MODEM_STATE_DISABLING && old_state > NM_MODEM_STATE_DISABLING) {
@@ -633,7 +611,7 @@ modem_state_cb(NMModem *modem, int new_state_i, int old_state_i, gpointer user_d
 static void
 modem_removed_cb(NMModem *modem, gpointer user_data)
 {
-    NMDeviceBt *  self = NM_DEVICE_BT(user_data);
+    NMDeviceBt   *self = NM_DEVICE_BT(user_data);
     NMDeviceState state;
 
     state = nm_device_get_state(NM_DEVICE(self));
@@ -651,7 +629,7 @@ static gboolean
 modem_try_claim(NMDeviceBt *self, NMModem *modem)
 {
     NMDeviceBtPrivate *priv             = NM_DEVICE_BT_GET_PRIVATE(self);
-    gs_free char *     rfcomm_base_name = NULL;
+    gs_free char      *rfcomm_base_name = NULL;
     NMDeviceState      state;
 
     if (priv->modem) {
@@ -687,7 +665,7 @@ modem_try_claim(NMDeviceBt *self, NMModem *modem)
     g_signal_connect(modem, NM_MODEM_PPP_STATS, G_CALLBACK(ppp_stats), self);
     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_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);
@@ -705,7 +683,7 @@ modem_try_claim(NMDeviceBt *self, NMModem *modem)
 static void
 mm_modem_added_cb(NMModemManager *manager, NMModem *modem, gpointer user_data)
 {
-    NMDeviceBt *       self = user_data;
+    NMDeviceBt        *self = user_data;
     NMDeviceBtPrivate *priv;
 
     if (!modem_try_claim(user_data, modem))
@@ -745,7 +723,7 @@ _nm_device_bt_notify_set_connected(NMDeviceBt *self, gboolean connected)
 static gboolean
 connect_watch_link_idle_cb(gpointer user_data)
 {
-    NMDeviceBt *       self = user_data;
+    NMDeviceBt        *self = user_data;
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
     int                ifindex;
 
@@ -766,15 +744,15 @@ connect_watch_link_idle_cb(gpointer user_data)
 }
 
 static void
-connect_watch_link_cb(NMPlatform *    platform,
+connect_watch_link_cb(NMPlatform     *platform,
                       int             obj_type_i,
                       int             ifindex,
                       NMPlatformLink *info,
                       int             change_type_i,
-                      NMDevice *      self)
+                      NMDevice       *self)
 {
     const NMPlatformSignalChangeType change_type = change_type_i;
-    NMDeviceBtPrivate *              priv;
+    NMDeviceBtPrivate               *priv;
 
     /* bluez doesn't notify us when the connection disconnects.
      * Neither does NMManager (or NMDevice) tell us when the ip-ifindex goes away.
@@ -790,7 +768,7 @@ connect_watch_link_cb(NMPlatform *    platform,
 static gboolean
 connect_wait_modem_timeout(gpointer user_data)
 {
-    NMDeviceBt *       self = NM_DEVICE_BT(user_data);
+    NMDeviceBt        *self = NM_DEVICE_BT(user_data);
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
 
     /* since this timeout is longer than the connect timeout, we must have already
@@ -814,11 +792,11 @@ connect_wait_modem_timeout(gpointer user_data)
 static void
 connect_bz_cb(NMBluezManager *bz_mgr,
               gboolean        is_complete,
-              const char *    device_name,
-              GError *        error,
+              const char     *device_name,
+              GError         *error,
               gpointer        user_data)
 {
-    NMDeviceBt *       self;
+    NMDeviceBt        *self;
     NMDeviceBtPrivate *priv;
     char               sbuf[100];
 
@@ -906,10 +884,10 @@ connect_bz_cb(NMBluezManager *bz_mgr,
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBt *       self     = NM_DEVICE_BT(device);
-    NMDeviceBtPrivate *priv     = NM_DEVICE_BT_GET_PRIVATE(self);
+    NMDeviceBt           *self  = NM_DEVICE_BT(device);
+    NMDeviceBtPrivate    *priv  = NM_DEVICE_BT_GET_PRIVATE(self);
     gs_free_error GError *error = NULL;
-    NMConnection *        connection;
+    NMConnection         *connection;
 
     connection = nm_device_get_applied_connection(device);
     g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
@@ -989,38 +967,25 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBt *       self = NM_DEVICE_BT(device);
+    NMDeviceBt        *self = NM_DEVICE_BT(device);
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
 
-    if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN)
-        nm_modem_act_stage2_config(priv->modem);
+    if (priv->connect_bt_type != NM_BT_CAPABILITY_DUN)
+        return NM_ACT_STAGE_RETURN_SUCCESS;
 
-    return NM_ACT_STAGE_RETURN_SUCCESS;
+    return nm_modem_act_stage2_config(priv->modem, device, out_failure_reason);
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    NMDeviceBtPrivate *priv    = NM_DEVICE_BT_GET_PRIVATE(device);
-    gboolean           autoip4 = FALSE;
-    NMActStageReturn   ret;
+    NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device);
 
     if (priv->connect_bt_type != NM_BT_CAPABILITY_DUN)
-        goto out_chain_up;
-
-    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);
-    if (ret != NM_ACT_STAGE_RETURN_SUCCESS || !autoip4)
-        return ret;
+        return;
 
-out_chain_up:
-    return NM_DEVICE_CLASS(nm_device_bt_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    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 void
@@ -1070,10 +1035,10 @@ _nm_device_bt_notify_removed(NMDeviceBt *self)
 /*****************************************************************************/
 
 gboolean
-_nm_device_bt_for_same_device(NMDeviceBt *            self,
-                              const char *            dbus_path,
-                              const char *            bdaddr,
-                              const char *            name,
+_nm_device_bt_for_same_device(NMDeviceBt             *self,
+                              const char             *dbus_path,
+                              const char             *bdaddr,
+                              const char             *name,
                               NMBluetoothCapabilities capabilities)
 {
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
@@ -1102,7 +1067,7 @@ _nm_device_bt_notify_set_name(NMDeviceBt *self, const char *name)
 static gboolean
 is_available(NMDevice *dev, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceBt *       self = NM_DEVICE_BT(dev);
+    NMDeviceBt        *self = NM_DEVICE_BT(dev);
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
 
     /* PAN doesn't need ModemManager, so devices that support it are always available */
@@ -1206,7 +1171,7 @@ nm_device_bt_init(NMDeviceBt *self)
 static void
 constructed(GObject *object)
 {
-    NMDeviceBt *       self = NM_DEVICE_BT(object);
+    NMDeviceBt        *self = NM_DEVICE_BT(object);
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
 
     G_OBJECT_CLASS(nm_device_bt_parent_class)->constructed(object);
@@ -1229,10 +1194,10 @@ constructed(GObject *object)
 }
 
 NMDeviceBt *
-nm_device_bt_new(NMBluezManager *        bz_mgr,
-                 const char *            dbus_path,
-                 const char *            bdaddr,
-                 const char *            name,
+nm_device_bt_new(NMBluezManager         *bz_mgr,
+                 const char             *dbus_path,
+                 const char             *bdaddr,
+                 const char             *name,
                  NMBluetoothCapabilities capabilities)
 {
     g_return_val_if_fail(NM_IS_BLUEZ_MANAGER(bz_mgr), NULL);
@@ -1270,7 +1235,7 @@ nm_device_bt_new(NMBluezManager *        bz_mgr,
 static void
 dispose(GObject *object)
 {
-    NMDeviceBt *       self = NM_DEVICE_BT(object);
+    NMDeviceBt        *self = NM_DEVICE_BT(object);
     NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(self);
 
     nm_clear_g_signal_handler(nm_device_get_platform(NM_DEVICE(self)),
@@ -1325,9 +1290,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_bluetooth = {
 static void
 nm_device_bt_class_init(NMDeviceBtClass *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->constructed  = constructed;
     object_class->get_property = get_property;
@@ -1344,7 +1309,7 @@ nm_device_bt_class_init(NMDeviceBtClass *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->act_stage3_ip_config        = act_stage3_ip_config;
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->check_connection_available  = check_connection_available;
     device_class->complete_connection         = complete_connection;
diff --git a/src/core/devices/bluetooth/nm-device-bt.h b/src/core/devices/bluetooth/nm-device-bt.h
index c2d3bc18..59457fba 100644
--- a/src/core/devices/bluetooth/nm-device-bt.h
+++ b/src/core/devices/bluetooth/nm-device-bt.h
@@ -33,15 +33,15 @@ GType nm_device_bt_get_type(void);
 struct _NMBluezManager;
 
 NMDeviceBt *nm_device_bt_new(struct _NMBluezManager *bz_mgr,
-                             const char *            dbus_path,
-                             const char *            bdaddr,
-                             const char *            name,
+                             const char             *dbus_path,
+                             const char             *bdaddr,
+                             const char             *name,
                              NMBluetoothCapabilities capabilities);
 
-gboolean _nm_device_bt_for_same_device(NMDeviceBt *            device,
-                                       const char *            dbus_path,
-                                       const char *            bdaddr,
-                                       const char *            name,
+gboolean _nm_device_bt_for_same_device(NMDeviceBt             *device,
+                                       const char             *dbus_path,
+                                       const char             *bdaddr,
+                                       const char             *name,
                                        NMBluetoothCapabilities capabilities);
 
 NMBluetoothCapabilities nm_device_bt_get_capabilities(NMDeviceBt *device);
diff --git a/src/core/devices/bluetooth/tests/nm-bt-test.c b/src/core/devices/bluetooth/tests/nm-bt-test.c
index 548e7249..5aab48a5 100644
--- a/src/core/devices/bluetooth/tests/nm-bt-test.c
+++ b/src/core/devices/bluetooth/tests/nm-bt-test.c
@@ -23,8 +23,8 @@
 struct {
     int                argc;
     const char *const *argv;
-    const char *       argv_cmd;
-    GMainLoop *        loop;
+    const char        *argv_cmd;
+    GMainLoop         *loop;
 } gl;
 
 typedef struct _MainCmdInfo {
@@ -38,7 +38,7 @@ typedef struct _MainCmdInfo {
 
 typedef struct {
     NMBluez5DunContext *dun_context;
-    GCancellable *      cancellable;
+    GCancellable       *cancellable;
     guint               timeout_id;
     guint               sig_term_id;
     guint               sig_int_id;
@@ -46,8 +46,8 @@ typedef struct {
 
 static void
 _dun_connect_cb(NMBluez5DunContext *context,
-                const char *        rfcomm_dev,
-                GError *            error,
+                const char         *rfcomm_dev,
+                GError             *error,
                 gpointer            user_data)
 {
     DunConnectData *dun_connect_data = user_data;
@@ -118,10 +118,10 @@ do_dun_connect(const MainCmdInfo *main_cmd_info)
 {
 #if WITH_BLUEZ5_DUN
     gs_unref_object GCancellable *cancellable = NULL;
-    gs_free_error GError *error               = NULL;
-    const char *          adapter;
-    const char *          remote;
-    DunConnectData        dun_connect_data = {};
+    gs_free_error GError         *error       = NULL;
+    const char                   *adapter;
+    const char                   *remote;
+    DunConnectData                dun_connect_data = {};
 
     if (gl.argc < 4) {
         _LOGE("missing arguments \"adapter\" and \"remote\"");
diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c
deleted file mode 100644
index c041163e..00000000
--- a/src/core/devices/nm-acd-manager.c
+++ /dev/null
@@ -1,497 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2015 - 2018 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "nm-acd-manager.h"
-
-#include <netinet/in.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <linux/if_ether.h>
-
-#include "libnm-platform/nm-platform.h"
-#include "nm-utils.h"
-#include "NetworkManagerUtils.h"
-#include "n-acd/src/n-acd.h"
-
-/*****************************************************************************/
-
-typedef enum {
-    STATE_INIT,
-    STATE_PROBING,
-    STATE_PROBE_DONE,
-    STATE_ANNOUNCING,
-} State;
-
-typedef struct {
-    in_addr_t  address;
-    gboolean   duplicate;
-    NAcdProbe *probe;
-} AddressInfo;
-
-struct _NMAcdManager {
-    int         ifindex;
-    guint8      hwaddr[ETH_ALEN];
-    State       state;
-    GHashTable *addresses;
-    guint       completed;
-    NAcd *      acd;
-    GSource *   event_source;
-
-    NMAcdCallbacks callbacks;
-    gpointer       user_data;
-};
-
-/*****************************************************************************/
-
-#define _NMLOG_DOMAIN      LOGD_IP4
-#define _NMLOG_PREFIX_NAME "acd"
-#define _NMLOG(level, ...)                                                     \
-    G_STMT_START                                                               \
-    {                                                                          \
-        char _sbuf[64];                                                        \
-                                                                               \
-        nm_log((level),                                                        \
-               _NMLOG_DOMAIN,                                                  \
-               self && self->ifindex > 0                                       \
-                   ? nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex) \
-                   : NULL,                                                     \
-               NULL,                                                           \
-               "%s%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__),                    \
-               _NMLOG_PREFIX_NAME,                                             \
-               self ? nm_sprintf_buf(_sbuf, "[%p,%d]", self, self->ifindex)    \
-                    : "" _NM_UTILS_MACRO_REST(__VA_ARGS__));                   \
-    }                                                                          \
-    G_STMT_END
-
-/*****************************************************************************/
-
-static const char *
-_acd_event_to_string(unsigned int event)
-{
-    switch (event) {
-    case N_ACD_EVENT_READY:
-        return "ready";
-    case N_ACD_EVENT_USED:
-        return "used";
-    case N_ACD_EVENT_DEFENDED:
-        return "defended";
-    case N_ACD_EVENT_CONFLICT:
-        return "conflict";
-    case N_ACD_EVENT_DOWN:
-        return "down";
-    }
-    return NULL;
-}
-
-#define ACD_EVENT_TO_STRING_BUF_SIZE 50
-
-static const char *
-_acd_event_to_string_buf(unsigned event, char buffer[static ACD_EVENT_TO_STRING_BUF_SIZE])
-{
-    const char *s;
-
-    s = _acd_event_to_string(event);
-    if (s)
-        return s;
-
-    g_snprintf(buffer, ACD_EVENT_TO_STRING_BUF_SIZE, "(%u)", event);
-    return buffer;
-}
-
-static const char *
-acd_error_to_string(int error)
-{
-    if (error < 0)
-        return nm_strerror_native(-error);
-
-    switch (error) {
-    case _N_ACD_E_SUCCESS:
-        return "success";
-    case N_ACD_E_PREEMPTED:
-        return "preempted";
-    case N_ACD_E_INVALID_ARGUMENT:
-        return "invalid argument";
-    }
-
-    g_return_val_if_reached(NULL);
-}
-
-static int
-acd_error_to_nmerr(int error, gboolean always_fail)
-{
-    if (error < 0)
-        return -nm_errno_native(error);
-
-    if (always_fail) {
-        if (NM_IN_SET(error, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT))
-            return -NME_UNSPEC;
-        g_return_val_if_reached(-NME_UNSPEC);
-    }
-
-    /* so, @error is either zero (indicating success) or one
-     * of the special status codes like N_ACD_E_*. In both cases,
-     * return the positive value here. */
-    if (NM_IN_SET(error, _N_ACD_E_SUCCESS, N_ACD_E_PREEMPTED, N_ACD_E_INVALID_ARGUMENT))
-        return error;
-
-    g_return_val_if_reached(error);
-}
-
-/*****************************************************************************/
-
-/**
- * nm_acd_manager_add_address:
- * @self: a #NMAcdManager
- * @address: an IP address
- *
- * Add @address to the list of IP addresses to probe.
-
- * Returns: %TRUE on success, %FALSE if the address was already in the list
- */
-gboolean
-nm_acd_manager_add_address(NMAcdManager *self, in_addr_t address)
-{
-    AddressInfo *info;
-
-    g_return_val_if_fail(self, FALSE);
-    g_return_val_if_fail(self->state == STATE_INIT, FALSE);
-
-    if (g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address)))
-        return FALSE;
-
-    info          = g_slice_new0(AddressInfo);
-    info->address = address;
-
-    g_hash_table_insert(self->addresses, GUINT_TO_POINTER(address), info);
-
-    return TRUE;
-}
-
-static gboolean
-acd_event(int fd, GIOCondition condition, gpointer data)
-{
-    NMAcdManager *self = data;
-    NAcdEvent *   event;
-    AddressInfo * info;
-    gboolean      emit_probe_terminated = FALSE;
-    char          address_str[INET_ADDRSTRLEN];
-    int           r;
-
-    if (n_acd_dispatch(self->acd))
-        return G_SOURCE_CONTINUE;
-
-    while (!n_acd_pop_event(self->acd, &event) && event) {
-        char          to_string_buffer[ACD_EVENT_TO_STRING_BUF_SIZE];
-        gs_free char *hwaddr_str         = NULL;
-        gboolean      check_probing_done = FALSE;
-        char          buf[ETH_ALEN * 3];
-
-        switch (event->event) {
-        case N_ACD_EVENT_READY:
-            n_acd_probe_get_userdata(event->ready.probe, (void **) &info);
-            info->duplicate = FALSE;
-            if (self->state == STATE_ANNOUNCING) {
-                /* fake probe ended, start announcing */
-                r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE);
-                if (r) {
-                    _LOGW("couldn't announce address %s on interface '%s': %s",
-                          _nm_utils_inet4_ntop(info->address, address_str),
-                          nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-                          acd_error_to_string(r));
-                } else {
-                    _LOGD("announcing address %s (hw-addr %s)",
-                          _nm_utils_inet4_ntop(info->address, address_str),
-                          _nm_utils_hwaddr_ntoa(self->hwaddr, ETH_ALEN, TRUE, buf, sizeof(buf)));
-                }
-            }
-            check_probing_done = TRUE;
-            break;
-        case N_ACD_EVENT_USED:
-            n_acd_probe_get_userdata(event->used.probe, (void **) &info);
-            info->duplicate    = TRUE;
-            check_probing_done = TRUE;
-            break;
-        case N_ACD_EVENT_DEFENDED:
-            n_acd_probe_get_userdata(event->defended.probe, (void **) &info);
-            _LOGD("defended address %s from host %s",
-                  _nm_utils_inet4_ntop(info->address, address_str),
-                  (hwaddr_str =
-                       nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender)));
-            break;
-        case N_ACD_EVENT_CONFLICT:
-            n_acd_probe_get_userdata(event->conflict.probe, (void **) &info);
-            _LOGW("conflict for address %s detected with host %s on interface '%s'",
-                  _nm_utils_inet4_ntop(info->address, address_str),
-                  (hwaddr_str =
-                       nm_utils_hwaddr_ntoa(event->defended.sender, event->defended.n_sender)),
-                  nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex));
-            break;
-        default:
-            _LOGD("unhandled event '%s'", _acd_event_to_string_buf(event->event, to_string_buffer));
-            break;
-        }
-
-        if (check_probing_done && self->state == STATE_PROBING
-            && ++self->completed == g_hash_table_size(self->addresses)) {
-            self->state           = STATE_PROBE_DONE;
-            emit_probe_terminated = TRUE;
-        }
-    }
-
-    if (emit_probe_terminated) {
-        if (self->callbacks.probe_terminated_callback) {
-            self->callbacks.probe_terminated_callback(self, self->user_data);
-        }
-    }
-
-    return G_SOURCE_CONTINUE;
-}
-
-static gboolean
-acd_probe_add(NMAcdManager *self, AddressInfo *info, guint64 timeout)
-{
-    NAcdProbeConfig *probe_config;
-    int              r;
-    char             sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    r = n_acd_probe_config_new(&probe_config);
-    if (r) {
-        _LOGW("could not create probe config for %s on interface '%s': %s",
-              _nm_utils_inet4_ntop(info->address, sbuf),
-              nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-              acd_error_to_string(r));
-        return FALSE;
-    }
-
-    n_acd_probe_config_set_ip(probe_config, (struct in_addr){info->address});
-    n_acd_probe_config_set_timeout(probe_config, timeout);
-
-    r = n_acd_probe(self->acd, &info->probe, probe_config);
-    if (r) {
-        _LOGW("could not start probe for %s on interface '%s': %s",
-              _nm_utils_inet4_ntop(info->address, sbuf),
-              nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-              acd_error_to_string(r));
-        n_acd_probe_config_free(probe_config);
-        return FALSE;
-    }
-
-    n_acd_probe_set_userdata(info->probe, info);
-    n_acd_probe_config_free(probe_config);
-
-    return TRUE;
-}
-
-static int
-acd_init(NMAcdManager *self)
-{
-    NAcdConfig *config;
-    int         r;
-
-    if (self->acd)
-        return 0;
-
-    r = n_acd_config_new(&config);
-    if (r)
-        return r;
-
-    n_acd_config_set_ifindex(config, self->ifindex);
-    n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET);
-    n_acd_config_set_mac(config, self->hwaddr, ETH_ALEN);
-
-    r = n_acd_new(&self->acd, config);
-    n_acd_config_free(config);
-    return r;
-}
-
-/**
- * nm_acd_manager_start_probe:
- * @self: a #NMAcdManager
- * @timeout: maximum probe duration in milliseconds
- * @error: location to store error, or %NULL
- *
- * Start probing IP addresses for duplicates; when the probe terminates a
- * PROBE_TERMINATED signal is emitted.
- *
- * Returns: 0 on success or a negative NetworkManager error code (NME_*).
- */
-int
-nm_acd_manager_start_probe(NMAcdManager *self, guint timeout)
-{
-    GHashTableIter iter;
-    AddressInfo *  info;
-    gboolean       success = FALSE;
-    int            fd, r;
-
-    g_return_val_if_fail(self, FALSE);
-    g_return_val_if_fail(self->state == STATE_INIT, FALSE);
-
-    r = acd_init(self);
-    if (r) {
-        _LOGW("couldn't init ACD for probing on interface '%s': %s",
-              nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-              acd_error_to_string(r));
-        return acd_error_to_nmerr(r, TRUE);
-    }
-
-    self->completed = 0;
-
-    g_hash_table_iter_init(&iter, self->addresses);
-    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info))
-        success |= acd_probe_add(self, info, timeout);
-
-    if (success)
-        self->state = STATE_PROBING;
-
-    nm_assert(!self->event_source);
-    n_acd_get_fd(self->acd, &fd);
-    self->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, acd_event, self);
-
-    return success ? 0 : -NME_UNSPEC;
-}
-
-/**
- * nm_acd_manager_check_address:
- * @self: a #NMAcdManager
- * @address: an IP address
- *
- * Check if an IP address is duplicate. @address must have been added with
- * nm_acd_manager_add_address().
- *
- * Returns: %TRUE if the address is not duplicate, %FALSE otherwise
- */
-gboolean
-nm_acd_manager_check_address(NMAcdManager *self, in_addr_t address)
-{
-    AddressInfo *info;
-
-    g_return_val_if_fail(self, FALSE);
-    g_return_val_if_fail(NM_IN_SET(self->state, STATE_INIT, STATE_PROBE_DONE), FALSE);
-
-    info = g_hash_table_lookup(self->addresses, GUINT_TO_POINTER(address));
-    g_return_val_if_fail(info, FALSE);
-
-    return !info->duplicate;
-}
-
-/**
- * nm_acd_manager_announce_addresses:
- * @self: a #NMAcdManager
- *
- * Start announcing addresses.
- *
- * Returns: a negative NetworkManager error number or zero on success.
- */
-int
-nm_acd_manager_announce_addresses(NMAcdManager *self)
-{
-    GHashTableIter iter;
-    AddressInfo *  info;
-    int            r;
-    int            fd;
-    gboolean       success = TRUE;
-    char           buf[ETH_ALEN * 3];
-
-    r = acd_init(self);
-    if (r) {
-        _LOGW("couldn't init ACD for announcing addresses on interface '%s': %s",
-              nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-              acd_error_to_string(r));
-        return acd_error_to_nmerr(r, TRUE);
-    }
-
-    if (self->state == STATE_INIT) {
-        /* n-acd can't announce without probing, therefore let's
-         * start a fake probe with zero timeout and then perform
-         * the announcement. */
-        g_hash_table_iter_init(&iter, self->addresses);
-        while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) {
-            if (!acd_probe_add(self, info, 0))
-                success = FALSE;
-        }
-        self->state = STATE_ANNOUNCING;
-    } else if (self->state == STATE_ANNOUNCING) {
-        char sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-        g_hash_table_iter_init(&iter, self->addresses);
-        while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &info)) {
-            if (info->duplicate)
-                continue;
-            r = n_acd_probe_announce(info->probe, N_ACD_DEFEND_ONCE);
-            if (r) {
-                _LOGW("couldn't announce address %s on interface '%s': %s",
-                      _nm_utils_inet4_ntop(info->address, sbuf),
-                      nm_platform_link_get_name(NM_PLATFORM_GET, self->ifindex),
-                      acd_error_to_string(r));
-                success = FALSE;
-            } else
-                _LOGD("announcing address %s (hw-addr %s)",
-                      _nm_utils_inet4_ntop(info->address, sbuf),
-                      _nm_utils_hwaddr_ntoa(self->hwaddr, ETH_ALEN, TRUE, buf, sizeof(buf)));
-        }
-    }
-
-    if (!self->event_source) {
-        n_acd_get_fd(self->acd, &fd);
-        self->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, acd_event, self);
-    }
-
-    return success ? 0 : -NME_UNSPEC;
-}
-
-static void
-destroy_address_info(gpointer data)
-{
-    AddressInfo *info = (AddressInfo *) data;
-
-    n_acd_probe_free(info->probe);
-
-    g_slice_free(AddressInfo, info);
-}
-
-/*****************************************************************************/
-
-NMAcdManager *
-nm_acd_manager_new(int                   ifindex,
-                   const guint8 *        hwaddr,
-                   guint                 hwaddr_len,
-                   const NMAcdCallbacks *callbacks,
-                   gpointer              user_data)
-{
-    NMAcdManager *self;
-
-    g_return_val_if_fail(ifindex > 0, NULL);
-    g_return_val_if_fail(hwaddr, NULL);
-    g_return_val_if_fail(hwaddr_len == ETH_ALEN, NULL);
-
-    self = g_slice_new0(NMAcdManager);
-
-    if (callbacks)
-        self->callbacks = *callbacks;
-    self->user_data = user_data;
-
-    self->addresses = g_hash_table_new_full(nm_direct_hash, NULL, NULL, destroy_address_info);
-    self->state     = STATE_INIT;
-    self->ifindex   = ifindex;
-    memcpy(self->hwaddr, hwaddr, ETH_ALEN);
-    return self;
-}
-
-void
-nm_acd_manager_free(NMAcdManager *self)
-{
-    g_return_if_fail(self);
-
-    if (self->callbacks.user_data_destroy)
-        self->callbacks.user_data_destroy(self->user_data);
-
-    nm_clear_pointer(&self->addresses, g_hash_table_destroy);
-    nm_clear_g_source_inst(&self->event_source);
-    nm_clear_pointer(&self->acd, n_acd_unref);
-
-    g_slice_free(NMAcdManager, self);
-}
diff --git a/src/core/devices/nm-acd-manager.h b/src/core/devices/nm-acd-manager.h
deleted file mode 100644
index e8ef6f2b..00000000
--- a/src/core/devices/nm-acd-manager.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2015 - 2018 Red Hat, Inc.
- */
-
-#ifndef __NM_ACD_MANAGER__
-#define __NM_ACD_MANAGER__
-
-#include <netinet/in.h>
-
-typedef struct _NMAcdManager NMAcdManager;
-
-typedef struct {
-    void (*probe_terminated_callback)(NMAcdManager *self, gpointer user_data);
-    GDestroyNotify user_data_destroy;
-} NMAcdCallbacks;
-
-NMAcdManager *nm_acd_manager_new(int                   ifindex,
-                                 const guint8 *        hwaddr,
-                                 guint                 hwaddr_len,
-                                 const NMAcdCallbacks *callbacks,
-                                 gpointer              user_data);
-
-void nm_acd_manager_free(NMAcdManager *self);
-
-gboolean nm_acd_manager_add_address(NMAcdManager *self, in_addr_t address);
-int      nm_acd_manager_start_probe(NMAcdManager *self, guint timeout);
-gboolean nm_acd_manager_check_address(NMAcdManager *self, in_addr_t address);
-int      nm_acd_manager_announce_addresses(NMAcdManager *self);
-
-NM_AUTO_DEFINE_FCN0(NMAcdManager *, _nm_auto_free_acdmgr, nm_acd_manager_free);
-#define nm_auto_free_acdmgr nm_auto(_nm_auto_free_acdmgr)
-
-#endif /* __NM_ACD_MANAGER__ */
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index 8b77c22c..d6543356 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -41,7 +41,7 @@ G_DEFINE_TYPE(NMDevice6Lowpan, nm_device_6lowpan, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void
-parent_state_changed(NMDevice *          parent,
+parent_state_changed(NMDevice           *parent,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason,
@@ -62,7 +62,7 @@ parent_changed_notify(NMDevice *device,
                       int       new_ifindex,
                       NMDevice *new_parent)
 {
-    NMDevice6Lowpan *       self = NM_DEVICE_6LOWPAN(device);
+    NMDevice6Lowpan        *self = NM_DEVICE_6LOWPAN(device);
     NMDevice6LowpanPrivate *priv = NM_DEVICE_6LOWPAN_GET_PRIVATE(self);
 
     NM_DEVICE_CLASS(nm_device_6lowpan_parent_class)
@@ -95,13 +95,13 @@ parent_changed_notify(NMDevice *device,
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *      iface = nm_device_get_iface(device);
+    const char       *iface = nm_device_get_iface(device);
     NMSetting6Lowpan *s_6lowpan;
     int               parent_ifindex;
     int               r;
@@ -175,11 +175,11 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 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)
 {
     NMSetting6Lowpan *s_6lowpan;
 
@@ -249,7 +249,7 @@ static void
 nm_device_6lowpan_class_init(NMDevice6LowpanClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_6lowpan);
 
@@ -275,11 +275,11 @@ nm_device_6lowpan_class_init(NMDevice6LowpanClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_6LOWPAN_DEVICE_FACTORY, NM6LowpanDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_6LOWPAN,
                         NM_DEVICE_IFACE,
@@ -310,7 +310,7 @@ static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
     NMSetting6Lowpan *s_6lowpan;
-    const char *      ifname;
+    const char       *ifname;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_6LOWPAN_SETTING_NAME), NULL);
 
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index f7d78f57..56b729e9 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -16,7 +16,6 @@
 #include "nm-device-factory.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
-#include "nm-ip4-config.h"
 #include "nm-setting-bond-port.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBond
@@ -75,11 +74,11 @@ get_generic_capabilities(NMDevice *dev)
 }
 
 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)
 {
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
@@ -163,18 +162,18 @@ ignore_option(NMSettingBond *s_bond, const char *option, const char *value)
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceBond * self    = NM_DEVICE_BOND(device);
+    NMDeviceBond  *self    = NM_DEVICE_BOND(device);
     NMSettingBond *s_bond  = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND);
     int            ifindex = nm_device_get_ifindex(device);
     NMBondMode     mode    = NM_BOND_MODE_UNKNOWN;
-    const char **  options;
+    const char   **options;
 
     /* Read bond options from sysfs and update the Bond setting to match */
     options = nm_setting_bond_get_valid_options(NULL);
     for (; options[0]; options++) {
-        const char *  option = options[0];
+        const char   *option = options[0];
         gs_free char *value  = NULL;
-        char *        p;
+        char         *p;
 
         if (NM_IN_STRSET(option, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE))
             continue;
@@ -216,15 +215,15 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-controller_update_port_connection(NMDevice *    self,
-                                  NMDevice *    port,
+controller_update_port_connection(NMDevice     *self,
+                                  NMDevice     *port,
                                   NMConnection *connection,
-                                  GError **     error)
+                                  GError      **error)
 {
     NMSettingBondPort *s_port;
     int                ifindex_port = nm_device_get_ifindex(port);
     uint               queue_id     = NM_BOND_PORT_QUEUE_ID_DEF;
-    gs_free char *     queue_id_str = NULL;
+    gs_free char      *queue_id_str = NULL;
 
     g_return_val_if_fail(ifindex_port > 0, FALSE);
 
@@ -252,8 +251,8 @@ static void
 set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new_arp_ip_target)
 {
     gs_unref_ptrarray GPtrArray *free_list = NULL;
-    gs_free const char **        cur_strv  = NULL;
-    gs_free const char **        new_strv  = NULL;
+    gs_free const char         **cur_strv  = NULL;
+    gs_free const char         **new_strv  = NULL;
     gsize                        cur_len;
     gsize                        new_len;
     gsize                        i;
@@ -311,7 +310,7 @@ static void
 set_bond_attr_or_default(NMDevice *device, NMSettingBond *s_bond, const char *opt)
 {
     NMDeviceBond *self = NM_DEVICE_BOND(device);
-    const char *  value;
+    const char   *value;
 
     value = nm_setting_bond_get_option_or_default(s_bond, opt);
     if (!value) {
@@ -353,11 +352,11 @@ set_bond_arp_ip_targets(NMDevice *device, NMSettingBond *s_bond)
 static gboolean
 apply_bonding_config(NMDeviceBond *self)
 {
-    NMDevice *     device = NM_DEVICE(self);
+    NMDevice      *device = NM_DEVICE(self);
     NMSettingBond *s_bond;
     NMBondMode     mode;
-    const char *   mode_str;
-    gs_free char * device_bond_mode = NULL;
+    const char    *mode_str;
+    gs_free char  *device_bond_mode = NULL;
 
     s_bond = nm_device_get_applied_setting(device, NM_TYPE_SETTING_BOND);
     g_return_val_if_fail(s_bond, FALSE);
@@ -387,7 +386,7 @@ apply_bonding_config(NMDeviceBond *self)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBond *   self = NM_DEVICE_BOND(device);
+    NMDeviceBond    *self = NM_DEVICE_BOND(device);
     NMActStageReturn ret  = NM_ACT_STAGE_RETURN_SUCCESS;
 
     /* Interface must be down to set bond options */
@@ -428,7 +427,7 @@ commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_
 static gboolean
 enslave_slave(NMDevice *device, NMDevice *port, NMConnection *connection, gboolean configure)
 {
-    NMDeviceBond *     self = NM_DEVICE_BOND(device);
+    NMDeviceBond      *self = NM_DEVICE_BOND(device);
     NMSettingBondPort *s_port;
 
     nm_device_master_check_slave_physical_port(device, port, LOGD_BOND);
@@ -479,9 +478,9 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
         _LOGD(LOGD_BOND, "bond slave %s is already released", nm_device_get_ip_iface(slave));
 
     if (configure) {
-        NMConnection *  applied;
+        NMConnection   *applied;
         NMSettingWired *s_wired;
-        const char *    cloned_mac;
+        const char     *cloned_mac;
 
         address = g_strdup(nm_device_get_hw_address(device));
 
@@ -524,11 +523,11 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     const char *iface = nm_device_get_iface(device);
     int         r;
@@ -550,12 +549,12 @@ create_and_realize(NMDevice *             device,
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -563,7 +562,7 @@ can_reapply_change(NMDevice *  device,
     if (nm_streq(setting_name, NM_SETTING_BOND_SETTING_NAME)) {
         NMSettingBond *s_a = NM_SETTING_BOND(s_old);
         NMSettingBond *s_b = NM_SETTING_BOND(s_new);
-        const char **  option_list;
+        const char   **option_list;
 
         if (!nm_device_hash_check_invalid_keys(diffs,
                                                NM_SETTING_BOND_SETTING_NAME,
@@ -602,9 +601,9 @@ can_reapply_change(NMDevice *  device,
 static void
 reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new)
 {
-    NMDeviceBond * self = NM_DEVICE_BOND(device);
+    NMDeviceBond  *self = NM_DEVICE_BOND(device);
     NMSettingBond *s_bond;
-    const char *   value;
+    const char    *value;
     NMBondMode     mode;
 
     NM_DEVICE_CLASS(nm_device_bond_parent_class)->reapply_connection(device, con_old, con_new);
@@ -646,7 +645,7 @@ static void
 nm_device_bond_class_init(NMDeviceBondClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bond);
 
@@ -677,11 +676,11 @@ nm_device_bond_class_init(NMDeviceBondClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BOND_DEVICE_FACTORY, NMBondDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_BOND,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index 36a73978..4e4c2637 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -48,13 +48,13 @@ get_generic_capabilities(NMDevice *dev)
 }
 
 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)
 {
-    NMDeviceBridge *    self = NM_DEVICE_BRIDGE(device);
+    NMDeviceBridge     *self = NM_DEVICE_BRIDGE(device);
     NMSettingBluetooth *s_bt;
 
     if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class)
@@ -97,7 +97,7 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMSettingBridge *s_bridge;
-    const char *     mac_address;
+    const char      *mac_address;
 
     if (!NM_DEVICE_CLASS(nm_device_bridge_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -140,11 +140,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
@@ -290,8 +290,8 @@ typedef struct {
 
 #define OPTION(_name, _sysname, ...) \
     {                                \
-        .name = ""_name              \
-                "",                  \
+        .name    = ""_name           \
+                   "",               \
         .sysname = ""_sysname        \
                    "",               \
         __VA_ARGS__                  \
@@ -339,7 +339,6 @@ static const Option master_options[] = {
            OPTION_TYPE_INT(NM_BRIDGE_AGEING_TIME_MIN,
                            NM_BRIDGE_AGEING_TIME_MAX,
                            NM_BRIDGE_AGEING_TIME_DEF),
-           .default_if_zero    = TRUE,
            .user_hz_compensate = TRUE, ),
     OPTION(NM_SETTING_BRIDGE_GROUP_FORWARD_MASK, "group_fwd_mask", OPTION_TYPE_INT(0, 0xFFFF, 0), ),
     OPTION(NM_SETTING_BRIDGE_MULTICAST_HASH_MAX,
@@ -433,8 +432,8 @@ commit_option(NMDevice *device, NMSetting *setting, const Option *option, gboole
 {
     int                         ifindex = nm_device_get_ifindex(device);
     nm_auto_unset_gvalue GValue val     = G_VALUE_INIT;
-    GParamSpec *                pspec;
-    const char *                value;
+    GParamSpec                 *pspec;
+    const char                 *value;
     char                        value_buf[100];
 
     if (slave)
@@ -522,7 +521,7 @@ static const NMPlatformBridgeVlan **
 setting_vlans_to_platform(GPtrArray *array)
 {
     NMPlatformBridgeVlan **arr;
-    NMPlatformBridgeVlan * p_data;
+    NMPlatformBridgeVlan  *p_data;
     guint                  i;
 
     if (!array || !array->len)
@@ -554,8 +553,8 @@ setting_vlans_to_platform(GPtrArray *array)
 static void
 commit_slave_options(NMDevice *device, NMSettingBridgePort *setting)
 {
-    const Option *  option;
-    NMSetting *     s;
+    const Option              *option;
+    NMSetting                 *s;
     gs_unref_object NMSetting *s_clear = NULL;
 
     if (setting)
@@ -570,11 +569,11 @@ commit_slave_options(NMDevice *device, NMSettingBridgePort *setting)
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceBridge * self     = NM_DEVICE_BRIDGE(device);
+    NMDeviceBridge  *self     = NM_DEVICE_BRIDGE(device);
     NMSettingBridge *s_bridge = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BRIDGE);
     int              ifindex  = nm_device_get_ifindex(device);
-    const Option *   option;
-    gs_free char *   stp = NULL;
+    const Option    *option;
+    gs_free char    *stp = NULL;
     int              stp_value;
 
     option = master_options;
@@ -590,8 +589,8 @@ update_connection(NMDevice *device, NMConnection *connection)
 
     for (; option->name; option++) {
         nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
-        gs_free char *              str   = NULL;
-        GParamSpec *                pspec;
+        gs_free char               *str   = NULL;
+        GParamSpec                 *pspec;
 
         str   = nm_platform_sysctl_master_get_option(nm_device_get_platform(device),
                                                    ifindex,
@@ -665,17 +664,17 @@ out:
 }
 
 static gboolean
-master_update_slave_connection(NMDevice *    device,
-                               NMDevice *    slave,
+master_update_slave_connection(NMDevice     *device,
+                               NMDevice     *slave,
                                NMConnection *connection,
-                               GError **     error)
+                               GError      **error)
 {
-    NMDeviceBridge *     self = NM_DEVICE_BRIDGE(device);
+    NMDeviceBridge      *self = NM_DEVICE_BRIDGE(device);
     NMSettingConnection *s_con;
     NMSettingBridgePort *s_port;
     int                  ifindex_slave = nm_device_get_ifindex(slave);
-    const char *         iface         = nm_device_get_iface(device);
-    const Option *       option;
+    const char          *iface         = nm_device_get_iface(device);
+    const Option        *option;
 
     g_return_val_if_fail(ifindex_slave > 0, FALSE);
 
@@ -721,14 +720,14 @@ master_update_slave_connection(NMDevice *    device,
 static gboolean
 bridge_set_vlan_options(NMDevice *device, NMSettingBridge *s_bridge)
 {
-    NMDeviceBridge *  self = NM_DEVICE_BRIDGE(device);
-    gconstpointer     hwaddr;
-    size_t            length;
-    gboolean          enabled;
-    guint16           pvid;
-    NMPlatform *      plat;
-    int               ifindex;
-    gs_unref_ptrarray GPtrArray *vlans              = NULL;
+    NMDeviceBridge                      *self = NM_DEVICE_BRIDGE(device);
+    gconstpointer                        hwaddr;
+    size_t                               length;
+    gboolean                             enabled;
+    guint16                              pvid;
+    NMPlatform                          *plat;
+    int                                  ifindex;
+    gs_unref_ptrarray GPtrArray         *vlans      = NULL;
     gs_free const NMPlatformBridgeVlan **plat_vlans = NULL;
 
     if (self->vlan_configured)
@@ -796,7 +795,7 @@ static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
     NMConnection *connection;
-    NMSetting *   s_bridge;
+    NMSetting    *s_bridge;
     const Option *option;
 
     connection = nm_device_get_applied_connection(device);
@@ -860,9 +859,9 @@ _nm_device_bridge_notify_unregister_bt_nap(NMDevice *device, const char *reason)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBridge *    self = NM_DEVICE_BRIDGE(device);
-    NMConnection *      connection;
-    NMSettingBluetooth *s_bt;
+    NMDeviceBridge       *self = NM_DEVICE_BRIDGE(device);
+    NMConnection         *connection;
+    NMSettingBluetooth   *s_bt;
     gs_free_error GError *error = NULL;
 
     connection = nm_device_get_applied_connection(device);
@@ -922,9 +921,9 @@ deactivate(NMDevice *device)
 static gboolean
 enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
 {
-    NMDeviceBridge *     self = NM_DEVICE_BRIDGE(device);
-    NMConnection *       master_connection;
-    NMSettingBridge *    s_bridge;
+    NMDeviceBridge      *self = NM_DEVICE_BRIDGE(device);
+    NMConnection        *master_connection;
+    NMSettingBridge     *s_bridge;
     NMSettingBridgePort *s_port;
 
     if (configure) {
@@ -943,7 +942,7 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
 
         if (nm_setting_bridge_get_vlan_filtering(s_bridge)) {
             gs_free const NMPlatformBridgeVlan **plat_vlans = NULL;
-            gs_unref_ptrarray GPtrArray *vlans              = NULL;
+            gs_unref_ptrarray GPtrArray         *vlans      = NULL;
 
             if (s_port)
                 g_object_get(s_port, NM_SETTING_BRIDGE_PORT_VLANS, &vlans, NULL);
@@ -1008,17 +1007,17 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    NMSettingWired *    s_wired;
-    NMSettingBridge *   s_bridge;
-    const char *        iface = nm_device_get_iface(device);
-    const char *        hwaddr;
-    gs_free char *      hwaddr_cloned = NULL;
+    NMSettingWired     *s_wired;
+    NMSettingBridge    *s_bridge;
+    const char         *iface = nm_device_get_iface(device);
+    const char         *hwaddr;
+    gs_free char       *hwaddr_cloned = NULL;
     guint8              mac_address[_NM_UTILS_HWADDR_LEN_MAX];
     NMPlatformLnkBridge props;
     int                 r;
@@ -1062,8 +1061,7 @@ create_and_realize(NMDevice *             device,
                                        NM_BRIDGE_HELLO_TIME_DEF_SYS),
         .max_age       = _DEFAULT_IF_ZERO(nm_setting_bridge_get_max_age(s_bridge) * 100u,
                                     NM_BRIDGE_MAX_AGE_DEF_SYS),
-        .ageing_time   = _DEFAULT_IF_ZERO(nm_setting_bridge_get_ageing_time(s_bridge) * 100u,
-                                        NM_BRIDGE_AGEING_TIME_DEF_SYS),
+        .ageing_time   = nm_setting_bridge_get_ageing_time(s_bridge) * 100u,
         .stp_state     = nm_setting_bridge_get_stp(s_bridge),
         .priority      = nm_setting_bridge_get_priority(s_bridge),
         .vlan_protocol = to_sysfs_vlan_protocol_sys(nm_setting_bridge_get_vlan_protocol(s_bridge)),
@@ -1138,7 +1136,7 @@ static void
 nm_device_bridge_class_init(NMDeviceBridgeClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_bridge);
 
@@ -1172,11 +1170,11 @@ nm_device_bridge_class_init(NMDeviceBridgeClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_BRIDGE_DEVICE_FACTORY, NMBridgeDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_BRIDGE,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index 4c12648f..de701a0d 100644
--- a/src/core/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -12,7 +12,6 @@
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-dummy.h"
@@ -43,11 +42,11 @@ get_generic_capabilities(NMDevice *dev)
 }
 
 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)
 {
     nm_utils_complete_generic_with_params(nm_device_get_platform(device),
                                           connection,
@@ -70,13 +69,13 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *    iface = nm_device_get_iface(device);
+    const char     *iface = nm_device_get_iface(device);
     NMSettingDummy *s_dummy;
     int             r;
 
@@ -117,7 +116,7 @@ static void
 nm_device_dummy_class_init(NMDeviceDummyClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_dummy);
 
@@ -140,11 +139,11 @@ nm_device_dummy_class_init(NMDeviceDummyClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DUMMY_DEVICE_FACTORY, NMDummyDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_DUMMY,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 667ea9b9..6e18d119 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -14,38 +14,36 @@
 #include <libudev.h>
 #include <linux/if_ether.h>
 
-#include "libnm-glib-aux/nm-uuid.h"
-#include "nm-device-private.h"
-#include "nm-act-request.h"
-#include "nm-ip4-config.h"
 #include "NetworkManagerUtils.h"
-#include "supplicant/nm-supplicant-manager.h"
-#include "supplicant/nm-supplicant-interface.h"
-#include "supplicant/nm-supplicant-config.h"
-#include "ppp/nm-ppp-manager.h"
-#include "ppp/nm-ppp-manager-call.h"
-#include "ppp/nm-ppp-status.h"
-#include "libnm-platform/nm-platform.h"
+#include "NetworkManagerUtils.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-glib-aux/nm-uuid.h"
 #include "libnm-platform/nm-platform-utils.h"
-#include "nm-dcb.h"
-#include "settings/nm-settings-connection.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-udev-aux/nm-udev-utils.h"
+#include "nm-act-request.h"
 #include "nm-config.h"
+#include "nm-dcb.h"
 #include "nm-device-ethernet-utils.h"
-#include "settings/nm-settings.h"
 #include "nm-device-factory.h"
-#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
-#include "libnm-core-intern/nm-core-internal.h"
-#include "NetworkManagerUtils.h"
-#include "libnm-udev-aux/nm-udev-utils.h"
+#include "nm-device-private.h"
 #include "nm-device-veth.h"
+#include "nm-manager.h"
+#include "ppp/nm-ppp-mgr.h"
+#include "settings/nm-settings-connection.h"
+#include "settings/nm-settings.h"
+#include "supplicant/nm-supplicant-config.h"
+#include "supplicant/nm-supplicant-interface.h"
+#include "supplicant/nm-supplicant-manager.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceEthernet
 #include "nm-device-logging.h"
 
 /*****************************************************************************/
 
-#define PPPOE_RECONNECT_DELAY 7
-#define PPPOE_ENCAP_OVERHEAD  8 /* 2 bytes for PPP, 6 for PPPoE */
+#define PPPOE_RECONNECT_DELAY_MSEC 7000
+#define PPPOE_ENCAP_OVERHEAD       8 /* 2 bytes for PPP, 6 for PPPoE */
 
 #define SUPPLICANT_LNK_TIMEOUT_SEC 15
 
@@ -67,21 +65,21 @@ typedef enum {
 
 typedef struct _NMDeviceEthernetPrivate {
     /* s390 */
-    char *      subchan1;
-    char *      subchan2;
-    char *      subchan3;
-    char *      subchannels;      /* Composite used for checking unmanaged specs */
-    char **     subchannels_dbus; /* Array exported on D-Bus */
-    char *      s390_nettype;
+    char       *subchan1;
+    char       *subchan2;
+    char       *subchan3;
+    char       *subchannels;      /* Composite used for checking unmanaged specs */
+    char      **subchannels_dbus; /* Array exported on D-Bus */
+    char       *s390_nettype;
     GHashTable *s390_options;
 
     guint32 speed;
     gulong  carrier_id;
 
     struct {
-        NMSupplicantManager *        mgr;
+        NMSupplicantManager         *mgr;
         NMSupplMgrCreateIfaceHandle *create_handle;
-        NMSupplicantInterface *      iface;
+        NMSupplicantInterface       *iface;
 
         gulong iface_state_id;
         gulong auth_state_id;
@@ -91,14 +89,16 @@ typedef struct _NMDeviceEthernetPrivate {
         guint lnk_timeout_id;
 
         bool is_associated : 1;
+        bool ready : 1;
     } supplicant;
 
     NMActRequestGetSecretsCallId *wired_secrets_id;
 
-    /* PPPoE */
-    NMPPPManager *ppp_manager;
-    gint32        last_pppoe_time;
-    guint         pppoe_wait_id;
+    struct {
+        NMPppMgr *ppp_mgr;
+        GSource  *wait_source;
+        gint64    last_pppoe_time_msec;
+    } ppp_data;
 
     /* DCB */
     DcbWait dcb_wait;
@@ -113,6 +113,8 @@ typedef struct _NMDeviceEthernetPrivate {
     bool ethtool_prev_set : 1;
     bool ethtool_prev_autoneg : 1;
 
+    bool stage2_ready_dcb : 1;
+
 } NMDeviceEthernetPrivate;
 
 NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceEthernet, PROP_SPEED, PROP_S390_SUBCHANNELS, );
@@ -150,12 +152,12 @@ static void
 _update_s390_subchannels(NMDeviceEthernet *self)
 {
     NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    struct udev_device *     dev    = NULL;
-    struct udev_device *     parent = NULL;
-    const char *             parent_path, *item;
+    struct udev_device      *dev    = NULL;
+    struct udev_device      *parent = NULL;
+    const char              *parent_path, *item;
     int                      ifindex;
-    GDir *                   dir;
-    GError *                 error = NULL;
+    GDir                    *dir;
+    GError                  *error = NULL;
 
     if (priv->subchannels) {
         /* only read the subchannels once. For one, we don't expect them to change
@@ -251,7 +253,7 @@ _update_s390_subchannels(NMDeviceEthernet *self)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
@@ -306,7 +308,7 @@ static gboolean
 match_subchans(NMDeviceEthernet *self, NMSettingWired *s_wired, gboolean *try_mac)
 {
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    const char *const *      subchans;
+    const char *const       *subchans;
     guint32                  num1, num2;
     int                      i;
 
@@ -345,7 +347,7 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMDeviceEthernet *self = NM_DEVICE_ETHERNET(device);
-    NMSettingWired *  s_wired;
+    NMSettingWired   *s_wired;
 
     if (!NM_DEVICE_CLASS(nm_device_ethernet_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -363,7 +365,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
     }
 
     if (s_wired) {
-        const char *       mac, *perm_hw_addr;
+        const char        *mac, *perm_hw_addr;
         gboolean           try_mac = TRUE;
         const char *const *mac_blacklist;
         int                i;
@@ -428,6 +430,7 @@ supplicant_interface_release(NMDeviceEthernet *self)
     nm_clear_g_source(&priv->supplicant.con_timeout_id);
     nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.iface_state_id);
     nm_clear_g_signal_handler(priv->supplicant.iface, &priv->supplicant.auth_state_id);
+    priv->supplicant.ready = FALSE;
 
     if (priv->supplicant.iface) {
         nm_supplicant_interface_disconnect(priv->supplicant.iface);
@@ -437,8 +440,8 @@ supplicant_interface_release(NMDeviceEthernet *self)
 
 static void
 supplicant_auth_state_changed(NMSupplicantInterface *iface,
-                              GParamSpec *           pspec,
-                              NMDeviceEthernet *     self)
+                              GParamSpec            *pspec,
+                              NMDeviceEthernet      *self)
 {
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     NMSupplicantAuthState    state;
@@ -466,40 +469,39 @@ static void
 wired_auth_cond_fail(NMDeviceEthernet *self, NMDeviceStateReason reason)
 {
     NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMDevice *               device = NM_DEVICE(self);
-
-    if (wired_auth_is_optional(self)) {
-        _LOGI(
-            LOGD_DEVICE | LOGD_ETHER,
-            "Activation: (ethernet) 802.1X authentication is optional, continuing after a failure");
-        if (NM_IN_SET(nm_device_get_state(device),
-                      NM_DEVICE_STATE_CONFIG,
-                      NM_DEVICE_STATE_NEED_AUTH))
-            nm_device_activate_schedule_stage3_ip_config_start(device);
-
-        if (!priv->supplicant.auth_state_id) {
-            priv->supplicant.auth_state_id =
-                g_signal_connect(priv->supplicant.iface,
-                                 "notify::" NM_SUPPLICANT_INTERFACE_AUTH_STATE,
-                                 G_CALLBACK(supplicant_auth_state_changed),
-                                 self);
-        }
+    NMDevice                *device = NM_DEVICE(self);
+
+    if (!wired_auth_is_optional(self)) {
+        supplicant_interface_release(self);
+        nm_device_state_changed(NM_DEVICE(self), NM_DEVICE_STATE_FAILED, reason);
         return;
     }
 
-    supplicant_interface_release(self);
-    nm_device_state_changed(NM_DEVICE(self), NM_DEVICE_STATE_FAILED, reason);
+    _LOGI(LOGD_DEVICE | LOGD_ETHER,
+          "Activation: (ethernet) 802.1X authentication is optional, continuing after a failure");
+    priv->supplicant.ready = TRUE;
+
+    if (NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH))
+        nm_device_activate_schedule_stage2_device_config(device, FALSE);
+
+    if (!priv->supplicant.auth_state_id) {
+        priv->supplicant.auth_state_id =
+            g_signal_connect(priv->supplicant.iface,
+                             "notify::" NM_SUPPLICANT_INTERFACE_AUTH_STATE,
+                             G_CALLBACK(supplicant_auth_state_changed),
+                             self);
+    }
 }
 
 static void
-wired_secrets_cb(NMActRequest *                req,
+wired_secrets_cb(NMActRequest                 *req,
                  NMActRequestGetSecretsCallId *call_id,
-                 NMSettingsConnection *        connection,
-                 GError *                      error,
+                 NMSettingsConnection         *connection,
+                 GError                       *error,
                  gpointer                      user_data)
 {
-    NMDeviceEthernet *       self   = user_data;
-    NMDevice *               device = user_data;
+    NMDeviceEthernet        *self   = user_data;
+    NMDevice                *device = user_data;
     NMDeviceEthernetPrivate *priv;
 
     g_return_if_fail(NM_IS_DEVICE_ETHERNET(self));
@@ -539,12 +541,12 @@ wired_secrets_cancel(NMDeviceEthernet *self)
 }
 
 static void
-wired_secrets_get_secrets(NMDeviceEthernet *           self,
-                          const char *                 setting_name,
+wired_secrets_get_secrets(NMDeviceEthernet            *self,
+                          const char                  *setting_name,
                           NMSecretAgentGetSecretsFlags flags)
 {
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMActRequest *           req;
+    NMActRequest            *req;
 
     wired_secrets_cancel(self);
 
@@ -559,12 +561,12 @@ wired_secrets_get_secrets(NMDeviceEthernet *           self,
 static gboolean
 supplicant_lnk_timeout_cb(gpointer user_data)
 {
-    NMDeviceEthernet *       self   = NM_DEVICE_ETHERNET(user_data);
+    NMDeviceEthernet        *self   = NM_DEVICE_ETHERNET(user_data);
     NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMDevice *               device = NM_DEVICE(self);
-    NMActRequest *           req;
-    NMConnection *           applied_connection;
-    const char *             setting_name;
+    NMDevice                *device = NM_DEVICE(self);
+    NMActRequest            *req;
+    NMConnection            *applied_connection;
+    const char              *setting_name;
 
     priv->supplicant.lnk_timeout_id = 0;
 
@@ -611,10 +613,10 @@ time_out:
 static NMSupplicantConfig *
 build_supplicant_config(NMDeviceEthernet *self, GError **error)
 {
-    const char *        con_uuid;
+    const char         *con_uuid;
     NMSupplicantConfig *config = NULL;
-    NMSetting8021x *    security;
-    NMConnection *      connection;
+    NMSetting8021x     *security;
+    NMConnection       *connection;
     guint32             mtu;
 
     connection = nm_device_get_applied_connection(NM_DEVICE(self));
@@ -644,14 +646,15 @@ supplicant_iface_state_is_completed(NMDeviceEthernet *self, NMSupplicantInterfac
     if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
         nm_clear_g_source(&priv->supplicant.lnk_timeout_id);
         nm_clear_g_source(&priv->supplicant.con_timeout_id);
+        priv->supplicant.ready = TRUE;
 
         /* If this is the initial association during device activation,
-         * schedule the next activation stage.
+         * schedule the activation stage again to proceed.
          */
         if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) {
             _LOGI(LOGD_DEVICE | LOGD_ETHER,
                   "Activation: (ethernet) Stage 2 of 5 (Device Configure) successful.");
-            nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self));
+            nm_device_activate_schedule_stage2_device_config(NM_DEVICE(self), FALSE);
         }
         return;
     }
@@ -664,7 +667,7 @@ supplicant_iface_state_is_completed(NMDeviceEthernet *self, NMSupplicantInterfac
 static void
 supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data)
 {
-    NMDeviceEthernet *       self;
+    NMDeviceEthernet        *self;
     NMDeviceEthernetPrivate *priv;
 
     if (nm_utils_error_is_cancelled_or_disposing(error))
@@ -692,9 +695,9 @@ supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer
 static gboolean
 supplicant_iface_start(NMDeviceEthernet *self)
 {
-    NMDeviceEthernetPrivate *priv              = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
+    NMDeviceEthernetPrivate            *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     gs_unref_object NMSupplicantConfig *config = NULL;
-    gs_free_error GError *error                = NULL;
+    gs_free_error GError               *error  = NULL;
 
     config = build_supplicant_config(self, &error);
     if (!config) {
@@ -720,8 +723,8 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
                           int                    disconnect_reason,
                           gpointer               user_data)
 {
-    NMDeviceEthernet *         self      = NM_DEVICE_ETHERNET(user_data);
-    NMDeviceEthernetPrivate *  priv      = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
+    NMDeviceEthernet          *self      = NM_DEVICE_ETHERNET(user_data);
+    NMDeviceEthernetPrivate   *priv      = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     NMSupplicantInterfaceState new_state = new_state_i;
     NMSupplicantInterfaceState old_state = old_state_i;
 
@@ -750,7 +753,7 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
 static gboolean
 handle_auth_or_fail(NMDeviceEthernet *self, NMActRequest *req, gboolean new_secrets)
 {
-    const char *  setting_name;
+    const char   *setting_name;
     NMConnection *applied_connection;
 
     if (!nm_device_auth_retries_try_next(NM_DEVICE(self)))
@@ -789,11 +792,11 @@ handle_auth_or_fail(NMDeviceEthernet *self, NMActRequest *req, gboolean new_secr
 static gboolean
 supplicant_connection_timeout_cb(gpointer user_data)
 {
-    NMDeviceEthernet *       self   = NM_DEVICE_ETHERNET(user_data);
+    NMDeviceEthernet        *self   = NM_DEVICE_ETHERNET(user_data);
     NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMDevice *               device = NM_DEVICE(self);
-    NMActRequest *           req;
-    NMSettingsConnection *   connection;
+    NMDevice                *device = NM_DEVICE(self);
+    NMActRequest            *req;
+    NMSettingsConnection    *connection;
     guint64                  timestamp   = 0;
     gboolean                 new_secrets = TRUE;
 
@@ -833,13 +836,13 @@ supplicant_connection_timeout_cb(gpointer user_data)
 }
 
 static void
-supplicant_interface_create_cb(NMSupplicantManager *        supplicant_manager,
+supplicant_interface_create_cb(NMSupplicantManager         *supplicant_manager,
                                NMSupplMgrCreateIfaceHandle *handle,
-                               NMSupplicantInterface *      iface,
-                               GError *                     error,
+                               NMSupplicantInterface       *iface,
+                               GError                      *error,
                                gpointer                     user_data)
 {
-    NMDeviceEthernet *       self;
+    NMDeviceEthernet        *self;
     NMDeviceEthernetPrivate *priv;
     guint                    timeout;
 
@@ -894,9 +897,9 @@ link_duplex_to_platform(const char *duplex)
 static void
 link_negotiation_set(NMDevice *device)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMSettingWired *         s_wired;
+    NMSettingWired          *s_wired;
     gboolean                 autoneg = TRUE;
     gboolean                 link_autoneg;
     NMPlatformLinkDuplexType duplex      = NM_PLATFORM_LINK_DUPLEX_UNKNOWN;
@@ -966,20 +969,20 @@ link_negotiation_set(NMDevice *device)
 static gboolean
 pppoe_reconnect_delay(gpointer user_data)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(user_data);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(user_data);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
-    priv->pppoe_wait_id   = 0;
-    priv->last_pppoe_time = 0;
+    nm_clear_g_source_inst(&priv->ppp_data.wait_source);
+    priv->ppp_data.last_pppoe_time_msec = 0;
     _LOGI(LOGD_DEVICE, "PPPoE reconnect delay complete, resuming connection...");
     nm_device_activate_schedule_stage1_device_prepare(NM_DEVICE(self), FALSE);
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
     if (nm_device_sys_iface_state_is_external_or_assume(device)) {
@@ -1014,21 +1017,24 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
      * at least for additional NM_SHUTDOWN_TIMEOUT_MS seconds because
      * otherwise after restart the device won't work for the first seconds.
      */
-    if (priv->last_pppoe_time != 0) {
-        gint32 delay = nm_utils_get_monotonic_timestamp_sec() - priv->last_pppoe_time;
+    if (priv->ppp_data.last_pppoe_time_msec != 0) {
+        gint64 delay =
+            nm_utils_get_monotonic_timestamp_msec() - priv->ppp_data.last_pppoe_time_msec;
 
-        if (delay < PPPOE_RECONNECT_DELAY
+        if (delay < PPPOE_RECONNECT_DELAY_MSEC
             && nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE)) {
-            if (priv->pppoe_wait_id == 0) {
+            if (!priv->ppp_data.wait_source) {
                 _LOGI(LOGD_DEVICE,
-                      "delaying PPPoE reconnect for %d seconds to ensure peer is ready...",
-                      delay);
-                priv->pppoe_wait_id = g_timeout_add_seconds(delay, pppoe_reconnect_delay, self);
+                      "delaying PPPoE reconnect for %d.%03d seconds to ensure peer is ready...",
+                      (int) (delay / 1000),
+                      (int) (delay % 1000));
+                priv->ppp_data.wait_source =
+                    nm_g_timeout_add_source(delay, pppoe_reconnect_delay, self);
             }
             return NM_ACT_STAGE_RETURN_POSTPONE;
         }
-        nm_clear_g_source(&priv->pppoe_wait_id);
-        priv->last_pppoe_time = 0;
+        nm_clear_g_source_inst(&priv->ppp_data.wait_source);
+        priv->ppp_data.last_pppoe_time_msec = 0;
     }
 
     return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -1038,9 +1044,9 @@ static NMActStageReturn
 supplicant_check_secrets_needed(NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason)
 {
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMConnection *           connection;
-    NMSetting8021x *         security;
-    const char *             setting_name;
+    NMConnection            *connection;
+    NMSetting8021x          *security;
+    const char              *setting_name;
 
     connection = nm_device_get_applied_connection(NM_DEVICE(self));
     g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
@@ -1105,105 +1111,79 @@ carrier_changed(NMSupplicantInterface *iface, GParamSpec *pspec, NMDeviceEtherne
 }
 
 /*****************************************************************************/
-/* PPPoE */
 
 static void
-ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+_ppp_mgr_cleanup(NMDeviceEthernet *self)
 {
-    NMDevice *device = NM_DEVICE(user_data);
+    NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
-    switch (status) {
-    case NM_PPP_STATUS_DISCONNECT:
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
-        break;
-    case NM_PPP_STATUS_DEAD:
-        nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
-        break;
-    default:
-        break;
-    }
+    nm_clear_pointer(&priv->ppp_data.ppp_mgr, nm_ppp_mgr_destroy);
 }
 
 static void
-ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpointer user_data)
+_ppp_mgr_stage3_maybe_ready(NMDeviceEthernet *self)
 {
-    NMDevice *device = NM_DEVICE(user_data);
+    NMDevice                *device = NM_DEVICE(self);
+    NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
+    int                      IS_IPv4;
 
-    if (!nm_device_set_ip_ifindex(device, ifindex)) {
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-    }
-}
+    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+        const int             addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const NMPppMgrIPData *ip_data;
 
-static void
-ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
-{
-    NMDevice *device = NM_DEVICE(user_data);
+        ip_data = nm_ppp_mgr_get_ip_data(priv->ppp_data.ppp_mgr, addr_family);
+        if (ip_data->ip_received)
+            nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, ip_data->l3cd);
+    }
 
-    /* Ignore PPP IP4 events that come in after initial configuration */
-    if (nm_device_activate_ip4_state_in_conf(device))
-        nm_device_activate_schedule_ip_config_result(device, AF_INET, NM_IP_CONFIG_CAST(config));
+    if (nm_ppp_mgr_get_state(priv->ppp_data.ppp_mgr) >= NM_PPP_MGR_STATE_HAVE_IP_CONFIG)
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
-static NMActStageReturn
-pppoe_stage3_ip4_config_start(NMDeviceEthernet *self, NMDeviceStateReason *out_failure_reason)
+static void
+_ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data, gpointer user_data)
 {
-    NMDevice *               device = NM_DEVICE(self);
-    NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMSettingPppoe *         s_pppoe;
-    NMActRequest *           req;
-    GError *                 err = NULL;
-
-    req = nm_device_get_act_request(device);
-
-    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
-
-    s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE);
+    NMDeviceEthernet *self   = NM_DEVICE_ETHERNET(user_data);
+    NMDevice         *device = NM_DEVICE(self);
+    NMDeviceState     device_state;
 
-    g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+    if (callback_data->callback_type != NM_PPP_MGR_CALLBACK_TYPE_STATE_CHANGED)
+        return;
 
-    priv->ppp_manager = nm_ppp_manager_create(nm_device_get_iface(device), &err);
+    device_state = nm_device_get_state(device);
 
-    if (priv->ppp_manager) {
-        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
-                                            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));
+    if (callback_data->data.state >= _NM_PPP_MGR_STATE_FAILED_START) {
+        if (device_state <= NM_DEVICE_STATE_ACTIVATED)
+            nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, callback_data->data.reason);
+        return;
     }
 
-    if (!priv->ppp_manager
-        || !nm_ppp_manager_start(priv->ppp_manager,
-                                 req,
-                                 nm_setting_pppoe_get_username(s_pppoe),
-                                 30,
-                                 0,
-                                 &err)) {
-        _LOGW(LOGD_DEVICE, "PPPoE failed to start: %s", err->message);
-        g_error_free(err);
+    if (device_state < NM_DEVICE_STATE_IP_CONFIG) {
+        if (callback_data->data.state >= NM_PPP_MGR_STATE_HAVE_IFINDEX) {
+            gs_free char         *old_name = NULL;
+            gs_free_error GError *error    = NULL;
+
+            if (!nm_device_take_over_link(device, callback_data->data.ifindex, &old_name, &error)) {
+                _LOGW(LOGD_DEVICE | LOGD_PPP,
+                      "could not take control of link %d: %s",
+                      callback_data->data.ifindex,
+                      error->message);
+                _ppp_mgr_cleanup(self);
+                nm_device_state_changed(device,
+                                        NM_DEVICE_STATE_FAILED,
+                                        NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+                return;
+            }
 
-        g_clear_object(&priv->ppp_manager);
+            if (old_name)
+                nm_manager_remove_device(NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_PPP);
 
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+            nm_device_activate_schedule_stage2_device_config(device, FALSE);
+        }
+        return;
     }
 
-    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);
-    return NM_ACT_STAGE_RETURN_POSTPONE;
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
 /*****************************************************************************/
@@ -1213,9 +1193,9 @@ static void dcb_state(NMDevice *device, gboolean timeout);
 static gboolean
 dcb_carrier_timeout(gpointer user_data)
 {
-    NMDeviceEthernet *       self   = NM_DEVICE_ETHERNET(user_data);
+    NMDeviceEthernet        *self   = NM_DEVICE_ETHERNET(user_data);
     NMDeviceEthernetPrivate *priv   = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMDevice *               device = NM_DEVICE(user_data);
+    NMDevice                *device = NM_DEVICE(user_data);
 
     g_return_val_if_fail(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG, G_SOURCE_REMOVE);
 
@@ -1230,10 +1210,10 @@ dcb_carrier_timeout(gpointer user_data)
 static gboolean
 dcb_configure(NMDevice *device)
 {
-    NMDeviceEthernet *       self = (NMDeviceEthernet *) device;
+    NMDeviceEthernet        *self = (NMDeviceEthernet *) device;
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMSettingDcb *           s_dcb;
-    GError *                 error = NULL;
+    NMSettingDcb            *s_dcb;
+    GError                  *error = NULL;
 
     nm_clear_g_source(&priv->dcb_timeout_id);
 
@@ -1259,9 +1239,9 @@ dcb_configure(NMDevice *device)
 static gboolean
 dcb_enable(NMDevice *device)
 {
-    NMDeviceEthernet *       self  = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self  = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv  = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    GError *                 error = NULL;
+    GError                  *error = NULL;
 
     nm_clear_g_source(&priv->dcb_timeout_id);
     if (!nm_dcb_enable(nm_device_get_iface(device), TRUE, &error)) {
@@ -1286,7 +1266,7 @@ dcb_enable(NMDevice *device)
 static void
 dcb_state(NMDevice *device, gboolean timeout)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     gboolean                 carrier;
 
@@ -1351,7 +1331,7 @@ dcb_state(NMDevice *device, gboolean timeout)
             nm_clear_g_source(&priv->dcb_timeout_id);
             priv->dcb_handle_carrier_changes = FALSE;
             priv->dcb_wait                   = DCB_WAIT_UNKNOWN;
-            nm_device_activate_schedule_stage3_ip_config_start(device);
+            nm_device_activate_schedule_stage2_device_config(device, FALSE);
         }
         break;
     default:
@@ -1365,8 +1345,8 @@ static gboolean
 wake_on_lan_enable(NMDevice *device)
 {
     NMSettingWiredWakeOnLan wol;
-    NMSettingWired *        s_wired;
-    const char *            password = NULL;
+    NMSettingWired         *s_wired;
+    const char             *password = NULL;
 
     s_wired = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRED);
 
@@ -1407,31 +1387,98 @@ found:
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceEthernet *       self = (NMDeviceEthernet *) device;
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMSettingConnection *    s_con;
-    const char *             connection_type;
-    gboolean                 do_postpone = FALSE;
-    NMSettingDcb *           s_dcb;
+    NMConnection            *connection;
+    NMSettingConnection     *s_con;
+    const char              *connection_type;
+    NMSettingDcb            *s_dcb;
+    NMActRequest            *req;
 
-    s_con = nm_device_get_applied_setting(device, NM_TYPE_SETTING_CONNECTION);
+    connection = nm_device_get_applied_connection(device);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
 
+    s_con = _nm_connection_get_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_return_val_if_fail(s_con, NM_ACT_STAGE_RETURN_FAILURE);
 
     nm_clear_g_source(&priv->dcb_timeout_id);
     priv->dcb_handle_carrier_changes = FALSE;
 
+    connection_type = nm_setting_connection_get_connection_type(s_con);
+
+    if (nm_streq(connection_type, NM_SETTING_PPPOE_SETTING_NAME)) {
+        if (!priv->ppp_data.ppp_mgr) {
+            gs_free_error GError *error = NULL;
+            NMSettingPppoe       *s_pppoe;
+            NMSettingPpp         *s_ppp;
+
+            s_ppp = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPP);
+            if (s_ppp) {
+                guint32 mtu;
+                guint32 mru;
+                guint32 mxu;
+
+                mtu = nm_setting_ppp_get_mtu(s_ppp);
+                mru = nm_setting_ppp_get_mru(s_ppp);
+                mxu = MAX(mru, mtu);
+                if (mxu) {
+                    _LOGD(LOGD_PPP,
+                          "set MTU to %u (PPP interface MRU %u, MTU %u)",
+                          mxu + PPPOE_ENCAP_OVERHEAD,
+                          mru,
+                          mtu);
+                    nm_platform_link_set_mtu(nm_device_get_platform(device),
+                                             nm_device_get_ifindex(device),
+                                             mxu + PPPOE_ENCAP_OVERHEAD);
+                }
+            }
+
+            req = nm_device_get_act_request(device);
+            g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+            s_pppoe = _nm_connection_get_setting(connection, NM_TYPE_SETTING_PPPOE);
+            g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+
+            priv->ppp_data.ppp_mgr =
+                nm_ppp_mgr_start(&((const NMPppMgrConfig){
+                                     .netns         = nm_device_get_netns(device),
+                                     .parent_iface  = nm_device_get_iface(device),
+                                     .callback      = _ppp_mgr_callback,
+                                     .user_data     = self,
+                                     .act_req       = req,
+                                     .ppp_username  = nm_setting_pppoe_get_username(s_pppoe),
+                                     .timeout_secs  = 30,
+                                     .baud_override = 0,
+                                 }),
+                                 &error);
+            if (!priv->ppp_data.ppp_mgr) {
+                _LOGW(LOGD_DEVICE | LOGD_PPP, "PPPoE 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 (nm_ppp_mgr_get_state(priv->ppp_data.ppp_mgr) < NM_PPP_MGR_STATE_HAVE_IFINDEX)
+            return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
+
     /* 802.1x has to run before any IP configuration since the 802.1x auth
      * process opens the port up for normal traffic.
      */
-    connection_type = nm_setting_connection_get_connection_type(s_con);
     if (nm_streq(connection_type, NM_SETTING_WIRED_SETTING_NAME)) {
         NMSetting8021x *security;
 
         security = nm_device_get_applied_setting(device, NM_TYPE_SETTING_802_1X);
 
         if (security) {
-            /* FIXME: for now 802.1x is mutually exclusive with DCB */
+            /* FIXME: we always return from this. stage2 must be re-entrant, and
+             * process all the necessary steps. Just returning for 8021x is wrong. */
+
+            if (priv->supplicant.ready)
+                return NM_ACT_STAGE_RETURN_SUCCESS;
+
             if (!nm_device_has_carrier(NM_DEVICE(self))) {
                 _LOGD(LOGD_DEVICE | LOGD_ETHER,
                       "delay supplicant initialization until carrier goes up");
@@ -1450,7 +1497,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
     /* DCB and FCoE setup */
     s_dcb = nm_device_get_applied_setting(device, NM_TYPE_SETTING_DCB);
-    if (s_dcb) {
+    if (!priv->stage2_ready_dcb && s_dcb) {
         /* lldpad really really wants the carrier to be up */
         if (nm_platform_link_is_connected(nm_device_get_platform(device),
                                           nm_device_get_ifindex(device))) {
@@ -1465,100 +1512,66 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         }
 
         priv->dcb_handle_carrier_changes = TRUE;
-        do_postpone                      = TRUE;
-    }
-
-    /* PPPoE setup */
-    if (nm_connection_is_type(nm_device_get_applied_connection(device),
-                              NM_SETTING_PPPOE_SETTING_NAME)) {
-        NMSettingPpp *s_ppp;
-
-        s_ppp = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPP);
-        if (s_ppp) {
-            guint32 mtu;
-            guint32 mru;
-            guint32 mxu;
-
-            mtu = nm_setting_ppp_get_mtu(s_ppp);
-            mru = nm_setting_ppp_get_mru(s_ppp);
-            mxu = MAX(mru, mtu);
-            if (mxu) {
-                _LOGD(LOGD_PPP,
-                      "set MTU to %u (PPP interface MRU %u, MTU %u)",
-                      mxu + PPPOE_ENCAP_OVERHEAD,
-                      mru,
-                      mtu);
-                nm_platform_link_set_mtu(nm_device_get_platform(device),
-                                         nm_device_get_ifindex(device),
-                                         mxu + PPPOE_ENCAP_OVERHEAD);
-            }
-        }
+        return NM_ACT_STAGE_RETURN_POSTPONE;
     }
 
-    return do_postpone ? NM_ACT_STAGE_RETURN_POSTPONE : NM_ACT_STAGE_RETURN_SUCCESS;
+    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)
+static guint32
+get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
 {
-    NMSettingConnection *s_con;
-    const char *         connection_type;
-    int                  ifindex;
+    NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(device);
 
-    ifindex = nm_device_get_ifindex(device);
+    /* MTU only set for plain ethernet */
+    if (priv->ppp_data.ppp_mgr)
+        return 0;
 
-    if (ifindex <= 0)
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    return nm_device_get_configured_mtu_for_wired(device, out_source, out_force);
+}
 
-    if (addr_family == AF_INET) {
-        s_con = nm_device_get_applied_setting(device, NM_TYPE_SETTING_CONNECTION);
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
+{
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
+    NMPppMgrState            ppp_state;
 
-        g_return_val_if_fail(s_con, NM_ACT_STAGE_RETURN_FAILURE);
+    if (!priv->ppp_data.ppp_mgr)
+        return;
 
-        connection_type = nm_setting_connection_get_connection_type(s_con);
-        if (!strcmp(connection_type, NM_SETTING_PPPOE_SETTING_NAME))
-            return pppoe_stage3_ip4_config_start(NM_DEVICE_ETHERNET(device), out_failure_reason);
-    }
+    ppp_state = nm_ppp_mgr_get_state(priv->ppp_data.ppp_mgr);
 
-    return NM_DEVICE_CLASS(nm_device_ethernet_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
-}
+    nm_assert(NM_IN_SET(ppp_state, NM_PPP_MGR_STATE_HAVE_IFINDEX, NM_PPP_MGR_STATE_HAVE_IP_CONFIG));
 
-static guint32
-get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
-{
-    /* MTU only set for plain ethernet */
-    if (NM_DEVICE_ETHERNET_GET_PRIVATE(device)->ppp_manager)
-        return 0;
+    if (ppp_state < NM_PPP_MGR_STATE_HAVE_IP_CONFIG) {
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING, NULL);
+        return;
+    }
 
-    return nm_device_get_configured_mtu_for_wired(device, out_source, out_force);
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
-    NMSettingDcb *           s_dcb;
-    GError *                 error = NULL;
+    NMSettingDcb            *s_dcb;
+    GError                  *error = NULL;
     int                      ifindex;
 
-    nm_clear_g_source(&priv->pppoe_wait_id);
+    nm_clear_g_source_inst(&priv->ppp_data.wait_source);
     nm_clear_g_signal_handler(self, &priv->carrier_id);
 
-    if (priv->ppp_manager) {
-        nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
-        g_clear_object(&priv->ppp_manager);
-    }
+    _ppp_mgr_cleanup(self);
 
     supplicant_interface_release(self);
 
     priv->dcb_wait = DCB_WAIT_UNKNOWN;
     nm_clear_g_source(&priv->dcb_timeout_id);
     priv->dcb_handle_carrier_changes = FALSE;
+    priv->stage2_ready_dcb           = FALSE;
 
     /* Tear down DCB/FCoE if it was enabled */
     s_dcb = nm_device_get_applied_setting(device, NM_TYPE_SETTING_DCB);
@@ -1571,7 +1584,7 @@ deactivate(NMDevice *device)
 
     /* Set last PPPoE connection time */
     if (nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE))
-        priv->last_pppoe_time = nm_utils_get_monotonic_timestamp_sec();
+        priv->ppp_data.last_pppoe_time_msec = nm_utils_get_monotonic_timestamp_msec();
 
     ifindex = nm_device_get_ifindex(device);
     if (ifindex > 0 && priv->ethtool_prev_set) {
@@ -1594,19 +1607,19 @@ deactivate(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)
 {
     NMSettingWired *s_wired;
     NMSettingPppoe *s_pppoe;
 
     if (nm_streq0(nm_connection_get_connection_type(connection), NM_SETTING_VETH_SETTING_NAME)) {
         NMSettingVeth *s_veth;
-        const char *   peer_name     = NULL;
-        const char *   con_peer_name = NULL;
+        const char    *peer_name     = NULL;
+        const char    *con_peer_name = NULL;
         int            ifindex;
 
         nm_utils_complete_generic(nm_device_get_platform(device),
@@ -1683,16 +1696,16 @@ complete_connection(NMDevice *           device,
 static NMConnection *
 new_default_connection(NMDevice *self)
 {
-    NMConnection *               connection;
-    NMSettingsConnection *const *connections;
-    NMSetting *                  setting;
+    NMConnection                  *connection;
+    NMSettingsConnection *const   *connections;
+    NMSetting                     *setting;
     gs_unref_hashtable GHashTable *existing_ids = NULL;
-    struct udev_device *           dev;
-    const char *                   perm_hw_addr;
-    const char *                   iface;
-    const char *                   uprop   = "0";
-    gs_free char *                 defname = NULL;
-    gs_free char *                 uuid    = NULL;
+    struct udev_device            *dev;
+    const char                    *perm_hw_addr;
+    const char                    *iface;
+    const char                    *uprop   = "0";
+    gs_free char                  *defname = NULL;
+    gs_free char                  *uuid    = NULL;
     guint                          i, n_connections;
 
     perm_hw_addr = nm_device_get_permanent_hw_address(self);
@@ -1778,12 +1791,12 @@ update_connection(NMDevice *device, NMConnection *connection)
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(device);
     NMSettingWired *s_wired = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRED);
     gboolean        perm_hw_addr_is_fake;
-    const char *    perm_hw_addr;
-    const char *    mac      = nm_device_get_hw_address(device);
-    const char *    mac_prop = NM_SETTING_WIRED_MAC_ADDRESS;
+    const char     *perm_hw_addr;
+    const char     *mac      = nm_device_get_hw_address(device);
+    const char     *mac_prop = NM_SETTING_WIRED_MAC_ADDRESS;
     GHashTableIter  iter;
-    const char *    key;
-    const char *    value;
+    const char     *key;
+    const char     *value;
 
     g_object_set(nm_connection_get_setting_connection(connection),
                  NM_SETTING_CONNECTION_TYPE,
@@ -1823,7 +1836,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 static void
 link_speed_update(NMDevice *device)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
     guint32                  speed;
 
@@ -1844,7 +1857,7 @@ link_speed_update(NMDevice *device)
 static void
 carrier_changed_notify(NMDevice *device, gboolean carrier)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(device);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(device);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
     if (priv->dcb_handle_carrier_changes) {
@@ -1880,12 +1893,12 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -1926,14 +1939,14 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
 static void
 dispose(GObject *object)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(object);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(object);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
     wired_secrets_cancel(self);
 
     supplicant_interface_release(self);
 
-    nm_clear_g_source(&priv->pppoe_wait_id);
+    nm_clear_g_source_inst(&priv->ppp_data.wait_source);
 
     nm_clear_g_source(&priv->dcb_timeout_id);
 
@@ -1945,7 +1958,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(object);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(object);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
     g_clear_object(&priv->supplicant.mgr);
@@ -1963,7 +1976,7 @@ finalize(GObject *object)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceEthernet *       self = NM_DEVICE_ETHERNET(object);
+    NMDeviceEthernet        *self = NM_DEVICE_ETHERNET(object);
     NMDeviceEthernetPrivate *priv = NM_DEVICE_ETHERNET_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -2007,9 +2020,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wired = {
 static void
 nm_device_ethernet_class_init(NMDeviceEthernetClass *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);
 
     g_type_class_add_private(object_class, sizeof(NMDeviceEthernetPrivate));
 
@@ -2032,7 +2045,7 @@ nm_device_ethernet_class_init(NMDeviceEthernetClass *klass)
     device_class->act_stage1_prepare                             = act_stage1_prepare;
     device_class->act_stage1_prepare_set_hwaddr_ethernet         = TRUE;
     device_class->act_stage2_config                              = act_stage2_config;
-    device_class->act_stage3_ip_config_start                     = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config                           = act_stage3_ip_config;
     device_class->get_configured_mtu                             = get_configured_mtu;
     device_class->deactivate                                     = deactivate;
     device_class->get_s390_subchannels                           = get_s390_subchannels;
@@ -2070,11 +2083,11 @@ nm_device_ethernet_class_init(NMDeviceEthernetClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_ETHERNET_DEVICE_FACTORY, NMEthernetDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_ETHERNET,
                         NM_DEVICE_IFACE,
@@ -2091,7 +2104,7 @@ create_device(NMDeviceFactory *     factory,
 static gboolean
 match_connection(NMDeviceFactory *factory, NMConnection *connection)
 {
-    const char *    type = nm_connection_get_connection_type(connection);
+    const char     *type = nm_connection_get_connection_type(connection);
     NMSettingPppoe *s_pppoe;
 
     if (nm_streq(type, NM_SETTING_WIRED_SETTING_NAME))
diff --git a/src/core/devices/nm-device-factory.c b/src/core/devices/nm-device-factory.c
index bdc64135..41e9480f 100644
--- a/src/core/devices/nm-device-factory.c
+++ b/src/core/devices/nm-device-factory.c
@@ -29,8 +29,8 @@ G_DEFINE_ABSTRACT_TYPE(NMDeviceFactory, nm_device_factory, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 static void
-nm_device_factory_get_supported_types(NMDeviceFactory *   factory,
-                                      const NMLinkType ** out_link_types,
+nm_device_factory_get_supported_types(NMDeviceFactory    *factory,
+                                      const NMLinkType  **out_link_types,
                                       const char *const **out_setting_types)
 {
     g_return_if_fail(NM_IS_DEVICE_FACTORY(factory));
@@ -50,15 +50,15 @@ nm_device_factory_start(NMDeviceFactory *factory)
 }
 
 NMDevice *
-nm_device_factory_create_device(NMDeviceFactory *     factory,
-                                const char *          iface,
+nm_device_factory_create_device(NMDeviceFactory      *factory,
+                                const char           *iface,
                                 const NMPlatformLink *plink,
-                                NMConnection *        connection,
-                                gboolean *            out_ignore,
-                                GError **             error)
+                                NMConnection         *connection,
+                                gboolean             *out_ignore,
+                                GError              **error)
 {
     NMDeviceFactoryClass *klass;
-    NMDevice *            device;
+    NMDevice             *device;
     gboolean              ignore = FALSE;
 
     g_return_val_if_fail(factory, NULL);
@@ -121,12 +121,12 @@ nm_device_factory_get_connection_parent(NMDeviceFactory *factory, NMConnection *
 
 char *
 nm_device_factory_get_connection_iface(NMDeviceFactory *factory,
-                                       NMConnection *   connection,
-                                       const char *     parent_iface,
-                                       GError **        error)
+                                       NMConnection    *connection,
+                                       const char      *parent_iface,
+                                       GError         **error)
 {
     NMDeviceFactoryClass *klass;
-    char *                ifname;
+    char                 *ifname;
 
     g_return_val_if_fail(factory != NULL, NULL);
     g_return_val_if_fail(connection != NULL, NULL);
@@ -196,9 +196,9 @@ NMDeviceFactory *
 nm_device_factory_manager_find_factory_for_connection(NMConnection *connection)
 {
     NMDeviceFactoryClass *klass;
-    NMDeviceFactory *     factory;
-    const char *          type;
-    GSList *              list;
+    NMDeviceFactory      *factory;
+    const char           *type;
+    GSList               *list;
 
     g_return_val_if_fail(factories_by_setting, NULL);
 
@@ -221,7 +221,7 @@ nm_device_factory_manager_for_each_factory(NMDeviceFactoryManagerFactoryFunc cal
 {
     GHashTableIter   iter;
     NMDeviceFactory *factory;
-    GSList *         list_iter, *list = NULL;
+    GSList          *list_iter, *list = NULL;
 
     if (factories_by_link) {
         g_hash_table_iter_init(&iter, factories_by_link);
@@ -248,14 +248,14 @@ nm_device_factory_manager_for_each_factory(NMDeviceFactoryManagerFactoryFunc cal
 }
 
 static gboolean
-_add_factory(NMDeviceFactory *                 factory,
-             const char *                      path,
+_add_factory(NMDeviceFactory                  *factory,
+             const char                       *path,
              NMDeviceFactoryManagerFactoryFunc callback,
              gpointer                          user_data)
 {
-    const NMLinkType * link_types    = NULL;
+    const NMLinkType  *link_types    = NULL;
     const char *const *setting_types = NULL;
-    GSList *           list, *list2;
+    GSList            *list, *list2;
     int                i;
 
     g_return_val_if_fail(factories_by_link, FALSE);
@@ -312,22 +312,22 @@ factories_list_unref(GSList *list)
 }
 
 static void
-load_factories_from_dir(const char *                      dirname,
+load_factories_from_dir(const char                       *dirname,
                         NMDeviceFactoryManagerFactoryFunc callback,
                         gpointer                          user_data)
 {
     NMDeviceFactory *factory;
-    GError *         error = NULL;
-    char **          path, **paths;
+    GError          *error = NULL;
+    char           **path, **paths;
 
     paths = nm_utils_read_plugin_paths(dirname, PLUGIN_PREFIX);
     if (!paths)
         return;
 
     for (path = paths; *path; path++) {
-        GModule *                 plugin;
+        GModule                  *plugin;
         NMDeviceFactoryCreateFunc create_func;
-        const char *              item;
+        const char               *item;
 
         item = strrchr(*path, '/');
         g_assert(item);
diff --git a/src/core/devices/nm-device-factory.h b/src/core/devices/nm-device-factory.h
index d9b50563..51d64767 100644
--- a/src/core/devices/nm-device-factory.h
+++ b/src/core/devices/nm-device-factory.h
@@ -45,8 +45,8 @@ typedef struct {
      * Returns the #NMLinkType and #NMSetting names that this plugin
      * supports.  This function MUST be implemented.
      */
-    void (*get_supported_types)(NMDeviceFactory *   factory,
-                                const NMLinkType ** out_link_types,
+    void (*get_supported_types)(NMDeviceFactory    *factory,
+                                const NMLinkType  **out_link_types,
                                 const char *const **out_setting_types);
 
     /**
@@ -91,8 +91,8 @@ typedef struct {
      * Returns: the interface name, or %NULL
      */
     char *(*get_connection_iface)(NMDeviceFactory *factory,
-                                  NMConnection *   connection,
-                                  const char *     parent_iface);
+                                  NMConnection    *connection,
+                                  const char      *parent_iface);
 
     /**
      * create_device:
@@ -112,11 +112,11 @@ typedef struct {
      *
      * Returns: the new unrealized #NMDevice, or %NULL
      */
-    NMDevice *(*create_device)(NMDeviceFactory *     factory,
-                               const char *          iface,
+    NMDevice *(*create_device)(NMDeviceFactory      *factory,
+                               const char           *iface,
                                const NMPlatformLink *plink,
-                               NMConnection *        connection,
-                               gboolean *            out_ignore);
+                               NMConnection         *connection,
+                               gboolean             *out_ignore);
 
 } NMDeviceFactoryClass;
 
@@ -143,21 +143,21 @@ typedef NMDeviceFactory *(*NMDeviceFactoryCreateFunc)(GError **error);
 /*****************************************************************************/
 
 const char *nm_device_factory_get_connection_parent(NMDeviceFactory *factory,
-                                                    NMConnection *   connection);
+                                                    NMConnection    *connection);
 
 char *nm_device_factory_get_connection_iface(NMDeviceFactory *factory,
-                                             NMConnection *   connection,
-                                             const char *     parent_iface,
-                                             GError **        error);
+                                             NMConnection    *connection,
+                                             const char      *parent_iface,
+                                             GError         **error);
 
 void nm_device_factory_start(NMDeviceFactory *factory);
 
-NMDevice *nm_device_factory_create_device(NMDeviceFactory *     factory,
-                                          const char *          iface,
+NMDevice *nm_device_factory_create_device(NMDeviceFactory      *factory,
+                                          const char           *iface,
                                           const NMPlatformLink *plink,
-                                          NMConnection *        connection,
-                                          gboolean *            out_ignore,
-                                          GError **             error);
+                                          NMConnection         *connection,
+                                          gboolean             *out_ignore,
+                                          GError              **error);
 
 #define NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(...)                                          \
     {                                                                                      \
@@ -171,14 +171,14 @@ NMDevice *nm_device_factory_create_device(NMDeviceFactory *     factory,
     }
 
 #define NM_DEVICE_FACTORY_DECLARE_TYPES(...)                                   \
-    static void get_supported_types(NMDeviceFactory *   factory,               \
-                                    const NMLinkType ** out_link_types,        \
+    static void get_supported_types(NMDeviceFactory    *factory,               \
+                                    const NMLinkType  **out_link_types,        \
                                     const char *const **out_setting_types)     \
     {                                                                          \
         static NMLinkType const  _link_types_null[1]    = {NM_LINK_TYPE_NONE}; \
         static const char *const _setting_types_null[1] = {NULL};              \
                                                                                \
-        const NMLinkType * _link_types    = _link_types_null;                  \
+        const NMLinkType  *_link_types    = _link_types_null;                  \
         const char *const *_setting_types = _setting_types_null;               \
                                                                                \
         {                                                                      \
diff --git a/src/core/devices/nm-device-generic.c b/src/core/devices/nm-device-generic.c
index 08ea5ac5..9f85925b 100644
--- a/src/core/devices/nm-device-generic.c
+++ b/src/core/devices/nm-device-generic.c
@@ -58,7 +58,7 @@ get_type_description(NMDevice *device)
 static void
 realize_start_notify(NMDevice *device, const NMPlatformLink *plink)
 {
-    NMDeviceGeneric *       self = NM_DEVICE_GENERIC(device);
+    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(device);
     NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
     int                     ifindex;
 
@@ -112,7 +112,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceGeneric *       self = NM_DEVICE_GENERIC(object);
+    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(object);
     NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -128,7 +128,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-    NMDeviceGeneric *       self = NM_DEVICE_GENERIC(object);
+    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(object);
     NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -180,7 +180,7 @@ nm_device_generic_new(const NMPlatformLink *plink, gboolean nm_plugin_missing)
 static void
 dispose(GObject *object)
 {
-    NMDeviceGeneric *       self = NM_DEVICE_GENERIC(object);
+    NMDeviceGeneric        *self = NM_DEVICE_GENERIC(object);
     NMDeviceGenericPrivate *priv = NM_DEVICE_GENERIC_GET_PRIVATE(self);
 
     nm_clear_g_free(&priv->type_description);
@@ -202,9 +202,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_generic = {
 static void
 nm_device_generic_class_init(NMDeviceGenericClass *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->constructor  = constructor;
     object_class->dispose      = dispose;
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index df14dfa8..aef61222 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -13,7 +13,6 @@
 #include "NetworkManagerUtils.h"
 #include "nm-device-private.h"
 #include "nm-act-request.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
@@ -64,7 +63,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     nm_auto_close int    dirfd = -1;
     NMSettingInfiniband *s_infiniband;
     char                 ifname_verified[IFNAMSIZ];
-    const char *         transport_mode;
+    const char          *transport_mode;
     gboolean             ok;
 
     s_infiniband = nm_device_get_applied_setting(device, NM_TYPE_SETTING_INFINIBAND);
@@ -139,11 +138,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     NMSettingInfiniband *s_infiniband;
 
@@ -197,12 +196,12 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -219,14 +218,14 @@ can_reapply_change(NMDevice *  device,
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     NMDeviceInfinibandPrivate *priv = NM_DEVICE_INFINIBAND_GET_PRIVATE(device);
-    NMSettingInfiniband *      s_infiniband;
+    NMSettingInfiniband       *s_infiniband;
     int                        r;
 
     s_infiniband = nm_connection_get_setting_infiniband(connection);
@@ -367,9 +366,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_infiniband = {
 static void
 nm_device_infiniband_class_init(NMDeviceInfinibandClass *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->get_property = get_property;
     object_class->set_property = set_property;
@@ -410,11 +409,11 @@ nm_device_infiniband_class_init(NMDeviceInfinibandClass *klass)
                                 NMInfinibandDeviceFactory))
 
 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)
 {
     gboolean is_partition = FALSE;
 
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index fe2e2023..02680dd7 100644
--- a/src/core/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -22,7 +22,6 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
-#include "nm-ip4-config.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceIPTunnel
 #include "nm-device-logging.h"
@@ -44,14 +43,14 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIPTunnel,
 
 typedef struct {
     NMIPTunnelMode  mode;
-    char *          local;
-    char *          remote;
+    char           *local;
+    char           *remote;
     guint8          ttl;
     guint8          tos;
     gboolean        path_mtu_discovery;
     int             addr_family;
-    char *          input_key;
-    char *          output_key;
+    char           *input_key;
+    char           *output_key;
     guint8          encap_limit;
     guint32         flow_label;
     NMIPTunnelFlags flags;
@@ -153,7 +152,7 @@ address_set(int addr_family, char **p_addr, const NMIPAddr *addr_new)
 static void
 update_properties_from_ifindex(NMDevice *device, int ifindex)
 {
-    NMDeviceIPTunnel *       self           = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnel        *self           = NM_DEVICE_IP_TUNNEL(device);
     NMDeviceIPTunnelPrivate *priv           = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
     int                      parent_ifindex = 0;
     NMIPAddr                 local          = NM_IP_ADDR_INIT;
@@ -164,7 +163,7 @@ update_properties_from_ifindex(NMDevice *device, int ifindex)
     gboolean                 pmtud          = FALSE;
     guint32                  flow_label     = 0;
     NMIPTunnelFlags          flags          = NM_IP_TUNNEL_FLAG_NONE;
-    char *                   key;
+    char                    *key;
 
     if (ifindex <= 0) {
 clear:
@@ -273,7 +272,7 @@ clear:
                          NM_IP_TUNNEL_MODE_IP6GRE,
                          NM_IP_TUNNEL_MODE_IP6GRETAP)) {
         const NMPlatformLnkIp6Tnl *lnk;
-        NMPlatform *               plat = nm_device_get_platform(device);
+        NMPlatform                *plat = nm_device_get_platform(device);
 
         if (priv->mode == NM_IP_TUNNEL_MODE_IP6GRE)
             lnk = nm_platform_link_get_lnk_ip6gre(plat, ifindex, NULL);
@@ -384,11 +383,11 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 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)
 {
     NMSettingIPTunnel *s_ip_tunnel;
 
@@ -417,9 +416,9 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnel        *self = NM_DEVICE_IP_TUNNEL(device);
     NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
-    NMSettingIPTunnel *      s_ip_tunnel =
+    NMSettingIPTunnel       *s_ip_tunnel =
         _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_IP_TUNNEL);
 
     if (nm_setting_ip_tunnel_get_mode(s_ip_tunnel) != priv->mode)
@@ -491,10 +490,10 @@ update_connection(NMDevice *device, NMConnection *connection)
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
-    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnel        *self = NM_DEVICE_IP_TUNNEL(device);
     NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
-    NMSettingIPTunnel *      s_ip_tunnel;
-    const char *             parent;
+    NMSettingIPTunnel       *s_ip_tunnel;
+    const char              *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_ip_tunnel_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -643,23 +642,23 @@ tunnel_mode_to_link_type(NMIPTunnelMode tunnel_mode)
 /*****************************************************************************/
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *        iface = nm_device_get_iface(device);
-    NMSettingIPTunnel * s_ip_tunnel;
+    const char         *iface = nm_device_get_iface(device);
+    NMSettingIPTunnel  *s_ip_tunnel;
     NMPlatformLnkGre    lnk_gre    = {};
     NMPlatformLnkSit    lnk_sit    = {};
     NMPlatformLnkIpIp   lnk_ipip   = {};
     NMPlatformLnkIp6Tnl lnk_ip6tnl = {};
-    const char *        str;
+    const char         *str;
     gint64              val;
     NMIPTunnelMode      mode;
     int                 r;
-    gs_free char *      hwaddr = NULL;
+    gs_free char       *hwaddr = NULL;
     guint8              mac_address[ETH_ALEN];
     gboolean            mac_address_valid = FALSE;
 
@@ -909,12 +908,12 @@ unrealize_notify(NMDevice *device)
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -934,7 +933,7 @@ can_reapply_change(NMDevice *  device,
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnel        *self = NM_DEVICE_IP_TUNNEL(device);
     NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
 
     if (_nm_ip_tunnel_mode_is_layer2(priv->mode)
@@ -1033,7 +1032,7 @@ constructed(GObject *object)
 static void
 dispose(GObject *object)
 {
-    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(object);
+    NMDeviceIPTunnel        *self = NM_DEVICE_IP_TUNNEL(object);
     NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
 
     nm_clear_g_free(&priv->local);
@@ -1079,9 +1078,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ip_tunnel = {
 static void
 nm_device_ip_tunnel_class_init(NMDeviceIPTunnelClass *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->constructed  = constructed;
     object_class->dispose      = dispose;
@@ -1203,11 +1202,11 @@ nm_device_ip_tunnel_class_init(NMDeviceIPTunnelClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IP_TUNNEL_DEVICE_FACTORY, NMIPTunnelDeviceFactory))
 
 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)
 {
     NMSettingIPTunnel *s_ip_tunnel;
     NMIPTunnelMode     mode;
@@ -1256,7 +1255,7 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
-    const char *       ifname;
+    const char        *ifname;
     NMSettingIPTunnel *s_ip_tunnel;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_IP_TUNNEL_SETTING_NAME),
diff --git a/src/core/devices/nm-device-macsec.c b/src/core/devices/nm-device-macsec.c
index 51b7225b..5cc0b8da 100644
--- a/src/core/devices/nm-device-macsec.c
+++ b/src/core/devices/nm-device-macsec.c
@@ -49,9 +49,9 @@ typedef struct {
     gulong              parent_mtu_id;
 
     struct {
-        NMSupplicantManager *        mgr;
+        NMSupplicantManager         *mgr;
         NMSupplMgrCreateIfaceHandle *create_handle;
-        NMSupplicantInterface *      iface;
+        NMSupplicantInterface       *iface;
 
         gulong iface_state_id;
 
@@ -92,7 +92,7 @@ static NM_UTILS_LOOKUP_STR_DEFINE(validation_mode_to_string,
                                   NM_UTILS_LOOKUP_STR_ITEM(2, "strict"), );
 
 static void
-parent_state_changed(NMDevice *          parent,
+parent_state_changed(NMDevice           *parent,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason,
@@ -126,7 +126,7 @@ parent_changed_notify(NMDevice *device,
                       int       new_ifindex,
                       NMDevice *new_parent)
 {
-    NMDeviceMacsec *       self = NM_DEVICE_MACSEC(device);
+    NMDeviceMacsec        *self = NM_DEVICE_MACSEC(device);
     NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self);
 
     NM_DEVICE_CLASS(nm_device_macsec_parent_class)
@@ -166,9 +166,9 @@ parent_changed_notify(NMDevice *device,
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceMacsec *           self;
-    NMDeviceMacsecPrivate *    priv;
-    const NMPlatformLink *     plink = NULL;
+    NMDeviceMacsec            *self;
+    NMDeviceMacsecPrivate     *priv;
+    const NMPlatformLink      *plink = NULL;
     const NMPlatformLnkMacsec *props = NULL;
     int                        ifindex;
 
@@ -220,10 +220,10 @@ static NMSupplicantConfig *
 build_supplicant_config(NMDeviceMacsec *self, GError **error)
 {
     gs_unref_object NMSupplicantConfig *config = NULL;
-    NMSettingMacsec *                   s_macsec;
-    NMSetting8021x *                    s_8021x;
-    NMConnection *                      connection;
-    const char *                        con_uuid;
+    NMSettingMacsec                    *s_macsec;
+    NMSetting8021x                     *s_8021x;
+    NMConnection                       *connection;
+    const char                         *con_uuid;
     guint32                             mtu;
 
     connection = nm_device_get_applied_connection(NM_DEVICE(self));
@@ -275,14 +275,14 @@ supplicant_interface_release(NMDeviceMacsec *self)
 }
 
 static void
-macsec_secrets_cb(NMActRequest *                req,
+macsec_secrets_cb(NMActRequest                 *req,
                   NMActRequestGetSecretsCallId *call_id,
-                  NMSettingsConnection *        connection,
-                  GError *                      error,
+                  NMSettingsConnection         *connection,
+                  GError                       *error,
                   gpointer                      user_data)
 {
-    NMDeviceMacsec *       self   = NM_DEVICE_MACSEC(user_data);
-    NMDevice *             device = NM_DEVICE(self);
+    NMDeviceMacsec        *self   = NM_DEVICE_MACSEC(user_data);
+    NMDevice              *device = NM_DEVICE(self);
     NMDeviceMacsecPrivate *priv;
 
     g_return_if_fail(NM_IS_DEVICE_MACSEC(self));
@@ -321,12 +321,12 @@ macsec_secrets_cancel(NMDeviceMacsec *self)
 }
 
 static void
-macsec_secrets_get_secrets(NMDeviceMacsec *             self,
-                           const char *                 setting_name,
+macsec_secrets_get_secrets(NMDeviceMacsec              *self,
+                           const char                  *setting_name,
                            NMSecretAgentGetSecretsFlags flags)
 {
     NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self);
-    NMActRequest *         req;
+    NMActRequest          *req;
 
     macsec_secrets_cancel(self);
 
@@ -341,12 +341,12 @@ macsec_secrets_get_secrets(NMDeviceMacsec *             self,
 static gboolean
 supplicant_lnk_timeout_cb(gpointer user_data)
 {
-    NMDeviceMacsec *       self = NM_DEVICE_MACSEC(user_data);
+    NMDeviceMacsec        *self = NM_DEVICE_MACSEC(user_data);
     NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self);
-    NMDevice *             dev  = NM_DEVICE(self);
-    NMActRequest *         req;
-    NMConnection *         applied_connection;
-    const char *           setting_name;
+    NMDevice              *dev  = NM_DEVICE(self);
+    NMActRequest          *req;
+    NMConnection          *applied_connection;
+    const char            *setting_name;
 
     priv->supplicant.lnk_timeout_id = 0;
 
@@ -409,7 +409,7 @@ supplicant_iface_state_is_completed(NMDeviceMacsec *self, NMSupplicantInterfaceS
          */
         if (nm_device_get_state(NM_DEVICE(self)) == NM_DEVICE_STATE_CONFIG) {
             _LOGI(LOGD_DEVICE, "Activation: Stage 2 of 5 (Device Configure) successful.");
-            nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self));
+            nm_device_activate_schedule_stage3_ip_config(NM_DEVICE(self), FALSE);
         }
         return;
     }
@@ -422,7 +422,7 @@ supplicant_iface_state_is_completed(NMDeviceMacsec *self, NMSupplicantInterfaceS
 static void
 supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data)
 {
-    NMDeviceMacsec *       self;
+    NMDeviceMacsec        *self;
     NMDeviceMacsecPrivate *priv;
 
     if (nm_utils_error_is_cancelled_or_disposing(error))
@@ -450,9 +450,9 @@ supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer
 static gboolean
 supplicant_iface_start(NMDeviceMacsec *self)
 {
-    NMDeviceMacsecPrivate *priv                = NM_DEVICE_MACSEC_GET_PRIVATE(self);
+    NMDeviceMacsecPrivate              *priv   = NM_DEVICE_MACSEC_GET_PRIVATE(self);
     gs_unref_object NMSupplicantConfig *config = NULL;
-    gs_free_error GError *error                = NULL;
+    gs_free_error GError               *error  = NULL;
 
     config = build_supplicant_config(self, &error);
     if (!config) {
@@ -476,8 +476,8 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
                           int                    disconnect_reason,
                           gpointer               user_data)
 {
-    NMDeviceMacsec *           self      = NM_DEVICE_MACSEC(user_data);
-    NMDeviceMacsecPrivate *    priv      = NM_DEVICE_MACSEC_GET_PRIVATE(self);
+    NMDeviceMacsec            *self      = NM_DEVICE_MACSEC(user_data);
+    NMDeviceMacsecPrivate     *priv      = NM_DEVICE_MACSEC_GET_PRIVATE(self);
     NMSupplicantInterfaceState new_state = new_state_i;
     NMSupplicantInterfaceState old_state = old_state_i;
 
@@ -506,7 +506,7 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
 static gboolean
 handle_auth_or_fail(NMDeviceMacsec *self, NMActRequest *req, gboolean new_secrets)
 {
-    const char *  setting_name;
+    const char   *setting_name;
     NMConnection *applied_connection;
 
     if (!nm_device_auth_retries_try_next(NM_DEVICE(self)))
@@ -536,11 +536,11 @@ handle_auth_or_fail(NMDeviceMacsec *self, NMActRequest *req, gboolean new_secret
 static gboolean
 supplicant_connection_timeout_cb(gpointer user_data)
 {
-    NMDeviceMacsec *       self   = NM_DEVICE_MACSEC(user_data);
+    NMDeviceMacsec        *self   = NM_DEVICE_MACSEC(user_data);
     NMDeviceMacsecPrivate *priv   = NM_DEVICE_MACSEC_GET_PRIVATE(self);
-    NMDevice *             device = NM_DEVICE(self);
-    NMActRequest *         req;
-    NMSettingsConnection * connection;
+    NMDevice              *device = NM_DEVICE(self);
+    NMActRequest          *req;
+    NMSettingsConnection  *connection;
     guint64                timestamp   = 0;
     gboolean               new_secrets = TRUE;
 
@@ -585,13 +585,13 @@ supplicant_connection_timeout_cb(gpointer user_data)
 }
 
 static void
-supplicant_interface_create_cb(NMSupplicantManager *        supplicant_manager,
+supplicant_interface_create_cb(NMSupplicantManager         *supplicant_manager,
                                NMSupplMgrCreateIfaceHandle *handle,
-                               NMSupplicantInterface *      iface,
-                               GError *                     error,
+                               NMSupplicantInterface       *iface,
+                               GError                      *error,
                                gpointer                     user_data)
 {
-    NMDeviceMacsec *       self;
+    NMDeviceMacsec        *self;
     NMDeviceMacsecPrivate *priv;
     guint                  timeout;
 
@@ -633,11 +633,11 @@ supplicant_interface_create_cb(NMSupplicantManager *        supplicant_manager,
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceMacsec *       self = NM_DEVICE_MACSEC(device);
+    NMDeviceMacsec        *self = NM_DEVICE_MACSEC(device);
     NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self);
-    NMConnection *         connection;
-    NMDevice *             parent;
-    const char *           setting_name;
+    NMConnection          *connection;
+    NMDevice              *parent;
+    const char            *setting_name;
     int                    ifindex;
 
     connection = nm_device_get_applied_connection(NM_DEVICE(self));
@@ -712,17 +712,17 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *        iface = nm_device_get_iface(device);
-    NMSettingMacsec *   s_macsec;
+    const char         *iface = nm_device_get_iface(device);
+    NMSettingMacsec    *s_macsec;
     NMPlatformLnkMacsec lnk = {};
     int                 parent_ifindex;
-    const char *        hw_addr;
+    const char         *hw_addr;
     union {
         struct {
             guint8  mac[6];
@@ -789,7 +789,7 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
@@ -803,7 +803,7 @@ device_state_changed(NMDevice *          device,
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceMacsec *       self = NM_DEVICE_MACSEC(object);
+    NMDeviceMacsec        *self = NM_DEVICE_MACSEC(object);
     NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -905,9 +905,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
 static void
 nm_device_macsec_class_init(NMDeviceMacsecClass *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->get_property = get_property;
     object_class->dispose      = dispose;
@@ -1015,11 +1015,11 @@ nm_device_macsec_class_init(NMDeviceMacsecClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MACSEC_DEVICE_FACTORY, NMMacsecDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_MACSEC,
                         NM_DEVICE_IFACE,
@@ -1037,8 +1037,8 @@ static const char *
 get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 {
     NMSettingMacsec *s_macsec;
-    NMSettingWired * s_wired;
-    const char *     parent = NULL;
+    NMSettingWired  *s_wired;
+    const char      *parent = NULL;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
 
@@ -1061,7 +1061,7 @@ static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
     NMSettingMacsec *s_macsec;
-    const char *     ifname;
+    const char      *ifname;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
 
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index 937ba9e4..40ecc5fd 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -19,7 +19,6 @@
 #include "nm-setting-macvlan.h"
 #include "nm-setting-wired.h"
 #include "nm-active-connection.h"
-#include "nm-ip4-config.h"
 #include "nm-utils.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceMacvlan
@@ -104,7 +103,7 @@ platform_mode_to_string(guint mode)
 /*****************************************************************************/
 
 static void
-parent_state_changed(NMDevice *          parent,
+parent_state_changed(NMDevice           *parent,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason,
@@ -138,7 +137,7 @@ parent_changed_notify(NMDevice *device,
                       int       new_ifindex,
                       NMDevice *new_parent)
 {
-    NMDeviceMacvlan *       self = NM_DEVICE_MACVLAN(device);
+    NMDeviceMacvlan        *self = NM_DEVICE_MACVLAN(device);
     NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(self);
 
     NM_DEVICE_CLASS(nm_device_macvlan_parent_class)
@@ -178,11 +177,11 @@ parent_changed_notify(NMDevice *device,
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceMacvlan *           self   = NM_DEVICE_MACVLAN(device);
-    NMDeviceMacvlanPrivate *    priv   = NM_DEVICE_MACVLAN_GET_PRIVATE(self);
-    GObject *                   object = G_OBJECT(device);
+    NMDeviceMacvlan            *self   = NM_DEVICE_MACVLAN(device);
+    NMDeviceMacvlanPrivate     *priv   = NM_DEVICE_MACVLAN_GET_PRIVATE(self);
+    GObject                    *object = G_OBJECT(device);
     const NMPlatformLnkMacvlan *props;
-    const NMPlatformLink *      plink;
+    const NMPlatformLink       *plink;
 
     if (priv->props.tap)
         props = nm_platform_link_get_lnk_macvtap(nm_device_get_platform(device),
@@ -228,14 +227,14 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *         iface = nm_device_get_iface(device);
-    NMSettingMacvlan *   s_macvlan;
+    const char          *iface = nm_device_get_iface(device);
+    NMSettingMacvlan    *s_macvlan;
     NMPlatformLnkMacvlan lnk = {};
     int                  parent_ifindex;
     int                  r;
@@ -312,8 +311,8 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device);
-    NMSettingMacvlan *      s_macvlan;
-    const char *            parent = NULL;
+    NMSettingMacvlan       *s_macvlan;
+    const char             *parent = NULL;
 
     if (!NM_DEVICE_CLASS(nm_device_macvlan_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -374,11 +373,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     NMSettingMacvlan *s_macvlan;
 
@@ -421,7 +420,7 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device);
-    NMSettingMacvlan *      s_macvlan =
+    NMSettingMacvlan       *s_macvlan =
         _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_MACVLAN);
     int new_mode;
 
@@ -511,9 +510,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
 static void
 nm_device_macvlan_class_init(NMDeviceMacvlanClass *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);
 
 #if NM_MORE_ASSERTS
     object_class->dispose = dispose;
@@ -570,11 +569,11 @@ nm_device_macvlan_class_init(NMDeviceMacvlanClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_MACVLAN_DEVICE_FACTORY, NMMacvlanDeviceFactory))
 
 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)
 {
     NMSettingMacvlan *s_macvlan;
     NMLinkType        link_type;
@@ -609,8 +608,8 @@ static const char *
 get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 {
     NMSettingMacvlan *s_macvlan;
-    NMSettingWired *  s_wired;
-    const char *      parent = NULL;
+    NMSettingWired   *s_wired;
+    const char       *parent = NULL;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL);
 
@@ -633,7 +632,7 @@ static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
     NMSettingMacvlan *s_macvlan;
-    const char *      ifname;
+    const char       *ifname;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_MACVLAN_SETTING_NAME), NULL);
 
diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 04051bec..5ff32401 100644
--- a/src/core/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -7,16 +7,14 @@
 
 #include "nm-device-ppp.h"
 
-#include "nm-ip4-config.h"
+#include "nm-l3-config-data.h"
 #include "nm-act-request.h"
 #include "nm-device-factory.h"
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-setting-pppoe.h"
 #include "libnm-platform/nm-platform.h"
-#include "ppp/nm-ppp-manager.h"
-#include "ppp/nm-ppp-manager-call.h"
-#include "ppp/nm-ppp-status.h"
+#include "ppp/nm-ppp-mgr.h"
 
 #define _NMLOG_DEVICE_TYPE NMDevicePpp
 #include "nm-device-logging.h"
@@ -24,8 +22,7 @@
 /*****************************************************************************/
 
 typedef struct _NMDevicePppPrivate {
-    NMPPPManager *ppp_manager;
-    NMIP4Config * ip4_config;
+    NMPppMgr *ppp_mgr;
 } NMDevicePppPrivate;
 
 struct _NMDevicePpp {
@@ -42,86 +39,92 @@ G_DEFINE_TYPE(NMDevicePpp, nm_device_ppp, NM_TYPE_DEVICE)
 #define NM_DEVICE_PPP_GET_PRIVATE(self) \
     _NM_GET_PRIVATE(self, NMDevicePpp, NM_IS_DEVICE_PPP, NMDevice)
 
+/*****************************************************************************/
+
 static NMDeviceCapabilities
 get_generic_capabilities(NMDevice *device)
 {
     return NM_DEVICE_CAP_IS_SOFTWARE;
 }
 
+/*****************************************************************************/
+
 static void
-ppp_state_changed(NMPPPManager *ppp_manager, NMPPPStatus status, gpointer user_data)
+_ppp_mgr_cleanup(NMDevicePpp *self)
 {
-    NMDevice *device = NM_DEVICE(user_data);
-
-    switch (status) {
-    case NM_PPP_STATUS_DISCONNECT:
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_PPP_DISCONNECT);
-        break;
-    case NM_PPP_STATUS_DEAD:
-        nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_PPP_FAILED);
-        break;
-    default:
-        break;
-    }
+    NMDevicePppPrivate *priv = NM_DEVICE_PPP_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_mgr_stage3_maybe_ready(NMDevicePpp *self)
 {
-    NMDevice *    device        = NM_DEVICE(user_data);
-    NMDevicePpp * self          = NM_DEVICE_PPP(device);
-    gs_free char *old_name      = NULL;
-    gs_free_error GError *error = NULL;
-
-    if (!nm_device_take_over_link(device, ifindex, &old_name, &error)) {
-        _LOGW(LOGD_DEVICE | LOGD_PPP,
-              "could not take control of link %d: %s",
-              ifindex,
-              error->message);
-        nm_device_state_changed(device,
-                                NM_DEVICE_STATE_FAILED,
-                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        return;
-    }
+    NMDevice           *device = NM_DEVICE(self);
+    NMDevicePppPrivate *priv   = NM_DEVICE_PPP_GET_PRIVATE(self);
+    int                 IS_IPv4;
+
+    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+        const int             addr_family = IS_IPv4 ? AF_INET : AF_INET6;
+        const NMPppMgrIPData *ip_data;
 
-    if (old_name)
-        nm_manager_remove_device(NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_PPP);
+        ip_data = nm_ppp_mgr_get_ip_data(priv->ppp_mgr, addr_family);
+        if (ip_data->ip_received)
+            nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, ip_data->l3cd);
+    }
 
-    nm_device_activate_schedule_stage3_ip_config_start(device);
+    if (nm_ppp_mgr_get_state(priv->ppp_mgr) >= NM_PPP_MGR_STATE_HAVE_IP_CONFIG)
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
 static void
-_ppp_ip4_config_handle(NMDevicePpp *self)
+_ppp_mgr_callback(NMPppMgr *ppp_mgr, const NMPppMgrCallbackData *callback_data, gpointer user_data)
 {
-    NMDevice *          device = NM_DEVICE(self);
-    NMDevicePppPrivate *priv   = NM_DEVICE_PPP_GET_PRIVATE(self);
+    NMDevicePpp  *self   = NM_DEVICE_PPP(user_data);
+    NMDevice     *device = NM_DEVICE(self);
+    NMDeviceState device_state;
 
-    if (!priv->ip4_config)
+    if (callback_data->callback_type != NM_PPP_MGR_CALLBACK_TYPE_STATE_CHANGED)
         return;
 
-    if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG
-        && nm_device_activate_ip4_state_in_conf(device)) {
-        nm_device_activate_schedule_ip_config_result(
-            device,
-            AF_INET,
-            NM_IP_CONFIG_CAST(g_steal_pointer(&priv->ip4_config)));
+    device_state = nm_device_get_state(device);
+
+    if (callback_data->data.state >= _NM_PPP_MGR_STATE_FAILED_START) {
+        if (device_state <= NM_DEVICE_STATE_ACTIVATED)
+            nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, callback_data->data.reason);
         return;
     }
-}
 
-static void
-ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
-{
-    NMDevicePpp *       self = NM_DEVICE_PPP(user_data);
-    NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
+    if (device_state < NM_DEVICE_STATE_IP_CONFIG) {
+        if (callback_data->data.state >= NM_PPP_MGR_STATE_HAVE_IFINDEX) {
+            gs_free char         *old_name = NULL;
+            gs_free_error GError *error    = NULL;
+
+            if (!nm_device_take_over_link(device, callback_data->data.ifindex, &old_name, &error)) {
+                _LOGW(LOGD_DEVICE | LOGD_PPP,
+                      "could not take control of link %d: %s",
+                      callback_data->data.ifindex,
+                      error->message);
+                _ppp_mgr_cleanup(self);
+                nm_device_state_changed(device,
+                                        NM_DEVICE_STATE_FAILED,
+                                        NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+                return;
+            }
+
+            if (old_name)
+                nm_manager_remove_device(NM_MANAGER_GET, old_name, NM_DEVICE_TYPE_PPP);
+
+            nm_device_activate_schedule_stage2_device_config(device, FALSE);
+        }
+        return;
+    }
 
-    _LOGT(LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
-    nm_g_object_ref_set(&priv->ip4_config, config);
-    _ppp_ip4_config_handle(self);
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
+/*****************************************************************************/
+
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
@@ -145,97 +148,92 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDevicePpp *       self = NM_DEVICE_PPP(device);
+    NMDevicePpp        *self = NM_DEVICE_PPP(device);
     NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
-    NMSettingPppoe *    s_pppoe;
-    NMActRequest *      req;
-    GError *            error = NULL;
+    NMSettingPppoe     *s_pppoe;
+    NMActRequest       *req;
+
+    if (!priv->ppp_mgr) {
+        gs_free_error GError *error = NULL;
+
+        req = nm_device_get_act_request(device);
+        g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+
+        s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE);
+        g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+
+        priv->ppp_mgr = nm_ppp_mgr_start(&((const NMPppMgrConfig){
+                                             .netns        = nm_device_get_netns(device),
+                                             .parent_iface = nm_setting_pppoe_get_parent(s_pppoe),
+                                             .callback     = _ppp_mgr_callback,
+                                             .user_data    = self,
+                                             .act_req      = req,
+                                             .ppp_username = nm_setting_pppoe_get_username(s_pppoe),
+                                             .timeout_secs = 30,
+                                             .baud_override = 0,
+                                         }),
+                                         &error);
+        if (!priv->ppp_mgr) {
+            _LOGW(LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
+            *out_failure_reason = NM_DEVICE_STATE_REASON_PPP_START_FAILED;
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
 
-    req = nm_device_get_act_request(device);
-    g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    }
 
-    s_pppoe = nm_device_get_applied_setting(device, NM_TYPE_SETTING_PPPOE);
-    g_return_val_if_fail(s_pppoe, NM_ACT_STAGE_RETURN_FAILURE);
+    if (nm_ppp_mgr_get_state(priv->ppp_mgr) < NM_PPP_MGR_STATE_HAVE_IFINDEX)
+        return NM_ACT_STAGE_RETURN_POSTPONE;
 
-    g_clear_object(&priv->ip4_config);
+    return NM_ACT_STAGE_RETURN_SUCCESS;
+}
 
-    priv->ppp_manager = nm_ppp_manager_create(nm_setting_pppoe_get_parent(s_pppoe), &error);
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
+{
+    NMDevicePpp        *self = NM_DEVICE_PPP(device);
+    NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
+    NMPppMgrState       ppp_state;
 
-    if (priv->ppp_manager) {
-        nm_ppp_manager_set_route_parameters(priv->ppp_manager,
-                                            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));
+    if (!priv->ppp_mgr) {
+        nm_assert_not_reached();
+        return;
     }
 
-    if (!priv->ppp_manager
-        || !nm_ppp_manager_start(priv->ppp_manager,
-                                 req,
-                                 nm_setting_pppoe_get_username(s_pppoe),
-                                 30,
-                                 0,
-                                 &error)) {
-        _LOGW(LOGD_DEVICE | LOGD_PPP, "PPPoE failed to start: %s", error->message);
-        g_error_free(error);
+    ppp_state = nm_ppp_mgr_get_state(priv->ppp_mgr);
 
-        g_clear_object(&priv->ppp_manager);
+    nm_assert(NM_IN_SET(ppp_state, NM_PPP_MGR_STATE_HAVE_IFINDEX, NM_PPP_MGR_STATE_HAVE_IP_CONFIG));
 
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PPP_START_FAILED);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (ppp_state < NM_PPP_MGR_STATE_HAVE_IP_CONFIG) {
+        nm_device_devip_set_state(device, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING, NULL);
+        return;
     }
 
-    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);
-    return NM_ACT_STAGE_RETURN_POSTPONE;
-}
-
-static gboolean
-_schedule_ip_config_result(gpointer user_data)
-{
-    gs_unref_object NMDevicePpp *self = user_data;
-
-    _ppp_ip4_config_handle(self);
-    return G_SOURCE_REMOVE;
+    _ppp_mgr_stage3_maybe_ready(self);
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static const char *
+get_ip_method_auto(NMDevice *device, int addr_family)
 {
-    if (addr_family == AF_INET) {
-        NMDevicePpp *       self = NM_DEVICE_PPP(device);
-        NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
-
-        if (priv->ip4_config)
-            nm_g_idle_add(_schedule_ip_config_result, g_object_ref(self));
-
-        /* Wait IPCP termination */
-        return NM_ACT_STAGE_RETURN_POSTPONE;
+    if (NM_IS_IPv4(addr_family)) {
+        /* We cannot do DHCPv4 on a PPP link, instead we get "auto" IP addresses
+         * by pppd. Return "manual" here, which has the suitable effect to a
+         * (zero) manual addresses in addition. */
+        return NM_SETTING_IP6_CONFIG_METHOD_MANUAL;
     }
 
-    return NM_DEVICE_CLASS(nm_device_ppp_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    /* We can do autoconf6 on an PPP link, but we should already get an IPv6
+     * address from pppd. Use that instead. We however do want to generate our
+     * (own) IPv6 link local address, so return "link-local". */
+    return NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL;
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     int parent_ifindex;
 
@@ -260,17 +258,13 @@ create_and_realize(NMDevice *             device,
 static void
 deactivate(NMDevice *device)
 {
-    NMDevicePpp *       self = NM_DEVICE_PPP(device);
-    NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
+    NMDevicePpp *self = NM_DEVICE_PPP(device);
 
-    if (priv->ppp_manager) {
-        nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
-        g_clear_object(&priv->ppp_manager);
-    }
-
-    g_clear_object(&priv->ip4_config);
+    _ppp_mgr_cleanup(self);
 }
 
+/*****************************************************************************/
+
 static void
 nm_device_ppp_init(NMDevicePpp *self)
 {}
@@ -278,10 +272,9 @@ nm_device_ppp_init(NMDevicePpp *self)
 static void
 dispose(GObject *object)
 {
-    NMDevicePpp *       self = NM_DEVICE_PPP(object);
-    NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
+    NMDevicePpp *self = NM_DEVICE_PPP(object);
 
-    g_clear_object(&priv->ip4_config);
+    _ppp_mgr_cleanup(self);
 
     G_OBJECT_CLASS(nm_device_ppp_parent_class)->dispose(object);
 }
@@ -293,9 +286,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ppp = {
 static void
 nm_device_ppp_class_init(NMDevicePppClass *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;
 
@@ -306,7 +299,8 @@ nm_device_ppp_class_init(NMDevicePppClass *klass)
     device_class->link_types                       = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_PPP);
 
     device_class->act_stage2_config           = act_stage2_config;
-    device_class->act_stage3_ip_config_start  = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config        = act_stage3_ip_config;
+    device_class->get_ip_method_auto          = get_ip_method_auto;
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->create_and_realize          = create_and_realize;
     device_class->deactivate                  = deactivate;
@@ -320,11 +314,11 @@ nm_device_ppp_class_init(NMDevicePppClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_PPP_DEVICE_FACTORY, NMPppDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_PPP,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index b55e8b43..790bb823 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -8,6 +8,7 @@
 #define __NETWORKMANAGER_DEVICE_PRIVATE_H__
 
 #include "nm-device.h"
+#include "nm-l3-config-data.h"
 
 /* This file should only be used by subclasses of NMDevice */
 
@@ -21,12 +22,6 @@ enum NMActStageReturn {
     NM_ACT_STAGE_RETURN_FAILURE = 0, /* Hard failure of activation */
     NM_ACT_STAGE_RETURN_SUCCESS,     /* Activation stage done */
     NM_ACT_STAGE_RETURN_POSTPONE,    /* Long-running operation in progress */
-    NM_ACT_STAGE_RETURN_IP_WAIT,     /* IP config stage is waiting (state IP_WAIT) */
-    NM_ACT_STAGE_RETURN_IP_DONE,     /* IP config stage is done (state IP_DONE),
-                                      * For the ip-config stage, this is similar to
-                                      * NM_ACT_STAGE_RETURN_SUCCESS, except that no
-                                      * IP config should be committed. */
-    NM_ACT_STAGE_RETURN_IP_FAIL, /* IP config stage failed (state IP_FAIL), activation may proceed */
 };
 
 #define NM_DEVICE_CAP_NONSTANDARD_CARRIER 0x80000000
@@ -34,8 +29,6 @@ enum NMActStageReturn {
 
 #define NM_DEVICE_CAP_INTERNAL_MASK 0xc0000000
 
-void nm_device_arp_announce(NMDevice *self);
-
 NMSettings *nm_device_get_settings(NMDevice *self);
 
 NMManager *nm_device_get_manager(NMDevice *self);
@@ -44,17 +37,13 @@ gboolean nm_device_set_ip_ifindex(NMDevice *self, int ifindex);
 
 gboolean nm_device_set_ip_iface(NMDevice *self, const char *iface);
 
-void nm_device_activate_schedule_stage3_ip_config_start(NMDevice *device);
-
-gboolean nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family);
-
 gboolean nm_device_bring_up(NMDevice *self, gboolean wait, gboolean *no_firmware);
 
 void nm_device_take_down(NMDevice *self, gboolean block);
 
 gboolean nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **error);
 
-gboolean nm_device_hw_addr_set(NMDevice *  device,
+gboolean nm_device_hw_addr_set(NMDevice   *device,
                                const char *addr,
                                const char *detail,
                                gboolean    set_permanent);
@@ -65,52 +54,7 @@ void nm_device_set_firmware_missing(NMDevice *self, gboolean missing);
 
 void nm_device_activate_schedule_stage1_device_prepare(NMDevice *device, gboolean do_sync);
 void nm_device_activate_schedule_stage2_device_config(NMDevice *device, gboolean do_sync);
-
-void
-nm_device_activate_schedule_ip_config_result(NMDevice *device, int addr_family, NMIPConfig *config);
-
-void nm_device_activate_schedule_ip_config_timeout(NMDevice *device, int addr_family);
-
-NMDeviceIPState nm_device_activate_get_ip_state(NMDevice *self, int addr_family);
-
-static inline gboolean
-nm_device_activate_ip4_state_in_conf(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_CONF;
-}
-
-static inline gboolean
-nm_device_activate_ip4_state_in_wait(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_WAIT;
-}
-
-static inline gboolean
-nm_device_activate_ip4_state_done(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET) == NM_DEVICE_IP_STATE_DONE;
-}
-
-static inline gboolean
-nm_device_activate_ip6_state_in_conf(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_CONF;
-}
-
-static inline gboolean
-nm_device_activate_ip6_state_in_wait(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_WAIT;
-}
-
-static inline gboolean
-nm_device_activate_ip6_state_done(NMDevice *self)
-{
-    return nm_device_activate_get_ip_state(self, AF_INET6) == NM_DEVICE_IP_STATE_DONE;
-}
-
-gboolean nm_device_dhcp4_renew(NMDevice *device, gboolean release);
-gboolean nm_device_dhcp6_renew(NMDevice *device, gboolean release);
+void nm_device_activate_schedule_stage3_ip_config(NMDevice *device, gboolean do_sync);
 
 void nm_device_recheck_available_connections(NMDevice *device);
 
@@ -122,46 +66,75 @@ void nm_device_master_release_slaves(NMDevice *self);
 void nm_device_set_carrier(NMDevice *self, gboolean carrier);
 
 void nm_device_queue_recheck_assume(NMDevice *device);
-void nm_device_queue_recheck_available(NMDevice *          device,
+void nm_device_queue_recheck_available(NMDevice           *device,
                                        NMDeviceStateReason available_reason,
                                        NMDeviceStateReason unavailable_reason);
 
-void nm_device_set_dev2_ip_config(NMDevice *device, int addr_family, NMIPConfig *config);
-
 gboolean nm_device_hw_addr_is_explict(NMDevice *device);
 
-void nm_device_ip_method_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason);
+NMDeviceIPState nm_device_devip_get_state(NMDevice *self, int addr_family);
 
-gboolean nm_device_sysctl_ip_conf_set(NMDevice *  self,
+void nm_device_devip_set_state_full(NMDevice             *self,
+                                    int                   addr_family,
+                                    NMDeviceIPState       ip_state,
+                                    const NML3ConfigData *l3cd,
+                                    NMDeviceStateReason   failed_reason);
+
+static inline void
+nm_device_devip_set_state(NMDevice             *self,
+                          int                   addr_family,
+                          NMDeviceIPState       ip_state,
+                          const NML3ConfigData *l3cd)
+{
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert_addr_family_or_unspec(addr_family);
+    nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
+    nm_assert(NM_IN_SET(ip_state, NM_DEVICE_IP_STATE_PENDING, NM_DEVICE_IP_STATE_READY));
+
+    nm_device_devip_set_state_full(self, addr_family, ip_state, l3cd, NM_DEVICE_STATE_REASON_NONE);
+}
+
+static inline void
+nm_device_devip_set_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason)
+{
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert_addr_family_or_unspec(addr_family);
+    nm_assert(reason != NM_DEVICE_STATE_REASON_NONE);
+
+    nm_device_devip_set_state_full(self, addr_family, NM_DEVICE_IP_STATE_FAILED, NULL, reason);
+}
+
+gboolean nm_device_sysctl_ip_conf_set(NMDevice   *self,
                                       int         addr_family,
                                       const char *property,
                                       const char *value);
 
-NMIP4Config *nm_device_ip4_config_new(NMDevice *self);
+NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source);
 
-NMIP6Config *nm_device_ip6_config_new(NMDevice *self);
+const NML3ConfigData *nm_device_create_l3_config_data_from_connection(NMDevice     *self,
+                                                                      NMConnection *connection);
 
-NMIPConfig *nm_device_ip_config_new(NMDevice *self, int addr_family);
+void nm_device_ip_method_dhcp4_start(NMDevice *self);
 
-NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source);
+void nm_device_ip_method_autoconf6_start(NMDevice *self);
 
 /*****************************************************************************/
 
-gint64 nm_device_get_configured_mtu_from_connection_default(NMDevice *  self,
+gint64 nm_device_get_configured_mtu_from_connection_default(NMDevice   *self,
                                                             const char *property_name,
                                                             guint32     max_mtu);
 
-guint32 nm_device_get_configured_mtu_from_connection(NMDevice *         device,
+guint32 nm_device_get_configured_mtu_from_connection(NMDevice          *device,
                                                      GType              setting_type,
                                                      NMDeviceMtuSource *out_source);
 
-guint32 nm_device_get_configured_mtu_for_wired(NMDevice *         self,
+guint32 nm_device_get_configured_mtu_for_wired(NMDevice          *self,
                                                NMDeviceMtuSource *out_source,
-                                               gboolean *         out_force);
+                                               gboolean          *out_force);
 
-guint32 nm_device_get_configured_mtu_wired_parent(NMDevice *         self,
+guint32 nm_device_get_configured_mtu_wired_parent(NMDevice          *self,
                                                   NMDeviceMtuSource *out_source,
-                                                  gboolean *         out_force);
+                                                  gboolean          *out_force);
 
 void nm_device_commit_mtu(NMDevice *self);
 
@@ -177,26 +150,26 @@ void nm_device_commit_mtu(NMDevice *self);
         _types;                                                                 \
     }))
 
-gboolean _nm_device_hash_check_invalid_keys(GHashTable *       hash,
-                                            const char *       setting_name,
-                                            GError **          error,
+gboolean _nm_device_hash_check_invalid_keys(GHashTable        *hash,
+                                            const char        *setting_name,
+                                            GError           **error,
                                             const char *const *whitelist);
 #define nm_device_hash_check_invalid_keys(hash, setting_name, error, ...) \
     _nm_device_hash_check_invalid_keys(hash, setting_name, error, NM_MAKE_STRV(__VA_ARGS__))
 
 gboolean nm_device_match_parent(NMDevice *device, const char *parent);
-gboolean nm_device_match_parent_hwaddr(NMDevice *    device,
+gboolean nm_device_match_parent_hwaddr(NMDevice     *device,
                                        NMConnection *connection,
                                        gboolean      fail_if_no_hwaddr);
 
 /*****************************************************************************/
 
-void nm_device_auth_request(NMDevice *                     self,
-                            GDBusMethodInvocation *        context,
-                            NMConnection *                 connection,
-                            const char *                   permission,
+void nm_device_auth_request(NMDevice                      *self,
+                            GDBusMethodInvocation         *context,
+                            NMConnection                  *connection,
+                            const char                    *permission,
                             gboolean                       allow_interaction,
-                            GCancellable *                 cancellable,
+                            GCancellable                  *cancellable,
                             NMManagerDeviceAuthRequestFunc callback,
                             gpointer                       user_data);
 
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index 0ab08f65..b6c4f88b 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -13,7 +13,6 @@
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-tun.h"
@@ -56,7 +55,7 @@ G_DEFINE_TYPE(NMDeviceTun, nm_device_tun, NM_TYPE_DEVICE)
 static void
 update_properties_from_struct(NMDeviceTun *self, const NMPlatformLnkTun *props)
 {
-    NMDeviceTunPrivate *   priv   = NM_DEVICE_TUN_GET_PRIVATE(self);
+    NMDeviceTunPrivate    *priv   = NM_DEVICE_TUN_GET_PRIVATE(self);
     const NMPlatformLnkTun props0 = {};
 
     if (!props) {
@@ -129,11 +128,11 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 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)
 {
     NMSettingTun *s_tun;
 
@@ -162,12 +161,12 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceTun *       self = NM_DEVICE_TUN(device);
+    NMDeviceTun        *self = NM_DEVICE_TUN(device);
     NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self);
-    NMSettingTun *      s_tun;
+    NMSettingTun       *s_tun;
     NMSettingTunMode    mode;
     char                s_buf[100];
-    const char *        str;
+    const char         *str;
 
     /* Note: since we read tun properties from sysctl for older kernels,
      *       we don't get proper change notifications. Make sure that all our
@@ -217,15 +216,15 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *     iface = nm_device_get_iface(device);
+    const char      *iface = nm_device_get_iface(device);
     NMPlatformLnkTun props = {};
-    NMSettingTun *   s_tun;
+    NMSettingTun    *s_tun;
     gint64           owner;
     gint64           group;
     int              r;
@@ -287,10 +286,10 @@ _same_og(const char *str, gboolean og_valid, guint32 og_num)
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
-    NMDeviceTun *       self = NM_DEVICE_TUN(device);
+    NMDeviceTun        *self = NM_DEVICE_TUN(device);
     NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self);
     NMSettingTunMode    mode;
-    NMSettingTun *      s_tun;
+    NMSettingTun       *s_tun;
 
     if (!NM_DEVICE_CLASS(nm_device_tun_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -361,7 +360,7 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceTun *       self = NM_DEVICE_TUN(device);
+    NMDeviceTun        *self = NM_DEVICE_TUN(device);
     NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self);
 
     if (priv->props.type == IFF_TUN) {
@@ -390,9 +389,9 @@ unrealize_notify(NMDevice *device)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceTun *       self = NM_DEVICE_TUN(object);
+    NMDeviceTun        *self = NM_DEVICE_TUN(object);
     NMDeviceTunPrivate *priv = NM_DEVICE_TUN_GET_PRIVATE(self);
-    const char *        s;
+    const char         *s;
 
     switch (prop_id) {
     case PROP_OWNER:
@@ -458,9 +457,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_tun = {
 static void
 nm_device_tun_class_init(NMDeviceTunClass *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->get_property = get_property;
 
@@ -531,11 +530,11 @@ nm_device_tun_class_init(NMDeviceTunClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_TUN_DEVICE_FACTORY, NMTunDeviceFactory))
 
 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 || plink->type == NM_LINK_TYPE_TUN, NULL);
     g_return_val_if_fail(!connection
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
index 2a6ab04e..170922eb 100644
--- a/src/core/devices/nm-device-utils.c
+++ b/src/core/devices/nm-device-utils.c
@@ -151,10 +151,9 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_device_ip_state_to_string,
                            NMDeviceIPState,
                            NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"),
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_WAIT, "wait"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), );
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_PENDING, "pending"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_READY, "done"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAILED, "fail"), );
 
 /*****************************************************************************/
 
@@ -166,7 +165,7 @@ typedef struct {
     int                                addr_family;
     NMIPAddr                           address;
     gulong                             cancellable_id;
-    GTask *                            task;
+    GTask                             *task;
     NMDnsSystemdResolvedResolveHandle *resolved_handle;
 } ResolveAddrInfo;
 
@@ -219,9 +218,9 @@ resolve_addr_complete(ResolveAddrInfo *info, char *hostname_take, GError *error_
 static void
 resolve_addr_helper_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    ResolveAddrInfo *info        = user_data;
+    ResolveAddrInfo      *info   = user_data;
     gs_free_error GError *error  = NULL;
-    gs_free char *        output = NULL;
+    gs_free char         *output = NULL;
 
     output = nm_utils_spawn_helper_finish(result, &error);
     if (nm_utils_error_is_cancelled(error))
@@ -246,12 +245,12 @@ resolve_addr_spawn_helper(ResolveAddrInfo *info)
 }
 
 static void
-resolve_addr_resolved_cb(NMDnsSystemdResolved *                   resolved,
-                         NMDnsSystemdResolvedResolveHandle *      handle,
+resolve_addr_resolved_cb(NMDnsSystemdResolved                    *resolved,
+                         NMDnsSystemdResolvedResolveHandle       *handle,
                          const NMDnsSystemdResolvedAddressResult *names,
                          guint                                    names_len,
                          guint64                                  flags,
-                         GError *                                 error,
+                         GError                                  *error,
                          gpointer                                 user_data)
 {
     ResolveAddrInfo *info = user_data;
@@ -301,7 +300,7 @@ static void
 resolve_addr_cancelled(GObject *object, gpointer user_data)
 {
     ResolveAddrInfo *info  = user_data;
-    GError *         error = NULL;
+    GError          *error = NULL;
 
     nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id);
     nm_clear_pointer(&info->resolved_handle, nm_dns_systemd_resolved_resolve_cancel);
@@ -312,11 +311,11 @@ resolve_addr_cancelled(GObject *object, gpointer user_data)
 void
 nm_device_resolve_address(int                 addr_family,
                           gconstpointer       address,
-                          GCancellable *      cancellable,
+                          GCancellable       *cancellable,
                           GAsyncReadyCallback callback,
                           gpointer            cb_data)
 {
-    ResolveAddrInfo *     info;
+    ResolveAddrInfo      *info;
     NMDnsSystemdResolved *resolved;
 
     info  = g_new(ResolveAddrInfo, 1);
diff --git a/src/core/devices/nm-device-utils.h b/src/core/devices/nm-device-utils.h
index 7200b0ea..7b879c90 100644
--- a/src/core/devices/nm-device-utils.h
+++ b/src/core/devices/nm-device-utils.h
@@ -73,23 +73,20 @@ const char *nm_device_sys_iface_state_to_string(NMDeviceSysIfaceState sys_iface_
 
 /*****************************************************************************/
 
-typedef enum {
+typedef enum _nm_packed {
     NM_DEVICE_IP_STATE_NONE,
-    NM_DEVICE_IP_STATE_WAIT,
-    NM_DEVICE_IP_STATE_CONF,
-    NM_DEVICE_IP_STATE_DONE,
-    NM_DEVICE_IP_STATE_FAIL,
+    NM_DEVICE_IP_STATE_PENDING,
+    NM_DEVICE_IP_STATE_READY,
+    NM_DEVICE_IP_STATE_FAILED,
 } NMDeviceIPState;
 
 const char *nm_device_ip_state_to_string(NMDeviceIPState ip_state);
 
 /*****************************************************************************/
 
-/*****************************************************************************/
-
 void nm_device_resolve_address(int                 addr_family,
                                gconstpointer       address,
-                               GCancellable *      cancellable,
+                               GCancellable       *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer            cb_data);
 
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index 28831b75..8c95a293 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -75,13 +75,13 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *   iface = nm_device_get_iface(device);
+    const char    *iface = nm_device_get_iface(device);
     NMSettingVeth *s_veth;
     int            r;
 
@@ -143,7 +143,7 @@ static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
     NMDeviceVeth *self = NM_DEVICE_VETH(object);
-    NMDevice *    peer;
+    NMDevice     *peer;
 
     switch (prop_id) {
     case PROP_PEER:
@@ -168,9 +168,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_veth = {
 static void
 nm_device_veth_class_init(NMDeviceVethClass *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->get_property = get_property;
 
@@ -201,11 +201,11 @@ nm_device_veth_class_init(NMDeviceVethClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VETH_DEVICE_FACTORY, NMVethDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_VETH,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index d1e57411..0c101c70 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -15,7 +15,6 @@
 #include "nm-device-private.h"
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-manager.h"
@@ -55,7 +54,7 @@ G_DEFINE_TYPE(NMDeviceVlan, nm_device_vlan, NM_TYPE_DEVICE)
 /*****************************************************************************/
 
 static void
-parent_state_changed(NMDevice *          parent,
+parent_state_changed(NMDevice           *parent,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason,
@@ -85,11 +84,11 @@ parent_mtu_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data
 static void
 parent_hwaddr_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_data)
 {
-    NMDevice *         device = NM_DEVICE(user_data);
-    NMDeviceVlan *     self   = NM_DEVICE_VLAN(device);
-    NMConnection *     connection;
-    const char *       new_mac, *old_mac;
-    NMSettingIPConfig *s_ip6;
+    NMDevice     *device = NM_DEVICE(user_data);
+    NMDeviceVlan *self   = NM_DEVICE_VLAN(device);
+    NMConnection *connection;
+    const char   *old_mac;
+    const char   *new_mac;
 
     /* Never touch assumed devices */
     if (nm_device_sys_iface_state_is_external_or_assume(device))
@@ -113,13 +112,10 @@ parent_hwaddr_maybe_changed(NMDevice *parent, GParamSpec *pspec, gpointer user_d
           NM_PRINT_FMT_QUOTE_STRING(new_mac));
     if (new_mac) {
         nm_device_hw_addr_set(device, new_mac, "vlan-parent", TRUE);
-        nm_device_arp_announce(device);
         /* When changing the hw address the interface is taken down,
          * removing the IPv6 configuration; reapply it.
          */
-        s_ip6 = nm_connection_get_setting_ip6_config(connection);
-        if (s_ip6)
-            nm_device_reactivate_ip_config(device, AF_INET6, s_ip6, s_ip6);
+        nm_device_l3cfg_commit(device, NM_L3_CFG_COMMIT_TYPE_UPDATE, FALSE);
     }
 }
 
@@ -130,7 +126,7 @@ parent_changed_notify(NMDevice *device,
                       int       new_ifindex,
                       NMDevice *new_parent)
 {
-    NMDeviceVlan *       self = NM_DEVICE_VLAN(device);
+    NMDeviceVlan        *self = NM_DEVICE_VLAN(device);
     NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(self);
 
     NM_DEVICE_CLASS(nm_device_vlan_parent_class)
@@ -179,8 +175,8 @@ parent_changed_notify(NMDevice *device,
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceVlanPrivate *    priv;
-    const NMPlatformLink *   plink = NULL;
+    NMDeviceVlanPrivate     *priv;
+    const NMPlatformLink    *plink = NULL;
     const NMPlatformLnkVlan *plnk  = NULL;
     int                      ifindex;
     int                      parent_ifindex = 0;
@@ -219,15 +215,15 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     NMDeviceVlanPrivate *priv  = NM_DEVICE_VLAN_GET_PRIVATE(device);
-    const char *         iface = nm_device_get_iface(device);
-    NMSettingVlan *      s_vlan;
+    const char          *iface = nm_device_get_iface(device);
+    NMSettingVlan       *s_vlan;
     int                  parent_ifindex;
     guint                vlan_id;
     int                  r;
@@ -295,7 +291,7 @@ create_and_realize(NMDevice *             device,
 static void
 unrealize_notify(NMDevice *device)
 {
-    NMDeviceVlan *       self = NM_DEVICE_VLAN(device);
+    NMDeviceVlan        *self = NM_DEVICE_VLAN(device);
     NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(self);
 
     NM_DEVICE_CLASS(nm_device_vlan_parent_class)->unrealize_notify(device);
@@ -331,8 +327,8 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMDeviceVlanPrivate *priv = NM_DEVICE_VLAN_GET_PRIVATE(device);
-    NMSettingVlan *      s_vlan;
-    const char *         parent;
+    NMSettingVlan       *s_vlan;
+    const char          *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_vlan_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -372,11 +368,11 @@ 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)
 {
     if (!nm_device_is_real(device))
         return TRUE;
@@ -386,11 +382,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)
 {
     NMSettingVlan *s_vlan;
 
@@ -432,11 +428,11 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceVlanPrivate * priv    = NM_DEVICE_VLAN_GET_PRIVATE(device);
-    NMSettingVlan *       s_vlan  = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VLAN);
+    NMDeviceVlanPrivate  *priv    = NM_DEVICE_VLAN_GET_PRIVATE(device);
+    NMSettingVlan        *s_vlan  = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VLAN);
     int                   ifindex = nm_device_get_ifindex(device);
     const NMPlatformLink *plink;
-    const NMPObject *     polnk;
+    const NMPObject      *polnk;
     guint                 vlan_id;
     _NMVlanFlags          vlan_flags;
 
@@ -482,7 +478,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDevice *     parent_device;
+    NMDevice      *parent_device;
     NMSettingVlan *s_vlan;
 
     /* Change MAC address to parent's one if needed */
@@ -553,9 +549,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_vlan = {
 static void
 nm_device_vlan_class_init(NMDeviceVlanClass *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->get_property = get_property;
 
@@ -599,11 +595,11 @@ nm_device_vlan_class_init(NMDeviceVlanClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VLAN_DEVICE_FACTORY, NMVlanDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_VLAN,
                         NM_DEVICE_IFACE,
@@ -622,9 +618,9 @@ create_device(NMDeviceFactory *     factory,
 static const char *
 get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 {
-    NMSettingVlan * s_vlan;
+    NMSettingVlan  *s_vlan;
     NMSettingWired *s_wired;
-    const char *    parent = NULL;
+    const char     *parent = NULL;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL);
 
@@ -646,7 +642,7 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
-    const char *   ifname;
+    const char    *ifname;
     NMSettingVlan *s_vlan;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VLAN_SETTING_NAME), NULL);
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index c752fa0a..ae80e1d4 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -44,7 +44,7 @@ static void
 do_update_properties(NMDeviceVrf *self, const NMPlatformLnkVrf *props)
 {
     NMDeviceVrfPrivate *priv   = NM_DEVICE_VRF_GET_PRIVATE(self);
-    GObject *           object = G_OBJECT(self);
+    GObject            *object = G_OBJECT(self);
     NMPlatformLnkVrf    props_null;
 
     if (!props) {
@@ -72,7 +72,7 @@ do_update_properties(NMDeviceVrf *self, const NMPlatformLnkVrf *props)
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceVrf *           self = NM_DEVICE_VRF(device);
+    NMDeviceVrf            *self = NM_DEVICE_VRF(device);
     const NMPlatformLnkVrf *props;
 
     props = nm_platform_link_get_lnk_vrf(nm_device_get_platform(device),
@@ -110,15 +110,15 @@ unrealize_notify(NMDevice *device)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *     iface = nm_device_get_iface(device);
+    const char      *iface = nm_device_get_iface(device);
     NMPlatformLnkVrf props = {};
-    NMSettingVrf *   s_vrf;
+    NMSettingVrf    *s_vrf;
     int              r;
 
     s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF);
@@ -145,7 +145,7 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMDeviceVrfPrivate *priv = NM_DEVICE_VRF_GET_PRIVATE(device);
-    NMSettingVrf *      s_vrf;
+    NMSettingVrf       *s_vrf;
 
     if (!NM_DEVICE_CLASS(nm_device_vrf_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -166,11 +166,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     NMSettingVrf *s_vrf;
 
@@ -200,7 +200,7 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceVrfPrivate *priv  = NM_DEVICE_VRF_GET_PRIVATE(device);
-    NMSettingVrf *      s_vrf = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VRF);
+    NMSettingVrf       *s_vrf = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VRF);
 
     if (priv->props.table != nm_setting_vrf_get_table(s_vrf))
         g_object_set(G_OBJECT(s_vrf), NM_SETTING_VRF_TABLE, priv->props.table, NULL);
@@ -211,7 +211,7 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
 {
     NMDeviceVrf *self        = NM_DEVICE_VRF(device);
     gboolean     success     = TRUE;
-    const char * slave_iface = nm_device_get_ip_iface(slave);
+    const char  *slave_iface = nm_device_get_ip_iface(slave);
 
     nm_device_master_check_slave_physical_port(device, slave, LOGD_DEVICE);
 
@@ -303,9 +303,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_vrf = {
 static void
 nm_device_vrf_class_init(NMDeviceVrfClass *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->get_property = get_property;
 
@@ -344,11 +344,11 @@ nm_device_vrf_class_init(NMDeviceVrfClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VRF_DEVICE_FACTORY, NMVrfDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_VRF,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index fcd6950b..bf7c94c0 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -16,7 +16,6 @@
 #include "nm-setting-wired.h"
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
-#include "nm-ip4-config.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 
@@ -65,9 +64,9 @@ G_DEFINE_TYPE(NMDeviceVxlan, nm_device_vxlan, NM_TYPE_DEVICE)
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceVxlan *           self   = NM_DEVICE_VXLAN(device);
-    NMDeviceVxlanPrivate *    priv   = NM_DEVICE_VXLAN_GET_PRIVATE(self);
-    GObject *                 object = G_OBJECT(device);
+    NMDeviceVxlan            *self   = NM_DEVICE_VXLAN(device);
+    NMDeviceVxlanPrivate     *priv   = NM_DEVICE_VXLAN_GET_PRIVATE(self);
+    GObject                  *object = G_OBJECT(device);
     const NMPlatformLnkVxlan *props;
 
     props = nm_platform_link_get_lnk_vxlan(nm_device_get_platform(device),
@@ -140,7 +139,7 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
 static void
 unrealize_notify(NMDevice *device)
 {
-    NMDeviceVxlan *       self = NM_DEVICE_VXLAN(device);
+    NMDeviceVxlan        *self = NM_DEVICE_VXLAN(device);
     NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(self);
     guint                 i;
 
@@ -153,16 +152,16 @@ unrealize_notify(NMDevice *device)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
-    const char *       iface = nm_device_get_iface(device);
+    const char        *iface = nm_device_get_iface(device);
     NMPlatformLnkVxlan props = {};
-    NMSettingVxlan *   s_vxlan;
-    const char *       str;
+    NMSettingVxlan    *s_vxlan;
+    const char        *str;
     int                r;
 
     s_vxlan = nm_connection_get_setting_vxlan(connection);
@@ -242,8 +241,8 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device);
-    NMSettingVxlan *      s_vxlan;
-    const char *          parent;
+    NMSettingVxlan       *s_vxlan;
+    const char           *parent;
 
     if (!NM_DEVICE_CLASS(nm_device_vxlan_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -367,11 +366,11 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
     NMSettingVxlan *s_vxlan;
 
@@ -605,9 +604,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_vxlan = {
 static void
 nm_device_vxlan_class_init(NMDeviceVxlanClass *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->get_property = get_property;
 
@@ -745,11 +744,11 @@ nm_device_vxlan_class_init(NMDeviceVxlanClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_VXLAN_DEVICE_FACTORY, NMVxlanDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_VXLAN,
                         NM_DEVICE_IFACE,
@@ -779,7 +778,7 @@ get_connection_parent(NMDeviceFactory *factory, NMConnection *connection)
 static char *
 get_connection_iface(NMDeviceFactory *factory, NMConnection *connection, const char *parent_iface)
 {
-    const char *    ifname;
+    const char     *ifname;
     NMSettingVxlan *s_vxlan;
 
     g_return_val_if_fail(nm_connection_is_type(connection, NM_SETTING_VXLAN_SETTING_NAME), NULL);
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index f129478f..bdb96cb2 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -12,12 +12,13 @@
 
 #include "nm-setting-wireguard.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "nm-l3-config-data.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
 #include "nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-object.h"
-#include "libnm-platform/nmp-rules-manager.h"
+#include "libnm-platform/nmp-route-manager.h"
 #include "nm-device-factory.h"
 #include "nm-active-connection.h"
 #include "nm-act-request.h"
@@ -178,9 +179,9 @@ static NM_UTILS_LOOKUP_STR_DEFINE(_link_config_mode_to_string,
 
 static void
 _auto_default_route_get_enabled(NMSettingWireGuard *s_wg,
-                                NMConnection *      connection,
-                                gboolean *          out_enabled_v4,
-                                gboolean *          out_enabled_v6)
+                                NMConnection       *connection,
+                                gboolean           *out_enabled_v4,
+                                gboolean           *out_enabled_v6)
 {
     NMTernary enabled_v4;
     NMTernary enabled_v6;
@@ -326,7 +327,7 @@ static void
 _auto_default_route_init(NMDeviceWireGuard *self)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    NMConnection *            connection;
+    NMConnection             *connection;
     gboolean                  enabled_v4 = FALSE;
     gboolean                  enabled_v6 = FALSE;
     gboolean                  refreshing_only;
@@ -379,12 +380,12 @@ _auto_default_route_init(NMDeviceWireGuard *self)
 static GPtrArray *
 get_extra_rules(NMDevice *device)
 {
-    NMDeviceWireGuard *       self           = NM_DEVICE_WIREGUARD(device);
-    NMDeviceWireGuardPrivate *priv           = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    NMDeviceWireGuard           *self        = NM_DEVICE_WIREGUARD(device);
+    NMDeviceWireGuardPrivate    *priv        = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
     gs_unref_ptrarray GPtrArray *extra_rules = NULL;
     guint32                      priority    = 0;
     int                          is_ipv4;
-    NMConnection *               connection;
+    NMConnection                *connection;
 
     _auto_default_route_init(self);
 
@@ -456,7 +457,7 @@ get_extra_rules(NMDevice *device)
 static guint32
 coerce_route_table(NMDevice *device, int addr_family, guint32 route_table, gboolean is_user_config)
 {
-    NMDeviceWireGuard *       self = NM_DEVICE_WIREGUARD(device);
+    NMDeviceWireGuard        *self = NM_DEVICE_WIREGUARD(device);
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
     gboolean                  auto_default_route_enabled;
 
@@ -580,7 +581,7 @@ static PeerData *
 _peers_add(NMDeviceWireGuard *self, NMWireGuardPeer *peer)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    PeerData *                peer_data;
+    PeerData                 *peer_data;
 
     nm_assert(peer);
     nm_assert(nm_wireguard_peer_is_sealed(peer));
@@ -605,9 +606,9 @@ _peers_add(NMDeviceWireGuard *self, NMWireGuardPeer *peer)
 static gboolean
 _peers_resolve_retry_timeout(gpointer user_data)
 {
-    NMDeviceWireGuard *       self = user_data;
+    NMDeviceWireGuard        *self = user_data;
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    PeerData *                peer_data;
+    PeerData                 *peer_data;
     gint64                    now;
     gint64                    next;
 
@@ -683,7 +684,7 @@ _peers_resolve_retry_reschedule(NMDeviceWireGuard *self, gint64 new_next_try_at_
 
 static void
 _peers_resolve_retry_reschedule_for_peer(NMDeviceWireGuard *self,
-                                         PeerData *         peer_data,
+                                         PeerData          *peer_data,
                                          gint64             retry_in_msec)
 {
     nm_assert(retry_in_msec >= 0);
@@ -725,16 +726,16 @@ _peers_retry_in_msec(PeerData *peer_data, gboolean after_failure)
 static void
 _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceWireGuard *       self;
+    NMDeviceWireGuard        *self;
     NMDeviceWireGuardPrivate *priv;
-    PeerData *                peer_data;
-    gs_free_error GError *resolv_error = NULL;
-    GList *               list;
-    gboolean              changed;
-    NMSockAddrUnion       sockaddr;
-    gint64                retry_in_msec;
-    char                  s_sockaddr[100];
-    char                  s_retry[100];
+    PeerData                 *peer_data;
+    gs_free_error GError     *resolv_error = NULL;
+    GList                    *list;
+    gboolean                  changed;
+    NMSockAddrUnion           sockaddr;
+    gint64                    retry_in_msec;
+    char                      s_sockaddr[100];
+    char                      s_retry[100];
 
     list = g_resolver_lookup_by_name_finish(G_RESOLVER(source_object), res, &resolv_error);
 
@@ -782,7 +783,7 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
         GList *iter;
 
         for (iter = list; iter; iter = iter->next) {
-            GInetAddress *   a = iter->data;
+            GInetAddress    *a = iter->data;
             NMSockAddrUnion  sockaddr_tmp;
             NMSockAddrUnion *s;
 
@@ -874,9 +875,9 @@ _peers_resolve_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 static void
 _peers_resolve_start(NMDeviceWireGuard *self, PeerData *peer_data)
 {
-    NMDeviceWireGuardPrivate *priv      = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    NMDeviceWireGuardPrivate  *priv     = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
     gs_unref_object GResolver *resolver = NULL;
-    const char *               host;
+    const char                *host;
 
     resolver = g_resolver_get_default();
 
@@ -913,7 +914,7 @@ static void
 _peers_resolve_reresolve_all(NMDeviceWireGuard *self)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    PeerData *                peer_data;
+    PeerData                 *peer_data;
 
     c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
         if (peer_data->ep_resolv.cancellable) {
@@ -931,13 +932,13 @@ _peers_resolve_reresolve_all(NMDeviceWireGuard *self)
 
 static gboolean
 _peers_update(NMDeviceWireGuard *self,
-              PeerData *         peer_data,
-              NMWireGuardPeer *  peer,
+              PeerData          *peer_data,
+              NMWireGuardPeer   *peer,
               gboolean           force_update)
 {
     nm_auto_unref_wgpeer NMWireGuardPeer *old_peer = NULL;
-    NMSockAddrEndpoint *                  old_endpoint;
-    NMSockAddrEndpoint *                  endpoint;
+    NMSockAddrEndpoint                   *old_endpoint;
+    NMSockAddrEndpoint                   *endpoint;
     gboolean                              endpoint_changed = FALSE;
     gboolean                              changed;
     NMSockAddrUnion                       sockaddr;
@@ -1018,7 +1019,7 @@ static void
 _peers_remove_all(NMDeviceWireGuard *self)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    PeerData *                peer_data;
+    PeerData                 *peer_data;
 
     while ((peer_data = c_list_first_entry(&priv->lst_peers_head, PeerData, lst_peers)))
         _peers_remove(self, peer_data);
@@ -1028,8 +1029,8 @@ static void
 _peers_update_all(NMDeviceWireGuard *self, NMSettingWireGuard *s_wg, gboolean *out_peers_removed)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    PeerData *                peer_data_safe;
-    PeerData *                peer_data;
+    PeerData                 *peer_data_safe;
+    PeerData                 *peer_data;
     guint                     i, n;
     gboolean                  peers_removed = FALSE;
 
@@ -1061,22 +1062,22 @@ _peers_update_all(NMDeviceWireGuard *self, NMSettingWireGuard *s_wg, gboolean *o
 }
 
 static void
-_peers_get_platform_list(NMDeviceWireGuardPrivate *           priv,
+_peers_get_platform_list(NMDeviceWireGuardPrivate            *priv,
                          LinkConfigMode                       config_mode,
-                         NMPWireGuardPeer **                  out_peers,
+                         NMPWireGuardPeer                   **out_peers,
                          NMPlatformWireGuardChangePeerFlags **out_peer_flags,
-                         guint *                              out_len,
-                         GArray **                            out_allowed_ips_data)
+                         guint                               *out_len,
+                         GArray                             **out_allowed_ips_data)
 {
-    gs_free NMPWireGuardPeer *plpeers                        = NULL;
+    gs_free NMPWireGuardPeer                   *plpeers      = NULL;
     gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
-    gs_unref_array GArray *allowed_ips                       = NULL;
-    PeerData *             peer_data;
-    guint                  i_good;
-    guint                  n_aip;
-    guint                  i_aip;
-    guint                  len;
-    guint                  i;
+    gs_unref_array GArray                      *allowed_ips  = NULL;
+    PeerData                                   *peer_data;
+    guint                                       i_good;
+    guint                                       n_aip;
+    guint                                       i_aip;
+    guint                                       len;
+    guint                                       i;
 
     nm_assert(out_peers && !*out_peers);
     nm_assert(out_peer_flags && !*out_peer_flags);
@@ -1096,7 +1097,7 @@ _peers_get_platform_list(NMDeviceWireGuardPrivate *           priv,
     i_good = 0;
     c_list_for_each_entry (peer_data, &priv->lst_peers_head, lst_peers) {
         NMPlatformWireGuardChangePeerFlags *plf = &plpeer_flags[i_good];
-        NMPWireGuardPeer *                  plp = &plpeers[i_good];
+        NMPWireGuardPeer                   *plp = &plpeers[i_good];
         NMSettingSecretFlags                psk_secret_flags;
 
         if (!nm_utils_base64secret_decode(nm_wireguard_peer_get_public_key(peer_data->peer),
@@ -1211,9 +1212,9 @@ skip:
 static void
 update_properties(NMDevice *device)
 {
-    NMDeviceWireGuard *           self;
-    NMDeviceWireGuardPrivate *    priv;
-    const NMPlatformLink *        plink;
+    NMDeviceWireGuard            *self;
+    NMDeviceWireGuardPrivate     *priv;
+    const NMPlatformLink         *plink;
     const NMPlatformLnkWireGuard *props = NULL;
     int                           ifindex;
 
@@ -1273,11 +1274,11 @@ get_generic_capabilities(NMDevice *dev)
 /*****************************************************************************/
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     const char *iface = nm_device_get_iface(device);
     int         r;
@@ -1312,14 +1313,14 @@ _secrets_cancel(NMDeviceWireGuard *self)
 }
 
 static void
-_secrets_cb(NMActRequest *                req,
+_secrets_cb(NMActRequest                 *req,
             NMActRequestGetSecretsCallId *call_id,
-            NMSettingsConnection *        connection,
-            GError *                      error,
+            NMSettingsConnection         *connection,
+            GError                       *error,
             gpointer                      user_data)
 {
-    NMDeviceWireGuard *       self   = NM_DEVICE_WIREGUARD(user_data);
-    NMDevice *                device = NM_DEVICE(self);
+    NMDeviceWireGuard        *self   = NM_DEVICE_WIREGUARD(user_data);
+    NMDevice                 *device = NM_DEVICE(self);
     NMDeviceWireGuardPrivate *priv;
 
     g_return_if_fail(NM_IS_DEVICE_WIREGUARD(self));
@@ -1348,13 +1349,13 @@ _secrets_cb(NMActRequest *                req,
 }
 
 static void
-_secrets_get_secrets(NMDeviceWireGuard *          self,
-                     const char *                 setting_name,
+_secrets_get_secrets(NMDeviceWireGuard           *self,
+                     const char                  *setting_name,
                      NMSecretAgentGetSecretsFlags flags,
-                     const char *const *          hints)
+                     const char *const           *hints)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    NMActRequest *            req;
+    NMActRequest             *req;
 
     _secrets_cancel(self);
 
@@ -1369,8 +1370,8 @@ _secrets_get_secrets(NMDeviceWireGuard *          self,
 static NMActStageReturn
 _secrets_handle_auth_or_fail(NMDeviceWireGuard *self, NMActRequest *req, gboolean new_secrets)
 {
-    NMConnection *    applied_connection;
-    const char *      setting_name;
+    NMConnection                *applied_connection;
+    const char                  *setting_name;
     gs_unref_ptrarray GPtrArray *hints = NULL;
 
     if (!nm_device_auth_retries_try_next(NM_DEVICE(self)))
@@ -1415,22 +1416,22 @@ _dns_config_changed(NMDnsManager *dns_manager, NMDeviceWireGuard *self)
 /*****************************************************************************/
 
 static NMActStageReturn
-link_config(NMDeviceWireGuard *  self,
-            const char *         reason,
+link_config(NMDeviceWireGuard   *self,
+            const char          *reason,
             LinkConfigMode       config_mode,
             NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWireGuardPrivate *           priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    nm_auto_bzero_secret_ptr NMSecretPtr wg_lnk_clear_private_key = NM_SECRET_PTR_INIT();
-    NMSettingWireGuard *                 s_wg;
-    NMConnection *                       connection;
-    NMActStageReturn                     ret;
-    gs_unref_array GArray *allowed_ips_data = NULL;
-    NMPlatformLnkWireGuard wg_lnk;
-    gs_free NMPWireGuardPeer *plpeers                        = NULL;
+    NMDeviceWireGuardPrivate                   *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    nm_auto_bzero_secret_ptr NMSecretPtr        wg_lnk_clear_private_key = NM_SECRET_PTR_INIT();
+    NMSettingWireGuard                         *s_wg;
+    NMConnection                               *connection;
+    NMActStageReturn                            ret;
+    gs_unref_array GArray                      *allowed_ips_data = NULL;
+    NMPlatformLnkWireGuard                      wg_lnk;
+    gs_free NMPWireGuardPeer                   *plpeers      = NULL;
     gs_free NMPlatformWireGuardChangePeerFlags *plpeer_flags = NULL;
     guint                                       plpeers_len  = 0;
-    const char *                                setting_name;
+    const char                                 *setting_name;
     gboolean                                    peers_removed;
     NMPlatformWireGuardChangeFlags              wg_change_flags;
     int                                         ifindex;
@@ -1583,7 +1584,7 @@ link_config_delayed_resolver_cb(gpointer user_data)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWireGuard *       self = NM_DEVICE_WIREGUARD(device);
+    NMDeviceWireGuard        *self = NM_DEVICE_WIREGUARD(device);
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
     NMDeviceSysIfaceState     sys_iface_state;
     NMDeviceStateReason       failure_reason;
@@ -1625,19 +1626,19 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     return ret;
 }
 
-static NMIPConfig *
+static const NML3ConfigData *
 _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
 {
-    NMDeviceWireGuardPrivate *priv        = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    gs_unref_object NMIPConfig *ip_config = NULL;
-    NMConnection *              connection;
-    NMSettingWireGuard *        s_wg;
-    guint                       n_peers;
-    guint                       i;
-    int                         ip_ifindex;
-    guint32                     route_metric;
-    guint32                     route_table_coerced;
-    gboolean                    auto_default_route_enabled;
+    NMDeviceWireGuardPrivate               *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+    NMConnection                           *connection;
+    NMSettingWireGuard                     *s_wg;
+    guint                                   n_peers;
+    guint                                   i;
+    int                                     ip_ifindex;
+    guint32                                 route_metric;
+    guint32                                 route_table_coerced;
+    gboolean                                auto_default_route_enabled;
 
     _auto_default_route_init(self);
 
@@ -1693,7 +1694,7 @@ _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
         for (j = 0; j < n_aips; j++) {
             NMPlatformIPXRoute rt;
             NMIPAddr           addrbin;
-            const char *       aip;
+            const char        *aip;
             gboolean           valid;
             int                prefix;
             guint32            rtable_coerced;
@@ -1715,11 +1716,10 @@ _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
                     continue;
             }
 
-            if (!ip_config) {
-                ip_config = nm_device_ip_config_new(NM_DEVICE(self), addr_family);
-                nm_ip_config_set_config_flags(ip_config,
-                                              NM_IP_CONFIG_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES,
-                                              0);
+            if (!l3cd) {
+                l3cd = nm_device_create_l3_config_data(NM_DEVICE(self), NM_IP_CONFIG_SOURCE_USER);
+                nm_l3_config_data_set_flags(l3cd,
+                                            NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES);
             }
 
             nm_utils_ipx_address_clear_host_address(addr_family, &addrbin, NULL, prefix);
@@ -1754,27 +1754,23 @@ _get_dev2_ip_config(NMDeviceWireGuard *self, int addr_family)
                 };
             }
 
-            nm_ip_config_add_route(ip_config, &rt.rx, NULL);
+            nm_l3_config_data_add_route(l3cd, addr_family, NULL, &rt.rx);
         }
     }
 
-    return g_steal_pointer(&ip_config);
+    if (!l3cd)
+        return NULL;
+
+    return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    gs_unref_object NMIPConfig *ip_config = NULL;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
 
-    ip_config = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family);
-
-    nm_device_set_dev2_ip_config(device, addr_family, ip_config);
-
-    return NM_DEVICE_CLASS(nm_device_wireguard_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    l3cd = _get_dev2_ip_config(NM_DEVICE_WIREGUARD(device), addr_family);
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, l3cd);
 }
 
 static guint32
@@ -1819,7 +1815,7 @@ _device_cleanup(NMDeviceWireGuard *self)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
@@ -1833,12 +1829,12 @@ device_state_changed(NMDevice *          device,
 /*****************************************************************************/
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     if (nm_streq(setting_name, NM_SETTING_WIREGUARD_SETTING_NAME)) {
         /* Most, but not all WireGuard settings can be reapplied. Whitelist.
@@ -1864,11 +1860,9 @@ can_reapply_change(NMDevice *  device,
 static void
 reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_new)
 {
-    NMDeviceWireGuard *       self         = NM_DEVICE_WIREGUARD(device);
-    NMDeviceWireGuardPrivate *priv         = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
-    gs_unref_object NMIPConfig *ip4_config = NULL;
-    gs_unref_object NMIPConfig *ip6_config = NULL;
-    NMDeviceState               state      = nm_device_get_state(device);
+    NMDeviceWireGuard        *self  = NM_DEVICE_WIREGUARD(device);
+    NMDeviceWireGuardPrivate *priv  = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
+    NMDeviceState             state = nm_device_get_state(device);
 
     NM_DEVICE_CLASS(nm_device_wireguard_parent_class)->reapply_connection(device, con_old, con_new);
 
@@ -1878,11 +1872,14 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
     }
 
     if (state >= NM_DEVICE_STATE_IP_CONFIG) {
-        ip4_config = _get_dev2_ip_config(self, AF_INET);
-        ip6_config = _get_dev2_ip_config(self, AF_INET6);
+        nm_auto_unref_l3cd const NML3ConfigData *l3cd_4 = NULL;
+        nm_auto_unref_l3cd const NML3ConfigData *l3cd_6 = NULL;
+
+        l3cd_4 = _get_dev2_ip_config(self, AF_INET);
+        l3cd_6 = _get_dev2_ip_config(self, AF_INET6);
 
-        nm_device_set_dev2_ip_config(device, AF_INET, ip4_config);
-        nm_device_set_dev2_ip_config(device, AF_INET6, ip6_config);
+        nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_READY, l3cd_4);
+        nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_READY, l3cd_6);
     }
 }
 
@@ -1893,7 +1890,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(device);
     NMSettingWireGuard *s_wg = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIREGUARD);
-    const NMPObject *   obj_wg;
+    const NMPObject    *obj_wg;
     const NMPObjectLnkWireGuard *olnk_wg;
     guint                        i;
 
@@ -1914,7 +1911,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 
     for (i = 0; i < olnk_wg->peers_len; i++) {
         nm_auto_unref_wgpeer NMWireGuardPeer *peer  = NULL;
-        const NMPWireGuardPeer *              ppeer = &olnk_wg->peers[i];
+        const NMPWireGuardPeer               *ppeer = &olnk_wg->peers[i];
 
         peer = nm_wireguard_peer_new();
 
@@ -1929,7 +1926,7 @@ update_connection(NMDevice *device, NMConnection *connection)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceWireGuard *       self = NM_DEVICE_WIREGUARD(object);
+    NMDeviceWireGuard        *self = NM_DEVICE_WIREGUARD(object);
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -1974,7 +1971,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDeviceWireGuard *       self = NM_DEVICE_WIREGUARD(object);
+    NMDeviceWireGuard        *self = NM_DEVICE_WIREGUARD(object);
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(self);
 
     nm_explicit_bzero(priv->lnk_curr.private_key, sizeof(priv->lnk_curr.private_key));
@@ -2007,9 +2004,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wireguard = {
 static void
 nm_device_wireguard_class_init(NMDeviceWireGuardClass *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->get_property = get_property;
     object_class->dispose      = dispose;
@@ -2025,7 +2022,7 @@ nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass)
     device_class->create_and_realize                            = create_and_realize;
     device_class->act_stage2_config                             = act_stage2_config;
     device_class->act_stage2_config_also_for_external_or_assume = TRUE;
-    device_class->act_stage3_ip_config_start                    = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config                          = act_stage3_ip_config;
     device_class->get_generic_capabilities                      = get_generic_capabilities;
     device_class->link_changed                                  = link_changed;
     device_class->update_connection                             = update_connection;
@@ -2069,11 +2066,11 @@ nm_device_wireguard_class_init(NMDeviceWireGuardClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WIREGUARD_DEVICE_FACTORY, NMWireGuardDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_WIREGUARD,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 9bc43a85..f7b712ea 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -14,7 +14,6 @@
 
 #include "nm-act-request.h"
 #include "nm-device-private.h"
-#include "nm-ip4-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-wpan.h"
@@ -39,11 +38,11 @@ G_DEFINE_TYPE(NMDeviceWpan, nm_device_wpan, NM_TYPE_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)
 {
     NMSettingWpan *s_wpan;
 
@@ -79,7 +78,7 @@ static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
     NMSettingWpan *s_wpan;
-    const char *   mac, *hw_addr;
+    const char    *mac, *hw_addr;
 
     if (!NM_DEVICE_CLASS(nm_device_wpan_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -104,17 +103,17 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWpan *        self = NM_DEVICE_WPAN(device);
-    NMSettingWpan *       s_wpan;
-    NMPlatform *          platform;
+    NMDeviceWpan         *self = NM_DEVICE_WPAN(device);
+    NMSettingWpan        *s_wpan;
+    NMPlatform           *platform;
     guint16               pan_id;
     guint16               short_address;
     gint16                page, channel;
     int                   ifindex;
-    const guint8 *        hwaddr;
+    const guint8         *hwaddr;
     gsize                 hwaddr_len = 0;
     const NMPlatformLink *lowpan_plink;
-    NMDevice *            lowpan_device = NULL;
+    NMDevice             *lowpan_device = NULL;
     NMActStageReturn      ret           = NM_ACT_STAGE_RETURN_FAILURE;
 
     platform = nm_device_get_platform(device);
@@ -203,7 +202,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wpan = {
 static void
 nm_device_wpan_class_init(NMDeviceWpanClass *klass)
 {
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wpan);
@@ -225,11 +224,11 @@ nm_device_wpan_class_init(NMDeviceWpanClass *klass)
     (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_WPAN_DEVICE_FACTORY, NMWpanDeviceFactory))
 
 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)
 {
     return g_object_new(NM_TYPE_DEVICE_WPAN,
                         NM_DEVICE_IFACE,
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index ea1b9647..696c0df8 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -34,22 +34,23 @@
 #include "nm-device-private.h"
 #include "nm-l3cfg.h"
 #include "nm-l3-config-data.h"
+#include "nm-l3-ipv4ll.h"
+#include "nm-l3-ipv6ll.h"
 #include "NetworkManagerUtils.h"
 #include "nm-manager.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nm-platform-utils.h"
 #include "libnm-platform/nmp-object.h"
-#include "libnm-platform/nmp-rules-manager.h"
+#include "libnm-platform/nmp-route-manager.h"
 #include "ndisc/nm-ndisc.h"
 #include "ndisc/nm-lndp-ndisc.h"
+
 #include "dhcp/nm-dhcp-manager.h"
 #include "dhcp/nm-dhcp-utils.h"
 #include "nm-act-request.h"
-#include "nm-proxy-config.h"
-#include "nm-ip4-config.h"
-#include "nm-ip6-config.h"
 #include "nm-pacrunner-manager.h"
 #include "dnsmasq/nm-dnsmasq-manager.h"
+#include "nm-ip-config.h"
 #include "nm-dhcp-config.h"
 #include "nm-rfkill-manager.h"
 #include "nm-firewall-utils.h"
@@ -66,7 +67,6 @@
 #include "nm-config.h"
 #include "c-list/src/c-list.h"
 #include "dns/nm-dns-manager.h"
-#include "nm-acd-manager.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-systemd-core/nm-sd.h"
 #include "nm-lldp-listener.h"
@@ -86,19 +86,7 @@
 
 #define DEFAULT_AUTOCONNECT TRUE
 
-static guint32
-dhcp_grace_period_from_timeout(guint32 timeout)
-{
-#define DHCP_GRACE_PERIOD_MULTIPLIER 2U
-
-    nm_assert(timeout > 0);
-    nm_assert(timeout < G_MAXINT32);
-
-    if (timeout < G_MAXUINT32 / DHCP_GRACE_PERIOD_MULTIPLIER)
-        return timeout * DHCP_GRACE_PERIOD_MULTIPLIER;
-
-    return G_MAXUINT32;
-}
+#define GRACE_PERIOD_MULTIPLIER 2U
 
 #define CARRIER_WAIT_TIME_MS           6000
 #define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
@@ -117,6 +105,13 @@ typedef enum {
     CLEANUP_TYPE_DECONFIGURE,
 } CleanupType;
 
+typedef enum _nm_packed {
+    ADDR_METHOD_STATE_DISABLED,
+    ADDR_METHOD_STATE_PENDING,
+    ADDR_METHOD_STATE_GOOD,
+    ADDR_METHOD_STATE_FAILED,
+} AddrMethodState;
+
 typedef struct {
     CList     lst_slave;
     NMDevice *slave;
@@ -131,36 +126,72 @@ typedef struct {
 } DeleteOnDeactivateData;
 
 typedef struct {
-    NMDevice *              device;
-    GCancellable *          cancellable;
+    NMDevice               *device;
+    GCancellable           *cancellable;
     NMPlatformAsyncCallback callback;
     gpointer                callback_data;
     guint                   num_vfs;
     NMOptionBool            autoprobe;
 } SriovOp;
 
-typedef void (*AcdCallback)(NMDevice *, NMIP4Config **, gboolean);
-
 typedef enum {
     /* The various NML3ConfigData types that we track explicitly. Note that
      * their relative order matters: higher numbers in this enum means more
-     * important (and during merge overwrites other settings). */
+     * important (and during merge overwrites other settings). This is passed
+     * as priority to nm_l3cfg_add_config(). */
+
     L3_CONFIG_DATA_TYPE_LL_4,
+    L3_CONFIG_DATA_TYPE_LL_6,
+
+#define L3_CONFIG_DATA_TYPE_LL_X(IS_IPv4) \
+    ((IS_IPv4) ? L3_CONFIG_DATA_TYPE_LL_4 : L3_CONFIG_DATA_TYPE_LL_6)
+
     L3_CONFIG_DATA_TYPE_AC_6,
+    L3_CONFIG_DATA_TYPE_PD_6,
+
     L3_CONFIG_DATA_TYPE_DHCP_4,
     L3_CONFIG_DATA_TYPE_DHCP_6,
-    L3_CONFIG_DATA_TYPE_DEV_4,
-    L3_CONFIG_DATA_TYPE_DEV_6,
-    L3_CONFIG_DATA_TYPE_SETTING,
+
+#define L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4) \
+    ((IS_IPv4) ? L3_CONFIG_DATA_TYPE_DHCP_4 : L3_CONFIG_DATA_TYPE_DHCP_6)
+
+    L3_CONFIG_DATA_TYPE_SHARED_4,
+    L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC,
+    L3_CONFIG_DATA_TYPE_DEVIP_4,
+    L3_CONFIG_DATA_TYPE_DEVIP_6,
+
+#define L3_CONFIG_DATA_TYPE_DEVIP(addr_family)     \
+    ({                                             \
+        L3ConfigDataType _t;                       \
+                                                   \
+        switch (addr_family) {                     \
+        case AF_INET:                              \
+            _t = L3_CONFIG_DATA_TYPE_DEVIP_4;      \
+            break;                                 \
+        case AF_INET6:                             \
+            _t = L3_CONFIG_DATA_TYPE_DEVIP_6;      \
+            break;                                 \
+        default:                                   \
+            nm_assert_not_reached();               \
+            /* fall-through */                     \
+        case AF_UNSPEC:                            \
+            _t = L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC; \
+            break;                                 \
+        }                                          \
+                                                   \
+        _t;                                        \
+    })
+
+    L3_CONFIG_DATA_TYPE_MANUALIP,
+
     _L3_CONFIG_DATA_TYPE_NUM,
     _L3_CONFIG_DATA_TYPE_NONE,
+    _L3_CONFIG_DATA_TYPE_ACD_ONLY,
 } L3ConfigDataType;
 
-typedef struct {
-    AcdCallback   callback;
-    NMDevice *    device;
-    NMIP4Config **configs;
-} AcdData;
+G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_IPV4LL == L3_CONFIG_DATA_TYPE_LL_4);
+G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_IPV6LL == L3_CONFIG_DATA_TYPE_LL_6);
+G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_VPN == L3_CONFIG_DATA_TYPE_DEVIP_6);
 
 typedef enum {
     HW_ADDR_TYPE_UNSET = 0,
@@ -177,26 +208,81 @@ typedef enum {
 } FirewallState;
 
 typedef struct {
-    NMIPConfig *orig;    /* the original configuration applied to the device */
-    NMIPConfig *current; /* configuration after external changes.  NULL means
-                          * that the original configuration didn't change. */
-} AppliedConfig;
+    NMIPConfig *ip_config;
+} L3IPData;
 
 typedef struct {
-    NMDhcpClient *client;
-    NMDhcpConfig *config;
-    gulong        notify_sigid;
-    guint         grace_id;
-    bool          grace_pending : 1;
-    bool          was_active : 1;
-} DhcpData;
+    GSource *check_async_source;
+    GSource *req_timeout_source;
+    union {
+        const NMDeviceIPState state;
+        NMDeviceIPState       state_;
+    };
+    bool wait_for_carrier : 1;
+    bool wait_for_ports : 1;
+    bool is_disabled : 1;
+    bool is_ignore : 1;
+    bool do_reapply : 1;
+} IPStateData;
+
+typedef struct {
+    NMDhcpClient   *client;
+    NMDhcpConfig   *config;
+    gulong          notify_sigid;
+    NMDeviceIPState state;
+    union {
+        struct {
+        } v4;
+        struct {
+            guint            needed_prefixes;
+            NMNDiscDHCPLevel mode;
+        } v6;
+    };
+} IPDhcpStateData;
+
+typedef struct {
+    NMDeviceIPState     state;
+    NMDeviceStateReason failed_reason;
+} IPDevStateData;
+
+typedef struct {
+    NMDeviceIPState state;
+    union {
+        struct {
+            NMDnsMasqManager      *dnsmasq_manager;
+            NMNetnsSharedIPHandle *shared_ip_handle;
+            NMFirewallConfig      *firewall_config;
+            gulong                 dnsmasq_state_id;
+            const NML3ConfigData  *l3cd;
+        } v4;
+        struct {
+        } v6;
+    };
+} IPSharedStateData;
+
+typedef struct {
+    NMDeviceIPState state;
+    union {
+        struct {
+            NML3IPv4LL             *ipv4ll;
+            NML3IPv4LLRegistration *ipv4ll_registation;
+            GSource                *timeout_source;
+        } v4;
+        struct {
+            NML3IPv6LL     *ipv6ll;
+            GSource        *retry_source;
+            NML3IPv6LLState llstate;
+            struct in6_addr lladdr;
+        } v6;
+    };
+} IPLLStateData;
 
 struct _NMDeviceConnectivityHandle {
     CList                        concheck_lst;
-    NMDevice *                   self;
+    NMDevice                    *self;
     NMDeviceConnectivityCallback callback;
     gpointer                     user_data;
-    NMConnectivityCheckHandle *  c_handle;
+    NMConnectivityCheckHandle   *c_handle;
     guint64                      seq;
     bool                         is_periodic : 1;
     bool                         is_periodic_bump : 1;
@@ -209,8 +295,8 @@ typedef struct {
     NMEthtoolFeatureStates *features;
     NMOptionBool            requested[_NM_ETHTOOL_ID_FEATURE_NUM];
     NMEthtoolCoalesceState *coalesce;
-    NMEthtoolRingState *    ring;
-    NMEthtoolPauseState *   pause;
+    NMEthtoolRingState     *ring;
+    NMEthtoolPauseState    *pause;
 } EthtoolState;
 
 typedef enum {
@@ -223,8 +309,8 @@ typedef struct {
     ResolverState state;
     GInetAddress *address;
     GCancellable *cancellable;
-    char *        hostname;
-    NMDevice *    device;
+    char         *hostname;
+    NMDevice     *device;
     guint         timeout_id; /* Used when waiting for the address */
     int           addr_family;
 } HostnameResolver;
@@ -234,14 +320,14 @@ typedef struct {
 enum {
     STATE_CHANGED,
     AUTOCONNECT_ALLOWED,
-    IP4_CONFIG_CHANGED,
-    IP6_CONFIG_CHANGED,
+    L3CD_CHANGED,
     IP6_PREFIX_DELEGATED,
     IP6_SUBNET_NEEDED,
     REMOVED,
     RECHECK_AUTO_ACTIVATE,
     RECHECK_ASSUME,
     DNS_LOOKUP_DONE,
+    PLATFORM_ADDRESS_CHANGED,
     LAST_SIGNAL,
 };
 static guint signals[LAST_SIGNAL] = {0};
@@ -260,6 +346,9 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDevice,
                              PROP_IP4_ADDRESS,
                              PROP_IP4_CONFIG,
                              PROP_DHCP4_CONFIG,
+
+#define PROP_DHCPX_CONFIG(IS_IPv4) ((IS_IPv4) ? PROP_DHCP4_CONFIG : PROP_DHCP6_CONFIG)
+
                              PROP_IP6_CONFIG,
                              PROP_DHCP6_CONFIG,
                              PROP_STATE,
@@ -294,8 +383,6 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDevice,
                              PROP_PORTS, );
 
 typedef struct _NMDevicePrivate {
-    bool in_state_changed;
-
     guint device_link_changed_id;
     guint device_ip_link_changed_id;
 
@@ -309,22 +396,12 @@ typedef struct _NMDevicePrivate {
         NMDeviceStateReason reason;
     } queued_state;
 
-    union {
-        struct {
-            guint queued_ip_config_id_6;
-            guint queued_ip_config_id_4;
-        };
-        guint queued_ip_config_id_x[2];
-    };
-
     struct {
         const char **arr;
         guint        len;
         guint        alloc;
     } pending_actions;
 
-    GSList *dad6_failed_addrs;
-
     NMDBusTrackObjPath parent_device;
 
     char *udi;
@@ -332,23 +409,41 @@ typedef struct _NMDevicePrivate {
 
     union {
         const char *const iface;
-        char *            iface_;
+        char             *iface_;
     };
     union {
         const char *const ip_iface;
-        char *            ip_iface_;
+        char             *ip_iface_;
     };
 
     union {
+        NML3Cfg *const l3cfg;
+        NML3Cfg       *l3cfg_;
+    };
+
+    union {
+        struct {
+            L3IPData l3ipdata_6;
+            L3IPData l3ipdata_4;
+        };
+        L3IPData l3ipdata_x[2];
+    };
+
+    NML3CfgCommitTypeHandle *l3cfg_commit_type;
+
+    union {
         const int ifindex;
         int       ifindex_;
     };
+
     union {
         const int ip_ifindex;
         int       ip_ifindex_;
     };
 
-    NMNetnsSharedIPHandle *shared_ip_handle;
+    union {
+        const NML3ConfigData *d;
+    } l3cds[_L3_CONFIG_DATA_TYPE_NUM];
 
     int parent_ifindex;
 
@@ -371,22 +466,21 @@ typedef struct _NMDevicePrivate {
 
     bool real : 1;
 
-    bool update_ip_config_completed_v4 : 1;
-    bool update_ip_config_completed_v6 : 1;
-
     NMDeviceType         type;
-    char *               type_desc;
+    char                *type_desc;
     NMLinkType           link_type;
     NMDeviceCapabilities capabilities;
-    char *               driver;
-    char *               driver_version;
-    char *               firmware_version;
+    char                *driver;
+    char                *driver_version;
+    char                *firmware_version;
     RfKillType           rfkill_type;
     bool                 firmware_missing : 1;
     bool                 nm_plugin_missing : 1;
     bool
         hw_addr_perm_fake : 1; /* whether the permanent HW address could not be read and is a fake */
 
+    guint8 in_state_changed : 4;
+
     NMUtilsStableType current_stable_id_type : 3;
 
     bool nm_owned : 1; /* whether the device is a device owned and created by NM */
@@ -397,10 +491,10 @@ typedef struct _NMDevicePrivate {
     guint64 udi_id;
 
     GHashTable *available_connections;
-    char *      hw_addr;
-    char *      hw_addr_perm;
-    char *      hw_addr_initial;
-    char *      physical_port_id;
+    char       *hw_addr;
+    char       *hw_addr_perm;
+    char       *hw_addr_initial;
+    char       *physical_port_id;
     guint       dev_id;
 
     NMUnmanagedFlags unmanaged_mask;
@@ -410,25 +504,12 @@ typedef struct _NMDevicePrivate {
 
     GCancellable *deactivating_cancellable;
 
-    NMActRequest *     queued_act_request;
+    NMActRequest      *queued_act_request;
     bool               queued_act_request_is_waiting_for_carrier : 1;
     NMDBusTrackObjPath act_request;
 
-    union {
-        struct {
-            guint activation_source_id_6;
-            guint activation_source_id_4; /* for layer2 and IPv4. */
-        };
-        guint activation_source_id_x[2];
-    };
-
-    union {
-        struct {
-            ActivationHandleFunc activation_source_func_6;
-            ActivationHandleFunc activation_source_func_4; /* for layer2 and IPv4. */
-        };
-        ActivationHandleFunc activation_source_func_x[2];
-    };
+    GSource             *activation_idle_source;
+    ActivationHandleFunc activation_func;
 
     guint recheck_assume_id;
 
@@ -452,7 +533,7 @@ typedef struct _NMDevicePrivate {
     gulong            config_changed_id;
     gulong            ifindex_changed_id;
     guint32           mtu;
-    guint32           ip6_mtu;
+    guint32           ip6_mtu; /* FIXME(l3cfg) */
     guint32           mtu_initial;
     guint32           ip6_mtu_initial;
     NMDeviceMtuSource mtu_source;
@@ -469,6 +550,14 @@ typedef struct _NMDevicePrivate {
     gint64 carrier_wait_until_ms;
 
     union {
+        struct {
+            NML3ConfigMergeFlags l3config_merge_flags_6;
+            NML3ConfigMergeFlags l3config_merge_flags_4;
+        };
+        NML3ConfigMergeFlags l3config_merge_flags_x[2];
+    };
+
+    union {
         const NMDeviceSysIfaceState sys_iface_state;
         NMDeviceSysIfaceState       sys_iface_state_;
     };
@@ -478,12 +567,6 @@ typedef struct _NMDevicePrivate {
 
     bool up : 1; /* IFF_UP */
 
-    bool v4_commit_first_time : 1;
-    bool v6_commit_first_time : 1;
-
-    bool default_route_metric_penalty_ip4_has : 1;
-    bool default_route_metric_penalty_ip6_has : 1;
-
     bool v4_route_table_initialized : 1;
     bool v6_route_table_initialized : 1;
 
@@ -496,165 +579,115 @@ typedef struct _NMDevicePrivate {
 
     bool is_enslaved : 1;
 
-    bool ipv6ll_handle : 1; /* TRUE if NM handles the device's IPv6LL address */
-    bool ipv6ll_has : 1;
-    bool ndisc_started : 1;
     bool device_link_changed_down : 1;
 
     bool concheck_rp_filter_checked : 1;
 
-    NMDeviceStageState stage1_sriov_state : 3;
-
-    bool ip_config_started : 1;
     bool tc_committed : 1;
 
-    char *current_stable_id;
+    NMDeviceStageState stage1_sriov_state : 3;
 
-    union {
-        struct {
-            GSource *ip_req_timeout_source_6;
-            GSource *ip_req_timeout_source_4;
-        };
-        GSource *ip_req_timeout_source_x[2];
-    };
+    char *current_stable_id;
 
-    /* Proxy Configuration */
-    NMProxyConfig *    proxy_config;
     NMPacrunnerConfId *pacrunner_conf_id;
 
-    /* IP configuration info. Combined config from VPN, settings, and device */
+    struct {
+        union {
+            const NMDeviceIPState state;
+            NMDeviceIPState       state_;
+        };
+    } ip_data;
+
     union {
         struct {
-            NMIP6Config *ip_config_6;
-            NMIP4Config *ip_config_4;
+            IPStateData ip_data_6;
+            IPStateData ip_data_4;
         };
-        NMIPConfig *ip_config_x[2];
+        IPStateData ip_data_x[2];
     };
 
-    /* Config from DHCP, PPP, LLv4, etc */
-    AppliedConfig dev_ip_config_4;
+    struct {
+        GSource *carrier_timeout;
+        union {
+            struct {
+                NMDeviceIPState state_6;
+                NMDeviceIPState state_4;
+            };
+            NMDeviceIPState state_x[2];
+        };
+        bool carrier_timeout_expired;
+    } ipmanual_data;
 
-    /* config from the setting */
     union {
         struct {
-            NMIP6Config *con_ip_config_6;
-            NMIP4Config *con_ip_config_4;
+            IPDhcpStateData ipdhcp_data_6;
+            IPDhcpStateData ipdhcp_data_4;
         };
-        NMIPConfig *con_ip_config_x[2];
+        IPDhcpStateData ipdhcp_data_x[2];
     };
 
-    /* Stuff added outside NM */
+    struct {
+        NMNDisc              *ndisc;
+        GSource              *ndisc_grace_source;
+        gulong                ndisc_changed_id;
+        gulong                ndisc_timeout_id;
+        NMDeviceIPState       state;
+        const NML3ConfigData *l3cd;
+    } ipac6_data;
+
     union {
         struct {
-            NMIP6Config *ext_ip_config_6;
-            NMIP4Config *ext_ip_config_4;
+            IPLLStateData ipll_data_6;
+            IPLLStateData ipll_data_4;
         };
-        NMIPConfig *ext_ip_config_x[2];
+        IPLLStateData ipll_data_x[2];
     };
 
-    /* VPNs which use this device */
     union {
         struct {
-            GSList *vpn_configs_6;
-            GSList *vpn_configs_4;
+            IPSharedStateData ipshared_data_6;
+            IPSharedStateData ipshared_data_4;
         };
-        GSList *vpn_configs_x[2];
+        IPSharedStateData ipshared_data_x[2];
     };
 
-    /* Extra device configuration, injected by the subclass of NMDevice.
-     * This is used for example by NMDeviceModem for WWAN configuration. */
     union {
         struct {
-            AppliedConfig dev2_ip_config_6;
-            AppliedConfig dev2_ip_config_4;
+            IPDevStateData ipdev_data_6;
+            IPDevStateData ipdev_data_4;
         };
-        AppliedConfig dev2_ip_config_x[2];
+        IPDevStateData ipdev_data_x[2];
     };
 
-    /* DHCPv4 tracking */
-    struct {
-        char *pac_url;
-    } dhcp4;
+    IPDevStateData ipdev_data_unspec;
 
     struct {
-        /* IP6 config from DHCP */
-        AppliedConfig ip6_config;
-        /* Event ID of the current IP6 config from DHCP */
-        char *           event_id;
-        NMNDiscDHCPLevel mode;
-        guint            needed_prefixes;
-    } dhcp6;
+        /* If we set the addrgenmode6, this records the previously set value. */
+        guint8 previous_mode_val;
 
-    union {
-        struct {
-            DhcpData dhcp_data_6;
-            DhcpData dhcp_data_4;
-        };
-        DhcpData dhcp_data_x[2];
-    };
+        /* whether @previous_mode_val is set. */
+        bool previous_mode_has : 1;
+    } addrgenmode6_data;
 
     struct {
         NMLogDomain log_domain;
         guint       timeout;
         guint       watch;
         GPid        pid;
-        char *      binary;
-        char *      address;
+        char       *binary;
+        char       *address;
         guint       deadline;
     } gw_ping;
 
-    /* dnsmasq stuff for shared connections */
-    NMDnsMasqManager *dnsmasq_manager;
-    gulong            dnsmasq_state_id;
-
     /* Firewall */
     FirewallState             fw_state : 4;
-    NMFirewalldManager *      fw_mgr;
+    NMFirewalldManager       *fw_mgr;
     NMFirewalldManagerCallId *fw_call;
 
-    /* IPv4LL stuff */
-    sd_ipv4ll *ipv4ll;
-    guint      ipv4ll_timeout;
-    guint      rt6_temporary_not_available_id;
-
-    /* IPv4 DAD stuff */
-    struct {
-        GSList *      dad_list;
-        NMAcdManager *announcing;
-    } acd;
-
-    union {
-        struct {
-            const NMDeviceIPState ip_state_6;
-            const NMDeviceIPState ip_state_4;
-        };
-        union {
-            const NMDeviceIPState ip_state_x[2];
-            NMDeviceIPState       ip_state_x_[2];
-        };
-    };
-
-    AppliedConfig   ac_ip6_config;           /* config from IPv6 autoconfiguration */
-    NMIP6Config *   ext_ip6_config_captured; /* Configuration captured from platform. */
-    NMIP6Config *   dad6_ip6_config;
-    struct in6_addr ipv6ll_addr;
-
-    GHashTable *rt6_temporary_not_available;
-
-    NMNDisc *                 ndisc;
-    gulong                    ndisc_changed_id;
-    gulong                    ndisc_timeout_id;
-    NMSettingIP6ConfigPrivacy ndisc_use_tempaddr;
-
-    guint  linklocal6_timeout_id;
-    guint8 linklocal6_dad_counter;
-
     GHashTable *ip6_saved_properties;
 
     EthtoolState *ethtool_state;
 
-    gboolean needs_ip6_subnet;
-
     /* master interface for bridge/bond/team slave */
     NMDevice *master;
     gulong    master_ready_id;
@@ -666,7 +699,7 @@ typedef struct _NMDevicePrivate {
     NMMetered metered;
 
     NMSettings *settings;
-    NMManager * manager;
+    NMManager  *manager;
 
     NMNetns *netns;
 
@@ -710,9 +743,12 @@ typedef struct _NMDevicePrivate {
 
     bool mtu_force_set_done : 1;
 
+    bool needs_ip6_subnet : 1;
+
     NMOptionBool promisc_reset;
 
     GVariant *ports_variant; /* Array of port devices D-Bus path */
+    char     *prop_ip_iface; /* IP interface D-Bus property */
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -724,58 +760,83 @@ G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
 static const NMDBusInterfaceInfoExtended interface_info_device;
 static const GDBusSignalInfo             signal_info_state_changed;
 
-static void nm_device_set_proxy_config(NMDevice *self, const char *pac_url);
-
-static gboolean update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs);
+static void _dev_l3_cfg_commit(NMDevice *self, gboolean do_sync);
 
-static gboolean nm_device_set_ip_config(NMDevice *  self,
-                                        int         addr_family,
-                                        NMIPConfig *config,
-                                        gboolean    commit,
-                                        GPtrArray * ip4_dev_route_blacklist);
-
-static gboolean ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit);
+static void _dev_l3_cfg_commit_type_reset(NMDevice *self);
 
 static gboolean nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure);
 static void     nm_device_slave_notify_enslave(NMDevice *self, gboolean success);
 static void     nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason);
 
-static void     addrconf6_start_with_link_ready(NMDevice *self);
-static gboolean linklocal6_start(NMDevice *self);
+static void _dev_ipll6_start(NMDevice *self);
 
-static guint32 default_route_metric_penalty_get(NMDevice *self, int addr_family);
+static void _dev_ipac6_start_continue(NMDevice *self);
+static void _dev_ipac6_ndisc_set_router_config(NMDevice *self);
 
-static guint _prop_get_ipv4_dad_timeout(NMDevice *self);
+static guint32 _dev_default_route_metric_penalty_get(NMDevice *self, int addr_family);
 
-static NMIP6Config *dad6_get_pending_addresses(NMDevice *self);
+static guint32 _prop_get_ipv4_dad_timeout(NMDevice *self);
 
 static void   _carrier_wait_check_queued_act_request(NMDevice *self);
 static gint64 _get_carrier_wait_ms(NMDevice *self);
 
+static GBytes *_prop_get_ipv6_dhcp_duid(NMDevice     *self,
+                                        NMConnection *connection,
+                                        GBytes       *hwaddr,
+                                        gboolean     *out_enforce);
+
 static const char *_activation_func_to_string(ActivationHandleFunc func);
 
 static void
 _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting);
-static void             queued_state_clear(NMDevice *device);
-static gboolean         queued_ip4_config_change(gpointer user_data);
-static gboolean         queued_ip6_config_change(gpointer user_data);
-static void             ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data);
-static gboolean         ip_config_valid(NMDeviceState state);
-static NMActStageReturn dhcp4_start(NMDevice *self);
-static gboolean         dhcp6_start(NMDevice *self, gboolean wait_for_ll);
-static void             nm_device_start_ip_check(NMDevice *self);
-static void             realize_start_setup(NMDevice *            self,
-                                            const NMPlatformLink *plink,
-                                            gboolean              assume_state_guess_assume,
-                                            const char *          assume_state_connection_uuid,
-                                            gboolean              set_nm_owned,
-                                            NMUnmanFlagOp         unmanaged_user_explicit,
-                                            gboolean              force_platform_init);
-static void             _set_mtu(NMDevice *self, guint32 mtu);
-static void             _commit_mtu(NMDevice *self, const NMIP4Config *config);
-static void             _cancel_activation(NMDevice *self);
-
-static void concheck_update_state(NMDevice *          self,
+static void queued_state_clear(NMDevice *device);
+static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data);
+static void nm_device_start_ip_check(NMDevice *self);
+static void realize_start_setup(NMDevice             *self,
+                                const NMPlatformLink *plink,
+                                gboolean              assume_state_guess_assume,
+                                const char           *assume_state_connection_uuid,
+                                gboolean              set_nm_owned,
+                                NMUnmanFlagOp         unmanaged_user_explicit,
+                                gboolean              force_platform_init);
+static void _set_mtu(NMDevice *self, guint32 mtu);
+static void _commit_mtu(NMDevice *self);
+static void _cancel_activation(NMDevice *self);
+
+static void _dev_ipll4_notify_event(NMDevice *self);
+
+static void _dev_ip_state_check(NMDevice *self, int addr_family);
+
+static void _dev_ipmanual_check_ready(NMDevice *self);
+
+static void
+_dev_ipdhcpx_cleanup(NMDevice *self, int addr_family, gboolean reset_dhcp_config, gboolean release);
+
+static void _dev_ip_state_check_async(NMDevice *self, int addr_family);
+
+static void _dev_ipdhcpx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state);
+
+static void _dev_ipdhcpx_restart(NMDevice *self, int addr_family, gboolean release);
+
+static gboolean
+_dev_ipac6_grace_period_start(NMDevice *self, guint32 timeout_sec, gboolean force_restart);
+
+static void _dev_ipac6_start(NMDevice *self);
+
+static void _dev_ipac6_set_state(NMDevice *self, NMDeviceIPState state);
+
+static void
+_dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, gboolean now);
+
+static void _dev_ipshared4_start(NMDevice *self);
+static void _dev_ipshared4_spawn_dnsmasq(NMDevice *self);
+
+static void _dev_ipshared6_start(NMDevice *self);
+
+static void
+_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply);
+
+static void concheck_update_state(NMDevice           *self,
                                   int                 addr_family,
                                   NMConnectivityState state,
                                   gboolean            is_periodic);
@@ -783,8 +844,80 @@ static void concheck_update_state(NMDevice *          self,
 static void sriov_op_cb(GError *error, gpointer user_data);
 
 static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
-static gboolean device_link_changed(NMDevice *self);
-static void     check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update);
+static gboolean device_link_changed(gpointer user_data);
+static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
+
+/*****************************************************************************/
+
+#define _NMLOG_addr_family(level, prefix, addr_family, fmt, ...)                             \
+    G_STMT_START                                                                             \
+    {                                                                                        \
+        const int _addr_family2 = (addr_family);                                             \
+                                                                                             \
+        _NMLOG(level,                                                                        \
+               (_addr_family2 == AF_UNSPEC ? LOGD_IP : LOGD_IPX(NM_IS_IPv4(_addr_family2))), \
+               "" prefix "%s: " fmt,                                                         \
+               nm_utils_addr_family_to_str(_addr_family2),                                   \
+               ##__VA_ARGS__);                                                               \
+    }                                                                                        \
+    G_STMT_END
+
+#define _NMLOG_ip(level, ...) _NMLOG_addr_family(level, "ip", __VA_ARGS__)
+#define _LOGT_ip(...)         _NMLOG_ip(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ip(...)         _NMLOG_ip(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ip(...)         _NMLOG_ip(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ip(...)         _NMLOG_ip(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipll(level, ...) _NMLOG_addr_family(level, "ip:ll", __VA_ARGS__)
+#define _LOGT_ipll(...)         _NMLOG_ipll(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipll(...)         _NMLOG_ipll(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipll(...)         _NMLOG_ipll(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipll(...)         _NMLOG_ipll(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipdev(level, ...) _NMLOG_addr_family(level, "ip:dev", __VA_ARGS__)
+#define _LOGT_ipdev(...)         _NMLOG_ipdev(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipdev(...)         _NMLOG_ipdev(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipdev(...)         _NMLOG_ipdev(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipdev(...)         _NMLOG_ipdev(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipdhcp(level, ...) _NMLOG_addr_family(level, "ip:dhcp", __VA_ARGS__)
+#define _LOGT_ipdhcp(...)         _NMLOG_ipdhcp(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipdhcp(...)         _NMLOG_ipdhcp(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipdhcp(...)         _NMLOG_ipdhcp(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipdhcp(...)         _NMLOG_ipdhcp(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipshared(level, ...) _NMLOG_addr_family(level, "ip:shared", __VA_ARGS__)
+#define _LOGT_ipshared(...)         _NMLOG_ipshared(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipshared(...)         _NMLOG_ipshared(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipshared(...)         _NMLOG_ipshared(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipshared(...)         _NMLOG_ipshared(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipac6(level, ...) _NMLOG_addr_family(level, "ip:ac6", AF_UNSPEC, __VA_ARGS__)
+#define _LOGT_ipac6(...)         _NMLOG_ipac6(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipac6(...)         _NMLOG_ipac6(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipac6(...)         _NMLOG_ipac6(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipac6(...)         _NMLOG_ipac6(LOGL_WARN, __VA_ARGS__)
+
+#define _NMLOG_ipmanual(level, ...) _NMLOG_addr_family(level, "ip:manual", __VA_ARGS__)
+#define _LOGT_ipmanual(...)         _NMLOG_ipmanual(LOGL_TRACE, __VA_ARGS__)
+#define _LOGD_ipmanual(...)         _NMLOG_ipmanual(LOGL_DEBUG, __VA_ARGS__)
+#define _LOGI_ipmanual(...)         _NMLOG_ipmanual(LOGL_INFO, __VA_ARGS__)
+#define _LOGW_ipmanual(...)         _NMLOG_ipmanual(LOGL_WARN, __VA_ARGS__)
+
+/*****************************************************************************/
+
+#define _CACHED_BOOL(cached_value, cmd)                  \
+    ({                                                   \
+        NMTernary *const _cached_value = (cached_value); \
+                                                         \
+        nm_assert(_cached_value);                        \
+        nm_assert_is_ternary(*_cached_value);            \
+                                                         \
+        if (*_cached_value == NM_TERNARY_DEFAULT)        \
+            *_cached_value = !!(cmd);                    \
+                                                         \
+        !!(*_cached_value);                              \
+    })
 
 /*****************************************************************************/
 
@@ -803,6 +936,38 @@ _hostname_resolver_free(HostnameResolver *resolver)
 
 /*****************************************************************************/
 
+/**
+ * Update the "ip_iface" property when something changes (device
+ * state, ifindex) and emit a notify signal if needed. Note that
+ * the property must be NULL for devices without an ifindex and
+ * when the device is not activated. This behavior is part of the
+ * API and should not be changed.
+ */
+static void
+update_prop_ip_iface(NMDevice *self)
+{
+    NMDevicePrivate *priv     = NM_DEVICE_GET_PRIVATE(self);
+    const char      *ip_iface = NULL;
+    gs_free char    *to_free  = NULL;
+
+    if (nm_device_get_ip_ifindex(self) > 0
+        && (priv->state == NM_DEVICE_STATE_UNMANAGED
+            || (priv->state >= NM_DEVICE_STATE_IP_CHECK
+                && priv->state <= NM_DEVICE_STATE_DEACTIVATING))) {
+        ip_iface = nm_utils_str_utf8safe_escape(nm_device_get_ip_iface(self),
+                                                NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+                                                &to_free);
+    }
+
+    if (!nm_streq0(priv->prop_ip_iface, ip_iface)) {
+        g_free(priv->prop_ip_iface);
+        priv->prop_ip_iface = to_free ? g_steal_pointer(&to_free) : g_strdup(ip_iface);
+        _notify(self, PROP_IP_IFACE);
+    }
+}
+
+/*****************************************************************************/
+
 static NMSettingIP6ConfigPrivacy
 _ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr)
 {
@@ -819,8 +984,8 @@ _ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr)
 /*****************************************************************************/
 
 static const char *
-_prop_get_connection_stable_id(NMDevice *         self,
-                               NMConnection *     connection,
+_prop_get_connection_stable_id(NMDevice          *self,
+                               NMConnection      *connection,
                                NMUtilsStableType *out_stable_type)
 {
     NMDevicePrivate *priv;
@@ -839,13 +1004,13 @@ _prop_get_connection_stable_id(NMDevice *         self,
      * Especially with ${RANDOM} stable-id we want to generate *one* configuration
      * for each activation. */
     if (G_UNLIKELY(!priv->current_stable_id)) {
-        gs_free char *       generated = NULL;
+        gs_free char        *generated = NULL;
         NMUtilsStableType    stable_type;
         NMSettingConnection *s_con;
         gboolean             hwaddr_is_fake;
-        const char *         hwaddr;
-        const char *         stable_id;
-        const char *         uuid;
+        const char          *hwaddr;
+        const char          *stable_id;
+        const char          *uuid;
 
         s_con = nm_connection_get_setting_connection(connection);
 
@@ -907,18 +1072,18 @@ _prop_get_connection_stable_id(NMDevice *         self,
 }
 
 static GBytes *
-_prop_get_ipv6_dhcp_duid(NMDevice *    self,
+_prop_get_ipv6_dhcp_duid(NMDevice     *self,
                          NMConnection *connection,
-                         GBytes *      hwaddr,
-                         gboolean *    out_enforce)
+                         GBytes       *hwaddr,
+                         gboolean     *out_enforce)
 {
     NMSettingIPConfig *s_ip6;
-    const char *       duid;
-    const char *       duid_error;
-    GBytes *           duid_out;
+    const char        *duid;
+    const char        *duid_error;
+    GBytes            *duid_out;
     gboolean           duid_enforce = TRUE;
-    gs_free char *     logstr1      = NULL;
-    const guint8 *     hwaddr_bin;
+    gs_free char      *logstr1      = NULL;
+    const guint8      *hwaddr_bin;
     gsize              hwaddr_len;
     int                arp_type;
 
@@ -979,12 +1144,12 @@ _prop_get_ipv6_dhcp_duid(NMDevice *    self,
          *
          * Implementations that are added later, should use a distinct salt instead,
          * like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
-        const guint32         DEFAULT_SALT   = 670531087u;
-        nm_auto_free_checksum GChecksum *sum = NULL;
+        const guint32                    DEFAULT_SALT = 670531087u;
+        nm_auto_free_checksum GChecksum *sum          = NULL;
         NMUtilsStableType                stable_type;
-        const char *                     stable_id = NULL;
+        const char                      *stable_id = NULL;
         guint32                          salted_header;
-        const guint8 *                   host_id;
+        const guint8                    *host_id;
         gsize                            host_id_len;
         union {
             guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256];
@@ -1157,7 +1322,7 @@ _prop_get_ipv6_ra_timeout(NMDevice *self)
 static NMSettingConnectionMdns
 _prop_get_connection_mdns(NMDevice *self)
 {
-    NMConnection *          connection;
+    NMConnection           *connection;
     NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_MDNS_DEFAULT);
@@ -1179,7 +1344,7 @@ _prop_get_connection_mdns(NMDevice *self)
 static NMSettingConnectionLlmnr
 _prop_get_connection_llmnr(NMDevice *self)
 {
-    NMConnection *           connection;
+    NMConnection            *connection;
     NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_LLMNR_DEFAULT);
@@ -1201,7 +1366,7 @@ _prop_get_connection_llmnr(NMDevice *self)
 static NMSettingConnectionDnsOverTls
 _prop_get_connection_dns_over_tls(NMDevice *self)
 {
-    NMConnection *                connection;
+    NMConnection                 *connection;
     NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
@@ -1224,14 +1389,14 @@ _prop_get_connection_dns_over_tls(NMDevice *self)
 static guint32
 _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 {
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
-    NMDeviceClass *      klass;
-    NMConnection *       connection;
-    NMSettingIPConfig *  s_ip;
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDeviceClass       *klass;
+    NMConnection        *connection;
+    NMSettingIPConfig   *s_ip;
     guint32              route_table    = 0;
     gboolean             is_user_config = TRUE;
     NMSettingConnection *s_con;
-    NMSettingVrf *       s_vrf;
+    NMSettingVrf        *s_vrf;
 
     nm_assert_addr_family(addr_family);
 
@@ -1311,8 +1476,8 @@ _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 static gboolean
 _prop_get_connection_lldp(NMDevice *self)
 {
-    NMConnection *          connection;
-    NMSettingConnection *   s_con;
+    NMConnection           *connection;
+    NMSettingConnection    *s_con;
     NMSettingConnectionLldp lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT;
 
     connection = nm_device_get_applied_connection(self);
@@ -1335,10 +1500,10 @@ _prop_get_connection_lldp(NMDevice *self)
     return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
 }
 
-static guint
+static guint32
 _prop_get_ipv4_dad_timeout(NMDevice *self)
 {
-    NMConnection *     connection;
+    NMConnection      *connection;
     NMSettingIPConfig *s_ip4   = NULL;
     int                timeout = -1;
 
@@ -1347,6 +1512,9 @@ _prop_get_ipv4_dad_timeout(NMDevice *self)
         s_ip4 = nm_connection_get_setting_ip4_config(connection);
     if (s_ip4)
         timeout = nm_setting_ip_config_get_dad_timeout(s_ip4);
+
+    nm_assert(timeout >= -1 && timeout <= NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX);
+
     if (timeout >= 0)
         return timeout;
 
@@ -1362,9 +1530,9 @@ static guint32
 _prop_get_ipvx_dhcp_timeout(NMDevice *self, int addr_family)
 {
     NMDeviceClass *klass;
-    NMConnection * connection;
-    int            timeout_i;
+    NMConnection  *connection;
     guint32        timeout;
+    int            timeout_i;
 
     nm_assert(NM_IS_DEVICE(self));
     nm_assert_addr_family(addr_family);
@@ -1407,9 +1575,40 @@ out:
 }
 
 static guint32
+_prop_get_ipvx_dns_priority(NMDevice *self, int addr_family)
+{
+    NMConnection      *connection;
+    NMSettingIPConfig *s_ip;
+    int                prio = 0;
+
+    connection = nm_device_get_applied_connection(self);
+    s_ip       = nm_connection_get_setting_ip_config(connection, addr_family);
+    if (s_ip)
+        prio = nm_setting_ip_config_get_dns_priority(s_ip);
+
+    if (prio == 0) {
+        prio = nm_config_data_get_connection_default_int64(
+            NM_CONFIG_GET_DATA,
+            NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.dns-priority")
+                                    : NM_CON_DEFAULT("ipv6.dns-priority"),
+            self,
+            G_MININT32,
+            G_MAXINT32,
+            0);
+        if (prio == 0) {
+            prio = nm_device_is_vpn(self) ? NM_DNS_PRIORITY_DEFAULT_VPN
+                                          : NM_DNS_PRIORITY_DEFAULT_NORMAL;
+        }
+    }
+
+    nm_assert(prio != 0);
+    return prio;
+}
+
+static guint32
 _prop_get_ipvx_required_timeout(NMDevice *self, int addr_family)
 {
-    NMConnection *     connection;
+    NMConnection      *connection;
     NMSettingIPConfig *s_ip;
     int                timeout;
 
@@ -1440,6 +1639,25 @@ _prop_get_ipvx_required_timeout(NMDevice *self, int addr_family)
         0);
 }
 
+static gboolean
+_prop_get_ipvx_may_fail(NMDevice *self, int addr_family)
+{
+    NMConnection      *connection;
+    NMSettingIPConfig *s_ip = NULL;
+
+    connection = nm_device_get_applied_connection(self);
+    if (connection)
+        s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
+
+    return !s_ip || nm_setting_ip_config_get_may_fail(s_ip);
+}
+
+static gboolean
+_prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache)
+{
+    return _CACHED_BOOL(cache, _prop_get_ipvx_may_fail(self, addr_family));
+}
+
 /**
  * _prop_get_ipvx_dhcp_iaid:
  * @self: the #NMDevice
@@ -1453,18 +1671,18 @@ _prop_get_ipvx_required_timeout(NMDevice *self, int addr_family)
  * Returns: a IAID value for this device and the given connection.
  */
 static guint32
-_prop_get_ipvx_dhcp_iaid(NMDevice *    self,
+_prop_get_ipvx_dhcp_iaid(NMDevice     *self,
                          int           addr_family,
                          NMConnection *connection,
                          gboolean      log_silent,
-                         gboolean *    out_is_explicit)
+                         gboolean     *out_is_explicit)
 {
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
     NMSettingIPConfig *s_ip;
-    const char *       iaid_str;
+    const char        *iaid_str;
     guint32            iaid;
-    const char *       iface;
-    const char *       fail_reason;
+    const char        *iface;
+    const char        *fail_reason;
     gboolean           is_explicit = TRUE;
 
     s_ip     = nm_connection_get_setting_ip_config(connection, addr_family);
@@ -1528,9 +1746,9 @@ _prop_get_ipvx_dhcp_iaid(NMDevice *    self,
         nm_auto_free_checksum GChecksum *sum = NULL;
         guint8                           digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
         NMUtilsStableType                stable_type;
-        const char *                     stable_id;
+        const char                      *stable_id;
         guint32                          salted_header;
-        const guint8 *                   host_id;
+        const guint8                    *host_id;
         gsize                            host_id_len;
 
         stable_id     = _prop_get_connection_stable_id(self, connection, &stable_type);
@@ -1583,9 +1801,9 @@ out_good:
 static NMDhcpHostnameFlags
 _prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
 {
-    NMConnection *      connection;
-    NMSettingIPConfig * s_ip;
-    NMDhcpHostnameFlags flags;
+    NMConnection         *connection;
+    NMSettingIPConfig    *s_ip;
+    NMDhcpHostnameFlags   flags;
     gs_free_error GError *error = NULL;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NM_DHCP_HOSTNAME_FLAG_NONE);
@@ -1660,15 +1878,15 @@ static GBytes *
 _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *hwaddr)
 {
     NMSettingIPConfig *s_ip4;
-    const char *       client_id;
-    guint8 *           client_id_buf;
-    const char *       fail_reason;
+    const char        *client_id;
+    guint8            *client_id_buf;
+    const char        *fail_reason;
     guint8             hwaddr_bin_buf[_NM_UTILS_HWADDR_LEN_MAX];
-    const guint8 *     hwaddr_bin;
+    const guint8      *hwaddr_bin;
     int                arp_type;
     gsize              hwaddr_len;
-    GBytes *           result;
-    gs_free char *     logstr1 = NULL;
+    GBytes            *result;
+    gs_free char      *logstr1 = NULL;
 
     s_ip4     = nm_connection_get_setting_ip4_config(connection);
     client_id = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4));
@@ -1739,7 +1957,7 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
         gs_unref_bytes GBytes *duid = NULL;
         gboolean               iaid_is_explicit;
         guint32                iaid;
-        const guint8 *         duid_arr;
+        const guint8          *duid_arr;
         gsize                  duid_len;
 
         iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, &iaid_is_explicit);
@@ -1764,9 +1982,9 @@ _prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *
         nm_auto_free_checksum GChecksum *sum = NULL;
         guint8                           digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
         NMUtilsStableType                stable_type;
-        const char *                     stable_id;
+        const char                      *stable_id;
         guint32                          salted_header;
-        const guint8 *                   host_id;
+        const guint8                    *host_id;
         gsize                            host_id_len;
 
         stable_id     = _prop_get_connection_stable_id(self, connection, &stable_type);
@@ -1812,9 +2030,9 @@ static GBytes *
 _prop_get_ipv4_dhcp_vendor_class_identifier(NMDevice *self, NMSettingIP4Config *s_ip4)
 {
     gs_free char *to_free = NULL;
-    const char *  conn_prop;
-    GBytes *      bytes = NULL;
-    const char *  bin;
+    const char   *conn_prop;
+    GBytes       *bytes = NULL;
+    const char   *bin;
     gsize         len;
 
     conn_prop = nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4);
@@ -1848,7 +2066,7 @@ static NMSettingIP6ConfigPrivacy
 _prop_get_ipv6_ip6_privacy(NMDevice *self)
 {
     NMSettingIP6ConfigPrivacy ip6_privacy;
-    NMConnection *            connection;
+    NMConnection             *connection;
 
     g_return_val_if_fail(self, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
 
@@ -1895,7 +2113,7 @@ _prop_get_ipv6_ip6_privacy(NMDevice *self)
 static const char *
 _prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
-    NMSetting * setting;
+    NMSetting  *setting;
     const char *addr = NULL;
 
     setting = nm_connection_get_setting(connection,
@@ -1941,7 +2159,7 @@ _prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolea
 static const char *
 _prop_get_x_generate_mac_address_mask(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
-    NMSetting * setting;
+    NMSetting  *setting;
     const char *value;
 
     setting = nm_connection_get_setting(connection,
@@ -1967,7 +2185,7 @@ _prop_get_x_generate_mac_address_mask(NMDevice *self, NMConnection *connection,
 static void
 _ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
 {
-    gs_free NMEthtoolFeatureStates *features;
+    gs_free NMEthtoolFeatureStates *features = NULL;
 
     features = g_steal_pointer(&ethtool_state->features);
 
@@ -1982,9 +2200,9 @@ _ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *etht
 }
 
 static void
-_ethtool_features_set(NMDevice *        self,
-                      NMPlatform *      platform,
-                      EthtoolState *    ethtool_state,
+_ethtool_features_set(NMDevice         *self,
+                      NMPlatform       *platform,
+                      EthtoolState     *ethtool_state,
                       NMSettingEthtool *s_ethtool)
 {
     gs_free NMEthtoolFeatureStates *features = NULL;
@@ -2033,18 +2251,18 @@ _ethtool_coalesce_reset(NMDevice *self, NMPlatform *platform, EthtoolState *etht
 }
 
 static void
-_ethtool_coalesce_set(NMDevice *        self,
-                      NMPlatform *      platform,
-                      EthtoolState *    ethtool_state,
+_ethtool_coalesce_set(NMDevice         *self,
+                      NMPlatform       *platform,
+                      EthtoolState     *ethtool_state,
                       NMSettingEthtool *s_ethtool)
 {
     NMEthtoolCoalesceState coalesce_old;
     NMEthtoolCoalesceState coalesce_new;
     gboolean               has_old = FALSE;
-    GHashTable *           hash;
+    GHashTable            *hash;
     GHashTableIter         iter;
-    const char *           name;
-    GVariant *             variant;
+    const char            *name;
+    GVariant              *variant;
 
     nm_assert(NM_IS_DEVICE(self));
     nm_assert(NM_IS_PLATFORM(platform));
@@ -2111,17 +2329,17 @@ _ethtool_ring_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_
 }
 
 static void
-_ethtool_ring_set(NMDevice *        self,
-                  NMPlatform *      platform,
-                  EthtoolState *    ethtool_state,
+_ethtool_ring_set(NMDevice         *self,
+                  NMPlatform       *platform,
+                  EthtoolState     *ethtool_state,
                   NMSettingEthtool *s_ethtool)
 {
     NMEthtoolRingState ring_old;
     NMEthtoolRingState ring_new;
-    GHashTable *       hash;
+    GHashTable        *hash;
     GHashTableIter     iter;
-    const char *       name;
-    GVariant *         variant;
+    const char        *name;
+    GVariant          *variant;
     gboolean           has_old = FALSE;
 
     nm_assert(NM_IS_DEVICE(self));
@@ -2207,17 +2425,17 @@ _ethtool_pause_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool
 }
 
 static void
-_ethtool_pause_set(NMDevice *        self,
-                   NMPlatform *      platform,
-                   EthtoolState *    ethtool_state,
+_ethtool_pause_set(NMDevice         *self,
+                   NMPlatform       *platform,
+                   EthtoolState     *ethtool_state,
                    NMSettingEthtool *s_ethtool)
 {
     NMEthtoolPauseState pause_old;
     NMEthtoolPauseState pause_new;
-    GHashTable *        hash;
+    GHashTable         *hash;
     GHashTableIter      iter;
-    const char *        name;
-    GVariant *          variant;
+    const char         *name;
+    GVariant           *variant;
     gboolean            has_old       = FALSE;
     NMTernary           pause_autoneg = NM_TERNARY_DEFAULT;
     NMTernary           pause_rx      = NM_TERNARY_DEFAULT;
@@ -2297,8 +2515,8 @@ _ethtool_pause_set(NMDevice *        self,
 static void
 _ethtool_state_reset(NMDevice *self)
 {
-    NMPlatform *     platform           = nm_device_get_platform(self);
-    NMDevicePrivate *priv               = NM_DEVICE_GET_PRIVATE(self);
+    NMPlatform           *platform      = nm_device_get_platform(self);
+    NMDevicePrivate      *priv          = NM_DEVICE_GET_PRIVATE(self);
     gs_free EthtoolState *ethtool_state = g_steal_pointer(&priv->ethtool_state);
 
     if (!ethtool_state)
@@ -2317,12 +2535,12 @@ _ethtool_state_reset(NMDevice *self)
 static void
 _ethtool_state_set(NMDevice *self)
 {
-    int               ifindex;
-    NMPlatform *      platform;
-    NMConnection *    connection;
-    NMSettingEthtool *s_ethtool;
+    int                   ifindex;
+    NMPlatform           *platform;
+    NMConnection         *connection;
+    NMSettingEthtool     *s_ethtool;
     gs_free EthtoolState *ethtool_state = NULL;
-    NMDevicePrivate *     priv          = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate      *priv          = NM_DEVICE_GET_PRIVATE(self);
 
     ifindex = nm_device_get_ip_ifindex(self);
     if (ifindex <= 0)
@@ -2416,27 +2634,6 @@ concheck_get_mgr(NMDevice *self)
     return priv->concheck_mgr;
 }
 
-NMIP4Config *
-nm_device_ip4_config_new(NMDevice *self)
-{
-    return nm_ip4_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self));
-}
-
-NMIP6Config *
-nm_device_ip6_config_new(NMDevice *self)
-{
-    return nm_ip6_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self));
-}
-
-NMIPConfig *
-nm_device_ip_config_new(NMDevice *self, int addr_family)
-{
-    nm_assert_addr_family(addr_family);
-
-    return NM_IS_IPv4(addr_family) ? (gpointer) nm_device_ip4_config_new(self)
-                                   : (gpointer) nm_device_ip6_config_new(self);
-}
-
 NML3ConfigData *
 nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source)
 {
@@ -2451,98 +2648,29 @@ nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source)
     return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex, source);
 }
 
-static void
-applied_config_clear(AppliedConfig *config)
-{
-    g_clear_object(&config->current);
-    g_clear_object(&config->orig);
-}
-
-static void
-applied_config_init(AppliedConfig *config, gpointer ip_config)
+const NML3ConfigData *
+nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *connection)
 {
-    nm_assert(!ip_config || (!config->orig && !config->current)
-              || nm_ip_config_get_addr_family(ip_config)
-                     == nm_ip_config_get_addr_family(config->orig ?: config->current));
-    nm_assert(!ip_config || NM_IS_IP_CONFIG(ip_config));
-
-    nm_g_object_ref(ip_config);
-    applied_config_clear(config);
-    config->orig = ip_config;
-}
-
-static void
-applied_config_init_new(AppliedConfig *config, NMDevice *self, int addr_family)
-{
-    gs_unref_object NMIPConfig *c = nm_device_ip_config_new(self, addr_family);
-
-    applied_config_init(config, c);
-}
-
-static NMIPConfig *
-applied_config_get_current(AppliedConfig *config)
-{
-    return config->current ?: config->orig;
-}
+    NML3ConfigData *l3cd;
+    int             ifindex;
 
-static void
-applied_config_add_address(AppliedConfig *config, const NMPlatformIPAddress *address)
-{
-    if (config->orig)
-        nm_ip_config_add_address(config->orig, address);
-    else
-        nm_assert(!config->current);
-
-    if (config->current)
-        nm_ip_config_add_address(config->current, address);
-}
-
-static void
-applied_config_add_nameserver(AppliedConfig *config, const NMIPAddr *ns)
-{
-    if (config->orig)
-        nm_ip_config_add_nameserver(config->orig, ns);
-    else
-        nm_assert(!config->current);
-
-    if (config->current)
-        nm_ip_config_add_nameserver(config->current, ns);
-}
-
-static void
-applied_config_add_search(AppliedConfig *config, const char *new)
-{
-    if (config->orig)
-        nm_ip_config_add_search(config->orig, new);
-    else
-        nm_assert(!config->current);
-
-    if (config->current)
-        nm_ip_config_add_search(config->current, new);
-}
-
-static void
-applied_config_reset_searches(AppliedConfig *config)
-{
-    if (config->orig)
-        nm_ip_config_reset_searches(config->orig);
-    else
-        nm_assert(!config->current);
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(!connection || NM_IS_CONNECTION(connection));
 
-    if (config->current)
-        nm_ip_config_reset_searches(config->current);
-}
+    if (!connection)
+        return NULL;
 
-static void
-applied_config_reset_nameservers(AppliedConfig *config)
-{
-    if (config->orig)
-        nm_ip_config_reset_nameservers(config->orig);
-    else
-        nm_assert(!config->current);
+    ifindex = nm_device_get_ip_ifindex(self);
+    if (ifindex <= 0)
+        g_return_val_if_reached(NULL);
 
-    if (config->current)
-        nm_ip_config_reset_nameservers(config->current);
+    l3cd =
+        nm_l3_config_data_new_from_connection(nm_device_get_multi_index(self), ifindex, connection);
+    nm_l3_config_data_set_mdns(l3cd, _prop_get_connection_mdns(self));
+    nm_l3_config_data_set_llmnr(l3cd, _prop_get_connection_llmnr(self));
+    nm_l3_config_data_set_dns_over_tls(l3cd, _prop_get_connection_dns_over_tls(self));
+    nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self));
+    return l3cd;
 }
 
 /*****************************************************************************/
@@ -2588,6 +2716,7 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
               nm_device_sys_iface_state_to_string(priv->sys_iface_state),
               nm_device_sys_iface_state_to_string(sys_iface_state));
         priv->sys_iface_state_ = sys_iface_state;
+        _dev_l3_cfg_commit_type_reset(self);
     }
 
     /* this function only sets a flag, no immediate actions are initiated.
@@ -2598,7 +2727,7 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
 }
 
 static void
-_active_connection_set_state_flags_full(NMDevice *             self,
+_active_connection_set_state_flags_full(NMDevice              *self,
                                         NMActivationStateFlags flags,
                                         NMActivationStateFlags mask)
 {
@@ -2618,12 +2747,12 @@ _active_connection_set_state_flags(NMDevice *self, NMActivationStateFlags flags)
 /*****************************************************************************/
 
 static gboolean
-set_interface_flags_full(NMDevice *             self,
+set_interface_flags_full(NMDevice              *self,
                          NMDeviceInterfaceFlags mask,
                          NMDeviceInterfaceFlags interface_flags,
                          gboolean               notify)
 {
-    NMDevicePrivate *      priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceInterfaceFlags f;
 
     nm_assert(!!mask);
@@ -2642,7 +2771,7 @@ set_interface_flags_full(NMDevice *             self,
 }
 
 static gboolean
-set_interface_flags(NMDevice *             self,
+set_interface_flags(NMDevice              *self,
                     NMDeviceInterfaceFlags interface_flags,
                     gboolean               set,
                     gboolean               notify)
@@ -2654,8 +2783,8 @@ set_interface_flags(NMDevice *             self,
 }
 
 void
-nm_device_assume_state_get(NMDevice *   self,
-                           gboolean *   out_assume_state_guess_assume,
+nm_device_assume_state_get(NMDevice    *self,
+                           gboolean    *out_assume_state_guess_assume,
                            const char **out_assume_state_connection_uuid)
 {
     NMDevicePrivate *priv;
@@ -2668,7 +2797,7 @@ nm_device_assume_state_get(NMDevice *   self,
 }
 
 static void
-_assume_state_set(NMDevice *  self,
+_assume_state_set(NMDevice   *self,
                   gboolean    assume_state_guess_assume,
                   const char *assume_state_connection_uuid)
 {
@@ -2700,33 +2829,6 @@ nm_device_assume_state_reset(NMDevice *self)
 
 /*****************************************************************************/
 
-static void
-init_ip_config_dns_priority(NMDevice *self, NMIPConfig *config)
-{
-    const char *property;
-    int         priority;
-
-    property = (nm_ip_config_get_addr_family(config) == AF_INET)
-                   ? NM_CON_DEFAULT("ipv4.dns-priority")
-                   : NM_CON_DEFAULT("ipv6.dns-priority");
-
-    priority = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
-                                                           property,
-                                                           self,
-                                                           G_MININT,
-                                                           G_MAXINT,
-                                                           0);
-
-    if (priority == 0) {
-        priority =
-            nm_device_is_vpn(self) ? NM_DNS_PRIORITY_DEFAULT_VPN : NM_DNS_PRIORITY_DEFAULT_NORMAL;
-    }
-
-    nm_ip_config_set_dns_priority(config, priority);
-}
-
-/*****************************************************************************/
-
 static char *
 nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property)
 {
@@ -2744,7 +2846,7 @@ nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *proper
 }
 
 static gint64
-nm_device_sysctl_ip_conf_get_int_checked(NMDevice *  self,
+nm_device_sysctl_ip_conf_get_int_checked(NMDevice   *self,
                                          int         addr_family,
                                          const char *property,
                                          guint       base,
@@ -2771,59 +2873,15 @@ nm_device_sysctl_ip_conf_get_int_checked(NMDevice *  self,
                                                       fallback);
 }
 
-static void
-set_ipv6_token(NMDevice *self, const NMUtilsIPv6IfaceId *iid, const char *token_str)
-{
-    NMPlatform *          platform;
-    int                   ifindex;
-    const NMPlatformLink *link;
-    char                  buf[32];
-    gint64                val;
-
-    /* Setting the kernel token is not strictly necessary as the
-     * IPv6 address is generated in userspace. However it is
-     * convenient so that users can see the token with iproute
-     * ('ip token'). */
-    platform = nm_device_get_platform(self);
-    ifindex  = nm_device_get_ip_ifindex(self);
-    link     = nm_platform_link_get(platform, ifindex);
-
-    if (link && link->inet6_token.id == iid->id) {
-        _LOGT(LOGD_DEVICE | LOGD_IP6, "token %s already set", token_str);
-        return;
-    }
-
-    /* The kernel allows setting a token only when 'accept_ra'
-     * is 1: temporarily flip it if necessary; unfortunately
-     * this will also generate an additional Router Solicitation
-     * from kernel. */
-    val = nm_device_sysctl_ip_conf_get_int_checked(self,
-                                                   AF_INET6,
-                                                   "accept_ra",
-                                                   10,
-                                                   G_MININT32,
-                                                   G_MAXINT32,
-                                                   1);
-    if (val != 1)
-        nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "1");
-
-    nm_platform_link_set_ipv6_token(platform, ifindex, iid);
-
-    if (val != 1) {
-        nm_sprintf_buf(buf, "%d", (int) val);
-        nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", buf);
-    }
-}
-
 gboolean
-nm_device_sysctl_ip_conf_set(NMDevice *  self,
+nm_device_sysctl_ip_conf_set(NMDevice   *self,
                              int         addr_family,
                              const char *property,
                              const char *value)
 {
-    NMPlatform *  platform      = nm_device_get_platform(self);
+    NMPlatform   *platform      = nm_device_get_platform(self);
     gs_free char *value_to_free = NULL;
-    const char *  ifname;
+    const char   *ifname;
 
     nm_assert_addr_family(addr_family);
 
@@ -2863,95 +2921,1064 @@ _add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities)
 
 /*****************************************************************************/
 
+static void
+_dev_ip_state_req_timeout_cancel(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (addr_family == AF_UNSPEC) {
+        _dev_ip_state_req_timeout_cancel(self, AF_INET);
+        _dev_ip_state_req_timeout_cancel(self, AF_INET6);
+        return;
+    }
+
+    if (nm_clear_g_source_inst(&priv->ip_data_x[NM_IS_IPv4(addr_family)].req_timeout_source))
+        _LOGD_ip(addr_family, "required-timeout: cancelled");
+}
+
 static gboolean
-ip_required_timeout_x(NMDevice *self, int addr_family)
+_dev_ip_state_req_timeout_cb_x(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    _LOGD(LOGD_CORE,
-          "required-timeout expired for IPv%c",
-          nm_utils_addr_family_to_char(addr_family));
-    nm_clear_g_source_inst(&priv->ip_req_timeout_source_x[NM_IS_IPv4(addr_family)]);
-    check_ip_state(self, FALSE, TRUE);
+    _LOGD_ip(addr_family, "required-timeout: expired");
+    nm_clear_g_source_inst(&priv->ip_data_x[NM_IS_IPv4(addr_family)].req_timeout_source);
+    _dev_ip_state_check(self, nm_utils_addr_family_other(addr_family));
     return G_SOURCE_CONTINUE;
 }
 
 static gboolean
-ip_required_timeout_4(gpointer data)
+_dev_ip_state_req_timeout_cb_4(gpointer user_data)
 {
-    return ip_required_timeout_x(data, AF_INET);
+    return _dev_ip_state_req_timeout_cb_x(user_data, AF_INET);
 }
 
 static gboolean
-ip_required_timeout_6(gpointer data)
+_dev_ip_state_req_timeout_cb_6(gpointer user_data)
 {
-    return ip_required_timeout_x(data, AF_INET6);
+    return _dev_ip_state_req_timeout_cb_x(user_data, AF_INET6);
 }
 
 static void
-_set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
+_dev_ip_state_req_timeout_schedule(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    guint            timeout_msec;
-    int              v4;
+    guint32          timeout_msec;
+    char             buf[32];
 
-    nm_assert_addr_family(addr_family);
+    nm_assert(!priv->ip_data_x[IS_IPv4].req_timeout_source);
 
-    if (new_state == NM_DEVICE_IP_STATE_CONF && !priv->ip_config_started) {
-        /* Start the required-timeout timers when one of IPv4/IPv6
-         * enters the CONF state. This means that if there is no carrier and
-         * ipv4.method=auto,ipv6.method=manual, the timeout for IPv4 will
-         * start as soon as connection is activated, even if DHCPv4 did not
-         * start yet.
-         */
-        priv->ip_config_started = TRUE;
+    timeout_msec = _prop_get_ipvx_required_timeout(self, addr_family);
+    if (timeout_msec == 0) {
+        _LOGD_ip(addr_family, "required-timeout: disabled");
+        return;
+    }
 
-        for (v4 = 1; v4 >= 0; v4--) {
-            char buf[32];
+    _LOGD_ip(addr_family,
+             "required-timeout: started (%s msec)",
+             timeout_msec == G_MAXINT32 ? "∞" : nm_sprintf_buf(buf, "%u", timeout_msec));
 
-            nm_assert(!priv->ip_req_timeout_source_x[v4]);
-            if ((timeout_msec = _prop_get_ipvx_required_timeout(self, v4 ? AF_INET : AF_INET6))) {
-                _LOGD(LOGD_CORE,
-                      "required-timeout in %s msec for IPv%c",
-                      timeout_msec == G_MAXINT32 ? "∞" : nm_sprintf_buf(buf, "%u", timeout_msec),
-                      v4 ? '4' : '6');
+    if (timeout_msec == G_MAXINT32) {
+        priv->ip_data_x[IS_IPv4].req_timeout_source = g_source_ref(nm_g_source_sentinel_get(0));
+    } else {
+        priv->ip_data_x[IS_IPv4].req_timeout_source = nm_g_timeout_add_source(
+            timeout_msec,
+            IS_IPv4 ? _dev_ip_state_req_timeout_cb_4 : _dev_ip_state_req_timeout_cb_6,
+            self);
+    }
+}
 
-                if (timeout_msec == G_MAXINT32) {
-                    priv->ip_req_timeout_source_x[v4] = g_source_ref(nm_g_source_sentinel_get(0));
-                } else {
-                    priv->ip_req_timeout_source_x[v4] =
-                        nm_g_timeout_add_source(timeout_msec,
-                                                v4 ? ip_required_timeout_4 : ip_required_timeout_6,
-                                                self);
+static gboolean
+_dev_ip_state_set_state(NMDevice       *self,
+                        int             addr_family,
+                        NMDeviceIPState ip_state,
+                        const char     *reason)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              IS_IPv4;
+
+    if (addr_family == AF_UNSPEC) {
+        if (priv->ip_data.state == ip_state)
+            return FALSE;
+        _LOGD_ip(addr_family,
+                 "set (combined) state %s (was %s, reason: %s)",
+                 nm_device_ip_state_to_string(ip_state),
+                 nm_device_ip_state_to_string(priv->ip_data.state),
+                 reason);
+        priv->ip_data.state_ = ip_state;
+        return TRUE;
+    }
+
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+
+    if (priv->ip_data_x[IS_IPv4].state_ == ip_state)
+        return FALSE;
+
+    _LOGD_ip(addr_family,
+             "set state %s (was %s, reason: %s)",
+             nm_device_ip_state_to_string(ip_state),
+             nm_device_ip_state_to_string(priv->ip_data_x[IS_IPv4].state),
+             reason);
+    priv->ip_data_x[IS_IPv4].state_ = ip_state;
+    return TRUE;
+}
+
+static void
+_device_ip_state_accumulate(NMDeviceIPState state,
+                            gboolean       *out_is_started,
+                            gboolean       *out_is_pending,
+                            gboolean       *out_is_failed)
+{
+    switch (state) {
+    case NM_DEVICE_IP_STATE_NONE:
+        return;
+    case NM_DEVICE_IP_STATE_PENDING:
+        *out_is_started = TRUE;
+        *out_is_pending = TRUE;
+        return;
+    case NM_DEVICE_IP_STATE_READY:
+        *out_is_started = TRUE;
+        return;
+    case NM_DEVICE_IP_STATE_FAILED:
+        *out_is_started = TRUE;
+        *out_is_failed  = TRUE;
+        return;
+    }
+    nm_assert_not_reached();
+    return;
+}
+
+static void
+_dev_ip_state_check(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate *priv         = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4      = NM_IS_IPv4(addr_family);
+    gboolean         s_is_started = FALSE;
+    gboolean         s_is_failed  = FALSE;
+    gboolean         s_is_pending = FALSE;
+    gboolean         v_bool;
+    NMDeviceIPState  ip_state;
+    NMDeviceIPState  ip_state_other;
+    NMDeviceIPState  combinedip_state;
+    NMTernary        may_fail                 = NM_TERNARY_DEFAULT;
+    NMTernary        may_fail_other           = NM_TERNARY_DEFAULT;
+    gboolean         disabled_or_ignore       = FALSE;
+    gboolean         disabled_or_ignore_other = FALSE;
+
+    if (priv->ip_data_x[IS_IPv4].is_disabled || priv->ip_data_x[IS_IPv4].is_ignore)
+        disabled_or_ignore = TRUE;
+    if (priv->ip_data_x[!IS_IPv4].is_disabled || priv->ip_data_x[!IS_IPv4].is_ignore)
+        disabled_or_ignore_other = TRUE;
+
+    /* State handling in NMDevice:
+     *
+     * NMDevice manages a lot of state, that is for the various IP addressing methods, the state
+     * of the interface (controller/port), and a overall nm_device_get_state().
+     *
+     * The idea is to compartmentalize these states into smaller units, and combine them as appropriate.
+     *
+     * For example, NMDhcpClient already provides an API that hides most of the complexity. But it still
+     * needs to expose some state, like whether we are still trying to get a lease (PENDING), whether there
+     * was a critical failure (FAILED) or we have a lease (READY). This state is grouped in NMDevice
+     * under priv->ipdhcp_data_x. Most important is priv->ipdhcp_data_x[].state, which distills all of this
+     * into 4 values of type NMDeviceIPState. This state is cached, so whenever something changes (e.g.
+     * an event from NMDhcpClient), we determine the new state and compare it with what is cached. If
+     * the cached state is as the new state, we are done. Otherwise, the change gets escalated (which
+     * means to call _dev_ip_state_check_async()).
+     *
+     * Then, the various sub-states escalate their changes to this function (_dev_ip_state_check). This
+     * function first takes the sub-states related to one IP address family, and combines them into
+     * priv->ip_data_x[] (and in particular priv->ip_data_x[].state). The same repeats. The current
+     * state is cached in priv->ip_data_x[].state, and _dev_ip_state_check() determines the new state.
+     * If there is no change, it ends here. Otherwise, it gets escalated. In this case, the escaplation
+     * happens in _dev_ip_state_check() below by combining the combined per-address-family into
+     * priv->ip_data. In particular this step needs to take into account settings like "may-fail"
+     * and "required-timeout".
+     *
+     * The escalation and compartmentalization priv->ip_data repeats. This time it escalates
+     * to the overall device state (nm_device_state_changed() and nm_device_get_state()), which then
+     * triggers larger state changes (e.g. the activation might fail).
+     */
+
+    if (priv->l3cfg && nm_l3cfg_commit_on_idle_is_scheduled(priv->l3cfg)) {
+        /* we have an update on NML3Cfg scheduled. We first process that, before
+         * progressing the IP state. When that's done, we will be called again. */
+        _dev_ip_state_check_async(self, addr_family);
+        return;
+    }
+
+    if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_NONE) {
+        ip_state = NM_DEVICE_IP_STATE_NONE;
+        goto got_ip_state;
+    }
+
+    if (nm_device_sys_iface_state_is_external(self)) {
+        ip_state = NM_DEVICE_IP_STATE_READY;
+        goto got_ip_state;
+    }
+
+    if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_PENDING
+        && (priv->state < NM_DEVICE_STATE_IP_CONFIG || priv->state > NM_DEVICE_STATE_ACTIVATED)) {
+        /* we can only leave pending state, if we are between (including) IP_CONFIG and ACTIVATED states. */
+        ip_state = NM_DEVICE_IP_STATE_PENDING;
+        goto got_ip_state;
+    }
+
+    if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_PENDING
+        && nm_active_connection_get_master(NM_ACTIVE_CONNECTION(priv->act_request.obj))
+        && !priv->is_enslaved) {
+        /* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
+         * master to enslave us. */
+        ip_state = NM_DEVICE_IP_STATE_PENDING;
+        goto got_ip_state;
+    }
+
+    if (priv->ip_data_x[IS_IPv4].wait_for_carrier || priv->ip_data_x[IS_IPv4].wait_for_ports) {
+        ip_state = NM_DEVICE_IP_STATE_PENDING;
+        goto got_ip_state;
+    }
+
+    if (disabled_or_ignore) {
+        ip_state = NM_DEVICE_IP_STATE_READY;
+        goto got_ip_state;
+    }
+
+    _device_ip_state_accumulate(priv->ipmanual_data.state_x[IS_IPv4],
+                                &s_is_started,
+                                &s_is_pending,
+                                &s_is_failed);
+
+    _device_ip_state_accumulate(priv->ipll_data_x[IS_IPv4].state,
+                                &s_is_started,
+                                &s_is_pending,
+                                &s_is_failed);
+
+    if (!IS_IPv4) {
+        _device_ip_state_accumulate(priv->ipac6_data.state,
+                                    &s_is_started,
+                                    &s_is_pending,
+                                    &s_is_failed);
+    }
+
+    v_bool = FALSE;
+    _device_ip_state_accumulate(priv->ipdhcp_data_x[IS_IPv4].state,
+                                &s_is_started,
+                                &s_is_pending,
+                                &v_bool);
+    if (v_bool) {
+        if (!IS_IPv4 && priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) {
+            /* DHCPv6 is best-effort and not required. */
+        } else
+            s_is_failed = TRUE;
+    }
+
+    _device_ip_state_accumulate(priv->ipshared_data_x[IS_IPv4].state,
+                                &s_is_started,
+                                &s_is_pending,
+                                &s_is_failed);
+
+    _device_ip_state_accumulate(priv->ipdev_data_x[IS_IPv4].state,
+                                &s_is_started,
+                                &s_is_pending,
+                                &s_is_failed);
+
+    _device_ip_state_accumulate(priv->ipdev_data_unspec.state,
+                                &s_is_started,
+                                &s_is_pending,
+                                &s_is_failed);
+
+    if (s_is_failed)
+        ip_state = NM_DEVICE_IP_STATE_FAILED;
+    else if (s_is_pending)
+        ip_state = NM_DEVICE_IP_STATE_PENDING;
+    else if (s_is_started)
+        ip_state = NM_DEVICE_IP_STATE_READY;
+    else
+        ip_state = NM_DEVICE_IP_STATE_PENDING;
+
+got_ip_state:
+
+#define _state_str_a(state, name)                                       \
+    ({                                                                  \
+        const NMDeviceIPState _state = (state);                         \
+        char                 *_s     = "";                              \
+                                                                        \
+        if (_state != NM_DEVICE_IP_STATE_NONE) {                        \
+            _s = nm_sprintf_bufa(NM_STRLEN(name) + 11,                  \
+                                 " " name "=%s",                        \
+                                 nm_device_ip_state_to_string(_state)); \
+        }                                                               \
+        _s;                                                             \
+    })
+
+    nm_assert(!priv->ip_data_4.is_ignore);
+
+    _LOGT_ip(addr_family,
+             "check-state: state %s => %s, is_failed=%d, is_pending=%d, is_started=%d, "
+             "may-fail-4=%d, may-fail-6=%d;"
+             "%s;%s%s%s%s%s%s;%s%s%s%s%s%s%s%s",
+             nm_device_ip_state_to_string(priv->ip_data_x[IS_IPv4].state),
+             nm_device_ip_state_to_string(ip_state),
+             s_is_failed,
+             s_is_pending,
+             s_is_started,
+             _prop_get_ipvx_may_fail_cached(self, AF_INET, IS_IPv4 ? &may_fail : &may_fail_other),
+             _prop_get_ipvx_may_fail_cached(self, AF_INET6, !IS_IPv4 ? &may_fail : &may_fail_other),
+             priv->ip_data_4.is_disabled ? " disabled4" : "",
+             _state_str_a(priv->ipmanual_data.state_4, "manualip4"),
+             _state_str_a(priv->ipdev_data_unspec.state, "dev"),
+             _state_str_a(priv->ipll_data_4.state, "ll4"),
+             _state_str_a(priv->ipdhcp_data_4.state, "dhcp4"),
+             _state_str_a(priv->ipdev_data_4.state, "dev4"),
+             _state_str_a(priv->ipshared_data_4.state, "shared4"),
+             priv->ip_data_6.is_disabled ? " disabled6" : "",
+             priv->ip_data_6.is_ignore ? " ignore6" : "",
+             _state_str_a(priv->ipmanual_data.state_6, "manualip6"),
+             _state_str_a(priv->ipll_data_6.state, "ll6"),
+             _state_str_a(priv->ipac6_data.state, "ac6"),
+             _state_str_a(priv->ipdhcp_data_6.state, "dhcp6"),
+             _state_str_a(priv->ipdev_data_6.state, "dev6"),
+             _state_str_a(priv->ipshared_data_6.state, "shared6"));
+
+    if (priv->ip_data_x[IS_IPv4].state == ip_state) {
+        /* no change. We can stop here. However, we also cancel the pending check, if any,
+         * because we just determined that there is no change. */
+    } else {
+        _dev_ip_state_set_state(self, addr_family, ip_state, "check-ip-state");
+    }
+
+    if (ip_state == NM_DEVICE_IP_STATE_NONE) {
+        /* Nothing to do. This almost cannot happen, and there is probably nothing
+         * to do about this case. */
+        goto out_done;
+    }
+
+    ip_state_other = priv->ip_data_x[!IS_IPv4].state;
+
+    if (ip_state == NM_DEVICE_IP_STATE_READY) {
+        /* we only set NM_ACTIVATION_STATE_FLAG_IP_READY_X() flag once we reach NM_DEVICE_IP_STATE_READY state.
+         * We don't ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAILED state.
+         *
+         * This is not documented/guaranteed behavior, but seems to make sense for now. */
+        _active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_IP_READY_X(IS_IPv4));
+    }
+
+    if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_READY)
+        combinedip_state = NM_DEVICE_IP_STATE_READY;
+    else if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_PENDING
+             && disabled_or_ignore) {
+        /* This IP method is disabled/ignore, but the other family is still pending.
+         * Regardless of ipvx.may-fail, this means that we always require the other IP family
+         * to get ready too. */
+        combinedip_state = NM_DEVICE_IP_STATE_PENDING;
+    } else if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_PENDING
+               && (priv->ip_data_x[!IS_IPv4].req_timeout_source
+                   || !_prop_get_ipvx_may_fail_cached(self,
+                                                      nm_utils_addr_family_other(addr_family),
+                                                      &may_fail_other)))
+        combinedip_state = NM_DEVICE_IP_STATE_PENDING;
+    else if (ip_state == NM_DEVICE_IP_STATE_READY
+             && _prop_get_ipvx_may_fail_cached(self,
+                                               nm_utils_addr_family_other(addr_family),
+                                               &may_fail_other))
+        combinedip_state = NM_DEVICE_IP_STATE_READY;
+    else if (ip_state == NM_DEVICE_IP_STATE_FAILED
+             && !_prop_get_ipvx_may_fail_cached(self, addr_family, &may_fail))
+        combinedip_state = NM_DEVICE_IP_STATE_FAILED;
+    else if ((ip_state == NM_DEVICE_IP_STATE_FAILED
+              || (ip_state == NM_DEVICE_IP_STATE_READY && disabled_or_ignore))
+             && (ip_state_other == NM_DEVICE_IP_STATE_FAILED
+                 || (ip_state_other == NM_DEVICE_IP_STATE_READY && disabled_or_ignore_other))) {
+        /* If both IP states failed, or one failed and the other is disabled
+         * then it's a failure. may-fail does not mean that both families may
+         * fail, instead it means that at least one family must succeed. */
+        if (nm_device_sys_iface_state_is_external_or_assume(self)) {
+            _dev_ip_state_set_state(self, AF_INET, NM_DEVICE_IP_STATE_READY, "assumed");
+            _dev_ip_state_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_READY, "assumed");
+            combinedip_state = NM_DEVICE_IP_STATE_READY;
+        } else {
+            combinedip_state = NM_DEVICE_IP_STATE_FAILED;
+        }
+    } else {
+        if (priv->ip_data.state == NM_DEVICE_IP_STATE_NONE)
+            combinedip_state = NM_DEVICE_IP_STATE_PENDING;
+        else
+            combinedip_state = priv->ip_data.state;
+    }
+
+    _LOGT_ip(AF_UNSPEC,
+             "check-state: (combined) state %s => %s",
+             nm_device_ip_state_to_string(priv->ip_data.state),
+             nm_device_ip_state_to_string(combinedip_state));
+
+    if (!_dev_ip_state_set_state(self, AF_UNSPEC, combinedip_state, "check-ip-state"))
+        goto out_done;
+
+    switch (combinedip_state) {
+    case NM_DEVICE_IP_STATE_PENDING:
+        break;
+    case NM_DEVICE_IP_STATE_READY:
+        _dev_ip_state_req_timeout_cancel(self, AF_UNSPEC);
+        if (priv->state == NM_DEVICE_STATE_IP_CONFIG) {
+            nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
+        }
+        break;
+    case NM_DEVICE_IP_STATE_FAILED:
+        nm_device_state_changed(self,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
+        break;
+    case NM_DEVICE_IP_STATE_NONE:
+    default:
+        nm_assert_not_reached();
+    }
+
+out_done:
+    /* we just checked the state. We can cancel the pending async check. */
+    nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].check_async_source);
+}
+
+static gboolean
+_dev_ip_state_check_async_cb(NMDevice *self, int addr_family)
+{
+    _dev_ip_state_check(self, addr_family);
+    return G_SOURCE_CONTINUE;
+}
+
+static gboolean
+_dev_ip_state_check_async_cb_4(gpointer user_data)
+{
+    return _dev_ip_state_check_async_cb(user_data, AF_INET);
+}
+
+static gboolean
+_dev_ip_state_check_async_cb_6(gpointer user_data)
+{
+    return _dev_ip_state_check_async_cb(user_data, AF_INET6);
+}
+
+static void
+_dev_ip_state_check_async(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              IS_IPv4;
+
+    if (addr_family == AF_UNSPEC) {
+        _dev_ip_state_check_async(self, AF_INET);
+        _dev_ip_state_check_async(self, AF_INET6);
+        return;
+    }
+
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+    if (!priv->ip_data_x[IS_IPv4].check_async_source) {
+        priv->ip_data_x[IS_IPv4].check_async_source = nm_g_idle_add_source(
+            (IS_IPv4 ? _dev_ip_state_check_async_cb_4 : _dev_ip_state_check_async_cb_6),
+            self);
+    }
+}
+
+static void
+_dev_ip_state_cleanup(NMDevice *self, int addr_family, gboolean from_reapply)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              IS_IPv4;
+
+    if (addr_family == AF_UNSPEC) {
+        _dev_ip_state_set_state(self,
+                                addr_family,
+                                from_reapply ? NM_DEVICE_IP_STATE_PENDING : NM_DEVICE_IP_STATE_NONE,
+                                "ip-state-clear");
+        return;
+    }
+
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+
+    nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].check_async_source);
+    nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].req_timeout_source);
+    _dev_ip_state_set_state(self,
+                            addr_family,
+                            from_reapply ? NM_DEVICE_IP_STATE_PENDING : NM_DEVICE_IP_STATE_NONE,
+                            "ip-state-clear");
+    priv->ip_data_x[IS_IPv4].wait_for_carrier = FALSE;
+    priv->ip_data_x[IS_IPv4].wait_for_ports   = FALSE;
+    priv->ip_data_x[IS_IPv4].is_disabled      = FALSE;
+    priv->ip_data_x[IS_IPv4].is_ignore        = FALSE;
+    priv->ip_data_x[IS_IPv4].do_reapply       = FALSE;
+}
+
+/*****************************************************************************/
+
+static gpointer
+_dev_l3_config_data_tag_get(NMDevicePrivate *priv, L3ConfigDataType l3cd_type)
+{
+    nm_assert(_NM_INT_NOT_NEGATIVE(l3cd_type) && l3cd_type < G_N_ELEMENTS(priv->l3cds));
+
+    return &priv->l3cds[l3cd_type];
+}
+
+static L3ConfigDataType
+_dev_l3_config_data_tag_to_type(NMDevice *self, gconstpointer tag)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              d;
+
+    /* In C it is undefined behavior to compare unrelated pointers.
+     * Work around that by using nm_ptr_to_uintptr(), which casts the pointers
+     * to integers.
+     *
+     * I guess, theoretically it's still a problem to assume that if tag pointers
+     * somewhere inside priv->l3cds, that the uintptr_t case would also yield
+     * a value in that range. In practice, I couldn't imaging this not not work
+     * reliably. */
+
+    if (nm_ptr_to_uintptr(tag) < nm_ptr_to_uintptr(&priv->l3cds[0])
+        || nm_ptr_to_uintptr(tag) >= nm_ptr_to_uintptr(&priv->l3cds[G_N_ELEMENTS(priv->l3cds)]))
+        return _L3_CONFIG_DATA_TYPE_NONE;
+
+    d = ((typeof(priv->l3cds[0]) *) tag) - (&priv->l3cds[0]);
+
+    nm_assert(d >= 0);
+    nm_assert(d < _L3_CONFIG_DATA_TYPE_NUM);
+    nm_assert(tag == &priv->l3cds[d]);
+    nm_assert(tag == _dev_l3_config_data_tag_get(priv, d));
+    return d;
+}
+
+static L3ConfigDataType
+_dev_l3_config_data_acd_addr_info_to_type(NMDevice              *self,
+                                          const NML3AcdAddrInfo *addr_info,
+                                          guint                  i_track_infos)
+{
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(addr_info);
+    nm_assert(i_track_infos < addr_info->n_track_infos);
+
+    return _dev_l3_config_data_tag_to_type(self, addr_info->track_infos[i_track_infos].tag);
+}
+
+// FIXME(l3cfg): unused function??
+_nm_unused static const NML3AcdAddrTrackInfo *
+_dev_l3_config_data_acd_addr_info_has_by_type(NMDevice              *self,
+                                              const NML3AcdAddrInfo *addr_info,
+                                              L3ConfigDataType       l3cd_type)
+{
+    guint i;
+
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(addr_info);
+    nm_assert(_NM_INT_NOT_NEGATIVE(l3cd_type) && l3cd_type < _L3_CONFIG_DATA_TYPE_NUM);
+
+    for (i = 0; i < addr_info->n_track_infos; i++) {
+        if (l3cd_type == _dev_l3_config_data_acd_addr_info_to_type(self, addr_info, i))
+            return &addr_info->track_infos[i];
+    }
+    return NULL;
+}
+
+static void
+_dev_l3_get_config_settings(NMDevice             *self,
+                            L3ConfigDataType      type,
+                            NML3ConfigMergeFlags *out_merge_flags,
+                            NML3AcdDefendType    *out_acd_defend_type,
+                            guint32              *out_acd_timeout_msec)
+{
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NML3ConfigMergeFlags flags;
+    NMConnection        *connection;
+    NMSettingIPConfig   *s_ip;
+
+    nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < _L3_CONFIG_DATA_TYPE_NUM);
+
+    if (G_UNLIKELY(!priv->l3config_merge_flags_has)) {
+        int IS_IPv4;
+
+        connection = nm_device_get_applied_connection(self);
+
+        for (IS_IPv4 = 0; IS_IPv4 < 2; IS_IPv4++) {
+            flags = NM_L3_CONFIG_MERGE_FLAGS_NONE;
+
+            if (connection
+                && (s_ip = nm_connection_get_setting_ip_config(connection,
+                                                               IS_IPv4 ? AF_INET : AF_INET6))) {
+                if (nm_setting_ip_config_get_ignore_auto_routes(s_ip))
+                    flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES;
+
+                if (nm_setting_ip_config_get_ignore_auto_dns(s_ip))
+                    flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DNS;
+
+                if (nm_setting_ip_config_get_never_default(s_ip)
+                    || nm_setting_ip_config_get_gateway(s_ip)) {
+                    /* if the connection has an explicit gateway, we also ignore
+                     * the default routes from other sources. */
+                    flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES;
                 }
             }
+
+            priv->l3config_merge_flags_x[IS_IPv4] = flags;
+        }
+        priv->l3config_merge_flags_has = TRUE;
+    }
+
+    switch (type) {
+    case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
+    case L3_CONFIG_DATA_TYPE_MANUALIP:
+    case L3_CONFIG_DATA_TYPE_LL_4:
+    case L3_CONFIG_DATA_TYPE_LL_6:
+    case L3_CONFIG_DATA_TYPE_PD_6:
+    case L3_CONFIG_DATA_TYPE_SHARED_4:
+    case L3_CONFIG_DATA_TYPE_DEVIP_4:
+    case L3_CONFIG_DATA_TYPE_AC_6:
+    case L3_CONFIG_DATA_TYPE_DHCP_6:
+    case L3_CONFIG_DATA_TYPE_DEVIP_6:
+        *out_acd_timeout_msec = _prop_get_ipv4_dad_timeout(self);
+        goto after_acd_timeout;
+
+    case L3_CONFIG_DATA_TYPE_DHCP_4:
+        /* For DHCP, we perform ACD separately, because we want to decline the
+         * lease in case of a conflict. */
+        *out_acd_timeout_msec = 0;
+        goto after_acd_timeout;
+
+    case _L3_CONFIG_DATA_TYPE_NUM:
+    case _L3_CONFIG_DATA_TYPE_NONE:
+    case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
+        break;
+    }
+    *out_acd_timeout_msec = nm_assert_unreachable_val(0);
+
+after_acd_timeout:
+    switch (type) {
+    case L3_CONFIG_DATA_TYPE_LL_4:
+        *out_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_ONCE;
+        goto after_acd_defend_type;
+
+    case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
+    case L3_CONFIG_DATA_TYPE_MANUALIP:
+    case L3_CONFIG_DATA_TYPE_LL_6:
+    case L3_CONFIG_DATA_TYPE_PD_6:
+    case L3_CONFIG_DATA_TYPE_SHARED_4:
+    case L3_CONFIG_DATA_TYPE_DHCP_4:
+    case L3_CONFIG_DATA_TYPE_DEVIP_4:
+    case L3_CONFIG_DATA_TYPE_AC_6:
+    case L3_CONFIG_DATA_TYPE_DHCP_6:
+    case L3_CONFIG_DATA_TYPE_DEVIP_6:
+        *out_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_ALWAYS;
+        goto after_acd_defend_type;
+
+    case _L3_CONFIG_DATA_TYPE_NUM:
+    case _L3_CONFIG_DATA_TYPE_NONE:
+    case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
+        break;
+    }
+    *out_acd_defend_type = nm_assert_unreachable_val(NM_L3_ACD_DEFEND_TYPE_ALWAYS);
+
+after_acd_defend_type:
+    switch (type) {
+    case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
+    case L3_CONFIG_DATA_TYPE_MANUALIP:
+    case L3_CONFIG_DATA_TYPE_LL_4:
+    case L3_CONFIG_DATA_TYPE_LL_6:
+    case L3_CONFIG_DATA_TYPE_PD_6:
+    case L3_CONFIG_DATA_TYPE_SHARED_4:
+        *out_merge_flags = NM_L3_CONFIG_MERGE_FLAGS_NONE;
+        goto after_merge_flags;
+
+    case L3_CONFIG_DATA_TYPE_DHCP_4:
+    case L3_CONFIG_DATA_TYPE_DEVIP_4:
+        *out_merge_flags = priv->l3config_merge_flags_4;
+        goto after_merge_flags;
+
+    case L3_CONFIG_DATA_TYPE_AC_6:
+    case L3_CONFIG_DATA_TYPE_DHCP_6:
+    case L3_CONFIG_DATA_TYPE_DEVIP_6:
+        *out_merge_flags = priv->l3config_merge_flags_6;
+        goto after_merge_flags;
+
+    case _L3_CONFIG_DATA_TYPE_NUM:
+    case _L3_CONFIG_DATA_TYPE_NONE:
+    case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
+        break;
+    }
+    *out_merge_flags = nm_assert_unreachable_val(NM_L3_CONFIG_MERGE_FLAGS_NONE);
+
+after_merge_flags:
+    return;
+}
+
+static gboolean
+_dev_l3_register_l3cds_add_config(NMDevice          *self,
+                                  L3ConfigDataType   l3cd_type,
+                                  NML3CfgConfigFlags flags)
+{
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NML3ConfigMergeFlags merge_flags;
+    NML3AcdDefendType    acd_defend_type;
+    guint32              acd_timeout_msec;
+
+    _dev_l3_get_config_settings(self, l3cd_type, &merge_flags, &acd_defend_type, &acd_timeout_msec);
+    return nm_l3cfg_add_config(priv->l3cfg,
+                               _dev_l3_config_data_tag_get(priv, l3cd_type),
+                               FALSE,
+                               priv->l3cds[l3cd_type].d,
+                               l3cd_type,
+                               nm_device_get_route_table(self, AF_INET),
+                               nm_device_get_route_table(self, AF_INET6),
+                               nm_device_get_route_metric(self, AF_INET),
+                               nm_device_get_route_metric(self, AF_INET6),
+                               _dev_default_route_metric_penalty_get(self, AF_INET),
+                               _dev_default_route_metric_penalty_get(self, AF_INET6),
+                               _prop_get_ipvx_dns_priority(self, AF_INET),
+                               _prop_get_ipvx_dns_priority(self, AF_INET6),
+                               acd_defend_type,
+                               acd_timeout_msec,
+                               flags,
+                               merge_flags);
+}
+
+static gboolean
+_dev_l3_register_l3cds_set_one_full(NMDevice             *self,
+                                    L3ConfigDataType      l3cd_type,
+                                    const NML3ConfigData *l3cd,
+                                    NML3CfgConfigFlags    flags,
+                                    NMTernary             commit_sync)
+{
+    NMDevicePrivate                         *priv     = NM_DEVICE_GET_PRIVATE(self);
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
+    gboolean                                 changed  = FALSE;
+
+    if (priv->l3cds[l3cd_type].d != l3cd) {
+        if (nm_l3_config_data_equal(priv->l3cds[l3cd_type].d, l3cd)) {
+            /* we would set to a different instance, but the same content!
+             * We keep the previous one and ignore the new @l3cd.
+             *
+             * Warning: this means, that after calling this function,
+             * priv->l3cds[l3cd_type].d still might point to a different
+             * (though semantically equal) l3cd instance. */
+        } else {
+            l3cd_old = g_steal_pointer(&priv->l3cds[l3cd_type].d);
+            if (l3cd)
+                priv->l3cds[l3cd_type].d = nm_l3_config_data_ref_and_seal(l3cd);
+        }
+    }
+
+    if (priv->l3cfg) {
+        if (priv->l3cds[l3cd_type].d) {
+            if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags))
+                changed = TRUE;
+        }
+
+        if (l3cd_old) {
+            if (nm_l3cfg_remove_config(priv->l3cfg,
+                                       _dev_l3_config_data_tag_get(priv, l3cd_type),
+                                       l3cd_old))
+                changed = TRUE;
         }
     }
 
-    if (priv->ip_state_x[IS_IPv4] == new_state)
+    if (changed && commit_sync != NM_TERNARY_DEFAULT)
+        _dev_l3_cfg_commit(self, !!commit_sync);
+
+    return changed;
+}
+
+static gboolean
+_dev_l3_register_l3cds_set_one(NMDevice             *self,
+                               L3ConfigDataType      l3cd_type,
+                               const NML3ConfigData *l3cd,
+                               NMTernary             commit_sync)
+{
+    return _dev_l3_register_l3cds_set_one_full(self,
+                                               l3cd_type,
+                                               l3cd,
+                                               NM_L3CFG_CONFIG_FLAGS_NONE,
+                                               commit_sync);
+}
+
+static void
+_dev_l3_update_l3cds_ifindex(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    int              ip_ifindex;
+    int              i;
+
+    ip_ifindex = nm_device_get_ip_ifindex(self);
+    if (ip_ifindex <= 0)
         return;
 
-    _LOGT(LOGD_DEVICE,
-          "ip%c-state: set to %d (%s)",
-          nm_utils_addr_family_to_char(addr_family),
-          (int) new_state,
-          nm_device_ip_state_to_string(new_state));
+    for (i = 0; i < (int) G_N_ELEMENTS(priv->l3cds); i++) {
+        if (priv->l3cds[i].d && nm_l3_config_data_get_ifindex(priv->l3cds[i].d) != ip_ifindex) {
+            nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
 
-    priv->ip_state_x_[IS_IPv4] = new_state;
+            l3cd_old = g_steal_pointer(&priv->l3cds[i].d);
 
-    if (new_state == NM_DEVICE_IP_STATE_DONE) {
-        /* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't
-         * ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state.
-         *
-         * This is not documented/guaranteed behavior, but seems to make sense for now. */
-        _active_connection_set_state_flags(self,
-                                           NM_IS_IPv4(addr_family)
-                                               ? NM_ACTIVATION_STATE_FLAG_IP4_READY
-                                               : NM_ACTIVATION_STATE_FLAG_IP6_READY);
+            priv->l3cds[i].d =
+                nm_l3_config_data_seal(nm_l3_config_data_new_clone(l3cd_old, ip_ifindex));
+        }
     }
 }
 
+static gboolean
+_dev_l3_register_l3cds(NMDevice *self,
+                       NML3Cfg  *l3cfg,
+                       gboolean  do_add /* else remove */,
+                       NMTernary do_commit)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    gboolean         is_external;
+    gboolean         changed;
+    int              i;
+
+    if (!l3cfg)
+        return FALSE;
+
+    is_external = nm_device_sys_iface_state_is_external(self);
+
+    changed = FALSE;
+    for (i = 0; i < (int) G_N_ELEMENTS(priv->l3cds); i++) {
+        if (!priv->l3cds[i].d)
+            continue;
+        if (!do_add) {
+            if (nm_l3cfg_remove_config(l3cfg,
+                                       _dev_l3_config_data_tag_get(priv, i),
+                                       priv->l3cds[i].d))
+                changed = TRUE;
+            continue;
+        }
+        if (is_external)
+            continue;
+        if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE))
+            changed = TRUE;
+    }
+
+    if (do_commit == NM_TERNARY_DEFAULT)
+        do_commit = changed;
+    if (do_commit)
+        _dev_l3_cfg_commit(self, TRUE);
+
+    return changed;
+}
+
+/*****************************************************************************/
+
+void
+nm_device_l3cfg_commit(NMDevice *self, NML3CfgCommitType commit_type, gboolean commit_sync)
+{
+    NMDevicePrivate *priv;
+
+    g_return_if_fail(NM_IS_DEVICE(self));
+
+    priv = NM_DEVICE_GET_PRIVATE(self);
+
+    if (!priv->l3cfg)
+        return;
+
+    /* FIXME(l3cfg): commit_sync should go away and not be used. The reason is that
+     * a commit does *a lot* of things which are outside the control of the caller,
+     * which makes it unsuitable to call in most cases. */
+    if (!commit_sync) {
+        nm_l3cfg_commit_on_idle_schedule(priv->l3cfg, commit_type);
+        return;
+    }
+
+    nm_l3cfg_commit(priv->l3cfg, commit_type);
+}
+
+static void
+_dev_l3_cfg_commit(NMDevice *self, gboolean commit_sync)
+{
+    nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, commit_sync);
+}
+
+static void
+update_external_connection(NMDevice *self)
+{
+    NMDevicePrivate              *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMSettingsConnection         *settings_connection;
+    gs_unref_object NMConnection *connection_new = NULL;
+    NMConnection                 *connection_old;
+    gs_unref_object NMSetting    *s_ip4_new = NULL;
+    gs_unref_object NMSetting    *s_ip6_new = NULL;
+    NMSetting                    *s_ip4_old;
+    NMSetting                    *s_ip6_old;
+
+    /* Update external connections with configuration from platform */
+
+    if (!nm_device_sys_iface_state_is_external(self))
+        return;
+
+    settings_connection = nm_device_get_settings_connection(self);
+    if (!settings_connection)
+        return;
+
+    if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection),
+                      NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
+        return;
+
+    if (nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(priv->act_request.obj))
+        != NM_ACTIVATION_TYPE_EXTERNAL)
+        return;
+
+    connection_old = nm_settings_connection_get_connection(settings_connection);
+    s_ip4_old      = nm_connection_get_setting(connection_old, NM_TYPE_SETTING_IP4_CONFIG);
+    s_ip6_old      = nm_connection_get_setting(connection_old, NM_TYPE_SETTING_IP6_CONFIG);
+
+    s_ip4_new = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
+                                                     AF_INET,
+                                                     nm_device_get_ip_ifindex(self),
+                                                     FALSE);
+    s_ip6_new = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
+                                                     AF_INET6,
+                                                     nm_device_get_ip_ifindex(self),
+                                                     _get_maybe_ipv6_disabled(self));
+
+    if (!s_ip4_old || !nm_setting_compare(s_ip4_new, s_ip4_old, NM_SETTING_COMPARE_FLAG_EXACT)) {
+        connection_new = nm_simple_connection_new_clone(connection_old);
+        nm_connection_add_setting(connection_new, g_steal_pointer(&s_ip4_new));
+    }
+
+    if (!s_ip6_old || !nm_setting_compare(s_ip6_new, s_ip6_old, NM_SETTING_COMPARE_FLAG_EXACT)) {
+        if (!connection_new)
+            connection_new = nm_simple_connection_new_clone(connection_old);
+        nm_connection_add_setting(connection_new, g_steal_pointer(&s_ip6_new));
+    }
+
+    if (connection_new) {
+        nm_settings_connection_update(settings_connection,
+                                      connection_new,
+                                      NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
+                                      NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                      NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
+                                      NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
+                                      "update-external",
+                                      NULL);
+    }
+}
+
+static void
+_dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    nm_assert(l3cfg == priv->l3cfg);
+
+    switch (notify_data->notify_type) {
+    case NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED:
+        if (notify_data->l3cd_changed.commited) {
+            g_signal_emit(self,
+                          signals[L3CD_CHANGED],
+                          0,
+                          notify_data->l3cd_changed.l3cd_old,
+                          notify_data->l3cd_changed.l3cd_new);
+        }
+        return;
+    case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
+    {
+        const NML3AcdAddrInfo *addr_info = &notify_data->acd_event.info;
+
+        if (addr_info->state > NM_L3_ACD_ADDR_STATE_PROBING)
+            _dev_ipmanual_check_ready(self);
+        return;
+    }
+    case NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT:
+    {
+        const NML3ConfigData *l3cd;
+
+        /* FIXME(l3cfg): MTU handling should be moved to l3cfg. */
+        l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
+        if (l3cd)
+            priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd);
+        _commit_mtu(self);
+        return;
+    }
+    case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
+        if (priv->ipshared_data_4.state == NM_DEVICE_IP_STATE_PENDING
+            && !priv->ipshared_data_4.v4.dnsmasq_manager && priv->ipshared_data_4.v4.l3cd) {
+            _dev_ipshared4_spawn_dnsmasq(self);
+            nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
+        }
+        _dev_ipmanual_check_ready(self);
+        return;
+    case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
+        nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
+        if (priv->ipll_data_4.v4.ipv4ll == notify_data->ipv4ll_event.ipv4ll)
+            _dev_ipll4_notify_event(self);
+        return;
+    case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
+        return;
+    case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED:
+        /* we commit again. This way we try to configure the routes.*/
+        _dev_l3_cfg_commit(self, FALSE);
+        return;
+    case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
+        if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
+                         nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK)
+                             | nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ADDRESS)
+                             | nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS)))
+            _dev_unmanaged_check_external_down(self, TRUE, TRUE);
+
+        if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
+                         nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ADDRESS)
+                             | nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS))) {
+            g_signal_emit(self, signals[PLATFORM_ADDRESS_CHANGED], 0);
+        }
+
+        /* Check if AC6 addresses completed DAD */
+        if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
+                         nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS))
+            && priv->ipac6_data.state == NM_DEVICE_IP_STATE_PENDING && priv->ipac6_data.l3cd
+            && nm_l3cfg_check_ready(l3cfg,
+                                    priv->ipac6_data.l3cd,
+                                    AF_INET6,
+                                    NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY,
+                                    NULL)) {
+            nm_clear_l3cd(&priv->ipac6_data.l3cd);
+            _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
+            _dev_ip_state_check_async(self, AF_INET6);
+        }
+
+        _dev_ipmanual_check_ready(self);
+        update_external_connection(self);
+        nm_device_queue_recheck_assume(self);
+        return;
+
+    case _NM_L3_CONFIG_NOTIFY_TYPE_NUM:
+        break;
+    }
+    nm_assert_not_reached();
+}
+
+static void
+_dev_l3_cfg_commit_type_reset(NMDevice *self)
+{
+    NMDevicePrivate  *priv = NM_DEVICE_GET_PRIVATE(self);
+    NML3CfgCommitType commit_type;
+
+    if (!priv->l3cfg)
+        return;
+
+    switch (priv->sys_iface_state) {
+    case NM_DEVICE_SYS_IFACE_STATE_EXTERNAL:
+    case NM_DEVICE_SYS_IFACE_STATE_REMOVED:
+        commit_type = NM_L3_CFG_COMMIT_TYPE_NONE;
+        goto do_set;
+    case NM_DEVICE_SYS_IFACE_STATE_ASSUME:
+        commit_type = NM_L3_CFG_COMMIT_TYPE_ASSUME;
+        goto do_set;
+    case NM_DEVICE_SYS_IFACE_STATE_MANAGED:
+        commit_type = NM_L3_CFG_COMMIT_TYPE_UPDATE;
+        goto do_set;
+    }
+    nm_assert_not_reached();
+    return;
+
+do_set:
+    priv->l3cfg_commit_type =
+        nm_l3cfg_commit_type_register(priv->l3cfg, commit_type, priv->l3cfg_commit_type, "device");
+    if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
+        nm_l3cfg_commit_type_reset_update(priv->l3cfg);
+}
+
 /*****************************************************************************/
 
 const char *
@@ -2973,11 +4000,18 @@ nm_device_get_iface(NMDevice *self)
 static gboolean
 _set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    int *            p_ifindex;
+    NMDevicePrivate         *priv                  = NM_DEVICE_GET_PRIVATE(self);
+    gs_unref_object NML3Cfg *l3cfg_old             = NULL;
+    NML3CfgCommitTypeHandle *l3cfg_commit_type_old = NULL;
+    gboolean                 l3_changed;
+    int                      ip_ifindex_new;
+    int                     *p_ifindex;
+    gboolean                 l3cfg_was_reset = FALSE;
 
-    if (ifindex < 0)
+    if (ifindex < 0) {
+        nm_assert_not_reached();
         ifindex = 0;
+    }
 
     p_ifindex = is_ip_ifindex ? &priv->ip_ifindex_ : &priv->ifindex_;
 
@@ -2986,13 +4020,108 @@ _set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
 
     *p_ifindex = ifindex;
 
-    _LOGD(LOGD_DEVICE, "ifindex: set %sifindex %d", is_ip_ifindex ? "ip-" : "", ifindex);
+    ip_ifindex_new = nm_device_get_ip_ifindex(self);
 
-    if (!is_ip_ifindex)
-        _notify(self, PROP_IFINDEX);
+    if (priv->l3cfg) {
+        if (ip_ifindex_new <= 0 || ip_ifindex_new != nm_l3cfg_get_ifindex(priv->l3cfg)) {
+            const NML3ConfigData *l3cd_old;
+
+            if (ip_ifindex_new <= 0) {
+                /* The ifindex was reset. Send a last L3CD_CHANGED
+                 * signal with a NULL l3cd so that the old one can
+                 * be removed from the DNS manager.
+                 */
+                l3cd_old = nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE);
+                if (l3cd_old)
+                    g_signal_emit(self, signals[L3CD_CHANGED], 0, l3cd_old, NULL);
+            }
+
+            g_signal_handlers_disconnect_by_func(priv->l3cfg,
+                                                 G_CALLBACK(_dev_l3_cfg_notify_cb),
+                                                 self);
+            l3cfg_old             = g_steal_pointer(&priv->l3cfg_);
+            l3cfg_commit_type_old = g_steal_pointer(&priv->l3cfg_commit_type);
+            l3cfg_was_reset       = TRUE;
+        }
+    }
+    if (!priv->l3cfg && ip_ifindex_new > 0) {
+        priv->l3cfg_ = nm_netns_l3cfg_acquire(priv->netns, ip_ifindex_new);
+
+        g_signal_connect(priv->l3cfg,
+                         NM_L3CFG_SIGNAL_NOTIFY,
+                         G_CALLBACK(_dev_l3_cfg_notify_cb),
+                         self);
+
+        _dev_l3_cfg_commit_type_reset(self);
+        l3cfg_was_reset = TRUE;
+    }
+    if (!priv->l3cfg) {
+        _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_KEEP, FALSE);
+        _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_KEEP, FALSE);
+    }
+
+    _LOGD(LOGD_DEVICE,
+          "ifindex: set %sifindex %d%s%s%s%s%s%s",
+          is_ip_ifindex ? "ip-" : "",
+          ifindex,
+          NM_PRINT_FMT_QUOTED(l3cfg_old && l3cfg_old != priv->l3cfg,
+                              " (old-l3cfg: ",
+                              nm_hash_obfuscated_ptr_str_a(l3cfg_old),
+                              ")",
+                              ""),
+          NM_PRINT_FMT_QUOTED(priv->l3cfg && l3cfg_old != priv->l3cfg,
+                              " (l3cfg: ",
+                              nm_hash_obfuscated_ptr_str_a(priv->l3cfg),
+                              ")",
+                              ""));
 
     if (priv->manager)
         nm_manager_emit_device_ifindex_changed(priv->manager, self);
+
+    if (!is_ip_ifindex)
+        _notify(self, PROP_IFINDEX);
+
+    if (l3cfg_was_reset) {
+        gs_unref_object NMIPConfig *ipconf_old_4 = NULL;
+        gs_unref_object NMIPConfig *ipconf_old_6 = NULL;
+
+        ipconf_old_4 = g_steal_pointer(&priv->l3ipdata_4.ip_config);
+        ipconf_old_6 = g_steal_pointer(&priv->l3ipdata_6.ip_config);
+        if (priv->l3cfg) {
+            priv->l3ipdata_4.ip_config = nm_l3cfg_ipconfig_acquire(priv->l3cfg, AF_INET);
+            priv->l3ipdata_6.ip_config = nm_l3cfg_ipconfig_acquire(priv->l3cfg, AF_INET6);
+        }
+        _notify(self, PROP_IP4_CONFIG);
+        _notify(self, PROP_IP6_CONFIG);
+    }
+
+    if (l3cfg_old != priv->l3cfg) {
+        l3_changed = FALSE;
+        if (_dev_l3_register_l3cds(self, l3cfg_old, FALSE, FALSE))
+            l3_changed = TRUE;
+
+        /* Now it gets ugly. We changed the ip-ifindex, which determines the NML3Cfg instance.
+         * But all the NML3ConfigData we currently track are still for the old ifindex. We
+         * need to update them.
+         *
+         * This should be all handled entirely different, where an NMDevice is strictly
+         * associated with one ifindex (and not the ifindex/ip-ifindex split). Or it
+         * is not at all associated with an ifindex, but only a controlling device for
+         * a real NMDevice (that has the ifindex). */
+        _dev_l3_update_l3cds_ifindex(self);
+
+        if (_dev_l3_register_l3cds(self, priv->l3cfg, TRUE, FALSE))
+            l3_changed = TRUE;
+
+        if (l3_changed)
+            _dev_l3_cfg_commit(self, TRUE);
+    }
+
+    if (l3cfg_commit_type_old)
+        nm_l3cfg_commit_type_unregister(l3cfg_old, l3cfg_commit_type_old);
+
+    update_prop_ip_iface(self);
+
     return TRUE;
 }
 
@@ -3013,9 +4142,9 @@ _set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
 gboolean
 nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **error)
 {
-    NMDevicePrivate *     priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
     const NMPlatformLink *plink;
-    NMPlatform *          platform;
+    NMPlatform           *platform;
 
     nm_assert(ifindex > 0);
     NM_SET_OUT(old_name, NULL);
@@ -3140,7 +4269,7 @@ static void
 _set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatform *     platform;
+    NMPlatform      *platform;
     gboolean         eq_name;
 
     /* normalize arguments */
@@ -3159,13 +4288,12 @@ _set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname)
           NM_PRINT_FMT_QUOTE_STRING(ifname),
           ifindex);
 
-    _set_ifindex(self, ifindex, TRUE);
-
     if (!eq_name) {
         g_free(priv->ip_iface_);
         priv->ip_iface_ = g_strdup(ifname);
-        _notify(self, PROP_IP_IFACE);
+        update_prop_ip_iface(self);
     }
+    _set_ifindex(self, ifindex, TRUE);
 
     if (priv->ip_ifindex > 0) {
         platform = nm_device_get_platform(self);
@@ -3265,10 +4393,10 @@ parent_changed_notify(NMDevice *self,
 static gboolean
 _parent_set_ifindex(NMDevice *self, int parent_ifindex, gboolean force_check)
 {
-    NMDevicePrivate *priv;
-    NMDevice *       parent_device;
-    gboolean         changed = FALSE;
-    int              old_ifindex;
+    NMDevicePrivate          *priv;
+    NMDevice                 *parent_device;
+    gboolean                  changed = FALSE;
+    int                       old_ifindex;
     gs_unref_object NMDevice *old_device = NULL;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
@@ -3359,7 +4487,7 @@ const char *
 nm_device_parent_find_for_connection(NMDevice *self, const char *current_setting_parent)
 {
     const char *new_parent;
-    NMDevice *  parent_device;
+    NMDevice   *parent_device;
 
     parent_device = nm_device_parent_get_device(self);
     if (!parent_device)
@@ -3496,10 +4624,10 @@ _stats_set_refresh_rate(NMDevice *self, guint refresh_rate_ms)
 static gboolean
 get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
 {
-    NMDevicePrivate *     priv     = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatform *          platform = nm_device_get_platform(self);
+    NMDevicePrivate      *priv     = NM_DEVICE_GET_PRIVATE(self);
+    NMPlatform           *platform = nm_device_get_platform(self);
     const NMPlatformLink *pllink;
-    const guint8 *        hwaddr;
+    const guint8         *hwaddr;
     guint8                pseudo_hwaddr[ETH_ALEN];
     gsize                 hwaddr_len;
     int                   ifindex;
@@ -3560,6 +4688,7 @@ get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
  * @self: an #NMDevice
  * @iid: where to place the interface identifier
  * @ignore_token: force creation of a non-tokenized address
+ * @out_is_token: on return, whether the identifier is tokenized
  *
  * Return the interface's identifier for the EUI64 address generation mode.
  * It's either a manually set token or and identifier generated in a
@@ -3571,19 +4700,26 @@ get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
  * Returns: #TRUE if the @iid could be set
  */
 static gboolean
-nm_device_get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *iid, gboolean ignore_token)
+nm_device_get_ip_iface_identifier(NMDevice           *self,
+                                  NMUtilsIPv6IfaceId *iid,
+                                  gboolean            ignore_token,
+                                  gboolean           *out_is_token)
 {
     NMSettingIP6Config *s_ip6;
-    const char *        token = NULL;
+    const char         *token = NULL;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
 
+    NM_SET_OUT(out_is_token, FALSE);
+
     if (!ignore_token) {
         s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
 
         g_return_val_if_fail(s_ip6, FALSE);
 
         token = nm_setting_ip6_config_get_token(s_ip6);
+        if (token)
+            NM_SET_OUT(out_is_token, TRUE);
     }
     if (token)
         return nm_utils_ipv6_interface_identifier_get_from_token(iid, token);
@@ -3729,29 +4865,16 @@ nm_device_get_route_metric_default(NMDeviceType device_type)
     return 11000;
 }
 
-static gboolean
-default_route_metric_penalty_detect(NMDevice *self, int addr_family)
+static guint32
+_dev_default_route_metric_penalty_get(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    /* currently we don't differentiate between IPv4 and IPv6 when detecting
-     * connectivity. */
     if (priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL
         && nm_connectivity_check_enabled(concheck_get_mgr(self)))
-        return TRUE;
-
-    return FALSE;
-}
-
-static guint32
-default_route_metric_penalty_get(NMDevice *self, int addr_family)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (NM_IS_IPv4(addr_family) ? priv->default_route_metric_penalty_ip4_has
-                                : priv->default_route_metric_penalty_ip6_has)
         return 20000;
+
     return 0;
 }
 
@@ -3760,8 +4883,8 @@ nm_device_get_route_metric(NMDevice *self, int addr_family)
 {
     gint64             route_metric;
     NMSettingIPConfig *s_ip;
-    NMConnection *     connection;
-    const char *       property;
+    NMConnection      *connection;
+    const char        *property;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_MAXUINT32);
     g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), G_MAXUINT32);
@@ -3813,28 +4936,23 @@ nm_device_get_route_table(NMDevice *self, int addr_family)
     return route_table ?: (guint32) RT_TABLE_MAIN;
 }
 
-static NMIPRouteTableSyncMode
+/* FIXME(l3cfg): need to properly handle the route-table sync mode and
+ * use it during commit. */
+_nm_unused static NMIPRouteTableSyncMode
 _get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
 {
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMDedupMultiIter ipconf_iter;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     gboolean         all_sync_now;
     gboolean         all_sync_eff;
 
     all_sync_now = _prop_get_ipvx_route_table(self, addr_family) != 0u;
 
     if (!all_sync_now) {
-        const NMPlatformIPRoute *route;
+        const NML3ConfigData *l3cd = priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d;
 
         /* If there's a local route switch to all-sync in order
          * to properly manage the local table */
-        nm_ip_config_iter_ip_route_for_each (&ipconf_iter, priv->con_ip_config_x[IS_IPv4], &route) {
-            if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) {
-                all_sync_now = TRUE;
-                break;
-            }
-        }
+        all_sync_now = l3cd && nm_l3_config_data_has_routes_with_type_local(l3cd, addr_family);
     }
 
     if (all_sync_now)
@@ -3866,18 +4984,20 @@ nm_device_get_best_default_route(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    switch (addr_family) {
-    case AF_INET:
-        return priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL;
-    case AF_INET6:
-        return priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6) : NULL;
-    case AF_UNSPEC:
-        return (priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL)
-                   ?: (priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6)
-                                         : NULL);
-    default:
-        g_return_val_if_reached(NULL);
-    }
+    if (!priv->l3cfg)
+        return NULL;
+
+    /* FIXME(l3cfg): this function returns the best default route that we
+     * *want* to configure. What is the meaning of that? Possibly the caller
+     * cares whether there *is* a default route configured, for which they
+     * should ask platform.
+     *
+     * Check callers why they call this. Quite possibly this whole notion of
+     * "has a default route" is wrong to being with, regardless whether we
+     * look at the desired or actual configuration. That is, because "has a default route"
+     * does not do justice to the complexity of routing (with policy routing,
+     * etc.). */
+    return nm_l3cfg_get_best_default_route(priv->l3cfg, addr_family, TRUE);
 }
 
 const char *
@@ -3982,7 +5102,7 @@ NMConnection *
 nm_device_get_settings_connection_get_connection(NMDevice *self)
 {
     NMSettingsConnection *sett_con;
-    NMDevicePrivate *     priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
 
     if (!priv->act_request.obj)
         return NULL;
@@ -4055,7 +5175,7 @@ typedef enum {
     CONCHECK_SCHEDULE_RETURNED_MAX,
 } ConcheckScheduleMode;
 
-static NMDeviceConnectivityHandle *concheck_start(NMDevice *                   self,
+static NMDeviceConnectivityHandle *concheck_start(NMDevice                    *self,
                                                   int                          addr_family,
                                                   NMDeviceConnectivityCallback callback,
                                                   gpointer                     user_data,
@@ -4361,7 +5481,7 @@ nm_device_check_connectivity_update_interval(NMDevice *self)
 }
 
 static void
-concheck_update_state(NMDevice *          self,
+concheck_update_state(NMDevice           *self,
                       int                 addr_family,
                       NMConnectivityState state,
                       gboolean            allow_periodic_bump)
@@ -4432,22 +5552,16 @@ concheck_update_state(NMDevice *          self,
 
     _notify(self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY);
 
-    if (priv->state == NM_DEVICE_STATE_ACTIVATED && !nm_device_sys_iface_state_is_external(self)) {
-        if (nm_device_get_best_default_route(self, AF_INET)
-            && !ip_config_merge_and_apply(self, AF_INET, TRUE))
-            _LOGW(LOGD_IP4, "Failed to update IPv4 route metric");
-        if (nm_device_get_best_default_route(self, AF_INET6)
-            && !ip_config_merge_and_apply(self, AF_INET6, TRUE))
-            _LOGW(LOGD_IP6, "Failed to update IPv6 route metric");
-    }
+    if (priv->state == NM_DEVICE_STATE_ACTIVATED && !nm_device_sys_iface_state_is_external(self))
+        _dev_l3_register_l3cds(self, priv->l3cfg, TRUE, NM_TERNARY_DEFAULT);
 }
 
 static const char *
 nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family)
 {
     NMDeviceClass *klass;
-    NMConnection * connection = nm_device_get_applied_connection(self);
-    const char *   method;
+    NMConnection  *connection = nm_device_get_applied_connection(self);
+    const char    *method;
     const int      IS_IPv4 = NM_IS_IPv4(addr_family);
 
     g_return_val_if_fail(NM_IS_CONNECTION(connection), "" /* bogus */);
@@ -4494,16 +5608,16 @@ concheck_handle_complete(NMDeviceConnectivityHandle *handle, GError *error)
 }
 
 static void
-concheck_cb(NMConnectivity *           connectivity,
+concheck_cb(NMConnectivity            *connectivity,
             NMConnectivityCheckHandle *c_handle,
             NMConnectivityState        state,
             gpointer                   user_data)
 {
     _nm_unused gs_unref_object NMDevice *self_keep_alive = NULL;
-    NMDevice *                           self;
-    NMDevicePrivate *                    priv;
-    NMDeviceConnectivityHandle *         handle;
-    NMDeviceConnectivityHandle *         other_handle;
+    NMDevice                            *self;
+    NMDevicePrivate                     *priv;
+    NMDeviceConnectivityHandle          *handle;
+    NMDeviceConnectivityHandle          *other_handle;
     gboolean                             handle_is_alive;
     gboolean                             allow_periodic_bump;
     gboolean                             any_periodic_before;
@@ -4627,16 +5741,16 @@ check_handles:
 }
 
 static NMDeviceConnectivityHandle *
-concheck_start(NMDevice *                   self,
+concheck_start(NMDevice                    *self,
                int                          addr_family,
                NMDeviceConnectivityCallback callback,
                gpointer                     user_data,
                gboolean                     is_periodic)
 {
     static guint64              seq_counter = 0;
-    NMDevicePrivate *           priv;
+    NMDevicePrivate            *priv;
     NMDeviceConnectivityHandle *handle;
-    const char *                ifname;
+    const char                 *ifname;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
 
@@ -4692,7 +5806,7 @@ concheck_start(NMDevice *                   self,
 }
 
 NMDeviceConnectivityHandle *
-nm_device_check_connectivity(NMDevice *                   self,
+nm_device_check_connectivity(NMDevice                    *self,
                              int                          addr_family,
                              NMDeviceConnectivityCallback callback,
                              gpointer                     user_data)
@@ -4748,8 +5862,8 @@ static SlaveInfo *
 find_slave_info(NMDevice *self, NMDevice *slave)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    CList *          iter;
-    SlaveInfo *      info;
+    CList           *iter;
+    SlaveInfo       *info;
 
     c_list_for_each (iter, &priv->slaves) {
         info = c_list_entry(iter, SlaveInfo, lst_slave);
@@ -4774,16 +5888,14 @@ find_slave_info(NMDevice *self, NMDevice *slave)
 static gboolean
 nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *connection)
 {
-    NMDevicePrivate *priv;
-    SlaveInfo *      info;
-    gboolean         success = FALSE;
-    gboolean         configure;
+    SlaveInfo *info;
+    gboolean   success = FALSE;
+    gboolean   configure;
 
     g_return_val_if_fail(self != NULL, FALSE);
     g_return_val_if_fail(slave != NULL, FALSE);
     g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE);
 
-    priv = NM_DEVICE_GET_PRIVATE(self);
     info = find_slave_info(self, slave);
     if (!info)
         return FALSE;
@@ -4806,22 +5918,17 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
      */
     nm_device_update_hw_address(self);
 
+    /* Since slave devices don't have their own IP configuration,
+     * set the MTU here.
+     */
+    _commit_mtu(slave);
+
     /* Restart IP configuration if we're waiting for slaves.  Do this
      * after updating the hardware address as IP config may need the
      * new address.
      */
-    if (success) {
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
-            nm_device_activate_stage3_ip_start(self, AF_INET);
-
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
-            nm_device_activate_stage3_ip_start(self, AF_INET6);
-    }
-
-    /* Since slave devices don't have their own IP configuration,
-     * set the MTU here.
-     */
-    _commit_mtu(slave, NM_DEVICE_GET_PRIVATE(slave)->ip_config_4);
+    if (success)
+        nm_device_activate_schedule_stage3_ip_config(self, FALSE);
 
     return success;
 }
@@ -4840,15 +5947,15 @@ nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *co
  * updates the state of @self and @slave to reflect its release.
  */
 static void
-nm_device_master_release_one_slave(NMDevice *          self,
-                                   NMDevice *          slave,
+nm_device_master_release_one_slave(NMDevice           *self,
+                                   NMDevice           *slave,
                                    gboolean            configure,
                                    gboolean            force,
                                    NMDeviceStateReason reason)
 {
-    NMDevicePrivate *priv;
-    NMDevicePrivate *slave_priv;
-    SlaveInfo *      info;
+    NMDevicePrivate          *priv;
+    NMDevicePrivate          *slave_priv;
+    SlaveInfo                *info;
     gs_unref_object NMDevice *self_free = NULL;
 
     g_return_if_fail(NM_DEVICE(self));
@@ -4908,6 +6015,8 @@ nm_device_master_release_one_slave(NMDevice *          self,
                                      NM_DEVICE_STATE_REASON_REMOVED);
 }
 
+/*****************************************************************************/
+
 /**
  * can_unmanaged_external_down:
  * @self: the device
@@ -4922,7 +6031,7 @@ can_unmanaged_external_down(NMDevice *self)
 }
 
 static NMUnmanFlagOp
-is_unmanaged_external_down(NMDevice *self, gboolean consider_can)
+_dev_unmanaged_is_external_down(NMDevice *self, gboolean consider_can)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -4939,7 +6048,7 @@ is_unmanaged_external_down(NMDevice *self, gboolean consider_can)
 }
 
 static void
-set_unmanaged_external_down(NMDevice *self, gboolean only_if_unmanaged)
+_dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, gboolean now)
 {
     NMUnmanFlagOp ext_flags;
 
@@ -4951,7 +6060,7 @@ set_unmanaged_external_down(NMDevice *self, gboolean only_if_unmanaged)
             return;
     }
 
-    ext_flags = is_unmanaged_external_down(self, FALSE);
+    ext_flags = _dev_unmanaged_is_external_down(self, FALSE);
     if (ext_flags != NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
         /* Ensure the assume check is queued before any queued state changes
          * from the transition to UNAVAILABLE.
@@ -4959,10 +6068,17 @@ set_unmanaged_external_down(NMDevice *self, gboolean only_if_unmanaged)
         nm_device_queue_recheck_assume(self);
     }
 
-    nm_device_set_unmanaged_by_flags(self,
-                                     NM_UNMANAGED_EXTERNAL_DOWN,
-                                     ext_flags,
-                                     NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+    if (now) {
+        nm_device_set_unmanaged_by_flags(self,
+                                         NM_UNMANAGED_EXTERNAL_DOWN,
+                                         ext_flags,
+                                         NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+    } else {
+        nm_device_set_unmanaged_by_flags_queue(self,
+                                               NM_UNMANAGED_EXTERNAL_DOWN,
+                                               ext_flags,
+                                               NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
+    }
 }
 
 void
@@ -4979,26 +6095,15 @@ nm_device_update_dynamic_ip_setup(NMDevice *self)
 
     g_hash_table_remove_all(priv->ip6_saved_properties);
 
-    if (priv->dhcp_data_4.client) {
-        if (!nm_device_dhcp4_renew(self, FALSE)) {
-            nm_device_state_changed(self,
-                                    NM_DEVICE_STATE_FAILED,
-                                    NM_DEVICE_STATE_REASON_DHCP_FAILED);
-            return;
-        }
-    }
-    if (priv->dhcp_data_6.client) {
-        if (!nm_device_dhcp6_renew(self, FALSE)) {
-            nm_device_state_changed(self,
-                                    NM_DEVICE_STATE_FAILED,
-                                    NM_DEVICE_STATE_REASON_DHCP_FAILED);
-            return;
-        }
-    }
-    if (priv->ndisc) {
+    if (priv->ipdhcp_data_4.state != NM_DEVICE_IP_STATE_NONE)
+        _dev_ipdhcpx_restart(self, AF_INET, FALSE);
+    if (priv->ipdhcp_data_6.state != NM_DEVICE_IP_STATE_NONE)
+        _dev_ipdhcpx_restart(self, AF_INET6, FALSE);
+
+    if (priv->ipac6_data.ndisc) {
         /* FIXME: todo */
     }
-    if (priv->dnsmasq_manager) {
+    if (priv->ipshared_data_4.v4.dnsmasq_manager) {
         /* FIXME: todo */
     }
 }
@@ -5033,10 +6138,8 @@ carrier_changed(NMDevice *self, gboolean carrier)
                 nm_device_update_dynamic_ip_setup(self);
             /* If needed, also resume IP configuration that is
              * waiting for carrier. */
-            if (nm_device_activate_ip4_state_in_wait(self))
-                nm_device_activate_stage3_ip_start(self, AF_INET);
-            if (nm_device_activate_ip6_state_in_wait(self))
-                nm_device_activate_stage3_ip_start(self, AF_INET6);
+            if (priv->state == NM_DEVICE_STATE_IP_CONFIG)
+                nm_device_activate_schedule_stage3_ip_config(self, FALSE);
             return;
         }
         /* fall-through and change state of device */
@@ -5082,7 +6185,7 @@ carrier_changed(NMDevice *self, gboolean carrier)
 static gboolean
 carrier_disconnected_action_cb(gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     _LOGD(LOGD_DEVICE,
@@ -5134,10 +6237,6 @@ nm_device_set_carrier(NMDevice *self, gboolean carrier)
             nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
             _carrier_wait_check_queued_act_request(self);
         }
-
-        /* Send ARP announcements if did not yet and have carrier. */
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !priv->acd.announcing)
-            nm_device_arp_announce(self);
     } else {
         if (priv->carrier_wait_id)
             nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
@@ -5188,10 +6287,10 @@ nm_device_set_carrier_from_platform(NMDevice *self)
 static void
 device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
 {
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
-    NMDevice *           master;
+    NMDevicePrivate                *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevice                       *master;
     nm_auto_nmpobj const NMPObject *plink_master_keep_alive = NULL;
-    const NMPlatformLink *          plink_master;
+    const NMPlatformLink           *plink_master;
 
     g_return_if_fail(plink);
 
@@ -5269,88 +6368,9 @@ device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice
 }
 
 static void
-ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
-{
-    NMDevicePrivate *priv                    = NM_DEVICE_GET_PRIVATE(self);
-    gs_unref_array GArray *addresses         = NULL;
-    gs_unref_array GArray *dns_servers       = NULL;
-    gs_unref_array GArray *      dns_domains = NULL;
-    guint                        len;
-    guint                        i;
-    const NMDedupMultiHeadEntry *head_entry;
-    NMDedupMultiIter             ipconf_iter;
-
-    if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
-        return;
-
-    head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6);
-    addresses =
-        g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0);
-    nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
-        const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj);
-        NMNDiscAddress *            ndisc_addr;
-        guint32                     lifetime;
-        guint32                     preferred;
-
-        if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
-            continue;
-
-        if (addr->n_ifa_flags & IFA_F_TENTATIVE || addr->n_ifa_flags & IFA_F_DADFAILED)
-            continue;
-
-        if (addr->plen != 64)
-            continue;
-
-        lifetime = nmp_utils_lifetime_get(addr->timestamp,
-                                          addr->lifetime,
-                                          addr->preferred,
-                                          NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
-                                          &preferred);
-        if (!lifetime)
-            continue;
-
-        g_array_set_size(addresses, addresses->len + 1);
-        ndisc_addr          = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
-        ndisc_addr->address = addr->address;
-        ndisc_addr->expiry_msec =
-            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime);
-        ndisc_addr->expiry_preferred_msec =
-            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred);
-    }
-
-    len         = nm_ip6_config_get_num_nameservers(priv->ip_config_6);
-    dns_servers = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSServer), len);
-    g_array_set_size(dns_servers, len);
-    for (i = 0; i < len; i++) {
-        const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i);
-        NMNDiscDNSServer *     ndisc_nameserver;
-
-        ndisc_nameserver          = &g_array_index(dns_servers, NMNDiscDNSServer, i);
-        ndisc_nameserver->address = *nameserver;
-        ndisc_nameserver->expiry_msec =
-            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
-    }
-
-    len         = nm_ip6_config_get_num_searches(priv->ip_config_6);
-    dns_domains = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSDomain), len);
-    g_array_set_size(dns_domains, len);
-    for (i = 0; i < len; i++) {
-        const char *      search = nm_ip6_config_get_search(priv->ip_config_6, i);
-        NMNDiscDNSDomain *ndisc_search;
-
-        ndisc_search         = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
-        ndisc_search->domain = (char *) search;
-        ndisc_search->expiry_msec =
-            _nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
-    }
-
-    nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains);
-}
-
-static void
 device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
 {
-    NMDevicePrivate *      priv  = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate       *priv  = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceInterfaceFlags flags = NM_DEVICE_INTERFACE_FLAG_NONE;
 
     if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP))
@@ -5378,14 +6398,15 @@ device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
 }
 
 static gboolean
-device_link_changed(NMDevice *self)
+device_link_changed(gpointer user_data)
 {
-    NMDeviceClass *      klass                        = NM_DEVICE_GET_CLASS(self);
-    NMDevicePrivate *    priv                         = NM_DEVICE_GET_PRIVATE(self);
-    gboolean             ip_ifname_changed            = FALSE;
+    NMDevice                       *self              = user_data;
+    NMDeviceClass                  *klass             = NM_DEVICE_GET_CLASS(self);
+    NMDevicePrivate                *priv              = NM_DEVICE_GET_PRIVATE(self);
+    gboolean                        ip_ifname_changed = FALSE;
     nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL;
-    const NMPlatformLink *          pllink;
-    const char *                    str;
+    const NMPlatformLink           *pllink;
+    const char                     *str;
     int                             ifindex;
     gboolean                        was_up;
     gboolean                        update_unmanaged_specs = FALSE;
@@ -5446,14 +6467,15 @@ device_link_changed(NMDevice *self)
         /* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
         ip_ifname_changed = !priv->ip_iface;
 
-        if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT))
-            nm_device_set_unmanaged_by_user_settings(self);
-        else
+        if (!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
+            /* Since the interface name changed, we need to re-evaluate the
+             * user settings specs. */
             update_unmanaged_specs = TRUE;
+        }
 
         _notify(self, PROP_IFACE);
         if (ip_ifname_changed)
-            _notify(self, PROP_IP_IFACE);
+            update_prop_ip_iface(self);
 
         /* Re-match available connections against the new interface name */
         nm_device_recheck_available_connections(self);
@@ -5464,8 +6486,8 @@ device_link_changed(NMDevice *self)
         nm_device_emit_recheck_auto_activate(self);
     }
 
-    if (priv->ndisc && pllink->inet6_token.id) {
-        if (nm_ndisc_set_iid(priv->ndisc, pllink->inet6_token))
+    if (priv->ipac6_data.ndisc && pllink->inet6_token.id) {
+        if (nm_ndisc_set_iid(priv->ipac6_data.ndisc, pllink->inet6_token, TRUE))
             _LOGD(LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
     }
 
@@ -5508,31 +6530,35 @@ device_link_changed(NMDevice *self)
             }
         }
 
+        /* The assume check should happen before the device transitions to
+         * UNAVAILABLE, because in UNAVAILABLE we already clean up the IP
+         * configuration. Therefore, this function should never trigger a
+         * sync state transition.
+         */
         nm_device_queue_recheck_assume(self);
-        nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
+        nm_device_set_unmanaged_by_flags_queue(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
     }
 
-    set_unmanaged_external_down(self, FALSE);
+    _dev_unmanaged_check_external_down(self, FALSE, FALSE);
 
     device_recheck_slave_status(self, pllink);
 
     if (priv->up && (!was_up || seen_down)) {
         /* the link was down and just came up. That happens for example, while changing MTU.
-         * We must restore IP configuration. */
-        if (NM_IN_SET(priv->ip_state_4, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) {
-            if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
-                _LOGW(LOGD_IP4, "failed applying IP4 config after link comes up again");
-        }
-
-        priv->linklocal6_dad_counter = 0;
-        if (NM_IN_SET(priv->ip_state_6, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) {
-            if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-                _LOGW(LOGD_IP6, "failed applying IP6 config after link comes up again");
-        }
+         * We must restore IP configuration.
+         *
+         * FIXME(l3cfg): when NML3Cfg notices that the device goes down and up, then
+         * it should automatically schedule a REAPPLY commit -- provided that the current
+         * commit-type is >= UPDATE. The idea is to move logic away from NMDevice
+         * so that it theoretically would also work for NMVpnConnection (although,
+         * NMVpnConnection should become like a regular device, akin to NMDevicePpp).
+         */
+        if (!nm_device_sys_iface_state_is_external(self))
+            nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
     }
 
     if (update_unmanaged_specs)
-        nm_device_set_unmanaged_by_user_settings(self);
+        nm_device_set_unmanaged_by_user_settings(self, FALSE);
 
     if (got_hw_addr && !priv->up && nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE) {
         /*
@@ -5550,11 +6576,12 @@ device_link_changed(NMDevice *self)
 }
 
 static gboolean
-device_ip_link_changed(NMDevice *self)
+device_ip_link_changed(gpointer user_data)
 {
-    NMDevicePrivate *     priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevice             *self = user_data;
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
     const NMPlatformLink *pllink;
-    const char *          ip_iface;
+    const char           *ip_iface;
 
     priv->device_ip_link_changed_id = 0;
 
@@ -5586,7 +6613,7 @@ device_ip_link_changed(NMDevice *self)
               ip_iface);
         g_free(priv->ip_iface_);
         priv->ip_iface_ = g_strdup(ip_iface);
-        _notify(self, PROP_IP_IFACE);
+        update_prop_ip_iface(self);
 
         nm_device_update_dynamic_ip_setup(self);
     }
@@ -5595,15 +6622,15 @@ device_ip_link_changed(NMDevice *self)
 }
 
 static void
-link_changed_cb(NMPlatform *    platform,
+link_changed_cb(NMPlatform     *platform,
                 int             obj_type_i,
                 int             ifindex,
                 NMPlatformLink *info,
                 int             change_type_i,
-                NMDevice *      self)
+                NMDevice       *self)
 {
     const NMPlatformSignalChangeType change_type = change_type_i;
-    NMDevicePrivate *                priv;
+    NMDevicePrivate                 *priv;
 
     if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
         return;
@@ -5614,13 +6641,12 @@ link_changed_cb(NMPlatform *    platform,
         if (!(info->n_ifi_flags & IFF_UP))
             priv->device_link_changed_down = TRUE;
         if (!priv->device_link_changed_id) {
-            priv->device_link_changed_id = g_idle_add((GSourceFunc) device_link_changed, self);
+            priv->device_link_changed_id = g_idle_add(device_link_changed, self);
             _LOGD(LOGD_DEVICE, "queued link change for ifindex %d", ifindex);
         }
     } else if (ifindex == nm_device_get_ip_ifindex(self)) {
         if (!priv->device_ip_link_changed_id) {
-            priv->device_ip_link_changed_id =
-                g_idle_add((GSourceFunc) device_ip_link_changed, self);
+            priv->device_ip_link_changed_id = g_idle_add(device_ip_link_changed, self);
             _LOGD(LOGD_DEVICE, "queued link change for ip-ifindex %d", ifindex);
         }
     }
@@ -5703,14 +6729,14 @@ link_type_compatible(NMDevice *self, NMLinkType link_type, gboolean *out_compati
  * Returns: %TRUE on success, %FALSE on error
  */
 gboolean
-nm_device_realize_start(NMDevice *            self,
+nm_device_realize_start(NMDevice             *self,
                         const NMPlatformLink *plink,
                         gboolean              assume_state_guess_assume,
-                        const char *          assume_state_connection_uuid,
+                        const char           *assume_state_connection_uuid,
                         gboolean              set_nm_owned,
                         NMUnmanFlagOp         unmanaged_user_explicit,
-                        gboolean *            out_compatible,
-                        GError **             error)
+                        gboolean             *out_compatible,
+                        GError              **error)
 {
     nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
 
@@ -5757,14 +6783,14 @@ nm_device_realize_start(NMDevice *            self,
  * Returns: %TRUE on success, %FALSE on error
  */
 gboolean
-nm_device_create_and_realize(NMDevice *    self,
+nm_device_create_and_realize(NMDevice     *self,
                              NMConnection *connection,
-                             NMDevice *    parent,
-                             GError **     error)
+                             NMDevice     *parent,
+                             GError      **error)
 {
     nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
-    NMDevicePrivate *               priv             = NM_DEVICE_GET_PRIVATE(self);
-    const NMPlatformLink *          plink;
+    NMDevicePrivate                *priv             = NM_DEVICE_GET_PRIVATE(self);
+    const NMPlatformLink           *plink;
     gboolean                        nm_owned;
 
     /* Must be set before device is realized */
@@ -5812,7 +6838,7 @@ void
 nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    const char *     str;
+    const char      *str;
     gboolean         ifindex_changed;
     guint32          mtu;
 
@@ -5889,9 +6915,9 @@ sriov_op_start(NMDevice *self, SriovOp *op)
 static void
 sriov_op_cb(GError *error, gpointer user_data)
 {
-    SriovOp *       op             = user_data;
+    SriovOp                  *op   = user_data;
     gs_unref_object NMDevice *self = op->device;
-    NMDevicePrivate *         priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate          *priv = NM_DEVICE_GET_PRIVATE(self);
 
     nm_assert(op == priv->sriov.pending);
 
@@ -5941,7 +6967,7 @@ sriov_op_queue_op(NMDevice *self, SriovOp *op)
 }
 
 static void
-sriov_op_queue(NMDevice *              self,
+sriov_op_queue(NMDevice               *self,
                guint                   num_vfs,
                NMOptionBool            autoprobe,
                NMPlatformAsyncCallback callback,
@@ -6000,11 +7026,11 @@ device_init_static_sriov_num_vfs(NMDevice *self)
 }
 
 static void
-config_changed(NMConfig *          config,
-               NMConfigData *      config_data,
+config_changed(NMConfig           *config,
+               NMConfigData       *config_data,
                NMConfigChangeFlags changes,
-               NMConfigData *      old_data,
-               NMDevice *          self)
+               NMConfigData       *old_data,
+               NMDevice           *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -6043,19 +7069,19 @@ realize_start_notify(NMDevice *self, const NMPlatformLink *pllink)
  * stuff).
  */
 static void
-realize_start_setup(NMDevice *            self,
+realize_start_setup(NMDevice             *self,
                     const NMPlatformLink *plink,
                     gboolean              assume_state_guess_assume,
-                    const char *          assume_state_connection_uuid,
+                    const char           *assume_state_connection_uuid,
                     gboolean              set_nm_owned,
                     NMUnmanFlagOp         unmanaged_user_explicit,
                     gboolean              force_platform_init)
 {
-    NMDevicePrivate *    priv;
-    NMDeviceClass *      klass;
-    NMPlatform *         platform;
+    NMDevicePrivate     *priv;
+    NMDeviceClass       *klass;
+    NMPlatform          *platform;
     NMDeviceCapabilities capabilities = 0;
-    NMConfig *           config;
+    NMConfig            *config;
     guint                refresh_rate_ms;
     gboolean             unmanaged;
 
@@ -6072,8 +7098,6 @@ realize_start_setup(NMDevice *            self,
     g_return_if_fail(nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT));
     g_return_if_fail(priv->ip_ifindex <= 0);
     g_return_if_fail(priv->ip_iface == NULL);
-    g_return_if_fail(!priv->queued_ip_config_id_4);
-    g_return_if_fail(!priv->queued_ip_config_id_6);
 
     _LOGD(LOGD_DEVICE,
           "start setup of %s, kernel ifindex %d",
@@ -6120,9 +7144,6 @@ realize_start_setup(NMDevice *            self,
         if (priv->firmware_version)
             _notify(self, PROP_FIRMWARE_VERSION);
 
-        priv->ipv6ll_handle = (nm_platform_link_get_inet6_addr_gen_mode(platform, priv->ifindex)
-                               == NM_IN6_ADDR_GEN_MODE_NONE);
-
         if (nm_platform_link_supports_sriov(platform, priv->ifindex))
             capabilities |= NM_DEVICE_CAP_SRIOV;
     }
@@ -6180,7 +7201,7 @@ realize_start_setup(NMDevice *            self,
      * or have IP addressing */
     nm_device_set_unmanaged_flags(self,
                                   NM_UNMANAGED_EXTERNAL_DOWN,
-                                  is_unmanaged_external_down(self, TRUE));
+                                  _dev_unmanaged_is_external_down(self, TRUE));
 
     /* Unmanaged the loopback device with an explicit NM_UNMANAGED_BY_TYPE flag.
      * Later we might want to manage 'lo' too. Currently, that doesn't work because
@@ -6219,9 +7240,6 @@ nm_device_realize_finish(NMDevice *self, const NMPlatformLink *plink)
     if (plink)
         device_recheck_slave_status(self, plink);
 
-    priv->update_ip_config_completed_v4 = FALSE;
-    priv->update_ip_config_completed_v6 = FALSE;
-
     priv->real = TRUE;
     _notify(self, PROP_REAL);
 
@@ -6242,7 +7260,7 @@ unrealize_notify(NMDevice *self)
 static gboolean
 available_connections_check_delete_unrealized_on_idle(gpointer user_data)
 {
-    NMDevice *       self = user_data;
+    NMDevice        *self = user_data;
     NMDevicePrivate *priv;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
@@ -6317,9 +7335,6 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
         }
     }
 
-    nm_clear_g_source(&priv->queued_ip_config_id_4);
-    nm_clear_g_source(&priv->queued_ip_config_id_6);
-
     g_object_freeze_notify(G_OBJECT(self));
     NM_DEVICE_GET_CLASS(self)->unrealize_notify(self);
 
@@ -6328,7 +7343,7 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
     _set_ifindex(self, 0, FALSE);
     _set_ifindex(self, 0, TRUE);
     if (nm_clear_g_free(&priv->ip_iface_))
-        _notify(self, PROP_IP_IFACE);
+        update_prop_ip_iface(self);
 
     priv->master_ifindex = 0;
 
@@ -6444,7 +7459,7 @@ NMConnection *
 nm_device_new_default_connection(NMDevice *self)
 {
     NMConnection *connection;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     if (!NM_DEVICE_GET_CLASS(self)->new_default_connection)
         return NULL;
@@ -6463,11 +7478,11 @@ nm_device_new_default_connection(NMDevice *self)
 }
 
 static void
-slave_state_changed(NMDevice *          slave,
+slave_state_changed(NMDevice           *slave,
                     NMDeviceState       slave_new_state,
                     NMDeviceState       slave_old_state,
                     NMDeviceStateReason reason,
-                    NMDevice *          self)
+                    NMDevice           *self)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     gboolean         release = FALSE;
@@ -6524,7 +7539,7 @@ nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure)
 {
     NMDevicePrivate *priv;
     NMDevicePrivate *slave_priv;
-    SlaveInfo *      info;
+    SlaveInfo       *info;
     gboolean         changed = FALSE;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
@@ -6594,9 +7609,9 @@ void
 nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLogDomain log_domain)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    const char *     slave_physical_port_id, *existing_physical_port_id;
-    SlaveInfo *      info;
-    CList *          iter;
+    const char      *slave_physical_port_id, *existing_physical_port_id;
+    SlaveInfo       *info;
+    CList           *iter;
 
     slave_physical_port_id = nm_device_get_physical_port_id(slave);
     if (!slave_physical_port_id)
@@ -6627,9 +7642,9 @@ nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLo
 void
 nm_device_master_release_slaves(NMDevice *self)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceStateReason reason;
-    CList *             iter, *safe;
+    CList              *iter, *safe;
 
     /* Don't release the slaves if this connection doesn't belong to NM. */
     if (nm_device_sys_iface_state_is_external(self))
@@ -6683,117 +7698,6 @@ nm_device_get_master(NMDevice *self)
     return NULL;
 }
 
-static gboolean
-get_ip_config_may_fail(NMDevice *self, int addr_family)
-{
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip;
-
-    connection = nm_device_get_applied_connection(self);
-
-    s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
-
-    return !s_ip || nm_setting_ip_config_get_may_fail(s_ip);
-}
-
-/*
- * check_ip_state
- *
- * When @full_state_update is TRUE, transition the device from IP_CONFIG to the
- * next state according to the outcome of IPv4 and IPv6 configuration. @may_fail
- * indicates that we are called just after the initial configuration and thus
- * IPv4/IPv6 are allowed to fail if the ipvx.may-fail properties say so, because
- * the IP methods couldn't even be started.
- * If @full_state_update is FALSE, just check if the connection should be failed
- * due to the state of both ip families and the ipvx.may-fail settings.
- */
-static void
-check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update)
-{
-    NMDevicePrivate *  priv         = NM_DEVICE_GET_PRIVATE(self);
-    gboolean           ip4_disabled = FALSE, ip6_disabled = FALSE;
-    NMSettingIPConfig *s_ip4, *s_ip6;
-    NMDeviceState      state;
-    int                IS_IPv4;
-
-    if (full_state_update && nm_device_get_state(self) != NM_DEVICE_STATE_IP_CONFIG)
-        return;
-
-    /* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
-     * master to enslave us. */
-    if (nm_active_connection_get_master(NM_ACTIVE_CONNECTION(priv->act_request.obj))
-        && !priv->is_enslaved)
-        return;
-
-    s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
-    if (s_ip4
-        && nm_streq0(nm_setting_ip_config_get_method(s_ip4), NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-        ip4_disabled = TRUE;
-
-    s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
-    if (s_ip6
-        && NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6),
-                        NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
-                        NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
-        ip6_disabled = TRUE;
-
-    if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
-        && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
-        /* Both method completed (or disabled), proceed with activation */
-        nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-        return;
-    }
-
-    for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
-        if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_CONF
-            && priv->ip_req_timeout_source_x[IS_IPv4]) {
-            return;
-        }
-    }
-
-    if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL
-         || (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
-        && (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL
-            || (ip6_disabled && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
-        /* Either both methods failed, or only one failed and the other is
-         * disabled */
-        if (nm_device_sys_iface_state_is_external_or_assume(self)) {
-            /* We have assumed configuration, but couldn't redo it. No problem,
-             * move to check state. */
-            _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
-            _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
-            state = NM_DEVICE_STATE_IP_CHECK;
-        } else if (may_fail && get_ip_config_may_fail(self, AF_INET)
-                   && get_ip_config_may_fail(self, AF_INET6)) {
-            /* Couldn't start either IPv6 and IPv4 autoconfiguration,
-             * but both are allowed to fail. */
-            state = NM_DEVICE_STATE_SECONDARIES;
-        } else {
-            /* Autoconfiguration attempted without success. */
-            state = NM_DEVICE_STATE_FAILED;
-        }
-
-        if (full_state_update || state == NM_DEVICE_STATE_FAILED) {
-            nm_device_state_changed(self, state, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        }
-        return;
-    }
-
-    /* If a method is still pending but required, wait */
-    if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET))
-        return;
-    if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET6))
-        return;
-
-    /* If at least a method has completed, proceed with activation */
-    if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
-        || (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_disabled)) {
-        if (full_state_update)
-            nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
-        return;
-    }
-}
-
 /**
  * nm_device_slave_notify_enslave:
  * @self: the slave device
@@ -6806,7 +7710,7 @@ static void
 nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
 {
     NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection = nm_device_get_applied_connection(self);
+    NMConnection    *connection = nm_device_get_applied_connection(self);
     gboolean         activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG);
 
     g_return_if_fail(priv->master);
@@ -6833,13 +7737,17 @@ nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
         }
     }
 
-    if (activating) {
-        if (success)
-            check_ip_state(self, FALSE, TRUE);
-        else
-            nm_device_queue_state(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
-    } else
+    if (!activating) {
         nm_device_queue_recheck_assume(self);
+        return;
+    }
+
+    if (!success) {
+        nm_device_queue_state(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
+        return;
+    }
+
+    nm_device_activate_schedule_stage3_ip_config(self, FALSE);
 }
 
 /**
@@ -6853,8 +7761,8 @@ static void
 nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
 {
     NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection = nm_device_get_applied_connection(self);
-    const char *     master_status;
+    NMConnection    *connection = nm_device_get_applied_connection(self);
+    const char      *master_status;
 
     g_return_if_fail(priv->master);
 
@@ -6913,6 +7821,9 @@ nm_device_removed(NMDevice *self, gboolean unconfigure_ip_config)
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
+    _dev_ipdhcpx_cleanup(self, AF_INET, TRUE, FALSE);
+    _dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, FALSE);
+
     priv = NM_DEVICE_GET_PRIVATE(self);
     if (priv->master) {
         /* this is called when something externally messes with the slave or during shut-down.
@@ -6924,15 +7835,7 @@ nm_device_removed(NMDevice *self, gboolean unconfigure_ip_config)
                                            NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
     }
 
-    if (unconfigure_ip_config) {
-        nm_device_set_ip_config(self, AF_INET, NULL, FALSE, NULL);
-        nm_device_set_ip_config(self, AF_INET6, NULL, FALSE, NULL);
-    } else {
-        if (priv->dhcp_data_4.client)
-            nm_dhcp_client_stop(priv->dhcp_data_4.client, FALSE);
-        if (priv->dhcp_data_6.client)
-            nm_dhcp_client_stop(priv->dhcp_data_6.client, FALSE);
-    }
+    _dev_l3_register_l3cds(self, priv->l3cfg, FALSE, unconfigure_ip_config);
 }
 
 static gboolean
@@ -7033,7 +7936,7 @@ nm_device_autoconnect_blocked_get(NMDevice *self, NMDeviceAutoconnectBlockedFlag
 }
 
 void
-nm_device_autoconnect_blocked_set_full(NMDevice *                      self,
+nm_device_autoconnect_blocked_set_full(NMDevice                       *self,
                                        NMDeviceAutoconnectBlockedFlags mask,
                                        NMDeviceAutoconnectBlockedFlags value)
 {
@@ -7068,8 +7971,8 @@ nm_device_autoconnect_blocked_set_full(NMDevice *                      self,
 
 static gboolean
 autoconnect_allowed_accumulator(GSignalInvocationHint *ihint,
-                                GValue *               return_accu,
-                                const GValue *         handler_return,
+                                GValue                *return_accu,
+                                const GValue          *handler_return,
                                 gpointer               data)
 {
     if (!g_value_get_boolean(handler_return))
@@ -7089,7 +7992,7 @@ gboolean
 nm_device_autoconnect_allowed(NMDevice *self)
 {
     NMDevicePrivate *priv     = NM_DEVICE_GET_PRIVATE(self);
-    NMDeviceClass *  klass    = NM_DEVICE_GET_CLASS(self);
+    NMDeviceClass   *klass    = NM_DEVICE_GET_CLASS(self);
     GValue           instance = G_VALUE_INIT;
     GValue           retval   = G_VALUE_INIT;
 
@@ -7190,22 +8093,36 @@ nm_device_can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char
 static gboolean
 device_has_config(NMDevice *self)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate             *priv = NM_DEVICE_GET_PRIVATE(self);
+    const NMDedupMultiHeadEntry *head_entry;
+    const NMPlatformLink        *pllink;
+    NMPLookup                    lookup;
+
+    pllink = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
+    if (!pllink)
+        return FALSE;
 
-    /* Check for IP configuration. */
-    if (priv->ip_config_4 && nm_ip4_config_get_num_addresses(priv->ip_config_4))
+    if (pllink->master > 0) {
+        /* Master-slave relationship is also a configuration */
         return TRUE;
-    if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6))
+    }
+
+    head_entry = nm_platform_lookup(
+        nm_device_get_platform(self),
+        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, pllink->ifindex));
+    if (head_entry)
         return TRUE;
 
-    /* The existence of a software device is good enough. */
-    if (nm_device_is_software(self) && nm_device_is_real(self))
+    head_entry = nm_platform_lookup(
+        nm_device_get_platform(self),
+        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, pllink->ifindex));
+    if (head_entry)
         return TRUE;
 
-    /* Master-slave relationship is also a configuration */
-    if (!c_list_is_empty(&priv->slaves)
-        || nm_platform_link_get_master(nm_device_get_platform(self), priv->ifindex) > 0)
+    if (nm_device_is_software(self) && nm_device_is_real(self)) {
+        /* The existence of a software device is good enough. */
         return TRUE;
+    }
 
     return FALSE;
 }
@@ -7224,10 +8141,10 @@ device_has_config(NMDevice *self)
  * %FALSE on failure.
  */
 gboolean
-nm_device_master_update_slave_connection(NMDevice *    self,
-                                         NMDevice *    slave,
+nm_device_master_update_slave_connection(NMDevice     *self,
+                                         NMDevice     *slave,
                                          NMConnection *connection,
-                                         GError **     error)
+                                         GError      **error)
 {
     NMDeviceClass *klass;
     gboolean       success;
@@ -7293,19 +8210,19 @@ NMConnection *
 nm_device_generate_connection(NMDevice *self,
                               NMDevice *master,
                               gboolean *out_maybe_later,
-                              GError ** error)
+                              GError  **error)
 {
-    NMDeviceClass *  klass                   = NM_DEVICE_GET_CLASS(self);
-    NMDevicePrivate *priv                    = NM_DEVICE_GET_PRIVATE(self);
-    const char *     ifname                  = nm_device_get_iface(self);
+    NMDeviceClass                *klass      = NM_DEVICE_GET_CLASS(self);
+    NMDevicePrivate              *priv       = NM_DEVICE_GET_PRIVATE(self);
+    const char                   *ifname     = nm_device_get_iface(self);
     gs_unref_object NMConnection *connection = NULL;
-    NMSetting *                   s_con;
-    NMSetting *                   s_ip4;
-    NMSetting *                   s_ip6;
+    NMSetting                    *s_con;
+    NMSetting                    *s_ip4;
+    NMSetting                    *s_ip6;
     char                          uuid[37];
-    const char *                  ip4_method, *ip6_method;
-    GError *                      local = NULL;
-    const NMPlatformLink *        pllink;
+    const char                   *ip4_method, *ip6_method;
+    GError                       *local = NULL;
+    const NMPlatformLink         *pllink;
 
     NM_SET_OUT(out_maybe_later, FALSE);
 
@@ -7363,10 +8280,16 @@ nm_device_generate_connection(NMDevice *self,
         }
     } else {
         /* Only regular and master devices get IP configuration; slaves do not */
-        s_ip4 = nm_ip4_config_create_setting(priv->ip_config_4);
+        s_ip4 = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
+                                                     AF_INET,
+                                                     nm_device_get_ip_ifindex(self),
+                                                     FALSE);
         nm_connection_add_setting(connection, s_ip4);
 
-        s_ip6 = nm_ip6_config_create_setting(priv->ip_config_6, _get_maybe_ipv6_disabled(self));
+        s_ip6 = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
+                                                     AF_INET6,
+                                                     nm_device_get_ip_ifindex(self),
+                                                     _get_maybe_ipv6_disabled(self));
         nm_connection_add_setting(connection, s_ip6);
 
         nm_connection_add_setting(connection, nm_setting_proxy_new());
@@ -7449,11 +8372,11 @@ nm_device_generate_connection(NMDevice *self,
  * Returns: success or failure.
  */
 gboolean
-nm_device_complete_connection(NMDevice *           self,
-                              NMConnection *       connection,
-                              const char *         specific_object,
+nm_device_complete_connection(NMDevice            *self,
+                              NMConnection        *connection,
+                              const char          *specific_object,
                               NMConnection *const *existing_connections,
-                              GError **            error)
+                              GError             **error)
 {
     NMDeviceClass *klass;
 
@@ -7511,14 +8434,14 @@ nm_device_match_parent(NMDevice *self, const char *parent)
 }
 
 gboolean
-nm_device_match_parent_hwaddr(NMDevice *    device,
+nm_device_match_parent_hwaddr(NMDevice     *device,
                               NMConnection *connection,
                               gboolean      fail_if_no_hwaddr)
 {
     NMSettingWired *s_wired;
-    NMDevice *      parent_device;
-    const char *    setting_mac;
-    const char *    parent_mac;
+    NMDevice       *parent_device;
+    const char     *setting_mac;
+    const char     *parent_mac;
 
     s_wired = nm_connection_get_setting_wired(connection);
     if (!s_wired)
@@ -7539,13 +8462,13 @@ nm_device_match_parent_hwaddr(NMDevice *    device,
 static gboolean
 check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error)
 {
-    NMDevicePrivate *priv            = NM_DEVICE_GET_PRIVATE(self);
-    const char *     device_iface    = nm_device_get_iface(self);
-    gs_free_error GError *local      = NULL;
-    gs_free char *        conn_iface = NULL;
-    NMDeviceClass *       klass;
-    NMSettingMatch *      s_match;
-    const GSList *        specs;
+    NMDevicePrivate      *priv         = NM_DEVICE_GET_PRIVATE(self);
+    const char           *device_iface = nm_device_get_iface(self);
+    gs_free_error GError *local        = NULL;
+    gs_free char         *conn_iface   = NULL;
+    NMDeviceClass        *klass;
+    NMSettingMatch       *s_match;
+    const GSList         *specs;
     gboolean              has_match = FALSE;
 
     klass = NM_DEVICE_GET_CLASS(self);
@@ -7668,7 +8591,7 @@ gboolean
 nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave)
 {
     NMSettingConnection *s_con;
-    const char *         connection_type, *slave_type;
+    const char          *connection_type, *slave_type;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
     g_return_val_if_fail(NM_IS_CONNECTION(slave), FALSE);
@@ -7746,7 +8669,7 @@ nm_device_unmanage_on_quit(NMDevice *self)
 static gboolean
 nm_device_emit_recheck_assume(gpointer user_data)
 {
-    NMDevice *       self = user_data;
+    NMDevice        *self = user_data;
     NMDevicePrivate *priv;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
@@ -7772,7 +8695,7 @@ nm_device_queue_recheck_assume(NMDevice *self)
 static gboolean
 recheck_available(gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     gboolean         now_available;
     NMDeviceState    state     = nm_device_get_state(self);
@@ -7810,7 +8733,7 @@ recheck_available(gpointer user_data)
 }
 
 void
-nm_device_queue_recheck_available(NMDevice *          self,
+nm_device_queue_recheck_available(NMDevice           *self,
                                   NMDeviceStateReason available_reason,
                                   NMDeviceStateReason unavailable_reason)
 {
@@ -7832,27 +8755,13 @@ nm_device_emit_recheck_auto_activate(NMDevice *self)
     g_signal_emit(self, signals[RECHECK_AUTO_ACTIVATE], 0);
 }
 
-static void
-dnsmasq_state_changed_cb(NMDnsMasqManager *manager, guint32 status, gpointer user_data)
-{
-    NMDevice *self = NM_DEVICE(user_data);
-
-    switch (status) {
-    case NM_DNSMASQ_STATUS_DEAD:
-        nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
-        break;
-    default:
-        break;
-    }
-}
-
 void
-nm_device_auth_request(NMDevice *                     self,
-                       GDBusMethodInvocation *        context,
-                       NMConnection *                 connection,
-                       const char *                   permission,
+nm_device_auth_request(NMDevice                      *self,
+                       GDBusMethodInvocation         *context,
+                       NMConnection                  *connection,
+                       const char                    *permission,
                        gboolean                       allow_interaction,
-                       GCancellable *                 cancellable,
+                       GCancellable                  *cancellable,
                        NMManagerDeviceAuthRequestFunc callback,
                        gpointer                       user_data)
 {
@@ -7870,156 +8779,120 @@ nm_device_auth_request(NMDevice *                     self,
 /*****************************************************************************/
 
 static void
-activation_source_clear(NMDevice *self, int addr_family)
+activation_source_clear(NMDevice *self)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->activation_source_id_x[IS_IPv4] != 0) {
+    if (nm_clear_g_source_inst(&priv->activation_idle_source)) {
         _LOGD(LOGD_DEVICE,
-              "activation-stage: clear %s,v%c (id %u)",
-              _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
-              nm_utils_addr_family_to_char(addr_family),
-              priv->activation_source_id_x[IS_IPv4]);
-        nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
-        priv->activation_source_func_x[IS_IPv4] = NULL;
+              "activation-stage: clear %s",
+              _activation_func_to_string(priv->activation_func));
+        priv->activation_func = NULL;
     }
 }
 
 static gboolean
-activation_source_handle_cb(NMDevice *self, int addr_family)
+activation_source_handle_cb(gpointer user_data)
 {
-    NMDevicePrivate *    priv;
-    const int            IS_IPv4 = NM_IS_IPv4(addr_family);
-    ActivationHandleFunc activation_source_func;
-    guint                activation_source_id;
+    NMDevice            *self = user_data;
+    NMDevicePrivate     *priv;
+    ActivationHandleFunc activation_func;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
-    activation_source_func = priv->activation_source_func_x[IS_IPv4];
-    activation_source_id   = priv->activation_source_id_x[IS_IPv4];
-
-    g_return_val_if_fail(activation_source_id != 0, G_SOURCE_REMOVE);
-    nm_assert(activation_source_func);
-
-    priv->activation_source_func_x[IS_IPv4] = NULL;
-    priv->activation_source_id_x[IS_IPv4]   = 0;
+    g_return_val_if_fail(priv->activation_idle_source, G_SOURCE_REMOVE);
 
-    _LOGD(LOGD_DEVICE,
-          "activation-stage: invoke %s,v%c (id %u)",
-          _activation_func_to_string(activation_source_func),
-          nm_utils_addr_family_to_char(addr_family),
-          activation_source_id);
+    nm_assert(priv->activation_func);
 
-    activation_source_func(self);
+    activation_func       = priv->activation_func;
+    priv->activation_func = NULL;
 
-    _LOGT(LOGD_DEVICE,
-          "activation-stage: complete %s,v%c (id %u)",
-          _activation_func_to_string(activation_source_func),
-          nm_utils_addr_family_to_char(addr_family),
-          activation_source_id);
+    nm_clear_g_source_inst(&priv->activation_idle_source);
 
-    return G_SOURCE_REMOVE;
-}
+    _LOGD(LOGD_DEVICE, "activation-stage: invoke %s", _activation_func_to_string(activation_func));
 
-static gboolean
-activation_source_handle_cb_4(gpointer user_data)
-{
-    return activation_source_handle_cb(user_data, AF_INET);
-}
+    activation_func(self);
 
-static gboolean
-activation_source_handle_cb_6(gpointer user_data)
-{
-    return activation_source_handle_cb(user_data, AF_INET6);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-activation_source_schedule(NMDevice *self, ActivationHandleFunc func, int addr_family)
+activation_source_schedule(NMDevice *self, ActivationHandleFunc func)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    guint            new_id  = 0;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->activation_source_id_x[IS_IPv4] != 0
-        && priv->activation_source_func_x[IS_IPv4] == func) {
+    if (priv->activation_idle_source && priv->activation_func == func) {
         /* Scheduling the same stage multiple times is fine. */
         _LOGT(LOGD_DEVICE,
-              "activation-stage: already scheduled %s,v%c (id %u)",
-              _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family),
-              priv->activation_source_id_x[IS_IPv4]);
+              "activation-stage: already scheduled %s",
+              _activation_func_to_string(func));
         return;
     }
 
-    new_id =
-        g_idle_add(IS_IPv4 ? activation_source_handle_cb_4 : activation_source_handle_cb_6, self);
-
-    if (priv->activation_source_id_x[IS_IPv4] != 0) {
+    if (priv->activation_idle_source) {
         _LOGD(LOGD_DEVICE,
-              "activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
+              "activation-stage: schedule %s (which replaces %s)",
               _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family),
-              _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
-              nm_utils_addr_family_to_char(addr_family),
-              priv->activation_source_id_x[IS_IPv4],
-              new_id);
-        nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
+              _activation_func_to_string(priv->activation_func));
+        nm_clear_g_source_inst(&priv->activation_idle_source);
     } else {
-        _LOGD(LOGD_DEVICE,
-              "activation-stage: schedule %s,v%c (id %u)",
-              _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family),
-              new_id);
+        _LOGD(LOGD_DEVICE, "activation-stage: schedule %s", _activation_func_to_string(func));
     }
 
-    priv->activation_source_func_x[IS_IPv4] = func;
-    priv->activation_source_id_x[IS_IPv4]   = new_id;
+    priv->activation_idle_source = nm_g_idle_add_source(activation_source_handle_cb, self);
+    priv->activation_func        = func;
 }
 
 static void
-activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func, int addr_family)
+activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->activation_source_id_x[IS_IPv4] == 0) {
+    if (!priv->activation_idle_source) {
         _LOGD(LOGD_DEVICE,
-              "activation-stage: synchronously invoke %s,v%c",
-              _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family));
-    } else if (priv->activation_source_func_x[IS_IPv4] == func) {
+              "activation-stage: synchronously invoke %s",
+              _activation_func_to_string(func));
+    } else if (priv->activation_func == func) {
         _LOGD(LOGD_DEVICE,
-              "activation-stage: synchronously invoke %s,v%c which was already scheduled (id %u)",
-              _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family),
-              priv->activation_source_id_x[IS_IPv4]);
+              "activation-stage: synchronously invoke %s (which was already scheduled)",
+              _activation_func_to_string(func));
     } else {
         _LOGD(LOGD_DEVICE,
-              "activation-stage: synchronously invoke %s,v%c which replaces %s,v%c (id %u)",
+              "activation-stage: synchronously invoke %s (which replaces %s)",
               _activation_func_to_string(func),
-              nm_utils_addr_family_to_char(addr_family),
-              _activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
-              nm_utils_addr_family_to_char(addr_family),
-              priv->activation_source_id_x[IS_IPv4]);
+              _activation_func_to_string(priv->activation_func));
     }
 
-    nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
-    priv->activation_source_func_x[IS_IPv4] = NULL;
+    nm_clear_g_source_inst(&priv->activation_idle_source);
+    priv->activation_func = NULL;
 
     func(self);
 }
 
+static void
+activation_source_invoke_or_schedule(NMDevice *self, ActivationHandleFunc func, gboolean do_sync)
+{
+    nm_assert(NM_IS_DEVICE(self));
+    nm_assert(NM_DEVICE_GET_PRIVATE(self)->act_request.obj);
+    nm_assert(func);
+
+    if (do_sync) {
+        activation_source_invoke_sync(self, func);
+        return;
+    }
+    activation_source_schedule(self, func);
+}
+
 /*****************************************************************************/
 
 static void
 master_ready(NMDevice *self, NMActiveConnection *active)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
     NMActiveConnection *master_connection;
-    NMDevice *          master;
+    NMDevice           *master;
 
     /* Notify a master device that it has a new slave */
     nm_assert(nm_active_connection_get_master_ready(active));
@@ -8057,10 +8930,10 @@ master_ready_cb(NMActiveConnection *active, GParamSpec *pspec, NMDevice *self)
 static NMPlatformVF *
 sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error)
 {
-    NMDevicePrivate *priv         = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate      *priv    = NM_DEVICE_GET_PRIVATE(self);
     gs_free NMPlatformVF *plat_vf = NULL;
-    const guint *         vlan_ids;
-    GVariant *            variant;
+    const guint          *vlan_ids;
+    GVariant             *variant;
     guint                 i, num_vlans;
     gsize                 length;
 
@@ -8131,8 +9004,8 @@ sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error)
 static void
 sriov_params_cb(GError *error, gpointer user_data)
 {
-    NMDevice *       self;
-    NMDevicePrivate *priv;
+    NMDevice                    *self;
+    NMDevicePrivate             *priv;
     nm_auto_freev NMPlatformVF **plat_vfs = NULL;
 
     nm_utils_user_data_unpack(user_data, &self, &plat_vfs);
@@ -8174,20 +9047,26 @@ sriov_params_cb(GError *error, gpointer user_data)
 static void
 activate_stage1_device_prepare(NMDevice *self)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
     NMActStageReturn    ret  = NM_ACT_STAGE_RETURN_SUCCESS;
     NMActiveConnection *active;
     NMActiveConnection *master;
-    NMDeviceClass *     klass;
+    NMDeviceClass      *klass;
 
-    priv->v4_route_table_initialized = FALSE;
-    priv->v6_route_table_initialized = FALSE;
+    nm_assert((priv->ip_data_4.state == NM_DEVICE_IP_STATE_NONE)
+              == (priv->ip_data_6.state == NM_DEVICE_IP_STATE_NONE));
 
-    _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_NONE);
-    _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
+    if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_NONE) {
+        _dev_ip_state_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING, "stage1");
+        _dev_ip_state_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_PENDING, "stage1");
 
-    /* Notify the new ActiveConnection along with the state change */
-    nm_dbus_track_obj_path_set(&priv->act_request, priv->act_request.obj, TRUE);
+        /* Notify the new ActiveConnection along with the state change */
+        nm_dbus_track_obj_path_set(&priv->act_request, priv->act_request.obj, TRUE);
+
+        priv->v4_route_table_initialized = FALSE;
+        priv->v6_route_table_initialized = FALSE;
+        priv->l3config_merge_flags_has   = FALSE;
+    }
 
     nm_device_state_changed(self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
 
@@ -8201,11 +9080,11 @@ activate_stage1_device_prepare(NMDevice *self)
         else if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)
                  && (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
             nm_auto_freev NMPlatformVF **plat_vfs = NULL;
-            gs_free_error GError *error           = NULL;
-            NMSriovVF *           vf;
-            NMTernary             autoprobe;
-            guint                 num;
-            guint                 i;
+            gs_free_error GError        *error    = NULL;
+            NMSriovVF                   *vf;
+            NMTernary                    autoprobe;
+            guint                        num;
+            guint                        i;
 
             autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
             if (autoprobe == NM_TERNARY_DEFAULT) {
@@ -8248,6 +9127,7 @@ activate_stage1_device_prepare(NMDevice *self)
             priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_PENDING;
             return;
         }
+
         priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
     }
 
@@ -8323,15 +9203,7 @@ activate_stage1_device_prepare(NMDevice *self)
 void
 nm_device_activate_schedule_stage1_device_prepare(NMDevice *self, gboolean do_sync)
 {
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(NM_DEVICE_GET_PRIVATE(self)->act_request.obj);
-
-    if (!do_sync) {
-        activation_source_schedule(self, activate_stage1_device_prepare, AF_INET);
-        return;
-    }
-
-    activation_source_invoke_sync(self, activate_stage1_device_prepare, AF_INET);
+    activation_source_invoke_or_schedule(self, activate_stage1_device_prepare, do_sync);
 }
 
 static NMActStageReturn
@@ -8408,8 +9280,8 @@ static void
 _routing_rules_sync(NMDevice *self, NMTernary set_mode)
 {
     NMDevicePrivate *priv               = NM_DEVICE_GET_PRIVATE(self);
-    NMPRulesManager *rules_manager      = nm_netns_get_rules_manager(nm_device_get_netns(self));
-    NMDeviceClass *  klass              = NM_DEVICE_GET_CLASS(self);
+    NMPRouteManager *route_manager      = nm_netns_get_route_manager(nm_device_get_netns(self));
+    NMDeviceClass   *klass              = NM_DEVICE_GET_CLASS(self);
     gboolean         untrack_only_dirty = FALSE;
     gboolean         keep_deleted_rules;
     gpointer         user_tag_1;
@@ -8420,15 +9292,12 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
     user_tag_2 = &priv->v6_route_table;
 
     if (set_mode == NM_TERNARY_TRUE) {
-        NMConnection *     applied_connection;
+        NMConnection      *applied_connection;
         NMSettingIPConfig *s_ip;
         guint              i, num;
         int                is_ipv4;
 
         untrack_only_dirty = TRUE;
-        nmp_rules_manager_set_dirty(rules_manager, user_tag_1);
-        if (klass->get_extra_rules)
-            nmp_rules_manager_set_dirty(rules_manager, user_tag_2);
 
         applied_connection = nm_device_get_applied_connection(self);
 
@@ -8442,19 +9311,19 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
             num = nm_setting_ip_config_get_num_routing_rules(s_ip);
             for (i = 0; i < num; i++) {
                 NMPlatformRoutingRule plrule;
-                NMIPRoutingRule *     rule;
+                NMIPRoutingRule      *rule;
 
                 rule = nm_setting_ip_config_get_routing_rule(s_ip, i);
                 nm_ip_routing_rule_to_platform(rule, &plrule);
 
                 /* We track this rule, but we also make it explicitly not weakly-tracked
-                 * (meaning to untrack NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
+                 * (meaning to untrack NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
                  * the same time). */
-                nmp_rules_manager_track(rules_manager,
-                                        &plrule,
-                                        10,
-                                        user_tag_1,
-                                        NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+                nmp_route_manager_track_rule(route_manager,
+                                             &plrule,
+                                             10,
+                                             user_tag_1,
+                                             NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
             }
         }
 
@@ -8464,24 +9333,25 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
             extra_rules = klass->get_extra_rules(self);
             if (extra_rules) {
                 for (i = 0; i < extra_rules->len; i++) {
-                    nmp_rules_manager_track(rules_manager,
-                                            NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]),
-                                            10,
-                                            user_tag_2,
-                                            NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
+                    nmp_route_manager_track_rule(
+                        route_manager,
+                        NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]),
+                        10,
+                        user_tag_2,
+                        NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
                 }
             }
         }
     }
 
-    nmp_rules_manager_untrack_all(rules_manager, user_tag_1, !untrack_only_dirty);
+    nmp_route_manager_untrack_all(route_manager, user_tag_1, !untrack_only_dirty, TRUE);
     if (klass->get_extra_rules)
-        nmp_rules_manager_untrack_all(rules_manager, user_tag_2, !untrack_only_dirty);
+        nmp_route_manager_untrack_all(route_manager, user_tag_2, !untrack_only_dirty, TRUE);
 
     keep_deleted_rules = FALSE;
     if (set_mode == NM_TERNARY_DEFAULT) {
         /* when exiting NM, we leave the device up and the rules configured.
-         * We just all nmp_rules_manager_sync() to forget about the synced rules,
+         * We just call nmp_route_manager_sync() to forget about the synced rules,
          * but we don't actually delete them.
          *
          * FIXME: that is a problem after restart of NetworkManager, because these
@@ -8495,7 +9365,7 @@ _routing_rules_sync(NMDevice *self, NMTernary set_mode)
          * file and track them after restart again. */
         keep_deleted_rules = TRUE;
     }
-    nmp_rules_manager_sync(rules_manager, keep_deleted_rules);
+    nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, keep_deleted_rules);
 }
 
 static gboolean
@@ -8503,8 +9373,8 @@ tc_commit(NMDevice *self)
 {
     gs_unref_ptrarray GPtrArray *qdiscs   = NULL;
     gs_unref_ptrarray GPtrArray *tfilters = NULL;
-    NMSettingTCConfig *          s_tc;
-    NMPlatform *                 platform;
+    NMSettingTCConfig           *s_tc;
+    NMPlatform                  *platform;
     int                          ip_ifindex;
 
     s_tc = nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG);
@@ -8536,11 +9406,11 @@ static void
 activate_stage2_device_config(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMDeviceClass *  klass;
+    NMDeviceClass   *klass;
     NMActStageReturn ret;
-    NMSettingWired * s_wired;
+    NMSettingWired  *s_wired;
     gboolean         no_firmware = FALSE;
-    CList *          iter;
+    CList           *iter;
     NMTernary        accept_all_mac_addresses;
 
     nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
@@ -8588,7 +9458,7 @@ activate_stage2_device_config(NMDevice *self)
 
     /* If we have slaves that aren't yet enslaved, do that now */
     c_list_for_each (iter, &priv->slaves) {
-        SlaveInfo *   info        = c_list_entry(iter, SlaveInfo, lst_slave);
+        SlaveInfo    *info        = c_list_entry(iter, SlaveInfo, lst_slave);
         NMDeviceState slave_state = nm_device_get_state(info->slave);
 
         if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
@@ -8623,1086 +9493,809 @@ activate_stage2_device_config(NMDevice *self)
 
     lldp_setup(self, NM_TERNARY_DEFAULT);
 
-    _commit_mtu(self, NULL);
+    _commit_mtu(self);
 
-    nm_device_activate_schedule_stage3_ip_config_start(self);
+    nm_device_activate_schedule_stage3_ip_config(self, TRUE);
 }
 
 void
 nm_device_activate_schedule_stage2_device_config(NMDevice *self, gboolean do_sync)
 {
-    g_return_if_fail(NM_IS_DEVICE(self));
-
-    if (!do_sync) {
-        activation_source_schedule(self, activate_stage2_device_config, AF_INET);
-        return;
-    }
-
-    activation_source_invoke_sync(self, activate_stage2_device_config, AF_INET);
-}
-
-void
-nm_device_ip_method_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason)
-{
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-
-    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
-
-    if (get_ip_config_may_fail(self, addr_family))
-        check_ip_state(self, FALSE, (nm_device_get_state(self) == NM_DEVICE_STATE_IP_CONFIG));
-    else
-        nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, reason);
+    activation_source_invoke_or_schedule(self, activate_stage2_device_config, do_sync);
 }
 
 /*****************************************************************************/
 
 static void
-acd_data_destroy(gpointer ptr)
+_dev_ipllx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
 {
-    AcdData *data = ptr;
-    int      i;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    for (i = 0; data->configs && data->configs[i]; i++)
-        g_object_unref(data->configs[i]);
-    g_free(data->configs);
-    g_slice_free(AcdData, data);
+    if (priv->ipll_data_x[IS_IPv4].state != state) {
+        _LOGD_ipll(addr_family,
+                   "set state %s (was %s)",
+                   nm_device_ip_state_to_string(state),
+                   nm_device_ip_state_to_string(priv->ipll_data_x[IS_IPv4].state));
+        priv->ipll_data_x[IS_IPv4].state = state;
+    }
 }
 
 static void
-ipv4_manual_method_apply(NMDevice *self, NMIP4Config **configs, gboolean success)
+_dev_ipllx_cleanup(NMDevice *self, int addr_family)
 {
-    NMConnection *connection;
-    const char *  method;
-
-    connection = nm_device_get_applied_connection(self);
-    nm_assert(connection);
-    method = nm_utils_get_ip_config_method(connection, AF_INET);
-    nm_assert(NM_IN_STRSET(method,
-                           NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
-                           NM_SETTING_IP4_CONFIG_METHOD_AUTO));
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    if (!success) {
-        nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
-        return;
-    }
+    if (IS_IPv4) {
+        if (nm_clear_pointer(&priv->ipll_data_4.v4.ipv4ll, nm_l3_ipv4ll_unref))
+            nm_clear_pointer(&priv->ipll_data_4.v4.ipv4ll_registation,
+                             nm_l3_ipv4ll_register_remove);
+        else
+            nm_assert(!priv->ipll_data_4.v4.ipv4ll_registation);
 
-    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
-        nm_device_activate_schedule_ip_config_result(self, AF_INET, NULL);
-    else {
-        if (NM_DEVICE_GET_PRIVATE(self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE)
-            ip_config_merge_and_apply(self, AF_INET, TRUE);
+        nm_clear_g_source_inst(&priv->ipll_data_4.v4.timeout_source);
+    } else {
+        nm_clear_pointer(&priv->ipll_data_6.v6.ipv6ll, nm_l3_ipv6ll_destroy);
+        priv->ipll_data_6.v6.llstate = NM_L3_IPV6LL_STATE_NONE;
+        priv->ipll_data_6.v6.lladdr  = nm_ip_addr_zero.addr6;
+        nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
     }
-}
 
-static void
-acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data)
-{
-    AcdData *                   data = user_data;
-    NMDevice *                  self;
-    NMDevicePrivate *           priv;
-    NMDedupMultiIter            ipconf_iter;
-    const NMPlatformIP4Address *address;
-    gboolean                    result, success = TRUE;
-    int                         i;
-
-    g_assert(data);
-    self = data->device;
-    priv = NM_DEVICE_GET_PRIVATE(self);
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_LL_X(IS_IPv4), NULL, FALSE);
 
-    for (i = 0; data->configs && data->configs[i]; i++) {
-        nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
-            char sbuf[NM_UTILS_INET_ADDRSTRLEN];
+    _dev_ipllx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
+}
 
-            result = nm_acd_manager_check_address(acd_manager, address->address);
-            success &= result;
+/*****************************************************************************/
 
-            _NMLOG(result ? LOGL_DEBUG : LOGL_WARN,
-                   LOGD_DEVICE,
-                   "IPv4 DAD result: address %s is %s",
-                   _nm_utils_inet4_ntop(address->address, sbuf),
-                   result ? "unique" : "duplicate");
-        }
+static void
+_dev_ipll4_notify_event(NMDevice *self)
+{
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
+    NML3IPv4LLState       ipv4ll_state;
+    const NML3ConfigData *l3cd;
+    NMDeviceIPState       state;
+
+    nm_assert(NM_IS_L3_IPV4LL(priv->ipll_data_4.v4.ipv4ll));
+    nm_assert(priv->ipll_data_4.state >= NM_DEVICE_IP_STATE_PENDING);
+
+    ipv4ll_state = nm_l3_ipv4ll_get_state(priv->ipll_data_4.v4.ipv4ll);
+
+    if (nm_l3_ipv4ll_state_is_good(ipv4ll_state)) {
+        l3cd = nm_l3_ipv4ll_get_l3cd(priv->ipll_data_4.v4.ipv4ll);
+        nm_assert(NM_IS_L3_CONFIG_DATA(l3cd));
+        nm_assert(!nm_l3_ipv4ll_is_timed_out(priv->ipll_data_4.v4.ipv4ll));
+        state = NM_DEVICE_IP_STATE_READY;
+    } else if (priv->ipll_data_4.v4.ipv4ll
+               && nm_l3_ipv4ll_is_timed_out(priv->ipll_data_4.v4.ipv4ll)) {
+        l3cd  = NULL;
+        state = NM_DEVICE_IP_STATE_FAILED;
+    } else {
+        l3cd  = NULL;
+        state = (priv->ipll_data_4.state == NM_DEVICE_IP_STATE_PENDING) ? NM_DEVICE_IP_STATE_PENDING
+                                                                        : NM_DEVICE_IP_STATE_FAILED;
     }
 
-    data->callback(self, data->configs, success);
+    _dev_ipllx_set_state(self, AF_INET, state);
+
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_LL_4, l3cd, FALSE);
 
-    priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager);
-    nm_acd_manager_free(acd_manager);
+    _dev_ip_state_check_async(self, AF_INET);
 }
 
-/**
- * ipv4_dad_start:
- * @self: device instance
- * @configs: NULL-terminated array of IPv4 configurations
- * @cb: callback function
- *
- * Start IPv4 DAD on device @self, check addresses in @configs and call @cb
- * when the procedure ends. @cb will be called in any case, even if DAD can't
- * be started. @configs will be unreferenced after @cb has been called.
- */
 static void
-ipv4_dad_start(NMDevice *self, NMIP4Config **configs, AcdCallback cb)
+_dev_ipll4_start(NMDevice *self)
 {
-    static const NMAcdCallbacks acd_callbacks = {
-        .probe_terminated_callback = acd_manager_probe_terminated,
-        .user_data_destroy         = acd_data_destroy,
-    };
-    NMDevicePrivate *           priv = NM_DEVICE_GET_PRIVATE(self);
-    NMAcdManager *              acd_manager;
-    const NMPlatformIP4Address *address;
-    NMDedupMultiIter            ipconf_iter;
-    AcdData *                   data;
-    guint                       timeout;
-    gboolean                    addr_found;
-    int                         r;
-    const guint8 *              hwaddr_arr;
-    size_t                      length;
-    guint                       i;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    guint32          timeout_msec;
 
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(configs);
-    g_return_if_fail(cb);
+    if (priv->ipll_data_4.state >= NM_DEVICE_IP_STATE_PENDING)
+        return;
 
-    for (i = 0, addr_found = FALSE; configs[i]; i++) {
-        if (nm_ip4_config_get_num_addresses(configs[i]) > 0) {
-            addr_found = TRUE;
-            break;
-        }
-    }
+    _dev_ipllx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING);
 
-    timeout    = _prop_get_ipv4_dad_timeout(self);
-    hwaddr_arr = nm_platform_link_get_address(nm_device_get_platform(self),
-                                              nm_device_get_ip_ifindex(self),
-                                              &length);
+    timeout_msec = _prop_get_ipv4_dad_timeout(self);
+    if (timeout_msec == 0)
+        timeout_msec = NM_ACD_TIMEOUT_RFC5227_MSEC;
 
-    if (!timeout || !hwaddr_arr || !addr_found || length != ETH_ALEN
-        || nm_device_sys_iface_state_is_external_or_assume(self)) {
-        /* DAD not needed, signal success */
-        cb(self, configs, TRUE);
+    priv->ipll_data_4.v4.ipv4ll = nm_l3cfg_access_ipv4ll(priv->l3cfg);
+    priv->ipll_data_4.v4.ipv4ll_registation =
+        nm_l3_ipv4ll_register_new(priv->ipll_data_4.v4.ipv4ll, timeout_msec);
+}
 
-        for (i = 0; configs[i]; i++)
-            g_object_unref(configs[i]);
-        g_free(configs);
+/*****************************************************************************/
 
-        return;
-    }
+static const char *
+_device_get_dhcp_anycast_address(NMDevice *self)
+{
+    NMDeviceClass *klass;
+
+    nm_assert(NM_IS_DEVICE(self));
 
-    data           = g_slice_new0(AcdData);
-    data->configs  = configs;
-    data->callback = cb;
-    data->device   = self;
+    klass = NM_DEVICE_GET_CLASS(self);
 
-    acd_manager        = nm_acd_manager_new(nm_device_get_ip_ifindex(self),
-                                     hwaddr_arr,
-                                     length,
-                                     &acd_callbacks,
-                                     data);
-    priv->acd.dad_list = g_slist_append(priv->acd.dad_list, acd_manager);
+    if (klass->get_dhcp_anycast_address)
+        return klass->get_dhcp_anycast_address(self);
 
-    for (i = 0; configs[i]; i++) {
-        nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
-            nm_acd_manager_add_address(acd_manager, address->address);
-    }
+    return NULL;
+}
 
-    r = nm_acd_manager_start_probe(acd_manager, timeout);
-    if (r < 0) {
-        _LOGW(LOGD_DEVICE, "acd probe failed");
+/*****************************************************************************/
 
-        /* DAD could not be started, signal success */
-        cb(self, configs, TRUE);
+static IPDevStateData *
+_dev_ipdev_data(NMDevice *self, int addr_family)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-        priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager);
-        nm_acd_manager_free(acd_manager);
+    switch (addr_family) {
+    case AF_INET:
+        return &priv->ipdev_data_4;
+    case AF_INET6:
+        return &priv->ipdev_data_6;
+    default:
+        nm_assert_not_reached();
+        /* fall-through */
+    case AF_UNSPEC:
+        return &priv->ipdev_data_unspec;
     }
 }
 
-/*****************************************************************************/
-/* IPv4LL stuff */
-
 static void
-ipv4ll_cleanup(NMDevice *self)
+_dev_ipdev_cleanup(NMDevice *self, int addr_family)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    IPDevStateData *p;
 
-    if (priv->ipv4ll) {
-        sd_ipv4ll_set_callback(priv->ipv4ll, NULL, NULL);
-        sd_ipv4ll_stop(priv->ipv4ll);
-        priv->ipv4ll = sd_ipv4ll_unref(priv->ipv4ll);
+    p = _dev_ipdev_data(self, addr_family);
+    if (p->state != NM_DEVICE_IP_STATE_NONE) {
+        _LOGD_ipdev(addr_family, "reset state");
+        p->state         = NM_DEVICE_IP_STATE_NONE;
+        p->failed_reason = NM_DEVICE_STATE_REASON_NONE;
     }
 
-    nm_clear_g_source(&priv->ipv4ll_timeout);
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DEVIP(addr_family), NULL, FALSE);
 }
 
-static NMIP4Config *
-ipv4ll_get_ip4_config(NMDevice *self, guint32 lla)
+NMDeviceIPState
+nm_device_devip_get_state(NMDevice *self, int addr_family)
 {
-    NMIP4Config *        config = NULL;
-    NMPlatformIP4Address address;
-    NMPlatformIP4Route   route;
+    g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_IP_STATE_NONE);
 
-    config = nm_device_ip4_config_new(self);
-    g_assert(config);
+    return _dev_ipdev_data(self, addr_family)->state;
+}
 
-    memset(&address, 0, sizeof(address));
-    nm_platform_ip4_address_set_addr(&address, lla, 16);
-    address.addr_source = NM_IP_CONFIG_SOURCE_IP4LL;
-    nm_ip4_config_add_address(config, &address);
+void
+nm_device_devip_set_state_full(NMDevice             *self,
+                               int                   addr_family,
+                               NMDeviceIPState       ip_state,
+                               const NML3ConfigData *l3cd,
+                               NMDeviceStateReason   failed_reason)
+{
+    NMDevicePrivate *priv;
+    IPDevStateData  *p;
+
+    g_return_if_fail(NM_IS_DEVICE(self));
+
+    priv = NM_DEVICE_GET_PRIVATE(self);
+
+    nm_assert_addr_family_or_unspec(addr_family);
+    nm_assert(NM_IN_SET(ip_state,
+                        NM_DEVICE_IP_STATE_PENDING,
+                        NM_DEVICE_IP_STATE_READY,
+                        NM_DEVICE_IP_STATE_FAILED));
+    nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
 
-    /* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
-    memset(&route, 0, sizeof(route));
-    route.network       = htonl(0xE0000000L);
-    route.plen          = 4;
-    route.rt_source     = NM_IP_CONFIG_SOURCE_IP4LL;
-    route.table_coerced = nm_platform_route_table_coerce(nm_device_get_route_table(self, AF_INET));
-    route.metric        = nm_device_get_route_metric(self, AF_INET);
-    nm_ip4_config_add_route(config, &route, NULL);
+    nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED)
+              == (failed_reason == NM_DEVICE_STATE_REASON_NONE));
+    nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED) || !l3cd);
 
-    return config;
+    p = _dev_ipdev_data(self, addr_family);
+
+    if (p->state == ip_state && p->failed_reason == failed_reason
+        && priv->l3cds[L3_CONFIG_DATA_TYPE_DEVIP(addr_family)].d == l3cd)
+        return;
+
+    if (ip_state == NM_DEVICE_IP_STATE_FAILED) {
+        _LOGD_ipdev(addr_family,
+                    "set state=failed (reason %s)",
+                    nm_device_state_reason_to_string_a(failed_reason));
+    } else {
+        _LOGD_ipdev(addr_family,
+                    "set state=%s%s",
+                    nm_device_ip_state_to_string(ip_state),
+                    l3cd ? " (has extra IP configuration)" : "");
+    }
+    p->state         = ip_state;
+    p->failed_reason = failed_reason;
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DEVIP(addr_family), l3cd, FALSE);
+    _dev_ip_state_check_async(self, addr_family);
 }
 
+/*****************************************************************************/
+
 static void
-nm_device_handle_ipv4ll_event(sd_ipv4ll *ll, int event, void *data)
+_dev_ipmanual_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
 {
-    NMDevice *       self = data;
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    struct in_addr   address;
-    NMIP4Config *    config;
-    int              r;
+    int              IS_IPv4;
 
-    if (priv->act_request.obj == NULL)
+    if (addr_family == AF_UNSPEC) {
+        _dev_ipmanual_set_state(self, AF_INET, state);
+        _dev_ipmanual_set_state(self, AF_INET6, state);
         return;
+    }
 
-    nm_assert(nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET),
-                       NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL));
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+    if (priv->ipmanual_data.state_x[IS_IPv4] != state) {
+        _LOGD_ipmanual(addr_family, "set state %s", nm_device_ip_state_to_string(state));
+        priv->ipmanual_data.state_x[IS_IPv4] = state;
+    }
+}
 
-    switch (event) {
-    case SD_IPV4LL_EVENT_BIND:
-        r = sd_ipv4ll_get_address(ll, &address);
-        if (r < 0) {
-            _LOGE(LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r);
-            nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-            return;
-        }
+static void
+_dev_ipmanual_cleanup(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-        if (!nm_utils_ip4_address_is_link_local(address.s_addr)) {
-            _LOGE(LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr);
-            nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
-            return;
-        }
+    if (priv->ipmanual_data.state_4 == NM_DEVICE_IP_STATE_NONE
+        && priv->ipmanual_data.state_6 == NM_DEVICE_IP_STATE_NONE) {
+        nm_assert(!priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d);
+        return;
+    }
 
-        config = ipv4ll_get_ip4_config(self, address.s_addr);
-        if (config == NULL) {
-            _LOGE(LOGD_AUTOIP4, "failed to get IPv4LL config");
-            nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
-            return;
-        }
+    _dev_ipmanual_set_state(self, AF_UNSPEC, NM_DEVICE_IP_STATE_NONE);
+    nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
+    priv->ipmanual_data.carrier_timeout_expired = FALSE;
 
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
-            nm_clear_g_source(&priv->ipv4ll_timeout);
-            nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(config));
-        } else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
-            applied_config_init(&priv->dev_ip_config_4, config);
-            if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) {
-                _LOGE(LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
-                nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
-            }
-        } else
-            g_assert_not_reached();
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_MANUALIP, NULL, FALSE);
 
-        g_object_unref(config);
-        break;
-    default:
-        _LOGW(LOGD_AUTOIP4, "IPv4LL address no longer valid after event %d.", event);
-        nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
-    }
+    _dev_ip_state_check_async(self, AF_INET);
+    _dev_ip_state_check_async(self, AF_INET6);
 }
 
 static gboolean
-ipv4ll_timeout_cb(gpointer user_data)
+_dev_ipmanual_carrier_timeout(NMDevice *self)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->ipv4ll_timeout) {
-        _LOGI(LOGD_AUTOIP4, "IPv4LL configuration timed out.");
-        priv->ipv4ll_timeout = 0;
-        ipv4ll_cleanup(self);
-
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
-            nm_device_activate_schedule_ip_config_timeout(self, AF_INET);
-    }
+    priv->ipmanual_data.carrier_timeout_expired = TRUE;
+    nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
+    _dev_ipmanual_check_ready(self);
 
-    return FALSE;
+    return G_SOURCE_CONTINUE;
 }
 
-static NMActStageReturn
-ipv4ll_start(NMDevice *self)
-{
-    NMDevicePrivate *        priv = NM_DEVICE_GET_PRIVATE(self);
-    const struct ether_addr *addr;
-    int                      ifindex, r;
-    size_t                   addr_len;
+static void
+_dev_ipmanual_check_ready(NMDevice *self)
+{
+    NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
+    const NMPlatformLink  *plink;
+    gboolean               has_carrier;
+    NML3CfgCheckReadyFlags flags;
+    gboolean               ready;
+    gboolean               acd_used = FALSE;
+    int                    IS_IPv4;
+
+    if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_PENDING
+        && priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_PENDING) {
+        /* we only care about PENDING to get it READY. Currently not other
+         * conditions are implemented. That is, we cannot get to FAILED
+         * (maybe we should, if DAD fails) and we cannot get from anything
+         * once we are READY. */
+        return;
+    }
 
-    ipv4ll_cleanup(self);
+    plink       = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
+    has_carrier = plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP);
 
-    r = sd_ipv4ll_new(&priv->ipv4ll);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: new() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (has_carrier) {
+        nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
+    } else {
+        if (priv->ipmanual_data.carrier_timeout_expired) {
+            /* go on */
+        } else if (priv->ipmanual_data.carrier_timeout) {
+            /* wait a bit more until timer expires */
+            return;
+        } else {
+            priv->ipmanual_data.carrier_timeout =
+                nm_g_timeout_add_source(2000, G_SOURCE_FUNC(_dev_ipmanual_carrier_timeout), self);
+            return;
+        }
     }
 
-    r = sd_ipv4ll_attach_event(priv->ipv4ll, NULL, 0);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: attach_event() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    flags = NM_L3CFG_CHECK_READY_FLAGS_NONE;
+    if (has_carrier) {
+        flags |= NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY;
+        flags |= NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY;
     }
 
-    ifindex = nm_device_get_ip_ifindex(self);
-    addr    = nm_platform_link_get_address(nm_device_get_platform(self), ifindex, &addr_len);
-    if (!addr || addr_len != ETH_ALEN) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: can't retrieve hardware address");
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
+    for (IS_IPv4 = 0; IS_IPv4 < 2; IS_IPv4++) {
+        const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
 
-    r = sd_ipv4ll_set_mac(priv->ipv4ll, addr);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: set_mac() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+        ready = nm_l3cfg_check_ready(priv->l3cfg,
+                                     priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d,
+                                     addr_family,
+                                     flags,
+                                     &acd_used);
+        if (acd_used) {
+            _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
+            _dev_ip_state_check_async(self, AF_UNSPEC);
+        } else if (ready) {
+            _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
+            _dev_ip_state_check_async(self, AF_UNSPEC);
+        }
     }
+}
 
-    r = sd_ipv4ll_set_ifindex(priv->ipv4ll, ifindex);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: set_ifindex() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
+static void
+_dev_ipmanual_start(NMDevice *self)
+{
+    NMDevicePrivate                         *priv = NM_DEVICE_GET_PRIVATE(self);
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+
+    if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_NONE
+        || priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
+        return;
 
-    r = sd_ipv4ll_set_callback(priv->ipv4ll, nm_device_handle_ipv4ll_event, self);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: set_callback() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (nm_device_get_ip_ifindex(self) > 0) {
+        l3cd =
+            nm_device_create_l3_config_data_from_connection(self,
+                                                            nm_device_get_applied_connection(self));
     }
 
-    r = sd_ipv4ll_start(priv->ipv4ll);
-    if (r < 0) {
-        _LOGE(LOGD_AUTOIP4, "IPv4LL: start() failed with error %d", r);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (!l3cd) {
+        _dev_ipmanual_cleanup(self);
+        return;
     }
 
-    _LOGI(LOGD_DEVICE | LOGD_AUTOIP4, "IPv4LL: started");
+    /* Initially we set the state to pending, because we (maybe) have to perform ACD first. */
+    _dev_ipmanual_set_state(self, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING);
+
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_MANUALIP, l3cd, FALSE);
 
-    /* Start a timeout to bound the address attempt */
-    priv->ipv4ll_timeout = g_timeout_add_seconds(20, ipv4ll_timeout_cb, self);
-    return NM_ACT_STAGE_RETURN_POSTPONE;
+    _dev_ip_state_check_async(self, AF_UNSPEC);
 }
 
 /*****************************************************************************/
 
 static void
-ensure_con_ip_config(NMDevice *self, int addr_family)
+_dev_ipdhcpx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMIPConfig *     con_ip_config;
 
-    if (priv->con_ip_config_x[IS_IPv4])
-        return;
-
-    connection = nm_device_get_applied_connection(self);
-    if (!connection)
-        return;
+    if (priv->ipdhcp_data_x[IS_IPv4].state != state) {
+        _LOGD_ipdhcp(addr_family,
+                     "set state %s (was %s)",
+                     nm_device_ip_state_to_string(state),
+                     nm_device_ip_state_to_string(priv->ipdhcp_data_x[IS_IPv4].state));
+        priv->ipdhcp_data_x[IS_IPv4].state = state;
+    }
+}
 
-    con_ip_config = nm_device_ip_config_new(self, addr_family);
+static void
+_dev_ipdhcpx_cleanup(NMDevice *self, int addr_family, gboolean full_cleanup, gboolean release)
+{
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    if (IS_IPv4) {
-        nm_ip4_config_merge_setting(NM_IP4_CONFIG(con_ip_config),
-                                    nm_connection_get_setting_ip4_config(connection),
-                                    _prop_get_connection_mdns(self),
-                                    _prop_get_connection_llmnr(self),
-                                    _prop_get_connection_dns_over_tls(self),
-                                    nm_device_get_route_table(self, addr_family),
-                                    nm_device_get_route_metric(self, addr_family));
-    } else {
-        nm_ip6_config_merge_setting(NM_IP6_CONFIG(con_ip_config),
-                                    nm_connection_get_setting_ip6_config(connection),
-                                    nm_device_get_route_table(self, addr_family),
-                                    nm_device_get_route_metric(self, addr_family));
-    }
+    _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
 
-    if (nm_device_sys_iface_state_is_external_or_assume(self)) {
-        /* For assumed connections ignore all addresses and routes. */
-        nm_ip_config_reset_addresses(con_ip_config);
-        nm_ip_config_reset_routes(con_ip_config);
+    if (full_cleanup && !IS_IPv4) {
+        priv->ipdhcp_data_6.v6.mode            = NM_NDISC_DHCP_LEVEL_NONE;
+        priv->ipdhcp_data_6.v6.needed_prefixes = 0;
     }
 
-    priv->con_ip_config_x[IS_IPv4] = con_ip_config;
-}
-
-/*****************************************************************************/
+    if (full_cleanup)
+        _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), NULL, FALSE);
 
-static const char *
-_device_get_dhcp_anycast_address(NMDevice *self)
-{
-    NMDeviceClass *klass;
-
-    nm_assert(NM_IS_DEVICE(self));
+    if (priv->ipdhcp_data_x[IS_IPv4].client) {
+        nm_clear_g_signal_handler(priv->ipdhcp_data_x[IS_IPv4].client,
+                                  &priv->ipdhcp_data_x[IS_IPv4].notify_sigid);
+        nm_dhcp_client_stop(priv->ipdhcp_data_x[IS_IPv4].client, release);
+        g_clear_object(&priv->ipdhcp_data_x[IS_IPv4].client);
+    }
 
-    klass = NM_DEVICE_GET_CLASS(self);
+    if (full_cleanup && priv->ipdhcp_data_x[IS_IPv4].config) {
+        gs_unref_object NMDhcpConfig *config =
+            g_steal_pointer(&priv->ipdhcp_data_x[IS_IPv4].config);
 
-    if (klass->get_dhcp_anycast_address)
-        return klass->get_dhcp_anycast_address(self);
+        _notify(self, PROP_DHCPX_CONFIG(IS_IPv4));
+        nm_dbus_object_unexport_on_idle(g_steal_pointer(&config));
+    }
 
-    return NULL;
+    _dev_ip_state_check_async(self, addr_family);
 }
 
 static void
-dhcp4_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
+_dev_ipdhcpx_handle_fail(NMDevice *self, int addr_family, const char *reason)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    priv->dhcp_data_4.was_active = FALSE;
-    nm_clear_g_source(&priv->dhcp_data_4.grace_id);
-    priv->dhcp_data_4.grace_pending = FALSE;
-    nm_clear_g_free(&priv->dhcp4.pac_url);
+    if (priv->ipdhcp_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_FAILED)
+        return;
 
-    if (priv->dhcp_data_4.client) {
-        /* Stop any ongoing DHCP transaction on this device */
-        nm_clear_g_signal_handler(priv->dhcp_data_4.client, &priv->dhcp_data_4.notify_sigid);
+    _LOGT_ipdhcp(addr_family, "DHCP failing: %s", reason ?: "unknown reason");
 
-        if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED)
-            nm_dhcp_client_stop(priv->dhcp_data_4.client, release);
+    _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
 
-        g_clear_object(&priv->dhcp_data_4.client);
-    }
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), NULL, FALSE);
 
-    if (priv->dhcp_data_4.config) {
-        nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
-        _notify(self, PROP_DHCP4_CONFIG);
-    }
+    if (priv->ipdhcp_data_x[IS_IPv4].config)
+        nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config, NULL);
+
+    _dev_ip_state_check_async(self, addr_family);
 }
 
-static gboolean
-ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit)
+static void
+_dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    gboolean         success;
-    gs_unref_object NMIPConfig *composite = NULL;
-    NMIPConfig *                config;
-    gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
-    NMConnection *               connection;
-    gboolean                     ignore_auto_routes    = FALSE;
-    gboolean                     ignore_auto_dns       = FALSE;
-    gboolean                     ignore_default_routes = FALSE;
-    GSList *                     iter;
-    const char *                 ip6_addr_gen_token = NULL;
-    const int                    IS_IPv4            = NM_IS_IPv4(addr_family);
+    NMDevicePrivate *priv        = NM_DEVICE_GET_PRIVATE(self);
+    const int        addr_family = nm_dhcp_client_get_addr_family(client);
+    const int        IS_IPv4     = NM_IS_IPv4(addr_family);
 
-    if (nm_device_sys_iface_state_is_external(self))
-        commit = FALSE;
+    nm_assert(notify_data);
+    nm_assert(priv->ipdhcp_data_x[IS_IPv4].state > NM_DEVICE_IP_STATE_NONE);
+    nm_assert(client && priv->ipdhcp_data_x[IS_IPv4].client == client);
 
-    connection = nm_device_get_applied_connection(self);
+    switch (notify_data->notify_type) {
+    case NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED:
+        nm_assert(!IS_IPv4);
+        /* Just re-emit. The device just contributes the prefix to the
+         * pool in NMPolicy, which decides about subnet allocation
+         * on the shared devices. */
+        g_signal_emit(self, signals[IP6_PREFIX_DELEGATED], 0, notify_data->prefix_delegated.prefix);
+        return;
 
-    /* Apply ignore-auto-routes and ignore-auto-dns settings */
-    if (connection) {
-        NMSettingIPConfig *s_ip;
+    case NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT:
+        /* Here we also fail if we had a lease and it expired. Maybe,
+         * ipv[46].dhcp-timeout should only cover the time until we get
+         * a lease for the first time. How it is here, it means that a
+         * connection can fail after being connected successfully for a
+         * longer time. */
+        _dev_ipdhcpx_handle_fail(self, addr_family, "timeout getting lease");
+        return;
 
-        s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
-        if (s_ip) {
-            ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes(s_ip);
-            ignore_auto_dns    = nm_setting_ip_config_get_ignore_auto_dns(s_ip);
+    case NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD:
+        /* Like NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT, this does not
+         * apply only if we never got a lease, but also after being fully
+         * connected. We can also fail then. */
+        _dev_ipdhcpx_handle_fail(self, addr_family, notify_data->it_looks_bad.reason);
+        return;
 
-            /* if the connection has an explicit gateway, we also ignore
-             * the default routes from other sources. */
-            ignore_default_routes = nm_setting_ip_config_get_never_default(s_ip)
-                                    || nm_setting_ip_config_get_gateway(s_ip);
+    case NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE:
 
-            if (!IS_IPv4) {
-                NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG(s_ip);
+        if (!notify_data->lease_update.l3cd) {
+            _LOGT_ipdhcp(addr_family, "lease lost");
+            goto lease_update_out;
+        }
 
-                if (nm_setting_ip6_config_get_addr_gen_mode(s_ip6)
-                    == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
-                    ip6_addr_gen_token = nm_setting_ip6_config_get_token(s_ip6);
+        if (notify_data->lease_update.accepted)
+            _LOGT_ipdhcp(addr_family, "lease accepted");
+        else
+            _LOGT_ipdhcp(addr_family, "lease update");
+
+        nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config,
+                                 notify_data->lease_update.l3cd);
+
+        /* Schedule a commit of the configuration. If the DHCP client
+         * needs to accept the lease, it will send later a LEASE_UPDATE
+         * notification with accepted=1 once the address appears in platform.
+         * Otherwise, this notification already has accepted=1. */
+        _dev_l3_register_l3cds_set_one_full(self,
+                                            L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
+                                            notify_data->lease_update.l3cd,
+                                            NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
+                                            FALSE);
+
+        if (notify_data->lease_update.accepted) {
+            if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_READY) {
+                _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
+                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4),
+                                          self,
+                                          NULL,
+                                          NULL,
+                                          NULL,
+                                          NULL);
+                _dev_ip_state_check_async(self, addr_family);
             }
         }
-    }
 
-    composite = nm_device_ip_config_new(self, addr_family);
-
-    if (!IS_IPv4) {
-        nm_ip6_config_set_privacy(NM_IP6_CONFIG(composite),
-                                  priv->ndisc ? priv->ndisc_use_tempaddr
-                                              : NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
+lease_update_out:
+        nm_device_update_metered(self);
+        return;
     }
 
-    init_ip_config_dns_priority(self, composite);
-
-    if (commit) {
-        if (priv->queued_ip_config_id_x[IS_IPv4])
-            update_ext_ip_config(self, addr_family, FALSE);
-        ensure_con_ip_config(self, addr_family);
-    }
+    nm_assert_not_reached();
+}
 
-    if (!IS_IPv4) {
-        if (commit && priv->ipv6ll_has) {
-            const NMPlatformIP6Address ll_a = {
-                .address     = priv->ipv6ll_addr,
-                .plen        = 64,
-                .addr_source = NM_IP_CONFIG_SOURCE_IP6LL,
-            };
-            const NMPlatformIP6Route ll_r = {
-                .network.s6_addr16[0] = htons(0xfe80u),
-                .plen                 = 64,
-                .metric               = nm_device_get_route_metric(self, addr_family),
-                .rt_source            = NM_IP_CONFIG_SOURCE_IP6LL,
-            };
+/*****************************************************************************/
 
-            nm_assert(IN6_IS_ADDR_LINKLOCAL(&priv->ipv6ll_addr));
+static void
+_dev_ipdhcpx_start(NMDevice *self, int addr_family)
+{
+    const int              IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate       *priv    = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection          *connection;
+    NMSettingConnection   *s_con;
+    NMSettingIPConfig     *s_ip;
+    const NML3ConfigData  *previous_lease;
+    gs_unref_bytes GBytes *hwaddr       = NULL;
+    gboolean               enforce_duid = FALSE;
+    gs_free_error GError  *error        = NULL;
+    const NMPlatformLink  *pllink;
+    guint                  no_lease_timeout_sec;
+    int                    ifindex;
+    const char            *str;
+    gboolean               request_broadcast;
+    const char            *fail_reason;
 
-            nm_ip6_config_add_address(NM_IP6_CONFIG(composite), &ll_a);
-            nm_ip6_config_add_route(NM_IP6_CONFIG(composite), &ll_r, NULL);
-        }
+    if (priv->ipdhcp_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_NONE)
+        _dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_PENDING);
+    else if (priv->ipdhcp_data_x[IS_IPv4].state > NM_DEVICE_IP_STATE_PENDING) {
+        /* already succeeded or failed */
+        return;
+    } else if (priv->ipdhcp_data_x[IS_IPv4].client) {
+        /* DHCP client already started */
+        return;
     }
 
-    if (commit) {
-        gboolean v;
+    if (nm_device_sys_iface_state_is_external(self)) {
+        fail_reason = nm_assert_unreachable_val("cannot run DHCP on external interface");
+        goto out_fail;
+    }
 
-        v = default_route_metric_penalty_detect(self, addr_family);
-        if (IS_IPv4)
-            priv->default_route_metric_penalty_ip4_has = v;
-        else
-            priv->default_route_metric_penalty_ip6_has = v;
+    connection = nm_device_get_applied_connection(self);
+    if (!connection) {
+        fail_reason = nm_assert_unreachable_val("no applied connection for starting DHCP");
+        goto out_fail;
     }
 
-    /* Merge all the IP configs into the composite config */
+    s_con = nm_connection_get_setting_connection(connection);
+    s_ip  = nm_connection_get_setting_ip_config(connection, addr_family);
+    nm_assert(s_con);
+    nm_assert(s_ip);
 
-    if (IS_IPv4) {
-        config = applied_config_get_current(&priv->dev_ip_config_4);
-        if (config) {
-            nm_ip4_config_merge(
-                NM_IP4_CONFIG(composite),
-                NM_IP4_CONFIG(config),
-                (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-                    | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-                    | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-                default_route_metric_penalty_get(self, addr_family));
-        }
+    ifindex = 0;
+    pllink  = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
+    if (pllink) {
+        ifindex = pllink->ifindex;
+        nm_assert(ifindex > 0);
+        nm_assert(ifindex == nm_device_get_ip_ifindex(self));
     }
-
-    if (!IS_IPv4) {
-        config = applied_config_get_current(&priv->ac_ip6_config);
-        if (config) {
-            nm_ip6_config_merge(
-                NM_IP6_CONFIG(composite),
-                NM_IP6_CONFIG(config),
-                (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-                    | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-                    | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-                default_route_metric_penalty_get(self, addr_family));
-        }
+    if (ifindex <= 0) {
+        fail_reason = "cannot start DHCP without interface";
+        goto out_fail;
     }
 
+    hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
+
     if (!IS_IPv4) {
-        config = applied_config_get_current(&priv->dhcp6.ip6_config);
-        if (config) {
-            nm_ip6_config_merge(
-                NM_IP6_CONFIG(composite),
-                NM_IP6_CONFIG(config),
-                (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-                    | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-                    | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-                default_route_metric_penalty_get(self, addr_family));
+        if (!hwaddr) {
+            fail_reason = "interface has no MAC address to start DHCPv6";
+            goto out_fail;
         }
     }
 
-    for (iter = priv->vpn_configs_x[IS_IPv4]; iter; iter = iter->next)
-        nm_ip_config_merge(composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
-
-    if (priv->ext_ip_config_x[IS_IPv4])
-        nm_ip_config_merge(composite,
-                           priv->ext_ip_config_x[IS_IPv4],
-                           NM_IP_CONFIG_MERGE_EXTERNAL,
-                           0);
-
-    /* Merge WWAN config *last* to ensure modem-given settings overwrite
-     * any external stuff set by pppd or other scripts.
-     */
-    config = applied_config_get_current(&priv->dev2_ip_config_x[IS_IPv4]);
-    if (config) {
-        nm_ip_config_merge(composite,
-                           config,
-                           (ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
-                               | (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
-                               | (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
-                           default_route_metric_penalty_get(self, addr_family));
-    }
-
-    if (!IS_IPv4) {
-        if (priv->rt6_temporary_not_available) {
-            const NMPObject *o;
-            GHashTableIter   hiter;
-
-            g_hash_table_iter_init(&hiter, priv->rt6_temporary_not_available);
-            while (g_hash_table_iter_next(&hiter, (gpointer *) &o, NULL)) {
-                nm_ip6_config_add_route(NM_IP6_CONFIG(composite),
-                                        NMP_OBJECT_CAST_IP6_ROUTE(o),
-                                        NULL);
-            }
+    request_broadcast = FALSE;
+    if (pllink) {
+        str = nmp_object_link_udev_device_get_property_value(NMP_OBJECT_UP_CAST(pllink),
+                                                             "ID_NET_DHCP_BROADCAST");
+        if (str && _nm_utils_ascii_str_to_bool(str, FALSE)) {
+            /* Use the device property ID_NET_DHCP_BROADCAST setting, which may be set for interfaces
+             * requiring that the DHCPOFFER message is being broadcast because they can't handle unicast
+             * messages while not fully configured.
+             */
+            request_broadcast = TRUE;
         }
     }
 
-    /* Merge user overrides into the composite config. For assumed connections,
-     * con_ip_config_x is empty. */
-    if (priv->con_ip_config_x[IS_IPv4]) {
-        nm_ip_config_merge(composite,
-                           priv->con_ip_config_x[IS_IPv4],
-                           NM_IP_CONFIG_MERGE_DEFAULT,
-                           default_route_metric_penalty_get(self, addr_family));
+    if (!IS_IPv4
+        && NM_IN_SET(priv->ipll_data_6.state,
+                     NM_DEVICE_IP_STATE_NONE,
+                     NM_DEVICE_IP_STATE_PENDING)) {
+        _dev_ipll6_start(self);
+        return;
     }
 
-    if (commit) {
-        gboolean is_vrf;
+    no_lease_timeout_sec = _prop_get_ipvx_dhcp_timeout(self, addr_family);
 
-        is_vrf = priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF;
+    if (IS_IPv4) {
+        NMDhcpClientConfig     config;
+        gs_unref_bytes GBytes *bcast_hwaddr            = NULL;
+        gs_unref_bytes GBytes *client_id               = NULL;
+        gs_unref_bytes GBytes *vendor_class_identifier = NULL;
+        const char *const     *reject_servers;
+        const char            *hostname;
+        gboolean               hostname_is_fqdn;
+
+        client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr);
+        vendor_class_identifier =
+            _prop_get_ipv4_dhcp_vendor_class_identifier(self, NM_SETTING_IP4_CONFIG(s_ip));
+        reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip, NULL);
+
+        bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
 
-        if (IS_IPv4) {
-            nm_ip4_config_add_dependent_routes(NM_IP4_CONFIG(composite),
-                                               nm_device_get_route_table(self, addr_family),
-                                               nm_device_get_route_metric(self, addr_family),
-                                               is_vrf,
-                                               &ip4_dev_route_blacklist);
+        hostname = nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip));
+        if (hostname) {
+            hostname_is_fqdn = TRUE;
         } else {
-            nm_ip6_config_add_dependent_routes(NM_IP6_CONFIG(composite),
-                                               nm_device_get_route_table(self, addr_family),
-                                               nm_device_get_route_metric(self, addr_family),
-                                               is_vrf);
+            hostname_is_fqdn = FALSE;
+            hostname         = nm_setting_ip_config_get_dhcp_hostname(s_ip);
         }
-    }
 
-    if (IS_IPv4) {
-        if (commit) {
-            if (NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit)
-                NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit(self, NM_IP4_CONFIG(composite));
-        }
-    }
+        config = (NMDhcpClientConfig){
+            .addr_family             = AF_INET,
+            .l3cfg                   = nm_device_get_l3cfg(self),
+            .iface                   = nm_device_get_ip_iface(self),
+            .uuid                    = nm_connection_get_uuid(connection),
+            .hwaddr                  = hwaddr,
+            .bcast_hwaddr            = bcast_hwaddr,
+            .send_hostname           = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
+            .hostname                = hostname,
+            .hostname_flags          = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
+            .client_id               = client_id,
+            .mud_url                 = _prop_get_connection_mud_url(self, s_con),
+            .timeout                 = no_lease_timeout_sec,
+            .anycast_address         = _device_get_dhcp_anycast_address(self),
+            .vendor_class_identifier = vendor_class_identifier,
+            .use_fqdn                = hostname_is_fqdn,
+            .reject_servers          = reject_servers,
+            .v4.request_broadcast    = request_broadcast,
+        };
 
-    if (!IS_IPv4) {
-        NMUtilsIPv6IfaceId iid;
+        priv->ipdhcp_data_4.client =
+            nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error);
+    } else {
+        gs_unref_bytes GBytes *duid = NULL;
+        gboolean               iaid_explicit;
+        guint32                iaid;
+        NMDhcpClientConfig     config;
+
+        iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_explicit);
+        duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
+
+        config = (NMDhcpClientConfig){
+            .addr_family        = AF_INET6,
+            .l3cfg              = nm_device_get_l3cfg(self),
+            .iface              = nm_device_get_ip_iface(self),
+            .uuid               = nm_connection_get_uuid(connection),
+            .send_hostname      = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
+            .hostname           = nm_setting_ip_config_get_dhcp_hostname(s_ip),
+            .hostname_flags     = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
+            .client_id          = duid,
+            .mud_url            = _prop_get_connection_mud_url(self, s_con),
+            .timeout            = no_lease_timeout_sec,
+            .anycast_address    = _device_get_dhcp_anycast_address(self),
+            .v6.enforce_duid    = enforce_duid,
+            .v6.iaid            = iaid,
+            .v6.iaid_explicit   = iaid_explicit,
+            .v6.info_only       = (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF),
+            .v6.needed_prefixes = priv->ipdhcp_data_6.v6.needed_prefixes,
+        };
 
-        if (commit && priv->ndisc_started && ip6_addr_gen_token
-            && nm_utils_ipv6_interface_identifier_get_from_token(&iid, ip6_addr_gen_token)) {
-            set_ipv6_token(self, &iid, ip6_addr_gen_token);
-        }
+        priv->ipdhcp_data_6.client =
+            nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error);
     }
 
-    success =
-        nm_device_set_ip_config(self, addr_family, composite, commit, ip4_dev_route_blacklist);
-    if (commit) {
-        if (IS_IPv4)
-            priv->v4_commit_first_time = FALSE;
-        else
-            priv->v6_commit_first_time = FALSE;
+    if (!priv->ipdhcp_data_x[IS_IPv4].client) {
+        fail_reason = error->message;
+        goto out_fail;
     }
 
-    return success;
-}
-
-static gboolean
-dhcp4_lease_change(NMDevice *self, NMIP4Config *config, gboolean bound)
-{
-    NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
-    gs_free_error GError *error = NULL;
-
-    g_return_val_if_fail(config, FALSE);
+    priv->ipdhcp_data_x[IS_IPv4].notify_sigid =
+        g_signal_connect(priv->ipdhcp_data_x[IS_IPv4].client,
+                         NM_DHCP_CLIENT_NOTIFY,
+                         G_CALLBACK(_dev_ipdhcpx_notify),
+                         self);
 
-    applied_config_init(&priv->dev_ip_config_4, config);
+    /* FIXME(l3cfg:dhcp:previous-lease): take the NML3ConfigData from the previous lease (if any)
+     * and pass it on to NMDhcpClient. This is a fake lease that we use initially (until
+     * NMDhcpClient got a real lease). Note that NMDhcpClient needs to check whether the
+     * lease already expired. */
 
-    if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) {
-        _LOGW(LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
-        return FALSE;
+    previous_lease = nm_dhcp_client_get_lease(priv->ipdhcp_data_x[IS_IPv4].client);
+    if (!priv->ipdhcp_data_x[IS_IPv4].config) {
+        priv->ipdhcp_data_x[IS_IPv4].config = nm_dhcp_config_new(addr_family, previous_lease);
+        _notify(self, PROP_DHCPX_CONFIG(IS_IPv4));
     }
-
-    /* TODO: we should perform DAD again whenever we obtain a
-     * new lease after an expiry. But what should we do if
-     * a duplicate address is detected? Fail the connection;
-     * restart DHCP; continue without an address? */
-    if (bound && !nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
-        _LOGW(LOGD_DHCP4, "error accepting lease: %s", error->message);
-        return FALSE;
+    if (previous_lease) {
+        nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config, previous_lease);
+        _dev_l3_register_l3cds_set_one_full(self,
+                                            L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
+                                            previous_lease,
+                                            NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
+                                            FALSE);
     }
 
-    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_4, self, NULL, NULL, NULL, NULL);
+    return;
 
-    return TRUE;
+out_fail:
+    _dev_ipdhcpx_handle_fail(self, addr_family, fail_reason);
 }
 
-static gboolean
-dhcp_grace_period_expired(NMDevice *self, int addr_family)
+static void
+_dev_ipdhcpx_start_continue(NMDevice *self, int addr_family)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    priv->dhcp_data_x[IS_IPv4].grace_id      = 0;
-    priv->dhcp_data_x[IS_IPv4].grace_pending = FALSE;
-
-    _LOGI(LOGD_DHCPX(IS_IPv4),
-          "DHCPv%c: grace period expired",
-          nm_utils_addr_family_to_char(addr_family));
-
-    nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
-    /* If the device didn't fail, the DHCP client will continue */
-
-    return G_SOURCE_REMOVE;
-}
-
-static gboolean
-dhcp_grace_period_expired_4(gpointer user_data)
-{
-    return dhcp_grace_period_expired(user_data, AF_INET);
-}
-
-static gboolean
-dhcp_grace_period_expired_6(gpointer user_data)
-{
-    return dhcp_grace_period_expired(user_data, AF_INET6);
+    if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_NONE)
+        _dev_ipdhcpx_start(self, addr_family);
 }
 
-static gboolean
-dhcp_grace_period_start(NMDevice *self, int addr_family)
+static void
+_dev_ipdhcpx_restart(NMDevice *self, int addr_family, gboolean release)
 {
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    guint32          timeout;
-
-    /* In any other case (expired lease, assumed connection, etc.),
-     * wait for some time before failing the IP method.
-     */
-    if (priv->dhcp_data_x[IS_IPv4].grace_pending) {
-        /* already pending. */
-        return FALSE;
-    }
 
-    /* Start a grace period equal to the DHCP timeout multiplied
-     * by a constant factor. */
-    timeout = _prop_get_ipvx_dhcp_timeout(self, addr_family);
-    if (timeout == NM_DHCP_TIMEOUT_INFINITY)
-        _LOGI(LOGD_DHCPX(IS_IPv4),
-              "DHCPv%c: trying to acquire a new lease",
-              nm_utils_addr_family_to_char(addr_family));
-    else {
-        timeout = dhcp_grace_period_from_timeout(timeout);
-        _LOGI(LOGD_DHCPX(IS_IPv4),
-              "DHCPv%c: trying to acquire a new lease within %u seconds",
-              nm_utils_addr_family_to_char(addr_family),
-              timeout);
-        nm_assert(!priv->dhcp_data_x[IS_IPv4].grace_id);
-        priv->dhcp_data_x[IS_IPv4].grace_id = g_timeout_add_seconds(
-            timeout,
-            IS_IPv4 ? dhcp_grace_period_expired_4 : dhcp_grace_period_expired_6,
-            self);
+    if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_NONE) {
+        _LOGI_ipdhcp(addr_family, "restarting%s", release ? " (release lease)" : "");
+        _dev_ipdhcpx_cleanup(self, addr_family, FALSE, release);
     }
 
-    priv->dhcp_data_x[IS_IPv4].grace_pending = TRUE;
-
-    return TRUE;
+    _dev_ipdhcpx_start(self, addr_family);
 }
-static void
-dhcp4_fail(NMDevice *self, NMDhcpState dhcp_state)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    _LOGD(LOGD_DHCP4,
-          "DHCPv4 failed (ip_state %s, was_active %d)",
-          nm_device_ip_state_to_string(priv->ip_state_4),
-          priv->dhcp_data_4.was_active);
 
-    /* The client is always left running after a failure. */
-
-    /* Nothing to do if we failed before... */
-    if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
-        goto clear_config;
-
-    /* ... and also if there are static addresses configured
-     * on the interface.
-     */
-    if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_4
-        && nm_ip4_config_get_num_addresses(priv->con_ip_config_4) > 0)
-        goto clear_config;
-
-    /* Fail the method when one of the following is true:
-     * 1) the DHCP client terminated: it does not make sense to start a grace
-     *    period without a client running;
-     * 2) we failed to get an initial lease AND the client was
-     *    not active before.
-     */
-    if (dhcp_state == NM_DHCP_STATE_TERMINATED
-        || (!priv->dhcp_data_4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
-        nm_device_activate_schedule_ip_config_timeout(self, AF_INET);
-        return;
-    }
-
-    if (dhcp_grace_period_start(self, AF_INET))
-        goto clear_config;
-
-    return;
-
-clear_config:
-    /* The previous configuration is no longer valid */
-    if (priv->dhcp_data_4.config) {
-        nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
-        priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET);
-        _notify(self, PROP_DHCP4_CONFIG);
-    }
-}
-
-static void
-dhcp4_dad_cb(NMDevice *self, NMIP4Config **configs, gboolean success)
+void
+nm_device_ip_method_dhcp4_start(NMDevice *self)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (success) {
-        nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(configs[1]));
-    } else {
-        nm_dhcp_client_decline(priv->dhcp_data_4.client, "Address conflict detected", NULL);
-        nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
-    }
+    _dev_ipdhcpx_start(self, AF_INET);
 }
 
 static void
-dhcp4_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
+_dev_ipdhcp6_set_dhcp_level(NMDevice *self, NMNDiscDHCPLevel dhcp_level)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMIP4Config *    manual;
-    NMIP4Config **   configs;
-    NMConnection *   connection;
-    NMDhcpState      state;
-    NMIP4Config *    ip4_config;
-    GHashTable *     options;
-
-    nm_assert(nm_dhcp_client_get_addr_family(client) == AF_INET);
-    nm_assert(notify_data);
-    nm_assert(notify_data->notify_type == NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED);
 
-    state      = notify_data->state_changed.dhcp_state;
-    ip4_config = NM_IP4_CONFIG(notify_data->state_changed.ip_config);
-    options    = notify_data->state_changed.options;
+    nm_assert(NM_IN_SET(dhcp_level,
+                        NM_NDISC_DHCP_LEVEL_NONE,
+                        NM_NDISC_DHCP_LEVEL_OTHERCONF,
+                        NM_NDISC_DHCP_LEVEL_MANAGED));
 
-    nm_assert(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
+    if (dhcp_level == NM_NDISC_DHCP_LEVEL_NONE && priv->ipdhcp_data_6.v6.needed_prefixes > 0)
+        dhcp_level = NM_NDISC_DHCP_LEVEL_OTHERCONF;
 
-    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", (int) state);
-
-    switch (state) {
-    case NM_DHCP_STATE_BOUND:
-    case NM_DHCP_STATE_EXTENDED:
-        if (!ip4_config) {
-            _LOGW(LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
-            dhcp4_fail(self, state);
-            break;
-        }
-
-        nm_clear_g_source(&priv->dhcp_data_4.grace_id);
-        priv->dhcp_data_4.grace_pending = FALSE;
-
-        /* After some failures, we have been able to renew the lease:
-         * update the ip state
-         */
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
-            _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_CONF);
-
-        g_free(priv->dhcp4.pac_url);
-        priv->dhcp4.pac_url = g_strdup(g_hash_table_lookup(options, "wpad"));
-        nm_device_set_proxy_config(self, priv->dhcp4.pac_url);
-
-        nm_dhcp_config_set_options(priv->dhcp_data_4.config, options);
-        _notify(self, PROP_DHCP4_CONFIG);
-
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
-            connection = nm_device_get_applied_connection(self);
-            g_assert(connection);
-
-            manual = nm_device_ip4_config_new(self);
-            nm_ip4_config_merge_setting(manual,
-                                        nm_connection_get_setting_ip4_config(connection),
-                                        NM_SETTING_CONNECTION_MDNS_DEFAULT,
-                                        NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-                                        NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT,
-                                        nm_device_get_route_table(self, AF_INET),
-                                        nm_device_get_route_metric(self, AF_INET));
-
-            configs    = g_new0(NMIP4Config *, 3);
-            configs[0] = manual;
-            configs[1] = g_object_ref(ip4_config);
-
-            ipv4_dad_start(self, configs, dhcp4_dad_cb);
-        } else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
-            if (dhcp4_lease_change(self, ip4_config, state == NM_DHCP_STATE_BOUND))
-                nm_device_update_metered(self);
-            else
-                dhcp4_fail(self, state);
-        }
-        break;
-    case NM_DHCP_STATE_TIMEOUT:
-        dhcp4_fail(self, state);
-        break;
-    case NM_DHCP_STATE_EXPIRE:
-        /* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
-            break;
-        /* fall-through */
-    case NM_DHCP_STATE_DONE:
-    case NM_DHCP_STATE_FAIL:
-    case NM_DHCP_STATE_TERMINATED:
-        dhcp4_fail(self, state);
-        break;
-    default:
-        break;
-    }
-}
-
-static NMActStageReturn
-dhcp4_start(NMDevice *self)
-{
-    NMDevicePrivate *  priv = NM_DEVICE_GET_PRIVATE(self);
-    NMSettingIPConfig *s_ip4;
-    gs_unref_bytes GBytes *vendor_class_identifier = NULL;
-    gs_unref_bytes GBytes *hwaddr                  = NULL;
-    gs_unref_bytes GBytes *bcast_hwaddr            = NULL;
-    gs_unref_bytes GBytes *client_id               = NULL;
-    NMConnection *         connection;
-    NMSettingConnection *  s_con;
-    GError *               error = NULL;
-    const NMPlatformLink * pllink;
-    const char *const *    reject_servers;
-    gboolean               request_broadcast;
-    const char *           str;
-
-    connection = nm_device_get_applied_connection(self);
-    g_return_val_if_fail(connection, FALSE);
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-
-    s_con = nm_connection_get_setting_connection(connection);
-    nm_assert(s_con);
-
-    /* Clear old exported DHCP options */
-    nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
-    priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET);
-
-    request_broadcast = FALSE;
+    if (priv->ipdhcp_data_6.v6.mode == dhcp_level)
+        return;
 
-    pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
-    if (pllink) {
-        hwaddr       = nmp_link_address_get_as_bytes(&pllink->l_address);
-        bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
+    _LOGD_ipdhcp(AF_INET6, "level: set to %s", nm_ndisc_dhcp_level_to_string(dhcp_level));
 
-        str = nmp_object_link_udev_device_get_property_value(NMP_OBJECT_UP_CAST(pllink),
-                                                             "ID_NET_DHCP_BROADCAST");
-        if (str && _nm_utils_ascii_str_to_bool(str, FALSE)) {
-            /* Use the device property ID_NET_DHCP_BROADCAST setting, which may be set for interfaces
-             * requiring that the DHCPOFFER message is being broadcast because they can't handle unicast
-             * messages while not fully configured.
-             */
-            request_broadcast = TRUE;
-        }
+    if (dhcp_level == NM_NDISC_DHCP_LEVEL_NONE) {
+        _dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, TRUE);
+        return;
     }
 
-    client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr);
-    vendor_class_identifier =
-        _prop_get_ipv4_dhcp_vendor_class_identifier(self, NM_SETTING_IP4_CONFIG(s_ip4));
-    reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, NULL);
-
-    g_warn_if_fail(priv->dhcp_data_4.client == NULL);
-    priv->dhcp_data_4.client = nm_dhcp_manager_start_ip4(
-        nm_dhcp_manager_get(),
-        nm_netns_get_multi_idx(nm_device_get_netns(self)),
-        nm_device_get_ip_iface(self),
-        nm_device_get_ip_ifindex(self),
-        hwaddr,
-        bcast_hwaddr,
-        nm_connection_get_uuid(connection),
-        nm_device_get_route_table(self, AF_INET),
-        nm_device_get_route_metric(self, AF_INET),
-        request_broadcast ? NM_DHCP_CLIENT_FLAGS_REQUEST_BROADCAST : NM_DHCP_CLIENT_FLAGS_NONE,
-        nm_setting_ip_config_get_dhcp_send_hostname(s_ip4),
-        nm_setting_ip_config_get_dhcp_hostname(s_ip4),
-        nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
-        _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
-        _prop_get_connection_mud_url(self, s_con),
-        client_id,
-        _prop_get_ipvx_dhcp_timeout(self, AF_INET),
-        _device_get_dhcp_anycast_address(self),
-        NULL,
-        vendor_class_identifier,
-        reject_servers,
-        &error);
-    if (!priv->dhcp_data_4.client) {
-        _LOGW(LOGD_DHCP4, "failure to start DHCP: %s", error->message);
-        g_clear_error(&error);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
-
-    priv->dhcp_data_4.notify_sigid = g_signal_connect(priv->dhcp_data_4.client,
-                                                      NM_DHCP_CLIENT_NOTIFY,
-                                                      G_CALLBACK(dhcp4_notify),
-                                                      self);
-
-    if (nm_device_sys_iface_state_is_external_or_assume(self))
-        priv->dhcp_data_4.was_active = TRUE;
-
-    /* DHCP devices will be notified by the DHCP manager when stuff happens */
-    return NM_ACT_STAGE_RETURN_POSTPONE;
+    priv->ipdhcp_data_6.v6.mode = dhcp_level;
+    _dev_ipdhcpx_restart(self, AF_INET6, TRUE);
 }
 
-gboolean
-nm_device_dhcp4_renew(NMDevice *self, gboolean release)
+/*
+ * Called on the requesting interface when a subnet can't be obtained
+ * from known prefixes for a newly active shared connection.
+ */
+void
+nm_device_request_ip6_prefixes(NMDevice *self, guint needed_prefixes)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    g_return_val_if_fail(priv->dhcp_data_4.client != NULL, FALSE);
-
-    _LOGI(LOGD_DHCP4, "DHCPv4 lease renewal requested");
-
-    /* Terminate old DHCP instance and release the old lease */
-    dhcp4_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release);
-
-    /* Start DHCP again on the interface */
-    return dhcp4_start(self) != NM_ACT_STAGE_RETURN_FAILURE;
-}
-
-/*****************************************************************************/
+    if (priv->ipdhcp_data_6.v6.needed_prefixes == needed_prefixes)
+        return;
 
-static NMIP4Config *
-shared4_new_config(NMDevice *self, NMConnection *connection)
-{
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
-    NMIP4Config *        config;
-    NMSettingIPConfig *  s_ip4;
-    NMPlatformIP4Address address = {
-        .addr_source = NM_IP_CONFIG_SOURCE_SHARED,
-    };
+    _LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %u prefixes", needed_prefixes);
 
-    g_return_val_if_fail(self, NULL);
-    g_return_val_if_fail(connection, NULL);
+    priv->ipdhcp_data_6.v6.needed_prefixes = needed_prefixes;
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    if (s_ip4 && nm_setting_ip_config_get_num_addresses(s_ip4) > 0) {
-        /* Use the first user-supplied address */
-        NMIPAddress *user = nm_setting_ip_config_get_address(s_ip4, 0);
-        in_addr_t    a;
-
-        nm_ip_address_get_address_binary(user, &a);
-        nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user));
-        nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release);
-    } else {
-        if (!priv->shared_ip_handle)
-            priv->shared_ip_handle = nm_netns_shared_ip_reserve(nm_device_get_netns(self));
-        nm_platform_ip4_address_set_addr(&address, priv->shared_ip_handle->addr, 24);
+    if (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_NONE) {
+        priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_OTHERCONF;
+        _LOGD_ipdhcp(AF_INET6,
+                     "level: set to %s",
+                     nm_ndisc_dhcp_level_to_string(NM_NDISC_DHCP_LEVEL_OTHERCONF));
     }
 
-    config = nm_device_ip4_config_new(self);
-    nm_ip4_config_add_address(config, &address);
-
-    return config;
+    _dev_ipdhcpx_restart(self, AF_INET6, TRUE);
 }
 
 /*****************************************************************************/
@@ -9710,7 +10303,7 @@ shared4_new_config(NMDevice *self, NMConnection *connection)
 static gboolean
 connection_ip_method_requires_carrier(NMConnection *connection,
                                       int           addr_family,
-                                      gboolean *    out_ip_enabled)
+                                      gboolean     *out_ip_enabled)
 {
     const char *method;
 
@@ -9737,7 +10330,7 @@ connection_ip_method_requires_carrier(NMConnection *connection,
 static gboolean
 connection_requires_carrier(NMConnection *connection)
 {
-    NMSettingIPConfig *  s_ip4, *s_ip6;
+    NMSettingIPConfig   *s_ip4, *s_ip6;
     NMSettingConnection *s_con;
     gboolean             ip4_carrier_wanted, ip6_carrier_wanted;
     gboolean             ip4_used = FALSE, ip6_used = FALSE;
@@ -9784,8 +10377,8 @@ static gboolean
 have_any_ready_slaves(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    SlaveInfo *      info;
-    CList *          iter;
+    SlaveInfo       *info;
+    CList           *iter;
 
     /* Any enslaved slave is "ready" in the generic case as it's
      * at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2
@@ -9800,392 +10393,6 @@ have_any_ready_slaves(NMDevice *self)
 }
 
 /*****************************************************************************/
-/* DHCPv6 stuff */
-
-static void
-dhcp6_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    priv->dhcp_data_6.was_active = FALSE;
-    priv->dhcp6.mode             = NM_NDISC_DHCP_LEVEL_NONE;
-    applied_config_clear(&priv->dhcp6.ip6_config);
-    nm_clear_g_free(&priv->dhcp6.event_id);
-    nm_clear_g_source(&priv->dhcp_data_6.grace_id);
-    priv->dhcp_data_6.grace_pending = FALSE;
-
-    if (priv->dhcp_data_6.client) {
-        nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp_data_6.notify_sigid);
-
-        if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED)
-            nm_dhcp_client_stop(priv->dhcp_data_6.client, release);
-
-        g_clear_object(&priv->dhcp_data_6.client);
-    }
-
-    if (priv->dhcp_data_6.config) {
-        nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
-        _notify(self, PROP_DHCP6_CONFIG);
-    }
-}
-
-static gboolean
-dhcp6_lease_change(NMDevice *self)
-{
-    NMDevicePrivate *     priv = NM_DEVICE_GET_PRIVATE(self);
-    NMSettingsConnection *settings_connection;
-
-    if (!applied_config_get_current(&priv->dhcp6.ip6_config)) {
-        _LOGW(LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
-        return FALSE;
-    }
-
-    g_assert(priv->dhcp_data_6.client); /* sanity check */
-
-    settings_connection = nm_device_get_settings_connection(self);
-    g_assert(settings_connection);
-
-    /* Apply the updated config */
-    if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) {
-        _LOGW(LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event");
-        return FALSE;
-    }
-
-    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_6, self, NULL, NULL, NULL, NULL);
-
-    return TRUE;
-}
-
-static void
-dhcp6_fail(NMDevice *self, NMDhcpState dhcp_state)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    gboolean         is_dhcp_managed;
-
-    _LOGD(LOGD_DHCP6,
-          "DHCPv6 failed (ip_state %s, was_active %d)",
-          nm_device_ip_state_to_string(priv->ip_state_6),
-          priv->dhcp_data_6.was_active);
-
-    /* The client is always left running after a failure. */
-
-    /* Nothing to do if we failed before... */
-    if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
-        goto clear_config;
-
-    is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
-
-    if (is_dhcp_managed) {
-        /* ... and also if there are static addresses configured
-         * on the interface.
-         */
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_6
-            && nm_ip6_config_get_num_addresses(priv->con_ip_config_6))
-            goto clear_config;
-
-        /* Fail the method when one of the following is true:
-         * 1) the DHCP client terminated: it does not make sense to start a grace
-         *    period without a client running;
-         * 2) we failed to get an initial lease AND the client was
-         *    not active before.
-         */
-        if (dhcp_state == NM_DHCP_STATE_TERMINATED
-            || (!priv->dhcp_data_6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
-            nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
-            return;
-        }
-
-        if (dhcp_grace_period_start(self, AF_INET6))
-            goto clear_config;
-    } else {
-        /* not a hard failure; just live with the RA info */
-        dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
-            nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-    }
-    return;
-
-clear_config:
-    /* The previous configuration is no longer valid */
-    if (priv->dhcp_data_6.config) {
-        nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
-        priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6);
-        _notify(self, PROP_DHCP6_CONFIG);
-    }
-}
-
-static void
-dhcp6_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
-{
-    NMDevicePrivate *priv     = NM_DEVICE_GET_PRIVATE(self);
-    gs_free char *   event_id = NULL;
-    NMDhcpState      state;
-    NMIP6Config *    ip6_config;
-    GHashTable *     options;
-
-    nm_assert(nm_dhcp_client_get_addr_family(client) == AF_INET6);
-    nm_assert(notify_data);
-
-    if (notify_data->notify_type == NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED) {
-        /* Just re-emit. The device just contributes the prefix to the
-         * pool in NMPolicy, which decides about subnet allocation
-         * on the shared devices. */
-        g_signal_emit(self, signals[IP6_PREFIX_DELEGATED], 0, notify_data->prefix_delegated.prefix);
-        return;
-    }
-
-    nm_assert(notify_data->notify_type == NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED);
-
-    state      = notify_data->state_changed.dhcp_state;
-    ip6_config = NM_IP6_CONFIG(notify_data->state_changed.ip_config);
-    options    = notify_data->state_changed.options;
-
-    nm_assert(!ip6_config || NM_IS_IP6_CONFIG(ip6_config));
-
-    _LOGD(LOGD_DHCP6, "new DHCPv6 client state %d", (int) state);
-
-    switch (state) {
-    case NM_DHCP_STATE_BOUND:
-    case NM_DHCP_STATE_EXTENDED:
-        nm_clear_g_source(&priv->dhcp_data_6.grace_id);
-        priv->dhcp_data_6.grace_pending = FALSE;
-        /* If the server sends multiple IPv6 addresses, we receive a state
-         * changed event for each of them. Use the event ID to merge IPv6
-         * addresses from the same transaction into a single configuration.
-         */
-
-        event_id = nm_dhcp_utils_get_dhcp6_event_id(options);
-
-        if (ip6_config && event_id && priv->dhcp6.event_id
-            && nm_streq(event_id, priv->dhcp6.event_id)) {
-            NMDedupMultiIter            ipconf_iter;
-            const NMPlatformIP6Address *a;
-
-            nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
-                applied_config_add_address(&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(a));
-        } else {
-            nm_clear_g_free(&priv->dhcp6.event_id);
-            if (ip6_config) {
-                applied_config_init(&priv->dhcp6.ip6_config, ip6_config);
-                priv->dhcp6.event_id = g_strdup(event_id);
-                nm_dhcp_config_set_options(priv->dhcp_data_6.config, options);
-                _notify(self, PROP_DHCP6_CONFIG);
-            } else
-                applied_config_clear(&priv->dhcp6.ip6_config);
-        }
-
-        /* After long time we have been able to renew the lease:
-         * update the ip state
-         */
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
-            _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
-
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
-            if (!applied_config_get_current(&priv->dhcp6.ip6_config)) {
-                nm_device_ip_method_failed(self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
-                break;
-            }
-            nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-        } else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
-            if (!dhcp6_lease_change(self))
-                dhcp6_fail(self, state);
-        break;
-    case NM_DHCP_STATE_TIMEOUT:
-        if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
-            dhcp6_fail(self, state);
-        else {
-            /* not a hard failure; just live with the RA info */
-            dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE);
-            if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
-                nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-        }
-        break;
-    case NM_DHCP_STATE_EXPIRE:
-        /* Ignore expiry before we even have a lease (NAK, old lease, etc) */
-        if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF)
-            dhcp6_fail(self, state);
-        break;
-    case NM_DHCP_STATE_TERMINATED:
-        /* In IPv6 info-only mode, the client doesn't handle leases so it
-         * may exit right after getting a response from the server.  That's
-         * normal.  In that case we just ignore the exit.
-         */
-        if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF)
-            break;
-        /* fall-through */
-    case NM_DHCP_STATE_DONE:
-    case NM_DHCP_STATE_FAIL:
-        dhcp6_fail(self, state);
-        break;
-    default:
-        break;
-    }
-}
-
-/*****************************************************************************/
-
-static gboolean
-dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
-{
-    NMDevicePrivate *  priv = NM_DEVICE_GET_PRIVATE(self);
-    NMSettingIPConfig *s_ip6;
-    gs_unref_bytes GBytes *hwaddr            = NULL;
-    gs_unref_bytes GBytes *     duid         = NULL;
-    gboolean                    enforce_duid = FALSE;
-    const NMPlatformLink *      pllink;
-    GError *                    error = NULL;
-    guint32                     iaid;
-    gboolean                    iaid_explicit;
-    NMSettingConnection *       s_con;
-    const NMPlatformIP6Address *ll_addr = NULL;
-    int                         ip_ifindex;
-
-    g_return_val_if_fail(connection, FALSE);
-
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    nm_assert(s_ip6);
-    s_con = nm_connection_get_setting_connection(connection);
-    nm_assert(s_con);
-
-    if (priv->ext_ip6_config_captured) {
-        ll_addr = nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
-                                                  NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                      | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
-    }
-
-    if (!ll_addr) {
-        _LOGW(LOGD_DHCP6, "can't start DHCPv6: no link-local address");
-        return FALSE;
-    }
-
-    ip_ifindex = nm_device_get_ip_ifindex(self);
-    if (ip_ifindex <= 0) {
-        _LOGD(LOGD_DHCP6, "can't start DHCPv6: interface is gone");
-        return FALSE;
-    }
-
-    pllink = nm_platform_link_get(nm_device_get_platform(self), ip_ifindex);
-    if (pllink)
-        hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
-
-    iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, TRUE, &iaid_explicit);
-    duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
-
-    priv->dhcp_data_6.client = nm_dhcp_manager_start_ip6(
-        nm_dhcp_manager_get(),
-        nm_device_get_multi_index(self),
-        nm_device_get_ip_iface(self),
-        ip_ifindex,
-        &ll_addr->address,
-        nm_connection_get_uuid(connection),
-        nm_device_get_route_table(self, AF_INET6),
-        nm_device_get_route_metric(self, AF_INET6),
-        (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? NM_DHCP_CLIENT_FLAGS_INFO_ONLY
-                                                            : NM_DHCP_CLIENT_FLAGS_NONE,
-        nm_setting_ip_config_get_dhcp_send_hostname(s_ip6),
-        nm_setting_ip_config_get_dhcp_hostname(s_ip6),
-        _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
-        _prop_get_connection_mud_url(self, s_con),
-        duid,
-        enforce_duid,
-        iaid,
-        iaid_explicit,
-        _prop_get_ipvx_dhcp_timeout(self, AF_INET6),
-        _device_get_dhcp_anycast_address(self),
-        nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)),
-        priv->dhcp6.needed_prefixes,
-        &error);
-    if (!priv->dhcp_data_6.client) {
-        _LOGW(LOGD_DHCP6, "failure to start DHCPv6: %s", error->message);
-        g_clear_error(&error);
-        if (nm_device_sys_iface_state_is_external_or_assume(self))
-            priv->dhcp_data_6.was_active = TRUE;
-        return FALSE;
-    }
-
-    priv->dhcp_data_6.notify_sigid = g_signal_connect(priv->dhcp_data_6.client,
-                                                      NM_DHCP_CLIENT_NOTIFY,
-                                                      G_CALLBACK(dhcp6_notify),
-                                                      self);
-
-    if (nm_device_sys_iface_state_is_external_or_assume(self))
-        priv->dhcp_data_6.was_active = TRUE;
-
-    return TRUE;
-}
-
-static gboolean
-dhcp6_start(NMDevice *self, gboolean wait_for_ll)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
-
-    nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
-    priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6);
-
-    nm_assert(!applied_config_get_current(&priv->dhcp6.ip6_config));
-    applied_config_clear(&priv->dhcp6.ip6_config);
-    nm_clear_g_free(&priv->dhcp6.event_id);
-
-    connection = nm_device_get_applied_connection(self);
-    g_return_val_if_fail(connection, FALSE);
-
-    if (wait_for_ll) {
-        /* ensure link local is ready... */
-        if (!linklocal6_start(self)) {
-            /* wait for the LL address to show up */
-            return TRUE;
-        }
-        /* already have the LL address; kick off DHCP */
-    }
-
-    if (!dhcp6_start_with_link_ready(self, connection))
-        return FALSE;
-
-    return TRUE;
-}
-
-gboolean
-nm_device_dhcp6_renew(NMDevice *self, gboolean release)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMNDiscDHCPLevel mode;
-
-    g_return_val_if_fail(priv->dhcp_data_6.client != NULL, FALSE);
-
-    _LOGI(LOGD_DHCP6, "DHCPv6 lease renewal requested");
-
-    /* Terminate old DHCP instance and release the old lease */
-    mode = priv->dhcp6.mode;
-    dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release);
-    priv->dhcp6.mode = mode;
-
-    /* Start DHCP again on the interface */
-    return dhcp6_start(self, FALSE);
-}
-
-/*****************************************************************************/
-
-/*
- * Called on the requesting interface when a subnet can't be obtained
- * from known prefixes for a newly active shared connection.
- */
-void
-nm_device_request_ip6_prefixes(NMDevice *self, int needed_prefixes)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    priv->dhcp6.needed_prefixes = needed_prefixes;
-
-    if (priv->dhcp_data_6.client) {
-        _LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %d prefixes", needed_prefixes);
-        nm_device_dhcp6_renew(self, FALSE);
-    } else {
-        priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_OTHERCONF;
-        _LOGD(LOGD_DEVICE | LOGD_DHCP6, "ipv6-pd: starting DHCPv6 to request a prefix");
-        dhcp6_start(self, FALSE);
-    }
-}
 
 gboolean
 nm_device_needs_ip6_subnet(NMDevice *self)
@@ -10200,25 +10407,25 @@ nm_device_needs_ip6_subnet(NMDevice *self)
 void
 nm_device_use_ip6_subnet(NMDevice *self, const NMPlatformIP6Address *subnet)
 {
-    NMDevicePrivate *    priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatformIP6Address address = *subnet;
-    char                 sbuf[NM_UTILS_INET_ADDRSTRLEN];
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+    char                                    sbuf[sizeof(_nm_utils_to_string_buffer)];
+    NMPlatformIP6Address                    address;
 
-    if (!applied_config_get_current(&priv->ac_ip6_config))
-        applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
+    l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
 
     /* Assign a ::1 address in the subnet for us. */
+    address = *subnet;
     address.address.s6_addr32[3] |= htonl(1);
-    applied_config_add_address(&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(&address));
+
+    nm_l3_config_data_add_address_6(l3cd, &address);
 
     _LOGD(LOGD_IP6,
-          "ipv6-pd: using %s address (preferred for %u seconds)",
-          _nm_utils_inet6_ntop(&address.address, sbuf),
-          subnet->preferred);
+          "ipv6-pd: using %s",
+          nm_platform_ip6_address_to_string(&address, sbuf, sizeof(sbuf)));
 
-    /* This also updates the ndisc if there are actual changes. */
-    if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-        _LOGW(LOGD_IP6, "ipv6-pd: failed applying IP6 config for connection sharing");
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_PD_6, l3cd, FALSE);
+    _dev_l3_cfg_commit(self, TRUE);
+    _dev_ipac6_ndisc_set_router_config(self);
 }
 
 /*
@@ -10228,217 +10435,231 @@ nm_device_use_ip6_subnet(NMDevice *self, const NMPlatformIP6Address *subnet)
 void
 nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device)
 {
-    NMDevicePrivate *priv        = NM_DEVICE_GET_PRIVATE(self);
-    NMIP6Config *    from_config = NULL;
-    guint            i, len;
+    NMDevicePrivate                        *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate                        *priv_src;
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd     = NULL;
+    const NML3ConfigData                   *l3cd_src = NULL;
 
-    if (applied_config_get_current(&priv->ac_ip6_config)) {
-        applied_config_reset_nameservers(&priv->ac_ip6_config);
-        applied_config_reset_searches(&priv->ac_ip6_config);
-    } else
-        applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
+    /* FIXME(l3cfg): this entire code an approach seems flawed. It's flawed, because the
+     *   very next RA will reset the changes. */
 
-    if (from_device)
-        from_config = nm_device_get_ip6_config(from_device);
-    if (!from_config)
-        return;
+    if (priv->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d) {
+        l3cd = nm_l3_config_data_new_clone(priv->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d, 0);
+        nm_l3_config_data_clear_nameservers(l3cd, AF_INET6);
+        nm_l3_config_data_clear_searches(l3cd, AF_INET6);
+    } else
+        l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
 
-    len = nm_ip6_config_get_num_nameservers(from_config);
-    for (i = 0; i < len; i++) {
-        applied_config_add_nameserver(
-            &priv->ac_ip6_config,
-            (const NMIPAddr *) nm_ip6_config_get_nameserver(from_config, i));
+    if (from_device) {
+        priv_src = NM_DEVICE_GET_PRIVATE(from_device);
+        l3cd_src = priv_src->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d;
     }
+    if (l3cd_src) {
+        const char *const            *strvarr;
+        const struct in6_addr *const *addrs;
+        guint                         n;
+        guint                         i;
+
+        addrs = nm_l3_config_data_get_nameservers(l3cd_src, AF_INET6, &n);
+        for (i = 0; i < n; i++)
+            nm_l3_config_data_add_nameserver(l3cd, AF_INET6, addrs[i]);
 
-    len = nm_ip6_config_get_num_searches(from_config);
-    for (i = 0; i < len; i++) {
-        applied_config_add_search(&priv->ac_ip6_config, nm_ip6_config_get_search(from_config, i));
+        strvarr = nm_l3_config_data_get_searches(l3cd_src, AF_INET6, &n);
+        for (i = 0; i < n; i++)
+            nm_l3_config_data_add_search(l3cd, AF_INET6, strvarr[i]);
     }
 
-    if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-        _LOGW(LOGD_IP6, "ipv6-pd: failed applying DNS config for connection sharing");
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE);
+
+    _dev_l3_cfg_commit(self, TRUE);
 }
 
 /*****************************************************************************/
 
-static void
-linklocal6_failed(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    nm_clear_g_source(&priv->linklocal6_timeout_id);
-    nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
-}
-
 static gboolean
-linklocal6_timeout_cb(gpointer user_data)
+_dev_ipll6_state_retry_cb(gpointer user_data)
 {
-    NMDevice *self = user_data;
+    NMDevice        *self = user_data;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    _LOGD(LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout");
-    linklocal6_failed(self);
-    return G_SOURCE_REMOVE;
+    nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
+    _dev_ipll6_start(self);
+    return G_SOURCE_CONTINUE;
 }
 
 static void
-linklocal6_check_complete(NMDevice *self)
+_dev_ipll6_set_llstate(NMDevice *self, NML3IPv6LLState llstate, const struct in6_addr *lladdr)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
-    const char *     method;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    gboolean         changed = FALSE;
+    NMDeviceIPState  state;
+    NMDeviceIPState  old_state;
+
+    if (!lladdr)
+        lladdr = &nm_ip_addr_zero.addr6;
+
+    if (priv->ipll_data_6.v6.llstate != llstate
+        || !IN6_ARE_ADDR_EQUAL(&priv->ipll_data_6.v6.lladdr, lladdr)) {
+        changed                      = TRUE;
+        priv->ipll_data_6.v6.llstate = llstate;
+        priv->ipll_data_6.v6.lladdr  = *lladdr;
+    }
+
+    nm_assert((priv->ipll_data_6.v6.ipv6ll
+               && NM_IN_SET(priv->ipll_data_6.v6.llstate,
+                            NM_L3_IPV6LL_STATE_STARTING,
+                            NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
+                            NM_L3_IPV6LL_STATE_READY,
+                            NM_L3_IPV6LL_STATE_DAD_FAILED))
+              || (!priv->ipll_data_6.v6.ipv6ll
+                  && NM_IN_SET(priv->ipll_data_6.v6.llstate,
+                               NM_L3_IPV6LL_STATE_NONE,
+                               NM_L3_IPV6LL_STATE_DEFUNCT)));
+
+    switch (priv->ipll_data_6.v6.llstate) {
+    case NM_L3_IPV6LL_STATE_NONE:
+        state = NM_DEVICE_IP_STATE_NONE;
+        break;
+    case NM_L3_IPV6LL_STATE_DEFUNCT:
+    case NM_L3_IPV6LL_STATE_DAD_FAILED:
+        state = NM_DEVICE_IP_STATE_FAILED;
+        break;
+    case NM_L3_IPV6LL_STATE_READY:
+        state = NM_DEVICE_IP_STATE_READY;
+        break;
+    case NM_L3_IPV6LL_STATE_STARTING:
+    case NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS:
+        state = NM_DEVICE_IP_STATE_PENDING;
+        break;
+    default:
+        state = nm_assert_unreachable_val(NM_DEVICE_IP_STATE_FAILED);
+        break;
+    }
 
-    if (!priv->linklocal6_timeout_id) {
-        /* we are not waiting for linklocal to complete. Nothing to do. */
-        return;
+    old_state = priv->ipll_data_6.state;
+    if (priv->ipll_data_6.state != state) {
+        priv->ipll_data_6.state = state;
+        changed                 = TRUE;
     }
 
-    if (!priv->ext_ip6_config_captured
-        || !nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
-                                            NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
-        /* we don't have a non-tentative link local address yet. Wait longer. */
-        return;
+    if (priv->ipll_data_6.v6.llstate != NM_L3_IPV6LL_STATE_DEFUNCT)
+        nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
+    else if (!priv->ipll_data_6.v6.retry_source) {
+        /* we schedule a timer to try to recover from this... Possibly some higher layer
+         * will however fail the activation... */
+        priv->ipll_data_6.v6.retry_source =
+            nm_g_timeout_add_source(10000, _dev_ipll6_state_retry_cb, self);
     }
 
-    nm_clear_g_source(&priv->linklocal6_timeout_id);
+    if (changed) {
+        char sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
-    connection = nm_device_get_applied_connection(self);
-    g_assert(connection);
+        _LOGT_ipll(AF_INET6,
+                   "set state %s (was %s, llstate=%s, lladdr=%s)",
+                   nm_device_ip_state_to_string(priv->ipll_data_6.state),
+                   nm_device_ip_state_to_string(old_state),
+                   nm_l3_ipv6ll_state_to_string(priv->ipll_data_6.v6.llstate),
+                   nm_ip_addr_is_null(AF_INET6, &priv->ipll_data_6.v6.lladdr)
+                       ? "(none)"
+                       : _nm_utils_inet6_ntop(&priv->ipll_data_6.v6.lladdr, sbuf));
+    }
 
-    method = nm_device_get_effective_ip_config_method(self, AF_INET6);
+    if (changed)
+        _dev_ip_state_check_async(self, AF_INET6);
 
-    _LOGD(LOGD_DEVICE,
-          "linklocal6: waiting for link-local addresses successful, continue with method %s",
-          method);
-
-    if (NM_IN_STRSET(method,
-                     NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-                     NM_SETTING_IP6_CONFIG_METHOD_SHARED))
-        addrconf6_start_with_link_ready(self);
-    else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
-        if (!dhcp6_start_with_link_ready(self, connection)) {
-            /* Time out IPv6 instead of failing the entire activation */
-            nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
-        }
-    } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
-        nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-    else
-        g_return_if_fail(FALSE);
+    if (priv->ipll_data_6.v6.llstate == NM_L3_IPV6LL_STATE_READY) {
+        /* if we got an IPv6LL address, we might poke some other methods
+         * to progress... */
+        _dev_ipac6_start_continue(self);
+        _dev_ipdhcpx_start_continue(self, AF_INET6);
+    }
 }
 
 static void
-check_and_add_ipv6ll_addr(NMDevice *self)
+_dev_ipll6_state_change_cb(NML3IPv6LL            *ipv6ll,
+                           NML3IPv6LLState        llstate,
+                           const struct in6_addr *lladdr,
+                           gpointer               user_data)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
-    struct in6_addr     lladdr;
-    NMConnection *      connection;
-    NMSettingIP6Config *s_ip6 = NULL;
-    GError *            error = NULL;
-    const char *        addr_type;
-    char                sbuf[NM_UTILS_INET_ADDRSTRLEN];
+    _dev_ipll6_set_llstate(user_data, llstate, lladdr);
+}
 
-    if (!priv->ipv6ll_handle)
-        return;
+static void
+_dev_ipll6_start(NMDevice *self)
+{
+    NMDevicePrivate       *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection          *connection;
+    NMSettingIP6Config    *s_ip6 = NULL;
+    gboolean               assume;
+    const char            *ifname;
+    NML3IPv6LLState        llstate;
+    const struct in6_addr *lladdr;
 
-    if (priv->ext_ip6_config_captured
-        && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
-                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
-                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
-        /* Already have an LL address, nothing to do */
+    if (priv->ipll_data_6.v6.ipv6ll)
         return;
-    }
 
-    priv->ipv6ll_has = FALSE;
-    memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr));
+    if (!priv->l3cfg) {
+        _LOGD(LOGD_IP6, "linklocal6: no IP link for IPv6");
+        goto out_fail;
+    }
 
-    memset(&lladdr, 0, sizeof(lladdr));
-    lladdr.s6_addr16[0] = htons(0xfe80);
+    ifname = nm_device_get_ip_iface(self);
+    if (!ifname) {
+        _LOGD(LOGD_IP6, "linklocal6: no interface name for IPv6");
+        goto out_fail;
+    }
 
     connection = nm_device_get_applied_connection(self);
     if (connection)
         s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
 
+    assume = nm_device_sys_iface_state_is_external_or_assume(self);
+
     if (s_ip6
         && nm_setting_ip6_config_get_addr_gen_mode(s_ip6)
                == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
         NMUtilsStableType stable_type;
-        const char *      stable_id;
+        const char       *stable_id;
 
         stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
-        if (!nm_utils_ipv6_addr_set_stable_privacy_may_fail(stable_type,
-                                                            &lladdr,
-                                                            nm_device_get_iface(self),
-                                                            stable_id,
-                                                            priv->linklocal6_dad_counter++,
-                                                            &error)) {
-            _LOGW(LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message);
-            g_clear_error(&error);
-            linklocal6_failed(self);
-            return;
-        }
-        addr_type = "stable-privacy";
+        priv->ipll_data_6.v6.ipv6ll =
+            nm_l3_ipv6ll_new_stable_privacy(priv->l3cfg,
+                                            assume,
+                                            stable_type,
+                                            ifname,
+                                            stable_id,
+                                            nm_device_get_route_table(self, AF_INET6),
+                                            _dev_ipll6_state_change_cb,
+                                            self);
     } else {
         NMUtilsIPv6IfaceId iid;
 
-        if (priv->linklocal6_timeout_id) {
-            /* We already started and attempt to add a LL address. For the EUI-64
-             * mode we can't pick a new one, we'll just fail. */
-            _LOGW(LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address");
-            linklocal6_failed(self);
-            return;
-        }
-
-        if (!nm_device_get_ip_iface_identifier(self, &iid, TRUE)) {
+        if (!nm_device_get_ip_iface_identifier(self, &iid, TRUE, NULL)) {
             _LOGW(LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
-            return;
+            goto out_fail;
         }
-        nm_utils_ipv6_addr_set_interface_identifier(&lladdr, &iid);
-        addr_type = "EUI-64";
-    }
-
-    _LOGD(LOGD_IP6,
-          "linklocal6: generated %s IPv6LL address %s",
-          addr_type,
-          _nm_utils_inet6_ntop(&lladdr, sbuf));
-    priv->ipv6ll_has  = TRUE;
-    priv->ipv6ll_addr = lladdr;
-    ip_config_merge_and_apply(self, AF_INET6, TRUE);
-}
-
-static gboolean
-linklocal6_start(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    nm_clear_g_source(&priv->linklocal6_timeout_id);
-
-    if (priv->ext_ip6_config_captured
-        && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ext_ip6_config_captured),
-                                           NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                               | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
-        return TRUE;
-
-    _LOGD(LOGD_DEVICE,
-          "linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses "
-          "configured. Wait.",
-          nm_device_get_effective_ip_config_method(self, AF_INET6));
+        priv->ipll_data_6.v6.ipv6ll =
+            nm_l3_ipv6ll_new_token(priv->l3cfg,
+                                   assume,
+                                   &iid,
+                                   nm_device_get_route_table(self, AF_INET6),
+                                   _dev_ipll6_state_change_cb,
+                                   self);
+    }
 
-    check_and_add_ipv6ll_addr(self);
+    llstate = nm_l3_ipv6ll_get_state(priv->ipll_data_6.v6.ipv6ll, &lladdr);
+    _dev_ipll6_set_llstate(self, llstate, lladdr);
+    return;
 
-    /* Depending on the network and what the 'dad_transmits' and 'retrans_time_ms'
-     * sysctl values are, DAD for the IPv6LL address may take quite a while.
-     * FIXME: use dad/retrans sysctl values if they are higher than a minimum time.
-     * (rh #1101809)
-     */
-    priv->linklocal6_timeout_id = g_timeout_add_seconds(15, linklocal6_timeout_cb, self);
-    return FALSE;
+out_fail:
+    _dev_ipll6_set_llstate(self, NM_L3_IPV6LL_STATE_DEFUNCT, NULL);
 }
 
 /*****************************************************************************/
 
 gint64
-nm_device_get_configured_mtu_from_connection_default(NMDevice *  self,
+nm_device_get_configured_mtu_from_connection_default(NMDevice   *self,
                                                      const char *property_name,
                                                      guint32     max_mtu)
 {
@@ -10451,13 +10672,13 @@ nm_device_get_configured_mtu_from_connection_default(NMDevice *  self,
 }
 
 guint32
-nm_device_get_configured_mtu_from_connection(NMDevice *         self,
+nm_device_get_configured_mtu_from_connection(NMDevice          *self,
                                              GType              setting_type,
                                              NMDeviceMtuSource *out_source)
 {
-    const char *  global_property_name;
+    const char   *global_property_name;
     NMConnection *connection;
-    NMSetting *   setting;
+    NMSetting    *setting;
     gint64        mtu_default;
     guint32       mtu     = 0;
     guint32       max_mtu = G_MAXUINT32;
@@ -10512,17 +10733,17 @@ nm_device_get_configured_mtu_from_connection(NMDevice *         self,
 }
 
 guint32
-nm_device_get_configured_mtu_for_wired(NMDevice *         self,
+nm_device_get_configured_mtu_for_wired(NMDevice          *self,
                                        NMDeviceMtuSource *out_source,
-                                       gboolean *         out_force)
+                                       gboolean          *out_force)
 {
     return nm_device_get_configured_mtu_from_connection(self, NM_TYPE_SETTING_WIRED, out_source);
 }
 
 guint32
-nm_device_get_configured_mtu_wired_parent(NMDevice *         self,
+nm_device_get_configured_mtu_wired_parent(NMDevice          *self,
                                           NMDeviceMtuSource *out_source,
-                                          gboolean *         out_force)
+                                          gboolean          *out_force)
 {
     guint32 mtu        = 0;
     guint32 parent_mtu = 0;
@@ -10595,13 +10816,16 @@ set_platform_mtu(NMDevice *self, guint32 mtu)
 }
 
 static void
-_commit_mtu(NMDevice *self, const NMIP4Config *config)
-{
-    NMDevicePrivate * priv   = NM_DEVICE_GET_PRIVATE(self);
-    NMDeviceMtuSource source = NM_DEVICE_MTU_SOURCE_NONE;
-    guint32           ip6_mtu, ip6_mtu_orig;
-    guint32           mtu_desired, mtu_desired_orig;
-    guint32           mtu_plat;
+_commit_mtu(NMDevice *self)
+{
+    NMDevicePrivate      *priv   = NM_DEVICE_GET_PRIVATE(self);
+    NMDeviceMtuSource     source = NM_DEVICE_MTU_SOURCE_NONE;
+    const NML3ConfigData *l3cd;
+    guint32               ip6_mtu_orig;
+    guint32               ip6_mtu;
+    guint32               mtu_desired_orig;
+    guint32               mtu_desired;
+    guint32               mtu_plat;
     struct {
         gboolean initialized;
         guint32  value;
@@ -10609,7 +10833,9 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
         0,
     };
     int      ifindex;
-    char     sbuf[64], sbuf1[64], sbuf2[64];
+    char     sbuf[64];
+    char     sbuf1[64];
+    char     sbuf2[64];
     gboolean success = TRUE;
 
     ifindex = nm_device_get_ip_ifindex(self);
@@ -10623,8 +10849,11 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
         return;
     }
 
+    l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, FALSE);
+
     {
-        guint32  mtu   = 0;
+        guint32  mtu = 0;
+        guint32  mtu2;
         gboolean force = FALSE;
 
         /* We take the MTU from various sources: (in order of increasing
@@ -10651,9 +10880,9 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
         if (NM_DEVICE_GET_CLASS(self)->get_configured_mtu)
             mtu = NM_DEVICE_GET_CLASS(self)->get_configured_mtu(self, &source, &force);
 
-        if (config && !force && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG
-            && nm_ip4_config_get_mtu(config)) {
-            mtu    = nm_ip4_config_get_mtu(config);
+        if (l3cd && !force && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG
+            && (mtu2 = nm_l3_config_data_get_mtu(l3cd)) > 0) {
+            mtu    = mtu2;
             source = NM_DEVICE_MTU_SOURCE_IP_CONFIG;
         }
 
@@ -10842,251 +11071,212 @@ nm_device_commit_mtu(NMDevice *self)
     state = nm_device_get_state(self);
     if (state >= NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
         _LOGT(LOGD_DEVICE, "mtu: commit-mtu...");
-        _commit_mtu(self, NM_DEVICE_GET_PRIVATE(self)->ip_config_4);
+        _commit_mtu(self);
     } else
         _LOGT(LOGD_DEVICE,
               "mtu: commit-mtu... skip due to state %s",
               nm_device_state_to_string(state));
 }
 
+/*****************************************************************************/
+
 static void
-ndisc_config_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, NMDevice *self)
+_dev_ipac6_ndisc_set_router_config(NMDevice *self)
 {
-    NMNDiscConfigMap changed = changed_int;
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    int              ifindex;
-    guint            i;
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
+    const NML3ConfigData *l3cd;
 
-    g_return_if_fail(priv->act_request.obj);
+    if (!priv->ipac6_data.ndisc)
+        return;
 
-    ifindex = nm_device_get_ip_ifindex(self);
+    if (nm_ndisc_get_node_type(priv->ipac6_data.ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
+        return;
 
-    if (ifindex <= 0
-        || (applied_config_get_current(&priv->ac_ip6_config)
-            && ifindex
-                   != nm_ip_config_get_ifindex(applied_config_get_current(&priv->ac_ip6_config))))
-        applied_config_clear(&priv->ac_ip6_config);
+    /* FIXME(l3cfg): this doesn't seem right. What is the meaning of the l3cd at this
+     * point? Also, when do we need to reset the config (and call this function again?). */
+    l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, FALSE);
+    if (l3cd)
+        nm_ndisc_set_config(priv->ipac6_data.ndisc, l3cd);
+}
 
-    if (ifindex <= 0)
-        return;
+static void
+_dev_ipac6_set_state(NMDevice *self, NMDeviceIPState state)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (!applied_config_get_current(&priv->ac_ip6_config))
-        applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
-
-    if (changed & NM_NDISC_CONFIG_ADDRESSES) {
-        guint32 ifa_flags;
-
-        /* Check, whether kernel is recent enough to help user space handling RA.
-         * If it's not supported, we have no ipv6-privacy and must add autoconf
-         * addresses as /128. The reason for the /128 is to prevent the kernel
-         * from adding a prefix route for this address. */
-        ifa_flags = IFA_F_NOPREFIXROUTE;
-        if (NM_IN_SET(priv->ndisc_use_tempaddr,
-                      NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
-                      NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
-            ifa_flags |= IFA_F_MANAGETEMPADDR;
-
-        nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.orig,
-                                            rdata->addresses,
-                                            rdata->addresses_n,
-                                            64,
-                                            ifa_flags);
-        if (priv->ac_ip6_config.current) {
-            nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.current,
-                                                rdata->addresses,
-                                                rdata->addresses_n,
-                                                64,
-                                                ifa_flags);
-        }
+    if (priv->ipac6_data.state != state) {
+        _LOGD_ipac6("set state: %s (was %s)",
+                    nm_device_ip_state_to_string(state),
+                    nm_device_ip_state_to_string(priv->ipac6_data.state));
+        priv->ipac6_data.state = state;
     }
+}
 
-    if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) {
-        nm_ip6_config_reset_routes_ndisc((NMIP6Config *) priv->ac_ip6_config.orig,
-                                         rdata->gateways,
-                                         rdata->gateways_n,
-                                         rdata->routes,
-                                         rdata->routes_n,
-                                         nm_device_get_route_table(self, AF_INET6),
-                                         nm_device_get_route_metric(self, AF_INET6));
-        if (priv->ac_ip6_config.current) {
-            nm_ip6_config_reset_routes_ndisc((NMIP6Config *) priv->ac_ip6_config.current,
-                                             rdata->gateways,
-                                             rdata->gateways_n,
-                                             rdata->routes,
-                                             rdata->routes_n,
-                                             nm_device_get_route_table(self, AF_INET6),
-                                             nm_device_get_route_metric(self, AF_INET6));
+static void
+_dev_ipac6_ndisc_config_changed(NMNDisc              *ndisc,
+                                const NMNDiscData    *rdata,
+                                guint                 changed_i,
+                                const NML3ConfigData *l3cd,
+                                NMDevice             *self)
+{
+    NMDevicePrivate *priv  = NM_DEVICE_GET_PRIVATE(self);
+    gboolean         ready = TRUE;
+    NMDedupMultiIter iter;
+    const NMPObject *obj;
+
+    _dev_ipac6_grace_period_start(self, 0, TRUE);
+
+    _dev_l3_register_l3cds_set_one_full(self,
+                                        L3_CONFIG_DATA_TYPE_AC_6,
+                                        l3cd,
+                                        NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
+                                        FALSE);
+
+    nm_clear_l3cd(&priv->ipac6_data.l3cd);
+
+    /* wait that addresses are committed to platform and
+     * become non-tentative before declaring AC6 is ready.*/
+    nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP6_ADDRESS) {
+        const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
+        const NMPlatformIP6Address *plat_addr;
+
+        plat_addr = nm_platform_ip6_address_get(nm_device_get_platform(self),
+                                                nm_device_get_ip_ifindex(self),
+                                                &addr->address);
+        if (!plat_addr || (plat_addr->n_ifa_flags & IFA_F_TENTATIVE)) {
+            ready = FALSE;
+            break;
         }
     }
 
-    if (changed & NM_NDISC_CONFIG_DNS_SERVERS) {
-        /* Rebuild DNS server list from neighbor discovery cache. */
-        applied_config_reset_nameservers(&priv->ac_ip6_config);
-
-        for (i = 0; i < rdata->dns_servers_n; i++)
-            applied_config_add_nameserver(&priv->ac_ip6_config,
-                                          (const NMIPAddr *) &rdata->dns_servers[i].address);
+    if (ready) {
+        _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
+    } else {
+        priv->ipac6_data.l3cd = nm_l3_config_data_ref(l3cd);
     }
 
-    if (changed & NM_NDISC_CONFIG_DNS_DOMAINS) {
-        /* Rebuild domain list from neighbor discovery cache. */
-        applied_config_reset_searches(&priv->ac_ip6_config);
-
-        for (i = 0; i < rdata->dns_domains_n; i++)
-            applied_config_add_search(&priv->ac_ip6_config, rdata->dns_domains[i].domain);
-    }
+    _dev_ipdhcp6_set_dhcp_level(self, rdata->dhcp_level);
 
-    if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
-        dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, TRUE);
+    _dev_l3_cfg_commit(self, FALSE);
 
-        priv->dhcp6.mode = rdata->dhcp_level;
-        if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE) {
-            _LOGD(LOGD_DEVICE | LOGD_DHCP6,
-                  "Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6"
-                  " as requested by IPv6 router...");
-            if (!dhcp6_start(self, FALSE)) {
-                if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED) {
-                    nm_device_state_changed(self,
-                                            NM_DEVICE_STATE_FAILED,
-                                            NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-                    return;
-                }
-            }
-        }
-    }
+    _dev_ip_state_check_async(self, AF_INET6);
+}
 
-    if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
-        nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(nm_device_get_platform(self),
-                                                           nm_device_get_ip_iface(self),
-                                                           rdata->hop_limit);
+static void
+_dev_ipac6_handle_timeout(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (changed & NM_NDISC_CONFIG_REACHABLE_TIME) {
-        nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(nm_device_get_platform(self),
-                                                            nm_device_get_ip_iface(self),
-                                                            rdata->reachable_time_ms);
-    }
+    _LOGD_ipac6("timeout for autoconf (IPv6 router advertisement) reached");
 
-    if (changed & NM_NDISC_CONFIG_RETRANS_TIMER) {
-        nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(nm_device_get_platform(self),
-                                                          nm_device_get_ip_iface(self),
-                                                          rdata->retrans_timer_ms);
-    }
+    nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
 
-    if (changed & NM_NDISC_CONFIG_MTU) {
-        if (priv->ip6_mtu != rdata->mtu) {
-            _LOGD(LOGD_DEVICE, "mtu: set IPv6 MTU to %u", (guint) rdata->mtu);
-            priv->ip6_mtu = rdata->mtu;
-        }
-    }
+    _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_FAILED);
 
-    nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
+    _dev_ip_state_check_async(self, AF_INET6);
 }
 
 static void
-ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self)
+_dev_ipac6_ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self)
+{
+    _dev_ipac6_handle_timeout(self);
+}
+
+static gboolean
+_dev_ipac6_grace_period_expired(gpointer user_data)
+{
+    _dev_ipac6_handle_timeout(user_data);
+    return G_SOURCE_REMOVE;
+}
+
+static gboolean
+_dev_ipac6_grace_period_start(NMDevice *self, guint32 timeout_sec, gboolean force_restart)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    gboolean         stopped;
 
-    /* We don't want to stop listening for router advertisements completely,
-     * but instead let device activation continue activating.  If an RA
-     * shows up later, we'll use it as long as the device is not disconnected.
+    /* In any other case (expired lease, assumed connection, etc.),
+     * wait for some time before failing the IP method.
      */
-
-    _LOGD(LOGD_IP6, "timed out waiting for IPv6 router advertisement");
-    if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
-        /* If RA is our only source of addressing information and we don't
-         * ever receive one, then time out IPv6.  But if there is other
-         * IPv6 configuration, like manual IPv6 addresses or external IPv6
-         * config, consider that sufficient for IPv6 success.
-         *
-         * FIXME: it doesn't seem correct to determine this based on which
-         *        addresses we find inside priv->ip_config_6.
-         */
-        if (priv->ip_config_6
-            && nm_ip_config_find_first_address(NM_IP_CONFIG(priv->ip_config_6),
-                                               NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
-                                                   | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
-            nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-        else
-            nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
+    if (!force_restart && priv->ipac6_data.ndisc_grace_source) {
+        /* already pending. */
+        return FALSE;
     }
-}
 
-static void
-addrconf6_start_with_link_ready(NMDevice *self)
-{
-    NMDevicePrivate *  priv = NM_DEVICE_GET_PRIVATE(self);
-    NMUtilsIPv6IfaceId iid;
+    /* Start a grace period equal to the RA timeout multiplied
+     * by a constant factor. */
 
-    g_assert(priv->ndisc);
+    stopped = nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
 
-    if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) {
-        _LOGD(LOGD_IP6, "addrconf6: using the device EUI-64 identifier");
-        nm_ndisc_set_iid(priv->ndisc, iid);
-    } else {
-        /* Don't abort the addrconf at this point -- if ndisc needs the iid
-         * it will notice this itself. */
-        _LOGI(LOGD_IP6, "addrconf6: no interface identifier; IPv6 address creation may fail");
+    if (timeout_sec == 0) {
+        if (stopped)
+            _LOGD_ipac6("grace period stopped");
+        return FALSE;
     }
 
-    /* Apply any manual configuration before starting RA */
-    if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-        _LOGW(LOGD_IP6, "failed to apply manual IPv6 configuration");
+    nm_assert(timeout_sec <= G_MAXINT32);
 
-    if (nm_ndisc_get_node_type(priv->ndisc) == NM_NDISC_NODE_TYPE_ROUTER) {
-        nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "1");
-        nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-        priv->needs_ip6_subnet = TRUE;
-        g_signal_emit(self, signals[IP6_SUBNET_NEEDED], 0);
-    }
+    if (timeout_sec >= G_MAXUINT / (GRACE_PERIOD_MULTIPLIER * 1000u))
+        timeout_sec = NM_RA_TIMEOUT_INFINITY;
 
-    priv->ndisc_changed_id = g_signal_connect(priv->ndisc,
-                                              NM_NDISC_CONFIG_RECEIVED,
-                                              G_CALLBACK(ndisc_config_changed),
-                                              self);
-    priv->ndisc_timeout_id = g_signal_connect(priv->ndisc,
-                                              NM_NDISC_RA_TIMEOUT_SIGNAL,
-                                              G_CALLBACK(ndisc_ra_timeout),
-                                              self);
+    if (timeout_sec == NM_RA_TIMEOUT_INFINITY) {
+        _LOGD_ipac6("grace period starts with infinity timeout");
+        priv->ipac6_data.ndisc_grace_source = g_source_ref(nm_g_source_sentinel_get(0));
+    } else {
+        _LOGD_ipac6("grace period starts with %u seconds", timeout_sec);
+        priv->ipac6_data.ndisc_grace_source =
+            nm_g_timeout_add_source(timeout_sec * (GRACE_PERIOD_MULTIPLIER * 1000u),
+                                    _dev_ipac6_grace_period_expired,
+                                    self);
+    }
 
-    ndisc_set_router_config(priv->ndisc, self);
-    nm_ndisc_start(priv->ndisc);
-    priv->ndisc_started = TRUE;
-    return;
+    return TRUE;
 }
 
-static gboolean
-addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
+static void
+_dev_ipac6_start(NMDevice *self)
 {
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *      connection;
-    NMSettingIP6Config *s_ip6 = NULL;
-    GError *            error = NULL;
-    NMUtilsStableType   stable_type;
-    const char *        stable_id;
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection       *connection;
+    NMSettingIP6Config *s_ip = NULL;
     NMNDiscNodeType     node_type;
+    NMUtilsStableType   stable_type;
+    const char         *stable_id;
     int                 max_addresses;
     int                 router_solicitations;
     int                 router_solicitation_interval;
     guint32             ra_timeout;
     guint32             default_ra_timeout;
+    NMUtilsIPv6IfaceId  iid;
+    gboolean            is_token;
+
+    if (priv->ipac6_data.state == NM_DEVICE_IP_STATE_NONE) {
+        if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
+            _LOGI_ipac6("addrconf6: kernel does not support IPv6");
+            _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_FAILED);
+            _dev_ip_state_check_async(self, AF_INET6);
+            return;
+        }
 
-    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
-        _LOGI(LOGD_IP6, "addrconf6: kernel does not support IPv6");
-        return FALSE;
+        _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_PENDING);
     }
 
-    connection = nm_device_get_applied_connection(self);
-    g_assert(connection);
+    if (NM_IN_SET(priv->ipll_data_6.state, NM_DEVICE_IP_STATE_NONE, NM_DEVICE_IP_STATE_PENDING)) {
+        _dev_ipac6_grace_period_start(self, 30, TRUE);
+        _dev_ipll6_start(self);
+        return;
+    }
 
-    nm_assert(!applied_config_get_current(&priv->ac_ip6_config));
-    applied_config_clear(&priv->ac_ip6_config);
+    if (priv->ipac6_data.ndisc) {
+        /* we already started. Nothing to do. */
+        return;
+    }
 
-    nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
-    nm_clear_g_source(&priv->rt6_temporary_not_available_id);
+    connection = nm_device_get_applied_connection(self);
+    if (connection)
+        s_ip = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
 
-    s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
-    g_assert(s_ip6);
+    g_return_if_fail(s_ip);
 
     if (nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET6),
                  NM_SETTING_IP6_CONFIG_METHOD_SHARED))
@@ -11109,60 +11299,102 @@ addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
             ra_timeout = default_ra_timeout;
     }
 
-    stable_id   = _prop_get_connection_stable_id(self, connection, &stable_type);
-    priv->ndisc = nm_lndp_ndisc_new(nm_device_get_platform(self),
-                                    nm_device_get_ip_ifindex(self),
-                                    nm_device_get_ip_iface(self),
-                                    stable_type,
-                                    stable_id,
-                                    nm_setting_ip6_config_get_addr_gen_mode(s_ip6),
-                                    node_type,
-                                    max_addresses,
-                                    router_solicitations,
-                                    router_solicitation_interval,
-                                    ra_timeout,
-                                    &error);
-    if (!priv->ndisc) {
-        _LOGE(LOGD_IP6, "addrconf6: failed to start neighbor discovery: %s", error->message);
-        g_error_free(error);
-        return FALSE;
+    stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
+
+    {
+        const NMNDiscConfig config = {
+            .l3cfg                        = nm_device_get_l3cfg(self),
+            .ifname                       = nm_device_get_ip_iface(self),
+            .stable_type                  = stable_type,
+            .network_id                   = stable_id,
+            .addr_gen_mode                = nm_setting_ip6_config_get_addr_gen_mode(s_ip),
+            .node_type                    = node_type,
+            .max_addresses                = max_addresses,
+            .router_solicitations         = router_solicitations,
+            .router_solicitation_interval = router_solicitation_interval,
+            .ra_timeout                   = ra_timeout,
+            .ip6_privacy                  = _prop_get_ipv6_ip6_privacy(self),
+        };
+
+        priv->ipac6_data.ndisc = nm_lndp_ndisc_new(&config);
+
+        priv->ipac6_data.ndisc_changed_id =
+            g_signal_connect(priv->ipac6_data.ndisc,
+                             NM_NDISC_CONFIG_RECEIVED,
+                             G_CALLBACK(_dev_ipac6_ndisc_config_changed),
+                             self);
+        priv->ipac6_data.ndisc_timeout_id =
+            g_signal_connect(priv->ipac6_data.ndisc,
+                             NM_NDISC_RA_TIMEOUT_SIGNAL,
+                             G_CALLBACK(_dev_ipac6_ndisc_ra_timeout),
+                             self);
     }
 
-    priv->ndisc_use_tempaddr = use_tempaddr;
+    if (nm_device_get_ip_iface_identifier(self, &iid, FALSE, &is_token)) {
+        _LOGD_ipac6("using the device EUI-64 identifier");
+        nm_ndisc_set_iid(priv->ipac6_data.ndisc, iid, is_token);
+    } else {
+        /* Don't abort the addrconf at this point -- if ndisc needs the iid
+         * it will notice this itself. */
+        _LOGD_ipac6("no interface identifier; IPv6 address creation may fail");
+    }
 
-    /* ensure link local is ready... */
-    if (!linklocal6_start(self)) {
-        /* wait for the LL address to show up */
-        return TRUE;
+    if (nm_ndisc_get_node_type(priv->ipac6_data.ndisc) == NM_NDISC_NODE_TYPE_ROUTER) {
+        nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "1");
+        priv->needs_ip6_subnet = TRUE;
+        g_signal_emit(self, signals[IP6_SUBNET_NEEDED], 0);
     }
 
-    /* already have the LL address; kick off neighbor discovery */
-    addrconf6_start_with_link_ready(self);
-    return TRUE;
+    _dev_ipac6_ndisc_set_router_config(self);
+
+    if (node_type == NM_NDISC_NODE_TYPE_ROUTER)
+        _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
+    else
+        _dev_ipac6_grace_period_start(self, ra_timeout, TRUE);
+
+    nm_ndisc_start(priv->ipac6_data.ndisc);
+}
+
+void
+nm_device_ip_method_autoconf6_start(NMDevice *self)
+{
+    _dev_ipac6_start(self);
 }
 
 static void
-addrconf6_cleanup(NMDevice *self)
+_dev_ipac6_start_continue(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    priv->ndisc_started = FALSE;
-    nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_changed_id);
-    nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_timeout_id);
+    if (priv->ipac6_data.state != NM_DEVICE_IP_STATE_NONE)
+        _dev_ipac6_start(self);
+}
+
+static void
+_dev_ipac6_cleanup(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    applied_config_clear(&priv->ac_ip6_config);
-    nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
-    nm_clear_g_source(&priv->rt6_temporary_not_available_id);
-    if (priv->ndisc) {
-        nm_ndisc_stop(priv->ndisc);
-        g_clear_object(&priv->ndisc);
+    nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
+    nm_clear_l3cd(&priv->ipac6_data.l3cd);
+
+    nm_clear_g_signal_handler(priv->ipac6_data.ndisc, &priv->ipac6_data.ndisc_changed_id);
+    nm_clear_g_signal_handler(priv->ipac6_data.ndisc, &priv->ipac6_data.ndisc_timeout_id);
+
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_AC_6, NULL, FALSE);
+
+    if (priv->ipac6_data.ndisc) {
+        nm_ndisc_stop(priv->ipac6_data.ndisc);
+        g_clear_object(&priv->ipac6_data.ndisc);
     }
+
+    _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_NONE);
 }
 
 /*****************************************************************************/
 
 static void
-save_ip6_properties(NMDevice *self)
+_dev_sysctl_save_ip6_properties(NMDevice *self)
 {
     static const char *const ip6_properties_to_save[] = {
         "accept_ra",
@@ -11172,9 +11404,9 @@ save_ip6_properties(NMDevice *self)
         "use_tempaddr",
     };
     NMDevicePrivate *priv     = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatform *     platform = nm_device_get_platform(self);
-    const char *     ifname;
-    char *           value;
+    NMPlatform      *platform = nm_device_get_platform(self);
+    const char      *ifname;
+    char            *value;
     int              i;
 
     g_hash_table_remove_all(priv->ip6_saved_properties);
@@ -11195,61 +11427,90 @@ save_ip6_properties(NMDevice *self)
 }
 
 static void
-restore_ip6_properties(NMDevice *self)
+_dev_sysctl_restore_ip6_properties(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     GHashTableIter   iter;
-    gpointer         key, value;
+    gpointer         key;
+    gpointer         value;
 
     g_hash_table_iter_init(&iter, priv->ip6_saved_properties);
-    while (g_hash_table_iter_next(&iter, &key, &value)) {
-        /* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
-        if (priv->ipv6ll_handle && nm_streq(key, "disable_ipv6"))
-            continue;
+    while (g_hash_table_iter_next(&iter, &key, &value))
         nm_device_sysctl_ip_conf_set(self, AF_INET6, key, value);
-    }
 }
 
 static void
-set_disable_ipv6(NMDevice *self, const char *value)
+_dev_sysctl_set_disable_ipv6(NMDevice *self, gboolean do_disable)
 {
-    /* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
-    if (!NM_DEVICE_GET_PRIVATE(self)->ipv6ll_handle)
-        nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", value);
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+
+    /* If we previously set addrgenmode=none, we are managing
+     * IPv6 in user space and we should not disable it. */
+    if (do_disable && priv->addrgenmode6_data.previous_mode_has
+        && priv->addrgenmode6_data.previous_mode_val == NM_IN6_ADDR_GEN_MODE_NONE)
+        return;
+
+    nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", do_disable ? "1" : "0");
 }
 
+/*****************************************************************************/
+
 static void
-set_nm_ipv6ll(NMDevice *self, gboolean enable)
+_dev_addrgenmode6_set(NMDevice *self, guint8 addr_gen_mode)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    int              ifindex = nm_device_get_ip_ifindex(self);
+    NMDevicePrivate      *priv    = NM_DEVICE_GET_PRIVATE(self);
+    int                   ifindex = nm_device_get_ip_ifindex(self);
+    const NMPlatformLink *plink;
+    int                   r;
+    int                   cur_addr_gen_mode;
+    char                  sbuf[100];
 
-    priv->ipv6ll_handle = enable;
-    if (ifindex > 0) {
-        int r;
+    if (ifindex <= 0)
+        return;
+
+    plink = nm_platform_link_get(nm_device_get_platform(self), ifindex);
+    if (!plink)
+        return;
+
+    cur_addr_gen_mode = _nm_platform_link_get_inet6_addr_gen_mode(plink);
+    nm_assert(cur_addr_gen_mode >= 0 && cur_addr_gen_mode <= 255);
 
-        _LOGD(LOGD_IP6, "will %s userland IPv6LL", enable ? "enable" : "disable");
+    if (!priv->addrgenmode6_data.previous_mode_has) {
+        priv->addrgenmode6_data.previous_mode_has = TRUE;
+        priv->addrgenmode6_data.previous_mode_val = cur_addr_gen_mode;
+        nm_assert(priv->addrgenmode6_data.previous_mode_val == cur_addr_gen_mode);
+    }
+
+    _LOGD_ip(AF_INET6,
+             "addrgenmode6: set %s%s",
+             nm_platform_link_inet6_addrgenmode2str(addr_gen_mode, sbuf, sizeof(sbuf)),
+             (cur_addr_gen_mode == addr_gen_mode) ? " (already set)" : "");
+
+    if (cur_addr_gen_mode != addr_gen_mode) {
         r = nm_platform_link_set_inet6_addr_gen_mode(nm_device_get_platform(self),
                                                      ifindex,
-                                                     enable ? NM_IN6_ADDR_GEN_MODE_NONE
-                                                            : NM_IN6_ADDR_GEN_MODE_EUI64);
+                                                     addr_gen_mode);
         if (r < 0) {
-            _NMLOG(NM_IN_SET(r, -NME_PL_NOT_FOUND, -NME_PL_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
-                   LOGD_IP6,
-                   "failed to %s userspace IPv6LL address handling (%s)",
-                   enable ? "enable" : "disable",
-                   nm_strerror(r));
+            _NMLOG_ip(NM_IN_SET(r, -NME_PL_NOT_FOUND, -NME_PL_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
+                      AF_INET6,
+                      "addrgenmode6: failed to set %s: (%s)",
+                      nm_platform_link_inet6_addrgenmode2str(addr_gen_mode, sbuf, sizeof(sbuf)),
+                      nm_strerror(r));
+        } else {
+            priv->addrgenmode6_data.previous_mode_val = addr_gen_mode;
         }
+    }
 
-        if (enable) {
-            gs_free char *value = NULL;
-
-            /* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
-            value = nm_device_sysctl_ip_conf_get(self, AF_INET6, "disable_ipv6");
-            if (nm_streq0(value, "0"))
-                nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
+    if (addr_gen_mode == NM_IN6_ADDR_GEN_MODE_NONE) {
+        gs_free char *value = NULL;
 
-            /* Ensure IPv6 is enabled */
+        /* Bounce IPv6 to ensure the kernel stops IPv6LL address and temporary
+         * address generation */
+        _LOGD_ip(AF_INET6,
+                 "addrgenmode6: toggle disable_ipv6 sysctl after disabling addr-gen-mode");
+        value = nm_device_sysctl_ip_conf_get(self, AF_INET6, "disable_ipv6");
+        if (nm_streq0(value, "0")) {
+            nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
             nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "0");
         }
     }
@@ -11275,30 +11536,41 @@ ip_requires_slaves(NMDevice *self, int addr_family)
                         NM_SETTING_IP6_CONFIG_METHOD_DHCP);
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           self,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static const char *
+get_ip_method_auto(NMDevice *self, int addr_family)
+{
+    return NM_IS_IPv4(addr_family) ? NM_SETTING_IP4_CONFIG_METHOD_AUTO
+                                   : NM_SETTING_IP6_CONFIG_METHOD_AUTO;
+}
+
+static void
+activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family, const char *method)
 {
     const int        IS_IPv4 = NM_IS_IPv4(addr_family);
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
-    NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
-    const char *     method;
+    NMDeviceClass   *klass   = NM_DEVICE_GET_CLASS(self);
+    NMConnection    *connection;
+    int              ip_ifindex;
 
-    nm_assert_addr_family(addr_family);
+    if (nm_device_sys_iface_state_is_external(self))
+        goto out;
 
     connection = nm_device_get_applied_connection(self);
+    g_return_if_fail(connection);
 
-    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+    ip_ifindex = nm_device_get_ip_ifindex(self);
 
     if (connection_ip_method_requires_carrier(connection, addr_family, NULL)
         && nm_device_is_master(self) && !priv->carrier) {
-        _LOGI(LOGD_IP | LOGD_DEVICE,
-              "IPv%c config waiting until carrier is on",
-              nm_utils_addr_family_to_char(addr_family));
-        return NM_ACT_STAGE_RETURN_IP_WAIT;
+        if (!priv->ip_data_x[IS_IPv4].wait_for_carrier) {
+            _LOGT_ip(addr_family, "waiting until carrier is on");
+            priv->ip_data_x[IS_IPv4].wait_for_carrier = TRUE;
+        }
+        goto out;
+    }
+    if (priv->ip_data_x[IS_IPv4].wait_for_carrier) {
+        _LOGT_ip(addr_family, "waiting until carrier completed");
+        priv->ip_data_x[IS_IPv4].wait_for_carrier = FALSE;
     }
 
     if (nm_device_is_master(self) && ip_requires_slaves(self, addr_family)) {
@@ -11306,94 +11578,45 @@ act_stage3_ip_config_start(NMDevice *           self,
          * a successful IP configuration attempt, then postpone IP addressing.
          */
         if (!have_any_ready_slaves(self)) {
-            _LOGI(LOGD_DEVICE | LOGD_IP,
-                  "IPv%c config waiting until slaves are ready",
-                  nm_utils_addr_family_to_char(addr_family));
-            return NM_ACT_STAGE_RETURN_IP_WAIT;
+            if (!priv->ip_data_x[IS_IPv4].wait_for_ports) {
+                _LOGT_ip(addr_family, "waiting for ports");
+                priv->ip_data_x[IS_IPv4].wait_for_ports = TRUE;
+            }
+            goto out;
         }
     }
+    if (priv->ip_data_x[IS_IPv4].wait_for_ports) {
+        _LOGT_ip(addr_family, "waiting until ports completed");
+        priv->ip_data_x[IS_IPv4].wait_for_ports = FALSE;
+    }
 
-    if (!IS_IPv4)
-        priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
-
-    method = nm_device_get_effective_ip_config_method(self, addr_family);
-
-    _LOGD(LOGD_IP | LOGD_DEVICE,
-          "IPv%c config method is %s",
-          nm_utils_addr_family_to_char(addr_family),
-          method);
+    if (klass->ready_for_ip_config && !klass->ready_for_ip_config(self))
+        goto out_devip;
 
     if (IS_IPv4) {
-        if (NM_IN_STRSET(method,
-                         NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-                         NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-            NMSettingIPConfig *s_ip4;
-            NMIP4Config **     configs, *config;
-            guint              num_addresses;
-
-            s_ip4 = nm_connection_get_setting_ip4_config(connection);
-            g_return_val_if_fail(s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
-            num_addresses = nm_setting_ip_config_get_num_addresses(s_ip4);
-
-            if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-                ret = dhcp4_start(self);
-                if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-                    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
-                    return ret;
-                }
-            } else {
-                g_return_val_if_fail(num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
-                ret = NM_ACT_STAGE_RETURN_POSTPONE;
-            }
-
-            if (num_addresses) {
-                config = nm_device_ip4_config_new(self);
-                nm_ip4_config_merge_setting(config,
-                                            nm_connection_get_setting_ip4_config(connection),
-                                            NM_SETTING_CONNECTION_MDNS_DEFAULT,
-                                            NM_SETTING_CONNECTION_LLMNR_DEFAULT,
-                                            NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT,
-                                            nm_device_get_route_table(self, AF_INET),
-                                            nm_device_get_route_metric(self, AF_INET));
-                configs    = g_new0(NMIP4Config *, 2);
-                configs[0] = config;
-                ipv4_dad_start(self, configs, ipv4_manual_method_apply);
-            }
-        } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
-            ret = ipv4ll_start(self);
-            if (ret == NM_ACT_STAGE_RETURN_FAILURE)
-                NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
-        } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-            if (out_config) {
-                *out_config = shared4_new_config(self, connection);
-                if (*out_config) {
-                    priv->dnsmasq_manager = nm_dnsmasq_manager_new(nm_device_get_ip_iface(self));
-                    ret                   = NM_ACT_STAGE_RETURN_SUCCESS;
-                } else {
-                    NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-                    ret = NM_ACT_STAGE_RETURN_FAILURE;
-                }
-            } else
-                g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE);
-        } else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-            ret = NM_ACT_STAGE_RETURN_SUCCESS;
-        else
-            _LOGW(LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
-
-        return ret;
-    } else {
-        NMSettingIP6ConfigPrivacy ip6_privacy     = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
-        const char *              ip6_privacy_str = "0";
-        NMPlatform *              platform;
-        int                       ifindex;
+        if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
+            _dev_ipdhcpx_start(self, AF_INET);
+        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
+            _dev_ipll4_start(self);
+        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+            _dev_ipshared4_start(self);
+        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+            priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
+        else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
+            /* pass */
+        } else
+            nm_assert_not_reached();
+    }
 
+    if (!IS_IPv4) {
         if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
-            nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
-            return NM_ACT_STAGE_RETURN_IP_DONE;
-        }
-
-        if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
-            if (!nm_device_sys_iface_state_is_external(self)) {
+            if (!priv->ip_data_x[IS_IPv4].is_disabled) {
+                priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
+                nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
+            }
+        } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+            if (!priv->ip_data_x[IS_IPv4].is_ignore) {
+                priv->ip_data_x[IS_IPv4].is_ignore = TRUE;
                 if (priv->master) {
                     /* If a device only has an IPv6 link-local address,
                      * we don't generate an assumed connection. Therefore,
@@ -11403,174 +11626,56 @@ act_stage3_ip_config_start(NMDevice *           self,
                      * slave should not depend on the previous state. Flush
                      * addresses and routes on activation.
                      */
-                    ifindex  = nm_device_get_ip_ifindex(self);
-                    platform = nm_device_get_platform(self);
-
-                    if (ifindex > 0) {
-                        gs_unref_object NMIP6Config *config = nm_device_ip6_config_new(self);
-
-                        nm_platform_ip_route_flush(platform, AF_INET6, ifindex);
-                        nm_platform_ip_address_flush(platform, AF_INET6, ifindex);
-                        nm_device_set_ip_config(self, AF_INET6, (NMIPConfig *) config, FALSE, NULL);
+                    if (ip_ifindex > 0) {
+                        nm_platform_ip_route_flush(nm_device_get_platform(self),
+                                                   AF_INET6,
+                                                   ip_ifindex);
+                        nm_platform_ip_address_flush(nm_device_get_platform(self),
+                                                     AF_INET6,
+                                                     ip_ifindex);
                     }
                 } else {
-                    gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
-
                     /* When activating an IPv6 'ignore' connection we need to revert back
                      * to kernel IPv6LL, but the kernel won't actually assign an address
                      * to the interface until disable_ipv6 is bounced.
                      */
-                    set_nm_ipv6ll(self, FALSE);
-                    if (ipv6ll_handle_old)
-                        nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
-                    restore_ip6_properties(self);
+                    _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
+                    _dev_sysctl_set_disable_ipv6(self, TRUE);
+                    _dev_sysctl_restore_ip6_properties(self);
                 }
             }
-            return NM_ACT_STAGE_RETURN_IP_DONE;
-        }
-
-        /* Ensure the MTU makes sense. If it was below 1280 the kernel would not
-         * expose any ipv6 sysctls or allow presence of any addresses on the interface,
-         * including LL, which * would make it impossible to autoconfigure MTU to a
-         * correct value. */
-        _commit_mtu(self, priv->ip_config_4);
-
-        /* Any method past this point requires an IPv6LL address. Use NM-controlled
-         * IPv6LL if this is not an assumed connection, since assumed connections
-         * will already have IPv6 set up.
-         */
-        if (!nm_device_sys_iface_state_is_external_or_assume(self))
-            set_nm_ipv6ll(self, TRUE);
-
-        /* Re-enable IPv6 on the interface */
-        nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
-        set_disable_ipv6(self, "0");
-
-        /* Synchronize external IPv6 configuration with kernel, since
-         * linklocal6_start() uses the information there to determine if we can
-         * proceed with the selected method (SLAAC, DHCP, link-local).
-         */
-        nm_platform_process_events(nm_device_get_platform(self));
-        g_clear_object(&priv->ext_ip6_config_captured);
-        priv->ext_ip6_config_captured =
-            nm_ip6_config_capture(nm_device_get_multi_index(self),
-                                  nm_device_get_platform(self),
-                                  nm_device_get_ip_ifindex(self),
-                                  NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-
-        ip6_privacy = _prop_get_ipv6_ip6_privacy(self);
-
-        if (NM_IN_STRSET(method,
-                         NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-                         NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
-            if (!addrconf6_start(self, ip6_privacy)) {
-                /* IPv6 might be disabled; allow IPv4 to proceed */
-                ret = NM_ACT_STAGE_RETURN_IP_FAIL;
-            } else
-                ret = NM_ACT_STAGE_RETURN_POSTPONE;
-        } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
-            ret =
-                linklocal6_start(self) ? NM_ACT_STAGE_RETURN_SUCCESS : NM_ACT_STAGE_RETURN_POSTPONE;
-        } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
-            priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
-            if (!dhcp6_start(self, TRUE)) {
-                /* IPv6 might be disabled; allow IPv4 to proceed */
-                ret = NM_ACT_STAGE_RETURN_IP_FAIL;
+        } else {
+            _dev_ipll6_start(self);
+
+            if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
+                _dev_ipac6_start(self);
+            else if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED))
+                _dev_ipshared6_start(self);
+            else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
+                priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
+                _dev_ipdhcpx_start(self, AF_INET6);
             } else
-                ret = NM_ACT_STAGE_RETURN_POSTPONE;
-        } else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
-            ret = NM_ACT_STAGE_RETURN_SUCCESS;
-        else
-            _LOGW(LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
-
-        if (ret != NM_ACT_STAGE_RETURN_FAILURE
-            && !nm_device_sys_iface_state_is_external_or_assume(self)) {
-            switch (ip6_privacy) {
-            case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
-            case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
-                ip6_privacy_str = "0";
-                break;
-            case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
-                ip6_privacy_str = "1";
-                break;
-            case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
-                ip6_privacy_str = "2";
-                break;
-            }
-            nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", ip6_privacy_str);
+                nm_assert(NM_IN_STRSET(method,
+                                       NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                                       NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL));
         }
-
-        return ret;
-    }
-}
-
-gboolean
-nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family)
-{
-    const int           IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMDevicePrivate *   priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMActStageReturn    ret;
-    NMDeviceStateReason failure_reason    = NM_DEVICE_STATE_REASON_NONE;
-    gs_unref_object NMIPConfig *ip_config = NULL;
-
-    g_assert(priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_WAIT);
-
-    if (nm_device_sys_iface_state_is_external(self)) {
-        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
-        check_ip_state(self, FALSE, TRUE);
-        return TRUE;
     }
 
-    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_CONF);
-
-    ret = NM_DEVICE_GET_CLASS(self)->act_stage3_ip_config_start(self,
-                                                                addr_family,
-                                                                (gpointer *) &ip_config,
-                                                                &failure_reason);
-
-    switch (ret) {
-    case NM_ACT_STAGE_RETURN_SUCCESS:
-        if (!IS_IPv4) {
-            /* Here we get a static IPv6 config, like for Shared where it's
-             * autogenerated or from modems where it comes from ModemManager.
-            */
-            if (!ip_config)
-                ip_config = nm_device_ip_config_new(self, addr_family);
-            nm_assert(!applied_config_get_current(&priv->ac_ip6_config));
-            applied_config_init(&priv->ac_ip6_config, ip_config);
-            ip_config = NULL;
-        }
-        nm_device_activate_schedule_ip_config_result(self, addr_family, ip_config);
-        break;
-    case NM_ACT_STAGE_RETURN_IP_DONE:
-        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
-        check_ip_state(self, FALSE, TRUE);
-        break;
-    case NM_ACT_STAGE_RETURN_FAILURE:
-        nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
-        return FALSE;
-    case NM_ACT_STAGE_RETURN_IP_FAIL:
-        /* Activation not wanted */
-        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
-        break;
-    case NM_ACT_STAGE_RETURN_IP_WAIT:
-        /* Wait for something to try IP config again */
-        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
-        break;
-    default:
-        g_assert(ret == NM_ACT_STAGE_RETURN_POSTPONE);
-    }
+out_devip:
+    if (klass->act_stage3_ip_config)
+        klass->act_stage3_ip_config(self, addr_family);
 
-    return TRUE;
+out:
+    _dev_ip_state_check_async(self, addr_family);
 }
 
 static void
-fw_change_zone_cb(NMFirewalldManager *      firewalld_manager,
+fw_change_zone_cb(NMFirewalldManager       *firewalld_manager,
                   NMFirewalldManagerCallId *call_id,
-                  GError *                  error,
+                  GError                   *error,
                   gpointer                  user_data)
 {
-    NMDevice *       self = user_data;
+    NMDevice        *self = user_data;
     NMDevicePrivate *priv;
 
     g_return_if_fail(NM_IS_DEVICE(self));
@@ -11588,12 +11693,12 @@ fw_change_zone_cb(NMFirewalldManager *      firewalld_manager,
     switch (priv->fw_state) {
     case FIREWALL_STATE_WAIT_STAGE_3:
         priv->fw_state = FIREWALL_STATE_INITIALIZED;
-        nm_device_activate_schedule_stage3_ip_config_start(self);
+        nm_device_activate_schedule_stage3_ip_config(self, TRUE);
         break;
     case FIREWALL_STATE_WAIT_IP_CONFIG:
         priv->fw_state = FIREWALL_STATE_INITIALIZED;
-        if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
-            || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
+        if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
+            || priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY)
             nm_device_start_ip_check(self);
         break;
     case FIREWALL_STATE_INITIALIZED:
@@ -11606,10 +11711,10 @@ fw_change_zone_cb(NMFirewalldManager *      firewalld_manager,
 static void
 fw_change_zone(NMDevice *self)
 {
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *       applied_connection;
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection        *applied_connection;
     NMSettingConnection *s_con;
-    const char *         zone;
+    const char          *zone;
 
     nm_assert(priv->fw_state >= FIREWALL_STATE_INITIALIZED);
 
@@ -11645,24 +11750,51 @@ fw_change_zone(NMDevice *self)
                                                             self);
 }
 
-/*
- * activate_stage3_ip_config_start
- *
- * Begin automatic/manual IP configuration
- *
- */
 static void
-activate_stage3_ip_config_start(NMDevice *self)
+activate_stage3_ip_config(NMDevice *self)
 {
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate *priv  = NM_DEVICE_GET_PRIVATE(self);
+    NMDeviceClass   *klass = NM_DEVICE_GET_CLASS(self);
     int              ifindex;
+    const char      *ipv4_method;
+    const char      *ipv6_method;
+
+    /* stage3 is different from stage1+2.
+     *
+     * What is true in all cases is that when we start a stage, we call the corresponding
+     * nm_device_activate_schedule_stage*() function. But usually the stage cannot complete
+     * right away but needs to wait for some things to happen. So the activate_stage*() function
+     * returns, and will be later proceeded by calling *the same* stage again. That means,
+     * activate_stage*() must be re-entrant and be called repeatedly until we can proceed
+     * to the next stage. Only when the stage is completed, we schedule the next one.
+     *
+     * stage3 is different. It does IP configuration and as such (the stage handling itself)
+     * cannot fail. If a failure happens (for example for DHCP), we remember that (in priv->ipdhcp_data_x)
+     * and issue _dev_ip_state_check_async(). That one combines the DHCP state to determine the
+     * overall per-address-family state (priv->ip_data_x). Those states are then combined
+     * further into priv->combinedip_state, which then leads to nm_device_state_changed()
+     * (which for example can make the device fully ACTIVATED or FAILED).
+     *
+     * The difference between stage1+2 and stage3 is that IP configuration is running continuously
+     * while the device is active. As such the activate_stage3_ip_config() does not fail directly,
+     * unlike the other stages which can abort via NM_ACT_STAGE_RETURN_FAILURE. */
 
     g_return_if_fail(priv->act_request.obj);
 
     ifindex = nm_device_get_ip_ifindex(self);
 
+    if (priv->ip_data_4.do_reapply) {
+        _LOGD_ip(AF_INET, "reapply...");
+        _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE, TRUE);
+    }
+    if (priv->ip_data_6.do_reapply) {
+        _LOGD_ip(AF_INET6, "reapply...");
+        _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE, TRUE);
+    }
+
     /* Add the interface to the specified firewall zone */
-    if (priv->fw_state == FIREWALL_STATE_UNMANAGED) {
+    switch (priv->fw_state) {
+    case FIREWALL_STATE_UNMANAGED:
         if (nm_device_sys_iface_state_is_external(self)) {
             /* fake success */
             priv->fw_state = FIREWALL_STATE_INITIALIZED;
@@ -11672,143 +11804,182 @@ activate_stage3_ip_config_start(NMDevice *self)
             return;
         }
         /* no ifindex, nothing to do for now */
-    } else if (priv->fw_state == FIREWALL_STATE_WAIT_STAGE_3) {
+        break;
+    case FIREWALL_STATE_WAIT_STAGE_3:
         /* a firewall call for stage3 is pending. Return and wait. */
         return;
+    default:
+        nm_assert(NM_IN_SET((FirewallState) priv->fw_state,
+                            FIREWALL_STATE_INITIALIZED,
+                            FIREWALL_STATE_WAIT_IP_CONFIG));
+        break;
     }
-
     nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
 
-    _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
-    _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
+    if (priv->state < NM_DEVICE_STATE_IP_CONFIG) {
+        _dev_ip_state_req_timeout_schedule(self, AF_INET);
+        _dev_ip_state_req_timeout_schedule(self, AF_INET6);
 
-    _active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
+        _active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
 
-    nm_device_state_changed(self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
+        nm_device_state_changed(self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
 
-    /* Device should be up before we can do anything with it */
-    if (!nm_device_sys_iface_state_is_external(self)
-        && (ifindex = nm_device_get_ip_ifindex(self)) > 0
-        && !nm_platform_link_is_up(nm_device_get_platform(self), ifindex))
-        _LOGW(LOGD_DEVICE,
-              "interface %s not up for IP configuration",
-              nm_device_get_ip_iface(self));
+        /* Device should be up before we can do anything with it */
+        if (!nm_device_sys_iface_state_is_external(self) && ifindex > 0
+            && !nm_platform_link_is_up(nm_device_get_platform(self), ifindex))
+            _LOGW(LOGD_DEVICE,
+                  "interface %s not up for IP configuration",
+                  nm_device_get_ip_iface(self));
+    }
 
-    if (nm_device_activate_ip4_state_in_wait(self)
-        && !nm_device_activate_stage3_ip_start(self, AF_INET))
-        return;
+    ipv4_method = nm_device_get_effective_ip_config_method(self, AF_INET);
+    if (nm_streq(ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+        /* "auto" usually means DHCPv4 or autoconf6, but it doesn't have to be. Subclasses
+         * can overwrite it. For example, you cannot run DHCPv4 on PPP/WireGuard links. */
+        ipv4_method = klass->get_ip_method_auto(self, AF_INET);
+    }
 
-    if (nm_device_activate_ip6_state_in_wait(self)
-        && !nm_device_activate_stage3_ip_start(self, AF_INET6))
-        return;
+    ipv6_method = nm_device_get_effective_ip_config_method(self, AF_INET6);
+    if (nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
+        ipv6_method = klass->get_ip_method_auto(self, AF_INET6);
+    }
+
+    if (!nm_device_sys_iface_state_is_external(self)
+        && (!klass->ready_for_ip_config || klass->ready_for_ip_config(self))) {
+        if (priv->ipmanual_data.state_6 == NM_DEVICE_IP_STATE_NONE
+            && !NM_IN_STRSET(ipv6_method,
+                             NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                             NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
+            /* Ensure the MTU makes sense. If it was below 1280 the kernel would not
+             * expose any ipv6 sysctls or allow presence of any addresses on the interface,
+             * including LL, which * would make it impossible to autoconfigure MTU to a
+             * correct value. */
+            _commit_mtu(self);
+
+            /* Any method past this point requires an IPv6LL address. Use NM-controlled
+             * IPv6LL if this is not an assumed connection, since assumed connections
+             * will already have IPv6 set up.
+             */
+            if (!nm_device_sys_iface_state_is_external_or_assume(self))
+                _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
 
-    /* Proxy */
-    nm_device_set_proxy_config(self, NULL);
+            /* Re-enable IPv6 on the interface */
+            nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
+            _dev_sysctl_set_disable_ipv6(self, FALSE);
+        }
+
+        _dev_ipmanual_start(self);
+    }
 
-    check_ip_state(self, TRUE, TRUE);
+    activate_stage3_ip_config_for_addr_family(self, AF_INET, ipv4_method);
+    activate_stage3_ip_config_for_addr_family(self, AF_INET6, ipv6_method);
 }
 
-/*
- * nm_device_activate_schedule_stage3_ip_config_start
- *
- * Schedule IP configuration start
- */
 void
-nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
+nm_device_activate_schedule_stage3_ip_config(NMDevice *self, gboolean do_sync)
 {
-    NMDevicePrivate *priv;
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    g_return_if_fail(priv->act_request.obj);
-
-    activation_source_schedule(self, activate_stage3_ip_config_start, AF_INET);
+    activation_source_invoke_or_schedule(self, activate_stage3_ip_config, do_sync);
 }
 
-static NMActStageReturn
-act_stage4_ip_config_timeout(NMDevice *           self,
-                             int                  addr_family,
-                             NMDeviceStateReason *out_failure_reason)
+/*****************************************************************************/
+
+static void
+_dev_ipsharedx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
 {
-    nm_assert_addr_family(addr_family);
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    if (!get_ip_config_may_fail(self, addr_family)) {
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
-        return NM_ACT_STAGE_RETURN_FAILURE;
+    if (priv->ipshared_data_x[IS_IPv4].state != state) {
+        _LOGD_ipshared(addr_family,
+                       "set state %s (was %s)",
+                       nm_device_ip_state_to_string(state),
+                       nm_device_ip_state_to_string(priv->ipshared_data_x[IS_IPv4].state));
+        priv->ipshared_data_x[IS_IPv4].state = state;
     }
-
-    return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
 static void
-activate_stage4_ip_config_timeout_x(NMDevice *self, int addr_family)
+_dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
 {
-    NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
-    NMActStageReturn    ret;
+    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    ret =
-        NM_DEVICE_GET_CLASS(self)->act_stage4_ip_config_timeout(self, addr_family, &failure_reason);
+    if (IS_IPv4) {
+        if (priv->ipshared_data_4.v4.dnsmasq_manager) {
+            nm_clear_g_signal_handler(priv->ipshared_data_4.v4.dnsmasq_manager,
+                                      &priv->ipshared_data_4.v4.dnsmasq_state_id);
+            nm_dnsmasq_manager_stop(priv->ipshared_data_4.v4.dnsmasq_manager);
+            g_clear_object(&priv->ipshared_data_4.v4.dnsmasq_manager);
+        }
 
-    if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
-        return;
+        if (priv->ipshared_data_4.v4.firewall_config) {
+            nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, FALSE);
+            nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free);
+        }
 
-    if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
-        nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
-        return;
+        nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release);
+        nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
+
+        _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE);
     }
-    g_assert(ret == NM_ACT_STAGE_RETURN_SUCCESS);
 
-    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
-    check_ip_state(self, FALSE, TRUE);
+    _dev_ipsharedx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
 }
 
-static void
-activate_stage4_ip_config_timeout_4(NMDevice *self)
-{
-    activate_stage4_ip_config_timeout_x(self, AF_INET);
-}
+/*****************************************************************************/
 
-static void
-activate_stage4_ip_config_timeout_6(NMDevice *self)
+static const NML3ConfigData *
+_dev_ipshared4_new_l3cd(NMDevice *self, NMConnection *connection, NMPlatformIP4Address *out_addr4)
 {
-    activate_stage4_ip_config_timeout_x(self, AF_INET6);
-}
+    NMDevicePrivate                        *priv = NM_DEVICE_GET_PRIVATE(self);
+    nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+    NMSettingIPConfig                      *s_ip4;
+    NMPlatformIP4Address                    address = {
+                           .addr_source = NM_IP_CONFIG_SOURCE_SHARED,
+    };
 
-#define activate_stage4_ip_config_timeout_x_fcn(addr_family)       \
-    (NM_IS_IPv4(addr_family) ? activate_stage4_ip_config_timeout_4 \
-                             : activate_stage4_ip_config_timeout_6)
+    g_return_val_if_fail(self, NULL);
+    g_return_val_if_fail(connection, NULL);
 
-void
-nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family)
-{
-    NMDevicePrivate *priv;
+    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    if (s_ip4 && nm_setting_ip_config_get_num_addresses(s_ip4) > 0) {
+        /* Use the first user-supplied address */
+        NMIPAddress *user = nm_setting_ip_config_get_address(s_ip4, 0);
+        in_addr_t    a;
 
-    g_return_if_fail(NM_IS_DEVICE(self));
+        nm_ip_address_get_address_binary(user, &a);
+        nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user));
+        nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release);
+    } else {
+        if (!priv->ipshared_data_4.v4.shared_ip_handle)
+            priv->ipshared_data_4.v4.shared_ip_handle =
+                nm_netns_shared_ip_reserve(nm_device_get_netns(self));
+        nm_platform_ip4_address_set_addr(&address,
+                                         priv->ipshared_data_4.v4.shared_ip_handle->addr,
+                                         24);
+    }
 
-    priv = NM_DEVICE_GET_PRIVATE(self);
+    l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
+    nm_l3_config_data_add_address_4(l3cd, &address);
 
-    g_return_if_fail(priv->act_request.obj);
+    NM_SET_OUT(out_addr4, address);
 
-    activation_source_schedule(self,
-                               activate_stage4_ip_config_timeout_x_fcn(addr_family),
-                               addr_family);
+    return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
 }
 
 static gboolean
-share_init(NMDevice *self, GError **error)
-{
-    const char *const modules[] = {"ip_tables",
-                                   "iptable_nat",
-                                   "nf_nat_ftp",
-                                   "nf_nat_irc",
-                                   "nf_nat_sip",
-                                   "nf_nat_tftp",
-                                   "nf_nat_pptp",
-                                   "nf_nat_h323"};
-    guint             i;
-    int               errsv;
+_dev_ipshared4_init(NMDevice *self)
+{
+    static const char *const modules[] = {"ip_tables",
+                                          "iptable_nat",
+                                          "nf_nat_ftp",
+                                          "nf_nat_irc",
+                                          "nf_nat_sip",
+                                          "nf_nat_tftp",
+                                          "nf_nat_pptp",
+                                          "nf_nat_h323"};
+    int                      errsv;
+    guint                    i;
 
     if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
                                      NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
@@ -11819,15 +11990,7 @@ share_init(NMDevice *self, GError **error)
                                        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
                                        "1")) {
         errsv = errno;
-        _LOGD(LOGD_SHARING,
-              "share: error enabling IPv4 forwarding: (%d) %s",
-              errsv,
-              nm_strerror_native(errsv));
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "cannot set ipv4/ip_forward: %s",
-                    nm_strerror_native(errsv));
+        _LOGW_ipshared(AF_INET, "error enabling IPv4 forwarding: %s", nm_strerror_native(errsv));
         return FALSE;
     }
 
@@ -11840,10 +12003,9 @@ share_init(NMDevice *self, GError **error)
                                        NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"),
                                        "1")) {
         errsv = errno;
-        _LOGD(LOGD_SHARING,
-              "share: error enabling dynamic addresses: (%d) %s",
-              errsv,
-              nm_strerror_native(errsv));
+        _LOGD_ipshared(AF_INET,
+                       "share: error enabling dynamic addresses: %s",
+                       nm_strerror_native(errsv));
     }
 
     for (i = 0; i < G_N_ELEMENTS(modules); i++)
@@ -11852,48 +12014,86 @@ share_init(NMDevice *self, GError **error)
     return TRUE;
 }
 
-static gboolean
-start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
+static void
+_dev_ipshared4_dnsmasq_state_changed_cb(NMDnsMasqManager *manager, guint status, gpointer user_data)
+{
+    NMDevice *self = NM_DEVICE(user_data);
+
+    if (status != NM_DNSMASQ_STATUS_DEAD)
+        return;
+
+    _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
+    _dev_ip_state_check_async(self, AF_INET);
+}
+
+static void
+_dev_ipshared4_start(NMDevice *self)
 {
-    NMDevicePrivate *           priv = NM_DEVICE_GET_PRIVATE(self);
-    NMActRequest *              req;
-    const NMPlatformIP4Address *ip4_addr = NULL;
-    const char *                ip_iface;
-    GError *                    local = NULL;
-    NMConnection *              conn;
-    NMSettingConnection *       s_con;
-    gboolean                    announce_android_metered;
-    NMFirewallConfig *          firewall_config;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
+    NMPlatformIP4Address                     ip4_addr;
+    NMDevicePrivate                         *priv = NM_DEVICE_GET_PRIVATE(self);
+    const char                              *ip_iface;
+    NMConnection                            *applied;
 
-    g_return_val_if_fail(config, FALSE);
+    if (priv->ipshared_data_4.state != NM_DEVICE_IP_STATE_NONE)
+        return;
+
+    nm_assert(!priv->ipshared_data_4.v4.firewall_config);
+    nm_assert(!priv->ipshared_data_4.v4.dnsmasq_manager);
+    nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
 
     ip_iface = nm_device_get_ip_iface(self);
-    if (!ip_iface) {
-        g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "device has no ip interface");
-        return FALSE;
-    }
+    g_return_if_fail(ip_iface);
 
-    ip4_addr = nm_ip4_config_get_first_address(config);
-    if (!ip4_addr || !ip4_addr->address) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "could not determine IPv4 address");
-        return FALSE;
+    applied = nm_device_get_applied_connection(self);
+    g_return_if_fail(applied);
+
+    _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING);
+
+    l3cd = _dev_ipshared4_new_l3cd(self, applied, &ip4_addr);
+    if (!l3cd) {
+        nm_assert_not_reached();
+        goto out_fail;
     }
 
-    if (!share_init(self, error))
-        return FALSE;
+    if (!_dev_ipshared4_init(self))
+        goto out_fail;
+
+    priv->ipshared_data_4.v4.firewall_config =
+        nm_firewall_config_new(ip_iface, ip4_addr.address, ip4_addr.plen);
+    nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, TRUE);
+
+    priv->ipshared_data_4.v4.l3cd = nm_l3_config_data_ref(l3cd);
+    _dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, l3cd, FALSE);
 
-    req = nm_device_get_act_request(self);
-    g_return_val_if_fail(req, FALSE);
+    /* Wait that the address gets committed before spawning dnsmasq */
+    return;
+out_fail:
+    _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
+    _dev_ip_state_check_async(self, AF_INET);
+}
 
-    firewall_config = nm_firewall_config_new(ip_iface, ip4_addr->address, ip4_addr->plen);
+static void
+_dev_ipshared4_spawn_dnsmasq(NMDevice *self)
+{
+    NMDevicePrivate      *priv = NM_DEVICE_GET_PRIVATE(self);
+    const char           *ip_iface;
+    gs_free_error GError *error = NULL;
+    NMSettingConnection  *s_con;
+    gboolean              announce_android_metered;
+    NMConnection         *applied;
 
-    nm_act_request_set_shared(req, firewall_config);
+    nm_assert(priv->ipshared_data_4.v4.firewall_config);
+    nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
+    nm_assert(!priv->ipshared_data_4.v4.dnsmasq_manager);
+    nm_assert(priv->ipshared_data_4.v4.l3cd);
 
-    conn  = nm_act_request_get_applied_connection(req);
-    s_con = nm_connection_get_setting_connection(conn);
+    ip_iface = nm_device_get_ip_iface(self);
+    g_return_if_fail(ip_iface);
+
+    applied = nm_device_get_applied_connection(self);
+    g_return_if_fail(applied);
+    s_con = nm_connection_get_setting_connection(applied);
 
     switch (nm_setting_connection_get_metered(s_con)) {
     case NM_METERED_YES:
@@ -11915,353 +12115,54 @@ start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
         break;
     }
 
-    if (!nm_dnsmasq_manager_start(priv->dnsmasq_manager,
-                                  config,
+    priv->ipshared_data_4.v4.dnsmasq_manager = nm_dnsmasq_manager_new(ip_iface);
+    if (!nm_dnsmasq_manager_start(priv->ipshared_data_4.v4.dnsmasq_manager,
+                                  priv->ipshared_data_4.v4.l3cd,
                                   announce_android_metered,
-                                  &local)) {
-        g_set_error(error,
-                    NM_UTILS_ERROR,
-                    NM_UTILS_ERROR_UNKNOWN,
-                    "could not start dnsmasq due to %s",
-                    local->message);
-        g_error_free(local);
-        nm_act_request_set_shared(req, NULL);
-        return FALSE;
+                                  &error)) {
+        _LOGW_ipshared(AF_INET, "could not start dnsmasq: %s", error->message);
+        goto out_fail;
     }
 
-    priv->dnsmasq_state_id = g_signal_connect(priv->dnsmasq_manager,
-                                              NM_DNS_MASQ_MANAGER_STATE_CHANGED,
-                                              G_CALLBACK(dnsmasq_state_changed_cb),
-                                              self);
-    return TRUE;
-}
+    priv->ipshared_data_4.v4.dnsmasq_state_id =
+        g_signal_connect(priv->ipshared_data_4.v4.dnsmasq_manager,
+                         NM_DNS_MASQ_MANAGER_STATE_CHANGED,
+                         G_CALLBACK(_dev_ipshared4_dnsmasq_state_changed_cb),
+                         self);
 
-static void
-arp_cleanup(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_READY);
+    _dev_ip_state_check_async(self, AF_INET);
+    return;
 
-    nm_clear_pointer(&priv->acd.announcing, nm_acd_manager_free);
+out_fail:
+    _dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
+    _dev_ip_state_check_async(self, AF_INET);
 }
 
-void
-nm_device_arp_announce(NMDevice *self)
-{
-    NMDevicePrivate *  priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    guint              num, i;
-    const guint8 *     hw_addr;
-    size_t             hw_addr_len = 0;
-
-    arp_cleanup(self);
-
-    hw_addr = nm_platform_link_get_address(nm_device_get_platform(self),
-                                           nm_device_get_ip_ifindex(self),
-                                           &hw_addr_len);
-
-    if (!hw_addr || hw_addr_len != ETH_ALEN)
-        return;
-
-    /* We only care about manually-configured addresses; DHCP- and autoip-configured
-     * ones should already have been seen on the network at this point.
-     */
-    connection = nm_device_get_applied_connection(self);
-    if (!connection)
-        return;
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    if (!s_ip4)
-        return;
-    num = nm_setting_ip_config_get_num_addresses(s_ip4);
-    if (num == 0)
-        return;
-
-    priv->acd.announcing =
-        nm_acd_manager_new(nm_device_get_ip_ifindex(self), hw_addr, hw_addr_len, NULL, NULL);
-
-    for (i = 0; i < num; i++) {
-        NMIPAddress *ip = nm_setting_ip_config_get_address(s_ip4, i);
-        in_addr_t    addr;
-
-        if (inet_pton(AF_INET, nm_ip_address_get_address(ip), &addr) == 1)
-            nm_acd_manager_add_address(priv->acd.announcing, addr);
-        else
-            g_warn_if_reached();
-    }
-
-    nm_acd_manager_announce_addresses(priv->acd.announcing);
-}
+/*****************************************************************************/
 
 static void
-activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
+_dev_ipshared6_start(NMDevice *self)
 {
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMActRequest *   req;
-    const char *     method;
-    int              ip_ifindex;
-    int              errsv;
-
-    req = nm_device_get_act_request(self);
-    g_assert(req);
-
-    nm_clear_g_source_inst(&priv->ip_req_timeout_source_x[IS_IPv4]);
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    /* Interface must be IFF_UP before IP config can be applied */
-    ip_ifindex = nm_device_get_ip_ifindex(self);
-    g_return_if_fail(ip_ifindex);
+    _dev_ipac6_start(self);
 
-    if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)
-        && !nm_device_sys_iface_state_is_external(self)) {
-        nm_platform_link_change_flags(nm_device_get_platform(self), ip_ifindex, IFF_UP, TRUE);
-        if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex))
-            _LOGW(LOGD_DEVICE,
-                  "interface %s not up for IP configuration",
-                  nm_device_get_ip_iface(self));
-    }
-
-    if (!ip_config_merge_and_apply(self, addr_family, TRUE)) {
-        _LOGD(LOGD_DEVICE | LOGD_IPX(IS_IPv4),
-              "Activation: Stage 5 of 5 (IPv%c Commit) failed",
-              nm_utils_addr_family_to_char(addr_family));
-        nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+    if (priv->ipshared_data_6.state != NM_DEVICE_IP_STATE_NONE)
         return;
-    }
-
-    if (!IS_IPv4) {
-        if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
-            && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
-            if (applied_config_get_current(&priv->dhcp6.ip6_config)) {
-                /* If IPv6 wasn't the first IP to complete, and DHCP was used,
-                 * then ensure dispatcher scripts get the DHCP lease information.
-                 */
-                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_6,
-                                          self,
-                                          NULL,
-                                          NULL,
-                                          NULL,
-                                          NULL);
-            } else {
-                /* still waiting for first dhcp6 lease. */
-                return;
-            }
-        }
-    }
-
-    /* Start IPv4 sharing/IPv6 forwarding if we need it */
-    method = nm_device_get_effective_ip_config_method(self, addr_family);
-    if (IS_IPv4) {
-        if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
-            gs_free_error GError *error = NULL;
-
-            if (!start_sharing(self, priv->ip_config_4, &error)) {
-                _LOGW(LOGD_SHARING,
-                      "Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s",
-                      error->message);
-                nm_device_ip_method_failed(self,
-                                           AF_INET,
-                                           NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
-                return;
-            }
-        }
-    } else {
-        if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
-            if (!nm_platform_sysctl_set(
-                    nm_device_get_platform(self),
-                    NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/all/forwarding"),
-                    "1")) {
-                errsv = errno;
-                _LOGE(LOGD_SHARING,
-                      "share: error enabling IPv6 forwarding: (%d) %s",
-                      errsv,
-                      nm_strerror_native(errsv));
-                nm_device_ip_method_failed(self,
-                                           AF_INET6,
-                                           NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
-                return;
-            }
-        }
-    }
-
-    if (IS_IPv4) {
-        if (priv->dhcp_data_4.client) {
-            gs_free_error GError *error = NULL;
-
-            if (!nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
-                _LOGW(LOGD_DHCP4,
-                      "Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s",
-                      error->message);
-                nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_DHCP_ERROR);
-                return;
-            }
-        }
-
-        /* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
-         * dispatcher scripts get the DHCP lease information.
-         */
-        if (priv->dhcp_data_4.client && nm_device_activate_ip4_state_in_conf(self)
-            && (nm_device_get_state(self) > NM_DEVICE_STATE_IP_CONFIG)) {
-            nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_4,
-                                      self,
-                                      NULL,
-                                      NULL,
-                                      NULL,
-                                      NULL);
-        }
-    }
-
-    if (!IS_IPv4) {
-        /* Check if we have to wait for DAD */
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) {
-            if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail(self, AF_INET6))
-                _LOGI(LOGD_DEVICE | LOGD_IP6,
-                      "IPv6 DAD: carrier missing and ignored, not delaying activation");
-            else
-                priv->dad6_ip6_config = dad6_get_pending_addresses(self);
-
-            if (priv->dad6_ip6_config) {
-                _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
-            } else {
-                _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
-                check_ip_state(self, FALSE, TRUE);
-            }
-        }
-    }
-
-    if (IS_IPv4 && priv->carrier) {
-        /* We send ARP announcements only when the link gets carrier,
-         * otherwise the announcements would be lost. Furthermore, for
-         * controllers having carrier implies that there is at least one
-         * port and therefore the MAC address is the correct one.
-         */
-        nm_device_arp_announce(self);
-    }
-
-    if (IS_IPv4) {
-        /* Enter the IP_CHECK state if this is the first method to complete */
-        _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
-        check_ip_state(self, FALSE, TRUE);
-    }
-}
-
-static void
-activate_stage5_ip_config_result_4(NMDevice *self)
-{
-    activate_stage5_ip_config_result_x(self, AF_INET);
-}
-
-static void
-activate_stage5_ip_config_result_6(NMDevice *self)
-{
-    activate_stage5_ip_config_result_x(self, AF_INET6);
-}
-
-#define activate_stage5_ip_config_result_x_fcn(addr_family)       \
-    (NM_IS_IPv4(addr_family) ? activate_stage5_ip_config_result_4 \
-                             : activate_stage5_ip_config_result_6)
-
-void
-nm_device_activate_schedule_ip_config_result(NMDevice *self, int addr_family, NMIPConfig *config)
-{
-    NMDevicePrivate *priv;
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(!config || (IS_IPv4 && nm_ip_config_get_addr_family(config) == AF_INET));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (IS_IPv4) {
-        applied_config_init(&priv->dev_ip_config_4, config);
-    } else {
-        /* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we
-         * clearly now have configuration.
-         */
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
-            _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
-    }
-
-    activation_source_schedule(self,
-                               activate_stage5_ip_config_result_x_fcn(addr_family),
-                               addr_family);
-}
-
-NMDeviceIPState
-nm_device_activate_get_ip_state(NMDevice *self, int addr_family)
-{
-    const int IS_IPv4 = NM_IS_IPv4(addr_family);
-
-    g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_IP_STATE_NONE);
-    g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE);
-
-    return NM_DEVICE_GET_PRIVATE(self)->ip_state_x[IS_IPv4];
-}
-
-static void
-dad6_add_pending_address(NMDevice *             self,
-                         NMPlatform *           platform,
-                         int                    ifindex,
-                         const struct in6_addr *address,
-                         NMIP6Config **         dad6_config)
-{
-    const NMPlatformIP6Address *pl_addr;
-
-    pl_addr = nm_platform_ip6_address_get(platform, ifindex, address);
-    if (pl_addr && NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
-        && !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_DADFAILED)
-        && !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
-        _LOGt(LOGD_DEVICE,
-              "IPv6 DAD: pending address %s",
-              nm_platform_ip6_address_to_string(pl_addr, NULL, 0));
-
-        if (!*dad6_config)
-            *dad6_config = nm_device_ip6_config_new(self);
-
-        nm_ip6_config_add_address(*dad6_config, pl_addr);
-    }
-}
-
-/*
- * Returns a NMIP6Config containing NM-configured addresses which
- * have the tentative flag, or NULL if none is present.
- */
-static NMIP6Config *
-dad6_get_pending_addresses(NMDevice *self)
-{
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    NMIP6Config *    confs[] = {(NMIP6Config *) applied_config_get_current(&priv->ac_ip6_config),
-                            (NMIP6Config *) applied_config_get_current(&priv->dhcp6.ip6_config),
-                            priv->con_ip_config_6,
-                            (NMIP6Config *) applied_config_get_current(&priv->dev2_ip_config_6)};
-    const NMPlatformIP6Address *addr;
-    NMIP6Config *               dad6_config = NULL;
-    NMDedupMultiIter            ipconf_iter;
-    guint                       i;
-    int                         ifindex;
-    NMPlatform *                platform;
-
-    ifindex = nm_device_get_ip_ifindex(self);
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    platform = nm_device_get_platform(self);
-
-    if (priv->ipv6ll_has) {
-        dad6_add_pending_address(self, platform, ifindex, &priv->ipv6ll_addr, &dad6_config);
-    }
-
-    /* We are interested only in addresses that we have explicitly configured,
-     * not in externally added ones.
-     */
-    for (i = 0; i < G_N_ELEMENTS(confs); i++) {
-        if (!confs[i])
-            continue;
-
-        nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
-            dad6_add_pending_address(self, platform, ifindex, &addr->address, &dad6_config);
-        }
+    if (!nm_platform_sysctl_set(
+            nm_device_get_platform(self),
+            NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/all/forwarding"),
+            "1")) {
+        _LOGW_ipshared(AF_INET6, "failure to enable ipv6 forwarding");
+        _dev_ipsharedx_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_FAILED);
+        _dev_ip_state_check_async(self, AF_INET6);
+        return;
     }
 
-    return dad6_config;
+    _dev_ipsharedx_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_READY);
+    _dev_ip_state_check_async(self, AF_INET6);
 }
 
 /*****************************************************************************/
@@ -12302,21 +12203,6 @@ act_request_set(NMDevice *self, NMActRequest *act_request)
     }
 }
 
-static void
-dnsmasq_cleanup(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (!priv->dnsmasq_manager)
-        return;
-
-    nm_clear_g_signal_handler(priv->dnsmasq_manager, &priv->dnsmasq_state_id);
-
-    nm_dnsmasq_manager_stop(priv->dnsmasq_manager);
-    g_object_unref(priv->dnsmasq_manager);
-    priv->dnsmasq_manager = NULL;
-}
-
 gboolean
 nm_device_is_nm_owned(NMDevice *self)
 {
@@ -12333,10 +12219,10 @@ nm_device_is_nm_owned(NMDevice *self)
 static gboolean
 delete_on_deactivate_link_delete(gpointer user_data)
 {
-    DeleteOnDeactivateData *data        = user_data;
-    nm_auto_unref_object NMDevice *self = data->device;
-    NMDevicePrivate *              priv = NM_DEVICE_GET_PRIVATE(self);
-    gs_free_error GError *error         = NULL;
+    DeleteOnDeactivateData        *data  = user_data;
+    nm_auto_unref_object NMDevice *self  = data->device;
+    NMDevicePrivate               *priv  = NM_DEVICE_GET_PRIVATE(self);
+    gs_free_error GError          *error = NULL;
 
     _LOGD(LOGD_DEVICE,
           "delete_on_deactivate: cleanup and delete virtual link (id=%u)",
@@ -12375,7 +12261,7 @@ delete_on_deactivate_unschedule(NMDevice *self)
 static void
 delete_on_deactivate_check_and_schedule(NMDevice *self)
 {
-    NMDevicePrivate *       priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate        *priv = NM_DEVICE_GET_PRIVATE(self);
     DeleteOnDeactivateData *data;
 
     if (!priv->nm_owned)
@@ -12401,40 +12287,32 @@ delete_on_deactivate_check_and_schedule(NMDevice *self)
 }
 
 static void
-_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type)
+_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply)
 {
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
+    const int IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
+    _dev_ipsharedx_cleanup(self, addr_family);
 
-    if (nm_clear_g_source(&priv->queued_ip_config_id_x[IS_IPv4])) {
-        _LOGD(LOGD_DEVICE,
-              "clearing queued IP%c config change",
-              nm_utils_addr_family_to_char(addr_family));
-    }
+    _dev_ipdev_cleanup(self, AF_UNSPEC);
+    _dev_ipdev_cleanup(self, addr_family);
 
-    if (IS_IPv4) {
-        dhcp4_cleanup(self, cleanup_type, FALSE);
-        arp_cleanup(self);
-        dnsmasq_cleanup(self);
-        ipv4ll_cleanup(self);
-        g_slist_free_full(priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_free);
-        priv->acd.dad_list = NULL;
-    } else {
-        g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
-        priv->dad6_failed_addrs = NULL;
-        g_clear_object(&priv->dad6_ip6_config);
-        dhcp6_cleanup(self, cleanup_type, FALSE);
-        nm_clear_g_source(&priv->linklocal6_timeout_id);
-        addrconf6_cleanup(self);
-    }
+    _dev_ipdhcpx_cleanup(self, addr_family, TRUE, FALSE);
+
+    if (!IS_IPv4)
+        _dev_ipac6_cleanup(self);
+
+    _dev_ipllx_cleanup(self, addr_family);
+
+    _dev_ipmanual_cleanup(self);
+
+    _dev_ip_state_cleanup(self, AF_UNSPEC, from_reapply);
+    _dev_ip_state_cleanup(self, addr_family, from_reapply);
 }
 
 gboolean
-_nm_device_hash_check_invalid_keys(GHashTable *       hash,
-                                   const char *       setting_name,
-                                   GError **          error,
+_nm_device_hash_check_invalid_keys(GHashTable        *hash,
+                                   const char        *setting_name,
+                                   GError           **error,
                                    const char *const *whitelist)
 {
     guint found_whitelisted_keys = 0;
@@ -12469,8 +12347,8 @@ _nm_device_hash_check_invalid_keys(GHashTable *       hash,
 
     if (error) {
         GHashTableIter iter;
-        const char *   k                 = NULL;
-        const char *   first_invalid_key = NULL;
+        const char    *k                 = NULL;
+        const char    *first_invalid_key = NULL;
 
         g_hash_table_iter_init(&iter, hash);
         while (g_hash_table_iter_next(&iter, (gpointer *) &k, NULL)) {
@@ -12499,128 +12377,6 @@ _nm_device_hash_check_invalid_keys(GHashTable *       hash,
     return FALSE;
 }
 
-void
-nm_device_reactivate_ip_config(NMDevice *         self,
-                               int                addr_family,
-                               NMSettingIPConfig *s_ip_old,
-                               NMSettingIPConfig *s_ip_new)
-{
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMDevicePrivate *priv;
-    const char *     method_old;
-    const char *     method_new;
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_NONE)
-        return;
-
-    g_clear_object(&priv->con_ip_config_x[IS_IPv4]);
-    g_clear_object(&priv->ext_ip_config_x[IS_IPv4]);
-    if (IS_IPv4) {
-        g_clear_object(&priv->dev_ip_config_4.current);
-    } else {
-        g_clear_object(&priv->ac_ip6_config.current);
-        g_clear_object(&priv->dhcp6.ip6_config.current);
-    }
-    g_clear_object(&priv->dev2_ip_config_x[IS_IPv4].current);
-
-    if (!IS_IPv4) {
-        if (priv->ipv6ll_handle && !IN6_IS_ADDR_UNSPECIFIED(&priv->ipv6ll_addr))
-            priv->ipv6ll_has = TRUE;
-    }
-
-    priv->con_ip_config_x[IS_IPv4] = nm_device_ip_config_new(self, addr_family);
-
-    if (IS_IPv4) {
-        nm_ip4_config_merge_setting(priv->con_ip_config_4,
-                                    s_ip_new,
-                                    _prop_get_connection_mdns(self),
-                                    _prop_get_connection_llmnr(self),
-                                    _prop_get_connection_dns_over_tls(self),
-                                    nm_device_get_route_table(self, AF_INET),
-                                    nm_device_get_route_metric(self, AF_INET));
-    } else {
-        nm_ip6_config_merge_setting(priv->con_ip_config_6,
-                                    s_ip_new,
-                                    nm_device_get_route_table(self, AF_INET6),
-                                    nm_device_get_route_metric(self, AF_INET6));
-    }
-
-    method_old = (s_ip_old ? nm_setting_ip_config_get_method(s_ip_old) : NULL)
-                     ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
-                                 : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-    method_new = (s_ip_new ? nm_setting_ip_config_get_method(s_ip_new) : NULL)
-                     ?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
-                                 : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
-
-    if (!nm_streq0(method_old, method_new)) {
-        _cleanup_ip_pre(self, addr_family, CLEANUP_TYPE_DECONFIGURE);
-        _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
-        if (!nm_device_activate_stage3_ip_start(self, addr_family)) {
-            _LOGW(LOGD_IP4,
-                  "Failed to apply IPv%c configuration",
-                  nm_utils_addr_family_to_char(addr_family));
-        }
-        return;
-    }
-
-    if (s_ip_old && s_ip_new) {
-        gint64 metric_old, metric_new;
-
-        /* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
-         * set at activation/renewal time using the value from static
-         * configuration. To support runtime change we need to update the
-         * dynamic configuration in place and tell the DHCP client the new
-         * value to use for future renewals.
-         */
-        metric_old = nm_setting_ip_config_get_route_metric(s_ip_old);
-        metric_new = nm_setting_ip_config_get_route_metric(s_ip_new);
-
-        if (metric_old != metric_new) {
-            if (IS_IPv4) {
-                if (priv->dev_ip_config_4.orig) {
-                    nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev_ip_config_4.orig,
-                                                       nm_device_get_route_metric(self, AF_INET));
-                }
-                if (priv->dev2_ip_config_4.orig) {
-                    nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev2_ip_config_4.orig,
-                                                       nm_device_get_route_metric(self, AF_INET));
-                }
-                if (priv->dhcp_data_4.client) {
-                    nm_dhcp_client_set_route_metric(priv->dhcp_data_4.client,
-                                                    nm_device_get_route_metric(self, AF_INET));
-                }
-            } else {
-                if (priv->ac_ip6_config.orig) {
-                    nm_ip6_config_update_routes_metric((NMIP6Config *) priv->ac_ip6_config.orig,
-                                                       nm_device_get_route_metric(self, AF_INET6));
-                }
-                if (priv->dhcp6.ip6_config.orig) {
-                    nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dhcp6.ip6_config.orig,
-                                                       nm_device_get_route_metric(self, AF_INET6));
-                }
-                if (priv->dev2_ip_config_6.orig) {
-                    nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dev2_ip_config_6.orig,
-                                                       nm_device_get_route_metric(self, AF_INET6));
-                }
-                if (priv->dhcp_data_6.client) {
-                    nm_dhcp_client_set_route_metric(priv->dhcp_data_6.client,
-                                                    nm_device_get_route_metric(self, AF_INET6));
-                }
-            }
-        }
-    }
-
-    if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, addr_family, TRUE)) {
-        _LOGW(LOGD_IPX(IS_IPv4),
-              "Failed to reapply IPv%c configuration",
-              nm_utils_addr_family_to_char(addr_family));
-    }
-}
-
 static void
 _pacrunner_manager_add(NMDevice *self)
 {
@@ -12629,10 +12385,8 @@ _pacrunner_manager_add(NMDevice *self)
     nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id);
 
     priv->pacrunner_conf_id = nm_pacrunner_manager_add(nm_pacrunner_manager_get(),
-                                                       priv->proxy_config,
                                                        nm_device_get_ip_iface(self),
-                                                       NULL,
-                                                       NULL);
+                                                       nm_device_get_l3cd(self, TRUE));
 }
 
 static void
@@ -12640,19 +12394,19 @@ reactivate_proxy_config(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (!priv->pacrunner_conf_id)
-        return;
-    nm_device_set_proxy_config(self, priv->dhcp4.pac_url);
-    _pacrunner_manager_add(self);
+    if (priv->pacrunner_conf_id)
+        _pacrunner_manager_add(self);
 }
 
+/*****************************************************************************/
+
 static gboolean
-can_reapply_change(NMDevice *  self,
+can_reapply_change(NMDevice   *self,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     if (nm_streq(setting_name, NM_SETTING_CONNECTION_SETTING_NAME)) {
         /* Whitelist allowed properties from "connection" setting which are
@@ -12730,20 +12484,19 @@ reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
  * Return: %FALSE if the new configuration can not be reapplied.
  */
 static gboolean
-check_and_reapply_connection(NMDevice *    self,
+check_and_reapply_connection(NMDevice     *self,
                              NMConnection *connection,
                              guint64       version_id,
-                             char **       audit_args,
-                             GError **     error)
-{
-    NMDeviceClass *  klass                      = NM_DEVICE_GET_CLASS(self);
-    NMDevicePrivate *priv                       = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   applied                    = nm_device_get_applied_connection(self);
-    gs_unref_object NMConnection *applied_clone = NULL;
-    gs_unref_hashtable GHashTable *diffs        = NULL;
-    NMConnection *                 con_old, *con_new;
-    NMSettingIPConfig *            s_ip4_old, *s_ip4_new;
-    NMSettingIPConfig *            s_ip6_old, *s_ip6_new;
+                             char        **audit_args,
+                             GError      **error)
+{
+    NMDeviceClass                 *klass         = NM_DEVICE_GET_CLASS(self);
+    NMDevicePrivate               *priv          = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection                  *applied       = nm_device_get_applied_connection(self);
+    gs_unref_object NMConnection  *applied_clone = NULL;
+    gs_unref_hashtable GHashTable *diffs         = NULL;
+    NMConnection                  *con_old;
+    NMConnection                  *con_new;
     GHashTableIter                 iter;
 
     if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) {
@@ -12771,7 +12524,7 @@ check_and_reapply_connection(NMDevice *    self,
      * check for unsupported changes and reject to reapply
      *************************************************************************/
     if (diffs) {
-        char *      setting_name;
+        char       *setting_name;
         GHashTable *setting_diff;
 
         g_hash_table_iter_init(&iter, diffs);
@@ -12792,11 +12545,11 @@ check_and_reapply_connection(NMDevice *    self,
         && version_id
                != nm_active_connection_version_id_get(
                    (NMActiveConnection *) priv->act_request.obj)) {
-        g_set_error_literal(
-            error,
-            NM_DEVICE_ERROR,
-            NM_DEVICE_ERROR_VERSION_ID_MISMATCH,
-            "Reapply failed because device changed in the meantime and the version-id mismatches");
+        g_set_error_literal(error,
+                            NM_DEVICE_ERROR,
+                            NM_DEVICE_ERROR_VERSION_ID_MISMATCH,
+                            "Reapply failed because device changed in the meantime and the "
+                            "version-id mismatches");
         return FALSE;
     }
 
@@ -12814,7 +12567,7 @@ check_and_reapply_connection(NMDevice *    self,
           diffs ? "" : " (unmodified)");
 
     if (diffs) {
-        NMConnection *  connection_clean                    = connection;
+        NMConnection                 *connection_clean      = connection;
         gs_unref_object NMConnection *connection_clean_free = NULL;
 
         {
@@ -12859,11 +12612,9 @@ check_and_reapply_connection(NMDevice *    self,
     } else
         con_old = con_new = applied;
 
-    priv->v4_commit_first_time = TRUE;
-    priv->v6_commit_first_time = TRUE;
-
     priv->v4_route_table_initialized = FALSE;
     priv->v6_route_table_initialized = FALSE;
+    priv->l3config_merge_flags_has   = FALSE;
 
     /**************************************************************************
      * Reapply changes
@@ -12880,20 +12631,21 @@ check_and_reapply_connection(NMDevice *    self,
         lldp_setup(self, NM_TERNARY_DEFAULT);
 
     if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
-        s_ip4_old = nm_connection_get_setting_ip4_config(con_old);
-        s_ip4_new = nm_connection_get_setting_ip4_config(con_new);
-        s_ip6_old = nm_connection_get_setting_ip6_config(con_old);
-        s_ip6_new = nm_connection_get_setting_ip6_config(con_new);
-
         /* Allow reapply of MTU */
         priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
 
-        nm_device_reactivate_ip_config(self, AF_INET, s_ip4_old, s_ip4_new);
-        nm_device_reactivate_ip_config(self, AF_INET6, s_ip6_old, s_ip6_new);
+        if (nm_g_hash_table_lookup(diffs, NM_SETTING_IP4_CONFIG_SETTING_NAME))
+            priv->ip_data_4.do_reapply = TRUE;
+        if (nm_g_hash_table_lookup(diffs, NM_SETTING_IP6_CONFIG_SETTING_NAME))
+            priv->ip_data_6.do_reapply = TRUE;
+
+        nm_device_activate_schedule_stage3_ip_config(self, FALSE);
 
         _routing_rules_sync(self, NM_TERNARY_TRUE);
 
         reactivate_proxy_config(self);
+
+        nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
     }
 
     if (priv->state >= NM_DEVICE_STATE_IP_CHECK)
@@ -12919,17 +12671,17 @@ typedef struct {
 } ReapplyData;
 
 static void
-reapply_cb(NMDevice *             self,
+reapply_cb(NMDevice              *self,
            GDBusMethodInvocation *context,
-           NMAuthSubject *        subject,
-           GError *               error,
+           NMAuthSubject         *subject,
+           GError                *error,
            gpointer               user_data)
 {
-    ReapplyData *   reapply_data             = user_data;
-    guint64         version_id               = 0;
-    gs_unref_object NMConnection *connection = NULL;
-    GError *                      local      = NULL;
-    gs_free char *                audit_args = NULL;
+    ReapplyData                  *reapply_data = user_data;
+    guint64                       version_id   = 0;
+    gs_unref_object NMConnection *connection   = NULL;
+    GError                       *local        = NULL;
+    gs_free char                 *audit_args   = NULL;
 
     if (reapply_data) {
         connection = reapply_data->connection;
@@ -12972,20 +12724,20 @@ reapply_cb(NMDevice *             self,
 }
 
 static void
-impl_device_reapply(NMDBusObject *                     obj,
+impl_device_reapply(NMDBusObject                      *obj,
                     const NMDBusInterfaceInfoExtended *interface_info,
-                    const NMDBusMethodInfoExtended *   method_info,
-                    GDBusConnection *                  dbus_connection,
-                    const char *                       sender,
-                    GDBusMethodInvocation *            invocation,
-                    GVariant *                         parameters)
-{
-    NMDevice *            self = NM_DEVICE(obj);
-    NMDevicePrivate *     priv = NM_DEVICE_GET_PRIVATE(self);
-    NMSettingsConnection *settings_connection;
-    NMConnection *        connection = NULL;
-    GError *              error      = NULL;
-    ReapplyData *         reapply_data;
+                    const NMDBusMethodInfoExtended    *method_info,
+                    GDBusConnection                   *dbus_connection,
+                    const char                        *sender,
+                    GDBusMethodInvocation             *invocation,
+                    GVariant                          *parameters)
+{
+    NMDevice                  *self = NM_DEVICE(obj);
+    NMDevicePrivate           *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMSettingsConnection      *settings_connection;
+    NMConnection              *connection = NULL;
+    GError                    *error      = NULL;
+    ReapplyData               *reapply_data;
     gs_unref_variant GVariant *settings = NULL;
     guint64                    version_id;
     guint32                    flags;
@@ -13063,20 +12815,20 @@ impl_device_reapply(NMDBusObject *                     obj,
 /*****************************************************************************/
 
 static void
-impl_device_get_applied_connection(NMDBusObject *                     obj,
+impl_device_get_applied_connection(NMDBusObject                      *obj,
                                    const NMDBusInterfaceInfoExtended *interface_info,
-                                   const NMDBusMethodInfoExtended *   method_info,
-                                   GDBusConnection *                  connection,
-                                   const char *                       sender,
-                                   GDBusMethodInvocation *            invocation,
-                                   GVariant *                         parameters)
-{
-    NMDevice *       self       = NM_DEVICE(obj);
-    NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
+                                   const NMDBusMethodInfoExtended    *method_info,
+                                   GDBusConnection                   *connection,
+                                   const char                        *sender,
+                                   GDBusMethodInvocation             *invocation,
+                                   GVariant                          *parameters)
+{
+    NMDevice             *self  = NM_DEVICE(obj);
+    NMDevicePrivate      *priv  = NM_DEVICE_GET_PRIVATE(self);
     gs_free_error GError *error = NULL;
-    NMConnection *        applied_connection;
+    NMConnection         *applied_connection;
     guint32               flags;
-    GVariant *            var_settings;
+    GVariant             *var_settings;
 
     g_variant_get(parameters, "(u)", &flags);
 
@@ -13123,105 +12875,15 @@ impl_device_get_applied_connection(NMDBusObject *                     obj,
 
 /*****************************************************************************/
 
-typedef struct {
-    gint64 timestamp_ms;
-    bool   dirty;
-} IP6RoutesTemporaryNotAvailableData;
-
-static gboolean
-_rt6_temporary_not_available_timeout(gpointer user_data)
-{
-    NMDevice *       self = NM_DEVICE(user_data);
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    priv->rt6_temporary_not_available_id = 0;
-    nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-
-    return G_SOURCE_REMOVE;
-}
-
-static gboolean
-_rt6_temporary_not_available_set(NMDevice *self, GPtrArray *temporary_not_available)
-{
-    NMDevicePrivate *                   priv = NM_DEVICE_GET_PRIVATE(self);
-    IP6RoutesTemporaryNotAvailableData *data;
-    GHashTableIter                      iter;
-    gint64                              now_ms, oldest_ms;
-    const gint64                        MAX_AGE_MS = 20000;
-    guint                               i;
-    gboolean                            success = TRUE;
-
-    if (!temporary_not_available || !temporary_not_available->len) {
-        /* nothing outstanding. Clear tracking the routes. */
-        nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
-        nm_clear_g_source(&priv->rt6_temporary_not_available_id);
-        return success;
-    }
-
-    if (priv->rt6_temporary_not_available) {
-        g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available);
-        while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data))
-            data->dirty = TRUE;
-    } else {
-        priv->rt6_temporary_not_available =
-            g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
-                                  (GEqualFunc) nmp_object_id_equal,
-                                  (GDestroyNotify) nmp_object_unref,
-                                  nm_g_slice_free_fcn(IP6RoutesTemporaryNotAvailableData));
-    }
-
-    now_ms    = nm_utils_get_monotonic_timestamp_msec();
-    oldest_ms = now_ms;
-
-    for (i = 0; i < temporary_not_available->len; i++) {
-        const NMPObject *o = temporary_not_available->pdata[i];
-
-        data = g_hash_table_lookup(priv->rt6_temporary_not_available, o);
-        if (data) {
-            if (!data->dirty)
-                continue;
-            data->dirty = FALSE;
-            nm_assert(data->timestamp_ms > 0 && data->timestamp_ms <= now_ms);
-            if (now_ms > data->timestamp_ms + MAX_AGE_MS) {
-                /* timeout. Could not add this address. */
-                _LOGW(LOGD_DEVICE,
-                      "failure to add IPv6 route: %s",
-                      nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-                success = FALSE;
-            } else
-                oldest_ms = MIN(data->timestamp_ms, oldest_ms);
-            continue;
-        }
-
-        data               = g_slice_new0(IP6RoutesTemporaryNotAvailableData);
-        data->timestamp_ms = now_ms;
-        g_hash_table_insert(priv->rt6_temporary_not_available, (gpointer) nmp_object_ref(o), data);
-    }
-
-    g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available);
-    while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data)) {
-        if (data->dirty)
-            g_hash_table_iter_remove(&iter);
-    }
-
-    nm_clear_g_source(&priv->rt6_temporary_not_available_id);
-    priv->rt6_temporary_not_available_id =
-        g_timeout_add(oldest_ms + MAX_AGE_MS - now_ms, _rt6_temporary_not_available_timeout, self);
-
-    return success;
-}
-
-/*****************************************************************************/
-
 static void
-disconnect_cb(NMDevice *             self,
+disconnect_cb(NMDevice              *self,
               GDBusMethodInvocation *context,
-              NMAuthSubject *        subject,
-              GError *               error,
+              NMAuthSubject         *subject,
+              GError                *error,
               gpointer               user_data)
 {
     NMDevicePrivate *priv  = NM_DEVICE_GET_PRIVATE(self);
-    GError *         local = NULL;
+    GError          *local = NULL;
 
     if (error) {
         g_dbus_method_invocation_return_gerror(context, error);
@@ -13269,17 +12931,17 @@ _clear_queued_act_request(NMDevicePrivate *priv, NMActiveConnectionStateReason a
 }
 
 static void
-impl_device_disconnect(NMDBusObject *                     obj,
+impl_device_disconnect(NMDBusObject                      *obj,
                        const NMDBusInterfaceInfoExtended *interface_info,
-                       const NMDBusMethodInfoExtended *   method_info,
-                       GDBusConnection *                  dbus_connection,
-                       const char *                       sender,
-                       GDBusMethodInvocation *            invocation,
-                       GVariant *                         parameters)
+                       const NMDBusMethodInfoExtended    *method_info,
+                       GDBusConnection                   *dbus_connection,
+                       const char                        *sender,
+                       GDBusMethodInvocation             *invocation,
+                       GVariant                          *parameters)
 {
-    NMDevice *       self = NM_DEVICE(obj);
+    NMDevice        *self = NM_DEVICE(obj);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
+    NMConnection    *connection;
 
     if (!priv->act_request.obj) {
         g_dbus_method_invocation_return_error_literal(invocation,
@@ -13303,10 +12965,10 @@ impl_device_disconnect(NMDBusObject *                     obj,
 }
 
 static void
-delete_cb(NMDevice *             self,
+delete_cb(NMDevice              *self,
           GDBusMethodInvocation *context,
-          NMAuthSubject *        subject,
-          GError *               error,
+          NMAuthSubject         *subject,
+          GError                *error,
           gpointer               user_data)
 {
     GError *local = NULL;
@@ -13331,13 +12993,13 @@ delete_cb(NMDevice *             self,
 }
 
 static void
-impl_device_delete(NMDBusObject *                     obj,
+impl_device_delete(NMDBusObject                      *obj,
                    const NMDBusInterfaceInfoExtended *interface_info,
-                   const NMDBusMethodInfoExtended *   method_info,
-                   GDBusConnection *                  connection,
-                   const char *                       sender,
-                   GDBusMethodInvocation *            invocation,
-                   GVariant *                         parameters)
+                   const NMDBusMethodInfoExtended    *method_info,
+                   GDBusConnection                   *connection,
+                   const char                        *sender,
+                   GDBusMethodInvocation             *invocation,
+                   GVariant                          *parameters)
 {
     NMDevice *self = NM_DEVICE(obj);
 
@@ -13428,7 +13090,7 @@ static gboolean
 _carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *   connection;
+    NMConnection    *connection;
 
     /* If we have carrier or if we are not waiting for it, the activation
      * request is not blocked waiting for carrier. */
@@ -13477,11 +13139,11 @@ _carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req)
 }
 
 void
-nm_device_disconnect_active_connection(NMActiveConnection *          active,
+nm_device_disconnect_active_connection(NMActiveConnection           *active,
                                        NMDeviceStateReason           device_reason,
                                        NMActiveConnectionStateReason active_reason)
 {
-    NMDevice *       self;
+    NMDevice        *self;
     NMDevicePrivate *priv;
 
     g_return_if_fail(NM_IS_ACTIVE_CONNECTION(active));
@@ -13592,324 +13254,44 @@ nm_device_is_activating(NMDevice *self)
      * handler is actually run.  If there's an activation handler scheduled
      * we're activating anyway.
      */
-    return priv->activation_source_id_4 != 0;
+    return !!priv->activation_idle_source;
 }
 
-NMProxyConfig *
-nm_device_get_proxy_config(NMDevice *self)
-{
-    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
-
-    return NM_DEVICE_GET_PRIVATE(self)->proxy_config;
-}
-
-static void
-nm_device_set_proxy_config(NMDevice *self, const char *pac_url)
-{
-    NMDevicePrivate *priv;
-    NMConnection *   connection;
-    NMSettingProxy * s_proxy = NULL;
-
-    g_return_if_fail(NM_IS_DEVICE(self));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    g_clear_object(&priv->proxy_config);
-    priv->proxy_config = nm_proxy_config_new();
-
-    if (pac_url) {
-        nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_AUTO);
-        nm_proxy_config_set_pac_url(priv->proxy_config, pac_url);
-        _LOGD(LOGD_PROXY, "proxy: PAC url \"%s\"", pac_url);
-    } else
-        nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_NONE);
-
-    connection = nm_device_get_applied_connection(self);
-    if (connection)
-        s_proxy = nm_connection_get_setting_proxy(connection);
-
-    if (s_proxy)
-        nm_proxy_config_merge_setting(priv->proxy_config, s_proxy);
-}
-
-/* IP Configuration stuff */
 NMDhcpConfig *
 nm_device_get_dhcp_config(NMDevice *self, int addr_family)
 {
-    const int IS_IPv4 = NM_IS_IPv4(addr_family);
-
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
 
-    nm_assert_addr_family(addr_family);
-
-    return NM_DEVICE_GET_PRIVATE(self)->dhcp_data_x[IS_IPv4].config;
+    return NM_DEVICE_GET_PRIVATE(self)->ipdhcp_data_x[NM_IS_IPv4(addr_family)].config;
 }
 
-NMIP4Config *
-nm_device_get_ip4_config(NMDevice *self)
+NML3Cfg *
+nm_device_get_l3cfg(NMDevice *self)
 {
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
 
-    return NM_DEVICE_GET_PRIVATE(self)->ip_config_4;
+    return NM_DEVICE_GET_PRIVATE(self)->l3cfg;
 }
 
-static gboolean
-nm_device_set_ip_config(NMDevice *  self,
-                        int         addr_family,
-                        NMIPConfig *new_config,
-                        gboolean    commit,
-                        GPtrArray * ip4_dev_route_blacklist)
-{
-    NMDevicePrivate *      priv    = NM_DEVICE_GET_PRIVATE(self);
-    const int              IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMIPConfig *           old_config;
-    gboolean               has_changes = FALSE;
-    gboolean               success     = TRUE;
-    NMSettingsConnection * settings_connection;
-    NMIPRouteTableSyncMode route_table_sync_mode;
-
-    nm_assert_addr_family(addr_family);
-    nm_assert(!new_config || nm_ip_config_get_addr_family(new_config) == addr_family);
-    nm_assert(!new_config
-              || (new_config && ({
-                      int ip_ifindex = nm_device_get_ip_ifindex(self);
-
-                      (ip_ifindex > 0 && ip_ifindex == nm_ip_config_get_ifindex(new_config));
-                  })));
-    nm_assert(IS_IPv4 || !ip4_dev_route_blacklist);
-
-    if (commit && new_config)
-        route_table_sync_mode = _get_route_table_sync_mode_stateful(self, addr_family);
-    else
-        route_table_sync_mode = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE;
-
-    _LOGD(LOGD_IPX(IS_IPv4),
-          "ip%c-config: update (commit=%d, new-config=" NM_HASH_OBFUSCATE_PTR_FMT
-          ", route-table-sync-mode=%d)",
-          nm_utils_addr_family_to_char(addr_family),
-          commit,
-          NM_HASH_OBFUSCATE_PTR(new_config),
-          (int) route_table_sync_mode);
-
-    /* Always commit to nm-platform to update lifetimes */
-    if (commit && new_config) {
-        _commit_mtu(self, IS_IPv4 ? NM_IP4_CONFIG(new_config) : priv->ip_config_4);
-
-        if (IS_IPv4) {
-            success = nm_ip4_config_commit(NM_IP4_CONFIG(new_config),
-                                           nm_device_get_platform(self),
-                                           route_table_sync_mode);
-            nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self),
-                                                    nm_ip_config_get_ifindex(new_config),
-                                                    ip4_dev_route_blacklist);
-        } else {
-            gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
-
-            success = nm_ip6_config_commit(NM_IP6_CONFIG(new_config),
-                                           nm_device_get_platform(self),
-                                           route_table_sync_mode,
-                                           &temporary_not_available);
-
-            if (!_rt6_temporary_not_available_set(self, temporary_not_available))
-                success = FALSE;
-        }
-    }
-
-    old_config = priv->ip_config_x[IS_IPv4];
-
-    if (new_config && old_config) {
-        /* has_changes is set only on relevant changes, because when the configuration changes,
-         * this causes a re-read and reset. This should only happen for relevant changes */
-        nm_ip_config_replace(old_config, new_config, &has_changes);
-        if (has_changes) {
-            _LOGD(LOGD_IPX(IS_IPv4),
-                  "ip%c-config: update IP Config instance (%s)",
-                  nm_utils_addr_family_to_char(addr_family),
-                  nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
-        }
-    } else if (new_config /*&& !old_config*/) {
-        has_changes                = TRUE;
-        priv->ip_config_x[IS_IPv4] = g_object_ref(new_config);
-        if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(new_config)))
-            nm_dbus_object_export(NM_DBUS_OBJECT(new_config));
-
-        _LOGD(LOGD_IPX(IS_IPv4),
-              "ip%c-config: set IP Config instance (%s)",
-              nm_utils_addr_family_to_char(addr_family),
-              nm_dbus_object_get_path(NM_DBUS_OBJECT(new_config)));
-    } else if (old_config /*&& !new_config*/) {
-        has_changes                = TRUE;
-        priv->ip_config_x[IS_IPv4] = NULL;
-        _LOGD(LOGD_IPX(IS_IPv4),
-              "ip%c-config: clear IP Config instance (%s)",
-              nm_utils_addr_family_to_char(addr_family),
-              nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
-        if (IS_IPv4) {
-            /* Device config is invalid if combined config is invalid */
-            applied_config_clear(&priv->dev_ip_config_4);
-        } else
-            priv->needs_ip6_subnet = FALSE;
-    }
-
-    if (has_changes) {
-        if (old_config != priv->ip_config_x[IS_IPv4])
-            _notify(self, IS_IPv4 ? PROP_IP4_CONFIG : PROP_IP6_CONFIG);
-
-        g_signal_emit(self,
-                      signals[IS_IPv4 ? IP4_CONFIG_CHANGED : IP6_CONFIG_CHANGED],
-                      0,
-                      priv->ip_config_x[IS_IPv4],
-                      old_config);
-
-        if (old_config != priv->ip_config_x[IS_IPv4])
-            nm_dbus_object_clear_and_unexport(&old_config);
-
-        if (nm_device_sys_iface_state_is_external(self)
-            && (settings_connection = nm_device_get_settings_connection(self))
-            && NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection),
-                            NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)
-            && nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(priv->act_request.obj))
-                   == NM_ACTIVATION_TYPE_EXTERNAL) {
-            gs_unref_object NMConnection *new_connection = NULL;
-
-            new_connection = nm_simple_connection_new_clone(
-                nm_settings_connection_get_connection(settings_connection));
-
-            nm_connection_add_setting(
-                new_connection,
-                IS_IPv4 ? nm_ip4_config_create_setting(priv->ip_config_4)
-                        : nm_ip6_config_create_setting(priv->ip_config_6,
-                                                       _get_maybe_ipv6_disabled(self)));
-
-            nm_settings_connection_update(settings_connection,
-                                          new_connection,
-                                          NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
-                                          NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
-                                          NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
-                                          NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
-                                          "update-external",
-                                          NULL);
-        }
-
-        nm_device_queue_recheck_assume(self);
-
-        if (!IS_IPv4) {
-            if (priv->ndisc)
-                ndisc_set_router_config(priv->ndisc, self);
-        }
-    }
-
-    nm_assert(!old_config || old_config == priv->ip_config_x[IS_IPv4]);
-
-    return success;
-}
-
-static gboolean
-_replace_vpn_config_in_list(GSList **plist, GObject *old, GObject *new)
-{
-    GSList *old_link;
-
-    /* Below, assert that @new is not yet tracked, but still behave
-     * correctly in any case. Don't complain for missing @old since
-     * it could have been removed when the parent device became
-     * unmanaged. */
-
-    if (old && (old_link = g_slist_find(*plist, old))) {
-        if (old != new) {
-            if (new)
-                old_link->data = g_object_ref(new);
-            else
-                *plist = g_slist_delete_link(*plist, old_link);
-            g_object_unref(old);
-        }
-        return TRUE;
-    }
-
-    if (new) {
-        if (!g_slist_find(*plist, new))
-            *plist = g_slist_append(*plist, g_object_ref(new));
-        else
-            g_return_val_if_reached(TRUE);
-        return TRUE;
-    }
-
-    return FALSE;
-}
-
-void
-nm_device_replace_vpn4_config(NMDevice *self, NMIP4Config *old, NMIP4Config *config)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    nm_assert(!old || NM_IS_IP4_CONFIG(old));
-    nm_assert(!config || NM_IS_IP4_CONFIG(config));
-    nm_assert(!old || nm_ip4_config_get_ifindex(old) == nm_device_get_ip_ifindex(self));
-    nm_assert(!config || nm_ip4_config_get_ifindex(config) == nm_device_get_ip_ifindex(self));
-
-    if (!_replace_vpn_config_in_list(&priv->vpn_configs_4, (GObject *) old, (GObject *) config))
-        return;
-
-    /* NULL to use existing configs */
-    if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
-        _LOGW(LOGD_IP4, "failed to set VPN routes for device");
-}
-
-void
-nm_device_set_dev2_ip_config(NMDevice *self, int addr_family, NMIPConfig *config)
+const NML3ConfigData *
+nm_device_get_l3cd(NMDevice *self, gboolean get_commited)
 {
     NMDevicePrivate *priv;
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
 
-    g_return_if_fail(NM_IS_DEVICE(self));
-    g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
-    g_return_if_fail(!config || nm_ip_config_get_addr_family(config) == addr_family);
+    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
-    applied_config_init(&priv->dev2_ip_config_x[IS_IPv4], config);
-    if (!ip_config_merge_and_apply(self, addr_family, TRUE)) {
-        _LOGW(LOGD_IP,
-              "failed to set extra device IPv%c configuration",
-              nm_utils_addr_family_to_char(addr_family));
-    }
-}
-
-void
-nm_device_replace_vpn6_config(NMDevice *self, NMIP6Config *old, NMIP6Config *config)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMDeviceState    state;
-
-    nm_assert(!old || NM_IS_IP6_CONFIG(old));
-    nm_assert(!old || nm_ip6_config_get_ifindex(old) > 0);
-    nm_assert(!old || nm_device_get_ip_ifindex(self) == 0
-              || nm_device_get_ip_ifindex(self) == nm_ip6_config_get_ifindex(old));
-    nm_assert(!config || NM_IS_IP6_CONFIG(config));
-    nm_assert(!config || nm_ip6_config_get_ifindex(config) > 0);
-    nm_assert(!config || nm_device_get_ip_ifindex(self) == nm_ip6_config_get_ifindex(config));
-
-    if (!_replace_vpn_config_in_list(&priv->vpn_configs_6, (GObject *) old, (GObject *) config))
-        return;
-
-    state = nm_device_get_state(self);
-    if (state >= NM_DEVICE_STATE_IP_CONFIG && state <= NM_DEVICE_STATE_ACTIVATED) {
-        if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-            _LOGW(LOGD_IP6, "failed to set VPN routes for device");
-    }
-}
-
-NMIP6Config *
-nm_device_get_ip6_config(NMDevice *self)
-{
-    g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
+    if (!priv->l3cfg)
+        return NULL;
 
-    return NM_DEVICE_GET_PRIVATE(self)->ip_config_6;
+    return nm_l3cfg_get_combined_l3cd(priv->l3cfg, get_commited);
 }
 
 /*****************************************************************************/
 
 static gboolean
-dispatcher_cleanup(NMDevice *self)
+_dispatcher_cleanup(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -13923,9 +13305,9 @@ dispatcher_cleanup(NMDevice *self)
 }
 
 static void
-dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data)
+_dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     g_return_if_fail(call_id == priv->dispatcher.call_id);
@@ -13943,7 +13325,7 @@ ip_check_pre_up(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (dispatcher_cleanup(self))
+    if (_dispatcher_cleanup(self))
         nm_assert_not_reached();
 
     priv->dispatcher.post_state        = NM_DEVICE_STATE_SECONDARIES;
@@ -13951,11 +13333,11 @@ ip_check_pre_up(NMDevice *self)
     if (!nm_dispatcher_call_device(NM_DISPATCHER_ACTION_PRE_UP,
                                    self,
                                    NULL,
-                                   dispatcher_complete_proceed_state,
+                                   _dispatcher_complete_proceed_state,
                                    self,
                                    &priv->dispatcher.call_id)) {
         /* Just proceed on errors */
-        dispatcher_complete_proceed_state(0, self);
+        _dispatcher_complete_proceed_state(0, self);
     }
 }
 
@@ -13985,10 +13367,10 @@ ip_check_gw_ping_cleanup(NMDevice *self)
 static gboolean
 spawn_ping(NMDevice *self)
 {
-    NMDevicePrivate *priv        = NM_DEVICE_GET_PRIVATE(self);
-    gs_free char *   str_timeout = NULL;
-    gs_free char *   tmp_str     = NULL;
-    const char *     args[]      = {priv->gw_ping.binary,
+    NMDevicePrivate      *priv        = NM_DEVICE_GET_PRIVATE(self);
+    gs_free char         *str_timeout = NULL;
+    gs_free char         *tmp_str     = NULL;
+    const char           *args[]      = {priv->gw_ping.binary,
                           "-I",
                           nm_device_get_ip_iface(self),
                           "-c",
@@ -13997,7 +13379,7 @@ spawn_ping(NMDevice *self)
                           NULL,
                           priv->gw_ping.address,
                           NULL};
-    gs_free_error GError *error  = NULL;
+    gs_free_error GError *error       = NULL;
     gboolean              ret;
 
     args[6] = str_timeout = g_strdup_printf("%u", priv->gw_ping.deadline);
@@ -14026,7 +13408,7 @@ spawn_ping(NMDevice *self)
 static gboolean
 respawn_ping_cb(gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     priv->gw_ping.watch = 0;
@@ -14044,7 +13426,7 @@ respawn_ping_cb(gpointer user_data)
 static void
 ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
 {
-    NMDevice *       self       = NM_DEVICE(user_data);
+    NMDevice        *self       = NM_DEVICE(user_data);
     NMDevicePrivate *priv       = NM_DEVICE_GET_PRIVATE(self);
     NMLogDomain      log_domain = priv->gw_ping.log_domain;
     gboolean         success    = FALSE;
@@ -14078,7 +13460,7 @@ ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
 static gboolean
 ip_check_ping_timeout_cb(gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     priv->gw_ping.timeout = 0;
@@ -14091,7 +13473,7 @@ ip_check_ping_timeout_cb(gpointer user_data)
 }
 
 static gboolean
-start_ping(NMDevice *  self,
+start_ping(NMDevice   *self,
            NMLogDomain log_domain,
            const char *binary,
            const char *address,
@@ -14120,11 +13502,11 @@ start_ping(NMDevice *  self,
 static void
 nm_device_start_ip_check(NMDevice *self)
 {
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
-    NMConnection *       connection;
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMConnection        *connection;
     NMSettingConnection *s_con;
     guint                timeout     = 0;
-    const char *         ping_binary = NULL;
+    const char          *ping_binary = NULL;
     char                 buf[NM_UTILS_INET_ADDRSTRLEN];
     NMLogDomain          log_domain = LOGD_IP4;
 
@@ -14135,8 +13517,8 @@ nm_device_start_ip_check(NMDevice *self)
     g_return_if_fail(!priv->gw_ping.watch);
     g_return_if_fail(!priv->gw_ping.timeout);
     g_return_if_fail(!priv->gw_ping.pid);
-    g_return_if_fail(priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
-                     || priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE);
+    g_return_if_fail(priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
+                     || priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY);
 
     connection = nm_device_get_applied_connection(self);
     g_assert(connection);
@@ -14147,17 +13529,21 @@ nm_device_start_ip_check(NMDevice *self)
 
     buf[0] = '\0';
     if (timeout) {
-        const NMPObject *gw;
+        const NMPObject      *gw;
+        const NML3ConfigData *l3cd;
 
-        if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
-            gw = nm_ip4_config_best_default_route_get(priv->ip_config_4);
+        l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
+        if (!l3cd) {
+            /* pass */
+        } else if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY) {
+            gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET);
             if (gw) {
                 _nm_utils_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway, buf);
                 ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL);
                 log_domain  = LOGD_IP4;
             }
-        } else if (priv->ip_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
-            gw = nm_ip6_config_best_default_route_get(priv->ip_config_6);
+        } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY) {
+            gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET6);
             if (gw) {
                 _nm_utils_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway, buf);
                 ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL);
@@ -14179,7 +13565,7 @@ nm_device_start_ip_check(NMDevice *self)
 static gboolean
 carrier_wait_timeout(gpointer user_data)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
+    NMDevice        *self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     priv->carrier_wait_id = 0;
@@ -14216,7 +13602,7 @@ _get_carrier_wait_ms(NMDevice *self)
 gboolean
 nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
 {
-    NMDevicePrivate *    priv         = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate     *priv         = NM_DEVICE_GET_PRIVATE(self);
     gboolean             device_is_up = FALSE;
     NMDeviceCapabilities capabilities;
     int                  ifindex;
@@ -14297,15 +13683,7 @@ nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
     /* Can only get HW address of some devices when they are up */
     nm_device_update_hw_address(self);
 
-    /* when the link comes up, we must restore IP configuration if necessary. */
-    if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
-        if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
-            _LOGW(LOGD_IP4, "failed applying IP4 config after bringing link up");
-    }
-    if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
-        if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
-            _LOGW(LOGD_IP6, "failed applying IP6 config after bringing link up");
-    }
+    _dev_l3_cfg_commit(self, TRUE);
 
     return TRUE;
 }
@@ -14368,381 +13746,6 @@ nm_device_get_firmware_missing(NMDevice *self)
     return NM_DEVICE_GET_PRIVATE(self)->firmware_missing;
 }
 
-static void
-intersect_ext_config(NMDevice *     self,
-                     AppliedConfig *config,
-                     gboolean       intersect_addresses,
-                     gboolean       intersect_routes)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMIPConfig *     ext;
-    guint32          penalty;
-    int              family;
-
-    if (!config->orig)
-        return;
-
-    family  = nm_ip_config_get_addr_family(config->orig);
-    penalty = default_route_metric_penalty_get(self, family);
-    ext     = family == AF_INET ? (NMIPConfig *) priv->ext_ip_config_4
-                                : (NMIPConfig *) priv->ext_ip_config_6;
-
-    if (config->current) {
-        nm_ip_config_intersect(config->current,
-                               ext,
-                               intersect_addresses,
-                               intersect_routes,
-                               penalty);
-    } else {
-        config->current = nm_ip_config_intersect_alloc(config->orig,
-                                                       ext,
-                                                       intersect_addresses,
-                                                       intersect_routes,
-                                                       penalty);
-    }
-}
-
-static gboolean
-update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    int              ifindex;
-    GSList *         iter;
-    gboolean         is_up;
-
-    nm_assert_addr_family(addr_family);
-
-    ifindex = nm_device_get_ip_ifindex(self);
-    if (!ifindex)
-        return FALSE;
-
-    is_up = nm_platform_link_is_up(nm_device_get_platform(self), ifindex);
-
-    if (NM_IS_IPv4(addr_family)) {
-        g_clear_object(&priv->ext_ip_config_4);
-        priv->ext_ip_config_4 = nm_ip4_config_capture(nm_device_get_multi_index(self),
-                                                      nm_device_get_platform(self),
-                                                      ifindex);
-        if (priv->ext_ip_config_4) {
-            if (intersect_configs) {
-                /* This function was called upon external changes. Remove the configuration
-                 * (addresses,routes) that is no longer present externally from the internal
-                 * config. This way, we don't re-add addresses that were manually removed
-                 * by the user. */
-                if (priv->con_ip_config_4) {
-                    nm_ip4_config_intersect(priv->con_ip_config_4,
-                                            priv->ext_ip_config_4,
-                                            TRUE,
-                                            is_up,
-                                            default_route_metric_penalty_get(self, AF_INET));
-                }
-
-                intersect_ext_config(self, &priv->dev_ip_config_4, TRUE, is_up);
-                intersect_ext_config(self, &priv->dev2_ip_config_4, TRUE, is_up);
-
-                for (iter = priv->vpn_configs_4; iter; iter = iter->next)
-                    nm_ip4_config_intersect(iter->data, priv->ext_ip_config_4, TRUE, is_up, 0);
-            }
-
-            /* Remove parts from ext_ip_config_4 to only contain the information that
-             * was configured externally -- we already have the same configuration from
-             * internal origins. */
-            if (priv->con_ip_config_4) {
-                nm_ip4_config_subtract(priv->ext_ip_config_4,
-                                       priv->con_ip_config_4,
-                                       default_route_metric_penalty_get(self, AF_INET));
-            }
-            if (applied_config_get_current(&priv->dev_ip_config_4)) {
-                nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4,
-                                      applied_config_get_current(&priv->dev_ip_config_4),
-                                      default_route_metric_penalty_get(self, AF_INET));
-            }
-            if (applied_config_get_current(&priv->dev2_ip_config_4)) {
-                nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4,
-                                      applied_config_get_current(&priv->dev2_ip_config_4),
-                                      default_route_metric_penalty_get(self, AF_INET));
-            }
-            for (iter = priv->vpn_configs_4; iter; iter = iter->next)
-                nm_ip4_config_subtract(priv->ext_ip_config_4, iter->data, 0);
-        }
-
-    } else {
-        nm_assert(!NM_IS_IPv4(addr_family));
-
-        g_clear_object(&priv->ext_ip_config_6);
-        g_clear_object(&priv->ext_ip6_config_captured);
-        priv->ext_ip6_config_captured =
-            nm_ip6_config_capture(nm_device_get_multi_index(self),
-                                  nm_device_get_platform(self),
-                                  ifindex,
-                                  NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
-        if (priv->ext_ip6_config_captured) {
-            priv->ext_ip_config_6 = nm_ip6_config_new_cloned(priv->ext_ip6_config_captured);
-
-            if (intersect_configs) {
-                /* This function was called upon external changes. Remove the configuration
-                 * (addresses,routes) that is no longer present externally from the internal
-                 * config. This way, we don't re-add addresses that were manually removed
-                 * by the user. */
-                if (priv->con_ip_config_6) {
-                    nm_ip6_config_intersect(priv->con_ip_config_6,
-                                            priv->ext_ip_config_6,
-                                            is_up,
-                                            is_up,
-                                            default_route_metric_penalty_get(self, AF_INET6));
-                }
-
-                intersect_ext_config(self, &priv->ac_ip6_config, is_up, is_up);
-                intersect_ext_config(self, &priv->dhcp6.ip6_config, is_up, is_up);
-                intersect_ext_config(self, &priv->dev2_ip_config_6, is_up, is_up);
-
-                for (iter = priv->vpn_configs_6; iter; iter = iter->next)
-                    nm_ip6_config_intersect(iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
-
-                if (is_up && priv->ipv6ll_has
-                    && !nm_ip6_config_lookup_address(priv->ext_ip_config_6, &priv->ipv6ll_addr))
-                    priv->ipv6ll_has = FALSE;
-            }
-
-            /* Remove parts from ext_ip_config_6 to only contain the information that
-             * was configured externally -- we already have the same configuration from
-             * internal origins. */
-            if (priv->con_ip_config_6) {
-                nm_ip6_config_subtract(priv->ext_ip_config_6,
-                                       priv->con_ip_config_6,
-                                       default_route_metric_penalty_get(self, AF_INET6));
-            }
-            if (applied_config_get_current(&priv->ac_ip6_config)) {
-                nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
-                                      applied_config_get_current(&priv->ac_ip6_config),
-                                      default_route_metric_penalty_get(self, AF_INET6));
-            }
-            if (applied_config_get_current(&priv->dhcp6.ip6_config)) {
-                nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
-                                      applied_config_get_current(&priv->dhcp6.ip6_config),
-                                      default_route_metric_penalty_get(self, AF_INET6));
-            }
-            if (applied_config_get_current(&priv->dev2_ip_config_6)) {
-                nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
-                                      applied_config_get_current(&priv->dev2_ip_config_6),
-                                      default_route_metric_penalty_get(self, AF_INET6));
-            }
-            for (iter = priv->vpn_configs_6; iter; iter = iter->next)
-                nm_ip6_config_subtract(priv->ext_ip_config_6, iter->data, 0);
-        }
-    }
-
-    return TRUE;
-}
-
-static void
-update_ip_config(NMDevice *self, int addr_family)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    nm_assert_addr_family(addr_family);
-
-    if (NM_IS_IPv4(addr_family))
-        priv->update_ip_config_completed_v4 = TRUE;
-    else
-        priv->update_ip_config_completed_v6 = TRUE;
-
-    if (update_ext_ip_config(self, addr_family, TRUE)) {
-        if (NM_IS_IPv4(addr_family)) {
-            if (priv->ext_ip_config_4)
-                ip_config_merge_and_apply(self, AF_INET, FALSE);
-        } else {
-            if (priv->ext_ip6_config_captured)
-                ip_config_merge_and_apply(self, AF_INET6, FALSE);
-        }
-    }
-}
-
-void
-nm_device_capture_initial_config(NMDevice *self)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (!priv->update_ip_config_completed_v4)
-        update_ip_config(self, AF_INET);
-    if (!priv->update_ip_config_completed_v6)
-        update_ip_config(self, AF_INET6);
-}
-
-static gboolean
-queued_ip_config_change(NMDevice *self, int addr_family)
-{
-    NMDevicePrivate *priv;
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-
-    g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    /* Wait for any queued state changes */
-    if (priv->queued_state.id)
-        return G_SOURCE_CONTINUE;
-
-    /* If a commit is scheduled, this function would potentially interfere with
-     * it changing IP configurations before they are applied. Postpone the
-     * update in such case.
-     */
-    if (priv->activation_source_id_x[IS_IPv4] != 0
-        && priv->activation_source_func_x[IS_IPv4]
-               == activate_stage5_ip_config_result_x_fcn(addr_family))
-        return G_SOURCE_CONTINUE;
-
-    priv->queued_ip_config_id_x[IS_IPv4] = 0;
-
-    update_ip_config(self, addr_family);
-
-    if (!IS_IPv4) {
-        /* Check whether we need to complete waiting for link-local.
-         * We are also called from an idle handler, so no problem doing state transitions
-         * now. */
-        linklocal6_check_complete(self);
-    }
-
-    if (!IS_IPv4) {
-        NMPlatform *          platform;
-        GSList *              dad6_failed_addrs, *iter;
-        const NMPlatformLink *pllink;
-
-        dad6_failed_addrs = g_steal_pointer(&priv->dad6_failed_addrs);
-
-        if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING
-            && priv->ifindex > 0 && !nm_device_sys_iface_state_is_external(self)
-            && (platform = nm_device_get_platform(self))
-            && (pllink = nm_platform_link_get(platform, priv->ifindex))
-            && (pllink->n_ifi_flags & IFF_UP)) {
-            gboolean         need_ipv6ll          = FALSE;
-            NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE;
-
-            /* Handle DAD failures */
-            for (iter = dad6_failed_addrs; iter; iter = iter->next) {
-                const NMPObject *           obj = iter->data;
-                const NMPlatformIP6Address *addr;
-
-                if (!nm_ndisc_dad_addr_is_fail_candidate(platform, obj))
-                    continue;
-
-                addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
-
-                _LOGI(LOGD_IP6,
-                      "ipv6: duplicate address check failed for the %s address",
-                      nm_platform_ip6_address_to_string(addr, NULL, 0));
-
-                if (IN6_IS_ADDR_LINKLOCAL(&addr->address))
-                    need_ipv6ll = TRUE;
-                else if (priv->ndisc)
-                    ndisc_config_changed |= nm_ndisc_dad_failed(priv->ndisc, &addr->address, FALSE);
-            }
-
-            if (ndisc_config_changed != NM_NDISC_CONFIG_NONE)
-                nm_ndisc_emit_config_change(priv->ndisc, ndisc_config_changed);
-
-            /* If no IPv6 link-local address exists but other addresses do then we
-             * must add the LL address to remain conformant with RFC 3513 chapter 2.1
-             * ("Addressing Model"): "All interfaces are required to have at least
-             * one link-local unicast address".
-             */
-            if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6))
-                need_ipv6ll = TRUE;
-            if (need_ipv6ll)
-                check_and_add_ipv6ll_addr(self);
-        }
-
-        g_slist_free_full(dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
-    }
-
-    if (!IS_IPv4) {
-        /* Check if DAD is still pending */
-        if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && priv->dad6_ip6_config
-            && priv->ext_ip6_config_captured
-            && !nm_ip6_config_has_any_dad_pending(priv->ext_ip6_config_captured,
-                                                  priv->dad6_ip6_config)) {
-            _LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
-            g_clear_object(&priv->dad6_ip6_config);
-            _set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
-            check_ip_state(self, FALSE, TRUE);
-            if (priv->rt6_temporary_not_available)
-                nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
-        }
-    }
-
-    set_unmanaged_external_down(self, TRUE);
-
-    return G_SOURCE_REMOVE;
-}
-
-static gboolean
-queued_ip4_config_change(gpointer user_data)
-{
-    return queued_ip_config_change(user_data, AF_INET);
-}
-
-static gboolean
-queued_ip6_config_change(gpointer user_data)
-{
-    return queued_ip_config_change(user_data, AF_INET6);
-}
-
-static void
-device_ipx_changed(NMPlatform *  platform,
-                   int           obj_type_i,
-                   int           ifindex,
-                   gconstpointer platform_object,
-                   int           change_type_i,
-                   NMDevice *    self)
-{
-    const NMPObjectType              obj_type    = obj_type_i;
-    const NMPlatformSignalChangeType change_type = change_type_i;
-    NMDevicePrivate *                priv;
-    const NMPlatformIP6Address *     addr;
-
-    if (nm_device_get_ip_ifindex(self) != ifindex)
-        return;
-
-    if (!nm_device_is_real(self))
-        return;
-
-    if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
-        /* ignore all platform signals until the link is initialized in platform. */
-        return;
-    }
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
-
-    switch (obj_type) {
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-        if (!priv->queued_ip_config_id_4) {
-            priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self);
-            _LOGD(LOGD_DEVICE, "queued IP4 config change");
-        }
-        break;
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        addr = platform_object;
-
-        if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING
-            && nm_ndisc_dad_addr_is_fail_candidate_event(change_type, addr)) {
-            priv->dad6_failed_addrs =
-                g_slist_prepend(priv->dad6_failed_addrs,
-                                (gpointer) nmp_object_ref(NMP_OBJECT_UP_CAST(addr)));
-        }
-
-        /* fall-through */
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        if (!priv->queued_ip_config_id_6) {
-            priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self);
-            _LOGD(LOGD_DEVICE, "queued IP6 config change");
-        }
-        break;
-    default:
-        g_return_if_reached();
-    }
-}
-
 /*****************************************************************************/
 
 NM_UTILS_FLAGS2STR_DEFINE(nm_unmanaged_flags2str,
@@ -14956,7 +13959,7 @@ nm_device_get_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flag)
  * Set the unmanaged flags of the device.
  **/
 static void
-_set_unmanaged_flags(NMDevice *          self,
+_set_unmanaged_flags(NMDevice           *self,
                      NMUnmanagedFlags    flags,
                      NMUnmanFlagOp       set_op,
                      gboolean            allow_state_transition,
@@ -14967,7 +13970,7 @@ _set_unmanaged_flags(NMDevice *          self,
     gboolean         was_managed, transition_state;
     NMUnmanagedFlags old_flags, old_mask;
     NMDeviceState    new_state;
-    const char *     operation = NULL;
+    const char      *operation = NULL;
     char             str1[512];
     char             str2[512];
     gboolean         do_notify_has_pending_actions = FALSE;
@@ -14995,12 +13998,6 @@ _set_unmanaged_flags(NMDevice *          self,
             nm_device_set_unmanaged_flags(self, NM_UNMANAGED_USER_SETTINGS, !!unmanaged);
         }
 
-        /* trigger an initial update of IP configuration. */
-        nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_4));
-        nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_6));
-        priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self);
-        priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self);
-
         if (priv->pending_actions.len == 0) {
             do_notify_has_pending_actions = TRUE;
             had_pending_actions           = nm_device_has_pending_action(self);
@@ -15099,7 +14096,7 @@ nm_device_set_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flags, NMUnmanFla
  * Set the unmanaged flags of the device and possibly trigger a state change.
  **/
 void
-nm_device_set_unmanaged_by_flags(NMDevice *          self,
+nm_device_set_unmanaged_by_flags(NMDevice           *self,
                                  NMUnmanagedFlags    flags,
                                  NMUnmanFlagOp       set_op,
                                  NMDeviceStateReason reason)
@@ -15108,7 +14105,7 @@ nm_device_set_unmanaged_by_flags(NMDevice *          self,
 }
 
 void
-nm_device_set_unmanaged_by_flags_queue(NMDevice *          self,
+nm_device_set_unmanaged_by_flags_queue(NMDevice           *self,
                                        NMUnmanagedFlags    flags,
                                        NMUnmanFlagOp       set_op,
                                        NMDeviceStateReason reason)
@@ -15143,7 +14140,7 @@ nm_device_check_unrealized_device_managed(NMDevice *self)
 }
 
 void
-nm_device_set_unmanaged_by_user_settings(NMDevice *self)
+nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now)
 {
     gboolean unmanaged;
 
@@ -15165,11 +14162,13 @@ nm_device_set_unmanaged_by_user_settings(NMDevice *self)
         self,
         nm_settings_get_unmanaged_specs(NM_DEVICE_GET_PRIVATE(self)->settings));
 
-    nm_device_set_unmanaged_by_flags(self,
-                                     NM_UNMANAGED_USER_SETTINGS,
-                                     !!unmanaged,
-                                     unmanaged ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED
-                                               : NM_DEVICE_STATE_REASON_NOW_MANAGED);
+    _set_unmanaged_flags(self,
+                         NM_UNMANAGED_USER_SETTINGS,
+                         !!unmanaged,
+                         TRUE,
+                         now,
+                         unmanaged ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED
+                                   : NM_DEVICE_STATE_REASON_NOW_MANAGED);
 }
 
 void
@@ -15247,12 +14246,12 @@ nm_device_set_unmanaged_by_quitting(NMDevice *self)
 void
 nm_device_reapply_settings_immediately(NMDevice *self)
 {
-    NMConnection *        applied_connection;
+    NMConnection         *applied_connection;
     NMSettingsConnection *settings_connection;
     NMDeviceState         state;
-    NMSettingConnection * s_con_settings;
-    NMSettingConnection * s_con_applied;
-    const char *          zone;
+    NMSettingConnection  *s_con_settings;
+    NMSettingConnection  *s_con_applied;
+    const char           *zone;
     NMMetered             metered;
     guint64               version_id;
 
@@ -15322,10 +14321,10 @@ void
 nm_device_update_metered(NMDevice *self)
 {
 #define NM_METERED_INVALID ((NMMetered) -1)
-    NMDevicePrivate *    priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate     *priv = NM_DEVICE_GET_PRIVATE(self);
     NMSettingConnection *setting;
     NMMetered            conn_value, value = NM_METERED_INVALID;
-    NMConnection *       connection = NULL;
+    NMConnection        *connection = NULL;
     NMDeviceState        state;
 
     g_return_if_fail(NM_IS_DEVICE(self));
@@ -15352,9 +14351,13 @@ nm_device_update_metered(NMDevice *self)
 
     /* Try to guess a value using the metered flag in IP configuration */
     if (value == NM_METERED_INVALID) {
-        if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
-            && nm_ip4_config_get_metered(priv->ip_config_4))
-            value = NM_METERED_GUESS_YES;
+        if (priv->l3cfg) {
+            const NML3ConfigData *l3cd;
+
+            l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE);
+            if (l3cd && nm_l3_config_data_get_metered(l3cd) == NM_TERNARY_TRUE)
+                value = NM_METERED_GUESS_YES;
+        }
     }
 
     /* Otherwise, look at connection type. For Bluetooth, we look at the type of
@@ -15400,14 +14403,14 @@ _device_check_dev_available_flags_from_con(NMDeviceCheckConAvailableFlags con_fl
 }
 
 static gboolean
-_nm_device_check_connection_available(NMDevice *                     self,
-                                      NMConnection *                 connection,
+_nm_device_check_connection_available(NMDevice                      *self,
+                                      NMConnection                  *connection,
                                       NMDeviceCheckConAvailableFlags flags,
-                                      const char *                   specific_object,
-                                      GError **                      error)
+                                      const char                    *specific_object,
+                                      GError                       **error)
 {
     NMDeviceState state;
-    GError *      local = NULL;
+    GError       *local = NULL;
 
     /* an unrealized software device is always available, hardware devices never. */
     if (!nm_device_is_real(self)) {
@@ -15511,11 +14514,11 @@ _nm_device_check_connection_available(NMDevice *                     self,
  * Returns: %TRUE if @connection can be activated on @self
  */
 gboolean
-nm_device_check_connection_available(NMDevice *                     self,
-                                     NMConnection *                 connection,
+nm_device_check_connection_available(NMDevice                      *self,
+                                     NMConnection                  *connection,
                                      NMDeviceCheckConAvailableFlags flags,
-                                     const char *                   specific_object,
-                                     GError **                      error)
+                                     const char                    *specific_object,
+                                     GError                       **error)
 {
     gboolean available;
 
@@ -15569,11 +14572,11 @@ available_connections_del(NMDevice *self, NMSettingsConnection *sett_conn)
 }
 
 static gboolean
-check_connection_available(NMDevice *                     self,
-                           NMConnection *                 connection,
+check_connection_available(NMDevice                      *self,
+                           NMConnection                  *connection,
                            NMDeviceCheckConAvailableFlags flags,
-                           const char *                   specific_object,
-                           GError **                      error)
+                           const char                    *specific_object,
+                           GError                       **error)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
@@ -15616,12 +14619,12 @@ check_connection_available(NMDevice *                     self,
 void
 nm_device_recheck_available_connections(NMDevice *self)
 {
-    NMDevicePrivate *            priv;
-    NMSettingsConnection *const *connections;
-    gboolean                     changed = FALSE;
-    GHashTableIter               h_iter;
-    NMSettingsConnection *       sett_conn;
-    guint                        i;
+    NMDevicePrivate               *priv;
+    NMSettingsConnection *const   *connections;
+    gboolean                       changed = FALSE;
+    GHashTableIter                 h_iter;
+    NMSettingsConnection          *sett_conn;
+    guint                          i;
     gs_unref_hashtable GHashTable *prune_list = NULL;
 
     g_return_if_fail(NM_IS_DEVICE(self));
@@ -15678,7 +14681,7 @@ nm_device_recheck_available_connections(NMDevice *self)
 NMSettingsConnection *
 nm_device_get_best_connection(NMDevice *self, const char *specific_object, GError **error)
 {
-    NMDevicePrivate *     priv      = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate      *priv      = NM_DEVICE_GET_PRIVATE(self);
     NMSettingsConnection *sett_conn = NULL;
     NMSettingsConnection *candidate;
     guint64               best_timestamp = 0;
@@ -15748,7 +14751,7 @@ cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpoin
 }
 
 static void
-cp_connection_updated(NMSettings *          settings,
+cp_connection_updated(NMSettings           *settings,
                       NMSettingsConnection *sett_conn,
                       guint                 update_reason_u,
                       gpointer              user_data)
@@ -15925,12 +14928,11 @@ _cancel_activation(NMDevice *self)
         priv->fw_state = FIREWALL_STATE_INITIALIZED;
     }
 
-    dispatcher_cleanup(self);
+    _dispatcher_cleanup(self);
     ip_check_gw_ping_cleanup(self);
 
     /* Break the activation chain */
-    activation_source_clear(self, AF_INET);
-    activation_source_clear(self, AF_INET6);
+    activation_source_clear(self);
 }
 
 static void
@@ -15960,17 +14962,13 @@ _cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
 
     queued_state_clear(self);
 
-    nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release);
-
     for (i = 0; i < 2; i++)
         nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
 
-    _cleanup_ip_pre(self, AF_INET, cleanup_type);
-    _cleanup_ip_pre(self, AF_INET6, cleanup_type);
+    _cleanup_ip_pre(self, AF_INET, cleanup_type, FALSE);
+    _cleanup_ip_pre(self, AF_INET6, cleanup_type, FALSE);
 
-    priv->ip_config_started = FALSE;
-    nm_clear_g_source_inst(&priv->ip_req_timeout_source_4);
-    nm_clear_g_source_inst(&priv->ip_req_timeout_source_6);
+    _dev_ip_state_req_timeout_cancel(self, AF_UNSPEC);
 }
 
 static void
@@ -15978,54 +14976,16 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    priv->v4_commit_first_time = TRUE;
-    priv->v6_commit_first_time = TRUE;
-
     priv->v4_route_table_initialized = FALSE;
     priv->v6_route_table_initialized = FALSE;
+    priv->l3config_merge_flags_has   = FALSE;
 
     priv->v4_route_table_all_sync_before = FALSE;
     priv->v6_route_table_all_sync_before = FALSE;
 
-    priv->default_route_metric_penalty_ip4_has = FALSE;
-    priv->default_route_metric_penalty_ip6_has = FALSE;
-
-    priv->linklocal6_dad_counter = 0;
-
     priv->mtu_force_set_done = FALSE;
 
-    /* Clean up IP configs; this does not actually deconfigure the
-     * interface; the caller must flush routes and addresses explicitly.
-     */
-    nm_device_set_ip_config(self, AF_INET, NULL, TRUE, NULL);
-    nm_device_set_ip_config(self, AF_INET6, NULL, TRUE, NULL);
-    g_clear_object(&priv->proxy_config);
-    g_clear_object(&priv->con_ip_config_4);
-    applied_config_clear(&priv->dev_ip_config_4);
-    applied_config_clear(&priv->dev2_ip_config_4);
-    g_clear_object(&priv->ext_ip_config_4);
-    g_clear_object(&priv->ip_config_4);
-    g_clear_object(&priv->con_ip_config_6);
-    applied_config_clear(&priv->ac_ip6_config);
-    g_clear_object(&priv->ext_ip_config_6);
-    g_clear_object(&priv->ext_ip6_config_captured);
-    applied_config_clear(&priv->dev2_ip_config_6);
-    g_clear_object(&priv->ip_config_6);
-    g_clear_object(&priv->dad6_ip6_config);
-    priv->ipv6ll_has = FALSE;
-    memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr));
-
-    nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
-    nm_clear_g_source(&priv->rt6_temporary_not_available_id);
-
-    g_slist_free_full(priv->vpn_configs_4, g_object_unref);
-    priv->vpn_configs_4 = NULL;
-    g_slist_free_full(priv->vpn_configs_6, g_object_unref);
-    priv->vpn_configs_6 = NULL;
-
-    /* We no longer accept the delegations. nm_device_set_ip_config(NULL)
-     * above disables them. */
-    nm_assert(priv->needs_ip6_subnet == FALSE);
+    priv->needs_ip6_subnet = FALSE;
 
     if (priv->act_request.obj) {
         nm_active_connection_set_default(NM_ACTIVE_CONNECTION(priv->act_request.obj),
@@ -16047,6 +15007,9 @@ _cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
      * or ATM device).
      */
     _set_ip_ifindex(self, 0, NULL);
+
+    nm_clear_g_source_inst(&priv->ip_data_4.check_async_source);
+    nm_clear_g_source_inst(&priv->ip_data_6.check_async_source);
 }
 
 /*
@@ -16078,7 +15041,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
 
     /* Turn off kernel IPv6 */
     if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
-        set_disable_ipv6(self, "1");
+        _dev_sysctl_set_disable_ipv6(self, TRUE);
         nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
     }
 
@@ -16094,12 +15057,10 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
 
         /* Take out any entries in the routing table and any IP address the device had. */
         if (ifindex > 0) {
-            NMPlatform *       platform = nm_device_get_platform(self);
-            NMUtilsIPv6IfaceId iid      = {};
+            NMPlatform *platform = nm_device_get_platform(self);
 
             nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
             nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
-            set_ipv6_token(self, &iid, "::");
 
             if (nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG)) {
                 nm_platform_tc_sync(platform, ifindex, NULL, NULL);
@@ -16192,213 +15153,13 @@ deactivate_reset_hw_addr(NMDevice *self)
     nm_device_hw_addr_reset(self, "deactivate");
 }
 
-static char *
-find_dhcp4_address(NMDevice *self)
-{
-    NMDevicePrivate *           priv = NM_DEVICE_GET_PRIVATE(self);
-    const NMPlatformIP4Address *a;
-    NMDedupMultiIter            ipconf_iter;
-
-    if (!priv->ip_config_4)
-        return NULL;
-
-    nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) {
-        if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
-            return nm_utils_inet4_ntop_dup(a->address);
-    }
-    return NULL;
-}
-
-void
-nm_device_spawn_iface_helper(NMDevice *self)
-{
-    NMDevicePrivate * priv       = NM_DEVICE_GET_PRIVATE(self);
-    gboolean          configured = FALSE;
-    NMConnection *    connection;
-    GError *          error = NULL;
-    const char *      method;
-    GPtrArray *       argv;
-    gs_free char *    dhcp4_address = NULL;
-    char *            logging_backend;
-    NMUtilsStableType stable_type;
-    const char *      stable_id;
-
-    if (priv->state != NM_DEVICE_STATE_ACTIVATED)
-        return;
-    if (!nm_device_can_assume_connections(self))
-        return;
-
-    connection = nm_device_get_applied_connection(self);
-
-    g_return_if_fail(connection);
-
-    argv = g_ptr_array_sized_new(10);
-    g_ptr_array_set_free_func(argv, g_free);
-
-    g_ptr_array_add(argv, g_strdup(LIBEXECDIR "/nm-iface-helper"));
-    g_ptr_array_add(argv, g_strdup("--ifname"));
-    g_ptr_array_add(argv, g_strdup(nm_device_get_ip_iface(self)));
-    g_ptr_array_add(argv, g_strdup("--uuid"));
-    g_ptr_array_add(argv, g_strdup(nm_connection_get_uuid(connection)));
-
-    stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
-    if (stable_type != NM_UTILS_STABLE_TYPE_UUID) {
-        g_ptr_array_add(argv, g_strdup("--stable-id"));
-        g_ptr_array_add(argv, g_strdup_printf("%d %s", (int) stable_type, stable_id));
-    }
-
-    logging_backend =
-        nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
-                                 NM_CONFIG_KEYFILE_GROUP_LOGGING,
-                                 NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
-                                 NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
-    if (logging_backend) {
-        g_ptr_array_add(argv, g_strdup("--logging-backend"));
-        g_ptr_array_add(argv, logging_backend);
-    }
-
-    g_ptr_array_add(argv, g_strdup("--log-level"));
-    g_ptr_array_add(argv, g_strdup(nm_logging_level_to_string()));
-
-    g_ptr_array_add(argv, g_strdup("--log-domains"));
-    g_ptr_array_add(argv, g_strdup(nm_logging_domains_to_string()));
-
-    dhcp4_address = find_dhcp4_address(self);
-
-    method = nm_device_get_effective_ip_config_method(self, AF_INET);
-    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
-        NMSettingIPConfig *s_ip4;
-
-        s_ip4 = nm_connection_get_setting_ip4_config(connection);
-        nm_assert(s_ip4);
-
-        g_ptr_array_add(argv, g_strdup("--priority4"));
-        g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET)));
-
-        g_ptr_array_add(argv, g_strdup("--dhcp4"));
-        g_ptr_array_add(argv, g_strdup(dhcp4_address));
-        if (nm_setting_ip_config_get_may_fail(s_ip4) == FALSE)
-            g_ptr_array_add(argv, g_strdup("--dhcp4-required"));
-
-        if (priv->dhcp_data_4.client) {
-            const char *hostname;
-            GBytes *    client_id;
-
-            client_id = nm_dhcp_client_get_client_id(priv->dhcp_data_4.client);
-            if (client_id) {
-                g_ptr_array_add(argv, g_strdup("--dhcp4-clientid"));
-                g_ptr_array_add(argv,
-                                nm_utils_bin2hexstr_full(g_bytes_get_data(client_id, NULL),
-                                                         g_bytes_get_size(client_id),
-                                                         ':',
-                                                         FALSE,
-                                                         NULL));
-            }
-
-            hostname = nm_dhcp_client_get_hostname(priv->dhcp_data_4.client);
-            if (hostname) {
-                if (NM_FLAGS_HAS(nm_dhcp_client_get_client_flags(priv->dhcp_data_4.client),
-                                 NM_DHCP_CLIENT_FLAGS_USE_FQDN))
-                    g_ptr_array_add(argv, g_strdup("--dhcp4-fqdn"));
-                else
-                    g_ptr_array_add(argv, g_strdup("--dhcp4-hostname"));
-                g_ptr_array_add(argv, g_strdup(hostname));
-            }
-        }
-
-        configured = TRUE;
-    }
-
-    method = nm_utils_get_ip_config_method(connection, AF_INET6);
-    if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
-        NMSettingIPConfig *s_ip6;
-        NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
-
-        s_ip6 = nm_connection_get_setting_ip6_config(connection);
-        g_assert(s_ip6);
-
-        g_ptr_array_add(argv, g_strdup("--priority6"));
-        g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET6)));
-
-        g_ptr_array_add(argv, g_strdup("--slaac"));
-
-        if (nm_setting_ip_config_get_may_fail(s_ip6) == FALSE)
-            g_ptr_array_add(argv, g_strdup("--slaac-required"));
-
-        g_ptr_array_add(argv, g_strdup("--slaac-tempaddr"));
-        g_ptr_array_add(argv, g_strdup_printf("%d", priv->ndisc_use_tempaddr));
-
-        if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) {
-            g_ptr_array_add(argv, g_strdup("--iid"));
-            g_ptr_array_add(
-                argv,
-                nm_utils_bin2hexstr_full(iid.id_u8, sizeof(NMUtilsIPv6IfaceId), ':', FALSE, NULL));
-        }
-
-        g_ptr_array_add(argv, g_strdup("--addr-gen-mode"));
-        g_ptr_array_add(
-            argv,
-            g_strdup_printf("%d",
-                            nm_setting_ip6_config_get_addr_gen_mode(NM_SETTING_IP6_CONFIG(s_ip6))));
-
-        configured = TRUE;
-    }
-
-    if (configured) {
-        GPid pid;
-
-        g_ptr_array_add(argv, NULL);
-
-        if (nm_logging_enabled(LOGL_DEBUG, LOGD_DEVICE)) {
-            char *tmp;
-
-            tmp = g_strjoinv(" ", (char **) argv->pdata);
-            _LOGD(LOGD_DEVICE, "running '%s'", tmp);
-            g_free(tmp);
-        }
-
-        if (g_spawn_async(NULL,
-                          (char **) argv->pdata,
-                          NULL,
-                          G_SPAWN_DO_NOT_REAP_CHILD,
-                          NULL,
-                          NULL,
-                          &pid,
-                          &error)) {
-            _LOGI(LOGD_DEVICE, "spawned helper PID %u", (guint) pid);
-        } else {
-            _LOGW(LOGD_DEVICE, "failed to spawn helper: %s", error->message);
-            g_error_free(error);
-        }
-    }
-
-    g_ptr_array_unref(argv);
-}
-
 /*****************************************************************************/
 
-static gboolean
-ip_config_valid(NMDeviceState state)
-{
-    return (state == NM_DEVICE_STATE_UNMANAGED)
-           || (state >= NM_DEVICE_STATE_IP_CHECK && state <= NM_DEVICE_STATE_DEACTIVATING);
-}
-
-static void
-notify_ip_properties(NMDevice *self)
-{
-    _notify(self, PROP_IP_IFACE);
-    _notify(self, PROP_IP4_CONFIG);
-    _notify(self, PROP_DHCP4_CONFIG);
-    _notify(self, PROP_IP6_CONFIG);
-    _notify(self, PROP_DHCP6_CONFIG);
-}
-
 static void
 ip6_managed_setup(NMDevice *self)
 {
-    set_nm_ipv6ll(self, TRUE);
-    set_disable_ipv6(self, "1");
+    _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
+    _dev_sysctl_set_disable_ipv6(self, FALSE);
     nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
     nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
     nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "0");
@@ -16422,7 +15183,7 @@ deactivate_ready(NMDevice *self, NMDeviceStateReason reason)
 static void
 sriov_reset_on_deactivate_cb(GError *error, gpointer user_data)
 {
-    NMDevice *       self;
+    NMDevice        *self;
     NMDevicePrivate *priv;
     gpointer         reason;
 
@@ -16440,7 +15201,7 @@ sriov_reset_on_deactivate_cb(GError *error, gpointer user_data)
 static void
 sriov_reset_on_failure_cb(GError *error, gpointer user_data)
 {
-    NMDevice *       self = user_data;
+    NMDevice        *self = user_data;
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     nm_assert(priv->sriov_reset_pending > 0);
@@ -16457,7 +15218,7 @@ sriov_reset_on_failure_cb(GError *error, gpointer user_data)
 static void
 deactivate_async_ready(NMDevice *self, GError *error, gpointer user_data)
 {
-    NMDevicePrivate *   priv   = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate    *priv   = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceStateReason reason = GPOINTER_TO_UINT(user_data);
 
     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
@@ -16478,8 +15239,8 @@ deactivate_async_ready(NMDevice *self, GError *error, gpointer user_data)
 static void
 deactivate_dispatcher_complete(NMDispatcherCallId *call_id, gpointer user_data)
 {
-    NMDevice *          self = NM_DEVICE(user_data);
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevice           *self = NM_DEVICE(user_data);
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceStateReason reason;
 
     g_return_if_fail(call_id == priv->dispatcher.call_id);
@@ -16532,20 +15293,19 @@ deactivate_dispatcher_complete(NMDispatcherCallId *call_id, gpointer user_data)
 static void
 _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting)
 {
-    NMDevicePrivate *priv;
-    NMDeviceState    old_state;
-    gs_unref_object NMActRequest *req         = NULL;
+    gs_unref_object NMActRequest *req = NULL;
+    NMDevicePrivate              *priv;
+    NMDeviceState                 old_state;
     gboolean                      no_firmware = FALSE;
-    NMSettingsConnection *        sett_conn;
-    NMSettingSriov *              s_sriov;
+    NMSettingsConnection         *sett_conn;
+    NMSettingSriov               *s_sriov;
     gboolean                      concheck_now;
 
     g_return_if_fail(NM_IS_DEVICE(self));
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
-    /* Track re-entry */
-    g_warn_if_fail(priv->in_state_changed == FALSE);
+    g_return_if_fail(priv->in_state_changed == 0);
 
     old_state = priv->state;
 
@@ -16582,14 +15342,14 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
      * by the device not having any pending action anymore
      * we add one here that gets removed at the end of the function */
     nm_device_add_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE);
-    priv->in_state_changed = TRUE;
+    priv->in_state_changed++;
 
     priv->state        = state;
     priv->state_reason = reason;
 
     queued_state_clear(self);
 
-    dispatcher_cleanup(self);
+    _dispatcher_cleanup(self);
 
     nm_clear_g_cancellable(&priv->deactivating_cancellable);
 
@@ -16646,15 +15406,15 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
                     nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
                 nm_device_take_down(self, TRUE);
                 nm_device_hw_addr_reset(self, "unmanage");
-                set_nm_ipv6ll(self, FALSE);
-                restore_ip6_properties(self);
+                _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
+                _dev_sysctl_restore_ip6_properties(self);
             }
         }
         nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
         break;
     case NM_DEVICE_STATE_UNAVAILABLE:
         if (old_state == NM_DEVICE_STATE_UNMANAGED) {
-            save_ip6_properties(self);
+            _dev_sysctl_save_ip6_properties(self);
             if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED)
                 ip6_managed_setup(self);
             device_init_static_sriov_num_vfs(self);
@@ -16683,7 +15443,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             /* Ensure devices that previously assumed a connection now have
              * userspace IPv6LL enabled.
              */
-            set_nm_ipv6ll(self, TRUE);
+            _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
 
             nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
         } else if (old_state < NM_DEVICE_STATE_DISCONNECTED) {
@@ -16703,8 +15463,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             /* Clean up any half-done IP operations if the device's layer2
              * finds out it needs authentication during IP config.
              */
-            _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE);
-            _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
+            _cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE, FALSE);
+            _cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE, FALSE);
         }
         break;
     default:
@@ -16796,9 +15556,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         _LOGI(LOGD_DEVICE, "Activation: successful, device activated.");
         nm_device_update_metered(self);
         nm_dispatcher_call_device(NM_DISPATCHER_ACTION_UP, self, req, NULL, NULL, NULL);
-
-        if (priv->proxy_config)
-            _pacrunner_manager_add(self);
+        _pacrunner_manager_add(self);
         break;
     case NM_DEVICE_STATE_FAILED:
         /* Usually upon failure the activation chain is interrupted in
@@ -16859,10 +15617,6 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         } else
             nm_device_start_ip_check(self);
 
-        /* IP-related properties are only valid when the device has IP configuration;
-         * now that it does, ensure their change notifications are emitted.
-         */
-        notify_ip_properties(self);
         break;
     }
     case NM_DEVICE_STATE_SECONDARIES:
@@ -16885,18 +15639,15 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         }
     }
 
-    /* IP-related properties are only valid when the device has IP configuration.
-     * If it no longer does, ensure their change notifications are emitted.
-     */
-    if (ip_config_valid(old_state) && !ip_config_valid(state))
-        notify_ip_properties(self);
-
     concheck_now = NM_IN_SET(state, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_DISCONNECTED)
                    || old_state >= NM_DEVICE_STATE_ACTIVATED;
     concheck_update_interval(self, AF_INET, concheck_now);
     concheck_update_interval(self, AF_INET6, concheck_now);
 
-    priv->in_state_changed = FALSE;
+    update_prop_ip_iface(self);
+
+    priv->in_state_changed--;
+
     nm_device_remove_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE);
 
     if ((old_state > NM_DEVICE_STATE_UNMANAGED) != (state > NM_DEVICE_STATE_UNMANAGED))
@@ -16912,8 +15663,8 @@ nm_device_state_changed(NMDevice *self, NMDeviceState state, NMDeviceStateReason
 static gboolean
 queued_state_set(gpointer user_data)
 {
-    NMDevice *          self = NM_DEVICE(user_data);
-    NMDevicePrivate *   priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevice           *self = NM_DEVICE(user_data);
+    NMDevicePrivate    *priv = NM_DEVICE_GET_PRIVATE(self);
     NMDeviceState       new_state;
     NMDeviceStateReason new_reason;
 
@@ -17048,7 +15799,7 @@ gboolean
 nm_device_update_hw_address(NMDevice *self)
 {
     NMDevicePrivate *priv;
-    const guint8 *   hwaddr;
+    const guint8    *hwaddr;
     gsize            hwaddrlen = 0;
 
     priv = NM_DEVICE_GET_PRIVATE(self);
@@ -17123,11 +15874,11 @@ nm_device_update_initial_hw_address(NMDevice *self)
 void
 nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze)
 {
-    NMDevicePrivate *              priv = NM_DEVICE_GET_PRIVATE(self);
+    NMDevicePrivate               *priv = NM_DEVICE_GET_PRIVATE(self);
     guint8                         buf[_NM_UTILS_HWADDR_LEN_MAX];
     gboolean                       success_read;
     int                            ifindex;
-    const NMPlatformLink *         pllink;
+    const NMPlatformLink          *pllink;
     const NMConfigDeviceStateData *dev_state;
     NMPLinkAddress                 cached_hw_addr_perm;
 
@@ -17236,7 +15987,7 @@ _hw_addr_matches(NMDevice *self, const guint8 *addr, gsize addr_len)
 }
 
 static gboolean
-_hw_addr_set(NMDevice *        self,
+_hw_addr_set(NMDevice         *self,
              const char *const addr,
              const char *const operation,
              const char *const detail)
@@ -17439,20 +16190,20 @@ nm_device_hw_addr_set(NMDevice *self, const char *addr, const char *detail, gboo
  * %TRUE otherwise
  */
 static gboolean
-_hw_addr_get_cloned(NMDevice *    self,
+_hw_addr_get_cloned(NMDevice     *self,
                     NMConnection *connection,
                     gboolean      is_wifi,
-                    gboolean *    preserve,
-                    char **       hwaddr,
-                    HwAddrType *  hwaddr_type,
-                    const char ** hwaddr_detail,
-                    GError **     error)
+                    gboolean     *preserve,
+                    char        **hwaddr,
+                    HwAddrType   *hwaddr_type,
+                    const char  **hwaddr_detail,
+                    GError      **error)
 {
     NMDevicePrivate *priv;
-    gs_free char *   hw_addr_generated = NULL;
-    const char *     addr;
-    const char *     addr_setting;
-    char *           addr_out;
+    gs_free char    *hw_addr_generated = NULL;
+    const char      *addr;
+    const char      *addr_setting;
+    char            *addr_out;
     HwAddrType       type_out;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
@@ -17520,7 +16271,7 @@ _hw_addr_get_cloned(NMDevice *    self,
         type_out = HW_ADDR_TYPE_GENERATED;
     } else if (NM_IN_STRSET(addr, NM_CLONED_MAC_STABLE)) {
         NMUtilsStableType stable_type;
-        const char *      stable_id;
+        const char       *stable_id;
 
         if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
             /* hm, we already use a generate MAC address. Most certainly, that is from the same
@@ -17569,12 +16320,12 @@ out_no_action:
 }
 
 gboolean
-nm_device_hw_addr_get_cloned(NMDevice *    self,
+nm_device_hw_addr_get_cloned(NMDevice     *self,
                              NMConnection *connection,
                              gboolean      is_wifi,
-                             char **       hwaddr,
-                             gboolean *    preserve,
-                             GError **     error)
+                             char        **hwaddr,
+                             gboolean     *preserve,
+                             GError      **error)
 {
     if (!_hw_addr_get_cloned(self, connection, is_wifi, preserve, hwaddr, NULL, NULL, error))
         return FALSE;
@@ -17585,12 +16336,12 @@ nm_device_hw_addr_get_cloned(NMDevice *    self,
 gboolean
 nm_device_hw_addr_set_cloned(NMDevice *self, NMConnection *connection, gboolean is_wifi)
 {
-    NMDevicePrivate *priv;
-    gboolean         preserve   = FALSE;
-    gs_free char *   hwaddr     = NULL;
-    const char *     detail     = NULL;
-    HwAddrType       type       = HW_ADDR_TYPE_UNSET;
-    gs_free_error GError *error = NULL;
+    NMDevicePrivate      *priv;
+    gboolean              preserve = FALSE;
+    gs_free char         *hwaddr   = NULL;
+    const char           *detail   = NULL;
+    HwAddrType            type     = HW_ADDR_TYPE_UNSET;
+    gs_free_error GError *error    = NULL;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
     priv = NM_DEVICE_GET_PRIVATE(self);
@@ -17622,7 +16373,7 @@ gboolean
 nm_device_hw_addr_reset(NMDevice *self, const char *detail)
 {
     NMDevicePrivate *priv;
-    const char *     addr;
+    const char      *addr;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
 
@@ -17711,9 +16462,9 @@ nm_device_spec_match_list(NMDevice *self, const GSList *specs)
 int
 nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value)
 {
-    NMDeviceClass *      klass;
+    NMDeviceClass       *klass;
     NMMatchSpecMatchType m;
-    const char *         hw_address = NULL;
+    const char          *hw_address = NULL;
     gboolean             is_fake;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
@@ -17748,7 +16499,7 @@ nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match
 guint
 nm_device_get_supplicant_timeout(NMDevice *self)
 {
-    NMConnection *  connection;
+    NMConnection   *connection;
     NMSetting8021x *s_8021x;
     int             timeout;
 #define SUPPLICANT_DEFAULT_TIMEOUT 25
@@ -17777,7 +16528,7 @@ nm_device_get_supplicant_timeout(NMDevice *self)
 gboolean
 nm_device_auth_retries_try_next(NMDevice *self)
 {
-    NMDevicePrivate *    priv;
+    NMDevicePrivate     *priv;
     NMSettingConnection *s_con;
     int                  auth_retries;
 
@@ -17826,11 +16577,11 @@ nm_device_auth_retries_try_next(NMDevice *self)
 static void
 hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    HostnameResolver *resolver;
-    NMDevice *        self;
-    gs_free char *    addr_str  = NULL;
-    gs_free char *    output    = NULL;
-    gs_free_error GError *error = NULL;
+    HostnameResolver     *resolver;
+    NMDevice             *self;
+    gs_free char         *addr_str = NULL;
+    gs_free char         *output   = NULL;
+    gs_free_error GError *error    = NULL;
 
     output = nm_device_resolve_address_finish(result, &error);
     if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
@@ -17869,7 +16620,7 @@ static gboolean
 hostname_dns_address_timeout(gpointer user_data)
 {
     HostnameResolver *resolver = user_data;
-    NMDevice *        self     = resolver->device;
+    NMDevice         *self     = resolver->device;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
 
@@ -17914,17 +16665,70 @@ nm_device_clear_dns_lookup_data(NMDevice *self)
         nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
 }
 
+static GInetAddress *
+get_address_for_hostname_dns_lookup(NMDevice *self, int addr_family)
+{
+    const int                    IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMPLookup                    lookup;
+    const NMDedupMultiHeadEntry *head_entry;
+    const NMDedupMultiEntry     *iter;
+    const guint8                *addr6_ll    = NULL;
+    const guint8                *addr6_nonll = NULL;
+    int                          ifindex;
+
+    ifindex = nm_device_get_ip_ifindex(self);
+    if (ifindex <= 0)
+        return NULL;
+
+    /* FIXME(l3cfg): now we lookup the address from platform. Should we instead look
+     *   it up from NML3Cfg? That is, take an address that we want to configure as
+     *   opposed to an address that is configured? */
+    head_entry = nm_platform_lookup(
+        nm_device_get_platform(self),
+        nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex));
+
+    if (head_entry) {
+        c_list_for_each_entry (iter, &head_entry->lst_entries_head, lst_entries) {
+            const NMPlatformIPAddress *addr = NMP_OBJECT_CAST_IP_ADDRESS(iter->obj);
+
+            if (IS_IPv4) {
+                return g_inet_address_new_from_bytes(addr->address_ptr, G_SOCKET_FAMILY_IPV4);
+            }
+
+            /* For IPv6 prefer, in order:
+             * - !link-local, !deprecated
+             * - !link-local, deprecated
+             * - link-local
+             */
+
+            if (!IN6_IS_ADDR_LINKLOCAL(addr->address_ptr)) {
+                if (!(addr->n_ifa_flags & IFA_F_DEPRECATED)) {
+                    return g_inet_address_new_from_bytes(addr->address_ptr, G_SOCKET_FAMILY_IPV6);
+                }
+                addr6_nonll = addr->address_ptr;
+                continue;
+            }
+
+            addr6_ll = addr->address_ptr;
+        }
+
+        if (addr6_nonll || addr6_ll)
+            return g_inet_address_new_from_bytes(addr6_nonll ?: addr6_ll, G_SOCKET_FAMILY_IPV6);
+    }
+
+    return NULL;
+}
+
 /* return value is valid only immediately */
 const char *
 nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait)
 {
-    NMDevicePrivate * priv;
-    const int         IS_IPv4 = NM_IS_IPv4(addr_family);
-    HostnameResolver *resolver;
-    NMIPConfig *      ip_config;
-    const char *      method;
-    gboolean          address_changed         = FALSE;
-    gs_unref_object GInetAddress *new_address = NULL;
+    const int                     IS_IPv4 = NM_IS_IPv4(addr_family);
+    NMDevicePrivate              *priv;
+    HostnameResolver             *resolver;
+    const char                   *method;
+    gboolean                      address_changed = FALSE;
+    gs_unref_object GInetAddress *new_address     = NULL;
 
     g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
     priv = NM_DEVICE_GET_PRIVATE(self);
@@ -17966,41 +16770,7 @@ nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean
 
     /* Determine the most suitable address of the interface
      * and whether it changed from the previous lookup */
-    ip_config = priv->ip_config_x[IS_IPv4];
-    if (ip_config) {
-        const NMPlatformIPAddress *addr = NULL;
-
-        if (IS_IPv4) {
-            addr = nm_ip_config_get_first_address(ip_config);
-        } else {
-            /* For IPv6 prefer, in order:
-             * - !link-local, !deprecated
-             * - !link-local, deprecated
-             * - link-local
-             */
-            addr = nm_ip_config_find_first_address(ip_config,
-                                                   NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
-                                                       | NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
-            if (!addr) {
-                addr = nm_ip_config_find_first_address(
-                    ip_config,
-                    NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
-                        | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED);
-            }
-            if (!addr) {
-                addr = nm_ip_config_find_first_address(ip_config,
-                                                       NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
-                                                           | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY);
-            }
-        }
-
-        if (addr) {
-            new_address = g_inet_address_new_from_bytes(addr->address_ptr,
-                                                        IS_IPv4 ? G_SOCKET_FAMILY_IPV4
-                                                                : G_SOCKET_FAMILY_IPV6);
-        }
-    }
-
+    new_address = get_address_for_hostname_dns_lookup(self, addr_family);
     if (new_address && resolver->address) {
         if (!g_inet_address_equal(new_address, resolver->address))
             address_changed = TRUE;
@@ -18072,11 +16842,7 @@ _activation_func_to_string(ActivationHandleFunc func)
     G_STMT_END
     FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage1_device_prepare);
     FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage2_device_config);
-    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage3_ip_config_start);
-    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_4);
-    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_6);
-    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_4);
-    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_6);
+    FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage3_ip_config);
     g_return_val_if_reached("unknown");
 }
 
@@ -18084,7 +16850,7 @@ static GVariant *
 _device_get_ports_variant(NMDevice *device)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(device);
-    SlaveInfo *      info;
+    SlaveInfo       *info;
     GVariantBuilder  builder;
     gboolean         any = FALSE;
 
@@ -18116,7 +16882,7 @@ _device_get_ports_variant(NMDevice *device)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDevice *       self = NM_DEVICE(object);
+    NMDevice        *self = NM_DEVICE(object);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -18139,13 +16905,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
             nm_utils_str_utf8safe_escape_cp(priv->iface, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
         break;
     case PROP_IP_IFACE:
-        if (ip_config_valid(priv->state)) {
-            g_value_take_string(
-                value,
-                nm_utils_str_utf8safe_escape_cp(nm_device_get_ip_iface(self),
-                                                NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
-        } else
-            g_value_set_string(value, NULL);
+        g_value_set_string(value, priv->prop_ip_iface);
         break;
     case PROP_IFINDEX:
         g_value_set_int(value, priv->ifindex);
@@ -18180,24 +16940,16 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_uint(value, priv->mtu);
         break;
     case PROP_IP4_CONFIG:
-        nm_dbus_utils_g_value_set_object_path(value,
-                                              ip_config_valid(priv->state) ? priv->ip_config_4
-                                                                           : NULL);
+        nm_dbus_utils_g_value_set_object_path(value, priv->l3ipdata_4.ip_config);
         break;
     case PROP_DHCP4_CONFIG:
-        nm_dbus_utils_g_value_set_object_path(
-            value,
-            ip_config_valid(priv->state) ? priv->dhcp_data_4.config : NULL);
+        nm_dbus_utils_g_value_set_object_path(value, priv->ipdhcp_data_4.config);
         break;
     case PROP_IP6_CONFIG:
-        nm_dbus_utils_g_value_set_object_path(value,
-                                              ip_config_valid(priv->state) ? priv->ip_config_6
-                                                                           : NULL);
+        nm_dbus_utils_g_value_set_object_path(value, priv->l3ipdata_6.ip_config);
         break;
     case PROP_DHCP6_CONFIG:
-        nm_dbus_utils_g_value_set_object_path(
-            value,
-            ip_config_valid(priv->state) ? priv->dhcp_data_6.config : NULL);
+        nm_dbus_utils_g_value_set_object_path(value, priv->ipdhcp_data_6.config);
         break;
     case PROP_STATE:
         g_value_set_uint(value, priv->state);
@@ -18307,7 +17059,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-    NMDevice *       self = (NMDevice *) object;
+    NMDevice        *self = (NMDevice *) object;
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -18404,6 +17156,8 @@ nm_device_init(NMDevice *self)
     c_list_init(&self->devices_lst);
     c_list_init(&priv->slaves);
 
+    priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_NONE;
+
     priv->concheck_x[0].state = NM_CONNECTIVITY_UNKNOWN;
     priv->concheck_x[1].state = NM_CONNECTIVITY_UNKNOWN;
 
@@ -18429,19 +17183,16 @@ nm_device_init(NMDevice *self)
     priv->ip6_saved_properties  = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
     priv->sys_iface_state_      = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
 
-    priv->v4_commit_first_time = TRUE;
-    priv->v6_commit_first_time = TRUE;
-
     priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
 }
 
 static GObject *
 constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params)
 {
-    GObject *             object;
-    GObjectClass *        klass;
-    NMDevice *            self;
-    NMDevicePrivate *     priv;
+    GObject              *object;
+    GObjectClass         *klass;
+    NMDevice             *self;
+    NMDevicePrivate      *priv;
     const NMPlatformLink *pllink;
 
     klass  = G_OBJECT_CLASS(nm_device_parent_class);
@@ -18481,31 +17232,14 @@ constructor(GType type, guint n_construct_params, GObjectConstructParam *constru
 static void
 constructed(GObject *object)
 {
-    NMDevice *       self = NM_DEVICE(object);
+    NMDevice        *self = NM_DEVICE(object);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatform *     platform;
+    NMPlatform      *platform;
 
     if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities)
         priv->capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self);
 
-    /* Watch for external IP config changes */
     platform = nm_device_get_platform(self);
-    g_signal_connect(platform,
-                     NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
-                     G_CALLBACK(device_ipx_changed),
-                     self);
-    g_signal_connect(platform,
-                     NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
-                     G_CALLBACK(device_ipx_changed),
-                     self);
-    g_signal_connect(platform,
-                     NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
-                     G_CALLBACK(device_ipx_changed),
-                     self);
-    g_signal_connect(platform,
-                     NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
-                     G_CALLBACK(device_ipx_changed),
-                     self);
     g_signal_connect(platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK(link_changed_cb), self);
 
     priv->manager  = g_object_ref(NM_MANAGER_GET);
@@ -18532,11 +17266,11 @@ constructed(GObject *object)
 static void
 dispose(GObject *object)
 {
-    NMDevice *                  self = NM_DEVICE(object);
-    NMDevicePrivate *           priv = NM_DEVICE_GET_PRIVATE(self);
-    NMPlatform *                platform;
+    NMDevice                   *self = NM_DEVICE(object);
+    NMDevicePrivate            *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMPlatform                 *platform;
     NMDeviceConnectivityHandle *con_handle;
-    gs_free_error GError *cancelled_error = NULL;
+    gs_free_error GError       *cancelled_error = NULL;
 
     _LOGD(LOGD_DEVICE, "disposing");
 
@@ -18557,15 +17291,12 @@ dispose(GObject *object)
     _parent_set_ifindex(self, 0, FALSE);
 
     platform = nm_device_get_platform(self);
-    g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(device_ipx_changed), self);
     g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(link_changed_cb), self);
 
-    arp_cleanup(self);
-
     nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id);
     nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id);
 
-    dispatcher_cleanup(self);
+    _dispatcher_cleanup(self);
 
     nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id);
 
@@ -18574,7 +17305,7 @@ dispose(GObject *object)
     nm_assert(c_list_is_empty(&priv->slaves));
 
     /* Let the kernel manage IPv6LL again */
-    set_nm_ipv6ll(self, FALSE);
+    _dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
 
     _cleanup_generic_post(self, CLEANUP_TYPE_KEEP);
 
@@ -18621,6 +17352,10 @@ dispose(GObject *object)
         priv->sriov.next = NULL;
     }
 
+    g_clear_object(&priv->l3cfg);
+    g_clear_object(&priv->l3ipdata_4.ip_config);
+    g_clear_object(&priv->l3ipdata_6.ip_config);
+
     G_OBJECT_CLASS(nm_device_parent_class)->dispose(object);
 
     if (nm_clear_g_source(&priv->queued_state.id)) {
@@ -18634,7 +17369,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDevice *       self = NM_DEVICE(object);
+    NMDevice        *self = NM_DEVICE(object);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
     _LOGD(LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME(self));
@@ -18643,7 +17378,6 @@ finalize(GObject *object)
     g_free(priv->hw_addr_perm);
     g_free(priv->hw_addr_initial);
     g_free(priv->pending_actions.arr);
-    g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
     nm_clear_g_free(&priv->physical_port_id);
     g_free(priv->udi);
     g_free(priv->path);
@@ -18806,7 +17540,7 @@ static const NMDBusInterfaceInfoExtended interface_info_device_statistics = {
 static void
 nm_device_class_init(NMDeviceClass *klass)
 {
-    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
 
     g_type_class_add_private(object_class, sizeof(NMDevicePrivate));
@@ -18824,10 +17558,9 @@ nm_device_class_init(NMDeviceClass *klass)
 
     klass->link_changed = link_changed;
 
-    klass->is_available                 = is_available;
-    klass->act_stage2_config            = act_stage2_config;
-    klass->act_stage3_ip_config_start   = act_stage3_ip_config_start;
-    klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
+    klass->is_available       = is_available;
+    klass->act_stage2_config  = act_stage2_config;
+    klass->get_ip_method_auto = get_ip_method_auto;
 
     klass->get_type_description          = get_type_description;
     klass->can_auto_connect              = can_auto_connect;
@@ -19166,29 +17899,17 @@ nm_device_class_init(NMDeviceClass *klass)
                                                 G_TYPE_BOOLEAN,
                                                 0);
 
-    signals[IP4_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP4_CONFIG_CHANGED,
-                                               G_OBJECT_CLASS_TYPE(object_class),
-                                               G_SIGNAL_RUN_FIRST,
-                                               0,
-                                               NULL,
-                                               NULL,
-                                               NULL,
-                                               G_TYPE_NONE,
-                                               2,
-                                               G_TYPE_OBJECT,
-                                               G_TYPE_OBJECT);
-
-    signals[IP6_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP6_CONFIG_CHANGED,
-                                               G_OBJECT_CLASS_TYPE(object_class),
-                                               G_SIGNAL_RUN_FIRST,
-                                               0,
-                                               NULL,
-                                               NULL,
-                                               NULL,
-                                               G_TYPE_NONE,
-                                               2,
-                                               G_TYPE_OBJECT,
-                                               G_TYPE_OBJECT);
+    signals[L3CD_CHANGED] = g_signal_new(NM_DEVICE_L3CD_CHANGED,
+                                         G_OBJECT_CLASS_TYPE(object_class),
+                                         G_SIGNAL_RUN_FIRST,
+                                         0,
+                                         NULL,
+                                         NULL,
+                                         NULL,
+                                         G_TYPE_NONE,
+                                         2,
+                                         G_TYPE_POINTER, /* (const NML3ConfigData *l3cd_old) */
+                                         G_TYPE_POINTER /* (const NML3ConfigData *l3cd_new) */);
 
     signals[IP6_PREFIX_DELEGATED] =
         g_signal_new(NM_DEVICE_IP6_PREFIX_DELEGATED,
@@ -19251,6 +17972,16 @@ nm_device_class_init(NMDeviceClass *klass)
                                             NULL,
                                             G_TYPE_NONE,
                                             0);
+
+    signals[PLATFORM_ADDRESS_CHANGED] = g_signal_new(NM_DEVICE_PLATFORM_ADDRESS_CHANGED,
+                                                     G_OBJECT_CLASS_TYPE(object_class),
+                                                     G_SIGNAL_RUN_FIRST,
+                                                     0,
+                                                     NULL,
+                                                     NULL,
+                                                     NULL,
+                                                     G_TYPE_NONE,
+                                                     0);
 }
 
 /* Connection defaults from plugins */
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index d967bcb2..cfcd4ade 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -9,13 +9,14 @@
 
 #include <netinet/in.h>
 
-#include "nm-setting-connection.h"
-#include "nm-dbus-object.h"
-#include "nm-dbus-interface.h"
-#include "nm-connection.h"
-#include "nm-rfkill-manager.h"
 #include "NetworkManagerUtils.h"
+#include "nm-connection.h"
+#include "nm-dbus-interface.h"
+#include "nm-dbus-object.h"
 #include "nm-device-utils.h"
+#include "nm-l3cfg.h"
+#include "nm-rfkill-manager.h"
+#include "nm-setting-connection.h"
 
 /* Properties */
 #define NM_DEVICE_UDI                   "udi"
@@ -70,17 +71,17 @@
 #define NM_DEVICE_HAS_PENDING_ACTION "has-pending-action" /* Internal only */
 
 /* Internal signals */
-#define NM_DEVICE_DNS_LOOKUP_DONE       "dns-lookup-done"
-#define NM_DEVICE_IP4_CONFIG_CHANGED    "ip4-config-changed"
-#define NM_DEVICE_IP6_CONFIG_CHANGED    "ip6-config-changed"
-#define NM_DEVICE_IP6_PREFIX_DELEGATED  "ip6-prefix-delegated"
-#define NM_DEVICE_IP6_SUBNET_NEEDED     "ip6-subnet-needed"
-#define NM_DEVICE_REMOVED               "removed"
-#define NM_DEVICE_RECHECK_AUTO_ACTIVATE "recheck-auto-activate"
-#define NM_DEVICE_RECHECK_ASSUME        "recheck-assume"
-#define NM_DEVICE_STATE_CHANGED         "state-changed"
-#define NM_DEVICE_LINK_INITIALIZED      "link-initialized"
-#define NM_DEVICE_AUTOCONNECT_ALLOWED   "autoconnect-allowed"
+#define NM_DEVICE_DNS_LOOKUP_DONE          "dns-lookup-done"
+#define NM_DEVICE_L3CD_CHANGED             "l3cd-changed"
+#define NM_DEVICE_IP6_PREFIX_DELEGATED     "ip6-prefix-delegated"
+#define NM_DEVICE_IP6_SUBNET_NEEDED        "ip6-subnet-needed"
+#define NM_DEVICE_REMOVED                  "removed"
+#define NM_DEVICE_RECHECK_AUTO_ACTIVATE    "recheck-auto-activate"
+#define NM_DEVICE_RECHECK_ASSUME           "recheck-assume"
+#define NM_DEVICE_STATE_CHANGED            "state-changed"
+#define NM_DEVICE_LINK_INITIALIZED         "link-initialized"
+#define NM_DEVICE_AUTOCONNECT_ALLOWED      "autoconnect-allowed"
+#define NM_DEVICE_PLATFORM_ADDRESS_CHANGED "platform-address-changed"
 
 #define NM_DEVICE_STATISTICS_REFRESH_RATE_MS "refresh-rate-ms"
 #define NM_DEVICE_STATISTICS_TX_BYTES        "tx-bytes"
@@ -169,7 +170,7 @@ typedef struct _NMDeviceClass {
     NMDBusObjectClass parent;
 
     struct _NMDeviceClass *default_type_description_klass;
-    const char *           default_type_description;
+    const char            *default_type_description;
 
     const char *connection_type_supported;
 
@@ -208,7 +209,7 @@ typedef struct _NMDeviceClass {
 
     bool can_reapply_change_ovs_external_ids : 1;
 
-    void (*state_changed)(NMDevice *          device,
+    void (*state_changed)(NMDevice           *device,
                           NMDeviceState       new_state,
                           NMDeviceState       old_state,
                           NMDeviceStateReason reason);
@@ -232,11 +233,11 @@ typedef struct _NMDeviceClass {
      *
      * Returns: %TRUE on success, %FALSE on error
      */
-    gboolean (*create_and_realize)(NMDevice *             self,
-                                   NMConnection *         connection,
-                                   NMDevice *             parent,
+    gboolean (*create_and_realize)(NMDevice              *self,
+                                   NMConnection          *connection,
+                                   NMDevice              *parent,
                                    const NMPlatformLink **out_plink,
-                                   GError **              error);
+                                   GError               **error);
 
     /**
      * realize_start_notify():
@@ -294,13 +295,13 @@ typedef struct _NMDeviceClass {
      * which emits NM_DEVICE_RECHECK_AUTO_ACTIVATE signal. */
     gboolean (*get_autoconnect_allowed)(NMDevice *self);
 
-    gboolean (*can_auto_connect)(NMDevice *            self,
+    gboolean (*can_auto_connect)(NMDevice             *self,
                                  NMSettingsConnection *sett_conn,
-                                 char **               specific_object);
+                                 char                **specific_object);
 
-    guint32 (*get_configured_mtu)(NMDevice *         self,
+    guint32 (*get_configured_mtu)(NMDevice          *self,
                                   NMDeviceMtuSource *out_source,
-                                  gboolean *         out_force);
+                                  gboolean          *out_force);
 
     /* allow the subclass to overwrite the routing table. This is mainly useful
      * to change from partial mode (route-table=0) to full-sync mode (route-table=254). */
@@ -315,9 +316,9 @@ typedef struct _NMDeviceClass {
      * only the devices type and characteristics.  Does not use any live
      * network information like Wi-Fi scan lists etc.
      */
-    gboolean (*check_connection_compatible)(NMDevice *    self,
+    gboolean (*check_connection_compatible)(NMDevice     *self,
                                             NMConnection *connection,
-                                            GError **     error);
+                                            GError      **error);
 
     /* Checks whether the connection is likely available to be activated,
      * including any live network information like scan lists.  The connection
@@ -330,33 +331,28 @@ typedef struct _NMDeviceClass {
      *
      * Specifying @specific_object can only reduce the availability of a connection.
      */
-    gboolean (*check_connection_available)(NMDevice *                     self,
-                                           NMConnection *                 connection,
+    gboolean (*check_connection_available)(NMDevice                      *self,
+                                           NMConnection                  *connection,
                                            NMDeviceCheckConAvailableFlags flags,
-                                           const char *                   specific_object,
-                                           GError **                      error);
+                                           const char                    *specific_object,
+                                           GError                       **error);
 
-    gboolean (*complete_connection)(NMDevice *           self,
-                                    NMConnection *       connection,
-                                    const char *         specific_object,
+    gboolean (*complete_connection)(NMDevice            *self,
+                                    NMConnection        *connection,
+                                    const char          *specific_object,
                                     NMConnection *const *existing_connections,
-                                    GError **            error);
+                                    GError             **error);
 
     NMActStageReturn (*act_stage1_prepare)(NMDevice *self, NMDeviceStateReason *out_failure_reason);
     NMActStageReturn (*act_stage2_config)(NMDevice *self, NMDeviceStateReason *out_failure_reason);
-    NMActStageReturn (*act_stage3_ip_config_start)(NMDevice *           self,
-                                                   int                  addr_family,
-                                                   gpointer *           out_config,
-                                                   NMDeviceStateReason *out_failure_reason);
-    NMActStageReturn (*act_stage4_ip_config_timeout)(NMDevice *           self,
-                                                     int                  addr_family,
-                                                     NMDeviceStateReason *out_failure_reason);
+    void (*act_stage3_ip_config)(NMDevice *self, int addr_family);
+    gboolean (*ready_for_ip_config)(NMDevice *self);
 
-    void (*ip4_config_pre_commit)(NMDevice *self, NMIP4Config *config);
+    const char *(*get_ip_method_auto)(NMDevice *self, int addr_family);
 
     /* Async deactivating (in the DEACTIVATING phase) */
-    void (*deactivate_async)(NMDevice *                 self,
-                             GCancellable *             cancellable,
+    void (*deactivate_async)(NMDevice                  *self,
+                             GCancellable              *cancellable,
                              NMDeviceDeactivateCallback callback,
                              gpointer                   user_data);
 
@@ -372,13 +368,13 @@ typedef struct _NMDeviceClass {
     /* Update the connection with currently configured L2 settings */
     void (*update_connection)(NMDevice *device, NMConnection *connection);
 
-    gboolean (*master_update_slave_connection)(NMDevice *    self,
-                                               NMDevice *    slave,
+    gboolean (*master_update_slave_connection)(NMDevice     *self,
+                                               NMDevice     *slave,
                                                NMConnection *connection,
-                                               GError **     error);
+                                               GError      **error);
 
-    gboolean (*enslave_slave)(NMDevice *    self,
-                              NMDevice *    slave,
+    gboolean (*enslave_slave)(NMDevice     *self,
+                              NMDevice     *slave,
                               NMConnection *connection,
                               gboolean      configure);
 
@@ -396,12 +392,12 @@ typedef struct _NMDeviceClass {
 
     gboolean (*unmanaged_on_quit)(NMDevice *self);
 
-    gboolean (*can_reapply_change)(NMDevice *  self,
+    gboolean (*can_reapply_change)(NMDevice   *self,
                                    const char *setting_name,
-                                   NMSetting * s_old,
-                                   NMSetting * s_new,
+                                   NMSetting  *s_old,
+                                   NMSetting  *s_new,
                                    GHashTable *diffs,
-                                   GError **   error);
+                                   GError    **error);
 
     void (*reapply_connection)(NMDevice *self, NMConnection *con_old, NMConnection *con_new);
 
@@ -420,8 +416,8 @@ typedef struct _NMDeviceClass {
 GType nm_device_get_type(void);
 
 struct _NMDedupMultiIndex *nm_device_get_multi_index(NMDevice *self);
-NMNetns *                  nm_device_get_netns(NMDevice *self);
-NMPlatform *               nm_device_get_platform(NMDevice *self);
+NMNetns                   *nm_device_get_netns(NMDevice *self);
+NMPlatform                *nm_device_get_platform(NMDevice *self);
 
 const char *nm_device_get_udi(NMDevice *dev);
 const char *nm_device_get_iface(NMDevice *dev);
@@ -437,13 +433,13 @@ _nm_device_get_iface(NMDevice *device)
 int          nm_device_get_ifindex(NMDevice *dev);
 gboolean     nm_device_is_software(NMDevice *dev);
 gboolean     nm_device_is_real(NMDevice *dev);
-const char * nm_device_get_ip_iface(NMDevice *dev);
-const char * nm_device_get_ip_iface_from_platform(NMDevice *dev);
+const char  *nm_device_get_ip_iface(NMDevice *dev);
+const char  *nm_device_get_ip_iface_from_platform(NMDevice *dev);
 int          nm_device_get_ip_ifindex(const NMDevice *dev);
-const char * nm_device_get_driver(NMDevice *dev);
-const char * nm_device_get_driver_version(NMDevice *dev);
-const char * nm_device_get_type_desc(NMDevice *dev);
-const char * nm_device_get_type_description(NMDevice *dev);
+const char  *nm_device_get_driver(NMDevice *dev);
+const char  *nm_device_get_driver_version(NMDevice *dev);
+const char  *nm_device_get_type_desc(NMDevice *dev);
+const char  *nm_device_get_type_description(NMDevice *dev);
 NMDeviceType nm_device_get_device_type(NMDevice *dev);
 NMLinkType   nm_device_get_link_type(NMDevice *dev);
 NMMetered    nm_device_get_metered(NMDevice *dev);
@@ -460,16 +456,13 @@ const char *nm_device_get_permanent_hw_address_full(NMDevice *self,
                                                     gboolean *out_is_fake);
 const char *nm_device_get_initial_hw_address(NMDevice *dev);
 
-NMProxyConfig *nm_device_get_proxy_config(NMDevice *dev);
-
 NMDhcpConfig *nm_device_get_dhcp_config(NMDevice *dev, int addr_family);
-NMIP4Config * nm_device_get_ip4_config(NMDevice *dev);
-void          nm_device_replace_vpn4_config(NMDevice *dev, NMIP4Config *old, NMIP4Config *config);
 
-NMIP6Config *nm_device_get_ip6_config(NMDevice *dev);
-void         nm_device_replace_vpn6_config(NMDevice *dev, NMIP6Config *old, NMIP6Config *config);
+NML3Cfg *nm_device_get_l3cfg(NMDevice *self);
+
+const NML3ConfigData *nm_device_get_l3cd(NMDevice *self, gboolean get_commited);
 
-void nm_device_capture_initial_config(NMDevice *dev);
+void nm_device_l3cfg_commit(NMDevice *self, NML3CfgCommitType commit_type, gboolean commit_sync);
 
 int       nm_device_parent_get_ifindex(NMDevice *dev);
 NMDevice *nm_device_parent_get_device(NMDevice *dev);
@@ -478,7 +471,7 @@ gboolean  nm_device_parent_notify_changed(NMDevice *self,
                                           NMDevice *change_candidate,
                                           gboolean  device_removed);
 
-const char *nm_device_parent_find_for_connection(NMDevice *  self,
+const char *nm_device_parent_find_for_connection(NMDevice   *self,
                                                  const char *current_setting_parent);
 
 /* Master */
@@ -487,11 +480,11 @@ gboolean nm_device_is_master(NMDevice *dev);
 /* Slave */
 NMDevice *nm_device_get_master(NMDevice *dev);
 
-NMActRequest *         nm_device_get_act_request(NMDevice *dev);
-NMSettingsConnection * nm_device_get_settings_connection(NMDevice *dev);
-NMConnection *         nm_device_get_settings_connection_get_connection(NMDevice *self);
-NMConnection *         nm_device_get_applied_connection(NMDevice *dev);
-gboolean               nm_device_has_unmodified_applied_connection(NMDevice *            self,
+NMActRequest          *nm_device_get_act_request(NMDevice *dev);
+NMSettingsConnection  *nm_device_get_settings_connection(NMDevice *dev);
+NMConnection          *nm_device_get_settings_connection_get_connection(NMDevice *self);
+NMConnection          *nm_device_get_applied_connection(NMDevice *dev);
+gboolean               nm_device_has_unmodified_applied_connection(NMDevice             *self,
                                                                    NMSettingCompareFlags compare_flags);
 NMActivationStateFlags nm_device_get_activation_state_flags(NMDevice *self);
 
@@ -507,21 +500,21 @@ gboolean nm_device_has_carrier(NMDevice *dev);
 NMConnection *nm_device_generate_connection(NMDevice *self,
                                             NMDevice *master,
                                             gboolean *out_maybe_later,
-                                            GError ** error);
+                                            GError  **error);
 
-gboolean nm_device_master_update_slave_connection(NMDevice *    master,
-                                                  NMDevice *    slave,
+gboolean nm_device_master_update_slave_connection(NMDevice     *master,
+                                                  NMDevice     *slave,
                                                   NMConnection *connection,
-                                                  GError **     error);
+                                                  GError      **error);
 
 gboolean
 nm_device_can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object);
 
-gboolean nm_device_complete_connection(NMDevice *           device,
-                                       NMConnection *       connection,
-                                       const char *         specific_object,
+gboolean nm_device_complete_connection(NMDevice            *device,
+                                       NMConnection        *connection,
+                                       const char          *specific_object,
                                        NMConnection *const *existing_connections,
-                                       GError **            error);
+                                       GError             **error);
 
 gboolean
 nm_device_check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error);
@@ -547,7 +540,7 @@ RfKillType nm_device_get_rfkill_type(NMDevice *device);
 
 /* IPv6 prefix delegation */
 
-void nm_device_request_ip6_prefixes(NMDevice *self, int needed_prefixes);
+void nm_device_request_ip6_prefixes(NMDevice *self, guint needed_prefixes);
 
 gboolean nm_device_needs_ip6_subnet(NMDevice *self);
 
@@ -620,15 +613,15 @@ gboolean         nm_device_get_managed(NMDevice *device, gboolean for_user_reque
 NMUnmanagedFlags nm_device_get_unmanaged_mask(NMDevice *device, NMUnmanagedFlags flag);
 NMUnmanagedFlags nm_device_get_unmanaged_flags(NMDevice *device, NMUnmanagedFlags flag);
 void nm_device_set_unmanaged_flags(NMDevice *device, NMUnmanagedFlags flags, NMUnmanFlagOp set_op);
-void nm_device_set_unmanaged_by_flags(NMDevice *          device,
+void nm_device_set_unmanaged_by_flags(NMDevice           *device,
                                       NMUnmanagedFlags    flags,
                                       NMUnmanFlagOp       set_op,
                                       NMDeviceStateReason reason);
-void nm_device_set_unmanaged_by_flags_queue(NMDevice *          self,
+void nm_device_set_unmanaged_by_flags_queue(NMDevice           *self,
                                             NMUnmanagedFlags    flags,
                                             NMUnmanFlagOp       set_op,
                                             NMDeviceStateReason reason);
-void nm_device_set_unmanaged_by_user_settings(NMDevice *self);
+void nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now);
 void nm_device_set_unmanaged_by_user_udev(NMDevice *self);
 void nm_device_set_unmanaged_by_user_conf(NMDevice *self);
 void nm_device_set_unmanaged_by_quitting(NMDevice *device);
@@ -641,26 +634,26 @@ gboolean nm_device_has_capability(NMDevice *self, NMDeviceCapabilities caps);
 
 /*****************************************************************************/
 
-void nm_device_assume_state_get(NMDevice *   self,
-                                gboolean *   out_assume_state_guess_assume,
+void nm_device_assume_state_get(NMDevice    *self,
+                                gboolean    *out_assume_state_guess_assume,
                                 const char **out_assume_state_connection_uuid);
 void nm_device_assume_state_reset(NMDevice *self);
 
 /*****************************************************************************/
 
-gboolean nm_device_realize_start(NMDevice *            device,
+gboolean nm_device_realize_start(NMDevice             *device,
                                  const NMPlatformLink *plink,
                                  gboolean              assume_state_guess_assume,
-                                 const char *          assume_state_connection_uuid,
+                                 const char           *assume_state_connection_uuid,
                                  gboolean              set_nm_owned,
                                  NMUnmanFlagOp         unmanaged_user_explicit,
-                                 gboolean *            out_compatible,
-                                 GError **             error);
+                                 gboolean             *out_compatible,
+                                 GError              **error);
 void     nm_device_realize_finish(NMDevice *self, const NMPlatformLink *plink);
-gboolean nm_device_create_and_realize(NMDevice *    self,
+gboolean nm_device_create_and_realize(NMDevice     *self,
                                       NMConnection *connection,
-                                      NMDevice *    parent,
-                                      GError **     error);
+                                      NMDevice     *parent,
+                                      GError      **error);
 gboolean nm_device_unrealize(NMDevice *device, gboolean remove_resources, GError **error);
 
 void nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink);
@@ -686,7 +679,7 @@ typedef enum {
 NMDeviceAutoconnectBlockedFlags
 nm_device_autoconnect_blocked_get(NMDevice *device, NMDeviceAutoconnectBlockedFlags mask);
 
-void nm_device_autoconnect_blocked_set_full(NMDevice *                      device,
+void nm_device_autoconnect_blocked_set_full(NMDevice                       *device,
                                             NMDeviceAutoconnectBlockedFlags mask,
                                             NMDeviceAutoconnectBlockedFlags values);
 
@@ -717,7 +710,7 @@ void nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateRea
 
 gboolean nm_device_get_firmware_missing(NMDevice *self);
 
-void nm_device_disconnect_active_connection(NMActiveConnection *          active,
+void nm_device_disconnect_active_connection(NMActiveConnection           *active,
                                             NMDeviceStateReason           device_reason,
                                             NMActiveConnectionStateReason active_reason);
 
@@ -740,11 +733,11 @@ nm_device_has_pending_action(NMDevice *device)
 NMSettingsConnection *
 nm_device_get_best_connection(NMDevice *device, const char *specific_object, GError **error);
 
-gboolean nm_device_check_connection_available(NMDevice *                     device,
-                                              NMConnection *                 connection,
+gboolean nm_device_check_connection_available(NMDevice                      *device,
+                                              NMConnection                  *connection,
                                               NMDeviceCheckConAvailableFlags flags,
-                                              const char *                   specific_object,
-                                              GError **                      error);
+                                              const char                    *specific_object,
+                                              GError                       **error);
 
 void nm_device_notify_availability_maybe_changed(NMDevice *self);
 
@@ -754,17 +747,11 @@ NMConnection *nm_device_new_default_connection(NMDevice *self);
 
 const NMPObject *nm_device_get_best_default_route(NMDevice *self, int addr_family);
 
-void nm_device_spawn_iface_helper(NMDevice *self);
-
 gboolean nm_device_reapply(NMDevice *self, NMConnection *connection, GError **error);
 void     nm_device_reapply_settings_immediately(NMDevice *self);
 
 void nm_device_update_firewall_zone(NMDevice *self);
 void nm_device_update_metered(NMDevice *self);
-void nm_device_reactivate_ip_config(NMDevice *         device,
-                                    int                addr_family,
-                                    NMSettingIPConfig *s_ip_old,
-                                    NMSettingIPConfig *s_ip_new);
 
 gboolean nm_device_update_hw_address(NMDevice *self);
 void     nm_device_update_initial_hw_address(NMDevice *self);
@@ -774,24 +761,24 @@ guint    nm_device_get_supplicant_timeout(NMDevice *self);
 
 gboolean nm_device_auth_retries_try_next(NMDevice *self);
 
-gboolean nm_device_hw_addr_get_cloned(NMDevice *    self,
+gboolean nm_device_hw_addr_get_cloned(NMDevice     *self,
                                       NMConnection *connection,
                                       gboolean      is_wifi,
-                                      char **       hwaddr,
-                                      gboolean *    preserve,
-                                      GError **     error);
+                                      char        **hwaddr,
+                                      gboolean     *preserve,
+                                      GError      **error);
 
 typedef struct _NMDeviceConnectivityHandle NMDeviceConnectivityHandle;
 
-typedef void (*NMDeviceConnectivityCallback)(NMDevice *                  self,
+typedef void (*NMDeviceConnectivityCallback)(NMDevice                   *self,
                                              NMDeviceConnectivityHandle *handle,
                                              NMConnectivityState         state,
-                                             GError *                    error,
+                                             GError                     *error,
                                              gpointer                    user_data);
 
 void nm_device_check_connectivity_update_interval(NMDevice *self);
 
-NMDeviceConnectivityHandle *nm_device_check_connectivity(NMDevice *                   self,
+NMDeviceConnectivityHandle *nm_device_check_connectivity(NMDevice                    *self,
                                                          int                          addr_family,
                                                          NMDeviceConnectivityCallback callback,
                                                          gpointer                     user_data);
@@ -806,16 +793,16 @@ typedef void (*NMBtVTableRegisterCallback)(GError *error, gpointer user_data);
 
 struct _NMBtVTableNetworkServer {
     gboolean (*is_available)(const NMBtVTableNetworkServer *vtable,
-                             const char *                   addr,
-                             NMDevice *                     device_accept_busy);
+                             const char                    *addr,
+                             NMDevice                      *device_accept_busy);
 
     gboolean (*register_bridge)(const NMBtVTableNetworkServer *vtable,
-                                const char *                   addr,
-                                NMDevice *                     device,
-                                GCancellable *                 cancellable,
+                                const char                    *addr,
+                                NMDevice                      *device,
+                                GCancellable                  *cancellable,
                                 NMBtVTableRegisterCallback     callback,
                                 gpointer                       callback_user_data,
-                                GError **                      error);
+                                GError                       **error);
     gboolean (*unregister_bridge)(const NMBtVTableNetworkServer *vtable, NMDevice *device);
 };
 
diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index 4e178491..137bf8fb 100644
--- a/src/core/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -34,7 +34,7 @@
 struct _NMLldpListener {
     sd_lldp_rx *lldp_handle;
     GHashTable *lldp_neighbors;
-    GVariant *  variant;
+    GVariant   *variant;
 
     NMLldpListenerNotify notify_callback;
     gpointer             notify_user_data;
@@ -49,10 +49,10 @@ struct _NMLldpListener {
 /*****************************************************************************/
 
 typedef struct {
-    GVariant *        variant;
+    GVariant         *variant;
     sd_lldp_neighbor *neighbor_sd;
-    char *            chassis_id;
-    char *            port_id;
+    char             *chassis_id;
+    char             *port_id;
     guint8            chassis_id_type;
     guint8            port_id_type;
 } LldpNeighbor;
@@ -112,12 +112,12 @@ lldp_neighbor_get_raw(LldpNeighbor *neigh, const guint8 **out_raw_data, gsize *o
 
 static gboolean
 lldp_neighbor_id_get(struct sd_lldp_neighbor *neighbor_sd,
-                     guint8 *                 out_chassis_id_type,
-                     const guint8 **          out_chassis_id,
-                     gsize *                  out_chassis_id_len,
-                     guint8 *                 out_port_id_type,
-                     const guint8 **          out_port_id,
-                     gsize *                  out_port_id_len)
+                     guint8                  *out_chassis_id_type,
+                     const guint8           **out_chassis_id,
+                     gsize                   *out_chassis_id_len,
+                     guint8                  *out_port_id_type,
+                     const guint8           **out_port_id,
+                     gsize                   *out_port_id_len)
 {
     int r;
 
@@ -144,8 +144,8 @@ lldp_neighbor_id_hash(gconstpointer ptr)
     const LldpNeighbor *neigh = ptr;
     guint8              chassis_id_type;
     guint8              port_id_type;
-    const guint8 *      chassis_id;
-    const guint8 *      port_id;
+    const guint8       *chassis_id;
+    const guint8       *port_id;
     gsize               chassis_id_len;
     gsize               port_id_len;
     NMHashState         h;
@@ -270,9 +270,9 @@ parse_management_address_tlv(const uint8_t *data, gsize len)
 {
     GVariantBuilder builder;
     gsize           addr_len;
-    const guint8 *  v_object_id_arr;
+    const guint8   *v_object_id_arr;
     gsize           v_object_id_len;
-    const guint8 *  v_address_arr;
+    const guint8   *v_address_arr;
     gsize           v_address_len;
     guint32         v_interface_number;
     guint32         v_interface_number_subtype;
@@ -393,7 +393,7 @@ format_string(const guint8 *data, gsize len, gboolean allow_trim, char **out_to_
 static char *
 format_string_cp(const guint8 *data, gsize len, gboolean allow_trim)
 {
-    char *      s_free = NULL;
+    char       *s_free = NULL;
     const char *s;
 
     s = format_string(data, len, allow_trim, &s_free);
@@ -482,11 +482,11 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
 {
     struct ether_addr destination_address;
     GVariantBuilder   builder;
-    const char *      str;
-    const guint8 *    raw_data;
+    const char       *str;
+    const guint8     *raw_data;
     gsize             raw_len;
     uint16_t          u16;
-    uint8_t *         data8;
+    uint8_t          *data8;
     gsize             len;
     int               r;
 
@@ -537,19 +537,19 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
     else {
         gboolean        v_management_addresses_has = FALSE;
         GVariantBuilder v_management_addresses;
-        GVariant *      v_ieee_802_1_pvid        = NULL;
-        GVariant *      v_ieee_802_1_ppvid       = NULL;
-        GVariant *      v_ieee_802_1_ppvid_flags = NULL;
+        GVariant       *v_ieee_802_1_pvid        = NULL;
+        GVariant       *v_ieee_802_1_ppvid       = NULL;
+        GVariant       *v_ieee_802_1_ppvid_flags = NULL;
         GVariantBuilder v_ieee_802_1_ppvids;
-        GVariant *      v_ieee_802_1_vid       = NULL;
-        GVariant *      v_ieee_802_1_vlan_name = NULL;
+        GVariant       *v_ieee_802_1_vid       = NULL;
+        GVariant       *v_ieee_802_1_vlan_name = NULL;
         GVariantBuilder v_ieee_802_1_vlans;
-        GVariant *      v_ieee_802_3_mac_phy_conf   = NULL;
-        GVariant *      v_ieee_802_3_power_via_mdi  = NULL;
-        GVariant *      v_ieee_802_3_max_frame_size = NULL;
-        GVariant *      v_mud_url                   = NULL;
+        GVariant       *v_ieee_802_3_mac_phy_conf   = NULL;
+        GVariant       *v_ieee_802_3_power_via_mdi  = NULL;
+        GVariant       *v_ieee_802_3_max_frame_size = NULL;
+        GVariant       *v_mud_url                   = NULL;
         GVariantBuilder tmp_builder;
-        GVariant *      tmp_variant;
+        GVariant       *tmp_variant;
 
         do {
             guint8 oui[3];
@@ -636,7 +636,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
                 case SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME:
                 {
                     gs_free char *name_to_free = NULL;
-                    const char *  name;
+                    const char   *name;
                     guint32       vid;
                     gsize         l;
 
@@ -716,7 +716,7 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
                 case SD_LLDP_OUI_IANA_SUBTYPE_MUD:
                     if (!v_mud_url) {
                         gs_free char *s_free = NULL;
-                        const char *  s;
+                        const char   *s;
 
                         s = format_string(data8, len, TRUE, &s_free);
                         if (s)
@@ -778,8 +778,8 @@ GVariant *
 nmtst_lldp_parse_from_raw(const guint8 *raw_data, gsize raw_len)
 {
     nm_auto(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *neighbor_sd = NULL;
-    nm_auto(lldp_neighbor_freep) LldpNeighbor *        neigh       = NULL;
-    GVariant *                                         variant;
+    nm_auto(lldp_neighbor_freep) LldpNeighbor         *neigh       = NULL;
+    GVariant                                          *variant;
     int                                                r;
 
     g_assert(raw_data);
@@ -846,7 +846,7 @@ static void
 process_lldp_neighbor(NMLldpListener *self, sd_lldp_neighbor *neighbor_sd, gboolean remove)
 {
     nm_auto(lldp_neighbor_freep) LldpNeighbor *neigh = NULL;
-    LldpNeighbor *                             neigh_old;
+    LldpNeighbor                              *neigh_old;
 
     nm_assert(self);
     nm_assert(self->lldp_handle);
@@ -933,10 +933,10 @@ NMLldpListener *
 nm_lldp_listener_new(int                  ifindex,
                      NMLldpListenerNotify notify_callback,
                      gpointer             notify_user_data,
-                     GError **            error)
+                     GError             **error)
 {
     NMLldpListener *self = NULL;
-    sd_lldp_rx *    lldp_handle;
+    sd_lldp_rx     *lldp_handle;
     int             r;
 
     g_return_val_if_fail(ifindex > 0, FALSE);
diff --git a/src/core/devices/nm-lldp-listener.h b/src/core/devices/nm-lldp-listener.h
index 762eb3d2..f2e9bcd2 100644
--- a/src/core/devices/nm-lldp-listener.h
+++ b/src/core/devices/nm-lldp-listener.h
@@ -13,7 +13,7 @@ typedef void (*NMLldpListenerNotify)(NMLldpListener *self, gpointer user_data);
 NMLldpListener *nm_lldp_listener_new(int                  ifindex,
                                      NMLldpListenerNotify notify_callback,
                                      gpointer             notify_user_data,
-                                     GError **            error);
+                                     GError             **error);
 void            nm_lldp_listener_destroy(NMLldpListener *self);
 
 int       nm_lldp_listener_get_ifindex(NMLldpListener *self);
diff --git a/src/core/devices/ovs/nm-device-ovs-bridge.c b/src/core/devices/ovs/nm-device-ovs-bridge.c
index 26a5ba00..683ada13 100644
--- a/src/core/devices/ovs/nm-device-ovs-bridge.c
+++ b/src/core/devices/ovs/nm-device-ovs-bridge.c
@@ -42,11 +42,11 @@ get_type_description(NMDevice *device)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     /* The actual backing resources will be created on enslavement by the port
      * when it can identify the port and the bridge. */
@@ -66,13 +66,16 @@ get_generic_capabilities(NMDevice *device)
     return NM_DEVICE_CAP_IS_SOFTWARE;
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static gboolean
+ready_for_ip_config(NMDevice *device)
+{
+    return FALSE;
+}
+
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    return NM_ACT_STAGE_RETURN_IP_FAIL;
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
 static gboolean
@@ -141,7 +144,7 @@ static void
 nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_bridge);
 
@@ -154,7 +157,8 @@ nm_device_ovs_bridge_class_init(NMDeviceOvsBridgeClass *klass)
     device_class->create_and_realize                  = create_and_realize;
     device_class->unrealize                           = unrealize;
     device_class->get_generic_capabilities            = get_generic_capabilities;
-    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config                = act_stage3_ip_config;
+    device_class->ready_for_ip_config                 = ready_for_ip_config;
     device_class->enslave_slave                       = enslave_slave;
     device_class->release_slave                       = release_slave;
     device_class->can_reapply_change_ovs_external_ids = TRUE;
diff --git a/src/core/devices/ovs/nm-device-ovs-interface.c b/src/core/devices/ovs/nm-device-ovs-interface.c
index 46a612ac..74707d24 100644
--- a/src/core/devices/ovs/nm-device-ovs-interface.c
+++ b/src/core/devices/ovs/nm-device-ovs-interface.c
@@ -49,11 +49,11 @@ get_type_description(NMDevice *device)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     /* The actual backing resources will be created once an interface is
      * added to a port of ours, since there can be neither an empty port nor
@@ -71,7 +71,7 @@ get_generic_capabilities(NMDevice *device)
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
     return nm_ovsdb_is_ready(priv->ovsdb);
@@ -80,7 +80,7 @@ is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 static gboolean
 can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
     return nm_ovsdb_is_ready(priv->ovsdb);
@@ -124,14 +124,19 @@ link_changed(NMDevice *device, const NMPlatformLink *pllink)
         if (!nm_device_hw_addr_set_cloned(device,
                                           nm_device_get_applied_connection(device),
                                           FALSE)) {
-            nm_device_state_changed(device,
-                                    NM_DEVICE_STATE_FAILED,
-                                    NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            nm_device_devip_set_failed(device, AF_INET, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            nm_device_devip_set_failed(device, AF_INET6, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
             return;
         }
         nm_device_bring_up(device, TRUE, NULL);
-        nm_device_activate_schedule_stage3_ip_config_start(device);
+
+        nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_PENDING, NULL);
+        nm_device_devip_set_state(device, AF_INET6, NM_DEVICE_IP_STATE_PENDING, NULL);
+        nm_device_activate_schedule_stage3_ip_config(device, FALSE);
+        return;
     }
+
+    nm_device_activate_schedule_stage2_device_config(device, FALSE);
 }
 
 static gboolean
@@ -149,7 +154,7 @@ _is_internal_interface(NMDevice *device)
 static void
 set_platform_mtu_cb(GError *error, gpointer user_data)
 {
-    NMDevice *            device = user_data;
+    NMDevice             *device = user_data;
     NMDeviceOvsInterface *self   = NM_DEVICE_OVS_INTERFACE(device);
 
     if (error && !g_error_matches(error, NM_UTILS_ERROR, NM_UTILS_ERROR_CANCELLED_DISPOSING)) {
@@ -165,7 +170,7 @@ set_platform_mtu_cb(GError *error, gpointer user_data)
 static gboolean
 set_platform_mtu(NMDevice *device, guint32 mtu)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
     /*
@@ -188,31 +193,43 @@ set_platform_mtu(NMDevice *device, guint32 mtu)
     return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)->set_platform_mtu(device, mtu);
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static gboolean
+ready_for_ip_config(NMDevice *device)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
-    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(device);
+    return nm_device_get_ip_ifindex(device) > 0;
+}
+
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
+{
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
-    if (!_is_internal_interface(device))
-        return NM_ACT_STAGE_RETURN_IP_FAIL;
+    if (!_is_internal_interface(device)) {
+        nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
+        return;
+    }
 
+    /* FIXME(l3cfg): we should create the IP ifindex before stage3 start.
+     *
+     * For now it's here because when the ovs-interface enters stage3, then it's added to the
+     * controller (ovs-port) and the entry is create in the ovsdb. Only after that the kernel
+     * link appears.
+     *
+     * This should change. */
     if (nm_device_get_ip_ifindex(device) <= 0) {
         _LOGT(LOGD_DEVICE, "waiting for link to appear");
         priv->waiting_for_interface = TRUE;
-        return NM_ACT_STAGE_RETURN_POSTPONE;
+        nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_PENDING, NULL);
+        return;
     }
 
     if (!nm_device_hw_addr_set_cloned(device, nm_device_get_applied_connection(device), FALSE)) {
-        *out_failure_reason = NM_DEVICE_STATE_REASON_CONFIG_FAILED;
-        return NM_ACT_STAGE_RETURN_FAILURE;
+        nm_device_devip_set_failed(device, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+        return;
     }
 
-    return NM_DEVICE_CLASS(nm_device_ovs_interface_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
 static gboolean
@@ -224,15 +241,15 @@ can_unmanaged_external_down(NMDevice *self)
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
     priv->waiting_for_interface = FALSE;
 }
 
 typedef struct {
-    NMDeviceOvsInterface *     self;
-    GCancellable *             cancellable;
+    NMDeviceOvsInterface      *self;
+    GCancellable              *cancellable;
     NMDeviceDeactivateCallback callback;
     gpointer                   callback_user_data;
     gulong                     link_changed_id;
@@ -258,14 +275,14 @@ deactivate_invoke_cb(DeactivateData *data, GError *error)
 }
 
 static void
-deactivate_link_changed_cb(NMPlatform *    platform,
+deactivate_link_changed_cb(NMPlatform     *platform,
                            int             obj_type_i,
                            int             ifindex,
                            NMPlatformLink *info,
                            int             change_type_i,
                            DeactivateData *data)
 {
-    NMDeviceOvsInterface *           self        = data->self;
+    NMDeviceOvsInterface            *self        = data->self;
     const NMPlatformSignalChangeType change_type = change_type_i;
 
     if (change_type == NM_PLATFORM_SIGNAL_REMOVED
@@ -280,7 +297,7 @@ deactivate_link_changed_cb(NMPlatform *    platform,
 static gboolean
 deactivate_link_timeout(gpointer user_data)
 {
-    DeactivateData *      data = user_data;
+    DeactivateData       *data = user_data;
     NMDeviceOvsInterface *self = data->self;
 
     _LOGT(LOGD_DEVICE, "deactivate: timeout waiting link removal");
@@ -300,7 +317,7 @@ deactivate_cancelled_cb(GCancellable *cancellable, gpointer user_data)
 static void
 deactivate_cb_on_idle(gpointer user_data, GCancellable *cancellable)
 {
-    DeactivateData *data                  = user_data;
+    DeactivateData       *data            = user_data;
     gs_free_error GError *cancelled_error = NULL;
 
     g_cancellable_set_error_if_cancelled(data->cancellable, &cancelled_error);
@@ -308,14 +325,14 @@ deactivate_cb_on_idle(gpointer user_data, GCancellable *cancellable)
 }
 
 static void
-deactivate_async(NMDevice *                 device,
-                 GCancellable *             cancellable,
+deactivate_async(NMDevice                  *device,
+                 GCancellable              *cancellable,
                  NMDeviceDeactivateCallback callback,
                  gpointer                   callback_user_data)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(device);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(device);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
-    DeactivateData *             data;
+    DeactivateData              *data;
 
     _LOGT(LOGD_CORE, "deactivate: start async");
 
@@ -402,7 +419,7 @@ nm_device_ovs_interface_init(NMDeviceOvsInterface *self)
 static void
 dispose(GObject *object)
 {
-    NMDeviceOvsInterface *       self = NM_DEVICE_OVS_INTERFACE(object);
+    NMDeviceOvsInterface        *self = NM_DEVICE_OVS_INTERFACE(object);
     NMDeviceOvsInterfacePrivate *priv = NM_DEVICE_OVS_INTERFACE_GET_PRIVATE(self);
 
     if (priv->ovsdb) {
@@ -420,9 +437,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_ovs_interface = {
 static void
 nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *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;
 
@@ -443,7 +460,8 @@ nm_device_ovs_interface_class_init(NMDeviceOvsInterfaceClass *klass)
     device_class->is_available                        = is_available;
     device_class->check_connection_compatible         = check_connection_compatible;
     device_class->link_changed                        = link_changed;
-    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config                = act_stage3_ip_config;
+    device_class->ready_for_ip_config                 = ready_for_ip_config;
     device_class->can_unmanaged_external_down         = can_unmanaged_external_down;
     device_class->set_platform_mtu                    = set_platform_mtu;
     device_class->get_configured_mtu                  = nm_device_get_configured_mtu_for_wired;
diff --git a/src/core/devices/ovs/nm-device-ovs-port.c b/src/core/devices/ovs/nm-device-ovs-port.c
index f11c5852..4419cfe9 100644
--- a/src/core/devices/ovs/nm-device-ovs-port.c
+++ b/src/core/devices/ovs/nm-device-ovs-port.c
@@ -41,11 +41,11 @@ get_type_description(NMDevice *device)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     /* The port will be added to ovsdb when an interface is enslaved,
      * because there's no such thing like an empty port. */
@@ -59,13 +59,16 @@ get_generic_capabilities(NMDevice *device)
     return NM_DEVICE_CAP_IS_SOFTWARE;
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static gboolean
+ready_for_ip_config(NMDevice *device)
+{
+    return FALSE;
+}
+
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    return NM_ACT_STAGE_RETURN_IP_FAIL;
+    nm_device_devip_set_state(device, addr_family, NM_DEVICE_IP_STATE_READY, NULL);
 }
 
 static void
@@ -87,10 +90,10 @@ add_iface_cb(GError *error, gpointer user_data)
 static gboolean
 enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
 {
-    NMDeviceOvsPort *   self      = NM_DEVICE_OVS_PORT(device);
+    NMDeviceOvsPort    *self      = NM_DEVICE_OVS_PORT(device);
     NMActiveConnection *ac_port   = NULL;
     NMActiveConnection *ac_bridge = NULL;
-    NMDevice *          bridge_device;
+    NMDevice           *bridge_device;
 
     if (!configure)
         return TRUE;
@@ -143,18 +146,23 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 {
     NMDeviceOvsPort *self = NM_DEVICE_OVS_PORT(device);
 
+    _LOGI(LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface(slave));
+
+    /* Even if the an interface's device has gone away (e.g. externally
+     * removed and thus we're called with configure=FALSE), we still need
+     * to make sure its OVSDB entry is gone.
+     */
+    nm_ovsdb_del_interface(nm_ovsdb_get(),
+                           nm_device_get_iface(slave),
+                           del_iface_cb,
+                           g_object_ref(slave));
+
     if (configure) {
-        _LOGI(LOGD_DEVICE, "releasing ovs interface %s", nm_device_get_ip_iface(slave));
-        nm_ovsdb_del_interface(nm_ovsdb_get(),
-                               nm_device_get_iface(slave),
-                               del_iface_cb,
-                               g_object_ref(slave));
         /* Open VSwitch is going to delete this one. We must ignore what happens
          * next with the interface. */
         if (NM_IS_DEVICE_OVS_INTERFACE(slave))
             nm_device_update_from_platform_link(slave, NULL);
-    } else
-        _LOGI(LOGD_DEVICE, "ovs interface %s was released", nm_device_get_ip_iface(slave));
+    }
 }
 
 /*****************************************************************************/
@@ -174,7 +182,7 @@ static void
 nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
 {
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
-    NMDeviceClass *    device_class      = NM_DEVICE_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
 
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_ovs_port);
 
@@ -186,7 +194,8 @@ nm_device_ovs_port_class_init(NMDeviceOvsPortClass *klass)
     device_class->get_type_description                = get_type_description;
     device_class->create_and_realize                  = create_and_realize;
     device_class->get_generic_capabilities            = get_generic_capabilities;
-    device_class->act_stage3_ip_config_start          = act_stage3_ip_config_start;
+    device_class->act_stage3_ip_config                = act_stage3_ip_config;
+    device_class->ready_for_ip_config                 = ready_for_ip_config;
     device_class->enslave_slave                       = enslave_slave;
     device_class->release_slave                       = release_slave;
     device_class->can_reapply_change_ovs_external_ids = TRUE;
diff --git a/src/core/devices/ovs/nm-ovs-factory.c b/src/core/devices/ovs/nm-ovs-factory.c
index 6f5d8832..3ff0abf8 100644
--- a/src/core/devices/ovs/nm-ovs-factory.c
+++ b/src/core/devices/ovs/nm-ovs-factory.c
@@ -61,7 +61,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(
                                                 NM_SETTING_OVS_PORT_SETTING_NAME))
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_OVS);
     return g_object_new(NM_TYPE_OVS_FACTORY, NULL);
@@ -106,14 +106,14 @@ new_device_from_type(const char *name, NMDeviceType device_type)
 }
 
 static void
-ovsdb_device_added(NMOvsdb *        ovsdb,
-                   const char *     name,
+ovsdb_device_added(NMOvsdb         *ovsdb,
+                   const char      *name,
                    guint            device_type_i,
-                   const char *     subtype,
+                   const char      *subtype,
                    NMDeviceFactory *self)
 {
     const NMDeviceType device_type = device_type_i;
-    NMDevice *         device;
+    NMDevice          *device;
 
     if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE
         && !NM_IN_STRSET(subtype, "internal", "patch")) {
@@ -133,14 +133,14 @@ ovsdb_device_added(NMOvsdb *        ovsdb,
 }
 
 static void
-ovsdb_device_removed(NMOvsdb *        ovsdb,
-                     const char *     name,
+ovsdb_device_removed(NMOvsdb         *ovsdb,
+                     const char      *name,
                      guint            device_type_i,
-                     const char *     subtype,
+                     const char      *subtype,
                      NMDeviceFactory *self)
 {
     const NMDeviceType device_type = device_type_i;
-    NMDevice *         device      = NULL;
+    NMDevice          *device      = NULL;
     NMDeviceState      device_state;
     gboolean           is_system_interface = FALSE;
 
@@ -149,8 +149,8 @@ ovsdb_device_removed(NMOvsdb *        ovsdb,
         return;
 
     if (device_type == NM_DEVICE_TYPE_OVS_INTERFACE && nm_streq0(subtype, "system")) {
-        NMDevice *             d;
-        const CList *          list;
+        NMDevice              *d;
+        const CList           *list;
         NMSettingOvsInterface *s_ovs_int;
 
         /* The device associated to an OVS system interface can be of
@@ -165,15 +165,6 @@ ovsdb_device_removed(NMOvsdb *        ovsdb,
                 continue;
             if (!nm_streq0(nm_setting_ovs_interface_get_interface_type(s_ovs_int), "system"))
                 continue;
-            /* Failing the system interface device is almost always the right
-             * thing to do when the ovsdb entry is removed. However, to avoid
-             * that a late device-removed signal tears down a different,
-             * newly-activated connection, let's also check that we have a master.
-             * Or in alternative, that the device is assumed/external: in such
-             * case it's always fine to fail the device.
-             */
-            if (!nm_device_get_master(d) && !nm_device_sys_iface_state_is_external_or_assume(d))
-                continue;
 
             device = d;
         }
@@ -195,22 +186,23 @@ ovsdb_device_removed(NMOvsdb *        ovsdb,
     }
 
     /* OVS system interfaces still exist even without the ovsdb entry */
-    if (!is_system_interface && device_state == NM_DEVICE_STATE_UNMANAGED) {
+    if (!is_system_interface
+        && (device_state == NM_DEVICE_STATE_UNMANAGED
+            || device_state == NM_DEVICE_STATE_UNAVAILABLE))
         nm_device_unrealize(device, TRUE, NULL);
-    }
 }
 
 static void
-ovsdb_interface_failed(NMOvsdb *        ovsdb,
-                       const char *     name,
-                       const char *     connection_uuid,
-                       const char *     error,
+ovsdb_interface_failed(NMOvsdb         *ovsdb,
+                       const char      *name,
+                       const char      *connection_uuid,
+                       const char      *error,
                        NMDeviceFactory *self)
 {
-    NMDevice *             device     = NULL;
-    NMSettingsConnection * connection = NULL;
-    NMConnection *         c;
-    const char *           type;
+    NMDevice              *device     = NULL;
+    NMSettingsConnection  *connection = NULL;
+    NMConnection          *c;
+    const char            *type;
     NMSettingOvsInterface *s_ovs_int;
     gboolean               is_patch = FALSE;
     gboolean               ignore;
@@ -280,14 +272,14 @@ start(NMDeviceFactory *self)
 }
 
 static NMDevice *
-create_device(NMDeviceFactory *     self,
-              const char *          iface,
+create_device(NMDeviceFactory      *self,
+              const char           *iface,
               const NMPlatformLink *plink,
-              NMConnection *        connection,
-              gboolean *            out_ignore)
+              NMConnection         *connection,
+              gboolean             *out_ignore)
 {
     NMDeviceType device_type     = NM_DEVICE_TYPE_UNKNOWN;
-    const char * connection_type = NULL;
+    const char  *connection_type = NULL;
 
     if (g_strcmp0(iface, "ovs-system") == 0) {
         *out_ignore = TRUE;
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index e5673347..7c45e0e4 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -30,34 +30,34 @@
 #endif
 
 typedef struct {
-    char *     port_uuid;
-    char *     name;
-    char *     connection_uuid;
+    char      *port_uuid;
+    char      *name;
+    char      *connection_uuid;
     GPtrArray *interfaces; /* interface uuids */
-    GArray *   external_ids;
+    GArray    *external_ids;
 } OpenvswitchPort;
 
 typedef struct {
-    char *     bridge_uuid;
-    char *     name;
-    char *     connection_uuid;
+    char      *bridge_uuid;
+    char      *name;
+    char      *connection_uuid;
     GPtrArray *ports; /* port uuids */
-    GArray *   external_ids;
+    GArray    *external_ids;
 } OpenvswitchBridge;
 
 typedef struct {
-    char *  interface_uuid;
-    char *  name;
-    char *  type;
-    char *  connection_uuid;
+    char   *interface_uuid;
+    char   *name;
+    char   *type;
+    char   *connection_uuid;
     GArray *external_ids;
 } OpenvswitchInterface;
 
 /*****************************************************************************/
 
 typedef void (*OvsdbMethodCallback)(NMOvsdb *self,
-                                    json_t * response,
-                                    GError * error,
+                                    json_t  *response,
+                                    GError  *error,
                                     gpointer user_data);
 
 typedef enum {
@@ -77,27 +77,27 @@ typedef union {
         NMConnection *bridge;
         NMConnection *port;
         NMConnection *interface;
-        NMDevice *    bridge_device;
-        NMDevice *    interface_device;
+        NMDevice     *bridge_device;
+        NMDevice     *interface_device;
     } add_interface;
     struct {
         char *ifname;
     } del_interface;
     struct {
-        char *  ifname;
+        char   *ifname;
         guint32 mtu;
     } set_interface_mtu;
     struct {
         NMDeviceType device_type;
-        char *       ifname;
-        char *       connection_uuid;
-        GHashTable * exid_old;
-        GHashTable * exid_new;
+        char        *ifname;
+        char        *connection_uuid;
+        GHashTable  *exid_old;
+        GHashTable  *exid_new;
     } set_external_ids;
 } OvsdbMethodPayload;
 
 typedef struct {
-    NMOvsdb *           self;
+    NMOvsdb            *self;
     CList               calls_lst;
     guint64             call_id;
     OvsdbCommand        command;
@@ -120,11 +120,11 @@ static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
     GSocketConnection *conn;
-    GCancellable *     conn_cancellable;
+    GCancellable      *conn_cancellable;
     char               buf[4096]; /* Input buffer */
     size_t             bufp;      /* Last decoded byte in the input buffer. */
-    GString *          input;     /* JSON stream waiting for decoding. */
-    GString *          output;    /* JSON stream to be sent. */
+    GString           *input;     /* JSON stream waiting for decoding. */
+    GString           *output;    /* JSON stream to be sent. */
     guint64            call_id_counter;
 
     CList calls_lst_head;
@@ -132,7 +132,7 @@ typedef struct {
     GHashTable *interfaces; /* interface uuid => OpenvswitchInterface */
     GHashTable *ports;      /* port uuid => OpenvswitchPort */
     GHashTable *bridges;    /* bridge uuid => OpenvswitchBridge */
-    char *      db_uuid;
+    char       *db_uuid;
     guint       num_failures;
     guint       num_pending_deletions;
     bool        ready : 1;
@@ -328,25 +328,25 @@ _free_interface(OpenvswitchInterface *ovs_interface)
 /*****************************************************************************/
 
 static void
-_signal_emit_device_added(NMOvsdb *    self,
-                          const char * name,
+_signal_emit_device_added(NMOvsdb     *self,
+                          const char  *name,
                           NMDeviceType device_type,
-                          const char * device_subtype)
+                          const char  *device_subtype)
 {
     g_signal_emit(self, signals[DEVICE_ADDED], 0, name, (guint) device_type, device_subtype);
 }
 
 static void
-_signal_emit_device_removed(NMOvsdb *    self,
-                            const char * name,
+_signal_emit_device_removed(NMOvsdb     *self,
+                            const char  *name,
                             NMDeviceType device_type,
-                            const char * device_subtype)
+                            const char  *device_subtype)
 {
     g_signal_emit(self, signals[DEVICE_REMOVED], 0, name, (guint) device_type, device_subtype);
 }
 
 static void
-_signal_emit_interface_failed(NMOvsdb *   self,
+_signal_emit_interface_failed(NMOvsdb    *self,
                               const char *name,
                               const char *connection_uuid,
                               const char *error)
@@ -363,14 +363,14 @@ _signal_emit_interface_failed(NMOvsdb *   self,
  * there's no command pending completion.
  */
 static void
-ovsdb_call_method(NMOvsdb *                 self,
+ovsdb_call_method(NMOvsdb                  *self,
                   OvsdbMethodCallback       callback,
                   gpointer                  user_data,
                   gboolean                  add_first,
                   OvsdbCommand              command,
                   const OvsdbMethodPayload *payload)
 {
-    NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
+    NMOvsdbPrivate  *priv = NM_OVSDB_GET_PRIVATE(self);
     OvsdbMethodCall *call;
 
     /* Ensure we're not unsynchronized before we queue the method call. */
@@ -653,11 +653,11 @@ _set_port_interfaces(json_t *params, const char *ifname, json_t *new_interfaces)
 static json_t *
 _j_create_external_ids_array_new(NMConnection *connection)
 {
-    json_t *                 array;
-    const char *const *      external_ids   = NULL;
+    json_t                  *array;
+    const char *const       *external_ids   = NULL;
     guint                    n_external_ids = 0;
     guint                    i;
-    const char *             uuid;
+    const char              *uuid;
     NMSettingOvsExternalIDs *s_exid;
 
     nm_assert(NM_IS_CONNECTION(connection));
@@ -688,10 +688,10 @@ _j_create_external_ids_array_update(const char *connection_uuid,
                                     GHashTable *exid_new)
 {
     GHashTableIter iter;
-    json_t *       mutations;
-    json_t *       array;
-    const char *   key;
-    const char *   val;
+    json_t        *mutations;
+    json_t        *array;
+    const char    *key;
+    const char    *val;
 
     nm_assert(connection_uuid);
 
@@ -741,21 +741,24 @@ _j_create_external_ids_array_update(const char *connection_uuid,
 /**
  * _insert_interface:
  *
- * Returns an commands that adds new interface from a given connection.
+ * Returns a command that adds new interface from a given connection.
  */
 static void
-_insert_interface(json_t *      params,
+_insert_interface(json_t       *params,
                   NMConnection *interface,
-                  NMDevice *    interface_device,
-                  const char *  cloned_mac)
+                  NMDevice     *interface_device,
+                  const char   *cloned_mac)
 {
-    const char *           type = NULL;
+    const char            *type = NULL;
     NMSettingOvsInterface *s_ovs_iface;
-    NMSettingOvsDpdk *     s_ovs_dpdk;
-    NMSettingOvsPatch *    s_ovs_patch;
-    const char *           dpdk_devargs;
-    json_t *               options = json_array();
-    json_t *               row;
+    NMSettingOvsDpdk      *s_ovs_dpdk;
+    const char            *devargs;
+    guint32                n_rxq;
+    char                   sbuf[64];
+    json_t                *dpdk_array;
+    NMSettingOvsPatch     *s_ovs_patch;
+    json_t                *options = json_array();
+    json_t                *row;
     guint32                mtu = 0;
 
     s_ovs_iface = nm_connection_get_setting_ovs_interface(interface);
@@ -778,11 +781,21 @@ _insert_interface(json_t *      params,
         s_ovs_patch = nm_connection_get_setting_ovs_patch(interface);
 
     if (s_ovs_dpdk) {
-        dpdk_devargs = nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk);
-        if (dpdk_devargs)
-            json_array_append_new(options, json_pack("[[s, s]]", "dpdk-devargs", dpdk_devargs));
-        else
-            json_array_append_new(options, json_array());
+        devargs = nm_setting_ovs_dpdk_get_devargs(s_ovs_dpdk);
+        n_rxq   = nm_setting_ovs_dpdk_get_n_rxq(s_ovs_dpdk);
+
+        dpdk_array = json_array();
+
+        if (devargs)
+            json_array_append_new(dpdk_array, json_pack("[s,s]", "dpdk-devargs", devargs));
+
+        if (n_rxq != 0) {
+            json_array_append_new(dpdk_array,
+                                  json_pack("[s,s]", "n_rxq", nm_sprintf_buf(sbuf, "%u", n_rxq)));
+        }
+
+        json_array_append_new(options, dpdk_array);
+
     } else if (s_ovs_patch) {
         json_array_append_new(
             options,
@@ -828,13 +841,13 @@ static void
 _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
 {
     NMSettingOvsPort *s_ovs_port;
-    const char *      vlan_mode      = NULL;
+    const char       *vlan_mode      = NULL;
     guint             tag            = 0;
-    const char *      lacp           = NULL;
-    const char *      bond_mode      = NULL;
+    const char       *lacp           = NULL;
+    const char       *bond_mode      = NULL;
     guint             bond_updelay   = 0;
     guint             bond_downdelay = 0;
-    json_t *          row;
+    json_t           *row;
 
     s_ovs_port = nm_connection_get_setting_ovs_port(port);
 
@@ -885,19 +898,19 @@ _insert_port(json_t *params, NMConnection *port, json_t *new_interfaces)
  * Returns an commands that adds new bridge from a given connection.
  */
 static void
-_insert_bridge(json_t *      params,
+_insert_bridge(json_t       *params,
                NMConnection *bridge,
-               NMDevice *    bridge_device,
-               json_t *      new_ports,
-               const char *  cloned_mac)
+               NMDevice     *bridge_device,
+               json_t       *new_ports,
+               const char   *cloned_mac)
 {
     NMSettingOvsBridge *s_ovs_bridge;
-    const char *        fail_mode             = NULL;
+    const char         *fail_mode             = NULL;
     gboolean            mcast_snooping_enable = FALSE;
     gboolean            rstp_enable           = FALSE;
     gboolean            stp_enable            = FALSE;
-    const char *        datapath_type         = NULL;
-    json_t *            row;
+    const char         *datapath_type         = NULL;
+    json_t             *row;
 
     s_ovs_bridge = nm_connection_get_setting_ovs_bridge(bridge);
 
@@ -977,24 +990,24 @@ _inc_next_cfg(const char *db_uuid)
  * a parent @port and @bridge if needed.
  */
 static void
-_add_interface(NMOvsdb *     self,
-               json_t *      params,
+_add_interface(NMOvsdb      *self,
+               json_t       *params,
                NMConnection *bridge,
                NMConnection *port,
                NMConnection *interface,
-               NMDevice *    bridge_device,
-               NMDevice *    interface_device)
+               NMDevice     *bridge_device,
+               NMDevice     *interface_device)
 {
-    NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
-    GHashTableIter        iter;
-    const char *          port_uuid;
-    const char *          interface_uuid;
-    const char *          bridge_name;
-    const char *          port_name;
-    const char *          interface_name;
-    OpenvswitchBridge *   ovs_bridge           = NULL;
-    OpenvswitchPort *     ovs_port             = NULL;
-    OpenvswitchInterface *ovs_interface        = NULL;
+    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
+    GHashTableIter              iter;
+    const char                 *port_uuid;
+    const char                 *interface_uuid;
+    const char                 *bridge_name;
+    const char                 *port_name;
+    const char                 *interface_name;
+    OpenvswitchBridge          *ovs_bridge     = NULL;
+    OpenvswitchPort            *ovs_port       = NULL;
+    OpenvswitchInterface       *ovs_interface  = NULL;
     nm_auto_decref_json json_t *bridges        = NULL;
     nm_auto_decref_json json_t *new_bridges    = NULL;
     nm_auto_decref_json json_t *ports          = NULL;
@@ -1003,9 +1016,9 @@ _add_interface(NMOvsdb *     self,
     nm_auto_decref_json json_t *new_interfaces = NULL;
     gboolean                    has_interface  = FALSE;
     gboolean                    interface_is_local;
-    gs_free char *              bridge_cloned_mac    = NULL;
-    gs_free char *              interface_cloned_mac = NULL;
-    GError *                    error                = NULL;
+    gs_free char               *bridge_cloned_mac    = NULL;
+    gs_free char               *interface_cloned_mac = NULL;
+    GError                     *error                = NULL;
     int                         pi;
     int                         ii;
 
@@ -1162,13 +1175,13 @@ _add_interface(NMOvsdb *     self,
 static void
 _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
 {
-    NMOvsdbPrivate *      priv = NM_OVSDB_GET_PRIVATE(self);
-    GHashTableIter        iter;
-    char *                port_uuid;
-    char *                interface_uuid;
-    OpenvswitchBridge *   ovs_bridge;
-    OpenvswitchPort *     ovs_port;
-    OpenvswitchInterface *ovs_interface;
+    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
+    GHashTableIter              iter;
+    char                       *port_uuid;
+    char                       *interface_uuid;
+    OpenvswitchBridge          *ovs_bridge;
+    OpenvswitchPort            *ovs_port;
+    OpenvswitchInterface       *ovs_interface;
     nm_auto_decref_json json_t *bridges     = NULL;
     nm_auto_decref_json json_t *new_bridges = NULL;
     gboolean                    bridges_changed;
@@ -1272,9 +1285,9 @@ _delete_interface(NMOvsdb *self, json_t *params, const char *ifname)
 static void
 ovsdb_next_command(NMOvsdb *self)
 {
-    NMOvsdbPrivate *    priv = NM_OVSDB_GET_PRIVATE(self);
-    OvsdbMethodCall *   call;
-    char *              cmd;
+    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
+    OvsdbMethodCall            *call;
+    char                       *cmd;
     nm_auto_decref_json json_t *msg = NULL;
 
     if (!priv->conn)
@@ -1420,9 +1433,9 @@ static void
 _uuids_to_array_inplace(GPtrArray *array, const json_t *items)
 {
     const char *key;
-    json_t *    value;
+    json_t     *value;
     size_t      index = 0;
-    json_t *    set_value;
+    json_t     *set_value;
     size_t      set_index;
 
     while (index < json_array_size(items)) {
@@ -1475,8 +1488,8 @@ _external_ids_extract(json_t *external_ids, GArray **out_array, const char **out
     array = json_array_get(external_ids, 1);
 
     json_array_foreach (array, index, value) {
-        const char *       key = json_string_value(json_array_get(value, 0));
-        const char *       val = json_string_value(json_array_get(value, 1));
+        const char        *key = json_string_value(json_array_get(value, 0));
+        const char        *val = json_string_value(json_array_get(value, 1));
         NMUtilsNamedValue *v;
 
         if (!key || !val)
@@ -1559,20 +1572,20 @@ static void
 ovsdb_got_update(NMOvsdb *self, json_t *msg)
 {
     NMOvsdbPrivate *priv      = NM_OVSDB_GET_PRIVATE(self);
-    json_t *        ovs       = NULL;
-    json_t *        bridge    = NULL;
-    json_t *        port      = NULL;
-    json_t *        interface = NULL;
-    json_t *        items;
-    json_t *        external_ids;
+    json_t         *ovs       = NULL;
+    json_t         *bridge    = NULL;
+    json_t         *port      = NULL;
+    json_t         *interface = NULL;
+    json_t         *items;
+    json_t         *external_ids;
     json_error_t    json_error = {
         0,
     };
-    void *      iter;
+    void       *iter;
     const char *name;
     const char *key;
     const char *type;
-    json_t *    value;
+    json_t     *value;
 
     if (json_unpack_ex(msg,
                        &json_error,
@@ -1602,10 +1615,10 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
     }
 
     json_object_foreach (interface, key, value) {
-        OpenvswitchInterface *ovs_interface;
+        OpenvswitchInterface  *ovs_interface;
         gs_unref_array GArray *external_ids_arr = NULL;
-        const char *           connection_uuid  = NULL;
-        json_t *               error            = NULL;
+        const char            *connection_uuid  = NULL;
+        json_t                *error            = NULL;
         int                    r;
 
         r = json_unpack(value,
@@ -1726,10 +1739,10 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 
     json_object_foreach (port, key, value) {
         gs_unref_ptrarray GPtrArray *interfaces = NULL;
-        OpenvswitchPort *            ovs_port;
-        gs_unref_array GArray *external_ids_arr = NULL;
-        const char *           connection_uuid  = NULL;
-        int                    r;
+        OpenvswitchPort             *ovs_port;
+        gs_unref_array GArray       *external_ids_arr = NULL;
+        const char                  *connection_uuid  = NULL;
+        int                          r;
 
         r = json_unpack(value,
                         "{s:{s:s, s:o, s:o}}",
@@ -1827,10 +1840,10 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 
     json_object_foreach (bridge, key, value) {
         gs_unref_ptrarray GPtrArray *ports = NULL;
-        OpenvswitchBridge *          ovs_bridge;
-        gs_unref_array GArray *external_ids_arr = NULL;
-        const char *           connection_uuid  = NULL;
-        int                    r;
+        OpenvswitchBridge           *ovs_bridge;
+        gs_unref_array GArray       *external_ids_arr = NULL;
+        const char                  *connection_uuid  = NULL;
+        int                          r;
 
         r = json_unpack(value,
                         "{s:{s:s, s:o, s:o}}",
@@ -1940,9 +1953,9 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 static void
 ovsdb_got_echo(NMOvsdb *self, json_int_t id, json_t *data)
 {
-    NMOvsdbPrivate *    priv        = NM_OVSDB_GET_PRIVATE(self);
-    nm_auto_decref_json json_t *msg = NULL;
-    char *                      reply;
+    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
+    nm_auto_decref_json json_t *msg  = NULL;
+    char                       *reply;
     gboolean                    output_was_empty;
 
     output_was_empty = priv->output->len == 0;
@@ -1969,12 +1982,12 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
     json_error_t    json_error = {
         0,
     };
-    json_t *    json_id = NULL;
+    json_t     *json_id = NULL;
     json_int_t  id      = (json_int_t) -1;
     const char *method  = NULL;
-    json_t *    params  = NULL;
-    json_t *    result  = NULL;
-    json_t *    error   = NULL;
+    json_t     *params  = NULL;
+    json_t     *result  = NULL;
+    json_t     *error   = NULL;
 
     if (json_unpack_ex(msg,
                        &json_error,
@@ -2020,9 +2033,9 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
     }
 
     if (id >= 0) {
-        OvsdbMethodCall *call;
+        OvsdbMethodCall      *call;
         gs_free_error GError *local      = NULL;
-        gs_free char *        msg_as_str = NULL;
+        gs_free char         *msg_as_str = NULL;
 
         /* This is a response to a method call. */
         if (c_list_is_empty(&priv->calls_lst_head)) {
@@ -2078,7 +2091,7 @@ ovsdb_got_msg(NMOvsdb *self, json_t *msg)
 static size_t
 _json_callback(void *buffer, size_t buflen, void *user_data)
 {
-    NMOvsdb *       self = NM_OVSDB(user_data);
+    NMOvsdb        *self = NM_OVSDB(user_data);
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
     if (priv->bufp == priv->input->len) {
@@ -2102,12 +2115,12 @@ _json_callback(void *buffer, size_t buflen, void *user_data)
 static void
 ovsdb_read_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    NMOvsdb *       self   = NM_OVSDB(user_data);
+    NMOvsdb        *self   = NM_OVSDB(user_data);
     NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
-    GInputStream *  stream = G_INPUT_STREAM(source_object);
-    GError *        error  = NULL;
+    GInputStream   *stream = G_INPUT_STREAM(source_object);
+    GError         *error  = NULL;
     gssize          size;
-    json_t *        msg;
+    json_t         *msg;
     json_error_t    json_error = {
         0,
     };
@@ -2160,10 +2173,10 @@ ovsdb_read(NMOvsdb *self)
 static void
 ovsdb_write_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
 {
-    GOutputStream * stream = G_OUTPUT_STREAM(source_object);
-    NMOvsdb *       self   = NM_OVSDB(user_data);
+    GOutputStream  *stream = G_OUTPUT_STREAM(source_object);
+    NMOvsdb        *self   = NM_OVSDB(user_data);
     NMOvsdbPrivate *priv   = NM_OVSDB_GET_PRIVATE(self);
-    GError *        error  = NULL;
+    GError         *error  = NULL;
     gssize          size;
 
     size = g_output_stream_write_finish(stream, res, &error);
@@ -2188,7 +2201,7 @@ static void
 ovsdb_write(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
-    GOutputStream * stream;
+    GOutputStream  *stream;
 
     if (!priv->output->len)
         return;
@@ -2221,7 +2234,7 @@ ovsdb_write(NMOvsdb *self)
 static void
 ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 {
-    NMOvsdbPrivate * priv = NM_OVSDB_GET_PRIVATE(self);
+    NMOvsdbPrivate  *priv = NM_OVSDB_GET_PRIVATE(self);
     OvsdbMethodCall *call;
 
     nm_assert(!retry || !is_disposing);
@@ -2278,8 +2291,8 @@ _check_ready(NMOvsdb *self)
 static void
 _del_initial_iface_cb(GError *error, gpointer user_data)
 {
-    NMOvsdb *       self;
-    gs_free char *  ifname = NULL;
+    NMOvsdb        *self;
+    gs_free char   *ifname = NULL;
     NMOvsdbPrivate *priv;
 
     nm_utils_user_data_unpack(user_data, &self, &ifname);
@@ -2305,9 +2318,9 @@ _del_initial_iface_cb(GError *error, gpointer user_data)
 static void
 ovsdb_cleanup_initial_interfaces(NMOvsdb *self)
 {
-    NMOvsdbPrivate *            priv = NM_OVSDB_GET_PRIVATE(self);
+    NMOvsdbPrivate             *priv = NM_OVSDB_GET_PRIVATE(self);
     const OpenvswitchInterface *interface;
-    NMUtilsUserData *           data;
+    NMUtilsUserData            *data;
     GHashTableIter              iter;
 
     if (priv->ready || priv->num_pending_deletions != 0)
@@ -2352,9 +2365,9 @@ _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_
 static void
 _ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take)
 {
-    NMOvsdbPrivate *priv            = NM_OVSDB_GET_PRIVATE(self);
+    NMOvsdbPrivate          *priv   = NM_OVSDB_GET_PRIVATE(self);
     gs_unref_object GSocket *socket = NULL;
-    gs_free_error GError *error     = NULL;
+    gs_free_error GError    *error  = NULL;
 
     socket = g_socket_new_from_fd(nm_steal_fd(&fd_take), &error);
     if (!socket) {
@@ -2374,7 +2387,7 @@ static void
 _ovsdb_connect_priv_helper_cb(int fd_take, GError *error, gpointer user_data)
 {
     nm_auto_close int fd = fd_take;
-    NMOvsdb *         self;
+    NMOvsdb          *self;
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -2394,9 +2407,9 @@ _ovsdb_connect_priv_helper_cb(int fd_take, GError *error, gpointer user_data)
 static void
 _ovsdb_connect_idle(gpointer user_data, GCancellable *cancellable)
 {
-    NMOvsdb *         self;
-    NMOvsdbPrivate *  priv;
-    nm_auto_close int fd        = -1;
+    NMOvsdb              *self;
+    NMOvsdbPrivate       *priv;
+    nm_auto_close int     fd    = -1;
     gs_free_error GError *error = NULL;
 
     if (g_cancellable_is_cancelled(cancellable))
@@ -2468,11 +2481,11 @@ typedef struct {
 static void
 _transact_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_data)
 {
-    OvsdbCall * call = user_data;
+    OvsdbCall  *call = user_data;
     const char *err;
     const char *err_details;
     size_t      index;
-    json_t *    value;
+    json_t     *value;
 
     if (error)
         goto out;
@@ -2516,12 +2529,12 @@ nm_ovsdb_is_ready(NMOvsdb *self)
 }
 
 void
-nm_ovsdb_add_interface(NMOvsdb *       self,
-                       NMConnection *  bridge,
-                       NMConnection *  port,
-                       NMConnection *  interface,
-                       NMDevice *      bridge_device,
-                       NMDevice *      interface_device,
+nm_ovsdb_add_interface(NMOvsdb        *self,
+                       NMConnection   *bridge,
+                       NMConnection   *port,
+                       NMConnection   *interface,
+                       NMDevice       *bridge_device,
+                       NMDevice       *interface_device,
                        NMOvsdbCallback callback,
                        gpointer        user_data)
 {
@@ -2538,8 +2551,8 @@ nm_ovsdb_add_interface(NMOvsdb *       self,
 }
 
 void
-nm_ovsdb_del_interface(NMOvsdb *       self,
-                       const char *    ifname,
+nm_ovsdb_del_interface(NMOvsdb        *self,
+                       const char     *ifname,
                        NMOvsdbCallback callback,
                        gpointer        user_data)
 {
@@ -2552,8 +2565,8 @@ nm_ovsdb_del_interface(NMOvsdb *       self,
 }
 
 void
-nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
-                           const char *    ifname,
+nm_ovsdb_set_interface_mtu(NMOvsdb        *self,
+                           const char     *ifname,
                            guint32         mtu,
                            NMOvsdbCallback callback,
                            gpointer        user_data)
@@ -2567,10 +2580,10 @@ nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
 }
 
 void
-nm_ovsdb_set_external_ids(NMOvsdb *                self,
+nm_ovsdb_set_external_ids(NMOvsdb                 *self,
                           NMDeviceType             device_type,
-                          const char *             ifname,
-                          const char *             connection_uuid,
+                          const char              *ifname,
+                          const char              *connection_uuid,
                           NMSettingOvsExternalIDs *s_exid_old,
                           NMSettingOvsExternalIDs *s_exid_new)
 {
@@ -2618,7 +2631,7 @@ nm_ovsdb_init(NMOvsdb *self)
 static void
 dispose(GObject *object)
 {
-    NMOvsdb *       self = NM_OVSDB(object);
+    NMOvsdb        *self = NM_OVSDB(object);
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
 
     ovsdb_disconnect(self, FALSE, TRUE);
diff --git a/src/core/devices/ovs/nm-ovsdb.h b/src/core/devices/ovs/nm-ovsdb.h
index d0b4d19d..a50db45a 100644
--- a/src/core/devices/ovs/nm-ovsdb.h
+++ b/src/core/devices/ovs/nm-ovsdb.h
@@ -27,32 +27,32 @@ NMOvsdb *nm_ovsdb_get(void);
 
 GType nm_ovsdb_get_type(void);
 
-void nm_ovsdb_add_interface(NMOvsdb *       self,
-                            NMConnection *  bridge,
-                            NMConnection *  port,
-                            NMConnection *  interface,
-                            NMDevice *      bridge_device,
-                            NMDevice *      interface_device,
+void nm_ovsdb_add_interface(NMOvsdb        *self,
+                            NMConnection   *bridge,
+                            NMConnection   *port,
+                            NMConnection   *interface,
+                            NMDevice       *bridge_device,
+                            NMDevice       *interface_device,
                             NMOvsdbCallback callback,
                             gpointer        user_data);
 
-void nm_ovsdb_del_interface(NMOvsdb *       self,
-                            const char *    ifname,
+void nm_ovsdb_del_interface(NMOvsdb        *self,
+                            const char     *ifname,
                             NMOvsdbCallback callback,
                             gpointer        user_data);
 
-void nm_ovsdb_set_interface_mtu(NMOvsdb *       self,
-                                const char *    ifname,
+void nm_ovsdb_set_interface_mtu(NMOvsdb        *self,
+                                const char     *ifname,
                                 guint32         mtu,
                                 NMOvsdbCallback callback,
                                 gpointer        user_data);
 
 struct _NMSettingOvsExternalIDs;
 
-void nm_ovsdb_set_external_ids(NMOvsdb *                        self,
+void nm_ovsdb_set_external_ids(NMOvsdb                         *self,
                                NMDeviceType                     device_type,
-                               const char *                     ifname,
-                               const char *                     connection_uuid,
+                               const char                      *ifname,
+                               const char                      *connection_uuid,
                                struct _NMSettingOvsExternalIDs *s_exid_old,
                                struct _NMSettingOvsExternalIDs *s_exid_new);
 
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index 3398e467..16cd2e87 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -23,7 +23,6 @@
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "nm-dbus-manager.h"
-#include "nm-ip4-config.h"
 #include "libnm-std-aux/nm-dbus-compat.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceTeam
@@ -34,15 +33,15 @@
 NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceTeam, PROP_CONFIG, );
 
 typedef struct {
-    struct teamdctl *  tdc;
-    char *             config;
+    struct teamdctl   *tdc;
+    char              *config;
     GPid               teamd_pid;
     guint              teamd_process_watch;
     guint              teamd_timeout;
     guint              teamd_read_timeout;
     guint              teamd_dbus_watch;
     bool               kill_in_progress : 1;
-    GFileMonitor *     usock_monitor;
+    GFileMonitor      *usock_monitor;
     NMDeviceStageState stage1_state : 3;
 } NMDeviceTeamPrivate;
 
@@ -73,11 +72,11 @@ get_generic_capabilities(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)
 {
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
@@ -97,7 +96,7 @@ complete_connection(NMDevice *           device,
 static gboolean
 ensure_teamd_connection(NMDevice *device)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(device);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
     int                  err;
 
@@ -126,7 +125,7 @@ static gboolean
 teamd_read_config(NMDeviceTeam *self)
 {
     NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    const char *         config = NULL;
+    const char          *config = NULL;
     int                  err;
 
     if (priv->tdc) {
@@ -151,7 +150,7 @@ teamd_read_config(NMDeviceTeam *self)
 static gboolean
 teamd_read_timeout_cb(gpointer user_data)
 {
-    NMDeviceTeam *       self = user_data;
+    NMDeviceTeam        *self = user_data;
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     priv->teamd_read_timeout = 0;
@@ -162,10 +161,10 @@ teamd_read_timeout_cb(gpointer user_data)
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceTeam *       self   = NM_DEVICE_TEAM(device);
-    NMSettingTeam *      s_team = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TEAM);
+    NMDeviceTeam        *self   = NM_DEVICE_TEAM(device);
+    NMSettingTeam       *s_team = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TEAM);
     NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    struct teamdctl *    tdc    = priv->tdc;
+    struct teamdctl     *tdc    = priv->tdc;
 
     /* Read the configuration only if not already set */
     if (!priv->config && ensure_teamd_connection(device))
@@ -184,18 +183,18 @@ update_connection(NMDevice *device, NMConnection *connection)
 /*****************************************************************************/
 
 static gboolean
-master_update_slave_connection(NMDevice *    self,
-                               NMDevice *    slave,
+master_update_slave_connection(NMDevice     *self,
+                               NMDevice     *slave,
                                NMConnection *connection,
-                               GError **     error)
+                               GError      **error)
 {
     NMSettingTeamPort *s_port;
-    char *             port_config = NULL;
+    char              *port_config = NULL;
     int                err         = 0;
-    struct teamdctl *  tdc;
-    const char *       team_port_config = NULL;
-    const char *       iface            = nm_device_get_iface(self);
-    const char *       iface_slave      = nm_device_get_iface(slave);
+    struct teamdctl   *tdc;
+    const char        *team_port_config = NULL;
+    const char        *iface            = nm_device_get_iface(self);
+    const char        *iface_slave      = nm_device_get_iface(slave);
 
     tdc = teamdctl_alloc();
     if (!tdc) {
@@ -260,7 +259,7 @@ static void
 teamd_kill_cb(pid_t pid, gboolean success, int child_status, void *user_data)
 {
     gs_unref_object NMDeviceTeam *self = user_data;
-    NMDeviceTeamPrivate *         priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    NMDeviceTeamPrivate          *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     priv->kill_in_progress = FALSE;
 
@@ -308,8 +307,8 @@ teamd_cleanup(NMDeviceTeam *self, gboolean free_tdc)
 static gboolean
 teamd_timeout_cb(gpointer user_data)
 {
-    NMDeviceTeam *       self   = NM_DEVICE_TEAM(user_data);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDeviceTeam        *self   = NM_DEVICE_TEAM(user_data);
+    NMDevice            *device = NM_DEVICE(self);
     NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     g_return_val_if_fail(priv->teamd_timeout, FALSE);
@@ -343,7 +342,7 @@ static void
 teamd_ready(NMDeviceTeam *self)
 {
     NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDevice            *device = NM_DEVICE(self);
     gboolean             success;
 
     if (priv->kill_in_progress) {
@@ -382,7 +381,7 @@ teamd_ready(NMDeviceTeam *self)
 static void
 teamd_gone(NMDeviceTeam *self)
 {
-    NMDevice *    device = NM_DEVICE(self);
+    NMDevice     *device = NM_DEVICE(self);
     NMDeviceState state;
 
     teamd_cleanup(self, TRUE);
@@ -400,11 +399,11 @@ teamd_gone(NMDeviceTeam *self)
 
 static void
 teamd_dbus_appeared(GDBusConnection *connection,
-                    const char *     name,
-                    const char *     name_owner,
+                    const char      *name,
+                    const char      *name_owner,
                     gpointer         user_data)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(user_data);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(user_data);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     g_return_if_fail(priv->teamd_dbus_watch);
@@ -452,7 +451,7 @@ teamd_dbus_appeared(GDBusConnection *connection,
 static void
 teamd_dbus_vanished(GDBusConnection *dbus_connection, const char *name, gpointer user_data)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(user_data);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(user_data);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     g_return_if_fail(priv->teamd_dbus_watch);
@@ -472,9 +471,9 @@ teamd_dbus_vanished(GDBusConnection *dbus_connection, const char *name, gpointer
 }
 
 static void
-monitor_changed_cb(GFileMonitor *    monitor,
-                   GFile *           file,
-                   GFile *           other_file,
+monitor_changed_cb(GFileMonitor     *monitor,
+                   GFile            *file,
+                   GFile            *other_file,
                    GFileMonitorEvent event_type,
                    gpointer          user_data)
 {
@@ -496,9 +495,9 @@ monitor_changed_cb(GFileMonitor *    monitor,
 static void
 teamd_process_watch_cb(GPid pid, int status, gpointer user_data)
 {
-    NMDeviceTeam *       self   = NM_DEVICE_TEAM(user_data);
+    NMDeviceTeam        *self   = NM_DEVICE_TEAM(user_data);
     NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDevice            *device = NM_DEVICE(self);
     NMDeviceState        state  = nm_device_get_state(device);
 
     g_return_if_fail(priv->teamd_process_watch);
@@ -546,8 +545,8 @@ static gboolean
 teamd_kill(NMDeviceTeam *self, const char *teamd_binary, GError **error)
 {
     gs_unref_ptrarray GPtrArray *argv    = NULL;
-    gs_free char *               tmp_str = NULL;
-    gs_free const char **        envp    = NULL;
+    gs_free char                *tmp_str = NULL;
+    gs_free const char         **envp    = NULL;
 
     if (!teamd_binary) {
         teamd_binary = nm_utils_find_helper("teamd", NULL, error);
@@ -582,18 +581,18 @@ teamd_kill(NMDeviceTeam *self, const char *teamd_binary, GError **error)
 static gboolean
 teamd_start(NMDeviceTeam *self)
 {
-    NMDeviceTeamPrivate *priv  = NM_DEVICE_TEAM_GET_PRIVATE(self);
-    const char *         iface = nm_device_get_ip_iface(NM_DEVICE(self));
-    NMConnection *       connection;
-    gs_unref_ptrarray GPtrArray *argv = NULL;
-    gs_free_error GError *   error    = NULL;
-    gs_free char *           tmp_str  = NULL;
-    const char *             teamd_binary;
-    const char *             config;
-    nm_auto_free const char *config_free = NULL;
-    NMSettingTeam *          s_team;
-    gs_free char *           cloned_mac = NULL;
-    gs_free const char **    envp       = NULL;
+    NMDeviceTeamPrivate         *priv  = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    const char                  *iface = nm_device_get_ip_iface(NM_DEVICE(self));
+    NMConnection                *connection;
+    gs_unref_ptrarray GPtrArray *argv    = NULL;
+    gs_free_error GError        *error   = NULL;
+    gs_free char                *tmp_str = NULL;
+    const char                  *teamd_binary;
+    const char                  *config;
+    nm_auto_free const char     *config_free = NULL;
+    NMSettingTeam               *s_team;
+    gs_free char                *cloned_mac = NULL;
+    gs_free const char         **envp       = NULL;
 
     connection = nm_device_get_applied_connection(NM_DEVICE(self));
 
@@ -640,7 +639,7 @@ teamd_start(NMDeviceTeam *self)
     }
 
     if (cloned_mac) {
-        json_t *     json, *hwaddr;
+        json_t      *json, *hwaddr;
         json_error_t jerror;
 
         /* Inject the hwaddr property into the JSON configuration.
@@ -707,11 +706,11 @@ teamd_start(NMDeviceTeam *self)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceTeam *       self   = NM_DEVICE_TEAM(device);
-    NMDeviceTeamPrivate *priv   = NM_DEVICE_TEAM_GET_PRIVATE(self);
+    NMDeviceTeam         *self  = NM_DEVICE_TEAM(device);
+    NMDeviceTeamPrivate  *priv  = NM_DEVICE_TEAM_GET_PRIVATE(self);
     gs_free_error GError *error = NULL;
-    NMSettingTeam *       s_team;
-    const char *          cfg;
+    NMSettingTeam        *s_team;
+    const char           *cfg;
 
     if (nm_device_sys_iface_state_is_external(device))
         return NM_ACT_STAGE_RETURN_SUCCESS;
@@ -774,7 +773,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(device);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     priv->stage1_state = NM_DEVICE_STAGE_STATE_INIT;
@@ -794,11 +793,11 @@ deactivate(NMDevice *device)
 static gboolean
 enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
 {
-    NMDeviceTeam *       self        = NM_DEVICE_TEAM(device);
+    NMDeviceTeam        *self        = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv        = NM_DEVICE_TEAM_GET_PRIVATE(self);
     gboolean             success     = TRUE;
-    const char *         slave_iface = nm_device_get_ip_iface(slave);
-    NMSettingTeamPort *  s_team_port;
+    const char          *slave_iface = nm_device_get_ip_iface(slave);
+    NMSettingTeamPort   *s_team_port;
 
     nm_device_master_check_slave_physical_port(device, slave, LOGD_TEAM);
 
@@ -852,10 +851,10 @@ enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gbool
 static void
 release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(device);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(device);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
     gboolean             do_release, success;
-    NMSettingTeamPort *  s_port;
+    NMSettingTeamPort   *s_port;
     int                  ifindex_slave;
     int                  ifindex;
 
@@ -902,11 +901,11 @@ release_slave(NMDevice *device, NMDevice *slave, gboolean configure)
 }
 
 static gboolean
-create_and_realize(NMDevice *             device,
-                   NMConnection *         connection,
-                   NMDevice *             parent,
+create_and_realize(NMDevice              *device,
+                   NMConnection          *connection,
+                   NMDevice              *parent,
                    const NMPlatformLink **out_plink,
-                   GError **              error)
+                   GError               **error)
 {
     const char *iface = nm_device_get_iface(device);
     int         r;
@@ -954,11 +953,11 @@ nm_device_team_init(NMDeviceTeam *self)
 static void
 constructed(GObject *object)
 {
-    NMDevice *           device  = NM_DEVICE(object);
-    NMDeviceTeamPrivate *priv    = NM_DEVICE_TEAM_GET_PRIVATE(device);
-    gs_free char *       tmp_str = NULL;
-    gs_unref_object GFile *file  = NULL;
-    GError *               error;
+    NMDevice              *device  = NM_DEVICE(object);
+    NMDeviceTeamPrivate   *priv    = NM_DEVICE_TEAM_GET_PRIVATE(device);
+    gs_free char          *tmp_str = NULL;
+    gs_unref_object GFile *file    = NULL;
+    GError                *error;
 
     G_OBJECT_CLASS(nm_device_team_parent_class)->constructed(object);
 
@@ -1006,7 +1005,7 @@ nm_device_team_new(const char *iface)
 static void
 dispose(GObject *object)
 {
-    NMDeviceTeam *       self = NM_DEVICE_TEAM(object);
+    NMDeviceTeam        *self = NM_DEVICE_TEAM(object);
     NMDeviceTeamPrivate *priv = NM_DEVICE_TEAM_GET_PRIVATE(self);
 
     if (priv->teamd_dbus_watch) {
@@ -1040,9 +1039,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_team = {
 static void
 nm_device_team_class_init(NMDeviceTeamClass *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->constructed  = constructed;
     object_class->dispose      = dispose;
diff --git a/src/core/devices/team/nm-team-factory.c b/src/core/devices/team/nm-team-factory.c
index e2040d19..b446cbd0 100644
--- a/src/core/devices/team/nm-team-factory.c
+++ b/src/core/devices/team/nm-team-factory.c
@@ -43,7 +43,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(NM_DEVICE_FACTORY_DECLARE_LINK_TYPES(
     NM_LINK_TYPE_TEAM) NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES(NM_SETTING_TEAM_SETTING_NAME))
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     nm_manager_set_capability(NM_MANAGER_GET, NM_CAPABILITY_TEAM);
     return g_object_new(NM_TYPE_TEAM_FACTORY, NULL);
@@ -52,11 +52,11 @@ G_MODULE_EXPORT NMDeviceFactory *
 /*****************************************************************************/
 
 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)
 {
     return nm_device_team_new(iface);
 }
diff --git a/src/core/devices/tests/meson.build b/src/core/devices/tests/meson.build
index 1bc88370..871c3206 100644
--- a/src/core/devices/tests/meson.build
+++ b/src/core/devices/tests/meson.build
@@ -1,7 +1,6 @@
 # SPDX-License-Identifier: LGPL-2.1-or-later
 
 test_units = [
-  'test-acd',
   'test-lldp',
 ]
 
diff --git a/src/core/devices/tests/test-acd.c b/src/core/devices/tests/test-acd.c
deleted file mode 100644
index b4b65165..00000000
--- a/src/core/devices/tests/test-acd.c
+++ /dev/null
@@ -1,259 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0-or-later */
-/*
- * Copyright (C) 2015 Red Hat, Inc.
- */
-
-#include "src/core/nm-default-daemon.h"
-
-#include "n-acd/src/n-acd.h"
-
-#include <linux/if_ether.h>
-
-#include "devices/nm-acd-manager.h"
-#include "platform/tests/test-common.h"
-
-#define IFACE_VETH0 "nm-test-veth0"
-#define IFACE_VETH1 "nm-test-veth1"
-
-#define ADDR1 0x01010101
-#define ADDR2 0x02020202
-#define ADDR3 0x03030303
-#define ADDR4 0x04040404
-
-/*****************************************************************************/
-
-static gboolean
-_skip_acd_test_check(void)
-{
-    NAcd *       acd;
-    NAcdConfig * config;
-    const guint8 hwaddr[ETH_ALEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
-    int          r;
-    static int   skip = -1;
-
-    if (skip == -1) {
-        r = n_acd_config_new(&config);
-        g_assert(r == 0);
-
-        n_acd_config_set_ifindex(config, 1);
-        n_acd_config_set_transport(config, N_ACD_TRANSPORT_ETHERNET);
-        n_acd_config_set_mac(config, hwaddr, sizeof(hwaddr));
-
-        r = n_acd_new(&acd, config);
-        n_acd_config_free(config);
-        if (r == 0)
-            n_acd_unref(acd);
-
-        skip = (r != 0);
-    }
-    return skip;
-}
-
-#define _skip_acd_test()                                               \
-    ({                                                                 \
-        gboolean _skip = _skip_acd_test_check();                       \
-                                                                       \
-        if (_skip)                                                     \
-            g_test_skip("Cannot create NAcd. Running under valgind?"); \
-        _skip;                                                         \
-    })
-
-/*****************************************************************************/
-
-typedef struct {
-    int           ifindex0;
-    int           ifindex1;
-    const guint8 *hwaddr0;
-    const guint8 *hwaddr1;
-    size_t        hwaddr0_len;
-    size_t        hwaddr1_len;
-} test_fixture;
-
-static void
-fixture_setup(test_fixture *fixture, gconstpointer user_data)
-{
-    /* create veth pair. */
-    fixture->ifindex0 =
-        nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
-    fixture->ifindex1 =
-        nmtstp_link_get_typed(NM_PLATFORM_GET, -1, IFACE_VETH1, NM_LINK_TYPE_VETH)->ifindex;
-
-    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, fixture->ifindex0, IFF_UP, TRUE) >= 0);
-    g_assert(nm_platform_link_change_flags(NM_PLATFORM_GET, fixture->ifindex1, IFF_UP, TRUE) >= 0);
-
-    fixture->hwaddr0 =
-        nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex0, &fixture->hwaddr0_len);
-    fixture->hwaddr1 =
-        nm_platform_link_get_address(NM_PLATFORM_GET, fixture->ifindex1, &fixture->hwaddr1_len);
-}
-
-typedef struct {
-    in_addr_t addresses[8];
-    in_addr_t peer_addresses[8];
-    gboolean  expected_result[8];
-} TestInfo;
-
-static void
-acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data)
-{
-    g_main_loop_quit(user_data);
-}
-
-static void
-test_acd_common(test_fixture *fixture, TestInfo *info)
-{
-    nm_auto_free_acdmgr NMAcdManager *manager = NULL;
-    nm_auto_unref_gmainloop GMainLoop *loop   = NULL;
-    int                                i;
-    const guint                        WAIT_TIME_OPTIMISTIC = 50;
-    guint                              wait_time;
-    static const NMAcdCallbacks        callbacks = {
-        .probe_terminated_callback = acd_manager_probe_terminated,
-        .user_data_destroy         = (GDestroyNotify) g_main_loop_unref,
-    };
-    int r;
-
-    if (_skip_acd_test())
-        return;
-
-    /* first, try with a short waittime. We hope that this is long enough
-     * to successfully complete the test. Only if that's not the case, we
-     * assume the computer is currently busy (high load) and we retry with
-     * a longer timeout. */
-    wait_time = WAIT_TIME_OPTIMISTIC;
-again:
-
-    nm_clear_pointer(&loop, g_main_loop_unref);
-    loop = g_main_loop_new(NULL, FALSE);
-
-    nm_clear_pointer(&manager, nm_acd_manager_free);
-    manager = nm_acd_manager_new(fixture->ifindex0,
-                                 fixture->hwaddr0,
-                                 fixture->hwaddr0_len,
-                                 &callbacks,
-                                 g_main_loop_ref(loop));
-    g_assert(manager != NULL);
-
-    for (i = 0; info->addresses[i]; i++)
-        g_assert(nm_acd_manager_add_address(manager, info->addresses[i]));
-
-    for (i = 0; info->peer_addresses[i]; i++) {
-        nmtstp_ip4_address_add(NULL,
-                               FALSE,
-                               fixture->ifindex1,
-                               info->peer_addresses[i],
-                               24,
-                               0,
-                               3600,
-                               1800,
-                               0,
-                               NULL);
-    }
-
-    r = nm_acd_manager_start_probe(manager, wait_time);
-    g_assert_cmpint(r, ==, 0);
-
-    g_assert(nmtst_main_loop_run(loop, 2000));
-
-    for (i = 0; info->addresses[i]; i++) {
-        gboolean val;
-        char     sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-        val = nm_acd_manager_check_address(manager, info->addresses[i]);
-        if (val == info->expected_result[i])
-            continue;
-
-        if (wait_time == WAIT_TIME_OPTIMISTIC) {
-            /* probably we just had a glitch and the system took longer than
-             * expected. Re-verify with a large timeout this time. */
-            wait_time = 1000;
-            goto again;
-        }
-
-        g_error("expected check for address #%d (%s) to %s, but it didn't",
-                i,
-                _nm_utils_inet4_ntop(info->addresses[i], sbuf),
-                info->expected_result[i] ? "detect no duplicated" : "detect a duplicate");
-    }
-}
-
-static void
-test_acd_probe_1(test_fixture *fixture, gconstpointer user_data)
-{
-    TestInfo info = {.addresses       = {ADDR1, ADDR2, ADDR3},
-                     .peer_addresses  = {ADDR4},
-                     .expected_result = {TRUE, TRUE, TRUE}};
-
-    test_acd_common(fixture, &info);
-}
-
-static void
-test_acd_probe_2(test_fixture *fixture, gconstpointer user_data)
-{
-    TestInfo info = {.addresses       = {ADDR1, ADDR2, ADDR3, ADDR4},
-                     .peer_addresses  = {ADDR3, ADDR2},
-                     .expected_result = {TRUE, FALSE, FALSE, TRUE}};
-
-    test_acd_common(fixture, &info);
-}
-
-static void
-test_acd_announce(test_fixture *fixture, gconstpointer user_data)
-{
-    nm_auto_free_acdmgr NMAcdManager *manager = NULL;
-    nm_auto_unref_gmainloop GMainLoop *loop   = NULL;
-    int                                r;
-
-    if (_skip_acd_test())
-        return;
-
-    manager =
-        nm_acd_manager_new(fixture->ifindex0, fixture->hwaddr0, fixture->hwaddr0_len, NULL, NULL);
-    g_assert(manager != NULL);
-
-    g_assert(nm_acd_manager_add_address(manager, ADDR1));
-    g_assert(nm_acd_manager_add_address(manager, ADDR2));
-
-    loop = g_main_loop_new(NULL, FALSE);
-    r    = nm_acd_manager_announce_addresses(manager);
-    g_assert_cmpint(r, ==, 0);
-    g_assert(!nmtst_main_loop_run(loop, 200));
-}
-
-static void
-fixture_teardown(test_fixture *fixture, gconstpointer user_data)
-{
-    nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex0);
-    nm_platform_link_delete(NM_PLATFORM_GET, fixture->ifindex1);
-}
-
-NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
-
-void
-_nmtstp_init_tests(int *argc, char ***argv)
-{
-    nmtst_init_with_logging(argc, argv, NULL, "ALL");
-}
-
-void
-_nmtstp_setup_tests(void)
-{
-    g_test_add("/acd/probe/1",
-               test_fixture,
-               NULL,
-               fixture_setup,
-               test_acd_probe_1,
-               fixture_teardown);
-    g_test_add("/acd/probe/2",
-               test_fixture,
-               NULL,
-               fixture_setup,
-               test_acd_probe_2,
-               fixture_teardown);
-    g_test_add("/acd/announce",
-               test_fixture,
-               NULL,
-               fixture_setup,
-               test_acd_announce,
-               fixture_teardown);
-}
diff --git a/src/core/devices/tests/test-lldp.c b/src/core/devices/tests/test-lldp.c
index c64c7c11..048410c1 100644
--- a/src/core/devices/tests/test-lldp.c
+++ b/src/core/devices/tests/test-lldp.c
@@ -22,15 +22,15 @@
 /*****************************************************************************/
 
 static GVariant *
-get_lldp_neighbor(GVariant *  neighbors,
+get_lldp_neighbor(GVariant   *neighbors,
                   int         chassis_id_type,
                   const char *chassis_id,
                   int         port_id_type,
                   const char *port_id)
 {
     GVariantIter iter;
-    GVariant *   variant;
-    GVariant *   result = NULL;
+    GVariant    *variant;
+    GVariant    *result = NULL;
 
     nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}"));
 
@@ -81,15 +81,15 @@ typedef struct {
 typedef struct {
     gsize          frame_len;
     const uint8_t *frame;
-    const char *   as_variant;
+    const char    *as_variant;
 } TestRecvFrame;
 
 #define TEST_RECV_FRAME_DEFINE(name, _as_variant, ...)        \
     static const guint8        _##name##_v[] = {__VA_ARGS__}; \
     static const TestRecvFrame name          = {              \
-        .as_variant = _as_variant,                   \
-        .frame_len  = sizeof(_##name##_v),           \
-        .frame      = _##name##_v,                   \
+                 .as_variant = _as_variant,                   \
+                 .frame_len  = sizeof(_##name##_v),           \
+                 .frame      = _##name##_v,                   \
     }
 
 typedef struct {
@@ -183,7 +183,7 @@ TEST_RECV_FRAME_DEFINE(
 static void
 _test_recv_data0_check_do(GMainLoop *loop, NMLldpListener *listener, const TestRecvFrame *frame)
 {
-    GVariant *       neighbors, *attr;
+    GVariant                  *neighbors, *attr;
     gs_unref_variant GVariant *neighbor = NULL;
 
     neighbors = nm_lldp_listener_get_neighbors(listener);
@@ -537,10 +537,10 @@ TEST_RECV_FRAME_DEFINE(
 static void
 _test_recv_data1_check(GMainLoop *loop, NMLldpListener *listener, TestRecvCallbackInfo *info)
 {
-    GVariant *       neighbors, *attr, *child;
+    GVariant                  *neighbors, *attr, *child;
     gs_unref_variant GVariant *neighbor = NULL;
     guint                      v_uint   = 0;
-    const char *               v_str    = NULL;
+    const char                *v_str    = NULL;
 
     neighbors = nm_lldp_listener_get_neighbors(listener);
     nmtst_assert_variant_is_of_type(neighbors, G_VARIANT_TYPE("aa{sv}"));
@@ -868,12 +868,12 @@ lldp_neighbors_changed(NMLldpListener *lldp_listener, gpointer user_data)
 static void
 test_recv(TestRecvFixture *fixture, gconstpointer user_data)
 {
-    const TestRecvData * data = user_data;
-    NMLldpListener *     listener;
-    GMainLoop *          loop;
+    const TestRecvData  *data = user_data;
+    NMLldpListener      *listener;
+    GMainLoop           *loop;
     TestRecvCallbackInfo info = {};
     gsize                i_frames;
-    GError *             error = NULL;
+    GError              *error = NULL;
     guint                sd_id;
 
     if (fixture->ifindex == 0) {
@@ -918,10 +918,10 @@ _test_recv_fixture_teardown(TestRecvFixture *fixture, gconstpointer user_data)
 static void
 test_parse_frames(gconstpointer test_data)
 {
-    const TestRecvFrame *frame            = test_data;
+    const TestRecvFrame       *frame      = test_data;
     gs_unref_variant GVariant *v_neighbor = NULL;
     gs_unref_variant GVariant *attr       = NULL;
-    gs_free char *             as_variant = NULL;
+    gs_free char              *as_variant = NULL;
 
     v_neighbor = nmtst_lldp_parse_from_raw(frame->frame, frame->frame_len);
     g_assert(v_neighbor);
diff --git a/src/core/devices/wifi/meson.build b/src/core/devices/wifi/meson.build
index 85553c53..715bc0c9 100644
--- a/src/core/devices/wifi/meson.build
+++ b/src/core/devices/wifi/meson.build
@@ -4,6 +4,7 @@ iwd_sources = files()
 if enable_iwd
   iwd_sources += files(
     'nm-device-iwd.c',
+    'nm-device-iwd-p2p.c',
     'nm-iwd-manager.c',
   )
 endif
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.c b/src/core/devices/wifi/nm-device-iwd-p2p.c
new file mode 100644
index 00000000..01774b12
--- /dev/null
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.c
@@ -0,0 +1,1262 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021 Intel Corporation
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-device-iwd-p2p.h"
+
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-private.h"
+#include "nm-act-request.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
+#include "nm-setting-wifi-p2p.h"
+#include "nm-utils.h"
+#include "nm-wifi-p2p-peer.h"
+#include "nm-iwd-manager.h"
+#include "settings/nm-settings.h"
+
+#define _NMLOG_DEVICE_TYPE NMDeviceIwdP2P
+#include "devices/nm-device-logging.h"
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIwdP2P, PROP_PEERS, );
+
+typedef struct {
+    GDBusObject *dbus_obj;
+    GDBusProxy  *dbus_p2p_proxy;
+    GDBusProxy  *dbus_peer_proxy;
+    CList        peers_lst_head;
+
+    GSource *find_peer_timeout_source;
+    GSource *peer_dump_source;
+
+    GCancellable *find_cancellable;
+    GCancellable *connect_cancellable;
+
+    bool enabled : 1;
+
+    bool stage2_ready : 1;
+
+    bool wfd_registered : 1;
+} NMDeviceIwdP2PPrivate;
+
+struct _NMDeviceIwdP2P {
+    NMDevice              parent;
+    NMDeviceIwdP2PPrivate _priv;
+};
+
+struct _NMDeviceIwdP2PClass {
+    NMDeviceClass parent;
+};
+
+G_DEFINE_TYPE(NMDeviceIwdP2P, nm_device_iwd_p2p, NM_TYPE_DEVICE)
+
+#define NM_DEVICE_IWD_P2P_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDeviceIwdP2P, NM_IS_DEVICE_IWD_P2P, NMDevice)
+
+/*****************************************************************************/
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p;
+static const GDBusSignalInfo             nm_signal_info_wifi_p2p_peer_added;
+static const GDBusSignalInfo             nm_signal_info_wifi_p2p_peer_removed;
+static gboolean                          iwd_discovery_timeout_cb(gpointer user_data);
+
+/*****************************************************************************/
+
+static void
+_peer_dump(NMDeviceIwdP2P      *self,
+           NMLogLevel           log_level,
+           const NMWifiP2PPeer *peer,
+           const char          *prefix,
+           gint32               now_s)
+{
+    char buf[1024];
+
+    _NMLOG(log_level,
+           LOGD_WIFI_SCAN,
+           "wifi-peer: %-7s %s",
+           prefix,
+           nm_wifi_p2p_peer_to_string(peer, buf, sizeof(buf), now_s));
+}
+
+static gboolean
+peer_list_dump(gpointer user_data)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(user_data);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    nm_clear_g_source_inst(&priv->peer_dump_source);
+
+    if (_LOGD_ENABLED(LOGD_WIFI_SCAN)) {
+        NMWifiP2PPeer *peer;
+        gint32         now_s = nm_utils_get_monotonic_timestamp_sec();
+
+        _LOGD(LOGD_WIFI_SCAN, "P2P Peers: [now:%u]", now_s);
+        c_list_for_each_entry (peer, &priv->peers_lst_head, peers_lst)
+            _peer_dump(self, LOGL_DEBUG, peer, "dump", now_s);
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+schedule_peer_list_dump(NMDeviceIwdP2P *self)
+{
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (!priv->peer_dump_source && _LOGD_ENABLED(LOGD_WIFI_SCAN)) {
+        priv->peer_dump_source = nm_g_timeout_add_seconds_source(1, peer_list_dump, self);
+    }
+}
+
+/*****************************************************************************/
+
+static gboolean
+is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    return priv->enabled;
+}
+
+static gboolean
+check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
+{
+    NMSettingWifiP2P  *s_wifi_p2p;
+    GBytes            *wfd_ies;
+    NMSettingIPConfig *s_ip;
+
+    if (!NM_DEVICE_CLASS(nm_device_iwd_p2p_parent_class)
+             ->check_connection_compatible(device, connection, error))
+        return FALSE;
+
+    s_wifi_p2p =
+        NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
+
+    /* Any of the existing values other than DISABLED is ok */
+    if (nm_setting_wifi_p2p_get_wps_method(s_wifi_p2p)
+        == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_DISABLED) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                                   "No WPS method enabled");
+        return FALSE;
+    }
+
+    wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p);
+    if (wfd_ies && !nm_wifi_utils_parse_wfd_ies(wfd_ies, NULL)) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                                   "Can't parse connection WFD IEs");
+        return FALSE;
+    }
+
+    s_ip = NM_SETTING_IP_CONFIG(nm_connection_get_setting_ip4_config(connection));
+    if (s_ip
+        && !NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip),
+                         NULL,
+                         NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+        nm_utils_error_set_literal(error,
+                                   NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                                   "P2P implies 'auto' IPv4 config method");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static gboolean
+check_connection_available(NMDevice                      *device,
+                           NMConnection                  *connection,
+                           NMDeviceCheckConAvailableFlags flags,
+                           const char                    *specific_object,
+                           GError                       **error)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMSettingWifiP2P      *s_wifi_p2p;
+    GBytes                *wfd_ies;
+    NMWifiP2PPeer         *peer;
+
+    if (specific_object) {
+        peer = nm_wifi_p2p_peer_lookup_for_device(NM_DEVICE(self), specific_object);
+        if (!peer) {
+            g_set_error(error,
+                        NM_UTILS_ERROR,
+                        NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                        "The P2P peer %s is unknown",
+                        specific_object);
+            return FALSE;
+        }
+
+        if (!nm_wifi_p2p_peer_check_compatible(peer, connection, FALSE)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "Requested P2P peer is not compatible with profile");
+            return FALSE;
+        }
+    } else {
+        peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE);
+        if (!peer) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "No compatible P2P peer found");
+            return FALSE;
+        }
+    }
+
+    s_wifi_p2p =
+        NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
+    wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p);
+    if (wfd_ies) {
+        NMIwdWfdInfo wfd_info = {};
+
+        if (!nm_wifi_utils_parse_wfd_ies(wfd_ies, &wfd_info)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
+                                       "Can't parse connection WFD IEs");
+            return FALSE;
+        }
+
+        if (!nm_iwd_manager_check_wfd_info_compatible(nm_iwd_manager_get(), &wfd_info)) {
+            nm_utils_error_set_literal(error,
+                                       NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
+                                       "An incompatible WFD connection is active");
+            return FALSE;
+        }
+    }
+
+    return TRUE;
+}
+
+static gboolean
+complete_connection(NMDevice            *device,
+                    NMConnection        *connection,
+                    const char          *specific_object,
+                    NMConnection *const *existing_connections,
+                    GError             **error)
+{
+    NMDeviceIwdP2P   *self         = NM_DEVICE_IWD_P2P(device);
+    gs_free char     *setting_name = NULL;
+    NMSettingWifiP2P *s_wifi_p2p;
+    NMWifiP2PPeer    *peer;
+    const char       *setting_peer;
+
+    s_wifi_p2p =
+        NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
+
+    if (!specific_object) {
+        /* If not given a specific object, we need at minimum a peer address */
+        if (!s_wifi_p2p) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "A '%s' setting is required if no Peer path was given",
+                        NM_SETTING_WIFI_P2P_SETTING_NAME);
+            return FALSE;
+        }
+
+        setting_peer = nm_setting_wifi_p2p_get_peer(s_wifi_p2p);
+        if (!setting_peer) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_INVALID_CONNECTION,
+                        "A '%s' setting with a valid Peer is required if no Peer path was given",
+                        NM_SETTING_WIFI_P2P_SETTING_NAME);
+            return FALSE;
+        }
+    } else {
+        peer = nm_wifi_p2p_peer_lookup_for_device(NM_DEVICE(self), specific_object);
+        if (!peer) {
+            g_set_error(error,
+                        NM_DEVICE_ERROR,
+                        NM_DEVICE_ERROR_SPECIFIC_OBJECT_NOT_FOUND,
+                        "The P2P peer %s is unknown",
+                        specific_object);
+            return FALSE;
+        }
+
+        setting_peer = nm_wifi_p2p_peer_get_address(peer);
+        g_return_val_if_fail(setting_peer, FALSE);
+    }
+
+    /* Add a Wi-Fi P2P setting if one doesn't exist yet */
+    s_wifi_p2p = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIFI_P2P);
+
+    g_object_set(G_OBJECT(s_wifi_p2p), NM_SETTING_WIFI_P2P_PEER, setting_peer, NULL);
+
+    setting_name = g_strdup_printf("Wi-Fi P2P Peer %s", setting_peer);
+    nm_utils_complete_generic(nm_device_get_platform(device),
+                              connection,
+                              NM_SETTING_WIFI_P2P_SETTING_NAME,
+                              existing_connections,
+                              setting_name,
+                              setting_name,
+                              NULL,
+                              NULL,
+                              TRUE);
+
+    return TRUE;
+}
+
+static gboolean
+get_enabled(NMDevice *device)
+{
+    return NM_DEVICE_IWD_P2P_GET_PRIVATE(device)->enabled;
+}
+
+static void
+set_enabled_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    NMDeviceIwdP2P            *self    = user_data;
+    gs_unref_variant GVariant *variant = NULL;
+    gs_free_error GError      *error   = NULL;
+
+    variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+    if (!variant) {
+        _LOGE(LOGD_DEVICE | LOGD_WIFI, ".Set failed: %s", error->message);
+        return;
+    }
+    _LOGD(LOGD_DEVICE | LOGD_WIFI, ".Set OK!");
+}
+
+static void
+set_enabled(NMDevice *device, gboolean enabled)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    enabled = !!enabled;
+
+    if (priv->enabled == enabled)
+        return;
+
+    _LOGD(LOGD_WIFI, "device will be %s", enabled ? "enabled" : "disabled");
+
+    g_dbus_proxy_call(
+        priv->dbus_p2p_proxy,
+        DBUS_INTERFACE_PROPERTIES ".Set",
+        g_variant_new("(ssv)", NM_IWD_P2P_INTERFACE, "Enabled", g_variant_new("b", enabled)),
+        G_DBUS_CALL_FLAGS_NONE,
+        2000,
+        NULL,
+        set_enabled_cb,
+        self);
+}
+
+static void
+p2p_properties_changed_cb(GDBusProxy *proxy,
+                          GVariant   *changed_properties,
+                          GStrv       invalidate_properties,
+                          gpointer    user_data)
+{
+    NMDeviceIwdP2P        *self   = user_data;
+    NMDeviceIwdP2PPrivate *priv   = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMDevice              *device = NM_DEVICE(self);
+    gboolean               new_bool;
+
+    if (g_variant_lookup(changed_properties, "Enabled", "b", &new_bool)
+        && new_bool != priv->enabled) {
+        priv->enabled = new_bool;
+
+        _LOGD(LOGD_WIFI, "device now %s", priv->enabled ? "enabled" : "disabled");
+
+        if (priv->enabled) {
+            NMDeviceState state = nm_device_get_state(device);
+
+            if (state != NM_DEVICE_STATE_UNAVAILABLE)
+                _LOGW(LOGD_CORE, "not in expected unavailable state!");
+
+            nm_device_queue_recheck_available(device,
+                                              NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+                                              NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        } else {
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_UNAVAILABLE,
+                                    NM_DEVICE_STATE_REASON_NONE);
+        }
+    }
+}
+
+static void
+iwd_request_discovery_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    NMDeviceIwdP2P            *self    = user_data;
+    NMDeviceIwdP2PPrivate     *priv    = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    gs_unref_variant GVariant *variant = NULL;
+    gs_free_error GError      *error   = NULL;
+
+    variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+    if (!variant) {
+        NMDevice *device = NM_DEVICE(self);
+
+        _LOGE(LOGD_DEVICE | LOGD_WIFI,
+              "%s(wifi-p2p) IWD p2p.Device.RequestDiscovery failed: %s",
+              nm_device_is_activating(device) ? "Activation: " : "",
+              error->message);
+
+        if (nm_utils_error_is_cancelled(error) && !nm_device_is_activating(device))
+            return;
+
+        nm_clear_g_cancellable(&priv->find_cancellable);
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+        return;
+    }
+
+    nm_clear_g_cancellable(&priv->find_cancellable);
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "%s(wifi-p2p) Target peer discovery running",
+          nm_device_is_activating(NM_DEVICE(self)) ? "Activation: " : "");
+}
+
+static void
+iwd_request_discovery(NMDeviceIwdP2P *self, unsigned timeout)
+{
+    NMDeviceIwdP2PPrivate *priv      = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    bool                   requested = priv->find_peer_timeout_source != NULL;
+
+    nm_clear_g_source_inst(&priv->find_peer_timeout_source);
+    priv->find_peer_timeout_source =
+        nm_g_timeout_add_seconds_source(timeout, iwd_discovery_timeout_cb, self);
+
+    if (!requested) {
+        priv->find_cancellable = g_cancellable_new();
+
+        g_dbus_proxy_call(priv->dbus_p2p_proxy,
+                          "RequestDiscovery",
+                          NULL,
+                          G_DBUS_CALL_FLAGS_NONE,
+                          G_MAXINT,
+                          priv->find_cancellable,
+                          iwd_request_discovery_cb,
+                          self);
+    }
+}
+
+static void
+iwd_release_discovery(NMDeviceIwdP2P *self)
+{
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    nm_clear_g_source_inst(&priv->find_peer_timeout_source);
+    nm_clear_g_cancellable(&priv->find_cancellable);
+
+    g_dbus_proxy_call(priv->dbus_p2p_proxy,
+                      "ReleaseDiscovery",
+                      NULL,
+                      G_DBUS_CALL_FLAGS_NONE,
+                      G_MAXINT,
+                      NULL,
+                      NULL,
+                      self);
+}
+
+/*
+ * Called when IWD has been unable to find the peer we want to connect to within the
+ * 10s time limit or when a D-bus Find() ends.
+ */
+static gboolean
+iwd_discovery_timeout_cb(gpointer user_data)
+{
+    NMDeviceIwdP2P        *self   = NM_DEVICE_IWD_P2P(user_data);
+    NMDeviceIwdP2PPrivate *priv   = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMDevice              *device = NM_DEVICE(self);
+
+    nm_clear_g_source_inst(&priv->find_peer_timeout_source);
+
+    iwd_release_discovery(self);
+
+    if (nm_device_is_activating(device)) {
+        _LOGW(LOGD_DEVICE | LOGD_WIFI,
+              "Activation: (wifi-p2p) Could not find peer, failing activation");
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+    } else {
+        _LOGD(LOGD_DEVICE | LOGD_WIFI, "(wifi-p2p) Find timeout");
+    }
+
+    return G_SOURCE_REMOVE;
+}
+
+static void
+cleanup_connect_attempt(NMDeviceIwdP2P *self)
+{
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (priv->find_peer_timeout_source)
+        iwd_release_discovery(self);
+
+    if (priv->wfd_registered) {
+        nm_iwd_manager_unregister_wfd(nm_iwd_manager_get());
+        priv->wfd_registered = FALSE;
+    }
+
+    if (!priv->dbus_peer_proxy)
+        return;
+
+    if (nm_device_is_activating(NM_DEVICE(self)))
+        nm_device_set_ip_iface(NM_DEVICE(self), NULL);
+
+    priv->stage2_ready = FALSE;
+    g_signal_handlers_disconnect_by_data(priv->dbus_peer_proxy, self);
+    g_clear_object(&priv->dbus_peer_proxy);
+    nm_clear_g_cancellable(&priv->connect_cancellable);
+}
+
+static void
+peer_properties_changed_cb(GDBusProxy *proxy,
+                           GVariant   *changed_properties,
+                           GStrv       invalidate_properties,
+                           gpointer    user_data)
+{
+    NMDeviceIwdP2P *self  = user_data;
+    NMDeviceState   state = nm_device_get_state(NM_DEVICE(self));
+    gboolean        new_bool;
+    const char     *new_str;
+
+    if (g_variant_lookup(changed_properties, "Connected", "b", &new_bool) && !new_bool
+        && state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_DEACTIVATING) {
+        cleanup_connect_attempt(self);
+        nm_device_state_changed(NM_DEVICE(self),
+                                NM_DEVICE_STATE_DISCONNECTED,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+    }
+
+    if (g_variant_lookup(changed_properties, "ConnectedInterface", "&s", &new_str)
+        && state >= NM_DEVICE_STATE_CONFIG && state <= NM_DEVICE_STATE_IP_CONFIG) {
+        nm_device_set_ip_iface(NM_DEVICE(self), new_str);
+    }
+}
+
+static NMActStageReturn
+act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMConnection          *connection;
+    NMSettingWifiP2P      *s_wifi_p2p;
+    NMWifiP2PPeer         *peer;
+    GBytes                *wfd_ies;
+
+    if (!priv->enabled) {
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    connection = nm_device_get_applied_connection(NM_DEVICE(self));
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+
+    s_wifi_p2p =
+        NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
+    g_return_val_if_fail(s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
+
+    /* Set the WFD IEs before connecting and before peer discovery if that is needed,
+     * usually the WFD IEs need to actually be sent in the Probe frames before we can
+     * receive the peers' WFD IEs and decide whether the peer is compatible with the
+     * requested WFD parameters.  In the current setup we only get the WFD IEs from
+     * the connection settings so during a normal find the client will not be getting
+     * any WFD information about the peers and has to decide to connect based on the
+     * name and device type (category + subcategory) -- assuming that the peers even
+     * bother to reply to probes without WFD IEs.  We'll then need to redo the find
+     * here in PREPARE because IWD wants to see that the parameters in the peer's
+     * WFD IEs match those in our WFD IEs.  The normal use case for IWD is that the
+     * WFD client registers its WFD parameters as soon as it starts and they remain
+     * registered during the find and then during the connect.  */
+    wfd_ies = nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p);
+    if (wfd_ies) {
+        NMIwdWfdInfo wfd_info = {};
+
+        if (!nm_wifi_utils_parse_wfd_ies(wfd_ies, &wfd_info)) {
+            _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) Can't parse connection WFD IEs");
+            NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        if (!nm_iwd_manager_check_wfd_info_compatible(nm_iwd_manager_get(), &wfd_info)) {
+            _LOGE(LOGD_DEVICE | LOGD_WIFI,
+                  "Activation: (wifi-p2p) An incompatible WFD connection is active");
+            NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        if (!nm_iwd_manager_register_wfd(nm_iwd_manager_get(), &wfd_info)) {
+            _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) Can't register WFD service");
+            NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
+            return NM_ACT_STAGE_RETURN_FAILURE;
+        }
+
+        priv->wfd_registered = TRUE;
+    }
+
+    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, TRUE);
+    if (!peer) {
+        iwd_request_discovery(self, 10);
+        return NM_ACT_STAGE_RETURN_POSTPONE;
+    } else if (priv->find_peer_timeout_source) {
+        iwd_release_discovery(self);
+    }
+
+    return NM_ACT_STAGE_RETURN_SUCCESS;
+}
+
+static void
+iwd_wsc_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    NMDeviceIwdP2P            *self    = user_data;
+    NMDeviceIwdP2PPrivate     *priv    = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    gs_unref_variant GVariant *variant = NULL;
+    gs_free_error GError      *error   = NULL;
+    NMDevice                  *device  = NM_DEVICE(self);
+
+    variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
+    if (!variant) {
+        _LOGE(LOGD_DEVICE | LOGD_WIFI,
+              "Activation: (wifi-p2p) IWD SimpleConfiguration.PushButton/StartPin() failed: %s",
+              error->message);
+
+        if (nm_utils_error_is_cancelled(error) && !nm_device_is_activating(device))
+            return;
+
+        nm_clear_g_cancellable(&priv->connect_cancellable);
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_FAILED,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        return;
+    }
+
+    nm_clear_g_cancellable(&priv->connect_cancellable);
+    _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi-p2p) IWD connection successful");
+
+    g_signal_connect(priv->dbus_peer_proxy,
+                     "g-properties-changed",
+                     G_CALLBACK(peer_properties_changed_cb),
+                     self);
+
+    priv->stage2_ready = TRUE;
+
+    nm_device_activate_schedule_stage2_device_config(device, FALSE);
+}
+
+static NMActStageReturn
+act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
+{
+    NMDeviceIwdP2P             *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate      *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMConnection               *connection;
+    NMSettingWifiP2P           *s_wifi_p2p;
+    NMWifiP2PPeer              *peer;
+    gs_unref_object GDBusProxy *peer_proxy = NULL;
+    gs_unref_object GDBusProxy *wsc_proxy  = NULL;
+
+    if (priv->stage2_ready)
+        return NM_ACT_STAGE_RETURN_SUCCESS;
+
+    if (!priv->dbus_p2p_proxy) {
+        cleanup_connect_attempt(self);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    if (nm_clear_g_source_inst(&priv->find_peer_timeout_source))
+        nm_assert_not_reached();
+
+    connection = nm_device_get_applied_connection(device);
+    g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
+    nm_assert(
+        NM_IS_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)));
+
+    /* The prepare stage ensures that the peer has been found */
+    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, TRUE);
+    if (!peer) {
+        cleanup_connect_attempt(self);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    s_wifi_p2p =
+        NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
+    if (nm_setting_wifi_p2p_get_wps_method(s_wifi_p2p)
+        == NM_SETTING_WIRELESS_SECURITY_WPS_METHOD_PIN) {
+        /* TODO: check we have the pin secret, if so use StartPin(pin) otherwise request pin,
+         * move to NEED_AUTH and return postpone */
+        cleanup_connect_attempt(self);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    peer_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(),
+                                                   nm_wifi_p2p_peer_get_supplicant_path(peer),
+                                                   NM_IWD_P2P_PEER_INTERFACE);
+    wsc_proxy  = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(),
+                                                  nm_wifi_p2p_peer_get_supplicant_path(peer),
+                                                  NM_IWD_WSC_INTERFACE);
+
+    if (!wsc_proxy || !peer_proxy) {
+        cleanup_connect_attempt(self);
+        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
+        return NM_ACT_STAGE_RETURN_FAILURE;
+    }
+
+    g_dbus_proxy_call(wsc_proxy,
+                      "PushButton",
+                      NULL,
+                      G_DBUS_CALL_FLAGS_NONE,
+                      G_MAXINT,
+                      priv->connect_cancellable,
+                      iwd_wsc_connect_cb,
+                      self);
+
+    priv->dbus_peer_proxy = g_steal_pointer(&peer_proxy);
+    return NM_ACT_STAGE_RETURN_POSTPONE;
+}
+
+/*****************************************************************************/
+
+static void
+emit_signal_p2p_peer_add_remove(NMDeviceIwdP2P *device,
+                                NMWifiP2PPeer  *peer,
+                                gboolean        is_added /* or else is_removed */)
+{
+    nm_dbus_object_emit_signal(NM_DBUS_OBJECT(device),
+                               &interface_info_device_wifi_p2p,
+                               is_added ? &nm_signal_info_wifi_p2p_peer_added
+                                        : &nm_signal_info_wifi_p2p_peer_removed,
+                               "(o)",
+                               nm_dbus_object_get_path(NM_DBUS_OBJECT(peer)));
+}
+
+static void
+act_check_new_peer_compatible(NMDeviceIwdP2P *self, NMWifiP2PPeer *peer)
+{
+    NMDevice     *device = NM_DEVICE(self);
+    NMConnection *connection;
+
+    connection = nm_device_get_applied_connection(device);
+    nm_assert(NM_IS_CONNECTION(connection));
+
+    if (nm_wifi_p2p_peer_check_compatible(peer, connection, TRUE)) {
+        /* A peer for the connection was found, cancel the timeout and go to configure state. */
+        iwd_release_discovery(self);
+        nm_device_activate_schedule_stage2_device_config(device, FALSE);
+    }
+}
+
+static void
+peer_add_remove(NMDeviceIwdP2P *self,
+                gboolean        is_adding, /* or else removing */
+                NMWifiP2PPeer  *peer,
+                gboolean        recheck_available_connections)
+{
+    NMDevice              *device = NM_DEVICE(self);
+    NMDeviceIwdP2PPrivate *priv   = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (is_adding) {
+        g_object_ref(peer);
+        peer->wifi_device = device;
+        c_list_link_tail(&priv->peers_lst_head, &peer->peers_lst);
+        nm_dbus_object_export(NM_DBUS_OBJECT(peer));
+        _peer_dump(self, LOGL_DEBUG, peer, "added", 0);
+
+        emit_signal_p2p_peer_add_remove(self, peer, TRUE);
+    } else {
+        peer->wifi_device = NULL;
+        c_list_unlink(&peer->peers_lst);
+        _peer_dump(self, LOGL_DEBUG, peer, "removed", 0);
+    }
+
+    _notify(self, PROP_PEERS);
+
+    if (!is_adding) {
+        emit_signal_p2p_peer_add_remove(self, peer, FALSE);
+        nm_dbus_object_clear_and_unexport(&peer);
+    }
+
+    if (is_adding) {
+        /* If we are in prepare state, then we are currently running a find
+         * to search for the requested peer. */
+        if (priv->find_peer_timeout_source
+            && nm_device_get_state(device) == NM_DEVICE_STATE_PREPARE)
+            act_check_new_peer_compatible(self, peer);
+
+        /* TODO: We may want to re-check auto-activation here */
+    }
+}
+
+static void
+iwd_peer_interface_added_cb(GDBusObject *peer_obj, GDBusInterface *interface, gpointer user_data)
+{
+    NMDeviceIwdP2P        *self = user_data;
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    const char            *iface_name;
+    NMWifiP2PPeer         *peer;
+
+    g_return_if_fail(G_IS_DBUS_PROXY(interface));
+
+    iface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface));
+    if (!nm_streq(iface_name, NM_IWD_P2P_WFD_INTERFACE))
+        return;
+
+    peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head,
+                                                     g_dbus_object_get_object_path(peer_obj));
+    if (!peer)
+        return;
+
+    nm_wifi_p2p_peer_update_from_iwd_object(peer, peer_obj);
+
+    /* If we are in prepare state, then we are currently running a find
+     * to search for the requested peer. */
+    if (priv->find_peer_timeout_source)
+        act_check_new_peer_compatible(self, peer);
+}
+
+static void
+iwd_peer_interface_removed_cb(GDBusObject *peer_obj, GDBusInterface *interface, gpointer user_data)
+{
+    NMDeviceIwdP2P        *self = user_data;
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    const char            *iface_name;
+    NMWifiP2PPeer         *peer;
+
+    g_return_if_fail(G_IS_DBUS_PROXY(interface));
+
+    iface_name = g_dbus_proxy_get_interface_name(G_DBUS_PROXY(interface));
+    if (!nm_streq(iface_name, NM_IWD_P2P_WFD_INTERFACE))
+        return;
+
+    peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head,
+                                                     g_dbus_object_get_object_path(peer_obj));
+    if (!peer)
+        return;
+
+    nm_wifi_p2p_peer_set_wfd_ies(peer, NULL);
+}
+
+void
+nm_device_iwd_p2p_peer_add_remove(NMDeviceIwdP2P *self, GDBusObject *peer_obj, bool add)
+{
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    NMWifiP2PPeer         *found_peer;
+
+    found_peer = nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head,
+                                                           g_dbus_object_get_object_path(peer_obj));
+
+    if (found_peer && !add) {
+        if (priv->dbus_peer_proxy
+            && !nm_streq(g_dbus_object_get_object_path(peer_obj),
+                         g_dbus_proxy_get_object_path(priv->dbus_peer_proxy))) {
+            cleanup_connect_attempt(self);
+            nm_device_state_changed(NM_DEVICE(self),
+                                    NM_DEVICE_STATE_DISCONNECTED,
+                                    NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+        }
+
+        peer_add_remove(self, FALSE, found_peer, TRUE);
+        g_signal_handlers_disconnect_by_data(peer_obj, self);
+    }
+
+    if (!found_peer && add) {
+        gs_unref_object NMWifiP2PPeer *peer = nm_wifi_p2p_peer_new_from_iwd_object(peer_obj);
+
+        if (!peer) {
+            _LOGD(LOGD_DEVICE | LOGD_WIFI,
+                  "Can't interpret IWD Peer properties at %s",
+                  g_dbus_object_get_object_path(peer_obj));
+            return;
+        }
+
+        peer_add_remove(self, TRUE, peer, TRUE);
+
+        /* None of the D-Bus properties that we use on this interface emit PropertiesChanges
+         * signals, only the WFD properties do.  We do listen to changes to "Connected"
+         * but only while we're connecting/connected to a given peer.
+         */
+        g_signal_connect(peer_obj,
+                         "interface-added",
+                         G_CALLBACK(iwd_peer_interface_added_cb),
+                         self);
+        g_signal_connect(peer_obj,
+                         "interface-removed",
+                         G_CALLBACK(iwd_peer_interface_removed_cb),
+                         self);
+
+        /* TODO: every now and then call p2p.Device.GetPeers() and update the signal strength
+         * values for all peers we got through ObjectManager events.
+         */
+    }
+
+    schedule_peer_list_dump(self);
+}
+
+/*****************************************************************************/
+
+static void
+deactivate(NMDevice *device)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (priv->find_peer_timeout_source)
+        iwd_release_discovery(self);
+
+    if (priv->dbus_peer_proxy) {
+        g_dbus_proxy_call(priv->dbus_peer_proxy,
+                          "Disconnect",
+                          NULL,
+                          G_DBUS_CALL_FLAGS_NONE,
+                          G_MAXINT,
+                          NULL,
+                          NULL,
+                          self);
+
+        cleanup_connect_attempt(self);
+    }
+}
+
+static guint32
+get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *out_force)
+{
+    *out_source = NM_DEVICE_MTU_SOURCE_NONE;
+    return 0;
+}
+
+static gboolean
+unmanaged_on_quit(NMDevice *self)
+{
+    return TRUE;
+}
+
+static void
+device_state_changed(NMDevice           *device,
+                     NMDeviceState       new_state,
+                     NMDeviceState       old_state,
+                     NMDeviceStateReason reason)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(device);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    switch (new_state) {
+    case NM_DEVICE_STATE_UNMANAGED:
+        break;
+    case NM_DEVICE_STATE_UNAVAILABLE:
+        if (priv->enabled) {
+            nm_device_queue_recheck_available(device,
+                                              NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+                                              NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+        }
+        break;
+    case NM_DEVICE_STATE_IP_CONFIG:
+        /* TODO: start periodic RSSI and bitrate updates? */
+        break;
+    default:
+        break;
+    }
+}
+
+static void
+impl_device_iwd_p2p_start_find(NMDBusObject                      *obj,
+                               const NMDBusInterfaceInfoExtended *interface_info,
+                               const NMDBusMethodInfoExtended    *method_info,
+                               GDBusConnection                   *connection,
+                               const char                        *sender,
+                               GDBusMethodInvocation             *invocation,
+                               GVariant                          *parameters)
+{
+    NMDeviceIwdP2P            *self    = NM_DEVICE_IWD_P2P(obj);
+    NMDeviceIwdP2PPrivate     *priv    = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    gs_unref_variant GVariant *options = NULL;
+    const char                *opts_key;
+    GVariant                  *opts_val;
+    GVariantIter               iter;
+    gint32                     timeout = 30;
+
+    g_variant_get(parameters, "(@a{sv})", &options);
+
+    g_variant_iter_init(&iter, options);
+    while (g_variant_iter_next(&iter, "{&sv}", &opts_key, &opts_val)) {
+        _nm_unused gs_unref_variant GVariant *opts_val_free = opts_val;
+
+        if (nm_streq(opts_key, "timeout")) {
+            if (!g_variant_is_of_type(opts_val, G_VARIANT_TYPE_INT32)) {
+                g_dbus_method_invocation_return_error_literal(
+                    invocation,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_INVALID_ARGUMENT,
+                    "\"timeout\" must be an integer \"i\"");
+                return;
+            }
+
+            timeout = g_variant_get_int32(opts_val);
+            if (timeout <= 0 || timeout > 600) {
+                g_dbus_method_invocation_return_error_literal(
+                    invocation,
+                    NM_DEVICE_ERROR,
+                    NM_DEVICE_ERROR_NOT_ALLOWED,
+                    "The timeout for a find operation needs to be in the range of 1-600s.");
+                return;
+            }
+
+            continue;
+        }
+
+        g_dbus_method_invocation_return_error(invocation,
+                                              NM_DEVICE_ERROR,
+                                              NM_DEVICE_ERROR_INVALID_ARGUMENT,
+                                              "Unsupported options key \"%s\"",
+                                              opts_key);
+        return;
+    }
+
+    if (!priv->enabled || nm_device_is_activating(NM_DEVICE(self))) {
+        g_dbus_method_invocation_return_error_literal(invocation,
+                                                      NM_DEVICE_ERROR,
+                                                      NM_DEVICE_ERROR_NOT_ACTIVE,
+                                                      "P2P device not enabled or busy.");
+        return;
+    }
+
+    iwd_request_discovery(self, timeout);
+    g_dbus_method_invocation_return_value(invocation, NULL);
+}
+
+static void
+impl_device_iwd_p2p_stop_find(NMDBusObject                      *obj,
+                              const NMDBusInterfaceInfoExtended *interface_info,
+                              const NMDBusMethodInfoExtended    *method_info,
+                              GDBusConnection                   *connection,
+                              const char                        *sender,
+                              GDBusMethodInvocation             *invocation,
+                              GVariant                          *parameters)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(obj);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (!priv->find_peer_timeout_source || nm_device_is_activating(NM_DEVICE(self))) {
+        g_dbus_method_invocation_return_error_literal(invocation,
+                                                      NM_DEVICE_ERROR,
+                                                      NM_DEVICE_ERROR_NOT_ACTIVE,
+                                                      "Find phase is not active.");
+        return;
+    }
+
+    iwd_release_discovery(self);
+    g_dbus_method_invocation_return_value(invocation, NULL);
+}
+
+/*****************************************************************************/
+
+static bool
+nm_device_iwd_p2p_set_dbus_obj(NMDeviceIwdP2P *self, GDBusObject *obj)
+{
+    NMDeviceIwdP2PPrivate     *priv;
+    gs_unref_variant GVariant *enabled_value = NULL;
+
+    g_return_val_if_fail(NM_IS_DEVICE_IWD_P2P(self), FALSE);
+
+    priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    if (priv->dbus_obj == obj)
+        goto done;
+
+    if (priv->dbus_obj) {
+        cleanup_connect_attempt(self);
+        g_signal_handlers_disconnect_by_data(priv->dbus_p2p_proxy, self);
+        g_clear_object(&priv->dbus_p2p_proxy);
+        g_clear_object(&priv->dbus_obj);
+        priv->enabled = FALSE;
+    }
+
+    if (!obj)
+        goto done;
+
+    priv->dbus_p2p_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_INTERFACE));
+    if (!priv->dbus_p2p_proxy)
+        return FALSE;
+
+    enabled_value = g_dbus_proxy_get_cached_property(priv->dbus_p2p_proxy, "Enabled");
+    if (!enabled_value || !g_variant_is_of_type(enabled_value, G_VARIANT_TYPE_BOOLEAN))
+        return FALSE;
+
+    priv->dbus_obj = g_object_ref(obj);
+
+    g_signal_connect(priv->dbus_p2p_proxy,
+                     "g-properties-changed",
+                     G_CALLBACK(p2p_properties_changed_cb),
+                     self);
+
+    priv->enabled = g_variant_get_boolean(enabled_value);
+    _LOGD(LOGD_WIFI, "iniital state is %s", priv->enabled ? "enabled" : "disabled");
+
+done:
+    nm_device_queue_recheck_available(NM_DEVICE(self),
+                                      NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
+                                      NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
+    return TRUE;
+}
+
+void
+nm_device_iwd_p2p_remove(NMDeviceIwdP2P *self)
+{
+    g_signal_emit_by_name(self, NM_DEVICE_REMOVED);
+}
+
+/*****************************************************************************/
+
+static const char *
+get_type_description(NMDevice *device)
+{
+    return "wifi-p2p";
+}
+
+/*****************************************************************************/
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_added = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(
+    "PeerAdded",
+    .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), );
+
+static const GDBusSignalInfo nm_signal_info_wifi_p2p_peer_removed =
+    NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(
+        "PeerRemoved",
+        .args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("peer", "o"), ), );
+
+static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
+    .parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
+        NM_DBUS_INTERFACE_DEVICE_WIFI_P2P,
+        .methods = NM_DEFINE_GDBUS_METHOD_INFOS(
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
+                NM_DEFINE_GDBUS_METHOD_INFO_INIT(
+                    "StartFind",
+                    .in_args = NM_DEFINE_GDBUS_ARG_INFOS(
+                        NM_DEFINE_GDBUS_ARG_INFO("options", "a{sv}"), ), ),
+                .handle = impl_device_iwd_p2p_start_find, ),
+            NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("StopFind", ),
+                                                .handle = impl_device_iwd_p2p_stop_find, ), ),
+        .signals    = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_wifi_p2p_peer_added,
+                                                &nm_signal_info_wifi_p2p_peer_removed, ),
+        .properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Peers",
+                                                           "ao",
+                                                           NM_DEVICE_IWD_P2P_PEERS), ), ),
+};
+
+/*****************************************************************************/
+
+static void
+get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(object);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+    const char           **list;
+
+    switch (prop_id) {
+    case PROP_PEERS:
+        list = nm_wifi_p2p_peers_get_paths(&priv->peers_lst_head);
+        g_value_take_boxed(value, nm_strv_make_deep_copied(list));
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+        break;
+    }
+}
+
+/*****************************************************************************/
+
+static void
+nm_device_iwd_p2p_init(NMDeviceIwdP2P *self)
+{
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(self);
+
+    c_list_init(&priv->peers_lst_head);
+}
+
+NMDeviceIwdP2P *
+nm_device_iwd_p2p_new(GDBusObject *dbus_obj)
+{
+    gs_unref_object NMDeviceIwdP2P *self = NULL;
+
+    g_return_val_if_fail(!dbus_obj || G_IS_DBUS_OBJECT(dbus_obj), NULL);
+
+    /* cfg80211 P2P-Device virtual interfaces don't map to netdev-type interfaces.
+     * Provide a false unique interface name only to avoid triggering assertions
+     * in NMManager and for that name to appear in debug messages.  */
+    self = g_object_new(NM_TYPE_DEVICE_IWD_P2P,
+                        NM_DEVICE_IFACE,
+                        g_dbus_object_get_object_path(dbus_obj),
+                        NM_DEVICE_TYPE_DESC,
+                        "802.11 Wi-Fi P2P",
+                        NM_DEVICE_DEVICE_TYPE,
+                        NM_DEVICE_TYPE_WIFI_P2P,
+                        NM_DEVICE_LINK_TYPE,
+                        NM_LINK_TYPE_WIFI,
+                        NM_DEVICE_RFKILL_TYPE,
+                        RFKILL_TYPE_WLAN,
+                        NULL);
+
+    if (!self || !nm_device_iwd_p2p_set_dbus_obj(self, dbus_obj))
+        return NULL;
+
+    return nm_steal_pointer(&self);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMDeviceIwdP2P        *self = NM_DEVICE_IWD_P2P(object);
+    NMDeviceIwdP2PPrivate *priv = NM_DEVICE_IWD_P2P_GET_PRIVATE(object);
+
+    nm_clear_g_source_inst(&priv->peer_dump_source);
+
+    nm_device_iwd_p2p_set_dbus_obj(self, NULL);
+
+    G_OBJECT_CLASS(nm_device_iwd_p2p_parent_class)->dispose(object);
+}
+
+static void
+nm_device_iwd_p2p_class_init(NMDeviceIwdP2PClass *klass)
+{
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
+    NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
+    NMDeviceClass     *device_class      = NM_DEVICE_CLASS(klass);
+
+    object_class->get_property = get_property;
+    object_class->dispose      = dispose;
+
+    dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_device_wifi_p2p);
+
+    device_class->connection_type_supported        = NM_SETTING_WIFI_P2P_SETTING_NAME;
+    device_class->connection_type_check_compatible = NM_SETTING_WIFI_P2P_SETTING_NAME;
+    device_class->link_types           = NM_DEVICE_DEFINE_LINK_TYPES(NM_LINK_TYPE_WIFI_P2P);
+    device_class->get_type_description = get_type_description;
+
+    device_class->is_available                = is_available;
+    device_class->check_connection_compatible = check_connection_compatible;
+    device_class->check_connection_available  = check_connection_available;
+    device_class->complete_connection         = complete_connection;
+    device_class->get_enabled                 = get_enabled;
+    device_class->set_enabled                 = set_enabled;
+
+    device_class->act_stage1_prepare = act_stage1_prepare;
+    device_class->act_stage2_config  = act_stage2_config;
+    device_class->get_configured_mtu = get_configured_mtu;
+
+    device_class->deactivate        = deactivate;
+    device_class->unmanaged_on_quit = unmanaged_on_quit;
+
+    device_class->state_changed = device_state_changed;
+
+    obj_properties[PROP_PEERS] = g_param_spec_boxed(NM_DEVICE_IWD_P2P_PEERS,
+                                                    "",
+                                                    "",
+                                                    G_TYPE_STRV,
+                                                    G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+}
diff --git a/src/core/devices/wifi/nm-device-iwd-p2p.h b/src/core/devices/wifi/nm-device-iwd-p2p.h
new file mode 100644
index 00000000..9ce353c0
--- /dev/null
+++ b/src/core/devices/wifi/nm-device-iwd-p2p.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2021 Intel Corporation
+ */
+
+#ifndef __NM_DEVICE_IWD_P2P_H__
+#define __NM_DEVICE_IWD_P2P_H__
+
+#include "devices/nm-device.h"
+#include "nm-device-wifi-p2p.h"
+
+#define NM_TYPE_DEVICE_IWD_P2P (nm_device_iwd_p2p_get_type())
+#define NM_DEVICE_IWD_P2P(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2P))
+#define NM_DEVICE_IWD_P2P_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2PClass))
+#define NM_IS_DEVICE_IWD_P2P(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DEVICE_IWD_P2P))
+#define NM_IS_DEVICE_IWD_P2P_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DEVICE_IWD_P2P))
+#define NM_DEVICE_IWD_P2P_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DEVICE_IWD_P2P, NMDeviceIwdP2PClass))
+
+#define NM_DEVICE_IWD_P2P_PEERS  NM_DEVICE_WIFI_P2P_PEERS
+#define NM_DEVICE_IWD_P2P_GROUPS NM_DEVICE_WIFI_P2P_GROUPS
+
+typedef struct _NMDeviceIwdP2P      NMDeviceIwdP2P;
+typedef struct _NMDeviceIwdP2PClass NMDeviceIwdP2PClass;
+
+GType nm_device_iwd_p2p_get_type(void);
+
+NMDeviceIwdP2P *nm_device_iwd_p2p_new(GDBusObject *object);
+
+void nm_device_iwd_p2p_remove(NMDeviceIwdP2P *p2p);
+
+void nm_device_iwd_p2p_peer_add_remove(NMDeviceIwdP2P *p2p, GDBusObject *peer_obj, bool add);
+
+#endif /* __NM_DEVICE_IWD_P2P_H__ */
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index 27a3188b..7543d9da 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -48,14 +48,14 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceIwd,
                              PROP_LAST_SCAN, );
 
 typedef struct {
-    GDBusObject *                 dbus_obj;
-    GDBusProxy *                  dbus_device_proxy;
-    GDBusProxy *                  dbus_station_proxy;
-    GDBusProxy *                  dbus_ap_proxy;
-    GDBusProxy *                  dbus_adhoc_proxy;
+    GDBusObject                  *dbus_obj;
+    GDBusProxy                   *dbus_device_proxy;
+    GDBusProxy                   *dbus_station_proxy;
+    GDBusProxy                   *dbus_ap_proxy;
+    GDBusProxy                   *dbus_adhoc_proxy;
     CList                         aps_lst_head;
-    NMWifiAP *                    current_ap;
-    GCancellable *                cancellable;
+    NMWifiAP                     *current_ap;
+    GCancellable                 *cancellable;
     _NMDeviceWifiCapabilities     capabilities;
     NMActRequestGetSecretsCallId *wifi_secrets_id;
     guint                         periodic_scan_id;
@@ -74,8 +74,8 @@ typedef struct {
     uint32_t                      ap_id;
     guint32                       rate;
     NMEtherAddr                   current_ap_bssid;
-    GDBusMethodInvocation *       pending_agent_request;
-    NMActiveConnection *          assumed_ac;
+    GDBusMethodInvocation        *pending_agent_request;
+    NMActiveConnection           *assumed_ac;
     guint                         assumed_ac_timeout;
 } NMDeviceIwdPrivate;
 
@@ -120,7 +120,7 @@ _ap_dump(NMDeviceIwd *self, NMLogLevel log_level, const NMWifiAP *ap, const char
 static void
 ap_add_remove(NMDeviceIwd *self,
               gboolean     is_adding, /* or else is removing */
-              NMWifiAP *   ap,
+              NMWifiAP    *ap,
               gboolean     recheck_available_connections)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
@@ -156,7 +156,7 @@ static void
 set_current_ap(NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
 {
     NMDeviceIwdPrivate *priv;
-    NMWifiAP *          old_ap;
+    NMWifiAP           *old_ap;
 
     g_return_if_fail(NM_IS_DEVICE_IWD(self));
 
@@ -186,7 +186,7 @@ static void
 remove_all_aps(NMDeviceIwd *self)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMWifiAP *          ap, *ap_safe;
+    NMWifiAP           *ap, *ap_safe;
 
     if (c_list_is_empty(&priv->aps_lst_head))
         return;
@@ -219,20 +219,20 @@ ap_security_flags_from_network_type(const char *type)
 
 static NMWifiAP *
 ap_from_network(NMDeviceIwd *self,
-                GDBusProxy * network,
+                GDBusProxy  *network,
                 NMRefString *bss_path,
                 gint64       last_seen_msec,
                 int16_t      signal)
 {
-    NMDeviceIwdPrivate *priv              = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDeviceIwdPrivate        *priv       = NM_DEVICE_IWD_GET_PRIVATE(self);
     gs_unref_variant GVariant *name_value = NULL;
     gs_unref_variant GVariant *type_value = NULL;
-    const char *               name;
-    const char *               type;
+    const char                *name;
+    const char                *type;
     uint32_t                   ap_id;
-    gs_unref_bytes GBytes *ssid = NULL;
-    NMWifiAP *             ap;
-    NMSupplicantBssInfo    bss_info;
+    gs_unref_bytes GBytes     *ssid = NULL;
+    NMWifiAP                  *ap;
+    NMSupplicantBssInfo        bss_info;
 
     g_return_val_if_fail(network, NULL);
 
@@ -284,14 +284,14 @@ ap_from_network(NMDeviceIwd *self,
 
 static void
 insert_ap_from_network(NMDeviceIwd *self,
-                       GHashTable * aps,
-                       const char * path,
+                       GHashTable  *aps,
+                       const char  *path,
                        gint64       last_seen_msec,
                        int16_t      signal)
 {
-    gs_unref_object GDBusProxy *network_proxy = NULL;
-    nm_auto_ref_string NMRefString *bss_path  = nm_ref_string_new(path);
-    NMWifiAP *                      ap;
+    gs_unref_object GDBusProxy     *network_proxy = NULL;
+    nm_auto_ref_string NMRefString *bss_path      = nm_ref_string_new(path);
+    NMWifiAP                       *ap;
 
     if (g_hash_table_lookup(aps, bss_path)) {
         _LOGD(LOGD_WIFI, "Duplicate network at %s", path);
@@ -311,16 +311,16 @@ insert_ap_from_network(NMDeviceIwd *self,
 static void
 get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *       self = user_data;
-    NMDeviceIwdPrivate *priv;
-    gs_free_error GError *error        = NULL;
-    gs_unref_variant GVariant *variant = NULL;
-    GVariantIter *             networks;
-    const char *               path;
-    int16_t                    signal;
-    NMWifiAP *                 ap, *ap_safe, *new_ap;
-    gboolean                   changed;
-    GHashTableIter             ap_iter;
+    NMDeviceIwd                   *self = user_data;
+    NMDeviceIwdPrivate            *priv;
+    gs_free_error GError          *error   = NULL;
+    gs_unref_variant GVariant     *variant = NULL;
+    GVariantIter                  *networks;
+    const char                    *path;
+    int16_t                        signal;
+    NMWifiAP                      *ap, *ap_safe, *new_ap;
+    gboolean                       changed;
+    GHashTableIter                 ap_iter;
     gs_unref_hashtable GHashTable *new_aps = NULL;
     gint64                         last_seen_msec;
 
@@ -421,7 +421,7 @@ periodic_update(NMDeviceIwd *self)
     int                 percent;
     NMEtherAddr         bssid;
     gboolean            ap_changed = FALSE;
-    NMPlatform *        platform;
+    NMPlatform         *platform;
 
     ifindex = nm_device_get_ifindex(NM_DEVICE(self));
     if (ifindex <= 0)
@@ -528,8 +528,8 @@ cleanup_association_attempt(NMDeviceIwd *self, gboolean disconnect)
 }
 
 static void
-reset_mode(NMDeviceIwd *       self,
-           GCancellable *      cancellable,
+reset_mode(NMDeviceIwd        *self,
+           GCancellable       *cancellable,
            GAsyncReadyCallback callback,
            gpointer            user_data)
 {
@@ -575,7 +575,7 @@ get_variant_state(GVariant *v)
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
 
     if (!priv->dbus_obj)
@@ -608,8 +608,8 @@ disconnect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     gs_unref_object NMDeviceIwd *self = NULL;
     NMDeviceDeactivateCallback   callback;
     gpointer                     callback_user_data;
-    gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
+    gs_unref_variant GVariant   *variant = NULL;
+    gs_free_error GError        *error   = NULL;
 
     nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data);
 
@@ -623,7 +623,7 @@ disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable)
     gs_unref_object NMDeviceIwd *self = NULL;
     NMDeviceDeactivateCallback   callback;
     gpointer                     callback_user_data;
-    gs_free_error GError *cancelled_error = NULL;
+    gs_free_error GError        *cancelled_error = NULL;
 
     nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data);
 
@@ -632,12 +632,12 @@ disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable)
 }
 
 static void
-deactivate_async(NMDevice *                 device,
-                 GCancellable *             cancellable,
+deactivate_async(NMDevice                  *device,
+                 GCancellable              *cancellable,
                  NMDeviceDeactivateCallback callback,
                  gpointer                   callback_user_data)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
     gpointer            user_data;
 
@@ -676,7 +676,7 @@ static gboolean
 is_connection_known_network(NMConnection *connection)
 {
     NMIwdNetworkSecurity security;
-    gs_free char *       ssid = NULL;
+    gs_free char        *ssid = NULL;
 
     if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, &security))
         return FALSE;
@@ -688,7 +688,7 @@ static gboolean
 is_ap_known_network(NMWifiAP *ap)
 {
     gs_unref_object GDBusProxy *network_proxy = NULL;
-    gs_unref_variant GVariant *known_network  = NULL;
+    gs_unref_variant GVariant  *known_network = NULL;
 
     network_proxy =
         nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(),
@@ -704,17 +704,17 @@ is_ap_known_network(NMWifiAP *ap)
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
-    NMDeviceIwd *        self = NM_DEVICE_IWD(device);
-    NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingWireless *  s_wireless;
-    const char *         mac;
-    const char *const *  mac_blacklist;
+    NMDeviceIwd         *self = NM_DEVICE_IWD(device);
+    NMDeviceIwdPrivate  *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingWireless   *s_wireless;
+    const char          *mac;
+    const char *const   *mac_blacklist;
     int                  i;
-    const char *         perm_hw_addr;
-    const char *         mode;
+    const char          *perm_hw_addr;
+    const char          *mode;
     NMIwdNetworkSecurity security;
-    GBytes *             ssid;
-    const guint8 *       ssid_bytes;
+    GBytes              *ssid;
+    const guint8        *ssid_bytes;
     gsize                ssid_len;
 
     if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class)
@@ -860,17 +860,17 @@ 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)
-{
-    NMDeviceIwd *        self = NM_DEVICE_IWD(device);
-    NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingWireless *  s_wifi;
-    const char *         mode;
-    NMWifiAP *           ap = NULL;
+                           const char                    *specific_object,
+                           GError                       **error)
+{
+    NMDeviceIwd         *self = NM_DEVICE_IWD(device);
+    NMDeviceIwdPrivate  *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingWireless   *s_wifi;
+    const char          *mode;
+    NMWifiAP            *ap = NULL;
     NMIwdNetworkSecurity security;
 
     s_wifi = nm_connection_get_setting_wireless(connection);
@@ -952,20 +952,20 @@ iwd_ssid_to_str(const GBytes *ssid)
 }
 
 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)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingWireless * s_wifi;
-    gs_free char *      ssid_utf8 = NULL;
-    NMWifiAP *          ap;
-    GBytes *            ssid   = NULL;
+    NMSettingWireless  *s_wifi;
+    gs_free char       *ssid_utf8 = NULL;
+    NMWifiAP           *ap;
+    GBytes             *ssid   = NULL;
     gboolean            hidden = FALSE;
-    const char *        mode;
+    const char         *mode;
 
     s_wifi = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
@@ -1059,7 +1059,7 @@ complete_connection(NMDevice *           device,
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceIwd *       self  = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self  = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv  = NM_DEVICE_IWD_GET_PRIVATE(self);
     NMDeviceState       state = nm_device_get_state(device);
 
@@ -1088,12 +1088,12 @@ get_autoconnect_allowed(NMDevice *device)
 static gboolean
 can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMConnection *      connection;
-    NMSettingWireless * s_wifi;
-    NMWifiAP *          ap;
-    const char *        mode;
+    NMConnection       *connection;
+    NMSettingWireless  *s_wifi;
+    NMWifiAP           *ap;
+    const char         *mode;
     guint64             timestamp = 0;
 
     nm_assert(!specific_object || !*specific_object);
@@ -1141,10 +1141,10 @@ _nm_device_iwd_get_aps(NMDeviceIwd *self)
 static void
 scan_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *       self = user_data;
-    NMDeviceIwdPrivate *priv;
+    NMDeviceIwd               *self = user_data;
+    NMDeviceIwdPrivate        *priv;
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
+    gs_free_error GError      *error   = NULL;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant && nm_utils_error_is_cancelled(error))
@@ -1165,14 +1165,14 @@ scan_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 }
 
 static void
-dbus_request_scan_cb(NMDevice *             device,
+dbus_request_scan_cb(NMDevice              *device,
                      GDBusMethodInvocation *context,
-                     NMAuthSubject *        subject,
-                     GError *               error,
+                     NMAuthSubject         *subject,
+                     GError                *error,
                      gpointer               user_data)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
-    NMDeviceIwdPrivate *priv;
+    NMDeviceIwd               *self = NM_DEVICE_IWD(device);
+    NMDeviceIwdPrivate        *priv;
     gs_unref_variant GVariant *scan_options = user_data;
 
     if (error) {
@@ -1229,7 +1229,7 @@ void
 _nm_device_iwd_request_scan(NMDeviceIwd *self, GVariant *options, GDBusMethodInvocation *invocation)
 {
     NMDeviceIwdPrivate *priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device = NM_DEVICE(self);
+    NMDevice           *device = NM_DEVICE(self);
 
     if (!priv->can_scan) {
         g_dbus_method_invocation_return_error_literal(invocation,
@@ -1283,7 +1283,7 @@ static const char *
 get_agent_request_network_path(GDBusMethodInvocation *invocation)
 {
     const char *method_name  = g_dbus_method_invocation_get_method_name(invocation);
-    GVariant *  params       = g_dbus_method_invocation_get_parameters(invocation);
+    GVariant   *params       = g_dbus_method_invocation_get_parameters(invocation);
     const char *network_path = NULL;
 
     if (nm_streq(method_name, "RequestPassphrase"))
@@ -1308,17 +1308,17 @@ get_agent_request_network_path(GDBusMethodInvocation *invocation)
  * Return TRUE in either case, return FALSE if an error is detected.
  */
 static gboolean
-try_reply_agent_request(NMDeviceIwd *          self,
-                        NMConnection *         connection,
+try_reply_agent_request(NMDeviceIwd           *self,
+                        NMConnection          *connection,
                         GDBusMethodInvocation *invocation,
                         gboolean               allow_existing,
-                        const char **          setting_name,
-                        const char **          setting_key,
-                        gboolean *             replied)
+                        const char           **setting_name,
+                        const char           **setting_key,
+                        gboolean              *replied)
 {
-    const char *               method_name = g_dbus_method_invocation_get_method_name(invocation);
+    const char                *method_name = g_dbus_method_invocation_get_method_name(invocation);
     NMSettingWirelessSecurity *s_wireless_sec;
-    NMSetting8021x *           s_8021x;
+    NMSetting8021x            *s_8021x;
 
     s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
     s_8021x        = nm_connection_get_setting_802_1x(connection);
@@ -1418,7 +1418,7 @@ try_reply_agent_request(NMDeviceIwd *          self,
 static gboolean
 assumed_ac_timeout_cb(gpointer user_data)
 {
-    NMDeviceIwd *       self = user_data;
+    NMDeviceIwd        *self = user_data;
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
 
     nm_assert(priv->assumed_ac);
@@ -1434,30 +1434,30 @@ assumed_ac_timeout_cb(gpointer user_data)
     return G_SOURCE_REMOVE;
 }
 
-static void wifi_secrets_get_one(NMDeviceIwd *                self,
-                                 const char *                 setting_name,
+static void wifi_secrets_get_one(NMDeviceIwd                 *self,
+                                 const char                  *setting_name,
                                  NMSecretAgentGetSecretsFlags flags,
-                                 const char *                 setting_key,
-                                 GDBusMethodInvocation *      invocation);
+                                 const char                  *setting_key,
+                                 GDBusMethodInvocation       *invocation);
 
 static void
-wifi_secrets_cb(NMActRequest *                req,
+wifi_secrets_cb(NMActRequest                 *req,
                 NMActRequestGetSecretsCallId *call_id,
-                NMSettingsConnection *        s_connection,
-                GError *                      error,
+                NMSettingsConnection         *s_connection,
+                GError                       *error,
                 gpointer                      user_data)
 {
-    NMDeviceIwd *                self;
-    NMDeviceIwdPrivate *         priv;
-    NMDevice *                   device;
-    GDBusMethodInvocation *      invocation;
-    const char *                 setting_name;
-    const char *                 setting_key;
+    NMDeviceIwd                 *self;
+    NMDeviceIwdPrivate          *priv;
+    NMDevice                    *device;
+    GDBusMethodInvocation       *invocation;
+    const char                  *setting_name;
+    const char                  *setting_key;
     gboolean                     replied;
     NMSecretAgentGetSecretsFlags get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
     NMIwdNetworkSecurity security;
-    NMConnection *       connection;
+    NMConnection        *connection;
 
     nm_utils_user_data_unpack(user_data, &self, &invocation);
 
@@ -1550,14 +1550,14 @@ secrets_error:
 }
 
 static void
-wifi_secrets_get_one(NMDeviceIwd *                self,
-                     const char *                 setting_name,
+wifi_secrets_get_one(NMDeviceIwd                 *self,
+                     const char                  *setting_name,
                      NMSecretAgentGetSecretsFlags flags,
-                     const char *                 setting_key,
-                     GDBusMethodInvocation *      invocation)
+                     const char                  *setting_key,
+                     GDBusMethodInvocation       *invocation)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMActRequest *      req;
+    NMActRequest       *req;
 
     wifi_secrets_cancel(self);
 
@@ -1576,16 +1576,16 @@ wifi_secrets_get_one(NMDeviceIwd *                self,
 static void
 network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *       self           = user_data;
-    NMDevice *          device         = NM_DEVICE(self);
-    NMDeviceIwdPrivate *priv           = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDeviceIwd               *self    = user_data;
+    NMDevice                  *device  = NM_DEVICE(self);
+    NMDeviceIwdPrivate        *priv    = NM_DEVICE_IWD_GET_PRIVATE(self);
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
-    NMConnection *        connection;
-    gs_free char *        ssid   = NULL;
-    NMDeviceStateReason   reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
-    GVariant *            value;
-    gboolean              disconnect;
+    gs_free_error GError      *error   = NULL;
+    NMConnection              *connection;
+    gs_free char              *ssid   = NULL;
+    NMDeviceStateReason        reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED;
+    GVariant                  *value;
+    gboolean                   disconnect;
 
     disconnect = !priv->iwd_autoconnect
                  || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
@@ -1642,7 +1642,7 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     _LOGI(LOGD_DEVICE | LOGD_WIFI,
           "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Connected to '%s'.",
           ssid);
-    nm_device_activate_schedule_stage3_ip_config_start(device);
+    nm_device_activate_schedule_stage3_ip_config(device, FALSE);
 
     return;
 
@@ -1667,10 +1667,10 @@ failed:
 static void
 act_failed_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *    self              = user_data;
-    NMDevice *       device            = NM_DEVICE(self);
+    NMDeviceIwd               *self    = user_data;
+    NMDevice                  *device  = NM_DEVICE(self);
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
+    gs_free_error GError      *error   = NULL;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant && nm_utils_error_is_cancelled(error))
@@ -1689,12 +1689,12 @@ act_failed_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 static void
 act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *       self           = user_data;
-    NMDeviceIwdPrivate *priv           = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device         = NM_DEVICE(self);
+    NMDeviceIwd               *self    = user_data;
+    NMDeviceIwdPrivate        *priv    = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice                  *device  = NM_DEVICE(self);
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
-    gs_free char *        ssid         = NULL;
+    gs_free_error GError      *error   = NULL;
+    gs_free char              *ssid    = NULL;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant) {
@@ -1721,7 +1721,7 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data)
     _LOGI(LOGD_DEVICE | LOGD_WIFI,
           "Activation: (wifi) Stage 2 of 5 (Device Configure) successful.  Started '%s'.",
           ssid);
-    nm_device_activate_schedule_stage3_ip_config_start(device);
+    nm_device_activate_schedule_stage3_ip_config(device, FALSE);
 
     return;
 
@@ -1736,12 +1736,12 @@ error:
 static void
 act_check_interface(NMDeviceIwd *self)
 {
-    NMDeviceIwdPrivate * priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
-    NMSettingWireless *  s_wireless;
-    GDBusProxy *         proxy = NULL;
-    gs_free char *       ssid  = NULL;
-    const char *         mode;
+    NMDeviceIwdPrivate  *priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice            *device = NM_DEVICE(self);
+    NMSettingWireless   *s_wireless;
+    GDBusProxy          *proxy = NULL;
+    gs_free char        *ssid  = NULL;
+    const char          *mode;
     NMIwdNetworkSecurity security;
 
     if (!priv->act_mode_switch)
@@ -1780,7 +1780,7 @@ act_check_interface(NMDeviceIwd *self)
                           self);
     } else if (security == NM_IWD_NETWORK_SECURITY_PSK) {
         NMSettingWirelessSecurity *s_wireless_sec;
-        const char *               psk;
+        const char                *psk;
 
         s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting(
             device,
@@ -1813,11 +1813,11 @@ failed:
 static void
 act_set_mode_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMDeviceIwd *       self           = user_data;
-    NMDeviceIwdPrivate *priv           = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device         = NM_DEVICE(self);
+    NMDeviceIwd               *self    = user_data;
+    NMDeviceIwdPrivate        *priv    = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice                  *device  = NM_DEVICE(self);
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
+    gs_free_error GError      *error   = NULL;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant) {
@@ -1848,10 +1848,10 @@ static void
 act_set_mode(NMDeviceIwd *self)
 {
     NMDeviceIwdPrivate *priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device = NM_DEVICE(self);
-    const char *        iwd_mode;
-    const char *        mode;
-    NMSettingWireless * s_wireless;
+    NMDevice           *device = NM_DEVICE(self);
+    const char         *iwd_mode;
+    const char         *mode;
+    NMSettingWireless  *s_wireless;
 
     s_wireless =
         (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
@@ -1880,15 +1880,15 @@ act_set_mode(NMDeviceIwd *self)
 }
 
 static void
-act_psk_cb(NMActRequest *                req,
+act_psk_cb(NMActRequest                 *req,
            NMActRequestGetSecretsCallId *call_id,
-           NMSettingsConnection *        s_connection,
-           GError *                      error,
+           NMSettingsConnection         *s_connection,
+           GError                       *error,
            gpointer                      user_data)
 {
-    NMDeviceIwd *       self = user_data;
+    NMDeviceIwd        *self = user_data;
     NMDeviceIwdPrivate *priv;
-    NMDevice *          device;
+    NMDevice           *device;
 
     if (nm_utils_error_is_cancelled(error))
         return;
@@ -1944,7 +1944,7 @@ static NMWifiAP *
 find_ap_by_supplicant_path(NMDeviceIwd *self, const NMRefString *path)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMWifiAP *          tmp;
+    NMWifiAP           *tmp;
 
     c_list_for_each_entry (tmp, &priv->aps_lst_head, aps_lst)
         if (nm_wifi_ap_get_supplicant_path(tmp) == path)
@@ -1956,7 +1956,7 @@ find_ap_by_supplicant_path(NMDeviceIwd *self, const NMRefString *path)
 static void
 assumed_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMDeviceIwd *self)
 {
-    NMSettingsConnection *  sett_conn = nm_active_connection_get_settings_connection(active);
+    NMSettingsConnection   *sett_conn = nm_active_connection_get_settings_connection(active);
     NMActiveConnectionState state     = nm_active_connection_get_state(active);
 
     /* Delete the temporary connection created for an external IWD connection
@@ -1983,10 +1983,10 @@ assumed_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec,
 
 static void
 assumed_connection_state_changed_before_managed(NMActiveConnection *active,
-                                                GParamSpec *        pspec,
-                                                NMDeviceIwd *       self)
+                                                GParamSpec         *pspec,
+                                                NMDeviceIwd        *self)
 {
-    NMDeviceIwdPrivate *    priv  = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDeviceIwdPrivate     *priv  = NM_DEVICE_IWD_GET_PRIVATE(self);
     NMActiveConnectionState state = nm_active_connection_get_state(active);
     gboolean                disconnect;
 
@@ -2020,11 +2020,11 @@ assumed_connection_state_changed_before_managed(NMActiveConnection *active,
 static void
 assume_connection(NMDeviceIwd *self, NMWifiAP *ap)
 {
-    NMDeviceIwdPrivate *  priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingsConnection *sett_conn;
+    NMDeviceIwdPrivate            *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMSettingsConnection          *sett_conn;
     gs_unref_object NMAuthSubject *subject = NULL;
-    NMActiveConnection *           ac;
-    gs_free_error GError *error = NULL;
+    NMActiveConnection            *ac;
+    gs_free_error GError          *error = NULL;
 
     /* We can use the .update_connection / nm_device_emit_recheck_assume
      * API but we can also pass an assumed/external activation type
@@ -2104,7 +2104,7 @@ static void
 assumed_connection_progress_to_ip_config(NMDeviceIwd *self, gboolean was_postponed)
 {
     NMDeviceIwdPrivate *priv      = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device    = NM_DEVICE(self);
+    NMDevice           *device    = NM_DEVICE(self);
     NMDeviceState       dev_state = nm_device_get_state(device);
 
     wifi_secrets_cancel(self);
@@ -2127,17 +2127,17 @@ assumed_connection_progress_to_ip_config(NMDeviceIwd *self, gboolean was_postpon
      * that stage2 is done.
      */
     if (was_postponed)
-        nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self));
+        nm_device_activate_schedule_stage3_ip_config(NM_DEVICE(self), FALSE);
 }
 
 static void
 initial_check_assume(NMDeviceIwd *self)
 {
-    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    const char *        network_path_str;
+    NMDeviceIwdPrivate             *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    const char                     *network_path_str;
     nm_auto_ref_string NMRefString *network_path = NULL;
-    NMWifiAP *                      ap           = NULL;
-    gs_unref_variant GVariant *state_value =
+    NMWifiAP                       *ap           = NULL;
+    gs_unref_variant GVariant      *state_value =
         g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
     gs_unref_variant GVariant *cn_value =
         g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork");
@@ -2174,15 +2174,15 @@ initial_check_assume(NMDeviceIwd *self)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceIwd *       self          = NM_DEVICE_IWD(device);
-    NMDeviceIwdPrivate *priv          = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMWifiAP *          ap            = NULL;
+    NMDeviceIwd              *self    = NM_DEVICE_IWD(device);
+    NMDeviceIwdPrivate       *priv    = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMWifiAP                 *ap      = NULL;
     gs_unref_object NMWifiAP *ap_fake = NULL;
-    NMActRequest *            req;
-    NMConnection *            connection;
-    NMSettingWireless *       s_wireless;
-    const char *              mode;
-    const char *              ap_path;
+    NMActRequest             *req;
+    NMConnection             *connection;
+    NMSettingWireless        *s_wireless;
+    const char               *mode;
+    const char               *ap_path;
 
     req = nm_device_get_act_request(device);
     g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -2246,12 +2246,12 @@ add_new:
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMActRequest *      req;
-    NMConnection *      connection;
-    NMSettingWireless * s_wireless;
-    const char *        mode;
+    NMActRequest       *req;
+    NMConnection       *connection;
+    NMSettingWireless  *s_wireless;
+    const char         *mode;
 
     req        = nm_device_get_act_request(device);
     connection = nm_act_request_get_applied_connection(req);
@@ -2262,8 +2262,8 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
     if (NM_IN_STRSET(mode, NULL, NM_SETTING_WIRELESS_MODE_INFRA)) {
         gs_unref_object GDBusProxy *network_proxy = NULL;
-        NMWifiAP *                  ap            = priv->current_ap;
-        NMSettingWirelessSecurity * s_wireless_sec;
+        NMWifiAP                   *ap            = priv->current_ap;
+        NMSettingWirelessSecurity  *s_wireless_sec;
 
         if (!ap) {
             NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
@@ -2432,7 +2432,7 @@ get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *ou
 static gboolean
 periodic_scan_timeout_cb(gpointer user_data)
 {
-    NMDeviceIwd *       self = user_data;
+    NMDeviceIwd        *self = user_data;
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
 
     priv->periodic_scan_id = 0;
@@ -2503,15 +2503,15 @@ set_can_scan(NMDeviceIwd *self, gboolean can_scan)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMSettingWireless * s_wireless;
-    const char *        mode;
+    NMSettingWireless  *s_wireless;
+    const char         *mode;
 
     switch (new_state) {
     case NM_DEVICE_STATE_UNMANAGED:
@@ -2559,7 +2559,7 @@ get_enabled(NMDevice *device)
 static void
 set_enabled(NMDevice *device, gboolean enabled)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(device);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(device);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
     NMDeviceState       state;
 
@@ -2596,12 +2596,12 @@ set_enabled(NMDevice *device, gboolean enabled)
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -2624,9 +2624,9 @@ can_reapply_change(NMDevice *  device,
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(object);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(object);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    const char **       list;
+    const char        **list;
 
     switch (prop_id) {
     case PROP_MODE:
@@ -2673,18 +2673,18 @@ static void
 state_changed(NMDeviceIwd *self, const char *new_state)
 {
     NMDeviceIwdPrivate *priv           = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMDevice *          device         = NM_DEVICE(self);
+    NMDevice           *device         = NM_DEVICE(self);
     NMDeviceState       dev_state      = nm_device_get_state(device);
     gboolean            nm_connection  = priv->current_ap || priv->assumed_ac;
     gboolean            iwd_connection = FALSE;
-    NMWifiAP *          ap             = NULL;
+    NMWifiAP           *ap             = NULL;
     gboolean            can_connect    = priv->nm_autoconnect;
 
     _LOGI(LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state);
 
     if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) {
-        gs_unref_variant GVariant *value = NULL;
-        const char *               network_path_str;
+        gs_unref_variant GVariant      *value = NULL;
+        const char                     *network_path_str;
         nm_auto_ref_string NMRefString *network_path = NULL;
 
         value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork");
@@ -2855,12 +2855,12 @@ scanning_changed(NMDeviceIwd *self, gboolean new_scanning)
 
 static void
 station_properties_changed(GDBusProxy *proxy,
-                           GVariant *  changed_properties,
+                           GVariant   *changed_properties,
                            GStrv       invalidate_properties,
                            gpointer    user_data)
 {
     NMDeviceIwd *self = user_data;
-    const char * new_str;
+    const char  *new_str;
     gboolean     new_bool;
 
     if (g_variant_lookup(changed_properties, "State", "&s", &new_str))
@@ -2872,7 +2872,7 @@ station_properties_changed(GDBusProxy *proxy,
 
 static void
 ap_adhoc_properties_changed(GDBusProxy *proxy,
-                            GVariant *  changed_properties,
+                            GVariant   *changed_properties,
                             GStrv       invalidate_properties,
                             gpointer    user_data)
 {
@@ -2889,7 +2889,7 @@ static void
 powered_changed(NMDeviceIwd *self, gboolean new_powered)
 {
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    GDBusInterface *    interface;
+    GDBusInterface     *interface;
 
     nm_device_queue_recheck_available(NM_DEVICE(self),
                                       NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
@@ -3004,7 +3004,7 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered)
 
 static void
 device_properties_changed(GDBusProxy *proxy,
-                          GVariant *  changed_properties,
+                          GVariant   *changed_properties,
                           GStrv       invalidate_properties,
                           gpointer    user_data)
 {
@@ -3016,11 +3016,11 @@ device_properties_changed(GDBusProxy *proxy,
 }
 
 static void
-config_changed(NMConfig *          config,
-               NMConfigData *      config_data,
+config_changed(NMConfig           *config,
+               NMConfigData       *config_data,
                NMConfigChangeFlags changes,
-               NMConfigData *      old_data,
-               NMDeviceIwd *       self)
+               NMConfigData       *old_data,
+               NMDeviceIwd        *self)
 {
     NMDeviceIwdPrivate *priv       = NM_DEVICE_IWD_GET_PRIVATE(self);
     gboolean            old_iwd_ac = priv->iwd_autoconnect;
@@ -3048,12 +3048,12 @@ config_changed(NMConfig *          config,
 void
 nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
 {
-    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    GDBusInterface *    interface;
-    gs_unref_variant GVariant *value          = NULL;
+    NMDeviceIwdPrivate         *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    GDBusInterface             *interface;
+    gs_unref_variant GVariant  *value         = NULL;
     gs_unref_object GDBusProxy *adapter_proxy = NULL;
-    GVariantIter *              iter;
-    const char *                mode;
+    GVariantIter               *iter;
+    const char                 *mode;
     gboolean                    powered;
     _NMDeviceWifiCapabilities   capabilities;
 
@@ -3159,18 +3159,18 @@ error:
 gboolean
 nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
 {
-    NMDevice *                   device = NM_DEVICE(self);
-    NMDeviceIwdPrivate *         priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDevice                    *device = NM_DEVICE(self);
+    NMDeviceIwdPrivate          *priv   = NM_DEVICE_IWD_GET_PRIVATE(self);
     NMDeviceState                state  = nm_device_get_state(device);
-    const char *                 setting_name;
-    const char *                 setting_key;
+    const char                  *setting_name;
+    const char                  *setting_key;
     gboolean                     replied;
-    NMWifiAP *                   ap;
+    NMWifiAP                    *ap;
     gboolean                     allow_existing = FALSE;
     NMSecretAgentGetSecretsFlags get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
-    NMIwdNetworkSecurity security;
-    NMConnection *       connection;
+    NMIwdNetworkSecurity            security;
+    NMConnection                   *connection;
     nm_auto_ref_string NMRefString *network_path = NULL;
 
     if (!invocation) {
@@ -3324,9 +3324,9 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation)
 void
 nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool add)
 {
-    NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
-    NMWifiAP *          ap   = NULL;
-    bool                recheck;
+    NMDeviceIwdPrivate             *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMWifiAP                       *ap   = NULL;
+    bool                            recheck;
     nm_auto_ref_string NMRefString *bss_path = NULL;
 
     bss_path = nm_ref_string_new(g_dbus_proxy_get_object_path(network));
@@ -3371,7 +3371,7 @@ nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool ad
 static void
 autoconnect_changed(NMDevice *device, GParamSpec *pspec, NMDeviceIwd *self)
 {
-    NMDeviceIwdPrivate *priv         = NM_DEVICE_IWD_GET_PRIVATE(self);
+    NMDeviceIwdPrivate        *priv  = NM_DEVICE_IWD_GET_PRIVATE(self);
     gs_unref_variant GVariant *value = NULL;
 
     /* Note IWD normally remains in "disconnected" during a secret request
@@ -3434,7 +3434,7 @@ nm_device_iwd_new(const char *iface)
 static void
 dispose(GObject *object)
 {
-    NMDeviceIwd *       self = NM_DEVICE_IWD(object);
+    NMDeviceIwd        *self = NM_DEVICE_IWD(object);
     NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self);
 
     nm_clear_g_cancellable(&priv->cancellable);
@@ -3450,9 +3450,9 @@ dispose(GObject *object)
 static void
 nm_device_iwd_class_init(NMDeviceIwdClass *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->get_property = get_property;
     object_class->dispose      = dispose;
diff --git a/src/core/devices/wifi/nm-device-iwd.h b/src/core/devices/wifi/nm-device-iwd.h
index ce94c9ea..bb136bf6 100644
--- a/src/core/devices/wifi/nm-device-iwd.h
+++ b/src/core/devices/wifi/nm-device-iwd.h
@@ -40,8 +40,8 @@ gboolean nm_device_iwd_agent_query(NMDeviceIwd *device, GDBusMethodInvocation *i
 
 const CList *_nm_device_iwd_get_aps(NMDeviceIwd *self);
 
-void _nm_device_iwd_request_scan(NMDeviceIwd *          self,
-                                 GVariant *             options,
+void _nm_device_iwd_request_scan(NMDeviceIwd           *self,
+                                 GVariant              *options,
                                  GDBusMethodInvocation *invocation);
 
 void nm_device_iwd_network_add_remove(NMDeviceIwd *device, GDBusProxy *network, bool add);
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index d829f9ae..fd851ebc 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -39,7 +39,7 @@
 NM_GOBJECT_PROPERTIES_DEFINE(NMDeviceOlpcMesh, PROP_COMPANION, PROP_ACTIVE_CHANNEL, );
 
 typedef struct {
-    NMDevice * companion;
+    NMDevice  *companion;
     NMManager *manager;
     bool       stage1_waiting : 1;
 } NMDeviceOlpcMeshPrivate;
@@ -63,7 +63,7 @@ G_DEFINE_TYPE(NMDeviceOlpcMesh, nm_device_olpc_mesh, NM_TYPE_DEVICE)
 static gboolean
 get_autoconnect_allowed(NMDevice *device)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(device);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(device);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     /* We can't even connect if we don't have a companion yet. */
@@ -81,11 +81,11 @@ get_autoconnect_allowed(NMDevice *device)
 #define DEFAULT_SSID "olpc-mesh"
 
 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)
 {
     NMSettingOlpcMesh *s_mesh;
 
@@ -133,7 +133,7 @@ get_dhcp_anycast_address(NMDevice *device)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(device);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(device);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     /* disconnect companion device, if it is connected */
@@ -183,9 +183,9 @@ _mesh_set_channel(NMDeviceOlpcMesh *self, guint32 channel)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceOlpcMesh * self = NM_DEVICE_OLPC_MESH(device);
+    NMDeviceOlpcMesh  *self = NM_DEVICE_OLPC_MESH(device);
     NMSettingOlpcMesh *s_mesh;
-    GBytes *           ssid;
+    GBytes            *ssid;
     gboolean           success;
 
     s_mesh = nm_device_get_applied_setting(device, NM_TYPE_SETTING_OLPC_MESH);
@@ -243,7 +243,7 @@ companion_cleanup(NMDeviceOlpcMesh *self)
 static void
 companion_notify_cb(NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_data)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(user_data);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(user_data);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     nm_assert(NM_IS_DEVICE_WIFI(companion));
@@ -260,7 +260,7 @@ companion_notify_cb(NMDeviceWifi *companion, GParamSpec *pspec, gpointer user_da
 
 /* disconnect from mesh if someone starts using the companion */
 static void
-companion_state_changed_cb(NMDeviceWifi *      companion,
+companion_state_changed_cb(NMDeviceWifi       *companion,
                            NMDeviceState       state,
                            NMDeviceState       old_state,
                            NMDeviceStateReason reason,
@@ -299,7 +299,7 @@ static gboolean
 check_companion(NMDeviceOlpcMesh *self, NMDevice *other)
 {
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
-    const char *             my_addr, *their_addr;
+    const char              *my_addr, *their_addr;
 
     if (!NM_IS_DEVICE_WIFI(other))
         return FALSE;
@@ -337,7 +337,7 @@ check_companion(NMDeviceOlpcMesh *self, NMDevice *other)
 static void
 device_added_cb(NMManager *manager, NMDevice *other, gpointer user_data)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(user_data);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(user_data);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     if (!priv->companion && check_companion(self, other)) {
@@ -363,8 +363,8 @@ static void
 find_companion(NMDeviceOlpcMesh *self)
 {
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
-    const CList *            tmp_lst;
-    NMDevice *               candidate;
+    const CList             *tmp_lst;
+    NMDevice                *candidate;
 
     if (priv->companion)
         return;
@@ -386,12 +386,12 @@ find_companion(NMDeviceOlpcMesh *self)
 }
 
 static void
-state_changed(NMDevice *          device,
+state_changed(NMDevice           *device,
               NMDeviceState       new_state,
               NMDeviceState       old_state,
               NMDeviceStateReason reason)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(device);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(device);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     if (new_state == NM_DEVICE_STATE_UNAVAILABLE)
@@ -422,8 +422,8 @@ get_dhcp_timeout_for_device(NMDevice *device, int addr_family)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceOlpcMesh *       self   = NM_DEVICE_OLPC_MESH(object);
-    NMDevice *               device = NM_DEVICE(self);
+    NMDeviceOlpcMesh        *self   = NM_DEVICE_OLPC_MESH(object);
+    NMDevice                *device = NM_DEVICE(self);
     NMDeviceOlpcMeshPrivate *priv   = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -450,7 +450,7 @@ nm_device_olpc_mesh_init(NMDeviceOlpcMesh *self)
 static void
 constructed(GObject *object)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(object);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(object);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     G_OBJECT_CLASS(nm_device_olpc_mesh_parent_class)->constructed(object);
@@ -479,7 +479,7 @@ nm_device_olpc_mesh_new(const char *iface)
 static void
 dispose(GObject *object)
 {
-    NMDeviceOlpcMesh *       self = NM_DEVICE_OLPC_MESH(object);
+    NMDeviceOlpcMesh        *self = NM_DEVICE_OLPC_MESH(object);
     NMDeviceOlpcMeshPrivate *priv = NM_DEVICE_OLPC_MESH_GET_PRIVATE(self);
 
     companion_cleanup(self);
@@ -510,9 +510,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_olpc_mesh = {
 static void
 nm_device_olpc_mesh_class_init(NMDeviceOlpcMeshClass *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->constructed  = constructed;
     object_class->get_property = get_property;
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index 67202a67..dfbf8978 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -14,18 +14,18 @@
 
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
-#include "nm-act-request.h"
 #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-glib-aux/nm-ref-string.h"
-#include "nm-ip4-config.h"
+#include "libnm-platform/nm-platform.h"
+#include "libnm-platform/nmp-object.h"
+#include "nm-act-request.h"
+#include "nm-l3-config-data.h"
 #include "nm-manager.h"
 #include "nm-manager.h"
 #include "nm-setting-wifi-p2p.h"
 #include "nm-utils.h"
 #include "nm-wifi-p2p-peer.h"
-#include "libnm-platform/nm-platform.h"
-#include "libnm-platform/nmp-object.h"
 #include "settings/nm-settings.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWifiP2P
@@ -82,10 +82,10 @@ static void supplicant_interfaces_release(NMDeviceWifiP2P *self, gboolean set_is
 /*****************************************************************************/
 
 static void
-_peer_dump(NMDeviceWifiP2P *    self,
+_peer_dump(NMDeviceWifiP2P     *self,
            NMLogLevel           log_level,
            const NMWifiP2PPeer *peer,
-           const char *         prefix,
+           const char          *prefix,
            gint32               now_s)
 {
     char buf[1024];
@@ -100,7 +100,7 @@ _peer_dump(NMDeviceWifiP2P *    self,
 static gboolean
 peer_list_dump(gpointer user_data)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(user_data);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(user_data);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     priv->peer_dump_id = 0;
@@ -153,10 +153,10 @@ static gboolean
 check_connection_peer_joined(NMDeviceWifiP2P *device)
 {
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(device);
-    NMConnection *          conn = nm_device_get_applied_connection(NM_DEVICE(device));
-    NMWifiP2PPeer *         peer;
-    const char *            group;
-    const char *const *     groups;
+    NMConnection           *conn = nm_device_get_applied_connection(NM_DEVICE(device));
+    NMWifiP2PPeer          *peer;
+    const char             *group;
+    const char *const      *groups;
 
     if (!conn || !priv->group_iface)
         return FALSE;
@@ -168,7 +168,7 @@ check_connection_peer_joined(NMDeviceWifiP2P *device)
         return FALSE;
 
     /* NOTE: We currently only support connections to a specific peer */
-    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, conn);
+    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, conn, FALSE);
     if (!peer)
         return FALSE;
 
@@ -182,8 +182,8 @@ check_connection_peer_joined(NMDeviceWifiP2P *device)
 static gboolean
 disconnect_on_connection_peer_missing_cb(gpointer user_data)
 {
-    NMDevice *              device = NM_DEVICE(user_data);
-    NMDeviceWifiP2P *       self   = NM_DEVICE_WIFI_P2P(device);
+    NMDevice               *device = NM_DEVICE(user_data);
+    NMDeviceWifiP2P        *self   = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv   = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     _LOGW(LOGD_WIFI, "Peer requested in connection is missing for too long, failing connection.");
@@ -222,8 +222,8 @@ update_disconnect_on_connection_peer_missing(NMDeviceWifiP2P *self)
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceWifiP2P *          self = NM_DEVICE_WIFI_P2P(device);
-    NMDeviceWifiP2PPrivate *   priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
+    NMDeviceWifiP2P           *self = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2PPrivate    *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
     NMSupplicantInterfaceState supplicant_state;
 
     if (!priv->mgmt_iface)
@@ -251,17 +251,17 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError *
 }
 
 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)
 {
-    NMDeviceWifiP2P * self         = NM_DEVICE_WIFI_P2P(device);
-    gs_free char *    setting_name = NULL;
+    NMDeviceWifiP2P  *self         = NM_DEVICE_WIFI_P2P(device);
+    gs_free char     *setting_name = NULL;
     NMSettingWifiP2P *s_wifi_p2p;
-    NMWifiP2PPeer *   peer;
-    const char *      setting_peer;
+    NMWifiP2PPeer    *peer;
+    const char       *setting_peer;
 
     s_wifi_p2p =
         NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
@@ -329,8 +329,8 @@ complete_connection(NMDevice *           device,
 static gboolean
 supplicant_find_timeout_cb(gpointer user_data)
 {
-    NMDevice *              device = NM_DEVICE(user_data);
-    NMDeviceWifiP2P *       self   = NM_DEVICE_WIFI_P2P(user_data);
+    NMDevice               *device = NM_DEVICE(user_data);
+    NMDeviceWifiP2P        *self   = NM_DEVICE_WIFI_P2P(user_data);
     NMDeviceWifiP2PPrivate *priv   = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     priv->find_peer_timeout_id = 0;
@@ -351,11 +351,11 @@ supplicant_find_timeout_cb(gpointer user_data)
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    NMConnection *          connection;
-    NMSettingWifiP2P *      s_wifi_p2p;
-    NMWifiP2PPeer *         peer;
+    NMConnection           *connection;
+    NMSettingWifiP2P       *s_wifi_p2p;
+    NMWifiP2PPeer          *peer;
 
     if (!priv->mgmt_iface) {
         NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
@@ -369,7 +369,7 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         NM_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P));
     g_return_val_if_fail(s_wifi_p2p, NM_ACT_STAGE_RETURN_FAILURE);
 
-    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection);
+    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE);
     if (!peer) {
         /* Set up a timeout on the find attempt and run a find for the same period of time */
         if (priv->find_peer_timeout_id == 0) {
@@ -393,8 +393,8 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static gboolean
 supplicant_connection_timeout_cb(gpointer user_data)
 {
-    NMDevice *              device = NM_DEVICE(user_data);
-    NMDeviceWifiP2P *       self   = NM_DEVICE_WIFI_P2P(user_data);
+    NMDevice               *device = NM_DEVICE(user_data);
+    NMDeviceWifiP2P        *self   = NM_DEVICE_WIFI_P2P(user_data);
     NMDeviceWifiP2PPrivate *priv   = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     priv->sup_timeout_id = 0;
@@ -415,12 +415,12 @@ supplicant_connection_timeout_cb(gpointer user_data)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    NMConnection *          connection;
-    NMSettingWifiP2P *      s_wifi_p2p;
-    NMWifiP2PPeer *         peer;
-    GBytes *                wfd_ies;
+    NMConnection           *connection;
+    NMSettingWifiP2P       *s_wifi_p2p;
+    NMWifiP2PPeer          *peer;
+    GBytes                 *wfd_ies;
 
     if (nm_clear_g_source(&priv->find_peer_timeout_id))
         nm_assert_not_reached();
@@ -436,7 +436,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
         NM_IS_SETTING_WIFI_P2P(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIFI_P2P)));
 
     /* The prepare stage ensures that the peer has been found */
-    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection);
+    peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE);
     if (!peer) {
         NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_PEER_NOT_FOUND);
         return NM_ACT_STAGE_RETURN_FAILURE;
@@ -469,7 +469,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 
 static void
 emit_signal_p2p_peer_add_remove(NMDeviceWifiP2P *device,
-                                NMWifiP2PPeer *  peer,
+                                NMWifiP2PPeer   *peer,
                                 gboolean         is_added /* or else is_removed */)
 {
     nm_dbus_object_emit_signal(NM_DBUS_OBJECT(device),
@@ -483,10 +483,10 @@ emit_signal_p2p_peer_add_remove(NMDeviceWifiP2P *device,
 static void
 peer_add_remove(NMDeviceWifiP2P *self,
                 gboolean         is_adding, /* or else removing */
-                NMWifiP2PPeer *  peer,
+                NMWifiP2PPeer   *peer,
                 gboolean         recheck_available_connections)
 {
-    NMDevice *              device = NM_DEVICE(self);
+    NMDevice               *device = NM_DEVICE(self);
     NMDeviceWifiP2PPrivate *priv   = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     if (is_adding) {
@@ -521,7 +521,8 @@ peer_add_remove(NMDeviceWifiP2P *self,
             connection = nm_device_get_applied_connection(device);
             nm_assert(NM_IS_CONNECTION(connection));
 
-            peer = nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection);
+            peer =
+                nm_wifi_p2p_peers_find_first_compatible(&priv->peers_lst_head, connection, FALSE);
             if (peer) {
                 /* A peer for the connection was found, cancel the timeout and go to configure state. */
                 nm_clear_g_source(&priv->find_peer_timeout_id);
@@ -539,7 +540,7 @@ static void
 remove_all_peers(NMDeviceWifiP2P *self)
 {
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    NMWifiP2PPeer *         peer;
+    NMWifiP2PPeer          *peer;
 
     if (c_list_is_empty(&priv->peers_lst_head))
         return;
@@ -552,46 +553,44 @@ remove_all_peers(NMDeviceWifiP2P *self)
 
 /*****************************************************************************/
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(device);
+    NMDeviceWifiP2PPrivate *priv    = NM_DEVICE_WIFI_P2P_GET_PRIVATE(device);
+    const int               IS_IPv4 = NM_IS_IPv4(addr_family);
     gboolean                indicate_addressing_running;
-    NMConnection *          connection;
-    const char *            method;
+    NMConnection           *connection;
+    const char             *method;
 
     connection = nm_device_get_applied_connection(device);
 
     method = nm_utils_get_ip_config_method(connection, addr_family);
 
     /* We may have an address assigned by the group owner */
-    if (NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) && priv->group_iface
+    if (IS_IPv4 && NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) && priv->group_iface
         && !nm_supplicant_interface_get_p2p_group_owner(priv->group_iface)) {
         in_addr_t addr;
         guint8    plen;
 
         if (nm_supplicant_interface_get_p2p_assigned_addr(priv->group_iface, &addr, &plen)) {
-            NMPlatformIP4Address address = {
-                .addr_source = NM_IP_CONFIG_SOURCE_DHCP,
+            nm_auto_unref_l3cd_init NML3ConfigData *l3cd    = NULL;
+            NMPlatformIP4Address                    address = {
+                                   .addr_source = NM_IP_CONFIG_SOURCE_DHCP,
             };
-            gs_unref_object NMIP4Config *ip4_config = NULL;
 
             nm_platform_ip4_address_set_addr(&address, addr, plen);
 
-            ip4_config = nm_device_ip4_config_new(device);
-            nm_ip4_config_add_address(ip4_config, &address);
+            l3cd = nm_device_create_l3_config_data(device, NM_IP_CONFIG_SOURCE_DHCP);
+            nm_l3_config_data_add_address_4(l3cd, &address);
 
-            nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG(ip4_config));
+            nm_device_devip_set_state(device, AF_INET, NM_DEVICE_IP_STATE_READY, l3cd);
 
             /* This just disables the addressing indicator. */
             method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
         }
     }
 
-    if (addr_family == AF_INET)
+    if (IS_IPv4)
         indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     else {
         indicate_addressing_running = NM_IN_STRSET(method,
@@ -603,15 +602,12 @@ act_stage3_ip_config_start(NMDevice *           device,
         nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
                                                      nm_device_get_ip_ifindex(device),
                                                      TRUE);
-
-    return NM_DEVICE_CLASS(nm_device_wifi_p2p_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
 }
 
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceWifiP2P *       self    = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2P        *self    = NM_DEVICE_WIFI_P2P(device);
     int                     ifindex = nm_device_get_ip_ifindex(device);
     NMDeviceWifiP2PPrivate *priv    = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
@@ -642,7 +638,7 @@ get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *ou
 static const char *
 get_auto_ip_config_method(NMDevice *device, int addr_family)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     if (addr_family == AF_INET && priv->group_iface
@@ -675,8 +671,8 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
                           int                    disconnect_reason,
                           gpointer               user_data)
 {
-    NMDeviceWifiP2P *          self      = NM_DEVICE_WIFI_P2P(user_data);
-    NMDevice *                 device    = NM_DEVICE(self);
+    NMDeviceWifiP2P           *self      = NM_DEVICE_WIFI_P2P(user_data);
+    NMDevice                  *device    = NM_DEVICE(self);
     NMSupplicantInterfaceState new_state = new_state_i;
     NMSupplicantInterfaceState old_state = old_state_i;
 
@@ -704,12 +700,12 @@ supplicant_iface_state_cb(NMSupplicantInterface *iface,
 
 static void
 supplicant_iface_peer_changed_cb(NMSupplicantInterface *iface,
-                                 NMSupplicantPeerInfo * peer_info,
+                                 NMSupplicantPeerInfo  *peer_info,
                                  gboolean               is_present,
-                                 NMDeviceWifiP2P *      self)
+                                 NMDeviceWifiP2P       *self)
 {
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    NMWifiP2PPeer *         found_peer;
+    NMWifiP2PPeer          *found_peer;
 
     found_peer =
         nm_wifi_p2p_peers_find_by_supplicant_path(&priv->peers_lst_head, peer_info->peer_path->str);
@@ -757,7 +753,7 @@ check_group_iface_ready(NMDeviceWifiP2P *self)
     nm_clear_g_source(&priv->sup_timeout_id);
     update_disconnect_on_connection_peer_missing(self);
 
-    nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self));
+    nm_device_activate_schedule_stage3_ip_config(NM_DEVICE(self), FALSE);
 }
 
 static void
@@ -786,7 +782,7 @@ supplicant_group_iface_state_cb(NMSupplicantInterface *iface,
                                 int                    disconnect_reason,
                                 gpointer               user_data)
 {
-    NMDeviceWifiP2P *          self      = NM_DEVICE_WIFI_P2P(user_data);
+    NMDeviceWifiP2P           *self      = NM_DEVICE_WIFI_P2P(user_data);
     NMSupplicantInterfaceState new_state = new_state_i;
     NMSupplicantInterfaceState old_state = old_state_i;
 
@@ -812,8 +808,8 @@ supplicant_group_iface_state_cb(NMSupplicantInterface *iface,
 
 static void
 supplicant_group_iface_group_finished_cb(NMSupplicantInterface *iface,
-                                         const char *           iface_path,
-                                         void *                 user_data)
+                                         const char            *iface_path,
+                                         void                  *user_data)
 {
     NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P(user_data);
 
@@ -826,8 +822,8 @@ supplicant_group_iface_group_finished_cb(NMSupplicantInterface *iface,
 
 static void
 supplicant_iface_group_joined_updated_cb(NMSupplicantInterface *iface,
-                                         GParamSpec *           pspec,
-                                         void *                 user_data)
+                                         GParamSpec            *pspec,
+                                         void                  *user_data)
 {
     NMDeviceWifiP2P *self = NM_DEVICE_WIFI_P2P(user_data);
 
@@ -837,9 +833,9 @@ supplicant_iface_group_joined_updated_cb(NMSupplicantInterface *iface,
 static void
 supplicant_iface_group_started_cb(NMSupplicantInterface *iface,
                                   NMSupplicantInterface *group_iface,
-                                  NMDeviceWifiP2P *      self)
+                                  NMDeviceWifiP2P       *self)
 {
-    NMDeviceWifiP2PPrivate *   priv;
+    NMDeviceWifiP2PPrivate    *priv;
     NMSupplicantInterfaceState state;
 
     g_return_if_fail(self);
@@ -923,12 +919,12 @@ supplicant_interfaces_release(NMDeviceWifiP2P *self, gboolean set_is_waiting)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(device);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     update_disconnect_on_connection_peer_missing(self);
@@ -984,19 +980,19 @@ device_state_changed(NMDevice *          device,
 }
 
 static void
-impl_device_wifi_p2p_start_find(NMDBusObject *                     obj,
+impl_device_wifi_p2p_start_find(NMDBusObject                      *obj,
                                 const NMDBusInterfaceInfoExtended *interface_info,
-                                const NMDBusMethodInfoExtended *   method_info,
-                                GDBusConnection *                  connection,
-                                const char *                       sender,
-                                GDBusMethodInvocation *            invocation,
-                                GVariant *                         parameters)
+                                const NMDBusMethodInfoExtended    *method_info,
+                                GDBusConnection                   *connection,
+                                const char                        *sender,
+                                GDBusMethodInvocation             *invocation,
+                                GVariant                          *parameters)
 {
-    NMDeviceWifiP2P *       self       = NM_DEVICE_WIFI_P2P(obj);
-    NMDeviceWifiP2PPrivate *priv       = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
+    NMDeviceWifiP2P           *self    = NM_DEVICE_WIFI_P2P(obj);
+    NMDeviceWifiP2PPrivate    *priv    = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
     gs_unref_variant GVariant *options = NULL;
-    const char *               opts_key;
-    GVariant *                 opts_val;
+    const char                *opts_key;
+    GVariant                  *opts_val;
     GVariantIter               iter;
     gint32                     timeout = 30;
 
@@ -1052,15 +1048,15 @@ impl_device_wifi_p2p_start_find(NMDBusObject *                     obj,
 }
 
 static void
-impl_device_wifi_p2p_stop_find(NMDBusObject *                     obj,
+impl_device_wifi_p2p_stop_find(NMDBusObject                      *obj,
                                const NMDBusInterfaceInfoExtended *interface_info,
-                               const NMDBusMethodInfoExtended *   method_info,
-                               GDBusConnection *                  connection,
-                               const char *                       sender,
-                               GDBusMethodInvocation *            invocation,
-                               GVariant *                         parameters)
+                               const NMDBusMethodInfoExtended    *method_info,
+                               GDBusConnection                   *connection,
+                               const char                        *sender,
+                               GDBusMethodInvocation             *invocation,
+                               GVariant                          *parameters)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(obj);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(obj);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
     if (!priv->mgmt_iface) {
@@ -1184,9 +1180,9 @@ static const NMDBusInterfaceInfoExtended interface_info_device_wifi_p2p = {
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(object);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(object);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
-    const char **           list;
+    const char            **list;
 
     switch (prop_id) {
     case PROP_PEERS:
@@ -1241,7 +1237,7 @@ nm_device_wifi_p2p_new(const char *iface)
 static void
 dispose(GObject *object)
 {
-    NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(object);
+    NMDeviceWifiP2P        *self = NM_DEVICE_WIFI_P2P(object);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(object);
 
     g_clear_object(&priv->sup_mgr);
@@ -1254,7 +1250,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDeviceWifiP2P *       peer = NM_DEVICE_WIFI_P2P(object);
+    NMDeviceWifiP2P        *peer = NM_DEVICE_WIFI_P2P(object);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(peer);
 
     nm_assert(c_list_is_empty(&priv->peers_lst_head));
@@ -1265,9 +1261,9 @@ finalize(GObject *object)
 static void
 nm_device_wifi_p2p_class_init(NMDeviceWifiP2PClass *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->constructed  = constructed;
     object_class->get_property = get_property;
@@ -1286,11 +1282,11 @@ nm_device_wifi_p2p_class_init(NMDeviceWifiP2PClass *klass)
     device_class->check_connection_compatible = check_connection_compatible;
     device_class->complete_connection         = complete_connection;
 
-    device_class->act_stage1_prepare         = act_stage1_prepare;
-    device_class->act_stage2_config          = act_stage2_config;
-    device_class->get_configured_mtu         = get_configured_mtu;
-    device_class->get_auto_ip_config_method  = get_auto_ip_config_method;
-    device_class->act_stage3_ip_config_start = act_stage3_ip_config_start;
+    device_class->act_stage1_prepare        = act_stage1_prepare;
+    device_class->act_stage2_config         = act_stage2_config;
+    device_class->get_configured_mtu        = get_configured_mtu;
+    device_class->get_auto_ip_config_method = get_auto_ip_config_method;
+    device_class->act_stage3_ip_config      = act_stage3_ip_config;
 
     device_class->deactivate        = deactivate;
     device_class->unmanaged_on_quit = unmanaged_on_quit;
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index 82896c77..010cee08 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -32,7 +32,6 @@
 #include "nm-setting-wireless-security.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-ip4-config.h"
-#include "nm-ip4-config.h"
 #include "nm-setting-ip6-config.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-auth-utils.h"
@@ -95,9 +94,9 @@ typedef struct {
 
     NMActRequestGetSecretsCallId *wifi_secrets_id;
 
-    NMSupplicantManager *        sup_mgr;
+    NMSupplicantManager         *sup_mgr;
     NMSupplMgrCreateIfaceHandle *sup_create_handle;
-    NMSupplicantInterface *      sup_iface;
+    NMSupplicantInterface       *sup_iface;
 
     gint64 scan_last_complete_msec;
     gint64 scan_periodic_next_msec;
@@ -136,6 +135,8 @@ typedef struct {
     bool ssid_found : 1;
     bool hidden_probe_scan_warn : 1;
 
+    bool addressing_running_indicated : 1;
+
 } NMDeviceWifiPrivate;
 
 struct _NMDeviceWifi {
@@ -160,7 +161,7 @@ static void supplicant_iface_state_down(NMDeviceWifi *self);
 
 static void cleanup_association_attempt(NMDeviceWifi *self, gboolean disconnect);
 
-static void supplicant_iface_state(NMDeviceWifi *             self,
+static void supplicant_iface_state(NMDeviceWifi              *self,
                                    NMSupplicantInterfaceState new_state,
                                    NMSupplicantInterfaceState old_state,
                                    int                        disconnect_reason,
@@ -173,27 +174,27 @@ static void supplicant_iface_state_cb(NMSupplicantInterface *iface,
                                       gpointer               user_data);
 
 static void supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
-                                            NMSupplicantBssInfo *  bss_info,
+                                            NMSupplicantBssInfo   *bss_info,
                                             gboolean               is_present,
-                                            NMDeviceWifi *         self);
+                                            NMDeviceWifi          *self);
 
 static void supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
-                                                GVariant *             credentials,
-                                                NMDeviceWifi *         self);
+                                                GVariant              *credentials,
+                                                NMDeviceWifi          *self);
 
 static void supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
-                                                GParamSpec *           pspec,
-                                                NMDeviceWifi *         self);
+                                                GParamSpec            *pspec,
+                                                NMDeviceWifi          *self);
 
 static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
-                                                  GParamSpec *           pspec,
-                                                  NMDeviceWifi *         self);
+                                                  GParamSpec            *pspec,
+                                                  NMDeviceWifi          *self);
 
 static void periodic_update(NMDeviceWifi *self);
 
 static void ap_add_remove(NMDeviceWifi *self,
                           gboolean      is_adding,
-                          NMWifiAP *    ap,
+                          NMWifiAP     *ap,
                           gboolean      recheck_available_connections);
 
 static void _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset);
@@ -290,7 +291,7 @@ static GPtrArray *
 _scan_request_ssids_fetch(NMDeviceWifiPrivate *priv, gint64 now_msec)
 {
     ScanRequestSsidData *srs_data;
-    GPtrArray *          ssids;
+    GPtrArray           *ssids;
     guint                len;
 
     _scan_request_ssids_remove_all(priv, now_msec, G_MAXUINT);
@@ -331,7 +332,7 @@ _scan_request_ssids_track(NMDeviceWifiPrivate *priv, const GPtrArray *ssids)
     c_list_splice(&old_lst_head, &priv->scan_request_ssids_lst_head);
 
     for (i = 0; i < ssids->len; i++) {
-        GBytes *             ssid = ssids->pdata[i];
+        GBytes              *ssid = ssids->pdata[i];
         ScanRequestSsidData *d;
 
         G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(ScanRequestSsidData, ssid) == 0);
@@ -364,7 +365,7 @@ nm_device_wifi_scanning_prohibited_track(NMDeviceWifi *self,
                                          gboolean      temporarily_prohibited)
 {
     NMDeviceWifiPrivate *priv;
-    NMCListElem *        elem;
+    NMCListElem         *elem;
 
     g_return_if_fail(NM_IS_DEVICE_WIFI(self));
     nm_assert(tag);
@@ -394,10 +395,26 @@ nm_device_wifi_scanning_prohibited_track(NMDeviceWifi *self,
 /*****************************************************************************/
 
 static void
-_ap_dump(NMDeviceWifi *  self,
+_indicate_addressing_running_reset(NMDeviceWifi *self)
+{
+    NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+
+    if (!priv->addressing_running_indicated)
+        return;
+
+    priv->addressing_running_indicated = FALSE;
+    nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(NM_DEVICE(self)),
+                                                 nm_device_get_ifindex(NM_DEVICE(self)),
+                                                 FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+_ap_dump(NMDeviceWifi   *self,
          NMLogLevel      log_level,
          const NMWifiAP *ap,
-         const char *    prefix,
+         const char     *prefix,
          gint64          now_msec)
 {
     char buf[1024];
@@ -530,8 +547,8 @@ _scan_notify_allowed(NMDeviceWifi *self, NMTernary do_kickoff)
 
 static void
 supplicant_iface_notify_scanning_cb(NMSupplicantInterface *iface,
-                                    GParamSpec *           pspec,
-                                    NMDeviceWifi *         self)
+                                    GParamSpec            *pspec,
+                                    NMDeviceWifi          *self)
 {
     _scan_notify_is_scanning(self);
 }
@@ -549,13 +566,13 @@ unmanaged_on_quit(NMDevice *self)
 }
 
 static void
-supplicant_interface_acquire_cb(NMSupplicantManager *        supplicant_manager,
+supplicant_interface_acquire_cb(NMSupplicantManager         *supplicant_manager,
                                 NMSupplMgrCreateIfaceHandle *handle,
-                                NMSupplicantInterface *      iface,
-                                GError *                     error,
+                                NMSupplicantInterface       *iface,
+                                GError                      *error,
                                 gpointer                     user_data)
 {
-    NMDeviceWifi *       self = user_data;
+    NMDeviceWifi        *self = user_data;
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     if (nm_utils_error_is_cancelled(error))
@@ -694,7 +711,7 @@ static void
 set_current_ap(NMDeviceWifi *self, NMWifiAP *new_ap, gboolean recheck_available_connections)
 {
     NMDeviceWifiPrivate *priv;
-    NMWifiAP *           old_ap;
+    NMWifiAP            *old_ap;
 
     g_return_if_fail(NM_IS_DEVICE_WIFI(self));
 
@@ -790,7 +807,7 @@ periodic_update_cb(gpointer user_data)
 static void
 ap_add_remove(NMDeviceWifi *self,
               gboolean      is_adding, /* or else removing */
-              NMWifiAP *    ap,
+              NMWifiAP     *ap,
               gboolean      recheck_available_connections)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
@@ -831,7 +848,7 @@ static void
 remove_all_aps(NMDeviceWifi *self)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMWifiAP *           ap;
+    NMWifiAP            *ap;
 
     if (c_list_is_empty(&priv->aps_lst_head))
         return;
@@ -847,7 +864,7 @@ remove_all_aps(NMDeviceWifi *self)
 static gboolean
 wake_on_wlan_restore(NMDeviceWifi *self)
 {
-    NMDeviceWifiPrivate *        priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifiPrivate         *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     _NMSettingWirelessWakeOnWLan w;
 
     w = priv->wowlan_restore;
@@ -879,7 +896,7 @@ disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable)
     gs_unref_object NMDeviceWifi *self = NULL;
     NMDeviceDeactivateCallback    callback;
     gpointer                      callback_user_data;
-    gs_free_error GError *cancelled_error = NULL;
+    gs_free_error GError         *cancelled_error = NULL;
 
     nm_utils_user_data_unpack(user_data, &self, &callback, &callback_user_data);
 
@@ -888,12 +905,12 @@ disconnect_cb_on_idle(gpointer user_data, GCancellable *cancellable)
 }
 
 static void
-deactivate_async(NMDevice *                 device,
-                 GCancellable *             cancellable,
+deactivate_async(NMDevice                  *device,
+                 GCancellable              *cancellable,
                  NMDeviceDeactivateCallback callback,
                  gpointer                   callback_user_data)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gpointer             user_data;
 
@@ -917,7 +934,7 @@ deactivate_async(NMDevice *                 device,
 static void
 deactivate(NMDevice *device)
 {
-    NMDeviceWifi *       self    = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self    = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv    = NM_DEVICE_WIFI_GET_PRIVATE(self);
     int                  ifindex = nm_device_get_ifindex(device);
 
@@ -932,8 +949,7 @@ deactivate(NMDevice *device)
     if (!wake_on_wlan_restore(self))
         _LOGW(LOGD_DEVICE | LOGD_WIFI, "Cannot unconfigure WoWLAN.");
 
-    /* Clear any critical protocol notification in the Wi-Fi stack */
-    nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE);
+    _indicate_addressing_running_reset(self);
 
     /* Ensure we're in infrastructure mode after deactivation; some devices
      * (usually older ones) don't scan well in adhoc mode.
@@ -962,14 +978,14 @@ deactivate_reset_hw_addr(NMDevice *device)
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSettingWireless *  s_wireless;
-    const char *         mac;
-    const char *const *  mac_blacklist;
+    NMSettingWireless   *s_wireless;
+    const char          *mac;
+    const char *const   *mac_blacklist;
     int                  i;
-    const char *         mode;
-    const char *         perm_hw_addr;
+    const char          *mode;
+    const char          *perm_hw_addr;
 
     if (!NM_DEVICE_CLASS(nm_device_wifi_parent_class)
              ->check_connection_compatible(device, connection, error))
@@ -1062,16 +1078,16 @@ 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)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSettingWireless *  s_wifi;
-    const char *         mode;
+    NMSettingWireless   *s_wifi;
+    const char          *mode;
 
     s_wifi = nm_connection_get_setting_wireless(connection);
     g_return_val_if_fail(s_wifi, FALSE);
@@ -1130,21 +1146,21 @@ 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)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSettingWireless *  s_wifi;
-    gs_free char *       ssid_utf8 = NULL;
-    NMWifiAP *           ap;
-    GBytes *             ssid         = NULL;
-    GBytes *             setting_ssid = NULL;
+    NMSettingWireless   *s_wifi;
+    gs_free char        *ssid_utf8 = NULL;
+    NMWifiAP            *ap;
+    GBytes              *ssid         = NULL;
+    GBytes              *setting_ssid = NULL;
     gboolean             hidden       = FALSE;
-    const char *         mode;
+    const char          *mode;
 
     s_wifi = nm_connection_get_setting_wireless(connection);
 
@@ -1265,8 +1281,8 @@ complete_connection(NMDevice *           device,
 static gboolean
 is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
 {
-    NMDeviceWifi *             self = NM_DEVICE_WIFI(device);
-    NMDeviceWifiPrivate *      priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifi              *self = NM_DEVICE_WIFI(device);
+    NMDeviceWifiPrivate       *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMSupplicantInterfaceState supplicant_state;
 
     if (!priv->enabled)
@@ -1292,12 +1308,12 @@ get_autoconnect_allowed(NMDevice *device)
 static gboolean
 can_auto_connect(NMDevice *device, NMSettingsConnection *sett_conn, char **specific_object)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMConnection *       connection;
-    NMSettingWireless *  s_wifi;
-    NMWifiAP *           ap;
-    const char *         method6, *mode;
+    NMConnection        *connection;
+    NMSettingWireless   *s_wifi;
+    NMWifiAP            *ap;
+    const char          *method6, *mode;
     gboolean             auto4, auto6;
 
     nm_assert(!specific_object || !*specific_object);
@@ -1345,7 +1361,7 @@ _nm_device_wifi_get_aps(NMDeviceWifi *self)
 static void
 _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
 {
-    NMDevice *           device = (NMDevice *) self;
+    NMDevice            *device = (NMDevice *) self;
     NMDeviceWifiPrivate *priv;
     guint32              now;
     gboolean             randomize;
@@ -1381,7 +1397,7 @@ _hw_addr_set_scanning(NMDeviceWifi *self, gboolean do_reset)
 
     if (now >= priv->hw_addr_scan_expire) {
         gs_free char *hw_addr_scan = NULL;
-        const char *  generate_mac_address_mask;
+        const char   *generate_mac_address_mask;
 
         /* the random MAC address for scanning expires after a while.
          *
@@ -1428,7 +1444,7 @@ ssids_options_to_ptrarray(GVariant *value, GError **error)
         for (i = 0; i < num_ssids; i++) {
             gs_unref_variant GVariant *v = NULL;
             gsize                      len;
-            const guint8 *             bytes;
+            const guint8              *bytes;
 
             v     = g_variant_get_child_value(value, i);
             bytes = g_variant_get_fixed_array(v, &len, sizeof(guint8));
@@ -1455,14 +1471,14 @@ nmtst_ssids_options_to_ptrarray(GVariant *value, GError **error)
 }
 
 static void
-dbus_request_scan_cb(NMDevice *             device,
+dbus_request_scan_cb(NMDevice              *device,
                      GDBusMethodInvocation *context,
-                     NMAuthSubject *        subject,
-                     GError *               error,
+                     NMAuthSubject         *subject,
+                     GError                *error,
                      gpointer               user_data)
 {
-    NMDeviceWifi *       self          = NM_DEVICE_WIFI(device);
-    NMDeviceWifiPrivate *priv          = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifi                *self  = NM_DEVICE_WIFI(device);
+    NMDeviceWifiPrivate         *priv  = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gs_unref_ptrarray GPtrArray *ssids = user_data;
 
     if (error) {
@@ -1477,13 +1493,13 @@ dbus_request_scan_cb(NMDevice *             device,
 }
 
 void
-_nm_device_wifi_request_scan(NMDeviceWifi *         self,
-                             GVariant *             options,
+_nm_device_wifi_request_scan(NMDeviceWifi          *self,
+                             GVariant              *options,
                              GDBusMethodInvocation *invocation)
 {
-    NMDeviceWifiPrivate *priv          = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMDevice *           device        = NM_DEVICE(self);
-    gs_unref_ptrarray GPtrArray *ssids = NULL;
+    NMDeviceWifiPrivate         *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDevice                    *device = NM_DEVICE(self);
+    gs_unref_ptrarray GPtrArray *ssids  = NULL;
 
     if (options) {
         gs_unref_variant GVariant *val = g_variant_lookup_value(options, "ssids", NULL);
@@ -1529,7 +1545,7 @@ _nm_device_wifi_request_scan(NMDeviceWifi *         self,
 static gboolean
 hidden_filter_func(NMSettings *settings, NMSettingsConnection *set_con, gpointer user_data)
 {
-    NMConnection *     connection = nm_settings_connection_get_connection(set_con);
+    NMConnection      *connection = nm_settings_connection_get_connection(set_con);
     NMSettingWireless *s_wifi;
 
     if (!nm_connection_is_type(connection, NM_SETTING_WIRELESS_SETTING_NAME))
@@ -1545,12 +1561,12 @@ hidden_filter_func(NMSettings *settings, NMSettingsConnection *set_con, gpointer
 static GPtrArray *
 _scan_request_ssids_build_hidden(NMDeviceWifi *self,
                                  gint64        now_msec,
-                                 gboolean *    out_has_hidden_profiles)
+                                 gboolean     *out_has_hidden_profiles)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    guint   max_scan_ssids    = nm_supplicant_interface_get_max_scan_ssids(priv->sup_iface);
+    guint max_scan_ssids      = nm_supplicant_interface_get_max_scan_ssids(priv->sup_iface);
     gs_free NMSettingsConnection **connections  = NULL;
-    gs_unref_ptrarray GPtrArray *ssids          = NULL;
+    gs_unref_ptrarray GPtrArray   *ssids        = NULL;
     gs_unref_hashtable GHashTable *unique_ssids = NULL;
     guint                          connections_len;
     guint                          n_hidden;
@@ -1608,7 +1624,7 @@ _scan_request_ssids_build_hidden(NMDeviceWifi *self,
     n_hidden = 0;
     for (i = 0; i < connections_len; i++) {
         NMSettingWireless *s_wifi;
-        GBytes *           ssid;
+        GBytes            *ssid;
 
         if (ssids->len >= max_scan_ssids)
             break;
@@ -1637,7 +1653,7 @@ _scan_request_ssids_build_hidden(NMDeviceWifi *self,
 static gboolean
 _scan_request_delay_cb(gpointer user_data)
 {
-    NMDeviceWifi *       self = user_data;
+    NMDeviceWifi        *self = user_data;
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     nm_clear_g_source_inst(&priv->scan_request_delay_source);
@@ -1650,10 +1666,10 @@ _scan_request_delay_cb(gpointer user_data)
 
 static void
 _scan_supplicant_request_scan_cb(NMSupplicantInterface *supp_iface,
-                                 GCancellable *         cancellable,
+                                 GCancellable          *cancellable,
                                  gpointer               user_data)
 {
-    NMDeviceWifi *       self;
+    NMDeviceWifi        *self;
     NMDeviceWifiPrivate *priv;
 
     if (g_cancellable_is_cancelled(cancellable))
@@ -1680,7 +1696,7 @@ _scan_supplicant_request_scan_cb(NMSupplicantInterface *supp_iface,
 static gboolean
 _scan_kickoff_timeout_cb(gpointer user_data)
 {
-    NMDeviceWifi *       self = user_data;
+    NMDeviceWifi        *self = user_data;
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     priv->scan_kickoff_timeout_id = 0;
@@ -1691,7 +1707,7 @@ _scan_kickoff_timeout_cb(gpointer user_data)
 static void
 _scan_kickoff(NMDeviceWifi *self)
 {
-    NMDeviceWifiPrivate *priv               = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifiPrivate         *priv       = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gs_unref_ptrarray GPtrArray *ssids      = NULL;
     gboolean                     is_explict = FALSE;
     NMDeviceState                device_state;
@@ -1866,7 +1882,7 @@ _scan_kickoff(NMDeviceWifi *self)
 static gboolean
 ap_list_dump(gpointer user_data)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(user_data);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(user_data);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     priv->ap_dump_id = 0;
@@ -1904,7 +1920,7 @@ schedule_ap_list_dump(NMDeviceWifi *self)
 static void
 try_fill_ssid_for_hidden_ap(NMDeviceWifi *self, NMWifiAP *ap)
 {
-    const char *                 bssid;
+    const char                  *bssid;
     NMSettingsConnection *const *connections;
     guint                        i;
 
@@ -1918,7 +1934,7 @@ try_fill_ssid_for_hidden_ap(NMDeviceWifi *self, NMWifiAP *ap)
     connections = nm_settings_get_connections(nm_device_get_settings((NMDevice *) self), NULL);
     for (i = 0; connections[i]; i++) {
         NMSettingsConnection *sett_conn = connections[i];
-        NMSettingWireless *   s_wifi;
+        NMSettingWireless    *s_wifi;
 
         if (!nm_settings_connection_has_seen_bssid(sett_conn, bssid))
             continue;
@@ -1934,13 +1950,13 @@ try_fill_ssid_for_hidden_ap(NMDeviceWifi *self, NMWifiAP *ap)
 
 static void
 supplicant_iface_bss_changed_cb(NMSupplicantInterface *iface,
-                                NMSupplicantBssInfo *  bss_info,
+                                NMSupplicantBssInfo   *bss_info,
                                 gboolean               is_present,
-                                NMDeviceWifi *         self)
+                                NMDeviceWifi          *self)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMWifiAP *           found_ap;
-    GBytes *             ssid;
+    NMWifiAP            *found_ap;
+    GBytes              *ssid;
 
     found_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, bss_info->bss_path);
 
@@ -2033,14 +2049,14 @@ cleanup_supplicant_failures(NMDeviceWifi *self)
 }
 
 static void
-wifi_secrets_cb(NMActRequest *                req,
+wifi_secrets_cb(NMActRequest                 *req,
                 NMActRequestGetSecretsCallId *call_id,
-                NMSettingsConnection *        connection,
-                GError *                      error,
+                NMSettingsConnection         *connection,
+                GError                       *error,
                 gpointer                      user_data)
 {
-    NMDevice *           device = user_data;
-    NMDeviceWifi *       self   = user_data;
+    NMDevice            *device = user_data;
+    NMDeviceWifi        *self   = user_data;
     NMDeviceWifiPrivate *priv;
 
     g_return_if_fail(NM_IS_DEVICE_WIFI(self));
@@ -2107,15 +2123,15 @@ wifi_secrets_cancel(NMDeviceWifi *self)
 
 static void
 supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
-                                    GVariant *             credentials,
-                                    NMDeviceWifi *         self)
+                                    GVariant              *credentials,
+                                    NMDeviceWifi          *self)
 {
-    NMActRequest *   req;
+    NMActRequest              *req;
     gs_unref_variant GVariant *val_key = NULL;
     gs_unref_variant GVariant *secrets = NULL;
-    gs_free_error GError *error        = NULL;
-    const char *          array;
-    gsize                 psk_len = 0;
+    gs_free_error GError      *error   = NULL;
+    const char                *array;
+    gsize                      psk_len = 0;
 
     if (nm_device_get_state(NM_DEVICE(self)) != NM_DEVICE_STATE_NEED_AUTH) {
         _LOGI(LOGD_DEVICE | LOGD_WIFI, "WPS: The connection can't be updated with credentials");
@@ -2168,7 +2184,7 @@ supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
 static gboolean
 wps_timeout_cb(gpointer user_data)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(user_data);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(user_data);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     priv->wps_timeout_id = 0;
@@ -2183,12 +2199,12 @@ wps_timeout_cb(gpointer user_data)
 }
 
 static void
-wifi_secrets_get_secrets(NMDeviceWifi *               self,
-                         const char *                 setting_name,
+wifi_secrets_get_secrets(NMDeviceWifi                *self,
+                         const char                  *setting_name,
                          NMSecretAgentGetSecretsFlags flags)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMActRequest *       req;
+    NMActRequest        *req;
 
     wifi_secrets_cancel(self);
 
@@ -2209,8 +2225,8 @@ wifi_secrets_get_secrets(NMDeviceWifi *               self,
 static gboolean
 link_timeout_cb(gpointer user_data)
 {
-    NMDevice *           device = NM_DEVICE(user_data);
-    NMDeviceWifi *       self   = NM_DEVICE_WIFI(device);
+    NMDevice            *device = NM_DEVICE(user_data);
+    NMDeviceWifi        *self   = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     _LOGW(LOGD_WIFI, "link timed out.");
@@ -2234,14 +2250,14 @@ link_timeout_cb(gpointer user_data)
 }
 
 static gboolean
-need_new_8021x_secrets(NMDeviceWifi *             self,
+need_new_8021x_secrets(NMDeviceWifi              *self,
                        NMSupplicantInterfaceState old_state,
-                       const char **              setting_name)
+                       const char               **setting_name)
 {
-    NMSetting8021x *           s_8021x;
+    NMSetting8021x            *s_8021x;
     NMSettingWirelessSecurity *s_wsec;
     NMSettingSecretFlags       secret_flags = NM_SETTING_SECRET_FLAG_NONE;
-    NMConnection *             connection;
+    NMConnection              *connection;
 
     g_return_val_if_fail(setting_name, FALSE);
 
@@ -2289,14 +2305,14 @@ need_new_8021x_secrets(NMDeviceWifi *             self,
 }
 
 static gboolean
-need_new_wpa_psk(NMDeviceWifi *             self,
+need_new_wpa_psk(NMDeviceWifi              *self,
                  NMSupplicantInterfaceState old_state,
                  int                        disconnect_reason,
-                 const char **              setting_name)
+                 const char               **setting_name)
 {
     NMSettingWirelessSecurity *s_wsec;
-    NMConnection *             connection;
-    const char *               key_mgmt = NULL;
+    NMConnection              *connection;
+    const char                *key_mgmt = NULL;
 
     g_return_val_if_fail(setting_name, FALSE);
 
@@ -2331,14 +2347,14 @@ need_new_wpa_psk(NMDeviceWifi *             self,
 }
 
 static gboolean
-handle_8021x_or_psk_auth_fail(NMDeviceWifi *             self,
+handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
                               NMSupplicantInterfaceState new_state,
                               NMSupplicantInterfaceState old_state,
                               int                        disconnect_reason)
 {
-    NMDevice *    device = NM_DEVICE(self);
+    NMDevice     *device = NM_DEVICE(self);
     NMActRequest *req;
-    const char *  setting_name = NULL;
+    const char   *setting_name = NULL;
     gboolean      handled      = FALSE;
 
     g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE);
@@ -2370,8 +2386,8 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi *             self,
 static gboolean
 reacquire_interface_cb(gpointer user_data)
 {
-    NMDevice *           device = NM_DEVICE(user_data);
-    NMDeviceWifi *       self   = NM_DEVICE_WIFI(device);
+    NMDevice            *device = NM_DEVICE(user_data);
+    NMDeviceWifi        *self   = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     priv->reacquire_iface_id = 0;
@@ -2389,7 +2405,7 @@ static void
 supplicant_iface_state_down(NMDeviceWifi *self)
 {
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDevice            *device = NM_DEVICE(self);
 
     nm_device_queue_recheck_available(device,
                                       NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE,
@@ -2409,14 +2425,14 @@ supplicant_iface_state_down(NMDeviceWifi *self)
 }
 
 static void
-supplicant_iface_state(NMDeviceWifi *             self,
+supplicant_iface_state(NMDeviceWifi              *self,
                        NMSupplicantInterfaceState new_state,
                        NMSupplicantInterfaceState old_state,
                        int                        disconnect_reason,
                        gboolean                   is_real_signal)
 {
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMDevice *           device = NM_DEVICE(self);
+    NMDevice            *device = NM_DEVICE(self);
     NMDeviceState        devstate;
     gboolean             scanning;
     gboolean             scan_changed;
@@ -2463,8 +2479,8 @@ supplicant_iface_state(NMDeviceWifi *             self,
          */
         if (devstate == NM_DEVICE_STATE_CONFIG) {
             NMSettingWireless *s_wifi;
-            GBytes *           ssid;
-            gs_free char *     ssid_str = NULL;
+            GBytes            *ssid;
+            gs_free char      *ssid_str = NULL;
 
             s_wifi = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS);
 
@@ -2478,7 +2494,7 @@ supplicant_iface_state(NMDeviceWifi *             self,
                   priv->mode == _NM_802_11_MODE_AP ? "Started Wi-Fi Hotspot"
                                                    : "Connected to wireless network",
                   (ssid_str = _nm_utils_ssid_to_string_gbytes(ssid)));
-            nm_device_activate_schedule_stage3_ip_config_start(device);
+            nm_device_activate_schedule_stage3_ip_config(device, FALSE);
         } else if (devstate == NM_DEVICE_STATE_ACTIVATED)
             periodic_update(self);
         break;
@@ -2540,7 +2556,7 @@ static void
 supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer user_data)
 {
     NMDeviceWifi *self   = NM_DEVICE_WIFI(user_data);
-    NMDevice *    device = NM_DEVICE(self);
+    NMDevice     *device = NM_DEVICE(self);
 
     if (error && !nm_utils_error_is_cancelled_or_disposing(error)
         && nm_device_is_activating(device)) {
@@ -2553,23 +2569,23 @@ supplicant_iface_assoc_cb(NMSupplicantInterface *iface, GError *error, gpointer
 
 static void
 supplicant_iface_notify_current_bss(NMSupplicantInterface *iface,
-                                    GParamSpec *           pspec,
-                                    NMDeviceWifi *         self)
+                                    GParamSpec            *pspec,
+                                    NMDeviceWifi          *self)
 {
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMRefString *        current_bss;
-    NMWifiAP *           new_ap = NULL;
-    NMActRequest *       req;
+    NMRefString         *current_bss;
+    NMWifiAP            *new_ap = NULL;
+    NMActRequest        *req;
 
     current_bss = nm_supplicant_interface_get_current_bss(iface);
     if (current_bss)
         new_ap = g_hash_table_lookup(priv->aps_idx_by_supplicant_path, current_bss);
 
     if (new_ap != priv->current_ap) {
-        const char *  new_bssid  = NULL;
-        GBytes *      new_ssid   = NULL;
-        const char *  old_bssid  = NULL;
-        GBytes *      old_ssid   = NULL;
+        const char   *new_bssid  = NULL;
+        GBytes       *new_ssid   = NULL;
+        const char   *old_bssid  = NULL;
+        GBytes       *old_ssid   = NULL;
         gs_free char *new_ssid_s = NULL;
         gs_free char *old_ssid_s = NULL;
 
@@ -2666,8 +2682,8 @@ recheck_p2p_availability(NMDeviceWifi *self)
 
 static void
 supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
-                                      GParamSpec *           pspec,
-                                      NMDeviceWifi *         self)
+                                      GParamSpec            *pspec,
+                                      NMDeviceWifi          *self)
 {
     if (nm_supplicant_interface_get_state(iface) > NM_SUPPLICANT_INTERFACE_STATE_STARTING)
         recheck_p2p_availability(self);
@@ -2676,14 +2692,14 @@ supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
 static gboolean
 handle_auth_or_fail(NMDeviceWifi *self, NMActRequest *req, gboolean new_secrets)
 {
-    NMDeviceWifiPrivate *              priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    const char *                       setting_name;
-    NMConnection *                     applied_connection;
-    NMSettingWirelessSecurity *        s_wsec;
-    const char *                       bssid = NULL;
+    NMDeviceWifiPrivate               *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    const char                        *setting_name;
+    NMConnection                      *applied_connection;
+    NMSettingWirelessSecurity         *s_wsec;
+    const char                        *bssid = NULL;
     NM80211ApFlags                     ap_flags;
     NMSettingWirelessSecurityWpsMethod wps_method;
-    const char *                       type;
+    const char                        *type;
     NMSecretAgentGetSecretsFlags       get_secret_flags =
         NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION;
 
@@ -2761,11 +2777,11 @@ handle_auth_or_fail(NMDeviceWifi *self, NMActRequest *req, gboolean new_secrets)
 static gboolean
 supplicant_connection_timeout_cb(gpointer user_data)
 {
-    NMDevice *           device = NM_DEVICE(user_data);
-    NMDeviceWifi *       self   = NM_DEVICE_WIFI(user_data);
+    NMDevice            *device = NM_DEVICE(user_data);
+    NMDeviceWifi        *self   = NM_DEVICE_WIFI(user_data);
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMActRequest *       req;
-    NMConnection *       connection;
+    NMActRequest        *req;
+    NMConnection        *connection;
 
     cleanup_association_attempt(self, TRUE);
 
@@ -2841,12 +2857,12 @@ static NMSupplicantConfig *
 build_supplicant_config(NMDeviceWifi *self,
                         NMConnection *connection,
                         guint32       fixed_freq,
-                        GError **     error)
+                        GError      **error)
 {
-    NMDeviceWifiPrivate *         priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMSupplicantConfig *          config = NULL;
-    NMSettingWireless *           s_wireless;
-    NMSettingWirelessSecurity *   s_wireless_sec;
+    NMDeviceWifiPrivate          *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMSupplicantConfig           *config = NULL;
+    NMSettingWireless            *s_wireless;
+    NMSettingWirelessSecurity    *s_wireless_sec;
     NMSettingWirelessSecurityPmf  pmf;
     NMSettingWirelessSecurityFils fils;
     NMTernary                     ap_isolation;
@@ -2889,7 +2905,7 @@ build_supplicant_config(NMDeviceWifi *self,
     s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
     if (s_wireless_sec) {
         NMSetting8021x *s_8021x;
-        const char *    con_uuid = nm_connection_get_uuid(connection);
+        const char     *con_uuid = nm_connection_get_uuid(connection);
         guint32         mtu      = nm_platform_link_get_mtu(nm_device_get_platform(NM_DEVICE(self)),
                                                nm_device_get_ifindex(NM_DEVICE(self)));
 
@@ -2950,10 +2966,10 @@ error:
 static gboolean
 wake_on_wlan_enable(NMDeviceWifi *self)
 {
-    NMDeviceWifiPrivate *        priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifiPrivate         *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMSettingWirelessWakeOnWLan  wowl;
     _NMSettingWirelessWakeOnWLan wowl2;
-    NMSettingWireless *          s_wireless;
+    NMSettingWireless           *s_wireless;
 
     s_wireless = nm_device_get_applied_setting(NM_DEVICE(self), NM_TYPE_SETTING_WIRELESS);
     if (s_wireless) {
@@ -3004,15 +3020,15 @@ found:
 static NMActStageReturn
 act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWifi *       self         = NM_DEVICE_WIFI(device);
-    NMDeviceWifiPrivate *priv         = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMWifiAP *           ap           = NULL;
+    NMDeviceWifi             *self    = NM_DEVICE_WIFI(device);
+    NMDeviceWifiPrivate      *priv    = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMWifiAP                 *ap      = NULL;
     gs_unref_object NMWifiAP *ap_fake = NULL;
-    NMActRequest *            req;
-    NMConnection *            connection;
-    NMSettingWireless *       s_wireless;
-    const char *              mode;
-    const char *              ap_path;
+    NMActRequest             *req;
+    NMConnection             *connection;
+    NMSettingWireless        *s_wireless;
+    const char               *mode;
+    const char               *ap_path;
 
     req = nm_device_get_act_request(NM_DEVICE(self));
     g_return_val_if_fail(req, NM_ACT_STAGE_RETURN_FAILURE);
@@ -3087,8 +3103,8 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap)
 {
-    NMDevice *    device     = NM_DEVICE(self);
-    const char *  band       = nm_setting_wireless_get_band(s_wifi);
+    NMDevice     *device     = NM_DEVICE(self);
+    const char   *band       = nm_setting_wireless_get_band(s_wifi);
     const guint32 a_freqs[]  = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0};
     const guint32 bg_freqs[] = {2412, 2437, 2462, 2472, 0};
     guint32       freq       = 0;
@@ -3117,8 +3133,8 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
 static void
 set_powersave(NMDevice *device)
 {
-    NMDeviceWifi *             self = NM_DEVICE_WIFI(device);
-    NMSettingWireless *        s_wireless;
+    NMDeviceWifi              *self = NM_DEVICE_WIFI(device);
+    NMSettingWireless         *s_wireless;
     NMSettingWirelessPowersave val;
 
     s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
@@ -3148,20 +3164,20 @@ set_powersave(NMDevice *device)
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceWifi *       self                  = NM_DEVICE_WIFI(device);
-    NMDeviceWifiPrivate *priv                  = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    NMDeviceWifi                       *self   = NM_DEVICE_WIFI(device);
+    NMDeviceWifiPrivate                *priv   = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gs_unref_object NMSupplicantConfig *config = NULL;
     _NM80211Mode                        ap_mode;
-    NMActRequest *                      req;
-    NMWifiAP *                          ap;
-    NMConnection *                      connection;
-    const char *                        setting_name;
-    NMSettingWireless *                 s_wireless;
-    GError *                            error = NULL;
+    NMActRequest                       *req;
+    NMWifiAP                           *ap;
+    NMConnection                       *connection;
+    const char                         *setting_name;
+    NMSettingWireless                  *s_wireless;
+    GError                             *error = NULL;
     guint                               timeout;
-    NMActRequest *                      request;
-    NMActiveConnection *                master_ac;
-    NMDevice *                          master;
+    NMActRequest                       *request;
+    NMActiveConnection                 *master_ac;
+    NMDevice                           *master;
 
     nm_clear_g_source(&priv->sup_timeout_id);
     nm_clear_g_source(&priv->link_timeout_id);
@@ -3263,20 +3279,24 @@ out_fail:
     return NM_ACT_STAGE_RETURN_FAILURE;
 }
 
-static NMActStageReturn
-act_stage3_ip_config_start(NMDevice *           device,
-                           int                  addr_family,
-                           gpointer *           out_config,
-                           NMDeviceStateReason *out_failure_reason)
+static void
+act_stage3_ip_config(NMDevice *device, int addr_family)
 {
-    gboolean      indicate_addressing_running;
-    NMConnection *connection;
-    const char *  method;
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
+    NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
+    const char          *method;
+    gboolean             indicate_addressing_running;
+
+    if (priv->addressing_running_indicated)
+        return;
 
-    connection = nm_device_get_applied_connection(device);
+    /* we always set the flag, even if we don't indicate it below. The reason
+     * is that we always want to *clear* the flag after we are done (as we don't
+     * know whether it isn't already set on the interface).  */
+    priv->addressing_running_indicated = TRUE;
 
-    method = nm_utils_get_ip_config_method(connection, addr_family);
-    if (addr_family == AF_INET)
+    method = nm_utils_get_ip_config_method(nm_device_get_applied_connection(device), addr_family);
+    if (NM_IS_IPv4(addr_family))
         indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     else {
         indicate_addressing_running = NM_IN_STRSET(method,
@@ -3284,13 +3304,11 @@ act_stage3_ip_config_start(NMDevice *           device,
                                                    NM_SETTING_IP6_CONFIG_METHOD_DHCP);
     }
 
-    if (indicate_addressing_running)
+    if (indicate_addressing_running) {
         nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
                                                      nm_device_get_ip_ifindex(device),
                                                      TRUE);
-
-    return NM_DEVICE_CLASS(nm_device_wifi_parent_class)
-        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
+    }
 }
 
 static guint32
@@ -3301,90 +3319,18 @@ get_configured_mtu(NMDevice *device, NMDeviceMtuSource *out_source, gboolean *ou
                                                         out_source);
 }
 
-static gboolean
-is_static_wep(NMConnection *connection)
-{
-    NMSettingWirelessSecurity *s_wsec;
-    const char *               str;
-
-    g_return_val_if_fail(connection != NULL, FALSE);
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    if (!s_wsec)
-        return FALSE;
-
-    str = nm_setting_wireless_security_get_key_mgmt(s_wsec);
-    if (g_strcmp0(str, "none") != 0)
-        return FALSE;
-
-    str = nm_setting_wireless_security_get_auth_alg(s_wsec);
-    if (g_strcmp0(str, "leap") == 0)
-        return FALSE;
-
-    return TRUE;
-}
-
-static NMActStageReturn
-act_stage4_ip_config_timeout(NMDevice *           device,
-                             int                  addr_family,
-                             NMDeviceStateReason *out_failure_reason)
-{
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
-    NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    NMConnection *       connection;
-    NMSettingIPConfig *  s_ip;
-    gboolean             may_fail;
-
-    connection = nm_device_get_applied_connection(device);
-    s_ip       = nm_connection_get_setting_ip_config(connection, addr_family);
-    may_fail   = nm_setting_ip_config_get_may_fail(s_ip);
-
-    if (priv->mode == _NM_802_11_MODE_AP)
-        goto call_parent;
-
-    if (may_fail || !is_static_wep(connection)) {
-        /* Not static WEP or failure allowed; let superclass handle it */
-        goto call_parent;
-    }
-
-    /* If IP configuration times out and it's a static WEP connection, that
-     * usually means the WEP key is wrong.  WEP's Open System auth mode has
-     * no provision for figuring out if the WEP key is wrong, so you just have
-     * to wait for DHCP to fail to figure it out.  For all other Wi-Fi security
-     * types (open, WPA, 802.1x, etc) if the secrets/certs were wrong the
-     * connection would have failed before IP configuration.
-     *
-     * Activation failed, we must have bad encryption key */
-    _LOGW(LOGD_DEVICE | LOGD_WIFI,
-          "Activation: (wifi) could not get IP configuration for connection '%s'.",
-          nm_connection_get_id(connection));
-
-    if (!handle_auth_or_fail(self, NULL, TRUE)) {
-        NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
-        return NM_ACT_STAGE_RETURN_FAILURE;
-    }
-
-    _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) asking for new secrets");
-    return NM_ACT_STAGE_RETURN_POSTPONE;
-
-call_parent:
-    return NM_DEVICE_CLASS(nm_device_wifi_parent_class)
-        ->act_stage4_ip_config_timeout(device, addr_family, out_failure_reason);
-}
-
 static void
 activation_success_handler(NMDevice *device)
 {
-    NMDeviceWifi *       self    = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self    = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv    = NM_DEVICE_WIFI_GET_PRIVATE(self);
     int                  ifindex = nm_device_get_ifindex(device);
-    NMActRequest *       req;
+    NMActRequest        *req;
 
     req = nm_device_get_act_request(device);
     g_assert(req);
 
-    /* Clear any critical protocol notification in the wifi stack */
-    nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device), ifindex, FALSE);
+    _indicate_addressing_running_reset(self);
 
     /* There should always be a current AP, either a fake one because we haven't
      * seen a scan result for the activated AP yet, or a real one from the
@@ -3439,12 +3385,12 @@ activation_success_handler(NMDevice *device)
 }
 
 static void
-device_state_changed(NMDevice *          device,
+device_state_changed(NMDevice           *device,
                      NMDeviceState       new_state,
                      NMDeviceState       old_state,
                      NMDeviceStateReason reason)
 {
-    NMDeviceWifi *       self      = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self      = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv      = NM_DEVICE_WIFI_GET_PRIVATE(self);
     gboolean             clear_aps = FALSE;
 
@@ -3484,19 +3430,13 @@ device_state_changed(NMDevice *          device,
             nm_supplicant_interface_disconnect(priv->sup_iface);
         break;
     case NM_DEVICE_STATE_IP_CHECK:
-        /* Clear any critical protocol notification in the wifi stack */
-        nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
-                                                     nm_device_get_ifindex(device),
-                                                     FALSE);
+        _indicate_addressing_running_reset(self);
         break;
     case NM_DEVICE_STATE_ACTIVATED:
         activation_success_handler(device);
         break;
     case NM_DEVICE_STATE_FAILED:
-        /* Clear any critical protocol notification in the wifi stack */
-        nm_platform_wifi_indicate_addressing_running(nm_device_get_platform(device),
-                                                     nm_device_get_ifindex(device),
-                                                     FALSE);
+        _indicate_addressing_running_reset(self);
         break;
     case NM_DEVICE_STATE_DISCONNECTED:
         break;
@@ -3519,7 +3459,7 @@ get_enabled(NMDevice *device)
 static void
 set_enabled(NMDevice *device, gboolean enabled)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
     NMDeviceState        state;
 
@@ -3573,19 +3513,19 @@ set_enabled(NMDevice *device, gboolean enabled)
 static gboolean
 get_guessed_metered(NMDevice *device)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(device);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(device);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     return priv->current_ap && nm_wifi_ap_get_metered(priv->current_ap);
 }
 
 static gboolean
-can_reapply_change(NMDevice *  device,
+can_reapply_change(NMDevice   *device,
                    const char *setting_name,
-                   NMSetting * s_old,
-                   NMSetting * s_new,
+                   NMSetting  *s_old,
+                   NMSetting  *s_new,
                    GHashTable *diffs,
-                   GError **   error)
+                   GError    **error)
 {
     NMDeviceClass *device_class;
 
@@ -3623,9 +3563,9 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(object);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(object);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
-    const char **        list;
+    const char         **list;
 
     switch (prop_id) {
     case PROP_MODE:
@@ -3664,7 +3604,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-    NMDeviceWifi *       device = NM_DEVICE_WIFI(object);
+    NMDeviceWifi        *device = NM_DEVICE_WIFI(object);
     NMDeviceWifiPrivate *priv   = NM_DEVICE_WIFI_GET_PRIVATE(device);
 
     switch (prop_id) {
@@ -3699,7 +3639,7 @@ nm_device_wifi_init(NMDeviceWifi *self)
 static void
 constructed(GObject *object)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(object);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(object);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     G_OBJECT_CLASS(nm_device_wifi_parent_class)->constructed(object);
@@ -3733,7 +3673,7 @@ nm_device_wifi_new(const char *iface, _NMDeviceWifiCapabilities capabilities)
 static void
 dispose(GObject *object)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(object);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(object);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     nm_assert(c_list_is_empty(&priv->scanning_prohibited_lst_head));
@@ -3762,7 +3702,7 @@ dispose(GObject *object)
 static void
 finalize(GObject *object)
 {
-    NMDeviceWifi *       self = NM_DEVICE_WIFI(object);
+    NMDeviceWifi        *self = NM_DEVICE_WIFI(object);
     NMDeviceWifiPrivate *priv = NM_DEVICE_WIFI_GET_PRIVATE(self);
 
     nm_assert(c_list_is_empty(&priv->aps_lst_head));
@@ -3776,9 +3716,9 @@ finalize(GObject *object)
 static void
 nm_device_wifi_class_init(NMDeviceWifiClass *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->constructed  = constructed;
     object_class->get_property = get_property;
@@ -3803,17 +3743,16 @@ nm_device_wifi_class_init(NMDeviceWifiClass *klass)
     device_class->get_guessed_metered         = get_guessed_metered;
     device_class->set_enabled                 = set_enabled;
 
-    device_class->act_stage1_prepare           = act_stage1_prepare;
-    device_class->act_stage2_config            = act_stage2_config;
-    device_class->get_configured_mtu           = get_configured_mtu;
-    device_class->act_stage3_ip_config_start   = act_stage3_ip_config_start;
-    device_class->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
-    device_class->deactivate_async             = deactivate_async;
-    device_class->deactivate                   = deactivate;
-    device_class->deactivate_reset_hw_addr     = deactivate_reset_hw_addr;
-    device_class->unmanaged_on_quit            = unmanaged_on_quit;
-    device_class->can_reapply_change           = can_reapply_change;
-    device_class->reapply_connection           = reapply_connection;
+    device_class->act_stage1_prepare       = act_stage1_prepare;
+    device_class->act_stage2_config        = act_stage2_config;
+    device_class->get_configured_mtu       = get_configured_mtu;
+    device_class->act_stage3_ip_config     = act_stage3_ip_config;
+    device_class->deactivate_async         = deactivate_async;
+    device_class->deactivate               = deactivate;
+    device_class->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
+    device_class->unmanaged_on_quit        = unmanaged_on_quit;
+    device_class->can_reapply_change       = can_reapply_change;
+    device_class->reapply_connection       = reapply_connection;
 
     device_class->state_changed = device_state_changed;
 
diff --git a/src/core/devices/wifi/nm-device-wifi.h b/src/core/devices/wifi/nm-device-wifi.h
index c7ff1a14..3a85192e 100644
--- a/src/core/devices/wifi/nm-device-wifi.h
+++ b/src/core/devices/wifi/nm-device-wifi.h
@@ -37,8 +37,8 @@ NMDevice *nm_device_wifi_new(const char *iface, _NMDeviceWifiCapabilities capabi
 
 const CList *_nm_device_wifi_get_aps(NMDeviceWifi *self);
 
-void _nm_device_wifi_request_scan(NMDeviceWifi *         self,
-                                  GVariant *             options,
+void _nm_device_wifi_request_scan(NMDeviceWifi          *self,
+                                  GVariant              *options,
                                   GDBusMethodInvocation *invocation);
 
 GPtrArray *nmtst_ssids_options_to_ptrarray(GVariant *value, GError **error);
diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c
index 56386c57..5563ebf8 100644
--- a/src/core/devices/wifi/nm-iwd-manager.c
+++ b/src/core/devices/wifi/nm-iwd-manager.c
@@ -15,6 +15,7 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "nm-manager.h"
 #include "nm-device-iwd.h"
+#include "nm-device-iwd-p2p.h"
 #include "nm-wifi-utils.h"
 #include "libnm-glib-aux/nm-uuid.h"
 #include "libnm-glib-aux/nm-random-utils.h"
@@ -25,30 +26,42 @@
 
 /*****************************************************************************/
 
+enum {
+    P2P_DEVICE_ADDED,
+
+    LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
 typedef struct {
-    const char *         name;
+    const char          *name;
     NMIwdNetworkSecurity security;
     char                 buf[0];
 } KnownNetworkId;
 
 typedef struct {
-    GDBusProxy *          known_network;
+    GDBusProxy           *known_network;
     NMSettingsConnection *mirror_connection;
     const KnownNetworkId *id;
 } KnownNetworkData;
 
 typedef struct {
-    NMManager *         manager;
-    NMSettings *        settings;
-    GCancellable *      cancellable;
+    NMManager          *manager;
+    NMSettings         *settings;
+    GCancellable       *cancellable;
     gboolean            running;
     GDBusObjectManager *object_manager;
     guint               agent_id;
-    char *              agent_path;
-    GHashTable *        known_networks;
-    NMDeviceIwd *       last_agent_call_device;
-    char *              last_state_dir;
-    char *              warned_state_dir;
+    char               *agent_path;
+    GHashTable         *known_networks;
+    NMDeviceIwd        *last_agent_call_device;
+    char               *last_state_dir;
+    char               *warned_state_dir;
+    bool                netconfig_enabled;
+    GHashTable         *p2p_devices;
+    NMIwdWfdInfo        wfd_info;
+    guint               wfd_use_count;
 } NMIwdManagerPrivate;
 
 struct _NMIwdManager {
@@ -98,7 +111,7 @@ G_DEFINE_TYPE(NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT)
 /*****************************************************************************/
 
 static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn,
-                                              KnownNetworkData *    data);
+                                              KnownNetworkData     *data);
 
 /*****************************************************************************/
 
@@ -146,10 +159,10 @@ get_property_bool(GDBusProxy *proxy, const char *property, gboolean default_val)
 static NMDeviceIwd *
 get_device_from_network(NMIwdManager *self, GDBusProxy *network)
 {
-    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    const char *         ifname;
-    const char *         device_path;
-    NMDevice *           device;
+    NMIwdManagerPrivate            *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char                     *ifname;
+    const char                     *device_path;
+    NMDevice                       *device;
     gs_unref_object GDBusInterface *device_obj = NULL;
 
     /* Try not to rely on the path of the Device being a prefix of the
@@ -182,21 +195,21 @@ get_device_from_network(NMIwdManager *self, GDBusProxy *network)
 }
 
 static void
-agent_dbus_method_cb(GDBusConnection *      connection,
-                     const char *           sender,
-                     const char *           object_path,
-                     const char *           interface_name,
-                     const char *           method_name,
-                     GVariant *             parameters,
+agent_dbus_method_cb(GDBusConnection       *connection,
+                     const char            *sender,
+                     const char            *object_path,
+                     const char            *interface_name,
+                     const char            *method_name,
+                     GVariant              *parameters,
                      GDBusMethodInvocation *invocation,
                      gpointer               user_data)
 {
-    NMIwdManager *       self = user_data;
-    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    const char *         network_path;
-    NMDeviceIwd *        device;
-    gs_free char *       name_owner         = NULL;
-    gs_unref_object GDBusInterface *network = NULL;
+    NMIwdManager                   *self = user_data;
+    NMIwdManagerPrivate            *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char                     *network_path;
+    NMDeviceIwd                    *device;
+    gs_free char                   *name_owner = NULL;
+    gs_unref_object GDBusInterface *network    = NULL;
 
     /* Be paranoid and check the sender address */
     name_owner = g_dbus_object_manager_client_get_name_owner(
@@ -315,7 +328,7 @@ static void
 register_agent(NMIwdManager *self)
 {
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GDBusInterface *     agent_manager;
+    GDBusInterface      *agent_manager;
 
     agent_manager = g_dbus_object_manager_get_interface(priv->object_manager,
                                                         "/net/connman/iwd", /* IWD 1.0+ */
@@ -388,9 +401,9 @@ static void
 set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object)
 {
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    const char *         ifname;
+    const char          *ifname;
     int                  ifindex;
-    NMDevice *           device;
+    NMDevice            *device;
     int                  errsv;
 
     ifname = get_property_string_or_null(proxy, "Name");
@@ -420,10 +433,70 @@ set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *objec
 }
 
 static void
+add_p2p_device(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object)
+{
+    NMIwdManagerPrivate            *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char                     *path = g_dbus_object_get_object_path(object);
+    NMDeviceIwdP2P                 *p2p;
+    gs_unref_object GDBusInterface *wiphy = NULL;
+    const char                     *phy_name;
+
+    if (g_hash_table_contains(priv->p2p_devices, path))
+        return;
+
+    wiphy = g_dbus_object_get_interface(object, NM_IWD_WIPHY_INTERFACE);
+    if (!wiphy)
+        return;
+
+    phy_name = get_property_string_or_null(G_DBUS_PROXY(wiphy), "Name");
+    if (!phy_name) {
+        _LOGE("Name not cached for phy at %s", path);
+        return;
+    }
+
+    p2p = nm_device_iwd_p2p_new(object);
+    if (!p2p) {
+        _LOGE("Can't create NMDeviceIwdP2P for phy at %s", path);
+        return;
+    }
+
+    g_hash_table_insert(priv->p2p_devices, g_strdup(path), p2p);
+    g_signal_emit(self, signals[P2P_DEVICE_ADDED], 0, p2p, phy_name);
+
+    /* There should be no peer objects before the device object appeared so don't
+     * try to look for them and notify the new device.  */
+}
+
+static void
+remove_p2p_device(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *object)
+{
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char          *path = g_dbus_object_get_object_path(object);
+    NMDeviceIwdP2P      *p2p  = g_hash_table_lookup(priv->p2p_devices, path);
+
+    if (!p2p)
+        return;
+
+    g_hash_table_remove(priv->p2p_devices, path);
+}
+
+static NMDeviceIwdP2P *
+get_p2p_device_from_peer(NMIwdManager *self, GDBusProxy *proxy)
+{
+    NMIwdManagerPrivate *priv        = NM_IWD_MANAGER_GET_PRIVATE(self);
+    const char          *device_path = get_property_string_or_null(proxy, "Device");
+
+    if (!device_path)
+        return NULL;
+
+    return g_hash_table_lookup(priv->p2p_devices, device_path);
+}
+
+static void
 known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
     gs_unref_variant GVariant *variant = NULL;
-    gs_free_error GError *error        = NULL;
+    gs_free_error GError      *error   = NULL;
 
     variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!variant) {
@@ -436,13 +509,13 @@ known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 }
 
 static gboolean
-iwd_config_write(GKeyFile *             config,
-                 const char *           filepath,
+iwd_config_write(GKeyFile              *config,
+                 const char            *filepath,
                  const struct timespec *mtime,
-                 GError **              error)
+                 GError               **error)
 {
     gsize           length;
-    gs_free char *  data     = g_key_file_to_data(config, &length, NULL);
+    gs_free char   *data     = g_key_file_to_data(config, &length, NULL);
     struct timespec times[2] = {{.tv_nsec = UTIME_OMIT}, *mtime};
 
     /* Atomically write or replace the file with the right permission bits
@@ -458,7 +531,7 @@ static const char *
 get_config_path(NMIwdManager *self)
 {
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    const char *         path;
+    const char          *path;
 
     path = nm_config_data_get_iwd_config_path(NM_CONFIG_GET_DATA);
     if (path && path[0] == '\0') {
@@ -489,26 +562,26 @@ get_config_path(NMIwdManager *self)
 }
 
 static void
-sett_conn_changed(NMSettingsConnection *  sett_conn,
+sett_conn_changed(NMSettingsConnection   *sett_conn,
                   guint                   update_reason,
                   const KnownNetworkData *data)
 {
-    NMSettingsConnectionIntFlags flags;
-    NMConnection *               conn          = nm_settings_connection_get_connection(sett_conn);
-    NMSettingConnection *        s_conn        = nm_connection_get_setting_connection(conn);
-    NMSettingWireless *          s_wifi        = nm_connection_get_setting_wireless(conn);
+    NMSettingsConnectionIntFlags    flags;
+    NMConnection                   *conn       = nm_settings_connection_get_connection(sett_conn);
+    NMSettingConnection            *s_conn     = nm_connection_get_setting_connection(conn);
+    NMSettingWireless              *s_wifi     = nm_connection_get_setting_wireless(conn);
     nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
-    const char *                    iwd_dir;
-    gs_free char *                  filename  = NULL;
-    gs_free char *                  full_path = NULL;
-    gs_free_error GError *error               = NULL;
-    NMIwdNetworkSecurity  security;
-    GBytes *              ssid;
-    const guint8 *        ssid_data;
-    gsize                 ssid_len;
-    gboolean              removed;
-    GStatBuf              statbuf;
-    gboolean              have_mtime;
+    const char                     *iwd_dir;
+    gs_free char                   *filename  = NULL;
+    gs_free char                   *full_path = NULL;
+    gs_free_error GError           *error     = NULL;
+    NMIwdNetworkSecurity            security;
+    GBytes                         *ssid;
+    const guint8                   *ssid_data;
+    gsize                           ssid_len;
+    gboolean                        removed;
+    GStatBuf                        statbuf;
+    gboolean                        have_mtime;
 
     nm_assert(sett_conn == data->mirror_connection);
 
@@ -660,24 +733,24 @@ sett_conn_changed(NMSettingsConnection *  sett_conn,
  * from the user.
  */
 static NMSettingsConnection *
-mirror_connection(NMIwdManager *        self,
+mirror_connection(NMIwdManager         *self,
                   const KnownNetworkId *id,
                   gboolean              create_new,
-                  GDBusProxy *          known_network)
+                  GDBusProxy           *known_network)
 {
-    NMIwdManagerPrivate *        priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    NMSettingsConnection *const *iter;
+    NMIwdManagerPrivate          *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    NMSettingsConnection *const  *iter;
     gs_unref_object NMConnection *connection          = NULL;
-    NMSettingsConnection *        settings_connection = NULL;
+    NMSettingsConnection         *settings_connection = NULL;
     char                          uuid[37];
-    NMSetting *                   setting;
-    gs_free_error GError *error            = NULL;
-    gs_unref_bytes GBytes *new_ssid        = NULL;
-    gsize                  ssid_len        = strlen(id->name);
-    gboolean               autoconnectable = TRUE;
-    gboolean               hidden          = FALSE;
-    gboolean               exact_match     = TRUE;
-    const char *           key_mgmt        = NULL;
+    NMSetting                    *setting;
+    gs_free_error GError         *error           = NULL;
+    gs_unref_bytes GBytes        *new_ssid        = NULL;
+    gsize                         ssid_len        = strlen(id->name);
+    gboolean                      autoconnectable = TRUE;
+    gboolean                      hidden          = FALSE;
+    gboolean                      exact_match     = TRUE;
+    const char                   *key_mgmt        = NULL;
 
     if (known_network) {
         autoconnectable = get_property_bool(known_network, "AutoConnect", TRUE);
@@ -686,10 +759,10 @@ mirror_connection(NMIwdManager *        self,
 
     for (iter = nm_settings_get_connections(priv->settings, NULL); *iter; iter++) {
         NMSettingsConnection *sett_conn = *iter;
-        NMConnection *        conn      = nm_settings_connection_get_connection(sett_conn);
+        NMConnection         *conn      = nm_settings_connection_get_connection(sett_conn);
         NMIwdNetworkSecurity  security;
-        NMSettingWireless *   s_wifi;
-        const guint8 *        ssid_bytes;
+        NMSettingWireless    *s_wifi;
+        const guint8         *ssid_bytes;
         gsize                 ssid_len2;
 
         if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security))
@@ -770,7 +843,7 @@ mirror_connection(NMIwdManager *        self,
         if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) {
             NMConnection *tmp_conn = nm_settings_connection_get_connection(settings_connection);
             NMSettingConnection *s_conn = nm_connection_get_setting_connection(tmp_conn);
-            NMSettingWireless *  s_wifi = nm_connection_get_setting_wireless(tmp_conn);
+            NMSettingWireless   *s_wifi = nm_connection_get_setting_wireless(tmp_conn);
 
             g_object_set(G_OBJECT(s_conn),
                          NM_SETTING_CONNECTION_AUTOCONNECT,
@@ -912,14 +985,14 @@ mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, KnownNetworkD
 
 static void
 interface_added(GDBusObjectManager *object_manager,
-                GDBusObject *       object,
-                GDBusInterface *    interface,
+                GDBusObject        *object,
+                GDBusInterface     *interface,
                 gpointer            user_data)
 {
-    NMIwdManager *       self = user_data;
+    NMIwdManager        *self = user_data;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GDBusProxy *         proxy;
-    const char *         iface_name;
+    GDBusProxy          *proxy;
+    const char          *iface_name;
 
     if (!priv->running)
         return;
@@ -935,11 +1008,11 @@ interface_added(GDBusObjectManager *object_manager,
     }
 
     if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
-        KnownNetworkId *      id;
-        KnownNetworkId *      orig_id;
-        KnownNetworkData *    data;
+        KnownNetworkId       *id;
+        KnownNetworkId       *orig_id;
+        KnownNetworkData     *data;
         NMIwdNetworkSecurity  security;
-        const char *          type_str, *name;
+        const char           *type_str, *name;
         NMSettingsConnection *sett_conn = NULL;
 
         type_str = get_property_string_or_null(proxy, "Type");
@@ -998,18 +1071,34 @@ interface_added(GDBusObjectManager *object_manager,
 
         return;
     }
+
+    if (nm_streq(iface_name, NM_IWD_P2P_INTERFACE)) {
+        add_p2p_device(self, proxy, object);
+        return;
+    }
+
+    if (nm_streq(iface_name, NM_IWD_P2P_PEER_INTERFACE)) {
+        NMDeviceIwdP2P *p2p = get_p2p_device_from_peer(self, proxy);
+
+        /* This is more conveniently done with a direct call than a signal because
+         * this way we only notify the interested NMDeviceIwdP2P.  */
+        if (p2p)
+            nm_device_iwd_p2p_peer_add_remove(p2p, object, TRUE);
+
+        return;
+    }
 }
 
 static void
 interface_removed(GDBusObjectManager *object_manager,
-                  GDBusObject *       object,
-                  GDBusInterface *    interface,
+                  GDBusObject        *object,
+                  GDBusInterface     *interface,
                   gpointer            user_data)
 {
-    NMIwdManager *       self = user_data;
+    NMIwdManager        *self = user_data;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GDBusProxy *         proxy;
-    const char *         iface_name;
+    GDBusProxy          *proxy;
+    const char          *iface_name;
 
     g_return_if_fail(G_IS_DBUS_PROXY(interface));
 
@@ -1023,7 +1112,7 @@ interface_removed(GDBusObjectManager *object_manager,
 
     if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) {
         KnownNetworkId id;
-        const char *   type_str;
+        const char    *type_str;
 
         type_str = get_property_string_or_null(proxy, "Type");
         id.name  = get_property_string_or_null(proxy, "Name");
@@ -1051,6 +1140,20 @@ interface_removed(GDBusObjectManager *object_manager,
 
         return;
     }
+
+    if (nm_streq(iface_name, NM_IWD_P2P_INTERFACE)) {
+        remove_p2p_device(self, proxy, object);
+        return;
+    }
+
+    if (nm_streq(iface_name, NM_IWD_P2P_PEER_INTERFACE)) {
+        NMDeviceIwdP2P *p2p = get_p2p_device_from_peer(self, proxy);
+
+        if (p2p)
+            nm_device_iwd_p2p_peer_add_remove(p2p, object, FALSE);
+
+        return;
+    }
 }
 
 static void
@@ -1088,19 +1191,19 @@ object_removed(GDBusObjectManager *object_manager, GDBusObject *object, gpointer
 static void
 connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
 {
-    NMIwdManager *        self = user_data;
-    NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    NMConnection *        conn = nm_settings_connection_get_connection(sett_conn);
-    NMSettingWireless *   s_wireless;
-    KnownNetworkData *    data;
+    NMIwdManager         *self = user_data;
+    NMIwdManagerPrivate  *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    NMConnection         *conn = nm_settings_connection_get_connection(sett_conn);
+    NMSettingWireless    *s_wireless;
+    KnownNetworkData     *data;
     KnownNetworkId        id;
     char                  ssid_buf[33];
-    const guint8 *        ssid_bytes;
+    const guint8         *ssid_bytes;
     gsize                 ssid_len;
     NMSettingsConnection *new_mirror_conn;
-    const char *          iwd_dir;
-    gs_free char *        filename  = NULL;
-    gs_free char *        full_path = NULL;
+    const char           *iwd_dir;
+    gs_free char         *filename  = NULL;
+    gs_free char         *full_path = NULL;
 
     if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security))
         return;
@@ -1170,13 +1273,13 @@ try_delete_file:
 static void
 connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
 {
-    NMIwdManager *       self   = user_data;
-    NMConnection *       conn   = nm_settings_connection_get_connection(sett_conn);
-    NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn);
-    const char *         iwd_dir;
-    gs_free char *       filename              = NULL;
-    gs_free char *       full_path             = NULL;
-    gs_free_error GError *error                = NULL;
+    NMIwdManager                   *self   = user_data;
+    NMConnection                   *conn   = nm_settings_connection_get_connection(sett_conn);
+    NMSettingConnection            *s_conn = nm_connection_get_setting_connection(conn);
+    const char                     *iwd_dir;
+    gs_free char                   *filename   = NULL;
+    gs_free char                   *full_path  = NULL;
+    gs_free_error GError           *error      = NULL;
     nm_auto_unref_keyfile GKeyFile *iwd_config = NULL;
     NMSettingsConnectionIntFlags    flags;
 
@@ -1259,7 +1362,7 @@ release_object_manager(NMIwdManager *self)
     g_signal_handlers_disconnect_by_data(priv->object_manager, self);
 
     if (priv->agent_id) {
-        GDBusConnection *         agent_connection;
+        GDBusConnection          *agent_connection;
         GDBusObjectManagerClient *omc = G_DBUS_OBJECT_MANAGER_CLIENT(priv->object_manager);
 
         agent_connection = g_dbus_object_manager_client_get_connection(omc);
@@ -1281,9 +1384,9 @@ static void prepare_object_manager(NMIwdManager *self);
 static void
 name_owner_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
 {
-    NMIwdManager *       self           = user_data;
+    NMIwdManager        *self           = user_data;
     NMIwdManagerPrivate *priv           = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GDBusObjectManager * object_manager = G_DBUS_OBJECT_MANAGER(object);
+    GDBusObjectManager  *object_manager = G_DBUS_OBJECT_MANAGER(object);
 
     nm_assert(object_manager == priv->object_manager);
 
@@ -1292,7 +1395,7 @@ name_owner_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
         prepare_object_manager(self);
     } else {
         const CList *tmp_lst;
-        NMDevice *   device;
+        NMDevice    *device;
 
         if (!priv->running)
             return;
@@ -1310,9 +1413,9 @@ name_owner_changed(GObject *object, GParamSpec *pspec, gpointer user_data)
 static void
 device_added(NMManager *manager, NMDevice *device, gpointer user_data)
 {
-    NMIwdManager *       self = user_data;
+    NMIwdManager        *self = user_data;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GList *              objects, *iter;
+    GList               *objects, *iter;
 
     if (!NM_IS_DEVICE_IWD(device))
         return;
@@ -1331,7 +1434,7 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data)
     objects = g_dbus_object_manager_get_objects(priv->object_manager);
 
     for (iter = objects; iter; iter = iter->next) {
-        GDBusObject *   object                    = G_DBUS_OBJECT(iter->data);
+        GDBusObject                    *object    = G_DBUS_OBJECT(iter->data);
         gs_unref_object GDBusInterface *interface = NULL;
 
         interface = g_dbus_object_get_interface(object, NM_IWD_NETWORK_INTERFACE);
@@ -1343,9 +1446,9 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data)
     }
 
     for (iter = objects; iter; iter = iter->next) {
-        GDBusObject *   object                    = G_DBUS_OBJECT(iter->data);
+        GDBusObject                    *object    = G_DBUS_OBJECT(iter->data);
         gs_unref_object GDBusInterface *interface = NULL;
-        const char *                    obj_ifname;
+        const char                     *obj_ifname;
 
         interface  = g_dbus_object_get_interface(object, NM_IWD_DEVICE_INTERFACE);
         obj_ifname = get_property_string_or_null((GDBusProxy *) interface, "Name");
@@ -1363,7 +1466,7 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data)
 static void
 device_removed(NMManager *manager, NMDevice *device, gpointer user_data)
 {
-    NMIwdManager *       self = user_data;
+    NMIwdManager        *self = user_data;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
 
     if (!NM_IS_DEVICE_IWD(device))
@@ -1431,13 +1534,13 @@ object_compare_interfaces(gconstpointer a, gconstpointer b)
 static void
 get_daemon_info_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 {
-    NMIwdManager *       self = user_data;
-    NMIwdManagerPrivate *priv;
+    NMIwdManager              *self = user_data;
+    NMIwdManagerPrivate       *priv;
     gs_unref_variant GVariant *properties = NULL;
-    gs_free_error GError *error           = NULL;
-    GVariantIter *        properties_iter;
-    const char *          key;
-    GVariant *            value;
+    gs_free_error GError      *error      = NULL;
+    GVariantIter              *properties_iter;
+    const char                *key;
+    GVariant                  *value;
 
     properties = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
     if (!properties) {
@@ -1469,6 +1572,15 @@ get_daemon_info_cb(GObject *source, GAsyncResult *res, gpointer user_data)
 
             nm_clear_g_free(&priv->last_state_dir);
             priv->last_state_dir = g_variant_dup_string(value, NULL);
+        } else if (nm_streq(key, "NetworkConfigurationEnabled")) {
+            if (!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) {
+                _LOGE("Daemon.GetInfo property %s is typed '%s' instead of 'b'",
+                      key,
+                      g_variant_get_type_string(value));
+                goto next;
+            }
+
+            priv->netconfig_enabled = g_variant_get_boolean(value);
         }
 
 next:
@@ -1481,11 +1593,11 @@ next:
 static void
 got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
 {
-    NMIwdManager *       self  = user_data;
+    NMIwdManager        *self  = user_data;
     NMIwdManagerPrivate *priv  = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GError *             error = NULL;
-    GDBusObjectManager * object_manager;
-    GDBusConnection *    connection;
+    GError              *error = NULL;
+    GDBusObjectManager  *object_manager;
+    GDBusConnection     *connection;
 
     object_manager = g_dbus_object_manager_client_new_for_bus_finish(result, &error);
     if (object_manager == NULL) {
@@ -1515,7 +1627,7 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
     }
 
     if (_om_has_name_owner(object_manager)) {
-        GList *         objects, *iter;
+        GList                          *objects, *iter;
         gs_unref_object GDBusInterface *daemon = NULL;
 
         priv->running = true;
@@ -1543,6 +1655,8 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data)
         if (priv->agent_id)
             register_agent(self);
 
+        priv->netconfig_enabled = false; /* Assume false until GetInfo() results come in */
+
         daemon = g_dbus_object_manager_get_interface(object_manager,
                                                      "/net/connman/iwd", /* IWD 1.15+ */
                                                      NM_IWD_DAEMON_INTERFACE);
@@ -1587,11 +1701,11 @@ nm_iwd_manager_is_known_network(NMIwdManager *self, const char *name, NMIwdNetwo
 NMSettingsConnection *
 nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap)
 {
-    NMIwdManagerPrivate *  priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    KnownNetworkData *     data;
+    NMIwdManagerPrivate   *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+    KnownNetworkData      *data;
     char                   name_buf[33];
     KnownNetworkId         kn_id = {name_buf, NM_IWD_NETWORK_SECURITY_OPEN};
-    const guint8 *         ssid_bytes;
+    const guint8          *ssid_bytes;
     gsize                  ssid_len;
     NM80211ApFlags         flags     = nm_wifi_ap_get_flags(ap);
     NM80211ApSecurityFlags sec_flags = nm_wifi_ap_get_wpa_flags(ap) | nm_wifi_ap_get_rsn_flags(ap);
@@ -1629,7 +1743,7 @@ GDBusProxy *
 nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name)
 {
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
-    GDBusInterface *     interface;
+    GDBusInterface      *interface;
 
     if (!priv->object_manager)
         return NULL;
@@ -1639,6 +1753,121 @@ nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const ch
     return interface ? G_DBUS_PROXY(interface) : NULL;
 }
 
+gboolean
+nm_iwd_manager_get_netconfig_enabled(NMIwdManager *self)
+{
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+
+    return priv->netconfig_enabled;
+}
+
+/* IWD's net.connman.iwd.p2p.ServiceManager.RegisterDisplayService() is global so
+ * two local Wi-Fi P2P devices can't be connected to (or even scanning for) WFD
+ * peers using different WFD IE contents, e.g. one as a sink and one as a source.
+ * If one device is connected to a peer without a WFD service, another can try
+ * to establish a WFD connection to a peer since this won't disturb the first
+ * connection.  Similarly if one device is connected to a peer with WFD, another
+ * can make a connection to a non-WFD peer (if that exists...) because a non-WFD
+ * peer will simply ignore the WFD IEs, but it cannot connect to or search for a
+ * peer that's WFD capable without passing our own WFD IEs, i.e. if the new
+ * NMSettingsConnection has no WFD IEs and we're already in a WFD connection on
+ * another device, we can't activate that new connection.  We expose methods
+ * for the NMDeviceIwdP2P's to register/unregister the service and one to check
+ * if there's already an incompatible connection active.
+ */
+gboolean
+nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIwdWfdInfo *wfd_info)
+{
+    NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
+
+    if (priv->wfd_use_count == 0)
+        return TRUE;
+
+    return nm_wifi_utils_wfd_info_eq(&priv->wfd_info, wfd_info);
+}
+
+gboolean
+nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info)
+{
+    NMIwdManagerPrivate            *priv            = NM_IWD_MANAGER_GET_PRIVATE(self);
+    gs_unref_object GDBusInterface *service_manager = NULL;
+    GVariantBuilder                 builder;
+
+    nm_assert(nm_iwd_manager_check_wfd_info_compatible(self, wfd_info));
+
+    if (!priv->object_manager)
+        return FALSE;
+
+    service_manager = g_dbus_object_manager_get_interface(priv->object_manager,
+                                                          "/net/connman/iwd",
+                                                          NM_IWD_P2P_SERVICE_MANAGER_INTERFACE);
+    if (!service_manager) {
+        _LOGE("IWD P2P service manager not found");
+        return FALSE;
+    }
+
+    g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
+    g_variant_builder_add(&builder, "{sv}", "Source", g_variant_new_boolean(wfd_info->source));
+    g_variant_builder_add(&builder, "{sv}", "Sink", g_variant_new_boolean(wfd_info->sink));
+
+    if (wfd_info->source)
+        g_variant_builder_add(&builder, "{sv}", "Port", g_variant_new_uint16(wfd_info->port));
+
+    if (wfd_info->sink && wfd_info->has_audio)
+        g_variant_builder_add(&builder, "{sv}", "HasAudio", g_variant_new_boolean(TRUE));
+
+    if (wfd_info->has_uibc)
+        g_variant_builder_add(&builder, "{sv}", "HasUIBC", g_variant_new_boolean(TRUE));
+
+    if (wfd_info->has_cp)
+        g_variant_builder_add(&builder,
+                              "{sv}",
+                              "HasContentProtection",
+                              g_variant_new_boolean(TRUE));
+
+    g_dbus_proxy_call(G_DBUS_PROXY(service_manager),
+                      "RegisterDisplayService",
+                      g_variant_new("(a{sv})", &builder),
+                      G_DBUS_CALL_FLAGS_NONE,
+                      -1,
+                      NULL,
+                      NULL,
+                      NULL);
+
+    memcpy(&priv->wfd_info, wfd_info, sizeof(priv->wfd_info));
+    priv->wfd_use_count++;
+    return TRUE;
+}
+
+void
+nm_iwd_manager_unregister_wfd(NMIwdManager *self)
+{
+    NMIwdManagerPrivate            *priv            = NM_IWD_MANAGER_GET_PRIVATE(self);
+    gs_unref_object GDBusInterface *service_manager = NULL;
+
+    nm_assert(priv->wfd_use_count > 0);
+
+    priv->wfd_use_count--;
+
+    if (!priv->object_manager)
+        return;
+
+    service_manager = g_dbus_object_manager_get_interface(priv->object_manager,
+                                                          "/net/connman/iwd",
+                                                          NM_IWD_P2P_SERVICE_MANAGER_INTERFACE);
+    if (!service_manager)
+        return;
+
+    g_dbus_proxy_call(G_DBUS_PROXY(service_manager),
+                      "UnregisterDisplayService",
+                      g_variant_new("()"),
+                      G_DBUS_CALL_FLAGS_NONE,
+                      -1,
+                      NULL,
+                      NULL,
+                      NULL);
+}
+
 /*****************************************************************************/
 
 NM_DEFINE_SINGLETON_GETTER(NMIwdManager, nm_iwd_manager_get, NM_TYPE_IWD_MANAGER);
@@ -1685,13 +1914,15 @@ nm_iwd_manager_init(NMIwdManager *self)
                                                  g_free,
                                                  (GDestroyNotify) known_network_data_free);
 
+    priv->p2p_devices = g_hash_table_new_full(nm_str_hash, g_str_equal, g_free, g_object_unref);
+
     prepare_object_manager(self);
 }
 
 static void
 dispose(GObject *object)
 {
-    NMIwdManager *       self = (NMIwdManager *) object;
+    NMIwdManager        *self = (NMIwdManager *) object;
     NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self);
 
     release_object_manager(self);
@@ -1718,6 +1949,8 @@ dispose(GObject *object)
     nm_clear_g_free(&priv->last_state_dir);
     nm_clear_g_free(&priv->warned_state_dir);
 
+    g_hash_table_unref(nm_steal_pointer(&priv->p2p_devices));
+
     G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object);
 }
 
@@ -1727,4 +1960,16 @@ nm_iwd_manager_class_init(NMIwdManagerClass *klass)
     GObjectClass *object_class = G_OBJECT_CLASS(klass);
 
     object_class->dispose = dispose;
+
+    signals[P2P_DEVICE_ADDED] = g_signal_new(NM_IWD_MANAGER_P2P_DEVICE_ADDED,
+                                             G_OBJECT_CLASS_TYPE(object_class),
+                                             G_SIGNAL_RUN_LAST,
+                                             0,
+                                             NULL,
+                                             NULL,
+                                             NULL,
+                                             G_TYPE_NONE,
+                                             2,
+                                             NM_TYPE_DEVICE,
+                                             G_TYPE_STRING);
 }
diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h
index e794e661..02cd6bba 100644
--- a/src/core/devices/wifi/nm-iwd-manager.h
+++ b/src/core/devices/wifi/nm-iwd-manager.h
@@ -13,18 +13,22 @@
 #define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM
 #define NM_IWD_SERVICE  "net.connman.iwd"
 
-#define NM_IWD_DAEMON_INTERFACE        "net.connman.iwd.Daemon"
-#define NM_IWD_AGENT_MANAGER_INTERFACE "net.connman.iwd.AgentManager"
-#define NM_IWD_WIPHY_INTERFACE         "net.connman.iwd.Adapter"
-#define NM_IWD_DEVICE_INTERFACE        "net.connman.iwd.Device"
-#define NM_IWD_NETWORK_INTERFACE       "net.connman.iwd.Network"
-#define NM_IWD_AGENT_INTERFACE         "net.connman.iwd.Agent"
-#define NM_IWD_WSC_INTERFACE           "net.connman.iwd.WiFiSimpleConfiguration"
-#define NM_IWD_KNOWN_NETWORK_INTERFACE "net.connman.iwd.KnownNetwork"
-#define NM_IWD_SIGNAL_AGENT_INTERFACE  "net.connman.iwd.SignalLevelAgent"
-#define NM_IWD_AP_INTERFACE            "net.connman.iwd.AccessPoint"
-#define NM_IWD_ADHOC_INTERFACE         "net.connman.iwd.AdHoc"
-#define NM_IWD_STATION_INTERFACE       "net.connman.iwd.Station"
+#define NM_IWD_DAEMON_INTERFACE              "net.connman.iwd.Daemon"
+#define NM_IWD_AGENT_MANAGER_INTERFACE       "net.connman.iwd.AgentManager"
+#define NM_IWD_WIPHY_INTERFACE               "net.connman.iwd.Adapter"
+#define NM_IWD_DEVICE_INTERFACE              "net.connman.iwd.Device"
+#define NM_IWD_NETWORK_INTERFACE             "net.connman.iwd.Network"
+#define NM_IWD_AGENT_INTERFACE               "net.connman.iwd.Agent"
+#define NM_IWD_WSC_INTERFACE                 "net.connman.iwd.SimpleConfiguration"
+#define NM_IWD_KNOWN_NETWORK_INTERFACE       "net.connman.iwd.KnownNetwork"
+#define NM_IWD_SIGNAL_AGENT_INTERFACE        "net.connman.iwd.SignalLevelAgent"
+#define NM_IWD_AP_INTERFACE                  "net.connman.iwd.AccessPoint"
+#define NM_IWD_ADHOC_INTERFACE               "net.connman.iwd.AdHoc"
+#define NM_IWD_STATION_INTERFACE             "net.connman.iwd.Station"
+#define NM_IWD_P2P_INTERFACE                 "net.connman.iwd.p2p.Device"
+#define NM_IWD_P2P_PEER_INTERFACE            "net.connman.iwd.p2p.Peer"
+#define NM_IWD_P2P_SERVICE_MANAGER_INTERFACE "net.connman.iwd.p2p.ServiceManager"
+#define NM_IWD_P2P_WFD_INTERFACE             "net.connman.iwd.p2p.Display"
 
 #define NM_TYPE_IWD_MANAGER (nm_iwd_manager_get_type())
 #define NM_IWD_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_IWD_MANAGER, NMIwdManager))
@@ -35,6 +39,8 @@
 #define NM_IWD_MANAGER_GET_CLASS(obj) \
     (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_IWD_MANAGER, NMIwdManagerClass))
 
+#define NM_IWD_MANAGER_P2P_DEVICE_ADDED "p2p-device-added"
+
 typedef struct _NMIwdManager      NMIwdManager;
 typedef struct _NMIwdManagerClass NMIwdManagerClass;
 
@@ -42,8 +48,8 @@ GType nm_iwd_manager_get_type(void);
 
 NMIwdManager *nm_iwd_manager_get(void);
 
-gboolean nm_iwd_manager_is_known_network(NMIwdManager *       self,
-                                         const char *         name,
+gboolean nm_iwd_manager_is_known_network(NMIwdManager        *self,
+                                         const char          *name,
                                          NMIwdNetworkSecurity security);
 
 NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap);
@@ -51,4 +57,10 @@ NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self
 GDBusProxy *
 nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name);
 
+gboolean nm_iwd_manager_get_netconfig_enabled(NMIwdManager *self);
+
+gboolean nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIwdWfdInfo *wfd_info);
+gboolean nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info);
+void     nm_iwd_manager_unregister_wfd(NMIwdManager *self);
+
 #endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index 72a60885..ac0b748a 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -41,8 +41,8 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMWifiAP,
 
 struct _NMWifiAPPrivate {
     /* Scanned or cached values */
-    GBytes *     ssid;
-    char *       address;
+    GBytes      *ssid;
+    char        *address;
     _NM80211Mode mode;
     guint8       strength;
     guint32      freq;        /* Frequency in MHz; ie 2412 (== 2.412 GHz) */
@@ -430,7 +430,7 @@ has_proto(NMSettingWirelessSecurity *sec, const char *proto)
 static void
 add_pair_ciphers(NMWifiAP *ap, NMSettingWirelessSecurity *sec)
 {
-    NMWifiAPPrivate *      priv  = NM_WIFI_AP_GET_PRIVATE(ap);
+    NMWifiAPPrivate       *priv  = NM_WIFI_AP_GET_PRIVATE(ap);
     guint32                num   = nm_setting_wireless_security_get_num_pairwise(sec);
     NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
     guint32                i;
@@ -458,7 +458,7 @@ add_pair_ciphers(NMWifiAP *ap, NMSettingWirelessSecurity *sec)
 static void
 add_group_ciphers(NMWifiAP *ap, NMSettingWirelessSecurity *sec)
 {
-    NMWifiAPPrivate *      priv  = NM_WIFI_AP_GET_PRIVATE(ap);
+    NMWifiAPPrivate       *priv  = NM_WIFI_AP_GET_PRIVATE(ap);
     guint32                num   = nm_setting_wireless_security_get_num_groups(sec);
     NM80211ApSecurityFlags flags = NM_802_11_AP_SEC_NONE;
     guint32                i;
@@ -491,10 +491,10 @@ const char *
 nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 now_msec)
 {
     const NMWifiAPPrivate *priv;
-    const char *           supplicant_id = "-";
-    const char *           export_path;
+    const char            *supplicant_id = "-";
+    const char            *export_path;
     guint32                chan;
-    gs_free char *         ssid_to_free = NULL;
+    gs_free char          *ssid_to_free = NULL;
     char                   str_buf_ts[100];
 
     g_return_val_if_fail(NM_IS_WIFI_AP(self), NULL);
@@ -558,13 +558,13 @@ freq_to_band(guint32 freq)
 gboolean
 nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 {
-    NMWifiAPPrivate *          priv;
-    NMSettingWireless *        s_wireless;
+    NMWifiAPPrivate           *priv;
+    NMSettingWireless         *s_wireless;
     NMSettingWirelessSecurity *s_wireless_sec;
-    GBytes *                   ssid;
-    const char *               mode;
-    const char *               band;
-    const char *               bssid;
+    GBytes                    *ssid;
+    const char                *mode;
+    const char                *band;
+    const char                *bssid;
     guint32                    channel;
 
     g_return_val_if_fail(NM_IS_WIFI_AP(self), FALSE);
@@ -629,10 +629,10 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 }
 
 gboolean
-nm_wifi_ap_complete_connection(NMWifiAP *    self,
+nm_wifi_ap_complete_connection(NMWifiAP     *self,
                                NMConnection *connection,
                                gboolean      lock_bssid,
-                               GError **     error)
+                               GError      **error)
 {
     NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self);
 
@@ -655,7 +655,7 @@ nm_wifi_ap_complete_connection(NMWifiAP *    self,
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMWifiAP *       self = NM_WIFI_AP(object);
+    NMWifiAP        *self = NM_WIFI_AP(object);
     NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -734,11 +734,11 @@ nm_wifi_ap_new_from_properties(const NMSupplicantBssInfo *bss_info)
 NMWifiAP *
 nm_wifi_ap_new_fake_from_connection(NMConnection *connection)
 {
-    NMWifiAP *                 ap;
-    NMWifiAPPrivate *          priv;
-    NMSettingWireless *        s_wireless;
+    NMWifiAP                  *ap;
+    NMWifiAPPrivate           *priv;
+    NMSettingWireless         *s_wireless;
     NMSettingWirelessSecurity *s_wireless_sec;
-    const char *               mode, *band, *key_mgmt;
+    const char                *mode, *band, *key_mgmt;
     guint32                    channel;
     NM80211ApSecurityFlags     flags;
     gboolean                   psk = FALSE, eap = FALSE, adhoc = FALSE;
@@ -845,7 +845,7 @@ error:
 static void
 finalize(GObject *object)
 {
-    NMWifiAP *       self = NM_WIFI_AP(object);
+    NMWifiAP        *self = NM_WIFI_AP(object);
     NMWifiAPPrivate *priv = NM_WIFI_AP_GET_PRIVATE(self);
 
     nm_assert(!self->wifi_device);
@@ -890,7 +890,7 @@ nm_wifi_ap_class_init(NMWifiAPClass *ap_class)
      | NM_802_11_AP_SEC_KEY_MGMT_SAE | NM_802_11_AP_SEC_KEY_MGMT_OWE                             \
      | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM | NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)
 
-    GObjectClass *     object_class      = G_OBJECT_CLASS(ap_class);
+    GObjectClass      *object_class      = G_OBJECT_CLASS(ap_class);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(ap_class);
 
     g_type_class_add_private(object_class, sizeof(NMWifiAPPrivate));
@@ -987,10 +987,10 @@ nm_wifi_ap_class_init(NMWifiAPClass *ap_class)
 const char **
 nm_wifi_aps_get_paths(const CList *aps_lst_head, gboolean include_without_ssid)
 {
-    NMWifiAP *   ap;
+    NMWifiAP    *ap;
     gsize        i, n;
     const char **list;
-    const char * path;
+    const char  *path;
 
     n    = c_list_length(aps_lst_head);
     list = g_new(const char *, n + 1);
diff --git a/src/core/devices/wifi/nm-wifi-ap.h b/src/core/devices/wifi/nm-wifi-ap.h
index bffd28a8..c0422900 100644
--- a/src/core/devices/wifi/nm-wifi-ap.h
+++ b/src/core/devices/wifi/nm-wifi-ap.h
@@ -32,9 +32,9 @@
 
 typedef struct {
     NMDBusObject             parent;
-    NMDevice *               wifi_device;
+    NMDevice                *wifi_device;
     CList                    aps_lst;
-    NMRefString *            _supplicant_path;
+    NMRefString             *_supplicant_path;
     struct _NMWifiAPPrivate *_priv;
 } NMWifiAP;
 
@@ -47,15 +47,15 @@ GType nm_wifi_ap_get_type(void);
 NMWifiAP *nm_wifi_ap_new_from_properties(const struct _NMSupplicantBssInfo *bss_info);
 NMWifiAP *nm_wifi_ap_new_fake_from_connection(NMConnection *connection);
 
-gboolean nm_wifi_ap_update_from_properties(NMWifiAP *                         ap,
+gboolean nm_wifi_ap_update_from_properties(NMWifiAP                          *ap,
                                            const struct _NMSupplicantBssInfo *bss_info);
 
 gboolean nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection);
 
-gboolean nm_wifi_ap_complete_connection(NMWifiAP *    self,
+gboolean nm_wifi_ap_complete_connection(NMWifiAP     *self,
                                         NMConnection *connection,
                                         gboolean      lock_bssid,
-                                        GError **     error);
+                                        GError      **error);
 
 static inline NMRefString *
 nm_wifi_ap_get_supplicant_path(NMWifiAP *ap)
@@ -65,9 +65,9 @@ nm_wifi_ap_get_supplicant_path(NMWifiAP *ap)
     return ap->_supplicant_path;
 }
 
-GBytes *               nm_wifi_ap_get_ssid(const NMWifiAP *ap);
+GBytes                *nm_wifi_ap_get_ssid(const NMWifiAP *ap);
 gboolean               nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid);
-const char *           nm_wifi_ap_get_address(const NMWifiAP *ap);
+const char            *nm_wifi_ap_get_address(const NMWifiAP *ap);
 gboolean               nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr);
 gboolean               nm_wifi_ap_set_address_bin(NMWifiAP *ap, const NMEtherAddr *addr);
 _NM80211Mode           nm_wifi_ap_get_mode(NMWifiAP *ap);
diff --git a/src/core/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c
index d029ce7f..681d9cb5 100644
--- a/src/core/devices/wifi/nm-wifi-common.c
+++ b/src/core/devices/wifi/nm-wifi-common.c
@@ -56,17 +56,17 @@ _dispatch_request_scan(NMDevice *device, GVariant *options, GDBusMethodInvocatio
 }
 
 static void
-impl_device_wifi_get_access_points(NMDBusObject *                     obj,
+impl_device_wifi_get_access_points(NMDBusObject                      *obj,
                                    const NMDBusInterfaceInfoExtended *interface_info,
-                                   const NMDBusMethodInfoExtended *   method_info,
-                                   GDBusConnection *                  connection,
-                                   const char *                       sender,
-                                   GDBusMethodInvocation *            invocation,
-                                   GVariant *                         parameters)
+                                   const NMDBusMethodInfoExtended    *method_info,
+                                   GDBusConnection                   *connection,
+                                   const char                        *sender,
+                                   GDBusMethodInvocation             *invocation,
+                                   GVariant                          *parameters)
 {
     gs_free const char **list = NULL;
-    GVariant *           v;
-    const CList *        all_aps;
+    GVariant            *v;
+    const CList         *all_aps;
 
     /* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
 
@@ -77,17 +77,17 @@ impl_device_wifi_get_access_points(NMDBusObject *                     obj,
 }
 
 static void
-impl_device_wifi_get_all_access_points(NMDBusObject *                     obj,
+impl_device_wifi_get_all_access_points(NMDBusObject                      *obj,
                                        const NMDBusInterfaceInfoExtended *interface_info,
-                                       const NMDBusMethodInfoExtended *   method_info,
-                                       GDBusConnection *                  connection,
-                                       const char *                       sender,
-                                       GDBusMethodInvocation *            invocation,
-                                       GVariant *                         parameters)
+                                       const NMDBusMethodInfoExtended    *method_info,
+                                       GDBusConnection                   *connection,
+                                       const char                        *sender,
+                                       GDBusMethodInvocation             *invocation,
+                                       GVariant                          *parameters)
 {
     gs_free const char **list = NULL;
-    GVariant *           v;
-    const CList *        all_aps;
+    GVariant            *v;
+    const CList         *all_aps;
 
     /* NOTE: this handler is called both for NMDevicwWifi and NMDeviceIwd. */
 
@@ -98,13 +98,13 @@ impl_device_wifi_get_all_access_points(NMDBusObject *                     obj,
 }
 
 static void
-impl_device_wifi_request_scan(NMDBusObject *                     obj,
+impl_device_wifi_request_scan(NMDBusObject                      *obj,
                               const NMDBusInterfaceInfoExtended *interface_info,
-                              const NMDBusMethodInfoExtended *   method_info,
-                              GDBusConnection *                  connection,
-                              const char *                       sender,
-                              GDBusMethodInvocation *            invocation,
-                              GVariant *                         parameters)
+                              const NMDBusMethodInfoExtended    *method_info,
+                              GDBusConnection                   *connection,
+                              const char                        *sender,
+                              GDBusMethodInvocation             *invocation,
+                              GVariant                          *parameters)
 {
     gs_unref_variant GVariant *options = NULL;
 
diff --git a/src/core/devices/wifi/nm-wifi-factory.c b/src/core/devices/wifi/nm-wifi-factory.c
index d2269c74..5e354c6b 100644
--- a/src/core/devices/wifi/nm-wifi-factory.c
+++ b/src/core/devices/wifi/nm-wifi-factory.c
@@ -14,6 +14,8 @@
 #include "nm-device-wifi-p2p.h"
 #include "nm-device-olpc-mesh.h"
 #include "nm-device-iwd.h"
+#include "nm-device-iwd-p2p.h"
+#include "nm-iwd-manager.h"
 #include "settings/nm-settings-connection.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-config.h"
@@ -50,7 +52,7 @@ NM_DEVICE_FACTORY_DECLARE_TYPES(
                                                 NM_SETTING_OLPC_MESH_SETTING_NAME))
 
 G_MODULE_EXPORT NMDeviceFactory *
-                nm_device_factory_create(GError **error)
+nm_device_factory_create(GError **error)
 {
     return g_object_new(NM_TYPE_WIFI_FACTORY, NULL);
 }
@@ -67,15 +69,29 @@ p2p_device_created(NMDeviceWifi *device, NMDeviceWifiP2P *p2p_device, NMDeviceFa
     g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
 }
 
+#if WITH_IWD
+static void
+iwd_p2p_device_added(NMIwdManager    *iwd,
+                     NMDeviceIwdP2P  *p2p_device,
+                     const char      *phy_name,
+                     NMDeviceFactory *self)
+{
+    nm_log_info(LOGD_PLATFORM | LOGD_WIFI, "Wi-Fi P2P device added on %s", phy_name);
+
+    g_signal_emit_by_name(self, NM_DEVICE_FACTORY_DEVICE_ADDED, p2p_device);
+}
+#endif
+
 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)
 {
     gs_free char *backend_free = NULL;
-    const char *  backend;
+    const char   *backend;
+    _NM80211Mode  mode;
 
     g_return_val_if_fail(iface != NULL, NULL);
     g_return_val_if_fail(plink != NULL, NULL);
@@ -85,6 +101,20 @@ create_device(NMDeviceFactory *     factory,
     if (plink->type != NM_LINK_TYPE_WIFI)
         return nm_device_olpc_mesh_new(iface);
 
+    /* Ignore monitor-mode and other unhandled interface types.
+     * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
+     * them if/when they change to a handled type.
+     */
+    mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex);
+    if (!NM_IN_SET(mode,
+                   _NM_802_11_MODE_INFRA,
+                   _NM_802_11_MODE_ADHOC,
+                   _NM_802_11_MODE_AP,
+                   _NM_802_11_MODE_MESH)) {
+        *out_ignore = TRUE;
+        return NULL;
+    }
+
     backend = nm_config_data_get_device_config_by_pllink(NM_CONFIG_GET_DATA,
                                                          NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND,
                                                          plink,
@@ -92,15 +122,17 @@ create_device(NMDeviceFactory *     factory,
                                                          NULL);
     backend = nm_strstrip_avoid_copy_a(300, backend, &backend_free);
 
+    if (!backend)
+        backend = "" NM_CONFIG_DEFAULT_WIFI_BACKEND;
+
     nm_log_dbg(LOGD_PLATFORM | LOGD_WIFI,
                "(%s) config: backend is %s%s%s%s",
                iface,
                NM_PRINT_FMT_QUOTE_STRING(backend),
                WITH_IWD ? " (iwd support enabled)" : "");
-    if (!backend || !g_ascii_strcasecmp(backend, "wpa_supplicant")) {
-        NMDevice *                device;
+    if (!g_ascii_strcasecmp(backend, "wpa_supplicant")) {
+        NMDevice                 *device;
         _NMDeviceWifiCapabilities capabilities;
-        _NM80211Mode              mode;
 
         if (!nm_platform_wifi_get_capabilities(NM_PLATFORM_GET, plink->ifindex, &capabilities)) {
             nm_log_warn(LOGD_PLATFORM | LOGD_WIFI,
@@ -110,16 +142,6 @@ create_device(NMDeviceFactory *     factory,
             return NULL;
         }
 
-        /* Ignore monitor-mode and other unhandled interface types.
-         * FIXME: keep TYPE_MONITOR devices in UNAVAILABLE state and manage
-         * them if/when they change to a handled type.
-         */
-        mode = nm_platform_wifi_get_mode(NM_PLATFORM_GET, plink->ifindex);
-        if (mode == _NM_802_11_MODE_UNKNOWN) {
-            *out_ignore = TRUE;
-            return NULL;
-        }
-
         device = nm_device_wifi_new(iface, capabilities);
 
         g_signal_connect_object(device,
@@ -131,8 +153,23 @@ create_device(NMDeviceFactory *     factory,
         return device;
     }
 #if WITH_IWD
-    else if (!g_ascii_strcasecmp(backend, "iwd"))
+    else if (!g_ascii_strcasecmp(backend, "iwd")) {
+        NMIwdManager *iwd = nm_iwd_manager_get();
+
+        if (!g_signal_handler_find(iwd,
+                                   G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA,
+                                   0,
+                                   0,
+                                   NULL,
+                                   G_CALLBACK(iwd_p2p_device_added),
+                                   factory))
+            g_signal_connect(iwd,
+                             NM_IWD_MANAGER_P2P_DEVICE_ADDED,
+                             G_CALLBACK(iwd_p2p_device_added),
+                             factory);
+
         return nm_device_iwd_new(iface);
+    }
 #endif
 
     nm_log_warn(LOGD_PLATFORM | LOGD_WIFI,
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c
index ac13952d..0a174270 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c
@@ -18,6 +18,7 @@
 #include "nm-setting-wireless.h"
 #include "nm-utils.h"
 #include "nm-wifi-utils.h"
+#include "nm-iwd-manager.h"
 #include "libnm-platform/nm-platform.h"
 #include "supplicant/nm-supplicant-types.h"
 
@@ -77,8 +78,8 @@ const char **
 nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head)
 {
     NMWifiP2PPeer *peer;
-    const char **  list;
-    const char *   path;
+    const char   **list;
+    const char    *path;
     gsize          i, n;
 
     n    = c_list_length(peers_lst_head);
@@ -100,14 +101,16 @@ nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head)
 }
 
 NMWifiP2PPeer *
-nm_wifi_p2p_peers_find_first_compatible(const CList *peers_lst_head, NMConnection *connection)
+nm_wifi_p2p_peers_find_first_compatible(const CList  *peers_lst_head,
+                                        NMConnection *connection,
+                                        gboolean      check_wfd)
 {
     NMWifiP2PPeer *peer;
 
     g_return_val_if_fail(connection, NULL);
 
     c_list_for_each_entry (peer, peers_lst_head, peers_lst) {
-        if (nm_wifi_p2p_peer_check_compatible(peer, connection))
+        if (nm_wifi_p2p_peer_check_compatible(peer, connection, check_wfd))
             return peer;
     }
     return NULL;
@@ -261,7 +264,7 @@ nm_wifi_p2p_peer_get_wfd_ies(const NMWifiP2PPeer *peer)
 gboolean
 nm_wifi_p2p_peer_set_wfd_ies(NMWifiP2PPeer *peer, GBytes *wfd_ies)
 {
-    NMWifiP2PPeerPrivate *priv;
+    NMWifiP2PPeerPrivate  *priv;
     gs_unref_bytes GBytes *wfd_ies_old = NULL;
 
     g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE);
@@ -419,12 +422,96 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP
     return changed;
 }
 
+gboolean
+nm_wifi_p2p_peer_update_from_iwd_object(NMWifiP2PPeer *peer, GDBusObject *obj)
+{
+    NMWifiP2PPeerPrivate           *priv;
+    gboolean                        changed    = FALSE;
+    nm_auto_ref_string NMRefString *peer_path  = NULL;
+    gs_unref_object GDBusProxy     *peer_proxy = NULL;
+    gs_unref_object GDBusProxy     *wfd_proxy  = NULL;
+    GVariant                       *value;
+    gs_unref_bytes GBytes          *wfd_ies = NULL;
+
+    g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(peer), FALSE);
+
+    peer_proxy = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_PEER_INTERFACE));
+    wfd_proxy  = G_DBUS_PROXY(g_dbus_object_get_interface(obj, NM_IWD_P2P_WFD_INTERFACE));
+    g_return_val_if_fail(peer_proxy, FALSE);
+
+    peer_path = nm_ref_string_new(g_dbus_object_get_object_path(obj));
+    priv      = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
+    nm_assert(!priv->supplicant_path || priv->supplicant_path == peer_path);
+
+    g_object_freeze_notify(G_OBJECT(peer));
+
+    if (!priv->supplicant_path) {
+        priv->supplicant_path = g_steal_pointer(&peer_path);
+        changed               = TRUE;
+    }
+
+    value = g_dbus_proxy_get_cached_property(peer_proxy, "Name");
+    if (value && g_variant_is_of_type(value, G_VARIANT_TYPE_STRING))
+        changed |= nm_wifi_p2p_peer_set_name(peer, g_variant_get_string(value, NULL));
+    else
+        changed |= nm_wifi_p2p_peer_set_name(peer, "");
+    nm_g_variant_unref(value);
+
+    value = g_dbus_proxy_get_cached_property(peer_proxy, "Address");
+    if (value && g_variant_is_of_type(value, G_VARIANT_TYPE_STRING))
+        changed |= nm_wifi_p2p_peer_set_address(peer, g_variant_get_string(value, NULL));
+    nm_g_variant_unref(value);
+
+    if (wfd_proxy) {
+        NMIwdWfdInfo wfd = {};
+
+        value      = g_dbus_proxy_get_cached_property(wfd_proxy, "Source");
+        wfd.source = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)
+                     && g_variant_get_boolean(value);
+        nm_g_variant_unref(value);
+
+        value    = g_dbus_proxy_get_cached_property(wfd_proxy, "Sink");
+        wfd.sink = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)
+                   && g_variant_get_boolean(value);
+        nm_g_variant_unref(value);
+
+        value    = g_dbus_proxy_get_cached_property(wfd_proxy, "Port");
+        wfd.port = (value && g_variant_is_of_type(value, G_VARIANT_TYPE_UINT16))
+                       ? g_variant_get_uint16(value)
+                       : 0;
+        nm_g_variant_unref(value);
+
+        value         = g_dbus_proxy_get_cached_property(wfd_proxy, "HasAudio");
+        wfd.has_audio = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)
+                        && g_variant_get_boolean(value);
+        nm_g_variant_unref(value);
+
+        value        = g_dbus_proxy_get_cached_property(wfd_proxy, "HasUIBC");
+        wfd.has_uibc = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)
+                       && g_variant_get_boolean(value);
+        nm_g_variant_unref(value);
+
+        value      = g_dbus_proxy_get_cached_property(wfd_proxy, "HasContentProtection");
+        wfd.has_cp = value && g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)
+                     && g_variant_get_boolean(value);
+        nm_g_variant_unref(value);
+
+        wfd_ies = nm_wifi_utils_build_wfd_ies(&wfd);
+    }
+
+    changed |= nm_wifi_p2p_peer_set_wfd_ies(peer, wfd_ies);
+
+    g_object_thaw_notify(G_OBJECT(peer));
+
+    return changed;
+}
+
 const char *
 nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_len, gint32 now_s)
 {
     const NMWifiP2PPeerPrivate *priv;
-    const char *                supplicant_id = "-";
-    const char *                export_path;
+    const char                 *supplicant_id = "-";
+    const char                 *export_path;
 
     g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(self), NULL);
 
@@ -458,11 +545,11 @@ nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_l
 }
 
 gboolean
-nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection)
+nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection, gboolean check_wfd)
 {
     NMWifiP2PPeerPrivate *priv;
-    NMSettingWifiP2P *    s_wifi_p2p;
-    const char *          hwaddr;
+    NMSettingWifiP2P     *s_wifi_p2p;
+    const char           *hwaddr;
 
     g_return_val_if_fail(NM_IS_WIFI_P2P_PEER(self), FALSE);
     g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
@@ -478,6 +565,10 @@ nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection)
     if (hwaddr && (!priv->address || !nm_utils_hwaddr_matches(hwaddr, -1, priv->address, -1)))
         return FALSE;
 
+    if (check_wfd && nm_setting_wifi_p2p_get_wfd_ies(s_wifi_p2p)
+        && !nm_wifi_p2p_peer_get_wfd_ies(self))
+        return FALSE;
+
     return TRUE;
 }
 
@@ -486,7 +577,7 @@ nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection)
 static void
 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 {
-    NMWifiP2PPeer *       self = NM_WIFI_P2P_PEER(object);
+    NMWifiP2PPeer        *self = NM_WIFI_P2P_PEER(object);
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self);
 
     switch (prop_id) {
@@ -559,10 +650,21 @@ nm_wifi_p2p_peer_new_from_properties(const NMSupplicantPeerInfo *peer_info)
     return peer;
 }
 
+NMWifiP2PPeer *
+nm_wifi_p2p_peer_new_from_iwd_object(GDBusObject *obj)
+{
+    NMWifiP2PPeer *peer;
+
+    /* TODO: Set the flags here */
+    peer = g_object_new(NM_TYPE_WIFI_P2P_PEER, NULL);
+    nm_wifi_p2p_peer_update_from_iwd_object(peer, obj);
+    return peer;
+}
+
 static void
 finalize(GObject *object)
 {
-    NMWifiP2PPeer *       self = NM_WIFI_P2P_PEER(object);
+    NMWifiP2PPeer        *self = NM_WIFI_P2P_PEER(object);
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(self);
 
     nm_assert(!self->wifi_device);
@@ -613,7 +715,7 @@ static const NMDBusInterfaceInfoExtended interface_info_p2p_peer = {
 static void
 nm_wifi_p2p_peer_class_init(NMWifiP2PPeerClass *klass)
 {
-    GObjectClass *     object_class      = G_OBJECT_CLASS(klass);
+    GObjectClass      *object_class      = G_OBJECT_CLASS(klass);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
 
     g_type_class_add_private(object_class, sizeof(NMWifiP2PPeerPrivate));
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.h b/src/core/devices/wifi/nm-wifi-p2p-peer.h
index ee4bfb53..5124d1de 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.h
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.h
@@ -33,7 +33,7 @@
 
 typedef struct {
     NMDBusObject                  parent;
-    NMDevice *                    wifi_device;
+    NMDevice                     *wifi_device;
     CList                         peers_lst;
     struct _NMWifiP2PPeerPrivate *_priv;
 } NMWifiP2PPeer;
@@ -45,11 +45,15 @@ struct _NMSupplicantPeerInfo;
 GType nm_wifi_p2p_peer_get_type(void);
 
 NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_properties(const struct _NMSupplicantPeerInfo *peer_info);
+NMWifiP2PPeer *nm_wifi_p2p_peer_new_from_iwd_object(GDBusObject *obj);
 
-gboolean nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *                     peer,
+gboolean nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer                      *peer,
                                                  const struct _NMSupplicantPeerInfo *peer_info);
+gboolean nm_wifi_p2p_peer_update_from_iwd_object(NMWifiP2PPeer *peer, GDBusObject *obj);
 
-gboolean nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self, NMConnection *connection);
+gboolean nm_wifi_p2p_peer_check_compatible(NMWifiP2PPeer *self,
+                                           NMConnection  *connection,
+                                           gboolean       check_wfd);
 
 const char *nm_wifi_p2p_peer_get_supplicant_path(NMWifiP2PPeer *peer);
 
@@ -64,12 +68,12 @@ gboolean    nm_wifi_p2p_peer_set_model_number(NMWifiP2PPeer *peer, const char *n
 const char *nm_wifi_p2p_peer_get_serial(const NMWifiP2PPeer *peer);
 gboolean    nm_wifi_p2p_peer_set_serial(NMWifiP2PPeer *peer, const char *serial);
 
-GBytes * nm_wifi_p2p_peer_get_wfd_ies(const NMWifiP2PPeer *peer);
+GBytes  *nm_wifi_p2p_peer_get_wfd_ies(const NMWifiP2PPeer *peer);
 gboolean nm_wifi_p2p_peer_set_wfd_ies(NMWifiP2PPeer *peer, GBytes *bytes);
 
 const char *const *nm_wifi_p2p_peer_get_groups(const NMWifiP2PPeer *peer);
 
-const char *   nm_wifi_p2p_peer_get_address(const NMWifiP2PPeer *peer);
+const char    *nm_wifi_p2p_peer_get_address(const NMWifiP2PPeer *peer);
 gboolean       nm_wifi_p2p_peer_set_address(NMWifiP2PPeer *peer, const char *addr);
 gint8          nm_wifi_p2p_peer_get_strength(NMWifiP2PPeer *peer);
 gboolean       nm_wifi_p2p_peer_set_strength(NMWifiP2PPeer *peer, gint8 strength);
@@ -80,11 +84,12 @@ nm_wifi_p2p_peer_to_string(const NMWifiP2PPeer *self, char *str_buf, gsize buf_l
 
 const char **nm_wifi_p2p_peers_get_paths(const CList *peers_lst_head);
 
-NMWifiP2PPeer *nm_wifi_p2p_peers_find_first_compatible(const CList * peers_lst_head,
-                                                       NMConnection *connection);
+NMWifiP2PPeer *nm_wifi_p2p_peers_find_first_compatible(const CList  *peers_lst_head,
+                                                       NMConnection *connection,
+                                                       gboolean      check_wfd);
 
 NMWifiP2PPeer *nm_wifi_p2p_peers_find_by_supplicant_path(const CList *peers_lst_head,
-                                                         const char * path);
+                                                         const char  *path);
 
 NMWifiP2PPeer *nm_wifi_p2p_peer_lookup_for_device(NMDevice *device, const char *exported_path);
 
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index 4939e786..1a8204b0 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -41,9 +41,9 @@ verify_no_wep(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error
 
 static gboolean
 verify_leap(NMSettingWirelessSecurity *s_wsec,
-            NMSetting8021x *           s_8021x,
+            NMSetting8021x            *s_8021x,
             gboolean                   adhoc,
-            GError **                  error)
+            GError                   **error)
 {
     const char *key_mgmt, *auth_alg, *leap_username;
 
@@ -224,9 +224,9 @@ verify_no_wpa(NMSettingWirelessSecurity *s_wsec, const char *tag, GError **error
 
 static gboolean
 verify_dynamic_wep(NMSettingWirelessSecurity *s_wsec,
-                   NMSetting8021x *           s_8021x,
+                   NMSetting8021x            *s_8021x,
                    gboolean                   adhoc,
-                   GError **                  error)
+                   GError                   **error)
 {
     const char *key_mgmt, *auth_alg, *leap_username;
 
@@ -303,11 +303,11 @@ verify_dynamic_wep(NMSettingWirelessSecurity *s_wsec,
 
 static gboolean
 verify_wpa_psk(NMSettingWirelessSecurity *s_wsec,
-               NMSetting8021x *           s_8021x,
+               NMSetting8021x            *s_8021x,
                gboolean                   adhoc,
                guint32                    wpa_flags,
                guint32                    rsn_flags,
-               GError **                  error)
+               GError                   **error)
 {
     const char *key_mgmt, *auth_alg;
 
@@ -400,10 +400,10 @@ verify_wpa_psk(NMSettingWirelessSecurity *s_wsec,
 
 static gboolean
 verify_wpa_eap(NMSettingWirelessSecurity *s_wsec,
-               NMSetting8021x *           s_8021x,
+               NMSetting8021x            *s_8021x,
                guint32                    wpa_flags,
                guint32                    rsn_flags,
-               GError **                  error)
+               GError                   **error)
 {
     const char *key_mgmt, *auth_alg;
     gboolean    is_wpa_eap = FALSE;
@@ -465,9 +465,9 @@ verify_wpa_eap(NMSettingWirelessSecurity *s_wsec,
 
 static gboolean
 verify_adhoc(NMSettingWirelessSecurity *s_wsec,
-             NMSetting8021x *           s_8021x,
+             NMSetting8021x            *s_8021x,
              gboolean                   adhoc,
-             GError **                  error)
+             GError                   **error)
 {
     const char *key_mgmt = NULL, *leap_username = NULL, *auth_alg = NULL;
 
@@ -529,8 +529,8 @@ verify_adhoc(NMSettingWirelessSecurity *s_wsec,
 }
 
 gboolean
-nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
-                                  const char *  bssid,
+nm_wifi_utils_complete_connection(GBytes       *ap_ssid,
+                                  const char   *bssid,
                                   _NM80211Mode  ap_mode,
                                   guint32       ap_freq,
                                   guint32       ap_flags,
@@ -538,13 +538,13 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
                                   guint32       ap_rsn_flags,
                                   NMConnection *connection,
                                   gboolean      lock_bssid,
-                                  GError **     error)
+                                  GError      **error)
 {
-    NMSettingWireless *        s_wifi;
+    NMSettingWireless         *s_wifi;
     NMSettingWirelessSecurity *s_wsec;
-    NMSetting8021x *           s_8021x;
-    GBytes *                   ssid;
-    const char *               mode, *key_mgmt, *auth_alg, *leap_username;
+    NMSetting8021x            *s_8021x;
+    GBytes                    *ssid;
+    const char                *mode, *key_mgmt, *auth_alg, *leap_username;
     gboolean                   adhoc = FALSE;
     gboolean                   mesh  = FALSE;
 
@@ -892,20 +892,20 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid)
 
 /* To be used for connections where the SSID has been validated before */
 gboolean
-nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
-                                             char **               ssid,
+nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
+                                             char                **ssid,
                                              NMIwdNetworkSecurity *security)
 {
-    NMSettingWireless *        s_wireless;
+    NMSettingWireless         *s_wireless;
     NMSettingWirelessSecurity *s_wireless_sec;
-    const char *               key_mgmt = NULL;
+    const char                *key_mgmt = NULL;
 
     s_wireless = nm_connection_get_setting_wireless(connection);
     if (!s_wireless)
         return FALSE;
 
     if (ssid) {
-        GBytes *    bytes = nm_setting_wireless_get_ssid(s_wireless);
+        GBytes     *bytes = nm_setting_wireless_get_ssid(s_wireless);
         gsize       ssid_len;
         const char *ssid_str = (const char *) g_bytes_get_data(bytes, &ssid_len);
 
@@ -947,7 +947,7 @@ nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
  * @ssid_len can be -1 instead of actual SSID length.
  */
 char *
-nm_wifi_utils_get_iwd_config_filename(const char *         ssid,
+nm_wifi_utils_get_iwd_config_filename(const char          *ssid,
                                       gssize               ssid_len,
                                       NMIwdNetworkSecurity security)
 {
@@ -995,10 +995,10 @@ static gboolean
 psk_setting_to_iwd_config(GKeyFile *file, NMSettingWirelessSecurity *s_wsec, GError **error)
 {
     NMSettingSecretFlags psk_flags = nm_setting_wireless_security_get_psk_flags(s_wsec);
-    const char *         psk       = nm_setting_wireless_security_get_psk(s_wsec);
+    const char          *psk       = nm_setting_wireless_security_get_psk(s_wsec);
     gsize                psk_len;
     guint8               buffer[32];
-    const char *         key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
+    const char          *key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wsec);
 
     if (!psk || NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) {
         if (NM_FLAGS_ANY(psk_flags, SECRETS_DONT_STORE_FLAGS)) {
@@ -1029,11 +1029,11 @@ psk_setting_to_iwd_config(GKeyFile *file, NMSettingWirelessSecurity *s_wsec, GEr
 }
 
 static gboolean
-eap_certs_to_iwd_config(GKeyFile *      file,
+eap_certs_to_iwd_config(GKeyFile       *file,
                         NMSetting8021x *s_8021x,
                         bool            phase2,
-                        char *          iwd_prefix,
-                        GError **       error)
+                        char           *iwd_prefix,
+                        GError        **error)
 {
     NMSetting8021xCKScheme ca_cert_scheme =
         phase2 ? nm_setting_802_1x_get_phase2_ca_cert_scheme(s_8021x)
@@ -1043,13 +1043,13 @@ eap_certs_to_iwd_config(GKeyFile *      file,
                : nm_setting_802_1x_get_client_cert_scheme(s_8021x);
     NMSetting8021xCKScheme key_scheme;
     NMSettingSecretFlags   key_password_flags;
-    const char *           ca_path = phase2 ? nm_setting_802_1x_get_phase2_ca_path(s_8021x)
+    const char            *ca_path = phase2 ? nm_setting_802_1x_get_phase2_ca_path(s_8021x)
                                             : nm_setting_802_1x_get_ca_path(s_8021x);
-    const char *           cert_path;
-    const char *           key_path = NULL;
-    const char *           key_password;
-    const char *           domain_suffix_match;
-    const char *           domain_match;
+    const char            *cert_path;
+    const char            *key_path = NULL;
+    const char            *key_password;
+    const char            *domain_suffix_match;
+    const char            *domain_match;
     char                   setting_buf[128];
 
     /* TODO: should check that all certificates and the key are RSA */
@@ -1220,7 +1220,7 @@ private_key_done:
                                  : nm_setting_802_1x_get_domain_match(s_8021x);
 
     if (domain_suffix_match || domain_match) {
-        GString *   s = g_string_sized_new(128);
+        GString    *s = g_string_sized_new(128);
         const char *ptr;
         const char *end;
 
@@ -1287,13 +1287,13 @@ eap_optional_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, cons
 }
 
 static gboolean
-eap_optional_password_to_iwd_config(GKeyFile *      file,
-                                    const char *    iwd_prefix,
+eap_optional_password_to_iwd_config(GKeyFile       *file,
+                                    const char     *iwd_prefix,
                                     NMSetting8021x *s_8021x,
-                                    GError **       error)
+                                    GError        **error)
 {
     char                 setting_buf[128];
-    const char *         password = nm_setting_802_1x_get_password(s_8021x);
+    const char          *password = nm_setting_802_1x_get_password(s_8021x);
     NMSettingSecretFlags flags    = nm_setting_802_1x_get_password_flags(s_8021x);
 
     if (!password && nm_setting_802_1x_get_password_raw(s_8021x)) {
@@ -1347,12 +1347,12 @@ eap_phase1_identity_to_iwd_config(GKeyFile *file, const char *iwd_prefix, NMSett
 }
 
 static gboolean
-eap_method_config_to_iwd_config(GKeyFile *      file,
+eap_method_config_to_iwd_config(GKeyFile       *file,
                                 NMSetting8021x *s_8021x,
                                 gboolean        phase2,
-                                const char *    method,
-                                const char *    iwd_prefix,
-                                GError **       error)
+                                const char     *method,
+                                const char     *iwd_prefix,
+                                GError        **error)
 {
     char prefix_buf[128];
 
@@ -1677,18 +1677,18 @@ ip6_config_to_iwd_config(GKeyFile *file, NMSettingIPConfig *s_ip, GError **error
 
 GKeyFile *
 nm_wifi_utils_connection_to_iwd_config(NMConnection *connection,
-                                       char **       out_filename,
-                                       GError **     error)
+                                       char        **out_filename,
+                                       GError      **error)
 {
-    NMSettingConnection * s_conn = nm_connection_get_setting_connection(connection);
-    NMSettingWireless *   s_wifi = nm_connection_get_setting_wireless(connection);
-    GBytes *              ssid;
-    const guint8 *        ssid_data;
-    gsize                 ssid_len;
-    NMIwdNetworkSecurity  security;
-    const char *          cloned_mac_addr;
-    gs_free char *        comment        = NULL;
-    nm_auto_unref_keyfile GKeyFile *file = NULL;
+    NMSettingConnection            *s_conn = nm_connection_get_setting_connection(connection);
+    NMSettingWireless              *s_wifi = nm_connection_get_setting_wireless(connection);
+    GBytes                         *ssid;
+    const guint8                   *ssid_data;
+    gsize                           ssid_len;
+    NMIwdNetworkSecurity            security;
+    const char                     *cloned_mac_addr;
+    gs_free char                   *comment = NULL;
+    nm_auto_unref_keyfile GKeyFile *file    = NULL;
 
     if (!s_conn || !s_wifi
         || !nm_streq(nm_setting_connection_get_connection_type(s_conn),
@@ -1800,3 +1800,151 @@ nm_wifi_utils_connection_to_iwd_config(NMConnection *connection,
 
     return g_steal_pointer(&file);
 }
+
+/* Wi-Fi Display Technical Specification v2.1.0 Table 27 */
+enum wfd_subelem_type {
+    WFD_SUBELEM_WFD_DEVICE_INFORMATION   = 0,
+    WFD_SUBELEM_ASSOCIATED_BSSID         = 1,
+    WFD_SUBELEM_COUPLED_SINK_INFORMATION = 6,
+    WFD_SUBELEM_EXTENDED_CAPABILITY      = 7,
+    WFD_SUBELEM_LOCAL_IP_ADDRESS         = 8,
+    WFD_SUBELEM_SESION_INFORMATION       = 9,
+    WFD_SUBELEM_ALTERNATIVE_MAC_ADDRESS  = 10,
+    WFD_SUBELEM_R2_DEVICE_INFORMATION    = 11,
+};
+
+bool
+nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd)
+{
+    size_t         len;
+    const uint8_t *data         = g_bytes_get_data(ies, &len);
+    const uint8_t *dev_info     = NULL;
+    uint16_t       dev_info_len = 0;
+    uint16_t       dev_info_flags;
+    const uint8_t *ext_capability     = NULL;
+    uint16_t       ext_capability_len = 0;
+
+    /* The single WFD IEs array provided by the client is supposed to be sent to
+     * the peer in the different frame types that may include the WFD IE: Probe
+     * Request/Response, Beacon, (Re)Association Request/Response, GO
+     * Negotiation Request/Response/Confirm and Provision Discovery
+     * Request/Response.
+     *
+     * It's going to be a subset of the elements allowed in all those frames.
+     * Validate that it contains at least a valid WFD Device Information (with
+     * the Session Available bit true) and that the sequence of subelements is
+     * valid.
+     */
+    while (len) {
+        uint8_t  subelem_id;
+        uint16_t subelem_len;
+
+        /* Does the subelement header fit */
+        if (len < 3)
+            return FALSE;
+
+        subelem_id  = data[0];
+        subelem_len = (data[1] << 8) | data[2];
+        data += 3;
+        len -= 3;
+
+        if (subelem_len > len)
+            return FALSE;
+
+        if (subelem_id == WFD_SUBELEM_WFD_DEVICE_INFORMATION) {
+            /* Is there a duplicate WFD Device Information */
+            if (dev_info)
+                return FALSE;
+
+            dev_info     = data;
+            dev_info_len = subelem_len;
+        }
+
+        if (subelem_id == WFD_SUBELEM_EXTENDED_CAPABILITY) {
+            /* Is there a duplicate WFD Extended Capability */
+            if (ext_capability)
+                return FALSE;
+
+            ext_capability     = data;
+            ext_capability_len = subelem_len;
+        }
+
+        data += subelem_len;
+        len -= subelem_len;
+    }
+
+    if (!dev_info || dev_info_len != 6)
+        return FALSE;
+
+    dev_info_flags = (dev_info[0] << 8) | dev_info[1];
+
+    /* Secondary sink not supported */
+    if ((dev_info_flags & 3) == 2)
+        return FALSE;
+
+    /* Must be available for WFD Session */
+    if (((dev_info_flags >> 4) & 3) != 1)
+        return FALSE;
+
+    /* TDLS persistent group re-invocation not supported */
+    if ((dev_info_flags >> 13) & 1)
+        return FALSE;
+
+    /* All other flags indicate support but not a requirement for something
+     * so not preserving them in the IEs IWD eventually sends doesn't break
+     * basic functionality.
+     */
+
+    if (ext_capability && ext_capability_len != 2)
+        return FALSE;
+
+    if (!out_wfd)
+        return TRUE;
+
+    out_wfd->source = NM_IN_SET(dev_info_flags & 3, 0, 3);
+    out_wfd->sink   = NM_IN_SET(dev_info_flags & 3, 1, 3);
+    out_wfd->port   = (dev_info[2] << 8) | dev_info[3];
+    out_wfd->has_audio =
+        out_wfd->sink ? ((dev_info_flags >> 10) & 1) == 0 : (((dev_info_flags >> 11) & 1) == 1);
+    out_wfd->has_uibc = ext_capability && (ext_capability[1] & 1) == 1;
+    out_wfd->has_cp   = ((dev_info_flags >> 8) & 1) == 1;
+    return TRUE;
+}
+
+GBytes *
+nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd)
+{
+    uint8_t  data[64];
+    uint8_t *ptr = data;
+
+    *ptr++ = WFD_SUBELEM_WFD_DEVICE_INFORMATION;
+    *ptr++ = 0; /* WFD Subelement length */
+    *ptr++ = 6;
+    *ptr++ = 0;                                               /* WFD Device Information bitmap: */
+    *ptr++ = (wfd->source ? (wfd->sink ? 3 : 0) : 1) | 0x10 | /* WFD Session Available */
+             (wfd->has_cp ? 0x100 : 0) | (wfd->has_audio ? 0 : 0x400);
+    *ptr++ = wfd->port >> 8;
+    *ptr++ = wfd->port & 255;
+    *ptr++ = 0; /* WFD Device Maximum throughput */
+    *ptr++ = 10;
+
+    if (wfd->has_uibc) {
+        *ptr++ = WFD_SUBELEM_EXTENDED_CAPABILITY;
+        *ptr++ = 0; /* WFD Subelement length */
+        *ptr++ = 2;
+        *ptr++ = 0x00; /* WFD Extended Capability Bitmap: */
+        *ptr++ = 0x10; /* UIBC Support */
+    }
+
+    return g_bytes_new(data, ptr - data);
+}
+
+bool
+nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b)
+{
+    if (!a || !b)
+        return a == b;
+
+    return a->source == b->source && a->sink == b->sink && a->port == b->port
+           && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp;
+}
diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h
index 2664c03e..1d46a900 100644
--- a/src/core/devices/wifi/nm-wifi-utils.h
+++ b/src/core/devices/wifi/nm-wifi-utils.h
@@ -20,8 +20,17 @@ typedef enum {
     NM_IWD_NETWORK_SECURITY_8021X,
 } NMIwdNetworkSecurity;
 
-gboolean nm_wifi_utils_complete_connection(GBytes *      ssid,
-                                           const char *  bssid,
+typedef struct {
+    bool     source;
+    bool     sink;
+    uint16_t port;
+    bool     has_audio;
+    bool     has_uibc;
+    bool     has_cp;
+} NMIwdWfdInfo;
+
+gboolean nm_wifi_utils_complete_connection(GBytes       *ssid,
+                                           const char   *bssid,
                                            _NM80211Mode  mode,
                                            guint32       ap_freq,
                                            guint32       flags,
@@ -29,18 +38,22 @@ gboolean nm_wifi_utils_complete_connection(GBytes *      ssid,
                                            guint32       rsn_flags,
                                            NMConnection *connection,
                                            gboolean      lock_bssid,
-                                           GError **     error);
+                                           GError      **error);
 
 gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid);
 
-gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *        connection,
-                                                      char **               ssid,
+gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
+                                                      char                **ssid,
                                                       NMIwdNetworkSecurity *security);
-char *   nm_wifi_utils_get_iwd_config_filename(const char *         ssid,
+char    *nm_wifi_utils_get_iwd_config_filename(const char          *ssid,
                                                gssize               ssid_len,
                                                NMIwdNetworkSecurity security);
 
 GKeyFile *
 nm_wifi_utils_connection_to_iwd_config(NMConnection *conn, char **out_filename, GError **error);
 
+bool    nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd);
+GBytes *nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd);
+bool    nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b);
+
 #endif /* __NM_WIFI_UTILS_H__ */
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index 5d1167cf..69d365e3 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -47,18 +47,18 @@
     }
 
 static gboolean
-complete_connection(const char *  ssid,
-                    const char *  bssid,
+complete_connection(const char   *ssid,
+                    const char   *bssid,
                     _NM80211Mode  mode,
                     guint32       flags,
                     guint32       wpa_flags,
                     guint32       rsn_flags,
                     gboolean      lock_bssid,
                     NMConnection *src,
-                    GError **     error)
+                    GError      **error)
 {
     gs_unref_bytes GBytes *ssid_b = NULL;
-    NMSettingWireless *    s_wifi;
+    NMSettingWireless     *s_wifi;
 
     /* Add a wifi setting if one doesn't exist */
     s_wifi = nm_connection_get_setting_wireless(src);
@@ -91,8 +91,8 @@ static void
 set_items(NMSetting *setting, const KeyData *items)
 {
     const KeyData *item;
-    GParamSpec *   pspec;
-    GBytes *       tmp;
+    GParamSpec    *pspec;
+    GBytes        *tmp;
 
     for (item = items; item && item->key; item++) {
         g_assert(item->key);
@@ -199,9 +199,9 @@ fill_8021x(NMConnection *connection, const KeyData items[])
 static NMConnection *
 create_basic(const char *ssid, const char *bssid, _NM80211Mode mode)
 {
-    NMConnection *     connection;
+    NMConnection      *connection;
     NMSettingWireless *s_wifi = NULL;
-    GBytes *           tmp;
+    GBytes            *tmp;
 
     connection = nm_simple_connection_new();
 
@@ -233,10 +233,10 @@ static void
 test_lock_bssid(void)
 {
     NMConnection *src, *expected;
-    const char *  bssid = "01:02:03:04:05:06";
-    const char *  ssid  = "blahblah";
+    const char   *bssid = "01:02:03:04:05:06";
+    const char   *ssid  = "blahblah";
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     src      = nm_simple_connection_new();
     success  = complete_connection(ssid,
@@ -261,10 +261,10 @@ static void
 test_open_ap_empty_connection(void)
 {
     NMConnection *src, *expected;
-    const char *  bssid = "01:02:03:04:05:06";
-    const char *  ssid  = "blahblah";
+    const char   *bssid = "01:02:03:04:05:06";
+    const char   *ssid  = "blahblah";
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an empty source connection is correctly filled with the
      * SSID and Infra modes of the given AP details.
@@ -293,11 +293,11 @@ static void
 test_open_ap_leap_connection_1(gconstpointer add_wifi)
 {
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, "Bill Smith", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that a basic connection filled with a LEAP username is
      * rejected when completion is attempted with an open AP.  LEAP requires
@@ -330,10 +330,10 @@ static void
 test_open_ap_leap_connection_2(void)
 {
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that a basic connection specifying IEEE8021x security (ie, Dynamic
      * WEP or LEAP) is rejected when completion is attempted with an open AP.
@@ -364,14 +364,14 @@ static void
 test_open_ap_wep_connection(gconstpointer add_wifi)
 {
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {
         {NM_SETTING_WIRELESS_SECURITY_WEP_KEY0, "11111111111111111111111111", 0},
         {NM_SETTING_WIRELESS_SECURITY_WEP_TX_KEYIDX, NULL, 0},
         {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
         {NULL}};
     gboolean success;
-    GError * error = NULL;
+    GError  *error = NULL;
 
     /* Test that a static WEP connection is rejected when completion is
      * attempted with an open AP.
@@ -399,8 +399,8 @@ test_open_ap_wep_connection(gconstpointer add_wifi)
 /*****************************************************************************/
 
 static void
-test_ap_wpa_psk_connection_base(const char *  key_mgmt,
-                                const char *  auth_alg,
+test_ap_wpa_psk_connection_base(const char   *key_mgmt,
+                                const char   *auth_alg,
                                 guint32       flags,
                                 guint32       wpa_flags,
                                 guint32       rsn_flags,
@@ -409,8 +409,8 @@ test_ap_wpa_psk_connection_base(const char *  key_mgmt,
                                 NMConnection *expected)
 {
     NMConnection *src;
-    const char *  ssid        = "blahblah";
-    const char *  bssid       = "01:02:03:04:05:06";
+    const char   *ssid        = "blahblah";
+    const char   *bssid       = "01:02:03:04:05:06";
     const KeyData exp_wifi[]  = {{NM_SETTING_WIRELESS_SSID, ssid, 0},
                                 {NM_SETTING_WIRELESS_MODE, "infrastructure", 0},
                                 {NULL}};
@@ -419,7 +419,7 @@ test_ap_wpa_psk_connection_base(const char *  key_mgmt,
                                  {NM_SETTING_WIRELESS_SECURITY_PSK, "asdfasdfasdfasdfasdfafs", 0},
                                  {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     src = nm_simple_connection_new();
     if (add_wifi)
@@ -538,13 +538,13 @@ test_ap_wpa_eap_connection_base(const char *key_mgmt,
                                 guint       error_code)
 {
     NMConnection *src;
-    const char *  bssid       = "01:02:03:04:05:06";
+    const char   *bssid       = "01:02:03:04:05:06";
     const KeyData src_empty[] = {{NULL}};
     const KeyData src_wsec[]  = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, key_mgmt, 0},
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, auth_alg, 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     src = nm_simple_connection_new();
     if (add_wifi)
@@ -738,11 +738,11 @@ static void
 test_priv_ap_empty_connection(void)
 {
     NMConnection *src, *expected;
-    const char *  bssid      = "01:02:03:04:05:06";
-    const char *  ssid       = "blahblah";
+    const char   *bssid      = "01:02:03:04:05:06";
+    const char   *ssid       = "blahblah";
     const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", 0}, {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an empty connection is completed to a valid Static WEP
      * connection when completed with an AP with the Privacy bit set.
@@ -774,9 +774,9 @@ static void
 test_priv_ap_leap_connection_1(gconstpointer add_wifi)
 {
     NMConnection *src, *expected;
-    const char *  ssid          = "blahblah";
-    const char *  bssid         = "01:02:03:04:05:06";
-    const char *  leap_username = "Bill Smith";
+    const char   *ssid          = "blahblah";
+    const char   *bssid         = "01:02:03:04:05:06";
+    const char   *leap_username = "Bill Smith";
     const KeyData src_wsec[]    = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
                                 {NULL}};
@@ -785,7 +785,7 @@ test_priv_ap_leap_connection_1(gconstpointer add_wifi)
                                 {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an minimal LEAP connection specifying only key management and
      * the LEAP username is completed to a full LEAP connection when completed
@@ -823,12 +823,12 @@ static void
 test_priv_ap_leap_connection_2(void)
 {
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an minimal LEAP connection specifying only key management and
      * the LEAP auth alg is completed to a full LEAP connection when completed
@@ -859,8 +859,8 @@ static void
 test_priv_ap_dynamic_wep_1(void)
 {
     NMConnection *src, *expected;
-    const char *  ssid         = "blahblah";
-    const char *  bssid        = "01:02:03:04:05:06";
+    const char   *ssid         = "blahblah";
+    const char   *bssid        = "01:02:03:04:05:06";
     const KeyData src_wsec[]   = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
                                 {NULL}};
@@ -872,7 +872,7 @@ test_priv_ap_dynamic_wep_1(void)
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an minimal Dynamic WEP connection specifying key management,
      * the auth algorithm, and valid 802.1x setting is completed to a valid
@@ -909,8 +909,8 @@ static void
 test_priv_ap_dynamic_wep_2(void)
 {
     NMConnection *src, *expected;
-    const char *  ssid         = "blahblah";
-    const char *  bssid        = "01:02:03:04:05:06";
+    const char   *ssid         = "blahblah";
+    const char   *bssid        = "01:02:03:04:05:06";
     const KeyData src_wsec[]   = {{NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0}, {NULL}};
     const KeyData both_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0},
                                   {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0},
@@ -920,7 +920,7 @@ test_priv_ap_dynamic_wep_2(void)
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that an minimal Dynamic WEP connection specifying only the auth
      * algorithm and a valid 802.1x setting is completed to a valid Dynamic
@@ -957,14 +957,14 @@ static void
 test_priv_ap_dynamic_wep_3(void)
 {
     NMConnection *src;
-    const char *  bssid       = "01:02:03:04:05:06";
+    const char   *bssid       = "01:02:03:04:05:06";
     const KeyData src_wsec[]  = {{NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "shared", 0}, {NULL}};
     const KeyData src_8021x[] = {{NM_SETTING_802_1X_EAP, "peap", 0},
                                  {NM_SETTING_802_1X_IDENTITY, "Bill Smith", 0},
                                  {NM_SETTING_802_1X_PHASE2_AUTH, "mschapv2", 0},
                                  {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Ensure that a basic connection specifying 'shared' auth and an 802.1x
      * setting is rejected, as 802.1x is incompatible with 'shared' auth.
@@ -1086,13 +1086,13 @@ test_wpa_ap_empty_connection(gconstpointer data)
 {
     guint         idx = GPOINTER_TO_UINT(data);
     NMConnection *src, *expected;
-    const char *  bssid      = "01:02:03:04:05:06";
-    const char *  ssid       = "blahblah";
+    const char   *bssid      = "01:02:03:04:05:06";
+    const char   *ssid       = "blahblah";
     const KeyData exp_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "open", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that a basic WPA-PSK connection specifying just key management and
      * the auth algorithm is completed successfully when given an AP with WPA
@@ -1126,14 +1126,14 @@ test_wpa_ap_leap_connection_1(gconstpointer data)
 {
     guint         idx = GPOINTER_TO_UINT(data);
     NMConnection *src;
-    const char *  ssid          = "blahblah";
-    const char *  bssid         = "01:02:03:04:05:06";
-    const char *  leap_username = "Bill Smith";
+    const char   *ssid          = "blahblah";
+    const char   *bssid         = "01:02:03:04:05:06";
+    const char   *leap_username = "Bill Smith";
     const KeyData src_wsec[]    = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_LEAP_USERNAME, leap_username, 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that completion of a LEAP connection with a WPA-enabled AP is
      * rejected since WPA APs (usually) do not support LEAP.
@@ -1164,12 +1164,12 @@ test_wpa_ap_leap_connection_2(gconstpointer data)
 {
     guint         idx = GPOINTER_TO_UINT(data);
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0},
                                 {NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, "leap", 0},
                                 {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that completion of a LEAP connection with a WPA-enabled AP is
      * rejected since WPA APs (usually) do not support LEAP.
@@ -1200,10 +1200,10 @@ test_wpa_ap_dynamic_wep_connection(gconstpointer data)
 {
     guint         idx = GPOINTER_TO_UINT(data);
     NMConnection *src;
-    const char *  bssid      = "01:02:03:04:05:06";
+    const char   *bssid      = "01:02:03:04:05:06";
     const KeyData src_wsec[] = {{NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", 0}, {NULL}};
     gboolean      success;
-    GError *      error = NULL;
+    GError       *error = NULL;
 
     /* Test that completion of a Dynamic WEP connection with a WPA-enabled AP is
      * rejected since WPA APs (usually) do not support Dynamic WEP.
@@ -1409,12 +1409,12 @@ test_strength_all(void)
 static void
 do_test_ssids_options_to_ptrarray(const char *const *ssids)
 {
-    GVariantBuilder  builder;
-    gs_unref_variant GVariant *variant     = NULL;
+    GVariantBuilder              builder;
+    gs_unref_variant GVariant   *variant   = NULL;
     gs_unref_ptrarray GPtrArray *ssids_arr = NULL;
-    gs_free_error GError *error            = NULL;
-    gsize                 len;
-    gsize                 i;
+    gs_free_error GError        *error     = NULL;
+    gsize                        len;
+    gsize                        i;
 
     g_assert(ssids);
 
@@ -1443,7 +1443,7 @@ do_test_ssids_options_to_ptrarray(const char *const *ssids)
     g_assert_cmpint(len, ==, ssids_arr->len);
     for (i = 0; i < len; i++) {
         const char *ssid  = ssids[i];
-        GBytes *    bytes = ssids_arr->pdata[i];
+        GBytes     *bytes = ssids_arr->pdata[i];
 
         g_assert(nm_g_bytes_equal_mem(bytes, ssid, strlen(ssid)));
     }
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;