summary refs log tree commit diff
path: root/src/core
diff options
context:
space:
mode:
Diffstat (limited to 'src/core')
-rw-r--r--src/core/NetworkManagerUtils.c72
-rw-r--r--src/core/NetworkManagerUtils.h64
-rw-r--r--src/core/devices/adsl/nm-device-adsl.c2
-rw-r--r--src/core/devices/bluetooth/nm-bluez-manager.c163
-rw-r--r--src/core/devices/bluetooth/nm-bluez5-dun.c44
-rw-r--r--src/core/devices/bluetooth/nm-device-bt.c31
-rw-r--r--src/core/devices/bluetooth/tests/nm-bt-test.c2
-rw-r--r--src/core/devices/nm-acd-manager.c8
-rw-r--r--src/core/devices/nm-device-6lowpan.c8
-rw-r--r--src/core/devices/nm-device-bond.c99
-rw-r--r--src/core/devices/nm-device-bridge.c22
-rw-r--r--src/core/devices/nm-device-dummy.c36
-rw-r--r--src/core/devices/nm-device-ethernet.c36
-rw-r--r--src/core/devices/nm-device-infiniband.c21
-rw-r--r--src/core/devices/nm-device-ip-tunnel.c13
-rw-r--r--src/core/devices/nm-device-logging.h12
-rw-r--r--src/core/devices/nm-device-macvlan.c13
-rw-r--r--src/core/devices/nm-device-ppp.c57
-rw-r--r--src/core/devices/nm-device-private.h2
-rw-r--r--src/core/devices/nm-device-tun.c7
-rw-r--r--src/core/devices/nm-device-utils.c27
-rw-r--r--src/core/devices/nm-device-utils.h16
-rw-r--r--src/core/devices/nm-device-vlan.c8
-rw-r--r--src/core/devices/nm-device-vrf.c8
-rw-r--r--src/core/devices/nm-device-vxlan.c12
-rw-r--r--src/core/devices/nm-device-wireguard.c11
-rw-r--r--src/core/devices/nm-device-wpan.c9
-rw-r--r--src/core/devices/nm-device.c776
-rw-r--r--src/core/devices/nm-device.h1
-rw-r--r--src/core/devices/nm-lldp-listener.c42
-rw-r--r--src/core/devices/ovs/nm-ovsdb.c141
-rw-r--r--src/core/devices/team/nm-device-team.c22
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c11
-rw-r--r--src/core/devices/wifi/nm-device-olpc-mesh.c7
-rw-r--r--src/core/devices/wifi/nm-device-wifi-p2p.c44
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c23
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c2
-rw-r--r--src/core/devices/wifi/nm-wifi-common.c2
-rw-r--r--src/core/devices/wifi/nm-wifi-p2p-peer.c18
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c27
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c2
-rw-r--r--src/core/devices/wwan/nm-device-modem.c22
-rw-r--r--src/core/devices/wwan/nm-modem-broadband.c7
-rw-r--r--src/core/devices/wwan/nm-modem-manager.c6
-rw-r--r--src/core/devices/wwan/nm-modem.c49
-rw-r--r--src/core/devices/wwan/nm-modem.h2
-rw-r--r--src/core/dhcp/nm-dhcp-client.c112
-rw-r--r--src/core/dhcp/nm-dhcp-client.h34
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient-utils.c10
-rw-r--r--src/core/dhcp/nm-dhcp-dhclient.c60
-rw-r--r--src/core/dhcp/nm-dhcp-dhcpcanon.c47
-rw-r--r--src/core/dhcp/nm-dhcp-dhcpcd.c47
-rw-r--r--src/core/dhcp/nm-dhcp-helper.c4
-rw-r--r--src/core/dhcp/nm-dhcp-listener.c3
-rw-r--r--src/core/dhcp/nm-dhcp-manager.c145
-rw-r--r--src/core/dhcp/nm-dhcp-nettools.c14
-rw-r--r--src/core/dhcp/nm-dhcp-systemd.c22
-rw-r--r--src/core/dhcp/nm-dhcp-utils.c20
-rw-r--r--src/core/dhcp/tests/test-dhcp-dhclient.c4
-rw-r--r--src/core/dns/nm-dns-manager.c32
-rw-r--r--src/core/dns/nm-dns-manager.h5
-rw-r--r--src/core/dns/nm-dns-systemd-resolved.c103
-rw-r--r--src/core/main-utils.c1
-rw-r--r--src/core/main.c4
-rw-r--r--src/core/meson.build2
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.c83
-rw-r--r--src/core/ndisc/nm-lndp-ndisc.h7
-rw-r--r--src/core/ndisc/nm-ndisc.c238
-rw-r--r--src/core/ndisc/nm-ndisc.h15
-rw-r--r--src/core/ndisc/tests/test-ndisc-linux.c12
-rw-r--r--src/core/nm-active-connection.c14
-rw-r--r--src/core/nm-audit-manager.c2
-rw-r--r--src/core/nm-auth-utils.c8
-rw-r--r--src/core/nm-config-data.c35
-rw-r--r--src/core/nm-config-data.h20
-rw-r--r--src/core/nm-config.c19
-rw-r--r--src/core/nm-connectivity.c14
-rw-r--r--src/core/nm-core-utils.c193
-rw-r--r--src/core/nm-core-utils.h68
-rw-r--r--src/core/nm-dbus-manager.c2
-rw-r--r--src/core/nm-dbus-object.c2
-rw-r--r--src/core/nm-dbus-utils.c20
-rw-r--r--src/core/nm-dcb.c5
-rw-r--r--src/core/nm-dhcp-config.c2
-rw-r--r--src/core/nm-dispatcher.c22
-rw-r--r--src/core/nm-dispatcher.h7
-rw-r--r--src/core/nm-firewalld-manager.c2
-rw-r--r--src/core/nm-hostname-manager.c255
-rw-r--r--src/core/nm-hostname-manager.h4
-rw-r--r--src/core/nm-iface-helper.c78
-rw-r--r--src/core/nm-ip4-config.c110
-rw-r--r--src/core/nm-ip4-config.h29
-rw-r--r--src/core/nm-ip6-config.c11
-rw-r--r--src/core/nm-ip6-config.h3
-rw-r--r--src/core/nm-l3-config-data.c701
-rw-r--r--src/core/nm-l3-config-data.h80
-rw-r--r--src/core/nm-l3-ipv4ll.c65
-rw-r--r--src/core/nm-l3-ipv6ll.c717
-rw-r--r--src/core/nm-l3-ipv6ll.h112
-rw-r--r--src/core/nm-l3cfg.c1762
-rw-r--r--src/core/nm-l3cfg.h131
-rw-r--r--src/core/nm-manager.c100
-rw-r--r--src/core/nm-netns.c13
-rw-r--r--src/core/nm-policy.c48
-rw-r--r--src/core/nm-priv-helper-call.c101
-rw-r--r--src/core/nm-priv-helper-call.h15
-rw-r--r--src/core/nm-session-monitor.c10
-rw-r--r--src/core/nm-sleep-monitor.c40
-rw-r--r--src/core/nm-test-utils-core.h12
-rw-r--r--src/core/nm-types.h2
-rw-r--r--src/core/platform/tests/meson.build1
-rw-r--r--src/core/platform/tests/test-common.c10
-rw-r--r--src/core/platform/tests/test-common.h6
-rw-r--r--src/core/platform/tests/test-link.c61
-rw-r--r--src/core/platform/tests/test-platform-general.c162
-rw-r--r--src/core/platform/tests/test-tc.c20
-rw-r--r--src/core/ppp/nm-ppp-manager.c4
-rw-r--r--src/core/settings/nm-settings-connection.c4
-rw-r--r--src/core/settings/nm-settings.c10
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c214
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c111
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected1
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-port9
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected3
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-vlanid-use.cexpected2
-rw-r--r--src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c3788
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c2
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c8
-rw-r--r--src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c6
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-plugin.c18
-rw-r--r--src/core/settings/plugins/keyfile/nms-keyfile-storage.c2
-rw-r--r--src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c8
-rw-r--r--src/core/supplicant/nm-supplicant-config.c2
-rw-r--r--src/core/supplicant/nm-supplicant-interface.c83
-rw-r--r--src/core/supplicant/nm-supplicant-interface.h4
-rw-r--r--src/core/supplicant/nm-supplicant-manager.c3
-rw-r--r--src/core/supplicant/nm-supplicant-settings-verify.c2
-rw-r--r--src/core/supplicant/nm-supplicant-types.h4
-rw-r--r--src/core/tests/test-core.c168
-rw-r--r--src/core/tests/test-ip6-config.c12
-rw-r--r--src/core/tests/test-l3cfg.c317
-rw-r--r--src/core/tests/test-systemd.c8
-rw-r--r--src/core/tests/test-utils.c75
-rw-r--r--src/core/vpn/nm-vpn-connection.c1
162 files changed, 7014 insertions, 6022 deletions
diff --git a/src/core/NetworkManagerUtils.c b/src/core/NetworkManagerUtils.c
index 7d3e80be..0da8e0a9 100644
--- a/src/core/NetworkManagerUtils.c
+++ b/src/core/NetworkManagerUtils.c
@@ -17,6 +17,7 @@
 #include "libnm-glib-aux/nm-uuid.h"
 #include "libnm-glib-aux/nm-str-buf.h"
 #include "libnm-base/nm-net-aux.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-aux-intern/nm-common-macros.h"
 #include "nm-utils.h"
 #include "nm-setting-connection.h"
@@ -112,7 +113,7 @@ get_new_connection_name(NMConnection *const *existing_connections,
          * connection id. */
         temp = g_strdup_printf(C_("connection id fallback", "%s %u"), fallback_prefix, i);
 
-        if (nm_utils_strv_find_first((char **) existing_names, existing_len, temp) < 0)
+        if (nm_strv_find_first(existing_names, existing_len, temp) < 0)
             return temp;
 
         g_free(temp);
@@ -227,28 +228,28 @@ out:
 /*****************************************************************************/
 
 void
-nm_utils_complete_generic(NMPlatform *         platform,
-                          NMConnection *       connection,
-                          const char *         ctype,
-                          NMConnection *const *existing_connections,
-                          const char *         preferred_id,
-                          const char *         fallback_id_prefix,
-                          const char *         ifname_prefix,
-                          const char *         ifname,
-                          gboolean             default_enable_ipv6)
+_nm_utils_complete_generic_with_params(NMPlatform *         platform,
+                                       NMConnection *       connection,
+                                       const char *         ctype,
+                                       NMConnection *const *existing_connections,
+                                       const char *         preferred_id,
+                                       const char *         fallback_id_prefix,
+                                       const char *         ifname_prefix,
+                                       const char *         ifname,
+                                       ...)
 {
     NMSettingConnection *s_con;
-    char *               id, *generated_ifname;
-    GHashTable *         parameters;
+    char *               id;
+    char *               generated_ifname;
+    gs_unref_hashtable GHashTable *parameters = NULL;
+    va_list                        ap;
+    const char *                   p_val;
+    const char *                   p_key;
 
     g_assert(fallback_id_prefix);
     g_return_if_fail(ifname_prefix == NULL || ifname == NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    if (!s_con) {
-        s_con = (NMSettingConnection *) nm_setting_connection_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_con));
-    }
+    s_con = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_TYPE, ctype, NULL);
 
     if (!nm_setting_connection_get_uuid(s_con)) {
@@ -268,7 +269,9 @@ nm_utils_complete_generic(NMPlatform *         platform,
     }
 
     /* Add an interface name, if requested */
-    if (ifname) {
+    if (nm_setting_connection_get_interface_name(s_con)) {
+        /* pass */
+    } else if (ifname) {
         g_object_set(G_OBJECT(s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
     } else if (ifname_prefix && !nm_setting_connection_get_interface_name(s_con)) {
         generated_ifname = get_new_connection_ifname(platform, existing_connections, ifname_prefix);
@@ -277,13 +280,20 @@ nm_utils_complete_generic(NMPlatform *         platform,
     }
 
     /* Normalize */
-    parameters = g_hash_table_new(nm_str_hash, g_str_equal);
-    g_hash_table_insert(parameters,
-                        NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD,
-                        default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO
-                                            : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+    va_start(ap, ifname);
+    while ((p_key = va_arg(ap, const char *))) {
+        p_val = va_arg(ap, const char *);
+        if (!p_val) {
+            if (parameters)
+                g_hash_table_remove(parameters, p_key);
+            continue;
+        }
+        if (!parameters)
+            parameters = g_hash_table_new(nm_str_hash, g_str_equal);
+        g_hash_table_insert(parameters, (char *) p_key, (char *) p_val);
+    }
+    va_end(ap);
     nm_connection_normalize(connection, parameters, NULL, NULL);
-    g_hash_table_destroy(parameters);
 }
 
 /*****************************************************************************/
@@ -1051,6 +1061,12 @@ nm_shutdown_wait_obj_register_full(gpointer           watched_obj,
     if (G_UNLIKELY(!_shutdown_waitobj_lst_head.next))
         c_list_init(&_shutdown_waitobj_lst_head);
 
+    /* Beware: there are callers with g_main_context_get_thread_default()
+     * not being g_main_context_get_default(). For example _fw_nft_call().
+     *
+     * If you schedule any sources or async operations, you probably need to
+     * make sure to use the default context. */
+
     handle  = g_slice_new(NMShutdownWaitObjHandle);
     *handle = (NMShutdownWaitObjHandle){
         /* depending on @free_msg_reason, we take ownership of @msg_reason.
@@ -1693,5 +1709,11 @@ nm_platform_get()
 void
 nm_linux_platform_setup(void)
 {
-    nm_platform_setup(nm_linux_platform_new(FALSE, FALSE));
+    nm_platform_setup(nm_linux_platform_new(FALSE, FALSE, FALSE));
+}
+
+void
+nm_linux_platform_setup_with_tc_cache(void)
+{
+    nm_platform_setup(nm_linux_platform_new(FALSE, FALSE, TRUE));
 }
diff --git a/src/core/NetworkManagerUtils.h b/src/core/NetworkManagerUtils.h
index 1c86387c..a2ac732e 100644
--- a/src/core/NetworkManagerUtils.h
+++ b/src/core/NetworkManagerUtils.h
@@ -19,15 +19,60 @@ const char *nm_utils_get_ip_config_method(NMConnection *connection, int addr_fam
 
 const char *nm_utils_get_shared_wifi_permission(NMConnection *connection);
 
-void nm_utils_complete_generic(NMPlatform *         platform,
-                               NMConnection *       connection,
-                               const char *         ctype,
-                               NMConnection *const *existing_connections,
-                               const char *         preferred_id,
-                               const char *         fallback_id_prefix,
-                               const char *         ifname_prefix,
-                               const char *         ifname,
-                               gboolean             default_enable_ipv6);
+void _nm_utils_complete_generic_with_params(NMPlatform *         platform,
+                                            NMConnection *       connection,
+                                            const char *         ctype,
+                                            NMConnection *const *existing_connections,
+                                            const char *         preferred_id,
+                                            const char *         fallback_id_prefix,
+                                            const char *         ifname_prefix,
+                                            const char *         ifname,
+                                            ...) G_GNUC_NULL_TERMINATED;
+
+#define nm_utils_complete_generic_with_params(platform,             \
+                                              connection,           \
+                                              ctype,                \
+                                              existing_connections, \
+                                              preferred_id,         \
+                                              fallback_id_prefix,   \
+                                              ifname_prefix,        \
+                                              ifname,               \
+                                              ...)                  \
+    _nm_utils_complete_generic_with_params(platform,                \
+                                           connection,              \
+                                           ctype,                   \
+                                           existing_connections,    \
+                                           preferred_id,            \
+                                           fallback_id_prefix,      \
+                                           ifname_prefix,           \
+                                           ifname,                  \
+                                           ##__VA_ARGS__,           \
+                                           NULL)
+
+static inline void
+nm_utils_complete_generic(NMPlatform *         platform,
+                          NMConnection *       connection,
+                          const char *         ctype,
+                          NMConnection *const *existing_connections,
+                          const char *         preferred_id,
+                          const char *         fallback_id_prefix,
+                          const char *         ifname_prefix,
+                          const char *         ifname,
+                          gboolean             default_enable_ipv6)
+{
+    nm_utils_complete_generic_with_params(platform,
+                                          connection,
+                                          ctype,
+                                          existing_connections,
+                                          preferred_id,
+                                          fallback_id_prefix,
+                                          ifname_prefix,
+                                          ifname,
+                                          NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD,
+                                          default_enable_ipv6
+                                              ? NM_SETTING_IP6_CONFIG_METHOD_AUTO
+                                              : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
+}
 
 typedef gboolean(NMUtilsMatchFilterFunc)(NMConnection *connection, gpointer user_data);
 
@@ -232,6 +277,7 @@ NMPlatform *nm_platform_get(void);
 #define NM_PLATFORM_GET (nm_platform_get())
 
 void nm_linux_platform_setup(void);
+void nm_linux_platform_setup_with_tc_cache(void);
 
 /*****************************************************************************/
 
diff --git a/src/core/devices/adsl/nm-device-adsl.c b/src/core/devices/adsl/nm-device-adsl.c
index 4e4c9dcc..adcf8785 100644
--- a/src/core/devices/adsl/nm-device-adsl.c
+++ b/src/core/devices/adsl/nm-device-adsl.c
@@ -356,7 +356,7 @@ br2684_create_iface(NMDeviceAdsl *self)
             return FALSE;
         }
 
-        nm_utils_strdup_reset(&priv->nas_ifname, ni.ifname);
+        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);
diff --git a/src/core/devices/bluetooth/nm-bluez-manager.c b/src/core/devices/bluetooth/nm-bluez-manager.c
index fc081580..36e442ad 100644
--- a/src/core/devices/bluetooth/nm-bluez-manager.c
+++ b/src/core/devices/bluetooth/nm-bluez-manager.c
@@ -31,9 +31,9 @@
 /*****************************************************************************/
 
 #if WITH_BLUEZ5_DUN
-    #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_DUN
 #else
-    #define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE
+#define _NM_BT_CAPABILITY_SUPPORTED_DUN NM_BT_CAPABILITY_NONE
 #endif
 #define _NM_BT_CAPABILITY_SUPPORTED (NM_BT_CAPABILITY_NAP | _NM_BT_CAPABILITY_SUPPORTED_DUN)
 
@@ -382,64 +382,56 @@ _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len)
         buf[0] = '\0';
 
     if (bzobj->d_has_adapter_iface) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append_str(&buf, &len, "Adapter1 {");
+        nm_strbuf_append_str(&buf, &len, "Adapter1 {");
         if (bzobj->d_adapter.address) {
-            nm_utils_strbuf_append(&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address);
+            nm_strbuf_append(&buf, &len, " d.address: \"%s\"", bzobj->d_adapter.address);
             if (bzobj->d_adapter_powered)
-                nm_utils_strbuf_append_str(&buf, &len, ",");
+                nm_strbuf_append_str(&buf, &len, ",");
         }
         if (bzobj->d_adapter_powered)
-            nm_utils_strbuf_append(&buf, &len, " d.powered: 1");
-        nm_utils_strbuf_append_str(&buf, &len, " }");
+            nm_strbuf_append(&buf, &len, " d.powered: 1");
+        nm_strbuf_append_str(&buf, &len, " }");
     }
 
     if (bzobj->d_has_device_iface) {
         const char *prefix1 = "";
 
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append_str(&buf, &len, "Device1 {");
+        nm_strbuf_append_str(&buf, &len, "Device1 {");
         if (bzobj->d_device.address) {
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   "%s d.address: \"%s\"",
-                                   prefix1,
-                                   bzobj->d_device.address);
+            nm_strbuf_append(&buf, &len, "%s d.address: \"%s\"", prefix1, bzobj->d_device.address);
             prefix1 = ",";
         }
         if (bzobj->d_device.name) {
-            nm_utils_strbuf_append(&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name);
+            nm_strbuf_append(&buf, &len, "%s d.name: \"%s\"", prefix1, bzobj->d_device.name);
             prefix1 = ",";
         }
         if (bzobj->d_device.adapter) {
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   "%s d.adapter: \"%s\"",
-                                   prefix1,
-                                   bzobj->d_device.adapter);
+            nm_strbuf_append(&buf, &len, "%s d.adapter: \"%s\"", prefix1, bzobj->d_device.adapter);
             prefix1 = ",";
         }
         if (bzobj->d_device_capabilities != NM_BT_CAPABILITY_NONE) {
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   "%s d.capabilities: \"%s\"",
-                                   prefix1,
-                                   nm_bluetooth_capability_to_string(bzobj->d_device_capabilities,
-                                                                     sbuf_cap,
-                                                                     sizeof(sbuf_cap)));
+            nm_strbuf_append(&buf,
+                             &len,
+                             "%s d.capabilities: \"%s\"",
+                             prefix1,
+                             nm_bluetooth_capability_to_string(bzobj->d_device_capabilities,
+                                                               sbuf_cap,
+                                                               sizeof(sbuf_cap)));
             prefix1 = ",";
         }
         if (bzobj->d_device_connected) {
-            nm_utils_strbuf_append(&buf, &len, "%s d.connected: 1", prefix1);
+            nm_strbuf_append(&buf, &len, "%s d.connected: 1", prefix1);
             prefix1 = ",";
         }
         if (bzobj->d_device_paired) {
-            nm_utils_strbuf_append(&buf, &len, "%s d.paired: 1", prefix1);
+            nm_strbuf_append(&buf, &len, "%s d.paired: 1", prefix1);
             prefix1 = ",";
         }
-        nm_utils_strbuf_append_str(&buf, &len, " }");
+        nm_strbuf_append_str(&buf, &len, " }");
     }
 
     network_server_is_usable = _bzobjs_network_server_is_usable(bzobj, TRUE);
@@ -450,43 +442,43 @@ _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len)
         || !nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL,
                       bzobj->x_network_server.adapter_address)
         || bzobj->x_network_server.device_br || bzobj->x_network_server.r_req_data) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
 
-        nm_utils_strbuf_append(&buf, &len, "NetworkServer1 { ");
+        nm_strbuf_append(&buf, &len, "NetworkServer1 { ");
 
         if (!bzobj->d_has_network_server_iface)
-            nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, ");
+            nm_strbuf_append(&buf, &len, " has-d-iface: 0, ");
 
         if (network_server_is_usable != (!c_list_is_empty(&bzobj->x_network_server.lst)))
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   "usable: %d, used: %d",
-                                   !!network_server_is_usable,
-                                   !network_server_is_usable);
+            nm_strbuf_append(&buf,
+                             &len,
+                             "usable: %d, used: %d",
+                             !!network_server_is_usable,
+                             !network_server_is_usable);
         else if (network_server_is_usable)
-            nm_utils_strbuf_append(&buf, &len, "used: 1");
+            nm_strbuf_append(&buf, &len, "used: 1");
         else
-            nm_utils_strbuf_append(&buf, &len, "usable: 0");
+            nm_strbuf_append(&buf, &len, "usable: 0");
 
         if (!nm_streq0(bzobj->d_has_adapter_iface ? bzobj->d_adapter.address : NULL,
                        bzobj->x_network_server.adapter_address)) {
             if (bzobj->x_network_server.adapter_address)
-                nm_utils_strbuf_append(&buf,
-                                       &len,
-                                       ", adapter-address: \"%s\"",
-                                       bzobj->x_network_server.adapter_address);
+                nm_strbuf_append(&buf,
+                                 &len,
+                                 ", adapter-address: \"%s\"",
+                                 bzobj->x_network_server.adapter_address);
             else
-                nm_utils_strbuf_append(&buf, &len, ", adapter-address: <NULL>");
+                nm_strbuf_append(&buf, &len, ", adapter-address: <NULL>");
         }
 
         if (bzobj->x_network_server.device_br)
-            nm_utils_strbuf_append(&buf, &len, ", bridge-device: 1");
+            nm_strbuf_append(&buf, &len, ", bridge-device: 1");
 
         if (bzobj->x_network_server.r_req_data)
-            nm_utils_strbuf_append(&buf, &len, ", register-in-progress: 1");
+            nm_strbuf_append(&buf, &len, ", register-in-progress: 1");
 
-        nm_utils_strbuf_append_str(&buf, &len, " }");
+        nm_strbuf_append_str(&buf, &len, " }");
     }
 
     device_is_usable = _bzobjs_device_is_usable(bzobj, NULL, &create_panu_connection);
@@ -497,76 +489,72 @@ _bzobj_to_string(const BzDBusObj *bzobj, char *buf, gsize len)
         || bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
         || bzobj->x_device.connect_dun_context || bzobj->x_device.c_req_data
         || bzobj->x_device_is_connected != bzobj->d_network_connected) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append_str(&buf, &len, "Network1 {");
+        nm_strbuf_append_str(&buf, &len, "Network1 {");
         if (bzobj->d_network.interface)
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   " d.interface: \"%s\", ",
-                                   bzobj->d_network.interface);
+            nm_strbuf_append(&buf, &len, " d.interface: \"%s\", ", bzobj->d_network.interface);
         if (bzobj->d_network_connected)
-            nm_utils_strbuf_append(&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected);
+            nm_strbuf_append(&buf, &len, " d.connected: %d, ", !!bzobj->d_network_connected);
         if (!bzobj->d_has_network_iface)
-            nm_utils_strbuf_append(&buf, &len, " has-d-iface: 0, ");
+            nm_strbuf_append(&buf, &len, " has-d-iface: 0, ");
         if (device_is_usable != bzobj->x_device_is_usable)
-            nm_utils_strbuf_append(&buf,
-                                   &len,
-                                   " usable: %d, used: %d",
-                                   !!device_is_usable,
-                                   !device_is_usable);
+            nm_strbuf_append(&buf,
+                             &len,
+                             " usable: %d, used: %d",
+                             !!device_is_usable,
+                             !device_is_usable);
         else if (device_is_usable)
-            nm_utils_strbuf_append(&buf, &len, " used: 1");
+            nm_strbuf_append(&buf, &len, " used: 1");
         else
-            nm_utils_strbuf_append(&buf, &len, " usable: 0");
+            nm_strbuf_append(&buf, &len, " usable: 0");
 
         if (create_panu_connection)
-            nm_utils_strbuf_append(&buf, &len, ", create-panu-connection: 1");
+            nm_strbuf_append(&buf, &len, ", create-panu-connection: 1");
 
         if (bzobj->x_device.panu_connection)
-            nm_utils_strbuf_append(&buf, &len, ", has-panu-connection: 1");
+            nm_strbuf_append(&buf, &len, ", has-panu-connection: 1");
 
         if (bzobj->x_device.device_bt)
-            nm_utils_strbuf_append(&buf, &len, ", has-device: 1");
+            nm_strbuf_append(&buf, &len, ", has-device: 1");
 
         if (bzobj->x_device_connect_bt_type != NM_BT_CAPABILITY_NONE
             || bzobj->x_device.connect_dun_context) {
-            nm_utils_strbuf_append(
-                &buf,
-                &len,
-                ", connect: %s%s",
-                nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type,
-                                                  sbuf_cap,
-                                                  sizeof(sbuf_cap)),
-                bzobj->x_device.connect_dun_context ? ",with-dun-context" : "");
+            nm_strbuf_append(&buf,
+                             &len,
+                             ", connect: %s%s",
+                             nm_bluetooth_capability_to_string(bzobj->x_device_connect_bt_type,
+                                                               sbuf_cap,
+                                                               sizeof(sbuf_cap)),
+                             bzobj->x_device.connect_dun_context ? ",with-dun-context" : "");
         }
 
         if (bzobj->x_device.c_req_data)
-            nm_utils_strbuf_append(&buf, &len, ", connecting: 1");
+            nm_strbuf_append(&buf, &len, ", connecting: 1");
 
         if (bzobj->x_device_is_connected != bzobj->d_network_connected)
-            nm_utils_strbuf_append(&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected);
+            nm_strbuf_append(&buf, &len, ", connected: %d", !!bzobj->x_device_is_connected);
 
-        nm_utils_strbuf_append_str(&buf, &len, " }");
+        nm_strbuf_append_str(&buf, &len, " }");
     }
 
     if (_bzobjs_is_dead(bzobj)) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append_str(&buf, &len, "dead: 1");
+        nm_strbuf_append_str(&buf, &len, "dead: 1");
     }
 
     if (!c_list_is_empty(&bzobj->process_change_lst)) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append(&buf, &len, "change-pending-on-idle: 1");
+        nm_strbuf_append(&buf, &len, "change-pending-on-idle: 1");
     }
 
     if (_bzobjs_adapter_is_usable_for_device(bzobj)
         != bzobj->was_usable_adapter_for_device_before) {
-        nm_utils_strbuf_append_str(&buf, &len, prefix);
+        nm_strbuf_append_str(&buf, &len, prefix);
         prefix = ", ";
-        nm_utils_strbuf_append(&buf, &len, "change-usable-adapter-for-device: 1");
+        nm_strbuf_append(&buf, &len, "change-usable-adapter-for-device: 1");
     }
 
     return buf0;
@@ -2864,9 +2852,7 @@ dispose(GObject *object)
      *   BzDBusObj instances and do necessary cleanup actions (like disconnecting devices
      *   or deleting panu_connection). */
 
-    nm_assert(c_list_is_empty(&priv->network_server_lst_head));
-    nm_assert(c_list_is_empty(&priv->process_change_lst_head));
-    nm_assert(priv->process_change_idle_id == 0);
+    nm_clear_g_source(&priv->process_change_idle_id);
 
     g_atomic_pointer_compare_and_exchange(&nm_bt_vtable_network_server,
                                           &priv->vtable_network_server,
@@ -2883,6 +2869,9 @@ dispose(GObject *object)
     nm_clear_pointer(&priv->bzobjs, g_hash_table_destroy);
     nm_clear_pointer(&priv->conn_data_heads, g_hash_table_destroy);
     nm_clear_pointer(&priv->conn_data_elems, g_hash_table_destroy);
+
+    nm_assert(c_list_is_empty(&priv->network_server_lst_head));
+    nm_assert(c_list_is_empty(&priv->process_change_lst_head));
 }
 
 static void
diff --git a/src/core/devices/bluetooth/nm-bluez5-dun.c b/src/core/devices/bluetooth/nm-bluez5-dun.c
index e29884d8..7efbfaf4 100644
--- a/src/core/devices/bluetooth/nm-bluez5-dun.c
+++ b/src/core/devices/bluetooth/nm-bluez5-dun.c
@@ -182,25 +182,18 @@ _connect_open_tty(NMBluez5DunContext *context)
                   nm_strerror_native(errsv),
                   errsv);
             context->cdat->connect_open_tty_started_at = nm_utils_get_monotonic_timestamp_nsec();
-            context->cdat->source                      = nm_g_timeout_source_new(100,
-                                                            G_PRIORITY_DEFAULT,
-                                                            _connect_open_tty_retry_cb,
-                                                            context,
-                                                            NULL);
-            g_source_attach(context->cdat->source, NULL);
+            context->cdat->source =
+                nm_g_timeout_add_source(100, _connect_open_tty_retry_cb, context);
         }
         return -errsv;
     }
 
     context->rfcomm_tty_fd = fd;
 
-    context->rfcomm_tty_poll_source = nm_g_unix_fd_source_new(context->rfcomm_tty_fd,
+    context->rfcomm_tty_poll_source = nm_g_unix_fd_add_source(context->rfcomm_tty_fd,
                                                               G_IO_ERR | G_IO_HUP,
-                                                              G_PRIORITY_DEFAULT,
                                                               _rfcomm_tty_poll_cb,
-                                                              context,
-                                                              NULL);
-    g_source_attach(context->rfcomm_tty_poll_source, NULL);
+                                                              context);
 
     _context_invoke_callback_success(context);
     return 0;
@@ -369,13 +362,10 @@ _connect_socket_connect(NMBluez5DunContext *context)
               context->dst_str,
               context->rfcomm_channel);
 
-        context->cdat->source = nm_g_unix_fd_source_new(context->rfcomm_sock_fd,
+        context->cdat->source = nm_g_unix_fd_add_source(context->rfcomm_sock_fd,
                                                         G_IO_OUT,
-                                                        G_PRIORITY_DEFAULT,
                                                         _connect_socket_connect_cb,
-                                                        context,
-                                                        NULL);
-        g_source_attach(context->cdat->source, NULL);
+                                                        context);
         return;
     }
 
@@ -567,12 +557,8 @@ _connect_sdp_io_cb(int fd, GIOCondition condition, gpointer user_data)
                   nm_strerror_native(errsv),
                   errsv);
             nm_clear_g_source_inst(&context->cdat->source);
-            context->cdat->source = nm_g_timeout_source_new(1000,
-                                                            G_PRIORITY_DEFAULT,
-                                                            _connect_sdp_session_start_on_idle_cb,
-                                                            context,
-                                                            NULL);
-            g_source_attach(context->cdat->source, NULL);
+            context->cdat->source =
+                nm_g_timeout_add_source(1000, _connect_sdp_session_start_on_idle_cb, context);
             return G_SOURCE_REMOVE;
         }
 
@@ -616,13 +602,10 @@ _connect_sdp_io_cb(int fd, GIOCondition condition, gpointer user_data)
     }
 
     /* Set callback responsible for update the internal SDP transaction */
-    context->cdat->source = nm_g_unix_fd_source_new(fd,
+    context->cdat->source = nm_g_unix_fd_add_source(fd,
                                                     G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-                                                    G_PRIORITY_DEFAULT,
                                                     _connect_sdp_search_io_cb,
-                                                    context,
-                                                    NULL);
-    g_source_attach(context->cdat->source, NULL);
+                                                    context);
 
 done:
     if (error)
@@ -664,13 +647,10 @@ _connect_sdp_session_start(NMBluez5DunContext *context, GError **error)
         return FALSE;
     }
 
-    context->cdat->source = nm_g_unix_fd_source_new(sdp_get_socket(context->cdat->sdp_session),
+    context->cdat->source = nm_g_unix_fd_add_source(sdp_get_socket(context->cdat->sdp_session),
                                                     G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-                                                    G_PRIORITY_DEFAULT,
                                                     _connect_sdp_io_cb,
-                                                    context,
-                                                    NULL);
-    g_source_attach(context->cdat->source, NULL);
+                                                    context);
     return TRUE;
 }
 
diff --git a/src/core/devices/bluetooth/nm-device-bt.c b/src/core/devices/bluetooth/nm-device-bt.c
index 8f4ceba7..3dbfbbe0 100644
--- a/src/core/devices/bluetooth/nm-device-bt.c
+++ b/src/core/devices/bluetooth/nm-device-bt.c
@@ -10,6 +10,7 @@
 #include <stdio.h>
 #include <linux/if_ether.h>
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "nm-bluez-common.h"
 #include "nm-bluez-manager.h"
@@ -273,11 +274,7 @@ complete_connection(NMDevice *           device,
     s_serial = nm_connection_get_setting_serial(connection);
     s_ppp    = nm_connection_get_setting_ppp(connection);
 
-    s_bt = nm_connection_get_setting_bluetooth(connection);
-    if (!s_bt) {
-        s_bt = (NMSettingBluetooth *) nm_setting_bluetooth_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_bt));
-    }
+    s_bt = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BLUETOOTH);
 
     ctype = nm_setting_bluetooth_get_connection_type(s_bt);
     if (ctype) {
@@ -1007,21 +1004,21 @@ act_stage3_ip_config_start(NMDevice *           device,
                            gpointer *           out_config,
                            NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceBtPrivate *priv = NM_DEVICE_BT_GET_PRIVATE(device);
+    NMDeviceBtPrivate *priv    = NM_DEVICE_BT_GET_PRIVATE(device);
+    gboolean           autoip4 = FALSE;
+    NMActStageReturn   ret;
 
-    nm_assert_addr_family(addr_family);
+    if (priv->connect_bt_type != NM_BT_CAPABILITY_DUN)
+        goto out_chain_up;
 
-    if (priv->connect_bt_type == NM_BT_CAPABILITY_DUN) {
-        if (addr_family == AF_INET) {
-            return nm_modem_stage3_ip4_config_start(priv->modem,
-                                                    device,
-                                                    NM_DEVICE_CLASS(nm_device_bt_parent_class),
-                                                    out_failure_reason);
-        } else {
-            return nm_modem_stage3_ip6_config_start(priv->modem, device, out_failure_reason);
-        }
-    }
+    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;
 
+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);
 }
diff --git a/src/core/devices/bluetooth/tests/nm-bt-test.c b/src/core/devices/bluetooth/tests/nm-bt-test.c
index 0fc8aa87..548e7249 100644
--- a/src/core/devices/bluetooth/tests/nm-bt-test.c
+++ b/src/core/devices/bluetooth/tests/nm-bt-test.c
@@ -2,8 +2,6 @@
 
 #include "src/core/nm-default-daemon.h"
 
-#include <glib-unix.h>
-
 #include "devices/bluetooth/nm-bluez5-dun.h"
 
 #include "nm-test-utils-core.h"
diff --git a/src/core/devices/nm-acd-manager.c b/src/core/devices/nm-acd-manager.c
index 145947bb..c041163e 100644
--- a/src/core/devices/nm-acd-manager.c
+++ b/src/core/devices/nm-acd-manager.c
@@ -349,9 +349,7 @@ nm_acd_manager_start_probe(NMAcdManager *self, guint timeout)
 
     nm_assert(!self->event_source);
     n_acd_get_fd(self->acd, &fd);
-    self->event_source =
-        nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL);
-    g_source_attach(self->event_source, NULL);
+    self->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, acd_event, self);
 
     return success ? 0 : -NME_UNSPEC;
 }
@@ -439,9 +437,7 @@ nm_acd_manager_announce_addresses(NMAcdManager *self)
 
     if (!self->event_source) {
         n_acd_get_fd(self->acd, &fd);
-        self->event_source =
-            nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, acd_event, self, NULL);
-        g_source_attach(self->event_source, NULL);
+        self->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, acd_event, self);
     }
 
     return success ? 0 : -NME_UNSPEC;
diff --git a/src/core/devices/nm-device-6lowpan.c b/src/core/devices/nm-device-6lowpan.c
index 96218658..8b77c22c 100644
--- a/src/core/devices/nm-device-6lowpan.c
+++ b/src/core/devices/nm-device-6lowpan.c
@@ -9,6 +9,7 @@
 
 #include "nm-device-private.h"
 #include "settings/nm-settings.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-6lowpan.h"
@@ -221,12 +222,7 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMSetting6Lowpan *s_6lowpan =
-        NM_SETTING_6LOWPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_6LOWPAN));
-
-    if (!s_6lowpan) {
-        s_6lowpan = (NMSetting6Lowpan *) nm_setting_6lowpan_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_6lowpan);
-    }
+        _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_6LOWPAN);
 
     g_object_set(
         s_6lowpan,
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index b208e2c0..f7d78f57 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -14,8 +14,10 @@
 #include "nm-device-private.h"
 #include "libnm-platform/nm-platform.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 "nm-ip4-config.h"
+#include "nm-setting-bond-port.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBond
 #include "nm-device-logging.h"
@@ -79,8 +81,6 @@ complete_connection(NMDevice *           device,
                     NMConnection *const *existing_connections,
                     GError **            error)
 {
-    NMSettingBond *s_bond;
-
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
                               NM_SETTING_BOND_SETTING_NAME,
@@ -91,11 +91,7 @@ complete_connection(NMDevice *           device,
                               NULL,
                               TRUE);
 
-    s_bond = nm_connection_get_setting_bond(connection);
-    if (!s_bond) {
-        s_bond = (NMSettingBond *) nm_setting_bond_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_bond));
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND);
 
     return TRUE;
 }
@@ -168,16 +164,11 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceBond * self    = NM_DEVICE_BOND(device);
-    NMSettingBond *s_bond  = nm_connection_get_setting_bond(connection);
+    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;
 
-    if (!s_bond) {
-        s_bond = (NMSettingBond *) nm_setting_bond_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_bond);
-    }
-
     /* Read bond options from sysfs and update the Bond setting to match */
     options = nm_setting_bond_get_valid_options(NULL);
     for (; options[0]; options++) {
@@ -225,11 +216,29 @@ update_connection(NMDevice *device, NMConnection *connection)
 }
 
 static gboolean
-master_update_slave_connection(NMDevice *    self,
-                               NMDevice *    slave,
-                               NMConnection *connection,
-                               GError **     error)
+controller_update_port_connection(NMDevice *    self,
+                                  NMDevice *    port,
+                                  NMConnection *connection,
+                                  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;
+
+    g_return_val_if_fail(ifindex_port > 0, FALSE);
+
+    s_port = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BOND_PORT);
+
+    queue_id_str =
+        nm_platform_sysctl_slave_get_option(nm_device_get_platform(self), ifindex_port, "queue_id");
+    if (queue_id_str) {
+        queue_id =
+            _nm_utils_ascii_str_to_int64(queue_id_str, 10, 0, 65535, NM_BOND_PORT_QUEUE_ID_DEF);
+        g_object_set(s_port, NM_SETTING_BOND_PORT_QUEUE_ID, queue_id, NULL);
+    } else
+        _LOGW(LOGD_BOND, "failed to read bond port setting '%s'", NM_SETTING_BOND_PORT_QUEUE_ID);
+
     g_object_set(nm_connection_get_setting_connection(connection),
                  NM_SETTING_CONNECTION_MASTER,
                  nm_device_get_iface(self),
@@ -250,9 +259,8 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
     gsize                        i;
     gsize                        j;
 
-    cur_strv = nm_utils_strsplit_set_full(cur_arp_ip_target,
-                                          NM_ASCII_SPACES,
-                                          NM_UTILS_STRSPLIT_SET_FLAGS_STRSTRIP);
+    cur_strv =
+        nm_strsplit_set_full(cur_arp_ip_target, NM_ASCII_SPACES, NM_STRSPLIT_SET_FLAGS_STRSTRIP);
     new_strv = nm_utils_bond_option_arp_ip_targets_split(new_arp_ip_target);
 
     cur_len = NM_PTRARRAY_LEN(cur_strv);
@@ -276,7 +284,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
                 }
             }
 
-            if (nm_utils_strv_find_first((char **) new_strv, i, s) < 0)
+            if (nm_strv_find_first(new_strv, i, s) < 0)
                 new_strv[j++] = s;
         }
         new_strv[j] = NULL;
@@ -286,7 +294,7 @@ set_arp_targets(NMDevice *device, const char *cur_arp_ip_target, const char *new
     if (cur_len == 0 && new_len == 0)
         return;
 
-    if (nm_utils_strv_equal(cur_strv, new_strv))
+    if (nm_strv_equal(cur_strv, new_strv))
         return;
 
     for (i = 0; i < cur_len; i++)
@@ -395,30 +403,57 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     return ret;
 }
 
+static void
+commit_port_options(NMDevice *bond_device, NMDevice *port, NMSettingBondPort *s_port)
+{
+    char queue_id_str[IFNAMSIZ + NM_STRLEN(":") + 5 + 100];
+
+    /*
+     * The queue-id of bond port is read only, we should modify bond interface using:
+     *    echo "eth1:2" > /sys/class/net/bond0/bonding/queue_id
+     * Kernel allows parital editing, so no need to care about other bond ports.
+     */
+    g_snprintf(queue_id_str,
+               sizeof(queue_id_str),
+               "%s:%" G_GUINT32_FORMAT,
+               nm_device_get_iface(port),
+               s_port ? nm_setting_bond_port_get_queue_id(s_port) : NM_BOND_PORT_QUEUE_ID_DEF);
+
+    nm_platform_sysctl_master_set_option(nm_device_get_platform(bond_device),
+                                         nm_device_get_ifindex(bond_device),
+                                         "queue_id",
+                                         queue_id_str);
+}
+
 static gboolean
-enslave_slave(NMDevice *device, NMDevice *slave, NMConnection *connection, gboolean configure)
+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, slave, LOGD_BOND);
+    nm_device_master_check_slave_physical_port(device, port, LOGD_BOND);
 
     if (configure) {
         gboolean success;
 
-        nm_device_take_down(slave, TRUE);
+        nm_device_take_down(port, TRUE);
         success = nm_platform_link_enslave(nm_device_get_platform(device),
                                            nm_device_get_ip_ifindex(device),
-                                           nm_device_get_ip_ifindex(slave));
-        nm_device_bring_up(slave, TRUE, NULL);
+                                           nm_device_get_ip_ifindex(port));
+        nm_device_bring_up(port, TRUE, NULL);
 
         if (!success) {
-            _LOGI(LOGD_BOND, "enslaved bond slave %s: failed", nm_device_get_ip_iface(slave));
+            _LOGI(LOGD_BOND, "assigning bond port %s: failed", nm_device_get_ip_iface(port));
             return FALSE;
         }
 
-        _LOGI(LOGD_BOND, "enslaved bond slave %s", nm_device_get_ip_iface(slave));
+        s_port = _nm_connection_get_setting(connection, NM_TYPE_SETTING_BOND_PORT);
+
+        commit_port_options(device, port, s_port);
+
+        _LOGI(LOGD_BOND, "assigned bond port %s", nm_device_get_ip_iface(port));
     } else
-        _LOGI(LOGD_BOND, "bond slave %s was enslaved", nm_device_get_ip_iface(slave));
+        _LOGI(LOGD_BOND, "bond port %s was assigned", nm_device_get_ip_iface(port));
 
     return TRUE;
 }
@@ -624,7 +659,7 @@ nm_device_bond_class_init(NMDeviceBondClass *klass)
     device_class->complete_connection      = complete_connection;
 
     device_class->update_connection              = update_connection;
-    device_class->master_update_slave_connection = master_update_slave_connection;
+    device_class->master_update_slave_connection = controller_update_port_connection;
 
     device_class->create_and_realize = create_and_realize;
     device_class->act_stage1_prepare = act_stage1_prepare;
diff --git a/src/core/devices/nm-device-bridge.c b/src/core/devices/nm-device-bridge.c
index a095dc3e..36a73978 100644
--- a/src/core/devices/nm-device-bridge.c
+++ b/src/core/devices/nm-device-bridge.c
@@ -14,6 +14,7 @@
 #include "nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceBridge
@@ -145,8 +146,6 @@ complete_connection(NMDevice *           device,
                     NMConnection *const *existing_connections,
                     GError **            error)
 {
-    NMSettingBridge *s_bridge;
-
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
                               NM_SETTING_BRIDGE_SETTING_NAME,
@@ -157,11 +156,7 @@ complete_connection(NMDevice *           device,
                               NULL,
                               TRUE);
 
-    s_bridge = nm_connection_get_setting_bridge(connection);
-    if (!s_bridge) {
-        s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_bridge));
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BRIDGE);
 
     return TRUE;
 }
@@ -576,17 +571,12 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceBridge * self     = NM_DEVICE_BRIDGE(device);
-    NMSettingBridge *s_bridge = nm_connection_get_setting_bridge(connection);
+    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;
     int              stp_value;
 
-    if (!s_bridge) {
-        s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_bridge);
-    }
-
     option = master_options;
     nm_assert(nm_streq(option->sysname, "stp_state"));
 
@@ -690,11 +680,7 @@ master_update_slave_connection(NMDevice *    device,
     g_return_val_if_fail(ifindex_slave > 0, FALSE);
 
     s_con  = nm_connection_get_setting_connection(connection);
-    s_port = nm_connection_get_setting_bridge_port(connection);
-    if (!s_port) {
-        s_port = (NMSettingBridgePort *) nm_setting_bridge_port_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_port));
-    }
+    s_port = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_BRIDGE_PORT);
 
     for (option = slave_options; option->name; option++) {
         gs_free char *str = nm_platform_sysctl_slave_get_option(nm_device_get_platform(device),
diff --git a/src/core/devices/nm-device-dummy.c b/src/core/devices/nm-device-dummy.c
index 488f0ba0..4c12648f 100644
--- a/src/core/devices/nm-device-dummy.c
+++ b/src/core/devices/nm-device-dummy.c
@@ -16,6 +16,7 @@
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-dummy.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceDummy
@@ -48,26 +49,16 @@ complete_connection(NMDevice *           device,
                     NMConnection *const *existing_connections,
                     GError **            error)
 {
-    NMSettingDummy *s_dummy;
+    nm_utils_complete_generic_with_params(nm_device_get_platform(device),
+                                          connection,
+                                          NM_SETTING_DUMMY_SETTING_NAME,
+                                          existing_connections,
+                                          NULL,
+                                          _("Dummy connection"),
+                                          NULL,
+                                          nm_device_get_ip_iface(device));
 
-    nm_utils_complete_generic(nm_device_get_platform(device),
-                              connection,
-                              NM_SETTING_DUMMY_SETTING_NAME,
-                              existing_connections,
-                              NULL,
-                              _("Dummy connection"),
-                              NULL,
-                              NULL,
-                              TRUE);
-
-    s_dummy = nm_connection_get_setting_dummy(connection);
-    if (!s_dummy) {
-        g_set_error_literal(error,
-                            NM_DEVICE_ERROR,
-                            NM_DEVICE_ERROR_INVALID_CONNECTION,
-                            "A 'dummy' setting is required.");
-        return FALSE;
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_DUMMY);
 
     return TRUE;
 }
@@ -75,12 +66,7 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMSettingDummy *s_dummy = nm_connection_get_setting_dummy(connection);
-
-    if (!s_dummy) {
-        s_dummy = (NMSettingDummy *) nm_setting_dummy_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_dummy);
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_DUMMY);
 }
 
 static gboolean
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 95336c7a..667ea9b9 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -33,6 +33,7 @@
 #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"
@@ -1618,11 +1619,7 @@ complete_connection(NMDevice *           device,
                                   NULL,
                                   TRUE);
 
-        s_veth = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VETH);
-        if (!s_veth) {
-            s_veth = (NMSettingVeth *) nm_setting_veth_new();
-            nm_connection_add_setting(connection, NM_SETTING(s_veth));
-        }
+        s_veth = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VETH);
 
         ifindex = nm_device_get_ip_ifindex(device);
         if (ifindex > 0) {
@@ -1664,11 +1661,7 @@ complete_connection(NMDevice *           device,
     if (s_pppoe && !nm_setting_verify(NM_SETTING(s_pppoe), NULL, error))
         return FALSE;
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    if (!s_wired) {
-        s_wired = (NMSettingWired *) nm_setting_wired_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_wired));
-    }
+    s_wired = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRED);
 
     /* Default to an ethernet-only connection, but if a PPPoE setting was given
      * then PPPoE should be our connection type.
@@ -1782,20 +1775,15 @@ get_s390_subchannels(NMDevice *device)
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceEthernetPrivate *priv    = NM_DEVICE_ETHERNET_GET_PRIVATE(device);
-    NMSettingWired *         s_wired = nm_connection_get_setting_wired(connection);
-    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;
-    GHashTableIter           iter;
-    const char *             key;
-    const char *             value;
-
-    if (!s_wired) {
-        s_wired = (NMSettingWired *) nm_setting_wired_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_wired);
-    }
+    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;
+    GHashTableIter  iter;
+    const char *    key;
+    const char *    value;
 
     g_object_set(nm_connection_get_setting_connection(connection),
                  NM_SETTING_CONNECTION_TYPE,
diff --git a/src/core/devices/nm-device-infiniband.c b/src/core/devices/nm-device-infiniband.c
index 37065bc8..df14dfa8 100644
--- a/src/core/devices/nm-device-infiniband.c
+++ b/src/core/devices/nm-device-infiniband.c
@@ -16,6 +16,7 @@
 #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"
 #include "libnm-core-intern/nm-core-internal.h"
 
 #define NM_DEVICE_INFINIBAND_IS_PARTITION "is-partition"
@@ -146,11 +147,7 @@ complete_connection(NMDevice *           device,
 {
     NMSettingInfiniband *s_infiniband;
 
-    s_infiniband = nm_connection_get_setting_infiniband(connection);
-    if (!s_infiniband) {
-        s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_infiniband));
-    }
+    s_infiniband = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_INFINIBAND);
 
     nm_utils_complete_generic(
         nm_device_get_platform(device),
@@ -175,15 +172,11 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMSettingInfiniband *s_infiniband   = nm_connection_get_setting_infiniband(connection);
-    const char *         mac            = nm_device_get_permanent_hw_address(device);
-    const char *         transport_mode = "datagram";
-    int                  ifindex;
-
-    if (!s_infiniband) {
-        s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_infiniband);
-    }
+    NMSettingInfiniband *s_infiniband =
+        _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_INFINIBAND);
+    const char *mac            = nm_device_get_permanent_hw_address(device);
+    const char *transport_mode = "datagram";
+    int         ifindex;
 
     if (mac && !nm_utils_hwaddr_matches(mac, -1, NULL, INFINIBAND_ALEN))
         g_object_set(s_infiniband, NM_SETTING_INFINIBAND_MAC_ADDRESS, mac, NULL);
diff --git a/src/core/devices/nm-device-ip-tunnel.c b/src/core/devices/nm-device-ip-tunnel.c
index c55dd9f6..fe2e2023 100644
--- a/src/core/devices/nm-device-ip-tunnel.c
+++ b/src/core/devices/nm-device-ip-tunnel.c
@@ -18,6 +18,7 @@
 #include "nm-manager.h"
 #include "libnm-platform/nm-platform.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 "settings/nm-settings.h"
 #include "nm-act-request.h"
@@ -416,14 +417,10 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceIPTunnel *       self        = NM_DEVICE_IP_TUNNEL(device);
-    NMDeviceIPTunnelPrivate *priv        = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
-    NMSettingIPTunnel *      s_ip_tunnel = nm_connection_get_setting_ip_tunnel(connection);
-
-    if (!s_ip_tunnel) {
-        s_ip_tunnel = (NMSettingIPTunnel *) nm_setting_ip_tunnel_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_ip_tunnel);
-    }
+    NMDeviceIPTunnel *       self = NM_DEVICE_IP_TUNNEL(device);
+    NMDeviceIPTunnelPrivate *priv = NM_DEVICE_IP_TUNNEL_GET_PRIVATE(self);
+    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)
         g_object_set(G_OBJECT(s_ip_tunnel), NM_SETTING_IP_TUNNEL_MODE, priv->mode, NULL);
diff --git a/src/core/devices/nm-device-logging.h b/src/core/devices/nm-device-logging.h
index 844e9949..c2602189 100644
--- a/src/core/devices/nm-device-logging.h
+++ b/src/core/devices/nm-device-logging.h
@@ -9,15 +9,13 @@
 #include "nm-device.h"
 
 #if !_NM_CC_SUPPORT_GENERIC
-    #define _NM_DEVICE_CAST(self) ((NMDevice *) (self))
+#define _NM_DEVICE_CAST(self) ((NMDevice *) (self))
 #elif !defined(_NMLOG_DEVICE_TYPE)
-    #define _NM_DEVICE_CAST(self)                         \
-        _Generic((self), NMDevice *                       \
-                 : ((NMDevice *) (self)), NMDevice *const \
-                 : ((NMDevice *) (self)))
+#define _NM_DEVICE_CAST(self) \
+    _Generic((self), NMDevice * : ((NMDevice *) (self)), NMDevice *const : ((NMDevice *) (self)))
 #else
-    #define _NM_DEVICE_CAST(self) \
-        _Generic((self), \
+#define _NM_DEVICE_CAST(self) \
+    _Generic((self), \
                  _NMLOG_DEVICE_TYPE *      : ((NMDevice *) (self)), \
                  _NMLOG_DEVICE_TYPE * const: ((NMDevice *) (self)), \
                  NMDevice *                : ((NMDevice *) (self)), \
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index 7302ecdf..937ba9e4 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -13,6 +13,7 @@
 #include "settings/nm-settings.h"
 #include "nm-act-request.h"
 #include "nm-manager.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-macvlan.h"
@@ -419,14 +420,10 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceMacvlanPrivate *priv      = NM_DEVICE_MACVLAN_GET_PRIVATE(device);
-    NMSettingMacvlan *      s_macvlan = nm_connection_get_setting_macvlan(connection);
-    int                     new_mode;
-
-    if (!s_macvlan) {
-        s_macvlan = (NMSettingMacvlan *) nm_setting_macvlan_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_macvlan);
-    }
+    NMDeviceMacvlanPrivate *priv = NM_DEVICE_MACVLAN_GET_PRIVATE(device);
+    NMSettingMacvlan *      s_macvlan =
+        _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_MACVLAN);
+    int new_mode;
 
     new_mode = platform_mode_to_setting(priv->props.mode);
     if (new_mode != nm_setting_macvlan_get_mode(s_macvlan))
diff --git a/src/core/devices/nm-device-ppp.c b/src/core/devices/nm-device-ppp.c
index 8df245bb..04051bec 100644
--- a/src/core/devices/nm-device-ppp.c
+++ b/src/core/devices/nm-device-ppp.c
@@ -93,28 +93,35 @@ ppp_ifindex_set(NMPPPManager *ppp_manager, int ifindex, const char *iface, gpoin
 }
 
 static void
-ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
+_ppp_ip4_config_handle(NMDevicePpp *self)
 {
-    NMDevice *          device = NM_DEVICE(user_data);
-    NMDevicePpp *       self   = NM_DEVICE_PPP(device);
+    NMDevice *          device = NM_DEVICE(self);
     NMDevicePppPrivate *priv   = NM_DEVICE_PPP_GET_PRIVATE(self);
 
-    _LOGT(LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
+    if (!priv->ip4_config)
+        return;
 
-    if (nm_device_get_state(device) == NM_DEVICE_STATE_IP_CONFIG) {
-        if (nm_device_activate_ip4_state_in_conf(device)) {
-            nm_device_activate_schedule_ip_config_result(device,
-                                                         AF_INET,
-                                                         NM_IP_CONFIG_CAST(config));
-            return;
-        }
-    } else {
-        if (priv->ip4_config)
-            g_object_unref(priv->ip4_config);
-        priv->ip4_config = g_object_ref(config);
+    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)));
+        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);
+
+    _LOGT(LOGD_DEVICE | LOGD_PPP, "received IPv4 config from pppd");
+    nm_g_object_ref_set(&priv->ip4_config, config);
+    _ppp_ip4_config_handle(self);
+}
+
 static gboolean
 check_connection_compatible(NMDevice *device, NMConnection *connection, GError **error)
 {
@@ -193,6 +200,15 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
     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;
+}
+
 static NMActStageReturn
 act_stage3_ip_config_start(NMDevice *           device,
                            int                  addr_family,
@@ -203,13 +219,8 @@ act_stage3_ip_config_start(NMDevice *           device,
         NMDevicePpp *       self = NM_DEVICE_PPP(device);
         NMDevicePppPrivate *priv = NM_DEVICE_PPP_GET_PRIVATE(self);
 
-        if (priv->ip4_config) {
-            if (out_config)
-                *out_config = g_steal_pointer(&priv->ip4_config);
-            else
-                g_clear_object(&priv->ip4_config);
-            return NM_ACT_STAGE_RETURN_SUCCESS;
-        }
+        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;
@@ -256,6 +267,8 @@ deactivate(NMDevice *device)
         nm_ppp_manager_stop(priv->ppp_manager, NULL, NULL, NULL);
         g_clear_object(&priv->ppp_manager);
     }
+
+    g_clear_object(&priv->ip4_config);
 }
 
 static void
diff --git a/src/core/devices/nm-device-private.h b/src/core/devices/nm-device-private.h
index eb37b14f..b55e8b43 100644
--- a/src/core/devices/nm-device-private.h
+++ b/src/core/devices/nm-device-private.h
@@ -143,7 +143,7 @@ NMIP6Config *nm_device_ip6_config_new(NMDevice *self);
 
 NMIPConfig *nm_device_ip_config_new(NMDevice *self, int addr_family);
 
-NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self);
+NML3ConfigData *nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source);
 
 /*****************************************************************************/
 
diff --git a/src/core/devices/nm-device-tun.c b/src/core/devices/nm-device-tun.c
index ebccddfc..0ab08f65 100644
--- a/src/core/devices/nm-device-tun.c
+++ b/src/core/devices/nm-device-tun.c
@@ -17,6 +17,7 @@
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-tun.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceTun
@@ -186,11 +187,7 @@ update_connection(NMDevice *device, NMConnection *connection)
         return;
     }
 
-    s_tun = nm_connection_get_setting_tun(connection);
-    if (!s_tun) {
-        s_tun = (NMSettingTun *) nm_setting_tun_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_tun);
-    }
+    s_tun = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TUN);
 
     if (mode != nm_setting_tun_get_mode(s_tun))
         g_object_set(G_OBJECT(s_tun), NM_SETTING_TUN_MODE, (guint) mode, NULL);
diff --git a/src/core/devices/nm-device-utils.c b/src/core/devices/nm-device-utils.c
index f40ca570..2a6ab04e 100644
--- a/src/core/devices/nm-device-utils.c
+++ b/src/core/devices/nm-device-utils.c
@@ -10,7 +10,7 @@
 /*****************************************************************************/
 
 NM_UTILS_LOOKUP_STR_DEFINE(
-    nm_device_state_queued_state_to_str,
+    nm_device_state_queued_state_to_string,
     NMDeviceState,
     NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN,
@@ -41,14 +41,14 @@ NM_UTILS_LOOKUP_STR_DEFINE(
                              NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), );
 
 const char *
-nm_device_state_to_str(NMDeviceState state)
+nm_device_state_to_string(NMDeviceState state)
 {
-    return nm_device_state_queued_state_to_str(state)
+    return nm_device_state_queued_state_to_string(state)
            + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE);
 }
 
 NM_UTILS_LOOKUP_STR_DEFINE(
-    nm_device_state_reason_to_str,
+    nm_device_state_reason_to_string,
     NMDeviceStateReason,
     NM_UTILS_LOOKUP_DEFAULT(NULL),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"),
@@ -129,7 +129,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(
                              "sriov-configuration-failed"),
     NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), );
 
-NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_str,
+NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_string,
                            NMDeviceMtuSource,
                            NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"),
@@ -138,7 +138,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_device_mtu_source_to_str,
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION,
                                                     "connection"), );
 
-NM_UTILS_LOOKUP_STR_DEFINE(nm_device_sys_iface_state_to_str,
+NM_UTILS_LOOKUP_STR_DEFINE(nm_device_sys_iface_state_to_string,
                            NMDeviceSysIfaceState,
                            NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
@@ -147,7 +147,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_device_sys_iface_state_to_str,
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED,
                                                     "removed"), );
 
-NM_UTILS_LOOKUP_STR_DEFINE(nm_device_ip_state_to_str,
+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"),
@@ -267,7 +267,18 @@ resolve_addr_resolved_cb(NMDnsSystemdResolved *                   resolved,
         _LOG2D(info, "error resolving via systemd-resolved: %s", error->message);
 
         dbus_error = g_dbus_error_get_remote_error(error);
-        if (nm_streq0(dbus_error, "org.freedesktop.resolve1.DnsError.NXDOMAIN")) {
+        if (NM_STR_HAS_PREFIX(dbus_error, "org.freedesktop.resolve1.")) {
+            /* systemd-resolved is enabled but it couldn't resolve the
+             * address via DNS.  Don't fall back to spawning the helper,
+             * because the helper will possibly ask again to
+             * systemd-resolved (via /etc/resolv.conf), potentially using
+             * other protocols than DNS or returning synthetic results.
+             *
+             * Consider the error as the final indication that the address
+             * can't be resolved.
+             *
+             * See: https://www.freedesktop.org/wiki/Software/systemd/resolved/#commonerrors
+             */
             resolve_addr_complete(info, NULL, g_error_copy(error));
             return;
         }
diff --git a/src/core/devices/nm-device-utils.h b/src/core/devices/nm-device-utils.h
index 8bc957a4..7200b0ea 100644
--- a/src/core/devices/nm-device-utils.h
+++ b/src/core/devices/nm-device-utils.h
@@ -5,11 +5,11 @@
 
 /*****************************************************************************/
 
-const char *nm_device_state_to_str(NMDeviceState state);
-const char *nm_device_state_reason_to_str(NMDeviceStateReason reason);
+const char *nm_device_state_to_string(NMDeviceState state);
+const char *nm_device_state_reason_to_string(NMDeviceStateReason reason);
 
-#define nm_device_state_reason_to_str_a(reason) \
-    NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, reason)
+#define nm_device_state_reason_to_string_a(reason) \
+    NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_string, reason)
 
 static inline NMDeviceStateReason
 nm_device_state_reason_check(NMDeviceStateReason reason)
@@ -41,7 +41,7 @@ nm_device_state_reason_check(NMDeviceStateReason reason)
 #define NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "queued-state-change-"
 #define NM_PENDING_ACTIONPREFIX_ACTIVATION          "activation-"
 
-const char *nm_device_state_queued_state_to_str(NMDeviceState state);
+const char *nm_device_state_queued_state_to_string(NMDeviceState state);
 
 /*****************************************************************************/
 
@@ -52,7 +52,7 @@ typedef enum {
     NM_DEVICE_MTU_SOURCE_CONNECTION,
 } NMDeviceMtuSource;
 
-const char *nm_device_mtu_source_to_str(NMDeviceMtuSource mtu_source);
+const char *nm_device_mtu_source_to_string(NMDeviceMtuSource mtu_source);
 
 /*****************************************************************************/
 
@@ -69,7 +69,7 @@ typedef enum _nm_packed {
     NM_DEVICE_SYS_IFACE_STATE_REMOVED,
 } NMDeviceSysIfaceState;
 
-const char *nm_device_sys_iface_state_to_str(NMDeviceSysIfaceState sys_iface_state);
+const char *nm_device_sys_iface_state_to_string(NMDeviceSysIfaceState sys_iface_state);
 
 /*****************************************************************************/
 
@@ -81,7 +81,7 @@ typedef enum {
     NM_DEVICE_IP_STATE_FAIL,
 } NMDeviceIPState;
 
-const char *nm_device_ip_state_to_str(NMDeviceIPState ip_state);
+const char *nm_device_ip_state_to_string(NMDeviceIPState ip_state);
 
 /*****************************************************************************/
 
diff --git a/src/core/devices/nm-device-vlan.c b/src/core/devices/nm-device-vlan.c
index 548245f8..d1e57411 100644
--- a/src/core/devices/nm-device-vlan.c
+++ b/src/core/devices/nm-device-vlan.c
@@ -19,6 +19,7 @@
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-manager.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-platform/nmp-object.h"
 #include "libnm-platform/nm-platform-utils.h"
@@ -432,18 +433,13 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceVlanPrivate * priv    = NM_DEVICE_VLAN_GET_PRIVATE(device);
-    NMSettingVlan *       s_vlan  = nm_connection_get_setting_vlan(connection);
+    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;
     guint                 vlan_id;
     _NMVlanFlags          vlan_flags;
 
-    if (!s_vlan) {
-        s_vlan = (NMSettingVlan *) nm_setting_vlan_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_vlan);
-    }
-
     polnk = nm_platform_link_get_lnk(nm_device_get_platform(device),
                                      ifindex,
                                      NM_LINK_TYPE_VLAN,
diff --git a/src/core/devices/nm-device-vrf.c b/src/core/devices/nm-device-vrf.c
index 2a1f42c6..c752fa0a 100644
--- a/src/core/devices/nm-device-vrf.c
+++ b/src/core/devices/nm-device-vrf.c
@@ -9,6 +9,7 @@
 #include "nm-device-private.h"
 #include "nm-manager.h"
 #include "nm-setting-vrf.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-platform/nm-platform.h"
 #include "settings/nm-settings.h"
 
@@ -199,12 +200,7 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceVrfPrivate *priv  = NM_DEVICE_VRF_GET_PRIVATE(device);
-    NMSettingVrf *      s_vrf = _nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF);
-
-    if (!s_vrf) {
-        s_vrf = (NMSettingVrf *) nm_setting_vrf_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_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);
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index f9dfad2a..fcd6950b 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -17,6 +17,7 @@
 #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"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceVxlan
@@ -399,14 +400,9 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMDeviceVxlanPrivate *priv    = NM_DEVICE_VXLAN_GET_PRIVATE(device);
-    NMSettingVxlan *      s_vxlan = nm_connection_get_setting_vxlan(connection);
-    char                  sbuf[NM_UTILS_INET_ADDRSTRLEN];
-
-    if (!s_vxlan) {
-        s_vxlan = (NMSettingVxlan *) nm_setting_vxlan_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_vxlan);
-    }
+    NMDeviceVxlanPrivate *priv = NM_DEVICE_VXLAN_GET_PRIVATE(device);
+    NMSettingVxlan *s_vxlan    = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_VXLAN);
+    char            sbuf[NM_UTILS_INET_ADDRSTRLEN];
 
     if (priv->props.id != nm_setting_vxlan_get_id(s_vxlan))
         g_object_set(G_OBJECT(s_vxlan), NM_SETTING_VXLAN_ID, priv->props.id, NULL);
diff --git a/src/core/devices/nm-device-wireguard.c b/src/core/devices/nm-device-wireguard.c
index aa9c46eb..f129478f 100644
--- a/src/core/devices/nm-device-wireguard.c
+++ b/src/core/devices/nm-device-wireguard.c
@@ -11,6 +11,7 @@
 #include <linux/fib_rules.h>
 
 #include "nm-setting-wireguard.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
 #include "nm-device-private.h"
@@ -1891,17 +1892,11 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceWireGuardPrivate *priv = NM_DEVICE_WIREGUARD_GET_PRIVATE(device);
-    NMSettingWireGuard *      s_wg =
-        NM_SETTING_WIREGUARD(nm_connection_get_setting(connection, NM_TYPE_SETTING_WIREGUARD));
-    const NMPObject *            obj_wg;
+    NMSettingWireGuard *s_wg = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIREGUARD);
+    const NMPObject *   obj_wg;
     const NMPObjectLnkWireGuard *olnk_wg;
     guint                        i;
 
-    if (!s_wg) {
-        s_wg = NM_SETTING_WIREGUARD(nm_setting_wireguard_new());
-        nm_connection_add_setting(connection, NM_SETTING(s_wg));
-    }
-
     g_object_set(s_wg,
                  NM_SETTING_WIREGUARD_FWMARK,
                  (guint) priv->lnk_curr.fwmark,
diff --git a/src/core/devices/nm-device-wpan.c b/src/core/devices/nm-device-wpan.c
index 73d79410..9bc43a85 100644
--- a/src/core/devices/nm-device-wpan.c
+++ b/src/core/devices/nm-device-wpan.c
@@ -18,6 +18,7 @@
 #include "libnm-platform/nm-platform.h"
 #include "nm-device-factory.h"
 #include "nm-setting-wpan.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceWpan
@@ -71,13 +72,7 @@ complete_connection(NMDevice *           device,
 static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
-    NMSettingWpan *s_wpan =
-        NM_SETTING_WPAN(nm_connection_get_setting(connection, NM_TYPE_SETTING_WPAN));
-
-    if (!s_wpan) {
-        s_wpan = (NMSettingWpan *) nm_setting_wpan_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_wpan);
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WPAN);
 }
 
 static gboolean
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index 50386d00..ea1b9647 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -185,7 +185,7 @@ typedef struct {
 typedef struct {
     NMDhcpClient *client;
     NMDhcpConfig *config;
-    gulong        state_sigid;
+    gulong        notify_sigid;
     guint         grace_id;
     bool          grace_pending : 1;
     bool          was_active : 1;
@@ -290,7 +290,8 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMDevice,
                              PROP_STATISTICS_RX_BYTES,
                              PROP_IP4_CONNECTIVITY,
                              PROP_IP6_CONNECTIVITY,
-                             PROP_INTERFACE_FLAGS, );
+                             PROP_INTERFACE_FLAGS,
+                             PROP_PORTS, );
 
 typedef struct _NMDevicePrivate {
     bool in_state_changed;
@@ -316,7 +317,12 @@ typedef struct _NMDevicePrivate {
         guint queued_ip_config_id_x[2];
     };
 
-    GSList *pending_actions;
+    struct {
+        const char **arr;
+        guint        len;
+        guint        alloc;
+    } pending_actions;
+
     GSList *dad6_failed_addrs;
 
     NMDBusTrackObjPath parent_device;
@@ -500,6 +506,7 @@ typedef struct _NMDevicePrivate {
     NMDeviceStageState stage1_sriov_state : 3;
 
     bool ip_config_started : 1;
+    bool tc_committed : 1;
 
     char *current_stable_id;
 
@@ -574,7 +581,6 @@ typedef struct _NMDevicePrivate {
         AppliedConfig ip6_config;
         /* Event ID of the current IP6 config from DHCP */
         char *           event_id;
-        gulong           prefix_sigid;
         NMNDiscDHCPLevel mode;
         guint            needed_prefixes;
     } dhcp6;
@@ -696,15 +702,17 @@ typedef struct _NMDevicePrivate {
     guint sriov_reset_pending;
 
     struct {
-        guint   timeout_id;
-        guint   refresh_rate_ms;
-        guint64 tx_bytes;
-        guint64 rx_bytes;
+        GSource *timeout_source;
+        guint    refresh_rate_ms;
+        guint64  tx_bytes;
+        guint64  rx_bytes;
     } stats;
 
     bool mtu_force_set_done : 1;
 
     NMOptionBool promisc_reset;
+
+    GVariant *ports_variant; /* Array of port devices D-Bus path */
 } NMDevicePrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
@@ -1190,6 +1198,29 @@ _prop_get_connection_llmnr(NMDevice *self)
                                                        NM_SETTING_CONNECTION_LLMNR_DEFAULT);
 }
 
+static NMSettingConnectionDnsOverTls
+_prop_get_connection_dns_over_tls(NMDevice *self)
+{
+    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);
+
+    connection = nm_device_get_applied_connection(self);
+    if (connection)
+        dns_over_tls = nm_setting_connection_get_dns_over_tls(
+            nm_connection_get_setting_connection(connection));
+    if (dns_over_tls != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT)
+        return dns_over_tls;
+
+    return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                       NM_CON_DEFAULT("connection.dns-over-tls"),
+                                                       self,
+                                                       NM_SETTING_CONNECTION_DNS_OVER_TLS_NO,
+                                                       NM_SETTING_CONNECTION_DNS_OVER_TLS_YES,
+                                                       NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
+}
+
 static guint32
 _prop_get_ipvx_route_table(NMDevice *self, int addr_family)
 {
@@ -2407,7 +2438,7 @@ nm_device_ip_config_new(NMDevice *self, int addr_family)
 }
 
 NML3ConfigData *
-nm_device_create_l3_config_data(NMDevice *self)
+nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source)
 {
     int ifindex;
 
@@ -2417,7 +2448,7 @@ nm_device_create_l3_config_data(NMDevice *self)
     if (ifindex <= 0)
         g_return_val_if_reached(NULL);
 
-    return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex);
+    return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex, source);
 }
 
 static void
@@ -2554,8 +2585,8 @@ nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_st
     if (priv->sys_iface_state != sys_iface_state) {
         _LOGT(LOGD_DEVICE,
               "sys-iface-state: %s -> %s",
-              nm_device_sys_iface_state_to_str(priv->sys_iface_state),
-              nm_device_sys_iface_state_to_str(sys_iface_state));
+              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;
     }
 
@@ -2741,7 +2772,7 @@ nm_device_sysctl_ip_conf_get_int_checked(NMDevice *  self,
 }
 
 static void
-set_ipv6_token(NMDevice *self, NMUtilsIPv6IfaceId iid, const char *token_str)
+set_ipv6_token(NMDevice *self, const NMUtilsIPv6IfaceId *iid, const char *token_str)
 {
     NMPlatform *          platform;
     int                   ifindex;
@@ -2757,7 +2788,7 @@ set_ipv6_token(NMDevice *self, NMUtilsIPv6IfaceId iid, const char *token_str)
     ifindex  = nm_device_get_ip_ifindex(self);
     link     = nm_platform_link_get(platform, ifindex);
 
-    if (link && link->inet6_token.id == iid.id) {
+    if (link && link->inet6_token.id == iid->id) {
         _LOGT(LOGD_DEVICE | LOGD_IP6, "token %s already set", token_str);
         return;
     }
@@ -2905,7 +2936,7 @@ _set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
           "ip%c-state: set to %d (%s)",
           nm_utils_addr_family_to_char(addr_family),
           (int) new_state,
-          nm_device_ip_state_to_str(new_state));
+          nm_device_ip_state_to_string(new_state));
 
     priv->ip_state_x_[IS_IPv4] = new_state;
 
@@ -3141,8 +3172,9 @@ _set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname)
 
         nm_platform_process_events_ensure_link(platform, priv->ip_ifindex, priv->ip_iface);
 
-        if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
-            nm_platform_link_set_user_ipv6ll_enabled(platform, priv->ip_ifindex, TRUE);
+        nm_platform_link_set_inet6_addr_gen_mode(platform,
+                                                 priv->ip_ifindex,
+                                                 NM_IN6_ADDR_GEN_MODE_NONE);
 
         if (!nm_platform_link_is_up(platform, priv->ip_ifindex))
             nm_platform_link_change_flags(platform, priv->ip_ifindex, IFF_UP, TRUE);
@@ -3444,7 +3476,7 @@ _stats_set_refresh_rate(NMDevice *self, guint refresh_rate_ms)
     if (_stats_refresh_rate_real(old_rate) == refresh_rate_ms)
         return;
 
-    nm_clear_g_source(&priv->stats.timeout_id);
+    nm_clear_g_source_inst(&priv->stats.timeout_source);
 
     if (!refresh_rate_ms)
         return;
@@ -3456,7 +3488,7 @@ _stats_set_refresh_rate(NMDevice *self, guint refresh_rate_ms)
     if (ifindex > 0)
         nm_platform_link_refresh(nm_device_get_platform(self), ifindex);
 
-    priv->stats.timeout_id = g_timeout_add(refresh_rate_ms, _stats_timeout_cb, self);
+    priv->stats.timeout_source = nm_g_timeout_add_source(refresh_rate_ms, _stats_timeout_cb, self);
 }
 
 /*****************************************************************************/
@@ -4509,7 +4541,7 @@ concheck_cb(NMConnectivity *           connectivity,
     seq = handle->seq;
 
     _LOGT(LOGD_CONCHECK,
-          "connectivity: [Ipv%c] complete check (seq:%llu, state:%s)",
+          "connectivity: [IPv%c] complete check (seq:%llu, state:%s)",
           nm_utils_addr_family_to_char(handle->addr_family),
           (long long unsigned) handle->seq,
           nm_connectivity_state_to_string(state));
@@ -5803,7 +5835,7 @@ nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink)
         _notify(self, PROP_PATH);
     }
 
-    if (plink && !nm_str_is_empty(plink->name) && nm_utils_strdup_reset(&priv->iface_, plink->name))
+    if (plink && !nm_str_is_empty(plink->name) && nm_strdup_reset(&priv->iface_, plink->name))
         _notify(self, PROP_IFACE);
 
     str = plink ? plink->driver : NULL;
@@ -5976,9 +6008,10 @@ config_changed(NMConfig *          config,
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state > NM_DEVICE_STATE_ACTIVATED) {
+    if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state >= NM_DEVICE_STATE_ACTIVATED) {
         priv->ignore_carrier = nm_config_data_get_ignore_carrier(config_data, self);
-        if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES))
+        if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES)
+            && !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
             device_init_static_sriov_num_vfs(self);
     }
 }
@@ -6023,7 +6056,7 @@ realize_start_setup(NMDevice *            self,
     NMPlatform *         platform;
     NMDeviceCapabilities capabilities = 0;
     NMConfig *           config;
-    guint                real_rate;
+    guint                refresh_rate_ms;
     gboolean             unmanaged;
 
     /* plink is a NMPlatformLink type, however, we require it to come from the platform
@@ -6087,8 +6120,8 @@ realize_start_setup(NMDevice *            self,
         if (priv->firmware_version)
             _notify(self, PROP_FIRMWARE_VERSION);
 
-        if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
-            priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled(platform, priv->ifindex);
+        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;
@@ -6131,10 +6164,12 @@ realize_start_setup(NMDevice *            self,
 
     nm_device_set_carrier_from_platform(self);
 
-    nm_assert(!priv->stats.timeout_id);
-    real_rate = _stats_refresh_rate_real(priv->stats.refresh_rate_ms);
-    if (real_rate)
-        priv->stats.timeout_id = g_timeout_add(real_rate, _stats_timeout_cb, self);
+    nm_assert(!priv->stats.timeout_source);
+    refresh_rate_ms = _stats_refresh_rate_real(priv->stats.refresh_rate_ms);
+    if (refresh_rate_ms > 0) {
+        priv->stats.timeout_source =
+            nm_g_timeout_add_source(refresh_rate_ms, _stats_timeout_cb, self);
+    }
 
     klass->realize_start_notify(self, plink);
 
@@ -6320,7 +6355,7 @@ nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
         _notify(self, PROP_PHYSICAL_PORT_ID);
     }
 
-    nm_clear_g_source(&priv->stats.timeout_id);
+    nm_clear_g_source_inst(&priv->stats.timeout_source);
     _stats_update_counters(self, 0, 0);
 
     priv->hw_addr_len_ = 0;
@@ -6442,9 +6477,9 @@ slave_state_changed(NMDevice *          slave,
           "slave %s state change %d (%s) -> %d (%s)",
           nm_device_get_iface(slave),
           slave_old_state,
-          nm_device_state_to_str(slave_old_state),
+          nm_device_state_to_string(slave_old_state),
           slave_new_state,
-          nm_device_state_to_str(slave_new_state));
+          nm_device_state_to_string(slave_new_state));
 
     /* Don't try to enslave slaves until the master is ready */
     if (priv->state < NM_DEVICE_STATE_CONFIG)
@@ -6788,7 +6823,9 @@ nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
             priv->is_enslaved = TRUE;
 
             _notify(self, PROP_MASTER);
-            _notify(priv->master, PROP_SLAVES);
+
+            nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
+            nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
         } else if (activating) {
             _LOGW(LOGD_DEVICE,
                   "Activation: connection '%s' could not be enslaved",
@@ -6852,8 +6889,11 @@ nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
 
     if (priv->is_enslaved) {
         priv->is_enslaved = FALSE;
+
         _notify(self, PROP_MASTER);
-        _notify(priv->master, PROP_SLAVES);
+
+        nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
+        nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
     }
 }
 
@@ -7339,7 +7379,7 @@ nm_device_generate_connection(NMDevice *self,
                          NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
                          NM_IN6_ADDR_GEN_MODE_EUI64,
                          NM_SETTING_IP6_CONFIG_TOKEN,
-                         nm_utils_inet6_interface_identifier_to_token(pllink->inet6_token, sbuf),
+                         nm_utils_inet6_interface_identifier_to_token(&pllink->inet6_token, sbuf),
                          NULL);
         }
     }
@@ -7756,7 +7796,8 @@ recheck_available(gpointer user_data)
               now_available ? "" : "not ",
               new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for"
                                                        : "will transition to",
-              nm_device_state_to_str(new_state == NM_DEVICE_STATE_UNAVAILABLE ? state : new_state));
+              nm_device_state_to_string(new_state == NM_DEVICE_STATE_UNAVAILABLE ? state
+                                                                                 : new_state));
 
         priv->recheck_available.available_reason   = NM_DEVICE_STATE_REASON_NONE;
         priv->recheck_available.unavailable_reason = NM_DEVICE_STATE_REASON_NONE;
@@ -8259,7 +8300,7 @@ activate_stage1_device_prepare(NMDevice *self)
                 priv->master_ready_id =
                     g_signal_connect(active,
                                      "notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY,
-                                     (GCallback) master_ready_cb,
+                                     G_CALLBACK(master_ready_cb),
                                      self);
             }
             return;
@@ -8478,10 +8519,7 @@ tc_commit(NMDevice *self)
     qdiscs   = nm_utils_qdiscs_from_tc_setting(platform, s_tc, ip_ifindex);
     tfilters = nm_utils_tfilters_from_tc_setting(platform, s_tc, ip_ifindex);
 
-    if (!nm_platform_qdisc_sync(platform, ip_ifindex, qdiscs))
-        return FALSE;
-
-    if (!nm_platform_tfilter_sync(platform, ip_ifindex, tfilters))
+    if (!nm_platform_tc_sync(platform, ip_ifindex, qdiscs, tfilters))
         return FALSE;
 
     return TRUE;
@@ -8511,13 +8549,14 @@ activate_stage2_device_config(NMDevice *self)
         _ethtool_state_set(self);
 
     if (!nm_device_sys_iface_state_is_external_or_assume(self)) {
-        if (!tc_commit(self)) {
-            _LOGW(LOGD_IP6, "failed applying traffic control rules");
+        if (!priv->tc_committed && !tc_commit(self)) {
+            _LOGW(LOGD_DEVICE, "failed applying traffic control rules");
             nm_device_state_changed(self,
                                     NM_DEVICE_STATE_FAILED,
                                     NM_DEVICE_STATE_REASON_CONFIG_FAILED);
             return;
         }
+        priv->tc_committed = TRUE;
     }
 
     _routing_rules_sync(self, NM_TERNARY_TRUE);
@@ -8981,6 +9020,7 @@ ensure_con_ip_config(NMDevice *self, int addr_family)
                                     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 {
@@ -9028,7 +9068,7 @@ dhcp4_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
 
     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.state_sigid);
+        nm_clear_g_signal_handler(priv->dhcp_data_4.client, &priv->dhcp_data_4.notify_sigid);
 
         if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED)
             nm_dhcp_client_stop(priv->dhcp_data_4.client, release);
@@ -9251,7 +9291,7 @@ ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit)
 
         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);
+            set_ipv6_token(self, &iid, ip6_addr_gen_token);
         }
     }
 
@@ -9291,7 +9331,7 @@ dhcp4_lease_change(NMDevice *self, NMIP4Config *config, gboolean bound)
         return FALSE;
     }
 
-    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE, self, NULL, NULL, NULL, NULL);
+    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_4, self, NULL, NULL, NULL, NULL);
 
     return TRUE;
 }
@@ -9373,7 +9413,7 @@ dhcp4_fail(NMDevice *self, NMDhcpState dhcp_state)
 
     _LOGD(LOGD_DHCP4,
           "DHCPv4 failed (ip_state %s, was_active %d)",
-          nm_device_ip_state_to_str(priv->ip_state_4),
+          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. */
@@ -9429,21 +9469,27 @@ dhcp4_dad_cb(NMDevice *self, NMIP4Config **configs, gboolean success)
 }
 
 static void
-dhcp4_state_changed(NMDhcpClient *client,
-                    NMDhcpState   state,
-                    NMIP4Config * ip4_config,
-                    GHashTable *  options,
-                    gpointer      user_data)
+dhcp4_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
 {
-    NMDevice *       self = NM_DEVICE(user_data);
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    NMIP4Config *    manual, **configs;
+    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);
 
-    g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET);
-    g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
+    state      = notify_data->state_changed.dhcp_state;
+    ip4_config = NM_IP4_CONFIG(notify_data->state_changed.ip_config);
+    options    = notify_data->state_changed.options;
 
-    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state);
+    nm_assert(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
+
+    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", (int) state);
 
     switch (state) {
     case NM_DHCP_STATE_BOUND:
@@ -9479,6 +9525,7 @@ dhcp4_state_changed(NMDhcpClient *client,
                                         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));
 
@@ -9594,10 +9641,10 @@ dhcp4_start(NMDevice *self)
         return NM_ACT_STAGE_RETURN_FAILURE;
     }
 
-    priv->dhcp_data_4.state_sigid = g_signal_connect(priv->dhcp_data_4.client,
-                                                     NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-                                                     G_CALLBACK(dhcp4_state_changed),
-                                                     self);
+    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;
@@ -9768,8 +9815,7 @@ dhcp6_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
     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.state_sigid);
-        nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp6.prefix_sigid);
+        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);
@@ -9805,7 +9851,7 @@ dhcp6_lease_change(NMDevice *self)
         return FALSE;
     }
 
-    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE, self, NULL, NULL, NULL, NULL);
+    nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_6, self, NULL, NULL, NULL, NULL);
 
     return TRUE;
 }
@@ -9818,7 +9864,7 @@ dhcp6_fail(NMDevice *self, NMDhcpState dhcp_state)
 
     _LOGD(LOGD_DHCP6,
           "DHCPv6 failed (ip_state %s, was_active %d)",
-          nm_device_ip_state_to_str(priv->ip_state_6),
+          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. */
@@ -9869,20 +9915,34 @@ clear_config:
 }
 
 static void
-dhcp6_state_changed(NMDhcpClient *client,
-                    NMDhcpState   state,
-                    NMIP6Config * ip6_config,
-                    GHashTable *  options,
-                    gpointer      user_data)
+dhcp6_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
 {
-    NMDevice *       self     = NM_DEVICE(user_data);
     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);
 
-    g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET6);
-    g_return_if_fail(!ip6_config || NM_IS_IP6_CONFIG(ip6_config));
+    state      = notify_data->state_changed.dhcp_state;
+    ip6_config = NM_IP6_CONFIG(notify_data->state_changed.ip_config);
+    options    = notify_data->state_changed.options;
 
-    _LOGD(LOGD_DHCP6, "new DHCPv6 client state %d", state);
+    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:
@@ -9962,17 +10022,6 @@ dhcp6_state_changed(NMDhcpClient *client,
     }
 }
 
-static void
-dhcp6_prefix_delegated(NMDhcpClient *client, NMPlatformIP6Address *prefix, gpointer user_data)
-{
-    NMDevice *self = NM_DEVICE(user_data);
-
-    /* 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, prefix);
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -10054,14 +10103,10 @@ dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
         return FALSE;
     }
 
-    priv->dhcp_data_6.state_sigid = g_signal_connect(priv->dhcp_data_6.client,
-                                                     NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-                                                     G_CALLBACK(dhcp6_state_changed),
-                                                     self);
-    priv->dhcp6.prefix_sigid      = g_signal_connect(priv->dhcp_data_6.client,
-                                                NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
-                                                G_CALLBACK(dhcp6_prefix_delegated),
-                                                self);
+    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;
@@ -10321,12 +10366,12 @@ check_and_add_ipv6ll_addr(NMDevice *self)
         const char *      stable_id;
 
         stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
-        if (!nm_utils_ipv6_addr_set_stable_privacy(stable_type,
-                                                   &lladdr,
-                                                   nm_device_get_iface(self),
-                                                   stable_id,
-                                                   priv->linklocal6_dad_counter++,
-                                                   &error)) {
+        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);
@@ -10348,7 +10393,7 @@ check_and_add_ipv6ll_addr(NMDevice *self)
             _LOGW(LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
             return;
         }
-        nm_utils_ipv6_addr_set_interface_identifier(&lladdr, iid);
+        nm_utils_ipv6_addr_set_interface_identifier(&lladdr, &iid);
         addr_type = "EUI-64";
     }
 
@@ -10616,9 +10661,9 @@ _commit_mtu(NMDevice *self, const NMIP4Config *config)
             _LOGT(LOGD_DEVICE,
                   "mtu: value %u from source '%s' (%u), current source '%s' (%u)%s",
                   (guint) mtu,
-                  nm_device_mtu_source_to_str(source),
+                  nm_device_mtu_source_to_string(source),
                   (guint) source,
-                  nm_device_mtu_source_to_str(priv->mtu_source),
+                  nm_device_mtu_source_to_string(priv->mtu_source),
                   (guint) priv->mtu_source,
                   force ? " (forced)" : "");
         }
@@ -10801,7 +10846,7 @@ nm_device_commit_mtu(NMDevice *self)
     } else
         _LOGT(LOGD_DEVICE,
               "mtu: commit-mtu... skip due to state %s",
-              nm_device_state_to_str(state));
+              nm_device_state_to_string(state));
 }
 
 static void
@@ -10809,66 +10854,68 @@ ndisc_config_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int
 {
     NMNDiscConfigMap changed = changed_int;
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    int              ifindex;
     guint            i;
 
     g_return_if_fail(priv->act_request.obj);
 
+    ifindex = nm_device_get_ip_ifindex(self);
+
+    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);
+
+    if (ifindex <= 0)
+        return;
+
     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) {
-        guint8  plen;
         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 = 0;
-        if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
-            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;
-            plen = 64;
-        } else
-            plen = 128;
+        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,
-                                            plen,
+                                            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,
-                                                plen,
+                                                64,
                                                 ifa_flags);
         }
     }
 
     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),
-            nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
+        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),
-                nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
+            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));
         }
     }
 
@@ -11024,6 +11071,11 @@ addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
     guint32             ra_timeout;
     guint32             default_ra_timeout;
 
+    if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
+        _LOGI(LOGD_IP6, "addrconf6: kernel does not support IPv6");
+        return FALSE;
+    }
+
     connection = nm_device_get_applied_connection(self);
     g_assert(connection);
 
@@ -11037,17 +11089,17 @@ addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
     g_assert(s_ip6);
 
     if (nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET6),
-                 NM_SETTING_IP4_CONFIG_METHOD_SHARED))
+                 NM_SETTING_IP6_CONFIG_METHOD_SHARED))
         node_type = NM_NDISC_NODE_TYPE_ROUTER;
     else
         node_type = NM_NDISC_NODE_TYPE_HOST;
 
-    nm_lndp_ndisc_get_sysctl(nm_device_get_platform(self),
-                             nm_device_get_ip_iface(self),
-                             &max_addresses,
-                             &router_solicitations,
-                             &router_solicitation_interval,
-                             &default_ra_timeout);
+    nm_ndisc_get_sysctl(nm_device_get_platform(self),
+                        nm_device_get_ip_iface(self),
+                        &max_addresses,
+                        &router_solicitations,
+                        &router_solicitation_interval,
+                        &default_ra_timeout);
 
     if (node_type == NM_NDISC_NODE_TYPE_ROUTER)
         ra_timeout = 0u;
@@ -11078,15 +11130,6 @@ addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
 
     priv->ndisc_use_tempaddr = use_tempaddr;
 
-    if (NM_IN_SET(use_tempaddr,
-                  NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
-                  NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
-        && !nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
-        _LOGW(LOGD_IP6,
-              "The kernel does not support extended IFA_FLAGS needed by NM for "
-              "IPv6 private addresses. This feature is not available");
-    }
-
     /* ensure link local is ready... */
     if (!linklocal6_start(self)) {
         /* wait for the LL address to show up */
@@ -11181,21 +11224,20 @@ set_nm_ipv6ll(NMDevice *self, gboolean enable)
     NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
     int              ifindex = nm_device_get_ip_ifindex(self);
 
-    if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
-        return;
-
     priv->ipv6ll_handle = enable;
     if (ifindex > 0) {
-        const char *detail = enable ? "enable" : "disable";
-        int         r;
+        int r;
 
-        _LOGD(LOGD_IP6, "will %s userland IPv6LL", detail);
-        r = nm_platform_link_set_user_ipv6ll_enabled(nm_device_get_platform(self), ifindex, enable);
+        _LOGD(LOGD_IP6, "will %s userland IPv6LL", enable ? "enable" : "disable");
+        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);
         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)",
-                   detail,
+                   enable ? "enable" : "disable",
                    nm_strerror(r));
         }
 
@@ -11310,6 +11352,7 @@ act_stage3_ip_config_start(NMDevice *           self,
                                             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);
@@ -11521,45 +11564,6 @@ nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family)
     return TRUE;
 }
 
-/*
- * activate_stage3_ip_config_start
- *
- * Begin automatic/manual IP configuration
- *
- */
-static void
-activate_stage3_ip_config_start(NMDevice *self)
-{
-    int ifindex;
-
-    _set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
-    _set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
-
-    _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);
-
-    /* Device should be up before we can do anything with it */
-    if ((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));
-
-    if (nm_device_activate_ip4_state_in_wait(self)
-        && !nm_device_activate_stage3_ip_start(self, AF_INET))
-        return;
-
-    if (nm_device_activate_ip6_state_in_wait(self)
-        && !nm_device_activate_stage3_ip_start(self, AF_INET6))
-        return;
-
-    /* Proxy */
-    nm_device_set_proxy_config(self, NULL);
-
-    check_ip_state(self, TRUE, TRUE);
-}
-
 static void
 fw_change_zone_cb(NMFirewalldManager *      firewalld_manager,
                   NMFirewalldManagerCallId *call_id,
@@ -11642,20 +11646,19 @@ fw_change_zone(NMDevice *self)
 }
 
 /*
- * nm_device_activate_schedule_stage3_ip_config_start
+ * activate_stage3_ip_config_start
+ *
+ * Begin automatic/manual IP configuration
  *
- * Schedule IP configuration start
  */
-void
-nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
+static void
+activate_stage3_ip_config_start(NMDevice *self)
 {
-    NMDevicePrivate *priv;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
     int              ifindex;
 
-    g_return_if_fail(NM_IS_DEVICE(self));
-
-    priv = NM_DEVICE_GET_PRIVATE(self);
     g_return_if_fail(priv->act_request.obj);
+
     ifindex = nm_device_get_ip_ifindex(self);
 
     /* Add the interface to the specified firewall zone */
@@ -11676,6 +11679,51 @@ nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
 
     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);
+
+    _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);
+
+    /* 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));
+
+    if (nm_device_activate_ip4_state_in_wait(self)
+        && !nm_device_activate_stage3_ip_start(self, AF_INET))
+        return;
+
+    if (nm_device_activate_ip6_state_in_wait(self)
+        && !nm_device_activate_stage3_ip_start(self, AF_INET6))
+        return;
+
+    /* Proxy */
+    nm_device_set_proxy_config(self, NULL);
+
+    check_ip_state(self, TRUE, TRUE);
+}
+
+/*
+ * nm_device_activate_schedule_stage3_ip_config_start
+ *
+ * Schedule IP configuration start
+ */
+void
+nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
+{
+    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);
 }
 
@@ -11728,22 +11776,23 @@ activate_stage4_ip_config_timeout_6(NMDevice *self)
     activate_stage4_ip_config_timeout_x(self, AF_INET6);
 }
 
+#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)
+
 void
 nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family)
 {
     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));
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
     g_return_if_fail(priv->act_request.obj);
 
     activation_source_schedule(self,
-                               IS_IPv4 ? activate_stage4_ip_config_timeout_4
-                                       : activate_stage4_ip_config_timeout_6,
+                               activate_stage4_ip_config_timeout_x_fcn(addr_family),
                                addr_family);
 }
 
@@ -11963,7 +12012,7 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
     g_return_if_fail(ip_ifindex);
 
     if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)
-        && !nm_device_sys_iface_state_is_external_or_assume(self)) {
+        && !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,
@@ -11986,7 +12035,7 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
                 /* 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_DHCP6_CHANGE,
+                nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_6,
                                           self,
                                           NULL,
                                           NULL,
@@ -12052,7 +12101,7 @@ activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
          */
         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_DHCP4_CHANGE,
+            nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_4,
                                       self,
                                       NULL,
                                       NULL,
@@ -12425,7 +12474,7 @@ _nm_device_hash_check_invalid_keys(GHashTable *       hash,
 
         g_hash_table_iter_init(&iter, hash);
         while (g_hash_table_iter_next(&iter, (gpointer *) &k, NULL)) {
-            if (nm_utils_strv_find_first((char **) whitelist, -1, k) < 0) {
+            if (nm_strv_find_first(whitelist, -1, k) < 0) {
                 first_invalid_key = k;
                 break;
             }
@@ -12490,6 +12539,7 @@ nm_device_reactivate_ip_config(NMDevice *         self,
                                     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 {
@@ -12623,7 +12673,8 @@ can_reapply_change(NMDevice *  self,
                                                  NM_SETTING_CONNECTION_METERED,
                                                  NM_SETTING_CONNECTION_LLDP,
                                                  NM_SETTING_CONNECTION_MDNS,
-                                                 NM_SETTING_CONNECTION_LLMNR);
+                                                 NM_SETTING_CONNECTION_LLMNR,
+                                                 NM_SETTING_CONNECTION_DNS_OVER_TLS);
     }
 
     if (NM_IN_STRSET(setting_name,
@@ -12912,12 +12963,12 @@ reapply_cb(NMDevice *             self,
                                audit_args,
                                subject,
                                local->message);
-        g_dbus_method_invocation_take_error(context, local);
-        local = NULL;
-    } else {
-        nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, self, TRUE, audit_args, subject, NULL);
-        g_dbus_method_invocation_return_value(context, NULL);
+        g_dbus_method_invocation_take_error(context, g_steal_pointer(&local));
+        return;
     }
+
+    nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, self, TRUE, audit_args, subject, NULL);
+    g_dbus_method_invocation_return_value(context, NULL);
 }
 
 static void
@@ -14737,15 +14788,15 @@ _unmanaged_flags2str(NMUnmanagedFlags flags, NMUnmanagedFlags mask, char *buf, g
         tmp = buf2;
         while (TRUE) {
             if (add_separator)
-                nm_utils_strbuf_append_c(&b, &len, ',');
+                nm_strbuf_append_c(&b, &len, ',');
             add_separator = TRUE;
 
             tmp2 = strchr(tmp, ',');
             if (tmp2)
                 tmp2[0] = '\0';
 
-            nm_utils_strbuf_append_c(&b, &len, '!');
-            nm_utils_strbuf_append_str(&b, &len, tmp);
+            nm_strbuf_append_c(&b, &len, '!');
+            nm_strbuf_append_str(&b, &len, tmp);
             if (!tmp2)
                 break;
 
@@ -14950,7 +15001,7 @@ _set_unmanaged_flags(NMDevice *          self,
         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) {
+        if (priv->pending_actions.len == 0) {
             do_notify_has_pending_actions = TRUE;
             had_pending_actions           = nm_device_has_pending_action(self);
         }
@@ -15004,7 +15055,7 @@ _set_unmanaged_flags(NMDevice *          self,
           flags,
           NM_PRINT_FMT_QUOTED(allow_state_transition,
                               ", reason ",
-                              nm_device_state_reason_to_str_a(reason),
+                              nm_device_state_reason_to_string_a(reason),
                               transition_state ? ", transition-state" : "",
                               ""));
 
@@ -15744,37 +15795,39 @@ gboolean
 nm_device_add_pending_action(NMDevice *self, const char *action, gboolean assert_not_yet_pending)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-    GSList *         iter;
-    guint            count = 0;
+    gssize           idx;
 
     g_return_val_if_fail(action, FALSE);
 
-    /* Check if the action is already pending. Cannot add duplicate actions */
-    for (iter = priv->pending_actions; iter; iter = iter->next) {
-        if (nm_streq(action, iter->data)) {
-            if (assert_not_yet_pending) {
-                _LOGW(LOGD_DEVICE,
-                      "add_pending_action (%d): '%s' already pending",
-                      count + g_slist_length(iter),
-                      action);
-                g_return_val_if_reached(FALSE);
-            } else {
-                _LOGT(LOGD_DEVICE,
-                      "add_pending_action (%d): '%s' already pending (expected)",
-                      count + g_slist_length(iter),
-                      action);
-            }
-            return FALSE;
+    idx = nm_strv_find_binary_search(priv->pending_actions.arr, priv->pending_actions.len, action);
+    if (idx >= 0) {
+        if (assert_not_yet_pending) {
+            _LOGW(LOGD_DEVICE,
+                  "add_pending_action (%u): '%s' already pending",
+                  priv->pending_actions.len,
+                  action);
+            g_return_val_if_reached(FALSE);
+        } else {
+            _LOGT(LOGD_DEVICE,
+                  "add_pending_action (%u): '%s' already pending (expected)",
+                  priv->pending_actions.len,
+                  action);
         }
-        count++;
+        return FALSE;
     }
 
-    priv->pending_actions = g_slist_prepend(priv->pending_actions, (char *) action);
-    count++;
+    if (priv->pending_actions.len == priv->pending_actions.alloc) {
+        nm_assert(priv->pending_actions.alloc < G_MAXUINT / 2u);
+        priv->pending_actions.alloc = NM_MAX(priv->pending_actions.alloc * 2u, 4u);
+        priv->pending_actions.arr =
+            g_renew(const char *, priv->pending_actions.arr, priv->pending_actions.alloc);
+    }
+    nm_arr_insert_at(priv->pending_actions.arr, priv->pending_actions.len, ~idx, action);
+    priv->pending_actions.len++;
 
-    _LOGD(LOGD_DEVICE, "add_pending_action (%d): '%s'", count, action);
+    _LOGD(LOGD_DEVICE, "add_pending_action (%u): '%s'", priv->pending_actions.len, action);
 
-    if (count == 1)
+    if (priv->pending_actions.len == 1)
         _notify(self, PROP_HAS_PENDING_ACTION);
 
     return TRUE;
@@ -15796,37 +15849,38 @@ gboolean
 nm_device_remove_pending_action(NMDevice *self, const char *action, gboolean assert_is_pending)
 {
     NMDevicePrivate *priv;
-    GSList *         iter, *next;
-    guint            count = 0;
+    gssize           idx;
 
     g_return_val_if_fail(self, FALSE);
     g_return_val_if_fail(action, FALSE);
 
     priv = NM_DEVICE_GET_PRIVATE(self);
 
-    for (iter = priv->pending_actions; iter; iter = next) {
-        next = iter->next;
-        if (nm_streq(action, iter->data)) {
-            _LOGD(LOGD_DEVICE,
-                  "remove_pending_action (%d): '%s'",
-                  count + g_slist_length(iter->next), /* length excluding 'iter' */
-                  action);
-            priv->pending_actions = g_slist_delete_link(priv->pending_actions, iter);
-            if (priv->pending_actions == NULL)
-                _notify(self, PROP_HAS_PENDING_ACTION);
-            return TRUE;
-        }
-        count++;
+    idx = nm_strv_find_binary_search(priv->pending_actions.arr, priv->pending_actions.len, action);
+    if (idx >= 0) {
+        _LOGD(LOGD_DEVICE,
+              "remove_pending_action (%u): '%s'",
+              priv->pending_actions.len - 1u,
+              action);
+        nm_arr_remove_at(priv->pending_actions.arr, priv->pending_actions.len, idx);
+        priv->pending_actions.len--;
+        if (priv->pending_actions.len == 0)
+            _notify(self, PROP_HAS_PENDING_ACTION);
+        return TRUE;
     }
 
     if (assert_is_pending) {
-        _LOGW(LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending", count, action);
+        _LOGW(LOGD_DEVICE,
+              "remove_pending_action (%u): '%s' not pending",
+              priv->pending_actions.len,
+              action);
         g_return_val_if_reached(FALSE);
-    } else
+    } else {
         _LOGT(LOGD_DEVICE,
-              "remove_pending_action (%d): '%s' not pending (expected)",
-              count,
+              "remove_pending_action (%u): '%s' not pending (expected)",
+              priv->pending_actions.len,
               action);
+    }
 
     return FALSE;
 }
@@ -15836,15 +15890,15 @@ nm_device_has_pending_action_reason(NMDevice *self)
 {
     NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
 
-    if (priv->pending_actions) {
-        if (!priv->pending_actions->next && nm_device_get_state(self) == NM_DEVICE_STATE_ACTIVATED
-            && nm_streq(priv->pending_actions->data, NM_PENDING_ACTION_CARRIER_WAIT)) {
+    if (priv->pending_actions.len > 0) {
+        if (priv->pending_actions.len == 1 && nm_device_get_state(self) == NM_DEVICE_STATE_ACTIVATED
+            && nm_streq(priv->pending_actions.arr[0], NM_PENDING_ACTION_CARRIER_WAIT)) {
             /* if the device is already in activated state, and the only reason
              * why it appears still busy is "carrier-wait", then we are already complete. */
             return NULL;
         }
 
-        return priv->pending_actions->data;
+        return priv->pending_actions.arr[0];
     }
 
     if (nm_device_is_real(self)
@@ -16014,7 +16068,7 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
     else
         _LOGD(LOGD_DEVICE,
               "deactivating device (reason '%s') [%d]",
-              nm_device_state_reason_to_str_a(reason),
+              nm_device_state_reason_to_string_a(reason),
               reason);
 
     /* Save whether or not we tried IPv6 for later */
@@ -16045,15 +16099,16 @@ nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanu
 
             nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
             nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
-            set_ipv6_token(self, iid, "::");
+            set_ipv6_token(self, &iid, "::");
 
             if (nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG)) {
-                nm_platform_tfilter_sync(platform, ifindex, NULL);
-                nm_platform_qdisc_sync(platform, ifindex, NULL);
+                nm_platform_tc_sync(platform, ifindex, NULL, NULL);
             }
         }
     }
 
+    priv->tc_committed = FALSE;
+
     _routing_rules_sync(self,
                         cleanup_type == CLEANUP_TYPE_KEEP ? NM_TERNARY_DEFAULT : NM_TERNARY_FALSE);
 
@@ -16508,20 +16563,20 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         && (state != NM_DEVICE_STATE_UNAVAILABLE || !priv->firmware_missing)) {
         _LOGD(LOGD_DEVICE,
               "state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
-              nm_device_state_to_str(old_state),
-              nm_device_state_to_str(state),
-              nm_device_state_reason_to_str_a(reason),
-              nm_device_sys_iface_state_to_str(priv->sys_iface_state),
+              nm_device_state_to_string(old_state),
+              nm_device_state_to_string(state),
+              nm_device_state_reason_to_string_a(reason),
+              nm_device_sys_iface_state_to_string(priv->sys_iface_state),
               priv->firmware_missing ? ", missing firmware" : "");
         return;
     }
 
     _LOGI(LOGD_DEVICE,
           "state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
-          nm_device_state_to_str(old_state),
-          nm_device_state_to_str(state),
-          nm_device_state_reason_to_str_a(reason),
-          nm_device_sys_iface_state_to_str(priv->sys_iface_state));
+          nm_device_state_to_string(old_state),
+          nm_device_state_to_string(state),
+          nm_device_state_reason_to_string_a(reason),
+          nm_device_sys_iface_state_to_string(priv->sys_iface_state));
 
     /* in order to prevent triggering any callback caused
      * by the device not having any pending action anymore
@@ -16866,8 +16921,8 @@ queued_state_set(gpointer user_data)
 
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
-          nm_device_state_to_str(priv->queued_state.state),
-          nm_device_state_reason_to_str_a(priv->queued_state.reason),
+          nm_device_state_to_string(priv->queued_state.state),
+          nm_device_state_reason_to_string_a(priv->queued_state.reason),
           priv->queued_state.id,
           "change state");
 
@@ -16879,7 +16934,7 @@ queued_state_set(gpointer user_data)
     new_reason            = priv->queued_state.reason;
 
     nm_device_state_changed(self, new_state, new_reason);
-    nm_device_remove_pending_action(self, nm_device_state_queued_state_to_str(new_state), TRUE);
+    nm_device_remove_pending_action(self, nm_device_state_queued_state_to_string(new_state), TRUE);
 
     return G_SOURCE_REMOVE;
 }
@@ -16896,13 +16951,13 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
     if (priv->queued_state.id && priv->queued_state.state == state) {
         _LOGD(LOGD_DEVICE,
               "queue-state[%s, reason:%s, id:%u]: %s%s%s%s",
-              nm_device_state_to_str(priv->queued_state.state),
-              nm_device_state_reason_to_str_a(priv->queued_state.reason),
+              nm_device_state_to_string(priv->queued_state.state),
+              nm_device_state_reason_to_string_a(priv->queued_state.reason),
               priv->queued_state.id,
               "ignore queuing same state change",
               NM_PRINT_FMT_QUOTED(priv->queued_state.reason != reason,
                                   " (reason differs: ",
-                                  nm_device_state_reason_to_str_a(reason),
+                                  nm_device_state_reason_to_string_a(reason),
                                   ")",
                                   ""));
         return;
@@ -16910,20 +16965,20 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
 
     /* Add pending action for the new state before clearing the queued states, so
      * that we don't accidentally pop all pending states and reach 'startup complete'  */
-    nm_device_add_pending_action(self, nm_device_state_queued_state_to_str(state), TRUE);
+    nm_device_add_pending_action(self, nm_device_state_queued_state_to_string(state), TRUE);
 
     /* We should only ever have one delayed state transition at a time */
     if (priv->queued_state.id) {
         _LOGW(LOGD_DEVICE,
               "queue-state[%s, reason:%s, id:%u]: %s",
-              nm_device_state_to_str(priv->queued_state.state),
-              nm_device_state_reason_to_str_a(priv->queued_state.reason),
+              nm_device_state_to_string(priv->queued_state.state),
+              nm_device_state_reason_to_string_a(priv->queued_state.reason),
               priv->queued_state.id,
               "replace previously queued state change");
         nm_clear_g_source(&priv->queued_state.id);
         nm_device_remove_pending_action(
             self,
-            nm_device_state_queued_state_to_str(priv->queued_state.state),
+            nm_device_state_queued_state_to_string(priv->queued_state.state),
             TRUE);
     }
 
@@ -16933,8 +16988,8 @@ nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason r
 
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
-          nm_device_state_to_str(state),
-          nm_device_state_reason_to_str_a(reason),
+          nm_device_state_to_string(state),
+          nm_device_state_reason_to_string_a(reason),
           priv->queued_state.id,
           "queue state change");
 }
@@ -16949,14 +17004,15 @@ queued_state_clear(NMDevice *self)
 
     _LOGD(LOGD_DEVICE,
           "queue-state[%s, reason:%s, id:%u]: %s",
-          nm_device_state_to_str(priv->queued_state.state),
-          nm_device_state_reason_to_str_a(priv->queued_state.reason),
+          nm_device_state_to_string(priv->queued_state.state),
+          nm_device_state_reason_to_string_a(priv->queued_state.reason),
           priv->queued_state.id,
           "clear queued state change");
     nm_clear_g_source(&priv->queued_state.id);
-    nm_device_remove_pending_action(self,
-                                    nm_device_state_queued_state_to_str(priv->queued_state.state),
-                                    TRUE);
+    nm_device_remove_pending_action(
+        self,
+        nm_device_state_queued_state_to_string(priv->queued_state.state),
+        TRUE);
 }
 
 NMDeviceState
@@ -17069,11 +17125,11 @@ nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze)
 {
     NMDevicePrivate *              priv = NM_DEVICE_GET_PRIVATE(self);
     guint8                         buf[_NM_UTILS_HWADDR_LEN_MAX];
-    size_t                         len = 0;
     gboolean                       success_read;
     int                            ifindex;
     const NMPlatformLink *         pllink;
     const NMConfigDeviceStateData *dev_state;
+    NMPLinkAddress                 cached_hw_addr_perm;
 
     if (priv->hw_addr_perm) {
         /* the permanent hardware address is only read once and not
@@ -17112,11 +17168,13 @@ nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze)
         return;
     }
 
-    success_read =
-        nm_platform_link_get_permanent_address(nm_device_get_platform(self), ifindex, buf, &len);
-    if (success_read && priv->hw_addr_len == len) {
+    success_read = nm_platform_link_get_permanent_address(nm_device_get_platform(self),
+                                                          pllink,
+                                                          &cached_hw_addr_perm);
+    if (success_read && priv->hw_addr_len == cached_hw_addr_perm.len) {
         priv->hw_addr_perm_fake = FALSE;
-        priv->hw_addr_perm      = nm_utils_hwaddr_ntoa(buf, len);
+        priv->hw_addr_perm =
+            nm_utils_hwaddr_ntoa(cached_hw_addr_perm.data, cached_hw_addr_perm.len);
         _LOGD(LOGD_DEVICE, "hw-addr: read permanent MAC address '%s'", priv->hw_addr_perm);
         goto notify_and_out;
     }
@@ -17791,7 +17849,7 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
         gboolean valid;
 
         resolver->hostname = g_steal_pointer(&output);
-        valid              = nm_hostname_manager_validate_hostname(resolver->hostname);
+        valid              = nm_utils_validate_hostname(resolver->hostname);
 
         _LOGD(LOGD_DNS,
               "hostname-from-dns: lookup done for %s, result %s%s%s%s",
@@ -17800,7 +17858,7 @@ hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer use
               valid ? "" : " (invalid)");
 
         if (!valid)
-            g_clear_pointer(&resolver->hostname, g_free);
+            nm_clear_g_free(&resolver->hostname);
     }
 
     nm_clear_g_cancellable(&resolver->cancellable);
@@ -18022,6 +18080,37 @@ _activation_func_to_string(ActivationHandleFunc func)
     g_return_val_if_reached("unknown");
 }
 
+static GVariant *
+_device_get_ports_variant(NMDevice *device)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(device);
+    SlaveInfo *      info;
+    GVariantBuilder  builder;
+    gboolean         any = FALSE;
+
+    if (priv->ports_variant)
+        return priv->ports_variant;
+
+    c_list_for_each_entry (info, &priv->slaves, lst_slave) {
+        const char *path;
+
+        if (!NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved)
+            continue;
+        path = nm_dbus_object_get_path(NM_DBUS_OBJECT(info->slave));
+        if (!path)
+            continue;
+        if (!any) {
+            any = TRUE;
+            g_variant_builder_init(&builder, G_VARIANT_TYPE("ao"));
+        }
+        g_variant_builder_add(&builder, "o", path);
+    }
+    priv->ports_variant = any ? g_variant_ref_sink(g_variant_builder_end(&builder))
+                              : g_variant_ref(nm_g_variant_singleton_ao());
+
+    return priv->ports_variant;
+}
+
 /*****************************************************************************/
 
 static void
@@ -18188,29 +18277,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_boolean(value, nm_device_is_real(self));
         break;
     case PROP_SLAVES:
-    {
-        CList *slave_iter;
-        char **slave_list;
-        gsize  i, n;
-
-        n          = c_list_length(&priv->slaves);
-        slave_list = g_new(char *, n + 1);
-        i          = 0;
-        c_list_for_each (slave_iter, &priv->slaves) {
-            SlaveInfo * info = c_list_entry(slave_iter, SlaveInfo, lst_slave);
-            const char *path;
-
-            if (!NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved)
-                continue;
-            path = nm_dbus_object_get_path(NM_DBUS_OBJECT(info->slave));
-            if (path)
-                slave_list[i++] = g_strdup(path);
-        }
-        nm_assert(i <= n);
-        slave_list[i] = NULL;
-        g_value_take_boxed(value, slave_list);
+    case PROP_PORTS:
+        g_value_set_variant(value, _device_get_ports_variant(self));
         break;
-    }
     case PROP_STATISTICS_REFRESH_RATE_MS:
         g_value_set_uint(value, priv->stats.refresh_rate_ms);
         break;
@@ -18518,7 +18587,7 @@ dispose(GObject *object)
 
     nm_clear_g_source(&priv->check_delete_unrealized_id);
 
-    nm_clear_g_source(&priv->stats.timeout_id);
+    nm_clear_g_source_inst(&priv->stats.timeout_source);
 
     carrier_disconnected_action_cancel(self);
 
@@ -18573,7 +18642,7 @@ finalize(GObject *object)
     g_free(priv->hw_addr);
     g_free(priv->hw_addr_perm);
     g_free(priv->hw_addr_initial);
-    g_slist_free(priv->pending_actions);
+    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);
@@ -18592,6 +18661,8 @@ finalize(GObject *object)
     nm_dbus_track_obj_path_deinit(&priv->parent_device);
     nm_dbus_track_obj_path_deinit(&priv->act_request);
 
+    nm_g_variant_unref(priv->ports_variant);
+
     G_OBJECT_CLASS(nm_device_parent_class)->finalize(object);
 
     /* for testing, NMDeviceTest does not invoke NMDevice::constructed,
@@ -18710,9 +18781,8 @@ static const NMDBusInterfaceInfoExtended interface_info_device = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("InterfaceFlags",
                                                            "u",
                                                            NM_DEVICE_INTERFACE_FLAGS),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress",
-                                                           "s",
-                                                           NM_DEVICE_HW_ADDRESS), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ports", "ao", NM_DEVICE_PORTS), ), ),
 };
 
 static const NMDBusInterfaceInfoExtended interface_info_device_statistics = {
@@ -19007,11 +19077,18 @@ nm_device_class_init(NMDeviceClass *klass)
                                                      "",
                                                      FALSE,
                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
-    obj_properties[PROP_SLAVES] = g_param_spec_boxed(NM_DEVICE_SLAVES,
-                                                     "",
-                                                     "",
-                                                     G_TYPE_STRV,
-                                                     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_SLAVES] = g_param_spec_variant(NM_DEVICE_SLAVES,
+                                                       "",
+                                                       "",
+                                                       G_VARIANT_TYPE("ao"),
+                                                       NULL,
+                                                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_PORTS]  = g_param_spec_variant(NM_DEVICE_PORTS,
+                                                      "",
+                                                      "",
+                                                      G_VARIANT_TYPE("ao"),
+                                                      NULL,
+                                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
 
     obj_properties[PROP_STATISTICS_REFRESH_RATE_MS] =
         g_param_spec_uint(NM_DEVICE_STATISTICS_REFRESH_RATE_MS,
@@ -19113,16 +19190,17 @@ nm_device_class_init(NMDeviceClass *klass)
                                                G_TYPE_OBJECT,
                                                G_TYPE_OBJECT);
 
-    signals[IP6_PREFIX_DELEGATED] = g_signal_new(NM_DEVICE_IP6_PREFIX_DELEGATED,
-                                                 G_OBJECT_CLASS_TYPE(object_class),
-                                                 G_SIGNAL_RUN_FIRST,
-                                                 0,
-                                                 NULL,
-                                                 NULL,
-                                                 NULL,
-                                                 G_TYPE_NONE,
-                                                 1,
-                                                 G_TYPE_POINTER);
+    signals[IP6_PREFIX_DELEGATED] =
+        g_signal_new(NM_DEVICE_IP6_PREFIX_DELEGATED,
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     NULL,
+                     NULL,
+                     NULL,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_POINTER /* const NMPlatformIP6Address *prefix */);
 
     signals[IP6_SUBNET_NEEDED] = g_signal_new(NM_DEVICE_IP6_SUBNET_NEEDED,
                                               G_OBJECT_CLASS_TYPE(object_class),
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index f59b6fa8..d967bcb2 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -45,6 +45,7 @@
 #define NM_DEVICE_PHYSICAL_PORT_ID      "physical-port-id"
 #define NM_DEVICE_MTU                   "mtu"
 #define NM_DEVICE_HW_ADDRESS            "hw-address"
+#define NM_DEVICE_PORTS                 "ports"
 
 /* "perm-hw-address" is exposed on D-Bus both for NMDeviceEthernet
  * and NMDeviceWifi. */
diff --git a/src/core/devices/nm-lldp-listener.c b/src/core/devices/nm-lldp-listener.c
index aba972ae..4e178491 100644
--- a/src/core/devices/nm-lldp-listener.c
+++ b/src/core/devices/nm-lldp-listener.c
@@ -32,7 +32,7 @@
 /*****************************************************************************/
 
 struct _NMLldpListener {
-    sd_lldp *   lldp_handle;
+    sd_lldp_rx *lldp_handle;
     GHashTable *lldp_neighbors;
     GVariant *  variant;
 
@@ -711,9 +711,9 @@ lldp_neighbor_to_variant(LldpNeighbor *neigh)
                             g_variant_new_uint32(unaligned_read_be16(data8));
                     break;
                 }
-            } else if (memcmp(oui, SD_LLDP_OUI_MUD, sizeof(oui)) == 0) {
+            } else if (memcmp(oui, SD_LLDP_OUI_IANA, sizeof(oui)) == 0) {
                 switch (subtype) {
-                case SD_LLDP_OUI_SUBTYPE_MUD_USAGE_DESCRIPTION:
+                case SD_LLDP_OUI_IANA_SUBTYPE_MUD:
                     if (!v_mud_url) {
                         gs_free char *s_free = NULL;
                         const char *  s;
@@ -884,12 +884,14 @@ handle_changed:
 }
 
 static void
-lldp_event_handler(sd_lldp *lldp, sd_lldp_event_t event, sd_lldp_neighbor *n, void *userdata)
+lldp_event_handler(sd_lldp_rx *lldp, sd_lldp_rx_event_t event, sd_lldp_neighbor *n, void *userdata)
 {
-    process_lldp_neighbor(
-        userdata,
-        n,
-        !NM_IN_SET(event, SD_LLDP_EVENT_ADDED, SD_LLDP_EVENT_UPDATED, SD_LLDP_EVENT_REFRESHED));
+    process_lldp_neighbor(userdata,
+                          n,
+                          !NM_IN_SET(event,
+                                     SD_LLDP_RX_EVENT_ADDED,
+                                     SD_LLDP_RX_EVENT_UPDATED,
+                                     SD_LLDP_RX_EVENT_REFRESHED));
 }
 
 /*****************************************************************************/
@@ -934,14 +936,14 @@ nm_lldp_listener_new(int                  ifindex,
                      GError **            error)
 {
     NMLldpListener *self = NULL;
-    sd_lldp *       lldp_handle;
+    sd_lldp_rx *    lldp_handle;
     int             r;
 
     g_return_val_if_fail(ifindex > 0, FALSE);
     g_return_val_if_fail(!error || !*error, FALSE);
     g_return_val_if_fail(notify_callback, FALSE);
 
-    r = sd_lldp_new(&lldp_handle);
+    r = sd_lldp_rx_new(&lldp_handle);
     if (r < 0) {
         g_set_error_literal(error,
                             NM_DEVICE_ERROR,
@@ -950,7 +952,7 @@ nm_lldp_listener_new(int                  ifindex,
         return FALSE;
     }
 
-    r = sd_lldp_set_ifindex(lldp_handle, ifindex);
+    r = sd_lldp_rx_set_ifindex(lldp_handle, ifindex);
     if (r < 0) {
         g_set_error_literal(error,
                             NM_DEVICE_ERROR,
@@ -959,7 +961,7 @@ nm_lldp_listener_new(int                  ifindex,
         goto fail_handle;
     }
 
-    r = sd_lldp_set_neighbors_max(lldp_handle, MAX_NEIGHBORS);
+    r = sd_lldp_rx_set_neighbors_max(lldp_handle, MAX_NEIGHBORS);
     nm_assert(r == 0);
 
     self  = g_slice_new(NMLldpListener);
@@ -969,19 +971,19 @@ nm_lldp_listener_new(int                  ifindex,
         .notify_user_data = notify_user_data,
     };
 
-    r = sd_lldp_set_callback(lldp_handle, lldp_event_handler, self);
+    r = sd_lldp_rx_set_callback(lldp_handle, lldp_event_handler, self);
     if (r < 0) {
         g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "set callback failed");
         goto fail_handle;
     }
 
-    r = sd_lldp_attach_event(lldp_handle, NULL, 0);
+    r = sd_lldp_rx_attach_event(lldp_handle, NULL, 0);
     if (r < 0) {
         g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "attach event failed");
         goto fail_attached;
     }
 
-    r = sd_lldp_start(lldp_handle);
+    r = sd_lldp_rx_start(lldp_handle);
     if (r < 0) {
         g_set_error_literal(error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "start failed");
         goto fail_attached;
@@ -997,11 +999,11 @@ nm_lldp_listener_new(int                  ifindex,
     return self;
 
 fail_attached:
-    sd_lldp_detach_event(lldp_handle);
+    sd_lldp_rx_detach_event(lldp_handle);
 fail_handle:
     if (self)
         nm_g_slice_free(self);
-    sd_lldp_unref(lldp_handle);
+    sd_lldp_rx_unref(lldp_handle);
     return NULL;
 }
 
@@ -1010,9 +1012,9 @@ nm_lldp_listener_destroy(NMLldpListener *self)
 {
     g_return_if_fail(self);
 
-    sd_lldp_stop(self->lldp_handle);
-    sd_lldp_detach_event(self->lldp_handle);
-    sd_lldp_unref(self->lldp_handle);
+    sd_lldp_rx_stop(self->lldp_handle);
+    sd_lldp_rx_detach_event(self->lldp_handle);
+    sd_lldp_rx_unref(self->lldp_handle);
 
     nm_clear_g_source_inst(&self->ratelimit_source);
 
diff --git a/src/core/devices/ovs/nm-ovsdb.c b/src/core/devices/ovs/nm-ovsdb.c
index b8d5311a..e5673347 100644
--- a/src/core/devices/ovs/nm-ovsdb.c
+++ b/src/core/devices/ovs/nm-ovsdb.c
@@ -17,6 +17,7 @@
 #include "devices/nm-device.h"
 #include "nm-manager.h"
 #include "nm-setting-ovs-external-ids.h"
+#include "nm-priv-helper-call.h"
 
 /*****************************************************************************/
 
@@ -25,7 +26,7 @@
 /*****************************************************************************/
 
 #if JANSSON_VERSION_HEX < 0x020400
-    #warning "requires at least libjansson 2.4"
+#warning "requires at least libjansson 2.4"
 #endif
 
 typedef struct {
@@ -118,9 +119,8 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct {
-    GSocketClient *    client;
     GSocketConnection *conn;
-    GCancellable *     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. */
@@ -753,6 +753,7 @@ _insert_interface(json_t *      params,
     NMSettingOvsInterface *s_ovs_iface;
     NMSettingOvsDpdk *     s_ovs_dpdk;
     NMSettingOvsPatch *    s_ovs_patch;
+    const char *           dpdk_devargs;
     json_t *               options = json_array();
     json_t *               row;
     guint32                mtu = 0;
@@ -777,9 +778,11 @@ _insert_interface(json_t *      params,
         s_ovs_patch = nm_connection_get_setting_ovs_patch(interface);
 
     if (s_ovs_dpdk) {
-        json_array_append_new(
-            options,
-            json_pack("[[s, s]]", "dpdk-devargs", nm_setting_ovs_dpdk_get_devargs(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());
     } else if (s_ovs_patch) {
         json_array_append_new(
             options,
@@ -1595,7 +1598,7 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
         iter = json_object_iter(ovs);
         s    = json_object_iter_key(iter);
         if (s)
-            nm_utils_strdup_reset(&priv->db_uuid, s);
+            nm_strdup_reset(&priv->db_uuid, s);
     }
 
     json_object_foreach (interface, key, value) {
@@ -1665,8 +1668,8 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 
             nm_assert(nm_streq0(ovs_interface->name, name));
 
-            changed |= nm_utils_strdup_reset(&ovs_interface->type, type);
-            changed |= nm_utils_strdup_reset(&ovs_interface->connection_uuid, connection_uuid);
+            changed |= nm_strdup_reset(&ovs_interface->type, type);
+            changed |= nm_strdup_reset(&ovs_interface->connection_uuid, connection_uuid);
             if (!_external_ids_equal(ovs_interface->external_ids, external_ids_arr)) {
                 NM_SWAP(&ovs_interface->external_ids, &external_ids_arr);
                 changed = TRUE;
@@ -1776,8 +1779,8 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 
             nm_assert(nm_streq0(ovs_port->name, name));
 
-            changed |= nm_utils_strdup_reset(&ovs_port->name, name);
-            changed |= nm_utils_strdup_reset(&ovs_port->connection_uuid, connection_uuid);
+            changed |= nm_strdup_reset(&ovs_port->name, name);
+            changed |= nm_strdup_reset(&ovs_port->connection_uuid, connection_uuid);
             if (nm_strv_ptrarray_cmp(ovs_port->interfaces, interfaces) != 0) {
                 NM_SWAP(&ovs_port->interfaces, &interfaces);
                 changed = TRUE;
@@ -1881,8 +1884,8 @@ ovsdb_got_update(NMOvsdb *self, json_t *msg)
 
             nm_assert(nm_streq0(ovs_bridge->name, name));
 
-            changed = nm_utils_strdup_reset(&ovs_bridge->name, name);
-            changed = nm_utils_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid);
+            changed = nm_strdup_reset(&ovs_bridge->name, name);
+            changed = nm_strdup_reset(&ovs_bridge->connection_uuid, connection_uuid);
             if (nm_strv_ptrarray_cmp(ovs_bridge->ports, ports) != 0) {
                 NM_SWAP(&ovs_bridge->ports, &ports);
                 changed = TRUE;
@@ -2223,7 +2226,7 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
 
     nm_assert(!retry || !is_disposing);
 
-    if (!priv->client)
+    if (!priv->conn && !priv->conn_cancellable)
         return;
 
     _LOGD("disconnecting from ovsdb, retry %d", retry);
@@ -2250,10 +2253,9 @@ ovsdb_disconnect(NMOvsdb *self, gboolean retry, gboolean is_disposing)
     priv->bufp = 0;
     g_string_truncate(priv->input, 0);
     g_string_truncate(priv->output, 0);
-    g_clear_object(&priv->client);
     g_clear_object(&priv->conn);
     nm_clear_g_free(&priv->db_uuid);
-    nm_clear_g_cancellable(&priv->cancellable);
+    nm_clear_g_cancellable(&priv->conn_cancellable);
 
     if (retry)
         ovsdb_try_connect(self);
@@ -2348,32 +2350,82 @@ _monitor_bridges_cb(NMOvsdb *self, json_t *result, GError *error, gpointer user_
 }
 
 static void
-_client_connect_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
+_ovsdb_connect_complete_with_fd(NMOvsdb *self, int fd_take)
 {
-    GSocketClient *    client = G_SOCKET_CLIENT(source_object);
-    NMOvsdb *          self   = NM_OVSDB(user_data);
-    NMOvsdbPrivate *   priv;
-    GError *           error = NULL;
-    GSocketConnection *conn;
-
-    conn = g_socket_client_connect_finish(client, res, &error);
-    if (conn == NULL) {
-        if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
-            _LOGI("%s", error->message);
+    NMOvsdbPrivate *priv            = NM_OVSDB_GET_PRIVATE(self);
+    gs_unref_object GSocket *socket = NULL;
+    gs_free_error GError *error     = NULL;
 
+    socket = g_socket_new_from_fd(nm_steal_fd(&fd_take), &error);
+    if (!socket) {
+        _LOGT("connect: failure to open socket for new FD: %s", error->message);
         ovsdb_disconnect(self, FALSE, FALSE);
-        g_clear_error(&error);
         return;
     }
 
-    priv       = NM_OVSDB_GET_PRIVATE(self);
-    priv->conn = conn;
-    g_clear_object(&priv->cancellable);
+    priv->conn = g_socket_connection_factory_create_connection(socket);
+    g_clear_object(&priv->conn_cancellable);
 
     ovsdb_read(self);
     ovsdb_next_command(self);
 }
 
+static void
+_ovsdb_connect_priv_helper_cb(int fd_take, GError *error, gpointer user_data)
+{
+    nm_auto_close int fd = fd_take;
+    NMOvsdb *         self;
+
+    if (nm_utils_error_is_cancelled(error))
+        return;
+
+    self = user_data;
+
+    if (error) {
+        _LOGT("connect: failure to get FD from nm-priv-helper: %s", error->message);
+        ovsdb_disconnect(self, FALSE, FALSE);
+        return;
+    }
+
+    _LOGT("connect: connected successfully with FD from nm-priv-helper");
+    _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd));
+}
+
+static void
+_ovsdb_connect_idle(gpointer user_data, GCancellable *cancellable)
+{
+    NMOvsdb *         self;
+    NMOvsdbPrivate *  priv;
+    nm_auto_close int fd        = -1;
+    gs_free_error GError *error = NULL;
+
+    if (g_cancellable_is_cancelled(cancellable))
+        return;
+
+    self = user_data;
+    priv = NM_OVSDB_GET_PRIVATE(self);
+
+    fd = nm_priv_helper_utils_open_fd(NM_PRIV_HELPER_GET_FD_TYPE_OVSDB_SOCKET, &error);
+    if (fd == -ENOENT) {
+        _LOGT("connect: opening %s failed (\"%s\")", NM_OVSDB_SOCKET, error->message);
+        ovsdb_disconnect(self, FALSE, FALSE);
+        return;
+    }
+    if (fd < 0) {
+        _LOGT("connect: opening %s failed (\"%s\"). Retry with nm-priv-helper",
+              NM_OVSDB_SOCKET,
+              error->message);
+        nm_priv_helper_call_get_fd(NM_PRIV_HELPER_GET_FD_TYPE_OVSDB_SOCKET,
+                                   priv->conn_cancellable,
+                                   _ovsdb_connect_priv_helper_cb,
+                                   self);
+        return;
+    }
+
+    _LOGT("connect: opening %s succeeded", NM_OVSDB_SOCKET);
+    _ovsdb_connect_complete_with_fd(self, nm_steal_fd(&fd));
+}
+
 /**
  * ovsdb_try_connect:
  *
@@ -2385,22 +2437,13 @@ static void
 ovsdb_try_connect(NMOvsdb *self)
 {
     NMOvsdbPrivate *priv = NM_OVSDB_GET_PRIVATE(self);
-    GSocketAddress *addr;
 
-    if (priv->client)
+    if (priv->conn || priv->conn_cancellable)
         return;
 
-    /* TODO: This should probably be made configurable via NetworkManager.conf */
-    addr = g_unix_socket_address_new(RUNSTATEDIR "/openvswitch/db.sock");
-
-    priv->client      = g_socket_client_new();
-    priv->cancellable = g_cancellable_new();
-    g_socket_client_connect_async(priv->client,
-                                  G_SOCKET_CONNECTABLE(addr),
-                                  priv->cancellable,
-                                  _client_connect_cb,
-                                  self);
-    g_object_unref(addr);
+    _LOGT("connect: start connecting socket %s on idle", NM_OVSDB_SOCKET);
+    priv->conn_cancellable = g_cancellable_new();
+    nm_utils_invoke_on_idle(priv->conn_cancellable, _ovsdb_connect_idle, self);
 
     /* Queue a monitor call before any other command, ensuring that we have an up
      * to date view of existing bridged that we need for add and remove ops. */
@@ -2534,12 +2577,10 @@ nm_ovsdb_set_external_ids(NMOvsdb *                self,
     gs_unref_hashtable GHashTable *exid_old = NULL;
     gs_unref_hashtable GHashTable *exid_new = NULL;
 
-    exid_old = s_exid_old
-                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_old))
-                   : NULL;
-    exid_new = s_exid_new
-                   ? nm_utils_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_new))
-                   : NULL;
+    exid_old =
+        s_exid_old ? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_old)) : NULL;
+    exid_new =
+        s_exid_new ? nm_strdict_clone(_nm_setting_ovs_external_ids_get_data(s_exid_new)) : NULL;
 
     ovsdb_call_method(self,
                       NULL,
diff --git a/src/core/devices/team/nm-device-team.c b/src/core/devices/team/nm-device-team.c
index 0f1a8c4c..3398e467 100644
--- a/src/core/devices/team/nm-device-team.c
+++ b/src/core/devices/team/nm-device-team.c
@@ -20,6 +20,7 @@
 #include "devices/nm-device-private.h"
 #include "libnm-platform/nm-platform.h"
 #include "nm-config.h"
+#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"
@@ -78,8 +79,6 @@ complete_connection(NMDevice *           device,
                     NMConnection *const *existing_connections,
                     GError **            error)
 {
-    NMSettingTeam *s_team;
-
     nm_utils_complete_generic(nm_device_get_platform(device),
                               connection,
                               NM_SETTING_TEAM_SETTING_NAME,
@@ -90,11 +89,7 @@ complete_connection(NMDevice *           device,
                               NULL,
                               TRUE);
 
-    s_team = nm_connection_get_setting_team(connection);
-    if (!s_team) {
-        s_team = (NMSettingTeam *) nm_setting_team_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_team));
-    }
+    _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TEAM);
 
     return TRUE;
 }
@@ -168,15 +163,10 @@ static void
 update_connection(NMDevice *device, NMConnection *connection)
 {
     NMDeviceTeam *       self   = NM_DEVICE_TEAM(device);
-    NMSettingTeam *      s_team = nm_connection_get_setting_team(connection);
+    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;
 
-    if (!s_team) {
-        s_team = (NMSettingTeam *) nm_setting_team_new();
-        nm_connection_add_setting(connection, (NMSetting *) s_team);
-    }
-
     /* Read the configuration only if not already set */
     if (!priv->config && ensure_teamd_connection(device))
         teamd_read_config(self);
@@ -250,11 +240,7 @@ master_update_slave_connection(NMDevice *    self,
         return FALSE;
     }
 
-    s_port = nm_connection_get_setting_team_port(connection);
-    if (!s_port) {
-        s_port = (NMSettingTeamPort *) nm_setting_team_port_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_port));
-    }
+    s_port = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_TEAM_PORT);
 
     g_object_set(G_OBJECT(s_port), NM_SETTING_TEAM_PORT_CONFIG, port_config, NULL);
     g_free(port_config);
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index 56338743..27a3188b 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -18,6 +18,7 @@
 #include "libnm-glib-aux/nm-ref-string.h"
 #include "nm-iwd-manager.h"
 #include "libnm-core-aux-intern/nm-common-macros.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wireless-security.h"
@@ -966,7 +967,7 @@ complete_connection(NMDevice *           device,
     gboolean            hidden = FALSE;
     const char *        mode;
 
-    s_wifi = nm_connection_get_setting_wireless(connection);
+    s_wifi = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
     mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL;
 
@@ -1031,12 +1032,6 @@ complete_connection(NMDevice *           device,
         }
 
         ssid = nm_wifi_ap_get_ssid(ap);
-
-        /* Add a wifi setting if one doesn't exist yet */
-        if (!s_wifi) {
-            s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-            nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-        }
     }
 
     if (ap) {
@@ -2651,7 +2646,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         break;
     case PROP_ACCESS_POINTS:
         list = nm_wifi_aps_get_paths(&priv->aps_lst_head, TRUE);
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(list));
         break;
     case PROP_ACTIVE_ACCESS_POINT:
         nm_dbus_utils_g_value_set_object_path(value, priv->current_ap);
diff --git a/src/core/devices/wifi/nm-device-olpc-mesh.c b/src/core/devices/wifi/nm-device-olpc-mesh.c
index 040350d5..d829f9ae 100644
--- a/src/core/devices/wifi/nm-device-olpc-mesh.c
+++ b/src/core/devices/wifi/nm-device-olpc-mesh.c
@@ -28,6 +28,7 @@
 #include "nm-setting-connection.h"
 #include "nm-setting-olpc-mesh.h"
 #include "nm-manager.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-platform/nm-platform.h"
 
 #define _NMLOG_DEVICE_TYPE NMDeviceOlpcMesh
@@ -88,11 +89,7 @@ complete_connection(NMDevice *           device,
 {
     NMSettingOlpcMesh *s_mesh;
 
-    s_mesh = nm_connection_get_setting_olpc_mesh(connection);
-    if (!s_mesh) {
-        s_mesh = (NMSettingOlpcMesh *) nm_setting_olpc_mesh_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_mesh));
-    }
+    s_mesh = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_OLPC_MESH);
 
     if (!nm_setting_olpc_mesh_get_ssid(s_mesh)) {
         gs_unref_bytes GBytes *ssid = NULL;
diff --git a/src/core/devices/wifi/nm-device-wifi-p2p.c b/src/core/devices/wifi/nm-device-wifi-p2p.c
index a8ea2ed7..67202a67 100644
--- a/src/core/devices/wifi/nm-device-wifi-p2p.c
+++ b/src/core/devices/wifi/nm-device-wifi-p2p.c
@@ -15,6 +15,7 @@
 #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"
@@ -302,10 +303,7 @@ complete_connection(NMDevice *           device,
     }
 
     /* Add a Wi-Fi P2P setting if one doesn't exist yet */
-    if (!s_wifi_p2p) {
-        s_wifi_p2p = NM_SETTING_WIFI_P2P(nm_setting_wifi_p2p_new());
-        nm_connection_add_setting(connection, NM_SETTING(s_wifi_p2p));
-    }
+    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);
 
@@ -560,14 +558,39 @@ act_stage3_ip_config_start(NMDevice *           device,
                            gpointer *           out_config,
                            NMDeviceStateReason *out_failure_reason)
 {
-    gboolean      indicate_addressing_running;
-    NMConnection *connection;
-    const char *  method;
+    NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(device);
+    gboolean                indicate_addressing_running;
+    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
+        && !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,
+            };
+            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);
+
+            nm_device_set_dev2_ip_config(device, AF_INET, NM_IP_CONFIG(ip4_config));
+
+            /* This just disables the addressing indicator. */
+            method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+        }
+    }
+
     if (addr_family == AF_INET)
         indicate_addressing_running = NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     else {
@@ -622,6 +645,11 @@ get_auto_ip_config_method(NMDevice *device, int addr_family)
     NMDeviceWifiP2P *       self = NM_DEVICE_WIFI_P2P(device);
     NMDeviceWifiP2PPrivate *priv = NM_DEVICE_WIFI_P2P_GET_PRIVATE(self);
 
+    if (addr_family == AF_INET && priv->group_iface
+        && !nm_supplicant_interface_get_p2p_group_owner(priv->group_iface)
+        && nm_supplicant_interface_get_p2p_assigned_addr(priv->group_iface, NULL, NULL))
+        return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
+
     /* Override the AUTO method to mean shared if we are group owner. */
     if (priv->group_iface && nm_supplicant_interface_get_p2p_group_owner(priv->group_iface)) {
         if (addr_family == AF_INET)
@@ -1163,7 +1191,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     switch (prop_id) {
     case PROP_PEERS:
         list = nm_wifi_p2p_peers_get_paths(&priv->peers_lst_head);
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(list));
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index fca2fde5..82896c77 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -17,6 +17,7 @@
 #include "nm-device-wifi-p2p.h"
 #include "nm-wifi-ap.h"
 #include "libnm-core-aux-intern/nm-common-macros.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "devices/nm-device.h"
 #include "devices/nm-device-private.h"
 #include "nm-dbus-manager.h"
@@ -321,7 +322,7 @@ _scan_request_ssids_track(NMDeviceWifiPrivate *priv, const GPtrArray *ssids)
     now_msec = nm_utils_get_monotonic_timestamp_msec();
 
     if (!priv->scan_request_ssids_hash)
-        priv->scan_request_ssids_hash = g_hash_table_new(nm_pgbytes_hash, nm_pgbytes_equal);
+        priv->scan_request_ssids_hash = g_hash_table_new(nm_pg_bytes_hash, nm_pg_bytes_equal);
 
     /* Do a little dance. New elements shall keep their order as in @ssids, but all
      * new elements should be sorted in the list preexisting elements of the list.
@@ -1205,10 +1206,7 @@ complete_connection(NMDevice *           device,
     }
 
     /* Add a wifi setting if one doesn't exist yet */
-    if (!s_wifi) {
-        s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-    }
+    s_wifi = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
     if (ap)
         ssid = nm_wifi_ap_get_ssid(ap);
@@ -1571,7 +1569,7 @@ _scan_request_ssids_build_hidden(NMDeviceWifi *self,
     if (ssids) {
         if (ssids->len < max_scan_ssids) {
             /* Add wildcard SSID using a static wildcard SSID used for every scan */
-            g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty()));
+            g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_g_bytes_get_empty()));
         }
         if (ssids->len >= max_scan_ssids) {
             /* there is no more space. Use what we have. */
@@ -1592,10 +1590,10 @@ _scan_request_ssids_build_hidden(NMDeviceWifi *self,
     if (!ssids) {
         ssids = g_ptr_array_new_full(max_scan_ssids, (GDestroyNotify) g_bytes_unref);
         /* Add wildcard SSID using a static wildcard SSID used for every scan */
-        g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_gbytes_get_empty()));
+        g_ptr_array_insert(ssids, 0, g_bytes_ref(nm_g_bytes_get_empty()));
     }
 
-    unique_ssids = g_hash_table_new(nm_gbytes_hash, nm_gbytes_equal);
+    unique_ssids = g_hash_table_new(nm_g_bytes_hash, nm_g_bytes_equal);
     for (i = 1; i < ssids->len; i++) {
         if (!g_hash_table_add(unique_ssids, ssids->pdata[i]))
             nm_assert_not_reached();
@@ -1673,12 +1671,7 @@ _scan_supplicant_request_scan_cb(NMSupplicantInterface *supp_iface,
      * Artificially keep the scanning state on, for another SCAN_EXTRA_DELAY_MSEC msec. */
     nm_clear_g_source_inst(&priv->scan_request_delay_source);
     priv->scan_request_delay_source =
-        nm_g_source_attach(nm_g_timeout_source_new(SCAN_EXTRA_DELAY_MSEC,
-                                                   G_PRIORITY_DEFAULT,
-                                                   _scan_request_delay_cb,
-                                                   self,
-                                                   NULL),
-                           NULL);
+        nm_g_timeout_add_source(SCAN_EXTRA_DELAY_MSEC, _scan_request_delay_cb, self);
 
     g_clear_object(&priv->scan_request_cancellable);
     _scan_notify_is_scanning(self);
@@ -3646,7 +3639,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         break;
     case PROP_ACCESS_POINTS:
         list = nm_wifi_aps_get_paths(&priv->aps_lst_head, TRUE);
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(list));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(list));
         break;
     case PROP_ACTIVE_ACCESS_POINT:
         nm_dbus_utils_g_value_set_object_path(value, priv->current_ap);
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index 20ed6833..72a60885 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -669,7 +669,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_uint(value, priv->rsn_flags);
         break;
     case PROP_SSID:
-        g_value_take_variant(value, nm_utils_gbytes_to_variant_ay(priv->ssid));
+        g_value_take_variant(value, nm_g_bytes_to_variant_ay(priv->ssid));
         break;
     case PROP_FREQUENCY:
         g_value_set_uint(value, priv->freq);
diff --git a/src/core/devices/wifi/nm-wifi-common.c b/src/core/devices/wifi/nm-wifi-common.c
index b98ef222..d029ce7f 100644
--- a/src/core/devices/wifi/nm-wifi-common.c
+++ b/src/core/devices/wifi/nm-wifi-common.c
@@ -13,7 +13,7 @@
 #include "nm-dbus-manager.h"
 
 #if WITH_IWD
-    #include "nm-device-iwd.h"
+#include "nm-device-iwd.h"
 #endif
 
 /*****************************************************************************/
diff --git a/src/core/devices/wifi/nm-wifi-p2p-peer.c b/src/core/devices/wifi/nm-wifi-p2p-peer.c
index 433c2833..ac13952d 100644
--- a/src/core/devices/wifi/nm-wifi-p2p-peer.c
+++ b/src/core/devices/wifi/nm-wifi-p2p-peer.c
@@ -168,7 +168,7 @@ nm_wifi_p2p_peer_set_name(NMWifiP2PPeer *peer, const char *str)
 {
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (!nm_utils_strdup_reset(&priv->name, str))
+    if (!nm_strdup_reset(&priv->name, str))
         return FALSE;
     _notify(peer, PROP_NAME);
     return TRUE;
@@ -187,7 +187,7 @@ nm_wifi_p2p_peer_set_manufacturer(NMWifiP2PPeer *peer, const char *str)
 {
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (!nm_utils_strdup_reset(&priv->manufacturer, str))
+    if (!nm_strdup_reset(&priv->manufacturer, str))
         return FALSE;
     _notify(peer, PROP_MANUFACTURER);
     return TRUE;
@@ -206,7 +206,7 @@ nm_wifi_p2p_peer_set_model(NMWifiP2PPeer *peer, const char *str)
 {
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (!nm_utils_strdup_reset(&priv->model, str))
+    if (!nm_strdup_reset(&priv->model, str))
         return FALSE;
     _notify(peer, PROP_MODEL);
     return TRUE;
@@ -225,7 +225,7 @@ nm_wifi_p2p_peer_set_model_number(NMWifiP2PPeer *peer, const char *str)
 {
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (!nm_utils_strdup_reset(&priv->model_number, str))
+    if (!nm_strdup_reset(&priv->model_number, str))
         return FALSE;
     _notify(peer, PROP_MODEL_NUMBER);
     return TRUE;
@@ -244,7 +244,7 @@ nm_wifi_p2p_peer_set_serial(NMWifiP2PPeer *peer, const char *str)
 {
     NMWifiP2PPeerPrivate *priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (!nm_utils_strdup_reset(&priv->serial, str))
+    if (!nm_strdup_reset(&priv->serial, str))
         return FALSE;
     _notify(peer, PROP_SERIAL);
     return TRUE;
@@ -268,7 +268,7 @@ nm_wifi_p2p_peer_set_wfd_ies(NMWifiP2PPeer *peer, GBytes *wfd_ies)
 
     priv = NM_WIFI_P2P_PEER_GET_PRIVATE(peer);
 
-    if (nm_gbytes_equal0(priv->wfd_ies, wfd_ies))
+    if (nm_g_bytes_equal0(priv->wfd_ies, wfd_ies))
         return FALSE;
 
     wfd_ies_old   = g_steal_pointer(&priv->wfd_ies);
@@ -408,9 +408,9 @@ nm_wifi_p2p_peer_update_from_properties(NMWifiP2PPeer *peer, const NMSupplicantP
 
     /* We currently only use the groups information internally to check if
      * the peer is still joined. */
-    if (!nm_utils_strv_equal(priv->groups, peer_info->groups)) {
+    if (!nm_strv_equal(priv->groups, peer_info->groups)) {
         g_free(priv->groups);
-        priv->groups = nm_utils_strv_dup_packed(peer_info->groups, -1);
+        priv->groups = nm_strv_dup_packed(peer_info->groups, -1);
         changed |= TRUE;
     }
 
@@ -509,7 +509,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_string(value, priv->serial);
         break;
     case PROP_WFD_IES:
-        g_value_take_variant(value, nm_utils_gbytes_to_variant_ay(priv->wfd_ies));
+        g_value_take_variant(value, nm_g_bytes_to_variant_ay(priv->wfd_ies));
         break;
     case PROP_HW_ADDRESS:
         g_value_set_string(value, priv->address);
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index 15ced990..4939e786 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -13,6 +13,7 @@
 
 #include "nm-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-aux-intern/nm-common-macros.h"
 #include "libnm-base/nm-config-base.h"
 
@@ -679,10 +680,7 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
     }
 
     /* Everything else requires security */
-    if (!s_wsec) {
-        s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-    }
+    s_wsec = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     key_mgmt      = nm_setting_wireless_security_get_key_mgmt(s_wsec);
     auth_alg      = nm_setting_wireless_security_get_auth_alg(s_wsec);
@@ -825,21 +823,11 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
                      "open",
                      NULL);
     } else if (nm_streq0(key_mgmt, "sae") || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_SAE)) {
-        g_object_set(s_wsec,
-                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-                     "sae",
-                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-                     "open",
-                     NULL);
+        g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "sae", NULL);
     } else if (nm_streq0(key_mgmt, "owe")
                || NM_FLAGS_ANY(ap_rsn_flags,
                                NM_802_11_AP_SEC_KEY_MGMT_OWE | NM_802_11_AP_SEC_KEY_MGMT_OWE_TM)) {
-        g_object_set(s_wsec,
-                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-                     "owe",
-                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-                     "open",
-                     NULL);
+        g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "owe", NULL);
     } else if (ap_wpa_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK
                || ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) {
         g_object_set(s_wsec,
@@ -853,12 +841,7 @@ nm_wifi_utils_complete_connection(GBytes *      ap_ssid,
          */
     } else if (nm_streq0(key_mgmt, "wpa-eap-suite-b-192")
                || (ap_rsn_flags & NM_802_11_AP_SEC_KEY_MGMT_EAP_SUITE_B_192)) {
-        g_object_set(s_wsec,
-                     NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
-                     "wpa-eap-suite-b-192",
-                     NM_SETTING_WIRELESS_SECURITY_AUTH_ALG,
-                     "open",
-                     NULL);
+        g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap-suite-b-192", NULL);
     } else {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index dc278c94..5d1167cf 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -1445,7 +1445,7 @@ do_test_ssids_options_to_ptrarray(const char *const *ssids)
         const char *ssid  = ssids[i];
         GBytes *    bytes = ssids_arr->pdata[i];
 
-        g_assert(nm_utils_gbytes_equal_mem(bytes, ssid, strlen(ssid)));
+        g_assert(nm_g_bytes_equal_mem(bytes, ssid, strlen(ssid)));
     }
 }
 
diff --git a/src/core/devices/wwan/nm-device-modem.c b/src/core/devices/wwan/nm-device-modem.c
index 4d892e6f..1b285469 100644
--- a/src/core/devices/wwan/nm-device-modem.c
+++ b/src/core/devices/wwan/nm-device-modem.c
@@ -616,18 +616,20 @@ act_stage3_ip_config_start(NMDevice *           device,
                            gpointer *           out_config,
                            NMDeviceStateReason *out_failure_reason)
 {
-    NMDeviceModemPrivate *priv = NM_DEVICE_MODEM_GET_PRIVATE(device);
-
-    nm_assert_addr_family(addr_family);
+    NMDeviceModemPrivate *priv    = NM_DEVICE_MODEM_GET_PRIVATE(device);
+    gboolean              autoip4 = FALSE;
+    NMActStageReturn      ret;
 
-    if (addr_family == AF_INET) {
-        return nm_modem_stage3_ip4_config_start(priv->modem,
-                                                device,
-                                                NM_DEVICE_CLASS(nm_device_modem_parent_class),
-                                                out_failure_reason);
-    } else {
+    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 NM_DEVICE_CLASS(nm_device_modem_parent_class)
+        ->act_stage3_ip_config_start(device, addr_family, out_config, out_failure_reason);
 }
 
 static void
diff --git a/src/core/devices/wwan/nm-modem-broadband.c b/src/core/devices/wwan/nm-modem-broadband.c
index 0872a8a1..a5139f08 100644
--- a/src/core/devices/wwan/nm-modem-broadband.c
+++ b/src/core/devices/wwan/nm-modem-broadband.c
@@ -11,6 +11,7 @@
 #include <arpa/inet.h>
 #include <libmm-glib.h>
 
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "libnm-core-intern/nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "devices/nm-device-private.h"
@@ -829,11 +830,7 @@ complete_connection(NMModem *            modem,
     if (MODEM_CAPS_3GPP2(modem_caps)) {
         NMSettingCdma *s_cdma;
 
-        s_cdma = nm_connection_get_setting_cdma(connection);
-        if (!s_cdma) {
-            s_cdma = (NMSettingCdma *) nm_setting_cdma_new();
-            nm_connection_add_setting(connection, NM_SETTING(s_cdma));
-        }
+        s_cdma = _nm_connection_ensure_setting(connection, NM_TYPE_SETTING_CDMA);
 
         if (!nm_setting_cdma_get_number(s_cdma))
             g_object_set(G_OBJECT(s_cdma), NM_SETTING_CDMA_NUMBER, "#777", NULL);
diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c
index 4fb9a378..8d087d13 100644
--- a/src/core/devices/wwan/nm-modem-manager.c
+++ b/src/core/devices/wwan/nm-modem-manager.c
@@ -12,9 +12,9 @@
 #include <libmm-glib.h>
 
 #if HAVE_LIBSYSTEMD
-    #include <systemd/sd-daemon.h>
+#include <systemd/sd-daemon.h>
 #else
-    #define sd_booted() FALSE
+#define sd_booted() FALSE
 #endif
 
 #include "libnm-std-aux/nm-dbus-compat.h"
@@ -22,7 +22,7 @@
 #include "nm-modem-broadband.h"
 
 #if WITH_OFONO
-    #include "nm-modem-ofono.h"
+#include "nm-modem-ofono.h"
 #endif
 
 #define MODEM_POKE_INTERVAL 120
diff --git a/src/core/devices/wwan/nm-modem.c b/src/core/devices/wwan/nm-modem.c
index 15baa677..ccea69db 100644
--- a/src/core/devices/wwan/nm-modem.c
+++ b/src/core/devices/wwan/nm-modem.c
@@ -526,48 +526,6 @@ static void
 ppp_ip4_config(NMPPPManager *ppp_manager, NMIP4Config *config, gpointer user_data)
 {
     NMModem *self = NM_MODEM(user_data);
-    guint32  i, num;
-    guint32  bad_dns1       = htonl(0x0A0B0C0D);
-    guint32  good_dns1      = htonl(0x04020201); /* GTE nameserver */
-    guint32  bad_dns2       = htonl(0x0A0B0C0E);
-    guint32  good_dns2      = htonl(0x04020202); /* GTE nameserver */
-    gboolean dns_workaround = FALSE;
-
-    /* Work around a PPP bug (#1732) which causes many mobile broadband
-     * providers to return 10.11.12.13 and 10.11.12.14 for the DNS servers.
-     * Apparently fixed in ppp-2.4.5 but we've had some reports that this is
-     * not the case.
-     *
-     * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=2e09ef6886bbf00bc5a9a641110f801e372ffde6
-     * http://git.ozlabs.org/?p=ppp.git;a=commitdiff_plain;h=f8191bf07df374f119a07910a79217c7618f113e
-     */
-
-    num = nm_ip4_config_get_num_nameservers(config);
-    if (num == 2) {
-        gboolean found1 = FALSE, found2 = FALSE;
-
-        for (i = 0; i < num; i++) {
-            guint32 ns = nm_ip4_config_get_nameserver(config, i);
-
-            if (ns == bad_dns1)
-                found1 = TRUE;
-            else if (ns == bad_dns2)
-                found2 = TRUE;
-        }
-
-        /* Be somewhat conservative about substitutions; the "bad" nameservers
-         * could actually be valid in some cases, so only substitute if ppp
-         * returns *only* the two bad nameservers.
-         */
-        dns_workaround = (found1 && found2);
-    }
-
-    if (!num || dns_workaround) {
-        _LOGW("compensating for invalid PPP-provided nameservers");
-        nm_ip4_config_reset_nameservers(config);
-        nm_ip4_config_add_nameserver(config, good_dns1);
-        nm_ip4_config_add_nameserver(config, good_dns2);
-    }
 
     g_signal_emit(self, signals[IP4_CONFIG_RESULT], 0, config, NULL);
 }
@@ -727,7 +685,7 @@ ppp_stage3_ip_config_start(NMModem *            self,
 NMActStageReturn
 nm_modem_stage3_ip4_config_start(NMModem *            self,
                                  NMDevice *           device,
-                                 NMDeviceClass *      device_class,
+                                 gboolean *           out_autoip4,
                                  NMDeviceStateReason *out_failure_reason)
 {
     NMModemPrivate * priv;
@@ -740,7 +698,7 @@ nm_modem_stage3_ip4_config_start(NMModem *            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);
-    g_return_val_if_fail(NM_IS_DEVICE_CLASS(device_class), 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);
@@ -774,7 +732,8 @@ nm_modem_stage3_ip4_config_start(NMModem *            self,
         break;
     case NM_MODEM_IP_METHOD_AUTO:
         _LOGD("MODEM_IP_METHOD_AUTO");
-        ret = device_class->act_stage3_ip_config_start(device, AF_INET, NULL, out_failure_reason);
+        *out_autoip4 = TRUE;
+        ret          = NM_ACT_STAGE_RETURN_SUCCESS;
         break;
     default:
         _LOGI("IPv4 configuration disabled");
diff --git a/src/core/devices/wwan/nm-modem.h b/src/core/devices/wwan/nm-modem.h
index 87162cfc..4bc81ff8 100644
--- a/src/core/devices/wwan/nm-modem.h
+++ b/src/core/devices/wwan/nm-modem.h
@@ -210,7 +210,7 @@ void nm_modem_act_stage2_config(NMModem *modem);
 
 NMActStageReturn nm_modem_stage3_ip4_config_start(NMModem *            modem,
                                                   NMDevice *           device,
-                                                  NMDeviceClass *      device_class,
+                                                  gboolean *           out_autoip4,
                                                   NMDeviceStateReason *out_failure_reason);
 
 NMActStageReturn nm_modem_stage3_ip6_config_start(NMModem *            modem,
diff --git a/src/core/dhcp/nm-dhcp-client.c b/src/core/dhcp/nm-dhcp-client.c
index ab8243d0..f88c79c0 100644
--- a/src/core/dhcp/nm-dhcp-client.c
+++ b/src/core/dhcp/nm-dhcp-client.c
@@ -28,7 +28,7 @@
 
 /*****************************************************************************/
 
-enum { SIGNAL_STATE_CHANGED, SIGNAL_PREFIX_DELEGATED, LAST_SIGNAL };
+enum { SIGNAL_NOTIFY, LAST_SIGNAL };
 
 static guint signals[LAST_SIGNAL] = {0};
 
@@ -78,6 +78,7 @@ typedef struct _NMDhcpClientPrivate {
     NMDhcpHostnameFlags hostname_flags;
     NMDhcpClientFlags   client_flags;
     bool                iaid_explicit : 1;
+    bool                is_stopped : 1;
 } NMDhcpClientPrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMDhcpClient, nm_dhcp_client, G_TYPE_OBJECT)
@@ -91,6 +92,33 @@ G_STATIC_ASSERT(!(((pid_t) -1) > 0));
 
 /*****************************************************************************/
 
+static void
+_emit_notify(NMDhcpClient *self, const NMDhcpClientNotifyData *notify_data)
+{
+    g_signal_emit(G_OBJECT(self), signals[SIGNAL_NOTIFY], 0, notify_data);
+}
+
+static void
+_emit_notify_state_changed(NMDhcpClient *self,
+                           NMDhcpState   dhcp_state,
+                           NMIPConfig *  ip_config,
+                           GHashTable *  options)
+{
+    const NMDhcpClientNotifyData notify_data = {
+        .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED,
+        .state_changed =
+            {
+                .dhcp_state = dhcp_state,
+                .ip_config  = ip_config,
+                .options    = options,
+            },
+    };
+
+    _emit_notify(self, &notify_data);
+}
+
+/*****************************************************************************/
+
 pid_t
 nm_dhcp_client_get_pid(NMDhcpClient *self)
 {
@@ -352,8 +380,7 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_dhcp_state_to_string,
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_NOOP, "noop"),
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_TERMINATED, "terminated"),
                            NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_TIMEOUT, "timeout"),
-                           NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_UNKNOWN, "unknown"),
-                           NM_UTILS_LOOKUP_ITEM_IGNORE(__NM_DHCP_STATE_MAX), );
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_DHCP_STATE_UNKNOWN, "unknown"), );
 
 static NMDhcpState
 reason_to_state(NMDhcpClient *self, const char *iface, const char *reason)
@@ -469,7 +496,7 @@ nm_dhcp_client_set_state(NMDhcpClient *self,
         gs_free const char **keys = NULL;
         guint                i, nkeys;
 
-        keys = nm_utils_strdict_get_keys(options, TRUE, &nkeys);
+        keys = nm_strdict_get_keys(options, TRUE, &nkeys);
         for (i = 0; i < nkeys; i++) {
             _LOGD("option %-20s => '%s'", keys[i], (char *) g_hash_table_lookup(options, keys[i]));
         }
@@ -497,7 +524,8 @@ nm_dhcp_client_set_state(NMDhcpClient *self,
     }
 
     priv->state = new_state;
-    g_signal_emit(G_OBJECT(self), signals[SIGNAL_STATE_CHANGED], 0, new_state, ip_config, options);
+
+    _emit_notify_state_changed(self, new_state, ip_config, options);
 }
 
 static gboolean
@@ -608,6 +636,20 @@ nm_dhcp_client_accept(NMDhcpClient *self, GError **error)
 }
 
 gboolean
+nm_dhcp_client_can_accept(NMDhcpClient *self)
+{
+    gboolean can_accept;
+
+    g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
+
+    can_accept = !!(NM_DHCP_CLIENT_GET_CLASS(self)->accept);
+
+    nm_assert(can_accept == (!!(NM_DHCP_CLIENT_GET_CLASS(self)->decline)));
+
+    return can_accept;
+}
+
+gboolean
 nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error)
 {
     g_return_val_if_fail(NM_IS_DHCP_CLIENT(self), FALSE);
@@ -733,6 +775,11 @@ nm_dhcp_client_stop(NMDhcpClient *self, gboolean release)
 
     priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
+    if (priv->is_stopped)
+        return;
+
+    priv->is_stopped = TRUE;
+
     /* Kill the DHCP client */
     old_pid = priv->pid;
     NM_DHCP_CLIENT_GET_CLASS(self)->stop(self, release);
@@ -855,7 +902,15 @@ maybe_add_option(NMDhcpClient *self, GHashTable *hash, const char *key, GVariant
 void
 nm_dhcp_client_emit_ipv6_prefix_delegated(NMDhcpClient *self, const NMPlatformIP6Address *prefix)
 {
-    g_signal_emit(G_OBJECT(self), signals[SIGNAL_PREFIX_DELEGATED], 0, prefix);
+    const NMDhcpClientNotifyData notify_data = {
+        .notify_type = NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED,
+        .prefix_delegated =
+            {
+                .prefix = prefix,
+            },
+    };
+
+    _emit_notify(self, &notify_data);
 }
 
 gboolean
@@ -1124,7 +1179,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         break;
     case PROP_REJECT_SERVERS:
         /* construct-only */
-        priv->reject_servers = nm_utils_strv_dup_packed(g_value_get_boxed(value), -1);
+        priv->reject_servers = nm_strv_dup_packed(g_value_get_boxed(value), -1);
         break;
     default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -1142,8 +1197,6 @@ nm_dhcp_client_init(NMDhcpClient *self)
     priv        = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DHCP_CLIENT, NMDhcpClientPrivate);
     self->_priv = priv;
 
-    c_list_init(&self->dhcp_client_lst);
-
     priv->pid = -1;
 }
 
@@ -1175,12 +1228,7 @@ dispose(GObject *object)
     NMDhcpClient *       self = NM_DHCP_CLIENT(object);
     NMDhcpClientPrivate *priv = NM_DHCP_CLIENT_GET_PRIVATE(self);
 
-    /* Stopping the client is left up to the controlling device
-     * explicitly since we may want to quit NetworkManager but not terminate
-     * the DHCP client.
-     */
-
-    nm_assert(c_list_is_empty(&self->dhcp_client_lst));
+    nm_dhcp_client_stop(self, FALSE);
 
     watch_cleanup(self);
     timeout_cleanup(self);
@@ -1369,27 +1417,15 @@ nm_dhcp_client_class_init(NMDhcpClientClass *client_class)
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
-    signals[SIGNAL_STATE_CHANGED] = g_signal_new(NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-                                                 G_OBJECT_CLASS_TYPE(object_class),
-                                                 G_SIGNAL_RUN_FIRST,
-                                                 0,
-                                                 NULL,
-                                                 NULL,
-                                                 NULL,
-                                                 G_TYPE_NONE,
-                                                 3,
-                                                 G_TYPE_UINT,
-                                                 G_TYPE_OBJECT,
-                                                 G_TYPE_HASH_TABLE);
-
-    signals[SIGNAL_PREFIX_DELEGATED] = g_signal_new(NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
-                                                    G_OBJECT_CLASS_TYPE(object_class),
-                                                    G_SIGNAL_RUN_FIRST,
-                                                    0,
-                                                    NULL,
-                                                    NULL,
-                                                    NULL,
-                                                    G_TYPE_NONE,
-                                                    1,
-                                                    G_TYPE_POINTER);
+    signals[SIGNAL_NOTIFY] =
+        g_signal_new(NM_DHCP_CLIENT_NOTIFY,
+                     G_OBJECT_CLASS_TYPE(object_class),
+                     G_SIGNAL_RUN_FIRST,
+                     0,
+                     NULL,
+                     NULL,
+                     g_cclosure_marshal_VOID__POINTER,
+                     G_TYPE_NONE,
+                     1,
+                     G_TYPE_POINTER /* const NMDhcpClientNotifyData *notify_data */);
 }
diff --git a/src/core/dhcp/nm-dhcp-client.h b/src/core/dhcp/nm-dhcp-client.h
index 3fe1b34e..2e7e0216 100644
--- a/src/core/dhcp/nm-dhcp-client.h
+++ b/src/core/dhcp/nm-dhcp-client.h
@@ -44,8 +44,7 @@
 #define NM_DHCP_CLIENT_VENDOR_CLASS_IDENTIFIER "vendor-class-identifier"
 #define NM_DHCP_CLIENT_REJECT_SERVERS          "reject-servers"
 
-#define NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED    "state-changed"
-#define NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED "prefix-delegated"
+#define NM_DHCP_CLIENT_NOTIFY "dhcp-notify"
 
 typedef enum {
     NM_DHCP_STATE_UNKNOWN = 0,
@@ -57,10 +56,27 @@ typedef enum {
     NM_DHCP_STATE_FAIL,       /* failed for some reason */
     NM_DHCP_STATE_TERMINATED, /* client is no longer running */
     NM_DHCP_STATE_NOOP,       /* state is a non operation for NetworkManager */
-    __NM_DHCP_STATE_MAX,
-    NM_DHCP_STATE_MAX = __NM_DHCP_STATE_MAX - 1,
 } NMDhcpState;
 
+typedef enum _nm_packed {
+    NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED,
+    NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED,
+} NMDhcpClientNotifyType;
+
+typedef struct {
+    NMDhcpClientNotifyType notify_type;
+    union {
+        struct {
+            NMIPConfig *ip_config;
+            GHashTable *options;
+            NMDhcpState dhcp_state;
+        } state_changed;
+        struct {
+            const NMPlatformIP6Address *prefix;
+        } prefix_delegated;
+    };
+} NMDhcpClientNotifyData;
+
 const char *nm_dhcp_state_to_string(NMDhcpState state);
 
 struct _NMDhcpClientPrivate;
@@ -68,7 +84,6 @@ struct _NMDhcpClientPrivate;
 typedef struct {
     GObject                      parent;
     struct _NMDhcpClientPrivate *_priv;
-    CList                        dhcp_client_lst;
 } NMDhcpClient;
 
 typedef enum _nm_packed {
@@ -173,6 +188,7 @@ gboolean nm_dhcp_client_start_ip6(NMDhcpClient *            self,
                                   GError **                 error);
 
 gboolean nm_dhcp_client_accept(NMDhcpClient *self, GError **error);
+gboolean nm_dhcp_client_can_accept(NMDhcpClient *self);
 
 gboolean nm_dhcp_client_decline(NMDhcpClient *self, const char *error_message, GError **error);
 
@@ -217,11 +233,13 @@ gboolean nm_dhcp_client_server_id_is_rejected(NMDhcpClient *self, gconstpointer
  *****************************************************************************/
 
 typedef struct {
-    GType (*get_type)(void);
-    GType (*get_type_per_addr_family)(int addr_family);
+    GType (*get_type_4)(void);
+    GType (*get_type_6)(void);
     const char *name;
     const char *(*get_path)(void);
-    bool experimental : 1;
+
+    /* whether this plugin is an undocumented, internal plugin. */
+    bool undocumented : 1;
 } NMDhcpClientFactory;
 
 GType nm_dhcp_nettools_get_type(void);
diff --git a/src/core/dhcp/nm-dhcp-dhclient-utils.c b/src/core/dhcp/nm-dhcp-dhclient-utils.c
index 4284a852..341ac7b2 100644
--- a/src/core/dhcp/nm-dhcp-dhclient-utils.c
+++ b/src/core/dhcp/nm-dhcp-dhclient-utils.c
@@ -59,7 +59,7 @@ grab_request_options(GPtrArray *store, const char *line)
     gsize                i;
 
     /* Grab each 'request' or 'also request'  option and save for later */
-    line_v = nm_utils_strsplit_set(line, "\t ,");
+    line_v = nm_strsplit_set(line, "\t ,");
     for (i = 0; line_v && line_v[i]; i++) {
         const char *ss = nm_str_skip_leading_spaces(line_v[i]);
         gsize       l;
@@ -282,7 +282,7 @@ read_interface(const char *line, char *interface, guint size)
     if (ptr[0] == '\0' || strlen(ptr) + 1 > size)
         return FALSE;
 
-    snprintf(interface, size, "%s", ptr);
+    g_snprintf(interface, size, "%s", ptr);
 
     return TRUE;
 }
@@ -330,7 +330,7 @@ nm_dhcp_dhclient_create_config(const char *        interface,
         g_string_append_printf(new_contents, _("# Merged from %s\n\n"), orig_path);
         intf[0] = '\0';
 
-        lines = nm_utils_strsplit_set(orig_contents, "\n\r");
+        lines = nm_strsplit_set(orig_contents, "\n\r");
         for (line_i = 0; lines && lines[line_i]; line_i++) {
             const char *line = nm_str_skip_leading_spaces(lines[line_i]);
             const char *p;
@@ -620,7 +620,7 @@ nm_dhcp_dhclient_read_duid(const char *leasefile, GError **error)
     if (!g_file_get_contents(leasefile, &contents, NULL, error))
         return NULL;
 
-    contents_v = nm_utils_strsplit_set(contents, "\n\r");
+    contents_v = nm_strsplit_set(contents, "\n\r");
     for (i = 0; contents_v && contents_v[i]; i++) {
         const char *p = nm_str_skip_leading_spaces(contents_v[i]);
         GBytes *    duid;
@@ -671,7 +671,7 @@ nm_dhcp_dhclient_save_duid(const char *leasefile, GBytes *duid, GError **error)
             return FALSE;
         }
 
-        lines = nm_utils_strsplit_set_with_empty(contents, "\n\r");
+        lines = nm_strsplit_set_with_empty(contents, "\n\r");
     }
 
     s = g_string_sized_new(len + 50);
diff --git a/src/core/dhcp/nm-dhcp-dhclient.c b/src/core/dhcp/nm-dhcp-dhclient.c
index 4a11250f..970a51f5 100644
--- a/src/core/dhcp/nm-dhcp-dhclient.c
+++ b/src/core/dhcp/nm-dhcp-dhclient.c
@@ -14,21 +14,21 @@
 
 #if WITH_DHCLIENT
 
-    #include <stdlib.h>
-    #include <unistd.h>
-    #include <stdio.h>
-    #include <netinet/in.h>
-    #include <arpa/inet.h>
-    #include <ctype.h>
-
-    #include "libnm-glib-aux/nm-dedup-multi.h"
-
-    #include "nm-utils.h"
-    #include "nm-dhcp-dhclient-utils.h"
-    #include "nm-dhcp-manager.h"
-    #include "NetworkManagerUtils.h"
-    #include "nm-dhcp-listener.h"
-    #include "nm-dhcp-client-logging.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+
+#include "libnm-glib-aux/nm-dedup-multi.h"
+
+#include "nm-utils.h"
+#include "nm-dhcp-dhclient-utils.h"
+#include "nm-dhcp-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
 
 /*****************************************************************************/
 
@@ -41,16 +41,15 @@ _addr_family_to_path_part(int addr_family)
 
 /*****************************************************************************/
 
-    #define NM_TYPE_DHCP_DHCLIENT (nm_dhcp_dhclient_get_type())
-    #define NM_DHCP_DHCLIENT(obj) \
-        (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
-    #define NM_DHCP_DHCLIENT_CLASS(klass) \
-        (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
-    #define NM_IS_DHCP_DHCLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCLIENT))
-    #define NM_IS_DHCP_DHCLIENT_CLASS(klass) \
-        (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCLIENT))
-    #define NM_DHCP_DHCLIENT_GET_CLASS(obj) \
-        (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
+#define NM_TYPE_DHCP_DHCLIENT (nm_dhcp_dhclient_get_type())
+#define NM_DHCP_DHCLIENT(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclient))
+#define NM_DHCP_DHCLIENT_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
+#define NM_IS_DHCP_DHCLIENT(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCLIENT))
+#define NM_IS_DHCP_DHCLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCLIENT))
+#define NM_DHCP_DHCLIENT_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCLIENT, NMDhcpDhclientClass))
 
 typedef struct _NMDhcpDhclient      NMDhcpDhclient;
 typedef struct _NMDhcpDhclientClass NMDhcpDhclientClass;
@@ -78,8 +77,8 @@ struct _NMDhcpDhclientClass {
 
 G_DEFINE_TYPE(NMDhcpDhclient, nm_dhcp_dhclient, NM_TYPE_DHCP_CLIENT)
 
-    #define NM_DHCP_DHCLIENT_GET_PRIVATE(self) \
-        _NM_GET_PRIVATE(self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT)
+#define NM_DHCP_DHCLIENT_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDhcpDhclient, NM_IS_DHCP_DHCLIENT)
 
 /*****************************************************************************/
 
@@ -731,9 +730,10 @@ nm_dhcp_dhclient_class_init(NMDhcpDhclientClass *dhclient_class)
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhclient = {
-    .name     = "dhclient",
-    .get_type = nm_dhcp_dhclient_get_type,
-    .get_path = nm_dhcp_dhclient_get_path,
+    .name       = "dhclient",
+    .get_type_4 = nm_dhcp_dhclient_get_type,
+    .get_type_6 = nm_dhcp_dhclient_get_type,
+    .get_path   = nm_dhcp_dhclient_get_path,
 };
 
 #endif /* WITH_DHCLIENT */
diff --git a/src/core/dhcp/nm-dhcp-dhcpcanon.c b/src/core/dhcp/nm-dhcp-dhcpcanon.c
index f3a52ea9..f993ffb9 100644
--- a/src/core/dhcp/nm-dhcp-dhcpcanon.c
+++ b/src/core/dhcp/nm-dhcp-dhcpcanon.c
@@ -7,25 +7,24 @@
 
 #if WITH_DHCPCANON
 
-    #include <stdlib.h>
-    #include <unistd.h>
-
-    #include "nm-utils.h"
-    #include "nm-dhcp-manager.h"
-    #include "NetworkManagerUtils.h"
-    #include "nm-dhcp-listener.h"
-    #include "nm-dhcp-client-logging.h"
-
-    #define NM_TYPE_DHCP_DHCPCANON (nm_dhcp_dhcpcanon_get_type())
-    #define NM_DHCP_DHCPCANON(obj) \
-        (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanon))
-    #define NM_DHCP_DHCPCANON_CLASS(klass) \
-        (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
-    #define NM_IS_DHCP_DHCPCANON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCANON))
-    #define NM_IS_DHCP_DHCPCANON_CLASS(klass) \
-        (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCANON))
-    #define NM_DHCP_DHCPCANON_GET_CLASS(obj) \
-        (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "nm-utils.h"
+#include "nm-dhcp-manager.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
+
+#define NM_TYPE_DHCP_DHCPCANON (nm_dhcp_dhcpcanon_get_type())
+#define NM_DHCP_DHCPCANON(obj) \
+    (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanon))
+#define NM_DHCP_DHCPCANON_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
+#define NM_IS_DHCP_DHCPCANON(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCANON))
+#define NM_IS_DHCP_DHCPCANON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCANON))
+#define NM_DHCP_DHCPCANON_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCANON, NMDhcpDhcpcanonClass))
 
 typedef struct _NMDhcpDhcpcanon      NMDhcpDhcpcanon;
 typedef struct _NMDhcpDhcpcanonClass NMDhcpDhcpcanonClass;
@@ -53,8 +52,8 @@ struct _NMDhcpDhcpcanonClass {
 
 G_DEFINE_TYPE(NMDhcpDhcpcanon, nm_dhcp_dhcpcanon, NM_TYPE_DHCP_CLIENT)
 
-    #define NM_DHCP_DHCPCANON_GET_PRIVATE(self) \
-        _NM_GET_PRIVATE(self, NMDhcpDhcpcanon, NM_IS_DHCP_DHCPCANON)
+#define NM_DHCP_DHCPCANON_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMDhcpDhcpcanon, NM_IS_DHCP_DHCPCANON)
 
 /*****************************************************************************/
 
@@ -232,9 +231,9 @@ nm_dhcp_dhcpcanon_class_init(NMDhcpDhcpcanonClass *dhcpcanon_class)
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcanon = {
-    .name     = "dhcpcanon",
-    .get_type = nm_dhcp_dhcpcanon_get_type,
-    .get_path = nm_dhcp_dhcpcanon_get_path,
+    .name       = "dhcpcanon",
+    .get_type_4 = nm_dhcp_dhcpcanon_get_type,
+    .get_path   = nm_dhcp_dhcpcanon_get_path,
 };
 
 #endif /* WITH_DHCPCANON */
diff --git a/src/core/dhcp/nm-dhcp-dhcpcd.c b/src/core/dhcp/nm-dhcp-dhcpcd.c
index 605fb84d..7522156b 100644
--- a/src/core/dhcp/nm-dhcp-dhcpcd.c
+++ b/src/core/dhcp/nm-dhcp-dhcpcd.c
@@ -8,29 +8,28 @@
 
 #if WITH_DHCPCD
 
-    #include <stdlib.h>
-    #include <unistd.h>
-    #include <stdio.h>
-    #include <netinet/in.h>
-    #include <arpa/inet.h>
-
-    #include "nm-dhcp-manager.h"
-    #include "nm-utils.h"
-    #include "NetworkManagerUtils.h"
-    #include "nm-dhcp-listener.h"
-    #include "nm-dhcp-client-logging.h"
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "nm-dhcp-manager.h"
+#include "nm-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-dhcp-listener.h"
+#include "nm-dhcp-client-logging.h"
 
 /*****************************************************************************/
 
-    #define NM_TYPE_DHCP_DHCPCD (nm_dhcp_dhcpcd_get_type())
-    #define NM_DHCP_DHCPCD(obj) \
-        (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcd))
-    #define NM_DHCP_DHCPCD_CLASS(klass) \
-        (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
-    #define NM_IS_DHCP_DHCPCD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCD))
-    #define NM_IS_DHCP_DHCPCD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCD))
-    #define NM_DHCP_DHCPCD_GET_CLASS(obj) \
-        (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
+#define NM_TYPE_DHCP_DHCPCD (nm_dhcp_dhcpcd_get_type())
+#define NM_DHCP_DHCPCD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcd))
+#define NM_DHCP_DHCPCD_CLASS(klass) \
+    (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
+#define NM_IS_DHCP_DHCPCD(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_DHCP_DHCPCD))
+#define NM_IS_DHCP_DHCPCD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_DHCP_DHCPCD))
+#define NM_DHCP_DHCPCD_GET_CLASS(obj) \
+    (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_DHCP_DHCPCD, NMDhcpDhcpcdClass))
 
 typedef struct _NMDhcpDhcpcd      NMDhcpDhcpcd;
 typedef struct _NMDhcpDhcpcdClass NMDhcpDhcpcdClass;
@@ -54,7 +53,7 @@ struct _NMDhcpDhcpcdClass {
 
 G_DEFINE_TYPE(NMDhcpDhcpcd, nm_dhcp_dhcpcd, NM_TYPE_DHCP_CLIENT)
 
-    #define NM_DHCP_DHCPCD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpDhcpcd, NM_IS_DHCP_DHCPCD)
+#define NM_DHCP_DHCPCD_GET_PRIVATE(self) _NM_GET_PRIVATE(self, NMDhcpDhcpcd, NM_IS_DHCP_DHCPCD)
 
 /*****************************************************************************/
 
@@ -233,9 +232,9 @@ nm_dhcp_dhcpcd_class_init(NMDhcpDhcpcdClass *dhcpcd_class)
 }
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_dhcpcd = {
-    .name     = "dhcpcd",
-    .get_type = nm_dhcp_dhcpcd_get_type,
-    .get_path = nm_dhcp_dhcpcd_get_path,
+    .name       = "dhcpcd",
+    .get_type_4 = nm_dhcp_dhcpcd_get_type,
+    .get_path   = nm_dhcp_dhcpcd_get_path,
 };
 
 #endif /* WITH_DHCPCD */
diff --git a/src/core/dhcp/nm-dhcp-helper.c b/src/core/dhcp/nm-dhcp-helper.c
index 56d20101..fd666676 100644
--- a/src/core/dhcp/nm-dhcp-helper.c
+++ b/src/core/dhcp/nm-dhcp-helper.c
@@ -16,9 +16,9 @@
 /*****************************************************************************/
 
 #if NM_MORE_LOGGING
-    #define _NMLOG_ENABLED(level) TRUE
+#define _NMLOG_ENABLED(level) TRUE
 #else
-    #define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
+#define _NMLOG_ENABLED(level) ((level) <= LOG_ERR)
 #endif
 
 #define _NMLOG(always_enabled, level, ...)                                                       \
diff --git a/src/core/dhcp/nm-dhcp-listener.c b/src/core/dhcp/nm-dhcp-listener.c
index b8bb3c33..ae2c40f1 100644
--- a/src/core/dhcp/nm-dhcp-listener.c
+++ b/src/core/dhcp/nm-dhcp-listener.c
@@ -26,8 +26,9 @@
 /*****************************************************************************/
 
 const NMDhcpClientFactory *const _nm_dhcp_manager_factories[6] = {
+
 /* the order here matters, as we will try the plugins in this order to find
-     * the first available plugin. */
+ * the first available plugin. */
 
 #if WITH_DHCPCANON
     &_nm_dhcp_client_factory_dhcpcanon,
diff --git a/src/core/dhcp/nm-dhcp-manager.c b/src/core/dhcp/nm-dhcp-manager.c
index 44b8ede2..3cb89393 100644
--- a/src/core/dhcp/nm-dhcp-manager.c
+++ b/src/core/dhcp/nm-dhcp-manager.c
@@ -27,7 +27,6 @@
 typedef struct {
     const NMDhcpClientFactory *client_factory;
     char *                     default_hostname;
-    CList                      dhcp_client_lst_head;
 } NMDhcpManagerPrivate;
 
 struct _NMDhcpManager {
@@ -45,14 +44,6 @@ G_DEFINE_TYPE(NMDhcpManager, nm_dhcp_manager, G_TYPE_OBJECT)
 
 /*****************************************************************************/
 
-static void client_state_changed(NMDhcpClient * client,
-                                 NMDhcpState    state,
-                                 GObject *      ip_config,
-                                 GVariant *     options,
-                                 NMDhcpManager *self);
-
-/*****************************************************************************/
-
 /* default to installed helper, but can be modified for testing */
 const char *nm_dhcp_helper_path = LIBEXECDIR "/nm-dhcp-helper";
 
@@ -63,9 +54,9 @@ _client_factory_find_by_name(const char *name)
 {
     int i;
 
-    g_return_val_if_fail(name, NULL);
+    nm_assert(name);
 
-    for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
+    for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
         const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
 
         if (f && nm_streq(f->name, name))
@@ -85,11 +76,10 @@ _client_factory_available(const NMDhcpClientFactory *client_factory)
 static GType
 _client_factory_get_gtype(const NMDhcpClientFactory *client_factory, int addr_family)
 {
-    GType                    gtype;
-    nm_auto_unref_gtypeclass NMDhcpClientClass *klass = NULL;
+    GType gtype;
+    GType (*get_type_fcn)(void);
 
     nm_assert(client_factory);
-    nm_assert_addr_family(addr_family);
 
     /* currently, the chosen DHCP plugin for IPv4 and IPv6 is configured in NetworkManager.conf
      * and cannot be reloaded. It would be nice to configure the plugin per address family
@@ -111,29 +101,22 @@ _client_factory_get_gtype(const NMDhcpClientFactory *client_factory, int addr_fa
      * to those plugins. But we don't intend to do so. The internal plugin is the way forward and
      * not extending other plugins. */
 
-    if (client_factory->get_type_per_addr_family)
-        gtype = client_factory->get_type_per_addr_family(addr_family);
+    if (NM_IS_IPv4(addr_family))
+        get_type_fcn = client_factory->get_type_4;
     else
-        gtype = client_factory->get_type();
-
-    if (client_factory == &_nm_dhcp_client_factory_internal) {
-        /* we are already using the internal plugin. Nothing to do. */
-        goto out;
+        get_type_fcn = client_factory->get_type_6;
+
+    if (!get_type_fcn) {
+        /* If the factory does not support the address family, we always
+         * fallback to the internal. */
+        if (NM_IS_IPv4(addr_family))
+            get_type_fcn = _nm_dhcp_client_factory_internal.get_type_4;
+        else
+            get_type_fcn = _nm_dhcp_client_factory_internal.get_type_6;
     }
 
-    klass = g_type_class_ref(gtype);
-
-    nm_assert(NM_IS_DHCP_CLIENT_CLASS(klass));
+    gtype = get_type_fcn();
 
-    if (addr_family == AF_INET6) {
-        if (!klass->ip6_start)
-            gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family);
-    } else {
-        if (!klass->ip4_start)
-            gtype = _client_factory_get_gtype(&_nm_dhcp_client_factory_internal, addr_family);
-    }
-
-out:
     nm_assert(g_type_is_a(gtype, NM_TYPE_DHCP_CLIENT));
     nm_assert(({
         nm_auto_unref_gtypeclass NMDhcpClientClass *k = g_type_class_ref(gtype);
@@ -147,56 +130,6 @@ out:
 /*****************************************************************************/
 
 static NMDhcpClient *
-get_client_for_ifindex(NMDhcpManager *manager, int addr_family, int ifindex)
-{
-    NMDhcpManagerPrivate *priv;
-    NMDhcpClient *        client;
-
-    g_return_val_if_fail(NM_IS_DHCP_MANAGER(manager), NULL);
-    g_return_val_if_fail(ifindex > 0, NULL);
-
-    priv = NM_DHCP_MANAGER_GET_PRIVATE(manager);
-
-    c_list_for_each_entry (client, &priv->dhcp_client_lst_head, dhcp_client_lst) {
-        if (nm_dhcp_client_get_ifindex(client) == ifindex
-            && nm_dhcp_client_get_addr_family(client) == addr_family)
-            return client;
-    }
-
-    return NULL;
-}
-
-static void
-remove_client(NMDhcpManager *self, NMDhcpClient *client)
-{
-    g_signal_handlers_disconnect_by_func(client, client_state_changed, self);
-    c_list_unlink(&client->dhcp_client_lst);
-
-    /* Stopping the client is left up to the controlling device
-     * explicitly since we may want to quit NetworkManager but not terminate
-     * the DHCP client.
-     */
-}
-
-static void
-remove_client_unref(NMDhcpManager *self, NMDhcpClient *client)
-{
-    remove_client(self, client);
-    g_object_unref(client);
-}
-
-static void
-client_state_changed(NMDhcpClient * client,
-                     NMDhcpState    state,
-                     GObject *      ip_config,
-                     GVariant *     options,
-                     NMDhcpManager *self)
-{
-    if (state >= NM_DHCP_STATE_TIMEOUT)
-        remove_client_unref(self, client);
-}
-
-static NMDhcpClient *
 client_start(NMDhcpManager *           self,
              int                       addr_family,
              NMDedupMultiIndex *       multi_idx,
@@ -226,10 +159,10 @@ client_start(NMDhcpManager *           self,
              GError **                 error)
 {
     NMDhcpManagerPrivate *priv;
-    NMDhcpClient *        client;
-    gboolean              success = FALSE;
-    gsize                 hwaddr_len;
-    GType                 gtype;
+    gs_unref_object NMDhcpClient *client  = NULL;
+    gboolean                      success = FALSE;
+    gsize                         hwaddr_len;
+    GType                         gtype;
 
     g_return_val_if_fail(NM_IS_DHCP_MANAGER(self), NULL);
     g_return_val_if_fail(iface, NULL);
@@ -278,20 +211,6 @@ client_start(NMDhcpManager *           self,
 
     priv = NM_DHCP_MANAGER_GET_PRIVATE(self);
 
-    /* Kill any old client instance */
-    client = get_client_for_ifindex(self, addr_family, ifindex);
-    if (client) {
-        /* FIXME: we cannot just call synchronously "stop()" and forget about the client.
-         * We need to wait for the client to be fully stopped because most/all clients
-         * cannot quit right away.
-         *
-         * FIXME(shutdown): also fix this during shutdown, to wait for all DHCP clients
-         * to be fully stopped. */
-        remove_client(self, client);
-        nm_dhcp_client_stop(client, FALSE);
-        g_object_unref(client);
-    }
-
     gtype = _client_factory_get_gtype(priv->client_factory, addr_family);
 
     nm_log_trace(LOGD_DHCP,
@@ -340,12 +259,6 @@ client_start(NMDhcpManager *           self,
                           NM_DHCP_CLIENT_ANYCAST_ADDRESS,
                           anycast_address,
                           NULL);
-    nm_assert(client && c_list_is_empty(&client->dhcp_client_lst));
-    c_list_link_tail(&priv->dhcp_client_lst_head, &client->dhcp_client_lst);
-    g_signal_connect(client,
-                     NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-                     G_CALLBACK(client_state_changed),
-                     self);
 
     /* unfortunately, our implementations work differently per address-family regarding client-id/DUID.
      *
@@ -385,12 +298,10 @@ client_start(NMDhcpManager *           self,
                                            error);
     }
 
-    if (!success) {
-        remove_client_unref(self, client);
+    if (!success)
         return NULL;
-    }
 
-    return g_object_ref(client);
+    return g_steal_pointer(&client);
 }
 
 /* Caller owns a reference to the NMDhcpClient on return */
@@ -596,9 +507,7 @@ nm_dhcp_manager_init(NMDhcpManager *self)
     int                        i;
     const NMDhcpClientFactory *client_factory = NULL;
 
-    c_list_init(&priv->dhcp_client_lst_head);
-
-    for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
+    for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
         const NMDhcpClientFactory *f = _nm_dhcp_manager_factories[i];
 
         if (!f)
@@ -608,7 +517,7 @@ nm_dhcp_manager_init(NMDhcpManager *self)
                    "dhcp-init: enabled DHCP client '%s'%s%s",
                    f->name,
                    _client_factory_available(f) ? "" : " (not available)",
-                   f->experimental ? " (undocumented internal plugin)" : "");
+                   f->undocumented ? " (undocumented internal plugin)" : "");
     }
 
     /* Client-specific setup */
@@ -644,7 +553,7 @@ nm_dhcp_manager_init(NMDhcpManager *self)
             }
         }
         if (!client_factory) {
-            for (i = 0; i < G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
+            for (i = 0; i < (int) G_N_ELEMENTS(_nm_dhcp_manager_factories); i++) {
                 client_factory = _client_factory_available(_nm_dhcp_manager_factories[i]);
                 if (client_factory)
                     break;
@@ -668,10 +577,6 @@ dispose(GObject *object)
 {
     NMDhcpManager *       self = NM_DHCP_MANAGER(object);
     NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE(self);
-    NMDhcpClient *        client, *client_safe;
-
-    c_list_for_each_entry_safe (client, client_safe, &priv->dhcp_client_lst_head, dhcp_client_lst)
-        remove_client_unref(self, client);
 
     G_OBJECT_CLASS(nm_dhcp_manager_parent_class)->dispose(object);
 
diff --git a/src/core/dhcp/nm-dhcp-nettools.c b/src/core/dhcp/nm-dhcp-nettools.c
index 4bb6c833..56b485dd 100644
--- a/src/core/dhcp/nm-dhcp-nettools.c
+++ b/src/core/dhcp/nm-dhcp-nettools.c
@@ -601,7 +601,7 @@ lease_to_ip4_config(NMDedupMultiIndex *multi_idx,
         nm_str_buf_append_len0(&sbuf, (const char *) l_data, l_data_len);
 
         /* Multiple domains sometimes stuffed into option 15 "Domain Name". */
-        domains = nm_utils_strsplit_set(nm_str_buf_get_str(&sbuf), " ");
+        domains = nm_strsplit_set(nm_str_buf_get_str(&sbuf), " ");
 
         nm_str_buf_reset(&sbuf);
         if (domains) {
@@ -997,9 +997,7 @@ nettools_create(NMDhcpNettools *self, GError **error)
 
     n_dhcp4_client_get_fd(priv->client, &fd);
 
-    priv->event_source =
-        nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, dhcp4_event_cb, self, NULL);
-    g_source_attach(priv->event_source, NULL);
+    priv->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, dhcp4_event_cb, self);
 
     return TRUE;
 }
@@ -1221,7 +1219,7 @@ ip4_start(NMDhcpClient *client, const char *last_ip4_address, GError **error)
         return FALSE;
     }
 
-    _LOGT("dhcp-client4: start %p", (gpointer) priv->client);
+    _LOGT("dhcp-client4: start " NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(priv->client));
 
     nm_dhcp_client_start_timeout(client);
     return TRUE;
@@ -1235,7 +1233,7 @@ stop(NMDhcpClient *client, gboolean release)
 
     NM_DHCP_CLIENT_CLASS(nm_dhcp_nettools_parent_class)->stop(client, release);
 
-    _LOGT("dhcp-client4: stop %p", (gpointer) priv->client);
+    _LOGT("dhcp-client4: stop " NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(priv->client));
 
     priv->probe = n_dhcp4_client_probe_free(priv->probe);
 }
@@ -1276,6 +1274,6 @@ nm_dhcp_nettools_class_init(NMDhcpNettoolsClass *class)
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_nettools = {
     .name         = "nettools",
-    .get_type     = nm_dhcp_nettools_get_type,
-    .experimental = TRUE,
+    .get_type_4   = nm_dhcp_nettools_get_type,
+    .undocumented = TRUE,
 };
diff --git a/src/core/dhcp/nm-dhcp-systemd.c b/src/core/dhcp/nm-dhcp-systemd.c
index c789aaee..af1d2238 100644
--- a/src/core/dhcp/nm-dhcp-systemd.c
+++ b/src/core/dhcp/nm-dhcp-systemd.c
@@ -592,7 +592,7 @@ ip4_start(NMDhcpClient *client, const char *last_ip4_address, GError **error)
         return FALSE;
     }
 
-    _LOGT("dhcp-client4: set %p", sd_client);
+    _LOGT("dhcp-client4: set " NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(sd_client));
 
     r = sd_dhcp_client_attach_event(sd_client, NULL, 0);
     if (r < 0) {
@@ -1124,23 +1124,15 @@ nm_dhcp_systemd_class_init(NMDhcpSystemdClass *sdhcp_class)
 
 const NMDhcpClientFactory _nm_dhcp_client_factory_systemd = {
     .name         = "systemd",
-    .get_type     = nm_dhcp_systemd_get_type,
-    .experimental = TRUE,
+    .get_type_4   = nm_dhcp_systemd_get_type,
+    .get_type_6   = nm_dhcp_systemd_get_type,
+    .undocumented = TRUE,
 };
 
 /*****************************************************************************/
 
-static GType
-_get_type_per_addr_family(int addr_family)
-{
-    nm_assert_addr_family(addr_family);
-
-    if (addr_family == AF_INET)
-        return nm_dhcp_nettools_get_type();
-    return nm_dhcp_systemd_get_type();
-}
-
 const NMDhcpClientFactory _nm_dhcp_client_factory_internal = {
-    .name                     = "internal",
-    .get_type_per_addr_family = _get_type_per_addr_family,
+    .name       = "internal",
+    .get_type_4 = nm_dhcp_nettools_get_type,
+    .get_type_6 = nm_dhcp_systemd_get_type,
 };
diff --git a/src/core/dhcp/nm-dhcp-utils.c b/src/core/dhcp/nm-dhcp-utils.c
index 7fd18b0a..3cc6987a 100644
--- a/src/core/dhcp/nm-dhcp-utils.c
+++ b/src/core/dhcp/nm-dhcp-utils.c
@@ -35,7 +35,7 @@ ip4_process_dhcpcd_rfc3442_routes(const char * iface,
     const char **        r;
     gboolean             have_routes = FALSE;
 
-    routes = nm_utils_strsplit_set(str, " ");
+    routes = nm_strsplit_set(str, " ");
     if (!routes)
         return FALSE;
 
@@ -164,7 +164,7 @@ ip4_process_dhclient_rfc3442_routes(const char * iface,
     const char *const *  o;
     gboolean             have_routes = FALSE;
 
-    octets = nm_utils_strsplit_set_with_empty(str, " .");
+    octets = nm_strsplit_set_with_empty(str, " .");
     if (NM_PTRARRAY_LEN(octets) < 5) {
         _LOG2W(LOGD_DHCP4, iface, "ignoring invalid classless static routes '%s'", str);
         return FALSE;
@@ -296,7 +296,7 @@ process_classful_routes(const char * iface,
     if (!str)
         return;
 
-    searches = nm_utils_strsplit_set(str, " ");
+    searches = nm_strsplit_set(str, " ");
     if (!searches)
         return;
 
@@ -376,7 +376,7 @@ process_domain_search(const char *iface, const char *str, GFunc add_func, gpoint
         return;
     }
 
-    searches = nm_utils_strsplit_set(unescaped, " ");
+    searches = nm_strsplit_set(unescaped, " ");
     for (s = searches; searches && *s; s++) {
         _LOG2I(LOGD_DHCP, iface, "  domain search '%s'", *s);
         add_func((gpointer) *s, user_data);
@@ -450,7 +450,7 @@ nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx,
          */
         str = g_hash_table_lookup(options, "routers");
         if (str) {
-            gs_free const char **routers = nm_utils_strsplit_set(str, " ");
+            gs_free const char **routers = nm_strsplit_set(str, " ");
             const char **        s;
 
             for (s = routers; routers && *s; s++) {
@@ -491,7 +491,7 @@ nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx,
 
     str = g_hash_table_lookup(options, "domain_name_servers");
     if (str) {
-        gs_free const char **dns = nm_utils_strsplit_set(str, " ");
+        gs_free const char **dns = nm_strsplit_set(str, " ");
         const char **        s;
 
         for (s = dns; dns && *s; s++) {
@@ -507,7 +507,7 @@ nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx,
 
     str = g_hash_table_lookup(options, "domain_name");
     if (str) {
-        gs_free const char **domains = nm_utils_strsplit_set(str, " ");
+        gs_free const char **domains = nm_strsplit_set(str, " ");
         const char **        s;
 
         for (s = domains; domains && *s; s++) {
@@ -522,7 +522,7 @@ nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx,
 
     str = g_hash_table_lookup(options, "netbios_name_servers");
     if (str) {
-        gs_free const char **nbns = nm_utils_strsplit_set(str, " ");
+        gs_free const char **nbns = nm_strsplit_set(str, " ");
         const char **        s;
 
         for (s = nbns; nbns && *s; s++) {
@@ -557,7 +557,7 @@ nm_dhcp_utils_ip4_config_from_options(NMDedupMultiIndex *multi_idx,
 
     str = g_hash_table_lookup(options, "nis_servers");
     if (str) {
-        gs_free const char **nis = nm_utils_strsplit_set(str, " ");
+        gs_free const char **nis = nm_strsplit_set(str, " ");
         const char **        s;
 
         for (s = nis; nis && *s; s++) {
@@ -689,7 +689,7 @@ nm_dhcp_utils_ip6_config_from_options(NMDedupMultiIndex *multi_idx,
 
     str = g_hash_table_lookup(options, "dhcp6_name_servers");
     if (str) {
-        gs_free const char **dns = nm_utils_strsplit_set(str, " ");
+        gs_free const char **dns = nm_strsplit_set(str, " ");
         const char **        s;
 
         for (s = dns; dns && *s; s++) {
diff --git a/src/core/dhcp/tests/test-dhcp-dhclient.c b/src/core/dhcp/tests/test-dhcp-dhclient.c
index e9a62096..9c6865cc 100644
--- a/src/core/dhcp/tests/test-dhcp-dhclient.c
+++ b/src/core/dhcp/tests/test-dhcp-dhclient.c
@@ -830,7 +830,7 @@ test_one_duid(const char *escaped, const guint8 *unescaped, guint len)
 
     t1 = nm_dhcp_dhclient_unescape_duid(escaped);
     g_assert(t1);
-    g_assert(nm_utils_gbytes_equal_mem(t1, unescaped, len));
+    g_assert(nm_g_bytes_equal_mem(t1, unescaped, len));
 
     t2 = g_bytes_new(unescaped, len);
     w  = nm_dhcp_dhclient_escape_duid(t2);
@@ -879,7 +879,7 @@ test_read_duid_from_leasefile(void)
     duid = nm_dhcp_dhclient_read_duid(TEST_DIR "/test-dhclient-duid.leases", &error);
     nmtst_assert_success(duid, error);
 
-    g_assert(nm_utils_gbytes_equal_mem(duid, expected, G_N_ELEMENTS(expected)));
+    g_assert(nm_g_bytes_equal_mem(duid, expected, G_N_ELEMENTS(expected)));
 }
 
 static void
diff --git a/src/core/dns/nm-dns-manager.c b/src/core/dns/nm-dns-manager.c
index 4c498c82..f0ec0c89 100644
--- a/src/core/dns/nm-dns-manager.c
+++ b/src/core/dns/nm-dns-manager.c
@@ -19,7 +19,7 @@
 #include <linux/fs.h>
 
 #if WITH_LIBPSL
-    #include <libpsl.h>
+#include <libpsl.h>
 #endif
 
 #include "libnm-glib-aux/nm-str-buf.h"
@@ -42,17 +42,17 @@
 #define HASH_LEN NM_UTILS_CHECKSUM_LENGTH_SHA1
 
 #ifndef RESOLVCONF_PATH
-    #define RESOLVCONF_PATH "/sbin/resolvconf"
-    #define HAS_RESOLVCONF  0
+#define RESOLVCONF_PATH "/sbin/resolvconf"
+#define HAS_RESOLVCONF  0
 #else
-    #define HAS_RESOLVCONF 1
+#define HAS_RESOLVCONF 1
 #endif
 
 #ifndef NETCONFIG_PATH
-    #define NETCONFIG_PATH "/sbin/netconfig"
-    #define HAS_NETCONFIG  0
+#define NETCONFIG_PATH "/sbin/netconfig"
+#define HAS_NETCONFIG  0
 #else
-    #define HAS_NETCONFIG 1
+#define HAS_NETCONFIG 1
 #endif
 
 /*****************************************************************************/
@@ -272,7 +272,7 @@ _dns_config_ip_data_new(NMDnsConfigData * data,
     g_signal_connect(ip_config,
                      NM_IS_IP4_CONFIG(ip_config) ? "notify::" NM_IP4_CONFIG_DNS_PRIORITY
                                                  : "notify::" NM_IP6_CONFIG_DNS_PRIORITY,
-                     (GCallback) _ip_config_dns_priority_changed,
+                     G_CALLBACK(_ip_config_dns_priority_changed),
                      ip_data);
 
     _ASSERT_dns_config_ip_data(ip_data);
@@ -1327,7 +1327,7 @@ get_ip_rdns_domains(NMIPConfig *ip_config)
     /* Free the array and return NULL if the only element was the ending NULL */
     strv = (char **) g_ptr_array_free(domains, (domains->len == 1));
 
-    return _nm_utils_strv_cleanup(strv, FALSE, FALSE, TRUE);
+    return nm_strv_cleanup(strv, FALSE, FALSE, TRUE);
 }
 
 static gboolean
@@ -1744,8 +1744,8 @@ plugin_skip:;
         nameservers    = g_new0(char *, 2);
         nameservers[0] = g_strdup(lladdr);
 
-        need_edns0 = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_EDNS0) < 0;
-        need_trust = nm_utils_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) < 0;
+        need_edns0 = nm_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_EDNS0) < 0;
+        need_trust = nm_strv_find_first(options, -1, NM_SETTING_DNS_OPTION_TRUST_AD) < 0;
 
         if (need_edns0 || need_trust) {
             gsize len;
@@ -2128,10 +2128,7 @@ _resolvconf_resolved_managed(void)
          * We want to handle that, because systemd-resolved might not
          * have started yet. */
         full_path = g_file_read_link(_PATH_RESCONF, NULL);
-        if (nm_utils_strv_find_first((char **) RESOLVED_PATHS,
-                                     G_N_ELEMENTS(RESOLVED_PATHS),
-                                     full_path)
-            >= 0)
+        if (nm_strv_find_first(RESOLVED_PATHS, G_N_ELEMENTS(RESOLVED_PATHS), full_path) >= 0)
             return TRUE;
 
         /* see if resolv.conf is a symlink that resolves exactly one
@@ -2143,10 +2140,7 @@ _resolvconf_resolved_managed(void)
          * We want to handle that, because systemd-resolved might not
          * have started yet. */
         real_path = realpath(_PATH_RESCONF, NULL);
-        if (nm_utils_strv_find_first((char **) RESOLVED_PATHS,
-                                     G_N_ELEMENTS(RESOLVED_PATHS),
-                                     real_path)
-            >= 0)
+        if (nm_strv_find_first(RESOLVED_PATHS, G_N_ELEMENTS(RESOLVED_PATHS), real_path) >= 0)
             return TRUE;
 
         /* fall-through and resolve the symlink, to check the file
diff --git a/src/core/dns/nm-dns-manager.h b/src/core/dns/nm-dns-manager.h
index 501085f7..1972a5dd 100644
--- a/src/core/dns/nm-dns-manager.h
+++ b/src/core/dns/nm-dns-manager.h
@@ -21,11 +21,6 @@ typedef enum {
     NM_DNS_IP_CONFIG_TYPE_VPN,
 } NMDnsIPConfigType;
 
-enum {
-    NM_DNS_PRIORITY_DEFAULT_NORMAL = 100,
-    NM_DNS_PRIORITY_DEFAULT_VPN    = 50,
-};
-
 /*****************************************************************************/
 
 struct _NMDnsConfigData;
diff --git a/src/core/dns/nm-dns-systemd-resolved.c b/src/core/dns/nm-dns-systemd-resolved.c
index 4b8c4f94..445f8498 100644
--- a/src/core/dns/nm-dns-systemd-resolved.c
+++ b/src/core/dns/nm-dns-systemd-resolved.c
@@ -36,6 +36,7 @@
 
 /* define a variable, so that we can compare the operation with pointer equality. */
 static const char *const DBUS_OP_SET_LINK_DEFAULT_ROUTE = "SetLinkDefaultRoute";
+static const char *const DBUS_OP_SET_LINK_DNS_OVER_TLS  = "SetLinkDNSOverTLS";
 
 /*****************************************************************************/
 
@@ -86,7 +87,12 @@ typedef struct {
     bool             try_start_blocked : 1;
     bool             dbus_initied : 1;
     bool             send_updates_waiting : 1;
-    NMTernary        has_link_default_route : 3;
+    /* These two variables ensure that the log is not spammed with
+     * API (not) supported messages.
+     * They can be removed when no distro uses systemd-resolved < v240 anymore
+     */
+    NMTernary has_link_default_route : 3;
+    NMTernary has_link_dns_over_tls : 3;
 } NMDnsSystemdResolvedPrivate;
 
 struct _NMDnsSystemdResolved {
@@ -201,16 +207,26 @@ call_done(GObject *source, GAsyncResult *r, gpointer user_data)
             priv->has_link_default_route = NM_TERNARY_TRUE;
             _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API supported");
         }
+        if (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
+            && priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT) {
+            priv->has_link_dns_over_tls = NM_TERNARY_TRUE;
+            _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API supported");
+        }
         priv->send_updates_warn_ratelimited = FALSE;
         return;
     }
 
-    if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
-        && nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
-        if (priv->has_link_default_route == NM_TERNARY_DEFAULT) {
+    if (nm_g_error_matches(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD)) {
+        if (priv->has_link_default_route == NM_TERNARY_DEFAULT
+            && request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE) {
             priv->has_link_default_route = NM_TERNARY_FALSE;
             _LOGD("systemd-resolved support for SetLinkDefaultRoute(): API not supported");
         }
+        if (priv->has_link_dns_over_tls == NM_TERNARY_DEFAULT
+            && request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS) {
+            priv->has_link_dns_over_tls = NM_TERNARY_FALSE;
+            _LOGD("systemd-resolved support for SetLinkDNSOverTLS(): API not supported");
+        }
         return;
     }
 
@@ -286,14 +302,15 @@ free_pending_updates(NMDnsSystemdResolved *self)
 static gboolean
 prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
 {
-    GVariantBuilder          dns;
-    GVariantBuilder          domains;
-    NMCListElem *            elem;
-    NMSettingConnectionMdns  mdns     = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-    NMSettingConnectionLlmnr llmnr    = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-    const char *             mdns_arg = NULL, *llmnr_arg = NULL;
-    gboolean                 has_config        = FALSE;
-    gboolean                 has_default_route = FALSE;
+    GVariantBuilder               dns;
+    GVariantBuilder               domains;
+    NMCListElem *                 elem;
+    NMSettingConnectionMdns       mdns         = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    NMSettingConnectionLlmnr      llmnr        = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
+    const char *                  mdns_arg = NULL, *llmnr_arg = NULL, *dns_over_tls_arg = NULL;
+    gboolean                      has_config        = FALSE;
+    gboolean                      has_default_route = FALSE;
 
     g_variant_builder_init(&dns, G_VARIANT_TYPE("(ia(iay))"));
     g_variant_builder_add(&dns, "i", ic->ifindex);
@@ -315,6 +332,8 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
         if (NM_IS_IP4_CONFIG(ip_config)) {
             mdns  = NM_MAX(mdns, nm_ip4_config_mdns_get(NM_IP4_CONFIG(ip_config)));
             llmnr = NM_MAX(llmnr, nm_ip4_config_llmnr_get(NM_IP4_CONFIG(ip_config)));
+            dns_over_tls =
+                NM_MAX(dns_over_tls, nm_ip4_config_dns_over_tls_get(NM_IP4_CONFIG(ip_config)));
         }
     }
 
@@ -353,7 +372,24 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
     }
     nm_assert(llmnr_arg);
 
-    if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg))
+    switch (dns_over_tls) {
+    case NM_SETTING_CONNECTION_DNS_OVER_TLS_NO:
+        dns_over_tls_arg = "no";
+        break;
+    case NM_SETTING_CONNECTION_DNS_OVER_TLS_OPPORTUNISTIC:
+        dns_over_tls_arg = "opportunistic";
+        break;
+    case NM_SETTING_CONNECTION_DNS_OVER_TLS_YES:
+        dns_over_tls_arg = "yes";
+        break;
+    case NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT:
+        dns_over_tls_arg = "";
+        break;
+    }
+    nm_assert(dns_over_tls_arg);
+
+    if (!nm_str_is_empty(mdns_arg) || !nm_str_is_empty(llmnr_arg)
+        || !nm_str_is_empty(dns_over_tls_arg))
         has_config = TRUE;
 
     _request_item_append(self, "SetLinkDomains", ic->ifindex, g_variant_builder_end(&domains));
@@ -370,6 +406,10 @@ prepare_one_interface(NMDnsSystemdResolved *self, InterfaceConfig *ic)
                          ic->ifindex,
                          g_variant_new("(is)", ic->ifindex, llmnr_arg ?: ""));
     _request_item_append(self, "SetLinkDNS", ic->ifindex, g_variant_builder_end(&dns));
+    _request_item_append(self,
+                         DBUS_OP_SET_LINK_DNS_OVER_TLS,
+                         ic->ifindex,
+                         g_variant_new("(is)", ic->ifindex, dns_over_tls_arg ?: ""));
 
     return has_config;
 }
@@ -422,12 +462,7 @@ ensure_resolved_running(NMDnsSystemdResolved *self)
         priv->try_start_blocked = TRUE;
 
         priv->try_start_timeout_source =
-            nm_g_source_attach(nm_g_timeout_source_new(4000,
-                                                       G_PRIORITY_DEFAULT,
-                                                       _ensure_resolved_running_timeout,
-                                                       self,
-                                                       NULL),
-                               NULL);
+            nm_g_timeout_add_source(4000, _ensure_resolved_running_timeout, self);
 
         nm_dbus_connection_call_start_service_by_name(priv->dbus_connection,
                                                       SYSTEMD_RESOLVED_DBUS_SERVICE,
@@ -468,15 +503,18 @@ send_updates(NMDnsSystemdResolved *self)
 
     priv->send_updates_waiting = FALSE;
 
-    _LOGT("send-updates: start %lu requests", c_list_length(&priv->request_queue_lst_head));
+    _LOGT("send-updates: start %zu requests", c_list_length(&priv->request_queue_lst_head));
 
     c_list_for_each_entry (request_item, &priv->request_queue_lst_head, request_queue_lst) {
         gs_free char *ss = NULL;
 
-        if (request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
-            && priv->has_link_default_route == NM_TERNARY_FALSE) {
+        if ((request_item->operation == DBUS_OP_SET_LINK_DEFAULT_ROUTE
+             && priv->has_link_default_route == NM_TERNARY_FALSE)
+            || (request_item->operation == DBUS_OP_SET_LINK_DNS_OVER_TLS
+                && priv->has_link_dns_over_tls == NM_TERNARY_FALSE)) {
             /* The "SetLinkDefaultRoute" API is only supported since v240.
-             * We detected that it is not supported, and skip the call. There
+             * The "SetLinkDNSOverTLS" API is only supported since v239.
+             * We detected whether they are supported, and skip the calls. There
              * is no special workaround, because in this case we rely on systemd-resolved
              * to do the right thing automatically. */
             continue;
@@ -601,13 +639,15 @@ name_owner_changed(NMDnsSystemdResolved *self, const char *owner)
 
     nm_clear_g_source_inst(&priv->try_start_timeout_source);
 
-    nm_utils_strdup_reset(&priv->dbus_owner, owner);
+    nm_strdup_reset(&priv->dbus_owner, owner);
 
     if (owner) {
         priv->try_start_blocked    = FALSE;
         priv->send_updates_waiting = TRUE;
-    } else
+    } else {
         priv->has_link_default_route = NM_TERNARY_DEFAULT;
+        priv->has_link_dns_over_tls  = NM_TERNARY_DEFAULT;
+    }
 
     send_updates(self);
 }
@@ -821,19 +861,13 @@ _resolve_start(NMDnsSystemdResolved *self, NMDnsSystemdResolvedResolveHandle *ha
         _LOG2T(handle, "systemd-resolved not running. Failing on idle...");
         nm_assert(!handle->timeout_source);
         handle->is_failing_on_idle = TRUE;
-        handle->timeout_source     = nm_g_source_attach(
-            nm_g_idle_source_new(G_PRIORITY_DEFAULT, _resolve_failing_on_idle, handle, NULL),
-            NULL);
+        handle->timeout_source     = nm_g_idle_add_source(_resolve_failing_on_idle, handle);
         return;
     }
 
     if (!handle->timeout_source) {
-        handle->timeout_source = nm_g_source_attach(nm_g_timeout_source_new(handle->timeout_msec,
-                                                                            G_PRIORITY_DEFAULT,
-                                                                            _resolve_handle_timeout,
-                                                                            handle,
-                                                                            NULL),
-                                                    NULL);
+        handle->timeout_source =
+            nm_g_timeout_add_source(handle->timeout_msec, _resolve_handle_timeout, handle);
     }
 
     if (is_running == NM_TERNARY_DEFAULT) {
@@ -929,6 +963,7 @@ nm_dns_systemd_resolved_init(NMDnsSystemdResolved *self)
     NMDnsSystemdResolvedPrivate *priv = NM_DNS_SYSTEMD_RESOLVED_GET_PRIVATE(self);
 
     priv->has_link_default_route = NM_TERNARY_DEFAULT;
+    priv->has_link_dns_over_tls  = NM_TERNARY_DEFAULT;
 
     c_list_init(&priv->request_queue_lst_head);
     c_list_init(&priv->handle_lst_head);
diff --git a/src/core/main-utils.c b/src/core/main-utils.c
index 9bc497ca..48f143c3 100644
--- a/src/core/main-utils.c
+++ b/src/core/main-utils.c
@@ -14,7 +14,6 @@
 #include <locale.h>
 
 #include <glib/gstdio.h>
-#include <glib-unix.h>
 
 #include "main-utils.h"
 #include "NetworkManagerUtils.h"
diff --git a/src/core/main.c b/src/core/main.c
index 3cb5c07f..7d448a59 100644
--- a/src/core/main.c
+++ b/src/core/main.c
@@ -38,7 +38,7 @@
 #include "nm-netns.h"
 
 #if !defined(NM_DIST_VERSION)
-    #define NM_DIST_VERSION VERSION
+#define NM_DIST_VERSION VERSION
 #endif
 
 #define NM_DEFAULT_PID_FILE NMRUNDIR "/NetworkManager.pid"
@@ -326,6 +326,7 @@ main(int argc, char *argv[])
 
     if (global_opt.show_version) {
         fprintf(stdout, NM_DIST_VERSION "\n");
+        nm_config_cmd_line_options_free(config_cli);
         exit(0);
     }
 
@@ -374,6 +375,7 @@ main(int argc, char *argv[])
         fprintf(stderr,
                 _("%s.  Please use --help to see a list of valid options.\n"),
                 error->message);
+        nm_config_cmd_line_options_free(config_cli);
         exit(1);
     }
 
diff --git a/src/core/meson.build b/src/core/meson.build
index f4d7af64..6e00928e 100644
--- a/src/core/meson.build
+++ b/src/core/meson.build
@@ -51,6 +51,7 @@ libNetworkManagerBase = static_library(
     'nm-netns.c',
     'nm-l3-config-data.c',
     'nm-l3-ipv4ll.c',
+    'nm-l3-ipv6ll.c',
     'nm-l3cfg.c',
     'nm-ip-config.c',
     'nm-ip4-config.c',
@@ -172,6 +173,7 @@ libNetworkManager = static_library(
     'nm-rfkill-manager.c',
     'nm-session-monitor.c',
     'nm-sleep-monitor.c',
+    'nm-priv-helper-call.c',
   ),
   dependencies: nm_deps,
   link_with: [
diff --git a/src/core/ndisc/nm-lndp-ndisc.c b/src/core/ndisc/nm-lndp-ndisc.c
index f11178f8..552b0e4b 100644
--- a/src/core/ndisc/nm-lndp-ndisc.c
+++ b/src/core/ndisc/nm-lndp-ndisc.c
@@ -85,14 +85,14 @@ send_rs(NMNDisc *ndisc, GError **error)
 static NMIcmpv6RouterPref
 _route_preference_coerce(enum ndp_route_preference pref)
 {
-#define _ASSERT_ENUM(v1, v2)                                       \
-    G_STMT_START                                                   \
-    {                                                              \
-        G_STATIC_ASSERT((NMIcmpv6RouterPref) (v1) == (v2));        \
-        G_STATIC_ASSERT((enum ndp_route_preference) (v2) == (v1)); \
-        G_STATIC_ASSERT((gint64) (v1) == (v2));                    \
-        G_STATIC_ASSERT((gint64) (v2) == (v1));                    \
-    }                                                              \
+#define _ASSERT_ENUM(v1, v2)                                      \
+    G_STMT_START                                                  \
+    {                                                             \
+        G_STATIC_ASSERT((NMIcmpv6RouterPref) (v1) == (v2));       \
+        G_STATIC_ASSERT((enum ndp_route_preference)(v2) == (v1)); \
+        G_STATIC_ASSERT((gint64) (v1) == (v2));                   \
+        G_STATIC_ASSERT((gint64) (v2) == (v1));                   \
+    }                                                             \
     G_STMT_END
 
     switch (pref) {
@@ -599,9 +599,7 @@ start(NMNDisc *ndisc)
 
     fd = ndp_get_eventfd(priv->ndp);
 
-    priv->event_source =
-        nm_g_unix_fd_source_new(fd, G_IO_IN, G_PRIORITY_DEFAULT, event_ready, ndisc, NULL);
-    g_source_attach(priv->event_source, NULL);
+    priv->event_source = nm_g_unix_fd_add_source(fd, G_IO_IN, event_ready, ndisc);
 
     /* Flush any pending messages to avoid using obsolete information */
     event_ready(fd, 0, ndisc);
@@ -666,69 +664,6 @@ stop(NMNDisc *ndisc)
 
 /*****************************************************************************/
 
-static int
-ipv6_sysctl_get(NMPlatform *platform,
-                const char *ifname,
-                const char *property,
-                int         min,
-                int         max,
-                int         defval)
-{
-    return nm_platform_sysctl_ip_conf_get_int_checked(platform,
-                                                      AF_INET6,
-                                                      ifname,
-                                                      property,
-                                                      10,
-                                                      min,
-                                                      max,
-                                                      defval);
-}
-
-void
-nm_lndp_ndisc_get_sysctl(NMPlatform *platform,
-                         const char *ifname,
-                         int *       out_max_addresses,
-                         int *       out_router_solicitations,
-                         int *       out_router_solicitation_interval,
-                         guint32 *   out_default_ra_timeout)
-{
-    int router_solicitation_interval = 0;
-    int router_solicitations         = 0;
-
-    if (out_max_addresses) {
-        *out_max_addresses = ipv6_sysctl_get(platform,
-                                             ifname,
-                                             "max_addresses",
-                                             0,
-                                             G_MAXINT32,
-                                             NM_NDISC_MAX_ADDRESSES_DEFAULT);
-    }
-    if (out_router_solicitations || out_default_ra_timeout) {
-        router_solicitations = ipv6_sysctl_get(platform,
-                                               ifname,
-                                               "router_solicitations",
-                                               1,
-                                               G_MAXINT32,
-                                               NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT);
-        NM_SET_OUT(out_router_solicitations, router_solicitations);
-    }
-    if (out_router_solicitation_interval || out_default_ra_timeout) {
-        router_solicitation_interval = ipv6_sysctl_get(platform,
-                                                       ifname,
-                                                       "router_solicitation_interval",
-                                                       1,
-                                                       G_MAXINT32,
-                                                       NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL);
-        NM_SET_OUT(out_router_solicitation_interval, router_solicitation_interval);
-    }
-    if (out_default_ra_timeout) {
-        *out_default_ra_timeout =
-            NM_MAX((((gint64) router_solicitations) * router_solicitation_interval) + 1, 30);
-    }
-}
-
-/*****************************************************************************/
-
 static void
 nm_lndp_ndisc_init(NMLndpNDisc *lndp_ndisc)
 {}
diff --git a/src/core/ndisc/nm-lndp-ndisc.h b/src/core/ndisc/nm-lndp-ndisc.h
index 96fc9737..151f4bf7 100644
--- a/src/core/ndisc/nm-lndp-ndisc.h
+++ b/src/core/ndisc/nm-lndp-ndisc.h
@@ -36,11 +36,4 @@ NMNDisc *nm_lndp_ndisc_new(NMPlatform *                  platform,
                            guint32                       ra_timeout,
                            GError **                     error);
 
-void nm_lndp_ndisc_get_sysctl(NMPlatform *platform,
-                              const char *ifname,
-                              int *       out_max_addresses,
-                              int *       out_router_solicitations,
-                              int *       out_router_solicitation_interval,
-                              guint32 *   out_default_ra_timeout);
-
 #endif /* __NETWORKMANAGER_LNDP_NDISC_H__ */
diff --git a/src/core/ndisc/nm-ndisc.c b/src/core/ndisc/nm-ndisc.c
index cf1b58ec..d7b1a2e1 100644
--- a/src/core/ndisc/nm-ndisc.c
+++ b/src/core/ndisc/nm-ndisc.c
@@ -23,6 +23,9 @@
 #define RFC7559_IRT ((gint32) 4)    /* RFC7559, Initial Retransmission Time, in seconds */
 #define RFC7559_MRT ((gint32) 3600) /* RFC7559, Maximum Retransmission Time, in seconds */
 
+#define NM_NDISC_PRE_EXPIRY_TIME_MSEC         60000
+#define NM_NDISC_PRE_EXPIRY_MIN_LIFETIME_MSEC 120000
+
 #define _SIZE_MAX_GATEWAYS    100u
 #define _SIZE_MAX_ADDRESSES   100u
 #define _SIZE_MAX_ROUTES      1000u
@@ -44,6 +47,7 @@ struct _NMNDiscPrivate {
     gint32 last_ra;
 
     gint32 solicit_retransmit_time_msec;
+    gint64 last_rs_msec;
 
     GSource *solicit_timer_source;
 
@@ -96,6 +100,16 @@ static gboolean timeout_expire_cb(gpointer user_data);
 
 /*****************************************************************************/
 
+NM_UTILS_LOOKUP_STR_DEFINE(nm_ndisc_dhcp_level_to_string,
+                           NMNDiscDHCPLevel,
+                           NM_UTILS_LOOKUP_DEFAULT("INVALID"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_NDISC_DHCP_LEVEL_UNKNOWN, "unknown"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_NDISC_DHCP_LEVEL_NONE, "none"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_NDISC_DHCP_LEVEL_OTHERCONF, "otherconf"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_NDISC_DHCP_LEVEL_MANAGED, "managed"), );
+
+/*****************************************************************************/
+
 NML3ConfigData *
 nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
                       int                       ifindex,
@@ -112,9 +126,7 @@ nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
     guint                                   i;
     const gint32                            now_sec = nm_utils_get_monotonic_timestamp_sec();
 
-    l3cd = nm_l3_config_data_new(multi_idx, ifindex);
-
-    nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_NDISC);
+    l3cd = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_NDISC);
 
     nm_l3_config_data_set_ip6_privacy(l3cd, ip6_privacy);
 
@@ -468,12 +480,12 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
 
     priv = NM_NDISC_GET_PRIVATE(ndisc);
     if (priv->addr_gen_mode == NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
-        if (!nm_utils_ipv6_addr_set_stable_privacy(priv->stable_type,
-                                                   &addr->address,
-                                                   priv->ifname,
-                                                   priv->network_id,
-                                                   addr->dad_counter++,
-                                                   &error)) {
+        if (!nm_utils_ipv6_addr_set_stable_privacy_may_fail(priv->stable_type,
+                                                            &addr->address,
+                                                            priv->ifname,
+                                                            priv->network_id,
+                                                            addr->dad_counter++,
+                                                            &error)) {
             _LOGW("complete-address: failed to generate an stable-privacy address: %s",
                   error->message);
             g_clear_error(&error);
@@ -490,7 +502,7 @@ complete_address(NMNDisc *ndisc, NMNDiscAddress *addr)
 
     if (addr->address.s6_addr32[2] == 0x0 && addr->address.s6_addr32[3] == 0x0) {
         _LOGD("complete-address: adding an EUI-64 address");
-        nm_utils_ipv6_addr_set_interface_identifier(&addr->address, priv->iid);
+        nm_utils_ipv6_addr_set_interface_identifier(&addr->address, &priv->iid);
         return TRUE;
     }
 
@@ -832,6 +844,8 @@ solicit_timer_cb(gpointer user_data)
     else {
         _LOGT("solicit: router solicitation sent");
         nm_clear_g_free(&priv->last_error);
+
+        priv->last_rs_msec = nm_utils_get_monotonic_timestamp_msec();
     }
 
     /* https://tools.ietf.org/html/rfc4861#section-6.3.7 describes how to send solicitations:
@@ -1213,21 +1227,6 @@ config_map_to_string(NMNDiscConfigMap map, char *p)
     *p = '\0';
 }
 
-static const char *
-dhcp_level_to_string(NMNDiscDHCPLevel dhcp_level)
-{
-    switch (dhcp_level) {
-    case NM_NDISC_DHCP_LEVEL_NONE:
-        return "none";
-    case NM_NDISC_DHCP_LEVEL_OTHERCONF:
-        return "otherconf";
-    case NM_NDISC_DHCP_LEVEL_MANAGED:
-        return "managed";
-    default:
-        return "INVALID";
-    }
-}
-
 static void
 _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
 {
@@ -1250,7 +1249,7 @@ _config_changed_log(NMNDisc *ndisc, NMNDiscConfigMap changed)
 
     config_map_to_string(changed, changedstr);
     _LOGD("neighbor discovery configuration changed [%s]:", changedstr);
-    _LOGD("  dhcp-level %s", dhcp_level_to_string(priv->rdata.public.dhcp_level));
+    _LOGD("  dhcp-level %s", nm_ndisc_dhcp_level_to_string(priv->rdata.public.dhcp_level));
 
     if (rdata->public.hop_limit)
         _LOGD("  hop limit      : %d", rdata->public.hop_limit);
@@ -1508,33 +1507,6 @@ check_timestamps(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
                                                                      ndisc);
     }
 
-    /* When we receive an RA, we don't disable solicitations entirely. Instead,
-     * we set the interval the maximum (RFC7559_MRT).
-     *
-     * This contradicts https://tools.ietf.org/html/rfc7559#section-2.1, which says
-     * that we SHOULD stop sending RS if we receive an RA -- but only on a multicast
-     * capable link and if the RA has a valid router lifetime.
-     *
-     * But we really want to recover from a dead router on the network, so we
-     * don't want to cease sending RS entirely.
-     *
-     * But we only re-schedule the timer if the current interval is not already
-     * "RFC7559_MRT * 1000". Otherwise, we already have a slow interval counter
-     * pending. */
-    if (priv->solicit_retransmit_time_msec != RFC7559_MRT * 1000) {
-        gint32 timeout_msec;
-
-        priv->solicit_retransmit_time_msec = RFC7559_MRT * 1000;
-        timeout_msec = solicit_retransmit_time_jitter(priv->solicit_retransmit_time_msec);
-
-        _LOGD("solicit: schedule sending next (slow) solicitation in about %.3f seconds",
-              ((double) timeout_msec) / 1000);
-
-        nm_clear_g_source_inst(&priv->solicit_timer_source);
-        priv->solicit_timer_source =
-            nm_g_timeout_add_source_approx(timeout_msec, 0, solicit_timer_cb, ndisc);
-    }
-
     if (changed != NM_NDISC_CONFIG_NONE)
         nm_ndisc_emit_config_change(ndisc, changed);
 }
@@ -1546,14 +1518,111 @@ timeout_expire_cb(gpointer user_data)
     return G_SOURCE_CONTINUE;
 }
 
+/* Calculate the earliest time where some part of the advertised data is about
+ * to expire.
+ *
+ * Entities are considered about to expire NM_NDISC_PRE_EXPIRY_TIME_MSEC before
+ * their expiration time.
+ *
+ * However, data which has a lifetime (as calculated from the time the last
+ * RS has been sent) shorter than NM_NDISC_PRE_EXPIRY_MIN_LIFETIME_MSEC, is
+ * ignored. This is because when we send out RSs because some data is about
+ * to expire, and the received RAs neither extend the lifetime nor remove
+ * the offending data, the data would be considered about to expire again,
+ * triggering more RS in an endless loop until it expired for good.
+ */
+
+static void
+_calc_pre_expiry_rs_msec_worker(gint64 *earliest_expiry_msec,
+                                gint64  last_rs_msec,
+                                gint64  expiry_msec)
+{
+    if (expiry_msec == NM_NDISC_EXPIRY_INFINITY)
+        return;
+
+    if (expiry_msec < last_rs_msec + NM_NDISC_PRE_EXPIRY_MIN_LIFETIME_MSEC)
+        return;
+
+    *earliest_expiry_msec = NM_MIN(*earliest_expiry_msec, expiry_msec);
+}
+
+static gint64
+calc_pre_expiry_rs_msec(NMNDisc *ndisc)
+{
+    NMNDiscPrivate *     priv        = NM_NDISC_GET_PRIVATE(ndisc);
+    NMNDiscDataInternal *rdata       = &priv->rdata;
+    gint64               expiry_msec = NM_NDISC_EXPIRY_INFINITY;
+    guint                i;
+
+    for (i = 0; i < rdata->gateways->len; i++) {
+        _calc_pre_expiry_rs_msec_worker(
+            &expiry_msec,
+            priv->last_rs_msec,
+            g_array_index(rdata->gateways, NMNDiscGateway, i).expiry_msec);
+    }
+
+    for (i = 0; i < rdata->addresses->len; i++) {
+        _calc_pre_expiry_rs_msec_worker(
+            &expiry_msec,
+            priv->last_rs_msec,
+            g_array_index(rdata->addresses, NMNDiscAddress, 0).expiry_msec);
+    }
+
+    for (i = 0; i < rdata->routes->len; i++) {
+        _calc_pre_expiry_rs_msec_worker(&expiry_msec,
+                                        priv->last_rs_msec,
+                                        g_array_index(rdata->routes, NMNDiscRoute, 0).expiry_msec);
+    }
+
+    for (i = 0; i < rdata->dns_servers->len; i++) {
+        _calc_pre_expiry_rs_msec_worker(
+            &expiry_msec,
+            priv->last_rs_msec,
+            g_array_index(rdata->dns_servers, NMNDiscDNSServer, 0).expiry_msec);
+    }
+
+    for (i = 0; i < rdata->dns_domains->len; i++) {
+        _calc_pre_expiry_rs_msec_worker(
+            &expiry_msec,
+            priv->last_rs_msec,
+            g_array_index(rdata->dns_domains, NMNDiscDNSDomain, 0).expiry_msec);
+    }
+
+    return expiry_msec - solicit_retransmit_time_jitter(NM_NDISC_PRE_EXPIRY_TIME_MSEC);
+}
+
 void
 nm_ndisc_ra_received(NMNDisc *ndisc, gint64 now_msec, NMNDiscConfigMap changed)
 {
     NMNDiscPrivate *priv = NM_NDISC_GET_PRIVATE(ndisc);
+    gint64          pre_expiry_msec;
+    gint32          timeout_msec;
 
     nm_clear_g_source_inst(&priv->ra_timeout_source);
     nm_clear_g_free(&priv->last_error);
     check_timestamps(ndisc, now_msec, changed);
+
+    /* When we receive an RA, we don't disable solicitations.
+     *
+     * This contradicts https://tools.ietf.org/html/rfc7559#section-2.1, which
+     * says that we SHOULD stop sending RS if we receive an RA -- but only on
+     * a multicast capable link and if the RA has a valid router lifetime.
+     *
+     * But there are routers out in the wild that won't send unsolicited RAs.
+     * So we begin sending out RS again when entities are about to expire.
+     */
+    pre_expiry_msec = NM_CLAMP(calc_pre_expiry_rs_msec(ndisc),
+                               priv->last_rs_msec + RFC7559_IRT * 1000,
+                               priv->last_rs_msec + RFC7559_MRT * 1000);
+    timeout_msec    = NM_CLAMP(pre_expiry_msec - now_msec, (gint64) 0, (gint64) G_MAXINT32);
+
+    _LOGD("solicit: schedule sending next (slow) solicitation in about %.3f seconds",
+          ((double) timeout_msec) / 1000);
+
+    priv->solicit_retransmit_time_msec = 0;
+    nm_clear_g_source_inst(&priv->solicit_timer_source);
+    priv->solicit_timer_source =
+        nm_g_timeout_add_source_approx(timeout_msec, 0, solicit_timer_cb, ndisc);
 }
 
 void
@@ -1567,6 +1636,69 @@ nm_ndisc_rs_received(NMNDisc *ndisc)
 
 /*****************************************************************************/
 
+static int
+ipv6_sysctl_get(NMPlatform *platform,
+                const char *ifname,
+                const char *property,
+                int         min,
+                int         max,
+                int         defval)
+{
+    return nm_platform_sysctl_ip_conf_get_int_checked(platform,
+                                                      AF_INET6,
+                                                      ifname,
+                                                      property,
+                                                      10,
+                                                      min,
+                                                      max,
+                                                      defval);
+}
+
+void
+nm_ndisc_get_sysctl(NMPlatform *platform,
+                    const char *ifname,
+                    int *       out_max_addresses,
+                    int *       out_router_solicitations,
+                    int *       out_router_solicitation_interval,
+                    guint32 *   out_default_ra_timeout)
+{
+    int router_solicitation_interval = 0;
+    int router_solicitations         = 0;
+
+    if (out_max_addresses) {
+        *out_max_addresses = ipv6_sysctl_get(platform,
+                                             ifname,
+                                             "max_addresses",
+                                             0,
+                                             G_MAXINT32,
+                                             NM_NDISC_MAX_ADDRESSES_DEFAULT);
+    }
+    if (out_router_solicitations || out_default_ra_timeout) {
+        router_solicitations = ipv6_sysctl_get(platform,
+                                               ifname,
+                                               "router_solicitations",
+                                               1,
+                                               G_MAXINT32,
+                                               NM_NDISC_ROUTER_SOLICITATIONS_DEFAULT);
+        NM_SET_OUT(out_router_solicitations, router_solicitations);
+    }
+    if (out_router_solicitation_interval || out_default_ra_timeout) {
+        router_solicitation_interval = ipv6_sysctl_get(platform,
+                                                       ifname,
+                                                       "router_solicitation_interval",
+                                                       1,
+                                                       G_MAXINT32,
+                                                       NM_NDISC_RFC4861_RTR_SOLICITATION_INTERVAL);
+        NM_SET_OUT(out_router_solicitation_interval, router_solicitation_interval);
+    }
+    if (out_default_ra_timeout) {
+        *out_default_ra_timeout =
+            NM_MAX((((gint64) router_solicitations) * router_solicitation_interval) + 1, 30);
+    }
+}
+
+/*****************************************************************************/
+
 static void
 dns_domain_free(gpointer data)
 {
diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h
index 15f95d20..5b827528 100644
--- a/src/core/ndisc/nm-ndisc.h
+++ b/src/core/ndisc/nm-ndisc.h
@@ -48,6 +48,8 @@ typedef enum {
     NM_NDISC_DHCP_LEVEL_MANAGED
 } NMNDiscDHCPLevel;
 
+const char *nm_ndisc_dhcp_level_to_string(NMNDiscDHCPLevel level);
+
 #define NM_NDISC_INFINITY_U32 ((uint32_t) -1)
 
 /* It's important that this is G_MAXINT64, so that we can meaningfully do
@@ -239,7 +241,7 @@ static inline gboolean
 nm_ndisc_dad_addr_is_fail_candidate_event(NMPlatformSignalChangeType  change_type,
                                           const NMPlatformIP6Address *addr)
 {
-    return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY)
+    return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY)
            && ((change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED)
                || (change_type == NM_PLATFORM_SIGNAL_REMOVED
                    && addr->n_ifa_flags & IFA_F_TENTATIVE));
@@ -253,7 +255,7 @@ nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj)
     addr = NMP_OBJECT_CAST_IP6_ADDRESS(
         nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj));
     if (addr
-        && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY)
+        && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY)
             || !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED))) {
         /* the address still/again exists and is not in DADFAILED state. Skip it. */
         return FALSE;
@@ -264,6 +266,15 @@ nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj)
 
 /*****************************************************************************/
 
+void nm_ndisc_get_sysctl(NMPlatform *platform,
+                         const char *ifname,
+                         int *       out_max_addresses,
+                         int *       out_router_solicitations,
+                         int *       out_router_solicitation_interval,
+                         guint32 *   out_default_ra_timeout);
+
+/*****************************************************************************/
+
 struct _NML3ConfigData;
 
 struct _NML3ConfigData *nm_ndisc_data_to_l3cd(NMDedupMultiIndex *       multi_idx,
diff --git a/src/core/ndisc/tests/test-ndisc-linux.c b/src/core/ndisc/tests/test-ndisc-linux.c
index c84ee142..57021606 100644
--- a/src/core/ndisc/tests/test-ndisc-linux.c
+++ b/src/core/ndisc/tests/test-ndisc-linux.c
@@ -49,12 +49,12 @@ main(int argc, char **argv)
         return EXIT_FAILURE;
     }
 
-    nm_lndp_ndisc_get_sysctl(NM_PLATFORM_GET,
-                             ifname,
-                             &max_addresses,
-                             &router_solicitations,
-                             &router_solicitation_interval,
-                             &ra_timeout);
+    nm_ndisc_get_sysctl(NM_PLATFORM_GET,
+                        ifname,
+                        &max_addresses,
+                        &router_solicitations,
+                        &router_solicitation_interval,
+                        &ra_timeout);
 
     ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET,
                               ifindex,
diff --git a/src/core/nm-active-connection.c b/src/core/nm-active-connection.c
index 40fe3c96..4cd6d488 100644
--- a/src/core/nm-active-connection.c
+++ b/src/core/nm-active-connection.c
@@ -208,12 +208,12 @@ _set_settings_connection(NMActiveConnection *self, NMSettingsConnection *sett_co
     if (sett_conn) {
         g_signal_connect(sett_conn,
                          NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
-                         (GCallback) _settings_connection_updated,
+                         G_CALLBACK(_settings_connection_updated),
                          self);
         if (nm_active_connection_get_activation_type(self) == NM_ACTIVATION_TYPE_EXTERNAL)
             g_signal_connect(sett_conn,
                              NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
-                             (GCallback) _settings_connection_flags_changed,
+                             G_CALLBACK(_settings_connection_flags_changed),
                              self);
     }
 
@@ -858,7 +858,7 @@ nm_active_connection_set_master(NMActiveConnection *self, NMActiveConnection *ma
     priv->master = g_object_ref(master);
     g_signal_connect(priv->master,
                      "notify::" NM_ACTIVE_CONNECTION_STATE,
-                     (GCallback) master_state_cb,
+                     G_CALLBACK(master_state_cb),
                      self);
 
     check_master_ready(self);
@@ -890,7 +890,7 @@ _set_activation_type(NMActiveConnection *self, NMActivationType activation_type)
         if (activation_type == NM_ACTIVATION_TYPE_EXTERNAL)
             g_signal_connect(priv->settings_connection.obj,
                              NM_SETTINGS_CONNECTION_FLAGS_CHANGED,
-                             (GCallback) _settings_connection_flags_changed,
+                             G_CALLBACK(_settings_connection_flags_changed),
                              self);
         else
             g_signal_handlers_disconnect_by_func(priv->settings_connection.obj,
@@ -1025,7 +1025,7 @@ unwatch_parent(NMActiveConnection *self, gboolean unref)
 {
     NMActiveConnectionPrivate *priv = NM_ACTIVE_CONNECTION_GET_PRIVATE(self);
 
-    g_signal_handlers_disconnect_by_func(priv->parent, (GCallback) parent_state_cb, self);
+    g_signal_handlers_disconnect_by_func(priv->parent, G_CALLBACK(parent_state_cb), self);
     if (unref)
         g_object_weak_unref((GObject *) priv->parent, parent_destroyed, self);
     priv->parent = NULL;
@@ -1049,7 +1049,7 @@ nm_active_connection_set_parent(NMActiveConnection *self, NMActiveConnection *pa
     priv->parent = parent;
     g_signal_connect(priv->parent,
                      "notify::" NM_ACTIVE_CONNECTION_STATE,
-                     (GCallback) parent_state_cb,
+                     G_CALLBACK(parent_state_cb),
                      self);
     g_object_weak_ref((GObject *) priv->parent, parent_destroyed, self);
 }
@@ -1529,7 +1529,7 @@ dispose(GObject *object)
     _device_cleanup(self);
 
     if (priv->master) {
-        g_signal_handlers_disconnect_by_func(priv->master, (GCallback) master_state_cb, self);
+        g_signal_handlers_disconnect_by_func(priv->master, G_CALLBACK(master_state_cb), self);
     }
     g_clear_object(&priv->master);
 
diff --git a/src/core/nm-audit-manager.c b/src/core/nm-audit-manager.c
index 2ef5d860..477e1798 100644
--- a/src/core/nm-audit-manager.c
+++ b/src/core/nm-audit-manager.c
@@ -8,7 +8,7 @@
 #include "nm-audit-manager.h"
 
 #if HAVE_LIBAUDIT
-    #include <libaudit.h>
+#include <libaudit.h>
 #endif
 
 #define NM_VALUE_TYPE_DEFINE_FUNCTIONS
diff --git a/src/core/nm-auth-utils.c b/src/core/nm-auth-utils.c
index 006264dd..da17b1a4 100644
--- a/src/core/nm-auth-utils.c
+++ b/src/core/nm-auth-utils.c
@@ -124,12 +124,8 @@ _cancellable_idle_cb(gpointer user_data)
 static void
 _cancellable_on_idle(NMAuthChain *self)
 {
-    if (self->cancellable_idle_source)
-        return;
-
-    self->cancellable_idle_source =
-        nm_g_idle_source_new(G_PRIORITY_DEFAULT, _cancellable_idle_cb, self, NULL);
-    g_source_attach(self->cancellable_idle_source, NULL);
+    if (!self->cancellable_idle_source)
+        self->cancellable_idle_source = nm_g_idle_add_source(_cancellable_idle_cb, self);
 }
 
 GCancellable *
diff --git a/src/core/nm-config-data.c b/src/core/nm-config-data.c
index 0cbff027..1cb8a43c 100644
--- a/src/core/nm-config-data.c
+++ b/src/core/nm-config-data.c
@@ -271,7 +271,7 @@ nm_config_data_get_plugins(const NMConfigData *self, gboolean allow_default)
                              NM_CONFIG_DEFAULT_MAIN_PLUGINS);
         list = g_key_file_get_string_list(kf, NM_CONFIG_KEYFILE_GROUP_MAIN, "plugins", NULL, NULL);
     }
-    return _nm_utils_strv_cleanup(list, TRUE, TRUE, TRUE);
+    return nm_strv_cleanup(list, TRUE, TRUE, TRUE);
 }
 
 gboolean
@@ -1026,7 +1026,7 @@ global_dns_config_seal_domains(NMGlobalDnsConfig *dns_config)
     if (g_hash_table_size(dns_config->domains) == 0)
         nm_clear_pointer(&dns_config->domains, g_hash_table_unref);
     else
-        dns_config->domain_list = nm_utils_strdict_get_keys(dns_config->domains, TRUE, NULL);
+        dns_config->domain_list = nm_strdict_get_keys(dns_config->domains, TRUE, NULL);
 }
 
 static NMGlobalDnsConfig *
@@ -1060,7 +1060,7 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
                                       NULL,
                                       NULL);
     if (strv) {
-        _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+        nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
         if (!strv[0])
             g_free(strv);
         else
@@ -1073,7 +1073,7 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
                                       NULL,
                                       NULL);
     if (strv) {
-        _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+        nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
         for (i = 0, j = 0; strv[i]; i++) {
             if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, TRUE, NULL))
                 strv[j++] = strv[i];
@@ -1103,7 +1103,7 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
                                           NULL,
                                           NULL);
         if (strv) {
-            _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
             for (i = 0, j = 0; strv[i]; i++) {
                 if (nm_utils_ipaddr_is_valid(AF_INET, strv[i])
                     || nm_utils_ipaddr_is_valid(AF_INET6, strv[i]))
@@ -1128,7 +1128,7 @@ load_global_dns(GKeyFile *keyfile, gboolean internal)
                                           NULL,
                                           NULL);
         if (strv) {
-            options = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            options = nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
             if (!options[0])
                 nm_clear_g_free(&options);
         }
@@ -1240,7 +1240,7 @@ global_dns_domain_from_dbus(char *name, GVariant *variant)
     while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) {
         if (nm_streq0(key, "servers") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) {
             strv = g_variant_dup_strv(val, NULL);
-            _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
             for (i = 0, j = 0; strv && strv[i]; i++) {
                 if (nm_utils_ipaddr_is_valid(AF_INET, strv[i])
                     || nm_utils_ipaddr_is_valid(AF_INET6, strv[i]))
@@ -1258,7 +1258,7 @@ global_dns_domain_from_dbus(char *name, GVariant *variant)
         } else if (nm_streq0(key, "options") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) {
             strv = g_variant_dup_strv(val, NULL);
             g_strfreev(domain->options);
-            domain->options = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            domain->options = nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
             if (!domain->options[0])
                 nm_clear_g_free(&domain->options);
         }
@@ -1305,10 +1305,10 @@ nm_global_dns_config_from_dbus(const GValue *value, GError **error)
     while (g_variant_iter_next(&iter, "{&sv}", &key, &val)) {
         if (nm_streq0(key, "searches") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) {
             strv                 = g_variant_dup_strv(val, NULL);
-            dns_config->searches = _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            dns_config->searches = nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
         } else if (nm_streq0(key, "options") && g_variant_is_of_type(val, G_VARIANT_TYPE("as"))) {
             strv = g_variant_dup_strv(val, NULL);
-            _nm_utils_strv_cleanup(strv, TRUE, TRUE, TRUE);
+            nm_strv_cleanup(strv, TRUE, TRUE, TRUE);
 
             for (i = 0, j = 0; strv && strv[i]; i++) {
                 if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, TRUE, NULL))
@@ -1370,8 +1370,7 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
     if (!old || !new)
         return FALSE;
 
-    if (!nm_utils_strv_equal(old->options, new->options)
-        || !nm_utils_strv_equal(old->searches, new->searches))
+    if (!nm_strv_equal(old->options, new->options) || !nm_strv_equal(old->searches, new->searches))
         return FALSE;
 
     if ((!old->domains || !new->domains) && old->domains != new->domains)
@@ -1389,8 +1388,8 @@ global_dns_equal(NMGlobalDnsConfig *old, NMGlobalDnsConfig *new)
         domain_old = value_old;
         domain_new = value_new;
 
-        if (!nm_utils_strv_equal(domain_old->options, domain_new->options)
-            || !nm_utils_strv_equal(domain_old->servers, domain_new->servers))
+        if (!nm_strv_equal(domain_old->options, domain_new->options)
+            || !nm_strv_equal(domain_old->servers, domain_new->servers))
             return FALSE;
     }
 
@@ -1681,7 +1680,7 @@ _match_section_info_init(MatchSectionInfo *connection_info,
     }
 
     keys = g_key_file_get_keys(keyfile, group, &n_keys, NULL);
-    nm_utils_strv_sort(keys, n_keys);
+    nm_strv_sort(keys, n_keys);
 
     vals = g_new(NMUtilsNamedValue, n_keys);
 
@@ -1963,8 +1962,8 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
             /* sort entries, remove duplicates and empty words. */
             value_arr =
                 len == 0 ? NULL : nm_memdup(value_arr_orig, sizeof(const char *) * (len + 1));
-            nm_utils_strv_sort(value_arr, len);
-            _nm_utils_strv_cleanup((char **) value_arr, FALSE, TRUE, TRUE);
+            nm_strv_sort(value_arr, len);
+            nm_strv_cleanup((char **) value_arr, FALSE, TRUE, TRUE);
 
             len = NM_PTRARRAY_LEN(value_arr);
             j   = 0;
@@ -1988,7 +1987,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
                 specs = g_slist_prepend(specs, spec);
             }
 
-            priv->no_auto_default.arr   = nm_utils_strv_dup(value_arr, j, TRUE);
+            priv->no_auto_default.arr   = nm_strv_dup(value_arr, j, TRUE);
             priv->no_auto_default.specs = g_slist_reverse(specs);
         }
         break;
diff --git a/src/core/nm-config-data.h b/src/core/nm-config-data.h
index fa58d869..92a7cb5c 100644
--- a/src/core/nm-config-data.h
+++ b/src/core/nm-config-data.h
@@ -195,16 +195,16 @@ const char *nm_config_data_get_iwd_config_path(const NMConfigData *self);
 extern const char *__start_connection_defaults[];
 extern const char *__stop_connection_defaults[];
 
-#define NM_CON_DEFAULT_NOP(name)                              \
-    static const char *NM_UNIQ_T(connection_default, NM_UNIQ) \
-        _nm_used       _nm_section("connection_defaults") = "" name
-
-#define NM_CON_DEFAULT(name)                                                                \
-    ({                                                                                      \
-        static const char *__con_default_prop _nm_used _nm_section("connection_defaults") = \
-            "" name;                                                                        \
-                                                                                            \
-        name;                                                                               \
+#define NM_CON_DEFAULT_NOP(name)                                   \
+    static const char *     NM_UNIQ_T(connection_default, NM_UNIQ) \
+        _nm_used _nm_retain _nm_section("connection_defaults") = "" name
+
+#define NM_CON_DEFAULT(name)                                                   \
+    ({                                                                         \
+        static const char *__con_default_prop _nm_used _nm_retain _nm_section( \
+            "connection_defaults") = "" name;                                  \
+                                                                               \
+        name;                                                                  \
     })
 
 const char *nm_config_data_get_connection_default(const NMConfigData *self,
diff --git a/src/core/nm-config.c b/src/core/nm-config.c
index 817368d1..c41f5823 100644
--- a/src/core/nm-config.c
+++ b/src/core/nm-config.c
@@ -336,7 +336,7 @@ no_auto_default_from_file(const char *no_auto_default_file)
     gsize         i;
 
     if (no_auto_default_file && g_file_get_contents(no_auto_default_file, &data, NULL, NULL))
-        list = nm_utils_strsplit_set(data, "\n");
+        list = nm_strsplit_set(data, "\n");
 
     if (list) {
         for (i = 0; list[i]; i++)
@@ -433,7 +433,7 @@ nm_config_set_no_auto_default_for_device(NMConfig *self, NMDevice *device)
 
     len = NM_PTRARRAY_LEN(no_auto_default_current);
 
-    idx = nm_utils_strv_find_binary_search(no_auto_default_current, len, spec);
+    idx = nm_strv_find_binary_search(no_auto_default_current, len, spec);
     if (idx >= 0) {
         /* @spec is already blocked. We don't have to update our in-memory representation.
          * Maybe we should write to no_auto_default_file anew, but let's save that too. */
@@ -1122,14 +1122,12 @@ read_config(GKeyFile *  keyfile,
                     /* merge the string lists, by omitting duplicates. */
 
                     for (iter_val = old_val; iter_val && *iter_val; iter_val++) {
-                        if (last_char != '-'
-                            || nm_utils_strv_find_first(new_val, -1, *iter_val) < 0)
+                        if (last_char != '-' || nm_strv_find_first(new_val, -1, *iter_val) < 0)
                             g_ptr_array_add(new, g_strdup(*iter_val));
                     }
                     for (iter_val = new_val; iter_val && *iter_val; iter_val++) {
                         /* don't add duplicates. That means an "option=a,b"; "option+=a,c" results in "option=a,b,c" */
-                        if (last_char == '+'
-                            && nm_utils_strv_find_first(old_val, -1, *iter_val) < 0)
+                        if (last_char == '+' && nm_strv_find_first(old_val, -1, *iter_val) < 0)
                             g_ptr_array_add(new, *iter_val);
                         else
                             g_free(*iter_val);
@@ -1147,7 +1145,7 @@ read_config(GKeyFile *  keyfile,
                             gs_free char *             specs_joined = NULL;
 
                             g_ptr_array_add(new, NULL);
-                            specs = _nm_utils_strv_to_slist((char **) new->pdata, FALSE);
+                            specs = nm_strv_to_gslist((char **) new->pdata, FALSE);
 
                             specs_joined = nm_match_spec_join(specs);
 
@@ -1333,9 +1331,8 @@ read_entire_config(const NMConfigCmdLineOptions *cli,
         const char *filename = system_confs->pdata[i];
 
         /* if a same named file exists in config_dir or run_config_dir, skip it. */
-        if (nm_utils_strv_find_first((char **) confs->pdata, confs->len, filename) >= 0
-            || nm_utils_strv_find_first((char **) run_confs->pdata, run_confs->len, filename)
-                   >= 0) {
+        if (nm_strv_ptrarray_find_first(confs, filename) >= 0
+            || nm_strv_ptrarray_find_first(run_confs, filename) >= 0) {
             g_ptr_array_remove_index(system_confs, i);
             continue;
         }
@@ -1349,7 +1346,7 @@ read_entire_config(const NMConfigCmdLineOptions *cli,
         const char *filename = run_confs->pdata[i];
 
         /* if a same named file exists in config_dir, skip it. */
-        if (nm_utils_strv_find_first((char **) confs->pdata, confs->len, filename) >= 0) {
+        if (nm_strv_ptrarray_find_first(confs, filename) >= 0) {
             g_ptr_array_remove_index(run_confs, i);
             continue;
         }
diff --git a/src/core/nm-connectivity.c b/src/core/nm-connectivity.c
index 85d41b49..13fb0265 100644
--- a/src/core/nm-connectivity.c
+++ b/src/core/nm-connectivity.c
@@ -10,10 +10,9 @@
 #include "nm-connectivity.h"
 
 #if WITH_CONCHECK
-    #include <curl/curl.h>
+#include <curl/curl.h>
 #endif
 #include <linux/rtnetlink.h>
-#include <glib-unix.h>
 
 #include "c-list/src/c-list.h"
 #include "libnm-core-intern/nm-core-internal.h"
@@ -509,15 +508,8 @@ multi_socket_cb(CURL *e_handle, curl_socket_t fd, int what, void *userdata, void
         else
             condition = 0;
 
-        if (condition) {
-            fdp->source = nm_g_unix_fd_source_new(fd,
-                                                  condition,
-                                                  G_PRIORITY_DEFAULT,
-                                                  _con_curl_socketevent_cb,
-                                                  fdp,
-                                                  NULL);
-            g_source_attach(fdp->source, NULL);
-        }
+        if (condition)
+            fdp->source = nm_g_unix_fd_add_source(fd, condition, _con_curl_socketevent_cb, fdp);
     }
 
     return CURLM_OK;
diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c
index 8fdc7379..747e1c09 100644
--- a/src/core/nm-core-utils.c
+++ b/src/core/nm-core-utils.c
@@ -39,8 +39,8 @@
 #include "nm-setting-wireless-security.h"
 
 #ifdef __NM_SD_UTILS_H__
-    #error \
-        "nm-core-utils.c should stay independent of systemd utils. Are you looking for NetworkMangerUtils.c? "
+#error \
+    "nm-core-utils.c should stay independent of systemd utils. Are you looking for NetworkMangerUtils.c? "
 #endif
 
 G_STATIC_ASSERT(sizeof(NMUtilsTestFlags) <= sizeof(int));
@@ -458,7 +458,7 @@ _kc_invoke_callback(pid_t                   pid,
     data->sync.success      = success;
     data->sync.child_status = child_status;
 
-    g_idle_add(_kc_invoke_callback_idle, data);
+    nm_g_idle_add(_kc_invoke_callback_idle, data);
 }
 
 /* nm_utils_kill_child_async:
@@ -2484,8 +2484,6 @@ out:
 
 typedef struct {
     NMUuid bin;
-    char
-        _nul_sentinel; /* just for safety, if somebody accidentally uses the binary in a string context. */
 
     /* depending on whether the string is packed or not (with/without hyphens),
      * it's 32 or 36 characters long (plus the trailing NUL).
@@ -2503,9 +2501,8 @@ _uuid_data_init(UuidData *uuid_data, gboolean packed, gboolean is_fake, const NM
     nm_assert(uuid_data);
     nm_assert(uuid);
 
-    uuid_data->bin           = *uuid;
-    uuid_data->_nul_sentinel = '\0';
-    uuid_data->is_fake       = is_fake;
+    uuid_data->bin     = *uuid;
+    uuid_data->is_fake = is_fake;
     if (packed) {
         G_STATIC_ASSERT_EXPR(sizeof(uuid_data->str) >= (sizeof(*uuid) * 2 + 1));
         nm_utils_bin2hexstr_full(uuid, sizeof(*uuid), '\0', FALSE, uuid_data->str);
@@ -2573,7 +2570,7 @@ again:
 
         if (is_fake) {
             const guint8 *seed_bin;
-            const char *  hash_seed;
+            const NMUuid *hash_seed;
             gsize         seed_len;
 
             if (!allow_fake) {
@@ -2583,6 +2580,9 @@ again:
             }
 
             if (nm_utils_host_id_get(&seed_bin, &seed_len)) {
+                static const NMUuid u =
+                    NM_UUID_INIT(ab, 08, 5f, 06, b6, 29, 46, d1, a5, 53, 84, ee, ba, 56, 83, b6);
+
                 /* We have no valid machine-id but we have a valid secrey_key.
                  * Generate a fake machine ID by hashing the secret-key. The secret_key
                  * is commonly persisted, so it should be stable across reboots (despite
@@ -2595,8 +2595,11 @@ again:
                  * will call _machine_id_get(FALSE), so it won't allow accessing a fake
                  * machine-id, thus avoiding the problem. */
                 fake_type = "secret-key";
-                hash_seed = "ab085f06-b629-46d1-a553-84eeba5683b6";
+                hash_seed = &u;
             } else {
+                static const NMUuid u =
+                    NM_UUID_INIT(7f, f0, c8, f5, 53, 99, 49, 01, ab, 63, 61, bf, 59, 4a, be, 8b);
+
                 /* the secret-key is not valid/persistent either. That happens when we fail
                  * to read/write the secret-key to disk. Fallback to boot-id. The boot-id
                  * itself may be fake and randomly generated ad-hoc, but that is as best
@@ -2604,7 +2607,7 @@ again:
                 seed_bin  = (const guint8 *) nm_utils_boot_id_bin();
                 seed_len  = sizeof(NMUuid);
                 fake_type = "boot-id";
-                hash_seed = "7ff0c8f5-5399-4901-ab63-61bf594abe8b";
+                hash_seed = &u;
             }
 
             /* the fake machine-id is based on secret-key/boot-id, but we hash it
@@ -2613,7 +2616,7 @@ again:
                                          (const char *) seed_bin,
                                          seed_len,
                                          NM_UUID_TYPE_VERSION5,
-                                         (gpointer) hash_seed);
+                                         hash_seed);
         }
 
         if (!g_once_init_enter(&lock))
@@ -2874,10 +2877,11 @@ typedef struct {
     bool    timestamp_is_good : 1;
 } HostIdData;
 
+static const HostIdData *volatile host_id_static;
+
 static const HostIdData *
 _host_id_get(void)
 {
-    static const HostIdData *volatile host_id_static;
     const HostIdData *host_id;
 
 again:
@@ -2938,6 +2942,78 @@ nm_utils_host_id_get_timestamp_ns(void)
     return _host_id_get()->timestamp_ns;
 }
 
+static GArray *   nmtst_host_id_stack = NULL;
+static GMutex     nmtst_host_id_lock;
+const HostIdData *nmtst_host_id_static_0 = NULL;
+
+void
+nmtst_utils_host_id_push(const guint8 *host_id,
+                         gssize        host_id_len,
+                         gboolean      is_good,
+                         const gint64 *timestamp_ns)
+{
+    NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
+    gs_free char *str1_to_free = NULL;
+    HostIdData *  h;
+
+    g_assert(host_id_len >= -1);
+
+    if (host_id_len < 0)
+        host_id_len = host_id ? strlen((const char *) host_id) : 0;
+
+    nm_log_dbg(LOGD_CORE,
+               "nmtst: host-id push: \"%s\" (%zu), is-good=%d, timestamp=%" G_GINT64_FORMAT "%s",
+               nm_utils_buf_utf8safe_escape(host_id,
+                                            host_id_len,
+                                            NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
+                                            &str1_to_free),
+               (gsize) host_id_len,
+               !!is_good,
+               timestamp_ns ? *timestamp_ns : 0,
+               timestamp_ns ? "" : " (not-good)");
+
+    if (!nmtst_host_id_stack) {
+        nmtst_host_id_stack    = g_array_new(FALSE, FALSE, sizeof(HostIdData));
+        nmtst_host_id_static_0 = g_atomic_pointer_get(&host_id_static);
+    }
+
+    h = nm_g_array_append_new(nmtst_host_id_stack, HostIdData);
+
+    *h = (HostIdData){
+        .host_id           = nm_memdup(host_id, host_id_len),
+        .host_id_len       = host_id_len,
+        .timestamp_ns      = timestamp_ns ? *timestamp_ns : 0,
+        .is_good           = is_good,
+        .timestamp_is_good = !!timestamp_ns,
+    };
+
+    g_atomic_pointer_set(&host_id_static, h);
+}
+
+void
+nmtst_utils_host_id_pop(void)
+{
+    NM_G_MUTEX_LOCKED(&nmtst_host_id_lock);
+    HostIdData *h;
+
+    g_assert(nmtst_host_id_stack);
+    g_assert(nmtst_host_id_stack->len > 0);
+
+    nm_log_dbg(LOGD_CORE, "nmtst: host-id pop");
+
+    h = &g_array_index(nmtst_host_id_stack, HostIdData, nmtst_host_id_stack->len - 1);
+
+    g_free((char *) h->host_id);
+    g_array_set_size(nmtst_host_id_stack, nmtst_host_id_stack->len - 1u);
+
+    if (!g_atomic_pointer_compare_and_exchange(
+            &host_id_static,
+            h,
+            nmtst_host_id_stack->len == 0u ? nmtst_host_id_static_0
+                                           : nm_g_array_last(nmtst_host_id_stack, HostIdData)))
+        g_assert_not_reached();
+}
+
 /*****************************************************************************/
 
 static const UuidData *
@@ -3423,15 +3499,14 @@ _is_reserved_ipv6_iid(const guint8 *iid)
     return FALSE;
 }
 
-static gboolean
-_set_stable_privacy(NMUtilsStableType stable_type,
-                    struct in6_addr * addr,
-                    const char *      ifname,
-                    const char *      network_id,
-                    guint32           dad_counter,
-                    const guint8 *    host_id,
-                    gsize             host_id_len,
-                    GError **         error)
+void
+nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NMUtilsStableType stable_type,
+                                                   struct in6_addr * addr,
+                                                   const char *      ifname,
+                                                   const char *      network_id,
+                                                   guint32           dad_counter,
+                                                   const guint8 *    host_id,
+                                                   gsize             host_id_len)
 {
     nm_auto_free_checksum GChecksum *sum = NULL;
     guint8                           digest[NM_UTILS_CHECKSUM_LENGTH_SHA256];
@@ -3480,30 +3555,29 @@ _set_stable_privacy(NMUtilsStableType stable_type,
     }
 
     memcpy(addr->s6_addr + 8, &digest[0], 8);
-    return TRUE;
 }
 
-gboolean
-nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type,
-                                           struct in6_addr * addr,
-                                           const char *      ifname,
-                                           const char *      network_id,
-                                           guint32           dad_counter,
-                                           guint8 *          host_id,
-                                           gsize             host_id_len,
-                                           GError **         error)
+void
+nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type,
+                                      struct in6_addr * addr,
+                                      const char *      ifname,
+                                      const char *      network_id,
+                                      guint32           dad_counter)
 {
-    return _set_stable_privacy(stable_type,
-                               addr,
-                               ifname,
-                               network_id,
-                               dad_counter,
-                               host_id,
-                               host_id_len,
-                               error);
+    const guint8 *host_id;
+    gsize         host_id_len;
+
+    nm_utils_host_id_get(&host_id, &host_id_len);
+
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(stable_type,
+                                                       addr,
+                                                       ifname,
+                                                       network_id,
+                                                       dad_counter,
+                                                       host_id,
+                                                       host_id_len);
 }
 
-#define RFC7217_IDGEN_RETRIES 3
 /**
  * nm_utils_ipv6_addr_set_stable_privacy:
  *
@@ -3513,19 +3587,16 @@ nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type,
  * Returns: %TRUE on success, %FALSE if the address could not be generated.
  */
 gboolean
-nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type,
-                                      struct in6_addr * addr,
-                                      const char *      ifname,
-                                      const char *      network_id,
-                                      guint32           dad_counter,
-                                      GError **         error)
+nm_utils_ipv6_addr_set_stable_privacy_may_fail(NMUtilsStableType stable_type,
+                                               struct in6_addr * addr,
+                                               const char *      ifname,
+                                               const char *      network_id,
+                                               guint32           dad_counter,
+                                               GError **         error)
 {
-    const guint8 *host_id;
-    gsize         host_id_len;
-
     g_return_val_if_fail(network_id, FALSE);
 
-    if (dad_counter >= RFC7217_IDGEN_RETRIES) {
+    if (dad_counter >= NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES) {
         g_set_error_literal(error,
                             NM_UTILS_ERROR,
                             NM_UTILS_ERROR_UNKNOWN,
@@ -3533,16 +3604,8 @@ nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type,
         return FALSE;
     }
 
-    nm_utils_host_id_get(&host_id, &host_id_len);
-
-    return _set_stable_privacy(stable_type,
-                               addr,
-                               ifname,
-                               network_id,
-                               dad_counter,
-                               host_id,
-                               host_id_len,
-                               error);
+    nm_utils_ipv6_addr_set_stable_privacy(stable_type, addr, ifname, network_id, dad_counter);
+    return TRUE;
 }
 
 /*****************************************************************************/
@@ -4069,8 +4132,8 @@ nm_utils_get_reverse_dns_domains_ip_4(guint32 addr, guint8 plen, GPtrArray *doma
         len = len0;
         str = s = g_malloc(len);
         for (i = octets; i > 0; i--)
-            nm_utils_strbuf_append(&s, &len, "%u.", p[i - 1] & 0xff);
-        nm_utils_strbuf_append_str(&s, &len, "in-addr.arpa");
+            nm_strbuf_append(&s, &len, "%u.", p[i - 1] & 0xff);
+        nm_strbuf_append_str(&s, &len, "in-addr.arpa");
 
         g_ptr_array_add(domains, str);
 
@@ -4121,8 +4184,8 @@ nm_utils_get_reverse_dns_domains_ip_6(const struct in6_addr *ip, guint8 plen, GP
         str = s = g_malloc(len);
 
         for (j = nibbles - 1; j >= 0; j--)
-            nm_utils_strbuf_append(&s, &len, "%x.", (addr.s6_addr[j / 2] >> N_SHIFT(j)) & 0xf);
-        nm_utils_strbuf_append_str(&s, &len, "ip6.arpa");
+            nm_strbuf_append(&s, &len, "%x.", (addr.s6_addr[j / 2] >> N_SHIFT(j)) & 0xf);
+        nm_strbuf_append_str(&s, &len, "ip6.arpa");
 
         g_ptr_array_add(domains, str);
 
diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h
index 76c340d1..38f56adc 100644
--- a/src/core/nm-core-utils.h
+++ b/src/core/nm-core-utils.h
@@ -40,7 +40,7 @@ void _nm_singleton_instance_register_destruction(GObject *instance);
 /* By default, the getter will assert that the singleton will be created only once. You can
  * change this by redefining NM_DEFINE_SINGLETON_ALLOW_MULTIPLE. */
 #ifndef NM_DEFINE_SINGLETON_ALLOW_MULTIPLE
-    #define NM_DEFINE_SINGLETON_ALLOW_MULTIPLE FALSE
+#define NM_DEFINE_SINGLETON_ALLOW_MULTIPLE FALSE
 #endif
 
 #define NM_DEFINE_SINGLETON_GETTER(TYPE, GETTER, GTYPE, ...)                                \
@@ -247,6 +247,33 @@ const char *const *   nm_utils_proc_cmdline_split(void);
 gboolean nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len);
 gint64   nm_utils_host_id_get_timestamp_ns(void);
 
+void nmtst_utils_host_id_push(const guint8 *host_id,
+                              gssize        host_id_len,
+                              gboolean      is_good,
+                              const gint64 *timestamp_ns);
+
+void nmtst_utils_host_id_pop(void);
+
+static inline void
+_nmtst_auto_utils_host_id_context_pop(const char *const *unused)
+{
+    nmtst_utils_host_id_pop();
+}
+
+#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id)                      \
+    _nm_unused            nm_auto(_nmtst_auto_utils_host_id_context_pop) \
+        const char *const NM_UNIQ_T(_host_id_context_, uniq) = ({        \
+            const gint64 _timestamp_ns = 1631000672;                     \
+                                                                         \
+            nmtst_utils_host_id_push((const guint8 *) "" host_id "",     \
+                                     NM_STRLEN(host_id),                 \
+                                     TRUE,                               \
+                                     &_timestamp_ns);                    \
+            "" host_id "";                                               \
+        })
+
+#define NMTST_UTILS_HOST_ID_CONTEXT(host_id) _NMTST_UTILS_HOST_ID_CONTEXT(NM_UNIQ, host_id)
+
 /*****************************************************************************/
 
 int nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len);
@@ -271,6 +298,8 @@ typedef enum {
     NM_UTILS_STABLE_TYPE_RANDOM    = 3,
 } NMUtilsStableType;
 
+#define NM_UTILS_STABLE_TYPE_NONE ((NMUtilsStableType) -1)
+
 NMUtilsStableType nm_utils_stable_id_parse(const char *stable_id,
                                            const char *deviceid,
                                            const char *hwaddr,
@@ -281,21 +310,28 @@ NMUtilsStableType nm_utils_stable_id_parse(const char *stable_id,
 char *nm_utils_stable_id_random(void);
 char *nm_utils_stable_id_generated_complete(const char *msg);
 
-gboolean nm_utils_ipv6_addr_set_stable_privacy_impl(NMUtilsStableType stable_type,
-                                                    struct in6_addr * addr,
-                                                    const char *      ifname,
-                                                    const char *      network_id,
-                                                    guint32           dad_counter,
-                                                    guint8 *          host_id,
-                                                    gsize             host_id_len,
-                                                    GError **         error);
-
-gboolean nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType id_type,
-                                               struct in6_addr * addr,
-                                               const char *      ifname,
-                                               const char *      network_id,
-                                               guint32           dad_counter,
-                                               GError **         error);
+#define NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES 3
+
+void nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NMUtilsStableType stable_type,
+                                                        struct in6_addr * addr,
+                                                        const char *      ifname,
+                                                        const char *      network_id,
+                                                        guint32           dad_counter,
+                                                        const guint8 *    host_id,
+                                                        gsize             host_id_len);
+
+void nm_utils_ipv6_addr_set_stable_privacy(NMUtilsStableType stable_type,
+                                           struct in6_addr * addr,
+                                           const char *      ifname,
+                                           const char *      network_id,
+                                           guint32           dad_counter);
+
+gboolean nm_utils_ipv6_addr_set_stable_privacy_may_fail(NMUtilsStableType stable_type,
+                                                        struct in6_addr * addr,
+                                                        const char *      ifname,
+                                                        const char *      network_id,
+                                                        guint32           dad_counter,
+                                                        GError **         error);
 
 char *nm_utils_hw_addr_gen_random_eth(const char *current_mac_address,
                                       const char *generate_mac_address_mask);
diff --git a/src/core/nm-dbus-manager.c b/src/core/nm-dbus-manager.c
index c47d4dbf..323d0a19 100644
--- a/src/core/nm-dbus-manager.c
+++ b/src/core/nm-dbus-manager.c
@@ -262,7 +262,7 @@ private_server_closed_connection(GDBusConnection *conn,
 
     /* Delay the close of connection to ensure that D-Bus signals
      * are handled */
-    g_idle_add(close_connection_in_idle, info);
+    nm_g_idle_add(close_connection_in_idle, info);
 }
 
 static gboolean
diff --git a/src/core/nm-dbus-object.c b/src/core/nm-dbus-object.c
index 04149739..95077626 100644
--- a/src/core/nm-dbus-object.c
+++ b/src/core/nm-dbus-object.c
@@ -178,7 +178,7 @@ nm_dbus_object_unexport_on_idle(gpointer /* (NMDBusObject *) */ self_take)
     nm_shutdown_wait_obj_register_object(self, "unexport-dbus-obj-on-idle");
 
     /* pass on ownership. */
-    g_idle_add(_unexport_on_idle_cb, g_steal_pointer(&self));
+    nm_g_idle_add(_unexport_on_idle_cb, g_steal_pointer(&self));
 }
 
 /*****************************************************************************/
diff --git a/src/core/nm-dbus-utils.c b/src/core/nm-dbus-utils.c
index d3e5612d..7f7b9fb9 100644
--- a/src/core/nm-dbus-utils.c
+++ b/src/core/nm-dbus-utils.c
@@ -97,27 +97,13 @@ nm_dbus_utils_get_property(GObject *obj, const char *signature, const char *prop
 void
 nm_dbus_utils_g_value_set_object_path(GValue *value, gpointer object)
 {
-    const char *path;
-
-    g_return_if_fail(!object || NM_IS_DBUS_OBJECT(object));
-
-    if (object && (path = nm_dbus_object_get_path(object)))
-        g_value_set_string(value, path);
-    else
-        g_value_set_string(value, NULL);
+    g_value_set_string(value, object ? nm_dbus_object_get_path(object) : NULL);
 }
 
 void
 nm_dbus_utils_g_value_set_object_path_still_exported(GValue *value, gpointer object)
 {
-    const char *path;
-
-    g_return_if_fail(!object || NM_IS_DBUS_OBJECT(object));
-
-    if (object && (path = nm_dbus_object_get_path_still_exported(object)))
-        g_value_set_string(value, path);
-    else
-        g_value_set_string(value, "/");
+    g_value_set_string(value, object ? nm_dbus_object_get_path_still_exported(object) : "/");
 }
 
 void
@@ -152,7 +138,7 @@ nm_dbus_utils_g_value_set_object_path_from_hash(
     strv[i] = NULL;
 
     /* sort the names, to give a well-defined, stable order. */
-    nm_utils_strv_sort(strv, i);
+    nm_strv_sort(strv, i);
 
     g_value_take_boxed(value, strv);
 }
diff --git a/src/core/nm-dcb.c b/src/core/nm-dcb.c
index 930a1023..f048d999 100644
--- a/src/core/nm-dcb.c
+++ b/src/core/nm-dcb.c
@@ -35,7 +35,7 @@ do_helper(const char *iface,
     cmdline = g_strdup_vprintf(fmt, args);
     va_end(args);
 
-    split = nm_utils_strsplit_set_with_empty(cmdline, " ");
+    split = nm_strsplit_set_with_empty(cmdline, " ");
     if (!split) {
         g_set_error(error,
                     NM_MANAGER_ERROR,
@@ -258,7 +258,8 @@ _fcoe_setup(const char *  iface,
 
     flags = nm_setting_dcb_get_app_fcoe_flags(s_dcb);
     if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
-        const char *mode = nm_setting_dcb_get_app_fcoe_mode(s_dcb);
+        const char *mode =
+            nm_setting_dcb_get_app_fcoe_mode(s_dcb) ?: NM_SETTING_DCB_FCOE_MODE_FABRIC;
 
         if (!do_helper(NULL, FCOEADM, run_func, user_data, error, "-m %s -c %s", mode, iface))
             return FALSE;
diff --git a/src/core/nm-dhcp-config.c b/src/core/nm-dhcp-config.c
index 1cbad49e..8a029119 100644
--- a/src/core/nm-dhcp-config.c
+++ b/src/core/nm-dhcp-config.c
@@ -87,7 +87,7 @@ nm_dhcp_config_set_options(NMDhcpConfig *self, GHashTable *options)
     priv = NM_DHCP_CONFIG_GET_PRIVATE(self);
 
     nm_g_variant_unref(priv->options);
-    priv->options = g_variant_ref_sink(nm_utils_strdict_to_variant_asv(options));
+    priv->options = g_variant_ref_sink(nm_strdict_to_variant_asv(options));
     _notify(self, PROP_OPTIONS);
 }
 
diff --git a/src/core/nm-dispatcher.c b/src/core/nm-dispatcher.c
index 32c9db62..b7719786 100644
--- a/src/core/nm-dispatcher.c
+++ b/src/core/nm-dispatcher.c
@@ -448,17 +448,17 @@ dispatcher_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
     dispatcher_call_id_free(call_id);
 }
 
-static const char *action_table[] = {[NM_DISPATCHER_ACTION_HOSTNAME]     = NMD_ACTION_HOSTNAME,
-                                     [NM_DISPATCHER_ACTION_PRE_UP]       = NMD_ACTION_PRE_UP,
-                                     [NM_DISPATCHER_ACTION_UP]           = NMD_ACTION_UP,
-                                     [NM_DISPATCHER_ACTION_PRE_DOWN]     = NMD_ACTION_PRE_DOWN,
-                                     [NM_DISPATCHER_ACTION_DOWN]         = NMD_ACTION_DOWN,
-                                     [NM_DISPATCHER_ACTION_VPN_PRE_UP]   = NMD_ACTION_VPN_PRE_UP,
-                                     [NM_DISPATCHER_ACTION_VPN_UP]       = NMD_ACTION_VPN_UP,
-                                     [NM_DISPATCHER_ACTION_VPN_PRE_DOWN] = NMD_ACTION_VPN_PRE_DOWN,
-                                     [NM_DISPATCHER_ACTION_VPN_DOWN]     = NMD_ACTION_VPN_DOWN,
-                                     [NM_DISPATCHER_ACTION_DHCP4_CHANGE] = NMD_ACTION_DHCP4_CHANGE,
-                                     [NM_DISPATCHER_ACTION_DHCP6_CHANGE] = NMD_ACTION_DHCP6_CHANGE,
+static const char *action_table[] = {[NM_DISPATCHER_ACTION_HOSTNAME]      = NMD_ACTION_HOSTNAME,
+                                     [NM_DISPATCHER_ACTION_PRE_UP]        = NMD_ACTION_PRE_UP,
+                                     [NM_DISPATCHER_ACTION_UP]            = NMD_ACTION_UP,
+                                     [NM_DISPATCHER_ACTION_PRE_DOWN]      = NMD_ACTION_PRE_DOWN,
+                                     [NM_DISPATCHER_ACTION_DOWN]          = NMD_ACTION_DOWN,
+                                     [NM_DISPATCHER_ACTION_VPN_PRE_UP]    = NMD_ACTION_VPN_PRE_UP,
+                                     [NM_DISPATCHER_ACTION_VPN_UP]        = NMD_ACTION_VPN_UP,
+                                     [NM_DISPATCHER_ACTION_VPN_PRE_DOWN]  = NMD_ACTION_VPN_PRE_DOWN,
+                                     [NM_DISPATCHER_ACTION_VPN_DOWN]      = NMD_ACTION_VPN_DOWN,
+                                     [NM_DISPATCHER_ACTION_DHCP_CHANGE_4] = NMD_ACTION_DHCP4_CHANGE,
+                                     [NM_DISPATCHER_ACTION_DHCP_CHANGE_6] = NMD_ACTION_DHCP6_CHANGE,
                                      [NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE] =
                                          NMD_ACTION_CONNECTIVITY_CHANGE};
 
diff --git a/src/core/nm-dispatcher.h b/src/core/nm-dispatcher.h
index d588de4e..8e4f01cb 100644
--- a/src/core/nm-dispatcher.h
+++ b/src/core/nm-dispatcher.h
@@ -19,11 +19,14 @@ typedef enum {
     NM_DISPATCHER_ACTION_VPN_UP,
     NM_DISPATCHER_ACTION_VPN_PRE_DOWN,
     NM_DISPATCHER_ACTION_VPN_DOWN,
-    NM_DISPATCHER_ACTION_DHCP4_CHANGE,
-    NM_DISPATCHER_ACTION_DHCP6_CHANGE,
+    NM_DISPATCHER_ACTION_DHCP_CHANGE_4,
+    NM_DISPATCHER_ACTION_DHCP_CHANGE_6,
     NM_DISPATCHER_ACTION_CONNECTIVITY_CHANGE
 } NMDispatcherAction;
 
+#define NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4) \
+    ((IS_IPv4) ? NM_DISPATCHER_ACTION_DHCP_CHANGE_4 : NM_DISPATCHER_ACTION_DHCP_CHANGE_6)
+
 typedef struct NMDispatcherCallId NMDispatcherCallId;
 
 typedef void (*NMDispatcherFunc)(NMDispatcherCallId *call_id, gpointer user_data);
diff --git a/src/core/nm-firewalld-manager.c b/src/core/nm-firewalld-manager.c
index 7866894a..43ab278a 100644
--- a/src/core/nm-firewalld-manager.c
+++ b/src/core/nm-firewalld-manager.c
@@ -487,7 +487,7 @@ name_owner_changed(NMFirewalldManager *self, const char *owner)
     just_initied = !priv->dbus_inited;
 
     priv->dbus_inited  = TRUE;
-    name_owner_changed = nm_utils_strdup_reset(&priv->name_owner, owner);
+    name_owner_changed = nm_strdup_reset(&priv->name_owner, owner);
 
     now_running = _get_running(priv);
 
diff --git a/src/core/nm-hostname-manager.c b/src/core/nm-hostname-manager.c
index 91e9baaa..32dc2db9 100644
--- a/src/core/nm-hostname-manager.c
+++ b/src/core/nm-hostname-manager.c
@@ -10,8 +10,8 @@
 #include <sys/stat.h>
 
 #if HAVE_SELINUX
-    #include <selinux/selinux.h>
-    #include <selinux/label.h>
+#include <selinux/selinux.h>
+#include <selinux/label.h>
 #endif
 
 #include "libnm-core-aux-intern/nm-common-macros.h"
@@ -37,17 +37,17 @@
 #if (defined(HOSTNAME_PERSIST_SUSE) + defined(HOSTNAME_PERSIST_SLACKWARE) \
      + defined(HOSTNAME_PERSIST_GENTOO))                                  \
     > 1
-    #error "Can only define one of HOSTNAME_PERSIST_*"
+#error "Can only define one of HOSTNAME_PERSIST_*"
 #endif
 
 #if defined(HOSTNAME_PERSIST_SUSE)
-    #define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
+#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
 #elif defined(HOSTNAME_PERSIST_SLACKWARE)
-    #define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
+#define HOSTNAME_FILE HOSTNAME_FILE_UCASE_HOSTNAME
 #elif defined(HOSTNAME_PERSIST_GENTOO)
-    #define HOSTNAME_FILE HOSTNAME_FILE_GENTOO
+#define HOSTNAME_FILE HOSTNAME_FILE_GENTOO
 #else
-    #define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT
+#define HOSTNAME_FILE HOSTNAME_FILE_DEFAULT
 #endif
 
 /*****************************************************************************/
@@ -86,6 +86,19 @@ NM_DEFINE_SINGLETON_GETTER(NMHostnameManager, nm_hostname_manager_get, NM_TYPE_H
 
 /*****************************************************************************/
 
+static inline GFileMonitor *
+_file_monitor_new(const char *path)
+{
+    gs_unref_object GFile *file = NULL;
+
+    nm_assert(path);
+
+    file = g_file_new_for_path(path);
+    return g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
+}
+
+/*****************************************************************************/
+
 #if defined(HOSTNAME_PERSIST_GENTOO)
 static char *
 read_hostname_gentoo(const char *path)
@@ -162,41 +175,6 @@ hostname_is_dynamic(void)
 }
 #endif
 
-/* Returns an allocated string which the caller owns and must eventually free */
-char *
-nm_hostname_manager_read_hostname(NMHostnameManager *self)
-{
-    NMHostnameManagerPrivate *priv     = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
-    char *                    hostname = NULL;
-
-    if (priv->hostnamed_proxy) {
-        hostname = g_strdup(priv->current_hostname);
-        goto out;
-    }
-
-#if defined(HOSTNAME_PERSIST_SUSE)
-    if (priv->dhcp_monitor_id && hostname_is_dynamic())
-        return NULL;
-#endif
-
-#if defined(HOSTNAME_PERSIST_GENTOO)
-    hostname = read_hostname_gentoo(HOSTNAME_FILE);
-#elif defined(HOSTNAME_PERSIST_SLACKWARE)
-    hostname     = read_hostname_slackware(HOSTNAME_FILE);
-#else
-    if (g_file_get_contents(HOSTNAME_FILE, &hostname, NULL, NULL))
-        g_strchomp(hostname);
-#endif
-
-out:
-    if (hostname && !hostname[0]) {
-        g_free(hostname);
-        return NULL;
-    }
-
-    return hostname;
-}
-
 /*****************************************************************************/
 
 const char *
@@ -207,76 +185,78 @@ nm_hostname_manager_get_hostname(NMHostnameManager *self)
 }
 
 static void
-_set_hostname_take(NMHostnameManager *self, char *hostname)
+_set_hostname(NMHostnameManager *self, const char *hostname)
 {
     NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
+    char *                    old_hostname;
+
+    hostname = nm_str_not_empty(hostname);
+
+    if (nm_streq0(hostname, priv->current_hostname))
+        return;
 
     _LOGI("hostname changed from %s%s%s to %s%s%s",
           NM_PRINT_FMT_QUOTED(priv->current_hostname, "\"", priv->current_hostname, "\"", "(none)"),
           NM_PRINT_FMT_QUOTED(hostname, "\"", hostname, "\"", "(none)"));
 
-    g_free(priv->current_hostname);
-    priv->current_hostname = hostname;
-    _notify(self, PROP_HOSTNAME);
-}
-
-static void
-_set_hostname(NMHostnameManager *self, const char *hostname)
-{
-    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
+    old_hostname           = priv->current_hostname;
+    priv->current_hostname = g_strdup(hostname);
+    g_free(old_hostname);
 
-    hostname = nm_str_not_empty(hostname);
-    if (!nm_streq0(hostname, priv->current_hostname))
-        _set_hostname_take(self, g_strdup(hostname));
+    _notify(self, PROP_HOSTNAME);
 }
 
 static void
-_set_hostname_read(NMHostnameManager *self)
+_set_hostname_read_file(NMHostnameManager *self)
 {
-    NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
-    char *                    hostname;
+    NMHostnameManagerPrivate *priv     = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
+    gs_free char *            hostname = NULL;
 
     if (priv->hostnamed_proxy) {
         /* read-hostname returns the current hostname with hostnamed. */
         return;
     }
 
-    hostname = nm_hostname_manager_read_hostname(self);
-
-    if (nm_streq0(hostname, priv->current_hostname)) {
-        g_free(hostname);
+#if defined(HOSTNAME_PERSIST_SUSE)
+    if (priv->dhcp_monitor_id && hostname_is_dynamic())
         return;
-    }
+#endif
+
+#if defined(HOSTNAME_PERSIST_GENTOO)
+    hostname = read_hostname_gentoo(HOSTNAME_FILE);
+#elif defined(HOSTNAME_PERSIST_SLACKWARE)
+    hostname     = read_hostname_slackware(HOSTNAME_FILE);
+#else
+    if (g_file_get_contents(HOSTNAME_FILE, &hostname, NULL, NULL))
+        g_strchomp(hostname);
+#endif
 
-    _set_hostname_take(self, hostname);
+    _set_hostname(self, hostname);
 }
 
 /*****************************************************************************/
 
-typedef struct {
-    char *                         hostname;
-    NMHostnameManagerSetHostnameCb cb;
-    gpointer                       user_data;
-} SetHostnameInfo;
-
 static void
 set_transient_hostname_done(GObject *object, GAsyncResult *res, gpointer user_data)
 {
-    GDBusProxy *proxy                 = G_DBUS_PROXY(object);
-    gs_free SetHostnameInfo *info     = user_data;
-    gs_unref_variant GVariant *result = NULL;
-    gs_free_error GError *error       = NULL;
+    GDBusProxy *     proxy                  = G_DBUS_PROXY(object);
+    gs_unref_variant GVariant *result       = NULL;
+    gs_free_error GError *         error    = NULL;
+    gs_free char *                 hostname = NULL;
+    NMHostnameManagerSetHostnameCb cb;
+    gpointer                       cb_user_data;
+
+    nm_utils_user_data_unpack(user_data, &hostname, &cb, &cb_user_data);
 
     result = g_dbus_proxy_call_finish(proxy, res, &error);
 
     if (error) {
         _LOGW("couldn't set the system hostname to '%s' using hostnamed: %s",
-              info->hostname,
+              hostname,
               error->message);
     }
 
-    info->cb(info->hostname, !error, info->user_data);
-    g_free(info->hostname);
+    cb(hostname, !error, cb_user_data);
 }
 
 void
@@ -286,7 +266,6 @@ nm_hostname_manager_set_transient_hostname(NMHostnameManager *            self,
                                            gpointer                       user_data)
 {
     NMHostnameManagerPrivate *priv;
-    SetHostnameInfo *         info;
 
     g_return_if_fail(NM_IS_HOSTNAME_MANAGER(self));
 
@@ -297,11 +276,6 @@ nm_hostname_manager_set_transient_hostname(NMHostnameManager *            self,
         return;
     }
 
-    info            = g_new0(SetHostnameInfo, 1);
-    info->hostname  = g_strdup(hostname);
-    info->cb        = cb;
-    info->user_data = user_data;
-
     g_dbus_proxy_call(priv->hostnamed_proxy,
                       "SetHostname",
                       g_variant_new("(sb)", hostname, FALSE),
@@ -309,7 +283,7 @@ nm_hostname_manager_set_transient_hostname(NMHostnameManager *            self,
                       -1,
                       NULL,
                       set_transient_hostname_done,
-                      info);
+                      nm_utils_user_data_pack(g_strdup(hostname), cb, user_data));
 }
 
 gboolean
@@ -428,71 +402,63 @@ nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname
     return TRUE;
 }
 
-gboolean
-nm_hostname_manager_validate_hostname(const char *hostname)
-{
-    const char *p;
-    gboolean    dot = TRUE;
-
-    if (!hostname || !hostname[0])
-        return FALSE;
-
-    for (p = hostname; *p; p++) {
-        if (*p == '.') {
-            if (dot)
-                return FALSE;
-            dot = TRUE;
-        } else {
-            if (!g_ascii_isalnum(*p) && (*p != '-') && (*p != '_'))
-                return FALSE;
-            dot = FALSE;
-        }
-    }
+/*****************************************************************************/
 
-    if (dot)
-        return FALSE;
+static void
+hostnamed_properties_changed(GDBusProxy *proxy,
+                             GVariant *  changed_properties,
+                             char **     invalidated_properties,
+                             gpointer    user_data)
+{
+    NMHostnameManager *       self     = user_data;
+    NMHostnameManagerPrivate *priv     = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
+    gs_unref_variant GVariant *variant = NULL;
 
-    return (p - hostname <= HOST_NAME_MAX);
+    variant = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "StaticHostname");
+    if (variant && g_variant_is_of_type(variant, G_VARIANT_TYPE_STRING))
+        _set_hostname(self, g_variant_get_string(variant, NULL));
 }
 
+/*****************************************************************************/
+
 static void
-hostname_file_changed_cb(GFileMonitor *    monitor,
-                         GFile *           file,
-                         GFile *           other_file,
-                         GFileMonitorEvent event_type,
-                         gpointer          user_data)
+_file_monitors_file_changed_cb(GFileMonitor *    monitor,
+                               GFile *           file,
+                               GFile *           other_file,
+                               GFileMonitorEvent event_type,
+                               gpointer          user_data)
 {
-    _set_hostname_read(user_data);
+    _set_hostname_read_file(user_data);
 }
 
-/*****************************************************************************/
-
 static void
-hostnamed_properties_changed(GDBusProxy *proxy,
-                             GVariant *  changed_properties,
-                             char **     invalidated_properties,
-                             gpointer    user_data)
+_file_monitors_clear(NMHostnameManager *self)
 {
-    NMHostnameManager *       self = user_data;
     NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
-    GVariant *                v_hostname;
 
-    v_hostname = g_dbus_proxy_get_cached_property(priv->hostnamed_proxy, "StaticHostname");
-    if (v_hostname) {
-        _set_hostname(self, g_variant_get_string(v_hostname, NULL));
-        g_variant_unref(v_hostname);
+    if (priv->monitor) {
+        nm_clear_g_signal_handler(priv->monitor, &priv->monitor_id);
+        g_file_monitor_cancel(priv->monitor);
+        g_clear_object(&priv->monitor);
+    }
+
+    if (priv->dhcp_monitor) {
+        nm_clear_g_signal_handler(priv->dhcp_monitor, &priv->dhcp_monitor_id);
+        g_file_monitor_cancel(priv->dhcp_monitor);
+        g_clear_object(&priv->dhcp_monitor);
     }
 }
 
 static void
-setup_hostname_file_monitors(NMHostnameManager *self)
+_file_monitors_setup(NMHostnameManager *self)
 {
     NMHostnameManagerPrivate *priv = NM_HOSTNAME_MANAGER_GET_PRIVATE(self);
     GFileMonitor *            monitor;
     const char *              path      = HOSTNAME_FILE;
-    char *                    link_path = NULL;
+    gs_free char *            link_path = NULL;
     struct stat               file_stat;
-    GFile *                   file;
+
+    _file_monitors_clear(self);
 
     /* resolve the path to the hostname file if it is a symbolic link */
     if (lstat(path, &file_stat) == 0 && S_ISLNK(file_stat.st_mode)
@@ -505,29 +471,24 @@ setup_hostname_file_monitors(NMHostnameManager *self)
     }
 
     /* monitor changes to hostname file */
-    file    = g_file_new_for_path(path);
-    monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
-    g_object_unref(file);
-    g_free(link_path);
+    monitor = _file_monitor_new(path);
     if (monitor) {
         priv->monitor_id =
-            g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self);
+            g_signal_connect(monitor, "changed", G_CALLBACK(_file_monitors_file_changed_cb), self);
         priv->monitor = monitor;
     }
 
 #if defined(HOSTNAME_PERSIST_SUSE)
     /* monitor changes to dhcp file to know whether the hostname is valid */
-    file    = g_file_new_for_path(CONF_DHCP);
-    monitor = g_file_monitor_file(file, G_FILE_MONITOR_NONE, NULL, NULL);
-    g_object_unref(file);
+    monitor = _file_monitor_new(CONF_DHCP);
     if (monitor) {
         priv->dhcp_monitor_id =
-            g_signal_connect(monitor, "changed", G_CALLBACK(hostname_file_changed_cb), self);
+            g_signal_connect(monitor, "changed", G_CALLBACK(_file_monitors_file_changed_cb), self);
         priv->dhcp_monitor = monitor;
     }
 #endif
 
-    _set_hostname_read(self);
+    _set_hostname_read_file(self);
 }
 
 /*****************************************************************************/
@@ -591,7 +552,7 @@ constructed(GObject *object)
     }
 
     if (!priv->hostnamed_proxy)
-        setup_hostname_file_monitors(self);
+        _file_monitors_setup(self);
 
     G_OBJECT_CLASS(nm_hostname_manager_parent_class)->constructed(object);
 }
@@ -609,21 +570,7 @@ dispose(GObject *object)
         g_clear_object(&priv->hostnamed_proxy);
     }
 
-    if (priv->monitor) {
-        if (priv->monitor_id)
-            g_signal_handler_disconnect(priv->monitor, priv->monitor_id);
-
-        g_file_monitor_cancel(priv->monitor);
-        g_clear_object(&priv->monitor);
-    }
-
-    if (priv->dhcp_monitor) {
-        if (priv->dhcp_monitor_id)
-            g_signal_handler_disconnect(priv->dhcp_monitor, priv->dhcp_monitor_id);
-
-        g_file_monitor_cancel(priv->dhcp_monitor);
-        g_clear_object(&priv->dhcp_monitor);
-    }
+    _file_monitors_clear(self);
 
     nm_clear_g_free(&priv->current_hostname);
 
diff --git a/src/core/nm-hostname-manager.h b/src/core/nm-hostname-manager.h
index 0fa13771..ff109e7e 100644
--- a/src/core/nm-hostname-manager.h
+++ b/src/core/nm-hostname-manager.h
@@ -36,8 +36,6 @@ NMHostnameManager *nm_hostname_manager_get(void);
 
 const char *nm_hostname_manager_get_hostname(NMHostnameManager *self);
 
-char *nm_hostname_manager_read_hostname(NMHostnameManager *self);
-
 gboolean nm_hostname_manager_write_hostname(NMHostnameManager *self, const char *hostname);
 
 void nm_hostname_manager_set_transient_hostname(NMHostnameManager *            self,
@@ -47,6 +45,4 @@ void nm_hostname_manager_set_transient_hostname(NMHostnameManager *            s
 
 gboolean nm_hostname_manager_get_transient_hostname(NMHostnameManager *self, char **hostname);
 
-gboolean nm_hostname_manager_validate_hostname(const char *hostname);
-
 #endif /* __NM_HOSTNAME_MANAGER_H__ */
diff --git a/src/core/nm-iface-helper.c b/src/core/nm-iface-helper.c
index 296bf091..83ae1b54 100644
--- a/src/core/nm-iface-helper.c
+++ b/src/core/nm-iface-helper.c
@@ -5,7 +5,6 @@
 
 #include "src/core/nm-default-daemon.h"
 
-#include <glib-unix.h>
 #include <getopt.h>
 #include <locale.h>
 #include <stdlib.h>
@@ -31,7 +30,7 @@
 #include "libnm-systemd-core/nm-sd.h"
 
 #if !defined(NM_DIST_VERSION)
-    #define NM_DIST_VERSION VERSION
+#define NM_DIST_VERSION VERSION
 #endif
 
 #define NMIH_PID_FILE_FMT NMRUNDIR "/nm-iface-helper-%d.pid"
@@ -93,24 +92,29 @@ static struct {
 /*****************************************************************************/
 
 static void
-dhcp4_state_changed(NMDhcpClient *client,
-                    NMDhcpState   state,
-                    NMIP4Config * ip4_config,
-                    GHashTable *  options,
-                    gpointer      user_data)
+_dhcp_client_notify_cb(NMDhcpClient *                client,
+                       const NMDhcpClientNotifyData *notify_data,
+                       gpointer                      user_data)
 {
     static NMIP4Config *last_config = NULL;
     NMIP4Config *       existing;
     gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
     gs_free_error GError *error                          = NULL;
+    NMIP4Config *         ip4_config;
 
-    g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
+    if (!notify_data || notify_data->notify_type != NM_DHCP_CLIENT_NOTIFY_TYPE_STATE_CHANGED)
+        g_return_if_reached();
 
-    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state);
+    nm_assert(!notify_data->state_changed.ip_config
+              || NM_IS_IP4_CONFIG(notify_data->state_changed.ip_config));
 
-    switch (state) {
+    _LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", (int) notify_data->state_changed.dhcp_state);
+
+    switch (notify_data->state_changed.dhcp_state) {
     case NM_DHCP_STATE_BOUND:
     case NM_DHCP_STATE_EXTENDED:
+        ip4_config = NM_IP4_CONFIG(notify_data->state_changed.ip_config);
+
         g_assert(ip4_config);
         g_assert(nm_ip4_config_get_ifindex(ip4_config) == gl.ifindex);
 
@@ -177,41 +181,33 @@ ndisc_config_changed(NMNDisc *          ndisc,
     }
 
     if (changed & NM_NDISC_CONFIG_ADDRESSES) {
-        guint8  plen;
         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 = 0;
-        if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
-            ifa_flags |= IFA_F_NOPREFIXROUTE;
-            if (NM_IN_SET(global_opt.tempaddr,
-                          NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
-                          NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
-                ifa_flags |= IFA_F_MANAGETEMPADDR;
-            plen = 64;
-        } else
-            plen = 128;
+        ifa_flags = IFA_F_NOPREFIXROUTE;
+        if (NM_IN_SET(global_opt.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(ndisc_config,
                                             rdata->addresses,
                                             rdata->addresses_n,
-                                            plen,
+                                            64,
                                             ifa_flags);
     }
 
     if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) {
-        nm_ip6_config_reset_routes_ndisc(
-            ndisc_config,
-            rdata->gateways,
-            rdata->gateways_n,
-            rdata->routes,
-            rdata->routes_n,
-            RT_TABLE_MAIN,
-            global_opt.priority_v6,
-            nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
+        nm_ip6_config_reset_routes_ndisc(ndisc_config,
+                                         rdata->gateways,
+                                         rdata->gateways_n,
+                                         rdata->routes,
+                                         rdata->routes_n,
+                                         RT_TABLE_MAIN,
+                                         global_opt.priority_v6);
     }
 
     if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
@@ -685,8 +681,8 @@ main(int argc, char *argv[])
             g_error("failure to start DHCP: %s", error->message);
 
         g_signal_connect(dhcp4_client,
-                         NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
-                         G_CALLBACK(dhcp4_state_changed),
+                         NM_DHCP_CLIENT_NOTIFY,
+                         G_CALLBACK(_dhcp_client_notify_cb),
                          NULL);
     }
 
@@ -698,7 +694,9 @@ main(int argc, char *argv[])
         guint32           default_ra_timeout;
         int               max_addresses;
 
-        nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, gl.ifindex, TRUE);
+        nm_platform_link_set_inet6_addr_gen_mode(NM_PLATFORM_GET,
+                                                 gl.ifindex,
+                                                 NM_IN6_ADDR_GEN_MODE_NONE);
 
         if (global_opt.stable_id
             && (global_opt.stable_id[0] >= '0' && global_opt.stable_id[0] <= '9')
@@ -710,12 +708,12 @@ main(int argc, char *argv[])
             stable_id   = &global_opt.stable_id[2];
         }
 
-        nm_lndp_ndisc_get_sysctl(NM_PLATFORM_GET,
-                                 global_opt.ifname,
-                                 &max_addresses,
-                                 &router_solicitations,
-                                 &router_solicitation_interval,
-                                 &default_ra_timeout);
+        nm_ndisc_get_sysctl(NM_PLATFORM_GET,
+                            global_opt.ifname,
+                            &max_addresses,
+                            &router_solicitations,
+                            &router_solicitation_interval,
+                            &default_ra_timeout);
 
         ndisc = nm_lndp_ndisc_new(NM_PLATFORM_GET,
                                   gl.ifindex,
diff --git a/src/core/nm-ip4-config.c b/src/core/nm-ip4-config.c
index 52a8faa7..90398dcc 100644
--- a/src/core/nm-ip4-config.c
+++ b/src/core/nm-ip4-config.c
@@ -287,27 +287,28 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMIP4Config,
                              PROP_DNS_PRIORITY, );
 
 typedef struct {
-    bool                     metered : 1;
-    bool                     never_default : 1;
-    guint32                  mtu;
-    int                      ifindex;
-    NMIPConfigSource         mtu_source;
-    int                      dns_priority;
-    NMSettingConnectionMdns  mdns;
-    NMSettingConnectionLlmnr llmnr;
-    GArray *                 nameservers;
-    GPtrArray *              domains;
-    GPtrArray *              searches;
-    GPtrArray *              dns_options;
-    GArray *                 nis;
-    char *                   nis_domain;
-    GArray *                 wins;
-    GVariant *               address_data_variant;
-    GVariant *               addresses_variant;
-    GVariant *               route_data_variant;
-    GVariant *               routes_variant;
-    NMDedupMultiIndex *      multi_idx;
-    const NMPObject *        best_default_route;
+    bool                          metered : 1;
+    bool                          never_default : 1;
+    guint32                       mtu;
+    int                           ifindex;
+    NMIPConfigSource              mtu_source;
+    int                           dns_priority;
+    NMSettingConnectionMdns       mdns;
+    NMSettingConnectionLlmnr      llmnr;
+    NMSettingConnectionDnsOverTls dns_over_tls;
+    GArray *                      nameservers;
+    GPtrArray *                   domains;
+    GPtrArray *                   searches;
+    GPtrArray *                   dns_options;
+    GArray *                      nis;
+    char *                        nis_domain;
+    GArray *                      wins;
+    GVariant *                    address_data_variant;
+    GVariant *                    addresses_variant;
+    GVariant *                    route_data_variant;
+    GVariant *                    routes_variant;
+    NMDedupMultiIndex *           multi_idx;
+    const NMPObject *             best_default_route;
     union {
         NMIPConfigDedupMultiIdxType idx_ip4_addresses_;
         NMDedupMultiIdxType         idx_ip4_addresses;
@@ -408,11 +409,11 @@ _nm_ip_config_best_default_route_find_better(const NMPObject *obj_cur, const NMP
                                NMP_OBJECT_TYPE_IP4_ROUTE,
                                NMP_OBJECT_TYPE_IP6_ROUTE))
               || NMP_OBJECT_GET_TYPE(obj_cur) == NMP_OBJECT_GET_TYPE(obj_cmp));
-    nm_assert(!obj_cur || nmp_object_ip_route_is_best_defaut_route(obj_cur));
+    nm_assert(!obj_cur || nmp_object_ip_route_is_best_default_route(obj_cur));
 
     /* assumes that @obj_cur is already the best default route (or NULL). It checks whether
      * @obj_cmp is also a default route and returns the best of both. */
-    if (obj_cmp && nmp_object_ip_route_is_best_defaut_route(obj_cmp)) {
+    if (obj_cmp && nmp_object_ip_route_is_best_default_route(obj_cmp)) {
         guint32 metric_cur, metric_cmp;
 
         if (!obj_cur)
@@ -747,12 +748,13 @@ nm_ip4_config_commit(const NMIP4Config *    self,
 }
 
 void
-nm_ip4_config_merge_setting(NMIP4Config *            self,
-                            NMSettingIPConfig *      setting,
-                            NMSettingConnectionMdns  mdns,
-                            NMSettingConnectionLlmnr llmnr,
-                            guint32                  route_table,
-                            guint32                  route_metric)
+nm_ip4_config_merge_setting(NMIP4Config *                 self,
+                            NMSettingIPConfig *           setting,
+                            NMSettingConnectionMdns       mdns,
+                            NMSettingConnectionLlmnr      llmnr,
+                            NMSettingConnectionDnsOverTls dns_over_tls,
+                            guint32                       route_table,
+                            guint32                       route_metric)
 {
     guint       naddresses, nroutes, nnameservers, nsearches;
     int         i, priority;
@@ -868,6 +870,7 @@ nm_ip4_config_merge_setting(NMIP4Config *            self,
 
     nm_ip4_config_mdns_set(self, mdns);
     nm_ip4_config_llmnr_set(self, llmnr);
+    nm_ip4_config_dns_over_tls_set(self, dns_over_tls);
 
     nm_ip4_config_set_never_default(self, nm_setting_ip_config_get_never_default(setting));
 
@@ -1112,6 +1115,10 @@ nm_ip4_config_merge(NMIP4Config *        dst,
     /* LLMNR */
     nm_ip4_config_llmnr_set(dst,
                             NM_MAX(nm_ip4_config_llmnr_get(src), nm_ip4_config_llmnr_get(dst)));
+    /* dns_over_tls */
+    nm_ip4_config_dns_over_tls_set(
+        dst,
+        NM_MAX(nm_ip4_config_dns_over_tls_get(src), nm_ip4_config_dns_over_tls_get(dst)));
 
     g_object_thaw_notify(G_OBJECT(dst));
 }
@@ -1357,6 +1364,10 @@ nm_ip4_config_subtract(NMIP4Config *      dst,
     if (nm_ip4_config_llmnr_get(src) == nm_ip4_config_llmnr_get(dst))
         nm_ip4_config_llmnr_set(dst, NM_SETTING_CONNECTION_LLMNR_DEFAULT);
 
+    /* dns_over_tls */
+    if (nm_ip4_config_dns_over_tls_get(src) == nm_ip4_config_dns_over_tls_get(dst))
+        nm_ip4_config_dns_over_tls_set(dst, NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
+
     g_object_thaw_notify(G_OBJECT(dst));
 }
 
@@ -1466,6 +1477,7 @@ skip_routes:
     /* ignore WINS */
     /* ignore mdns */
     /* ignore LLMNR */
+    /* ignore dns_over_tls */
 
     if (update_dst)
         g_object_thaw_notify(G_OBJECT(dst));
@@ -1777,6 +1789,11 @@ nm_ip4_config_replace(NMIP4Config *dst, const NMIP4Config *src, gboolean *releva
         has_relevant_changes = TRUE;
     }
 
+    if (src_priv->dns_over_tls != dst_priv->dns_over_tls) {
+        dst_priv->dns_over_tls = src_priv->dns_over_tls;
+        has_relevant_changes   = TRUE;
+    }
+
     /* DNS priority */
     if (src_priv->dns_priority != dst_priv->dns_priority) {
         nm_ip4_config_set_dns_priority(dst, src_priv->dns_priority);
@@ -2333,7 +2350,7 @@ _nm_ip_config_check_and_add_domain(GPtrArray *array, const char *domain)
     if (domain[len - 1] == '.')
         domain = copy = g_strndup(domain, len - 1);
 
-    if (nm_utils_strv_find_first((char **) array->pdata, array->len, domain) >= 0) {
+    if (nm_strv_ptrarray_find_first(array, domain) >= 0) {
         g_free(copy);
         return FALSE;
     }
@@ -2521,6 +2538,18 @@ nm_ip4_config_llmnr_set(NMIP4Config *self, NMSettingConnectionLlmnr llmnr)
     NM_IP4_CONFIG_GET_PRIVATE(self)->llmnr = llmnr;
 }
 
+NMSettingConnectionDnsOverTls
+nm_ip4_config_dns_over_tls_get(const NMIP4Config *self)
+{
+    return NM_IP4_CONFIG_GET_PRIVATE(self)->dns_over_tls;
+}
+
+void
+nm_ip4_config_dns_over_tls_set(NMIP4Config *self, NMSettingConnectionDnsOverTls dns_over_tls)
+{
+    NM_IP4_CONFIG_GET_PRIVATE(self)->dns_over_tls = dns_over_tls;
+}
+
 /*****************************************************************************/
 
 NMIPConfigFlags
@@ -2901,6 +2930,10 @@ nm_ip4_config_hash(const NMIP4Config *self, GChecksum *sum, gboolean dns_only)
     if (val != NM_SETTING_CONNECTION_LLMNR_DEFAULT)
         g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
 
+    val = nm_ip4_config_dns_over_tls_get(self);
+    if (val != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT)
+        g_checksum_update(sum, (const guint8 *) &val, sizeof(val));
+
     /* FIXME(ip-config-checksum): the DNS priority should be considered relevant
      * and added into the checksum as well, but this can't be done right now
      * because in the DNS manager we rely on the fact that an empty
@@ -3095,14 +3128,15 @@ nm_ip4_config_init(NMIP4Config *self)
     nm_ip_config_dedup_multi_idx_type_init((NMIPConfigDedupMultiIdxType *) &priv->idx_ip4_routes,
                                            NMP_OBJECT_TYPE_IP4_ROUTE);
 
-    priv->mdns        = NM_SETTING_CONNECTION_MDNS_DEFAULT;
-    priv->llmnr       = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
-    priv->nameservers = g_array_new(FALSE, FALSE, sizeof(guint32));
-    priv->domains     = g_ptr_array_new_with_free_func(g_free);
-    priv->searches    = g_ptr_array_new_with_free_func(g_free);
-    priv->dns_options = g_ptr_array_new_with_free_func(g_free);
-    priv->nis         = g_array_new(FALSE, TRUE, sizeof(guint32));
-    priv->wins        = g_array_new(FALSE, TRUE, sizeof(guint32));
+    priv->mdns         = NM_SETTING_CONNECTION_MDNS_DEFAULT;
+    priv->llmnr        = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
+    priv->dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
+    priv->nameservers  = g_array_new(FALSE, FALSE, sizeof(guint32));
+    priv->domains      = g_ptr_array_new_with_free_func(g_free);
+    priv->searches     = g_ptr_array_new_with_free_func(g_free);
+    priv->dns_options  = g_ptr_array_new_with_free_func(g_free);
+    priv->nis          = g_array_new(FALSE, TRUE, sizeof(guint32));
+    priv->wins         = g_array_new(FALSE, TRUE, sizeof(guint32));
 }
 
 NMIP4Config *
diff --git a/src/core/nm-ip4-config.h b/src/core/nm-ip4-config.h
index fdaa6d46..2eb193b2 100644
--- a/src/core/nm-ip4-config.h
+++ b/src/core/nm-ip4-config.h
@@ -124,12 +124,13 @@ gboolean nm_ip4_config_commit(const NMIP4Config *    self,
                               NMPlatform *           platform,
                               NMIPRouteTableSyncMode route_table_sync);
 
-void       nm_ip4_config_merge_setting(NMIP4Config *            self,
-                                       NMSettingIPConfig *      setting,
-                                       NMSettingConnectionMdns  mdns,
-                                       NMSettingConnectionLlmnr llmnr,
-                                       guint32                  route_table,
-                                       guint32                  route_metric);
+void       nm_ip4_config_merge_setting(NMIP4Config *                 self,
+                                       NMSettingIPConfig *           setting,
+                                       NMSettingConnectionMdns       mdns,
+                                       NMSettingConnectionLlmnr      llmnr,
+                                       NMSettingConnectionDnsOverTls dns_over_tls,
+                                       guint32                       route_table,
+                                       guint32                       route_metric);
 NMSetting *nm_ip4_config_create_setting(const NMIP4Config *self);
 
 void         nm_ip4_config_merge(NMIP4Config *        dst,
@@ -161,6 +162,8 @@ NMSettingConnectionMdns  nm_ip4_config_mdns_get(const NMIP4Config *self);
 void                     nm_ip4_config_mdns_set(NMIP4Config *self, NMSettingConnectionMdns mdns);
 NMSettingConnectionLlmnr nm_ip4_config_llmnr_get(const NMIP4Config *self);
 void                     nm_ip4_config_llmnr_set(NMIP4Config *self, NMSettingConnectionLlmnr llmnr);
+NMSettingConnectionDnsOverTls nm_ip4_config_dns_over_tls_get(const NMIP4Config *self);
+void nm_ip4_config_dns_over_tls_set(NMIP4Config *self, NMSettingConnectionDnsOverTls dns_over_tls);
 
 void nm_ip4_config_set_config_flags(NMIP4Config *self, NMIPConfigFlags flags, NMIPConfigFlags mask);
 NMIPConfigFlags nm_ip4_config_get_config_flags(const NMIP4Config *self);
@@ -275,7 +278,7 @@ NM_IS_IP_CONFIG_ADDR_FAMILY(gconstpointer config, int addr_family)
 }
 
 #if _NM_CC_SUPPORT_GENERIC
-    /* _NM_IS_IP_CONFIG() is a bit unusual. If _Generic() is supported,
+/* _NM_IS_IP_CONFIG() is a bit unusual. If _Generic() is supported,
  * it checks whether @config is either NM_IS_IP4_CONFIG() or NM_IS_IP6_CONFIG(),
  * depending on the pointer type of @config.
  *
@@ -286,10 +289,10 @@ NM_IS_IP_CONFIG_ADDR_FAMILY(gconstpointer config, int addr_family)
  *    NMIP4Config *ptr = nm_ip4_config_new(...);
  *    g_assert (_NM_IS_IP_CONFIG (ptr, ptr));
  */
-    #define _NM_IS_IP_CONFIG(typeexpr, config)    \
-        ({                                        \
-            const void *const _config = (config); \
-            _Generic ((typeexpr), \
+#define _NM_IS_IP_CONFIG(typeexpr, config)    \
+    ({                                        \
+        const void *const _config = (config); \
+        _Generic ((typeexpr), \
                   const void        *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \
                   const void        *     : (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \
                         void        *const: (NM_IS_IP4_CONFIG (_config) || NM_IS_IP6_CONFIG (_config)), \
@@ -306,9 +309,9 @@ NM_IS_IP_CONFIG_ADDR_FAMILY(gconstpointer config, int addr_family)
                   const NMIP6Config *     : (NM_IS_IP6_CONFIG (_config)), \
                         NMIP6Config *const: (NM_IS_IP6_CONFIG (_config)), \
                         NMIP6Config *     : (NM_IS_IP6_CONFIG (_config)));               \
-        })
+    })
 #else
-    #define _NM_IS_IP_CONFIG(typeexpr, config) NM_IS_IP_CONFIG(config)
+#define _NM_IS_IP_CONFIG(typeexpr, config) NM_IS_IP_CONFIG(config)
 #endif
 
 #define NM_IP_CONFIG_CAST(config)                                                    \
diff --git a/src/core/nm-ip6-config.c b/src/core/nm-ip6-config.c
index 65e84737..5ec68bd2 100644
--- a/src/core/nm-ip6-config.c
+++ b/src/core/nm-ip6-config.c
@@ -1678,8 +1678,7 @@ nm_ip6_config_reset_routes_ndisc(NMIP6Config *         self,
                                  const NMNDiscRoute *  routes,
                                  guint                 routes_n,
                                  guint32               route_table,
-                                 guint32               route_metric,
-                                 gboolean              kernel_support_rta_pref)
+                                 guint32               route_metric)
 {
     NMIP6ConfigPrivate *priv;
     guint               i;
@@ -1735,7 +1734,6 @@ nm_ip6_config_reset_routes_ndisc(NMIP6Config *         self,
             .table_coerced = nm_platform_route_table_coerce(route_table),
             .metric        = route_metric,
         };
-        const NMIcmpv6RouterPref first_pref = gateways[0].preference;
 
         for (i = 0; i < gateways_n; i++) {
             r.gateway = gateways[i].address;
@@ -1753,13 +1751,6 @@ nm_ip6_config_reset_routes_ndisc(NMIP6Config *         self,
                 changed = TRUE;
             new_best_default_route =
                 _nm_ip_config_best_default_route_find_better(new_best_default_route, obj_new);
-
-            if (first_pref != gateways[i].preference && !kernel_support_rta_pref) {
-                /* We are unable to configure a router preference. Hence, we skip all gateways
-                 * with a different preference from the first gateway. Note, that the gateways
-                 * are sorted in order of highest to lowest preference. */
-                break;
-            }
         }
     }
 
diff --git a/src/core/nm-ip6-config.h b/src/core/nm-ip6-config.h
index 8694ab0c..a54040fc 100644
--- a/src/core/nm-ip6-config.h
+++ b/src/core/nm-ip6-config.h
@@ -198,8 +198,7 @@ void nm_ip6_config_reset_routes_ndisc(NMIP6Config *                 self,
                                       const struct _NMNDiscRoute *  routes,
                                       guint                         routes_n,
                                       guint32                       route_table,
-                                      guint32                       route_metric,
-                                      gboolean                      kernel_support_rta_pref);
+                                      guint32                       route_metric);
 
 void nm_ip6_config_update_routes_metric(NMIP6Config *self, gint64 metric);
 
diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c
index f2ec040b..033c8942 100644
--- a/src/core/nm-l3-config-data.c
+++ b/src/core/nm-l3-config-data.c
@@ -131,6 +131,14 @@ struct _NML3ConfigData {
     guint32 ndisc_reachable_time_msec_val;
     guint32 ndisc_retrans_timer_msec_val;
 
+    union {
+        struct {
+            NMOptionBool never_default_6;
+            NMOptionBool never_default_4;
+        };
+        NMOptionBool never_default_x[2];
+    };
+
     NMTernary metered : 3;
 
     NMTernary proxy_browser_only : 3;
@@ -323,7 +331,7 @@ _strv_ptrarray_merge(GPtrArray **p_dst, const GPtrArray *src)
         const char *s = src->pdata[i];
 
         if (dst_initial_len > 0
-            && nm_utils_strv_find_first((char **) ((*p_dst)->pdata), dst_initial_len, s) >= 0)
+            && nm_strv_find_first((const char *const *) ((*p_dst)->pdata), dst_initial_len, s) >= 0)
             continue;
 
         g_ptr_array_add(*p_dst, g_strdup(s));
@@ -439,27 +447,23 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                 const char *s_prefix = "ndisc: ";
 
                 if (self->ndisc_hop_limit_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%shop-limit=%d",
-                                           s_prefix,
-                                           self->ndisc_hop_limit_val);
+                    nm_strbuf_append(&p, &l, "%shop-limit=%d", s_prefix, self->ndisc_hop_limit_val);
                     s_prefix = ", ";
                 }
                 if (self->ndisc_reachable_time_msec_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%sreachable-time-msec=%u",
-                                           s_prefix,
-                                           self->ndisc_reachable_time_msec_val);
+                    nm_strbuf_append(&p,
+                                     &l,
+                                     "%sreachable-time-msec=%u",
+                                     s_prefix,
+                                     self->ndisc_reachable_time_msec_val);
                     s_prefix = ", ";
                 }
                 if (self->ndisc_retrans_timer_msec_set) {
-                    nm_utils_strbuf_append(&p,
-                                           &l,
-                                           "%sretrans-timer-msec=%u",
-                                           s_prefix,
-                                           self->ndisc_retrans_timer_msec_val);
+                    nm_strbuf_append(&p,
+                                     &l,
+                                     "%sretrans-timer-msec=%u",
+                                     s_prefix,
+                                     self->ndisc_retrans_timer_msec_val);
                     s_prefix = ", ";
                 }
                 _L("%s", sbuf);
@@ -541,6 +545,9 @@ nm_l3_config_data_log(const NML3ConfigData *self,
                    options[i].value_str);
             }
         }
+
+        if (self->never_default_x[IS_IPv4] != NM_OPTION_BOOL_DEFAULT)
+            _L("never-default: %s", self->never_default_x[IS_IPv4] ? "yes" : "no");
     }
 
     if (self->mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT) {
@@ -643,12 +650,14 @@ _idx_type_init(DedupMultiIdxType *idx_type, NMPObjectType obj_type)
 }
 
 NML3ConfigData *
-nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex)
+nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSource source)
 {
     NML3ConfigData *self;
 
     nm_assert(multi_idx);
     nm_assert(ifindex > 0);
+    nm_assert(source == NM_IP_CONFIG_SOURCE_UNKNOWN
+              || (source >= NM_IP_CONFIG_SOURCE_KERNEL && source <= NM_IP_CONFIG_SOURCE_USER));
 
     self  = g_slice_new(NML3ConfigData);
     *self = (NML3ConfigData){
@@ -663,7 +672,9 @@ nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex)
         .proxy_method                  = NM_PROXY_CONFIG_METHOD_UNKNOWN,
         .route_table_sync_4            = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
         .route_table_sync_6            = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE,
-        .source                        = NM_IP_CONFIG_SOURCE_UNKNOWN,
+        .never_default_6               = NM_OPTION_BOOL_DEFAULT,
+        .never_default_4               = NM_OPTION_BOOL_DEFAULT,
+        .source                        = source,
         .ip6_privacy                   = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
         .ndisc_hop_limit_set           = FALSE,
         .ndisc_reachable_time_msec_set = FALSE,
@@ -691,17 +702,21 @@ nm_l3_config_data_ref(const NML3ConfigData *self)
 const NML3ConfigData *
 nm_l3_config_data_ref_and_seal(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    ((NML3ConfigData *) self)->is_sealed = TRUE;
-    ((NML3ConfigData *) self)->ref_count++;
+    if (self) {
+        nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+        ((NML3ConfigData *) self)->is_sealed = TRUE;
+        ((NML3ConfigData *) self)->ref_count++;
+    }
     return self;
 }
 
 const NML3ConfigData *
 nm_l3_config_data_seal(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    ((NML3ConfigData *) self)->is_sealed = TRUE;
+    if (self) {
+        nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+        ((NML3ConfigData *) self)->is_sealed = TRUE;
+    }
     return self;
 }
 
@@ -871,6 +886,26 @@ nm_l3_config_data_lookup_obj(const NML3ConfigData *self, const NMPObject *obj)
     return nm_dedup_multi_index_lookup_obj(self->multi_idx, idx, obj);
 }
 
+const NMPlatformIP4Address *
+nm_l3_config_data_lookup_address_4(const NML3ConfigData *self,
+                                   in_addr_t             addr,
+                                   guint8                plen,
+                                   in_addr_t             peer_addr)
+{
+    const NMDedupMultiEntry *head;
+    NMPObject                obj_stack;
+
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    nmp_object_stackinit_id_ip4_address(&obj_stack, self->ifindex, addr, plen, peer_addr);
+
+    head = nm_l3_config_data_lookup_obj(self, &obj_stack);
+    if (!head)
+        return NULL;
+
+    return NMP_OBJECT_CAST_IP4_ADDRESS(head->obj);
+}
+
 const NMPlatformIP6Address *
 nm_l3_config_data_lookup_address_6(const NML3ConfigData *self, const struct in6_addr *addr)
 {
@@ -879,7 +914,7 @@ nm_l3_config_data_lookup_address_6(const NML3ConfigData *self, const struct in6_
 
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
-    /* this works only, because the primary key for a Ipv6 address is the
+    /* this works only, because the primary key for a IPv6 address is the
      * ifindex and the "struct in6_addr". */
     nmp_object_stackinit_id_ip6_address(&obj_stack, self->ifindex, addr);
 
@@ -1104,6 +1139,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
                     obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source;
                     modified                                 = TRUE;
                 }
+
+                /* OR assume_config_once flag */
+                if (obj_new->ip_address.a_assume_config_once
+                    && !obj_old->ip_address.a_assume_config_once) {
+                    obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+                    obj_new_stackinit.ip_address.a_assume_config_once = TRUE;
+                    modified                                          = TRUE;
+                }
                 break;
             case NMP_OBJECT_TYPE_IP4_ROUTE:
             case NMP_OBJECT_TYPE_IP6_ROUTE:
@@ -1113,6 +1156,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex *     multi_idx,
                     obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source;
                     modified                             = TRUE;
                 }
+
+                /* OR assume_config_once flag */
+                if (obj_new->ip_route.r_assume_config_once
+                    && !obj_old->ip_route.r_assume_config_once) {
+                    obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new);
+                    obj_new_stackinit.ip_route.r_assume_config_once = TRUE;
+                    modified                                        = TRUE;
+                }
                 break;
             default:
                 nm_assert_not_reached();
@@ -1168,11 +1219,11 @@ _l3_config_best_default_route_find_better(const NMPObject *obj_cur, const NMPObj
                                NMP_OBJECT_TYPE_IP4_ROUTE,
                                NMP_OBJECT_TYPE_IP6_ROUTE))
               || NMP_OBJECT_GET_TYPE(obj_cur) == NMP_OBJECT_GET_TYPE(obj_cmp));
-    nm_assert(!obj_cur || nmp_object_ip_route_is_best_defaut_route(obj_cur));
+    nm_assert(!obj_cur || nmp_object_ip_route_is_best_default_route(obj_cur));
 
     /* assumes that @obj_cur is already the best default route (or NULL). It checks whether
      * @obj_cmp is also a default route and returns the best of both. */
-    if (obj_cmp && nmp_object_ip_route_is_best_defaut_route(obj_cmp)) {
+    if (obj_cmp && nmp_object_ip_route_is_best_default_route(obj_cmp)) {
         guint32 metric_cur, metric_cmp;
 
         if (!obj_cur)
@@ -1297,9 +1348,16 @@ const NMPObject *
 nm_l3_config_data_get_best_default_route(const NML3ConfigData *self, int addr_family)
 {
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
-    nm_assert_addr_family(addr_family);
 
-    return self->best_default_route_x[NM_IS_IPv4(addr_family)];
+    switch (addr_family) {
+    case AF_INET:
+        return self->best_default_route_4;
+    case AF_INET6:
+        return self->best_default_route_6;
+    case AF_UNSPEC:
+        return self->best_default_route_4 ?: self->best_default_route_6;
+    }
+    return nm_assert_unreachable_val(NULL);
 }
 
 /*****************************************************************************/
@@ -1336,10 +1394,15 @@ _check_and_add_domain(GPtrArray **p_arr, const char *domain)
 gconstpointer
 nm_l3_config_data_get_nameservers(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return _garray_inaddr_get(self->nameservers_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1372,7 +1435,13 @@ nm_l3_config_data_clear_nameservers(NML3ConfigData *self, int addr_family)
 const in_addr_t *
 nm_l3_config_data_get_wins(const NML3ConfigData *self, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(out_len);
+
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
 
     return _garray_inaddr_get(self->wins, out_len);
 }
@@ -1420,10 +1489,15 @@ nm_l3_config_data_set_nis_domain(NML3ConfigData *self, const char *nis_domain)
 const char *const *
 nm_l3_config_data_get_domains(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->domains_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1439,10 +1513,15 @@ nm_l3_config_data_add_domain(NML3ConfigData *self, int addr_family, const char *
 const char *const *
 nm_l3_config_data_get_searches(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->searches_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1492,10 +1571,15 @@ nm_l3_config_data_add_dns_option(NML3ConfigData *self, int addr_family, const ch
 const char *const *
 nm_l3_config_data_get_dns_options(const NML3ConfigData *self, int addr_family, guint *out_len)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
     nm_assert_addr_family(addr_family);
     nm_assert(out_len);
 
+    if (!self) {
+        *out_len = 0;
+        return NULL;
+    }
+
     return nm_strv_ptrarray_get_unsafe(self->dns_options_x[NM_IS_IPv4(addr_family)], out_len);
 }
 
@@ -1622,6 +1706,29 @@ nm_l3_config_data_set_route_table_sync(NML3ConfigData *       self,
 }
 
 NMTernary
+nm_l3_config_data_get_never_default(const NML3ConfigData *self, int addr_family)
+{
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    return NM_TERNARY_FROM_OPTION_BOOL(self->never_default_x[NM_IS_IPv4(addr_family)]);
+}
+
+gboolean
+nm_l3_config_data_set_never_default(NML3ConfigData *self, int addr_family, NMTernary never_default)
+{
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMOptionBool v       = NM_TERNARY_TO_OPTION_BOOL(never_default);
+
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+
+    if (self->never_default_x[IS_IPv4] == v)
+        return FALSE;
+
+    self->never_default_x[IS_IPv4] = v;
+    return TRUE;
+}
+
+NMTernary
 nm_l3_config_data_get_metered(const NML3ConfigData *self)
 {
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
@@ -1682,22 +1789,13 @@ nm_l3_config_data_set_ip6_mtu(NML3ConfigData *self, guint32 ip6_mtu)
     return TRUE;
 }
 
-gboolean
-nm_l3_config_data_set_source(NML3ConfigData *self, NMIPConfigSource source)
-{
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
-
-    if (self->source == source)
-        return FALSE;
-
-    self->source = source;
-    return TRUE;
-}
-
 NMSettingIP6ConfigPrivacy
 nm_l3_config_data_get_ip6_privacy(const NML3ConfigData *self)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(!self || _NM_IS_L3_CONFIG_DATA(self, TRUE));
+
+    if (!self)
+        return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
 
     return self->ip6_privacy;
 }
@@ -1799,7 +1897,7 @@ nm_l3_config_data_set_proxy_pac_script(NML3ConfigData *self, const char *value)
 gboolean
 nm_l3_config_data_get_ndisc_hop_limit(const NML3ConfigData *self, int *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_hop_limit_set) {
         NM_SET_OUT(out_val, 0);
@@ -1822,7 +1920,7 @@ nm_l3_config_data_set_ndisc_hop_limit(NML3ConfigData *self, int val)
 gboolean
 nm_l3_config_data_get_ndisc_reachable_time_msec(const NML3ConfigData *self, guint32 *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_reachable_time_msec_set) {
         NM_SET_OUT(out_val, 0);
@@ -1845,7 +1943,7 @@ nm_l3_config_data_set_ndisc_reachable_time_msec(NML3ConfigData *self, guint32 va
 gboolean
 nm_l3_config_data_get_ndisc_retrans_timer_msec(const NML3ConfigData *self, guint32 *out_val)
 {
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     if (!self->ndisc_retrans_timer_msec_set) {
         NM_SET_OUT(out_val, 0);
@@ -1922,14 +2020,26 @@ nm_l3_config_data_set_dhcp_lease_from_options(NML3ConfigData *self,
 /*****************************************************************************/
 
 static int
-_dedup_multi_index_cmp(const NML3ConfigData *a, const NML3ConfigData *b, NMPObjectType obj_type)
+_dedup_multi_index_cmp(const NML3ConfigData *a,
+                       const NML3ConfigData *b,
+                       NMPObjectType         obj_type,
+                       gboolean              ignore_ifindex)
 {
     const NMDedupMultiHeadEntry *h_a = nm_l3_config_data_lookup_objs(a, obj_type);
     const NMDedupMultiHeadEntry *h_b = nm_l3_config_data_lookup_objs(b, obj_type);
     NMDedupMultiIter             iter_a;
     NMDedupMultiIter             iter_b;
 
+    /* We handle ignore_index via NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX flag, which is
+     * only implemented for certain types. */
+    nm_assert(NM_IN_SET(obj_type,
+                        NMP_OBJECT_TYPE_IP4_ADDRESS,
+                        NMP_OBJECT_TYPE_IP6_ADDRESS,
+                        NMP_OBJECT_TYPE_IP4_ROUTE,
+                        NMP_OBJECT_TYPE_IP6_ROUTE));
+
     NM_CMP_SELF(h_a, h_b);
+
     NM_CMP_DIRECT(h_a->len, h_b->len);
 
     nm_assert(h_a->len > 0);
@@ -1952,7 +2062,10 @@ _dedup_multi_index_cmp(const NML3ConfigData *a, const NML3ConfigData *b, NMPObje
         have_b = nm_platform_dedup_multi_iter_next_obj(&iter_b, &obj_b, obj_type);
         nm_assert(have_b);
 
-        NM_CMP_RETURN(nmp_object_cmp(obj_a, obj_b));
+        NM_CMP_RETURN(nmp_object_cmp_full(obj_a,
+                                          obj_b,
+                                          ignore_ifindex ? NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX
+                                                         : NMP_OBJECT_CMP_FLAGS_NONE));
     }
 }
 
@@ -1961,25 +2074,30 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
                            const NML3ConfigData *b,
                            NML3ConfigCmpFlags    cmp_flags)
 {
-    int IS_IPv4;
+    int      IS_IPv4;
+    gboolean ignore_ifindex;
 
     NM_CMP_SELF(a, b);
 
-    if (!NM_FLAGS_HAS(cmp_flags, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX))
+    ignore_ifindex = NM_FLAGS_HAS(cmp_flags, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX);
+
+    if (!ignore_ifindex)
         NM_CMP_DIRECT(a->ifindex, b->ifindex);
 
     NM_CMP_DIRECT(a->flags, b->flags);
 
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ADDRESS));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ADDRESS));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ROUTE));
-    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ROUTE));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ADDRESS, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ADDRESS, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP4_ROUTE, ignore_ifindex));
+    NM_CMP_RETURN(_dedup_multi_index_cmp(a, b, NMP_OBJECT_TYPE_IP6_ROUTE, ignore_ifindex));
 
     for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
         const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
 
-        NM_CMP_RETURN(
-            nmp_object_cmp(a->best_default_route_x[IS_IPv4], b->best_default_route_x[IS_IPv4]));
+        NM_CMP_RETURN(nmp_object_cmp_full(a->best_default_route_x[IS_IPv4],
+                                          b->best_default_route_x[IS_IPv4],
+                                          ignore_ifindex ? NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX
+                                                         : NMP_OBJECT_CMP_FLAGS_NONE));
 
         NM_CMP_RETURN(
             _garray_inaddr_cmp(a->nameservers_x[IS_IPv4], b->nameservers_x[IS_IPv4], addr_family));
@@ -1999,6 +2117,7 @@ nm_l3_config_data_cmp_full(const NML3ConfigData *a,
             NM_CMP_DIRECT(a->dns_priority_x[IS_IPv4], b->dns_priority_x[IS_IPv4]);
 
         NM_CMP_DIRECT(a->route_table_sync_x[IS_IPv4], b->route_table_sync_x[IS_IPv4]);
+        NM_CMP_DIRECT(a->never_default_x[IS_IPv4], b->never_default_x[IS_IPv4]);
     }
 
     NM_CMP_RETURN(_garray_inaddr_cmp(a->wins, b->wins, AF_INET));
@@ -2066,8 +2185,8 @@ _data_get_direct_route_for_host(const NML3ConfigData *self,
                                          NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
         const NMPlatformIPXRoute *item = NMP_OBJECT_CAST_IPX_ROUTE(item_obj);
 
-        if (nm_ip_addr_is_null(addr_family,
-                               nm_platform_ip_route_get_gateway(addr_family, &item->rx)))
+        if (!nm_ip_addr_is_null(addr_family,
+                                nm_platform_ip_route_get_gateway(addr_family, &item->rx)))
             continue;
 
         if (best_route && best_route->rx.plen > item->rx.plen)
@@ -2114,7 +2233,7 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
     const NMPObject *            my_addr_obj;
     NMDedupMultiIter             iter;
 
-    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, TRUE));
 
     /* For IPv6 slaac, we explicitly add the device-routes (onlink).
      * As we don't do that for IPv4 and manual IPv6 addresses. Add them here
@@ -2125,9 +2244,6 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
         in_addr_t                         network_4;
         NMPlatformIPXRoute                rx;
 
-        if (my_addr->external)
-            continue;
-
         nm_assert(my_addr->plen <= 32);
         if (my_addr->plen == 0)
             continue;
@@ -2176,120 +2292,137 @@ nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, gboolea
 /*****************************************************************************/
 
 void
-nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
-                                       int             addr_family,
-                                       guint32         route_table,
-                                       guint32         route_metric,
-                                       gboolean        is_vrf)
+nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_family)
 {
-    const int         IS_IPv4                        = NM_IS_IPv4(addr_family);
     gs_unref_ptrarray GPtrArray *extra_onlink_routes = NULL;
-    const NMPObject *            my_addr_obj;
-    const NMPObject *            my_route_obj;
-    NMPlatformIPXRoute           rx;
+    const NMPObject *            obj_src;
     NMDedupMultiIter             iter;
-    in_addr_t                    network_4 = 0;
+    int                          IS_IPv4;
     guint                        i;
 
+    if (addr_family == AF_UNSPEC) {
+        nm_l3_config_data_add_dependent_onlink_routes(self, AF_INET);
+        nm_l3_config_data_add_dependent_onlink_routes(self, AF_INET6);
+        return;
+    }
+
+    IS_IPv4 = NM_IS_IPv4(addr_family);
+
     nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
-    nm_assert_addr_family(addr_family);
 
     /* For IPv6 slaac, we explicitly add the device-routes (onlink).
      * As we don't do that for IPv4 and manual IPv6 addresses. Add them here
      * as dependent routes. */
 
-    if (!IS_IPv4) {
-        /* Pre-generate multicast route */
-        rx.r6 = (NMPlatformIP6Route){
-            .ifindex            = self->ifindex,
-            .network.s6_addr[0] = 0xffu,
-            .plen               = 8,
-            .table_coerced      = nm_platform_route_table_coerce(RT_TABLE_LOCAL),
-            .type_coerced       = nm_platform_route_type_coerce(RTN_UNICAST),
-            .metric             = 256,
-        };
-        nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
-    }
+    nm_l3_config_data_iter_obj_for_each (&iter, self, &obj_src, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
+        const NMPlatformIPXRoute *route_src = NMP_OBJECT_CAST_IPX_ROUTE(obj_src);
+        NMPObject *               new_route;
+        NMPlatformIPXRoute *      new_r;
+        const NMIPAddr *          p_gateway;
 
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self,
-                                         &my_addr_obj,
-                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
-        const NMPlatformIPXAddress *const my_addr = NMP_OBJECT_CAST_IPX_ADDRESS(my_addr_obj);
+        p_gateway = nm_platform_ip_route_get_gateway(addr_family, &route_src->rx);
+
+        if (nm_ip_addr_is_null(addr_family, p_gateway))
+            continue;
 
-        if (my_addr->ax.external)
+        if (_data_get_direct_route_for_host(
+                self,
+                addr_family,
+                p_gateway,
+                nm_platform_route_table_uncoerce(route_src->rx.table_coerced, TRUE)))
             continue;
 
+        new_route = nmp_object_clone(obj_src, FALSE);
+        new_r     = NMP_OBJECT_CAST_IPX_ROUTE(new_route);
         if (IS_IPv4) {
-            nm_assert(my_addr->a4.plen <= 32);
-            if (my_addr->a4.plen == 0)
-                continue;
+            new_r->r4.network = route_src->r4.gateway;
+            new_r->r4.plen    = 32;
+            new_r->r4.gateway = 0;
+        } else {
+            new_r->r6.network = route_src->r6.gateway;
+            new_r->r6.plen    = 128;
+            new_r->r6.gateway = in6addr_any;
         }
 
-        if (IS_IPv4) {
-            rx.r4 = (NMPlatformIP4Route){
-                .ifindex      = self->ifindex,
-                .rt_source    = NM_IP_CONFIG_SOURCE_KERNEL,
-                .network      = my_addr->a4.address,
-                .plen         = 32,
-                .pref_src     = my_addr->a4.address,
-                .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL),
-                .scope_inv    = nm_platform_route_scope_inv(RT_SCOPE_HOST),
-                .table_coerced =
-                    nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL),
-            };
-        } else {
-            rx.r6 = (NMPlatformIP6Route){
-                .ifindex      = self->ifindex,
-                .network      = my_addr->a6.address,
-                .plen         = 128,
-                .type_coerced = nm_platform_route_type_coerce(RTN_LOCAL),
-                .metric       = 0,
-                .table_coerced =
-                    nm_platform_route_table_coerce(is_vrf ? route_table : RT_TABLE_LOCAL),
-            };
+        /* we cannot add the route right away, because that invalidates the iteration. */
+        if (!extra_onlink_routes)
+            extra_onlink_routes = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+        g_ptr_array_add(extra_onlink_routes, new_route);
+    }
+    if (extra_onlink_routes) {
+        for (i = 0; i < extra_onlink_routes->len; i++) {
+            nm_l3_config_data_add_route_full(self,
+                                             addr_family,
+                                             extra_onlink_routes->pdata[i],
+                                             NULL,
+                                             NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
+                                             NULL,
+                                             NULL);
         }
-        nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
+    }
+}
 
-        if (my_addr->ax.plen == 0)
+void
+nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
+                                              int                   addr_family,
+                                              guint32               route_table,
+                                              guint32               route_metric,
+                                              const NML3ConfigData *source)
+{
+    const int          IS_IPv4 = NM_IS_IPv4(addr_family);
+    const NMPObject *  obj_src;
+    NMPlatformIPXRoute rx;
+    NMDedupMultiIter   iter;
+
+    nm_assert_addr_family(addr_family);
+    nm_assert(_NM_IS_L3_CONFIG_DATA(self, FALSE));
+    nm_assert(_NM_IS_L3_CONFIG_DATA(source, TRUE));
+    nm_assert(self != source);
+
+    /* For IPv6 slaac, we explicitly add the device-routes (onlink) and track them
+     * as regular routes in NML3ConfigData.
+     *
+     * For IPv4 and for manual IPv6 addresses we don't do that. Instead, add those
+     * routes automatically afterwards.
+     *
+     * As route-table/metric is associated with the source l3cd, we need to process
+     * all source l3cds separately. */
+
+    nm_l3_config_data_iter_obj_for_each (&iter,
+                                         source,
+                                         &obj_src,
+                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
+        const NMPlatformIPXAddress *const addr_src = NMP_OBJECT_CAST_IPX_ADDRESS(obj_src);
+        const NMPlatformIPXAddress *      addr_dst;
+
+        addr_dst = NMP_OBJECT_CAST_IPX_ADDRESS(
+            nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj(self, obj_src)));
+        if (!addr_dst)
             continue;
 
         if (IS_IPv4) {
-            network_4 =
-                nm_utils_ip4_address_clear_host_address(my_addr->a4.peer_address, my_addr->a4.plen);
+            NMPlatformIPXRoute       r_stack;
+            const NMPlatformIPRoute *r;
 
-            if (nm_utils_ip4_address_is_zeronet(network_4)) {
-                /* Kernel doesn't add device-routes for destinations that
-                 * start with 0.x.y.z. Skip them. */
+            if (addr_dst->a4.a_acd_not_ready)
                 continue;
-            }
 
-            if (my_addr->a4.plen == 32 && my_addr->a4.address == my_addr->a4.peer_address) {
-                /* Kernel doesn't add device-routes for /32 addresses unless
-                 * they have a peer. */
-                continue;
-            }
+            r = (NMPlatformIPRoute *) nm_platform_ip4_address_generate_device_route(&addr_src->a4,
+                                                                                    self->ifindex,
+                                                                                    route_table,
+                                                                                    route_metric,
+                                                                                    &r_stack.r4);
+            if (r)
+                nm_l3_config_data_add_route(self, addr_family, NULL, r);
         } else {
-            if (NM_FLAGS_HAS(my_addr->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE))
+            const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED(&addr_src->a6.peer_address);
+            int            routes_i;
+
+            if (addr_src->ax.plen == 0)
                 continue;
-        }
 
-        if (IS_IPv4) {
-            rx.r4 = (NMPlatformIP4Route){
-                .ifindex       = self->ifindex,
-                .rt_source     = NM_IP_CONFIG_SOURCE_KERNEL,
-                .network       = network_4,
-                .plen          = my_addr->a4.plen,
-                .pref_src      = my_addr->a4.address,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
-                .scope_inv     = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK),
-            };
-            nm_platform_ip_route_normalize(addr_family, &rx.rx);
-            nm_l3_config_data_add_route(self, addr_family, NULL, &rx.rx);
-        } else {
-            const gboolean has_peer = !IN6_IS_ADDR_UNSPECIFIED(&my_addr->a6.peer_address);
-            int            routes_i;
+            if (NM_FLAGS_HAS(addr_src->a6.n_ifa_flags, IFA_F_NOPREFIXROUTE))
+                continue;
 
             /* If we have an IPv6 peer, we add two /128 routes
              * (unless, both addresses are identical). */
@@ -2299,20 +2432,20 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
                 guint8                 plen;
 
                 if (routes_i == 1 && has_peer
-                    && IN6_ARE_ADDR_EQUAL(&my_addr->a6.address, &my_addr->a6.peer_address))
+                    && IN6_ARE_ADDR_EQUAL(&addr_src->a6.address, &addr_src->a6.peer_address))
                     break;
 
                 if (has_peer) {
                     if (routes_i == 0)
-                        a6 = &my_addr->a6.address;
+                        a6 = &addr_src->a6.address;
                     else
-                        a6 = &my_addr->a6.peer_address;
+                        a6 = &addr_src->a6.peer_address;
                     plen = 128;
                 } else {
                     a6   = nm_utils_ip6_address_clear_host_address(&a6_stack,
-                                                                 &my_addr->a6.address,
-                                                                 my_addr->a6.plen);
-                    plen = my_addr->a6.plen;
+                                                                 &addr_src->a6.address,
+                                                                 addr_src->a6.plen);
+                    plen = addr_src->a6.plen;
                 }
 
                 rx.r6 = (NMPlatformIP6Route){
@@ -2328,73 +2461,16 @@ nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
             }
         }
     }
-
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self,
-                                         &my_route_obj,
-                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-        const NMPlatformIPXRoute *my_route = NMP_OBJECT_CAST_IPX_ROUTE(my_route_obj);
-        NMPObject *               new_route;
-        NMPlatformIPXRoute *      new_r;
-        const NMIPAddr *          p_gateway;
-
-        if (!NM_PLATFORM_IP_ROUTE_IS_DEFAULT(my_route)
-            || NM_IS_IP_CONFIG_SOURCE_RTPROT(my_route->rx.rt_source))
-            continue;
-
-        p_gateway = nm_platform_ip_route_get_gateway(addr_family, &my_route->rx);
-
-        if (nm_ip_addr_is_null(addr_family, p_gateway))
-            continue;
-
-        if (_data_get_direct_route_for_host(
-                self,
-                addr_family,
-                p_gateway,
-                nm_platform_route_table_uncoerce(my_route->rx.table_coerced, TRUE)))
-            continue;
-
-        new_route = nmp_object_clone(my_route_obj, FALSE);
-        new_r     = NMP_OBJECT_CAST_IPX_ROUTE(new_route);
-        if (IS_IPv4) {
-            new_r->r4.network = my_route->r4.gateway;
-            new_r->r4.plen    = 32;
-            new_r->r4.gateway = 0;
-        } else {
-            new_r->r6.network = my_route->r6.gateway;
-            new_r->r6.plen    = 128;
-            new_r->r6.gateway = in6addr_any;
-        }
-
-        /* we cannot add the route right away, because that invalidates the iteration. */
-        if (!extra_onlink_routes)
-            extra_onlink_routes = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
-        g_ptr_array_add(extra_onlink_routes, new_route);
-    }
-    if (extra_onlink_routes) {
-        for (i = 0; i < extra_onlink_routes->len; i++) {
-            nm_l3_config_data_add_route_full(self,
-                                             addr_family,
-                                             extra_onlink_routes->pdata[i],
-                                             NULL,
-                                             NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
-                                             NULL,
-                                             NULL);
-        }
-    }
 }
 
 /*****************************************************************************/
 
 static void
-_init_from_connection_ip(NML3ConfigData *self,
-                         int             addr_family,
-                         NMConnection *  connection,
-                         guint32         route_table,
-                         guint32         route_metric)
+_init_from_connection_ip(NML3ConfigData *self, int addr_family, NMConnection *connection)
 {
     const int          IS_IPv4 = NM_IS_IPv4(addr_family);
     NMSettingIPConfig *s_ip;
+    gboolean           never_default;
     guint              naddresses;
     guint              nroutes;
     guint              nnameservers;
@@ -2415,25 +2491,28 @@ _init_from_connection_ip(NML3ConfigData *self,
     if (!s_ip)
         return;
 
-    if (!nm_setting_ip_config_get_never_default(s_ip)
-        && (gateway_str = nm_setting_ip_config_get_gateway(s_ip))
+    never_default = nm_setting_ip_config_get_never_default(s_ip);
+
+    nm_l3_config_data_set_never_default(self, addr_family, !!never_default);
+
+    if (!never_default && (gateway_str = nm_setting_ip_config_get_gateway(s_ip))
         && inet_pton(addr_family, gateway_str, &gateway_bin) == 1
         && !nm_ip_addr_is_null(addr_family, &gateway_bin)) {
         NMPlatformIPXRoute r;
 
         if (IS_IPv4) {
             r.r4 = (NMPlatformIP4Route){
-                .rt_source     = NM_IP_CONFIG_SOURCE_USER,
-                .gateway       = gateway_bin.addr4,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
+                .gateway    = gateway_bin.addr4,
+                .table_any  = TRUE,
+                .metric_any = TRUE,
             };
         } else {
             r.r6 = (NMPlatformIP6Route){
-                .rt_source     = NM_IP_CONFIG_SOURCE_USER,
-                .gateway       = gateway_bin.addr6,
-                .table_coerced = nm_platform_route_table_coerce(route_table),
-                .metric        = route_metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
+                .gateway    = gateway_bin.addr6,
+                .table_any  = TRUE,
+                .metric_any = TRUE,
             };
         }
 
@@ -2488,6 +2567,7 @@ _init_from_connection_ip(NML3ConfigData *self,
         NMIPAddr           next_hop_bin;
         gint64             metric64;
         guint32            metric;
+        gboolean           metric_any;
         guint              plen;
 
         nm_assert(nm_ip_route_get_family(s_route) == addr_family);
@@ -2496,11 +2576,14 @@ _init_from_connection_ip(NML3ConfigData *self,
         nm_ip_route_get_next_hop_binary(s_route, &next_hop_bin);
 
         metric64 = nm_ip_route_get_metric(s_route);
-        if (metric64 < 0)
-            metric = route_metric;
-        else
-            metric = metric64;
-        metric = nm_utils_ip_route_metric_normalize(addr_family, metric);
+        if (metric64 < 0) {
+            metric_any = TRUE;
+            metric     = 0;
+        } else {
+            metric_any = FALSE;
+            metric     = metric64;
+            metric     = nm_utils_ip_route_metric_normalize(addr_family, metric);
+        }
 
         plen = nm_ip_route_get_prefix(s_route);
 
@@ -2508,25 +2591,27 @@ _init_from_connection_ip(NML3ConfigData *self,
 
         if (IS_IPv4) {
             r.r4 = (NMPlatformIP4Route){
-                .network   = network_bin.addr4,
-                .plen      = nm_ip_route_get_prefix(s_route),
-                .gateway   = next_hop_bin.addr4,
-                .metric    = metric,
-                .rt_source = NM_IP_CONFIG_SOURCE_USER,
+                .network    = network_bin.addr4,
+                .plen       = nm_ip_route_get_prefix(s_route),
+                .gateway    = next_hop_bin.addr4,
+                .metric_any = metric_any,
+                .metric     = metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
             };
             nm_assert(r.r4.plen <= 32);
         } else {
             r.r6 = (NMPlatformIP6Route){
-                .network   = network_bin.addr6,
-                .plen      = nm_ip_route_get_prefix(s_route),
-                .gateway   = next_hop_bin.addr6,
-                .metric    = metric,
-                .rt_source = NM_IP_CONFIG_SOURCE_USER,
+                .network    = network_bin.addr6,
+                .plen       = nm_ip_route_get_prefix(s_route),
+                .gateway    = next_hop_bin.addr6,
+                .metric_any = metric_any,
+                .metric     = metric,
+                .rt_source  = NM_IP_CONFIG_SOURCE_USER,
             };
             nm_assert(r.r6.plen <= 128);
         }
 
-        nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, route_table);
+        nm_utils_ip_route_attribute_to_platform(addr_family, s_route, &r.rx, -1);
 
         nm_l3_config_data_add_route(self, addr_family, NULL, &r.rx);
     }
@@ -2550,34 +2635,36 @@ _init_from_connection_ip(NML3ConfigData *self,
     }
 
     idx = 0;
-    while ((idx = nm_setting_ip_config_next_valid_dns_option(s_ip, i)) >= 0) {
+    while ((idx = nm_setting_ip_config_next_valid_dns_option(s_ip, idx)) >= 0) {
         nm_l3_config_data_add_dns_option(self,
                                          addr_family,
-                                         nm_setting_ip_config_get_dns_option(s_ip, i));
+                                         nm_setting_ip_config_get_dns_option(s_ip, idx));
         idx++;
     }
 
     nm_l3_config_data_set_dns_priority(self,
                                        addr_family,
                                        nm_setting_ip_config_get_dns_priority(s_ip));
+
+    if (!IS_IPv4) {
+        nm_l3_config_data_set_ip6_privacy(
+            self,
+            nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip)));
+    }
 }
 
 NML3ConfigData *
 nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx,
                                       int                ifindex,
-                                      NMConnection *     connection,
-                                      guint32            route_table_4,
-                                      guint32            route_table_6,
-                                      guint32            route_metric_4,
-                                      guint32            route_metric_6)
+                                      NMConnection *     connection)
 {
     NML3ConfigData *self;
     NMSettingProxy *s_proxy;
 
-    self = nm_l3_config_data_new(multi_idx, ifindex);
+    self = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_USER);
 
-    _init_from_connection_ip(self, AF_INET, connection, route_table_4, route_metric_4);
-    _init_from_connection_ip(self, AF_INET6, connection, route_table_6, route_metric_6);
+    _init_from_connection_ip(self, AF_INET, connection);
+    _init_from_connection_ip(self, AF_INET6, connection);
 
     s_proxy = _nm_connection_get_setting(connection, NM_TYPE_SETTING_PROXY);
     if (s_proxy) {
@@ -2693,7 +2780,7 @@ nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       multi_idx,
     if (nm_platform_link_get_master(platform, ifindex) > 0)
         return NULL;
 
-    self = nm_l3_config_data_new(multi_idx, ifindex);
+    self = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_KERNEL);
 
     _init_from_platform(self, AF_INET, platform, ipv6_privacy_rfc4941);
     _init_from_platform(self, AF_INET6, platform, ipv6_privacy_rfc4941);
@@ -2710,13 +2797,16 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                         const guint32 *       default_route_table_x /* length 2, for IS_IPv4 */,
                         const guint32 *       default_route_metric_x /* length 2, for IS_IPv4 */,
                         const guint32 *       default_route_penalty_x /* length 2, for IS_IPv4 */,
-                        NML3ConfigMergeHookAddObj hook_add_addr,
+                        const int *           default_dns_priority_x /* length 2, for IS_IPv4 */,
+                        NML3ConfigMergeHookAddObj hook_add_obj,
                         gpointer                  hook_user_data)
 {
     static const guint32 x_default_route_table_x[2]   = {RT_TABLE_MAIN, RT_TABLE_MAIN};
     static const guint32 x_default_route_metric_x[2]  = {NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
                                                         NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4};
     static const guint32 x_default_route_penalty_x[2] = {0, 0};
+    static const int     x_default_dns_priority_x[2]  = {NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                                                    NM_DNS_PRIORITY_DEFAULT_NORMAL};
     NMDedupMultiIter     iter;
     const NMPObject *    obj;
     int                  IS_IPv4;
@@ -2730,6 +2820,8 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
         default_route_metric_x = x_default_route_metric_x;
     if (!default_route_penalty_x)
         default_route_penalty_x = x_default_route_penalty_x;
+    if (!default_dns_priority_x)
+        default_dns_priority_x = x_default_dns_priority_x;
 
     nm_assert(default_route_table_x[0] != 0);
     nm_assert(default_route_table_x[1] != 0);
@@ -2747,51 +2839,100 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
                                              src,
                                              &obj,
                                              NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
-            NMPlatformIPXAddress       addr_stack;
-            const NMPlatformIPAddress *addr             = NULL;
-            NMTernary                  ip4acd_not_ready = NM_TERNARY_DEFAULT;
+            const NMPlatformIPAddress *a_src = NMP_OBJECT_CAST_IP_ADDRESS(obj);
+            NMPlatformIPXAddress       a;
+            NML3ConfigMergeHookResult  hook_result = {
+                .ip4acd_not_ready   = NM_OPTION_BOOL_DEFAULT,
+                .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+            };
+
+#define _ensure_a()                                       \
+    G_STMT_START                                          \
+    {                                                     \
+        if (a_src != &a.ax) {                             \
+            if (IS_IPv4)                                  \
+                a.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); \
+            else                                          \
+                a.a6 = *NMP_OBJECT_CAST_IP6_ADDRESS(obj); \
+            a_src = &a.ax;                                \
+        }                                                 \
+    }                                                     \
+    G_STMT_END
 
-            if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data))
+            if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
                 continue;
 
-            if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT
-                && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) {
-                addr_stack.a4                  = *NMP_OBJECT_CAST_IP4_ADDRESS(obj);
-                addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready);
-                addr                           = &addr_stack.ax;
-            } else
-                nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT);
+            nm_assert(IS_IPv4 || hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+            if (a_src->ifindex != self->ifindex) {
+                _ensure_a();
+                a.ax.ifindex = self->ifindex;
+            }
+
+            if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4
+                && (!!hook_result.ip4acd_not_ready)
+                       != ((const NMPlatformIP4Address *) a_src)->a_acd_not_ready) {
+                _ensure_a();
+                a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready);
+            }
+
+            if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+                && (!!hook_result.assume_config_once) != a_src->a_assume_config_once) {
+                _ensure_a();
+                a.ax.a_assume_config_once = (!!hook_result.assume_config_once);
+            }
 
             nm_l3_config_data_add_address_full(self,
                                                addr_family,
-                                               addr ? NULL : obj,
-                                               addr,
+                                               a_src == &a.ax ? NULL : obj,
+                                               a_src == &a.ax ? a_src : NULL,
                                                NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
                                                NULL);
         }
 
+#undef _ensure_a
+
         if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) {
             nm_l3_config_data_iter_obj_for_each (&iter,
                                                  src,
                                                  &obj,
                                                  NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-                const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
-                NMPlatformIPXRoute       r;
+                const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj);
+                NMPlatformIPXRoute        r;
+                NML3ConfigMergeHookResult hook_result = {
+                    .ip4acd_not_ready   = NM_OPTION_BOOL_DEFAULT,
+                    .assume_config_once = NM_OPTION_BOOL_DEFAULT,
+                };
 
 #define _ensure_r()                                     \
     G_STMT_START                                        \
     {                                                   \
         if (r_src != &r.rx) {                           \
-            r_src = &r.rx;                              \
             if (IS_IPv4)                                \
                 r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE(obj); \
             else                                        \
                 r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE(obj); \
-            r.rx.ifindex = self->ifindex;               \
+            r_src = &r.rx;                              \
         }                                               \
     }                                                   \
     G_STMT_END
 
+                if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data))
+                    continue;
+
+                nm_assert(hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+
+                if (r_src->ifindex != self->ifindex) {
+                    _ensure_r();
+                    r.rx.ifindex = self->ifindex;
+                }
+
+                if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT
+                    && (!!hook_result.assume_config_once) != r_src->r_assume_config_once) {
+                    _ensure_r();
+                    r.rx.r_assume_config_once = (!!hook_result.assume_config_once);
+                }
+
                 if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) {
                     if (r_src->table_any) {
                         _ensure_r();
@@ -2848,12 +2989,20 @@ nm_l3_config_data_merge(NML3ConfigData *      self,
 
         if (!NM_FLAGS_ANY(self->flags, has_dns_priority_flag)
             && NM_FLAGS_ANY(src->flags, has_dns_priority_flag)) {
-            self->dns_priority_x[IS_IPv4] = src->dns_priority_x[IS_IPv4];
+            int p = src->dns_priority_x[IS_IPv4];
+
+            if (p == 0 && !NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE))
+                p = default_dns_priority_x[IS_IPv4];
+
+            self->dns_priority_x[IS_IPv4] = p;
             self->flags |= has_dns_priority_flag;
         }
 
         if (self->route_table_sync_x[IS_IPv4] == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE)
             self->route_table_sync_x[IS_IPv4] = src->route_table_sync_x[IS_IPv4];
+
+        if (self->never_default_x[IS_IPv4] == NM_OPTION_BOOL_DEFAULT)
+            self->never_default_x[IS_IPv4] = src->never_default_x[IS_IPv4];
     }
 
     if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_DNS)) {
@@ -2933,7 +3082,7 @@ nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex)
     if (ifindex <= 0)
         ifindex = src->ifindex;
 
-    self = nm_l3_config_data_new(src->multi_idx, ifindex);
+    self = nm_l3_config_data_new(src->multi_idx, ifindex, src->source);
     nm_l3_config_data_merge(self,
                             src,
                             NM_L3_CONFIG_MERGE_FLAGS_CLONE,
@@ -2941,9 +3090,15 @@ nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifindex)
                             NULL,
                             NULL,
                             NULL,
+                            NULL,
                             NULL);
 
-    nm_assert(nm_l3_config_data_cmp_full(src, self, NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX) == 0);
+    nm_assert(nm_l3_config_data_cmp_full(src,
+                                         self,
+                                         src->ifindex != ifindex
+                                             ? NM_L3_CONFIG_CMP_FLAGS_IGNORE_IFINDEX
+                                             : NM_L3_CONFIG_CMP_FLAGS_NONE)
+              == 0);
     nm_assert(nm_l3_config_data_get_ifindex(self) == ifindex);
 
     return self;
diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h
index 9bed47e0..1ad23c32 100644
--- a/src/core/nm-l3-config-data.h
+++ b/src/core/nm-l3-config-data.h
@@ -52,15 +52,6 @@ typedef enum {
 /**
  * NML3ConfigMergeFlags:
  * @NM_L3_CONFIG_MERGE_FLAGS_NONE: no flags set
- * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set,
- *   the the NML3ConfigData doesn't get merged and it's information won't be
- *   synced. The only purpose is to run ACD on its IPv4 addresses, but
- *   regardless whether ACD succeeds/fails, the IP addresses won't be configured.
- *   The point is to run ACD first (without configuring it), and only
- *   commit the settings if requested. That can either happen by
- *   nm_l3cfg_add_config() the same NML3Cfg again (with a different
- *   tag), or by calling nm_l3cfg_add_config() again with this flag
- *   cleared (and the same tag).
  * @NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES: don't merge routes
  * @NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES: don't merge default routes.
  *   Note that if the respective NML3ConfigData has NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES
@@ -71,18 +62,18 @@ typedef enum {
  */
 typedef enum _nm_packed {
     NM_L3_CONFIG_MERGE_FLAGS_NONE              = 0,
-    NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD      = (1LL << 0),
-    NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES         = (1LL << 1),
-    NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 2),
-    NM_L3_CONFIG_MERGE_FLAGS_NO_DNS            = (1LL << 3),
-    NM_L3_CONFIG_MERGE_FLAGS_CLONE             = (1LL << 4),
+    NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES         = (1LL << 0),
+    NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 1),
+    NM_L3_CONFIG_MERGE_FLAGS_NO_DNS            = (1LL << 2),
+    NM_L3_CONFIG_MERGE_FLAGS_CLONE             = (1LL << 3),
 } NML3ConfigMergeFlags;
 
 /*****************************************************************************/
 
 static inline gboolean NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self);
 
-NML3ConfigData *      nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex);
+NML3ConfigData *
+nm_l3_config_data_new(NMDedupMultiIndex *multi_idx, int ifindex, NMIPConfigSource source);
 const NML3ConfigData *nm_l3_config_data_ref(const NML3ConfigData *self);
 const NML3ConfigData *nm_l3_config_data_ref_and_seal(const NML3ConfigData *self);
 const NML3ConfigData *nm_l3_config_data_seal(const NML3ConfigData *self);
@@ -137,21 +128,22 @@ NML3ConfigData *nm_l3_config_data_new_clone(const NML3ConfigData *src, int ifind
 
 NML3ConfigData *nm_l3_config_data_new_from_connection(NMDedupMultiIndex *multi_idx,
                                                       int                ifindex,
-                                                      NMConnection *     connection,
-                                                      guint32            route_table_4,
-                                                      guint32            route_table_6,
-                                                      guint32            route_metric_4,
-                                                      guint32            route_metric_6);
+                                                      NMConnection *     connection);
 
 NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex *       multi_idx,
                                                     int                       ifindex,
                                                     NMPlatform *              platform,
                                                     NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941);
 
-typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd,
-                                              const NMPObject *     obj,
-                                              NMTernary *           out_ip4acd_not_ready,
-                                              gpointer              user_data);
+typedef struct {
+    NMOptionBool ip4acd_not_ready;
+    NMOptionBool assume_config_once;
+} NML3ConfigMergeHookResult;
+
+typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *     l3cd,
+                                              const NMPObject *          obj,
+                                              NML3ConfigMergeHookResult *result,
+                                              gpointer                   user_data);
 
 void nm_l3_config_data_merge(NML3ConfigData *      self,
                              const NML3ConfigData *src,
@@ -159,17 +151,20 @@ void nm_l3_config_data_merge(NML3ConfigData *      self,
                              const guint32 *default_route_table_x /* length 2, for IS_IPv4 */,
                              const guint32 *default_route_metric_x /* length 2, for IS_IPv4 */,
                              const guint32 *default_route_penalty_x /* length 2, for IS_IPv4 */,
-                             NML3ConfigMergeHookAddObj hook_add_addr,
+                             const int *    default_dns_priority_x /* length 2, for IS_IPv4 */,
+                             NML3ConfigMergeHookAddObj hook_add_obj,
                              gpointer                  hook_user_data);
 
 GPtrArray *nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self,
                                                         gboolean              is_vrf);
 
-void nm_l3_config_data_add_dependent_routes(NML3ConfigData *self,
-                                            int             addr_family,
-                                            guint32         route_table,
-                                            guint32         route_metric,
-                                            gboolean        is_vrf);
+void nm_l3_config_data_add_dependent_onlink_routes(NML3ConfigData *self, int addr_family);
+
+void nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *      self,
+                                                   int                   addr_family,
+                                                   guint32               route_table,
+                                                   guint32               route_metric,
+                                                   const NML3ConfigData *source);
 
 /*****************************************************************************/
 
@@ -192,7 +187,7 @@ NM_IS_L3_CONFIG_DATA(const NML3ConfigData *self)
      *
      * Additionally, also call nm_l3_config_data_get_ifindex(), which does more
      * checks during nm_assert(). */
-    nm_assert(nm_l3_config_data_get_ifindex(self) > 0);
+    nm_assert(!self || nm_l3_config_data_get_ifindex(self) > 0);
     return !!self;
 }
 
@@ -229,6 +224,11 @@ const NMDedupMultiIdxType *nm_l3_config_data_lookup_index(const NML3ConfigData *
 const NMDedupMultiEntry *nm_l3_config_data_lookup_obj(const NML3ConfigData *self,
                                                       const NMPObject *     obj);
 
+const NMPlatformIP4Address *nm_l3_config_data_lookup_address_4(const NML3ConfigData *self,
+                                                               in_addr_t             addr,
+                                                               guint8                plen,
+                                                               in_addr_t             peer_addr);
+
 const NMPlatformIP6Address *nm_l3_config_data_lookup_address_6(const NML3ConfigData * self,
                                                                const struct in6_addr *addr);
 
@@ -363,8 +363,6 @@ nm_l3_config_data_unset_flags(NML3ConfigData *self, NML3ConfigDatFlags flags)
 
 /*****************************************************************************/
 
-gboolean nm_l3_config_data_set_source(NML3ConfigData *self, NMIPConfigSource source);
-
 const NMPObject *nm_l3_config_data_get_first_obj(const NML3ConfigData *self,
                                                  NMPObjectType         obj_type,
                                                  gboolean (*predicate)(const NMPObject *obj));
@@ -455,6 +453,11 @@ gboolean nm_l3_config_data_set_route_table_sync(NML3ConfigData *       self,
                                                 int                    addr_family,
                                                 NMIPRouteTableSyncMode route_table_sync);
 
+NMTernary nm_l3_config_data_get_never_default(const NML3ConfigData *self, int addr_family);
+
+gboolean
+nm_l3_config_data_set_never_default(NML3ConfigData *self, int addr_family, NMTernary never_default);
+
 NMTernary nm_l3_config_data_get_metered(const NML3ConfigData *self);
 
 gboolean nm_l3_config_data_set_metered(NML3ConfigData *self, NMTernary metered);
@@ -509,6 +512,17 @@ nm_l3_config_data_get_dns_options(const NML3ConfigData *self, int addr_family, g
 gboolean
 nm_l3_config_data_get_dns_priority(const NML3ConfigData *self, int addr_family, int *out_prio);
 
+static inline int
+nm_l3_config_data_get_dns_priority_or_default(const NML3ConfigData *self, int addr_family)
+{
+    int v;
+
+    nm_assert_addr_family(addr_family);
+    if (!self || !nm_l3_config_data_get_dns_priority(self, addr_family, &v))
+        return 0;
+    return v;
+}
+
 gboolean
 nm_l3_config_data_set_dns_priority(NML3ConfigData *self, int addr_family, int dns_priority);
 
diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c
index 28ceb39e..eb9ceb9b 100644
--- a/src/core/nm-l3-ipv4ll.c
+++ b/src/core/nm-l3-ipv4ll.c
@@ -317,51 +317,32 @@ _acd_info_is_good(const NML3AcdAddrInfo *acd_info)
 
 /*****************************************************************************/
 
-static NMPlatformIP4Address *
-_l3cd_config_plat_init_addr(NMPlatformIP4Address *a, int ifindex, in_addr_t addr)
-{
-    nm_assert(nm_utils_ip4_address_is_link_local(addr));
-
-    *a = (NMPlatformIP4Address){
-        .ifindex      = ifindex,
-        .address      = addr,
-        .peer_address = addr,
-        .plen         = ADDR_IPV4LL_PREFIX_LEN,
-        .addr_source  = NM_IP_CONFIG_SOURCE_IP4LL,
-    };
-    return a;
-}
-
-static NMPlatformIP4Route *
-_l3cd_config_plat_init_route(NMPlatformIP4Route *r, int ifindex)
-{
-    *r = (NMPlatformIP4Route){
-        .ifindex    = ifindex,
-        .network    = htonl(0xE0000000u),
-        .plen       = 4,
-        .rt_source  = NM_IP_CONFIG_SOURCE_IP4LL,
-        .table_any  = TRUE,
-        .metric_any = TRUE,
-    };
-    return r;
-}
-
 static const NML3ConfigData *
 _l3cd_config_create(int ifindex, in_addr_t addr, NMDedupMultiIndex *multi_idx)
 {
     nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
-    NMPlatformIP4Address                    a;
-    NMPlatformIP4Route                      r;
 
     nm_assert(nm_utils_ip4_address_is_link_local(addr));
     nm_assert(ifindex > 0);
     nm_assert(multi_idx);
 
-    l3cd = nm_l3_config_data_new(multi_idx, ifindex);
-    nm_l3_config_data_set_source(l3cd, NM_IP_CONFIG_SOURCE_IP4LL);
+    l3cd = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_IP4LL);
+
+    nm_l3_config_data_add_address_4(
+        l3cd,
+        NM_PLATFORM_IP4_ADDRESS_INIT(.ifindex      = ifindex,
+                                     .address      = addr,
+                                     .peer_address = addr,
+                                     .plen         = ADDR_IPV4LL_PREFIX_LEN,
+                                     .addr_source  = NM_IP_CONFIG_SOURCE_IP4LL));
 
-    nm_l3_config_data_add_address_4(l3cd, _l3cd_config_plat_init_addr(&a, ifindex, addr));
-    nm_l3_config_data_add_route_4(l3cd, _l3cd_config_plat_init_route(&r, ifindex));
+    nm_l3_config_data_add_route_4(l3cd,
+                                  NM_PLATFORM_IP4_ROUTE_INIT(.ifindex   = ifindex,
+                                                             .network   = htonl(0xE0000000u),
+                                                             .plen      = 4,
+                                                             .rt_source = NM_IP_CONFIG_SOURCE_IP4LL,
+                                                             .table_any = TRUE,
+                                                             .metric_any = TRUE));
 
     return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
 }
@@ -610,15 +591,19 @@ _l3cd_config_add(NML3IPv4LL *self)
                              NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
                              0,
                              0,
+                             NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                             NM_DNS_PRIORITY_DEFAULT_NORMAL,
                              NM_L3_ACD_DEFEND_TYPE_ONCE,
                              self->reg_timeout_msec,
-                             NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+                             NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD,
+                             NM_L3_CONFIG_MERGE_FLAGS_NONE))
         nm_assert_not_reached();
 
     self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
                                                               NM_L3_CFG_COMMIT_TYPE_ASSUME,
-                                                              self->l3cfg_commit_handle);
-    nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+                                                              self->l3cfg_commit_handle,
+                                                              "ipv4ll");
+    nm_l3cfg_commit_on_idle_schedule(self->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
 }
 
 static gboolean
@@ -640,7 +625,7 @@ _l3cd_config_remove(NML3IPv4LL *self)
         nm_assert_not_reached();
 
     nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
-    nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+    nm_l3cfg_commit_on_idle_schedule(self->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
     return TRUE;
 }
 
@@ -1044,7 +1029,7 @@ nm_l3_ipv4ll_unref(NML3IPv4LL *self)
             nm_assert_not_reached();
 
         nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
-        nm_l3cfg_commit_on_idle_schedule(self->l3cfg);
+        nm_l3cfg_commit_on_idle_schedule(self->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
     } else
         nm_assert(!self->l3cfg_commit_handle);
 
diff --git a/src/core/nm-l3-ipv6ll.c b/src/core/nm-l3-ipv6ll.c
new file mode 100644
index 00000000..14387321
--- /dev/null
+++ b/src/core/nm-l3-ipv6ll.c
@@ -0,0 +1,717 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-l3-ipv6ll.h"
+
+#include <linux/if_addr.h>
+
+#include "nm-core-utils.h"
+
+/*****************************************************************************/
+
+/* FIXME(l3cfg): ensure that NML3IPv6LL generates the same stable privacy addresses
+ *  as previous implementation. */
+
+/*****************************************************************************/
+
+NM_UTILS_LOOKUP_STR_DEFINE(nm_l3_ipv6ll_state_to_string,
+                           NML3IPv6LLState,
+                           NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("???"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_NONE, "none"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DEFUNCT, "defunct"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_STARTING, "starting"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
+                                                    "dad-in-progress"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_READY, "ready"),
+                           NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_FAILED, "dad-failed"), );
+
+/*****************************************************************************/
+
+struct _NML3IPv6LL {
+    NML3Cfg *                l3cfg;
+    NML3CfgCommitTypeHandle *l3cfg_commit_handle;
+    NML3IPv6LLNotifyFcn      notify_fcn;
+    gpointer                 user_data;
+    GSource *                starting_on_idle_source;
+    GSource *                wait_for_addr_source;
+    GSource *                emit_changed_idle_source;
+    gulong                   l3cfg_signal_notify_id;
+    NML3IPv6LLState          state;
+
+    /* if we have cur_lladdr set, then this might cache the last
+     * matching NMPObject from the platform cache. This only serves
+     * for optimizing the lookup to the platform cache. */
+    const NMPlatformIP6Address *cur_lladdr_obj;
+
+    struct in6_addr cur_lladdr;
+
+    /* if we have cur_lladdr and _state_has_lladdr() indicates that
+     * the LL address is suitable, this is a NML3ConfigData instance
+     * with the configuration. */
+    const NML3ConfigData *l3cd;
+
+    /* "assume" means that we first look whether there is any suitable
+     * IPv6 address on the device, and in that case, try to use that
+     * instead of generating a new one. Otherwise, we always try to
+     * generate a new LL address. */
+    bool assume : 1;
+
+    struct {
+        NMUtilsStableType stable_type;
+        guint32           dad_counter;
+        struct {
+            const char *ifname;
+            const char *network_id;
+        } stable_privacy;
+        struct {
+            NMUtilsIPv6IfaceId iid;
+        } token;
+    } addrgen;
+};
+
+/*****************************************************************************/
+
+#define _NMLOG_DOMAIN      LOGD_IP6
+#define _NMLOG_PREFIX_NAME "ipv6ll"
+#define _NMLOG(level, ...)                                                             \
+    G_STMT_START                                                                       \
+    {                                                                                  \
+        nm_log((level),                                                                \
+               (_NMLOG_DOMAIN),                                                        \
+               NULL,                                                                   \
+               NULL,                                                                   \
+               _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT                        \
+                                  ",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+               NM_HASH_OBFUSCATE_PTR(self),                                            \
+               nm_l3cfg_get_ifindex((self)->l3cfg) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+    }                                                                                  \
+    G_STMT_END
+
+/*****************************************************************************/
+
+#define L3CD_TAG(self) (&(self)->notify_fcn)
+
+/*****************************************************************************/
+
+#define _ASSERT(self)                      \
+    G_STMT_START                           \
+    {                                      \
+        NML3IPv6LL *const _self = (self);  \
+                                           \
+        nm_assert(NM_IS_L3_IPV6LL(_self)); \
+    }                                      \
+    G_STMT_END
+
+/*****************************************************************************/
+
+static void _check(NML3IPv6LL *self);
+
+/*****************************************************************************/
+
+NML3Cfg *
+nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self)
+{
+    nm_assert(NM_IS_L3_IPV6LL(self));
+
+    return self->l3cfg;
+}
+
+int
+nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self)
+{
+    nm_assert(NM_IS_L3_IPV6LL(self));
+
+    return nm_l3cfg_get_ifindex(self->l3cfg);
+}
+
+NMPlatform *
+nm_l3_ipv6ll_get_platform(NML3IPv6LL *self)
+{
+    nm_assert(NM_IS_L3_IPV6LL(self));
+
+    return nm_l3cfg_get_platform(self->l3cfg);
+}
+
+/*****************************************************************************/
+
+static gboolean
+_state_has_lladdr(NML3IPv6LLState state)
+{
+    return NM_IN_SET(state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY);
+}
+
+NML3IPv6LLState
+nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr)
+{
+    nm_assert(NM_IS_L3_IPV6LL(self));
+
+    NM_SET_OUT(out_lladdr, _state_has_lladdr(self->state) ? &self->cur_lladdr : NULL);
+    return self->state;
+}
+
+static const NML3ConfigData *
+_l3cd_config_create(int ifindex, const struct in6_addr *lladdr, NMDedupMultiIndex *multi_idx)
+{
+    NML3ConfigData *l3cd;
+
+    nm_assert(ifindex > 0);
+    nm_assert(lladdr);
+    nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr));
+
+    l3cd = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_IP6LL);
+
+    nm_l3_config_data_add_address_6(
+        l3cd,
+        NM_PLATFORM_IP6_ADDRESS_INIT(.address     = *lladdr,
+                                     .plen        = 64,
+                                     .addr_source = NM_IP_CONFIG_SOURCE_IP6LL));
+
+    nm_l3_config_data_add_route_6(
+        l3cd,
+        NM_PLATFORM_IP6_ROUTE_INIT(.network.s6_addr16[0] = htons(0xfe80u),
+                                   .plen                 = 64,
+                                   .metric_any           = TRUE,
+                                   .table_any            = TRUE,
+                                   .rt_source            = NM_IP_CONFIG_SOURCE_IP6LL));
+
+    return nm_l3_config_data_seal(l3cd);
+}
+
+const NML3ConfigData *
+nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self)
+{
+    nm_assert(NM_IS_L3_IPV6LL(self));
+
+    if (!_state_has_lladdr(self->state)) {
+        nm_assert(!self->l3cd);
+        return NULL;
+    }
+
+    if (!self->l3cd) {
+        self->l3cd = _l3cd_config_create(nm_l3_ipv6ll_get_ifindex(self),
+                                         &self->cur_lladdr,
+                                         nm_l3cfg_get_multi_idx(self->l3cfg));
+    }
+
+    return self->l3cd;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_emit_changed_on_idle_cb(gpointer user_data)
+{
+    NML3IPv6LL *           self = user_data;
+    const struct in6_addr *lladdr;
+    NML3IPv6LLState        state;
+    char                   sbuf[INET6_ADDRSTRLEN];
+
+    nm_clear_g_source_inst(&self->emit_changed_idle_source);
+
+    state = nm_l3_ipv6ll_get_state(self, &lladdr);
+
+    _LOGT("emit changed signal (state=%s%s%s)",
+          nm_l3_ipv6ll_state_to_string(state),
+          lladdr ? ", " : "",
+          lladdr ? _nm_utils_inet6_ntop(lladdr, sbuf) : "");
+
+    self->notify_fcn(self, state, lladdr, self->user_data);
+
+    return G_SOURCE_CONTINUE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_generate_new_address(NML3IPv6LL *self, struct in6_addr *out_lladdr)
+{
+    struct in6_addr lladdr;
+
+    memset(&lladdr, 0, sizeof(struct in6_addr));
+    lladdr.s6_addr16[0] = htons(0xfe80u);
+
+    if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) {
+        if (self->addrgen.dad_counter > 0)
+            return FALSE;
+        self->addrgen.dad_counter++;
+        nm_utils_ipv6_addr_set_interface_identifier(&lladdr, &self->addrgen.token.iid);
+    } else {
+        /* RFC7217 says we MUST limit the number of retries, and it SHOULD try
+         * at least IDGEN_RETRIES times (that is, 3 times).
+         *
+         * 3 times seems really low. Instead, let's try 6 times. */
+        G_STATIC_ASSERT(NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES == 3);
+        if (self->addrgen.dad_counter >= NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES + 3)
+            return FALSE;
+
+        nm_utils_ipv6_addr_set_stable_privacy(self->addrgen.stable_type,
+                                              &lladdr,
+                                              self->addrgen.stable_privacy.ifname,
+                                              self->addrgen.stable_privacy.network_id,
+                                              self->addrgen.dad_counter++);
+    }
+
+    *out_lladdr = lladdr;
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static gboolean
+_pladdr_is_ll_failed(const NMPlatformIP6Address *addr)
+{
+    nm_assert(addr);
+    nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address));
+
+    return NM_FLAGS_ANY(addr->n_ifa_flags, IFA_F_DADFAILED | IFA_F_DEPRECATED);
+}
+
+static gboolean
+_pladdr_is_ll_tentative(const NMPlatformIP6Address *addr)
+{
+    nm_assert(addr);
+    nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address));
+    nm_assert(!_pladdr_is_ll_failed(addr));
+
+    return NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE)
+           && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC);
+}
+
+static const NMPlatformIP6Address *
+_pladdr_find_ll(NML3IPv6LL *self, gboolean *out_cur_addr_failed)
+{
+    NMDedupMultiIter            iter;
+    NMPLookup                   lookup;
+    const NMPlatformIP6Address *pladdr1 = NULL;
+    const NMPObject *           obj;
+    const NMPlatformIP6Address *pladdr_ready      = NULL;
+    const NMPlatformIP6Address *pladdr_tentative  = NULL;
+    gboolean                    cur_addr_check    = TRUE;
+    gboolean                    cur_addr_failed   = FALSE;
+    gboolean                    pladdr1_looked_up = FALSE;
+
+    nm_assert(!self->cur_lladdr_obj
+              || IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &self->cur_lladdr_obj->address));
+
+    *out_cur_addr_failed = FALSE;
+
+    if (self->state == NM_L3_IPV6LL_STATE_READY && self->cur_lladdr_obj) {
+        nm_assert(!_pladdr_is_ll_tentative(self->cur_lladdr_obj));
+        pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS(
+            nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self),
+                                   NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+                                   NMP_OBJECT_UP_CAST(self->cur_lladdr_obj)));
+        if (self->cur_lladdr_obj == pladdr1) {
+            /* Fast-path. We are ready and the cur_lladdr_obj is still in the cache. We
+             * got the result with a dictionary lookup without need to iterate over
+             * all addresses. */
+            return self->cur_lladdr_obj;
+        }
+        pladdr1_looked_up = TRUE;
+    }
+
+    if (!self->assume) {
+        /* We don't accept any suitable LL address, only he one we are waiting for.
+         * Let's do a dictionary lookup. */
+
+        if (IN6_IS_ADDR_LINKLOCAL(&self->cur_lladdr)) {
+            if (!pladdr1_looked_up) {
+                NMPObject needle;
+
+                nmp_object_stackinit_id_ip6_address(&needle,
+                                                    nm_l3_ipv6ll_get_ifindex(self),
+                                                    &self->cur_lladdr);
+                pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS(
+                    nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self),
+                                           NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+                                           &needle));
+            }
+            if (pladdr1) {
+                if (!_pladdr_is_ll_failed(pladdr1))
+                    return pladdr1;
+                *out_cur_addr_failed = TRUE;
+            }
+        } else
+            nm_assert(!pladdr1_looked_up);
+
+        return NULL;
+    }
+
+    if (!NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY))
+        cur_addr_check = FALSE;
+
+    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3_ipv6ll_get_ifindex(self));
+
+    nm_platform_iter_obj_for_each (&iter, nm_l3_ipv6ll_get_platform(self), &lookup, &obj) {
+        const NMPlatformIP6Address *pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
+
+        if (!IN6_IS_ADDR_LINKLOCAL(&pladdr->address))
+            continue;
+
+        if (_pladdr_is_ll_failed(pladdr)) {
+            if (cur_addr_check && IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address)) {
+                /* "pladdr" is the address we are currently doing DAD for. But it failed.
+                 * We need to recognize and report to the caller, to stop waiting for this
+                 * address. */
+                cur_addr_failed = TRUE;
+                cur_addr_check  = FALSE;
+            }
+            continue;
+        }
+
+        if (_pladdr_is_ll_tentative(pladdr)) {
+            if (!pladdr_tentative)
+                pladdr_tentative = pladdr;
+            else if (pladdr == self->cur_lladdr_obj)
+                pladdr_tentative = pladdr;
+            else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address))
+                pladdr_tentative = pladdr;
+            continue;
+        }
+
+        if (pladdr == self->cur_lladdr_obj) {
+            /* it doesn't get any better. We have our best address. */
+            return pladdr;
+        }
+        if (!pladdr_ready)
+            pladdr_ready = pladdr;
+        else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address))
+            pladdr_ready = pladdr;
+    }
+
+    *out_cur_addr_failed = cur_addr_failed;
+    return pladdr_ready ?: pladdr_tentative;
+}
+
+/*****************************************************************************/
+
+static void
+_lladdr_handle_changed(NML3IPv6LL *self)
+{
+    const NML3ConfigData *l3cd;
+    gboolean              changed = FALSE;
+
+    /* We register the l3cd with l3cfg to start DAD. That is different from
+     * NML3IPv4LL, where we use NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD. The difference
+     * is that for IPv6 we let kernel do DAD, so we need to actually configure the
+     * address. For IPv4, we can run ACD without configuring anything in kernel,
+     * and let the user decide how to proceed.
+     *
+     * Also in this case, we use the most graceful commit-type (NM_L3_CFG_COMMIT_TYPE_ASSUME),
+     * but for that to work, we also need NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE flag. */
+
+    l3cd = nm_l3_ipv6ll_get_l3cd(self);
+
+    if (l3cd) {
+        if (nm_l3cfg_add_config(self->l3cfg,
+                                L3CD_TAG(self),
+                                TRUE,
+                                l3cd,
+                                NM_L3CFG_CONFIG_PRIORITY_IPV6LL,
+                                0,
+                                0,
+                                NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4,
+                                NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
+                                0,
+                                0,
+                                NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                                NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                                NM_L3_ACD_DEFEND_TYPE_ALWAYS,
+                                0,
+                                NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE,
+                                NM_L3_CONFIG_MERGE_FLAGS_NONE))
+            changed = TRUE;
+    } else {
+        if (nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self)))
+            changed = TRUE;
+    }
+
+    self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg,
+                                                              l3cd ? NM_L3_CFG_COMMIT_TYPE_ASSUME
+                                                                   : NM_L3_CFG_COMMIT_TYPE_NONE,
+                                                              self->l3cfg_commit_handle,
+                                                              "ipv6ll");
+
+    if (changed)
+        nm_l3cfg_commit_on_idle_schedule(self->l3cfg, NM_L3_CFG_COMMIT_TYPE_AUTO);
+
+    if (!self->emit_changed_idle_source) {
+        _LOGT("schedule changed signal on idle");
+        self->emit_changed_idle_source = nm_g_idle_add_source(_emit_changed_on_idle_cb, self);
+    }
+}
+
+/*****************************************************************************/
+
+static gboolean
+_set_cur_lladdr(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr)
+{
+    gboolean changed = FALSE;
+
+    if (lladdr) {
+        nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr));
+        if (!IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, lladdr)) {
+            self->cur_lladdr = *lladdr;
+            nm_clear_l3cd(&self->l3cd);
+            changed = TRUE;
+        }
+    } else {
+        if (!nm_ip_addr_is_null(AF_INET6, &self->cur_lladdr)) {
+            nm_clear_l3cd(&self->l3cd);
+            self->cur_lladdr = nm_ip_addr_zero.addr6;
+            changed          = TRUE;
+        }
+        nm_assert(!self->l3cd);
+        nm_assert(!_state_has_lladdr(state));
+    }
+
+    if (self->state != state) {
+        if (!_state_has_lladdr(state))
+            nm_clear_l3cd(&self->l3cd);
+        self->state = state;
+        changed     = TRUE;
+    }
+
+    return changed;
+}
+
+static gboolean
+_set_cur_lladdr_obj(NML3IPv6LL *self, NML3IPv6LLState state, const NMPlatformIP6Address *lladdr_obj)
+{
+    nm_assert(lladdr_obj);
+    nm_assert(_state_has_lladdr(state));
+
+    nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, lladdr_obj);
+    return _set_cur_lladdr(self, state, &lladdr_obj->address);
+}
+
+static gboolean
+_set_cur_lladdr_bin(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr)
+{
+    nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, NULL);
+    return _set_cur_lladdr(self, state, lladdr);
+}
+
+static gboolean
+_wait_for_addr_timeout_cb(gpointer user_data)
+{
+    NML3IPv6LL *self = user_data;
+
+    nm_clear_g_source_inst(&self->wait_for_addr_source);
+
+    nm_assert(
+        NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_FAILED, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS));
+
+    _check(self);
+
+    return G_SOURCE_CONTINUE;
+}
+
+static void
+_check(NML3IPv6LL *self)
+{
+    const NMPlatformIP6Address *pladdr;
+    char                        sbuf[INET6_ADDRSTRLEN];
+    gboolean                    cur_addr_failed;
+    struct in6_addr             lladdr;
+
+    pladdr = _pladdr_find_ll(self, &cur_addr_failed);
+
+    if (pladdr) {
+        nm_clear_g_source_inst(&self->wait_for_addr_source);
+
+        if (_pladdr_is_ll_tentative(pladdr)) {
+            if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, pladdr)) {
+                _LOGT("changed: waiting for address %s to complete DAD",
+                      _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
+                _lladdr_handle_changed(self);
+            }
+            return;
+        }
+
+        if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_READY, pladdr)) {
+            _LOGT("changed: address %s is ready", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
+            _lladdr_handle_changed(self);
+        }
+        return;
+    }
+
+    if (self->cur_lladdr_obj || cur_addr_failed) {
+        /* we were doing DAD, but the address is no longer a suitable candidate.
+         * Prematurely abort DAD to generate a new address below. */
+        nm_assert(
+            NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY));
+        if (self->state == NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS)
+            _LOGT("changed: address %s did not complete DAD",
+                  _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
+        else {
+            _LOGT("changed: address %s is gone", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
+        }
+
+        /* reset the state here, so that we are sure that the following
+         * _set_cur_lladdr_bin() calls (below) will notice the change
+         * and trigger a _lladdr_handle_changed(). */
+        _set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_STARTING, NULL);
+        nm_clear_g_source_inst(&self->wait_for_addr_source);
+    } else if (self->wait_for_addr_source) {
+        /* we are waiting. Nothing to do for now. */
+        return;
+    }
+
+    if (!_generate_new_address(self, &lladdr)) {
+        /* our DAD counter expired. We reset it, and start a timer to retry
+         * and recover. */
+        self->addrgen.dad_counter = 0;
+        self->wait_for_addr_source =
+            nm_g_timeout_add_source(10000, _wait_for_addr_timeout_cb, self);
+        if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_FAILED, NULL)) {
+            _LOGW("changed: no IPv6 link local address to retry after Duplicate Address Detection "
+                  "failures (back off)");
+            _lladdr_handle_changed(self);
+        }
+        return;
+    }
+
+    /* we give NML3Cfg 2 seconds to configure the address on the interface. We
+     * thus very soon expect to see this address configured (and kernel started DAD).
+     * If that does not happen within timeout, we assume that this address failed DAD. */
+    self->wait_for_addr_source = nm_g_timeout_add_source(2000, _wait_for_addr_timeout_cb, self);
+    if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, &lladdr)) {
+        _LOGT("changed: starting DAD for address %s",
+              _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf));
+        _lladdr_handle_changed(self);
+    }
+    return;
+}
+
+/*****************************************************************************/
+
+static void
+_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv6LL *self)
+{
+    if (notify_data->notify_type == 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_IP6_ADDRESS)))
+            _check(self);
+        return;
+    }
+}
+
+/*****************************************************************************/
+
+static gboolean
+_starting_on_idle_cb(gpointer user_data)
+{
+    NML3IPv6LL *self = user_data;
+
+    nm_clear_g_source_inst(&self->starting_on_idle_source);
+
+    self->l3cfg_signal_notify_id =
+        g_signal_connect(self->l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self);
+
+    _check(self);
+
+    return G_SOURCE_CONTINUE;
+}
+
+/*****************************************************************************/
+
+NML3IPv6LL *
+_nm_l3_ipv6ll_new(NML3Cfg *                 l3cfg,
+                  gboolean                  assume,
+                  NMUtilsStableType         stable_type,
+                  const char *              ifname,
+                  const char *              network_id,
+                  const NMUtilsIPv6IfaceId *token_iid,
+                  NML3IPv6LLNotifyFcn       notify_fcn,
+                  gpointer                  user_data)
+{
+    NML3IPv6LL *self;
+
+    g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL);
+    g_return_val_if_fail(notify_fcn, NULL);
+    g_return_val_if_fail(
+        (stable_type == NM_UTILS_STABLE_TYPE_NONE && !ifname && !network_id && token_iid)
+            || (stable_type != NM_UTILS_STABLE_TYPE_NONE && ifname && network_id && !token_iid),
+        NULL);
+
+    self  = g_slice_new(NML3IPv6LL);
+    *self = (NML3IPv6LL){
+        .l3cfg                   = g_object_ref(l3cfg),
+        .notify_fcn              = notify_fcn,
+        .user_data               = user_data,
+        .state                   = NM_L3_IPV6LL_STATE_STARTING,
+        .starting_on_idle_source = nm_g_idle_add_source(_starting_on_idle_cb, self),
+        .l3cfg_signal_notify_id  = 0,
+        .cur_lladdr_obj          = NULL,
+        .cur_lladdr              = IN6ADDR_ANY_INIT,
+        .assume                  = assume,
+        .addrgen =
+            {
+                .stable_type = stable_type,
+                .dad_counter = 0,
+            },
+    };
+
+    if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) {
+        char sbuf_token[sizeof(self->addrgen.token.iid) * 3];
+
+        self->addrgen.token.iid = *token_iid;
+        _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d, token=%s%s",
+              NM_HASH_OBFUSCATE_PTR(l3cfg),
+              nm_l3cfg_get_ifindex(l3cfg),
+              nm_utils_bin2hexstr_full(&self->addrgen.token.iid,
+                                       sizeof(self->addrgen.token.iid),
+                                       ':',
+                                       FALSE,
+                                       sbuf_token),
+              self->assume ? ", assume" : "");
+    } else {
+        self->addrgen.stable_privacy.ifname     = g_strdup(ifname);
+        self->addrgen.stable_privacy.network_id = g_strdup(network_id);
+        _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT
+              ", ifindex=%d, stable-type=%u, ifname=%s, network_id=%s%s",
+              NM_HASH_OBFUSCATE_PTR(l3cfg),
+              nm_l3cfg_get_ifindex(l3cfg),
+              (unsigned) self->addrgen.stable_type,
+              self->addrgen.stable_privacy.ifname,
+              self->addrgen.stable_privacy.network_id,
+              self->assume ? ", assume" : "");
+    }
+
+    return self;
+}
+
+void
+nm_l3_ipv6ll_destroy(NML3IPv6LL *self)
+{
+    if (!self)
+        return;
+
+    _ASSERT(self);
+
+    _LOGT("finalize");
+
+    nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle));
+
+    nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self));
+
+    nm_clear_g_source_inst(&self->starting_on_idle_source);
+    nm_clear_g_source_inst(&self->wait_for_addr_source);
+    nm_clear_g_source_inst(&self->emit_changed_idle_source);
+    nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id);
+
+    g_clear_object(&self->l3cfg);
+
+    nm_clear_l3cd(&self->l3cd);
+
+    nm_clear_nmp_object_up_cast(&self->cur_lladdr_obj);
+
+    if (self->addrgen.stable_type != NM_UTILS_STABLE_TYPE_NONE) {
+        g_free((char *) self->addrgen.stable_privacy.ifname);
+        g_free((char *) self->addrgen.stable_privacy.network_id);
+    }
+
+    nm_g_slice_free(self);
+}
diff --git a/src/core/nm-l3-ipv6ll.h b/src/core/nm-l3-ipv6ll.h
new file mode 100644
index 00000000..770a4268
--- /dev/null
+++ b/src/core/nm-l3-ipv6ll.h
@@ -0,0 +1,112 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef __NM_L3_IPV6LL_H__
+#define __NM_L3_IPV6LL_H__
+
+#include "nm-l3cfg.h"
+#include "nm-core-utils.h"
+
+/*****************************************************************************/
+
+typedef struct _NML3IPv6LL NML3IPv6LL;
+
+typedef enum _nm_packed {
+    NM_L3_IPV6LL_STATE_STARTING,
+    NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
+    NM_L3_IPV6LL_STATE_READY,
+    NM_L3_IPV6LL_STATE_DAD_FAILED,
+
+    /* The following flags are not actually use by NML3IPv6LL. They exist for
+     * convenience of the users, to encode additional states. */
+
+    /* IPv6LL is disabled */
+    NM_L3_IPV6LL_STATE_NONE,
+
+    /* We want to do IPv6LL, but something is missing so we cannot even create
+     * a NML3IPv6LL instance. For example, no NML3Cfg instance or no interface name. */
+    NM_L3_IPV6LL_STATE_DEFUNCT,
+
+} NML3IPv6LLState;
+
+const char *nm_l3_ipv6ll_state_to_string(NML3IPv6LLState state);
+
+typedef void (*NML3IPv6LLNotifyFcn)(NML3IPv6LL *           ipv6ll,
+                                    NML3IPv6LLState        state,
+                                    const struct in6_addr *lladdr,
+                                    gpointer               user_data);
+
+static inline gboolean
+NM_IS_L3_IPV6LL(const NML3IPv6LL *self)
+{
+    nm_assert(!self || (NM_IS_L3CFG(*((NML3Cfg **) self))));
+    return !!self;
+}
+
+NML3IPv6LL *_nm_l3_ipv6ll_new(NML3Cfg *                 l3cfg,
+                              gboolean                  assume,
+                              NMUtilsStableType         stable_type,
+                              const char *              ifname,
+                              const char *              network_id,
+                              const NMUtilsIPv6IfaceId *token_iid,
+                              NML3IPv6LLNotifyFcn       notify_fcn,
+                              gpointer                  user_data);
+
+static inline NML3IPv6LL *
+nm_l3_ipv6ll_new_stable_privacy(NML3Cfg *           l3cfg,
+                                gboolean            assume,
+                                NMUtilsStableType   stable_type,
+                                const char *        ifname,
+                                const char *        network_id,
+                                NML3IPv6LLNotifyFcn notify_fcn,
+                                gpointer            user_data)
+{
+    nm_assert(stable_type != NM_UTILS_STABLE_TYPE_NONE);
+    return _nm_l3_ipv6ll_new(l3cfg,
+                             assume,
+                             stable_type,
+                             ifname,
+                             network_id,
+                             NULL,
+                             notify_fcn,
+                             user_data);
+}
+
+static inline NML3IPv6LL *
+nm_l3_ipv6ll_new_token(NML3Cfg *                 l3cfg,
+                       gboolean                  assume,
+                       const NMUtilsIPv6IfaceId *token_iid,
+                       NML3IPv6LLNotifyFcn       notify_fcn,
+                       gpointer                  user_data)
+{
+    return _nm_l3_ipv6ll_new(l3cfg,
+                             assume,
+                             NM_UTILS_STABLE_TYPE_NONE,
+                             NULL,
+                             NULL,
+                             token_iid,
+                             notify_fcn,
+                             user_data);
+}
+
+void nm_l3_ipv6ll_destroy(NML3IPv6LL *self);
+
+NM_AUTO_DEFINE_FCN0(NML3IPv6LL *, _nm_auto_destroy_l3ipv6ll, nm_l3_ipv6ll_destroy);
+#define nm_auto_destroy_l3ipv6ll nm_auto(_nm_auto_destroy_l3ipv6ll)
+
+/*****************************************************************************/
+
+NML3Cfg *nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self);
+
+int nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self);
+
+NMPlatform *nm_l3_ipv6ll_get_platform(NML3IPv6LL *self);
+
+/*****************************************************************************/
+
+NML3IPv6LLState nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr);
+
+const NML3ConfigData *nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self);
+
+/*****************************************************************************/
+
+#endif /* __NM_L3_IPV6LL_H__ */
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c
index 93e92673..aa56c13f 100644
--- a/src/core/nm-l3cfg.c
+++ b/src/core/nm-l3cfg.c
@@ -18,6 +18,17 @@
 
 /*****************************************************************************/
 
+#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000)
+
+/* When a ObjStateData becomes a "zombie", we aim to delete it from platform
+ * on the next commit (until it disappears from platform). But we might have
+ * a bug, so that we fail to delete the platform (for example, related to
+ * IPv6 multicast routes). We thus rate limit how often we try to do this,
+ * before giving up. */
+#define ZOMBIE_COUNT_START 5
+
+/*****************************************************************************/
+
 G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227);
 
 #define ACD_SUPPORTED_ETH_ALEN                  ETH_ALEN
@@ -97,6 +108,58 @@ typedef struct {
 
 G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
 
+typedef struct {
+    const NMPObject *obj;
+
+    /* Whether obj is currently in the platform cache or not.
+     * Since "obj" is the NMPObject from the merged NML3ConfigData,
+     * the object in platform has the same ID (but may otherwise not
+     * be identical). If this is not NULL, then currently the object
+     * is configured in kernel. */
+    const NMPObject *os_plobj;
+
+    CList os_lst;
+
+    /* If we have a timeout pending, we link the instance to
+     * self->priv.p->obj_state_temporary_not_available_lst_head. */
+    CList os_temporary_not_available_lst;
+
+    /* If a NMPObject is no longer to be configured (but was configured
+     * during a previous commit), then we need to remember it so that the
+     * next commit can delete the address/route in kernel. It becomes a zombie. */
+    CList os_zombie_lst;
+
+    /* We might want to configure "obj" in platform, but it's currently not possible.
+     * For example, certain IPv6 routes can only be added after the IPv6 address
+     * becomes non-tentative (*sigh*). In such a case, we need to remember that, and
+     * retry later. If this timestamp is set to a non-zero value, then it means
+     * we tried to configure the obj (at that timestamp) and failed, but we are
+     * waiting to retry.
+     *
+     * See also self->priv.p->obj_state_temporary_not_available_lst_head
+     * and self->priv.p->obj_state_temporary_not_available_timeout_source. */
+    gint64 os_temporary_not_available_timestamp_msec;
+
+    /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but
+     * now no longer), it needs to be deleted from platform. This ratelimits the time
+     * how often we try that. When the counter reaches zero, we forget about it. */
+    guint8 os_zombie_count;
+
+    /* whether we ever saw the object in platform. */
+    bool os_was_in_platform : 1;
+
+    /* Indicates whether NetworkManager actively tried to configure the object
+     * in platform once. */
+    bool os_nm_configured : 1;
+
+    /* This flag is only used temporarily to do a bulk update and
+     * clear all the ones that are no longer in used. */
+    bool os_dirty : 1;
+    bool os_tna_dirty : 1;
+} ObjStateData;
+
+G_STATIC_ASSERT(G_STRUCT_OFFSET(ObjStateData, obj) == 0);
+
 struct _NML3CfgCommitTypeHandle {
     CList             commit_type_lst;
     NML3CfgCommitType commit_type;
@@ -104,6 +167,7 @@ struct _NML3CfgCommitTypeHandle {
 
 typedef struct {
     const NML3ConfigData *l3cd;
+    NML3CfgConfigFlags    config_flags;
     NML3ConfigMergeFlags  merge_flags;
     union {
         struct {
@@ -126,6 +190,13 @@ typedef struct {
         };
         guint32 default_route_penalty_x[2];
     };
+    union {
+        struct {
+            int default_dns_priority_6;
+            int default_dns_priority_4;
+        };
+        int default_dns_priority_x[2];
+    };
     gconstpointer     tag_confdata;
     guint64           pseudo_timestamp_confdata;
     int               priority_confdata;
@@ -146,7 +217,6 @@ enum {
 static guint signals[LAST_SIGNAL] = {0};
 
 typedef struct _NML3CfgPrivate {
-    GArray *property_emit_list;
     GArray *l3_config_datas;
 
     NML3IPv4LL *ipv4ll;
@@ -157,9 +227,11 @@ typedef struct _NML3CfgPrivate {
 
     CList commit_type_lst_head;
 
-    GHashTable *routes_temporary_not_available_hash;
+    GHashTable *obj_state_hash;
 
-    GHashTable *externally_removed_objs_hash;
+    CList obj_state_lst_head;
+    CList obj_state_zombie_lst_head;
+    CList obj_state_temporary_not_available_lst_head;
 
     GHashTable *acd_ipv4_addresses_on_link;
 
@@ -181,41 +253,30 @@ typedef struct _NML3CfgPrivate {
 
     guint64 pseudo_timestamp_counter;
 
-    union {
-        struct {
-            guint externally_removed_objs_cnt_addresses_6;
-            guint externally_removed_objs_cnt_addresses_4;
-        };
-        guint externally_removed_objs_cnt_addresses_x[2];
-    };
+    GSource *obj_state_temporary_not_available_timeout_source;
 
-    union {
-        struct {
-            guint externally_removed_objs_cnt_routes_6;
-            guint externally_removed_objs_cnt_routes_4;
-        };
-        guint externally_removed_objs_cnt_routes_x[2];
-    };
+    NML3CfgCommitType commit_on_idle_type;
 
-    union {
-        struct {
-            GPtrArray *last_addresses_6;
-            GPtrArray *last_addresses_4;
-        };
-        GPtrArray *last_addresses_x[2];
-    };
+    gint8 commit_reentrant_count;
 
-    union {
-        struct {
-            GPtrArray *last_routes_6;
-            GPtrArray *last_routes_4;
-        };
-        GPtrArray *last_routes_x[2];
-    };
+    /* The value that was set before we touched the sysctl (this only is
+     * meaningful if "ip6_privacy_set" is true. At the end, we want to restore
+     * this value. */
+    NMSettingIP6ConfigPrivacy ip6_privacy_initial : 4;
 
-    guint routes_temporary_not_available_id;
+    /* The value that we set the last time. This is cached so that we don't
+     * repeatedly try to commit the same value. */
+    NMSettingIP6ConfigPrivacy ip6_privacy_set_before : 4;
 
-    gint8 commit_reentrant_count;
+    guint32 ndisc_retrans_timer_msec;
+    guint32 ndisc_reachable_time_msec;
+    int     ndisc_hop_limit;
+
+    /* Whether "self" set the ip6_privacy sysctl (and whether it needs to be reset). */
+    bool ip6_privacy_set : 1;
+    bool ndisc_reachable_time_msec_set : 1;
+    bool ndisc_retrans_timer_msec_set : 1;
+    bool ndisc_hop_limit_set : 1;
 
     bool commit_type_update_sticky : 1;
 
@@ -268,8 +329,6 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
 
 static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle);
 
-static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type);
-
 static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self);
 
 static gboolean _acd_has_valid_link(const NMPObject *obj,
@@ -305,6 +364,7 @@ static NM_UTILS_ENUM2STR_DEFINE(
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED, "l3cd-changed"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
+    NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, "pre-commit"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
     NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
                       "routes-temporary-not-available-expired"),
@@ -314,7 +374,7 @@ static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string,
                                 NML3AcdDefendType,
                                 NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ALWAYS, "always"),
                                 NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NEVER, "never"),
-                                NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NONE, "none"),
+                                NM_UTILS_ENUM2STR(_NM_L3_ACD_DEFEND_TYPE_NONE, "none"),
                                 NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), );
 
 static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd,
@@ -342,6 +402,17 @@ static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string,
 
 /*****************************************************************************/
 
+gboolean
+nm_l3cfg_is_vrf(const NML3Cfg *self)
+{
+    const NMPlatformLink *pllink;
+
+    pllink = nm_l3cfg_get_pllink(self, TRUE);
+    return pllink && pllink->type == NM_LINK_TYPE_VRF;
+}
+
+/*****************************************************************************/
+
 static const char *
 _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
                                  char *                      sbuf,
@@ -358,31 +429,29 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
     nm_assert(sbuf_size > 0);
 
     _l3_config_notify_type_to_string(notify_data->notify_type, s, l);
-    nm_utils_strbuf_seek_end(&s, &l);
+    nm_strbuf_seek_end(&s, &l);
 
     switch (notify_data->notify_type) {
     case NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED:
-        nm_utils_strbuf_append(
-            &s,
-            &l,
-            ", l3cd-old=%s",
-            NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_old, sbufobf));
-        nm_utils_strbuf_append(
-            &s,
-            &l,
-            ", l3cd-new=%s",
-            NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_new, sbufobf));
-        nm_utils_strbuf_append(&s, &l, ", commited=%d", notify_data->l3cd_changed.commited);
+        nm_strbuf_append(&s,
+                         &l,
+                         ", l3cd-old=%s",
+                         NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_old, sbufobf));
+        nm_strbuf_append(&s,
+                         &l,
+                         ", l3cd-new=%s",
+                         NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_new, sbufobf));
+        nm_strbuf_append(&s, &l, ", commited=%d", notify_data->l3cd_changed.commited);
         break;
     case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
-        nm_utils_strbuf_append(&s,
-                               &l,
-                               ", addr=%s, state=%s",
-                               _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr),
-                               _l3_acd_addr_state_to_string(notify_data->acd_event.info.state));
+        nm_strbuf_append(&s,
+                         &l,
+                         ", addr=%s, state=%s",
+                         _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr),
+                         _l3_acd_addr_state_to_string(notify_data->acd_event.info.state));
         break;
     case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
-        nm_utils_strbuf_append(
+        nm_strbuf_append(
             &s,
             &l,
             ", obj-type=%s, change=%s, obj=",
@@ -391,15 +460,15 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
         nmp_object_to_string(notify_data->platform_change.obj, NMP_OBJECT_TO_STRING_PUBLIC, s, l);
         break;
     case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
-        nm_utils_strbuf_append(&s,
-                               &l,
-                               ", obj-type-flags=0x%x",
-                               notify_data->platform_change_on_idle.obj_type_flags);
+        nm_strbuf_append(&s,
+                         &l,
+                         ", obj-type-flags=0x%x",
+                         notify_data->platform_change_on_idle.obj_type_flags);
         break;
     case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
         nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
         addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll);
-        nm_utils_strbuf_append(
+        nm_strbuf_append(
             &s,
             &l,
             ", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s",
@@ -555,157 +624,447 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp
 
 /*****************************************************************************/
 
-static guint *
-_l3cfg_externally_removed_objs_counter(NML3Cfg *self, NMPObjectType obj_type)
+#define nm_assert_obj_state(self, obj_state)                                                      \
+    G_STMT_START                                                                                  \
+    {                                                                                             \
+        if (NM_MORE_ASSERTS > 0) {                                                                \
+            const NML3Cfg *     _self      = (self);                                              \
+            const ObjStateData *_obj_state = (obj_state);                                         \
+                                                                                                  \
+            nm_assert(_obj_state);                                                                \
+            nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj),                             \
+                                NMP_OBJECT_TYPE_IP4_ADDRESS,                                      \
+                                NMP_OBJECT_TYPE_IP6_ADDRESS,                                      \
+                                NMP_OBJECT_TYPE_IP4_ROUTE,                                        \
+                                NMP_OBJECT_TYPE_IP6_ROUTE));                                      \
+            nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform);                   \
+            nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0)                \
+                      == c_list_is_empty(&_obj_state->os_temporary_not_available_lst));           \
+            if (_self) {                                                                          \
+                if (c_list_is_empty(&_obj_state->os_zombie_lst)) {                                \
+                    nm_assert(_self->priv.p->combined_l3cd_commited);                             \
+                                                                                                  \
+                    if (NM_MORE_ASSERTS > 5) {                                                    \
+                        nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head,             \
+                                                  &_obj_state->os_lst));                          \
+                        nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0)    \
+                                  || c_list_contains(                                             \
+                                      &_self->priv.p->obj_state_temporary_not_available_lst_head, \
+                                      &_obj_state->os_temporary_not_available_lst));              \
+                        nm_assert(_obj_state->os_plobj                                            \
+                                  == nm_platform_lookup_obj(_self->priv.platform,                 \
+                                                            NMP_CACHE_ID_TYPE_OBJECT_TYPE,        \
+                                                            _obj_state->obj));                    \
+                        nm_assert(                                                                \
+                            c_list_is_empty(&obj_state->os_zombie_lst)                            \
+                                ? (_obj_state->obj                                                \
+                                   == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj(  \
+                                       _self->priv.p->combined_l3cd_commited,                     \
+                                       _obj_state->obj)))                                         \
+                                : (!nm_l3_config_data_lookup_obj(                                 \
+                                    _self->priv.p->combined_l3cd_commited,                        \
+                                    _obj_state->obj)));                                           \
+                    }                                                                             \
+                }                                                                                 \
+            }                                                                                     \
+        }                                                                                         \
+    }                                                                                             \
+    G_STMT_END
+
+static gboolean
+_obj_state_data_get_assume_config_once(const ObjStateData *obj_state)
 {
-    switch (obj_type) {
-    case NMP_OBJECT_TYPE_IP4_ADDRESS:
-        return &self->priv.p->externally_removed_objs_cnt_addresses_4;
-    case NMP_OBJECT_TYPE_IP6_ADDRESS:
-        return &self->priv.p->externally_removed_objs_cnt_addresses_6;
-    case NMP_OBJECT_TYPE_IP4_ROUTE:
-        return &self->priv.p->externally_removed_objs_cnt_routes_4;
-    case NMP_OBJECT_TYPE_IP6_ROUTE:
-        return &self->priv.p->externally_removed_objs_cnt_routes_6;
-    default:
-        return nm_assert_unreachable_val(NULL);
-    }
+    nm_assert_obj_state(NULL, obj_state);
+
+    return nmp_object_get_assume_config_once(obj_state->obj);
+}
+
+static ObjStateData *
+_obj_state_data_new(const NMPObject *obj, const NMPObject *plobj)
+{
+    ObjStateData *obj_state;
+
+    obj_state  = g_slice_new(ObjStateData);
+    *obj_state = (ObjStateData){
+        .obj                            = nmp_object_ref(obj),
+        .os_plobj                       = nmp_object_ref(plobj),
+        .os_was_in_platform             = !!plobj,
+        .os_nm_configured               = FALSE,
+        .os_dirty                       = FALSE,
+        .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst),
+        .os_zombie_lst                  = C_LIST_INIT(obj_state->os_zombie_lst),
+    };
+    return obj_state;
 }
 
 static void
-_l3cfg_externally_removed_objs_drop(NML3Cfg *self)
+_obj_state_data_free(gpointer data)
 {
-    nm_assert(NM_IS_L3CFG(self));
+    ObjStateData *obj_state = data;
+
+    c_list_unlink_stale(&obj_state->os_lst);
+    c_list_unlink_stale(&obj_state->os_zombie_lst);
+    c_list_unlink_stale(&obj_state->os_temporary_not_available_lst);
+    nmp_object_unref(obj_state->obj);
+    nmp_object_unref(obj_state->os_plobj);
+    nm_g_slice_free(obj_state);
+}
+
+static const char *
+_obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_size)
+{
+    const char *buf0     = buf;
+    gint64      now_msec = 0;
+
+    nm_assert(buf);
+    nm_assert(buf_size > 0);
+    nm_assert_obj_state(NULL, obj_state);
+
+    nm_strbuf_append(&buf,
+                     &buf_size,
+                     "[" NM_HASH_OBFUSCATE_PTR_FMT ", %s, ",
+                     NM_HASH_OBFUSCATE_PTR(obj_state),
+                     NMP_OBJECT_GET_CLASS(obj_state->obj)->obj_type_name);
+
+    nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, buf, buf_size);
+    nm_strbuf_seek_end(&buf, &buf_size);
+    nm_strbuf_append_c(&buf, &buf_size, ']');
+
+    if (!c_list_is_empty(&obj_state->os_zombie_lst))
+        nm_strbuf_append(&buf, &buf_size, ", zombie[%u]", obj_state->os_zombie_count);
+
+    if (obj_state->os_nm_configured)
+        nm_strbuf_append_str(&buf, &buf_size, ", nm-configured");
+
+    if (obj_state->os_plobj) {
+        nm_assert(obj_state->os_was_in_platform);
+        nm_strbuf_append_str(&buf, &buf_size, ", in-platform");
+    } else if (obj_state->os_was_in_platform)
+        nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform");
+
+    if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+        nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+        nm_strbuf_append(
+            &buf,
+            &buf_size,
+            ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d",
+            (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000,
+            (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000));
+    }
+
+    return buf0;
+}
+
+static gboolean
+_obj_state_data_update(ObjStateData *obj_state, const NMPObject *obj)
+{
+    gboolean changed = FALSE;
+
+    nm_assert_obj_state(NULL, obj_state);
+    nm_assert(obj);
+    nm_assert(nmp_object_id_equal(obj_state->obj, obj));
+
+    obj_state->os_dirty = FALSE;
 
-    self->priv.p->externally_removed_objs_cnt_addresses_4 = 0;
-    self->priv.p->externally_removed_objs_cnt_addresses_6 = 0;
-    self->priv.p->externally_removed_objs_cnt_routes_4    = 0;
-    self->priv.p->externally_removed_objs_cnt_routes_6    = 0;
-    if (nm_g_hash_table_size(self->priv.p->externally_removed_objs_hash) > 0)
-        _LOGD("externally-removed: untrack all");
-    nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
+    if (obj_state->obj != obj) {
+        nm_auto_nmpobj const NMPObject *obj_old = NULL;
+
+        if (!nmp_object_equal(obj_state->obj, obj))
+            changed = TRUE;
+        obj_old        = g_steal_pointer(&obj_state->obj);
+        obj_state->obj = nmp_object_ref(obj);
+    }
+
+    if (!c_list_is_empty(&obj_state->os_zombie_lst)) {
+        c_list_unlink(&obj_state->os_zombie_lst);
+        changed = TRUE;
+    }
+
+    return changed;
 }
 
+/*****************************************************************************/
+
 static void
-_l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self)
+_obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboolean in_platform)
 {
-    GHashTableIter   h_iter;
-    const NMPObject *obj;
-    char             sbuf[sizeof(_nm_utils_to_string_buffer)];
+    char          sbuf[sizeof(_nm_utils_to_string_buffer)];
+    ObjStateData *obj_state;
 
     nm_assert(NM_IS_L3CFG(self));
+    nm_assert_is_bool(in_platform);
 
-    if (!self->priv.p->externally_removed_objs_hash)
+    nm_assert(
+        in_platform
+            ? (obj
+               == nm_platform_lookup_obj(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
+            : (!nm_platform_lookup_obj(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)));
+
+    obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj);
+    if (!obj_state)
         return;
 
-    if (!self->priv.p->combined_l3cd_commited) {
-        _l3cfg_externally_removed_objs_drop(self);
+    if (!in_platform)
+        obj = NULL;
+
+    if (obj_state->os_plobj == obj)
+        goto out;
+
+    if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) {
+        /* this is a zombie. We can forget about it.*/
+        nm_clear_nmp_object(&obj_state->os_plobj);
+        c_list_unlink(&obj_state->os_zombie_lst);
+        _LOGD("obj-state: zombie gone (untrack): %s",
+              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+        g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
         return;
     }
 
-    g_hash_table_iter_init(&h_iter, self->priv.p->externally_removed_objs_hash);
-    while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj, NULL)) {
-        if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) {
-            /* The object is no longer tracked in the configuration.
-             * The externally_removed_objs_hash is to prevent adding entires that were
-             * removed externally, so if we don't plan to add the entry, we no longer need to track
-             * it. */
-            _LOGD("externally-removed: untrack %s",
-                  nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
-            (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--;
-            g_hash_table_iter_remove(&h_iter);
-        }
+    nm_assert(c_list_is_empty(&obj_state->os_zombie_lst));
+
+    if (in_platform) {
+        nmp_object_ref_set(&obj_state->os_plobj, obj);
+        obj_state->os_was_in_platform = TRUE;
+        _LOGD("obj-state: appeared in platform: %s",
+              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+        goto out;
     }
+
+    nm_clear_nmp_object(&obj_state->os_plobj);
+    _LOGD("obj-state: remove from platform: %s",
+          _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+
+out:
+    nm_assert_obj_state(self, obj_state);
 }
 
 static void
-_l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboolean is_removed)
+_obj_states_update_all(NML3Cfg *self)
 {
-    char sbuf[1000];
+    static const NMPObjectType obj_types[] = {
+        NMP_OBJECT_TYPE_IP4_ADDRESS,
+        NMP_OBJECT_TYPE_IP6_ADDRESS,
+        NMP_OBJECT_TYPE_IP4_ROUTE,
+        NMP_OBJECT_TYPE_IP6_ROUTE,
+    };
+    char          sbuf[sizeof(_nm_utils_to_string_buffer)];
+    ObjStateData *obj_state;
+    int           i;
+    gboolean      any_dirty = FALSE;
 
     nm_assert(NM_IS_L3CFG(self));
 
-    if (!self->priv.p->combined_l3cd_commited)
-        return;
+    c_list_for_each_entry (obj_state, &self->priv.p->obj_state_lst_head, os_lst) {
+        if (!c_list_is_empty(&obj_state->os_zombie_lst)) {
+            /* we can ignore zombies. */
+            continue;
+        }
+        any_dirty           = TRUE;
+        obj_state->os_dirty = TRUE;
+    }
+
+    for (i = 0; i < (int) G_N_ELEMENTS(obj_types); i++) {
+        const NMPObjectType obj_type = obj_types[i];
+        NMDedupMultiIter    o_iter;
+        const NMPObject *   obj;
+
+        if (!self->priv.p->combined_l3cd_commited)
+            continue;
+
+        nm_l3_config_data_iter_obj_for_each (&o_iter,
+                                             self->priv.p->combined_l3cd_commited,
+                                             &obj,
+                                             obj_type) {
+            obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj);
+            if (!obj_state) {
+                obj_state =
+                    _obj_state_data_new(obj,
+                                        nm_platform_lookup_obj(self->priv.platform,
+                                                               NMP_CACHE_ID_TYPE_OBJECT_TYPE,
+                                                               obj));
+                c_list_link_tail(&self->priv.p->obj_state_lst_head, &obj_state->os_lst);
+                g_hash_table_add(self->priv.p->obj_state_hash, obj_state);
+                _LOGD("obj-state: track: %s",
+                      _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+                nm_assert_obj_state(self, obj_state);
+                continue;
+            }
 
-    if (!is_removed) {
-        /* the object is still (or again) present. It no longer gets hidden. */
-        if (self->priv.p->externally_removed_objs_hash) {
-            const NMPObject *obj2;
-            gpointer         x_val;
-
-            if (g_hash_table_steal_extended(self->priv.p->externally_removed_objs_hash,
-                                            obj,
-                                            (gpointer *) &obj2,
-                                            &x_val)) {
-                (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj2))))--;
-                _LOGD("externally-removed: untrack %s",
-                      nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
-                nmp_object_unref(obj2);
+            if (_obj_state_data_update(obj_state, obj)) {
+                _LOGD("obj-state: update: %s",
+                      _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
             }
+
+            nm_assert_obj_state(self, obj_state);
         }
-        return;
     }
 
-    if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) {
-        /* we don't care about this object, so there is nothing to hide hide */
-        return;
+    if (any_dirty) {
+        GHashTableIter h_iter;
+
+        g_hash_table_iter_init(&h_iter, self->priv.p->obj_state_hash);
+        while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_state, NULL)) {
+            if (!c_list_is_empty(&obj_state->os_zombie_lst))
+                continue;
+            if (!obj_state->os_dirty)
+                continue;
+
+            if (obj_state->os_plobj && obj_state->os_nm_configured) {
+                c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head,
+                                 &obj_state->os_zombie_lst);
+                obj_state->os_zombie_count = ZOMBIE_COUNT_START;
+                _LOGD("obj-state: now zombie: %s",
+                      _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+                continue;
+            }
+
+            _LOGD("obj-state: untrack: %s",
+                  _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            g_hash_table_iter_remove(&h_iter);
+        }
     }
+}
 
-    if (G_UNLIKELY(!self->priv.p->externally_removed_objs_hash)) {
-        self->priv.p->externally_removed_objs_hash =
-            g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
-                                  (GEqualFunc) nmp_object_id_equal,
-                                  (GDestroyNotify) nmp_object_unref,
-                                  NULL);
+typedef struct {
+    NML3Cfg *         self;
+    NML3CfgCommitType commit_type;
+} ObjStatesSyncFilterData;
+
+static gboolean
+_obj_states_sync_filter(/* const NMDedupMultiObj * */ gconstpointer o, gpointer user_data)
+{
+    char                           sbuf[sizeof(_nm_utils_to_string_buffer)];
+    const NMPObject *              obj              = o;
+    const ObjStatesSyncFilterData *sync_filter_data = user_data;
+    NMPObjectType                  obj_type;
+    ObjStateData *                 obj_state;
+
+    nm_assert(sync_filter_data);
+    nm_assert(NM_IS_L3CFG(sync_filter_data->self));
+
+    obj_type = NMP_OBJECT_GET_TYPE(obj);
+
+    if (obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS
+        && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->a_acd_not_ready)
+        return FALSE;
+
+    obj_state = g_hash_table_lookup(sync_filter_data->self->priv.p->obj_state_hash, &obj);
+
+    nm_assert_obj_state(sync_filter_data->self, obj_state);
+    nm_assert(obj_state->obj == obj);
+    nm_assert(c_list_is_empty(&obj_state->os_zombie_lst));
+
+    if (!obj_state->os_nm_configured) {
+        NML3Cfg *self;
+
+        if (sync_filter_data->commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME
+            && !_obj_state_data_get_assume_config_once(obj_state))
+            return FALSE;
+
+        obj_state->os_nm_configured = TRUE;
+
+        self = sync_filter_data->self;
+        _LOGD("obj-state: configure-first-time: %s",
+              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+        return TRUE;
     }
 
-    if (g_hash_table_add(self->priv.p->externally_removed_objs_hash,
-                         (gpointer) nmp_object_ref(obj))) {
-        (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))++;
-        _LOGD("externally-removed: track %s",
-              nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
+    if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+        /* we currently try to configure this address (but failed earlier).
+         * Definitely retry. */
+        return TRUE;
     }
+
+    if (!obj_state->os_plobj && sync_filter_data->commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY)
+        return FALSE;
+
+    return TRUE;
 }
 
 static void
-_l3cfg_externally_removed_objs_pickup(NML3Cfg *self, int addr_family)
+_obj_state_zombie_lst_get_prune_lists(NML3Cfg *   self,
+                                      int         addr_family,
+                                      GPtrArray **out_addresses_prune,
+                                      GPtrArray **out_routes_prune)
 {
-    const int        IS_IPv4 = NM_IS_IPv4(addr_family);
-    NMDedupMultiIter iter;
-    const NMPObject *obj;
+    const int           IS_IPv4          = NM_IS_IPv4(addr_family);
+    const NMPObjectType obj_type_route   = NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4);
+    const NMPObjectType obj_type_address = NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4);
+    char                sbuf[sizeof(_nm_utils_to_string_buffer)];
+    ObjStateData *      obj_state;
+    ObjStateData *      obj_state_safe;
 
-    if (!self->priv.p->combined_l3cd_commited)
-        return;
+    nm_assert(NM_IS_L3CFG(self));
+    nm_assert(out_addresses_prune && !*out_addresses_prune);
+    nm_assert(out_routes_prune && !*out_routes_prune);
 
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self->priv.p->combined_l3cd_commited,
-                                         &obj,
-                                         NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
-        if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
-            _l3cfg_externally_removed_objs_track(self, obj, TRUE);
-    }
-    nm_l3_config_data_iter_obj_for_each (&iter,
-                                         self->priv.p->combined_l3cd_commited,
-                                         &obj,
-                                         NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) {
-        if (!nm_platform_lookup_entry(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))
-            _l3cfg_externally_removed_objs_track(self, obj, TRUE);
+    c_list_for_each_entry_safe (obj_state,
+                                obj_state_safe,
+                                &self->priv.p->obj_state_zombie_lst_head,
+                                os_zombie_lst) {
+        NMPObjectType obj_type;
+        GPtrArray **  p_a;
+
+        nm_assert_obj_state(self, obj_state);
+        nm_assert(obj_state->os_zombie_count > 0);
+
+        obj_type = NMP_OBJECT_GET_TYPE(obj_state->obj);
+
+        if (obj_type == obj_type_route)
+            p_a = out_routes_prune;
+        else if (obj_type == obj_type_address)
+            p_a = out_addresses_prune;
+        else
+            continue;
+
+        if (!*p_a)
+            *p_a = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
+
+        g_ptr_array_add(*p_a, (gpointer) nmp_object_ref(obj_state->obj));
+
+        if (--obj_state->os_zombie_count == 0) {
+            _LOGD("obj-state: prune zombie (untrack): %s",
+                  _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
+            continue;
+        }
+        _LOGD("obj-state: prune zombie: %s",
+              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
     }
 }
 
-static gboolean
-_l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointer o,
-                                      gpointer                                    user_data)
+static void
+_obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family)
 {
-    const NMPObject *obj                          = o;
-    GHashTable *     externally_removed_objs_hash = user_data;
+    char          sbuf[sizeof(_nm_utils_to_string_buffer)];
+    ObjStateData *obj_state;
+    ObjStateData *obj_state_safe;
 
-    if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS
-        && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready)
-        return FALSE;
+    /* we call this during reapply. Then we delete all the routes/addresses
+     * that are configured, and not only the zombies.
+     *
+     * Still, we need to adjust the os_zombie_count and assume that we
+     * are going to drop them. */
+
+    c_list_for_each_entry_safe (obj_state,
+                                obj_state_safe,
+                                &self->priv.p->obj_state_zombie_lst_head,
+                                os_zombie_lst) {
+        nm_assert_obj_state(self, obj_state);
+        nm_assert(obj_state->os_zombie_count > 0);
+
+        if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) != addr_family)
+            continue;
 
-    return !nm_g_hash_table_contains(externally_removed_objs_hash, obj);
+        if (--obj_state->os_zombie_count == 0) {
+            _LOGD("obj-state: zombie pruned during reapply (untrack): %s",
+                  _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+            g_hash_table_remove(self->priv.p->obj_state_hash, obj_state);
+            continue;
+        }
+        _LOGD("obj-state: zombie pruned during reapply: %s",
+              _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)));
+    }
 }
 
 /*****************************************************************************/
@@ -810,11 +1169,6 @@ _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags)
     _nm_l3cfg_emit_signal_notify(self, &notify_data);
 
     _nm_l3cfg_emit_signal_notify_acd_event_all(self);
-
-    if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ROUTE)))
-        _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE);
-    if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ROUTE)))
-        _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE);
 }
 
 void
@@ -847,7 +1201,7 @@ _nm_l3cfg_notify_platform_change(NML3Cfg *                  self,
     case NMP_OBJECT_TYPE_IP6_ADDRESS:
     case NMP_OBJECT_TYPE_IP4_ROUTE:
     case NMP_OBJECT_TYPE_IP6_ROUTE:
-        _l3cfg_externally_removed_objs_track(self, obj, change_type == NM_PLATFORM_SIGNAL_REMOVED);
+        _obj_states_externally_removed_track(self, obj, change_type != NM_PLATFORM_SIGNAL_REMOVED);
     default:
         break;
     }
@@ -864,146 +1218,6 @@ _nm_l3cfg_notify_platform_change(NML3Cfg *                  self,
 
 /*****************************************************************************/
 
-typedef struct {
-    GObject *               target_obj;
-    const GParamSpec *      target_property;
-    NML3CfgPropertyEmitType emit_type;
-} PropertyEmitData;
-
-static void
-_property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type)
-{
-    gs_free PropertyEmitData *collected_heap = NULL;
-    PropertyEmitData *        collected      = NULL;
-    PropertyEmitData *        emit_data;
-    guint                     num;
-    guint                     i;
-    guint                     j;
-
-    if (!self->priv.p->property_emit_list)
-        return;
-
-    num       = 0;
-    emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0);
-    for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
-        if (emit_data->emit_type == emit_type) {
-            collected = emit_data;
-            num++;
-        }
-    }
-
-    if (num == 0)
-        return;
-
-    if (num == 1) {
-        g_object_notify_by_pspec(collected->target_obj, (GParamSpec *) collected->target_property);
-        return;
-    }
-
-    if (num < 300u / sizeof(*collected))
-        collected = g_alloca(sizeof(PropertyEmitData) * num);
-    else {
-        collected_heap = g_new(PropertyEmitData, num);
-        collected      = collected_heap;
-    }
-
-    emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0);
-    for (i = 0, j = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
-        if (emit_data->emit_type == emit_type) {
-            collected[j++] = *emit_data;
-            g_object_ref(collected->target_obj);
-        }
-    }
-
-    nm_assert(j == num);
-
-    for (i = 0; i < num; i++) {
-        g_object_notify_by_pspec(collected[i].target_obj,
-                                 (GParamSpec *) collected[i].target_property);
-        if (i > 0)
-            g_object_unref(collected[i].target_obj);
-    }
-}
-
-void
-nm_l3cfg_property_emit_register(NML3Cfg *               self,
-                                GObject *               target_obj,
-                                const GParamSpec *      target_property,
-                                NML3CfgPropertyEmitType emit_type)
-{
-    PropertyEmitData *emit_data;
-    guint             i;
-
-    nm_assert(NM_IS_L3CFG(self));
-    nm_assert(G_IS_OBJECT(target_obj));
-    nm_assert(target_property);
-    nm_assert(NM_IN_SET(emit_type,
-                        NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE,
-                        NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE));
-    nm_assert(target_property
-              == nm_g_object_class_find_property_from_gtype(G_OBJECT_TYPE(target_obj),
-                                                            target_property->name));
-
-    if (!self->priv.p->property_emit_list)
-        self->priv.p->property_emit_list = g_array_new(FALSE, FALSE, sizeof(PropertyEmitData));
-    else {
-        emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, 0);
-        for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
-            if (emit_data->target_obj != target_obj
-                || emit_data->target_property != target_property)
-                continue;
-            nm_assert(emit_data->emit_type == emit_type);
-            emit_data->emit_type = emit_type;
-            return;
-        }
-    }
-
-    emit_data  = nm_g_array_append_new(self->priv.p->property_emit_list, PropertyEmitData);
-    *emit_data = (PropertyEmitData){
-        .target_obj      = target_obj,
-        .target_property = target_property,
-        .emit_type       = emit_type,
-    };
-}
-
-void
-nm_l3cfg_property_emit_unregister(NML3Cfg *         self,
-                                  GObject *         target_obj,
-                                  const GParamSpec *target_property)
-{
-    PropertyEmitData *emit_data;
-    guint             i;
-
-    nm_assert(NM_IS_L3CFG(self));
-    nm_assert(G_IS_OBJECT(target_obj));
-    nm_assert(!target_property
-              || target_property
-                     == nm_g_object_class_find_property_from_gtype(G_OBJECT_TYPE(target_obj),
-                                                                   target_property->name));
-
-    if (!self->priv.p->property_emit_list)
-        return;
-
-    for (i = self->priv.p->property_emit_list->len; i > 0; i--) {
-        emit_data = &g_array_index(self->priv.p->property_emit_list, PropertyEmitData, i);
-
-        if (emit_data->target_obj != target_obj)
-            continue;
-        if (target_property && emit_data->target_property != target_property)
-            continue;
-
-        g_array_remove_index_fast(self->priv.p->property_emit_list, i);
-
-        if (target_property) {
-            /* if a target-property is given, we don't have another entry in
-             * the list. */
-            return;
-        }
-    }
-}
-
-/*****************************************************************************/
-
 gboolean
 nm_l3cfg_get_acd_is_pending(NML3Cfg *self)
 {
@@ -1012,12 +1226,6 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self)
     return self->priv.p->acd_is_pending;
 }
 
-static gboolean
-_acd_track_data_is_not_dirty(const NML3AcdAddrTrackInfo *acd_track)
-{
-    return acd_track && !acd_track->_priv.acd_dirty_track;
-}
-
 static void
 _acd_track_data_clear(NML3AcdAddrTrackInfo *acd_track)
 {
@@ -1044,7 +1252,7 @@ _acd_data_collect_tracks_data(const AcdData *    acd_data,
                               guint32 *          out_best_acd_timeout_msec,
                               NML3AcdDefendType *out_best_acd_defend_type)
 {
-    NML3AcdDefendType best_acd_defend_type  = NM_L3_ACD_DEFEND_TYPE_NONE;
+    NML3AcdDefendType best_acd_defend_type  = _NM_L3_ACD_DEFEND_TYPE_NONE;
     guint32           best_acd_timeout_msec = G_MAXUINT32;
     guint             n                     = 0;
     guint             i;
@@ -1063,7 +1271,7 @@ _acd_data_collect_tracks_data(const AcdData *    acd_data,
             best_acd_defend_type = acd_track->_priv.acd_defend_type_track;
     }
 
-    nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE);
+    nm_assert(n == 0 || best_acd_defend_type > _NM_L3_ACD_DEFEND_TYPE_NONE);
     nm_assert(best_acd_defend_type <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
 
     NM_SET_OUT(out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u);
@@ -1136,9 +1344,9 @@ _l3_acd_nacd_event_down_timeout_cb(gpointer user_data)
 static gboolean
 _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
 {
-    NML3Cfg *self    = user_data;
-    gboolean success = FALSE;
-    int      r;
+    gs_unref_object NML3Cfg *self    = g_object_ref(user_data);
+    gboolean                 success = FALSE;
+    int                      r;
 
     nm_assert(NM_IS_L3CFG(self));
     nm_assert(self->priv.p->nacd);
@@ -1155,6 +1363,13 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
         AcdData *          acd_data;
         NAcdEvent *        event;
 
+        if (!self->priv.p->nacd) {
+            /* In the loop we emit signals, where *anything* might happen.
+             * Check that we still have the nacd instance. */
+            success = TRUE;
+            goto out;
+        }
+
         r = n_acd_pop_event(self->priv.p->nacd, &event);
         if (r) {
             _LOGT("acd: pop-event failed with error %d", r);
@@ -1286,7 +1501,7 @@ _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_d
     switch (start_timer) {
     case NM_TERNARY_FALSE:
         _l3_changed_configs_set_dirty(self);
-        nm_l3cfg_commit_on_idle_schedule(self);
+        nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
         break;
     case NM_TERNARY_TRUE:
         self->priv.p->nacd_instance_ensure_retry =
@@ -1527,8 +1742,8 @@ _l3_acd_data_add(NML3Cfg *             self,
             .n_track_infos_alloc       = 0,
             .acd_event_notify_lst      = C_LIST_INIT(acd_data->acd_event_notify_lst),
             .probing_timestamp_msec    = 0,
-            .acd_defend_type_desired   = NM_L3_ACD_DEFEND_TYPE_NONE,
-            .acd_defend_type_current   = NM_L3_ACD_DEFEND_TYPE_NONE,
+            .acd_defend_type_desired   = _NM_L3_ACD_DEFEND_TYPE_NONE,
+            .acd_defend_type_current   = _NM_L3_ACD_DEFEND_TYPE_NONE,
             .acd_defend_type_is_active = FALSE,
         };
         c_list_link_tail(&self->priv.p->acd_lst_head, &acd_data->acd_lst);
@@ -1794,7 +2009,7 @@ _nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg *        self,
         /* The availability of an address just changed (and we are instructed to
          * trigger a new commit). Do it. */
         _l3_changed_configs_set_dirty(self);
-        nm_l3cfg_commit_on_idle_schedule(self);
+        nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO);
     }
 }
 
@@ -2464,7 +2679,7 @@ handle_start_defending:
                                       ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
                                       ACD_STATE_CHANGE_MODE_POST_COMMIT));
 
-    nm_assert(acd_data->acd_defend_type_desired > NM_L3_ACD_DEFEND_TYPE_NONE);
+    nm_assert(acd_data->acd_defend_type_desired > _NM_L3_ACD_DEFEND_TYPE_NONE);
     nm_assert(acd_data->acd_defend_type_desired <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
 
     if (acd_data->acd_defend_type_desired != acd_data->acd_defend_type_current) {
@@ -2570,33 +2785,163 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr)
 
 /*****************************************************************************/
 
+gboolean
+nm_l3cfg_check_ready(NML3Cfg *              self,
+                     const NML3ConfigData * l3cd,
+                     int                    addr_family,
+                     NML3CfgCheckReadyFlags flags,
+                     gboolean *             acd_used)
+{
+    NMDedupMultiIter iter;
+    const NMPObject *obj;
+
+    nm_assert(NM_IS_L3CFG(self));
+    nm_assert_addr_family_or_unspec(addr_family);
+
+    NM_SET_OUT(acd_used, FALSE);
+
+    if (!l3cd)
+        return TRUE;
+
+    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)
+        && NM_FLAGS_HAS(flags, NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY)) {
+        gboolean pending = FALSE;
+
+        nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) {
+            const NML3AcdAddrInfo *addr_info;
+
+            addr_info = nm_l3cfg_get_acd_addr_info(self, NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address);
+            if (!addr_info) {
+                /* We don't track the this address? That's odd. Not ready. */
+                pending = TRUE;
+            } else {
+                if (addr_info->state <= NM_L3_ACD_ADDR_STATE_PROBING) {
+                    /* Still probing. Not ready. */
+                    pending = TRUE;
+                } else if (addr_info->state == NM_L3_ACD_ADDR_STATE_USED) {
+                    NM_SET_OUT(acd_used, TRUE);
+                }
+            }
+            /* we only care that we don't have ACD still pending. Otherwise we are ready,
+             * including if we have no addr_info about this address or the address is in use. */
+        }
+        if (pending)
+            return FALSE;
+    }
+
+    if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)
+        && NM_FLAGS_HAS(flags, NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY)) {
+        nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP6_ADDRESS) {
+            ObjStateData *obj_state;
+
+            obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj);
+
+            if (!obj_state) {
+                /* Hm, we don't track this object? That is odd. Not ready. */
+                return FALSE;
+            }
+
+            if (!obj_state->os_nm_configured && !obj_state->os_plobj) {
+                /* We didn't (yet) configure this address and it also is not in platform.
+                 * Not ready. */
+                return FALSE;
+            }
+
+            if (obj_state->os_plobj
+                && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP6_ADDRESS(obj_state->os_plobj)->n_ifa_flags,
+                                IFA_F_TENTATIVE)) {
+                /* The address is configured in kernel, but still tentative. Not ready. */
+                return FALSE;
+            }
+
+            /* This address is ready. Even if it is not (not anymore) configured in kernel (as
+             * indicated by obj_state->os_plobj). We apparently did configure it once, and
+             * it's no longer tentative. This address are good. */
+        }
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
 static gboolean
 _l3_commit_on_idle_cb(gpointer user_data)
 {
-    NML3Cfg *self = user_data;
+    _nm_unused gs_unref_object NML3Cfg *self_keep_alive = NULL;
+    NML3Cfg *                           self            = user_data;
+    NML3CfgCommitType                   commit_type;
+
+    commit_type = self->priv.p->commit_on_idle_type;
+
+    if (nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source))
+        self_keep_alive = self;
+    else
+        nm_assert_not_reached();
 
-    nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
+    self->priv.p->commit_on_idle_type = NM_L3_CFG_COMMIT_TYPE_AUTO;
 
-    _LOGT("commit on idle");
-    _l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE);
+    _l3_commit(self, commit_type, TRUE);
     return G_SOURCE_REMOVE;
 }
 
+/* DOC(l3cfg:commit-type):
+ *
+ * Usually we don't want to call the synchronous nm_l3cfg_commit(), because
+ * that has side effects and might not be safe to do (depending on the current
+ * circumstances in which commit is called). The usually proper thing to do
+ * is schedule a commit on an idle handler. Use this function.
+ *
+ * During commit, the actually used commit-type (that is, the level of "how much"
+ * will be synced) is determined by users who register their desired commit
+ * type via nm_l3cfg_commit_type_register(), where always the "maxium" is used.
+ *
+ * nm_l3cfg_commit() and nm_l3cfg_commit_on_idle_schedule() also accept an additional
+ * commit_type argument. This acts like a one-shot registration.
+ */
 gboolean
-nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self)
+nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self, NML3CfgCommitType commit_type)
 {
+    char sbuf_commit_type[50];
+
     nm_assert(NM_IS_L3CFG(self));
+    nm_assert(NM_IN_SET(commit_type,
+                        NM_L3_CFG_COMMIT_TYPE_AUTO,
+                        NM_L3_CFG_COMMIT_TYPE_ASSUME,
+                        NM_L3_CFG_COMMIT_TYPE_UPDATE,
+                        NM_L3_CFG_COMMIT_TYPE_REAPPLY));
 
-    if (self->priv.p->commit_on_idle_source)
+    if (self->priv.p->commit_on_idle_source) {
+        if (self->priv.p->commit_on_idle_type < commit_type) {
+            /* For multiple calls, we collect the maximum "commit-type". */
+            _LOGT("commit on idle (scheduled) (update to %s)",
+                  _l3_cfg_commit_type_to_string(commit_type,
+                                                sbuf_commit_type,
+                                                sizeof(sbuf_commit_type)));
+            self->priv.p->commit_on_idle_type = commit_type;
+        }
         return FALSE;
+    }
+
+    _LOGT("commit on idle (scheduled) (%s)",
+          _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type)));
+    self->priv.p->commit_on_idle_source = nm_g_idle_add_source(_l3_commit_on_idle_cb, self);
+    self->priv.p->commit_on_idle_type   = commit_type;
+
+    /* While we have an idle update scheduled, we need to keep the instance alive. */
+    g_object_ref(self);
 
-    _LOGT("commit on idle (scheduled)");
-    self->priv.p->commit_on_idle_source =
-        nm_g_idle_source_new(G_PRIORITY_DEFAULT, _l3_commit_on_idle_cb, self, NULL);
-    g_source_attach(self->priv.p->commit_on_idle_source, NULL);
     return TRUE;
 }
 
+gboolean
+nm_l3cfg_commit_on_idle_is_scheduled(NML3Cfg *self)
+{
+    nm_assert(NM_IS_L3CFG(self));
+
+    return !!(self->priv.p->commit_on_idle_source);
+}
+
 /*****************************************************************************/
 
 #define _l3_config_datas_at(l3_config_datas, idx) \
@@ -2695,8 +3040,11 @@ nm_l3cfg_add_config(NML3Cfg *             self,
                     guint32               default_route_metric_6,
                     guint32               default_route_penalty_4,
                     guint32               default_route_penalty_6,
+                    int                   default_dns_priority_4,
+                    int                   default_dns_priority_6,
                     NML3AcdDefendType     acd_defend_type,
                     guint32               acd_timeout_msec,
+                    NML3CfgConfigFlags    config_flags,
                     NML3ConfigMergeFlags  merge_flags)
 {
     L3ConfigData *l3_config_data;
@@ -2761,6 +3109,7 @@ nm_l3cfg_add_config(NML3Cfg *             self,
         *l3_config_data = (L3ConfigData){
             .tag_confdata              = tag,
             .l3cd                      = nm_l3_config_data_ref_and_seal(l3cd),
+            .config_flags              = config_flags,
             .merge_flags               = merge_flags,
             .default_route_table_4     = default_route_table_4,
             .default_route_table_6     = default_route_table_6,
@@ -2768,6 +3117,8 @@ nm_l3cfg_add_config(NML3Cfg *             self,
             .default_route_metric_6    = default_route_metric_6,
             .default_route_penalty_4   = default_route_penalty_4,
             .default_route_penalty_6   = default_route_penalty_6,
+            .default_dns_priority_4    = default_dns_priority_4,
+            .default_dns_priority_6    = default_dns_priority_6,
             .acd_defend_type_confdata  = acd_defend_type,
             .acd_timeout_msec_confdata = acd_timeout_msec,
             .priority_confdata         = priority,
@@ -2784,6 +3135,10 @@ nm_l3cfg_add_config(NML3Cfg *             self,
             l3_config_data->priority_confdata = priority;
             changed                           = TRUE;
         }
+        if (l3_config_data->config_flags != config_flags) {
+            l3_config_data->config_flags = config_flags;
+            changed                      = TRUE;
+        }
         if (l3_config_data->merge_flags != merge_flags) {
             l3_config_data->merge_flags = merge_flags;
             changed                     = TRUE;
@@ -2812,6 +3167,14 @@ nm_l3cfg_add_config(NML3Cfg *             self,
             l3_config_data->default_route_penalty_6 = default_route_penalty_6;
             changed                                 = TRUE;
         }
+        if (l3_config_data->default_dns_priority_4 != default_dns_priority_4) {
+            l3_config_data->default_dns_priority_4 = default_dns_priority_4;
+            changed                                = TRUE;
+        }
+        if (l3_config_data->default_dns_priority_6 != default_dns_priority_6) {
+            l3_config_data->default_dns_priority_6 = default_dns_priority_6;
+            changed                                = TRUE;
+        }
         if (l3_config_data->acd_defend_type_confdata != acd_defend_type) {
             l3_config_data->acd_defend_type_confdata = acd_defend_type;
             changed                                  = TRUE;
@@ -2879,17 +3242,23 @@ _l3cfg_remove_config(NML3Cfg *             self,
 }
 
 gboolean
-nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg)
+nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *l3cd)
 {
-    nm_assert(ifcfg);
+    nm_assert(l3cd);
+
+    return _l3cfg_remove_config(self, tag, FALSE, l3cd);
+}
 
-    return _l3cfg_remove_config(self, tag, FALSE, ifcfg);
+gboolean
+nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag)
+{
+    return _l3cfg_remove_config(self, tag, FALSE, NULL);
 }
 
 gboolean
-nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty)
+nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag)
 {
-    return _l3cfg_remove_config(self, tag, only_dirty, NULL);
+    return _l3cfg_remove_config(self, tag, TRUE, NULL);
 }
 
 /*****************************************************************************/
@@ -2897,13 +3266,15 @@ nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty
 typedef struct {
     NML3Cfg *     self;
     gconstpointer tag;
+    bool          assume_config_once;
+    bool          to_commit;
 } L3ConfigMergeHookAddObjData;
 
 static gboolean
-_l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
-                     const NMPObject *     obj,
-                     NMTernary *           out_ip4acd_not_ready,
-                     gpointer              user_data)
+_l3_hook_add_obj_cb(const NML3ConfigData *     l3cd,
+                    const NMPObject *          obj,
+                    NML3ConfigMergeHookResult *hook_result,
+                    gpointer                   user_data)
 {
     const L3ConfigMergeHookAddObjData *hook_data = user_data;
     NML3Cfg *                          self      = hook_data->self;
@@ -2911,40 +3282,68 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
     in_addr_t                          addr;
     gboolean                           acd_bad = FALSE;
 
-    nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT);
+    nm_assert(obj);
+    nm_assert(hook_result);
+    nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT);
+    nm_assert(hook_result->assume_config_once == NM_OPTION_BOOL_DEFAULT);
 
-    if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS)
-        return TRUE;
+    hook_result->assume_config_once = hook_data->assume_config_once;
 
-    addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
+    switch (NMP_OBJECT_GET_TYPE(obj)) {
+    case NMP_OBJECT_TYPE_IP4_ADDRESS:
 
-    if (ACD_ADDR_SKIP(addr))
-        goto out;
+        addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
 
-    acd_data = _l3_acd_data_find(self, addr);
+        if (ACD_ADDR_SKIP(addr))
+            goto out_ip4_address;
 
-    if (!acd_data) {
-        /* we don't yet track an ACD state for this address. That can only
-         * happend during _l3cfg_update_combined_config() with !to_commit,
-         * where we didn't update the ACD state.
-         *
-         * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE)
-         * won't consider IPv4 addresses ready, that have no known ACD state yet. */
-        nm_assert(self->priv.p->changed_configs_acd_state);
-        acd_bad = TRUE;
-        goto out;
-    }
+        acd_data = _l3_acd_data_find(self, addr);
 
-    nm_assert(
-        _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag)));
-    if (!NM_IN_SET(acd_data->info.state,
-                   NM_L3_ACD_ADDR_STATE_READY,
-                   NM_L3_ACD_ADDR_STATE_DEFENDING))
-        acd_bad = TRUE;
+        if (!hook_data->to_commit) {
+            nm_assert(self->priv.p->changed_configs_acd_state);
+            /* We don't do an actual commit in _l3cfg_update_combined_config(). That means our acd-data
+             * is not up to date. Check whether we have no acd_data ready, and if not, consider the address
+             * as not ready. It cannot be ready until the next commit starts ACD. */
+            if (!acd_data) {
+                acd_bad = TRUE;
+                goto out_ip4_address;
+            }
+            nm_assert(({
+                NML3AcdAddrTrackInfo *_ti =
+                    _acd_data_find_track(acd_data, l3cd, obj, hook_data->tag);
 
-out:
-    *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE;
-    return TRUE;
+                !_ti || _ti->_priv.acd_dirty_track;
+            }));
+        } else {
+            /* If we commit, we called _l3_acd_data_add_all(), thus our acd_data must be present
+             * and not dirty. */
+            nm_assert(({
+                NML3AcdAddrTrackInfo *_ti =
+                    _acd_data_find_track(acd_data, l3cd, obj, hook_data->tag);
+
+                _ti && !_ti->_priv.acd_dirty_track;
+            }));
+        }
+
+        if (!NM_IN_SET(acd_data->info.state,
+                       NM_L3_ACD_ADDR_STATE_READY,
+                       NM_L3_ACD_ADDR_STATE_DEFENDING)) {
+            acd_bad = TRUE;
+            goto out_ip4_address;
+        }
+
+out_ip4_address:
+        hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE;
+        return TRUE;
+
+    default:
+        nm_assert_not_reached();
+        /* fall-through */
+    case NMP_OBJECT_TYPE_IP6_ADDRESS:
+    case NMP_OBJECT_TYPE_IP4_ROUTE:
+    case NMP_OBJECT_TYPE_IP6_ROUTE:
+        return TRUE;
+    }
 }
 
 static void
@@ -3007,29 +3406,54 @@ _l3cfg_update_combined_config(NML3Cfg *              self,
 
     if (l3_config_datas_len > 0) {
         L3ConfigMergeHookAddObjData hook_data = {
-            .self = self,
+            .self      = self,
+            .to_commit = to_commit,
         };
 
         l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(self->priv.platform),
-                                     self->priv.ifindex);
+                                     self->priv.ifindex,
+                                     NM_IP_CONFIG_SOURCE_UNKNOWN);
 
         for (i = 0; i < l3_config_datas_len; i++) {
             const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
 
-            if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
+            if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD))
                 continue;
 
             hook_data.tag = l3cd_data->tag_confdata;
+            hook_data.assume_config_once =
+                NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE);
+
             nm_l3_config_data_merge(l3cd,
                                     l3cd_data->l3cd,
                                     l3cd_data->merge_flags,
                                     l3cd_data->default_route_table_x,
                                     l3cd_data->default_route_metric_x,
                                     l3cd_data->default_route_penalty_x,
-                                    _l3_hook_add_addr_cb,
+                                    l3cd_data->default_dns_priority_x,
+                                    _l3_hook_add_obj_cb,
                                     &hook_data);
         }
 
+        for (i = 0; i < l3_config_datas_len; i++) {
+            const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
+            int                 IS_IPv4;
+
+            if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD))
+                continue;
+
+            for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) {
+                nm_l3_config_data_add_dependent_device_routes(
+                    l3cd,
+                    IS_IPv4 ? AF_INET : AF_INET6,
+                    l3cd_data->default_route_table_x[IS_IPv4],
+                    l3cd_data->default_route_metric_x[IS_IPv4],
+                    l3cd_data->l3cd);
+            }
+        }
+
+        nm_l3_config_data_add_dependent_onlink_routes(l3cd, AF_UNSPEC);
+
         nm_assert(l3cd);
         nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
 
@@ -3060,6 +3484,8 @@ out:
             nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged);
         commited_changed = TRUE;
 
+        _obj_states_update_all(self);
+
         _nm_l3cfg_emit_signal_notify_l3cd_changed(self,
                                                   l3cd_commited_old,
                                                   self->priv.p->combined_l3cd_commited,
@@ -3095,75 +3521,45 @@ out:
 
 /*****************************************************************************/
 
-typedef struct {
-    const NMPObject *obj;
-    gint64           timestamp_msec;
-    bool             dirty;
-} RoutesTemporaryNotAvailableData;
-
-static void
-_routes_temporary_not_available_data_free(gpointer user_data)
-{
-    RoutesTemporaryNotAvailableData *data = user_data;
-
-    nmp_object_unref(data->obj);
-    nm_g_slice_free(data);
-}
-
-#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000)
-
 static gboolean
 _routes_temporary_not_available_timeout(gpointer user_data)
 {
-    RoutesTemporaryNotAvailableData *data;
-    NML3Cfg *                        self = NM_L3CFG(user_data);
-    GHashTableIter                   iter;
-    gint64                           expiry_threshold_msec;
-    gboolean                         any_expired = FALSE;
-    gint64                           now_msec;
-    gint64                           oldest_msec;
+    NML3Cfg *     self = NM_L3CFG(user_data);
+    ObjStateData *obj_state;
+    gint64        now_msec;
+    gint64        expiry_msec;
 
-    self->priv.p->routes_temporary_not_available_id = 0;
+    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
 
-    if (!self->priv.p->routes_temporary_not_available_hash)
-        return G_SOURCE_REMOVE;
+    obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
+                                   ObjStateData,
+                                   os_temporary_not_available_lst);
 
-    /* we check the timeouts again. That is, because we allow to remove
-     * entries from routes_temporary_not_available_hash, without rescheduling
-     * out timeouts. */
+    if (!obj_state)
+        return G_SOURCE_CONTINUE;
 
     now_msec = nm_utils_get_monotonic_timestamp_msec();
 
-    expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
-    oldest_msec           = G_MAXINT64;
+    expiry_msec = obj_state->os_temporary_not_available_timestamp_msec
+                  + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
 
-    g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
-    while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
-        if (data->timestamp_msec >= expiry_threshold_msec) {
-            any_expired = TRUE;
-            break;
-        }
-        if (data->timestamp_msec < oldest_msec)
-            oldest_msec = data->timestamp_msec;
-    }
-
-    if (any_expired) {
-        /* a route expired. We emit a signal, but we don't schedule it again. That will
-         * only happen if the user calls nm_l3cfg_commit() again. */
-        _nm_l3cfg_emit_signal_notify_simple(
-            self,
-            NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
-        return G_SOURCE_REMOVE;
+    if (now_msec < expiry_msec) {
+        /* the timeout is not yet reached. Restart the timer... */
+        self->priv.p->obj_state_temporary_not_available_timeout_source =
+            nm_g_timeout_add_source(expiry_msec - now_msec,
+                                    _routes_temporary_not_available_timeout,
+                                    self);
+        return G_SOURCE_CONTINUE;
     }
 
-    if (oldest_msec != G_MAXINT64) {
-        /* we have a timeout still. Reschedule. */
-        self->priv.p->routes_temporary_not_available_id =
-            g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
-                          _routes_temporary_not_available_timeout,
-                          self);
-    }
-    return G_SOURCE_REMOVE;
+    /* One (or several) routes expired. We emit a signal, but we don't schedule it again.
+     * We expect the callers to commit again, which will one last time try to configure
+     * the route. If that again fails, we detect the timeout, log a warning and don't
+     * track the object as not temporary-not-available anymore. */
+    _nm_l3cfg_emit_signal_notify_simple(
+        self,
+        NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED);
+    return G_SOURCE_CONTINUE;
 }
 
 static gboolean
@@ -3171,14 +3567,15 @@ _routes_temporary_not_available_update(NML3Cfg *  self,
                                        int        addr_family,
                                        GPtrArray *routes_temporary_not_available_arr)
 {
-    RoutesTemporaryNotAvailableData *data;
-    GHashTableIter                   iter;
-    gint64                           oldest_msec;
-    gint64                           now_msec;
-    gboolean                         prune_all = FALSE;
-    gboolean                         success   = TRUE;
-    guint                            i;
-
+    ObjStateData *  obj_state;
+    ObjStateData *  obj_state_safe;
+    gint64          now_msec;
+    gboolean        prune_all = FALSE;
+    gboolean        success   = TRUE;
+    guint           i;
+    const NMPClass *klass;
+
+    klass    = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
     now_msec = nm_utils_get_monotonic_timestamp_msec();
 
     if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) {
@@ -3186,36 +3583,45 @@ _routes_temporary_not_available_update(NML3Cfg *  self,
         goto out_prune;
     }
 
-    if (self->priv.p->routes_temporary_not_available_hash) {
-        g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
-        while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
-            if (NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family)
-                data->dirty = TRUE;
+    c_list_for_each_entry (obj_state,
+                           &self->priv.p->obj_state_temporary_not_available_lst_head,
+                           os_temporary_not_available_lst) {
+        if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
+            nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0);
+            obj_state->os_tna_dirty = TRUE;
         }
-    } else {
-        self->priv.p->routes_temporary_not_available_hash =
-            g_hash_table_new_full(nmp_object_indirect_id_hash,
-                                  nmp_object_indirect_id_equal,
-                                  _routes_temporary_not_available_data_free,
-                                  NULL);
     }
 
     for (i = 0; i < routes_temporary_not_available_arr->len; i++) {
         const NMPObject *o = routes_temporary_not_available_arr->pdata[i];
-        char             sbuf[1024];
+        char             sbuf[sizeof(_nm_utils_to_string_buffer)];
 
         nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family)));
 
-        data = g_hash_table_lookup(self->priv.p->routes_temporary_not_available_hash, &o);
+        obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &o);
 
-        if (data) {
-            if (!data->dirty)
-                continue;
+        if (!obj_state) {
+            /* Hm? We don't track this object? Very odd, a bug? */
+            nm_assert_not_reached();
+            continue;
+        }
 
-            nm_assert(data->timestamp_msec > 0 && data->timestamp_msec <= now_msec);
+        if (obj_state->os_temporary_not_available_timestamp_msec > 0) {
+            nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0
+                      && obj_state->os_temporary_not_available_timestamp_msec <= now_msec);
 
-            if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
-                /* timeout. Could not add this address. */
+            if (!obj_state->os_tna_dirty) {
+                /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates.
+                 * It should not. */
+                nm_assert_not_reached();
+                continue;
+            }
+
+            if (now_msec > obj_state->os_temporary_not_available_timestamp_msec
+                               + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
+                /* Timeout. Could not add this address.
+                 *
+                 * For now, keep it obj_state->os_tna_dirty and prune it below. */
                 _LOGW("failure to add IPv%c route: %s",
                       nm_utils_addr_family_to_char(addr_family),
                       nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
@@ -3223,7 +3629,7 @@ _routes_temporary_not_available_update(NML3Cfg *  self,
                 continue;
             }
 
-            data->dirty = FALSE;
+            obj_state->os_tna_dirty = FALSE;
             continue;
         }
 
@@ -3231,47 +3637,229 @@ _routes_temporary_not_available_update(NML3Cfg *  self,
               nm_utils_addr_family_to_char(addr_family),
               nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf)));
 
-        data  = g_slice_new(RoutesTemporaryNotAvailableData);
-        *data = (RoutesTemporaryNotAvailableData){
-            .obj            = nmp_object_ref(o),
-            .timestamp_msec = now_msec,
-            .dirty          = FALSE,
-        };
-        g_hash_table_add(self->priv.p->routes_temporary_not_available_hash, data);
+        obj_state->os_tna_dirty                              = FALSE;
+        obj_state->os_temporary_not_available_timestamp_msec = now_msec;
+        c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head,
+                         &obj_state->os_temporary_not_available_lst);
     }
 
 out_prune:
-    oldest_msec = G_MAXINT64;
-
-    if (self->priv.p->routes_temporary_not_available_hash) {
-        g_hash_table_iter_init(&iter, self->priv.p->routes_temporary_not_available_hash);
-        while (g_hash_table_iter_next(&iter, (gpointer *) &data, NULL)) {
-            nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(data->obj) == addr_family || !data->dirty);
-            if (!prune_all && !data->dirty) {
-                if (data->timestamp_msec < oldest_msec)
-                    oldest_msec = data->timestamp_msec;
-                continue;
+    c_list_for_each_entry_safe (obj_state,
+                                obj_state_safe,
+                                &self->priv.p->obj_state_temporary_not_available_lst_head,
+                                os_temporary_not_available_lst) {
+        if (prune_all || obj_state->os_tna_dirty) {
+            if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) {
+                obj_state->os_temporary_not_available_timestamp_msec = 0;
+                c_list_unlink(&obj_state->os_temporary_not_available_lst);
             }
-            g_hash_table_iter_remove(&iter);
         }
-        if (oldest_msec != G_MAXINT64)
-            nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash,
-                             g_hash_table_unref);
     }
 
-    nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
-    if (oldest_msec != G_MAXINT64) {
-        nm_assert(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC < now_msec);
-        self->priv.p->routes_temporary_not_available_id =
-            g_timeout_add(oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
-                          _routes_temporary_not_available_timeout,
-                          self);
+    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
+
+    obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head,
+                                   ObjStateData,
+                                   os_temporary_not_available_lst);
+    if (obj_state) {
+        self->priv.p->obj_state_temporary_not_available_timeout_source =
+            nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec
+                                     + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec),
+                                    _routes_temporary_not_available_timeout,
+                                    self);
     }
 
     return success;
 }
 
 /*****************************************************************************/
+static const char *
+ip6_privacy_to_str(NMSettingIP6ConfigPrivacy ip6_privacy)
+{
+    switch (ip6_privacy) {
+    case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
+        return "0";
+    case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
+        return "1";
+    case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
+        return "2";
+    case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
+        break;
+    }
+    return nm_assert_unreachable_val("0");
+}
+
+static void
+_l3_commit_ndisc_params(NML3Cfg *self, NML3CfgCommitType commit_type)
+{
+    const NML3ConfigData *l3cd;
+    gboolean              retrans_set   = FALSE;
+    gboolean              reachable_set = FALSE;
+    gboolean              hop_limit_set = FALSE;
+    guint32               reachable     = 0;
+    guint32               retrans       = 0;
+    int                   hop_limit     = 0;
+    const char *          ifname;
+
+    if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE) {
+        self->priv.p->ndisc_reachable_time_msec_set = FALSE;
+        self->priv.p->ndisc_retrans_timer_msec_set  = FALSE;
+        self->priv.p->ndisc_hop_limit_set           = FALSE;
+        return;
+    }
+
+    l3cd = self->priv.p->combined_l3cd_commited;
+    if (l3cd) {
+        reachable_set = nm_l3_config_data_get_ndisc_reachable_time_msec(l3cd, &reachable);
+        retrans_set   = nm_l3_config_data_get_ndisc_retrans_timer_msec(l3cd, &retrans);
+        hop_limit     = nm_l3_config_data_get_ndisc_hop_limit(l3cd, &hop_limit);
+    }
+    ifname = nm_l3cfg_get_ifname(self, TRUE);
+
+    if (reachable_set
+        && (!self->priv.p->ndisc_reachable_time_msec_set
+            || self->priv.p->ndisc_reachable_time_msec != reachable)) {
+        self->priv.p->ndisc_reachable_time_msec     = reachable;
+        self->priv.p->ndisc_reachable_time_msec_set = TRUE;
+        if (ifname) {
+            nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(self->priv.platform,
+                                                                ifname,
+                                                                reachable);
+        }
+    }
+
+    if (retrans_set
+        && (!self->priv.p->ndisc_retrans_timer_msec_set
+            || self->priv.p->ndisc_reachable_time_msec != retrans)) {
+        self->priv.p->ndisc_retrans_timer_msec     = retrans;
+        self->priv.p->ndisc_retrans_timer_msec_set = TRUE;
+        if (ifname) {
+            nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(self->priv.platform, ifname, retrans);
+        }
+    }
+
+    if (hop_limit_set
+        && (!self->priv.p->ndisc_hop_limit_set || self->priv.p->ndisc_hop_limit != hop_limit)) {
+        self->priv.p->ndisc_hop_limit     = hop_limit;
+        self->priv.p->ndisc_hop_limit_set = TRUE;
+        if (ifname) {
+            nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(self->priv.platform,
+                                                               ifname,
+                                                               hop_limit);
+        }
+    }
+
+    // FIXME: restore values if necessary
+}
+
+static void
+_l3_commit_ip6_privacy(NML3Cfg *self, NML3CfgCommitType commit_type)
+{
+    NMSettingIP6ConfigPrivacy ip6_privacy;
+    NMSettingIP6ConfigPrivacy ip6_privacy_set_before;
+    const char *              ifname;
+
+    if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE)
+        ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+    else
+        ip6_privacy = nm_l3_config_data_get_ip6_privacy(self->priv.p->combined_l3cd_commited);
+
+    if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) {
+        if (!self->priv.p->ip6_privacy_set) {
+            /* Nothing to set. But do we need to reset a previous value? */
+            return;
+        }
+        self->priv.p->ip6_privacy_set = FALSE;
+        ip6_privacy                   = self->priv.p->ip6_privacy_initial;
+        ifname                        = nm_l3cfg_get_ifname(self, TRUE);
+        _LOGT("commit-ip6-privacy: reset initial value %d (was %d)%s%s",
+              (int) ip6_privacy,
+              (int) self->priv.p->ip6_privacy_set_before,
+              NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)"));
+        if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
+            return;
+        if (!ifname)
+            return;
+        goto set;
+    }
+
+    nm_assert(NM_IN_SET(ip6_privacy,
+                        NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED,
+                        NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR,
+                        NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR));
+
+    if (self->priv.p->ip6_privacy_set && self->priv.p->ip6_privacy_set_before == ip6_privacy
+        && commit_type < NM_L3_CFG_COMMIT_TYPE_REAPPLY) {
+        /* Already set. We leave this alone except during reapply. */
+        return;
+    }
+
+    ip6_privacy_set_before               = self->priv.p->ip6_privacy_set_before;
+    self->priv.p->ip6_privacy_set_before = ip6_privacy;
+
+    if (!self->priv.p->ip6_privacy_set) {
+        gint64 s = G_MININT64;
+
+        self->priv.p->ip6_privacy_set = TRUE;
+        ifname                        = nm_l3cfg_get_ifname(self, TRUE);
+        if (ifname) {
+            s = nm_platform_sysctl_ip_conf_get_int_checked(self->priv.platform,
+                                                           AF_INET6,
+                                                           ifname,
+                                                           "use_tempaddr",
+                                                           10,
+                                                           G_MININT32,
+                                                           G_MAXINT32,
+                                                           G_MININT64);
+            if (s != G_MININT64)
+                s = NM_CLAMP(s, 0, 2);
+        }
+        switch (s) {
+        case 0:
+            self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED;
+            break;
+        case 1:
+            self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR;
+            break;
+        case 2:
+            self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR;
+            break;
+        default:
+            nm_assert_not_reached();
+            /* fall-through */
+        case G_MININT64:
+            self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
+            break;
+        }
+        _LOGT("commit-ip6-privacy: set value %d (initial value was %d)%s%s",
+              (int) ip6_privacy,
+              (int) self->priv.p->ip6_privacy_initial,
+              NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)"));
+        if (!ifname)
+            return;
+        /* The first time, we always set the value, and don't skip it based on what we
+         * read. */
+        goto set;
+    }
+
+    ifname = nm_l3cfg_get_ifname(self, TRUE);
+    _LOGT("commit-ip6-privacy: set value %d (after %d, initial value was %d)%s%s",
+          (int) ip6_privacy,
+          (int) ip6_privacy_set_before,
+          (int) self->priv.p->ip6_privacy_initial,
+          NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)"));
+    if (!ifname)
+        return;
+
+set:
+    nm_assert(ifname);
+    self->priv.p->ip6_privacy_set_before = ip6_privacy;
+    nm_platform_sysctl_ip_conf_set(self->priv.platform,
+                                   AF_INET6,
+                                   ifname,
+                                   "use_tempaddr",
+                                   ip6_privacy_to_str(ip6_privacy));
+}
 
 static gboolean
 _l3_commit_one(NML3Cfg *             self,
@@ -3303,58 +3891,35 @@ _l3_commit_one(NML3Cfg *             self,
           nm_utils_addr_family_to_char(addr_family),
           _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type)));
 
-    if (changed_combined_l3cd) {
-        /* our combined configuration changed. We may track entries in externally_removed_objs_hash,
-         * which are not longer to be considered by our configuration. We need to forget about them. */
-        _l3cfg_externally_removed_objs_drop_unused(self);
-    }
-
-    if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) {
-        /* we need to artificially pre-populate the externally remove hash. */
-        _l3cfg_externally_removed_objs_pickup(self, addr_family);
-    }
-
     if (self->priv.p->combined_l3cd_commited) {
-        GHashTable *                   externally_removed_objs_hash;
-        NMDedupMultiFcnSelectPredicate predicate;
-        const NMDedupMultiHeadEntry *  head_entry;
-
-        if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
-            && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) {
-            predicate                    = _l3cfg_externally_removed_objs_filter;
-            externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
-        } else {
-            if (IS_IPv4)
-                predicate = _l3cfg_externally_removed_objs_filter;
-            else
-                predicate = NULL;
-            externally_removed_objs_hash = NULL;
-        }
+        const NMDedupMultiHeadEntry * head_entry;
+        const ObjStatesSyncFilterData sync_filter_data = {
+            .self        = self,
+            .commit_type = commit_type,
+        };
+
         head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
                                                    NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
         addresses  = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
-                                                          predicate,
-                                                          externally_removed_objs_hash);
+                                                          _obj_states_sync_filter,
+                                                          (gpointer) &sync_filter_data);
 
-        if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
-            && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) {
-            predicate                    = _l3cfg_externally_removed_objs_filter;
-            externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
-        } else {
-            predicate                    = NULL;
-            externally_removed_objs_hash = NULL;
-        }
         head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
                                                    NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
         routes     = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
-                                                       predicate,
-                                                       externally_removed_objs_hash);
+                                                       _obj_states_sync_filter,
+                                                       (gpointer) &sync_filter_data);
 
         route_table_sync =
             nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited,
                                                    addr_family);
     }
 
+    if (!IS_IPv4) {
+        _l3_commit_ip6_privacy(self, commit_type);
+        _l3_commit_ndisc_params(self, commit_type);
+    }
+
     if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE)
         route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL;
 
@@ -3367,17 +3932,11 @@ _l3_commit_one(NML3Cfg *             self,
                                                            addr_family,
                                                            self->priv.ifindex,
                                                            route_table_sync);
-    } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) {
-        addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]);
-        routes_prune    = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]);
-    }
-
-    nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses);
-    nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes);
+        _obj_state_zombie_lst_prune_all(self, addr_family);
+    } else
+        _obj_state_zombie_lst_get_prune_lists(self, addr_family, &addresses_prune, &routes_prune);
 
-    /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
-    /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_mtu(). */
     /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */
 
     nm_platform_ip_address_sync(self->priv.platform,
@@ -3409,8 +3968,11 @@ _l3_commit_one(NML3Cfg *             self,
 static void
 _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
 {
-    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old             = NULL;
-    gboolean                                 commit_type_detected = FALSE;
+    _nm_unused gs_unref_object NML3Cfg *self_keep_alive = NULL;
+    nm_auto_unref_l3cd const NML3ConfigData *l3cd_old   = NULL;
+    NML3CfgCommitType                        commit_type_auto;
+    gboolean                                 commit_type_from_auto = FALSE;
+    gboolean                                 is_sticky_update      = FALSE;
     char                                     sbuf_ct[30];
     gboolean                                 changed_combined_l3cd;
 
@@ -3423,44 +3985,45 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
                         NM_L3_CFG_COMMIT_TYPE_REAPPLY));
     nm_assert(self->priv.p->commit_reentrant_count == 0);
 
-    switch (commit_type) {
-    case NM_L3_CFG_COMMIT_TYPE_AUTO:
-        /* if in "AUTO" mode we currently have commit-type "UPDATE", that
-         * causes also the following update to still be "UPDATE". Either
-         * the same commit */
-        commit_type_detected = TRUE;
-        commit_type          = nm_l3cfg_commit_type_get(self);
-        if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE)
-            self->priv.p->commit_type_update_sticky = TRUE;
-        else if (self->priv.p->commit_type_update_sticky) {
+    /* The actual commit type is always the maximum of what is requested
+     * and what is registered via nm_l3cfg_commit_type_register(), combined
+     * with the ad-hoc requested @commit_type argument. */
+    commit_type_auto = nm_l3cfg_commit_type_get(self);
+    if (commit_type == NM_L3_CFG_COMMIT_TYPE_AUTO || commit_type_auto > commit_type) {
+        commit_type_from_auto = TRUE;
+        commit_type           = commit_type_auto;
+    }
+
+    /* Levels UPDATE and higher are sticky. That means, when do perform such a commit
+     * type, then the next one will at least be of level "UPDATE". The idea is
+     * that if the current commit adds an address, then the following needs
+     * to do at least "UPDATE" level to remove it again. Even if in the meantime
+     * the "UPDATE" is unregistered (nm_l3cfg_commit_type_unregister()). */
+    if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE) {
+        if (self->priv.p->commit_type_update_sticky) {
             self->priv.p->commit_type_update_sticky = FALSE;
             commit_type                             = NM_L3_CFG_COMMIT_TYPE_UPDATE;
+            is_sticky_update                        = TRUE;
         }
-        break;
-    case NM_L3_CFG_COMMIT_TYPE_ASSUME:
-        break;
-    case NM_L3_CFG_COMMIT_TYPE_REAPPLY:
-    case NM_L3_CFG_COMMIT_TYPE_UPDATE:
-        self->priv.p->commit_type_update_sticky = FALSE;
-        break;
-    case NM_L3_CFG_COMMIT_TYPE_NONE:
-        break;
-    }
+    } else
+        self->priv.p->commit_type_update_sticky = TRUE;
 
-    _LOGT("commit %s%s%s",
+    _LOGT("commit %s%s%s%s",
           _l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)),
-          commit_type_detected ? " (auto)" : "",
+          commit_type_from_auto ? " (auto)" : "",
+          is_sticky_update ? " (sticky-update)" : "",
           is_idle ? " (idle handler)" : "");
 
-    if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
-        return;
+    nm_assert(commit_type > NM_L3_CFG_COMMIT_TYPE_AUTO);
 
-    self->priv.p->commit_reentrant_count++;
+    if (nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source))
+        self_keep_alive = self;
+    self->priv.p->commit_on_idle_type = NM_L3_CFG_COMMIT_TYPE_AUTO;
 
-    nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
+    if (commit_type <= NM_L3_CFG_COMMIT_TYPE_NONE)
+        return;
 
-    if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
-        _l3cfg_externally_removed_objs_drop(self);
+    self->priv.p->commit_reentrant_count++;
 
     _l3cfg_update_combined_config(self,
                                   TRUE,
@@ -3468,7 +4031,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
                                   &l3cd_old,
                                   &changed_combined_l3cd);
 
-    /* FIXME(l3cfg): handle items currently not configurable in kernel. */
+    _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT);
 
     _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old);
     _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old);
@@ -3481,6 +4044,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
     _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT);
 }
 
+/* See DOC(l3cfg:commit-type) */
 void
 nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type)
 {
@@ -3508,29 +4072,39 @@ nm_l3cfg_commit_type_get(NML3Cfg *self)
  * @commit_type: the commit type to register
  * @existing_handle: instead of being a new registration, update an existing handle.
  *   This may be %NULL, which is like having no previous registration.
+ * @source: the source of the commit type, for logging.
  *
  * NML3Cfg needs to know whether it is in charge of an interface (and how "much").
  * By default, it is not in charge, but various users can register themself with
  * a certain @commit_type. The "higher" commit type is the used one when calling
  * nm_l3cfg_commit() with %NM_L3_CFG_COMMIT_TYPE_AUTO.
  *
- * Returns: a handle tracking the registration, or %NULL of @commit_type
+ * Returns: a handle tracking the registration, or %NULL if @commit_type
  *   is %NM_L3_CFG_COMMIT_TYPE_NONE.
  */
 NML3CfgCommitTypeHandle *
 nm_l3cfg_commit_type_register(NML3Cfg *                self,
                               NML3CfgCommitType        commit_type,
-                              NML3CfgCommitTypeHandle *existing_handle)
+                              NML3CfgCommitTypeHandle *existing_handle,
+                              const char *             source)
 {
     NML3CfgCommitTypeHandle *handle;
     NML3CfgCommitTypeHandle *h;
     gboolean                 linked;
+    NML3CfgCommitTypeHandle *ret = NULL;
+    char                     buf[64];
 
     nm_assert(NM_IS_L3CFG(self));
     nm_assert(NM_IN_SET(commit_type,
                         NM_L3_CFG_COMMIT_TYPE_NONE,
                         NM_L3_CFG_COMMIT_TYPE_ASSUME,
                         NM_L3_CFG_COMMIT_TYPE_UPDATE));
+
+    /* It would be easy (and maybe convenient) to allow that @existing_handle
+     * can currently be registered on another NML3Cfg instance. But then we couldn't
+     * do this assertion, and it seems error prone to allow arbitrary handles where
+     * we cannot check whether it is valid. So if @existing_handle is given, it
+     * must be tracked by @self (and only by @self). */
     nm_assert(
         !existing_handle
         || c_list_contains(&self->priv.p->commit_type_lst_head, &existing_handle->commit_type_lst));
@@ -3538,21 +4112,24 @@ nm_l3cfg_commit_type_register(NML3Cfg *                self,
     if (existing_handle) {
         if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) {
             nm_l3cfg_commit_type_unregister(self, existing_handle);
-            return NULL;
+            goto out;
+        }
+        if (existing_handle->commit_type == commit_type) {
+            ret = existing_handle;
+            goto out;
         }
-        if (existing_handle->commit_type == commit_type)
-            return existing_handle;
         c_list_unlink_stale(&existing_handle->commit_type_lst);
         handle = existing_handle;
     } else {
         if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
-            return NULL;
-        handle              = g_slice_new(NML3CfgCommitTypeHandle);
-        handle->commit_type = commit_type;
+            goto out;
+        handle = g_slice_new(NML3CfgCommitTypeHandle);
         if (c_list_is_empty(&self->priv.p->commit_type_lst_head))
             g_object_ref(self);
     }
 
+    handle->commit_type = commit_type;
+
     linked = FALSE;
     c_list_for_each_entry (h, &self->priv.p->commit_type_lst_head, commit_type_lst) {
         if (handle->commit_type >= h->commit_type) {
@@ -3564,7 +4141,15 @@ nm_l3cfg_commit_type_register(NML3Cfg *                self,
     if (!linked)
         c_list_link_tail(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst);
 
-    return handle;
+    ret = handle;
+out:
+    _LOGT("commit type register (type \"%s\", source \"%s\", existing " NM_HASH_OBFUSCATE_PTR_FMT
+          ") -> " NM_HASH_OBFUSCATE_PTR_FMT "",
+          _l3_cfg_commit_type_to_string(commit_type, buf, sizeof(buf)),
+          source,
+          NM_HASH_OBFUSCATE_PTR(existing_handle),
+          NM_HASH_OBFUSCATE_PTR(ret));
+    return ret;
 }
 
 void
@@ -3577,6 +4162,8 @@ nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle)
 
     nm_assert(c_list_contains(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst));
 
+    _LOGT("commit type unregister " NM_HASH_OBFUSCATE_PTR_FMT "", NM_HASH_OBFUSCATE_PTR(handle));
+
     c_list_unlink_stale(&handle->commit_type_lst);
     if (c_list_is_empty(&self->priv.p->commit_type_lst_head))
         g_object_unref(self);
@@ -3688,7 +4275,7 @@ _nm_l3cfg_unregister_ipv4ll(NML3Cfg *self)
 {
     nm_assert(NM_IS_L3CFG(self));
 
-    /* we don't own the refernce to "self->priv.p->ipv4ll", but
+    /* we don't own the reference to "self->priv.p->ipv4ll", but
      * when that instance gets destroyed, we get called back to
      * forget about it. Basically, it's like a weak pointer. */
 
@@ -3747,6 +4334,14 @@ nm_l3cfg_init(NML3Cfg *self)
     c_list_init(&self->priv.p->acd_lst_head);
     c_list_init(&self->priv.p->acd_event_notify_lst_head);
     c_list_init(&self->priv.p->commit_type_lst_head);
+    c_list_init(&self->priv.p->obj_state_lst_head);
+    c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head);
+    c_list_init(&self->priv.p->obj_state_zombie_lst_head);
+
+    self->priv.p->obj_state_hash = g_hash_table_new_full(nmp_object_indirect_id_hash,
+                                                         nmp_object_indirect_id_equal,
+                                                         _obj_state_data_free,
+                                                         NULL);
 }
 
 static void
@@ -3786,9 +4381,7 @@ finalize(GObject *object)
 
     nm_assert(c_list_is_empty(&self->priv.p->commit_type_lst_head));
 
-    nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
-
-    nm_assert(nm_g_array_len(self->priv.p->property_emit_list) == 0u);
+    nm_assert(!self->priv.p->commit_on_idle_source);
 
     _l3_acd_data_prune(self, TRUE);
 
@@ -3801,15 +4394,12 @@ finalize(GObject *object)
     nm_clear_g_source_inst(&self->priv.p->nacd_source);
     nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
 
-    nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref);
-    nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref);
-    nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref);
-    nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref);
-
-    nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
-    nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
+    nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source);
 
-    nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
+    nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy);
+    nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head));
+    nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head));
+    nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head));
 
     g_clear_object(&self->priv.netns);
     g_clear_object(&self->priv.platform);
diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h
index 037f2178..19dec118 100644
--- a/src/core/nm-l3cfg.h
+++ b/src/core/nm-l3cfg.h
@@ -7,6 +7,7 @@
 #include "nm-l3-config-data.h"
 
 #define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0
+#define NM_L3CFG_CONFIG_PRIORITY_IPV6LL 0
 #define NM_ACD_TIMEOUT_RFC5227_MSEC     9000u
 
 #define NM_TYPE_L3CFG            (nm_l3cfg_get_type())
@@ -22,12 +23,43 @@
 #define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify"
 
 typedef enum _nm_packed {
-    NM_L3_ACD_DEFEND_TYPE_NONE,
+    _NM_L3_ACD_DEFEND_TYPE_NONE,
     NM_L3_ACD_DEFEND_TYPE_NEVER,
     NM_L3_ACD_DEFEND_TYPE_ONCE,
     NM_L3_ACD_DEFEND_TYPE_ALWAYS,
 } NML3AcdDefendType;
 
+/**
+ * NML3CfgConfigFlags:
+ * @NM_L3CFG_CONFIG_FLAGS_NONE: no flags, the default.
+ * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set,
+ *   the the NML3ConfigData doesn't get merged and it's information won't be
+ *   synced. The only purpose is to run ACD on its IPv4 addresses, but
+ *   regardless whether ACD succeeds/fails, the IP addresses won't be configured.
+ *   The point is to run ACD first (without configuring it), and only
+ *   commit the settings if requested. That can either happen by
+ *   nm_l3cfg_add_config() the same NML3Cfg again (with a different
+ *   tag), or by calling nm_l3cfg_add_config() again with this flag
+ *   cleared (and the same tag).
+ * @NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE: a commit with
+ *   %NM_L3_CFG_COMMIT_TYPE_ASSUME, means to not remove/add
+ *   addresses that are missing/already exist. The assume mode
+ *   is for taking over a device gracefully after restart, so
+ *   it aims to preserve whatever was configured (or not configured).
+ *   With this flag enabled, the first commit in assume mode will still
+ *   add the addresses/routes. This is necessary for example with IPv6LL.
+ *   Also while assuming a device, we want to configure things
+ *   (like an IPv6 address), so we need to bypass the common
+ *   "don't change" behavior. At least once. If the address/route
+ *   is still not (no longer) configured on the subsequent
+ *   commit, it's not getting added again.
+ */
+typedef enum _nm_packed {
+    NM_L3CFG_CONFIG_FLAGS_NONE               = 0,
+    NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD       = (1LL << 0),
+    NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1),
+} NML3CfgConfigFlags;
+
 typedef enum _nm_packed {
     NM_L3_ACD_ADDR_STATE_INIT,
     NM_L3_ACD_ADDR_STATE_PROBING,
@@ -98,6 +130,12 @@ typedef enum {
 
     NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT,
 
+    /* emitted before the merged l3cd is committed to platform.
+     *
+     * This event also gets emitted "under unsafe circumstances".
+     * See NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED. */
+    NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT,
+
     /* emitted at the end of nm_l3cfg_platform_commit(). This signals also that
      * nm_l3cfg_is_ready() might have switched to TRUE. */
     NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT,
@@ -105,7 +143,9 @@ typedef enum {
     /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes.
      * It re-emits the platform signal.
      * Contrary to NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, this even
-     * is re-emitted synchronously. */
+     * is re-emitted synchronously. You probably want to hook to the on-idle signal,
+     * unless you need to catch all intermediate changes too. Note that this
+     * event is not re-entrant safe (so beware what you are doing). */
     NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
 
     /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes.
@@ -235,6 +275,8 @@ nm_l3cfg_get_ifname(const NML3Cfg *self, gboolean get_next)
     return nmp_object_link_get_ifname(nm_l3cfg_get_plobj(self, get_next));
 }
 
+gboolean nm_l3cfg_is_vrf(const NML3Cfg *self);
+
 static inline NMNetns *
 nm_l3cfg_get_netns(const NML3Cfg *self)
 {
@@ -251,31 +293,12 @@ nm_l3cfg_get_platform(const NML3Cfg *self)
     return self->priv.platform;
 }
 
-gboolean nm_l3cfg_get_acd_is_pending(NML3Cfg *self);
-
 /*****************************************************************************/
 
 void _nm_l3cfg_emit_signal_notify(NML3Cfg *self, const NML3ConfigNotifyData *notify_data);
 
 /*****************************************************************************/
 
-typedef enum {
-    NM_L3CFG_PROPERTY_EMIT_TYPE_ANY,
-    NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE,
-    NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE,
-} NML3CfgPropertyEmitType;
-
-void nm_l3cfg_property_emit_register(NML3Cfg *               self,
-                                     GObject *               target_obj,
-                                     const GParamSpec *      target_property,
-                                     NML3CfgPropertyEmitType emit_type);
-
-void nm_l3cfg_property_emit_unregister(NML3Cfg *         self,
-                                       GObject *         target_obj,
-                                       const GParamSpec *target_property);
-
-/*****************************************************************************/
-
 void nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty);
 
 gboolean nm_l3cfg_add_config(NML3Cfg *             self,
@@ -289,17 +312,39 @@ gboolean nm_l3cfg_add_config(NML3Cfg *             self,
                              guint32               default_route_metric_6,
                              guint32               default_route_penalty_4,
                              guint32               default_route_penalty_6,
+                             int                   default_dns_priority_4,
+                             int                   default_dns_priority_6,
                              NML3AcdDefendType     acd_defend_type,
                              guint32               acd_timeout_msec,
+                             NML3CfgConfigFlags    config_flags,
                              NML3ConfigMergeFlags  merge_flags);
 
-gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg);
+gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *l3cd);
 
-gboolean nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty);
+gboolean nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag);
+gboolean nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag);
 
 /*****************************************************************************/
 
-/* The numeric values of the enum matters: higher number mean more "important".
+/* DOC(l3cfg:commit-type):
+ *
+ * The major idea of NML3Cfg is that independent parties can register configuration
+ * (NML3ConfigData via nm_l3cfg_add_config()), and then nm_l3cfg_commit() will
+ * actually configure it. Usually we would not call the synchronous nm_l3cfg_commit(),
+ * but instead nm_l3cfg_commit_on_idle_schedule().
+ *
+ * We have different levels of "how much" we should sync during commit. That is
+ * NML3CfgCommitType. Since independent parties should be able to work together,
+ * they can only ask for their minimal required commit-type level. That means,
+ * during commit we will commit with the highest level of how much one of the
+ * users request the commit. To request a commit level, users can call
+ * nm_l3cfg_commit_type_register(). nm_l3cfg_commit_on_idle_schedule() also
+ * accepts a one-time commit-type argument.
+ *
+ * This is related to NMDevice's sys_iface_state, which we use to control whether
+ * to touch/assume/manage the interface.
+ *
+ * The numeric values of the enum matters: higher number mean more "important".
  * E.g. "assume" tries to preserve the most settings, while "reapply" forces
  * all configuration to match. */
 typedef enum _nm_packed {
@@ -314,7 +359,10 @@ typedef enum _nm_packed {
     /* ASSUME means to keep any pre-existing extra routes/addresses, while
      * also not adding routes/addresses that are not present yet. This is to
      * gracefully take over after restart, where the existing IP configuration
-     * should not change. */
+     * should not change.
+     *
+     * The flag NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE can make certain addresses/
+     * routes commitable also during "assume". */
     NM_L3_CFG_COMMIT_TYPE_ASSUME,
 
     /* UPDATE means to add new addresses/routes, while also removing addresses/routes
@@ -331,24 +379,53 @@ typedef enum _nm_packed {
 
 void nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type);
 
-gboolean nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self);
+gboolean nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self, NML3CfgCommitType commit_type);
+
+gboolean nm_l3cfg_commit_on_idle_is_scheduled(NML3Cfg *self);
 
 /*****************************************************************************/
 
+gboolean nm_l3cfg_get_acd_is_pending(NML3Cfg *self);
+
 const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr);
 
 /*****************************************************************************/
 
+typedef enum {
+    NM_L3CFG_CHECK_READY_FLAGS_NONE          = 0,
+    NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY = (1ull << 0),
+    NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY = (1ull << 1),
+} NML3CfgCheckReadyFlags;
+
+gboolean nm_l3cfg_check_ready(NML3Cfg *              self,
+                              const NML3ConfigData * l3cd,
+                              int                    addr_family,
+                              NML3CfgCheckReadyFlags flags,
+                              gboolean *             acd_used);
+
+/*****************************************************************************/
+
 NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self);
 
 typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle;
 
 NML3CfgCommitTypeHandle *nm_l3cfg_commit_type_register(NML3Cfg *                self,
                                                        NML3CfgCommitType        commit_type,
-                                                       NML3CfgCommitTypeHandle *existing_handle);
+                                                       NML3CfgCommitTypeHandle *existing_handle,
+                                                       const char *             source);
 
 void nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle);
 
+static inline gboolean
+nm_l3cfg_commit_type_clear(NML3Cfg *self, NML3CfgCommitTypeHandle **handle)
+{
+    if (!handle || !*handle)
+        return FALSE;
+
+    nm_l3cfg_commit_type_unregister(self, g_steal_pointer(handle));
+    return TRUE;
+}
+
 /*****************************************************************************/
 
 const NML3ConfigData *nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited);
diff --git a/src/core/nm-manager.c b/src/core/nm-manager.c
index d859c1ea..3c9660c7 100644
--- a/src/core/nm-manager.c
+++ b/src/core/nm-manager.c
@@ -8,47 +8,46 @@
 
 #include "nm-manager.h"
 
-#include <stdlib.h>
 #include <fcntl.h>
-#include <unistd.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/sendfile.h>
 #include <limits.h>
+#include <stdlib.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
 
-#include "libnm-glib-aux/nm-c-list.h"
-
-#include "libnm-core-aux-intern/nm-common-macros.h"
-#include "nm-dbus-manager.h"
-#include "vpn/nm-vpn-manager.h"
-#include "devices/nm-device.h"
+#include "NetworkManagerUtils.h"
+#include "devices/nm-device-factory.h"
 #include "devices/nm-device-generic.h"
+#include "devices/nm-device.h"
+#include "dhcp/nm-dhcp-manager.h"
+#include "libnm-core-aux-intern/nm-common-macros.h"
+#include "libnm-core-intern/nm-core-internal.h"
+#include "libnm-glib-aux/nm-c-list.h"
 #include "libnm-platform/nm-platform.h"
 #include "libnm-platform/nmp-object.h"
-#include "nm-hostname-manager.h"
-#include "nm-keep-alive.h"
-#include "nm-rfkill-manager.h"
-#include "dhcp/nm-dhcp-manager.h"
-#include "settings/nm-settings.h"
-#include "settings/nm-settings-connection.h"
-#include "nm-auth-utils.h"
-#include "nm-auth-manager.h"
-#include "NetworkManagerUtils.h"
-#include "devices/nm-device-factory.h"
-#include "nm-sleep-monitor.h"
-#include "nm-connectivity.h"
-#include "nm-policy.h"
-#include "nm-session-monitor.h"
+#include "libnm-std-aux/nm-dbus-compat.h"
 #include "nm-act-request.h"
-#include "libnm-core-intern/nm-core-internal.h"
-#include "nm-config.h"
 #include "nm-audit-manager.h"
-#include "libnm-std-aux/nm-dbus-compat.h"
-#include "nm-checkpoint.h"
+#include "nm-auth-manager.h"
+#include "nm-auth-utils.h"
 #include "nm-checkpoint-manager.h"
+#include "nm-checkpoint.h"
+#include "nm-config.h"
+#include "nm-connectivity.h"
+#include "nm-dbus-manager.h"
 #include "nm-dbus-object.h"
 #include "nm-dispatcher.h"
-#include "NetworkManagerUtils.h"
+#include "nm-hostname-manager.h"
+#include "nm-keep-alive.h"
+#include "nm-policy.h"
+#include "nm-priv-helper-call.h"
+#include "nm-rfkill-manager.h"
+#include "nm-session-monitor.h"
+#include "nm-sleep-monitor.h"
+#include "settings/nm-settings-connection.h"
+#include "settings/nm-settings.h"
+#include "vpn/nm-vpn-manager.h"
 
 #define DEVICE_STATE_PRUNE_RATELIMIT_MAX 100u
 
@@ -214,6 +213,13 @@ struct _NMManager {
 
 typedef struct {
     NMDBusObjectClass parent;
+
+#if WITH_OPENVSWITCH
+    /* these fields only serve the purpose to use the symbols.*/
+    void (*_use_symbol_nm_priv_helper_call_get_fd)(void);
+    void (*_use_symbol_nm_priv_helper_utils_open_fd)(void);
+#endif
+
 } NMManagerClass;
 
 G_DEFINE_TYPE(NMManager, nm_manager, NM_TYPE_DBUS_OBJECT)
@@ -2854,9 +2860,10 @@ recheck_assume_connection(NMManager *self, NMDevice *device)
     g_return_val_if_fail(NM_IS_DEVICE(device), FALSE);
 
     if (!nm_device_get_managed(device, FALSE)) {
-        /* If the device is only unmanaged by NM_UNMANAGED_PLATFORM_INIT,
+        /* If the device is unmanaged by NM_UNMANAGED_PLATFORM_INIT or NM_UNMANAGED_PARENT,
          * don't reset the state now but wait until it becomes managed. */
-        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL) != NM_UNMANAGED_PLATFORM_INIT)
+        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL)
+            & ~(NM_UNMANAGED_PLATFORM_INIT | NM_UNMANAGED_PARENT))
             nm_device_assume_state_reset(device);
         _LOG2D(LOGD_DEVICE, device, "assume: don't assume because %s", "not managed");
         return FALSE;
@@ -2867,7 +2874,7 @@ recheck_assume_connection(NMManager *self, NMDevice *device)
         _LOG2D(LOGD_DEVICE,
                device,
                "assume: don't assume due to device state %s",
-               nm_device_state_to_str(state));
+               nm_device_state_to_string(state));
         return FALSE;
     }
 
@@ -3179,9 +3186,10 @@ _device_realize_finish(NMManager *self, NMDevice *device, const NMPlatformLink *
     nm_device_realize_finish(device, plink);
 
     if (!nm_device_get_managed(device, FALSE)) {
-        /* If the device is only unmanaged by NM_UNMANAGED_PLATFORM_INIT,
+        /* If the device is unmanaged by NM_UNMANAGED_PLATFORM_INIT or NM_UNMANAGED_PARENT,
          * don't reset the state now but wait until it becomes managed. */
-        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL) != NM_UNMANAGED_PLATFORM_INIT)
+        if (nm_device_get_unmanaged_flags(device, NM_UNMANAGED_ALL)
+            & ~(NM_UNMANAGED_PLATFORM_INIT | NM_UNMANAGED_PARENT))
             nm_device_assume_state_reset(device);
         return;
     }
@@ -4751,7 +4759,7 @@ active_connection_parent_active(NMActiveConnection *active,
     NMSettingsConnection *sett_conn;
     NMDevice *            parent;
 
-    g_signal_handlers_disconnect_by_func(active, (GCallback) active_connection_parent_active, self);
+    g_signal_handlers_disconnect_by_func(active, G_CALLBACK(active_connection_parent_active), self);
 
     if (!parent_ac) {
         _LOGW(LOGD_CORE,
@@ -4926,7 +4934,7 @@ _internal_activate_device(NMManager *self, NMActiveConnection *active, GError **
             /* We can't realize now; defer until the parent device is ready. */
             g_signal_connect(active,
                              NM_ACTIVE_CONNECTION_PARENT_ACTIVE,
-                             (GCallback) active_connection_parent_active,
+                             G_CALLBACK(active_connection_parent_active),
                              self);
             nm_active_connection_set_parent(active, parent_ac);
         } else {
@@ -6164,7 +6172,7 @@ sleep_devices_add(NMManager *self, NMDevice *device, gboolean suspending)
     g_hash_table_insert(priv->sleep_devices,
                         g_object_ref(device),
                         suspending ? nm_sleep_monitor_inhibit_take(priv->sleep_monitor) : NULL);
-    g_signal_connect(device, "notify::" NM_DEVICE_STATE, (GCallback) device_sleep_cb, self);
+    g_signal_connect(device, "notify::" NM_DEVICE_STATE, G_CALLBACK(device_sleep_cb), self);
     return TRUE;
 }
 
@@ -8076,7 +8084,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         g_value_set_boolean(value, priv->sleeping);
         break;
     case PROP_DEVICES:
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, FALSE)));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(_get_devices_paths(self, FALSE)));
         break;
     case PROP_METERED:
         g_value_set_uint(value, priv->metered);
@@ -8087,12 +8095,12 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         nm_global_dns_config_to_dbus(dns_config, value);
         break;
     case PROP_ALL_DEVICES:
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(_get_devices_paths(self, TRUE)));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(_get_devices_paths(self, TRUE)));
         break;
     case PROP_CHECKPOINTS:
         g_value_take_boxed(
             value,
-            priv->checkpoint_mgr ? nm_utils_strv_make_deep_copied(
+            priv->checkpoint_mgr ? nm_strv_make_deep_copied(
                 nm_checkpoint_manager_get_checkpoint_paths(priv->checkpoint_mgr, NULL))
                                  : NULL);
         break;
@@ -8530,6 +8538,16 @@ nm_manager_class_init(NMManagerClass *manager_class)
     GObjectClass *     object_class      = G_OBJECT_CLASS(manager_class);
     NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(manager_class);
 
+#if WITH_OPENVSWITCH
+    /* Use the symbols. These symbols are in NetworkManager binary but will be
+     * used by the OVS device plugin. If we don't use the symbol here, it will
+     * be wrongly dropped. */
+    manager_class->_use_symbol_nm_priv_helper_call_get_fd =
+        (void (*)(void)) nm_priv_helper_call_get_fd;
+    manager_class->_use_symbol_nm_priv_helper_utils_open_fd =
+        (void (*)(void)) nm_priv_helper_utils_open_fd;
+#endif
+
     dbus_object_class->export_path     = NM_DBUS_EXPORT_PATH_STATIC(NM_DBUS_PATH);
     dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS(&interface_info_manager);
 
diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c
index d5a0317d..224070be 100644
--- a/src/core/nm-netns.c
+++ b/src/core/nm-netns.c
@@ -29,7 +29,7 @@ typedef struct {
     GHashTable *     l3cfgs;
     GHashTable *     shared_ips;
     CList            l3cfg_signal_pending_lst_head;
-    guint            signal_pending_idle_id;
+    GSource *        signal_pending_idle_source;
 } NMNetnsPrivate;
 
 struct _NMNetns {
@@ -189,7 +189,7 @@ _platform_signal_on_idle_cb(gpointer user_data)
     L3CfgData *              l3cfg_data;
     CList                    work_list;
 
-    priv->signal_pending_idle_id = 0;
+    nm_clear_g_source_inst(&priv->signal_pending_idle_source);
 
     /* we emit all queued signals together. However, we don't want to hook the
      * main loop for longer than the currently queued elements.
@@ -210,7 +210,7 @@ _platform_signal_on_idle_cb(gpointer user_data)
             nm_steal_int(&l3cfg_data->signal_pending_obj_type_flags));
     }
 
-    return G_SOURCE_REMOVE;
+    return G_SOURCE_CONTINUE;
 }
 
 static void
@@ -235,8 +235,9 @@ _platform_signal_cb(NMPlatform *  platform,
 
     if (c_list_is_empty(&l3cfg_data->signal_pending_lst)) {
         c_list_link_tail(&priv->l3cfg_signal_pending_lst_head, &l3cfg_data->signal_pending_lst);
-        if (priv->signal_pending_idle_id == 0)
-            priv->signal_pending_idle_id = g_idle_add(_platform_signal_on_idle_cb, self);
+        if (!priv->signal_pending_idle_source)
+            priv->signal_pending_idle_source =
+                nm_g_idle_add_source(_platform_signal_on_idle_cb, self);
     }
 
     _nm_l3cfg_notify_platform_change(l3cfg_data->l3cfg,
@@ -460,7 +461,7 @@ dispose(GObject *object)
     nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head));
     nm_assert(!priv->shared_ips);
 
-    nm_clear_g_source(&priv->signal_pending_idle_id);
+    nm_clear_g_source_inst(&priv->signal_pending_idle_source);
 
     if (priv->platform)
         g_signal_handlers_disconnect_by_data(priv->platform, &priv->_self_signal_user_data);
diff --git a/src/core/nm-policy.c b/src/core/nm-policy.c
index e147e504..03e082f1 100644
--- a/src/core/nm-policy.c
+++ b/src/core/nm-policy.c
@@ -322,7 +322,9 @@ ip6_remove_device_prefix_delegations(NMPolicy *self, NMDevice *device)
 }
 
 static void
-device_ip6_prefix_delegated(NMDevice *device, NMPlatformIP6Address *prefix, gpointer user_data)
+device_ip6_prefix_delegated(NMDevice *                  device,
+                            const NMPlatformIP6Address *prefix,
+                            gpointer                    user_data)
 {
     NMPolicyPrivate *    priv       = user_data;
     NMPolicy *           self       = _PRIV_TO_SELF(priv);
@@ -913,7 +915,7 @@ update_system_hostname(NMPolicy *self, const char *msg)
                 if (wait) {
                     g_signal_connect(info->device,
                                      NM_DEVICE_DNS_LOOKUP_DONE,
-                                     (GCallback) device_dns_lookup_done,
+                                     G_CALLBACK(device_dns_lookup_done),
                                      self);
                     return;
                 }
@@ -2057,7 +2059,7 @@ device_state_changed(NMDevice *          device,
                 _LOGD(LOGD_DEVICE,
                       "blocking autoconnect of connection '%s': %s",
                       nm_settings_connection_get_id(sett_conn),
-                      NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str,
+                      NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_string,
                                             nm_device_state_reason_check(reason)));
                 nm_settings_connection_autoconnect_blocked_reason_set(sett_conn,
                                                                       blocked_reason,
@@ -2141,6 +2143,7 @@ device_ip_config_changed(NMDevice *  device,
     NMPolicyPrivate *priv = user_data;
     NMPolicy *       self = _PRIV_TO_SELF(priv);
     int              addr_family;
+    NMDeviceState    state;
 
     nm_assert(new_config || old_config);
     nm_assert(!new_config || NM_IS_IP_CONFIG(new_config));
@@ -2159,7 +2162,8 @@ device_ip_config_changed(NMDevice *  device,
      * ignore IP config changes but when the device is in activated state.
      * Prevents unnecessary changes to DNS information.
      */
-    if (nm_device_get_state(device) == NM_DEVICE_STATE_ACTIVATED) {
+    state = nm_device_get_state(device);
+    if (state > NM_DEVICE_STATE_IP_CONFIG && state <= NM_DEVICE_STATE_ACTIVATED) {
         if (old_config != new_config) {
             if (new_config)
                 _dns_manager_set_ip_config(priv->dns_manager,
@@ -2222,30 +2226,30 @@ devices_list_register(NMPolicy *self, NMDevice *device)
     NMPolicyPrivate *priv = NM_POLICY_GET_PRIVATE(self);
 
     /* Connect state-changed with _after, so that the handler is invoked after other handlers. */
-    g_signal_connect_after(device, NM_DEVICE_STATE_CHANGED, (GCallback) device_state_changed, priv);
+    g_signal_connect_after(device, NM_DEVICE_STATE_CHANGED, G_CALLBACK(device_state_changed), priv);
     g_signal_connect(device,
                      NM_DEVICE_IP4_CONFIG_CHANGED,
-                     (GCallback) device_ip_config_changed,
+                     G_CALLBACK(device_ip_config_changed),
                      priv);
     g_signal_connect(device,
                      NM_DEVICE_IP6_CONFIG_CHANGED,
-                     (GCallback) device_ip_config_changed,
+                     G_CALLBACK(device_ip_config_changed),
                      priv);
     g_signal_connect(device,
                      NM_DEVICE_IP6_PREFIX_DELEGATED,
-                     (GCallback) device_ip6_prefix_delegated,
+                     G_CALLBACK(device_ip6_prefix_delegated),
                      priv);
     g_signal_connect(device,
                      NM_DEVICE_IP6_SUBNET_NEEDED,
-                     (GCallback) device_ip6_subnet_needed,
+                     G_CALLBACK(device_ip6_subnet_needed),
                      priv);
     g_signal_connect(device,
                      "notify::" NM_DEVICE_AUTOCONNECT,
-                     (GCallback) device_autoconnect_changed,
+                     G_CALLBACK(device_autoconnect_changed),
                      priv);
     g_signal_connect(device,
                      NM_DEVICE_RECHECK_AUTO_ACTIVATE,
-                     (GCallback) device_recheck_auto_activate,
+                     G_CALLBACK(device_recheck_auto_activate),
                      priv);
 }
 
@@ -2801,49 +2805,49 @@ constructed(GObject *object)
 
     g_signal_connect(priv->hostname_manager,
                      "notify::" NM_HOSTNAME_MANAGER_HOSTNAME,
-                     (GCallback) hostname_changed,
+                     G_CALLBACK(hostname_changed),
                      priv);
 
     g_signal_connect(priv->manager,
                      "notify::" NM_MANAGER_SLEEPING,
-                     (GCallback) sleeping_changed,
+                     G_CALLBACK(sleeping_changed),
                      priv);
     g_signal_connect(priv->manager,
                      "notify::" NM_MANAGER_NETWORKING_ENABLED,
-                     (GCallback) sleeping_changed,
+                     G_CALLBACK(sleeping_changed),
                      priv);
     g_signal_connect(priv->manager,
                      NM_MANAGER_INTERNAL_DEVICE_ADDED,
-                     (GCallback) device_added,
+                     G_CALLBACK(device_added),
                      priv);
     g_signal_connect(priv->manager,
                      NM_MANAGER_INTERNAL_DEVICE_REMOVED,
-                     (GCallback) device_removed,
+                     G_CALLBACK(device_removed),
                      priv);
     g_signal_connect(priv->manager,
                      NM_MANAGER_ACTIVE_CONNECTION_ADDED,
-                     (GCallback) active_connection_added,
+                     G_CALLBACK(active_connection_added),
                      priv);
     g_signal_connect(priv->manager,
                      NM_MANAGER_ACTIVE_CONNECTION_REMOVED,
-                     (GCallback) active_connection_removed,
+                     G_CALLBACK(active_connection_removed),
                      priv);
 
     g_signal_connect(priv->settings,
                      NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
-                     (GCallback) connection_added,
+                     G_CALLBACK(connection_added),
                      priv);
     g_signal_connect(priv->settings,
                      NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
-                     (GCallback) connection_updated,
+                     G_CALLBACK(connection_updated),
                      priv);
     g_signal_connect(priv->settings,
                      NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
-                     (GCallback) connection_removed,
+                     G_CALLBACK(connection_removed),
                      priv);
     g_signal_connect(priv->settings,
                      NM_SETTINGS_SIGNAL_CONNECTION_FLAGS_CHANGED,
-                     (GCallback) connection_flags_changed,
+                     G_CALLBACK(connection_flags_changed),
                      priv);
 
     g_signal_connect(priv->agent_mgr,
diff --git a/src/core/nm-priv-helper-call.c b/src/core/nm-priv-helper-call.c
new file mode 100644
index 00000000..06249ece
--- /dev/null
+++ b/src/core/nm-priv-helper-call.c
@@ -0,0 +1,101 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-priv-helper-call.h"
+
+#include <gio/gunixfdlist.h>
+
+#include "nm-dbus-manager.h"
+
+/*****************************************************************************/
+
+static void
+_nm_priv_helper_call_get_fd_cb(GObject *source, GAsyncResult *res, gpointer user_data)
+{
+    NMPrivHelperCallGetFDCallback callback;
+    gpointer                      callback_data;
+    gs_unref_variant GVariant *ret       = NULL;
+    gs_free_error GError *error          = NULL;
+    gs_unref_object GUnixFDList *fd_list = NULL;
+    gs_free int *                fd_arr  = NULL;
+
+    nm_utils_user_data_unpack(user_data, &callback, &callback_data);
+
+    ret = g_dbus_connection_call_with_unix_fd_list_finish(G_DBUS_CONNECTION(source),
+                                                          &fd_list,
+                                                          res,
+                                                          &error);
+
+    if (error) {
+        callback(-1, error, callback_data);
+        return;
+    }
+
+    if (!fd_list || g_unix_fd_list_get_length(fd_list) != 1) {
+        nm_utils_error_set(&error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "Unexpectedly not one FD is returned by nm-priv-helper GetFD()");
+        callback(-1, error, callback_data);
+        return;
+    }
+
+    fd_arr = g_unix_fd_list_steal_fds(fd_list, NULL);
+
+    /* we transfer ownership of the file descriptor! */
+    callback(fd_arr[0], NULL, callback_data);
+}
+
+static gboolean
+_nm_priv_helper_call_get_fd_fail_on_idle(gpointer user_data)
+{
+    gs_unref_object GCancellable *cancellable = NULL;
+    NMPrivHelperCallGetFDCallback callback;
+    gpointer                      callback_data;
+    gs_free_error GError *error = NULL;
+
+    nm_utils_user_data_unpack(user_data, &cancellable, &callback, &callback_data);
+
+    if (!g_cancellable_set_error_if_cancelled(cancellable, &error))
+        nm_utils_error_set(&error,
+                           NM_UTILS_ERROR_UNKNOWN,
+                           "Cannot talk to nm-priv-helper without D-Bus");
+
+    callback(-1, error, callback_data);
+    return G_SOURCE_REMOVE;
+}
+
+void
+nm_priv_helper_call_get_fd(NMPrivHelperGetFDType         fd_type,
+                           GCancellable *                cancellable,
+                           NMPrivHelperCallGetFDCallback callback,
+                           gpointer                      user_data)
+{
+    GDBusConnection *dbus_connection;
+
+    nm_assert(NM_IN_SET(fd_type, NM_PRIV_HELPER_GET_FD_TYPE_OVSDB_SOCKET));
+    nm_assert(!cancellable || G_IS_CANCELLABLE(cancellable));
+    nm_assert(callback);
+
+    dbus_connection = NM_MAIN_DBUS_CONNECTION_GET;
+
+    if (!dbus_connection) {
+        nm_g_idle_add(_nm_priv_helper_call_get_fd_fail_on_idle,
+                      nm_utils_user_data_pack(g_object_ref(cancellable), callback, user_data));
+        return;
+    }
+
+    g_dbus_connection_call_with_unix_fd_list(dbus_connection,
+                                             NM_PRIV_HELPER_DBUS_BUS_NAME,
+                                             NM_PRIV_HELPER_DBUS_OBJECT_PATH,
+                                             NM_PRIV_HELPER_DBUS_IFACE_NAME,
+                                             "GetFD",
+                                             g_variant_new("(u)", fd_type),
+                                             G_VARIANT_TYPE("()"),
+                                             G_DBUS_CALL_FLAGS_NONE,
+                                             10000,
+                                             NULL,
+                                             cancellable,
+                                             _nm_priv_helper_call_get_fd_cb,
+                                             nm_utils_user_data_pack(callback, user_data));
+}
diff --git a/src/core/nm-priv-helper-call.h b/src/core/nm-priv-helper-call.h
new file mode 100644
index 00000000..1915c5b3
--- /dev/null
+++ b/src/core/nm-priv-helper-call.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#ifndef __NM_PRIV_HELPER_CALL_H__
+#define __NM_PRIV_HELPER_CALL_H__
+
+#include "../libnm-base/nm-priv-helper-utils.h"
+
+typedef void (*NMPrivHelperCallGetFDCallback)(int fd_take, GError *error, gpointer user_data);
+
+void nm_priv_helper_call_get_fd(NMPrivHelperGetFDType         fd_type,
+                                GCancellable *                cancellable,
+                                NMPrivHelperCallGetFDCallback callback,
+                                gpointer                      user_data);
+
+#endif /* __NM_PRIV_HELPER_CALL_H__ */
diff --git a/src/core/nm-session-monitor.c b/src/core/nm-session-monitor.c
index 88de1a00..2cc99ab3 100644
--- a/src/core/nm-session-monitor.c
+++ b/src/core/nm-session-monitor.c
@@ -14,17 +14,17 @@
 #include <sys/stat.h>
 
 #if SESSION_TRACKING_SYSTEMD && SESSION_TRACKING_ELOGIND
-    #error Cannot build both systemd-logind and elogind support
+#error Cannot build both systemd-logind and elogind support
 #endif
 
 #if SESSION_TRACKING_SYSTEMD
-    #include <systemd/sd-login.h>
-    #define LOGIND_NAME "systemd-logind"
+#include <systemd/sd-login.h>
+#define LOGIND_NAME "systemd-logind"
 #endif
 
 #if SESSION_TRACKING_ELOGIND
-    #include <elogind/sd-login.h>
-    #define LOGIND_NAME "elogind"
+#include <elogind/sd-login.h>
+#define LOGIND_NAME "elogind"
 #endif
 
 #include "NetworkManagerUtils.h"
diff --git a/src/core/nm-sleep-monitor.c b/src/core/nm-sleep-monitor.c
index 306453e7..76f13203 100644
--- a/src/core/nm-sleep-monitor.c
+++ b/src/core/nm-sleep-monitor.c
@@ -16,23 +16,23 @@
 
 #if defined(SUSPEND_RESUME_UPOWER)
 
-    #define SUSPEND_DBUS_NAME      "org.freedesktop.UPower"
-    #define SUSPEND_DBUS_PATH      "/org/freedesktop/UPower"
-    #define SUSPEND_DBUS_INTERFACE "org.freedesktop.UPower"
-    #define USE_UPOWER             1
-    #define _NMLOG_PREFIX_NAME     "sleep-monitor-up"
+#define SUSPEND_DBUS_NAME      "org.freedesktop.UPower"
+#define SUSPEND_DBUS_PATH      "/org/freedesktop/UPower"
+#define SUSPEND_DBUS_INTERFACE "org.freedesktop.UPower"
+#define USE_UPOWER             1
+#define _NMLOG_PREFIX_NAME     "sleep-monitor-up"
 
 #elif defined(SUSPEND_RESUME_SYSTEMD) || defined(SUSPEND_RESUME_ELOGIND)
 
-    #define SUSPEND_DBUS_NAME      "org.freedesktop.login1"
-    #define SUSPEND_DBUS_PATH      "/org/freedesktop/login1"
-    #define SUSPEND_DBUS_INTERFACE "org.freedesktop.login1.Manager"
-    #define USE_UPOWER             0
-    #if defined(SUSPEND_RESUME_SYSTEMD)
-        #define _NMLOG_PREFIX_NAME "sleep-monitor-sd"
-    #else
-        #define _NMLOG_PREFIX_NAME "sleep-monitor-el"
-    #endif
+#define SUSPEND_DBUS_NAME      "org.freedesktop.login1"
+#define SUSPEND_DBUS_PATH      "/org/freedesktop/login1"
+#define SUSPEND_DBUS_INTERFACE "org.freedesktop.login1.Manager"
+#define USE_UPOWER             0
+#if defined(SUSPEND_RESUME_SYSTEMD)
+#define _NMLOG_PREFIX_NAME "sleep-monitor-sd"
+#else
+#define _NMLOG_PREFIX_NAME "sleep-monitor-el"
+#endif
 
 #elif defined(SUSPEND_RESUME_CONSOLEKIT)
 
@@ -40,15 +40,15 @@
  * uses. http://consolekit2.github.io/ConsoleKit2/#Manager.Inhibit
  */
 
-    #define SUSPEND_DBUS_NAME      "org.freedesktop.ConsoleKit"
-    #define SUSPEND_DBUS_PATH      "/org/freedesktop/ConsoleKit/Manager"
-    #define SUSPEND_DBUS_INTERFACE "org.freedesktop.ConsoleKit.Manager"
-    #define USE_UPOWER             0
-    #define _NMLOG_PREFIX_NAME     "sleep-monitor-ck"
+#define SUSPEND_DBUS_NAME      "org.freedesktop.ConsoleKit"
+#define SUSPEND_DBUS_PATH      "/org/freedesktop/ConsoleKit/Manager"
+#define SUSPEND_DBUS_INTERFACE "org.freedesktop.ConsoleKit.Manager"
+#define USE_UPOWER             0
+#define _NMLOG_PREFIX_NAME     "sleep-monitor-ck"
 
 #else
 
-    #error define one of SUSPEND_RESUME_SYSTEMD, SUSPEND_RESUME_ELOGIND, SUSPEND_RESUME_CONSOLEKIT, or SUSPEND_RESUME_UPOWER
+#error define one of SUSPEND_RESUME_SYSTEMD, SUSPEND_RESUME_ELOGIND, SUSPEND_RESUME_CONSOLEKIT, or SUSPEND_RESUME_UPOWER
 
 #endif
 
diff --git a/src/core/nm-test-utils-core.h b/src/core/nm-test-utils-core.h
index 99d12ba9..e037ba92 100644
--- a/src/core/nm-test-utils-core.h
+++ b/src/core/nm-test-utils-core.h
@@ -222,7 +222,7 @@ nmtst_platform_ip4_routes_equal(const NMPlatformIP4Route *a,
     }
 }
 
-    #ifdef __NMP_OBJECT_H__
+#ifdef __NMP_OBJECT_H__
 
 static inline void
 nmtst_platform_ip4_routes_equal_aptr(const NMPObject *const *  a,
@@ -242,7 +242,7 @@ nmtst_platform_ip4_routes_equal_aptr(const NMPObject *const *  a,
     nmtst_platform_ip4_routes_equal(c_a, b, len, ignore_order);
 }
 
-    #endif
+#endif
 
 static inline int
 _nmtst_platform_ip6_routes_equal_sort(gconstpointer a, gconstpointer b, gpointer user_data)
@@ -291,7 +291,7 @@ nmtst_platform_ip6_routes_equal(const NMPlatformIP6Route *a,
     }
 }
 
-    #ifdef __NMP_OBJECT_H__
+#ifdef __NMP_OBJECT_H__
 
 static inline void
 nmtst_platform_ip6_routes_equal_aptr(const NMPObject *const *  a,
@@ -311,13 +311,13 @@ nmtst_platform_ip6_routes_equal_aptr(const NMPObject *const *  a,
     nmtst_platform_ip6_routes_equal(c_a, b, len, ignore_order);
 }
 
-    #endif
+#endif
 
 #endif
 
 #ifdef __NETWORKMANAGER_IP4_CONFIG_H__
 
-    #include "libnm-glib-aux/nm-dedup-multi.h"
+#include "libnm-glib-aux/nm-dedup-multi.h"
 
 static inline NMIP4Config *
 nmtst_ip4_config_new(int ifindex)
@@ -331,7 +331,7 @@ nmtst_ip4_config_new(int ifindex)
 
 #ifdef __NETWORKMANAGER_IP6_CONFIG_H__
 
-    #include "libnm-glib-aux/nm-dedup-multi.h"
+#include "libnm-glib-aux/nm-dedup-multi.h"
 
 static inline NMIP6Config *
 nmtst_ip6_config_new(int ifindex)
diff --git a/src/core/nm-types.h b/src/core/nm-types.h
index 7ad5aee1..294cf409 100644
--- a/src/core/nm-types.h
+++ b/src/core/nm-types.h
@@ -7,7 +7,7 @@
 #define __NETWORKMANAGER_TYPES_H__
 
 #if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_DAEMON)
-    #error Cannot use this header.
+#error Cannot use this header.
 #endif
 
 /* core */
diff --git a/src/core/platform/tests/meson.build b/src/core/platform/tests/meson.build
index 5d55707a..8824ed2f 100644
--- a/src/core/platform/tests/meson.build
+++ b/src/core/platform/tests/meson.build
@@ -14,7 +14,6 @@ test_units = [
   ['test-platform-general', 'test-platform-general.c', test_c_flags, default_test_timeout],
   ['test-route-fake', 'test-route.c', test_fake_c_flags, default_test_timeout],
   ['test-route-linux', 'test-route.c', test_linux_c_flags, default_test_timeout],
-  ['test-tc-fake', 'test-tc.c', test_fake_c_flags, default_test_timeout],
   ['test-tc-linux', 'test-tc.c', test_linux_c_flags, default_test_timeout],
 ]
 
diff --git a/src/core/platform/tests/test-common.c b/src/core/platform/tests/test-common.c
index 2fc9e836..4f56a3a7 100644
--- a/src/core/platform/tests/test-common.c
+++ b/src/core/platform/tests/test-common.c
@@ -37,7 +37,9 @@ gboolean
 nmtstp_is_root_test(void)
 {
     g_assert(_nmtstp_setup_platform_func);
-    return _nmtstp_setup_platform_func == nm_linux_platform_setup;
+    return NM_IN_SET(_nmtstp_setup_platform_func,
+                     nm_linux_platform_setup,
+                     nm_linux_platform_setup_with_tc_cache);
 }
 
 gboolean
@@ -2694,9 +2696,13 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
                   NM_HASH_OBFUSCATE_PTR(defender),
                   NM_ETHER_ADDR_FORMAT_VAL((const NMEtherAddr *) event->defended.sender));
             break;
+        case N_ACD_EVENT_DOWN:
+            /* Not sure why this sometimes happens. But this is only the test stub, ignore it. */
+            _LOGT("acd-defender[" NM_HASH_OBFUSCATE_PTR_FMT "]: link down event received",
+                  NM_HASH_OBFUSCATE_PTR(defender));
+            break;
         case N_ACD_EVENT_USED:
         case N_ACD_EVENT_CONFLICT:
-        case N_ACD_EVENT_DOWN:
         default:
             g_assert_not_reached();
             break;
diff --git a/src/core/platform/tests/test-common.h b/src/core/platform/tests/test-common.h
index 1fb97d24..fc4149f0 100644
--- a/src/core/platform/tests/test-common.h
+++ b/src/core/platform/tests/test-common.h
@@ -104,11 +104,11 @@ SignalData *add_signal_full(const char *               name,
                             int                        ifindex,
                             const char *               ifname);
 #define add_signal(name, change_type, callback) \
-    add_signal_full(name, change_type, (GCallback) callback, 0, NULL)
+    add_signal_full(name, change_type, G_CALLBACK(callback), 0, NULL)
 #define add_signal_ifindex(name, change_type, callback, ifindex) \
-    add_signal_full(name, change_type, (GCallback) callback, ifindex, NULL)
+    add_signal_full(name, change_type, G_CALLBACK(callback), ifindex, NULL)
 #define add_signal_ifname(name, change_type, callback, ifname) \
-    add_signal_full(name, change_type, (GCallback) callback, 0, ifname)
+    add_signal_full(name, change_type, G_CALLBACK(callback), 0, ifname)
 void _accept_signal(const char *file, int line, const char *func, SignalData *data);
 void
 _accept_signals(const char *file, int line, const char *func, SignalData *data, int min, int max);
diff --git a/src/core/platform/tests/test-link.c b/src/core/platform/tests/test-link.c
index f6c3841e..24e3fd99 100644
--- a/src/core/platform/tests/test-link.c
+++ b/src/core/platform/tests/test-link.c
@@ -580,6 +580,7 @@ test_bridge_addr(void)
     char                  addr[ETH_ALEN];
     NMPlatformLink        link;
     const NMPlatformLink *plink = NULL;
+    NMPLinkAddress        hw_perm_addr;
 
     nm_utils_hwaddr_aton("de:ad:be:ef:00:11", addr, sizeof(addr));
 
@@ -599,27 +600,49 @@ test_bridge_addr(void)
 
     plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex);
     g_assert(plink);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, plink, &hw_perm_addr));
 
-    if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL)) {
-        g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex));
+    if (nmtstp_is_root_test()) {
+        g_assert_cmpint(nm_platform_link_get_inet6_addr_gen_mode(NM_PLATFORM_GET, link.ifindex),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_EUI64);
+        g_assert_cmpint(_nm_platform_link_get_inet6_addr_gen_mode(plink),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_EUI64);
         g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv),
                         ==,
                         NM_IN6_ADDR_GEN_MODE_EUI64);
 
         g_assert(NMTST_NM_ERR_SUCCESS(
-            nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, TRUE)));
-        g_assert(nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex));
+            nm_platform_link_set_inet6_addr_gen_mode(NM_PLATFORM_GET,
+                                                     link.ifindex,
+                                                     NM_IN6_ADDR_GEN_MODE_NONE)));
+        g_assert_cmpint(nm_platform_link_get_inet6_addr_gen_mode(NM_PLATFORM_GET, link.ifindex),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_NONE);
         plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex);
         g_assert(plink);
+        g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, plink, &hw_perm_addr));
+        g_assert_cmpint(_nm_platform_link_get_inet6_addr_gen_mode(plink),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_NONE);
         g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv),
                         ==,
                         NM_IN6_ADDR_GEN_MODE_NONE);
 
         g_assert(NMTST_NM_ERR_SUCCESS(
-            nm_platform_link_set_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex, FALSE)));
-        g_assert(!nm_platform_link_get_user_ipv6ll_enabled(NM_PLATFORM_GET, link.ifindex));
+            nm_platform_link_set_inet6_addr_gen_mode(NM_PLATFORM_GET,
+                                                     link.ifindex,
+                                                     NM_IN6_ADDR_GEN_MODE_EUI64)));
+        g_assert_cmpint(nm_platform_link_get_inet6_addr_gen_mode(NM_PLATFORM_GET, link.ifindex),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_EUI64);
         plink = nm_platform_link_get(NM_PLATFORM_GET, link.ifindex);
         g_assert(plink);
+        g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, plink, &hw_perm_addr));
+        g_assert_cmpint(_nm_platform_link_get_inet6_addr_gen_mode(plink),
+                        ==,
+                        NM_IN6_ADDR_GEN_MODE_EUI64);
         g_assert_cmpint(_nm_platform_uint8_inv(plink->inet6_addr_gen_mode_inv),
                         ==,
                         NM_IN6_ADDR_GEN_MODE_EUI64);
@@ -730,6 +753,7 @@ static void
 test_external(void)
 {
     const NMPlatformLink *pllink;
+    NMPLinkAddress        hw_perm_addr;
     SignalData *          link_added, *link_changed, *link_removed;
     int                   ifindex;
 
@@ -758,6 +782,7 @@ test_external(void)
 
     pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex);
     g_assert(pllink);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
     if (!pllink->initialized) {
         /* we still lack the notification via UDEV. Expect another link changed signal. */
         wait_signal(link_changed);
@@ -1488,7 +1513,7 @@ test_software_detect(gconstpointer user_data)
          * The fix (17af2bce) is included kernel 4.7, dated 24 July, 2016.
          */
         for (i = ifindex_parent + 1; i < ifindex_parent + 100; i++) {
-            snprintf(buf, sizeof(buf), "/sys/class/macvtap/tap%d", i);
+            g_snprintf(buf, sizeof(buf), "/sys/class/macvtap/tap%d", i);
             if (!g_file_test(buf, G_FILE_TEST_IS_SYMLINK))
                 break;
 
@@ -2522,6 +2547,7 @@ test_nl_bugs_veth(void)
     const NMPlatformLink *pllink_veth0, *pllink_veth1;
     gs_free_error GError * error     = NULL;
     NMTstpNamespaceHandle *ns_handle = NULL;
+    NMPLinkAddress         hw_perm_addr;
 
     /* create veth pair. */
     ifindex_veth0 = nmtstp_link_veth_add(NM_PLATFORM_GET, -1, IFACE_VETH0, IFACE_VETH1)->ifindex;
@@ -2538,6 +2564,7 @@ test_nl_bugs_veth(void)
     /* assert that NMPlatformLink.parent is the peer-ifindex. */
     pllink_veth0 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth0);
     g_assert(pllink_veth0);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink_veth0, &hw_perm_addr));
     if (pllink_veth0->parent == 0) {
         /* Kernels prior to 4.1 dated 21 June, 2015 don't support exposing the veth peer
          * as IFA_LINK. skip the remainder of the test. */
@@ -2549,6 +2576,7 @@ test_nl_bugs_veth(void)
      * https://bugzilla.redhat.com/show_bug.cgi?id=1285827 in place. */
     pllink_veth1 = nm_platform_link_get(NM_PLATFORM_GET, ifindex_veth1);
     g_assert(pllink_veth1);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink_veth1, &hw_perm_addr));
     g_assert_cmpint(pllink_veth1->parent, ==, ifindex_veth0);
 
     /* move one veth peer to another namespace and check that the
@@ -2588,6 +2616,7 @@ test_nl_bugs_spuroius_newlink(void)
     const char *          IFACE_DUMMY0 = "nm-test-dummy0";
     int                   ifindex_bond0, ifindex_dummy0;
     const NMPlatformLink *pllink;
+    NMPLinkAddress        hw_perm_addr;
     gboolean              wait_for_settle;
 
     /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
@@ -2609,6 +2638,7 @@ test_nl_bugs_spuroius_newlink(void)
 
         pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0);
         g_assert(pllink);
+        g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
         if (pllink->master == ifindex_bond0)
             break;
     });
@@ -2621,6 +2651,7 @@ again:
     nm_platform_process_events(NM_PLATFORM_GET);
     pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bond0);
     g_assert(!pllink);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
 
     if (wait_for_settle) {
         wait_for_settle = FALSE;
@@ -2641,6 +2672,7 @@ test_nl_bugs_spuroius_dellink(void)
     const char *          IFACE_DUMMY0  = "nm-test-dummy0";
     int                   ifindex_bridge0, ifindex_dummy0;
     const NMPlatformLink *pllink;
+    NMPLinkAddress        hw_perm_addr;
     gboolean              wait_for_settle;
 
     /* see https://bugzilla.redhat.com/show_bug.cgi?id=1285719 */
@@ -2663,6 +2695,7 @@ test_nl_bugs_spuroius_dellink(void)
 
         pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0);
         g_assert(pllink);
+        g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
         if (pllink->master == ifindex_bridge0)
             break;
     });
@@ -2677,8 +2710,10 @@ again:
     nm_platform_process_events(NM_PLATFORM_GET);
     pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_bridge0);
     g_assert(pllink);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
     pllink = nm_platform_link_get(NM_PLATFORM_GET, ifindex_dummy0);
     g_assert(pllink);
+    g_assert(!nm_platform_link_get_permanent_address(NM_PLATFORM_GET, pllink, &hw_perm_addr));
     g_assert_cmpint(pllink->parent, ==, 0);
 
     if (wait_for_settle) {
@@ -2716,7 +2751,7 @@ _test_netns_create_platform(void)
     netns = nmp_netns_new();
     g_assert(NMP_IS_NETNS(netns));
 
-    platform = nm_linux_platform_new(TRUE, TRUE);
+    platform = nm_linux_platform_new(TRUE, TRUE, TRUE);
     g_assert(NM_IS_LINUX_PLATFORM(platform));
 
     nmp_netns_pop(netns);
@@ -2805,7 +2840,7 @@ test_netns_general(gpointer fixture, gconstpointer test_data)
     if (_check_sysctl_skip())
         return;
 
-    platform_1 = nm_linux_platform_new(TRUE, TRUE);
+    platform_1 = nm_linux_platform_new(TRUE, TRUE, TRUE);
     platform_2 = _test_netns_create_platform();
 
     /* add some dummy devices. The "other-*" devices are there to bump the ifindex */
@@ -2933,7 +2968,7 @@ test_netns_set_netns(gpointer fixture, gconstpointer test_data)
     if (_test_netns_check_skip())
         return;
 
-    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE);
+    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE, TRUE);
     platforms[1] = platform_1 = _test_netns_create_platform();
     platforms[2] = platform_2 = _test_netns_create_platform();
 
@@ -3032,7 +3067,7 @@ test_netns_push(gpointer fixture, gconstpointer test_data)
     if (_check_sysctl_skip())
         return;
 
-    pl[0].platform = platform_0 = nm_linux_platform_new(TRUE, TRUE);
+    pl[0].platform = platform_0 = nm_linux_platform_new(TRUE, TRUE, TRUE);
     pl[1].platform = platform_1 = _test_netns_create_platform();
     pl[2].platform = platform_2 = _test_netns_create_platform();
 
@@ -3179,7 +3214,7 @@ test_netns_bind_to_path(gpointer fixture, gconstpointer test_data)
     if (_test_netns_check_skip())
         return;
 
-    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE);
+    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE, TRUE);
     platforms[1] = platform_1 = _test_netns_create_platform();
     platforms[2] = platform_2 = _test_netns_create_platform();
 
@@ -3344,7 +3379,7 @@ test_sysctl_netns_switch(void)
     if (_test_netns_check_skip())
         return;
 
-    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE);
+    platforms[0] = platform_0 = nm_linux_platform_new(TRUE, TRUE, TRUE);
     platforms[1] = platform_1 = _test_netns_create_platform();
     platforms[2] = platform_2 = _test_netns_create_platform();
     PL                        = platforms[nmtst_get_rand_uint32() % 3];
diff --git a/src/core/platform/tests/test-platform-general.c b/src/core/platform/tests/test-platform-general.c
index 05158228..8ffd92b5 100644
--- a/src/core/platform/tests/test-platform-general.c
+++ b/src/core/platform/tests/test-platform-general.c
@@ -31,7 +31,7 @@ test_init_linux_platform(void)
 {
     gs_unref_object NMPlatform *platform = NULL;
 
-    platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT);
+    platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT, TRUE);
 }
 
 /*****************************************************************************/
@@ -42,7 +42,7 @@ test_link_get_all(void)
     gs_unref_object NMPlatform *platform = NULL;
     gs_unref_ptrarray GPtrArray *links   = NULL;
 
-    platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT);
+    platform = nm_linux_platform_new(TRUE, NM_PLATFORM_NETNS_SUPPORT_DEFAULT, TRUE);
 
     links = nm_platform_link_get_all(platform, TRUE);
 }
@@ -104,34 +104,34 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
         [0] = ("5b1aea34648cabfec7c3523f76cf1ce34ca17a9a32f3f0f218424e48836dd1cb504e03d53e1124c5"
                "0065aeb2e6fbf952902383028e3b47f280f062ea1a7e0b7be218d067530e1b0487b8c3b99f2b8a1a"
                "8982c42f0000003437c5156e072b2f2f0037c9cfe07c34ddb3980deb14ab7b5af84a034703000000"
-               "883b0f3fd6ed84d6c959e553b887edcd01c2f5d200000043b809d259e499db7d00f1853bdcb0e4bc"
-               "0e2b00b667b7b16d8d1e69c803000000b973972c17a47631c169f11ff9119c400368b6630000007a"
+               "883b0f3fd6ed84d6c959e553b887edcd6101f5d200000043b809d259e499db7d00f1853bdcb0e4bc"
+               "0e2b00b667b7b16d8d1e69c803000000b973972c17a47631c169f11ff9119c40b403b6630000007a"
                "034f43340d01683c0045097aea4a849f060ddf57b24a5be9636360d603000000ad7c499dd538d345"
-               "74c038404923e75d0209e2fc0000004acc807cdce682f80f00315c45ef817264c89a736ed55ed637"
-               "b96c200203000000faf1809becd2506315a6da29b2e94d3a031fe0e900000083a36035fb6297dfa7"
+               "74c038404923e75d0e02e2fc0000004acc807cdce682f80f00315c45ef817264c89a736ed55ed637"
+               "b96c200203000000faf1809becd2506315a6da29b2e94d3ab503e0e900000083a36035fb6297dfa7"
                "00686b5efd0d53bb6215de4bb6f6f3d031a79028030000008bb836c0a25ea71f5daaed4d99eb2ebe"
-               "033c432f000000bf4ccf30d3aaaf02a4005d7308b67f91bf9d82c856ba942455e8d07c8403000000"
-               "f2abb982b001ec16901f55f960c55c22022099a80000002b4d4647f53b1921af0088e3759a08e7a5"
-               "6663861eea1bf42c12ea3b9503000000fb95e8332fdfff658483a2d039a7bf1402d3481e00000060"
+               "be03432f000000bf4ccf30d3aaaf02a4005d7308b67f91bf9d82c856ba942455e8d07c8403000000"
+               "f2abb982b001ec16901f55f960c55c22f30299a80000002b4d4647f53b1921af0088e3759a08e7a5"
+               "6663861eea1bf42c12ea3b9503000000fb95e8332fdfff658483a2d039a7bf148e02481e00000060"
                "e89f7abdb682380a00eae374835b4a49a2b980b6aba92da6409969aa03000000e00473755d31e5b2"
-               "de252167c1c91b3a02ba0c700000007740318db913a353ed006efc068829c0e66ad0143a0554efb4"
-               "40e55b8b03000000c0cfb2b4386bec092fa5757ecde9348b00c12ebd000000ab667224dae775e5cc"
+               "de252167c1c91b3a36020c700000007740318db913a353ed006efc068829c0e66ad0143a0554efb4"
+               "40e55b8b03000000c0cfb2b4386bec092fa5757ecde9348bda002ebd000000ab667224dae775e5cc"
                "0041aca2ff0f576767d3648102b61886d149f07403000000153ece68ade15cec25a59273e7519f34"
                "c4458d70000000f3819aa46fbe1439340033ae6dec0fb124f264af67eed7c9a8ecc8fb1103000000"
-               "fcebbaeb0c56535923f14874042a8aff021f8e5ec3cc13cc36bbe3c9bb0ec36f00e007bb64a2827b"
-               "7cdd38d0314c178e5a06c40b03000000ab90135fa636af4464d210a256be75e0029c44770000004b"
+               "fcebbaeb0c56535923f14874042a8aff1d028e5ec3cc13cc36bbe3c9bb0ec36f00e007bb64a2827b"
+               "7cdd38d0314c178e5a06c40b03000000ab90135fa636af4464d210a256be75e0500244770000004b"
                "2e69220d6c0fc09c25d6534c809829af4a9df58dbfef186d416f3a1e030000002c932e655203d82a"
-               "3c84c4eb274ed18603780281000000f2235376239daeaacb3cae864b437baaae91921681c2162b9b"
-               "69e66142030000004fda8a3e0b841cf76391dd68269b53ec0244a831000000b78c54dda9ac3bb1b1"
+               "3c84c4eb274ed18687030281000000f2235376239daeaacb3cae864b437baaae91921681c2162b9b"
+               "69e66142030000004fda8a3e0b841cf76391dd68269b53eccb02a831000000b78c54dda9ac3bb1b1"
                "d43e6505621b9a7f0422ae3fc8979ee0416f95e70300000057d6249b652ba98c7dc7f17f666969e4"
-               "023baf7a000000ee0b06fa9e988f80f0de6dc8dfcf2a3ad3bbcc0fa3b314f695111d891d03000000"
-               "cd897619f51f44e644d7cf1d06b2b1150082549e62c12fba5b1cdec48d10bbb13b8313d8cd2a24d3"
-               "4fc812bd2f8a59d90fcc00ac030000005292cd32dc096cd5d8a4c5cf3351ee1c03c2056c00000051"
+               "5c02af7a000000ee0b06fa9e988f80f0de6dc8dfcf2a3ad3bbcc0fa3b314f695111d891d03000000"
+               "cd897619f51f44e644d7cf1d06b2b115d800549e62c12fba5b1cdec48d10bbb13b8313d8cd2a24d3"
+               "4fc812bd2f8a59d90fcc00ac030000005292cd32dc096cd5d8a4c5cf3351ee1cfc03056c00000051"
                "bbce426cfa4b861cc78592be7b14e7ba9c15acb881ae55f0e5fe7d360300000066a3ae3939762df3"
-               "3a2d55060c78d55100b110fd00000041b9aca07b6e4925dd27943a272c171ed15abbbe1cd911db7b"
-               "86ed271803000000a5edc511c1507a141e0f515638c7ba31027609450000003357ae79989870ccec"
+               "3a2d55060c78d551cb0010fd00000041b9aca07b6e4925dd27943a272c171ed15abbbe1cd911db7b"
+               "86ed271803000000a5edc511c1507a141e0f515638c7ba31f50209450000003357ae79989870ccec"
                "3def0ad92749e016663fe6ee0228c1da82d1595603000000348352d715cf9d411ea012e5307294b6"
-               "0146dac4000000075efee38dd16f8ee4ccd2f50c30706cae3fdcc2f0ee3d5e26bb20413203000000"
+               "e301dac4000000075efee38dd16f8ee4ccd2f50c30706cae3fdcc2f0ee3d5e26bb20413203000000"
                "862573c2303dd1d65c7b2cffeca6d1adaccae11f0000000f855ebf3b772eb2b1c896c9a7304f6645"
                "0a5f6abd850b06e3b10123e6030000001dff045298cfee0636674cdefb57b9ae54cfe8f400000038"
                "1ba2c4396de60f032bc7f34de2959871c0d4c0d4eb720c4ab550c5db03000000f32f4af595d785d5"
@@ -139,108 +139,108 @@ test_platform_ip_address_pretty_sort_cmp(gconstpointer test_data)
                "b16c7376030000005219061ab4c5c79489b2cc6a883c146972decb8b000000f5e6d66df46ea13910"
                "7754dee62c36d2fc70ccc567df7a49b8585287dc03000000cfb18b2b2cb749e2e03e544d0eb4f73e"
                "75039fcf251b32fc79685b05ddd3aa9ba511d2e40edb4d758fb554158ae5c7c0beb42b3403000000"
-               "895d5f24037d233302ad3b82d639272e0246eadfbd2146bf8cfdb205f90e54b58a6ee136a779f37c"
+               "895d5f24037d233302ad3b82d639272e4a02eadfbd2146bf8cfdb205f90e54b58a6ee136a779f37c"
                "30d2c5053c40ecaec38b6b8e03000000bea73223e59bf0193432e9fa7a899f2d8ec7e4b89bf5a5d0"
                "6776e66a9d80ab132e1ac921eb76adbb229df32e561fa80a0fc4676703000000a23eb66e720da9e0"
-               "7ac998b5690807d50217369ee1af4ca5a6a95581af5fd7ceacdca10f47d7b351a36b178aabc78a4a"
-               "1a0dd8c003000000e2815a1a37a52bddd1c2f1018b587eed03bb58f0e9201f17bd99fcf72909ac9e"
+               "7ac998b5690807d52602369ee1af4ca5a6a95581af5fd7ceacdca10f47d7b351a36b178aabc78a4a"
+               "1a0dd8c003000000e2815a1a37a52bddd1c2f1018b587eed720358f0e9201f17bd99fcf72909ac9e"
                "7a55299e9bb4fd53bc7417940fcffe3f81cafd6302000000d6732578acd14320aefd4503189f7630"
-               "038e501c0000002b9f3c39f24b0572b100745cb25851429b3bbfb50168dfd04eb62f22ca02000000"
-               "891715df7fc6a902edae579e2e10c7f7022ba0340436242cbeb0248cee3fbc160032d4f28aa28c08"
+               "2403501c0000002b9f3c39f24b0572b100745cb25851429b3bbfb50168dfd04eb62f22ca02000000"
+               "891715df7fc6a902edae579e2e10c7f7a202a0340436242cbeb0248cee3fbc160032d4f28aa28c08"
                "f80dd50c6712dfb4abba4a32020000008ffe423d01883918039249f398f9b37ea091465100000064"
                "3722d9b707c0d8a400b7c8307f06b4b29088f20d9ac676d5e4bafc4e02000000fde69eec3af2e6d0"
                "bd68ab722af14548b29572e504265f6c72923e22594f3f790008ed2e2ebb0771db46a54cadb245ea"
-               "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd6011fa56dfdcfc1155f236c8b9c79a620"
+               "8c3b371502000000710c030690f5f18ea125dbf7d7e93bd65c01a56dfdcfc1155f236c8b9c79a620"
                "00660bbf024b03ff0a8e27c405e64244e36f90d402000000fd41fe47684b370b6ec6584d64496089"
-               "570968ead4d1ae91c819bb068196d59900de3246e43f5e7945aaf95e2ffa3a119c64ed0402000000"
-               "a660ac824b7fae389861419c50da49bf02092583000000259f9f0251becc987907879cca68fec7bb"
-               "bb5f8edf248b4995d184e82002000000a19cdf6dd1c173f6078a806d329c9b0000bfc972000000f5"
-               "b2cd3dbddc74e26de958e48d2ab8b0313e7f8933e315130f641e447502000000b9b68c08a5e4351e"
-               "a349e1ccf662e058b879a45100000045fcb6a035339d504c9726d80d9c2d89df765b4d9a130257dc"
-               "d1e1b34902000000bcd7be07d78e6222e45aaf61814f703b40125e6b000000cbaaa37b861e6d46da"
-               "fe7d6ec4ac1ea051010911915ddb05f2c43bd794010000005bfe47c6f53a54e01b0c1d89414d94d0"
-               "032d2ec50000000103781b0f294a2b7300421398f4de67e9cee64b38b56e03e01539ce4101000000"
-               "18238487a417f3da01d99dae5f19009601582cab0000005b2363d13edc5aa115005eb914d8fbe9a4"
-               "fdb3d117d76b0de5bd82e9ea010000003d1b91caae8cb60b49ba9be338d856a40267c3d400000064"
+               "570968ead4d1ae91c819bb068196d59900de3246e43f5e7945aaf95e2ffa3a11d1e1b34902000000"
+               "bcd7be07d78e6222e45aaf61814f703b40125e6b000000cbaaa37b861e6d46dafe7d6ec4ac1ea051"
+               "010911915ddb05f29c64ed0402000000a660ac824b7fae389861419c50da49bf2b02258300000025"
+               "9f9f0251becc987907879cca68fec7bbbb5f8edf248b4995d184e82002000000a19cdf6dd1c173f6"
+               "078a806d329c9b008b00c972000000f5b2cd3dbddc74e26de958e48d2ab8b0313e7f8933e315130f"
+               "641e447502000000b9b68c08a5e4351ea349e1ccf662e058b879a45100000045fcb6a035339d504c"
+               "9726d80d9c2d89df765b4d9a130257dcc43bd794010000005bfe47c6f53a54e01b0c1d89414d94d0"
+               "e0032ec50000000103781b0f294a2b7300421398f4de67e9cee64b38b56e03e01539ce4101000000"
+               "18238487a417f3da01d99dae5f190096cc012cab0000005b2363d13edc5aa115005eb914d8fbe9a4"
+               "fdb3d117d76b0de5bd82e9ea010000003d1b91caae8cb60b49ba9be338d856a40c02c3d400000064"
                "9da90bd2fff2f2560046870bd7d5f14870c6d18d6242b356b9ef1b2101000000f90adac616a31dc2"
-               "e46a234558817151008fc9c900000073e64b0bd761fdf274005dca8ce1bd1871ae17bb4515856092"
-               "b4d9e89b010000006855676c277cf1bd017c9148da5892bb0351c3a70000009f3391ce7d3fd48469"
+               "e46a234558817151b300c9c900000073e64b0bd761fdf274005dca8ce1bd1871ae17bb4515856092"
+               "b4d9e89b010000006855676c277cf1bd017c9148da5892bb4903c3a70000009f3391ce7d3fd48469"
                "005f0c233dbdd2a97835df1f1782dee86c1de913010000003ee1d76fc1ea76e98c9dfc78997ab53e"
-               "00c21963f5b4cb2454830c68e44ea74b002b83f9b3bee14d861a4c9eeecc89f65408c1b701000000"
-               "d9e1825fa638e1af284a024b7f9e85ff00e050a2000000276e08cb887464b93400c3127c750fabdd"
-               "69121aec129cdc690d58fbcc01000000d0d44ff1e51c35157666c05348e6f50700a8e89800000012"
+               "27001963f5b4cb2454830c68e44ea74b002b83f9b3bee14d861a4c9eeecc89f65408c1b701000000"
+               "d9e1825fa638e1af284a024b7f9e85ff730050a2000000276e08cb887464b93400c3127c750fabdd"
+               "69121aec129cdc690d58fbcc01000000d0d44ff1e51c35157666c05348e6f507ff00e89800000012"
                "b900b2061d0c334b009f2dd1cdf64b0f9a60e0e289f08db3fde6b0250100000039dd8f88152a5845"
-               "4d9ca9d20f45dfa702e78604000000fbcd7db68b9ae586da00b4070c50320427c4dd3d031e33f22c"
-               "210aeb09010000007000b96d06992b6a58acd3995b9663d20248f333ef467092818aa77d6732b678"
+               "4d9ca9d20f45dfa774028604000000fbcd7db68b9ae586da00b4070c50320427c4dd3d031e33f22c"
+               "210aeb09010000007000b96d06992b6a58acd3995b9663d2df02f333ef467092818aa77d6732b678"
                "00844ef5a943825fcd743f59bd14c89b955e1a13010000007acbce3e3bcdf3824f1b134847ff26ba"
-               "009a774900000017ec852d59f3d17232edd86ad6c3103a68843a9aef34983882d3d3878501000000"
-               "5e0dfe491d1ba96742c7b5e02b2271220188b02a06d0dc5504b0595daf37deb499996bfb667f072d"
-               "ec1e5d9cdc8a11f4409bcfb30100000099d90a8543961b2ccd47724a3c460ba80383f4c500000063"
+               "4400774900000017ec852d59f3d17232edd86ad6c3103a68843a9aef34983882d3d3878501000000"
+               "5e0dfe491d1ba96742c7b5e02b2271229c01b02a06d0dc5504b0595daf37deb499996bfb667f072d"
+               "ec1e5d9cdc8a11f4409bcfb30100000099d90a8543961b2ccd47724a3c460ba85103f4c500000063"
                "eafb1ac4c0982b283aa9986700b2a2b3ed257b8b0489f48f053ec8ca0100000027335a25a364d101"
-               "5ffac03089f4553902b01784000000d7b83579b8da27345a72437f9b6245de39ec9e71ee4b951507"
+               "5ffac03089f45539e1021784000000d7b83579b8da27345a72437f9b6245de39ec9e71ee4b951507"
                "f121014a01000000efc67bce716c856e3973dc42a1003be94f89d8ee0000009b5d5bbe6c10085f3d"
                "6176f78a19bb8df1804c122fce5078c156e8f3fe010000000ea8042602a1f8e6f5657f3f9e3eb807"
                "cbad7645000000b8df6f628a70456d79f25d5895fb57fa60d9279fb2b8fcbac65ad47b8a01000000"
                "da40d88d40a6d75bc404156225b7eedefc2b44574b15e2ae496ad01bf007eacb0a28aec868282510"
-               "b60291ea6480e356925b568a0000000087bb24e5264fd3ebe9cf9f6df9615189018ee815000000a7"
+               "b60291ea6480e356925b568a0000000087bb24e5264fd3ebe9cf9f6df9615189f701e815000000a7"
                "5c9555876b6a3f13002b6cb8360feaac1d5c302df59dd32a7a859db500000000362956cd46646a0e"
-               "222160e5f769bb290366ed370000005b6a813387e99bb834009da86c64fefab2548759d313a5b92d"
-               "8e47935e0000000034f0386a253c21d94064f6b021281e230094ae20000000d71fd050bf8d85055b"
+               "222160e5f769bb295703ed370000005b6a813387e99bb834009da86c64fefab2548759d313a5b92d"
+               "8e47935e0000000034f0386a253c21d94064f6b021281e235f00ae20000000d71fd050bf8d85055b"
                "00e3756ccdb3455c60ca7b11c66af76e594f24a100000000e143fd52599364e13468f80fd514573f"
                "b572671c0000006932d1d5f5d0ce2cf6007a70ba5193a162bc92ec1b11d9172c857ae81200000000"
                "3e29535402e9b690c628d048eacce745ea213cb1000000b632ef3be6070dafa200187470e9da5570"
                "9427c226d324d9a08487fd0d00000000b7a350f9fc1519defa7db4532545666937c22a3b000000dc"
                "c405cbee5016c25200d8901d7a0165fe20744edb6ba04f14a4c73cf500000000a4bab14874afdf54"
                "e6aae816430607ca0675e09818e9bbec5918c59068baf76a008940f6fc3bbdc7f6090f756aae660b"
-               "6e4c699300000000d9c1e67743efb54e54270e46042e911803894e38000000376feecb80ac245409"
-               "c0becc271d9c2f67179bff0644399ae7e3c9472e00000000c5cfd9f2343b21362c19a0921dce2f83"
-               "00defa45000000270b9977e166bee737fe73670c439a644c323b59b4cd20eb7dabea74f700000000"
-               "f6989d2d6a909e986ff7add5df2c93e05459507b0000000f466554d2ae4d52a8c67b2e48b47003c8"
-               "1785d3ffdbd9a617df6b3542000000004c5cf8107ba282f4f983821918f93e742d08f0550000006f"
-               "2292362e5d68265d9f98c82d9b7a559be3acf4fc36fa6b5159747cf200000000ab2dadc5a39411fd"
+               "6e4c699300000000d9c1e67743efb54e54270e46042e91186a034e38000000376feecb80ac245409"
+               "c0becc271d9c2f67179bff0644399ae7df6b3542000000004c5cf8107ba282f4f983821918f93e74"
+               "2d08f0550000006f2292362e5d68265d9f98c82d9b7a559be3acf4fc36fa6b51e3c9472e00000000"
+               "c5cfd9f2343b21362c19a0921dce2f839200fa45000000270b9977e166bee737fe73670c439a644c"
+               "323b59b4cd20eb7dabea74f700000000f6989d2d6a909e986ff7add5df2c93e05459507b0000000f"
+               "466554d2ae4d52a8c67b2e48b47003c81785d3ffdbd9a61759747cf200000000ab2dadc5a39411fd"
                "4ff1116d478987316a553fc2000000cfc6ebe434a7ae8ff040483e310819e3b10db116431ec6f769"
                "438a72e1000000002495a609675344f7e2e3a5ebaec3c85f0a1742f70abe95c50345132a61eda239"
                "d9d083c3bf085387046ef8a36f0e9e696b382ab0000000009a6ce5d906837dbca6a5ee19d6f63fe9"
-               "03dd01f7246f13b2050424a2b3a45ef7a029c896b4132bd895072cfeffe9d6815997069500000000"
-               "0d3c723b91adb0da7c4aa7e7eb5a15bc03015fb98b841fd84cc43c510385b9a4c2aac1d67a909b29"
+               "cb0301f7246f13b2050424a2b3a45ef7a029c896b4132bd895072cfeffe9d6815997069500000000"
+               "0d3c723b91adb0da7c4aa7e7eb5a15bcde035fb98b841fd84cc43c510385b9a4c2aac1d67a909b29"
                "7c703915312e9c3cae02dfa000000000dd603bd35e7fa0f02f2f3313d8469d09a92409c0b7f0318a"
                "575a4f8e061db3dd7fde25654a4059d565dbc8a91e3b4457b077ddad3108be69f9b97d05c917ad6b"
                "10e693bb6e26f2ba90c8e909a9fe20e5c7a4c656482a9b0d00625009a40aeb62a42b6a62548e3c38"
-               "cd3c72f203000000ca82ac5180101be4f85cef468ea086ea01aafdc3a9fe1ec787bc45db7c52a52d"
+               "cd3c72f203000000ca82ac5180101be4f85cef468ea086ea9a01fdc3a9fe1ec787bc45db7c52a52d"
                "00bd39a44e8e8bc17c01ac63eca0c1cf5ff7f03a03000000c9a89192c1c8be55281a59d1fd338f35"
-               "0075f8cea9fec34573654ea6624f138ef9531cd9367a02e4d241989477a363d53b02239e03000000"
-               "24438387def0f4c6544e4b275d9b714600f810d2a9fec17647176b7c07d856e3b883efebc09dd9d6"
-               "1966b7ae7412041d57393c6f03000000182c0287822a272bec4501a1e27acfee018588ffa9fe6cae"
+               "7e00f8cea9fec34573654ea6624f138ef9531cd9367a02e4d241989477a363d53b02239e03000000"
+               "24438387def0f4c6544e4b275d9b7146a70010d2a9fec17647176b7c07d856e3b883efebc09dd9d6"
+               "1966b7ae7412041d57393c6f03000000182c0287822a272bec4501a1e27acfee7a0188ffa9fe6cae"
                "426de59560fad65d67c624f285d7174177a47579dda0b6eaa9a84c820300000070b1646d8026e9f1"
-               "704f1b16286ba2da017ef082a9feed33ef60a8b540b26f66761d1f13badfad0fe8fa8f3c1aad2a82"
+               "704f1b16286ba2dabc01f082a9feed33ef60a8b540b26f66761d1f13badfad0fe8fa8f3c1aad2a82"
                "fa40546c03000000df2d7c2790d3119a051bb2ee8192ac0cfa3abc1ea9fe3e7d75a2f42b50c6a363"
                "40132378b95c59313bacba64dbe996206e6904f50200000047150b9b14010469823acb72bb89182f"
-               "00112196a9feb9153b36bc60be5b534e006527f67485ab35aca0c7ee419733853cf09e8b02000000"
-               "e79c10acfce165e332a62384ec04e5ba009185ada9fe0070a36dd51323b2c54200154d12f86c260a"
-               "9edfa7a74c1c83c1050f63f802000000443cacf59c6379a44b7892f487afa98c0221c19ca9fe942f"
+               "93002196a9feb9153b36bc60be5b534e006527f67485ab35aca0c7ee419733853cf09e8b02000000"
+               "e79c10acfce165e332a62384ec04e5bab40085ada9fe0070a36dd51323b2c54200154d12f86c260a"
+               "9edfa7a74c1c83c1050f63f802000000443cacf59c6379a44b7892f487afa98cb102c19ca9fe942f"
                "460bcea75481f25e007d0de9a7afe283bd2f22ead05ff72006c83bc0020000004bdade862c224f6f"
-               "36506ebd455e679c00369bb8a9fecec3f8c8fa6867a982be8a934f852cc3d4d82bc0ec7303f99f8f"
-               "def85b7502000000a0bef8675b29a197b7b3cceaf5f1bb120335256aa9fe6e5d58099ffc4a503a71"
+               "36506ebd455e679cef009bb8a9fecec3f8c8fa6867a982be8a934f852cc3d4d82bc0ec7303f99f8f"
+               "def85b7502000000a0bef8675b29a197b7b3cceaf5f1bb12c503256aa9fe6e5d58099ffc4a503a71"
                "2350acbd48411f0dc15d2f0f49dad345d966279502000000e06302aba042aaa218dc091e9aa1477f"
                "6fdc9830a9fec95829a8838314dff34d24c332219a1b163a732d803e0e2f4f916d06412601000000"
-               "98c39e7cc282208fefc57ff447036b9501adcb22a9fe793f797a3c7dadd1c86e009d0c90bc512e13"
-               "7dcef5e4a27985bd5cfd5ce601000000152f2b70eaef7443e0f79ab6902dde5301b3ec71a9fe9f25"
+               "98c39e7cc282208fefc57ff447036b955101cb22a9fe793f797a3c7dadd1c86e009d0c90bc512e13"
+               "7dcef5e4a27985bd5cfd5ce601000000152f2b70eaef7443e0f79ab6902dde533601ec71a9fe9f25"
                "4ac95883195580410062ed564153e17478f8c3344d89c0bbfaa100fd01000000be184524a6bdc878"
-               "9cf851782d895bcc03a98489a9fe8c1287e6f7bb020ffdb00012098610e52bb2a16a4008aefd545b"
-               "0d80684e01000000ba8110fb9733cc24904f288262e6ea77032fa5f8a9feeefa701d120523bd98f2"
+               "9cf851782d895bcc8d038489a9fe8c1287e6f7bb020ffdb00012098610e52bb2a16a4008aefd545b"
+               "0d80684e01000000ba8110fb9733cc24904f288262e6ea77a203a5f8a9feeefa701d120523bd98f2"
                "00098b43cd68be6e3f81268193fd637e9037d7a701000000c47cf0f551e96770a754ac19ef820fe0"
-               "0031f2d3a9fe049150b8d10ab700cc3a7cf51be0403b654ba2f56808092069af5f5b481b01000000"
+               "2900f2d3a9fe049150b8d10ab700cc3a7cf51be0403b654ba2f56808092069af5f5b481b01000000"
                "68cb3bc873b04d937a6ed8f7bc51e54066fed098a9fe048a92d3adc69a84eb47622400207799416a"
-               "f1f0a086fbd7e2f7dea0077a00000000c386e9c6e6a2cbfa10ee58bdc75183600085d627a9feb1cb"
+               "f1f0a086fbd7e2f7dea0077a00000000c386e9c6e6a2cbfa10ee58bdc75183609900d627a9feb1cb"
                "e491cbbbf9443fd6007eb3c5bf64b671d6f18dbf463f9b83f512dc1c00000000fbab244735d67c61"
-               "283031667b2d74a102e0b1b1a9fe2aa590a2312e17f1a35900459582f4ef43c780908872746e39ef"
-               "a9a89f8700000000fcdf6d9be94030b34774d1d7dddedd9802f0f627a9fe965a87041331b2834bcf"
+               "283031667b2d74a10302b1b1a9fe2aa590a2312e17f1a35900459582f4ef43c780908872746e39ef"
+               "a9a89f8700000000fcdf6d9be94030b34774d1d7dddedd989902f627a9fe965a87041331b2834bcf"
                "00b4e3ce848518c4e3f6cbf25e5e1b992231bb0200000000173c333cd03bc905b7b899afeb760e3f"
-               "024a2efca9fe96b89b1bc8f415bd4e77be46bae5a1b3cae76665a268abfe8a41a84e27c100000000"
-               "cb29efdf672d2fa57fc85ebbe276c5660364192da9fee7af5eb888e9eb37bb046686943b101e1f55"
-               "3215abf8fbdf17c3677e5a3a00000000608df061d45d864d09f4ecf17625f82d03c74828a9fe1e37"
+               "00022efca9fe96b89b1bc8f415bd4e77be46bae5a1b3cae76665a268abfe8a41a84e27c100000000"
+               "cb29efdf672d2fa57fc85ebbe276c5661a03192da9fee7af5eb888e9eb37bb046686943b101e1f55"
+               "3215abf8fbdf17c3677e5a3a00000000608df061d45d864d09f4ecf17625f82da1034828a9fe1e37"
                "1051852c972ea7954079884af257b044fd13a6826a4c619f3d136cac000000009402a4c216772167"
-               "3f2b02b3256ead1f03ea9bc1a9fefca162fb81e733cff620ca7feefe1933631e8e69f6d9d6962d2c"),
+               "3f2b02b3256ead1f2f039bc1a9fefca162fb81e733cff620ca7feefe1933631e8e69f6d9d6962d2c"),
         [1] = ("54270e46020000006a894e387625da376feecb80ac245409c0becc271d9c2f67179bff0644399ae7"
                "9c64ed0432d599eaa660ac824b7fae389861419c7a899f2d010000009bf5a5d06776e66a9d80ab13"
                "2e1ac921eb76adbb229df32e561fa80a40e55b8b1dd92e18c0cfb2b4386bec092fa5757ecde9348b"
diff --git a/src/core/platform/tests/test-tc.c b/src/core/platform/tests/test-tc.c
index cd9536cc..ee7b861e 100644
--- a/src/core/platform/tests/test-tc.c
+++ b/src/core/platform/tests/test-tc.c
@@ -57,7 +57,7 @@ test_qdisc1(void)
     g_ptr_array_add(known, qdisc_new(ifindex, "fq_codel", TC_H_ROOT));
     g_ptr_array_add(known, qdisc_new(ifindex, "ingress", TC_H_INGRESS));
 
-    g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known));
+    g_assert(nm_platform_tc_sync(NM_PLATFORM_GET, ifindex, known, NULL));
     plat = qdiscs_lookup(ifindex);
     g_assert(plat);
     g_assert_cmpint(plat->len, ==, 2);
@@ -97,7 +97,7 @@ test_qdisc_fq_codel(void)
     obj->qdisc.fq_codel.quantum = 1000;
     g_ptr_array_add(known, obj);
 
-    g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known));
+    g_assert(nm_platform_tc_sync(NM_PLATFORM_GET, ifindex, known, NULL));
     plat = qdiscs_lookup(ifindex);
     g_assert(plat);
     g_assert_cmpint(plat->len, ==, 1);
@@ -136,7 +136,7 @@ test_qdisc_sfq(void)
     obj->qdisc.sfq.flows          = 256;
     g_ptr_array_add(known, obj);
 
-    g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known));
+    g_assert(nm_platform_tc_sync(NM_PLATFORM_GET, ifindex, known, NULL));
     plat = qdiscs_lookup(ifindex);
     g_assert(plat);
     g_assert_cmpint(plat->len, ==, 1);
@@ -179,7 +179,7 @@ test_qdisc_tbf(void)
     obj->qdisc.handle = TC_H_MAKE(0x8005 << 16, 0);
     g_ptr_array_add(known, obj);
 
-    g_assert(nm_platform_qdisc_sync(NM_PLATFORM_GET, ifindex, known));
+    g_assert(nm_platform_tc_sync(NM_PLATFORM_GET, ifindex, known, NULL));
     plat = qdiscs_lookup(ifindex);
     g_assert(plat);
     g_assert_cmpint(plat->len, ==, 2);
@@ -202,7 +202,7 @@ test_qdisc_tbf(void)
 
 /*****************************************************************************/
 
-NMTstpSetupFunc const _nmtstp_setup_platform_func = SETUP;
+NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup_with_tc_cache;
 
 void
 _nmtstp_init_tests(int *argc, char ***argv)
@@ -213,10 +213,8 @@ _nmtstp_init_tests(int *argc, char ***argv)
 void
 _nmtstp_setup_tests(void)
 {
-    if (nmtstp_is_root_test()) {
-        nmtstp_env1_add_test_func("/link/qdisc/1", test_qdisc1, TRUE);
-        nmtstp_env1_add_test_func("/link/qdisc/fq_codel", test_qdisc_fq_codel, TRUE);
-        nmtstp_env1_add_test_func("/link/qdisc/sfq", test_qdisc_sfq, TRUE);
-        nmtstp_env1_add_test_func("/link/qdisc/tbf", test_qdisc_tbf, TRUE);
-    }
+    nmtstp_env1_add_test_func("/link/qdisc/1", test_qdisc1, TRUE);
+    nmtstp_env1_add_test_func("/link/qdisc/fq_codel", test_qdisc_fq_codel, TRUE);
+    nmtstp_env1_add_test_func("/link/qdisc/sfq", test_qdisc_sfq, TRUE);
+    nmtstp_env1_add_test_func("/link/qdisc/tbf", test_qdisc_tbf, TRUE);
 }
diff --git a/src/core/ppp/nm-ppp-manager.c b/src/core/ppp/nm-ppp-manager.c
index fefc5d61..e6790f22 100644
--- a/src/core/ppp/nm-ppp-manager.c
+++ b/src/core/ppp/nm-ppp-manager.c
@@ -21,7 +21,7 @@
 
 #include <linux/ppp_defs.h>
 #ifndef aligned_u64
-    #define aligned_u64 unsigned long long __attribute__((aligned(8)))
+#define aligned_u64 unsigned long long __attribute__((aligned(8)))
 #endif
 #include <linux/if.h>
 #include <linux/if_ppp.h>
@@ -761,7 +761,7 @@ create_pppd_cmd_line(NMPPPManager *  self,
     g_return_val_if_fail(setting != NULL, NULL);
 
 #ifndef PPPD_PATH
-    #define PPPD_PATH NULL
+#define PPPD_PATH NULL
 #endif
 
     pppd_binary = nm_utils_find_helper("pppd", PPPD_PATH, err);
diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c
index b423bf5b..71da97d0 100644
--- a/src/core/settings/nm-settings-connection.c
+++ b/src/core/settings/nm-settings-connection.c
@@ -346,7 +346,7 @@ _getsettings_cached_get(NMSettingsConnection *self, const NMConnectionSerializat
 
     priv->getsettings_cached.options = *options;
     priv->getsettings_cached.options.seen_bssids =
-        nm_utils_strv_dup_packed(priv->getsettings_cached.options.seen_bssids, -1);
+        nm_strv_dup_packed(priv->getsettings_cached.options.seen_bssids, -1);
 
     return priv->getsettings_cached.variant;
 }
@@ -382,7 +382,7 @@ _nm_settings_connection_set_connection(NMSettingsConnection *           self,
                                   NM_SETTING_COMPARE_FLAG_EXACT)) {
         connection_old   = priv->connection;
         priv->connection = g_object_ref(new_connection);
-        nmtst_connection_assert_unchanging(priv->connection);
+        nm_assert_connection_unchanging(priv->connection);
 
         _getsettings_cached_clear(priv);
         _nm_settings_notify_sorted_by_autoconnect_priority_maybe_changed(priv->settings);
diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c
index f9f98de7..b71b9d18 100644
--- a/src/core/settings/nm-settings.c
+++ b/src/core/settings/nm-settings.c
@@ -17,7 +17,7 @@
 #include <pwd.h>
 
 #if HAVE_SELINUX
-    #include <selinux/selinux.h>
+#include <selinux/selinux.h>
 #endif
 
 #include "libnm-core-aux-intern/nm-common-macros.h"
@@ -1352,7 +1352,7 @@ _connection_changed_track(NMSettings *       self,
               || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS));
     nm_assert(!connection || nm_streq0(uuid, nm_connection_get_uuid(connection)));
 
-    nmtst_connection_assert_unchanging(connection);
+    nm_assert_connection_unchanging(connection);
 
     sett_conn_entry =
         _sett_conn_entries_get(self, uuid) ?: _sett_conn_entries_create_and_add(self, uuid);
@@ -3372,7 +3372,7 @@ load_plugins(NMSettings *self, const char *const *plugins, GError **error)
             continue;
         }
 
-        if (nm_utils_strv_find_first((char **) plugins, iter - plugins, pname) >= 0) {
+        if (nm_strv_find_first(plugins, iter - plugins, pname) >= 0) {
             /* the plugin is already mentioned in the list previously.
              * Don't load a duplicate. */
             continue;
@@ -3452,7 +3452,7 @@ impl_settings_save_hostname(NMDBusObject *                     obj,
     g_variant_get(parameters, "(&s)", &hostname);
 
     /* Minimal validation of the hostname */
-    if (!nm_hostname_manager_validate_hostname(hostname)) {
+    if (!nm_utils_validate_hostname(hostname)) {
         error_code   = NM_SETTINGS_ERROR_INVALID_HOSTNAME;
         error_reason = "The hostname was too long or contained invalid characters";
         goto err;
@@ -3938,7 +3938,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
             priv->connections_len,
             G_STRUCT_OFFSET(NMSettingsConnection, _connections_lst),
             TRUE);
-        g_value_take_boxed(value, nm_utils_strv_make_deep_copied(strv));
+        g_value_take_boxed(value, nm_strv_make_deep_copied(strv));
         break;
     case PROP_STARTUP_COMPLETE:
         g_value_set_boolean(value, !nm_settings_get_startup_complete_blocked_reason(self, FALSE));
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
index 5038ac6a..8fdc01fb 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-plugin.c
@@ -848,7 +848,7 @@ _unhandled_specs_from_hashtable(GHashTable *hash)
     GSList *             list = NULL;
     guint                i, l;
 
-    keys = nm_utils_strdict_get_keys(hash, TRUE, &l);
+    keys = nm_strdict_get_keys(hash, TRUE, &l);
     for (i = l; i > 0;) {
         i--;
         list = g_slist_prepend(list, g_strdup(keys[i]));
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
index 03b347f9..6dd5ef05 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-reader.c
@@ -31,6 +31,7 @@
 #include "nm-setting-ethtool.h"
 #include "nm-setting-8021x.h"
 #include "nm-setting-bond.h"
+#include "nm-setting-bond-port.h"
 #include "nm-setting-team.h"
 #include "nm-setting-team-port.h"
 #include "nm-setting-bridge.h"
@@ -474,7 +475,7 @@ make_connection_setting(const char *file,
     if (v) {
         gs_free const char **items = NULL;
 
-        items = nm_utils_strsplit_set(v, " ");
+        items = nm_strsplit_set(v, " ");
         for (iter = items; iter && *iter; iter++) {
             if (!nm_setting_connection_add_permission(s_con, "user", *iter, NULL))
                 PARSE_WARNING("invalid USERS item '%s'", *iter);
@@ -490,7 +491,7 @@ make_connection_setting(const char *file,
     if (v) {
         gs_free const char **items = NULL;
 
-        items = nm_utils_strsplit_set(v, " \t");
+        items = nm_strsplit_set(v, " \t");
         for (iter = items; iter && *iter; iter++) {
             if (!nm_setting_connection_add_secondary(s_con, *iter))
                 PARSE_WARNING("secondary connection UUID '%s' already added", *iter);
@@ -633,6 +634,15 @@ make_connection_setting(const char *file,
         PARSE_WARNING("invalid LLMNR setting");
     g_object_set(s_con, NM_SETTING_CONNECTION_LLMNR, i_val, NULL);
 
+    i_val = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
+    if (!svGetValueEnum(ifcfg,
+                        "DNS_OVER_TLS",
+                        nm_setting_connection_dns_over_tls_get_type(),
+                        &i_val,
+                        NULL))
+        PARSE_WARNING("invalid DNS_OVER_TLS setting");
+    g_object_set(s_con, NM_SETTING_CONNECTION_DNS_OVER_TLS, i_val, NULL);
+
     return NM_SETTING(s_con);
 }
 
@@ -1036,7 +1046,7 @@ parse_route_line(const char *line,
      * Maybe later we want to support some form of quotation here.
      * Which of course, would be incompatible with initscripts.
      */
-    words_free = nm_utils_strsplit_set(line, " \t\n");
+    words_free = nm_strsplit_set(line, " \t\n");
 
     words = words_free ?: NM_PTRARRAY_EMPTY(const char *);
 
@@ -1582,7 +1592,7 @@ parse_dns_options(NMSettingIPConfig *ip_config, const char *value)
     if (!nm_setting_ip_config_has_dns_options(ip_config))
         nm_setting_ip_config_clear_dns_options(ip_config, TRUE);
 
-    options = nm_utils_strsplit_set(value, " ");
+    options = nm_strsplit_set(value, " ");
     if (options) {
         for (item = options; *item; item++) {
             if (!nm_setting_ip_config_add_dns_option(ip_config, *item))
@@ -1974,26 +1984,27 @@ make_ip4_setting(shvarFile *ifcfg,
         /* DNS servers
          * Pick up just IPv4 addresses (IPv6 addresses are taken by make_ip6_setting())
          */
-        for (i = 1; i <= 10; i++) {
+        for (i = 1; i < 10000; i++) {
             char tag[256];
 
             numbered_tag(tag, "DNS", i);
             nm_clear_g_free(&value);
             v = svGetValueStr(ifcfg, tag, &value);
-            if (v) {
-                if (nm_utils_ipaddr_is_valid(AF_INET, v)) {
-                    if (!nm_setting_ip_config_add_dns(s_ip4, v))
-                        PARSE_WARNING("duplicate DNS server %s", tag);
-                } else if (nm_utils_ipaddr_is_valid(AF_INET6, v)) {
-                    /* Ignore IPv6 addresses */
-                } else {
-                    g_set_error(error,
-                                NM_SETTINGS_ERROR,
-                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
-                                "Invalid DNS server address '%s'",
-                                v);
-                    return NULL;
-                }
+            if (!v)
+                break;
+
+            if (nm_utils_ipaddr_is_valid(AF_INET, v)) {
+                if (!nm_setting_ip_config_add_dns(s_ip4, v))
+                    PARSE_WARNING("duplicate DNS server %s", tag);
+            } else if (nm_utils_ipaddr_is_valid(AF_INET6, v)) {
+                /* Ignore IPv6 addresses */
+            } else {
+                g_set_error(error,
+                            NM_SETTINGS_ERROR,
+                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                            "Invalid DNS server address '%s'",
+                            v);
+                return NULL;
             }
         }
 
@@ -2003,7 +2014,7 @@ make_ip4_setting(shvarFile *ifcfg,
         if (v) {
             gs_free const char **searches = NULL;
 
-            searches = nm_utils_strsplit_set(v, " ");
+            searches = nm_strsplit_set(v, " ");
             if (searches) {
                 for (item = searches; *item; item++) {
                     if (!nm_setting_ip_config_add_dns_search(s_ip4, *item))
@@ -2063,7 +2074,7 @@ make_ip4_setting(shvarFile *ifcfg,
         if (v) {
             gs_free const char **searches = NULL;
 
-            searches = nm_utils_strsplit_set(v, " ");
+            searches = nm_strsplit_set(v, " ");
             if (searches) {
                 for (item = searches; *item; item++) {
                     if (!nm_setting_ip_config_add_dns_search(s_ip4, *item))
@@ -2249,6 +2260,7 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
     gboolean                           ip6_privacy   = FALSE, ip6_privacy_prefer_public_ip;
     NMSettingIP6ConfigPrivacy          ip6_privacy_val;
     guint32                            route_table;
+    gboolean                           is_disabled;
 
     s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
 
@@ -2375,10 +2387,9 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
                  NULL);
 
     /* Don't bother to read IP, DNS and routes when IPv6 is disabled */
-    if (NM_IN_STRSET(method,
-                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
-                     NM_SETTING_IP6_CONFIG_METHOD_DISABLED))
-        return NM_SETTING(g_steal_pointer(&s_ip6));
+    is_disabled = NM_IN_STRSET(method,
+                               NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                               NM_SETTING_IP6_CONFIG_METHOD_DISABLED);
 
     nm_clear_g_free(&value);
     v = svGetValueStr(ifcfg, "DHCPV6_DUID", &value);
@@ -2437,16 +2448,24 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
                       ipv6addr_secondaries ?: "",
                       NULL);
 
-    list = nm_utils_strsplit_set(value, " ");
-    for (iter = list, i = 0; iter && *iter; iter++, i++) {
-        NMIPAddress *addr = NULL;
+    list = nm_strsplit_set(value, " ");
+    if (list) {
+        if (is_disabled)
+            PARSE_WARNING("ignore IPv6 addresses with method disabled/ignore");
+        else {
+            for (iter = list, i = 0; *iter; iter++, i++) {
+                nm_auto_unref_ip_address NMIPAddress *addr = NULL;
 
-        if (!parse_full_ip6_address(ifcfg, *iter, i, &addr, error))
-            return NULL;
+                if (!parse_full_ip6_address(ifcfg, *iter, i, &addr, is_disabled ? NULL : error)) {
+                    if (is_disabled)
+                        break;
+                    return NULL;
+                }
 
-        if (!nm_setting_ip_config_add_address(s_ip6, addr))
-            PARSE_WARNING("duplicate IP6 address");
-        nm_ip_address_unref(addr);
+                if (!nm_setting_ip_config_add_address(s_ip6, addr))
+                    PARSE_WARNING("duplicate IP6 address");
+            }
+        }
     }
 
     /* Gateway */
@@ -2462,18 +2481,20 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
         }
         if (v) {
             char *ptr;
+
             if ((ptr = strchr(v, '%')) != NULL)
                 *ptr = '\0'; /* remove %interface prefix if present */
             if (!nm_utils_ipaddr_is_valid(AF_INET6, v)) {
-                g_set_error(error,
-                            NM_SETTINGS_ERROR,
-                            NM_SETTINGS_ERROR_INVALID_CONNECTION,
-                            "Invalid IP6 address '%s'",
-                            v);
-                return NULL;
-            }
-
-            g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
+                if (!is_disabled) {
+                    g_set_error(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "Invalid IP6 address '%s'",
+                                v);
+                    return NULL;
+                }
+            } else
+                g_object_set(s_ip6, NM_SETTING_IP_CONFIG_GATEWAY, v, NULL);
         }
     }
 
@@ -2497,23 +2518,27 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
     /* DNS servers
      * Pick up just IPv6 addresses (IPv4 addresses are taken by make_ip4_setting())
      */
-    for (i = 1; i <= 10; i++) {
+    for (i = 1; i < 10000; i++) {
         char tag[256];
 
         numbered_tag(tag, "DNS", i);
         nm_clear_g_free(&value);
         v = svGetValueStr(ifcfg, tag, &value);
-        if (!v) {
-            /* all done */
+        if (!v)
             break;
-        }
 
         if (nm_utils_ipaddr_is_valid(AF_INET6, v)) {
+            if (is_disabled) {
+                PARSE_WARNING("ignore DNS server addresses with method disabled/ignore");
+                break;
+            }
             if (!nm_setting_ip_config_add_dns(s_ip6, v))
                 PARSE_WARNING("duplicate DNS server %s", tag);
         } else if (nm_utils_ipaddr_is_valid(AF_INET, v)) {
             /* Ignore IPv4 addresses */
         } else {
+            if (is_disabled)
+                continue;
             g_set_error(error,
                         NM_SETTINGS_ERROR,
                         NM_SETTINGS_ERROR_INVALID_CONNECTION,
@@ -2540,11 +2565,15 @@ make_ip6_setting(shvarFile *ifcfg, shvarFile *network_ifcfg, gboolean routes_rea
     if (v) {
         gs_free const char **searches = NULL;
 
-        searches = nm_utils_strsplit_set(v, " ");
+        searches = nm_strsplit_set(v, " ");
         if (searches) {
-            for (iter = searches; *iter; iter++) {
-                if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter))
-                    PARSE_WARNING("duplicate DNS domain '%s'", *iter);
+            if (is_disabled) {
+                PARSE_WARNING("ignore IPV6_DOMAIN with method disabled/ignore");
+            } else {
+                for (iter = searches; *iter; iter++) {
+                    if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter))
+                        PARSE_WARNING("duplicate DNS domain '%s'", *iter);
+                }
             }
         }
     }
@@ -2957,7 +2986,7 @@ read_dcb_percent_array(shvarFile *       ifcfg,
         return TRUE;
     }
 
-    split = nm_utils_strsplit_set(val, ",");
+    split = nm_strsplit_set(val, ",");
     if (NM_PTRARRAY_LEN(split) != 8) {
         PARSE_WARNING("invalid %s percentage list value '%s'", prop, val);
         g_set_error_literal(error,
@@ -3001,6 +3030,7 @@ make_dcb_setting(shvarFile *ifcfg, NMSetting **out_setting, GError **error)
     gs_unref_object NMSettingDcb *s_dcb = NULL;
     gboolean                      dcb_on;
     NMSettingDcbFlags             flags = NM_SETTING_DCB_FLAG_NONE;
+    gs_free char *                val   = NULL;
 
     g_return_val_if_fail(out_setting, FALSE);
     *out_setting = NULL;
@@ -3020,21 +3050,18 @@ make_dcb_setting(shvarFile *ifcfg, NMSetting **out_setting, GError **error)
                       error)) {
         return FALSE;
     }
-    if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE) {
-        gs_free char *val = NULL;
 
-        val = svGetValueStr_cp(ifcfg, KEY_DCB_APP_FCOE_MODE);
-        if (val) {
-            if (NM_IN_STRSET(val, NM_SETTING_DCB_FCOE_MODE_FABRIC, NM_SETTING_DCB_FCOE_MODE_VN2VN))
-                g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL);
-            else {
-                PARSE_WARNING("invalid FCoE mode '%s'", val);
-                g_set_error_literal(error,
-                                    NM_SETTINGS_ERROR,
-                                    NM_SETTINGS_ERROR_INVALID_CONNECTION,
-                                    "invalid FCoE mode");
-                return FALSE;
-            }
+    val = svGetValueStr_cp(ifcfg, KEY_DCB_APP_FCOE_MODE);
+    if (val) {
+        if (NM_IN_STRSET(val, NM_SETTING_DCB_FCOE_MODE_FABRIC, NM_SETTING_DCB_FCOE_MODE_VN2VN))
+            g_object_set(G_OBJECT(s_dcb), NM_SETTING_DCB_APP_FCOE_MODE, val, NULL);
+        else {
+            PARSE_WARNING("invalid FCoE mode '%s'", val);
+            g_set_error_literal(error,
+                                NM_SETTINGS_ERROR,
+                                NM_SETTINGS_ERROR_INVALID_CONNECTION,
+                                "invalid FCoE mode");
+            return FALSE;
         }
     }
 
@@ -3376,7 +3403,7 @@ fill_wpa_ciphers(shvarFile *ifcfg, NMSettingWirelessSecurity *wsec, gboolean gro
     if (!p)
         return TRUE;
 
-    list = nm_utils_strsplit_set(p, " ");
+    list = nm_strsplit_set(p, " ");
     for (iter = list; iter && *iter; iter++, i++) {
         if (!strcmp(*iter, "CCMP")) {
             if (group)
@@ -3612,7 +3639,7 @@ parse_8021x_phase2_auth(shvarFile *     ifcfg,
     }
 
     inner_auth = g_ascii_strdown(v, -1);
-    list       = nm_utils_strsplit_set(inner_auth, " ");
+    list       = nm_strsplit_set(inner_auth, " ");
     for (iter = list; iter && *iter; iter++) {
         if (NM_IN_STRSET(*iter, "pap", "chap", "mschap", "mschapv2", "gtc", "otp", "md5")) {
             if (num_auth == 0) {
@@ -3787,7 +3814,7 @@ eap_fast_reader(const char *    eap_method,
     if (fast_provisioning) {
         gs_free const char **list = NULL;
 
-        list = nm_utils_strsplit_set(fast_provisioning, " \t");
+        list = nm_strsplit_set(fast_provisioning, " \t");
         for (iter = list; iter && *iter; iter++) {
             if (strcmp(*iter, "allow-unauth") == 0)
                 allow_unauth = TRUE;
@@ -3865,7 +3892,7 @@ read_8021x_list_value(shvarFile *     ifcfg,
     if (!v)
         return;
 
-    strv = nm_utils_strsplit_set(v, " \t");
+    strv = nm_strsplit_set(v, " \t");
     if (strv)
         g_object_set(setting, prop_name, strv, NULL);
 }
@@ -3892,7 +3919,7 @@ fill_8021x(shvarFile *ifcfg, const char *file, const char *key_mgmt, gboolean wi
         return NULL;
     }
 
-    list = nm_utils_strsplit_set(v, " ");
+    list = nm_strsplit_set(v, " ");
 
     s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
 
@@ -4245,7 +4272,7 @@ transform_hwaddr_blacklist(const char *blacklist)
     const char **strv;
     gsize        i, j;
 
-    strv = nm_utils_strsplit_set(blacklist, " \t");
+    strv = nm_strsplit_set(blacklist, " \t");
     if (!strv)
         return NULL;
     for (i = 0, j = 0; strv[j]; j++) {
@@ -4714,7 +4741,7 @@ parse_ethtool_option(const char *             value,
     gs_free const char **words        = NULL;
     NMEthtoolType        ethtool_type = NM_ETHTOOL_TYPE_UNKNOWN;
 
-    words = nm_utils_strsplit_set(value, " \t\n");
+    words = nm_strsplit_set(value, " \t\n");
     if (!words)
         return;
 
@@ -4976,7 +5003,7 @@ parse_ethtool_options(shvarFile *ifcfg, NMConnection *connection)
             gs_free const char **opts = NULL;
             const char *const *  iter;
 
-            opts = nm_utils_strsplit_set(ethtool_opts, ";");
+            opts = nm_strsplit_set(ethtool_opts, ";");
             for (iter = opts; iter && iter[0]; iter++) {
                 /* in case of repeated wol_passwords, parse_ethtool_option()
                  * will do the right thing and clear wol_password before resetting. */
@@ -5084,7 +5111,7 @@ make_wired_setting(shvarFile *ifcfg, const char *file, NMSetting8021x **s_8021x,
                 gs_free const char **chans = NULL;
                 guint32              num_chans;
 
-                chans     = nm_utils_strsplit_set(cvalue, ",");
+                chans     = nm_strsplit_set(cvalue, ",");
                 num_chans = NM_PTRARRAY_LEN(chans);
                 if (num_chans < 2 || num_chans > 3) {
                     PARSE_WARNING("invalid SUBCHANNELS '%s' (%u channels, 2 or 3 expected)",
@@ -5456,7 +5483,7 @@ make_bond_setting(shvarFile *ifcfg, const char *file, GError **error)
         gs_free const char **items = NULL;
         const char *const *  iter;
 
-        items = nm_utils_strsplit_set(v, " ");
+        items = nm_strsplit_set(v, " ");
         for (iter = items; iter && *iter; iter++) {
             gs_free char *key = NULL;
             const char *  val;
@@ -5474,6 +5501,31 @@ make_bond_setting(shvarFile *ifcfg, const char *file, GError **error)
     return (NMSetting *) s_bond;
 }
 
+static NMSetting *
+make_bond_port_setting(shvarFile *ifcfg)
+{
+    NMSetting *   s_port        = NULL;
+    gs_free char *value_to_free = NULL;
+    const char *  value;
+    guint         queue_id;
+
+    g_return_val_if_fail(ifcfg != NULL, FALSE);
+
+    value = svGetValue(ifcfg, "BOND_PORT_QUEUE_ID", &value_to_free);
+    if (value) {
+        s_port = nm_setting_bond_port_new();
+        queue_id =
+            _nm_utils_ascii_str_to_uint64(value, 10, 0, G_MAXUINT16, NM_BOND_PORT_QUEUE_ID_DEF);
+        if (errno != 0) {
+            PARSE_WARNING("Invalid bond port queue_id value '%s'", value);
+            return s_port;
+        }
+        g_object_set(G_OBJECT(s_port), NM_SETTING_BOND_PORT_QUEUE_ID, queue_id, NULL);
+    }
+
+    return s_port;
+}
+
 static NMConnection *
 bond_connection_from_ifcfg(const char *file, shvarFile *ifcfg, GError **error)
 {
@@ -5766,7 +5818,7 @@ handle_bridging_opts(NMSetting *   setting,
     gs_free const char **items = NULL;
     const char *const *  iter;
 
-    items = nm_utils_strsplit_set(value, " ");
+    items = nm_strsplit_set(value, " ");
     for (iter = items; iter && *iter; iter++) {
         gs_free char *key = NULL;
         const char *  val;
@@ -6027,7 +6079,7 @@ parse_prio_map_list(NMSettingVlan *s_vlan, shvarFile *ifcfg, const char *key, NM
     v = svGetValueStr(ifcfg, key, &value);
     if (!v)
         return;
-    list = nm_utils_strsplit_set(v, ",");
+    list = nm_strsplit_set(v, ",");
 
     for (iter = list; iter && *iter; iter++) {
         if (!strchr(*iter, ':'))
@@ -6138,7 +6190,7 @@ make_vlan_setting(shvarFile *ifcfg, const char *file, GError **error)
         gs_free const char **strv = NULL;
         const char *const *  ptr;
 
-        strv = nm_utils_strsplit_set(v, ", ");
+        strv = nm_strsplit_set(v, ", ");
         for (ptr = strv; ptr && *ptr; ptr++) {
             if (nm_streq(*ptr, "GVRP") && gvrp == -1)
                 vlan_flags |= NM_VLAN_FLAG_GVRP;
@@ -6284,7 +6336,7 @@ check_dns_search_domains(shvarFile *ifcfg, NMSetting *s_ip4, NMSetting *s_ip6)
             gs_free const char **searches = NULL;
             const char *const *  item;
 
-            searches = nm_utils_strsplit_set(v, " ");
+            searches = nm_strsplit_set(v, " ");
             if (searches) {
                 for (item = searches; *item; item++) {
                     if (!nm_setting_ip_config_add_dns_search(NM_SETTING_IP_CONFIG(s_ip6), *item))
@@ -6641,6 +6693,10 @@ connection_from_file_full(const char *filename,
     if (setting)
         nm_connection_add_setting(connection, setting);
 
+    setting = make_bond_port_setting(main_ifcfg);
+    if (setting)
+        nm_connection_add_setting(connection, setting);
+
     setting = make_team_port_setting(main_ifcfg);
     if (setting)
         nm_connection_add_setting(connection, setting);
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
index 134bdf68..3feb4440 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c
@@ -112,7 +112,7 @@ nms_ifcfg_rh_storage_new_connection(NMSIfcfgRHPlugin *     plugin,
 
     nm_assert(NM_IS_CONNECTION(connection_take));
     nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS);
-    nmtst_connection_assert_unchanging(connection_take);
+    nm_assert_connection_unchanging(connection_take);
 
     self             = _storage_new(plugin, nm_connection_get_uuid(connection_take), filename);
     self->connection = connection_take;
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
index febfc120..1afdf8ee 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.c
@@ -827,6 +827,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("BAND", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("BONDING_MASTER", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("BONDING_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
+    _KEY_TYPE("BOND_PORT_QUEUE_ID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("BOOTPROTO", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("BRIDGE", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("BRIDGE_MACADDR", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
@@ -891,6 +892,7 @@ const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[] = {
     _KEY_TYPE("DHCPv6_DUID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DHCPv6_IAID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DNS", NMS_IFCFG_KEY_TYPE_IS_NUMBERED),
+    _KEY_TYPE("DNS_OVER_TLS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("DOMAIN", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("ESSID", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
     _KEY_TYPE("ETHTOOL_OPTS", NMS_IFCFG_KEY_TYPE_IS_PLAIN),
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
index b7751ec9..6b396a9a 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-utils.h
@@ -33,7 +33,7 @@ typedef struct {
     NMSIfcfgKeyTypeFlags key_flags;
 } NMSIfcfgKeyTypeInfo;
 
-extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[251];
+extern const NMSIfcfgKeyTypeInfo nms_ifcfg_well_known_keys[253];
 
 const NMSIfcfgKeyTypeInfo *nms_ifcfg_well_known_key_find_info(const char *key, gssize *out_idx);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
index ef85b6bf..263c9253 100644
--- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
+++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-writer.c
@@ -19,6 +19,7 @@
 #include "libnm-glib-aux/nm-str-buf.h"
 #include "libnm-glib-aux/nm-io-utils.h"
 #include "nm-manager.h"
+#include "nm-setting-bond-port.h"
 #include "nm-setting-connection.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
@@ -204,18 +205,18 @@ write_object(NMSetting8021x *                s_8021x,
     g_return_val_if_fail(ifcfg != NULL, FALSE);
     g_return_val_if_fail(objtype != NULL, FALSE);
 
-    scheme = (*(objtype->vtable->scheme_func)) (s_8021x);
+    scheme = (*(objtype->vtable->scheme_func))(s_8021x);
     switch (scheme) {
     case NM_SETTING_802_1X_CK_SCHEME_UNKNOWN:
         break;
     case NM_SETTING_802_1X_CK_SCHEME_BLOB:
-        blob = (*(objtype->vtable->blob_func)) (s_8021x);
+        blob = (*(objtype->vtable->blob_func))(s_8021x);
         break;
     case NM_SETTING_802_1X_CK_SCHEME_PATH:
-        value = (*(objtype->vtable->path_func)) (s_8021x);
+        value = (*(objtype->vtable->path_func))(s_8021x);
         break;
     case NM_SETTING_802_1X_CK_SCHEME_PKCS11:
-        value = (*(objtype->vtable->uri_func)) (s_8021x);
+        value = (*(objtype->vtable->uri_func))(s_8021x);
         break;
     default:
         g_set_error(error,
@@ -228,8 +229,8 @@ write_object(NMSetting8021x *                s_8021x,
     /* Set the password for certificate/private key. */
     nm_sprintf_buf(secret_name, "%s_PASSWORD", objtype->ifcfg_rh_key);
     nm_sprintf_buf(secret_flags, "%s_PASSWORD_FLAGS", objtype->ifcfg_rh_key);
-    password = (*(objtype->vtable->passwd_func)) (s_8021x);
-    flags    = (*(objtype->vtable->pwflag_func)) (s_8021x);
+    password = (*(objtype->vtable->passwd_func))(s_8021x);
+    flags    = (*(objtype->vtable->pwflag_func))(s_8021x);
     set_secret(ifcfg, secrets, secret_name, password, secret_flags, flags);
 
     if (!objtype->vtable->format_func)
@@ -1442,12 +1443,12 @@ write_vlan_setting(NMConnection *connection, shvarFile *ifcfg, gboolean *wired,
     svSetValueBoolean(ifcfg, "REORDER_HDR", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS));
     svSetValueBoolean(ifcfg, "GVRP", NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_GVRP));
 
-    nm_utils_strbuf_init(s_buf, &s_buf_ptr, &s_buf_len);
+    nm_strbuf_init_arr(s_buf, &s_buf_ptr, &s_buf_len);
 
     if (NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_LOOSE_BINDING))
-        nm_utils_strbuf_append_str(&s_buf_ptr, &s_buf_len, "LOOSE_BINDING");
+        nm_strbuf_append_str(&s_buf_ptr, &s_buf_len, "LOOSE_BINDING");
     if (!NM_FLAGS_HAS(vlan_flags, NM_VLAN_FLAG_REORDER_HEADERS))
-        nm_utils_strbuf_append(&s_buf_ptr, &s_buf_len, "%sNO_REORDER_HDR", s_buf[0] ? "," : "");
+        nm_strbuf_append(&s_buf_ptr, &s_buf_len, "%sNO_REORDER_HDR", s_buf[0] ? "," : "");
 
     svSetValueStr(ifcfg, "VLAN_FLAGS", s_buf);
 
@@ -1888,6 +1889,16 @@ write_bridge_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **e
     return TRUE;
 }
 
+static void
+write_bond_port_setting(NMConnection *connection, shvarFile *ifcfg)
+{
+    NMSettingBondPort *s_port;
+
+    s_port = _nm_connection_get_setting(connection, NM_TYPE_SETTING_BOND_PORT);
+    if (s_port)
+        svSetValueInt64(ifcfg, "BOND_PORT_QUEUE_ID", nm_setting_bond_port_get_queue_id(s_port));
+}
+
 static gboolean
 write_team_port_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
 {
@@ -2020,8 +2031,7 @@ write_dcb_setting(NMConnection *connection, shvarFile *ifcfg, GError **error)
                   "APP_FCOE",
                   nm_setting_dcb_get_app_fcoe_flags(s_dcb),
                   nm_setting_dcb_get_app_fcoe_priority(s_dcb));
-    if (nm_setting_dcb_get_app_fcoe_flags(s_dcb) & NM_SETTING_DCB_FLAG_ENABLE)
-        svSetValueStr(ifcfg, KEY_DCB_APP_FCOE_MODE, nm_setting_dcb_get_app_fcoe_mode(s_dcb));
+    svSetValueStr(ifcfg, KEY_DCB_APP_FCOE_MODE, nm_setting_dcb_get_app_fcoe_mode(s_dcb));
 
     write_dcb_app(ifcfg,
                   "APP_ISCSI",
@@ -2076,6 +2086,7 @@ write_connection_setting(NMSettingConnection *s_con, shvarFile *ifcfg)
     gint32                        vint32;
     NMSettingConnectionMdns       mdns;
     NMSettingConnectionLlmnr      llmnr;
+    NMSettingConnectionDnsOverTls dns_over_tls;
     guint32                       vuint32;
     const char *                  tmp, *mud_url;
 
@@ -2259,6 +2270,14 @@ write_connection_setting(NMSettingConnection *s_con, shvarFile *ifcfg)
     if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT) {
         svSetValueEnum(ifcfg, "LLMNR", nm_setting_connection_llmnr_get_type(), llmnr);
     }
+
+    dns_over_tls = nm_setting_connection_get_dns_over_tls(s_con);
+    if (dns_over_tls != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT) {
+        svSetValueEnum(ifcfg,
+                       "DNS_OVER_TLS",
+                       nm_setting_connection_dns_over_tls_get_type(),
+                       dns_over_tls);
+    }
 }
 
 static char *
@@ -2685,12 +2704,11 @@ write_dns_setting(shvarFile *ifcfg, NMConnection *connection, int addr_family)
     }
 }
 
-static gboolean
+static void
 write_ip4_setting(NMConnection *connection,
                   shvarFile *   ifcfg,
                   shvarFile **  out_route_content_svformat,
-                  GString **    out_route_content,
-                  GError **     error)
+                  GString **    out_route_content)
 {
     NMSettingIPConfig *    s_ip4;
     const char *           value;
@@ -2713,7 +2731,7 @@ write_ip4_setting(NMConnection *connection,
 
     s_ip4 = nm_connection_get_setting_ip4_config(connection);
     if (!s_ip4)
-        return TRUE;
+        return;
 
     method = nm_setting_ip_config_get_method(s_ip4);
 
@@ -2722,7 +2740,7 @@ write_ip4_setting(NMConnection *connection,
         method = NM_SETTING_IP4_CONFIG_METHOD_AUTO;
 
     if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
-        return TRUE;
+        return;
 
     num = nm_setting_ip_config_get_num_addresses(s_ip4);
 
@@ -2881,8 +2899,6 @@ write_ip4_setting(NMConnection *connection,
         }
         svSetValueStr(ifcfg, "DHCP_REJECT_SERVERS", str->str);
     }
-
-    return TRUE;
 }
 
 static void
@@ -2968,11 +2984,8 @@ write_ip4_aliases(NMConnection *connection, const char *base_ifcfg_path)
     }
 }
 
-static gboolean
-write_ip6_setting(NMConnection *connection,
-                  shvarFile *   ifcfg,
-                  GString **    out_route6_content,
-                  GError **     error)
+static void
+write_ip6_setting(NMConnection *connection, shvarFile *ifcfg, GString **out_route6_content)
 {
     NMSettingIPConfig *           s_ip6;
     const char *                  value;
@@ -2991,17 +3004,15 @@ write_ip6_setting(NMConnection *connection,
 
     s_ip6 = nm_connection_get_setting_ip6_config(connection);
     if (!s_ip6)
-        return TRUE;
+        return;
 
     value = nm_setting_ip_config_get_method(s_ip6);
     g_assert(value);
     if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
         svSetValueStr(ifcfg, "IPV6INIT", "no");
-        return TRUE;
     } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
         svSetValueStr(ifcfg, "IPV6_DISABLED", "yes");
         svSetValueStr(ifcfg, "IPV6INIT", "no");
-        return TRUE;
     } else if (!strcmp(value, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
         svSetValueStr(ifcfg, "IPV6INIT", "yes");
         svSetValueStr(ifcfg, "IPV6_AUTOCONF", "yes");
@@ -3148,8 +3159,6 @@ write_ip6_setting(NMConnection *connection,
     write_res_options(ifcfg, s_ip6, "IPV6_RES_OPTIONS");
 
     NM_SET_OUT(out_route6_content, write_route_file(s_ip6));
-
-    return TRUE;
 }
 
 static void
@@ -3228,10 +3237,11 @@ do_write_construct(NMConnection *                  connection,
                    GError **                       error)
 {
     NMSettingConnection *    s_con;
-    nm_auto_shvar_file_close shvarFile *ifcfg       = NULL;
-    gs_free char *                      ifcfg_name  = NULL;
-    gs_free char *                      route_path  = NULL;
-    gs_free char *                      route6_path = NULL;
+    nm_auto_shvar_file_close shvarFile *ifcfg = NULL;
+    const char *                        ifcfg_name;
+    gs_free char *                      ifcfg_name_free = NULL;
+    gs_free char *                      route_path      = NULL;
+    gs_free char *                      route6_path     = NULL;
     const char *                        type;
     gs_unref_hashtable GHashTable *blobs   = NULL;
     gs_unref_hashtable GHashTable *secrets = NULL;
@@ -3255,11 +3265,7 @@ do_write_construct(NMConnection *                  connection,
 
     if (filename) {
         /* For existing connections, 'filename' should be full path to ifcfg file */
-        ifcfg = svOpenFile(filename, error);
-        if (!ifcfg)
-            return FALSE;
-
-        ifcfg_name = g_strdup(filename);
+        ifcfg_name = filename;
     } else if (ifcfg_dir) {
         gs_free char *escaped = NULL;
         int           i_path;
@@ -3280,21 +3286,27 @@ do_write_construct(NMConnection *                  connection,
             if (g_file_test(path_candidate, G_FILE_TEST_EXISTS))
                 continue;
 
-            ifcfg_name = g_steal_pointer(&path_candidate);
+            ifcfg_name_free = g_steal_pointer(&path_candidate);
             break;
         }
 
-        if (!ifcfg_name) {
+        if (!ifcfg_name_free) {
             g_set_error_literal(error,
                                 NM_SETTINGS_ERROR,
                                 NM_SETTINGS_ERROR_FAILED,
                                 "Failed to find usable ifcfg file name");
             return FALSE;
         }
+        ifcfg_name = ifcfg_name_free;
+    } else {
+        g_set_error(error,
+                    NM_SETTINGS_ERROR,
+                    NM_SETTINGS_ERROR_FAILED,
+                    "No file name given for storing profile to ifcfg-rh");
+        g_return_val_if_reached(FALSE);
+    }
 
-        ifcfg = svCreateFile(ifcfg_name);
-    } else
-        ifcfg = svCreateFile("/tmp/ifcfg-dummy");
+    ifcfg = svCreateFile(ifcfg_name);
 
     route_path = utils_get_route_path(svFileGetName(ifcfg));
     if (!route_path) {
@@ -3377,6 +3389,8 @@ do_write_construct(NMConnection *                  connection,
     if (!write_bridge_port_setting(connection, ifcfg, error))
         return FALSE;
 
+    write_bond_port_setting(connection, ifcfg);
+
     if (!write_team_port_setting(connection, ifcfg, error))
         return FALSE;
 
@@ -3441,15 +3455,12 @@ do_write_construct(NMConnection *                  connection,
     } else
         route_ignore = FALSE;
 
-    if (!write_ip4_setting(connection,
-                           ifcfg,
-                           !route_ignore && route_path_is_svformat ? &route_content_svformat : NULL,
-                           !route_ignore && route_path_is_svformat ? NULL : &route_content,
-                           error))
-        return FALSE;
+    write_ip4_setting(connection,
+                      ifcfg,
+                      !route_ignore && route_path_is_svformat ? &route_content_svformat : NULL,
+                      !route_ignore && route_path_is_svformat ? NULL : &route_content);
 
-    if (!write_ip6_setting(connection, ifcfg, !route_ignore ? &route6_content : NULL, error))
-        return FALSE;
+    write_ip6_setting(connection, ifcfg, !route_ignore ? &route6_content : NULL);
 
     write_ip_routing_rules(connection, ifcfg, route_ignore);
 
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
index 240cd5c1..d2f5c453 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-System_test-wired-802-1X-subj-matches.cexpected
@@ -16,6 +16,8 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="System test-wired-802-1X-subj-matches"
 UUID=${UUID}
 DEVICE=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
index b288c04a..ccc0f35a 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Bond_Main.cexpected
@@ -11,6 +11,9 @@ GATEWAY=1.1.1.1
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write Bond Main"
 UUID=${UUID}
 DEVICE=bond0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
index 80e96921..d4a978b6 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Permissions.cexpected
@@ -5,6 +5,9 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write Permissions"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
index d3db19b2..f1b01a21 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_LEAP.cexpected
@@ -11,6 +11,9 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write Wifi LEAP"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
index 32db7262..60540fbe 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wifi_WEP_104_ASCII.cexpected
@@ -10,6 +10,9 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write Wifi WEP 104 ASCII"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
index cc29a2cc..fe864c43 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_Static_Routes.cexpected
@@ -17,6 +17,8 @@ IPV4_FAILURE_FATAL=no
 ACD_TIMEOUT=400
 ARPING_WAIT=1
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="Test Write Wired Static Routes"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
index 9499278e..908cb917 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Test_Write_Wired_match.cexpected
@@ -8,6 +8,8 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="Test Write Wired with Match setting"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
index 44eb777c..6ad151aa 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-Vlan_test-vlan-interface.cexpected
@@ -17,6 +17,8 @@ PREFIX=24
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="Vlan test-vlan-interface"
 UUID=${UUID}
 DEVICE=vlan43
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
index 56e233cc..fef36f2f 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-dcb-test.cexpected
@@ -4,7 +4,6 @@ DCB_APP_FCOE_ENABLE=yes
 DCB_APP_FCOE_ADVERTISE=yes
 DCB_APP_FCOE_WILLING=yes
 DCB_APP_FCOE_PRIORITY=5
-DCB_APP_FCOE_MODE=fabric
 DCB_APP_ISCSI_ENABLE=yes
 DCB_APP_ISCSI_ADVERTISE=yes
 DCB_APP_ISCSI_WILLING=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
index ead3a047..1f5e04ff 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection.cexpected
@@ -8,6 +8,8 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="random wifi connection"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
index 7bc1ae6c..3dcdbda0 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-random_wifi_connection_2.cexpected
@@ -8,6 +8,8 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="random wifi connection 2"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-port b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-port
new file mode 100644
index 00000000..111924c0
--- /dev/null
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-bond-port
@@ -0,0 +1,9 @@
+TYPE=Ethernet
+NAME=eth0
+UUID=43737e70-7c4f-4b67-9f4f-6d2d4747b1ff
+DEVICE=eth0
+ONBOOT=yes
+LLDP=no
+MASTER=bond99
+SLAVE=yes
+BOND_PORT_QUEUE_ID=1
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
index cae51b89..b3199c01 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-ip6-disabled.cexpected
@@ -6,6 +6,8 @@ DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6_DISABLED=yes
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="Test Write Wired Disabled IP6"
 UUID=${UUID}
 ONBOOT=yes
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
index c882c479..a77c615d 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-sriov-write.cexpected
@@ -12,6 +12,9 @@ GATEWAY=1.1.1.1
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write SR-IOV config"
 UUID=${UUID}
 DEVICE=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
index 8197f320..41714df6 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-static-routes-legacy.cexpected
@@ -6,6 +6,8 @@ BOOTPROTO=dhcp
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME=test-static-routes-legacy
 UUID=ba60d05a-7898-820d-c2db-427a88f8f2a5
 DEVICE=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected
index 4df768b4..1125ef55 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write-empty.cexpected
@@ -9,6 +9,9 @@ GATEWAY=1.1.1.1
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write TC config"
 UUID=${UUID}
 DEVICE=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
index a67ca598..d87efd72 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-tc-write.cexpected
@@ -10,6 +10,9 @@ GATEWAY=1.1.1.1
 DEFROUTE=yes
 IPV4_FAILURE_FATAL=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
+IPV6_ADDR_GEN_MODE=stable-privacy
 NAME="Test Write TC config"
 UUID=${UUID}
 DEVICE=eth0
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-vlanid-use.cexpected b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-vlanid-use.cexpected
index a7be14ce..2eaec850 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-vlanid-use.cexpected
+++ b/src/core/settings/plugins/ifcfg-rh/tests/network-scripts/ifcfg-test-vlan-vlanid-use.cexpected
@@ -9,6 +9,8 @@ HWADDR=
 PROXY_METHOD=none
 BROWSER_ONLY=no
 IPV6INIT=no
+IPV6_DEFROUTE=yes
+IPV6_FAILURE_FATAL=no
 NAME="Vlan test-vlan-vlanid-use"
 UUID=${UUID}
 DEVICE=eth0.9
diff --git a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
index d9829f84..b1cfdfec 100644
--- a/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
+++ b/src/core/settings/plugins/ifcfg-rh/tests/test-ifcfg-rh.c
@@ -167,9 +167,8 @@ _assert_expected_content(NMConnection *connection, const char *filename, const c
                 g_string_erase(str, i, NM_STRLEN("${UUID}"));
                 g_string_insert_len(str, i, uuid, j);
 
-                g_free(content_expectd);
-                len_expectd     = str->len;
-                content_expectd = g_string_free(str, FALSE);
+                len_expectd = str->len;
+                nm_strdup_reset_take(&content_expectd, g_string_free(str, FALSE));
                 i += j;
                 continue;
             }
@@ -344,7 +343,7 @@ _writer_new_connection_reread(NMConnection * connection,
 {
     gboolean        success;
     GError *        error                      = NULL;
-    char *          filename                   = NULL;
+    gs_free char *  filename                   = NULL;
     gs_unref_object NMConnection *con_verified = NULL;
     gs_unref_object NMConnection *reread_copy  = NULL;
     NMConnection **reread = out_reread ?: ((nmtst_get_rand_uint32() % 2) ? &reread_copy : NULL);
@@ -372,11 +371,9 @@ _writer_new_connection_reread(NMConnection * connection,
     _assert_expected_content(con_verified, filename, expected);
 
     if (out_filename)
-        *out_filename = filename;
-    else {
+        *out_filename = g_steal_pointer(&filename);
+    else
         nmtst_file_unlink(filename);
-        g_free(filename);
-    }
 }
 
 static void
@@ -467,12 +464,10 @@ test_read_netmask_1(void)
 
     connection = _connection_from_file(FILENAME, NULL, TYPE_ETHERNET, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System netmask-1");
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpuint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 1);
     ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
     g_assert(ip4_addr);
@@ -501,12 +496,12 @@ verify_cert_or_key(NMSetting8021x *s_compare,
                    const char *    privkey_password,
                    const char *    property)
 {
-    NMSetting8021x *       s_8021x;
-    GError *               error    = NULL;
-    gboolean               success  = FALSE;
-    const char *           expected = NULL, *setting = NULL;
-    gboolean               phase2 = FALSE;
-    NMSetting8021xCKScheme scheme = NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
+    gs_unref_object NMSetting8021x *s_8021x  = NULL;
+    GError *                        error    = NULL;
+    gboolean                        success  = FALSE;
+    const char *                    expected = NULL, *setting = NULL;
+    gboolean                        phase2 = FALSE;
+    NMSetting8021xCKScheme          scheme = NM_SETTING_802_1X_CK_SCHEME_UNKNOWN;
 
     if (strstr(property, "phase2"))
         phase2 = TRUE;
@@ -615,28 +610,24 @@ verify_cert_or_key(NMSetting8021x *s_compare,
             setting = nm_setting_802_1x_get_private_key_path(s_compare);
     }
     g_assert_cmpstr(setting, ==, expected);
-
-    g_object_unref(s_8021x);
     return TRUE;
 }
 
 static void
 test_read_basic(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    const char *         mac;
-    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-minimal", NULL, TYPE_ETHERNET, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-minimal");
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
@@ -647,47 +638,38 @@ test_read_basic(void)
      * depending on where the tests are run.
      */
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
 
-    /* MAC address */
     mac = nm_setting_wired_get_mac_address(s_wired);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
 
-    /* ===== IPv6 SETTING ===== */
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
     g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_miscellaneous_variables(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    char *               expected_mac_blacklist[3] = {"00:16:41:11:22:88",
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    char *                        expected_mac_blacklist[3] = {"00:16:41:11:22:88",
                                        "00:16:41:11:22:99",
                                        "6a:5d:5a:fa:dd:f0"};
-    int                  mac_blacklist_num, i;
-    guint64              expected_timestamp = 0;
+    int                           mac_blacklist_num, i;
+    guint64                       expected_timestamp = 0;
 
     NMTST_EXPECT_NM_WARN("*invalid MAC in HWADDR_BLACKLIST 'XX:aa:invalid'*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-misc-variables",
@@ -696,19 +678,14 @@ test_read_miscellaneous_variables(void)
                                        NULL);
     g_test_assert_expected_messages();
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
     g_assert_cmpint(nm_setting_connection_get_autoconnect_retries(s_con), ==, 100);
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
 
-    /* MAC blacklist */
     mac_blacklist_num = nm_setting_wired_get_num_mac_blacklist_items(s_wired);
     g_assert_cmpint(mac_blacklist_num, ==, 3);
     for (i = 0; i < mac_blacklist_num; i++)
@@ -717,26 +694,22 @@ test_read_miscellaneous_variables(void)
                                          expected_mac_blacklist[i],
                                          -1));
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_variables_corner_cases(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    const char *         mac;
-    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
 
     NMTST_EXPECT_NM_WARN("*key NAME is badly quoted and is treated as \"\"*");
     NMTST_EXPECT_NM_WARN("*key ZONE is badly quoted and is treated as \"\"*");
@@ -746,9 +719,7 @@ test_read_variables_corner_cases(void)
                                        NULL);
     g_test_assert_expected_messages();
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System test-variables-corner-cases-1");
@@ -756,34 +727,27 @@ test_read_variables_corner_cases(void)
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 0);
 
-    /* MAC address */
     mac = nm_setting_wired_get_mac_address(s_wired);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_unmanaged(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    char *               unhandled_spec     = NULL;
-    guint64              expected_timestamp = 0;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    gs_free char *                unhandled_spec     = NULL;
+    guint64                       expected_timestamp = 0;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled",
                                        NULL,
@@ -791,24 +755,19 @@ test_read_unmanaged(void)
                                        &unhandled_spec);
     g_assert_cmpstr(unhandled_spec, ==, "unmanaged:mac:00:11:22:33:f8:9f");
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-nm-controlled");
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
-
-    g_free(unhandled_spec);
-    g_object_unref(connection);
 }
 
 static void
 test_read_unmanaged_unrecognized(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    gs_free char *       unhandled_spec     = NULL;
-    guint64              expected_timestamp = 0;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    gs_free char *                unhandled_spec     = NULL;
+    guint64                       expected_timestamp = 0;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-nm-controlled-unrecognized",
                                        NULL,
@@ -816,22 +775,18 @@ test_read_unmanaged_unrecognized(void)
                                        &unhandled_spec);
     g_assert_cmpstr(unhandled_spec, ==, "unmanaged:interface-name:=ipoac0");
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "PigeonNet");
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_unrecognized(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    gs_free char *       unhandled_spec     = NULL;
-    guint64              expected_timestamp = 0;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    gs_free char *                unhandled_spec     = NULL;
+    guint64                       expected_timestamp = 0;
 
     NMTST_EXPECT_NM_WARN("*key NAME is badly quoted and is treated as \"\"*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-unrecognized",
@@ -841,30 +796,26 @@ test_read_unrecognized(void)
     g_test_assert_expected_messages();
     g_assert_cmpstr(unhandled_spec, ==, "unrecognized:mac:00:11:22:33");
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-unrecognized");
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, expected_timestamp);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_static(gconstpointer test_data)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
-    const char *         mac;
-    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
-    NMIPAddress *        ip4_addr;
-    NMIPAddress *        ip6_addr;
-    const char *         file, *expected_id;
-    gpointer             expect_ip6_p;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
+    const char *                  mac;
+    char         expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
+    NMIPAddress *ip4_addr;
+    NMIPAddress *ip6_addr;
+    const char * file, *expected_id;
+    gpointer     expect_ip6_p;
 
     nmtst_test_data_unpack(test_data, &file, &expected_id, &expect_ip6_p);
 
@@ -873,26 +824,19 @@ test_read_wired_static(gconstpointer test_data)
     connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
     g_assert_cmpstr(unmanaged, ==, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_assert_cmpint(nm_setting_wired_get_mtu(s_wired), ==, 1492);
 
-    /* MAC address */
     mac = nm_setting_wired_get_mac_address(s_wired);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -901,24 +845,19 @@ test_read_wired_static(gconstpointer test_data)
     g_assert(nm_setting_ip_config_has_dns_options(s_ip4));
     g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 0);
 
-    /* DNS Addresses */
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
 
-    /* IP addresses */
     g_assert_cmpint(nm_setting_ip_config_get_num_addresses(s_ip4), ==, 1);
     ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
     g_assert(ip4_addr);
     g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 24);
     g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "192.168.1.5");
 
-    /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1");
 
-    /* ===== IPv6 SETTING ===== */
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     if (GPOINTER_TO_INT(expect_ip6_p)) {
         g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                         ==,
@@ -951,19 +890,18 @@ test_read_wired_static(gconstpointer test_data)
                         NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
         g_assert(!nm_setting_ip_config_has_dns_options(s_ip6));
     }
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_static_no_prefix(gconstpointer user_data)
 {
-    guint32              expected_prefix = GPOINTER_TO_UINT(user_data);
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMIPAddress *        ip4_addr;
-    char *               file, *expected_id;
+    guint32         expected_prefix          = GPOINTER_TO_UINT(user_data);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
+    gs_free char *                file        = NULL;
+    gs_free char *                expected_id = NULL;
 
     file = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wired-static-no-prefix-%u", expected_prefix);
     expected_id = g_strdup_printf("System test-wired-static-no-prefix-%u", expected_prefix);
@@ -972,14 +910,10 @@ test_read_wired_static_no_prefix(gconstpointer user_data)
     connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL);
     g_test_assert_expected_messages();
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -991,23 +925,19 @@ test_read_wired_static_no_prefix(gconstpointer user_data)
     ip4_addr = nm_setting_ip_config_get_address(s_ip4, 0);
     g_assert(ip4_addr);
     g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, expected_prefix);
-
-    g_free(file);
-    g_free(expected_id);
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_dhcp(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged                      = NULL;
-    char                 expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
-    const char *         mac;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged    = NULL;
+    char        expected_mac_address[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0xee};
+    const char *mac;
 
     NMTST_EXPECT_NM_WARN("*key IPV6INIT is duplicated and the early occurrence ignored*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp",
@@ -1017,25 +947,18 @@ test_read_wired_dhcp(void)
     g_test_assert_expected_messages();
     g_assert(unmanaged == NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp");
     g_assert_cmpuint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* MAC address */
     mac = nm_setting_wired_get_mac_address(s_wired);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert_cmpstr(nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
                     ==,
@@ -1048,35 +971,29 @@ test_read_wired_dhcp(void)
                      ==,
                      NM_DHCP_HOSTNAME_FLAG_FQDN_ENCODED | NM_DHCP_HOSTNAME_FLAG_FQDN_NO_UPDATE);
 
-    /* ===== IPv6 SETTING ===== */
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
     g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "foo.bar");
     g_assert_cmpuint(nm_setting_ip_config_get_dhcp_hostname_flags(s_ip6),
                      ==,
                      NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_dhcp_plus_ip(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    NMSettingIPConfig *s_ip6;
-    NMIPAddress *      ip4_addr;
-    NMIPAddress *      ip6_addr;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    NMIPAddress *                 ip4_addr;
+    NMIPAddress *                 ip6_addr;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp-plus-ip",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_may_fail(s_ip4));
 
@@ -1100,9 +1017,7 @@ test_read_wired_dhcp_plus_ip(void)
     g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 16);
     g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "9.8.7.6");
 
-    /* ===== IPv6 SETTING ===== */
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_may_fail(s_ip6));
 
@@ -1127,25 +1042,21 @@ test_read_wired_dhcp_plus_ip(void)
     g_assert(ip6_addr);
     g_assert_cmpint(nm_ip_address_get_prefix(ip6_addr), ==, 96);
     g_assert_cmpstr(nm_ip_address_get_address(ip6_addr), ==, "3001:abba::3234");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_shared_plus_ip(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    NMIPAddress *      ip4_addr;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-shared-plus-ip",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_SHARED);
@@ -1160,19 +1071,16 @@ test_read_wired_shared_plus_ip(void)
 
     /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_global_gateway(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMIPAddress *        ip4_addr;
-    char *               unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
+    char *                        unmanaged = NULL;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-global-gateway",
                                        TEST_IFCFG_DIR "/network-test-wired-global-gateway",
@@ -1180,18 +1088,12 @@ test_read_wired_global_gateway(void)
                                        &unmanaged);
     g_assert(unmanaged == NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-global-gateway");
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -1204,19 +1106,16 @@ test_read_wired_global_gateway(void)
 
     /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.2");
-
-    g_object_unref(connection);
 }
 
 /* Ignore GATEWAY from /etc/sysconfig/network for automatic connections */
 static void
 test_read_wired_global_gateway_ignore(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    char *               unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    char *                        unmanaged = NULL;
 
     NMTST_EXPECT_NM_WARN("*ignoring GATEWAY (/etc/sysconfig/network) for * because the connection "
                          "has no static addresses");
@@ -1227,20 +1126,14 @@ test_read_wired_global_gateway_ignore(void)
     g_test_assert_expected_messages();
     g_assert(unmanaged == NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System test-wired-global-gateway-ignore");
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
     /* Addresses */
@@ -1248,25 +1141,21 @@ test_read_wired_global_gateway_ignore(void)
 
     /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, NULL);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_obsolete_gateway_n(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    NMIPAddress *      ip4_addr;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-obsolete-gateway-n",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -1280,8 +1169,6 @@ test_read_wired_obsolete_gateway_n(void)
 
     /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "1.1.1.1");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -1294,7 +1181,8 @@ test_user_1(void)
 
     connection =
         nmtst_create_minimal_connection("Test User 1", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
-    s_user = NM_SETTING_USER(nm_setting_user_new());
+
+    s_user = _nm_connection_new_setting(connection, NM_TYPE_SETTING_USER);
 
 #define _USER_SET_DATA(s_user, key, val)                                      \
     G_STMT_START                                                              \
@@ -1330,8 +1218,6 @@ test_user_1(void)
     _USER_SET_DATA_X(s_user, "my.keys.1");
     _USER_SET_DATA_X(s_user, "my.other.KEY.42");
 
-    nm_connection_add_setting(connection, NM_SETTING(s_user));
-
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
                            TEST_IFCFG_DIR "/ifcfg-Test_User_1.cexpected",
@@ -1345,42 +1231,35 @@ test_user_1(void)
 static void
 test_read_wired_never_default(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    NMSettingIPConfig *s_ip6;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-never-default",
                                        TEST_IFCFG_DIR "/network-test-wired-never-default",
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== WIRED SETTING ===== */
-    g_assert(nm_connection_get_setting_wired(connection));
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4));
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 0);
 
-    /* ===== IPv6 SETTING ===== */
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip6));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_defroute_no(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no",
                                        NULL,
@@ -1388,33 +1267,28 @@ test_read_wired_defroute_no(void)
                                        &unmanaged);
     g_assert(unmanaged == NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-defroute-no");
 
-    g_assert(nm_connection_get_setting_wired(connection));
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4));
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip6));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_defroute_no_gatewaydev_yes(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-defroute-no-gatewaydev-yes",
@@ -1422,58 +1296,45 @@ test_read_wired_defroute_no_gatewaydev_yes(void)
                               TYPE_ETHERNET,
                               &unmanaged);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System test-wired-defroute-no-gatewaydev-yes");
 
-    g_assert(nm_connection_get_setting_wired(connection));
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
     g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_static_routes(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMIPRoute *          ip4_route;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMIPRoute *                   ip4_route;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-static-routes");
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
 
-    /* Routes */
     g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 4);
 
     ip4_route = nm_setting_ip_config_get_route(s_ip4, 0);
@@ -1524,19 +1385,16 @@ test_read_wired_static_routes(void)
     g_assert_cmpint(nm_ip_route_get_prefix(ip4_route), ==, 32);
     nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
     nmtst_assert_route_attribute_byte(ip4_route, NM_IP_ROUTE_ATTRIBUTE_SCOPE, 254);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_static_routes_legacy(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    char *               unmanaged = NULL;
-    NMIPRoute *          ip4_route;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    char *                        unmanaged = NULL;
+    NMIPRoute *                   ip4_route;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-static-routes-legacy",
                                        NULL,
@@ -1544,28 +1402,18 @@ test_read_wired_static_routes_legacy(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System test-wired-static-routes-legacy");
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
 
-    /* Routes */
     g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 5);
 
     /* Route #1 */
@@ -1612,20 +1460,17 @@ test_read_wired_static_routes_legacy(void)
     g_assert(ip4_route != NULL);
     g_assert_cmpstr(nm_ip_route_get_dest(ip4_route), ==, "1.2.3.4");
     nmtst_assert_route_attribute_string(ip4_route, NM_IP_ROUTE_ATTRIBUTE_TYPE, "local");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_ipv4_manual(gconstpointer data)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    char *               unmanaged = NULL;
-    NMIPAddress *        ip4_addr;
-    const char *         file, *expected_id;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    char *                        unmanaged = NULL;
+    NMIPAddress *                 ip4_addr;
+    const char *                  file, *expected_id;
 
     nmtst_test_data_unpack(data, &file, &expected_id);
 
@@ -1634,21 +1479,12 @@ test_read_wired_ipv4_manual(gconstpointer data)
     connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
 
-    /* ===== WIRED SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -1674,21 +1510,18 @@ test_read_wired_ipv4_manual(gconstpointer data)
     g_assert(ip4_addr != NULL);
     g_assert_cmpstr(nm_ip_address_get_address(ip4_addr), ==, "3.3.3.3");
     g_assert_cmpint(nm_ip_address_get_prefix(ip4_addr), ==, 8);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_ipv6_manual(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
-    NMIPAddress *        ip6_addr;
-    NMIPRoute *          ip6_route;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
+    NMIPAddress *                 ip6_addr;
+    NMIPRoute *                   ip6_route;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ipv6-manual",
                                        NULL,
@@ -1696,21 +1529,12 @@ test_read_wired_ipv6_manual(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ipv6-manual");
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-
-    /* ===== IPv4 SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
 
     /* DNS Addresses */
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
@@ -1721,10 +1545,7 @@ test_read_wired_ipv6_manual(void)
     g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 1), ==, "ipsum.org");
     g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip4, 2), ==, "dolor.edu");
 
-    /* ===== IPv6 SETTING ===== */
-
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_MANUAL);
@@ -1794,22 +1615,19 @@ test_read_wired_ipv6_manual(void)
 
     /* DNS domains - none as domains are stuffed to 'ipv4' setting */
     g_assert_cmpint(nm_setting_ip_config_get_num_dns_searches(s_ip6), ==, 0);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_ipv6_only(gconstpointer test_data)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
-    NMIPAddress *        ip6_addr;
-    const char *         method;
-    const char *         file, *expected_id;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
+    NMIPAddress *                 ip6_addr;
+    const char *                  method;
+    const char *                  file, *expected_id;
 
     nmtst_test_data_unpack(test_data, &file, &expected_id);
 
@@ -1818,29 +1636,17 @@ test_read_wired_ipv6_only(gconstpointer test_data)
     connection = _connection_from_file(file, NULL, TYPE_ETHERNET, &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
 
     method = nm_setting_ip_config_get_method(s_ip4);
     g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
 
-    /* ===== IPv6 SETTING ===== */
-
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_MANUAL);
@@ -1863,20 +1669,17 @@ test_read_wired_ipv6_only(gconstpointer test_data)
     g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "lorem.com");
     g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, "ipsum.org");
     g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, "dolor.edu");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_dhcp6_only(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingIPConfig *  s_ip6;
-    char *               unmanaged = NULL;
-    const char *         method;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
+    const char *                  method;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-dhcp6-only",
                                        NULL,
@@ -1884,32 +1687,18 @@ test_read_wired_dhcp6_only(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-dhcp6-only");
 
-    /* ===== WIRED SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
 
     method = nm_setting_ip_config_get_method(s_ip4);
     g_assert_cmpstr(method, ==, NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
 
-    /* ===== IPv6 SETTING ===== */
-
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6), ==, NM_SETTING_IP6_CONFIG_METHOD_DHCP);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -1926,24 +1715,23 @@ test_read_wired_autoip(void)
                                        &unmanaged);
     g_assert(unmanaged == NULL);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
     g_assert(!nm_setting_ip_config_get_may_fail(s_ip4));
     g_assert(nm_setting_ip_config_get_ignore_auto_dns(s_ip4));
 
-    s_con = nm_connection_get_setting_connection(connection);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpint(nm_setting_connection_get_wait_device_timeout(s_con), ==, 2600);
 }
 
 static void
 test_read_onboot_no(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    char *               unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    char *                        unmanaged = NULL;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-onboot-no",
                                        NULL,
@@ -1951,39 +1739,32 @@ test_read_onboot_no(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     g_assert(!nm_setting_connection_get_autoconnect(s_con));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_noip(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4;
-    NMSettingIPConfig *s_ip6;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-noip", NULL, TYPE_ETHERNET, NULL);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_DISABLED);
     g_assert(nm_setting_ip_config_get_never_default(s_ip4) == FALSE);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip6),
                     ==,
                     NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
     g_assert(nm_setting_ip_config_get_never_default(s_ip6) == FALSE);
-
-    g_object_unref(connection);
 }
 
 #define TEST_IFCFG_WIRED_8021x_PEAP_MSCHAPV2_CA_CERT TEST_IFCFG_DIR "/test_ca_cert.pem"
@@ -1991,16 +1772,15 @@ test_read_noip(void)
 static void
 test_read_wired_8021x_peap_mschapv2(void)
 {
-    NMConnection *     connection;
-    NMSettingWired *   s_wired;
-    NMSettingIPConfig *s_ip4;
-    NMSetting8021x *   s_8021x;
-    NMSetting8021x *   tmp_8021x;
-    char *             unmanaged = NULL;
-    GError *           error     = NULL;
-    gboolean           success   = FALSE;
-    const char *       expected_ca_cert_path;
-    const char *       read_ca_cert_path;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSetting8021x *              s_8021x;
+    gs_unref_object NMSetting8021x *tmp_8021x = NULL;
+    char *                          unmanaged = NULL;
+    GError *                        error     = NULL;
+    gboolean                        success   = FALSE;
+    const char *                    expected_ca_cert_path;
+    const char *                    read_ca_cert_path;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-8021x-peap-mschapv2",
                                        NULL,
@@ -2008,20 +1788,12 @@ test_read_wired_8021x_peap_mschapv2(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-
-    /* ===== IPv4 SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
@@ -2049,33 +1821,26 @@ test_read_wired_8021x_peap_mschapv2(void)
     g_assert(read_ca_cert_path);
 
     g_assert_cmpstr(read_ca_cert_path, ==, expected_ca_cert_path);
-
-    g_object_unref(tmp_8021x);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_8021x_tls_secret_flags(gconstpointer test_data)
 {
-    NMConnection *  connection;
-    NMSettingWired *s_wired;
-    NMSetting8021x *s_8021x;
-    char *          dirname, *tmp;
-    const char *    ifcfg;
-    gpointer        expected_flags_p;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting8021x *              s_8021x;
+    gs_free char *                dirname = NULL;
+    gs_free char *                tmp1    = NULL;
+    gs_free char *                tmp2    = NULL;
+    const char *                  ifcfg;
+    gpointer                      expected_flags_p;
 
     nmtst_test_data_unpack(test_data, &ifcfg, &expected_flags_p);
 
     connection = _connection_from_file(ifcfg, NULL, TYPE_ETHERNET, NULL);
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith");
@@ -2084,18 +1849,13 @@ test_read_wired_8021x_tls_secret_flags(gconstpointer test_data)
                     GPOINTER_TO_INT(expected_flags_p));
 
     dirname = g_path_get_dirname(ifcfg);
-    tmp     = g_build_path("/", dirname, "test_ca_cert.pem", NULL);
-    g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, tmp);
-    g_free(tmp);
 
-    tmp = g_build_path("/", dirname, "test1_key_and_cert.pem", NULL);
-    g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), ==, tmp);
-    g_assert_cmpstr(nm_setting_802_1x_get_private_key_path(s_8021x), ==, tmp);
-    g_free(tmp);
+    tmp1 = g_build_path("/", dirname, "test_ca_cert.pem", NULL);
+    g_assert_cmpstr(nm_setting_802_1x_get_ca_cert_path(s_8021x), ==, tmp1);
 
-    g_free(dirname);
-
-    g_object_unref(connection);
+    tmp2 = g_build_path("/", dirname, "test1_key_and_cert.pem", NULL);
+    g_assert_cmpstr(nm_setting_802_1x_get_client_cert_path(s_8021x), ==, tmp2);
+    g_assert_cmpstr(nm_setting_802_1x_get_private_key_path(s_8021x), ==, tmp2);
 }
 
 static void
@@ -2111,9 +1871,7 @@ test_read_write_802_1X_subj_matches(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman");
@@ -2143,8 +1901,7 @@ test_read_write_802_1X_subj_matches(void)
     nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
 
     /* Check 802.1X stuff of the re-read connection. */
-    s_8021x = nm_connection_get_setting_802_1x(reread);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "peap");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Jara Cimrman");
@@ -2168,8 +1925,8 @@ test_read_write_802_1X_subj_matches(void)
 static void
 test_read_802_1x_ttls_eapgtc(void)
 {
-    NMConnection *  connection;
-    NMSetting8021x *s_8021x;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSetting8021x *              s_8021x;
 
     /* Test that EAP-* inner methods are correctly read into the
      * NMSetting8021x::autheap property.
@@ -2180,9 +1937,7 @@ test_read_802_1x_ttls_eapgtc(void)
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     /* EAP methods */
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
@@ -2191,8 +1946,6 @@ test_read_802_1x_ttls_eapgtc(void)
     /* Auth methods */
     g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, NULL);
     g_assert_cmpstr(nm_setting_802_1x_get_phase2_autheap(s_8021x), ==, "gtc");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -2208,8 +1961,7 @@ test_read_802_1x_tls_p12_no_client_cert(void)
                               TYPE_ETHERNET,
                               NULL);
 
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     g_assert_cmpint(nm_setting_802_1x_get_private_key_scheme(s_8021x),
                     ==,
@@ -2242,9 +1994,7 @@ test_read_write_802_1x_password_raw(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     bytes = nm_setting_802_1x_get_password_raw(s_8021x);
     g_assert(bytes);
@@ -2269,18 +2019,18 @@ test_read_write_802_1x_password_raw(void)
 static void
 test_read_wired_aliases_good(gconstpointer test_data)
 {
-    const int            N = GPOINTER_TO_INT(test_data);
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    int                  expected_num_addresses;
-    const char *         expected_address_0[] = {"192.168.1.5",
+    const int       N                        = GPOINTER_TO_INT(test_data);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    int                           expected_num_addresses;
+    const char *                  expected_address_0[] = {"192.168.1.5",
                                         "192.168.1.6",
                                         "192.168.1.9",
                                         "192.168.1.99",
                                         NULL};
-    const char *         expected_address_3[] = {"192.168.1.5", "192.168.1.6", NULL};
-    const char *         expected_label_0[]   = {
+    const char *                  expected_address_3[] = {"192.168.1.5", "192.168.1.6", NULL};
+    const char *                  expected_label_0[]   = {
         NULL,
         "aliasem0:1",
         "aliasem0:2",
@@ -2305,19 +2055,13 @@ test_read_wired_aliases_good(gconstpointer test_data)
 
     connection = _connection_from_file(path, NULL, TYPE_ETHERNET, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     if (N == 0)
         g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem0");
     else
         g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System aliasem3");
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -2358,33 +2102,25 @@ test_read_wired_aliases_good(gconstpointer test_data)
 
     for (i = 0; i < expected_num_addresses; i++)
         g_assert(!expected_address[i]);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_aliases_bad(const char *base, const char *expected_id)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMIPAddress *        ip4_addr;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMIPAddress *                 ip4_addr;
 
     g_assert(expected_id);
 
     connection = _connection_from_file(base, NULL, TYPE_ETHERNET, NULL);
     g_test_assert_expected_messages();
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, expected_id);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
@@ -2400,8 +2136,6 @@ test_read_wired_aliases_bad(const char *base, const char *expected_id)
 
     /* Gateway */
     g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip4), ==, "192.168.1.1");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -2421,13 +2155,15 @@ test_read_wired_aliases_bad_2(void)
 static void
 test_read_dns_options(void)
 {
-    NMConnection *     connection;
-    NMSettingIPConfig *s_ip4, *s_ip6;
-    char *             unmanaged = NULL;
-    const char *       option;
-    const char *       options4[] = {"ndots:3", "single-request-reopen"};
-    const char *       options6[] = {"inet6"};
-    guint32            i, num;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    char *                        unmanaged = NULL;
+    const char *                  option;
+    const char *                  options4[] = {"ndots:3", "single-request-reopen"};
+    const char *                  options6[] = {"inet6"};
+    guint32                       num;
+    guint32                       i;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dns-options",
                                        NULL,
@@ -2435,11 +2171,9 @@ test_read_dns_options(void)
                                        &unmanaged);
     g_assert_cmpstr(unmanaged, ==, NULL);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
 
     num = nm_setting_ip_config_get_num_dns_options(s_ip4);
     g_assert_cmpint(num, ==, G_N_ELEMENTS(options4));
@@ -2456,8 +2190,6 @@ test_read_dns_options(void)
         option = nm_setting_ip_config_get_dns_option(s_ip6, i);
         g_assert_cmpstr(options6[i], ==, option);
     }
-
-    g_object_unref(connection);
 }
 
 static void
@@ -2477,8 +2209,7 @@ test_clear_master(void)
                                        &unmanaged);
     g_assert_cmpstr(unmanaged, ==, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
     g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, "bridge");
@@ -2534,10 +2265,7 @@ test_write_dns_options(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test DNS options",
@@ -2549,16 +2277,10 @@ test_write_dns_options(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -2576,10 +2298,7 @@ test_write_dns_options(void)
 
     nm_setting_ip_config_add_dns_option(s_ip4, "debug");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
@@ -2608,23 +2327,20 @@ test_write_dns_options(void)
 static void
 test_read_wifi_open(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4, *s_ip6;
-    GBytes *                   ssid;
-    const char *               mac;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
+    GBytes *                      ssid;
+    const char *                  mac;
     char        expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
     const char *expected_ssid                  = "blahblah";
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open", NULL, TYPE_WIRELESS, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-open)");
 
     /* UUID can't be tested if the ifcfg does not contain the UUID key, because
@@ -2636,109 +2352,83 @@ test_read_wifi_open(void)
     g_assert(nm_setting_connection_get_autoconnect(s_con));
     g_assert_cmpint(nm_setting_connection_get_autoconnect_priority(s_con), ==, -1);
 
-    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* MAC address */
-    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    mac = nm_setting_wireless_get_mac_address(s_wifi);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
 
-    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wifi), ==, 0);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
-    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
-
-    /* ===== Wi-Fi SECURITY SETTING ===== */
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec == NULL);
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wifi), ==, 1);
 
-    /* ===== IPv4 SETTING ===== */
+    nmtst_connection_assert_no_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip4), ==, 104);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(s_ip6), ==, 106);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_open_auto(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWireless *  s_wireless;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-auto",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-open-auto)");
 
-    /* ===== WIRELESS SETTING ===== */
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
-
-    g_object_unref(connection);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
 }
 
 static void
 test_read_wifi_open_ssid_hex(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWireless *  s_wireless;
-    GBytes *             ssid;
-    const char *         expected_ssid = "blahblah";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "blahblah";
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-hex",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-open-ssid-hex)");
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
-
-    g_object_unref(connection);
 }
 
 static void
@@ -2746,7 +2436,7 @@ test_read_wifi_open_ssid_hex_bad(void)
 {
     gs_unref_object NMConnection *connection = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWireless *           s_wireless;
+    NMSettingWireless *           s_wifi;
     GBytes *                      ssid;
     const char *                  expected_ssid = "0x626cxx";
 
@@ -2755,16 +2445,14 @@ test_read_wifi_open_ssid_hex_bad(void)
                                        TYPE_WIRELESS,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System 0x626cxx (test-wifi-open-ssid-bad-hex)");
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
@@ -2781,50 +2469,42 @@ test_read_wifi_open_ssid_bad(gconstpointer data)
 static void
 test_read_wifi_open_ssid_quoted(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWireless *  s_wireless;
-    GBytes *             ssid;
-    const char *         expected_ssid = "foo\"bar\\";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "foo\"bar\\";
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-open-ssid-quoted",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System foo\"bar\\ (test-wifi-open-ssid-quoted)");
 
-    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
-    GBytes *                   ssid;
-    const char *               mac;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    GBytes *                      ssid;
+    const char *                  mac;
     char         expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
     const char * expected_ssid                  = "blahblah";
     NMWepKeyType key_type;
@@ -2832,10 +2512,7 @@ test_read_wifi_wep(void)
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep", NULL, TYPE_WIRELESS, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wep)");
 
     /* UUID can't be tested if the ifcfg does not contain the UUID key, because
@@ -2846,34 +2523,27 @@ test_read_wifi_wep(void)
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* MAC address */
-    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    mac = nm_setting_wireless_get_mac_address(s_wifi);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
 
     /* MTU */
-    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wifi), ==, 0);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
-    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wifi), ==, 1);
 
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
     g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "shared");
     g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
@@ -2888,35 +2558,27 @@ test_read_wifi_wep(void)
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_adhoc(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
-    GBytes *                   ssid;
-    const char *               expected_ssid = "blahblah";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "blahblah";
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-adhoc",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-wep-adhoc)");
@@ -2928,26 +2590,20 @@ test_read_wifi_wep_adhoc(void)
 
     g_assert(!nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc");
-    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 11);
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "adhoc");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wifi), ==, 11);
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
     g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
     g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
@@ -2959,10 +2615,7 @@ test_read_wifi_wep_adhoc(void)
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
     /* Ignore auto DNS */
@@ -2972,37 +2625,24 @@ test_read_wifi_wep_adhoc(void)
     g_assert_cmpint(nm_setting_ip_config_get_num_dns(s_ip4), ==, 2);
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 0), ==, "4.2.2.1");
     g_assert_cmpstr(nm_setting_ip_config_get_dns(s_ip4, 1), ==, "4.2.2.2");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_passphrase(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-passphrase",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
     g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
     g_assert_cmpint(nm_setting_wireless_security_get_wep_key_type(s_wsec),
@@ -3012,38 +2652,25 @@ test_read_wifi_wep_passphrase(void)
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_40_ascii(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMWepKeyType               key_type;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMWepKeyType                  key_type;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-40-ascii",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
-    /* ===== WIRELESS SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
     g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
 
@@ -3054,38 +2681,25 @@ test_read_wifi_wep_40_ascii(void)
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_104_ascii(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMWepKeyType               key_type;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMWepKeyType                  key_type;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-104-ascii",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
-
-    /* ===== WIRELESS SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
     g_assert_cmpint(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec), ==, 0);
 
@@ -3096,64 +2710,45 @@ test_read_wifi_wep_104_ascii(void)
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 1));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 2));
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 3));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_leap(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWirelessSecurity *   s_wsec;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-leap", NULL, TYPE_WIRELESS, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-leap)");
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
     g_assert_cmpstr(nm_setting_wireless_security_get_auth_alg(s_wsec), ==, "leap");
     g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, "Bill Smith");
     g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, "foobarblah");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_leap_secret_flags(gconstpointer test_data)
 {
-    NMConnection *             connection;
-    NMSettingWireless *        s_wifi;
-    NMSettingWirelessSecurity *s_wsec;
-    const char *               file;
-    gpointer                   expected_flags_p;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    const char *                  file;
+    gpointer                      expected_flags_p;
 
     nmtst_test_data_unpack(test_data, &file, &expected_flags_p);
 
     connection = _connection_from_file(file, NULL, TYPE_WIRELESS, NULL);
 
-    /* ===== WIRELESS SETTING ===== */
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     g_assert(g_strcmp0(nm_setting_wireless_security_get_key_mgmt(s_wsec), "ieee8021x") == 0);
     g_assert(g_strcmp0(nm_setting_wireless_security_get_auth_alg(s_wsec), "leap") == 0);
@@ -3162,8 +2757,6 @@ test_read_wifi_leap_secret_flags(gconstpointer test_data)
     g_assert(nm_setting_wireless_security_get_leap_password_flags(s_wsec)
              == GPOINTER_TO_INT(expected_flags_p));
     g_assert(nm_setting_wireless_security_get_leap_password(s_wsec) == NULL);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -3179,13 +2772,13 @@ test_ifcfg_no_trailing_newline(void)
 static void
 test_read_wifi_wpa_psk(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
-    GBytes *                   ssid;
-    const char *               mac;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    GBytes *                      ssid;
+    const char *                  mac;
     char        expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
     const char *expected_ssid                  = "blahblah";
     guint32     n, i;
@@ -3201,10 +2794,7 @@ test_read_wifi_wpa_psk(void)
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk", NULL, TYPE_WIRELESS, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-wpa-psk)");
 
     /* UUID can't be tested if the ifcfg does not contain the UUID key, because
@@ -3215,32 +2805,26 @@ test_read_wifi_wpa_psk(void)
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRELESS SETTING ===== */
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    mac = nm_setting_wireless_get_mac_address(s_wireless);
+    mac = nm_setting_wireless_get_mac_address(s_wifi);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
 
-    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wifi), ==, 0);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
-    g_assert_cmpint(nm_setting_wireless_get_channel(s_wireless), ==, 1);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
+    g_assert_cmpint(nm_setting_wireless_get_channel(s_wifi), ==, 1);
 
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
                     ==,
@@ -3295,13 +2879,8 @@ test_read_wifi_wpa_psk(void)
     g_assert(found_proto_wpa);
     g_assert(found_proto_rsn);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -3309,7 +2888,7 @@ test_read_wifi_sae(void)
 {
     gs_unref_object NMConnection *connection = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWireless *           s_wireless;
+    NMSettingWireless *           s_wifi;
     NMSettingWirelessSecurity *   s_wsec;
     GBytes *                      ssid;
     const char *                  expected_ssid = "blahblah";
@@ -3317,30 +2896,27 @@ test_read_wifi_sae(void)
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-sae", NULL, TYPE_WIRELESS, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah (test-wifi-sae)");
 
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wifi), ==, 0);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
 
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "sae");
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "The king is dead.");
     g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
@@ -3351,7 +2927,7 @@ test_read_wifi_owe(void)
 {
     gs_unref_object NMConnection *connection = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWireless *           s_wireless;
+    NMSettingWireless *           s_wifi;
     NMSettingWirelessSecurity *   s_wsec;
     GBytes *                      ssid;
     const char *                  expected_ssid = "blahblah_owe";
@@ -3359,30 +2935,27 @@ test_read_wifi_owe(void)
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-owe", NULL, TYPE_WIRELESS, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System blahblah_owe (test-wifi-owe)");
 
     g_assert_cmpint(nm_setting_connection_get_timestamp(s_con), ==, 0);
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wireless), ==, 0);
+    g_assert_cmpint(nm_setting_wireless_get_mtu(s_wifi), ==, 0);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
     g_assert_cmpmem(g_bytes_get_data(ssid, NULL),
                     g_bytes_get_size(ssid),
                     expected_ssid,
                     strlen(expected_ssid));
 
-    g_assert(!nm_setting_wireless_get_bssid(s_wireless));
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "infrastructure");
+    g_assert(!nm_setting_wireless_get_bssid(s_wifi));
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "infrastructure");
 
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "owe");
     g_assert(!nm_setting_wireless_security_get_psk(s_wsec));
     g_assert(!nm_setting_wireless_security_get_auth_alg(s_wsec));
@@ -3391,71 +2964,47 @@ test_read_wifi_owe(void)
 static void
 test_read_wifi_wpa_psk_2(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWirelessSecurity *   s_wsec;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-2",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System ipsum (test-wifi-wpa-psk-2)");
 
-    /* ===== WIRELESS SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
                     ==,
                     "They're really saying I love you. >>`<< '");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wpa_psk_unquoted(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWirelessSecurity *   s_wsec;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-unquoted",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-wpa-psk-unquoted)");
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec), ==, "54336845e2f3f321c4c7");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -3472,36 +3021,27 @@ test_read_wifi_wpa_psk_unquoted2(void)
 static void
 test_read_wifi_wpa_psk_adhoc(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-adhoc",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-wpa-psk-adhoc)");
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wireless), ==, "adhoc");
+    g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, "adhoc");
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
                     ==,
@@ -3516,64 +3056,45 @@ test_read_wifi_wpa_psk_adhoc(void)
     g_assert_cmpint(nm_setting_wireless_security_get_num_protos(s_wsec), ==, 1);
     g_assert_cmpstr(nm_setting_wireless_security_get_proto(s_wsec, 0), ==, "rsn");
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wpa_psk_hex(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
-    GBytes *                   ssid;
-    const char *               expected_ssid = "blahblah";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    GBytes *                      ssid;
+    const char *                  expected_ssid = "blahblah";
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-psk-hex",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System blahblah (test-wifi-wpa-psk-hex)");
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    ssid = nm_setting_wireless_get_ssid(s_wireless);
+    ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
-    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+    g_assert(nm_g_bytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-psk");
     g_assert_cmpstr(nm_setting_wireless_security_get_psk(s_wsec),
                     ==,
                     "1da190379817bc360dda52e85c388c439a21ea5c7bf819c64e9da051807deae6");
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
-
-    g_object_unref(connection);
 }
 
 #define TEST_IFCFG_WIFI_WPA_EAP_TLS_CA_CERT     TEST_IFCFG_DIR "/test_ca_cert.pem"
@@ -3583,12 +3104,11 @@ test_read_wifi_wpa_psk_hex(void)
 static void
 test_read_wifi_wpa_eap_tls(void)
 {
-    NMConnection *     connection;
-    NMSettingWireless *s_wireless;
-    NMSettingIPConfig *s_ip4;
-    NMSetting8021x *   s_8021x;
-    char *             unmanaged                 = NULL;
-    const char *       expected_privkey_password = "test1";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSetting8021x *              s_8021x;
+    char *                        unmanaged                 = NULL;
+    const char *                  expected_privkey_password = "test1";
 
     NMTST_EXPECT_NM_WARN("*key ONBOOT is duplicated and the early occurrence ignored*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-tls",
@@ -3598,20 +3118,12 @@ test_read_wifi_wpa_eap_tls(void)
     g_test_assert_expected_messages();
     g_assert(!unmanaged);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "tls");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Bill Smith");
@@ -3638,8 +3150,6 @@ test_read_wifi_wpa_eap_tls(void)
                        TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
                        expected_privkey_password,
                        NM_SETTING_802_1X_PRIVATE_KEY);
-
-    g_object_unref(connection);
 }
 
 /* Also use TLS defines from the previous test */
@@ -3647,12 +3157,11 @@ test_read_wifi_wpa_eap_tls(void)
 static void
 test_read_wifi_wpa_eap_ttls_tls(void)
 {
-    NMConnection *     connection;
-    NMSettingWireless *s_wireless;
-    NMSettingIPConfig *s_ip4;
-    NMSetting8021x *   s_8021x;
-    char *             unmanaged                 = NULL;
-    const char *       expected_privkey_password = "test1";
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingIPConfig *           s_ip4;
+    NMSetting8021x *              s_8021x;
+    char *                        unmanaged                 = NULL;
+    const char *                  expected_privkey_password = "test1";
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-ttls-tls",
                                        NULL,
@@ -3660,20 +3169,12 @@ test_read_wifi_wpa_eap_ttls_tls(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== IPv4 SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
     g_assert_cmpstr(nm_setting_802_1x_get_eap_method(s_8021x, 0), ==, "ttls");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "Chuck Shumer");
@@ -3709,59 +3210,40 @@ test_read_wifi_wpa_eap_ttls_tls(void)
                        TEST_IFCFG_WIFI_WPA_EAP_TLS_PRIVATE_KEY,
                        expected_privkey_password,
                        NM_SETTING_802_1X_PHASE2_PRIVATE_KEY);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wpa_eap_suite_b_192_tls(void)
 {
-    NMConnection *             connection;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wireless_sec;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wpa-eap-suite-b-192-tls",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wireless_sec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wireless_sec);
-    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wireless_sec),
-                    ==,
-                    "wpa-eap-suite-b-192");
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    g_object_unref(connection);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
+    g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "wpa-eap-suite-b-192");
 }
 
 static void
 test_read_wifi_dynamic_wep_leap(void)
 {
-    NMConnection *             connection;
-    NMSettingWireless *        s_wifi;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSetting8021x *           s_8021x;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSetting8021x *              s_8021x;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-dynamic-wep-leap",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== Wi-Fi SECURITY SETTING ===== */
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     /* Key management */
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
@@ -3775,9 +3257,7 @@ test_read_wifi_dynamic_wep_leap(void)
     g_assert_cmpstr(nm_setting_wireless_security_get_leap_username(s_wsec), ==, NULL);
     g_assert_cmpstr(nm_setting_wireless_security_get_leap_password(s_wsec), ==, NULL);
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     /* EAP method should be "leap" */
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
@@ -3786,19 +3266,16 @@ test_read_wifi_dynamic_wep_leap(void)
     /* username & password */
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "bill smith");
     g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_eap_ttls_chap(void)
 {
-    NMConnection *             connection;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMSettingIPConfig *        s_ip4;
-    NMSetting8021x *           s_8021x;
-    char *                     unmanaged = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMSettingIPConfig *           s_ip4;
+    NMSetting8021x *              s_8021x;
+    char *                        unmanaged = NULL;
 
     NMTST_EXPECT_NM_WARN("*key ONBOOT is duplicated and the early occurrence ignored*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-eap-ttls-chap",
@@ -3808,25 +3285,15 @@ test_read_wifi_wep_eap_ttls_chap(void)
     g_test_assert_expected_messages();
     g_assert(!unmanaged);
 
-    /* ===== WIRELESS SETTING ===== */
-
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
-    /* ===== 802.1x SETTING ===== */
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "ieee8021x");
 
-    /* ===== 802.1x SETTING ===== */
-    s_8021x = nm_connection_get_setting_802_1x(connection);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
 
     /* EAP methods */
     g_assert_cmpint(nm_setting_802_1x_get_num_eap_methods(s_8021x), ==, 1);
@@ -3841,38 +3308,32 @@ test_read_wifi_wep_eap_ttls_chap(void)
     g_assert_cmpstr(nm_setting_802_1x_get_phase2_auth(s_8021x), ==, "chap");
     g_assert_cmpstr(nm_setting_802_1x_get_identity(s_8021x), ==, "David Smith");
     g_assert_cmpstr(nm_setting_802_1x_get_password(s_8021x), ==, "foobar baz");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_wake_on_lan(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-wake-on-lan",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_assert_cmpint(nm_setting_wired_get_wake_on_lan(s_wired),
                     ==,
                     NM_SETTING_WIRED_WAKE_ON_LAN_ARP | NM_SETTING_WIRED_WAKE_ON_LAN_PHY
                         | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC);
 
     g_assert_cmpstr(nm_setting_wired_get_wake_on_lan_password(s_wired), ==, "00:11:22:33:44:55");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -3887,14 +3348,12 @@ test_read_wired_auto_negotiate_off(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
     g_assert(!nm_setting_wired_get_auto_negotiate(s_wired));
     g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 100);
@@ -3913,14 +3372,12 @@ test_read_wired_auto_negotiate_on(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
     g_assert(nm_setting_wired_get_auto_negotiate(s_wired));
     g_assert_cmpint(nm_setting_wired_get_speed(s_wired), ==, 0);
@@ -3939,14 +3396,12 @@ test_read_wired_unknown_ethtool_opt(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
 
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
     g_assert(!nm_setting_wired_get_auto_negotiate(s_wired));
     g_assert(!nm_setting_wired_get_speed(s_wired));
@@ -3980,8 +3435,8 @@ test_roundtrip_ethtool(void)
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_ethtool  = nm_setting_ethtool_new();
-    nm_connection_add_setting(connection, s_ethtool);
+
+    s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL);
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
                            TEST_IFCFG_DIR "/ifcfg-test_roundtrip_ethtool-2.cexpected",
@@ -3992,7 +3447,7 @@ test_roundtrip_ethtool(void)
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_wired    = nm_connection_get_setting(connection, NM_TYPE_SETTING_WIRED);
+    s_wired    = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL);
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
@@ -4004,8 +3459,8 @@ test_roundtrip_ethtool(void)
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_ethtool  = nm_setting_ethtool_new();
-    nm_connection_add_setting(connection, s_ethtool);
+
+    s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL);
     nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX, TRUE);
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
@@ -4017,10 +3472,10 @@ test_roundtrip_ethtool(void)
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_wired    = nm_connection_get_setting(connection, NM_TYPE_SETTING_WIRED);
+    s_wired    = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL);
-    s_ethtool = nm_setting_ethtool_new();
-    nm_connection_add_setting(connection, s_ethtool);
+
+    s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL);
     nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RX, TRUE);
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
@@ -4032,24 +3487,20 @@ test_roundtrip_ethtool(void)
 static void
 test_read_wifi_hidden(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWireless *  s_wifi;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-hidden", NULL, TYPE_WIRELESS, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRELESS_SETTING_NAME);
 
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_assert(nm_setting_wireless_get_hidden(s_wifi) == TRUE);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -4061,15 +3512,12 @@ test_write_wifi_hidden(void)
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
     shvarFile *                   f;
-    GBytes *                      ssid;
-    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    gs_unref_bytes GBytes *ssid =
+        nmtst_gbytes_from_arr(0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44);
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wi-Fi Hidden",
@@ -4079,16 +3527,9 @@ test_write_wifi_hidden(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_HIDDEN, TRUE, NULL);
 
-    g_bytes_unref(ssid);
-
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connec_exp(connection,
@@ -4121,8 +3562,7 @@ test_read_wifi_mac_random(gconstpointer user_data)
     path       = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-wifi-mac-random-%s", name);
     connection = _connection_from_file(path, NULL, TYPE_WIRELESS, NULL);
 
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_assert_cmpint(nm_setting_wireless_get_mac_address_randomization(s_wifi), ==, value);
 }
 
@@ -4134,10 +3574,10 @@ test_write_wifi_mac_random(gconstpointer user_data)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
-    char *                        val;
+    gs_free char *                val = NULL;
     shvarFile *                   f;
-    GBytes *                      ssid;
-    const unsigned char       ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    gs_unref_bytes GBytes *ssid =
+        nmtst_gbytes_from_arr(0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44);
     const char *              name, *write_expected;
     gpointer                  value_p;
     NMSettingMacRandomization value;
@@ -4150,9 +3590,7 @@ test_write_wifi_mac_random(gconstpointer user_data)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     val = g_strdup_printf("Test Write Wi-Fi MAC %s", name);
     g_object_set(s_con,
@@ -4163,13 +3601,8 @@ test_write_wifi_mac_random(gconstpointer user_data)
                  NM_SETTING_CONNECTION_TYPE,
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
-    g_free(val);
-
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
 
-    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -4178,7 +3611,6 @@ test_write_wifi_mac_random(gconstpointer user_data)
                  NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
                  value,
                  NULL);
-    g_bytes_unref(ssid);
 
     nmtst_assert_connection_verifies(connection);
 
@@ -4205,16 +3637,12 @@ test_write_wired_wake_on_lan(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingWired *              s_wired;
-    NMSettingWiredWakeOnLan       wol;
-    char *                        val;
+    gs_free char *                val = NULL;
     shvarFile *                   f;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired Wake-on-LAN",
@@ -4224,16 +3652,12 @@ test_write_wired_wake_on_lan(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    wol = NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST | NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST
-          | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC;
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_WAKE_ON_LAN,
-                 wol,
+                 (guint) (NM_SETTING_WIRED_WAKE_ON_LAN_MULTICAST
+                          | NM_SETTING_WIRED_WAKE_ON_LAN_UNICAST
+                          | NM_SETTING_WIRED_WAKE_ON_LAN_MAGIC),
                  NM_SETTING_WIRED_WAKE_ON_LAN_PASSWORD,
                  "00:00:00:11:22:33",
                  NULL);
@@ -4250,7 +3674,6 @@ test_write_wired_wake_on_lan(void)
     g_assert(val);
     g_assert(strstr(val, "wol"));
     g_assert(strstr(val, "sopass 00:00:00:11:22:33"));
-    g_free(val);
     svCloseFile(f);
 
     reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
@@ -4265,14 +3688,14 @@ test_write_wired_auto_negotiate_off(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingWired *              s_wired;
-    char *                        val;
+    gs_free char *                val = NULL;
     shvarFile *                   f;
 
     connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate",
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_wired    = nm_connection_get_setting_wired(connection);
+    s_wired    = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_AUTO_NEGOTIATE,
                  FALSE,
@@ -4293,7 +3716,6 @@ test_write_wired_auto_negotiate_off(void)
     g_assert(strstr(val, "autoneg off"));
     g_assert(strstr(val, "speed 10"));
     g_assert(strstr(val, "duplex half"));
-    g_free(val);
     svCloseFile(f);
 
     reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
@@ -4309,20 +3731,19 @@ test_write_wired_auto_negotiate_on(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingWired *              s_wired;
     NMSettingEthtool *            s_ethtool;
-    char *                        val;
+    gs_free char *                val = NULL;
     shvarFile *                   f;
 
     connection = nmtst_create_minimal_connection("Test Write Wired Auto-Negotiate",
                                                  NULL,
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  NULL);
-    s_wired    = nm_connection_get_setting_wired(connection);
+    s_wired    = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_AUTO_NEGOTIATE, TRUE, NULL);
 
-    s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new());
+    s_ethtool = _nm_connection_new_setting(connection, NM_TYPE_SETTING_ETHTOOL);
     nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX, NM_TERNARY_TRUE);
     nm_setting_ethtool_set_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_RXVLAN, NM_TERNARY_FALSE);
-    nm_connection_add_setting(connection, NM_SETTING(s_ethtool));
 
     _writer_new_connec_exp(connection,
                            TEST_SCRATCH_DIR,
@@ -4335,7 +3756,6 @@ test_write_wired_auto_negotiate_on(void)
     g_assert(strstr(val, "autoneg on"));
     g_assert(!strstr(val, "speed"));
     g_assert(!strstr(val, "duplex"));
-    g_free(val);
     svCloseFile(f);
 
     reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
@@ -4344,8 +3764,7 @@ test_write_wired_auto_negotiate_on(void)
 
     nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
 
-    s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(reread, NM_TYPE_SETTING_ETHTOOL));
-    g_assert(s_ethtool);
+    s_ethtool = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_ETHTOOL);
     g_assert_cmpint(nm_setting_ethtool_get_feature(s_ethtool, NM_ETHTOOL_OPTNAME_FEATURE_TX),
                     ==,
                     NM_TERNARY_TRUE);
@@ -4360,24 +3779,20 @@ test_write_wired_auto_negotiate_on(void)
 static void
 test_read_wifi_band_a(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWireless *  s_wifi;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWireless *           s_wifi;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-band-a", NULL, TYPE_WIRELESS, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRELESS_SETTING_NAME);
 
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    s_wifi = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_assert_cmpstr(nm_setting_wireless_get_band(s_wifi), ==, "a");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -4389,15 +3804,12 @@ test_write_wifi_band_a(void)
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
     shvarFile *                   f;
-    GBytes *                      ssid;
-    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
+    gs_unref_bytes GBytes *ssid =
+        nmtst_gbytes_from_arr(0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44);
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wi-Fi Band A",
@@ -4407,12 +3819,7 @@ test_write_wifi_band_a(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -4422,8 +3829,6 @@ test_write_wifi_band_a(void)
                  "a",
                  NULL);
 
-    g_bytes_unref(ssid);
-
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connec_exp(connection,
@@ -4448,14 +3853,11 @@ test_write_wifi_ap_mode(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
-    gs_unref_bytes GBytes *ssid = NULL;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("MySSID");
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wi-Fi AP Mode",
@@ -4465,12 +3867,7 @@ test_write_wifi_ap_mode(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new("MySSID", NM_STRLEN("MySSID"));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -4523,12 +3920,12 @@ test_read_wifi_band_bg_channel_mismatch(void)
 static void
 test_read_wired_qeth_static(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    char *               unmanaged = NULL;
-    const char *const *  subchannels;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    char *                        unmanaged = NULL;
+    const char *const *           subchannels;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-qeth-static",
                                        NULL,
@@ -4536,16 +3933,10 @@ test_read_wired_qeth_static(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-qeth-static");
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
     g_assert(!nm_setting_wired_get_mac_address(s_wired));
 
@@ -4563,25 +3954,20 @@ test_read_wired_qeth_static(void)
     g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "portno"), ==, "0");
     g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "layer2"), ==, "1");
 
-    /* ===== IPv4 SETTING ===== */
-
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4),
                     ==,
                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wired_ctc_static(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    char *               unmanaged = NULL;
-    const char *const *  subchannels;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWired *              s_wired;
+    char *                        unmanaged = NULL;
+    const char *const *           subchannels;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wired-ctc-static",
                                        NULL,
@@ -4589,14 +3975,10 @@ test_read_wired_ctc_static(void)
                                        &unmanaged);
     g_assert(unmanaged == NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con != NULL);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con), ==, "System test-wired-ctc-static");
 
-    /* ===== WIRED SETTING ===== */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired != NULL);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
     g_assert(nm_setting_wired_get_mac_address(s_wired) == NULL);
 
@@ -4610,28 +3992,22 @@ test_read_wired_ctc_static(void)
 
     g_assert_cmpstr(nm_setting_wired_get_s390_nettype(s_wired), ==, "ctc");
     g_assert_cmpstr(nm_setting_wired_get_s390_option_by_key(s_wired, "ctcprot"), ==, "0");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_no_keys(void)
 {
-    NMConnection *             connection;
-    NMSettingConnection *      s_con;
-    NMSettingWireless *        s_wireless;
-    NMSettingWirelessSecurity *s_wsec;
-    NMWepKeyType               key_type;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMWepKeyType                  key_type;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-no-keys",
                                        NULL,
                                        TYPE_WIRELESS,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_id(s_con),
                     ==,
                     "System foobar (test-wifi-wep-no-keys)");
@@ -4641,15 +4017,9 @@ test_read_wifi_wep_no_keys(void)
      * depending on where the tests are run.
      */
 
-    /* ===== WIRELESS SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    s_wireless = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wireless);
-
-    /* ===== WIRELESS SECURITY SETTING ===== */
-
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     /* Key management */
     g_assert_cmpstr(nm_setting_wireless_security_get_key_mgmt(s_wsec), ==, "none");
@@ -4663,26 +4033,21 @@ test_read_wifi_wep_no_keys(void)
 
     /* WEP key index 0; we don't expect it to be filled */
     g_assert(!nm_setting_wireless_security_get_wep_key(s_wsec, 0));
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_permissions(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    gboolean             success;
-    guint32              num;
-    const char *         tmp;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    gboolean                      success;
+    guint32                       num;
+    const char *                  tmp;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-permissions", NULL, TYPE_ETHERNET, NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     num = nm_setting_connection_get_num_permissions(s_con);
     g_assert_cmpint(num, ==, 3);
@@ -4702,18 +4067,15 @@ test_read_permissions(void)
     success = nm_setting_connection_get_permission(s_con, 2, NULL, &tmp, NULL);
     g_assert(success);
     g_assert_cmpstr(tmp, ==, "johnny5");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_wifi_wep_agent_keys(void)
 {
-    NMConnection *             connection;
-    NMSettingWireless *        s_wifi;
-    NMSettingWirelessSecurity *s_wsec;
-    NMWepKeyType               key_type;
-    NMSettingSecretFlags       flags;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingWirelessSecurity *   s_wsec;
+    NMWepKeyType                  key_type;
+    NMSettingSecretFlags          flags;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-wifi-wep-agent-keys",
                                        NULL,
@@ -4724,13 +4086,9 @@ test_read_wifi_wep_agent_keys(void)
      * we don't have any WEP keys because they are agent owned.
      */
 
-    /* ===== WIRELESS SETTING ===== */
-    s_wifi = nm_connection_get_setting_wireless(connection);
-    g_assert(s_wifi);
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS);
 
-    /* ===== WIRELESS SECURITY SETTING ===== */
-    s_wsec = nm_connection_get_setting_wireless_security(connection);
-    g_assert(s_wsec);
+    s_wsec = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     g_assert(strcmp(nm_setting_wireless_security_get_key_mgmt(s_wsec), "none") == 0);
     g_assert(nm_setting_wireless_security_get_wep_tx_keyidx(s_wsec) == 0);
@@ -4743,8 +4101,6 @@ test_read_wifi_wep_agent_keys(void)
 
     flags = nm_setting_wireless_security_get_wep_key_flags(s_wsec);
     g_assert(flags & NM_SETTING_SECRET_FLAG_AGENT_OWNED);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -4765,10 +4121,7 @@ test_write_wired_static(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired Static",
@@ -4782,10 +4135,7 @@ test_write_wired_static(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_MAC_ADDRESS,
                  "31:33:33:37:be:cd",
@@ -4793,10 +4143,7 @@ test_write_wired_static(void)
                  (guint32) 1492,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -4824,10 +4171,7 @@ test_write_wired_static(void)
     nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
     nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
@@ -4891,13 +4235,13 @@ test_write_wired_static(void)
 
     reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
 
-    reread_s_ip4 = nm_connection_get_setting_ip4_config(reread);
-    reread_s_ip6 = nm_connection_get_setting_ip6_config(reread);
+    reread_s_ip4 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP4_CONFIG);
+    reread_s_ip6 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP6_CONFIG);
 
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204);
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_equals(connection, FALSE, reread, FALSE);
 }
@@ -4920,10 +4264,7 @@ test_write_wired_static_with_generic(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired Static",
@@ -4937,10 +4278,7 @@ test_write_wired_static_with_generic(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_MAC_ADDRESS,
                  "31:33:33:37:be:cd",
@@ -4948,10 +4286,7 @@ test_write_wired_static_with_generic(void)
                  (guint32) 1492,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -4979,10 +4314,7 @@ test_write_wired_static_with_generic(void)
     nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
     nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
@@ -5039,21 +4371,22 @@ test_write_wired_static_with_generic(void)
     nm_setting_ip_config_add_dns_search(s_ip6, "foobar6.com");
     nm_setting_ip_config_add_dns_search(s_ip6, "lab6.foobar.com");
 
-    nm_connection_add_setting(connection, nm_setting_generic_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_GENERIC);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection_FIXME(connection, TEST_SCRATCH_DIR, &testfile);
     route6file = utils_get_route6_path(testfile);
 
-    reread       = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
-    reread_s_ip4 = nm_connection_get_setting_ip4_config(reread);
-    reread_s_ip6 = nm_connection_get_setting_ip6_config(reread);
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    reread_s_ip4 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP4_CONFIG);
+    reread_s_ip6 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP6_CONFIG);
 
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip4), ==, 204);
     g_assert_cmpint(nm_setting_ip_config_get_route_metric(reread_s_ip6), ==, 206);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     {
         gs_unref_hashtable GHashTable *diffs = NULL;
@@ -5064,8 +4397,8 @@ test_write_wired_static_with_generic(void)
         g_assert(g_hash_table_lookup(diffs, "generic"));
         g_assert(!nm_connection_compare(connection, reread, NM_SETTING_COMPARE_FLAG_EXACT));
     }
-    g_assert(!nm_connection_get_setting(reread, NM_TYPE_SETTING_GENERIC));
-    nm_connection_add_setting(reread, nm_setting_generic_new());
+    nmtst_connection_assert_no_setting(reread, NM_TYPE_SETTING_GENERIC);
+    _nm_connection_new_setting(reread, NM_TYPE_SETTING_GENERIC);
     {
         gs_unref_hashtable GHashTable *diffs = NULL;
 
@@ -5082,16 +4415,12 @@ test_write_wired_dhcp(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired DHCP",
@@ -5103,14 +4432,9 @@ test_write_wired_dhcp(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
@@ -5130,10 +4454,7 @@ test_write_wired_dhcp(void)
 
     nmtst_assert_connection_verifies(connection);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_DHCP,
@@ -5195,15 +4516,12 @@ test_write_routing_rules(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
 
     connection = nm_simple_connection_new();
 
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Routing Rules",
@@ -5215,17 +4533,12 @@ test_write_routing_rules(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
 
     _ip_routing_rule_add_to_setting(s_ip4, "pref 10 from 0.0.0.0/0 table 1");
@@ -5251,17 +4564,13 @@ test_write_wired_match(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingMatch *              s_match;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired with Match setting",
@@ -5273,31 +4582,26 @@ test_write_wired_match(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
-    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+                 NULL);
 
-    /* Match setting */
-    s_match = (NMSettingMatch *) nm_setting_match_new();
+    s_match = _nm_connection_new_setting(connection, NM_TYPE_SETTING_MATCH);
     nm_setting_match_add_interface_name(s_match, "ens*");
     nm_setting_match_add_interface_name(s_match, "eth 1?");
     nm_setting_match_add_interface_name(s_match, "!veth*");
     nm_setting_match_add_driver(s_match, "!virtio");
     nm_setting_match_add_driver(s_match, "e1000e");
     nm_setting_match_add_kernel_command_line(s_match, "!ip=");
-    nm_connection_add_setting(connection, NM_SETTING(s_match));
 
     nmtst_assert_connection_verifies(connection);
     _writer_new_connec_exp(connection,
@@ -5343,10 +4647,8 @@ test_read_write_wired_dhcp_send_hostname(void)
                                        NULL);
 
     /* Check dhcp-hostname and dhcp-send-hostname */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip4);
-    g_assert(s_ip6);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == TRUE);
     g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, "svata-pulec");
     g_assert(!nm_setting_ip_config_get_dhcp_hostname(s_ip6));
@@ -5363,10 +4665,8 @@ test_read_write_wired_dhcp_send_hostname(void)
     nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
 
     /* Check dhcp-hostname and dhcp-send-hostname from the re-read connection. */
-    s_ip4 = nm_connection_get_setting_ip4_config(reread);
-    s_ip6 = nm_connection_get_setting_ip6_config(reread);
-    g_assert(s_ip4);
-    g_assert(s_ip6);
+    s_ip4 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP4_CONFIG);
+    s_ip6 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip4) == FALSE);
     g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip4), ==, dhcp_hostname);
     g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, dhcp_hostname);
@@ -5383,8 +4683,7 @@ test_read_wired_dhcpv6_hostname_fallback(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_ip6 = nm_connection_get_setting_ip6_config(connection);
-    g_assert(s_ip6);
+    s_ip6 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_assert(nm_setting_ip_config_get_dhcp_send_hostname(s_ip6) == TRUE);
     g_assert_cmpstr(nm_setting_ip_config_get_dhcp_hostname(s_ip6), ==, "fully.qualified.domain");
 }
@@ -5404,10 +4703,7 @@ test_write_wired_static_ip6_only(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired Static IP6 Only",
@@ -5419,22 +4715,13 @@ test_write_wired_static_ip6_only(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
 
     /* Add addresses */
@@ -5462,7 +4749,6 @@ test_write_ip6_disabled(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
 
@@ -5471,16 +4757,18 @@ test_write_ip6_disabled(void)
                                                  NM_SETTING_WIRED_SETTING_NAME,
                                                  &s_con);
 
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_DISABLED, NULL);
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
+    g_object_set(s_ip6,
+                 NM_SETTING_IP_CONFIG_METHOD,
+                 NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
+                 NULL);
 
     nmtst_assert_connection_verifies(connection);
 
@@ -5513,16 +4801,14 @@ test_write_wired_static_ip6_only_gw(gconstpointer user_data)
     NMSettingIPConfig *           s_ip6;
     NMIPAddress *                 addr6;
     GError *                      error                 = NULL;
-    char *                        id                    = NULL;
+    gs_free char *                id                    = NULL;
     gs_free char *                written_ifcfg_gateway = NULL;
     const char *                  gateway6              = user_data;
     shvarFile *                   ifcfg;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     id = g_strdup_printf("Test Write Wired Static IP6 Only With Gateway %s", gateway6 ?: "NULL");
     g_object_set(s_con,
@@ -5535,24 +4821,14 @@ test_write_wired_static_ip6_only_gw(gconstpointer user_data)
                  NM_SETTING_CONNECTION_TYPE,
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
-    g_free(id);
-
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
 
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, "31:33:33:37:be:cd", NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_DISABLED, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
@@ -5582,14 +4858,14 @@ test_write_wired_static_ip6_only_gw(gconstpointer user_data)
     svCloseFile(ifcfg);
 
     /* access the gateway from the loaded connection. */
-    s_ip6 = nm_connection_get_setting_ip6_config(reread);
-    g_assert(s_ip6 && nm_setting_ip_config_get_num_addresses(s_ip6) == 1);
+    s_ip6 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP6_CONFIG);
+    g_assert(nm_setting_ip_config_get_num_addresses(s_ip6) == 1);
     addr6 = nm_setting_ip_config_get_address(s_ip6, 0);
     g_assert(addr6);
 
     /* assert that the gateway was written and reloaded as expected */
     if (!gateway6 || !strcmp(gateway6, "::")) {
-        g_assert(nm_setting_ip_config_get_gateway(s_ip6) == NULL);
+        g_assert_cmpstr(nm_setting_ip_config_get_gateway(s_ip6), ==, NULL);
         g_assert(written_ifcfg_gateway == NULL);
     } else {
         g_assert(nm_setting_ip_config_get_gateway(s_ip6) != NULL);
@@ -5607,7 +4883,6 @@ test_read_write_static_routes_legacy(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     const char *                  tmp;
 
@@ -5616,10 +4891,7 @@ test_read_write_static_routes_legacy(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== CONNECTION SETTING ===== */
-
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     /* ID */
     tmp = nm_setting_connection_get_id(s_con);
@@ -5628,15 +4900,9 @@ test_read_write_static_routes_legacy(void)
     /* Autoconnect */
     g_assert(nm_setting_connection_get_autoconnect(s_con));
 
-    /* ===== WIRED SETTING ===== */
-
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-
-    /* ===== IPv4 SETTING ===== */
+    nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpstr(nm_setting_ip_config_get_method(s_ip4), ==, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
 
     g_assert(!nm_setting_ip_config_get_never_default(s_ip4));
@@ -5676,10 +4942,7 @@ test_write_wired_static_routes(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired Static Routes",
@@ -5691,10 +4954,7 @@ test_write_wired_static_routes(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_MAC_ADDRESS,
                  "31:33:33:37:be:cd",
@@ -5702,10 +4962,7 @@ test_write_wired_static_routes(void)
                  (guint32) 1492,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -5746,15 +5003,14 @@ test_write_wired_static_routes(void)
     nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com");
     nm_setting_ip_config_add_dns_search(s_ip4, "lab.foobar.com");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
     nmtst_assert_connection_verifies(connection);
@@ -5770,8 +5026,7 @@ test_write_wired_static_routes(void)
      * connection equals. */
     g_assert(!reread_same);
     nmtst_assert_connection_verifies_without_normalization(reread);
-    s_ip4 = nm_connection_get_setting_ip4_config(reread);
-    g_assert(s_ip4);
+    s_ip4 = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert_cmpint(nm_setting_ip_config_get_num_routes(s_ip4), ==, 2);
     route = nm_setting_ip_config_get_route(s_ip4, 1);
     g_assert(route);
@@ -5791,7 +5046,6 @@ test_write_wired_dhcp_8021x_peap_mschapv2(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     NMSetting8021x *              s_8021x;
@@ -5800,10 +5054,7 @@ test_write_wired_dhcp_8021x_peap_mschapv2(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired DHCP 802.1x PEAP MSCHAPv2",
@@ -5815,31 +5066,22 @@ test_write_wired_dhcp_8021x_peap_mschapv2(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
-    /* 802.1x setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
-
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     g_object_set(s_8021x,
                  NM_SETTING_802_1X_IDENTITY,
                  "Bob Saget",
@@ -5854,7 +5096,6 @@ test_write_wired_dhcp_8021x_peap_mschapv2(void)
                  NM_SETTING_802_1X_PHASE2_AUTH,
                  "mschapv2",
                  NULL);
-
     nm_setting_802_1x_add_eap_method(s_8021x, "peap");
 
     success = nm_setting_802_1x_set_ca_cert(s_8021x,
@@ -5883,7 +5124,6 @@ test_write_wired_8021x_tls(gconstpointer test_data)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     NMSetting8021x *              s_8021x;
@@ -5891,7 +5131,9 @@ test_write_wired_8021x_tls(gconstpointer test_data)
     GError *                      error  = NULL;
     NMSetting8021xCKFormat        format = NM_SETTING_802_1X_CK_FORMAT_UNKNOWN;
     const char *                  pw;
-    char *                        tmp;
+    gs_free char *                tmp1 = NULL;
+    gs_free char *                tmp2 = NULL;
+    gs_free char *                tmp3 = NULL;
     gpointer                      scheme_p, flags_p;
     NMSetting8021xCKScheme        scheme;
     NMSettingSecretFlags          flags;
@@ -5902,10 +5144,7 @@ test_write_wired_8021x_tls(gconstpointer test_data)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired 802.1x TLS Blobs",
@@ -5917,29 +5156,20 @@ test_write_wired_8021x_tls(gconstpointer test_data)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
                  NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
-    /* 802.1x setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
 
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
     nm_setting_802_1x_add_eap_method(s_8021x, "tls");
 
@@ -5992,8 +5222,7 @@ test_write_wired_8021x_tls(gconstpointer test_data)
      * matter what scheme was used in the original connection they will be read
      * back in as paths.
      */
-    s_8021x = nm_connection_get_setting_802_1x(reread);
-    g_assert(s_8021x);
+    s_8021x = nmtst_connection_assert_setting(reread, NM_TYPE_SETTING_802_1X);
     g_assert_cmpint(nm_setting_802_1x_get_ca_cert_scheme(s_8021x),
                     ==,
                     NM_SETTING_802_1X_CK_SCHEME_PATH);
@@ -6027,7 +5256,7 @@ test_write_wired_8021x_tls(gconstpointer test_data)
              * say it's not system-owned, and therefore it should not show up
              * in the re-read connection.
              */
-            s_8021x = nm_connection_get_setting_802_1x(connection);
+            s_8021x = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_802_1X);
             g_object_set(s_8021x, NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD, NULL, NULL);
         }
 
@@ -6035,17 +5264,14 @@ test_write_wired_8021x_tls(gconstpointer test_data)
     }
 
     /* Clean up created certs and keys */
-    tmp = utils_cert_path(testfile, "ca-cert", "der");
-    nmtst_file_unlink_if_exists(tmp);
-    g_free(tmp);
+    tmp1 = utils_cert_path(testfile, "ca-cert", "der");
+    nmtst_file_unlink_if_exists(tmp1);
 
-    tmp = utils_cert_path(testfile, "client-cert", "der");
-    nmtst_file_unlink_if_exists(tmp);
-    g_free(tmp);
+    tmp2 = utils_cert_path(testfile, "client-cert", "der");
+    nmtst_file_unlink_if_exists(tmp2);
 
-    tmp = utils_cert_path(testfile, "private-key", "pem");
-    nmtst_file_unlink_if_exists(tmp);
-    g_free(tmp);
+    tmp3 = utils_cert_path(testfile, "private-key", "pem");
+    nmtst_file_unlink_if_exists(tmp3);
 }
 
 static void
@@ -6055,7 +5281,6 @@ test_write_wired_aliases(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     int                           num_addresses = 4;
     const char *                  ip[]          = {"1.1.1.1", "1.1.1.2", "1.1.1.3", "1.1.1.4"};
@@ -6072,10 +5297,7 @@ test_write_wired_aliases(void)
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "alias0",
@@ -6085,14 +5307,9 @@ test_write_wired_aliases(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -6145,7 +5362,7 @@ test_write_wired_aliases(void)
      * aliases get read back in essentially random order. So just
      * verify the aliases manually.
      */
-    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+    s_ip4 = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_assert(nm_setting_ip_config_get_num_addresses(s_ip4) == num_addresses);
 
     /* Addresses */
@@ -6191,7 +5408,6 @@ test_write_gateway(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     GError *                      error = NULL;
     shvarFile *                   f;
@@ -6199,10 +5415,7 @@ test_write_gateway(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Static Addresses Gateway",
@@ -6212,14 +5425,9 @@ test_write_gateway(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -6270,16 +5478,13 @@ test_write_wifi_open(void)
     NMSettingWireless *           s_wifi;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const unsigned char ssid_data[] = {0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44};
-    shvarFile *         ifcfg;
+    gs_unref_bytes GBytes *ssid =
+        nmtst_gbytes_from_arr(0x54, 0x65, 0x73, 0x74, 0x20, 0x53, 0x53, 0x49, 0x44);
+    shvarFile *ifcfg;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi Open",
@@ -6291,12 +5496,7 @@ test_write_wifi_open(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6314,23 +5514,17 @@ test_write_wifi_open(void)
                  (guint32) 1345,
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
     nmtst_assert_connection_verifies(connection);
@@ -6356,16 +5550,23 @@ test_write_wifi_open_hex_ssid(void)
     NMSettingWireless *           s_wifi;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const unsigned char           ssid_data[] =
-        {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd};
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_arr(0x11,
+                                                        0x22,
+                                                        0x33,
+                                                        0x44,
+                                                        0x55,
+                                                        0x66,
+                                                        0x77,
+                                                        0x88,
+                                                        0x99,
+                                                        0xaa,
+                                                        0xbb,
+                                                        0xcc,
+                                                        0xdd);
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi Open Hex SSID",
@@ -6377,12 +5578,7 @@ test_write_wifi_open_hex_ssid(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, sizeof(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6390,18 +5586,10 @@ test_write_wifi_open_hex_ssid(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -6430,16 +5618,12 @@ test_write_wifi_wep(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    struct stat                   statbuf;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
+    struct stat            statbuf;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP",
@@ -6451,12 +5635,7 @@ test_write_wifi_wep(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6464,12 +5643,7 @@ test_write_wifi_wep(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "none",
@@ -6483,21 +5657,17 @@ test_write_wifi_wep(void)
     nm_setting_wireless_security_set_wep_key(s_wsec, 2, "aaaaaaaaaaaaaaaaaaaaaaaaaa");
     nm_setting_wireless_security_set_wep_key(s_wsec, 3, "BBBBBBBBBBBBBBBBBBBBBBBBBB");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
     nmtst_assert_connection_verifies(connection);
@@ -6527,17 +5697,13 @@ test_write_wifi_wep_adhoc(void)
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     GError *                      error = NULL;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    struct stat                   statbuf;
-    NMIPAddress *                 addr;
+    gs_unref_bytes GBytes *ssid         = nmtst_gbytes_from_str("blahblah");
+    struct stat            statbuf;
+    NMIPAddress *          addr;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP AdHoc",
@@ -6549,27 +5715,14 @@ test_write_wifi_wep_adhoc(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi, NM_SETTING_WIRELESS_SSID, ssid, NM_SETTING_WIRELESS_MODE, "adhoc", NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL);
     nm_setting_wireless_security_set_wep_key(s_wsec, 0, "0123456789abcdef0123456789");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -6585,10 +5738,7 @@ test_write_wifi_wep_adhoc(void)
 
     nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -6622,16 +5772,12 @@ test_write_wifi_wep_passphrase(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    struct stat                   statbuf;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
+    struct stat            statbuf;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP Passphrase",
@@ -6643,12 +5789,7 @@ test_write_wifi_wep_passphrase(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6656,12 +5797,7 @@ test_write_wifi_wep_passphrase(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "none",
@@ -6674,16 +5810,10 @@ test_write_wifi_wep_passphrase(void)
                  NULL);
     nm_setting_wireless_security_set_wep_key(s_wsec, 0, "asdfdjaslfjasd;flasjdfl;aksdf");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -6717,16 +5847,12 @@ test_write_wifi_wep_40_ascii(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah40";
-    struct stat                   statbuf;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah40");
+    struct stat            statbuf;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP 40 ASCII",
@@ -6738,12 +5864,7 @@ test_write_wifi_wep_40_ascii(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6751,12 +5872,7 @@ test_write_wifi_wep_40_ascii(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "none",
@@ -6772,16 +5888,10 @@ test_write_wifi_wep_40_ascii(void)
     nm_setting_wireless_security_set_wep_key(s_wsec, 2, "dolor");
     nm_setting_wireless_security_set_wep_key(s_wsec, 3, "donec");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -6815,16 +5925,12 @@ test_write_wifi_wep_104_ascii(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah104";
-    struct stat                   statbuf;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah104");
+    struct stat            statbuf;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP 104 ASCII",
@@ -6836,12 +5942,7 @@ test_write_wifi_wep_104_ascii(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6849,12 +5950,7 @@ test_write_wifi_wep_104_ascii(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "none",
@@ -6870,16 +5966,10 @@ test_write_wifi_wep_104_ascii(void)
     nm_setting_wireless_security_set_wep_key(s_wsec, 2, "WEP-104 ASCII");
     nm_setting_wireless_security_set_wep_key(s_wsec, 3, "thisismyascii");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -6916,16 +6006,12 @@ test_write_wifi_leap(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    struct stat                   statbuf;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
+    struct stat            statbuf;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi LEAP",
@@ -6937,12 +6023,7 @@ test_write_wifi_leap(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -6950,12 +6031,7 @@ test_write_wifi_leap(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "ieee8021x",
@@ -6967,16 +6043,10 @@ test_write_wifi_leap(void)
                  "foobar22",
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7013,17 +6083,13 @@ test_write_wifi_leap_secret_flags(gconstpointer data)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    NMSettingSecretFlags          flags     = GPOINTER_TO_UINT(data);
+    gs_unref_bytes GBytes *ssid  = nmtst_gbytes_from_str("blahblah");
+    NMSettingSecretFlags   flags = GPOINTER_TO_UINT(data);
 
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi LEAP Secret Flags",
@@ -7033,23 +6099,15 @@ test_write_wifi_leap_secret_flags(gconstpointer data)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
                  NM_SETTING_WIRELESS_MODE,
                  "infrastructure",
                  NULL);
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
 
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "ieee8021x",
@@ -7063,15 +6121,10 @@ test_write_wifi_leap_secret_flags(gconstpointer data)
                  flags,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7108,8 +6161,7 @@ test_write_wifi_wpa_psk(gconstpointer test_data)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
     struct {
         const char *name, *psk;
         gpointer    wep_group_p, wpa_p, wpa2_p;
@@ -7126,10 +6178,7 @@ test_write_wifi_wpa_psk(gconstpointer test_data)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  args.name,
@@ -7141,12 +6190,7 @@ test_write_wifi_wpa_psk(gconstpointer test_data)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7154,12 +6198,7 @@ test_write_wifi_wpa_psk(gconstpointer test_data)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "wpa-psk",
@@ -7184,16 +6223,10 @@ test_write_wifi_wpa_psk(gconstpointer test_data)
         nm_setting_wireless_security_add_group(s_wsec, "ccmp");
     }
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7225,16 +6258,12 @@ test_write_wifi_wpa_psk_adhoc(void)
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     GError *                      error = NULL;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    NMIPAddress *                 addr;
+    gs_unref_bytes GBytes *ssid         = nmtst_gbytes_from_str("blahblah");
+    NMIPAddress *          addr;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WPA PSK",
@@ -7246,12 +6275,7 @@ test_write_wifi_wpa_psk_adhoc(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7263,12 +6287,7 @@ test_write_wifi_wpa_psk_adhoc(void)
                  "bg",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "wpa-psk",
@@ -7280,10 +6299,7 @@ test_write_wifi_wpa_psk_adhoc(void)
     nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
     nm_setting_wireless_security_add_group(s_wsec, "ccmp");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -7299,10 +6315,7 @@ test_write_wifi_wpa_psk_adhoc(void)
 
     nm_setting_ip_config_add_dns(s_ip4, "4.2.2.1");
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7336,15 +6349,11 @@ test_write_wifi_wpa_eap_tls(void)
     NMSettingIPConfig *           s_ip6;
     gboolean                      success;
     GError *                      error = NULL;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
+    gs_unref_bytes GBytes *ssid         = nmtst_gbytes_from_str("blahblah");
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WPA EAP-TLS",
@@ -7356,12 +6365,7 @@ test_write_wifi_wpa_eap_tls(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7369,12 +6373,7 @@ test_write_wifi_wpa_eap_tls(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "wpa-eap",
@@ -7385,17 +6384,14 @@ test_write_wifi_wpa_eap_tls(void)
     nm_setting_wireless_security_add_pairwise(s_wsec, "tkip");
     nm_setting_wireless_security_add_group(s_wsec, "tkip");
 
-    /* Wireless security setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
-
-    g_object_set(s_8021x, NM_SETTING_802_1X_IDENTITY, "Bill Smith", NULL);
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     g_object_set(s_8021x,
+                 NM_SETTING_802_1X_IDENTITY,
+                 "Bill Smith",
                  NM_SETTING_802_1X_PHASE1_AUTH_FLAGS,
                  (guint) (NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_0_DISABLE
                           | NM_SETTING_802_1X_AUTH_FLAGS_TLS_1_1_DISABLE),
                  NULL);
-
     nm_setting_802_1x_add_eap_method(s_8021x, "tls");
 
     success = nm_setting_802_1x_set_ca_cert(s_8021x,
@@ -7420,16 +6416,10 @@ test_write_wifi_wpa_eap_tls(void)
                                                 &error);
     nmtst_assert_success(success, error);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7463,15 +6453,11 @@ test_write_wifi_wpa_eap_ttls_tls(void)
     NMSettingIPConfig *           s_ip6;
     gboolean                      success;
     GError *                      error = NULL;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
+    gs_unref_bytes GBytes *ssid         = nmtst_gbytes_from_str("blahblah");
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WPA EAP-TTLS (TLS)",
@@ -7483,12 +6469,7 @@ test_write_wifi_wpa_eap_ttls_tls(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7496,23 +6477,14 @@ test_write_wifi_wpa_eap_ttls_tls(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
     nm_setting_wireless_security_add_proto(s_wsec, "rsn");
     nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
     nm_setting_wireless_security_add_group(s_wsec, "ccmp");
 
-    /* Wireless security setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
-
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     nm_setting_802_1x_add_eap_method(s_8021x, "ttls");
-
     g_object_set(s_8021x,
                  NM_SETTING_802_1X_IDENTITY,
                  "Bill Smith",
@@ -7556,16 +6528,10 @@ test_write_wifi_wpa_eap_ttls_tls(void)
                                                        &error);
     nmtst_assert_success(success, error);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7599,15 +6565,11 @@ test_write_wifi_wpa_eap_ttls_mschapv2(void)
     NMSettingIPConfig *           s_ip6;
     gboolean                      success;
     GError *                      error = NULL;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
+    gs_unref_bytes GBytes *ssid         = nmtst_gbytes_from_str("blahblah");
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WPA EAP-TTLS (MSCHAPv2)",
@@ -7619,12 +6581,7 @@ test_write_wifi_wpa_eap_ttls_mschapv2(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7632,12 +6589,7 @@ test_write_wifi_wpa_eap_ttls_mschapv2(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-eap", NULL);
     nm_setting_wireless_security_add_proto(s_wsec, "wpa");
     nm_setting_wireless_security_add_proto(s_wsec, "rsn");
@@ -7646,12 +6598,8 @@ test_write_wifi_wpa_eap_ttls_mschapv2(void)
     nm_setting_wireless_security_add_group(s_wsec, "tkip");
     nm_setting_wireless_security_add_group(s_wsec, "ccmp");
 
-    /* Wireless security setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
-
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     nm_setting_802_1x_add_eap_method(s_8021x, "ttls");
-
     g_object_set(s_8021x,
                  NM_SETTING_802_1X_IDENTITY,
                  "Bill Smith",
@@ -7670,16 +6618,10 @@ test_write_wifi_wpa_eap_ttls_mschapv2(void)
                                             &error);
     nmtst_assert_success(success, error);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -7704,14 +6646,14 @@ test_write_wifi_wpa_then_open(void)
     nmtst_auto_unlinkfile char *testfile     = NULL;
     gs_free char *              keyfile      = NULL;
     gs_unref_object NMConnection *connection = NULL;
-    gs_unref_object NMConnection *reread     = NULL;
+    gs_unref_object NMConnection *reread1    = NULL;
+    gs_unref_object NMConnection *reread2    = NULL;
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
 
     /* Test that writing out a WPA config then changing that to an open
      * config doesn't leave various WPA-related keys lying around in the ifcfg.
@@ -7719,10 +6661,7 @@ test_write_wifi_wpa_then_open(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "random wifi connection",
@@ -7734,12 +6673,7 @@ test_write_wifi_wpa_then_open(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7747,12 +6681,7 @@ test_write_wifi_wpa_then_open(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "wpa-psk",
@@ -7768,30 +6697,26 @@ test_write_wifi_wpa_then_open(void)
     nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
     nm_setting_wireless_security_add_group(s_wsec, "ccmp");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
 
-    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
-
-    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+    reread1 = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
 
-    g_object_unref(reread);
+    nmtst_assert_connection_equals(connection, TRUE, reread1, FALSE);
 
     nmtst_connection_normalize(connection);
 
@@ -7806,9 +6731,9 @@ test_write_wifi_wpa_then_open(void)
     keyfile = utils_get_keys_path(testfile);
     g_assert(!g_file_test(keyfile, G_FILE_TEST_EXISTS));
 
-    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+    reread2 = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
 
-    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+    nmtst_assert_connection_equals(connection, TRUE, reread2, FALSE);
 }
 
 static void
@@ -7817,16 +6742,15 @@ test_write_wifi_wpa_then_wep_with_perms(void)
     nmtst_auto_unlinkfile char *testfile     = NULL;
     nmtst_auto_unlinkfile char *keyfile      = NULL;
     gs_unref_object NMConnection *connection = NULL;
-    gs_unref_object NMConnection *reread     = NULL;
+    gs_unref_object NMConnection *reread1    = NULL;
+    gs_unref_object NMConnection *reread2    = NULL;
     NMSettingConnection *         s_con;
     NMSettingWireless *           s_wifi;
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     gboolean                      success;
-    GBytes *                      ssid;
-    char **                       perms;
-    const char *                  ssid_data = "SomeSSID";
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("SomeSSID");
 
     /* Test that writing out a WPA config then changing that to a WEP
      * config works and doesn't cause infinite loop or other issues.
@@ -7835,11 +6759,7 @@ test_write_wifi_wpa_then_wep_with_perms(void)
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
-    perms = g_strsplit("user:superman:", ",", -1);
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "random wifi connection 2",
@@ -7848,19 +6768,13 @@ test_write_wifi_wpa_then_wep_with_perms(void)
                  NM_SETTING_CONNECTION_AUTOCONNECT,
                  TRUE,
                  NM_SETTING_CONNECTION_PERMISSIONS,
-                 perms,
+                 NM_MAKE_STRV("user:superman:"),
                  NM_SETTING_CONNECTION_TYPE,
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
-    g_strfreev(perms);
     g_assert_cmpint(nm_setting_connection_get_num_permissions(s_con), ==, 1);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7868,12 +6782,7 @@ test_write_wifi_wpa_then_wep_with_perms(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "wpa-psk",
@@ -7889,38 +6798,31 @@ test_write_wifi_wpa_then_wep_with_perms(void)
     nm_setting_wireless_security_add_pairwise(s_wsec, "ccmp");
     nm_setting_wireless_security_add_group(s_wsec, "ccmp");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
 
-    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+    reread1 = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
 
-    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
-
-    g_object_unref(reread);
+    nmtst_assert_connection_equals(connection, TRUE, reread1, FALSE);
 
     nmtst_connection_normalize(connection);
 
     /* Now change the connection to WEP and recheck */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
 
     g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "none", NULL);
     nm_setting_wireless_security_set_wep_key(s_wsec, 0, "abraka  dabra");
@@ -7931,11 +6833,11 @@ test_write_wifi_wpa_then_wep_with_perms(void)
                               testfile,
                               TEST_IFCFG_DIR "/ifcfg-random_wifi_connection_2.cexpected");
 
-    reread = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
+    reread2 = _connection_from_file(testfile, NULL, TYPE_WIRELESS, NULL);
 
     nmtst_connection_normalize(connection);
     success = nm_connection_compare(connection,
-                                    reread,
+                                    reread2,
                                     NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
                                         | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS);
     g_assert(success);
@@ -7956,15 +6858,12 @@ test_write_wifi_dynamic_wep_leap(void)
     NMSetting8021x *              s_8021x;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    GBytes *                      ssid;
-    const char *                  ssid_data = "blahblah";
-    shvarFile *                   ifcfg;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("blahblah");
+    shvarFile *            ifcfg;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi Dynamic WEP LEAP",
@@ -7974,12 +6873,7 @@ test_write_wifi_dynamic_wep_leap(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(ssid_data, strlen(ssid_data));
-
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
@@ -7987,20 +6881,11 @@ test_write_wifi_dynamic_wep_leap(void)
                  "infrastructure",
                  NULL);
 
-    g_bytes_unref(ssid);
-
-    /* Wireless security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
-
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec, NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, "ieee8021x", NULL);
 
-    /* Wireless security setting */
-    s_8021x = (NMSetting8021x *) nm_setting_802_1x_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_8021x));
-
+    s_8021x = _nm_connection_new_setting(connection, NM_TYPE_SETTING_802_1X);
     nm_setting_802_1x_add_eap_method(s_8021x, "leap");
-
     g_object_set(s_8021x,
                  NM_SETTING_802_1X_IDENTITY,
                  "Bill Smith",
@@ -8008,15 +6893,10 @@ test_write_wifi_dynamic_wep_leap(void)
                  ";alkdfja;dslkfjsad;lkfjsadf",
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
 
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -8054,14 +6934,10 @@ test_write_wired_qeth_dhcp(void)
     NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    char **                       subchans;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired qeth Static",
@@ -8073,34 +6949,23 @@ test_write_wired_qeth_dhcp(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    subchans = g_strsplit("0.0.600,0.0.601,0.0.602", ",", -1);
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_S390_SUBCHANNELS,
-                 subchans,
+                 NM_MAKE_STRV("0.0.600", "0.0.601", "0.0.602"),
                  NM_SETTING_WIRED_S390_NETTYPE,
                  "qeth",
                  NULL);
-    g_strfreev(subchans);
 
     nm_setting_wired_add_s390_option(s_wired, "portname", "FOOBAR");
     nm_setting_wired_add_s390_option(s_wired, "portno", "1");
     nm_setting_wired_add_s390_option(s_wired, "layer2", "0");
     nm_setting_wired_add_s390_option(s_wired, "protocol", "blahbalh");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -8127,15 +6992,11 @@ test_write_wired_ctc_dhcp(void)
     NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    char **                       subchans;
     shvarFile *                   ifcfg;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired ctc Static",
@@ -8145,29 +7006,19 @@ test_write_wired_ctc_dhcp(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    subchans = g_strsplit("0.0.600,0.0.601", ",", -1);
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired,
                  NM_SETTING_WIRED_S390_SUBCHANNELS,
-                 subchans,
+                 NM_MAKE_STRV("0.0.600", "0.0.601"),
                  NM_SETTING_WIRED_S390_NETTYPE,
                  "ctc",
                  NULL);
-    g_strfreev(subchans);
     nm_setting_wired_add_s390_option(s_wired, "ctcprot", "0");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -8201,16 +7052,12 @@ test_write_permissions(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Permissions",
@@ -8226,20 +7073,12 @@ test_write_permissions(void)
     nm_setting_connection_add_permission(s_con, "user", "foobar", NULL);
     nm_setting_connection_add_permission(s_con, "user", "asdfasdf", NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
@@ -8270,16 +7109,12 @@ test_write_wifi_wep_agent_keys(void)
     NMSettingWirelessSecurity *   s_wsec;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    const char *                  str_ssid = "foobarbaz";
-    GBytes *                      ssid;
+    gs_unref_bytes GBytes *ssid = nmtst_gbytes_from_str("foobarbaz");
 
     connection = nm_simple_connection_new();
     g_assert(connection != NULL);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wifi WEP Agent Owned",
@@ -8289,39 +7124,28 @@ test_write_wifi_wep_agent_keys(void)
                  NM_SETTING_WIRELESS_SETTING_NAME,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
                  NM_SETTING_IP_CONFIG_MAY_FAIL,
                  TRUE,
+                 NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
+                 (int) NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64,
                  NULL);
 
-    /* Wifi setting */
-    s_wifi = (NMSettingWireless *) nm_setting_wireless_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wifi));
-
-    ssid = g_bytes_new(str_ssid, strlen(str_ssid));
+    s_wifi = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS);
     g_object_set(s_wifi,
                  NM_SETTING_WIRELESS_SSID,
                  ssid,
                  NM_SETTING_WIRELESS_MODE,
                  "infrastructure",
                  NULL);
-    g_bytes_unref(ssid);
-
-    /* Wifi security setting */
-    s_wsec = (NMSettingWirelessSecurity *) nm_setting_wireless_security_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wsec));
 
+    s_wsec = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRELESS_SECURITY);
     g_object_set(s_wsec,
                  NM_SETTING_WIRELESS_SECURITY_KEY_MGMT,
                  "none",
@@ -8352,20 +7176,14 @@ test_write_wifi_wep_agent_keys(void)
 static void
 test_write_wired_pppoe(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingWired *     s_wired;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingPppoe *     s_pppoe;
-    NMSettingPpp *       s_ppp;
-    GError *             error = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingPppoe *              s_pppoe;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Wired PPPoE",
@@ -8377,21 +7195,13 @@ test_write_wired_pppoe(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* PPPoE setting */
-    s_pppoe = (NMSettingPppoe *) nm_setting_pppoe_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_pppoe));
-
-    g_object_set(G_OBJECT(s_pppoe),
+    s_pppoe = _nm_connection_new_setting(connection, NM_TYPE_SETTING_PPPOE);
+    g_object_set(s_pppoe,
                  NM_SETTING_PPPOE_SERVICE,
                  "stupid-service",
                  NM_SETTING_PPPOE_USERNAME,
@@ -8400,33 +7210,24 @@ test_write_wired_pppoe(void)
                  "test1",
                  NULL);
 
-    /* PPP setting */
-    s_ppp = (NMSettingPpp *) nm_setting_ppp_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ppp));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PPP);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
-
-    g_object_unref(connection);
-    g_clear_error(&error);
 }
 
 static void
 test_write_vpn(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingVpn *       s_vpn;
-    GError *             error = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingVpn *                s_vpn;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write VPN",
@@ -8438,10 +7239,7 @@ test_write_vpn(void)
                  NM_SETTING_VPN_SETTING_NAME,
                  NULL);
 
-    /* VPN setting */
-    s_vpn = (NMSettingVpn *) nm_setting_vpn_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_vpn));
-
+    s_vpn = _nm_connection_new_setting(connection, NM_TYPE_SETTING_VPN);
     g_object_set(s_vpn,
                  NM_SETTING_VPN_SERVICE_TYPE,
                  "awesomevpn",
@@ -8452,39 +7250,27 @@ test_write_vpn(void)
     nm_setting_vpn_add_data_item(s_vpn, "server", "vpn.somewhere.com");
     nm_setting_vpn_add_secret(s_vpn, "password", "sup3rs3cr3t");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
-
-    g_object_unref(connection);
-    g_clear_error(&error);
 }
 
 static void
 test_write_mobile_broadband(gconstpointer data)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingIPConfig *  s_ip4;
-    NMSettingGsm *       s_gsm;
-    NMSettingCdma *      s_cdma;
-    NMSettingPpp *       s_ppp;
-    NMSettingSerial *    s_serial;
-    GError *             error = NULL;
-    gboolean             gsm   = GPOINTER_TO_UINT(data);
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingCdma *               s_cdma;
+    NMSettingSerial *             s_serial;
+    gboolean                      gsm = GPOINTER_TO_UINT(data);
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  gsm ? "Test Write GSM" : "Test Write CDMA",
@@ -8496,22 +7282,14 @@ test_write_mobile_broadband(gconstpointer data)
                  gsm ? NM_SETTING_GSM_SETTING_NAME : NM_SETTING_CDMA_SETTING_NAME,
                  NULL);
 
-    if (gsm) {
-        /* GSM setting */
-        s_gsm = (NMSettingGsm *) nm_setting_gsm_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_gsm));
-    } else {
-        /* CDMA setting */
-        s_cdma = (NMSettingCdma *) nm_setting_cdma_new();
-        nm_connection_add_setting(connection, NM_SETTING(s_cdma));
-
+    if (gsm)
+        _nm_connection_new_setting(connection, NM_TYPE_SETTING_GSM);
+    else {
+        s_cdma = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CDMA);
         g_object_set(s_cdma, NM_SETTING_CDMA_NUMBER, "#777", NULL);
     }
 
-    /* Serial setting */
-    s_serial = (NMSettingSerial *) nm_setting_serial_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_serial));
-
+    s_serial = _nm_connection_new_setting(connection, NM_TYPE_SETTING_SERIAL);
     g_object_set(s_serial,
                  NM_SETTING_SERIAL_BAUD,
                  115200,
@@ -8523,42 +7301,31 @@ test_write_mobile_broadband(gconstpointer data)
                  1,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* PPP setting */
-    s_ppp = (NMSettingPpp *) nm_setting_ppp_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ppp));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PPP);
 
     nmtst_assert_connection_verifies(connection);
 
     _writer_new_connection_fail(connection, TEST_SCRATCH_DIR, NULL);
-
-    g_object_unref(connection);
-    g_clear_error(&error);
 }
 
 static void
 test_read_bridge_main(void)
 {
-    NMConnection *   connection;
-    NMSettingBridge *s_bridge;
-    NMSettingWired * s_wired;
-    const char *     mac;
-    char             expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingBridge *             s_bridge;
+    NMSettingWired *              s_wired;
+    const char *                  mac;
+    char expected_mac_address[ETH_ALEN] = {0x00, 0x16, 0x41, 0x11, 0x22, 0x33};
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-main", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0");
 
-    /* ===== Bridging SETTING ===== */
-
-    s_bridge = nm_connection_get_setting_bridge(connection);
-    g_assert(s_bridge);
+    s_bridge = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BRIDGE);
     g_assert_cmpuint(nm_setting_bridge_get_forward_delay(s_bridge), ==, 2);
     g_assert(nm_setting_bridge_get_stp(s_bridge));
     g_assert_cmpuint(nm_setting_bridge_get_priority(s_bridge), ==, 32744);
@@ -8570,14 +7337,10 @@ test_read_bridge_main(void)
     g_assert_cmpint(nm_setting_bridge_get_vlan_filtering(s_bridge), ==, TRUE);
     g_assert_cmpint(nm_setting_bridge_get_vlan_default_pvid(s_bridge), ==, 99);
 
-    /* MAC address */
-    s_wired = nm_connection_get_setting_wired(connection);
-    g_assert(s_wired);
-    mac = nm_setting_wired_get_cloned_mac_address(s_wired);
+    s_wired = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_WIRED);
+    mac     = nm_setting_wired_get_cloned_mac_address(s_wired);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, ETH_ALEN));
-
-    g_object_unref(connection);
 }
 
 static void
@@ -8588,8 +7351,8 @@ test_write_bridge_main(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingBridge *             s_bridge;
-    NMSettingIPConfig *           s_ip4, *s_ip6;
-    NMSettingWired *              s_wired;
+    NMSettingIPConfig *           s_ip4;
+    NMSettingIPConfig *           s_ip6;
     NMIPAddress *                 addr;
     static const char *           mac   = "31:33:33:37:be:cd";
     GError *                      error = NULL;
@@ -8599,10 +7362,7 @@ test_write_bridge_main(void)
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Bridge Main",
@@ -8616,9 +7376,7 @@ test_write_bridge_main(void)
                  NM_SETTING_BRIDGE_SETTING_NAME,
                  NULL);
 
-    /* bridge setting */
-    s_bridge = (NMSettingBridge *) nm_setting_bridge_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_bridge));
+    s_bridge = _nm_connection_new_setting(connection, NM_TYPE_SETTING_BRIDGE);
 
     vlans = g_ptr_array_new_with_free_func((GDestroyNotify) nm_bridge_vlan_unref);
     vlan  = nm_bridge_vlan_new(10, 16);
@@ -8644,10 +7402,7 @@ test_write_bridge_main(void)
                  vlans,
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -8662,17 +7417,12 @@ test_write_bridge_main(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -8686,29 +7436,25 @@ test_write_bridge_main(void)
 static void
 test_read_bridge_component(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingBridgePort *s_port;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBridgePort *         s_port;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-component",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "br0");
     g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
                     ==,
                     NM_SETTING_BRIDGE_SETTING_NAME);
 
-    s_port = nm_connection_get_setting_bridge_port(connection);
-    g_assert(s_port);
+    s_port = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BRIDGE_PORT);
     g_assert(nm_setting_bridge_port_get_hairpin_mode(s_port));
     g_assert_cmpuint(nm_setting_bridge_port_get_priority(s_port), ==, 28);
     g_assert_cmpuint(nm_setting_bridge_port_get_path_cost(s_port), ==, 100);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -8728,10 +7474,7 @@ test_write_bridge_component(void)
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Bridge Component",
@@ -8747,10 +7490,7 @@ test_write_bridge_component(void)
                  NM_SETTING_BRIDGE_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
 
     /* Bridge port */
@@ -8765,8 +7505,7 @@ test_write_bridge_component(void)
     nm_bridge_vlan_set_pvid(vlan, TRUE);
     g_ptr_array_add(vlans, vlan);
 
-    s_port = nm_setting_bridge_port_new();
-    nm_connection_add_setting(connection, s_port);
+    s_port = _nm_connection_new_setting(connection, NM_TYPE_SETTING_BRIDGE_PORT);
     g_object_set(s_port,
                  NM_SETTING_BRIDGE_PORT_PRIORITY,
                  50,
@@ -8791,8 +7530,8 @@ test_write_bridge_component(void)
 static void
 test_read_bridge_missing_stp(void)
 {
-    NMConnection *   connection;
-    NMSettingBridge *s_bridge;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingBridge *             s_bridge;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bridge-missing-stp",
                                        NULL,
@@ -8801,13 +7540,8 @@ test_read_bridge_missing_stp(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "br0");
 
-    /* ===== Bridging SETTING ===== */
-
-    s_bridge = nm_connection_get_setting_bridge(connection);
-    g_assert(s_bridge);
+    s_bridge = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BRIDGE);
     g_assert(nm_setting_bridge_get_stp(s_bridge) == FALSE);
-
-    g_object_unref(connection);
 }
 
 #define TEST_IFCFG_VLAN_INTERFACE TEST_IFCFG_DIR "/ifcfg-test-vlan-interface"
@@ -8815,16 +7549,16 @@ test_read_bridge_missing_stp(void)
 static void
 test_read_vlan_interface(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
-    guint32        from = 0, to = 0;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
+    guint32                       from = 0;
+    guint32                       to   = 0;
 
     connection = _connection_from_file(TEST_IFCFG_VLAN_INTERFACE, NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan43");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43);
@@ -8857,8 +7591,6 @@ test_read_vlan_interface(void)
     g_assert(nm_setting_vlan_get_priority(s_vlan, NM_VLAN_EGRESS_MAP, 2, &from, &to));
     g_assert_cmpint(from, ==, 14);
     g_assert_cmpint(to, ==, 7);
-
-    g_object_unref(connection);
 }
 
 #define TEST_IFCFG_VLAN_ONLY_VLANID TEST_IFCFG_DIR "/ifcfg-test-vlan-only-vlanid"
@@ -8866,21 +7598,18 @@ test_read_vlan_interface(void)
 static void
 test_read_vlan_only_vlan_id(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection = _connection_from_file(TEST_IFCFG_VLAN_ONLY_VLANID, NULL, TYPE_ETHERNET, NULL);
 
     g_assert(nm_connection_get_interface_name(connection) == NULL);
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 43);
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -8898,8 +7627,7 @@ test_read_vlan_vlanid_use(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0.9");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 10);
@@ -8918,8 +7646,8 @@ test_read_vlan_vlanid_use(void)
 static void
 test_read_vlan_only_device(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-only-device",
                                        NULL,
@@ -8928,40 +7656,34 @@ test_read_vlan_only_device(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0.9");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 9);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_vlan_physdev(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-physdev", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_vlan_reorder_hdr_1(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     NMTST_EXPECT_NM_WARN("*REORDER_HDR key is deprecated, use VLAN_FLAGS*");
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-1",
@@ -8972,22 +7694,19 @@ test_read_vlan_reorder_hdr_1(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
     /* Check that REORDER_HDR=0 is ignored */
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_vlan_reorder_hdr_2(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-reorder-hdr-2",
                                        NULL,
@@ -8996,61 +7715,52 @@ test_read_vlan_reorder_hdr_2(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan0.3");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth0");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 3);
     /* Check that VLAN_FLAGS=NO_REORDER_HDR works */
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_LOOSE_BINDING);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_vlan_flags_1(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-1", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44);
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan),
                     ==,
                     NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS);
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_vlan_flags_2(void)
 {
-    NMConnection * connection;
-    NMSettingVlan *s_vlan;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingVlan *               s_vlan;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-vlan-flags-2", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "super-vlan");
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "eth9");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 44);
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan),
                     ==,
                     NM_VLAN_FLAG_GVRP | NM_VLAN_FLAG_LOOSE_BINDING | NM_VLAN_FLAG_REORDER_HEADERS);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -9108,14 +7818,10 @@ test_write_vlan_reorder_hdr(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingVlan *               s_vlan;
-    NMSettingWired *              s_wired;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write VLAN reorder_hdr",
@@ -9127,14 +7833,9 @@ test_write_vlan_reorder_hdr(void)
                  NM_SETTING_VLAN_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* VLAN setting */
-    s_vlan = (NMSettingVlan *) nm_setting_vlan_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_vlan));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_vlan = _nm_connection_new_setting(connection, NM_TYPE_SETTING_VLAN);
     g_object_set(s_vlan,
                  NM_SETTING_VLAN_PARENT,
                  "eth0",
@@ -9161,16 +7862,12 @@ test_write_ethernet_missing_ipv6(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
 
     connection = nm_simple_connection_new();
     g_assert(connection);
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Ethernet Without IPv6 Setting",
@@ -9182,13 +7879,9 @@ test_write_ethernet_missing_ipv6(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_AUTO,
@@ -9227,31 +7920,26 @@ test_read_ibft_ignored(void)
 static void
 test_read_bond_main(void)
 {
-    NMConnection * connection;
-    NMSettingBond *s_bond;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingBond *               s_bond;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-main", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
 
-    /* ===== Bonding SETTING ===== */
-
-    s_bond = nm_connection_get_setting_bond(connection);
-    g_assert(s_bond);
+    s_bond = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BOND);
 
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
                     ==,
                     "100");
-
-    g_object_unref(connection);
 }
 
 static void
 test_read_bond_eth_type(void)
 {
-    NMConnection * connection;
-    NMSettingBond *s_bond;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingBond *               s_bond;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-eth-type",
                                        NULL,
@@ -9260,10 +7948,7 @@ test_read_bond_eth_type(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
 
-    /* ===== Bonding SETTING ===== */
-
-    s_bond = nm_connection_get_setting_bond(connection);
-    g_assert(s_bond);
+    s_bond = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BOND);
 
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MIIMON),
                     ==,
@@ -9271,8 +7956,6 @@ test_read_bond_eth_type(void)
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_LACP_RATE),
                     ==,
                     "1");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -9285,16 +7968,12 @@ test_write_bond_main(void)
     NMSettingBond *               s_bond;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    NMSettingWired *              s_wired;
     NMIPAddress *                 addr;
     GError *                      error = NULL;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Bond Main",
@@ -9308,22 +7987,14 @@ test_write_bond_main(void)
                  NM_SETTING_BOND_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* bond setting */
-    s_bond = (NMSettingBond *) nm_setting_bond_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_bond));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_bond = _nm_connection_new_setting(connection, NM_TYPE_SETTING_BOND);
     nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_DOWNDELAY, "5");
     nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_UPDELAY, "10");
     nm_setting_bond_add_option(s_bond, NM_SETTING_BOND_OPTION_MIIMON, "100");
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -9338,13 +8009,10 @@ test_write_bond_main(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -9361,21 +8029,18 @@ test_write_bond_main(void)
 static void
 test_read_bond_slave(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave", NULL, TYPE_ETHERNET, NULL);
     g_test_assert_expected_messages();
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0");
 
     g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -9391,10 +8056,7 @@ test_write_bond_slave(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Bond Slave",
@@ -9410,10 +8072,7 @@ test_write_bond_slave(void)
                  NM_SETTING_BOND_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
+    s_wired = _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
     g_object_set(s_wired, NM_SETTING_WIRED_MAC_ADDRESS, mac, NM_SETTING_WIRED_MTU, mtu, NULL);
 
     nmtst_assert_connection_verifies(connection);
@@ -9426,12 +8085,71 @@ test_write_bond_slave(void)
 }
 
 static void
+test_read_bond_port(void)
+{
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con      = NULL;
+    NMSettingBondPort *           s_port     = NULL;
+
+    connection =
+        _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-port", NULL, TYPE_ETHERNET, NULL);
+
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
+    g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond99");
+    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME);
+
+    s_port = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BOND_PORT);
+    g_assert_cmpuint(nm_setting_bond_port_get_queue_id(s_port), ==, 1);
+}
+
+static void
+test_write_bond_port(void)
+{
+    nmtst_auto_unlinkfile char *testfile     = NULL;
+    gs_unref_object NMConnection *connection = NULL;
+    gs_unref_object NMConnection *reread     = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBondPort *           s_bond_port;
+
+    connection = nm_simple_connection_new();
+
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
+    g_object_set(s_con,
+                 NM_SETTING_CONNECTION_ID,
+                 "Test Write Bond Port",
+                 NM_SETTING_CONNECTION_UUID,
+                 nm_uuid_generate_random_str_a(),
+                 NM_SETTING_CONNECTION_AUTOCONNECT,
+                 TRUE,
+                 NM_SETTING_CONNECTION_TYPE,
+                 NM_SETTING_WIRED_SETTING_NAME,
+                 NM_SETTING_CONNECTION_MASTER,
+                 "bond0",
+                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_BOND_SETTING_NAME,
+                 NULL);
+
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
+
+    s_bond_port = _nm_connection_new_setting(connection, NM_TYPE_SETTING_BOND_PORT);
+    g_object_set(s_bond_port, NM_SETTING_BOND_PORT_QUEUE_ID, 1, NULL);
+
+    nmtst_assert_connection_verifies(connection);
+
+    _writer_new_connection(connection, TEST_SCRATCH_DIR, &testfile);
+
+    reread = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
+
+    nmtst_assert_connection_equals(connection, TRUE, reread, FALSE);
+}
+
+static void
 test_read_infiniband(void)
 {
-    NMConnection *       connection;
-    NMSettingInfiniband *s_infiniband;
-    char *               unmanaged = NULL;
-    const char *         mac;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingInfiniband *         s_infiniband;
+    char *                        unmanaged = NULL;
+    const char *                  mac;
     char        expected_mac_address[INFINIBAND_ALEN] = {0x80, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55,
                                                   0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
                                                   0xdd, 0xee, 0xff, 0x00, 0x11, 0x22};
@@ -9443,22 +8161,15 @@ test_read_infiniband(void)
                                        &unmanaged);
     g_assert(!unmanaged);
 
-    /* ===== INFINIBAND SETTING ===== */
-
-    s_infiniband = nm_connection_get_setting_infiniband(connection);
-    g_assert(s_infiniband);
+    s_infiniband = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_INFINIBAND);
 
-    /* MAC address */
     mac = nm_setting_infiniband_get_mac_address(s_infiniband);
     g_assert(mac);
     g_assert(nm_utils_hwaddr_matches(mac, -1, expected_mac_address, sizeof(expected_mac_address)));
 
-    /* Transport mode */
     transport_mode = nm_setting_infiniband_get_transport_mode(s_infiniband);
     g_assert(transport_mode);
     g_assert_cmpstr(transport_mode, ==, "connected");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -9478,10 +8189,7 @@ test_write_infiniband(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write InfiniBand",
@@ -9493,10 +8201,7 @@ test_write_infiniband(void)
                  NM_SETTING_INFINIBAND_SETTING_NAME,
                  NULL);
 
-    /* InfiniBand setting */
-    s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_infiniband));
-
+    s_infiniband = _nm_connection_new_setting(connection, NM_TYPE_SETTING_INFINIBAND);
     g_object_set(s_infiniband,
                  NM_SETTING_INFINIBAND_MAC_ADDRESS,
                  mac,
@@ -9506,10 +8211,7 @@ test_write_infiniband(void)
                  "connected",
                  NULL);
 
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -9524,10 +8226,7 @@ test_write_infiniband(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
     nmtst_assert_connection_verifies(connection);
@@ -9548,8 +8247,7 @@ test_read_bond_slave_ib(void)
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-slave-ib", NULL, NULL, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
 
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "bond0");
     g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NM_SETTING_BOND_SETTING_NAME);
@@ -9567,10 +8265,7 @@ test_write_bond_slave_ib(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Bond Slave InfiniBand",
@@ -9586,10 +8281,7 @@ test_write_bond_slave_ib(void)
                  NM_SETTING_BOND_SETTING_NAME,
                  NULL);
 
-    /* InfiniBand setting */
-    s_infiniband = (NMSettingInfiniband *) nm_setting_infiniband_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_infiniband));
-
+    s_infiniband = _nm_connection_new_setting(connection, NM_TYPE_SETTING_INFINIBAND);
     g_object_set(s_infiniband,
                  NM_SETTING_INFINIBAND_MAC_ADDRESS,
                  mac,
@@ -9611,9 +8303,9 @@ test_write_bond_slave_ib(void)
 static void
 test_read_bond_opts_mode_numeric(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingBond *      s_bond;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingBond *               s_bond;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-bond-mode-numeric",
                                        NULL,
@@ -9622,19 +8314,15 @@ test_read_bond_opts_mode_numeric(void)
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "bond0");
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_BOND_SETTING_NAME);
 
-    s_bond = nm_connection_get_setting_bond(connection);
-    g_assert(s_bond);
+    s_bond = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_BOND);
     g_assert_cmpstr(nm_setting_bond_get_option_by_name(s_bond, NM_SETTING_BOND_OPTION_MODE),
                     ==,
                     "802.3ad");
-
-    g_object_unref(connection);
 }
 
 #define DCB_ALL_FLAGS \
@@ -9643,20 +8331,19 @@ test_read_bond_opts_mode_numeric(void)
 static void
 test_read_dcb_basic(void)
 {
-    NMConnection *connection;
-    NMSettingDcb *s_dcb;
-    guint         i;
-    guint         expected_group_ids[8]        = {0, 0, 0, 0, 1, 1, 1, 0xF};
-    guint         expected_group_bandwidths[8] = {25, 0, 0, 75, 0, 0, 0, 0};
-    guint         expected_bandwidths[8]       = {5, 10, 30, 25, 10, 50, 5, 0};
-    gboolean      expected_strict[8] = {FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE};
-    guint         expected_traffic_classes[8] = {7, 6, 5, 4, 3, 2, 1, 0};
-    gboolean      expected_pfcs[8] = {TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE};
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingDcb *                s_dcb;
+    guint                         i;
+    guint                         expected_group_ids[8]        = {0, 0, 0, 0, 1, 1, 1, 0xF};
+    guint                         expected_group_bandwidths[8] = {25, 0, 0, 75, 0, 0, 0, 0};
+    guint                         expected_bandwidths[8]       = {5, 10, 30, 25, 10, 50, 5, 0};
+    gboolean expected_strict[8]          = {FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE, TRUE};
+    guint    expected_traffic_classes[8] = {7, 6, 5, 4, 3, 2, 1, 0};
+    gboolean expected_pfcs[8]            = {TRUE, FALSE, FALSE, TRUE, TRUE, FALSE, TRUE, FALSE};
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-dcb", NULL, TYPE_ETHERNET, NULL);
 
-    s_dcb = nm_connection_get_setting_dcb(connection);
-    g_assert(s_dcb);
+    s_dcb = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_DCB);
 
     g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, DCB_ALL_FLAGS);
     g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, 7);
@@ -9702,8 +8389,6 @@ test_read_dcb_basic(void)
         g_assert_cmpint(nm_setting_dcb_get_priority_traffic_class(s_dcb, i),
                         ==,
                         expected_traffic_classes[i]);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -9713,7 +8398,6 @@ test_write_dcb_basic(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingDcb *                s_dcb;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
@@ -9727,9 +8411,8 @@ test_write_dcb_basic(void)
 
     connection = nm_simple_connection_new();
 
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-    g_object_set(G_OBJECT(s_con),
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
+    g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "dcb-test",
                  NM_SETTING_CONNECTION_UUID,
@@ -9740,30 +8423,16 @@ test_write_dcb_basic(void)
                  "eth0",
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    /* IP stuff */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    g_object_set(G_OBJECT(s_ip4),
-                 NM_SETTING_IP_CONFIG_METHOD,
-                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-                 NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
-
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    g_object_set(G_OBJECT(s_ip6),
-                 NM_SETTING_IP_CONFIG_METHOD,
-                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-                 NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* DCB */
-    s_dcb = (NMSettingDcb *) nm_setting_dcb_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_dcb));
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
 
-    g_object_set(G_OBJECT(s_dcb),
+    s_dcb = _nm_connection_new_setting(connection, NM_TYPE_SETTING_DCB);
+    g_object_set(s_dcb,
                  NM_SETTING_DCB_APP_FCOE_FLAGS,
                  DCB_ALL_FLAGS,
                  NM_SETTING_DCB_APP_FCOE_PRIORITY,
@@ -9814,8 +8483,7 @@ test_read_dcb_default_app_priorities(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_dcb = nm_connection_get_setting_dcb(connection);
-    g_assert(s_dcb);
+    s_dcb = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_DCB);
 
     g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
     g_assert_cmpint(nm_setting_dcb_get_app_fcoe_priority(s_dcb), ==, -1);
@@ -9950,8 +8618,7 @@ test_read_fcoe_mode(gconstpointer user_data)
     file       = g_strdup_printf(TEST_IFCFG_DIR "/ifcfg-test-fcoe-%s", expected_mode);
     connection = _connection_from_file(file, NULL, TYPE_ETHERNET, NULL);
 
-    s_dcb = nm_connection_get_setting_dcb(connection);
-    g_assert(s_dcb);
+    s_dcb = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_DCB);
 
     g_assert_cmpint(nm_setting_dcb_get_app_fcoe_flags(s_dcb), ==, NM_SETTING_DCB_FLAG_ENABLE);
     g_assert_cmpstr(nm_setting_dcb_get_app_fcoe_mode(s_dcb), ==, expected_mode);
@@ -9965,7 +8632,6 @@ test_write_fcoe_mode(gconstpointer user_data)
     gs_unref_object NMConnection *reread        = NULL;
     const char *                  expected_mode = user_data;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingDcb *                s_dcb;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
@@ -9973,9 +8639,8 @@ test_write_fcoe_mode(gconstpointer user_data)
 
     connection = nm_simple_connection_new();
 
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-    g_object_set(G_OBJECT(s_con),
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
+    g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "fcoe-test",
                  NM_SETTING_CONNECTION_UUID,
@@ -9986,30 +8651,16 @@ test_write_fcoe_mode(gconstpointer user_data)
                  "eth0",
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP stuff */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    g_object_set(G_OBJECT(s_ip4),
-                 NM_SETTING_IP_CONFIG_METHOD,
-                 NM_SETTING_IP4_CONFIG_METHOD_AUTO,
-                 NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    g_object_set(G_OBJECT(s_ip6),
-                 NM_SETTING_IP_CONFIG_METHOD,
-                 NM_SETTING_IP6_CONFIG_METHOD_AUTO,
-                 NULL);
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* DCB */
-    s_dcb = (NMSettingDcb *) nm_setting_dcb_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_dcb));
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
+    g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
 
-    g_object_set(G_OBJECT(s_dcb),
+    s_dcb = _nm_connection_new_setting(connection, NM_TYPE_SETTING_DCB);
+    g_object_set(s_dcb,
                  NM_SETTING_DCB_APP_FCOE_FLAGS,
                  NM_SETTING_DCB_FLAG_ENABLE,
                  NM_SETTING_DCB_APP_FCOE_MODE,
@@ -10032,28 +8683,24 @@ test_write_fcoe_mode(gconstpointer user_data)
 static void
 test_read_team_master(gconstpointer user_data)
 {
-    const char *const    PATH_NAME = user_data;
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingTeam *      s_team;
-    const char *         expected_config =
+    const char *const PATH_NAME              = user_data;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeam *               s_team;
+    const char *                  expected_config =
         "{\"device\": \"team0\", \"link_watch\": {\"name\": \"ethtool\"}}";
 
     connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "team0");
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_TEAM_SETTING_NAME);
 
-    s_team = nm_connection_get_setting_team(connection);
-    g_assert(s_team);
+    s_team = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_TEAM);
     g_assert_cmpstr(nm_setting_team_get_config(s_team), ==, expected_config);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -10080,7 +8727,6 @@ test_write_team_master(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingTeam *               s_team;
-    NMSettingWired *              s_wired;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
     const char *                  expected_config =
@@ -10089,10 +8735,7 @@ test_write_team_master(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Team Master",
@@ -10104,29 +8747,18 @@ test_write_team_master(void)
                  NM_SETTING_TEAM_SETTING_NAME,
                  NULL);
 
-    /* Team setting */
-    s_team = (NMSettingTeam *) nm_setting_team_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_team));
-
+    s_team = _nm_connection_new_setting(connection, NM_TYPE_SETTING_TEAM);
     g_object_set(s_team, NM_SETTING_TEAM_CONFIG, expected_config, NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_AUTO, NULL);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_AUTO, NULL);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -10145,26 +8777,22 @@ test_write_team_master(void)
 static void
 test_read_team_port(gconstpointer user_data)
 {
-    const char *const    PATH_NAME = user_data;
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
-    NMSettingTeamPort *  s_team_port;
-    const char *         expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}";
+    const char *const PATH_NAME              = user_data;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeamPort *           s_team_port;
+    const char *                  expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}";
 
     connection = _connection_from_file(PATH_NAME, NULL, TYPE_ETHERNET, NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0");
 
-    s_team_port = nm_connection_get_setting_team_port(connection);
-    g_assert(s_team_port);
+    s_team_port = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_TEAM_PORT);
     g_assert_cmpstr(nm_setting_team_port_get_config(s_team_port), ==, expected_config);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -10175,16 +8803,12 @@ test_write_team_port(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingTeamPort *           s_team_port;
-    NMSettingWired *              s_wired;
     const char *                  expected_config = "{\"p4p1\": {\"prio\": -10, \"sticky\": true}}";
     shvarFile *                   f;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Team Port",
@@ -10198,14 +8822,10 @@ test_write_team_port(void)
                  NM_SETTING_TEAM_SETTING_NAME,
                  NULL);
 
-    /* Team setting */
-    s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_team_port));
+    s_team_port = _nm_connection_new_setting(connection, NM_TYPE_SETTING_TEAM_PORT);
     g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
     nmtst_assert_connection_verifies(connection);
 
@@ -10234,16 +8854,13 @@ test_write_team_infiniband_port(void)
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
     NMSettingTeamPort *           s_team_port;
-    NMSettingInfiniband *         s_inf;
+    NMSettingInfiniband *         s_infiniband;
     const char *                  expected_config = "{\"inf1\": {\"prio\": -10, \"sticky\": true}}";
     shvarFile *                   f;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Team Infiniband Port",
@@ -10259,15 +8876,11 @@ test_write_team_infiniband_port(void)
                  "inf1",
                  NULL);
 
-    /* Team setting */
-    s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_team_port));
+    s_team_port = _nm_connection_new_setting(connection, NM_TYPE_SETTING_TEAM_PORT);
     g_object_set(s_team_port, NM_SETTING_TEAM_PORT_CONFIG, expected_config, NULL);
 
-    /* Infiniband setting */
-    s_inf = (NMSettingInfiniband *) nm_setting_infiniband_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_inf));
-    g_object_set(s_inf, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
+    s_infiniband = _nm_connection_new_setting(connection, NM_TYPE_SETTING_INFINIBAND);
+    g_object_set(s_infiniband, NM_SETTING_INFINIBAND_TRANSPORT_MODE, "datagram", NULL);
 
     nmtst_assert_connection_verifies(connection);
 
@@ -10291,28 +8904,26 @@ test_write_team_infiniband_port(void)
 static void
 test_read_team_port_empty_config(void)
 {
-    NMConnection *       connection;
-    NMSettingConnection *s_con;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingConnection *         s_con;
+    NMSettingTeamPort *           s_team_port;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-team-port-empty-config",
                                        NULL,
                                        TYPE_ETHERNET,
                                        NULL);
 
-    s_con = nm_connection_get_setting_connection(connection);
-    g_assert(s_con);
+    s_con = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_assert_cmpstr(nm_setting_connection_get_connection_type(s_con),
                     ==,
                     NM_SETTING_WIRED_SETTING_NAME);
     g_assert_cmpstr(nm_setting_connection_get_master(s_con), ==, "team0");
 
     /* Normalization adds a team-port setting */
-    g_assert(nm_connection_get_setting_team_port(connection));
+    s_team_port = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_TEAM_PORT);
 
     /* empty/missing config */
-    g_assert(!nm_setting_team_port_get_config(nm_connection_get_setting_team_port(connection)));
-
-    g_object_unref(connection);
+    g_assert(!nm_setting_team_port_get_config(s_team_port));
 }
 
 static void
@@ -10359,7 +8970,7 @@ test_team_reread_slave(void)
                  NM_SETTING_CONNECTION_SLAVE_TYPE,
                  "team",
                  NULL);
-    g_object_set(nm_connection_get_setting_vlan(connection_2),
+    g_object_set(nmtst_connection_assert_setting(connection_2, NM_TYPE_SETTING_VLAN),
                  NM_SETTING_VLAN_FLAGS,
                  1,
                  NM_SETTING_VLAN_ID,
@@ -10367,7 +8978,9 @@ test_team_reread_slave(void)
                  NM_SETTING_VLAN_PARENT,
                  "enp31s0f1",
                  NULL);
-    nm_connection_add_setting(connection_2, nm_setting_team_port_new());
+
+    _nm_connection_new_setting(connection_2, NM_TYPE_SETTING_TEAM_PORT);
+
     nmtst_connection_normalize(connection_2);
 
     nmtst_assert_connection_equals(connection_1, FALSE, connection_2, FALSE);
@@ -10392,8 +9005,8 @@ test_team_reread_slave(void)
 static void
 test_read_proxy_basic(void)
 {
-    NMConnection *  connection;
-    NMSettingProxy *s_proxy;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingProxy *              s_proxy;
 
     /* Test basic proxy configuration */
 
@@ -10402,18 +9015,13 @@ test_read_proxy_basic(void)
                                        TYPE_ETHERNET,
                                        NULL);
 
-    /* ===== Proxy setting ===== */
-    s_proxy = nm_connection_get_setting_proxy(connection);
-    g_assert(s_proxy);
+    s_proxy = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_PROXY);
 
-    /* Proxy method */
     g_assert_cmpint(nm_setting_proxy_get_method(s_proxy), ==, NM_SETTING_PROXY_METHOD_AUTO);
     g_assert(nm_setting_proxy_get_browser_only(s_proxy));
     g_assert_cmpstr(nm_setting_proxy_get_pac_url(s_proxy),
                     ==,
                     "http://wpad.mycompany.com/wpad.dat");
-
-    g_object_unref(connection);
 }
 
 static void
@@ -10423,17 +9031,13 @@ test_write_proxy_basic(void)
     gs_unref_object NMConnection *connection = NULL;
     gs_unref_object NMConnection *reread     = NULL;
     NMSettingConnection *         s_con;
-    NMSettingWired *              s_wired;
     NMSettingProxy *              s_proxy;
     const char *                  expected_url = "https://wpad.neverland.org/wpad.dat";
     shvarFile *                   f;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write Proxy Basic",
@@ -10443,15 +9047,11 @@ test_write_proxy_basic(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Proxy setting */
-    s_proxy = (NMSettingProxy *) nm_setting_proxy_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_proxy));
+    s_proxy = _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
     g_object_set(s_proxy, NM_SETTING_PROXY_METHOD, NM_SETTING_PROXY_METHOD_AUTO, NULL);
     g_object_set(s_proxy, NM_SETTING_PROXY_PAC_URL, expected_url, NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
     nmtst_assert_connection_verifies(connection);
 
@@ -10919,12 +9519,12 @@ test_write_unknown(gconstpointer test_data)
 static void
 test_read_vlan_trailing_spaces(void)
 {
-    const char *   testfile = TEST_IFCFG_DIR "/ifcfg-test-vlan-trailing-spaces";
-    NMConnection * connection;
-    gboolean       success;
-    GError *       error = NULL;
-    NMSettingVlan *s_vlan;
-    char *         contents = NULL;
+    const char *    testfile                 = TEST_IFCFG_DIR "/ifcfg-test-vlan-trailing-spaces";
+    gs_unref_object NMConnection *connection = NULL;
+    gboolean                      success;
+    GError *                      error = NULL;
+    NMSettingVlan *               s_vlan;
+    gs_free char *                contents = NULL;
 
     /* Ensure there is whitespace at the end of the VLAN interface name,
      * to prevent the whitespace getting stripped off and committed mistakenly
@@ -10935,19 +9535,15 @@ test_read_vlan_trailing_spaces(void)
     g_assert(success);
     g_assert(contents && contents[0]);
     g_assert(strstr(contents, "DEVICE=\"vlan201\"  \n"));
-    g_free(contents);
 
     connection = _connection_from_file(testfile, NULL, TYPE_ETHERNET, NULL);
 
-    s_vlan = nm_connection_get_setting_vlan(connection);
-    g_assert(s_vlan);
+    s_vlan = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_VLAN);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "vlan201");
     g_assert_cmpstr(nm_setting_vlan_get_parent(s_vlan), ==, "enccw0.0.fb00");
     g_assert_cmpint(nm_setting_vlan_get_id(s_vlan), ==, 201);
     g_assert_cmpint(nm_setting_vlan_get_flags(s_vlan), ==, NM_VLAN_FLAG_REORDER_HEADERS);
-
-    g_object_unref(connection);
 }
 
 /*****************************************************************************/
@@ -10998,31 +9594,28 @@ test_utils_name(void)
 static void
 do_test_utils_path_ifcfg(const char *desc, const char *path, const char *expected)
 {
-    char *result;
+    gs_free char *result = NULL;
 
     result = utils_get_ifcfg_path(path);
     g_assert_cmpstr(result, ==, expected);
-    g_free(result);
 }
 
 static void
 do_test_utils_path_keys(const char *desc, const char *path, const char *expected)
 {
-    char *result;
+    gs_free char *result = NULL;
 
     result = utils_get_keys_path(path);
     g_assert_cmpstr(result, ==, expected);
-    g_free(result);
 }
 
 static void
 do_test_utils_path_route(const char *desc, const char *path, const char *expected)
 {
-    char *result;
+    gs_free char *result = NULL;
 
     result = utils_get_route_path(path);
     g_assert_cmpstr(result, ==, expected);
-    g_free(result);
 }
 
 static void
@@ -11092,15 +9685,15 @@ test_sriov_read(void)
     NMSriovVF *                   vf;
     GVariant *                    variant;
     GError *                      error = NULL;
-    char *                        str;
+    gs_free char *                str1  = NULL;
+    gs_free char *                str2  = NULL;
 
     connection =
         _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-sriov", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0");
 
-    s_sriov = NM_SETTING_SRIOV(nm_connection_get_setting(connection, NM_TYPE_SETTING_SRIOV));
-    g_assert(s_sriov);
+    s_sriov = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_SRIOV);
 
     g_assert_cmpint(nm_setting_sriov_get_total_vfs(s_sriov), ==, 16);
     g_assert_cmpint(nm_setting_sriov_get_num_vfs(s_sriov), ==, 3);
@@ -11122,18 +9715,16 @@ test_sriov_read(void)
     g_assert_cmpint(g_variant_get_boolean(variant), ==, TRUE);
 
     /* VF 12 */
-    vf  = nm_setting_sriov_get_vf(s_sriov, 1);
-    str = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
+    vf   = nm_setting_sriov_get_vf(s_sriov, 1);
+    str1 = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
     g_assert_no_error(error);
-    g_assert_cmpstr(str, ==, "12 min-tx-rate=100 trust=false vlans=1.200.ad");
-    g_free(str);
+    g_assert_cmpstr(str1, ==, "12 min-tx-rate=100 trust=false vlans=1.200.ad");
 
     /* VF 15 */
-    vf  = nm_setting_sriov_get_vf(s_sriov, 2);
-    str = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
+    vf   = nm_setting_sriov_get_vf(s_sriov, 2);
+    str2 = nm_utils_sriov_vf_to_str(vf, FALSE, &error);
     g_assert_no_error(error);
-    g_assert_cmpstr(str, ==, "15 mac=01:23:45:67:89:ab max-tx-rate=200 vlans=2");
-    g_free(str);
+    g_assert_cmpstr(str2, ==, "15 mac=01:23:45:67:89:ab max-tx-rate=200 vlans=2");
 }
 
 static void
@@ -11145,7 +9736,6 @@ test_sriov_write(void)
     NMSettingConnection *         s_con;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    NMSettingWired *              s_wired;
     NMSettingSriov *              s_sriov;
     NMSriovVF *                   vf;
     gs_unref_ptrarray GPtrArray *vfs = NULL;
@@ -11154,10 +9744,7 @@ test_sriov_write(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write SR-IOV config",
@@ -11171,14 +9758,9 @@ test_sriov_write(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -11193,15 +9775,10 @@ test_sriov_write(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
-    /* SRIOV setting */
-    s_sriov = (NMSettingSriov *) nm_setting_sriov_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_sriov));
+    s_sriov = _nm_connection_new_setting(connection, NM_TYPE_SETTING_SRIOV);
 
     vfs = g_ptr_array_new_with_free_func((GDestroyNotify) nm_sriov_vf_unref);
 
@@ -11222,7 +9799,7 @@ test_sriov_write(void)
                  NM_TERNARY_TRUE,
                  NULL);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -11241,18 +9818,17 @@ test_sriov_write(void)
 static void
 test_tc_read(void)
 {
-    NMConnection *     connection;
-    NMSettingTCConfig *s_tc;
-    NMTCQdisc *        qdisc;
-    NMTCTfilter *      filter;
-    char *             str;
+    gs_unref_object NMConnection *connection = NULL;
+    NMSettingTCConfig *           s_tc;
+    NMTCQdisc *                   qdisc;
+    NMTCTfilter *                 filter;
+    gs_free char *                str = NULL;
 
     connection = _connection_from_file(TEST_IFCFG_DIR "/ifcfg-test-tc", NULL, TYPE_ETHERNET, NULL);
 
     g_assert_cmpstr(nm_connection_get_interface_name(connection), ==, "eth0");
 
-    s_tc = nm_connection_get_setting_tc_config(connection);
-    g_assert(s_tc);
+    s_tc = nmtst_connection_assert_setting(connection, NM_TYPE_SETTING_TC_CONFIG);
 
     g_assert_cmpint(nm_setting_tc_config_get_num_qdiscs(s_tc), ==, 1);
     qdisc = nm_setting_tc_config_get_qdisc(s_tc, 0);
@@ -11266,9 +9842,6 @@ test_tc_read(void)
     g_assert(filter);
     str = nm_utils_tc_tfilter_to_str(filter, NULL);
     g_assert_cmpstr(str, ==, "parent 1234: matchall action simple sdata Hello");
-    g_free(str);
-
-    g_object_unref(connection);
 }
 
 static void
@@ -11280,17 +9853,12 @@ test_tc_write_empty(void)
     NMSettingConnection *         s_con;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    NMSettingWired *              s_wired;
-    NMSettingTCConfig *           s_tc;
     NMIPAddress *                 addr;
     GError *                      error = NULL;
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write TC config",
@@ -11304,14 +9872,9 @@ test_tc_write_empty(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -11326,17 +9889,12 @@ test_tc_write_empty(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
-    /* TC setting */
-    s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_tc));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_TC_CONFIG);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -11359,7 +9917,6 @@ test_tc_write(void)
     NMSettingConnection *         s_con;
     NMSettingIPConfig *           s_ip4;
     NMSettingIPConfig *           s_ip6;
-    NMSettingWired *              s_wired;
     NMSettingTCConfig *           s_tc;
     NMTCQdisc *                   qdisc;
     NMTCTfilter *                 tfilter;
@@ -11368,10 +9925,7 @@ test_tc_write(void)
 
     connection = nm_simple_connection_new();
 
-    /* Connection setting */
-    s_con = (NMSettingConnection *) nm_setting_connection_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_con));
-
+    s_con = _nm_connection_new_setting(connection, NM_TYPE_SETTING_CONNECTION);
     g_object_set(s_con,
                  NM_SETTING_CONNECTION_ID,
                  "Test Write TC config",
@@ -11385,14 +9939,9 @@ test_tc_write(void)
                  NM_SETTING_WIRED_SETTING_NAME,
                  NULL);
 
-    /* Wired setting */
-    s_wired = (NMSettingWired *) nm_setting_wired_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_wired));
-
-    /* IP4 setting */
-    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip4));
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_WIRED);
 
+    s_ip4 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP4_CONFIG);
     g_object_set(s_ip4,
                  NM_SETTING_IP_CONFIG_METHOD,
                  NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
@@ -11407,15 +9956,10 @@ test_tc_write(void)
     nm_setting_ip_config_add_address(s_ip4, addr);
     nm_ip_address_unref(addr);
 
-    /* IP6 setting */
-    s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_ip6));
-
+    s_ip6 = _nm_connection_new_setting(connection, NM_TYPE_SETTING_IP6_CONFIG);
     g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_IGNORE, NULL);
 
-    /* TC setting */
-    s_tc = (NMSettingTCConfig *) nm_setting_tc_config_new();
-    nm_connection_add_setting(connection, NM_SETTING(s_tc));
+    s_tc = _nm_connection_new_setting(connection, NM_TYPE_SETTING_TC_CONFIG);
 
     qdisc = nm_tc_qdisc_new("pfifo_fast", TC_H_MAKE(0x2468 << 16, 0x2), &error);
     g_assert_no_error(error);
@@ -11428,7 +9972,7 @@ test_tc_write(void)
     nm_setting_tc_config_add_tfilter(s_tc, tfilter);
     nm_tc_tfilter_unref(tfilter);
 
-    nm_connection_add_setting(connection, nm_setting_proxy_new());
+    _nm_connection_new_setting(connection, NM_TYPE_SETTING_PROXY);
 
     nmtst_assert_connection_verifies_without_normalization(connection);
 
@@ -12040,6 +10584,8 @@ main(int argc, char **argv)
     g_test_add_func(TPATH "bond/write-slave", test_write_bond_slave);
     g_test_add_func(TPATH "bond/write-slave-ib", test_write_bond_slave_ib);
     g_test_add_func(TPATH "bond/bonding-opts-numeric-mode", test_read_bond_opts_mode_numeric);
+    g_test_add_func(TPATH "bond/read-bond-port", test_read_bond_port);
+    g_test_add_func(TPATH "bond/write-bond-port", test_write_bond_port);
 
     g_test_add_func(TPATH "bridge/read-master", test_read_bridge_main);
     g_test_add_func(TPATH "bridge/write-master", test_write_bridge_main);
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
index a8a9c40a..84986877 100644
--- a/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-interface-parser.c
@@ -112,7 +112,7 @@ _recursive_ifparser(if_parser *parser, const char *eni_file, int quiet)
     /* Check if interfaces file exists and open it */
     if (!g_file_test(eni_file, G_FILE_TEST_EXISTS)) {
         if (!quiet)
-            _LOGW("interfaces file %s doesn't exist", eni_file);
+            _LOGI("interfaces file %s doesn't exist", eni_file);
         return;
     }
     inp = fopen(eni_file, "re");
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
index db6b1aef..5bad2362 100644
--- a/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-parser.c
@@ -397,7 +397,7 @@ ifupdown_ip4_add_dns(NMSettingIPConfig *s_ip4, const char *dns)
     if (dns == NULL)
         return;
 
-    list = nm_utils_strsplit_set(dns, " \t");
+    list = nm_strsplit_set(dns, " \t");
     for (iter = list; iter && *iter; iter++) {
         if (!inet_pton(AF_INET, *iter, &addr)) {
             _LOGW("    ignoring invalid nameserver '%s'", *iter);
@@ -497,7 +497,7 @@ update_ip4_setting_from_if_block(NMConnection *connection, if_block *block, GErr
             gs_free const char **list = NULL;
             const char **        iter;
 
-            list = nm_utils_strsplit_set(search_v, " \t");
+            list = nm_strsplit_set(search_v, " \t");
             for (iter = list; iter && *iter; iter++) {
                 if (!nm_setting_ip_config_add_dns_search(s_ip4, *iter))
                     _LOGW("    duplicate DNS domain '%s'", *iter);
@@ -521,7 +521,7 @@ ifupdown_ip6_add_dns(NMSettingIPConfig *s_ip6, const char *dns)
     if (dns == NULL)
         return;
 
-    list = nm_utils_strsplit_set(dns, " \t");
+    list = nm_strsplit_set(dns, " \t");
     for (iter = list; iter && *iter; iter++) {
         if (!inet_pton(AF_INET6, *iter, &addr)) {
             _LOGW("    ignoring invalid nameserver '%s'", *iter);
@@ -605,7 +605,7 @@ update_ip6_setting_from_if_block(NMConnection *connection, if_block *block, GErr
             gs_free const char **list = NULL;
             const char **        iter;
 
-            list = nm_utils_strsplit_set(search_v, " \t");
+            list = nm_strsplit_set(search_v, " \t");
             for (iter = list; iter && *iter; iter++) {
                 if (!nm_setting_ip_config_add_dns_search(s_ip6, *iter))
                     _LOGW("    duplicate DNS domain '%s'", *iter);
diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
index 80a5638a..602a7327 100644
--- a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
+++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c
@@ -189,7 +189,7 @@ _unmanaged_specs(GHashTable *eni_ifaces)
     GSList *             specs = NULL;
     guint                i, len;
 
-    keys = nm_utils_strdict_get_keys(eni_ifaces, TRUE, &len);
+    keys = nm_strdict_get_keys(eni_ifaces, TRUE, &len);
     for (i = len; i > 0;) {
         i--;
         specs = g_slist_prepend(specs,
@@ -264,7 +264,7 @@ load_eni_ifaces(NMSIfupdownPlugin *self)
 
                     _LOGD("parse: found bridge ports %s for %s", ports, block->name);
 
-                    port_ifaces = nm_utils_strsplit_set(ports, " \t");
+                    port_ifaces = nm_strsplit_set(ports, " \t");
                     for (i = 0; port_ifaces && port_ifaces[i]; i++) {
                         const char *token = port_ifaces[i];
 
@@ -323,7 +323,7 @@ load_eni_ifaces(NMSIfupdownPlugin *self)
                       NM_PRINT_FMT_QUOTED(local, " (", local->message, ")", ""));
                 sd = NULL;
             } else {
-                nmtst_connection_assert_unchanging(connection);
+                nm_assert_connection_unchanging(connection);
                 uuid = nm_connection_get_uuid(connection);
 
                 if (!storage)
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
index e8ae1814..aa36ee8f 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-plugin.c
@@ -101,27 +101,27 @@ _extra_flags_to_string(char *   str,
     const char *str0 = str;
 
     if (!is_nm_generated && !is_volatile && !is_external)
-        nm_utils_strbuf_append_str(&str, &str_len, "");
+        nm_strbuf_append_str(&str, &str_len, "");
     else {
         char ch = '(';
 
-        nm_utils_strbuf_append_c(&str, &str_len, ' ');
+        nm_strbuf_append_c(&str, &str_len, ' ');
         if (is_nm_generated) {
-            nm_utils_strbuf_append_c(&str, &str_len, ch);
-            nm_utils_strbuf_append_str(&str, &str_len, "nm-generated");
+            nm_strbuf_append_c(&str, &str_len, ch);
+            nm_strbuf_append_str(&str, &str_len, "nm-generated");
             ch = ',';
         }
         if (is_volatile) {
-            nm_utils_strbuf_append_c(&str, &str_len, ch);
-            nm_utils_strbuf_append_str(&str, &str_len, "volatile");
+            nm_strbuf_append_c(&str, &str_len, ch);
+            nm_strbuf_append_str(&str, &str_len, "volatile");
             ch = ',';
         }
         if (is_external) {
-            nm_utils_strbuf_append_c(&str, &str_len, ch);
-            nm_utils_strbuf_append_str(&str, &str_len, "external");
+            nm_strbuf_append_c(&str, &str_len, ch);
+            nm_strbuf_append_str(&str, &str_len, "external");
             ch = ',';
         }
-        nm_utils_strbuf_append_c(&str, &str_len, ')');
+        nm_strbuf_append_c(&str, &str_len, ')');
     }
 
     return str0;
diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
index 8c526c81..ec0634c4 100644
--- a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
+++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c
@@ -168,7 +168,7 @@ nms_keyfile_storage_new_connection(NMSKeyfilePlugin *     plugin,
     nm_assert(filename && filename[0] == '/');
     nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN
               && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST);
-    nmtst_connection_assert_unchanging(connection_take);
+    nm_assert_connection_unchanging(connection_take);
 
     self = _storage_new(plugin,
                         nm_connection_get_uuid(connection_take),
diff --git a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
index a13c2586..4ceed49d 100644
--- a/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
+++ b/src/core/settings/plugins/keyfile/tests/test-keyfile-settings.c
@@ -992,7 +992,7 @@ test_read_intlike_ssid(void)
 
     ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
-    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+    g_assert(nm_g_bytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
 }
 
 static void
@@ -1010,7 +1010,7 @@ test_read_intlike_ssid_2(void)
 
     ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
-    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+    g_assert(nm_g_bytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
 }
 
 static void
@@ -1774,7 +1774,7 @@ test_write_wired_8021x_tls_connection_blob(void)
 
     password_raw = nm_setting_802_1x_get_password_raw(s_8021x);
     g_assert(password_raw);
-    g_assert(nm_utils_gbytes_equal_mem(password_raw, PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW)));
+    g_assert(nm_g_bytes_equal_mem(password_raw, PASSWORD_RAW, NM_STRLEN(PASSWORD_RAW)));
 
     unlink(testfile);
 
@@ -2160,7 +2160,7 @@ test_read_new_wireless_group_names(void)
 
     ssid = nm_setting_wireless_get_ssid(s_wifi);
     g_assert(ssid);
-    g_assert(nm_utils_gbytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
+    g_assert(nm_g_bytes_equal_mem(ssid, expected_ssid, strlen(expected_ssid)));
 
     g_assert_cmpstr(nm_setting_wireless_get_mode(s_wifi), ==, NM_SETTING_WIRELESS_MODE_INFRA);
 
diff --git a/src/core/supplicant/nm-supplicant-config.c b/src/core/supplicant/nm-supplicant-config.c
index 1959a161..c486ba1d 100644
--- a/src/core/supplicant/nm-supplicant-config.c
+++ b/src/core/supplicant/nm-supplicant-config.c
@@ -417,7 +417,7 @@ nm_supplicant_config_add_setting_macsec(NMSupplicantConfig *self,
 
     port = nm_setting_macsec_get_port(setting);
     if (port > 0 && port < 65534) {
-        snprintf(buf, sizeof(buf), "%d", port);
+        g_snprintf(buf, sizeof(buf), "%d", port);
         if (!nm_supplicant_config_add_option(self, "macsec_port", buf, -1, NULL, error))
             return FALSE;
     }
diff --git a/src/core/supplicant/nm-supplicant-interface.c b/src/core/supplicant/nm-supplicant-interface.c
index 1556e290..2d56b836 100644
--- a/src/core/supplicant/nm-supplicant-interface.c
+++ b/src/core/supplicant/nm-supplicant-interface.c
@@ -115,6 +115,9 @@ typedef struct _NMSupplicantInterfacePrivate {
     CList       peer_lst_head;
     CList       peer_initializing_lst_head;
 
+    in_addr_t p2p_assigned_addr;
+    guint8    p2p_assigned_plen;
+
     gint64 last_scan_msec;
 
     NMSupplicantAuthState auth_state;
@@ -693,7 +696,7 @@ _bss_info_properties_changed(NMSupplicantInterface *self,
         } else
             arr_len = 0;
 
-        if (!nm_utils_gbytes_equal_mem(bss_info->ssid, arr_data, arr_len)) {
+        if (!nm_g_bytes_equal_mem(bss_info->ssid, arr_data, arr_len)) {
             _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&bss_info->ssid);
 
             bss_info->ssid = (arr_len == 0) ? NULL : g_bytes_new(arr_data, arr_len);
@@ -915,23 +918,23 @@ _peer_info_properties_changed(NMSupplicantInterface *self,
         peer_info->signal_percent = nm_wifi_utils_level_to_quality(v_i32);
 
     if (nm_g_variant_lookup(properties, "DeviceName", "&s", &v_s))
-        nm_utils_strdup_reset(&peer_info->device_name, v_s);
+        nm_strdup_reset(&peer_info->device_name, v_s);
 
     if (nm_g_variant_lookup(properties, "Manufacturer", "&s", &v_s))
-        nm_utils_strdup_reset(&peer_info->manufacturer, v_s);
+        nm_strdup_reset(&peer_info->manufacturer, v_s);
 
     if (nm_g_variant_lookup(properties, "Model", "&s", &v_s))
-        nm_utils_strdup_reset(&peer_info->model, v_s);
+        nm_strdup_reset(&peer_info->model, v_s);
 
     if (nm_g_variant_lookup(properties, "ModelNumber", "&s", &v_s))
-        nm_utils_strdup_reset(&peer_info->model_number, v_s);
+        nm_strdup_reset(&peer_info->model_number, v_s);
 
     if (nm_g_variant_lookup(properties, "Serial", "&s", &v_s))
-        nm_utils_strdup_reset(&peer_info->serial, v_s);
+        nm_strdup_reset(&peer_info->serial, v_s);
 
     if (nm_g_variant_lookup(properties, "Groups", "^a&o", &v_strv)) {
         g_free(peer_info->groups);
-        peer_info->groups = nm_utils_strv_dup_packed(v_strv, -1);
+        peer_info->groups = nm_strv_dup_packed(v_strv, -1);
 
         g_free(v_strv);
     }
@@ -967,7 +970,7 @@ _peer_info_properties_changed(NMSupplicantInterface *self,
     v_v = nm_g_variant_lookup_value(properties, "IEs", G_VARIANT_TYPE_BYTESTRING);
     if (v_v) {
         arr_data = g_variant_get_fixed_array(v_v, &arr_len, 1);
-        if (!nm_utils_gbytes_equal_mem(peer_info->ies, arr_data, arr_len)) {
+        if (!nm_g_bytes_equal_mem(peer_info->ies, arr_data, arr_len)) {
             _nm_unused gs_unref_bytes GBytes *old_free = g_steal_pointer(&peer_info->ies);
 
             peer_info->ies = g_bytes_new(arr_data, arr_len);
@@ -1904,11 +1907,11 @@ _properties_changed_main(NMSupplicantInterface *self, GVariant *properties)
     }
 
     if (nm_g_variant_lookup(properties, "Ifname", "&s", &v_s)) {
-        if (nm_utils_strdup_reset(&priv->ifname, v_s))
+        if (nm_strdup_reset(&priv->ifname, v_s))
             do_log_driver_info = TRUE;
     }
     if (nm_g_variant_lookup(properties, "Driver", "&s", &v_s)) {
-        if (nm_utils_strdup_reset(&priv->driver, v_s))
+        if (nm_strdup_reset(&priv->driver, v_s))
             do_log_driver_info = TRUE;
     }
 
@@ -2305,7 +2308,7 @@ assoc_add_network_cb(GObject *source, GAsyncResult *result, gpointer user_data)
             self,
             NM_WPAS_DBUS_IFACE_INTERFACE,
             "AddBlob",
-            g_variant_new("(s@ay)", blob_name, nm_utils_gbytes_to_variant_ay(blob_data)),
+            g_variant_new("(s@ay)", blob_name, nm_g_bytes_to_variant_ay(blob_data)),
             G_VARIANT_TYPE("()"),
             G_DBUS_CALL_FLAGS_NONE,
             DBUS_TIMEOUT_MSEC,
@@ -2609,7 +2612,7 @@ nm_supplicant_interface_request_scan(NMSupplicantInterface *                  se
         g_variant_builder_init(&ssids_builder, G_VARIANT_TYPE_BYTESTRING_ARRAY);
         for (i = 0; i < ssids_len; i++) {
             nm_assert(ssids[i]);
-            g_variant_builder_add(&ssids_builder, "@ay", nm_utils_gbytes_to_variant_ay(ssids[i]));
+            g_variant_builder_add(&ssids_builder, "@ay", nm_g_bytes_to_variant_ay(ssids[i]));
         }
         g_variant_builder_add(&builder, "{sv}", "SSIDs", g_variant_builder_end(&ssids_builder));
     }
@@ -2943,6 +2946,15 @@ _get_all_p2p_device_cb(GVariant *result, GError *error, gpointer user_data)
 }
 
 static void
+_set_p2p_assigned_addr(NMSupplicantInterface *self, gconstpointer addr, guint8 plen)
+{
+    NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+
+    nm_ip_addr_set(AF_INET, &priv->p2p_assigned_addr, addr);
+    priv->p2p_assigned_plen = plen;
+}
+
+static void
 _signal_handle(NMSupplicantInterface *self,
                const char *           signal_interface_name,
                const char *           signal_name,
@@ -3036,6 +3048,7 @@ _signal_handle(NMSupplicantInterface *self,
                 gs_unref_object NMSupplicantInterface *iface = NULL;
                 const char *                           group_path;
                 const char *                           iface_path;
+                GVariant *                             v_v = NULL;
 
                 g_variant_get(parameters, "(@a{sv})", &args);
                 if (!g_variant_lookup(args, "group_object", "&o", &group_path))
@@ -3057,6 +3070,34 @@ _signal_handle(NMSupplicantInterface *self,
                     }
                 }
 
+                v_v = g_variant_lookup_value(args, "IpAddr", G_VARIANT_TYPE_BYTESTRING);
+                if (v_v) {
+                    const guint8 *addr_data;
+                    gsize         addr_len  = 0;
+                    const guint8 *mask_data = NULL;
+                    gsize         mask_len  = 0;
+
+                    /* The address is passed in network-byte-order */
+                    addr_data = g_variant_get_fixed_array(v_v, &addr_len, 1);
+
+                    /* TODO: Should we expose IpAddrGo? If yes, maybe as gateway? */
+                    v_v = g_variant_lookup_value(args, "IpAddrMask", G_VARIANT_TYPE_BYTESTRING);
+                    if (v_v)
+                        mask_data = g_variant_get_fixed_array(v_v, &mask_len, 1);
+
+                    if (addr_len == NM_AF_INET_SIZE && mask_len == NM_AF_INET_SIZE) {
+                        guint32 netmask;
+
+                        memcpy(&netmask, mask_data, NM_AF_INET_SIZE);
+
+                        _set_p2p_assigned_addr(iface,
+                                               addr_data,
+                                               nm_utils_ip4_netmask_to_prefix(netmask));
+                    } else {
+                        _LOGW("P2P: GroupStarted signaled invalid IP Address information");
+                    }
+                }
+
                 /* Signal existence of the (new) interface. */
                 g_signal_emit(self, signals[GROUP_STARTED], 0, iface);
             }
@@ -3137,6 +3178,24 @@ nm_supplicant_interface_get_p2p_group_owner(NMSupplicantInterface *self)
     return NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self)->p2p_group_owner_cached;
 }
 
+gboolean
+nm_supplicant_interface_get_p2p_assigned_addr(NMSupplicantInterface *self,
+                                              in_addr_t *            addr,
+                                              guint8 *               plen)
+{
+    NMSupplicantInterfacePrivate *priv = NM_SUPPLICANT_INTERFACE_GET_PRIVATE(self);
+
+    if (nm_ip_addr_is_null(AF_INET, &priv->p2p_assigned_addr))
+        return FALSE;
+
+    if (addr)
+        nm_ip_addr_set(AF_INET, addr, &priv->p2p_assigned_addr);
+    if (plen)
+        *plen = priv->p2p_assigned_plen;
+
+    return TRUE;
+}
+
 /*****************************************************************************/
 
 static void
diff --git a/src/core/supplicant/nm-supplicant-interface.h b/src/core/supplicant/nm-supplicant-interface.h
index a62eeb62..4c5d8f64 100644
--- a/src/core/supplicant/nm-supplicant-interface.h
+++ b/src/core/supplicant/nm-supplicant-interface.h
@@ -164,6 +164,10 @@ const char *nm_supplicant_interface_get_p2p_group_path(NMSupplicantInterface *se
 
 gboolean nm_supplicant_interface_get_p2p_group_owner(NMSupplicantInterface *self);
 
+gboolean nm_supplicant_interface_get_p2p_assigned_addr(NMSupplicantInterface *self,
+                                                       in_addr_t *            assigned_addr,
+                                                       guint8 *               plen);
+
 void nm_supplicant_interface_p2p_start_find(NMSupplicantInterface *self, guint timeout);
 void nm_supplicant_interface_p2p_stop_find(NMSupplicantInterface *self);
 
diff --git a/src/core/supplicant/nm-supplicant-manager.c b/src/core/supplicant/nm-supplicant-manager.c
index e432889b..52beae78 100644
--- a/src/core/supplicant/nm-supplicant-manager.c
+++ b/src/core/supplicant/nm-supplicant-manager.c
@@ -244,8 +244,7 @@ nm_supplicant_manager_set_wfd_ies(NMSupplicantManager *self, GBytes *wfd_ies)
 
     g_variant_builder_add(&params, "s", NM_WPAS_DBUS_INTERFACE);
     g_variant_builder_add(&params, "s", "WFDIEs");
-    g_variant_builder_add_value(&params,
-                                g_variant_new_variant(nm_utils_gbytes_to_variant_ay(wfd_ies)));
+    g_variant_builder_add_value(&params, g_variant_new_variant(nm_g_bytes_to_variant_ay(wfd_ies)));
 
     g_dbus_connection_call(priv->dbus_connection,
                            priv->name_owner->str,
diff --git a/src/core/supplicant/nm-supplicant-settings-verify.c b/src/core/supplicant/nm-supplicant-settings-verify.c
index 5e77b207..ceb4b6ae 100644
--- a/src/core/supplicant/nm-supplicant-settings-verify.c
+++ b/src/core/supplicant/nm-supplicant-settings-verify.c
@@ -223,7 +223,7 @@ validate_type_keyword(const struct Opt *opt, const char *value, const guint32 le
             s++;
         }
 
-        if (nm_utils_strv_find_first((char **) opt->str_allowed, -1, value) < 0)
+        if (nm_strv_find_first(opt->str_allowed, -1, value) < 0)
             return FALSE;
 
         if (!s)
diff --git a/src/core/supplicant/nm-supplicant-types.h b/src/core/supplicant/nm-supplicant-types.h
index c9b72de1..743ef8f0 100644
--- a/src/core/supplicant/nm-supplicant-types.h
+++ b/src/core/supplicant/nm-supplicant-types.h
@@ -14,9 +14,9 @@
 #define NM_WPAS_DBUS_INTERFACE "fi.w1.wpa_supplicant1"
 
 #if HAVE_WEXT
-    #define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211,wext"
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211,wext"
 #else
-    #define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211"
+#define NM_WPAS_DEFAULT_WIFI_DRIVER "nl80211"
 #endif
 
 #define NM_WPAS_DBUS_IFACE_INTERFACE            NM_WPAS_DBUS_INTERFACE ".Interface"
diff --git a/src/core/tests/test-core.c b/src/core/tests/test-core.c
index f28c4178..3524cb17 100644
--- a/src/core/tests/test-core.c
+++ b/src/core/tests/test-core.c
@@ -1728,86 +1728,86 @@ test_nm_utils_strbuf_append(void)
     char buf[NM_STRLEN(BUF_ORIG) + 1];
     char str[NM_STRLEN(BUF_ORIG) + 1];
 
-#define _strbuf_append(buf, len, format, ...)                           \
-    G_STMT_START                                                        \
-    {                                                                   \
-        char **       _buf = (buf);                                     \
-        gsize *       _len = (len);                                     \
-        const char *  _str_iter;                                        \
-        gs_free char *_str = NULL;                                      \
-                                                                        \
-        switch (nmtst_get_rand_uint32() % 4) {                          \
-        case 0:                                                         \
-            nm_utils_strbuf_append(_buf, _len, (format), __VA_ARGS__);  \
-            break;                                                      \
-        case 1:                                                         \
-            _str = g_strdup_printf((format), __VA_ARGS__);              \
-            nm_utils_strbuf_append_str(_buf, _len, _str);               \
-            break;                                                      \
-        case 2:                                                         \
-            _str = g_strdup_printf((format), __VA_ARGS__);              \
-            nm_utils_strbuf_append_bin(_buf, _len, _str, strlen(_str)); \
-            break;                                                      \
-        case 3:                                                         \
-            _str = g_strdup_printf((format), __VA_ARGS__);              \
-            if (!_str[0])                                               \
-                nm_utils_strbuf_append_str(_buf, _len, _str);           \
-            for (_str_iter = _str; _str_iter[0]; _str_iter++)           \
-                nm_utils_strbuf_append_c(_buf, _len, _str_iter[0]);     \
-            break;                                                      \
-        }                                                               \
-    }                                                                   \
+#define _strbuf_append(buf, len, format, ...)                     \
+    G_STMT_START                                                  \
+    {                                                             \
+        char **       _buf = (buf);                               \
+        gsize *       _len = (len);                               \
+        const char *  _str_iter;                                  \
+        gs_free char *_str = NULL;                                \
+                                                                  \
+        switch (nmtst_get_rand_uint32() % 4) {                    \
+        case 0:                                                   \
+            nm_strbuf_append(_buf, _len, (format), __VA_ARGS__);  \
+            break;                                                \
+        case 1:                                                   \
+            _str = g_strdup_printf((format), __VA_ARGS__);        \
+            nm_strbuf_append_str(_buf, _len, _str);               \
+            break;                                                \
+        case 2:                                                   \
+            _str = g_strdup_printf((format), __VA_ARGS__);        \
+            nm_strbuf_append_bin(_buf, _len, _str, strlen(_str)); \
+            break;                                                \
+        case 3:                                                   \
+            _str = g_strdup_printf((format), __VA_ARGS__);        \
+            if (!_str[0])                                         \
+                nm_strbuf_append_str(_buf, _len, _str);           \
+            for (_str_iter = _str; _str_iter[0]; _str_iter++)     \
+                nm_strbuf_append_c(_buf, _len, _str_iter[0]);     \
+            break;                                                \
+        }                                                         \
+    }                                                             \
     G_STMT_END
 
-#define _strbuf_append_str(buf, len, str)                                          \
-    G_STMT_START                                                                   \
-    {                                                                              \
-        char **     _buf = (buf);                                                  \
-        gsize *     _len = (len);                                                  \
-        const char *_str = (str);                                                  \
-                                                                                   \
-        switch (nmtst_get_rand_uint32() % 4) {                                     \
-        case 0:                                                                    \
-            nm_utils_strbuf_append(_buf, _len, "%s", _str ?: "");                  \
-            break;                                                                 \
-        case 1:                                                                    \
-            nm_utils_strbuf_append_str(_buf, _len, _str);                          \
-            break;                                                                 \
-        case 2:                                                                    \
-            nm_utils_strbuf_append_bin(_buf, _len, _str, _str ? strlen(_str) : 0); \
-            break;                                                                 \
-        case 3:                                                                    \
-            if (!_str || !_str[0])                                                 \
-                nm_utils_strbuf_append_str(_buf, _len, _str);                      \
-            for (; _str && _str[0]; _str++)                                        \
-                nm_utils_strbuf_append_c(_buf, _len, _str[0]);                     \
-            break;                                                                 \
-        }                                                                          \
-    }                                                                              \
+#define _strbuf_append_str(buf, len, str)                                    \
+    G_STMT_START                                                             \
+    {                                                                        \
+        char **     _buf = (buf);                                            \
+        gsize *     _len = (len);                                            \
+        const char *_str = (str);                                            \
+                                                                             \
+        switch (nmtst_get_rand_uint32() % 4) {                               \
+        case 0:                                                              \
+            nm_strbuf_append(_buf, _len, "%s", _str ?: "");                  \
+            break;                                                           \
+        case 1:                                                              \
+            nm_strbuf_append_str(_buf, _len, _str);                          \
+            break;                                                           \
+        case 2:                                                              \
+            nm_strbuf_append_bin(_buf, _len, _str, _str ? strlen(_str) : 0); \
+            break;                                                           \
+        case 3:                                                              \
+            if (!_str || !_str[0])                                           \
+                nm_strbuf_append_str(_buf, _len, _str);                      \
+            for (; _str && _str[0]; _str++)                                  \
+                nm_strbuf_append_c(_buf, _len, _str[0]);                     \
+            break;                                                           \
+        }                                                                    \
+    }                                                                        \
     G_STMT_END
 
-#define _strbuf_append_c(buf, len, ch)                                   \
-    G_STMT_START                                                         \
-    {                                                                    \
-        char **_buf = (buf);                                             \
-        gsize *_len = (len);                                             \
-        char   _ch  = (ch);                                              \
-                                                                         \
-        switch (nmtst_get_rand_uint32() % 4) {                           \
-        case 0:                                                          \
-            nm_utils_strbuf_append(_buf, _len, "%c", _ch);               \
-            break;                                                       \
-        case 1:                                                          \
-            nm_utils_strbuf_append_str(_buf, _len, ((char[2]){_ch, 0})); \
-            break;                                                       \
-        case 2:                                                          \
-            nm_utils_strbuf_append_bin(_buf, _len, &_ch, 1);             \
-            break;                                                       \
-        case 3:                                                          \
-            nm_utils_strbuf_append_c(_buf, _len, _ch);                   \
-            break;                                                       \
-        }                                                                \
-    }                                                                    \
+#define _strbuf_append_c(buf, len, ch)                             \
+    G_STMT_START                                                   \
+    {                                                              \
+        char **_buf = (buf);                                       \
+        gsize *_len = (len);                                       \
+        char   _ch  = (ch);                                        \
+                                                                   \
+        switch (nmtst_get_rand_uint32() % 4) {                     \
+        case 0:                                                    \
+            nm_strbuf_append(_buf, _len, "%c", _ch);               \
+            break;                                                 \
+        case 1:                                                    \
+            nm_strbuf_append_str(_buf, _len, ((char[2]){_ch, 0})); \
+            break;                                                 \
+        case 2:                                                    \
+            nm_strbuf_append_bin(_buf, _len, &_ch, 1);             \
+            break;                                                 \
+        case 3:                                                    \
+            nm_strbuf_append_c(_buf, _len, _ch);                   \
+            break;                                                 \
+        }                                                          \
+    }                                                              \
     G_STMT_END
 
     for (buf_len = 0; buf_len < 10; buf_len++) {
@@ -1854,28 +1854,28 @@ test_nm_utils_strbuf_append(void)
                 if (t_len > 0 && strlen(str) >= buf_len && (nmtst_get_rand_uint32() % 2)) {
                     /* the string was truncated by g_snprintf(). That means, at the last position in the
                      * buffer is now NUL.
-                     * Replace the NUL by the actual character, and check that nm_utils_strbuf_seek_end()
+                     * Replace the NUL by the actual character, and check that nm_strbuf_seek_end()
                      * does the right thing: NUL terminate the buffer and seek past the end of the buffer. */
                     g_assert_cmpmem(t_buf, t_len - 1, str, t_len - 1);
                     g_assert(t_buf[t_len - 1] == '\0');
                     g_assert(str[t_len - 1] != '\0');
                     t_buf[t_len - 1] = str[t_len - 1];
-                    nm_utils_strbuf_seek_end(&t_buf, &t_len);
+                    nm_strbuf_seek_end(&t_buf, &t_len);
                     g_assert(t_len == 0);
                     g_assert(t_buf == &buf[buf_len]);
                     g_assert(t_buf[-1] == '\0');
                 } else {
-                    nm_utils_strbuf_seek_end(&t_buf, &t_len);
+                    nm_strbuf_seek_end(&t_buf, &t_len);
                     if (buf_len > 0 && strlen(str) + 1 > buf_len) {
                         /* the buffer was truncated by g_snprintf() above.
                          *
-                         * But nm_utils_strbuf_seek_end() does not recognize that and returns
+                         * But nm_strbuf_seek_end() does not recognize that and returns
                          * a remaining length of 1.
                          *
-                         * Note that other nm_utils_strbuf_append*() functions recognize
+                         * Note that other nm_strbuf_append*() functions recognize
                          * truncation, and properly set the remaining length to zero.
-                         * As the assertions below check for the behavior of nm_utils_strbuf_append*(),
-                         * we assert here that nm_utils_strbuf_seek_end() behaved as expected, and then
+                         * As the assertions below check for the behavior of nm_strbuf_append*(),
+                         * we assert here that nm_strbuf_seek_end() behaved as expected, and then
                          * adjust t_buf/t_len according to the "is-truncated" case. */
                         g_assert(t_len == 1);
                         g_assert(t_buf == &buf[buf_len - 1]);
@@ -2594,7 +2594,7 @@ main(int argc, char **argv)
     g_test_add_func("/general/test_logging_domains", test_logging_domains);
     g_test_add_func("/general/test_logging_error", test_logging_error);
 
-    g_test_add_func("/general/nm_utils_strbuf_append", test_nm_utils_strbuf_append);
+    g_test_add_func("/general/nm_strbuf_append", test_nm_utils_strbuf_append);
 
     g_test_add_func("/general/nm_utils_ip6_address_clear_host_address",
                     test_nm_utils_ip6_address_clear_host_address);
diff --git a/src/core/tests/test-ip6-config.c b/src/core/tests/test-ip6-config.c
index ddf4c789..2e6d8aaa 100644
--- a/src/core/tests/test-ip6-config.c
+++ b/src/core/tests/test-ip6-config.c
@@ -327,7 +327,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("2607:f0d0:1002:51::8",
              NULL,
              64,
@@ -336,7 +336,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("2607:f0d0:1002:51::0",
              NULL,
              64,
@@ -345,7 +345,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("fec0::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
     ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
     ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0);
@@ -374,7 +374,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0);
     ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0);
     ADDR_ADD("2607:f0d0:1002:51::8",
@@ -385,7 +385,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("2607:f0d0:1002:51::0",
              NULL,
              64,
@@ -394,7 +394,7 @@ test_nm_ip6_config_addresses_sort(void)
              0,
              0,
              0,
-             IFA_F_TEMPORARY);
+             IFA_F_SECONDARY);
     ADDR_ADD("2607:f0d0:1002:51::6",
              NULL,
              64,
diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c
index ba065c09..100c2496 100644
--- a/src/core/tests/test-l3cfg.c
+++ b/src/core/tests/test-l3cfg.c
@@ -2,8 +2,11 @@
 
 #include "src/core/nm-default-daemon.h"
 
+#include <linux/if_addr.h>
+
 #include "nm-l3cfg.h"
 #include "nm-l3-ipv4ll.h"
+#include "nm-l3-ipv6ll.h"
 #include "nm-netns.h"
 #include "libnm-platform/nm-platform.h"
 
@@ -132,6 +135,7 @@ typedef struct {
     NML3AcdDefendType acd_defend_type_a;
 
     TestL3cfgNotifyType notify_type;
+    guint               pre_commit_event_count;
     guint               post_commit_event_count;
     guint               general_event_count;
     guint               general_event_flags;
@@ -151,6 +155,7 @@ _test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notif
     g_assert(tdata);
 
     tdata->notify_type             = notify_type;
+    tdata->pre_commit_event_count  = 0;
     tdata->post_commit_event_count = 0;
     tdata->general_event_count     = 0;
     tdata->general_event_flags     = 0;
@@ -209,6 +214,10 @@ _test_l3cfg_signal_notify(NML3Cfg *                   l3cfg,
     case TEST_L3CFG_NOTIFY_TYPE_COMMIT_1:
         g_assert_cmpint(tdata->post_commit_event_count, ==, 0);
         switch (notify_data->notify_type) {
+        case NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT:
+            g_assert_cmpint(tdata->pre_commit_event_count, ==, 0);
+            tdata->pre_commit_event_count++;
+            return;
         case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
             tdata->post_commit_event_count++;
             return;
@@ -255,6 +264,7 @@ _test_l3cfg_signal_notify(NML3Cfg *                   l3cfg,
             1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0);
 
         if (NM_IN_SET(notify_data->notify_type,
+                      NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT,
                       NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
                       NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
             return;
@@ -368,7 +378,8 @@ test_l3cfg(gconstpointer test_data)
 
     g_signal_connect(l3cfg0, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_test_l3cfg_signal_notify), tdata);
 
-    commit_type_1 = nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL);
+    commit_type_1 =
+        nm_l3cfg_commit_type_register(l3cfg0, NM_L3_CFG_COMMIT_TYPE_UPDATE, NULL, "test1");
 
     if (!nmtst_get_rand_one_case_in(4)) {
         commit_type_2 =
@@ -376,7 +387,8 @@ test_l3cfg(gconstpointer test_data)
                                           nmtst_rand_select(NM_L3_CFG_COMMIT_TYPE_NONE,
                                                             NM_L3_CFG_COMMIT_TYPE_ASSUME,
                                                             NM_L3_CFG_COMMIT_TYPE_UPDATE),
-                                          NULL);
+                                          NULL,
+                                          "test2");
     } else
         commit_type_2 = NULL;
 
@@ -389,7 +401,7 @@ test_l3cfg(gconstpointer test_data)
     {
         nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
 
-        l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0);
+        l3cd = nm_l3_config_data_new(f->multiidx, f->ifindex0, NM_IP_CONFIG_SOURCE_UNKNOWN);
 
         nm_l3_config_data_add_address_4(
             l3cd,
@@ -432,8 +444,11 @@ test_l3cfg(gconstpointer test_data)
                             NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
                             0,
                             0,
+                            NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                            NM_DNS_PRIORITY_DEFAULT_NORMAL,
                             tdata->acd_defend_type_a,
                             tdata->acd_timeout_msec_a,
+                            NM_L3CFG_CONFIG_FLAGS_NONE,
                             NM_L3_CONFIG_MERGE_FLAGS_NONE);
     }
 
@@ -445,6 +460,7 @@ test_l3cfg(gconstpointer test_data)
 
     _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_COMMIT_1);
     nm_l3cfg_commit(l3cfg0, NM_L3_CFG_COMMIT_TYPE_REAPPLY);
+    g_assert_cmpint(tdata->pre_commit_event_count, ==, 1);
     g_assert_cmpint(tdata->post_commit_event_count, ==, 1);
     _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
 
@@ -490,7 +506,7 @@ test_l3cfg(gconstpointer test_data)
     if (nmtst_get_rand_one_case_in(3))
         _test_fixture_1_teardown(&test_fixture);
 
-    nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'), FALSE);
+    nm_l3cfg_remove_config_all(l3cfg0, GINT_TO_POINTER('a'));
 
     if (nmtst_get_rand_one_case_in(3))
         _test_fixture_1_teardown(&test_fixture);
@@ -593,16 +609,21 @@ _test_l3_ipv4ll_signal_notify(NML3Cfg *                   l3cfg,
                                          105,
                                          0,
                                          0,
+                                         NM_DNS_PRIORITY_DEFAULT_NORMAL,
+                                         NM_DNS_PRIORITY_DEFAULT_NORMAL,
                                          NM_L3_ACD_DEFEND_TYPE_ONCE,
                                          nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u,
+                                         NM_L3CFG_CONFIG_FLAGS_NONE,
                                          NM_L3_CONFIG_MERGE_FLAGS_NONE))
                     g_assert_not_reached();
-                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                                 NM_L3_CFG_COMMIT_TYPE_AUTO);
 
                 tdata->l3cfg_commit_type_1 =
                     nm_l3cfg_commit_type_register(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
                                                   NM_L3_CFG_COMMIT_TYPE_UPDATE,
-                                                  tdata->l3cfg_commit_type_1);
+                                                  tdata->l3cfg_commit_type_1,
+                                                  "test");
             }
         } else if (nm_l3_ipv4ll_get_state(tdata->l3ipv4ll) != NM_L3_IPV4LL_STATE_DEFENDING
                    && tdata->ready_seen > 0) {
@@ -615,10 +636,10 @@ _test_l3_ipv4ll_signal_notify(NML3Cfg *                   l3cfg,
                 _LOGT("remove address %s that previously passed ACD",
                       _nm_utils_inet4_ntop(tdata->addr_commit_addr, sbuf_addr));
                 if (!nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
-                                                TEST_L3_IPV4LL_TAG(tdata, 1),
-                                                FALSE))
+                                                TEST_L3_IPV4LL_TAG(tdata, 1)))
                     g_assert_not_reached();
-                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll));
+                nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
+                                                 NM_L3_CFG_COMMIT_TYPE_AUTO);
                 nm_l3cfg_commit_type_unregister(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll),
                                                 g_steal_pointer(&tdata->l3cfg_commit_type_1));
             }
@@ -766,9 +787,7 @@ test_l3_ipv4ll(gconstpointer test_data)
     _LOGT("poll 1 end");
 
     if (tdata->addr_commit || nmtst_get_rand_bool()) {
-        nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll),
-                                   TEST_L3_IPV4LL_TAG(tdata, 1),
-                                   FALSE);
+        nm_l3cfg_remove_config_all(nm_l3_ipv4ll_get_l3cfg(l3ipv4ll), TEST_L3_IPV4LL_TAG(tdata, 1));
     }
 
     nmtstp_acd_defender_destroy(g_steal_pointer(&acd_defender_1));
@@ -781,6 +800,276 @@ test_l3_ipv4ll(gconstpointer test_data)
 
 /*****************************************************************************/
 
+#define _LLADDR_TEST1 "fe80::dd5a:8a44:48bc:3ad"
+#define _LLADDR_TEST2 "fe80::878b:938e:46f9:4807"
+
+typedef struct {
+    const TestFixture1 *f;
+    NML3Cfg *           l3cfg0;
+    NML3IPv6LL *        l3ipv6ll;
+    int                 step;
+    int                 ipv6ll_callback_step;
+    bool                steps_done : 1;
+    const NMPObject *   lladdr0;
+} TestL3IPv6LLData;
+
+static const NMPlatformIP6Address *
+_test_l3_ipv6ll_find_lladdr(TestL3IPv6LLData *tdata, int ifindex)
+{
+    const NMPlatformIP6Address *found = NULL;
+    NMDedupMultiIter            iter;
+    const NMPObject *           obj;
+    NMPLookup                   lookup;
+
+    g_assert(tdata);
+
+    nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex);
+    nm_platform_iter_obj_for_each (&iter, tdata->f->platform, &lookup, &obj) {
+        const NMPlatformIP6Address *a = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
+
+        if (!IN6_IS_ADDR_LINKLOCAL(&a->address))
+            continue;
+
+        if (!found)
+            found = a;
+        else
+            g_assert_not_reached();
+    }
+
+    return found;
+}
+
+static const NMPObject *
+_test_l3_ipv6ll_find_lladdr_wait(TestL3IPv6LLData *tdata, int ifindex)
+{
+    const NMPObject *obj = NULL;
+
+    nmtst_main_context_iterate_until_assert(NULL, 3000, ({
+                                                const NMPlatformIP6Address *a;
+
+                                                a = _test_l3_ipv6ll_find_lladdr(tdata, ifindex);
+                                                if (a
+                                                    && !NM_FLAGS_HAS(a->n_ifa_flags,
+                                                                     IFA_F_TENTATIVE))
+                                                    obj = NMP_OBJECT_UP_CAST(a);
+                                                obj;
+                                            }));
+
+    return obj;
+}
+
+static const NMPlatformIP6Address *
+_test_l3_ipv6ll_find_inet6(TestL3IPv6LLData *tdata, const struct in6_addr *addr)
+{
+    const NMPlatformIP6Address *a;
+
+    a = nmtstp_platform_ip6_address_find(nm_l3cfg_get_platform(tdata->l3cfg0),
+                                         nmtst_get_rand_bool() ? 0 : tdata->f->ifindex0,
+                                         addr);
+    if (a) {
+        g_assert_cmpint(a->ifindex, ==, tdata->f->ifindex0);
+        g_assert_cmpmem(addr, sizeof(*addr), &a->address, sizeof(a->address));
+    }
+
+    g_assert(a
+             == nm_platform_ip6_address_get(nm_l3cfg_get_platform(tdata->l3cfg0),
+                                            tdata->f->ifindex0,
+                                            addr));
+
+    return a;
+}
+
+static void
+_test_l3_ipv6ll_signal_notify(NML3Cfg *                   l3cfg,
+                              const NML3ConfigNotifyData *notify_data,
+                              TestL3IPv6LLData *          tdata)
+{
+    g_assert_cmpint(tdata->step, >=, 1);
+    g_assert_cmpint(tdata->step, <=, 2);
+}
+
+static void
+_test_l3_ipv6ll_callback_changed(NML3IPv6LL *           ipv6ll,
+                                 NML3IPv6LLState        state,
+                                 const struct in6_addr *lladdr,
+                                 gpointer               user_data)
+{
+    TestL3IPv6LLData *          tdata = user_data;
+    int                         step  = tdata->ipv6ll_callback_step++;
+    const NMPlatformIP6Address *a1;
+
+    g_assert_cmpint(tdata->step, ==, 1);
+    g_assert(!tdata->steps_done);
+
+    switch (step) {
+    case 0:
+        if (NM_IN_SET(tdata->f->test_idx, 1, 2, 4)) {
+            g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS);
+            g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1);
+        } else if (NM_IN_SET(tdata->f->test_idx, 3)) {
+            g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
+            g_assert(
+                IN6_ARE_ADDR_EQUAL(lladdr, &NMP_OBJECT_CAST_IP6_ADDRESS(tdata->lladdr0)->address));
+            tdata->steps_done = TRUE;
+        } else
+            g_assert_not_reached();
+        break;
+    case 1:
+        if (NM_IN_SET(tdata->f->test_idx, 1, 2)) {
+            g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
+            g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1);
+            a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr);
+            g_assert(a1);
+            g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE));
+            tdata->steps_done = TRUE;
+        } else if (NM_IN_SET(tdata->f->test_idx, 4)) {
+            g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS);
+            g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2);
+        } else
+            g_assert_not_reached();
+        break;
+    case 2:
+        if (NM_IN_SET(tdata->f->test_idx, 4)) {
+            g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY);
+            g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2);
+            a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr);
+            g_assert(a1);
+            g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE));
+            tdata->steps_done = TRUE;
+        } else
+            g_assert_not_reached();
+        break;
+    default:
+        g_assert_not_reached();
+    }
+}
+
+static void
+test_l3_ipv6ll(gconstpointer test_data)
+{
+    NMTST_UTILS_HOST_ID_CONTEXT("l3-ipv6ll");
+    const int                                      TEST_IDX     = GPOINTER_TO_INT(test_data);
+    nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
+    gs_unref_object NML3Cfg *l3cfg0                             = NULL;
+    TestL3IPv6LLData         tdata_stack                        = {
+        .step       = 0,
+        .steps_done = FALSE,
+    };
+    TestL3IPv6LLData *const tdata = &tdata_stack;
+    char                    sbuf1[sizeof(_nm_utils_to_string_buffer)];
+    int                     r;
+
+    _LOGD("test start (/l3-ipv6ll/%d)", TEST_IDX);
+
+    if (nmtst_test_quick()) {
+        gs_free char *msg =
+            g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n",
+                            g_get_prgname() ?: "test-l3-ipv6ll");
+
+        g_test_skip(msg);
+        return;
+    }
+
+    tdata->f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
+
+    if (NM_IN_SET(tdata->f->test_idx, 4)) {
+        _LOGD("add conflicting IPv6LL on other interface...");
+        r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, FALSE);
+        g_assert_cmpint(r, >=, 0);
+
+        r = nm_platform_link_set_inet6_addr_gen_mode(tdata->f->platform,
+                                                     tdata->f->ifindex1,
+                                                     NM_IN6_ADDR_GEN_MODE_NONE);
+        g_assert_cmpint(r, >=, 0);
+
+        r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, TRUE);
+        g_assert_cmpint(r, >=, 0);
+
+        nmtstp_ip6_address_add(tdata->f->platform,
+                               -1,
+                               tdata->f->ifindex1,
+                               *nmtst_inet6_from_string(_LLADDR_TEST1),
+                               64,
+                               in6addr_any,
+                               NM_PLATFORM_LIFETIME_PERMANENT,
+                               NM_PLATFORM_LIFETIME_PERMANENT,
+                               0);
+
+        _LOGD("wait for IPv6 LL address...");
+        tdata->lladdr0 =
+            nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex1));
+    } else if (NM_IN_SET(tdata->f->test_idx, 2, 3)) {
+        _LOGD("wait for IPv6 LL address...");
+        tdata->lladdr0 =
+            nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex0));
+    }
+
+    if (tdata->lladdr0) {
+        _LOGD("got IPv6 LL address %s",
+              nmp_object_to_string(tdata->lladdr0,
+                                   NMP_OBJECT_TO_STRING_PUBLIC,
+                                   sbuf1,
+                                   sizeof(sbuf1)));
+    }
+
+    l3cfg0        = _netns_access_l3cfg(tdata->f->netns, tdata->f->ifindex0);
+    tdata->l3cfg0 = l3cfg0;
+
+    g_signal_connect(tdata->l3cfg0,
+                     NM_L3CFG_SIGNAL_NOTIFY,
+                     G_CALLBACK(_test_l3_ipv6ll_signal_notify),
+                     tdata);
+
+    tdata->l3ipv6ll = nm_l3_ipv6ll_new_stable_privacy(tdata->l3cfg0,
+                                                      NM_IN_SET(tdata->f->test_idx, 3),
+                                                      NM_UTILS_STABLE_TYPE_UUID,
+                                                      tdata->f->ifname0,
+                                                      "b6a5b934-c649-43dc-a524-3dfdb74f9419",
+                                                      _test_l3_ipv6ll_callback_changed,
+                                                      tdata);
+
+    g_assert(nm_l3_ipv6ll_get_l3cfg(tdata->l3ipv6ll) == tdata->l3cfg0);
+    g_assert_cmpint(nm_l3_ipv6ll_get_ifindex(tdata->l3ipv6ll), ==, tdata->f->ifindex0);
+
+    tdata->step = 1;
+    nmtst_main_context_iterate_until_assert(NULL, 7000, tdata->steps_done);
+
+    g_assert_cmpint(tdata->step, ==, 1);
+    if (NM_IN_SET(tdata->f->test_idx, 3))
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1);
+    else if (NM_IN_SET(tdata->f->test_idx, 4))
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3);
+    else
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2);
+    g_assert(tdata->steps_done);
+
+    tdata->step = 2;
+    nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 1000, FALSE);
+
+    g_assert_cmpint(tdata->step, ==, 2);
+    if (NM_IN_SET(tdata->f->test_idx, 3))
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1);
+    else if (NM_IN_SET(tdata->f->test_idx, 4))
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3);
+    else
+        g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2);
+    g_assert(tdata->steps_done);
+    g_assert(tdata->steps_done);
+
+    tdata->step       = 0;
+    tdata->steps_done = FALSE;
+
+    g_signal_handlers_disconnect_by_func(tdata->l3cfg0,
+                                         G_CALLBACK(_test_l3_ipv6ll_signal_notify),
+                                         tdata);
+
+    nm_l3_ipv6ll_destroy(tdata->l3ipv6ll);
+
+    nm_clear_nmp_object(&tdata->lladdr0);
+}
+
+/*****************************************************************************/
+
 NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup;
 
 void
@@ -798,4 +1087,8 @@ _nmtstp_setup_tests(void)
     g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg);
     g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll);
     g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll);
+    g_test_add_data_func("/l3-ipv6ll/1", GINT_TO_POINTER(1), test_l3_ipv6ll);
+    g_test_add_data_func("/l3-ipv6ll/2", GINT_TO_POINTER(2), test_l3_ipv6ll);
+    g_test_add_data_func("/l3-ipv6ll/3", GINT_TO_POINTER(3), test_l3_ipv6ll);
+    g_test_add_data_func("/l3-ipv6ll/4", GINT_TO_POINTER(4), test_l3_ipv6ll);
 }
diff --git a/src/core/tests/test-systemd.c b/src/core/tests/test-systemd.c
index 12d2b911..be070248 100644
--- a/src/core/tests/test-systemd.c
+++ b/src/core/tests/test-systemd.c
@@ -36,14 +36,14 @@ test_dhcp_create(void)
 static void
 test_lldp_create(void)
 {
-    sd_lldp *lldp = NULL;
-    int      r;
+    sd_lldp_rx *lldp = NULL;
+    int         r;
 
-    r = sd_lldp_new(&lldp);
+    r = sd_lldp_rx_new(&lldp);
     g_assert(r == 0);
     g_assert(lldp);
 
-    sd_lldp_unref(lldp);
+    sd_lldp_rx_unref(lldp);
 }
 
 /*****************************************************************************/
diff --git a/src/core/tests/test-utils.c b/src/core/tests/test-utils.c
index 1d562bde..59d5fd53 100644
--- a/src/core/tests/test-utils.c
+++ b/src/core/tests/test-utils.c
@@ -16,60 +16,55 @@ test_stable_privacy(void)
     struct in6_addr addr1;
 
     inet_pton(AF_INET6, "1234::", &addr1);
-    nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID,
-                                               &addr1,
-                                               "eth666",
-                                               "6b138152-9f3e-4b97-aaf7-e6e553f2a24e",
-                                               0,
-                                               (guint8 *) "key",
-                                               3,
-                                               NULL);
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NM_UTILS_STABLE_TYPE_UUID,
+                                                       &addr1,
+                                                       "eth666",
+                                                       "6b138152-9f3e-4b97-aaf7-e6e553f2a24e",
+                                                       0,
+                                                       (guint8 *) "key",
+                                                       3);
     nmtst_assert_ip6_address(&addr1, "1234::4ceb:14cd:3d54:793f");
 
     /* We get an address without the UUID. */
     inet_pton(AF_INET6, "1::", &addr1);
-    nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID,
-                                               &addr1,
-                                               "eth666",
-                                               "",
-                                               384,
-                                               (guint8 *) "key",
-                                               3,
-                                               NULL);
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NM_UTILS_STABLE_TYPE_UUID,
+                                                       &addr1,
+                                                       "eth666",
+                                                       "",
+                                                       384,
+                                                       (guint8 *) "key",
+                                                       3);
     nmtst_assert_ip6_address(&addr1, "1::11aa:2530:9144:dafa");
 
     /* We get a different address in a different network. */
     inet_pton(AF_INET6, "2::", &addr1);
-    nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_UUID,
-                                               &addr1,
-                                               "eth666",
-                                               "",
-                                               384,
-                                               (guint8 *) "key",
-                                               3,
-                                               NULL);
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NM_UTILS_STABLE_TYPE_UUID,
+                                                       &addr1,
+                                                       "eth666",
+                                                       "",
+                                                       384,
+                                                       (guint8 *) "key",
+                                                       3);
     nmtst_assert_ip6_address(&addr1, "2::338e:8d:c11:8726");
 
     inet_pton(AF_INET6, "1234::", &addr1);
-    nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_STABLE_ID,
-                                               &addr1,
-                                               "eth666",
-                                               "6b138152-9f3e-4b97-aaf7-e6e553f2a24e",
-                                               0,
-                                               (guint8 *) "key",
-                                               3,
-                                               NULL);
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NM_UTILS_STABLE_TYPE_STABLE_ID,
+                                                       &addr1,
+                                                       "eth666",
+                                                       "6b138152-9f3e-4b97-aaf7-e6e553f2a24e",
+                                                       0,
+                                                       (guint8 *) "key",
+                                                       3);
     nmtst_assert_ip6_address(&addr1, "1234::ad4c:ae44:3d30:af1e");
 
     inet_pton(AF_INET6, "1234::", &addr1);
-    nm_utils_ipv6_addr_set_stable_privacy_impl(NM_UTILS_STABLE_TYPE_STABLE_ID,
-                                               &addr1,
-                                               "eth666",
-                                               "stable-id-1",
-                                               0,
-                                               (guint8 *) "key",
-                                               3,
-                                               NULL);
+    nm_utils_ipv6_addr_set_stable_privacy_with_host_id(NM_UTILS_STABLE_TYPE_STABLE_ID,
+                                                       &addr1,
+                                                       "eth666",
+                                                       "stable-id-1",
+                                                       0,
+                                                       (guint8 *) "key",
+                                                       3);
     nmtst_assert_ip6_address(&addr1, "1234::4944:67b0:7a6c:1cf");
 }
 
diff --git a/src/core/vpn/nm-vpn-connection.c b/src/core/vpn/nm-vpn-connection.c
index d06ca46a..d2034278 100644
--- a/src/core/vpn/nm-vpn-connection.c
+++ b/src/core/vpn/nm-vpn-connection.c
@@ -1659,6 +1659,7 @@ nm_vpn_connection_ip4_config_get(NMVpnConnection *self, GVariant *dict)
                                 s_ip,
                                 nm_setting_connection_get_mdns(s_con),
                                 nm_setting_connection_get_llmnr(s_con),
+                                nm_setting_connection_get_dns_over_tls(s_con),
                                 route_table,
                                 route_metric);