summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
committerMichael Biebl <biebl@debian.org>2024-01-25 09:46:18 +0100
commit70e18d99b8e3e77bb37e218d7ac582130156f8ef (patch)
treed40c587e6d3f0e094ff558e415f1bb9803643214 /src/libnm-core-impl
parentd4d8b2b91f7ba000d97a8b2aab48c85000c11314 (diff)
New upstream version 1.45.90 upstream/1.45.90
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in50
-rw-r--r--src/libnm-core-impl/meson.build2
-rw-r--r--src/libnm-core-impl/nm-connection-private.h2
-rw-r--r--src/libnm-core-impl/nm-connection.c75
-rw-r--r--src/libnm-core-impl/nm-keyfile.c11
-rw-r--r--src/libnm-core-impl/nm-meta-setting-base-impl.c9
-rw-r--r--src/libnm-core-impl/nm-property-compare.c136
-rw-r--r--src/libnm-core-impl/nm-property-compare.h16
-rw-r--r--src/libnm-core-impl/nm-setting-6lowpan.c16
-rw-r--r--src/libnm-core-impl/nm-setting-8021x.c118
-rw-r--r--src/libnm-core-impl/nm-setting-adsl.c24
-rw-r--r--src/libnm-core-impl/nm-setting-bluetooth.c15
-rw-r--r--src/libnm-core-impl/nm-setting-bond-port.c4
-rw-r--r--src/libnm-core-impl/nm-setting-bond.c12
-rw-r--r--src/libnm-core-impl/nm-setting-bridge-port.c16
-rw-r--r--src/libnm-core-impl/nm-setting-bridge.c8
-rw-r--r--src/libnm-core-impl/nm-setting-cdma.c21
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c470
-rw-r--r--src/libnm-core-impl/nm-setting-dcb.c16
-rw-r--r--src/libnm-core-impl/nm-setting-dummy.c3
-rw-r--r--src/libnm-core-impl/nm-setting-ethtool.c31
-rw-r--r--src/libnm-core-impl/nm-setting-generic.c12
-rw-r--r--src/libnm-core-impl/nm-setting-gsm.c47
-rw-r--r--src/libnm-core-impl/nm-setting-hsr.c266
-rw-r--r--src/libnm-core-impl/nm-setting-infiniband.c18
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c423
-rw-r--r--src/libnm-core-impl/nm-setting-ip-tunnel.c27
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c116
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c85
-rw-r--r--src/libnm-core-impl/nm-setting-loopback.c8
-rw-r--r--src/libnm-core-impl/nm-setting-macsec.c25
-rw-r--r--src/libnm-core-impl/nm-setting-macvlan.c15
-rw-r--r--src/libnm-core-impl/nm-setting-match.c118
-rw-r--r--src/libnm-core-impl/nm-setting-olpc-mesh.c12
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-bridge.c8
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-dpdk.c3
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-interface.c11
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-patch.c3
-rw-r--r--src/libnm-core-impl/nm-setting-ovs-port.c19
-rw-r--r--src/libnm-core-impl/nm-setting-ppp.c13
-rw-r--r--src/libnm-core-impl/nm-setting-pppoe.c24
-rw-r--r--src/libnm-core-impl/nm-setting-private.h349
-rw-r--r--src/libnm-core-impl/nm-setting-proxy.c17
-rw-r--r--src/libnm-core-impl/nm-setting-serial.c12
-rw-r--r--src/libnm-core-impl/nm-setting-team-port.c16
-rw-r--r--src/libnm-core-impl/nm-setting-team.c15
-rw-r--r--src/libnm-core-impl/nm-setting-tun.c19
-rw-r--r--src/libnm-core-impl/nm-setting-veth.c3
-rw-r--r--src/libnm-core-impl/nm-setting-vlan.c44
-rw-r--r--src/libnm-core-impl/nm-setting-vpn.c19
-rw-r--r--src/libnm-core-impl/nm-setting-vxlan.c21
-rw-r--r--src/libnm-core-impl/nm-setting-wifi-p2p.c3
-rw-r--r--src/libnm-core-impl/nm-setting-wimax.c15
-rw-r--r--src/libnm-core-impl/nm-setting-wired.c60
-rw-r--r--src/libnm-core-impl/nm-setting-wireguard.c7
-rw-r--r--src/libnm-core-impl/nm-setting-wireless-security.c75
-rw-r--r--src/libnm-core-impl/nm-setting-wireless.c161
-rw-r--r--src/libnm-core-impl/nm-setting-wpan.c12
-rw-r--r--src/libnm-core-impl/nm-setting.c360
-rw-r--r--src/libnm-core-impl/nm-simple-connection.c3
-rw-r--r--src/libnm-core-impl/nm-team-utils.c12
-rw-r--r--src/libnm-core-impl/nm-team-utils.h5
-rw-r--r--src/libnm-core-impl/nm-utils-private.h2
-rw-r--r--src/libnm-core-impl/nm-utils.c886
-rw-r--r--src/libnm-core-impl/nm-vpn-editor-plugin.c2
-rw-r--r--src/libnm-core-impl/nm-vpn-plugin-info.c12
-rw-r--r--src/libnm-core-impl/tests/meson.build1
-rw-r--r--src/libnm-core-impl/tests/test-compare.c231
-rw-r--r--src/libnm-core-impl/tests/test-general.c330
-rw-r--r--src/libnm-core-impl/tests/test-setting.c250
70 files changed, 3180 insertions, 2070 deletions
diff --git a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
index 4e17a45b..e008b135 100644
--- a/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
+++ b/src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in
@@ -779,6 +779,10 @@
                   dbus-type="i"
                   gprop-type="NMSettingConnectionAutoconnectSlaves"
                   />
+        <property name="controller"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="dns-over-tls"
                   dbus-type="i"
                   gprop-type="gint"
@@ -804,6 +808,7 @@
                   gprop-type="gint"
                   />
         <property name="master"
+                  is-deprecated="1"
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
@@ -831,6 +836,10 @@
                   dbus-type="as"
                   gprop-type="GStrv"
                   />
+        <property name="port-type"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="read-only"
                   is-deprecated="1"
                   dbus-type="b"
@@ -841,6 +850,7 @@
                   gprop-type="GStrv"
                   />
         <property name="slave-type"
+                  is-deprecated="1"
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
@@ -1276,6 +1286,10 @@
                   dbus-type="b"
                   is-setting-option="1"
                   />
+        <property name="eee-enabled"
+                  dbus-type="b"
+                  is-setting-option="1"
+                  />
         <property name="ring-rx"
                   dbus-type="u"
                   is-setting-option="1"
@@ -1292,6 +1306,22 @@
                   dbus-type="u"
                   is-setting-option="1"
                   />
+        <property name="channels-rx"
+                  dbus-type="u"
+                  is-setting-option="1"
+                  />
+        <property name="channels-tx"
+                  dbus-type="u"
+                  is-setting-option="1"
+                  />
+        <property name="channels-other"
+                  dbus-type="u"
+                  is-setting-option="1"
+                  />
+        <property name="channels-combined"
+                  dbus-type="u"
+                  is-setting-option="1"
+                  />
     </setting>
     <setting name="generic"
              gtype="NMSettingGeneric"
@@ -1398,6 +1428,26 @@
                   gprop-type="gint"
                   />
     </setting>
+    <setting name="hsr"
+             gtype="NMSettingHsr"
+             >
+        <property name="multicast-spec"
+                  dbus-type="u"
+                  gprop-type="guint"
+                  />
+        <property name="port1"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
+        <property name="port2"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
+        <property name="prp"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+    </setting>
     <setting name="infiniband"
              gtype="NMSettingInfiniband"
              >
diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build
index 635b417b..e1f11f32 100644
--- a/src/libnm-core-impl/meson.build
+++ b/src/libnm-core-impl/meson.build
@@ -19,6 +19,7 @@ libnm_core_settings_sources = files(
   'nm-setting-generic.c',
   'nm-setting-gsm.c',
   'nm-setting-hostname.c',
+  'nm-setting-hsr.c',
   'nm-setting-infiniband.c',
   'nm-setting-ip-config.c',
   'nm-setting-ip-tunnel.c',
@@ -67,7 +68,6 @@ libnm_core_impl_sources = files(
   'nm-keyfile-utils.c',
   'nm-keyfile.c',
   'nm-meta-setting-base-impl.c',
-  'nm-property-compare.c',
   'nm-setting.c',
   'nm-simple-connection.c',
   'nm-team-utils.c',
diff --git a/src/libnm-core-impl/nm-connection-private.h b/src/libnm-core-impl/nm-connection-private.h
index 08abb9a4..3b2c36fd 100644
--- a/src/libnm-core-impl/nm-connection-private.h
+++ b/src/libnm-core-impl/nm-connection-private.h
@@ -6,7 +6,7 @@
 #ifndef __NM_CONNECTION_PRIVATE_H__
 #define __NM_CONNECTION_PRIVATE_H__
 
-#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
 #error Cannot use this header.
 #endif
 
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index 6ffc750a..a23dc113 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -908,7 +908,7 @@ _nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error)
      * Now, when we find any invalid/non-normalized values, we reject/normalize
      * them. We also filter out duplicates. */
 
-    strv = nm_strvarray_get_strv_non_empty(secondaries, NULL);
+    strv = nm_strvarray_get_strv_notempty(secondaries, NULL);
 
     for (i = 0; i < len; i++) {
         const char *uuid = strv[i];
@@ -977,7 +977,7 @@ _normalize_connection_secondaries(NMConnection *self)
     if (_nm_setting_connection_verify_secondaries(secondaries, NULL))
         return FALSE;
 
-    strv = nm_strvarray_get_strv_non_empty_dup(secondaries, NULL);
+    strv = nm_strvarray_get_strv_notempty_dup(secondaries, NULL);
     for (i = 0, j = 0; strv[i]; i++) {
         gs_free char *s = g_steal_pointer(&strv[i]);
         char          uuid_normalized[37];
@@ -986,7 +986,7 @@ _normalize_connection_secondaries(NMConnection *self)
         if (!nm_uuid_is_valid_nm(s, &uuid_is_normalized, uuid_normalized))
             continue;
 
-        if (nm_strv_find_first(strv, j, uuid_is_normalized ? uuid_normalized : s) >= 0)
+        if (nm_strv_contains(strv, j, uuid_is_normalized ? uuid_normalized : s))
             continue;
 
         strv[j++] = uuid_is_normalized ? g_strdup(uuid_normalized) : g_steal_pointer(&s);
@@ -1096,7 +1096,7 @@ _normalize_connection_slave_type(NMConnection *self)
     if (!nm_setting_connection_get_master(s_con))
         return FALSE;
 
-    slave_type = nm_setting_connection_get_slave_type(s_con);
+    slave_type = nm_setting_connection_get_port_type(s_con);
     if (slave_type) {
         if (_nm_setting_slave_type_is_valid(slave_type, &port_type) && port_type) {
             NMSetting *s_port;
@@ -1112,7 +1112,7 @@ _normalize_connection_slave_type(NMConnection *self)
         }
     } else {
         if ((slave_type = _nm_connection_detect_slave_type(self, NULL))) {
-            g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, slave_type, NULL);
+            g_object_set(s_con, NM_SETTING_CONNECTION_PORT_TYPE, slave_type, NULL);
             return TRUE;
         }
     }
@@ -1172,7 +1172,7 @@ _supports_addr_family(NMConnection *self, int family)
     if (strcmp(connection_type, NM_SETTING_6LOWPAN_SETTING_NAME) == 0)
         return family == AF_INET6 || family == AF_UNSPEC;
     if ((s_con = nm_connection_get_setting_connection(self))
-        && (nm_streq0(nm_setting_connection_get_slave_type(s_con), NM_SETTING_VRF_SETTING_NAME)))
+        && (nm_streq0(nm_setting_connection_get_port_type(s_con), NM_SETTING_VRF_SETTING_NAME)))
         return TRUE;
 
     return !nm_setting_connection_get_master(nm_connection_get_setting_connection(self));
@@ -1430,52 +1430,42 @@ again:
 }
 
 static gboolean
-_normalize_wireless_mac_address_randomization(NMConnection *self)
+_normalize_wireless_mac_address_randomization(NMSettingWireless *s_wifi)
 {
-    NMSettingWireless        *s_wifi = nm_connection_get_setting_wireless(self);
+    const char               *desired_cloned_mac_address;
     const char               *cloned_mac_address;
+    NMSettingMacRandomization desired_mac_address_randomization;
     NMSettingMacRandomization mac_address_randomization;
+    gboolean                  changed = FALSE;
 
-    if (!s_wifi)
-        return FALSE;
+    _nm_setting_wireless_normalize_mac_address_randomization(s_wifi,
+                                                             &desired_cloned_mac_address,
+                                                             &desired_mac_address_randomization);
 
     mac_address_randomization = nm_setting_wireless_get_mac_address_randomization(s_wifi);
-    if (!NM_IN_SET(mac_address_randomization,
-                   NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
-                   NM_SETTING_MAC_RANDOMIZATION_NEVER,
-                   NM_SETTING_MAC_RANDOMIZATION_ALWAYS))
-        return FALSE;
+    cloned_mac_address        = nm_setting_wireless_get_cloned_mac_address(s_wifi);
 
-    cloned_mac_address = nm_setting_wireless_get_cloned_mac_address(s_wifi);
-    if (cloned_mac_address) {
-        if (nm_streq(cloned_mac_address, "random")) {
-            if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
-                return FALSE;
-            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS;
-        } else if (nm_streq(cloned_mac_address, "permanent")) {
-            if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER)
-                return FALSE;
-            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER;
-        } else {
-            if (mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
-                return FALSE;
-            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
-        }
+    /* Note that "mac_address_randomization" is possibly the string owned by
+     * "s_wifi".  We must be careful that modifying "s_wifi" might invalidate
+     * the string. */
+
+    if (!nm_streq0(cloned_mac_address, desired_cloned_mac_address)) {
         g_object_set(s_wifi,
-                     NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
-                     mac_address_randomization,
+                     NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+                     desired_cloned_mac_address,
                      NULL);
-        return TRUE;
+        changed = TRUE;
     }
-    if (mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) {
+
+    if (mac_address_randomization != desired_mac_address_randomization) {
         g_object_set(s_wifi,
-                     NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
-                     mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS ? "random"
-                                                                                      : "permanent",
+                     NM_SETTING_WIRELESS_MAC_ADDRESS_RANDOMIZATION,
+                     (guint) desired_mac_address_randomization,
                      NULL);
-        return TRUE;
+        changed = TRUE;
     }
-    return FALSE;
+
+    return changed;
 }
 
 static gboolean
@@ -1497,6 +1487,9 @@ _normalize_wireless(NMConnection *self)
         changed = TRUE;
     }
 
+    if (_normalize_wireless_mac_address_randomization(s_wifi))
+        changed = TRUE;
+
     return changed;
 }
 
@@ -1775,7 +1768,7 @@ _normalize_invalid_slave_port_settings(NMConnection *self)
     const char          *slave_type;
     gboolean             changed = FALSE;
 
-    slave_type = nm_setting_connection_get_slave_type(s_con);
+    slave_type = nm_setting_connection_get_port_type(s_con);
 
     if (!nm_streq0(slave_type, NM_SETTING_BRIDGE_SETTING_NAME)
         && _nm_connection_remove_setting(self, NM_TYPE_SETTING_BRIDGE_PORT))
@@ -2043,7 +2036,6 @@ _connection_normalize(NMConnection *connection,
     was_modified |= _normalize_infiniband(connection);
     was_modified |= _normalize_bond_mode(connection);
     was_modified |= _normalize_bond_options(connection);
-    was_modified |= _normalize_wireless_mac_address_randomization(connection);
     was_modified |= _normalize_wireless(connection);
     was_modified |= _normalize_macsec(connection);
     was_modified |= _normalize_team_config(connection);
@@ -3182,6 +3174,7 @@ nm_connection_is_virtual(NMConnection *connection)
                      NM_SETTING_BOND_SETTING_NAME,
                      NM_SETTING_BRIDGE_SETTING_NAME,
                      NM_SETTING_DUMMY_SETTING_NAME,
+                     NM_SETTING_HSR_SETTING_NAME,
                      NM_SETTING_IP_TUNNEL_SETTING_NAME,
                      NM_SETTING_MACSEC_SETTING_NAME,
                      NM_SETTING_MACVLAN_SETTING_NAME,
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index 82581d9f..078ea5d7 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -1219,9 +1219,14 @@ mac_address_parser(KeyfileReaderInfo *info,
 
     tmp_string = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL);
 
-    if (cloned_mac_addr && NM_CLONED_MAC_IS_SPECIAL(tmp_string)) {
-        mac_str = tmp_string;
-        goto out;
+    if (cloned_mac_addr) {
+        gboolean is_wifi;
+
+        is_wifi = NM_IS_SETTING_WIRELESS(setting);
+        if (NM_CLONED_MAC_IS_SPECIAL(tmp_string, is_wifi)) {
+            mac_str = tmp_string;
+            goto out;
+        }
     }
 
     if (tmp_string && nm_utils_hwaddr_aton(tmp_string, addr_bin, addr_len))
diff --git a/src/libnm-core-impl/nm-meta-setting-base-impl.c b/src/libnm-core-impl/nm-meta-setting-base-impl.c
index b531ae85..34a7d22e 100644
--- a/src/libnm-core-impl/nm-meta-setting-base-impl.c
+++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c
@@ -29,6 +29,7 @@
 #include "nm-setting-generic.h"
 #include "nm-setting-gsm.h"
 #include "nm-setting-hostname.h"
+#include "nm-setting-hsr.h"
 #include "nm-setting-infiniband.h"
 #include "nm-setting-ip-config.h"
 #include "nm-setting-ip-tunnel.h"
@@ -335,6 +336,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
             .setting_name      = NM_SETTING_HOSTNAME_SETTING_NAME,
             .get_setting_gtype = nm_setting_hostname_get_type,
         },
+    [NM_META_SETTING_TYPE_HSR] =
+        {
+            .meta_type         = NM_META_SETTING_TYPE_HSR,
+            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
+            .setting_name      = NM_SETTING_HSR_SETTING_NAME,
+            .get_setting_gtype = nm_setting_hsr_get_type,
+        },
     [NM_META_SETTING_TYPE_INFINIBAND] =
         {
             .meta_type         = NM_META_SETTING_TYPE_INFINIBAND,
@@ -632,6 +640,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
     NM_META_SETTING_TYPE_DUMMY,
     NM_META_SETTING_TYPE_GENERIC,
     NM_META_SETTING_TYPE_GSM,
+    NM_META_SETTING_TYPE_HSR,
     NM_META_SETTING_TYPE_INFINIBAND,
     NM_META_SETTING_TYPE_IP_TUNNEL,
     NM_META_SETTING_TYPE_LOOPBACK,
diff --git a/src/libnm-core-impl/nm-property-compare.c b/src/libnm-core-impl/nm-property-compare.c
deleted file mode 100644
index 6273e90b..00000000
--- a/src/libnm-core-impl/nm-property-compare.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2007 - 2014 Red Hat, Inc.
- * Copyright (C) 2007 - 2008 Novell, Inc.
- */
-
-#include "libnm-core-impl/nm-default-libnm-core.h"
-
-#include "nm-property-compare.h"
-
-#include <netinet/in.h>
-
-static int
-_nm_property_compare_collection(GVariant *value1, GVariant *value2)
-{
-    GVariant *child1, *child2;
-    int       i, len1, len2;
-    int       ret;
-
-    len1 = g_variant_n_children(value1);
-    len2 = g_variant_n_children(value2);
-
-    if (len1 != len2)
-        return len1 < len2 ? -1 : len1 > len2;
-
-    for (i = 0; i < len1; i++) {
-        child1 = g_variant_get_child_value(value1, i);
-        child2 = g_variant_get_child_value(value2, i);
-
-        ret = nm_property_compare(child1, child2);
-        g_variant_unref(child1);
-        g_variant_unref(child2);
-
-        if (ret)
-            return ret;
-    }
-
-    return 0;
-}
-
-static int
-_nm_property_compare_vardict(GVariant *value1, GVariant *value2)
-{
-    GVariantIter iter;
-    int          len1, len2;
-    const char  *key;
-    GVariant    *val1, *val2;
-
-    len1 = g_variant_n_children(value1);
-    len2 = g_variant_n_children(value2);
-
-    if (len1 != len2)
-        return len1 < len2 ? -1 : 1;
-
-    g_variant_iter_init(&iter, value1);
-    while (g_variant_iter_next(&iter, "{&sv}", &key, &val1)) {
-        if (!g_variant_lookup(value2, key, "v", &val2)) {
-            g_variant_unref(val1);
-            return -1;
-        }
-        if (!g_variant_equal(val1, val2)) {
-            g_variant_unref(val1);
-            g_variant_unref(val2);
-            return -1;
-        }
-        g_variant_unref(val1);
-        g_variant_unref(val2);
-    }
-
-    return 0;
-}
-
-static int
-_nm_property_compare_strdict(GVariant *value1, GVariant *value2)
-{
-    GVariantIter iter;
-    int          len1, len2;
-    const char  *key, *val1, *val2;
-    int          ret;
-
-    len1 = g_variant_n_children(value1);
-    len2 = g_variant_n_children(value2);
-
-    if (len1 != len2)
-        return len1 < len2 ? -1 : len1 > len2;
-
-    g_variant_iter_init(&iter, value1);
-    while (g_variant_iter_next(&iter, "{&s&s}", &key, &val1)) {
-        if (!g_variant_lookup(value2, key, "&s", &val2))
-            return -1;
-
-        ret = strcmp(val1, val2);
-        if (ret)
-            return ret;
-    }
-
-    return 0;
-}
-
-int
-nm_property_compare(GVariant *value1, GVariant *value2)
-{
-    const GVariantType *type1;
-    const GVariantType *type2;
-    int                 ret;
-
-    if (value1 == value2)
-        return 0;
-    if (!value1)
-        return 1;
-    if (!value2)
-        return -1;
-
-    type1 = g_variant_get_type(value1);
-    type2 = g_variant_get_type(value2);
-
-    if (!g_variant_type_equal(type1, type2))
-        return type1 < type2 ? -1 : type1 > type2;
-
-    if (g_variant_type_is_basic(type1))
-        ret = g_variant_compare(value1, value2);
-    else if (g_variant_is_of_type(value1, G_VARIANT_TYPE("a{ss}")))
-        ret = _nm_property_compare_strdict(value1, value2);
-    else if (g_variant_is_of_type(value1, G_VARIANT_TYPE("a{sv}")))
-        ret = _nm_property_compare_vardict(value1, value2);
-    else if (g_variant_type_is_array(type1))
-        ret = _nm_property_compare_collection(value1, value2);
-    else if (g_variant_type_is_tuple(type1))
-        ret = _nm_property_compare_collection(value1, value2);
-    else {
-        g_warning("Don't know how to compare variant type '%s'", (const char *) type1);
-        ret = value1 == value2;
-    }
-
-    return ret;
-}
diff --git a/src/libnm-core-impl/nm-property-compare.h b/src/libnm-core-impl/nm-property-compare.h
deleted file mode 100644
index 2400bc42..00000000
--- a/src/libnm-core-impl/nm-property-compare.h
+++ /dev/null
@@ -1,16 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2007 - 2014 Red Hat, Inc.
- * Copyright (C) 2007 - 2008 Novell, Inc.
- */
-
-#ifndef __NM_PROPERTY_COMPARE_H__
-#define __NM_PROPERTY_COMPARE_H__
-
-#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
-#error Cannot use this header.
-#endif
-
-int nm_property_compare(GVariant *value1, GVariant *value2);
-
-#endif /* __NM_PROPERTY_COMPARE_H__ */
diff --git a/src/libnm-core-impl/nm-setting-6lowpan.c b/src/libnm-core-impl/nm-setting-6lowpan.c
index e0b5d650..7066c3e6 100644
--- a/src/libnm-core-impl/nm-setting-6lowpan.c
+++ b/src/libnm-core-impl/nm-setting-6lowpan.c
@@ -33,7 +33,8 @@ typedef struct {
  * Since: 1.14
  */
 struct _NMSetting6Lowpan {
-    NMSetting parent;
+    NMSetting               parent;
+    NMSetting6LowpanPrivate _priv;
 };
 
 struct _NMSetting6LowpanClass {
@@ -43,7 +44,7 @@ struct _NMSetting6LowpanClass {
 G_DEFINE_TYPE(NMSetting6Lowpan, nm_setting_6lowpan, NM_TYPE_SETTING)
 
 #define NM_SETTING_6LOWPAN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_6LOWPAN, NMSetting6LowpanPrivate))
+    _NM_GET_PRIVATE(o, NMSetting6Lowpan, NM_IS_SETTING_6LOWPAN, NMSetting)
 
 /*****************************************************************************/
 
@@ -92,7 +93,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         if (s_con) {
             const char *master = NULL, *slave_type = NULL;
 
-            slave_type = nm_setting_connection_get_slave_type(s_con);
+            slave_type = nm_setting_connection_get_port_type(s_con);
             if (!g_strcmp0(slave_type, NM_SETTING_6LOWPAN_SETTING_NAME))
                 master = nm_setting_connection_get_master(s_con);
 
@@ -102,7 +103,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
                             _("'%s' value doesn't match '%s=%s'"),
                             priv->parent,
-                            NM_SETTING_CONNECTION_MASTER,
+                            NM_SETTING_CONNECTION_CONTROLLER,
                             master);
                 g_prefix_error(error,
                                "%s.%s: ",
@@ -156,8 +157,6 @@ nm_setting_6lowpan_class_init(NMSetting6LowpanClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSetting6LowpanPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -177,7 +176,8 @@ nm_setting_6lowpan_class_init(NMSetting6LowpanClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSetting6LowpanPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -185,5 +185,5 @@ nm_setting_6lowpan_class_init(NMSetting6LowpanClass *klass)
                              NM_META_SETTING_TYPE_6LOWPAN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSetting6Lowpan, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index 626673e8..945fd6d3 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -189,20 +189,18 @@ typedef struct {
  * IEEE 802.1x Authentication Settings
  */
 struct _NMSetting8021x {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting             parent;
+    NMSetting8021xPrivate _priv;
 };
 
 struct _NMSetting8021xClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSetting8021x, nm_setting_802_1x, NM_TYPE_SETTING)
 
 #define NM_SETTING_802_1X_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_802_1X, NMSetting8021xPrivate))
+    _NM_GET_PRIVATE(o, NMSetting8021x, NM_IS_SETTING_802_1X, NMSetting)
 
 /*****************************************************************************/
 
@@ -3199,9 +3197,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
-    GArray         *properties_override = _nm_sett_info_property_override_create_array();
-
-    g_type_class_add_private(klass, sizeof(NMSetting8021xPrivate));
+    GArray         *properties_override = _nm_sett_info_property_override_create_array_sized(55);
 
     object_class->get_property = get_property;
     object_class->set_property = set_property;
@@ -3227,11 +3223,11 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
      * example: IEEE_8021X_EAP_METHODS=PEAP
      * ---end---
      */
-    obj_properties[PROP_EAP] = g_param_spec_boxed(NM_SETTING_802_1X_EAP,
-                                                  "",
-                                                  "",
-                                                  G_TYPE_STRV,
-                                                  G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_802_1X_EAP,
+                                                    PROP_EAP,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSetting8021x:identity:
@@ -3252,7 +3248,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_IDENTITY,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              identity);
+                                              identity,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:anonymous-identity:
@@ -3273,7 +3270,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_ANONYMOUS_IDENTITY,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              anonymous_identity);
+                                              anonymous_identity,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:pac-file:
@@ -3293,7 +3291,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PAC_FILE,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              pac_file);
+                                              pac_file,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:ca-cert:
@@ -3346,7 +3345,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_CA_CERT_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              ca_cert_password);
+                                              ca_cert_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:ca-cert-password-flags:
@@ -3384,7 +3384,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_CA_PATH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              ca_path);
+                                              ca_path,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:subject-match:
@@ -3410,7 +3411,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
                                               subject_match,
-                                              .is_deprecated = TRUE, );
+                                              .is_deprecated             = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:altsubject-matches:
@@ -3426,12 +3428,11 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
      * example: IEEE_8021X_ALTSUBJECT_MATCHES="s1.domain.cc"
      * ---end---
      */
-    obj_properties[PROP_ALTSUBJECT_MATCHES] =
-        g_param_spec_boxed(NM_SETTING_802_1X_ALTSUBJECT_MATCHES,
-                           "",
-                           "",
-                           G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_802_1X_ALTSUBJECT_MATCHES,
+                                                    PROP_ALTSUBJECT_MATCHES,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSetting8021x:domain-suffix-match:
@@ -3458,7 +3459,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_DOMAIN_SUFFIX_MATCH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              domain_suffix_match);
+                                              domain_suffix_match,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:domain-match:
@@ -3484,7 +3486,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_DOMAIN_MATCH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              domain_match);
+                                              domain_match,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:client-cert:
@@ -3532,7 +3535,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_CLIENT_CERT_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              client_cert_password);
+                                              client_cert_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:client-cert-password-flags:
@@ -3571,7 +3575,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE1_PEAPVER,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase1_peapver);
+                                              phase1_peapver,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase1-peaplabel:
@@ -3595,7 +3600,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE1_PEAPLABEL,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase1_peaplabel);
+                                              phase1_peaplabel,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase1-fast-provisioning:
@@ -3621,7 +3627,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE1_FAST_PROVISIONING,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase1_fast_provisioning);
+                                              phase1_fast_provisioning,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase1-auth-flags:
@@ -3685,7 +3692,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_AUTH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase2_auth);
+                                              phase2_auth,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-autheap:
@@ -3712,7 +3720,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_AUTHEAP,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase2_autheap);
+                                              phase2_autheap,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-ca-cert:
@@ -3759,7 +3768,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_CA_CERT_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              phase2_ca_cert_password);
+                                              phase2_ca_cert_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-ca-cert-password-flags:
@@ -3797,7 +3807,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_CA_PATH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase2_ca_path);
+                                              phase2_ca_path,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-subject-match:
@@ -3824,7 +3835,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
                                               phase2_subject_match,
-                                              .is_deprecated = TRUE, );
+                                              .is_deprecated             = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-altsubject-matches:
@@ -3839,12 +3851,11 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
      * variable: IEEE_8021X_PHASE2_ALTSUBJECT_MATCHES(+)
      * ---end---
      */
-    obj_properties[PROP_PHASE2_ALTSUBJECT_MATCHES] =
-        g_param_spec_boxed(NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES,
-                           "",
-                           "",
-                           G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_802_1X_PHASE2_ALTSUBJECT_MATCHES,
+                                                    PROP_PHASE2_ALTSUBJECT_MATCHES,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSetting8021x:phase2-domain-suffix-match:
@@ -3872,7 +3883,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_DOMAIN_SUFFIX_MATCH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase2_domain_suffix_match);
+                                              phase2_domain_suffix_match,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-domain-match:
@@ -3899,7 +3911,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_DOMAIN_MATCH,
                                               NM_SETTING_PARAM_NONE,
                                               NMSetting8021xPrivate,
-                                              phase2_domain_match);
+                                              phase2_domain_match,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-client-cert:
@@ -3950,7 +3963,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_CLIENT_CERT_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              phase2_client_cert_password);
+                                              phase2_client_cert_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-client-cert-password-flags:
@@ -3987,7 +4001,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              password);
+                                              password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:password-flags:
@@ -4119,7 +4134,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PRIVATE_KEY_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              private_key_password);
+                                              private_key_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:private-key-password-flags:
@@ -4205,7 +4221,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PHASE2_PRIVATE_KEY_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              phase2_private_key_password);
+                                              phase2_private_key_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:phase2-private-key-password-flags:
@@ -4245,7 +4262,8 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               PROP_PIN,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSetting8021xPrivate,
-                                              pin);
+                                              pin,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSetting8021x:pin-flags:
@@ -4352,5 +4370,5 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                              NM_META_SETTING_TYPE_802_1X,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSetting8021x, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-adsl.c b/src/libnm-core-impl/nm-setting-adsl.c
index ca291389..f9aacb83 100644
--- a/src/libnm-core-impl/nm-setting-adsl.c
+++ b/src/libnm-core-impl/nm-setting-adsl.c
@@ -46,20 +46,18 @@ typedef struct {
  * ADSL Settings
  */
 struct _NMSettingAdsl {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting            parent;
+    NMSettingAdslPrivate _priv;
 };
 
 struct _NMSettingAdslClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingAdsl, nm_setting_adsl, NM_TYPE_SETTING)
 
 #define NM_SETTING_ADSL_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_ADSL, NMSettingAdslPrivate))
+    _NM_GET_PRIVATE(o, NMSettingAdsl, NM_IS_SETTING_ADSL, NMSetting)
 
 /*****************************************************************************/
 
@@ -267,8 +265,6 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingAdslPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -287,7 +283,8 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
                                               PROP_USERNAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingAdslPrivate,
-                                              username);
+                                              username,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingAdsl:password:
@@ -300,7 +297,8 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
                                               PROP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingAdslPrivate,
-                                              password);
+                                              password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingAdsl:password-flags:
@@ -326,7 +324,8 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingAdslPrivate,
                                               protocol,
-                                              .direct_set_string_ascii_strdown = TRUE);
+                                              .direct_set_string_ascii_strdown = TRUE,
+                                              .direct_string_allow_empty       = TRUE);
 
     /**
      * NMSettingAdsl:encapsulation:
@@ -340,7 +339,8 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingAdslPrivate,
                                               encapsulation,
-                                              .direct_set_string_ascii_strdown = TRUE);
+                                              .direct_set_string_ascii_strdown = TRUE,
+                                              .direct_string_allow_empty       = TRUE);
 
     /**
      * NMSettingAdsl:vpi:
@@ -380,5 +380,5 @@ nm_setting_adsl_class_init(NMSettingAdslClass *klass)
                              NM_META_SETTING_TYPE_ADSL,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingAdsl, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-bluetooth.c b/src/libnm-core-impl/nm-setting-bluetooth.c
index f657ff13..3c2605a1 100644
--- a/src/libnm-core-impl/nm-setting-bluetooth.c
+++ b/src/libnm-core-impl/nm-setting-bluetooth.c
@@ -42,20 +42,18 @@ typedef struct {
  * Bluetooth Settings
  */
 struct _NMSettingBluetooth {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                 parent;
+    NMSettingBluetoothPrivate _priv;
 };
 
 struct _NMSettingBluetoothClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingBluetooth, nm_setting_bluetooth, NM_TYPE_SETTING)
 
 #define NM_SETTING_BLUETOOTH_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BLUETOOTH, NMSettingBluetoothPrivate))
+    _NM_GET_PRIVATE(o, NMSettingBluetooth, NM_IS_SETTING_BLUETOOTH, NMSetting)
 
 /*****************************************************************************/
 
@@ -254,8 +252,6 @@ nm_setting_bluetooth_class_init(NMSettingBluetoothClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingBluetoothPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -287,7 +283,8 @@ nm_setting_bluetooth_class_init(NMSettingBluetoothClass *klass)
                                               PROP_TYPE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingBluetoothPrivate,
-                                              type);
+                                              type,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -295,5 +292,5 @@ nm_setting_bluetooth_class_init(NMSettingBluetoothClass *klass)
                              NM_META_SETTING_TYPE_BLUETOOTH,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingBluetooth, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-bond-port.c b/src/libnm-core-impl/nm-setting-bond-port.c
index e8917902..342d6dd0 100644
--- a/src/libnm-core-impl/nm-setting-bond-port.c
+++ b/src/libnm-core-impl/nm-setting-bond-port.c
@@ -101,7 +101,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             return FALSE;
         }
 
-        slave_type = nm_setting_connection_get_slave_type(s_con);
+        slave_type = nm_setting_connection_get_port_type(s_con);
         if (slave_type && !nm_streq(slave_type, NM_SETTING_BOND_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
@@ -114,7 +114,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c
index 81c67d51..60ae0294 100644
--- a/src/libnm-core-impl/nm-setting-bond.c
+++ b/src/libnm-core-impl/nm-setting-bond.c
@@ -44,20 +44,18 @@ typedef struct {
  * Bonding Settings
  */
 struct _NMSettingBond {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting            parent;
+    NMSettingBondPrivate _priv;
 };
 
 struct _NMSettingBondClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingBond, nm_setting_bond, NM_TYPE_SETTING)
 
 #define NM_SETTING_BOND_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BOND, NMSettingBondPrivate))
+    _NM_GET_PRIVATE(o, NMSettingBond, NM_IS_SETTING_BOND, NMSetting)
 
 /*****************************************************************************/
 
@@ -1322,8 +1320,6 @@ nm_setting_bond_class_init(NMSettingBondClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingBondPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -1380,5 +1376,5 @@ nm_setting_bond_class_init(NMSettingBondClass *klass)
                              NM_META_SETTING_TYPE_BOND,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingBond, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-bridge-port.c b/src/libnm-core-impl/nm-setting-bridge-port.c
index b94a348e..dcb17896 100644
--- a/src/libnm-core-impl/nm-setting-bridge-port.c
+++ b/src/libnm-core-impl/nm-setting-bridge-port.c
@@ -45,20 +45,18 @@ typedef struct {
  * Bridge Port Settings
  */
 struct _NMSettingBridgePort {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                  parent;
+    NMSettingBridgePortPrivate _priv;
 };
 
 struct _NMSettingBridgePortClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingBridgePort, nm_setting_bridge_port, NM_TYPE_SETTING)
 
 #define NM_SETTING_BRIDGE_PORT_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_BRIDGE_PORT, NMSettingBridgePortPrivate))
+    _NM_GET_PRIVATE(o, NMSettingBridgePort, NM_IS_SETTING_BRIDGE_PORT, NMSetting)
 
 /*****************************************************************************/
 
@@ -330,7 +328,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             return FALSE;
         }
 
-        slave_type = nm_setting_connection_get_slave_type(s_con);
+        slave_type = nm_setting_connection_get_port_type(s_con);
         if (slave_type && strcmp(slave_type, NM_SETTING_BRIDGE_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
@@ -343,7 +341,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
@@ -455,8 +453,6 @@ nm_setting_bridge_port_class_init(NMSettingBridgePortClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingBridgePortPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -579,5 +575,5 @@ nm_setting_bridge_port_class_init(NMSettingBridgePortClass *klass)
                              NM_META_SETTING_TYPE_BRIDGE_PORT,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingBridgePort, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-bridge.c b/src/libnm-core-impl/nm-setting-bridge.c
index c1a2621d..7e9a0964 100644
--- a/src/libnm-core-impl/nm-setting-bridge.c
+++ b/src/libnm-core-impl/nm-setting-bridge.c
@@ -1416,7 +1416,7 @@ nm_setting_bridge_class_init(NMSettingBridgeClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
-    GArray         *properties_override = _nm_sett_info_property_override_create_array();
+    GArray         *properties_override = _nm_sett_info_property_override_create_array_sized(35);
 
     object_class->get_property = get_property;
     object_class->set_property = set_property;
@@ -1811,7 +1811,8 @@ nm_setting_bridge_class_init(NMSettingBridgeClass *klass)
                                               PROP_VLAN_PROTOCOL,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingBridge,
-                                              _priv.vlan_protocol);
+                                              _priv.vlan_protocol,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingBridge:vlan-stats-enabled:
@@ -1862,7 +1863,8 @@ nm_setting_bridge_class_init(NMSettingBridgeClass *klass)
                                               PROP_MULTICAST_ROUTER,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingBridge,
-                                              _priv.multicast_router);
+                                              _priv.multicast_router,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingBridge:multicast-query-use-ifaddr:
diff --git a/src/libnm-core-impl/nm-setting-cdma.c b/src/libnm-core-impl/nm-setting-cdma.c
index c32af6b7..b497cf14 100644
--- a/src/libnm-core-impl/nm-setting-cdma.c
+++ b/src/libnm-core-impl/nm-setting-cdma.c
@@ -42,20 +42,18 @@ typedef struct {
  * CDMA-based Mobile Broadband Settings
  */
 struct _NMSettingCdma {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting            parent;
+    NMSettingCdmaPrivate _priv;
 };
 
 struct _NMSettingCdmaClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingCdma, nm_setting_cdma, NM_TYPE_SETTING)
 
 #define NM_SETTING_CDMA_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_CDMA, NMSettingCdmaPrivate))
+    _NM_GET_PRIVATE(o, NMSettingCdma, NM_IS_SETTING_CDMA, NMSetting)
 
 /*****************************************************************************/
 
@@ -218,8 +216,6 @@ nm_setting_cdma_class_init(NMSettingCdmaClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingCdmaPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -240,7 +236,8 @@ nm_setting_cdma_class_init(NMSettingCdmaClass *klass)
                                               PROP_NUMBER,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingCdmaPrivate,
-                                              number);
+                                              number,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingCdma:username:
@@ -255,7 +252,8 @@ nm_setting_cdma_class_init(NMSettingCdmaClass *klass)
                                               PROP_USERNAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingCdmaPrivate,
-                                              username);
+                                              username,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingCdma:password:
@@ -270,7 +268,8 @@ nm_setting_cdma_class_init(NMSettingCdmaClass *klass)
                                               PROP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingCdmaPrivate,
-                                              password);
+                                              password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingCdma:password-flags:
@@ -309,5 +308,5 @@ nm_setting_cdma_class_init(NMSettingCdmaClass *klass)
                              NM_META_SETTING_TYPE_CDMA,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingCdma, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 3c26ab5d..892e7d31 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -56,7 +56,9 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingConnection,
                              PROP_READ_ONLY,
                              PROP_ZONE,
                              PROP_MASTER,
+                             PROP_CONTROLLER,
                              PROP_SLAVE_TYPE,
+                             PROP_PORT_TYPE,
                              PROP_AUTOCONNECT_SLAVES,
                              PROP_SECONDARIES,
                              PROP_GATEWAY_PING_TIMEOUT,
@@ -80,8 +82,8 @@ typedef struct {
     char       *stable_id;
     char       *interface_name;
     char       *type;
-    char       *master;
-    char       *slave_type;
+    char       *controller;
+    char       *port_type;
     char       *zone;
     char       *mud_url;
     guint64     timestamp;
@@ -109,20 +111,18 @@ typedef struct {
  * General Connection Profile Settings
  */
 struct _NMSettingConnection {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                  parent;
+    NMSettingConnectionPrivate _priv;
 };
 
 struct _NMSettingConnectionClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingConnection, nm_setting_connection, NM_TYPE_SETTING)
 
 #define NM_SETTING_CONNECTION_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_CONNECTION, NMSettingConnectionPrivate))
+    _NM_GET_PRIVATE(o, NMSettingConnection, NM_IS_SETTING_CONNECTION, NMSetting)
 
 /*****************************************************************************/
 
@@ -357,24 +357,14 @@ invalid:
     return TRUE;
 }
 
-/**
- * nm_setting_connection_permissions_user_allowed:
- * @setting: the #NMSettingConnection
- * @uname: the user name to check permissions for
- *
- * Checks whether the given username is allowed to view/access this connection.
- *
- * Returns: %TRUE if the requested user is allowed to view this connection,
- * %FALSE if the given user is not allowed to view this connection
- */
-gboolean
-nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, const char *uname)
+static gboolean
+_permissions_user_allowed(NMSettingConnection *setting, const char *uname, gulong uid)
 {
+    gs_free struct passwd      *pw = NULL;
     NMSettingConnectionPrivate *priv;
     guint                       i;
 
-    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
-    g_return_val_if_fail(uname != NULL, FALSE);
+    nm_assert(NM_IS_SETTING_CONNECTION(setting));
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
@@ -386,7 +376,19 @@ nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, con
     for (i = 0; i < priv->permissions->len; i++) {
         const Permission *permission = &nm_g_array_index(priv->permissions, Permission, i);
 
-        if (permission->ptype == PERM_TYPE_USER && nm_streq(permission->item, uname))
+        if (permission->ptype != PERM_TYPE_USER)
+            continue;
+
+        if (!uname) {
+            if (uid != G_MAXULONG) {
+                pw    = nm_getpwuid(uid);
+                uname = nm_passwd_name(pw);
+            }
+            if (!uname)
+                return FALSE;
+        }
+
+        if (nm_streq(permission->item, uname))
             return TRUE;
     }
 
@@ -394,6 +396,33 @@ nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, con
 }
 
 /**
+ * nm_setting_connection_permissions_user_allowed:
+ * @setting: the #NMSettingConnection
+ * @uname: the user name to check permissions for
+ *
+ * Checks whether the given username is allowed to view/access this connection.
+ *
+ * Returns: %TRUE if the requested user is allowed to view this connection,
+ * %FALSE if the given user is not allowed to view this connection
+ */
+gboolean
+nm_setting_connection_permissions_user_allowed(NMSettingConnection *setting, const char *uname)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
+    g_return_val_if_fail(uname != NULL, FALSE);
+
+    return _permissions_user_allowed(setting, uname, G_MAXULONG);
+}
+
+gboolean
+nm_setting_connection_permissions_user_allowed_by_uid(NMSettingConnection *setting, gulong uid)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
+
+    return _permissions_user_allowed(setting, NULL, uid);
+}
+
+/**
  * nm_setting_connection_add_permission:
  * @setting: the #NMSettingConnection
  * @ptype: the permission type; at this time only "user" is supported
@@ -682,13 +711,51 @@ nm_setting_connection_get_zone(NMSettingConnection *setting)
  *
  * Returns: interface name of the master device or UUID of the master
  * connection.
+ *
+ * Deprecated: 1.46. Use nm_setting_connection_get_controller() instead which
+ * is just an alias.
  */
 const char *
 nm_setting_connection_get_master(NMSettingConnection *setting)
 {
+    return nm_setting_connection_get_controller(setting);
+}
+
+/**
+ * nm_setting_connection_get_controller:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:controller property of the connection.
+ *
+ * Returns: interface name of the controller device or UUID of the controller
+ * connection.
+ *
+ * Since: 1.46
+ */
+const char *
+nm_setting_connection_get_controller(NMSettingConnection *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
+
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->controller;
+}
+
+/**
+ * nm_setting_connection_get_port_type:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:port-type property of the connection.
+ *
+ * Returns: the type of port this connection is, if any.
+ *
+ * Since: 1.46
+ */
+const char *
+nm_setting_connection_get_port_type(NMSettingConnection *setting)
+{
     g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
 
-    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->master;
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->port_type;
 }
 
 /**
@@ -698,13 +765,14 @@ nm_setting_connection_get_master(NMSettingConnection *setting)
  * Returns the #NMSettingConnection:slave-type property of the connection.
  *
  * Returns: the type of slave this connection is, if any
+ *
+ * Deprecated: 1.46. Use nm_setting_connection_get_port_type() instead which
+ * is just an alias.
  */
 const char *
 nm_setting_connection_get_slave_type(NMSettingConnection *setting)
 {
-    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
-
-    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->slave_type;
+    return nm_setting_connection_get_port_type(setting);
 }
 
 /**
@@ -714,13 +782,15 @@ nm_setting_connection_get_slave_type(NMSettingConnection *setting)
  * against @setting's slave type
  *
  * Returns: %TRUE if connection is of the given slave @type
+ *
+ * Deprecated: 1.46.
  */
 gboolean
 nm_setting_connection_is_slave_type(NMSettingConnection *setting, const char *type)
 {
     g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
 
-    return !g_strcmp0(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->slave_type, type);
+    return nm_streq0(NM_SETTING_CONNECTION_GET_PRIVATE(setting)->port_type, type);
 }
 
 /**
@@ -810,21 +880,11 @@ nm_setting_connection_get_num_secondaries(NMSettingConnection *setting)
 const char *
 nm_setting_connection_get_secondary(NMSettingConnection *setting, guint32 idx)
 {
-    NMSettingConnectionPrivate *priv;
-    guint                       secondaries_len;
-
     g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
 
-    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
-
-    secondaries_len = nm_g_array_len(priv->secondaries.arr);
-    if (idx >= secondaries_len) {
-        /* access one past the length is OK. */
-        g_return_val_if_fail(idx == secondaries_len, NULL);
-        return NULL;
-    }
-
-    return nm_strvarray_get_idx(priv->secondaries.arr, idx);
+    return nm_strvarray_get_idxnull_or_greturn(
+        NM_SETTING_CONNECTION_GET_PRIVATE(setting)->secondaries.arr,
+        idx);
 }
 
 /**
@@ -864,10 +924,9 @@ nm_setting_connection_add_secondary(NMSettingConnection *setting, const char *se
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    if (nm_strvarray_find_first(priv->secondaries.arr, sec_uuid) >= 0)
+    if (!nm_strvarray_ensure_and_add_unique(&priv->secondaries.arr, sec_uuid))
         return FALSE;
 
-    nm_strvarray_add(nm_strvarray_ensure(&priv->secondaries.arr), sec_uuid);
     _notify(setting, PROP_SECONDARIES);
     return TRUE;
 }
@@ -890,7 +949,7 @@ nm_setting_connection_remove_secondary(NMSettingConnection *setting, guint32 idx
 
     g_return_if_fail(idx < nm_g_array_len(priv->secondaries.arr));
 
-    g_array_remove_index(priv->secondaries.arr, idx);
+    nm_strvarray_remove_index(priv->secondaries.arr, idx);
     _notify(setting, PROP_SECONDARIES);
 }
 
@@ -913,11 +972,11 @@ nm_setting_connection_remove_secondary_by_value(NMSettingConnection *setting, co
 
     priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
 
-    if (nm_strvarray_remove_first(priv->secondaries.arr, sec_uuid)) {
-        _notify(setting, PROP_SECONDARIES);
-        return TRUE;
-    }
-    return FALSE;
+    if (!nm_strvarray_remove_first(priv->secondaries.arr, sec_uuid))
+        return FALSE;
+
+    _notify(setting, PROP_SECONDARIES);
+    return TRUE;
 }
 
 /**
@@ -1065,7 +1124,7 @@ _nm_connection_detect_slave_type_full(NMSettingConnection *s_con,
 
     is_slave           = FALSE;
     slave_setting_type = NULL;
-    slave_type         = priv->slave_type;
+    slave_type         = priv->port_type;
     if (slave_type) {
         is_slave = _nm_setting_slave_type_is_valid(slave_type, &slave_setting_type);
         if (!is_slave) {
@@ -1077,22 +1136,22 @@ _nm_connection_detect_slave_type_full(NMSettingConnection *s_con,
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
 
     if (is_slave) {
-        if (!priv->master) {
+        if (!priv->controller) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_MISSING_PROPERTY,
                         _("Slave connections need a valid '%s' property"),
-                        NM_SETTING_CONNECTION_MASTER);
+                        NM_SETTING_CONNECTION_CONTROLLER);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_MASTER);
+                           NM_SETTING_CONNECTION_CONTROLLER);
             return FALSE;
         }
         if (slave_setting_type && connection
@@ -1100,7 +1159,7 @@ _nm_connection_detect_slave_type_full(NMSettingConnection *s_con,
             normerr_slave_setting_type = slave_setting_type;
     } else {
         nm_assert(!slave_type);
-        if (priv->master) {
+        if (priv->controller) {
             NMSetting *s_port;
 
             if (connection
@@ -1112,12 +1171,12 @@ _nm_connection_detect_slave_type_full(NMSettingConnection *s_con,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_MISSING_PROPERTY,
                             _("Cannot set '%s' without '%s'"),
-                            NM_SETTING_CONNECTION_MASTER,
-                            NM_SETTING_CONNECTION_SLAVE_TYPE);
+                            NM_SETTING_CONNECTION_CONTROLLER,
+                            NM_SETTING_CONNECTION_PORT_TYPE);
                 g_prefix_error(error,
                                "%s.%s: ",
                                NM_SETTING_CONNECTION_SETTING_NAME,
-                               NM_SETTING_CONNECTION_SLAVE_TYPE);
+                               NM_SETTING_CONNECTION_PORT_TYPE);
                 return FALSE;
             }
         }
@@ -1328,7 +1387,7 @@ after_interface_name:
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_CONNECTION_SETTING_NAME,
-                       NM_SETTING_CONNECTION_SLAVE_TYPE);
+                       NM_SETTING_CONNECTION_PORT_TYPE);
         return FALSE;
     }
 
@@ -1545,8 +1604,8 @@ after_interface_name:
         g_set_error(error,
                     NM_CONNECTION_ERROR,
                     NM_CONNECTION_ERROR_MISSING_SETTING,
-                    _("slave-type '%s' requires a '%s' setting in the connection"),
-                    priv->slave_type,
+                    _("port-type '%s' requires a '%s' setting in the connection"),
+                    priv->port_type,
                     normerr_slave_setting_type);
         g_prefix_error(error, "%s: ", normerr_slave_setting_type);
         return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
@@ -1558,39 +1617,39 @@ after_interface_name:
                     NM_CONNECTION_ERROR_MISSING_PROPERTY,
                     _("Detect a slave connection with '%s' set and a port type '%s'. '%s' should "
                       "be set to '%s'"),
-                    NM_SETTING_CONNECTION_MASTER,
+                    NM_SETTING_CONNECTION_CONTROLLER,
                     normerr_missing_slave_type_port,
-                    NM_SETTING_CONNECTION_SLAVE_TYPE,
+                    NM_SETTING_CONNECTION_PORT_TYPE,
                     normerr_missing_slave_type);
         g_prefix_error(error,
                        "%s.%s: ",
                        NM_SETTING_CONNECTION_SETTING_NAME,
-                       NM_SETTING_CONNECTION_SLAVE_TYPE);
+                       NM_SETTING_CONNECTION_PORT_TYPE);
         return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
     }
 
     if (connection) {
         gboolean has_bridge_port = FALSE;
 
-        if ((!nm_streq0(priv->slave_type, NM_SETTING_BRIDGE_SETTING_NAME)
+        if ((!nm_streq0(priv->port_type, NM_SETTING_BRIDGE_SETTING_NAME)
              && (has_bridge_port =
                      !!nm_connection_get_setting_by_name(connection,
                                                          NM_SETTING_BRIDGE_PORT_SETTING_NAME)))
-            || (!nm_streq0(priv->slave_type, NM_SETTING_TEAM_SETTING_NAME)
+            || (!nm_streq0(priv->port_type, NM_SETTING_TEAM_SETTING_NAME)
                 && nm_connection_get_setting_by_name(connection,
                                                      NM_SETTING_TEAM_PORT_SETTING_NAME))) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_SETTING,
                         _("A slave connection with '%s' set to '%s' cannot have a '%s' setting"),
-                        NM_SETTING_CONNECTION_SLAVE_TYPE,
-                        priv->slave_type ?: "",
+                        NM_SETTING_CONNECTION_PORT_TYPE,
+                        priv->port_type ?: "",
                         has_bridge_port ? NM_SETTING_BRIDGE_PORT_SETTING_NAME
                                         : NM_SETTING_TEAM_PORT_SETTING_NAME);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
         }
     }
@@ -1775,6 +1834,126 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 
 /*****************************************************************************/
 
+gboolean
+_nm_setting_connection_master_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *str;
+
+    if (!_nm_setting_use_legacy_property(setting,
+                                         connection_dict,
+                                         NM_SETTING_CONNECTION_MASTER,
+                                         NM_SETTING_CONNECTION_CONTROLLER)) {
+        *out_is_modified = FALSE;
+        return TRUE;
+    }
+    str = g_variant_get_string(value, NULL);
+
+    g_object_set(setting, NM_SETTING_CONNECTION_MASTER, str, NULL);
+    return TRUE;
+}
+
+GVariant *
+_nm_setting_connection_controller_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *controller;
+
+    /* FIXME: `controller` is an alias of `master` property. Serializing the
+     * property to the clients would break them as they won't be able to drop
+     * it if they are not aware of the existance of `controller`. In order to
+     * give them time to adapt their code, NetworkManager is not serializing
+     * `controller` on DBus.
+     */
+    if (_nm_utils_is_manager_process) {
+        return NULL;
+    }
+
+    controller = nm_setting_connection_get_controller(NM_SETTING_CONNECTION(setting));
+    if (!controller)
+        return NULL;
+
+    return g_variant_new_string(controller);
+}
+
+gboolean
+_nm_setting_connection_controller_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *str;
+
+    /* Ignore 'controller' if we're going to process 'master' */
+    if (_nm_setting_use_legacy_property(setting,
+                                        connection_dict,
+                                        NM_SETTING_CONNECTION_MASTER,
+                                        NM_SETTING_CONNECTION_CONTROLLER)) {
+        *out_is_modified = FALSE;
+        return TRUE;
+    }
+    str = g_variant_get_string(value, NULL);
+
+    g_object_set(setting, NM_SETTING_CONNECTION_CONTROLLER, str, NULL);
+    return TRUE;
+}
+
+gboolean
+_nm_setting_connection_slave_type_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *str;
+
+    if (!_nm_setting_use_legacy_property(setting,
+                                         connection_dict,
+                                         NM_SETTING_CONNECTION_SLAVE_TYPE,
+                                         NM_SETTING_CONNECTION_PORT_TYPE)) {
+        *out_is_modified = FALSE;
+        return TRUE;
+    }
+    str = g_variant_get_string(value, NULL);
+
+    g_object_set(setting, NM_SETTING_CONNECTION_SLAVE_TYPE, str, NULL);
+    return TRUE;
+}
+
+GVariant *
+_nm_setting_connection_port_type_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *port_type;
+
+    /* FIXME: `port-type` is an alias of `slave-type` property. Serializing the
+     * property to the clients would break them as they won't be able to drop
+     * it if they are not aware of the existance of `port-type`. In order to
+     * give them time to adapt their code, NetworkManager is not serializing
+     * `port-type` on DBus.
+     */
+    if (_nm_utils_is_manager_process) {
+        return NULL;
+    }
+
+    port_type = nm_setting_connection_get_port_type(NM_SETTING_CONNECTION(setting));
+    if (!port_type)
+        return NULL;
+
+    return g_variant_new_string(port_type);
+}
+
+gboolean
+_nm_setting_connection_port_type_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
+{
+    const char *str;
+
+    /* Ignore 'port-type' if we're going to process 'slave-type' */
+    if (_nm_setting_use_legacy_property(setting,
+                                        connection_dict,
+                                        NM_SETTING_CONNECTION_SLAVE_TYPE,
+                                        NM_SETTING_CONNECTION_PORT_TYPE)) {
+        *out_is_modified = FALSE;
+        return TRUE;
+    }
+    str = g_variant_get_string(value, NULL);
+
+    g_object_set(setting, NM_SETTING_CONNECTION_PORT_TYPE, str, NULL);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
 static void
 nm_setting_connection_init(NMSettingConnection *setting)
 {}
@@ -1798,7 +1977,6 @@ finalize(GObject *object)
     NMSettingConnectionPrivate *priv = NM_SETTING_CONNECTION_GET_PRIVATE(object);
 
     nm_clear_pointer(&priv->permissions, g_array_unref);
-    nm_clear_pointer(&priv->secondaries.arr, g_array_unref);
 
     G_OBJECT_CLASS(nm_setting_connection_parent_class)->finalize(object);
 }
@@ -1808,9 +1986,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
-    GArray         *properties_override = _nm_sett_info_property_override_create_array();
-
-    g_type_class_add_private(klass, sizeof(NMSettingConnectionPrivate));
+    GArray         *properties_override = _nm_sett_info_property_override_create_array_sized(35);
+    guint           prop_idx;
 
     object_class->get_property = get_property;
     object_class->set_property = set_property;
@@ -1915,13 +2092,15 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *
      * The '$' character is treated special to perform dynamic substitutions at
      * activation time. Currently, supported are "${CONNECTION}", "${DEVICE}",
-     * "${MAC}", "${BOOT}", "${RANDOM}".  These effectively create unique IDs
-     * per-connection, per-device, per-boot, or every time. The "${CONNECTION}"
-     * uses the profile's connection.uuid, the "${DEVICE}" uses the interface
-     * name of the device and "${MAC}" the permanent MAC address of the device.
-     * Any unrecognized patterns following '$' are treated verbatim, however
-     * are reserved for future use. You are thus advised to avoid '$' or escape
-     * it as "$$".  For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to
+     * "${MAC}", "${NETWORK_SSID}", "${BOOT}", "${RANDOM}".  These effectively
+     * create unique IDs per-connection, per-device, per-SSID, per-boot, or
+     * every time.  The "${CONNECTION}" uses the profile's connection.uuid, the
+     * "${DEVICE}" uses the interface name of the device and "${MAC}" the
+     * permanent MAC address of the device. "${NETWORK_SSID}" uses the SSID for
+     * Wi-Fi networks and falls back to "${CONNECTION}" on other networks. Any
+     * unrecognized patterns following '$' are treated verbatim, however are
+     * reserved for future use. You are thus advised to avoid '$' or escape it
+     * as "$$".  For example, set it to "${CONNECTION}-${BOOT}-${DEVICE}" to
      * create a unique id for this connection that changes with every reboot
      * and differs depending on the interface where the profile activates.
      *
@@ -1943,7 +2122,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                               PROP_STABLE_ID,
                                               NM_SETTING_PARAM_FUZZY_IGNORE,
                                               NMSettingConnectionPrivate,
-                                              stable_id);
+                                              stable_id,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingConnection:interface-name:
@@ -1984,7 +2164,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                        .from_dbus_is_full                = TRUE,
                                        .from_dbus_direct_allow_transform = TRUE),
         NMSettingConnectionPrivate,
-        interface_name);
+        interface_name,
+        .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingConnection:type:
@@ -2011,7 +2192,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingConnectionPrivate,
                                               type,
-                                              .direct_string_is_refstr = TRUE);
+                                              .direct_string_is_refstr   = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingConnection:permissions:
@@ -2038,12 +2220,11 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * example: USERS="joe bob"
      * ---end---
      */
-    obj_properties[PROP_PERMISSIONS] =
-        g_param_spec_boxed(NM_SETTING_CONNECTION_PERMISSIONS,
-                           "",
-                           "",
-                           G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_CONNECTION_PERMISSIONS,
+                                                    PROP_PERMISSIONS,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSettingConnection:autoconnect:
@@ -2244,12 +2425,15 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                               NM_SETTING_PARAM_FUZZY_IGNORE
                                                   | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY,
                                               NMSettingConnectionPrivate,
-                                              zone);
+                                              zone,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingConnection:master:
      *
      * Interface name of the master device or UUID of the master connection.
+     *
+     * Deprecated 1.46. Use #NMSettingConnection:controller instead, this is just an alias.
      **/
     /* ---ifcfg-rh---
      * property: master
@@ -2260,14 +2444,46 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *   for compatibility with legacy tooling.
      * ---end---
      */
-    _nm_setting_property_define_direct_string(properties_override,
-                                              obj_properties,
-                                              NM_SETTING_CONNECTION_MASTER,
-                                              PROP_MASTER,
-                                              NM_SETTING_PARAM_FUZZY_IGNORE
-                                                  | NM_SETTING_PARAM_INFERRABLE,
-                                              NMSettingConnectionPrivate,
-                                              master);
+    prop_idx = _nm_setting_property_define_direct_string_full(
+        properties_override,
+        obj_properties,
+        NM_SETTING_CONNECTION_MASTER,
+        PROP_MASTER,
+        NM_SETTING_PARAM_INFERRABLE | NM_SETTING_PARAM_FUZZY_IGNORE,
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING,
+                                       .direct_type   = NM_VALUE_TYPE_STRING,
+                                       .compare_fcn   = _nm_setting_property_compare_fcn_direct,
+                                       .to_dbus_fcn   = _nm_setting_property_to_dbus_fcn_direct,
+                                       .from_dbus_fcn = _nm_setting_connection_master_from_dbus, ),
+        NMSettingConnectionPrivate,
+        controller,
+        .direct_string_allow_empty = TRUE,
+        .is_deprecated             = TRUE);
+
+    /**
+     * NMSettingConnection:controller:
+     *
+     * Interface name of the controller device or UUID of the controller connection.
+     **/
+    _nm_setting_property_define_direct_string_full(
+        properties_override,
+        obj_properties,
+        NM_SETTING_CONNECTION_CONTROLLER,
+        PROP_CONTROLLER,
+        NM_SETTING_PARAM_INFERRABLE | NM_SETTING_PARAM_FUZZY_IGNORE,
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING,
+                                       .direct_type = NM_VALUE_TYPE_STRING,
+                                       .compare_fcn = _nm_setting_property_compare_fcn_direct,
+                                       .to_dbus_fcn = _nm_setting_connection_controller_to_dbus,
+                                       .from_dbus_fcn =
+                                           _nm_setting_connection_controller_from_dbus),
+        NMSettingConnectionPrivate,
+        controller,
+        .direct_string_allow_empty = TRUE,
+        .direct_also_notify        = obj_properties[PROP_MASTER]);
+
+    nm_g_array_index(properties_override, NMSettInfoProperty, prop_idx).direct_also_notify =
+        obj_properties[PROP_CONTROLLER];
 
     /**
      * NMSettingConnection:slave-type:
@@ -2275,6 +2491,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * Setting name of the device type of this slave's master connection (eg,
      * %NM_SETTING_BOND_SETTING_NAME), or %NULL if this connection is not a
      * slave.
+     *
+     * Deprecated 1.46. Use #NMSettingConnection:port-type instead, this is just an alias.
      **/
     /* ---ifcfg-rh---
      * property: slave-type
@@ -2286,14 +2504,51 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *   and BRIDGE_UUID for bridging.
      * ---end---
      */
-    _nm_setting_property_define_direct_string(properties_override,
-                                              obj_properties,
-                                              NM_SETTING_CONNECTION_SLAVE_TYPE,
-                                              PROP_SLAVE_TYPE,
-                                              NM_SETTING_PARAM_FUZZY_IGNORE
-                                                  | NM_SETTING_PARAM_INFERRABLE,
-                                              NMSettingConnectionPrivate,
-                                              slave_type);
+    prop_idx = _nm_setting_property_define_direct_string_full(
+        properties_override,
+        obj_properties,
+        NM_SETTING_CONNECTION_SLAVE_TYPE,
+        PROP_SLAVE_TYPE,
+        NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE,
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING,
+                                       .direct_type = NM_VALUE_TYPE_STRING,
+                                       .compare_fcn = _nm_setting_property_compare_fcn_direct,
+                                       .to_dbus_fcn = _nm_setting_property_to_dbus_fcn_direct,
+                                       .from_dbus_fcn =
+                                           _nm_setting_connection_slave_type_from_dbus, ),
+        NMSettingConnectionPrivate,
+        port_type,
+        .is_deprecated             = 1,
+        .direct_string_allow_empty = TRUE);
+
+    /**
+     * NMSettingConnection:port-type:
+     *
+     * Setting name of the device type of this port's controller connection (eg,
+     * %NM_SETTING_BOND_SETTING_NAME), or %NULL if this connection is not a
+     * port.
+     *
+     * Since: 1.46
+     **/
+    _nm_setting_property_define_direct_string_full(
+        properties_override,
+        obj_properties,
+        NM_SETTING_CONNECTION_PORT_TYPE,
+        PROP_PORT_TYPE,
+        NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE,
+        NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING,
+                                       .direct_type = NM_VALUE_TYPE_STRING,
+                                       .compare_fcn = _nm_setting_property_compare_fcn_direct,
+                                       .to_dbus_fcn = _nm_setting_connection_port_type_to_dbus,
+                                       .from_dbus_fcn =
+                                           _nm_setting_connection_port_type_from_dbus, ),
+        NMSettingConnectionPrivate,
+        port_type,
+        .direct_string_allow_empty = TRUE,
+        .direct_also_notify        = obj_properties[PROP_SLAVE_TYPE]);
+
+    nm_g_array_index(properties_override, NMSettInfoProperty, prop_idx).direct_also_notify =
+        obj_properties[PROP_PORT_TYPE];
 
     /**
      * NMSettingConnection:autoconnect-slaves:
@@ -2740,7 +2995,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                               PROP_MUD_URL,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingConnectionPrivate,
-                                              mud_url);
+                                              mud_url,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingConnection:wait-activation-delay:
@@ -2779,5 +3035,5 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                              NM_META_SETTING_TYPE_CONNECTION,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingConnection, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-dcb.c b/src/libnm-core-impl/nm-setting-dcb.c
index d6890891..c59ee8b4 100644
--- a/src/libnm-core-impl/nm-setting-dcb.c
+++ b/src/libnm-core-impl/nm-setting-dcb.c
@@ -71,20 +71,17 @@ typedef struct {
  * Data Center Bridging Settings
  */
 struct _NMSettingDcb {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting           parent;
+    NMSettingDcbPrivate _priv;
 };
 
 struct _NMSettingDcbClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingDcb, nm_setting_dcb, NM_TYPE_SETTING)
 
-#define NM_SETTING_DCB_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_DCB, NMSettingDcbPrivate))
+#define NM_SETTING_DCB_GET_PRIVATE(o) _NM_GET_PRIVATE(o, NMSettingDcb, NM_IS_SETTING_DCB, NMSetting)
 
 /*****************************************************************************/
 
@@ -845,8 +842,6 @@ nm_setting_dcb_class_init(NMSettingDcbClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingDcbPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
 
@@ -925,7 +920,8 @@ nm_setting_dcb_class_init(NMSettingDcbClass *klass)
                                               PROP_APP_FCOE_MODE,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingDcbPrivate,
-                                              app_fcoe_mode);
+                                              app_fcoe_mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingDcb:app-iscsi-flags:
@@ -1230,5 +1226,5 @@ nm_setting_dcb_class_init(NMSettingDcbClass *klass)
                              NM_META_SETTING_TYPE_DCB,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingDcb, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-dummy.c b/src/libnm-core-impl/nm-setting-dummy.c
index bfc82aea..c37b8c08 100644
--- a/src/libnm-core-impl/nm-setting-dummy.c
+++ b/src/libnm-core-impl/nm-setting-dummy.c
@@ -28,13 +28,10 @@
  */
 struct _NMSettingDummy {
     NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
 };
 
 struct _NMSettingDummyClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingDummy, nm_setting_dummy, NM_TYPE_SETTING)
diff --git a/src/libnm-core-impl/nm-setting-ethtool.c b/src/libnm-core-impl/nm-setting-ethtool.c
index ab1ed0e2..06c9c12b 100644
--- a/src/libnm-core-impl/nm-setting-ethtool.c
+++ b/src/libnm-core-impl/nm-setting-ethtool.c
@@ -76,6 +76,37 @@ nm_ethtool_optname_is_ring(const char *optname)
 }
 
 /**
+ * nm_ethtool_optname_is_channels:
+ * @optname: (nullable): the option name to check
+ *
+ * Checks whether @optname is a valid option name for a channels setting.
+ *
+ * Returns: %TRUE, if @optname is valid
+ *
+ * Since: 1.46
+ */
+gboolean
+nm_ethtool_optname_is_channels(const char *optname)
+{
+    return optname && nm_ethtool_id_is_channels(nm_ethtool_id_get_by_name(optname));
+}
+
+/**
+ * nm_ethtool_optname_is_eee:
+ * @optname: (nullable): the option name to check
+ *
+ * Checks whether @optname is a valid option name for an eee setting.
+ *
+ * Returns: %TRUE, if @optname is valid
+ *
+ * Since: 1.46
+ */
+gboolean
+nm_ethtool_optname_is_eee(const char *optname)
+{
+    return optname && nm_ethtool_id_is_eee(nm_ethtool_id_get_by_name(optname));
+}
+/**
  * nm_ethtool_optname_is_pause:
  * @optname: (nullable): the option name to check
  *
diff --git a/src/libnm-core-impl/nm-setting-generic.c b/src/libnm-core-impl/nm-setting-generic.c
index ce212554..6623e71f 100644
--- a/src/libnm-core-impl/nm-setting-generic.c
+++ b/src/libnm-core-impl/nm-setting-generic.c
@@ -23,10 +23,6 @@
 
 /*****************************************************************************/
 
-typedef struct {
-    int dummy;
-} NMSettingGenericPrivate;
-
 /**
  * NMSettingGeneric:
  *
@@ -34,20 +30,14 @@ typedef struct {
  */
 struct _NMSettingGeneric {
     NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
 };
 
 struct _NMSettingGenericClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingGeneric, nm_setting_generic, NM_TYPE_SETTING)
 
-#define NM_SETTING_GENERIC_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_GENERIC, NMSettingGenericPrivate))
-
 /*****************************************************************************/
 
 static void
@@ -72,7 +62,5 @@ nm_setting_generic_class_init(NMSettingGenericClass *klass)
 {
     NMSettingClass *setting_class = NM_SETTING_CLASS(klass);
 
-    g_type_class_add_private(klass, sizeof(NMSettingGenericPrivate));
-
     _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_GENERIC, NULL, NULL, 0);
 }
diff --git a/src/libnm-core-impl/nm-setting-gsm.c b/src/libnm-core-impl/nm-setting-gsm.c
index 4dde515a..632b0ccc 100644
--- a/src/libnm-core-impl/nm-setting-gsm.c
+++ b/src/libnm-core-impl/nm-setting-gsm.c
@@ -65,20 +65,17 @@ typedef struct {
  * GSM-based Mobile Broadband Settings
  */
 struct _NMSettingGsm {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting           parent;
+    NMSettingGsmPrivate _priv;
 };
 
 struct _NMSettingGsmClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingGsm, nm_setting_gsm, NM_TYPE_SETTING)
 
-#define NM_SETTING_GSM_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_GSM, NMSettingGsmPrivate))
+#define NM_SETTING_GSM_GET_PRIVATE(o) _NM_GET_PRIVATE(o, NMSettingGsm, NM_IS_SETTING_GSM, NMSetting)
 
 /*****************************************************************************/
 
@@ -565,8 +562,6 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingGsmPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -607,7 +602,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
                                               number,
-                                              .is_deprecated = TRUE, );
+                                              .is_deprecated             = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:username:
@@ -622,7 +618,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_USERNAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
-                                              username);
+                                              username,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:password:
@@ -637,7 +634,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingGsmPrivate,
-                                              password);
+                                              password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:password-flags:
@@ -660,6 +658,10 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
      * is important to use the correct APN for the user's mobile broadband plan.
      * The APN may only be composed of the characters a-z, 0-9, ., and - per GSM
      * 03.60 Section 14.9.
+     *
+     * If the APN is unset (the default) then it may be detected based on
+     * "auto-config" setting. The property can be explicitly set to the
+     * empty string to prevent that and use no APN.
      **/
     _nm_setting_property_define_direct_string(properties_override,
                                               obj_properties,
@@ -668,7 +670,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
                                               apn,
-                                              .direct_set_string_strip = TRUE);
+                                              .direct_set_string_strip   = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:network-id:
@@ -686,7 +689,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
                                               network_id,
-                                              .direct_set_string_strip = TRUE);
+                                              .direct_set_string_strip   = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:pin:
@@ -701,7 +705,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_PIN,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingGsmPrivate,
-                                              pin);
+                                              pin,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:pin-flags:
@@ -745,7 +750,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_DEVICE_ID,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
-                                              device_id);
+                                              device_id,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:sim-id:
@@ -763,7 +769,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_SIM_ID,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
-                                              sim_id);
+                                              sim_id,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:sim-operator-id:
@@ -782,7 +789,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_SIM_OPERATOR_ID,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
-                                              sim_operator_id);
+                                              sim_operator_id,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingGsm:mtu:
@@ -836,7 +844,8 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               PROP_INITIAL_EPS_APN,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingGsmPrivate,
-                                              initial_eps_apn);
+                                              initial_eps_apn,
+                                              .direct_string_allow_empty = TRUE);
 
     /* Ignore incoming deprecated properties */
     _nm_properties_override_dbus(properties_override,
@@ -854,5 +863,5 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                              NM_META_SETTING_TYPE_GSM,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingGsm, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-hsr.c b/src/libnm-core-impl/nm-setting-hsr.c
new file mode 100644
index 00000000..d9a662b1
--- /dev/null
+++ b/src/libnm-core-impl/nm-setting-hsr.c
@@ -0,0 +1,266 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2023 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-setting-hsr.h"
+
+#include <linux/if_ether.h>
+
+#include "nm-connection-private.h"
+#include "nm-utils.h"
+#include "nm-utils-private.h"
+
+/**
+ * SECTION:nm-setting-hsr
+ * @short_description: Describes connection properties for HSR/PRP interfaces
+ *
+ * The #NMSettingHsr object is a #NMSetting subclass that describes properties
+ * necessary for HSR/PRP connections.
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE(NMSettingHsr, PROP_PORT1, PROP_PORT2, PROP_MULTICAST_SPEC, PROP_PRP, );
+
+typedef struct {
+    char   *port1;
+    char   *port2;
+    guint32 multicast_spec;
+    bool    prp;
+} NMSettingHsrPrivate;
+
+/**
+ * NMSettingHsr:
+ *
+ * HSR/PRP Settings
+ */
+struct _NMSettingHsr {
+    NMSetting           parent;
+    NMSettingHsrPrivate _priv;
+};
+
+struct _NMSettingHsrClass {
+    NMSettingClass parent;
+};
+
+G_DEFINE_TYPE(NMSettingHsr, nm_setting_hsr, NM_TYPE_SETTING)
+
+#define NM_SETTING_HSR_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMSettingHsr, NM_IS_SETTING_HSR, NMSetting)
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_hsr_get_port1:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:port1 property of the setting
+ *
+ * Since: 1.46
+ **/
+const char *
+nm_setting_hsr_get_port1(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), NULL);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->port1;
+}
+
+/**
+ * nm_setting_hsr_get_port2:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:port2 property of the setting
+ *
+ * Since: 1.46
+ **/
+const char *
+nm_setting_hsr_get_port2(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), NULL);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->port2;
+}
+
+/**
+ * nm_setting_hsr_get_multicast_spec:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:multicast_spec property of the setting
+ *
+ * Since: 1.46
+ **/
+guint32
+nm_setting_hsr_get_multicast_spec(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), 0);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->multicast_spec;
+}
+
+/**
+ * nm_setting_hsr_get_prp:
+ * @setting: the #NMSettingHsr
+ *
+ * Returns: the #NMSettingHsr:prp property of the setting
+ *
+ * Since: 1.46
+ **/
+gboolean
+nm_setting_hsr_get_prp(NMSettingHsr *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_HSR(setting), FALSE);
+
+    return NM_SETTING_HSR_GET_PRIVATE(setting)->prp;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify(NMSetting *setting, NMConnection *connection, GError **error)
+{
+    NMSettingHsrPrivate *priv = NM_SETTING_HSR_GET_PRIVATE(setting);
+
+    if (!priv->port1) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                    _("property is not specified"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_HSR_SETTING_NAME, NM_SETTING_HSR_PORT1);
+        return FALSE;
+    }
+    if (!nm_utils_ifname_valid(priv->port1, NMU_IFACE_KERNEL, NULL)) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("'%s' is not a valid interface name"),
+                    priv->port1);
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_HSR_SETTING_NAME, NM_SETTING_HSR_PORT1);
+        return FALSE;
+    }
+
+    if (!priv->port2) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                    _("property is not specified"));
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_HSR_SETTING_NAME, NM_SETTING_HSR_PORT2);
+        return FALSE;
+    }
+    if (!nm_utils_ifname_valid(priv->port2, NMU_IFACE_KERNEL, NULL)) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("'%s' is not a valid interface name"),
+                    priv->port2);
+        g_prefix_error(error, "%s.%s: ", NM_SETTING_HSR_SETTING_NAME, NM_SETTING_HSR_PORT2);
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_hsr_init(NMSettingHsr *setting)
+{}
+
+/**
+ * nm_setting_hsr_new:
+ *
+ * Creates a new #NMSettingHsr object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingHsr object
+ *
+ * Since: 1.46
+ **/
+NMSetting *
+nm_setting_hsr_new(void)
+{
+    return g_object_new(NM_TYPE_SETTING_HSR, NULL);
+}
+
+static void
+nm_setting_hsr_class_init(NMSettingHsrClass *klass)
+{
+    GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
+    NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
+    GArray         *properties_override = _nm_sett_info_property_override_create_array();
+
+    object_class->get_property = _nm_setting_property_get_property_direct;
+    object_class->set_property = _nm_setting_property_set_property_direct;
+
+    setting_class->verify = verify;
+
+    /**
+      * NMSettingHsr:port1:
+      *
+      * The port1 interface name of the HSR. This property is mandatory.
+      *
+      * Since: 1.46
+      **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_HSR_PORT1,
+                                              PROP_PORT1,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingHsr,
+                                              _priv.port1);
+
+    /**
+      * NMSettingHsr:port2:
+      *
+      * The port2 interface name of the HSR. This property is mandatory.
+      *
+      * Since: 1.46
+      **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_HSR_PORT2,
+                                              PROP_PORT2,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingHsr,
+                                              _priv.port2);
+
+    /**
+      * NMSettingHsr:multicast-spec:
+      *
+      * The last byte of supervision address.
+      *
+      * Since: 1.46
+      **/
+    _nm_setting_property_define_direct_uint32(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_HSR_MULTICAST_SPEC,
+                                              PROP_MULTICAST_SPEC,
+                                              0,
+                                              G_MAXUINT8,
+                                              0,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingHsr,
+                                              _priv.multicast_spec);
+
+    /**
+      * NMSettingHsr:prp:
+      *
+      * The protocol used by the interface, whether it is PRP or HSR.
+      *
+      * Since: 1.46
+      **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_HSR_PRP,
+                                               PROP_PRP,
+                                               FALSE,
+                                               NM_SETTING_PARAM_INFERRABLE,
+                                               NMSettingHsr,
+                                               _priv.prp);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    _nm_setting_class_commit(setting_class, NM_META_SETTING_TYPE_HSR, NULL, properties_override, 0);
+}
diff --git a/src/libnm-core-impl/nm-setting-infiniband.c b/src/libnm-core-impl/nm-setting-infiniband.c
index a0d5578c..203e4b2b 100644
--- a/src/libnm-core-impl/nm-setting-infiniband.c
+++ b/src/libnm-core-impl/nm-setting-infiniband.c
@@ -47,20 +47,18 @@ typedef struct {
  * Infiniband Settings
  */
 struct _NMSettingInfiniband {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                  parent;
+    NMSettingInfinibandPrivate _priv;
 };
 
 struct _NMSettingInfinibandClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingInfiniband, nm_setting_infiniband, NM_TYPE_SETTING)
 
 #define NM_SETTING_INFINIBAND_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_INFINIBAND, NMSettingInfinibandPrivate))
+    _NM_GET_PRIVATE(o, NMSettingInfiniband, NM_IS_SETTING_INFINIBAND, NMSetting)
 
 /*****************************************************************************/
 
@@ -332,8 +330,6 @@ nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingInfinibandPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -418,7 +414,8 @@ nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
                                               PROP_TRANSPORT_MODE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingInfinibandPrivate,
-                                              transport_mode);
+                                              transport_mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingInfiniband:p-key:
@@ -484,7 +481,8 @@ nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingInfinibandPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -492,5 +490,5 @@ nm_setting_infiniband_class_init(NMSettingInfinibandClass *klass)
                              NM_META_SETTING_TYPE_INFINIBAND,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingInfiniband, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index 727a4bae..bfebe7d1 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -52,6 +52,8 @@ const NMUtilsDNSOptionDesc _nm_utils_dns_option_descs[] = {
     {NM_SETTING_DNS_OPTION_NO_RELOAD, FALSE, FALSE},
     {NM_SETTING_DNS_OPTION_TRUST_AD, FALSE, FALSE},
     {NM_SETTING_DNS_OPTION_NO_AAAA, FALSE, FALSE},
+    {NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_EDNS0, FALSE, FALSE},
+    {NM_SETTING_DNS_OPTION_INTERNAL_NO_ADD_TRUST_AD, FALSE, FALSE},
     {NULL, FALSE, FALSE}};
 
 static char *
@@ -4014,7 +4016,7 @@ _NM_SETTING_IP_CONFIG_GET_PRIVATE(NMSettingIPConfig *self)
 
     klass = NM_SETTING_IP_CONFIG_GET_CLASS(self);
 
-    nm_assert(klass->private_offset < 0);
+    nm_assert(klass->private_offset > 0);
 
     return (gpointer) (((char *) ((gpointer) self)) + klass->private_offset);
 }
@@ -4229,7 +4231,7 @@ nm_setting_ip_config_get_num_dns_searches(NMSettingIPConfig *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0);
 
-    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_search->len;
+    return nm_g_array_len(NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_search.arr);
 }
 
 /**
@@ -4237,19 +4239,18 @@ nm_setting_ip_config_get_num_dns_searches(NMSettingIPConfig *setting)
  * @setting: the #NMSettingIPConfig
  * @idx: index number of the DNS search domain to return
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the DNS search domain at index @idx
  **/
 const char *
 nm_setting_ip_config_get_dns_search(NMSettingIPConfig *setting, int idx)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL);
 
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    g_return_val_if_fail(idx >= 0 && idx < priv->dns_search->len, NULL);
-
-    return priv->dns_search->pdata[idx];
+    return nm_strvarray_get_idxnull_or_greturn(
+        NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_search.arr,
+        idx);
 }
 
 /**
@@ -4266,19 +4267,16 @@ gboolean
 nm_setting_ip_config_add_dns_search(NMSettingIPConfig *setting, const char *dns_search)
 {
     NMSettingIPConfigPrivate *priv;
-    guint                     i;
 
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE);
     g_return_val_if_fail(dns_search != NULL, FALSE);
     g_return_val_if_fail(dns_search[0] != '\0', FALSE);
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    for (i = 0; i < priv->dns_search->len; i++) {
-        if (!strcmp(dns_search, priv->dns_search->pdata[i]))
-            return FALSE;
-    }
 
-    g_ptr_array_add(priv->dns_search, g_strdup(dns_search));
+    if (!nm_strvarray_ensure_and_add_unique(&priv->dns_search.arr, dns_search))
+        return FALSE;
+
     _notify(setting, PROP_DNS_SEARCH);
     return TRUE;
 }
@@ -4298,9 +4296,10 @@ nm_setting_ip_config_remove_dns_search(NMSettingIPConfig *setting, int idx)
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    g_return_if_fail(idx >= 0 && idx < priv->dns_search->len);
 
-    g_ptr_array_remove_index(priv->dns_search, idx);
+    g_return_if_fail(idx >= 0 && idx < nm_g_array_len(priv->dns_search.arr));
+
+    nm_strvarray_remove_index(priv->dns_search.arr, idx);
     _notify(setting, PROP_DNS_SEARCH);
 }
 
@@ -4317,21 +4316,18 @@ gboolean
 nm_setting_ip_config_remove_dns_search_by_value(NMSettingIPConfig *setting, const char *dns_search)
 {
     NMSettingIPConfigPrivate *priv;
-    guint                     i;
 
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), FALSE);
     g_return_val_if_fail(dns_search != NULL, FALSE);
     g_return_val_if_fail(dns_search[0] != '\0', FALSE);
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    for (i = 0; i < priv->dns_search->len; i++) {
-        if (!strcmp(dns_search, priv->dns_search->pdata[i])) {
-            g_ptr_array_remove_index(priv->dns_search, i);
-            _notify(setting, PROP_DNS_SEARCH);
-            return TRUE;
-        }
-    }
-    return FALSE;
+
+    if (!nm_strvarray_remove_first(priv->dns_search.arr, dns_search))
+        return FALSE;
+
+    _notify(setting, PROP_DNS_SEARCH);
+    return TRUE;
 }
 
 /**
@@ -4343,16 +4339,16 @@ nm_setting_ip_config_remove_dns_search_by_value(NMSettingIPConfig *setting, cons
 void
 nm_setting_ip_config_clear_dns_searches(NMSettingIPConfig *setting)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
 
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-
-    if (priv->dns_search->len != 0) {
-        g_ptr_array_set_size(priv->dns_search, 0);
+    if (nm_strvarray_clear(&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_search.arr))
         _notify(setting, PROP_DNS_SEARCH);
-    }
+}
+
+static gssize
+_dns_option_find_idx_garray(const GArray *arr, const char *option)
+{
+    return _nm_utils_dns_option_find_idx(nm_g_array_data(arr), nm_g_array_len(arr), option);
 }
 
 /**
@@ -4366,13 +4362,9 @@ nm_setting_ip_config_clear_dns_searches(NMSettingIPConfig *setting)
 guint
 nm_setting_ip_config_get_num_dns_options(NMSettingIPConfig *setting)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0);
 
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-
-    return priv->dns_options ? priv->dns_options->len : 0;
+    return nm_g_array_len(NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_options.arr);
 }
 
 /**
@@ -4394,7 +4386,7 @@ nm_setting_ip_config_has_dns_options(NMSettingIPConfig *setting)
 {
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0);
 
-    return !!NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_options;
+    return !!NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_options.arr;
 }
 
 /**
@@ -4402,6 +4394,8 @@ nm_setting_ip_config_has_dns_options(NMSettingIPConfig *setting)
  * @setting: the #NMSettingIPConfig
  * @idx: index number of the DNS option
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the DNS option at index @idx
  *
  * Since: 1.2
@@ -4409,15 +4403,11 @@ nm_setting_ip_config_has_dns_options(NMSettingIPConfig *setting)
 const char *
 nm_setting_ip_config_get_dns_option(NMSettingIPConfig *setting, guint idx)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL);
 
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    g_return_val_if_fail(priv->dns_options, NULL);
-    g_return_val_if_fail(idx < priv->dns_options->len, NULL);
-
-    return priv->dns_options->pdata[idx];
+    return nm_strvarray_get_idxnull_or_greturn(
+        NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dns_options.arr,
+        idx);
 }
 
 /**
@@ -4437,14 +4427,14 @@ nm_setting_ip_config_next_valid_dns_option(NMSettingIPConfig *setting, guint idx
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
 
-    if (!priv->dns_options)
+    if (!priv->dns_options.arr)
         return -1;
 
-    for (; idx < priv->dns_options->len; idx++) {
-        if (_nm_utils_dns_option_validate(priv->dns_options->pdata[idx],
+    for (; idx < priv->dns_options.arr->len; idx++) {
+        if (_nm_utils_dns_option_validate(nm_strvarray_get_idx(priv->dns_options.arr, idx),
                                           NULL,
                                           NULL,
-                                          NM_IS_SETTING_IP6_CONFIG(setting),
+                                          NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
                                           _nm_utils_dns_option_descs))
             return idx;
     }
@@ -4472,18 +4462,15 @@ nm_setting_ip_config_add_dns_option(NMSettingIPConfig *setting, const char *dns_
     g_return_val_if_fail(dns_option != NULL, FALSE);
     g_return_val_if_fail(dns_option[0] != '\0', FALSE);
 
-    if (!_nm_utils_dns_option_validate(dns_option, NULL, NULL, FALSE, NULL))
+    if (!_nm_utils_dns_option_validate(dns_option, NULL, NULL, AF_UNSPEC, NULL))
         return FALSE;
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    if (!priv->dns_options)
-        priv->dns_options = g_ptr_array_new_with_free_func(g_free);
-    else {
-        if (_nm_utils_dns_option_find_idx(priv->dns_options, dns_option) >= 0)
-            return FALSE;
-    }
 
-    g_ptr_array_add(priv->dns_options, g_strdup(dns_option));
+    if (_dns_option_find_idx_garray(priv->dns_options.arr, dns_option) >= 0)
+        return FALSE;
+
+    nm_strvarray_ensure_and_add(&priv->dns_options.arr, dns_option);
     _notify(setting, PROP_DNS_OPTIONS);
     return TRUE;
 }
@@ -4505,10 +4492,10 @@ nm_setting_ip_config_remove_dns_option(NMSettingIPConfig *setting, int idx)
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    g_return_if_fail(priv->dns_options);
-    g_return_if_fail(idx >= 0 && idx < priv->dns_options->len);
 
-    g_ptr_array_remove_index(priv->dns_options, idx);
+    g_return_if_fail(idx >= 0 && idx < nm_g_array_len(priv->dns_options.arr));
+
+    nm_strvarray_remove_index(priv->dns_options.arr, idx);
     _notify(setting, PROP_DNS_OPTIONS);
 }
 
@@ -4534,17 +4521,14 @@ nm_setting_ip_config_remove_dns_option_by_value(NMSettingIPConfig *setting, cons
     g_return_val_if_fail(dns_option[0] != '\0', FALSE);
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    if (!priv->dns_options)
-        return FALSE;
 
-    i = _nm_utils_dns_option_find_idx(priv->dns_options, dns_option);
-    if (i >= 0) {
-        g_ptr_array_remove_index(priv->dns_options, i);
-        _notify(setting, PROP_DNS_OPTIONS);
-        return TRUE;
-    }
+    i = _dns_option_find_idx_garray(priv->dns_options.arr, dns_option);
+    if (i < 0)
+        return FALSE;
 
-    return FALSE;
+    nm_strvarray_remove_index(priv->dns_options.arr, i);
+    _notify(setting, PROP_DNS_OPTIONS);
+    return TRUE;
 }
 
 /**
@@ -4565,18 +4549,17 @@ nm_setting_ip_config_clear_dns_options(NMSettingIPConfig *setting, gboolean is_s
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    if (!priv->dns_options) {
+    if (!priv->dns_options.arr) {
         if (!is_set)
             return;
-        priv->dns_options = g_ptr_array_new_with_free_func(g_free);
+        nm_strvarray_ensure(&priv->dns_options.arr);
     } else {
-        if (!is_set) {
-            g_ptr_array_unref(priv->dns_options);
-            priv->dns_options = NULL;
-        } else {
-            if (priv->dns_options->len == 0)
+        if (!is_set)
+            nm_strvarray_clear(&priv->dns_options.arr);
+        else {
+            if (priv->dns_options.arr->len == 0)
                 return;
-            g_ptr_array_set_size(priv->dns_options, 0);
+            g_array_set_size(priv->dns_options.arr, 0);
         }
     }
     _notify(setting, PROP_DNS_OPTIONS);
@@ -5361,8 +5344,10 @@ const char *const *
 nm_setting_ip_config_get_dhcp_reject_servers(NMSettingIPConfig *setting, guint *out_len)
 {
     g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL);
-    return nm_strvarray_get_strv(&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers,
-                                 out_len);
+
+    return nm_strvarray_get_strv_notnull(
+        NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers.arr,
+        out_len);
 }
 
 /**
@@ -5377,13 +5362,11 @@ nm_setting_ip_config_get_dhcp_reject_servers(NMSettingIPConfig *setting, guint *
 void
 nm_setting_ip_config_add_dhcp_reject_server(NMSettingIPConfig *setting, const char *server)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
-    g_return_if_fail(server != NULL);
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
+    g_return_if_fail(server);
 
-    nm_strvarray_add(nm_strvarray_ensure(&priv->dhcp_reject_servers), server);
+    nm_strvarray_ensure_and_add(&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers.arr,
+                                server);
     _notify(setting, PROP_DHCP_REJECT_SERVERS);
 }
 
@@ -5402,10 +5385,12 @@ nm_setting_ip_config_remove_dhcp_reject_server(NMSettingIPConfig *setting, guint
     NMSettingIPConfigPrivate *priv;
 
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
+
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    g_return_if_fail(priv->dhcp_reject_servers && idx < priv->dhcp_reject_servers->len);
 
-    g_array_remove_index(priv->dhcp_reject_servers, idx);
+    g_return_if_fail(idx < nm_g_array_len(priv->dhcp_reject_servers.arr));
+
+    nm_strvarray_remove_index(priv->dhcp_reject_servers.arr, idx);
     _notify(setting, PROP_DHCP_REJECT_SERVERS);
 }
 
@@ -5420,15 +5405,10 @@ nm_setting_ip_config_remove_dhcp_reject_server(NMSettingIPConfig *setting, guint
 void
 nm_setting_ip_config_clear_dhcp_reject_servers(NMSettingIPConfig *setting)
 {
-    NMSettingIPConfigPrivate *priv;
-
     g_return_if_fail(NM_IS_SETTING_IP_CONFIG(setting));
 
-    priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    if (nm_g_array_len(priv->dhcp_reject_servers) != 0) {
-        nm_clear_pointer(&priv->dhcp_reject_servers, g_array_unref);
+    if (nm_strvarray_clear(&NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_reject_servers.arr))
         _notify(setting, PROP_DHCP_REJECT_SERVERS);
-    }
 }
 
 /**
@@ -5718,7 +5698,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     }
 
     /* Validate reject servers */
-    if (priv->dhcp_reject_servers && priv->dhcp_reject_servers->len != 0) {
+    if (priv->dhcp_reject_servers.arr && priv->dhcp_reject_servers.arr->len > 0) {
         if (NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting) != AF_INET) {
             g_set_error_literal(error,
                                 NM_CONNECTION_ERROR,
@@ -5731,17 +5711,17 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             return FALSE;
         }
 
-        for (i = 0; i < priv->dhcp_reject_servers->len; i++) {
+        for (i = 0; i < priv->dhcp_reject_servers.arr->len; i++) {
             if (!nm_inet_parse_with_prefix_str(
                     NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
-                    nm_g_array_index(priv->dhcp_reject_servers, const char *, i),
+                    nm_g_array_index(priv->dhcp_reject_servers.arr, const char *, i),
                     NULL,
                     NULL)) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
                             _("'%s' is not a valid IP or subnet"),
-                            nm_g_array_index(priv->dhcp_reject_servers, const char *, i));
+                            nm_g_array_index(priv->dhcp_reject_servers.arr, const char *, i));
                 g_prefix_error(error,
                                "%s.%s: ",
                                nm_setting_get_name(setting),
@@ -5963,7 +5943,7 @@ dns_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 GArray *
 _nm_sett_info_property_override_create_array_ip_config(int addr_family)
 {
-    GArray *properties_override = _nm_sett_info_property_override_create_array();
+    GArray *properties_override = _nm_sett_info_property_override_create_array_sized(45);
 
     nm_assert_addr_family(addr_family);
 
@@ -5971,7 +5951,8 @@ _nm_sett_info_property_override_create_array_ip_config(int addr_family)
         properties_override,
         obj_properties[PROP_METHOD],
         &nm_sett_info_propert_type_direct_string,
-        .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, method));
+        .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, method),
+        .direct_string_allow_empty = TRUE);
 
     _nm_properties_override_gobj(
         properties_override,
@@ -5984,20 +5965,23 @@ _nm_sett_info_property_override_create_array_ip_config(int addr_family)
             .from_dbus_fcn = _nm_setting_property_from_dbus_fcn_direct_ip_config_gateway),
         .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, gateway),
         .direct_set_string_ip_address_addr_family                  = addr_family + 1,
-        .direct_set_string_ip_address_addr_family_map_zero_to_null = TRUE);
+        .direct_set_string_ip_address_addr_family_map_zero_to_null = TRUE,
+        .direct_string_allow_empty                                 = TRUE);
 
     _nm_properties_override_gobj(
         properties_override,
         obj_properties[PROP_DHCP_HOSTNAME],
         &nm_sett_info_propert_type_direct_string,
         .direct_offset =
-            NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, dhcp_hostname));
+            NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, dhcp_hostname),
+        .direct_string_allow_empty = TRUE);
 
     _nm_properties_override_gobj(
         properties_override,
         obj_properties[PROP_DHCP_IAID],
         &nm_sett_info_propert_type_direct_string,
-        .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, dhcp_iaid));
+        .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, dhcp_iaid),
+        .direct_string_allow_empty = TRUE);
 
     /* ---dbus---
      * property: routing-rules
@@ -6130,6 +6114,29 @@ _nm_sett_info_property_override_create_array_ip_config(int addr_family)
         .direct_offset =
             NM_STRUCT_OFFSET_ENSURE_TYPE(int, NMSettingIPConfigPrivate, replace_local_rule));
 
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_DNS_SEARCH],
+        &nm_sett_info_propert_type_direct_strv,
+        .direct_offset =
+            NM_STRUCT_OFFSET_ENSURE_TYPE(NMValueStrv, NMSettingIPConfigPrivate, dns_search));
+
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_DNS_OPTIONS],
+        &nm_sett_info_propert_type_direct_strv,
+        .direct_offset =
+            NM_STRUCT_OFFSET_ENSURE_TYPE(NMValueStrv, NMSettingIPConfigPrivate, dns_options),
+        .direct_strv_preserve_empty = TRUE, );
+
+    _nm_properties_override_gobj(properties_override,
+                                 obj_properties[PROP_DHCP_REJECT_SERVERS],
+                                 &nm_sett_info_propert_type_direct_strv,
+                                 .direct_offset =
+                                     NM_STRUCT_OFFSET_ENSURE_TYPE(NMValueStrv,
+                                                                  NMSettingIPConfigPrivate,
+                                                                  dhcp_reject_servers));
+
     return properties_override;
 }
 
@@ -6143,15 +6150,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 
     switch (prop_id) {
     case PROP_DNS:
-        g_value_take_boxed(value, _nm_utils_ptrarray_to_strv(priv->dns));
-        break;
-    case PROP_DNS_SEARCH:
-        g_value_take_boxed(value, _nm_utils_ptrarray_to_strv(priv->dns_search));
-        break;
-    case PROP_DNS_OPTIONS:
-        g_value_take_boxed(value,
-                           priv->dns_options ? _nm_utils_ptrarray_to_strv(priv->dns_options)
-                                             : NULL);
+        g_value_take_boxed(value, nm_strv_ptrarray_to_strv(priv->dns));
         break;
     case PROP_ADDRESSES:
         g_value_take_boxed(value,
@@ -6165,9 +6164,6 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
                                                 (NMUtilsCopyFunc) nm_ip_route_dup,
                                                 (GDestroyNotify) nm_ip_route_unref));
         break;
-    case PROP_DHCP_REJECT_SERVERS:
-        g_value_set_boxed(value, nm_strvarray_get_strv_non_empty(priv->dhcp_reject_servers, NULL));
-        break;
     default:
         _nm_setting_property_get_property_direct(object, prop_id, value, pspec);
         break;
@@ -6179,7 +6175,8 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 {
     NMSettingIPConfig        *setting = NM_SETTING_IP_CONFIG(object);
     NMSettingIPConfigPrivate *priv    = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
-    char                    **strv;
+    gs_unref_array GArray    *array   = NULL;
+    const char *const        *strv;
     guint                     i;
 
     switch (prop_id) {
@@ -6195,28 +6192,22 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
         }
         break;
     }
-    case PROP_DNS_SEARCH:
-        g_ptr_array_unref(priv->dns_search);
-        priv->dns_search = nm_strv_to_ptrarray(g_value_get_boxed(value));
-        break;
     case PROP_DNS_OPTIONS:
         strv = g_value_get_boxed(value);
-        if (!strv) {
-            if (priv->dns_options) {
-                g_ptr_array_unref(priv->dns_options);
-                priv->dns_options = NULL;
-            }
-        } else {
-            if (priv->dns_options)
-                g_ptr_array_set_size(priv->dns_options, 0);
-            else
-                priv->dns_options = g_ptr_array_new_with_free_func(g_free);
+        if (strv) {
+            nm_strvarray_ensure(&array);
             for (i = 0; strv[i]; i++) {
-                if (_nm_utils_dns_option_validate(strv[i], NULL, NULL, FALSE, NULL)
-                    && _nm_utils_dns_option_find_idx(priv->dns_options, strv[i]) < 0)
-                    g_ptr_array_add(priv->dns_options, g_strdup(strv[i]));
+                const char *str = strv[i];
+
+                if (_nm_utils_dns_option_validate(str, NULL, NULL, AF_UNSPEC, NULL)
+                    && _dns_option_find_idx_garray(array, str) < 0)
+                    nm_strvarray_add(array, str);
             }
         }
+        if (!nm_strvarray_equal(priv->dns_options.arr, array)) {
+            NM_SWAP(&priv->dns_options.arr, &array);
+            _notify(setting, PROP_DNS_OPTIONS);
+        }
         break;
     case PROP_ADDRESSES:
         g_ptr_array_unref(priv->addresses);
@@ -6230,9 +6221,6 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
                                             (NMUtilsCopyFunc) nm_ip_route_dup,
                                             (GDestroyNotify) nm_ip_route_unref);
         break;
-    case PROP_DHCP_REJECT_SERVERS:
-        nm_strvarray_set_strv(&priv->dhcp_reject_servers, g_value_get_boxed(value));
-        break;
     default:
         _nm_setting_property_set_property_direct(object, prop_id, value, pspec);
         break;
@@ -6246,9 +6234,8 @@ _nm_setting_ip_config_private_init(gpointer self, NMSettingIPConfigPrivate *priv
 {
     nm_assert(NM_IS_SETTING_IP_CONFIG(self));
 
-    priv->dns_search = g_ptr_array_new_with_free_func(g_free);
-    priv->addresses  = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
-    priv->routes     = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
+    priv->addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
+    priv->routes    = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
 }
 
 static void
@@ -6264,12 +6251,9 @@ finalize(GObject *object)
     NMSettingIPConfigPrivate *priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(self);
 
     nm_g_ptr_array_unref(priv->dns);
-    g_ptr_array_unref(priv->dns_search);
-    nm_g_ptr_array_unref(priv->dns_options);
     g_ptr_array_unref(priv->addresses);
     g_ptr_array_unref(priv->routes);
     nm_g_ptr_array_unref(priv->routing_rules);
-    nm_g_array_unref(priv->dhcp_reject_servers);
 
     G_OBJECT_CLASS(nm_setting_ip_config_parent_class)->finalize(object);
 }
@@ -6311,12 +6295,13 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * shared method must be configured on the interface which shares the internet
      * to a subnet, not on the uplink which is shared.
      **/
-    obj_properties[PROP_METHOD] = g_param_spec_string(
-        NM_SETTING_IP_CONFIG_METHOD,
-        "",
-        "",
-        NULL,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_METHOD] =
+        g_param_spec_string(NM_SETTING_IP_CONFIG_METHOD,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY
+                                | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dns:
@@ -6359,22 +6344,29 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                            "",
                            "",
                            G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dns-options:
      *
-     * Array of DNS options as described in man 5 resolv.conf.
+     * Array of DNS options to be added to resolv.conf.
      *
      * %NULL means that the options are unset and left at the default.
      * In this case NetworkManager will use default options. This is
      * distinct from an empty list of properties.
      *
-     * The currently supported options are "attempts", "debug", "edns0",
+     * The following options are directly added to resolv.conf: "attempts",
+     *  "debug", "edns0",
      * "inet6", "ip6-bytestring", "ip6-dotint", "ndots", "no-aaaa",
      * "no-check-names", "no-ip6-dotint", "no-reload", "no-tld-query",
      * "rotate", "single-request", "single-request-reopen", "timeout",
-     * "trust-ad", "use-vc".
+     * "trust-ad", "use-vc". See the resolv.conf(5) man page for a
+     * detailed description of these options.
+     *
+     * In addition, NetworkManager supports the special options "_no-add-edns0"
+     * and "_no-add-trust-ad". They are not added to resolv.conf, and can be
+     * used to prevent the automatic addition of options "edns0" and "trust-ad"
+     * when using caching DNS plugins (see below).
      *
      * The "trust-ad" setting is only honored if the profile contributes
      * name servers to resolv.conf, and if all contributing profiles have
@@ -6382,7 +6374,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      *
      * When using a caching DNS plugin (dnsmasq or systemd-resolved in
      * NetworkManager.conf) then "edns0" and "trust-ad" are automatically
-     * added.
+     * added, unless "_no-add-edns0" and "_no-add-trust-ad" are present.
      *
      * Since: 1.2
      **/
@@ -6391,7 +6383,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                            "",
                            "",
                            G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dns-priority:
@@ -6453,7 +6445,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                          G_MININT32,
                          G_MAXINT32,
                          0,
-                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:addresses: (type GPtrArray(NMIPAddress))
@@ -6484,14 +6476,15 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * for WireGuard interfaces, so usually it should not be set in that case. See
      * #NMSettingWireGuard:ip4-auto-default-route.
      **/
-    obj_properties[PROP_GATEWAY] = g_param_spec_string(
-        NM_SETTING_IP_CONFIG_GATEWAY,
-        "",
-        "",
-        NULL,
-        /* On D-Bus, the legacy property "addresses" contains the gateway.
-         * This was replaced by "address-data" and "gateway". */
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_GATEWAY] =
+        g_param_spec_string(NM_SETTING_IP_CONFIG_GATEWAY,
+                            "",
+                            "",
+                            NULL,
+                            /* On D-Bus, the legacy property "addresses" contains the gateway.
+                             * This was replaced by "address-data" and "gateway". */
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY
+                                | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:routes: (type GPtrArray(NMIPRoute))
@@ -6528,7 +6521,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                            -1,
                            G_MAXUINT32,
                            -1,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:route-table:
@@ -6551,14 +6544,15 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      *
      * Since: 1.10
      **/
-    obj_properties[PROP_ROUTE_TABLE] = g_param_spec_uint(
-        NM_SETTING_IP_CONFIG_ROUTE_TABLE,
-        "",
-        "",
-        0,
-        G_MAXUINT32,
-        0,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_ROUTE_TABLE] =
+        g_param_spec_uint(NM_SETTING_IP_CONFIG_ROUTE_TABLE,
+                          "",
+                          "",
+                          0,
+                          G_MAXUINT32,
+                          0,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY
+                              | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
     /**
      * NMSettingIPConfig:ignore-auto-routes:
      *
@@ -6571,7 +6565,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                              "",
                              "",
                              FALSE,
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:ignore-auto-dns:
@@ -6587,7 +6581,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                              "",
                              "",
                              FALSE,
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-hostname:
@@ -6602,7 +6596,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                             "",
                             "",
                             NULL,
-                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-send-hostname:
@@ -6618,7 +6612,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                              "",
                              "",
                              TRUE,
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:never-default:
@@ -6632,7 +6626,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                              "",
                              "",
                              FALSE,
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:may-fail:
@@ -6650,30 +6644,33 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                              "",
                              "",
                              TRUE,
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                             G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dad-timeout:
      *
-     * Timeout in milliseconds used to check for the presence of duplicate IP
-     * addresses on the network.  If an address conflict is detected, the
-     * activation will fail.  A zero value means that no duplicate address
-     * detection is performed, -1 means the default value (either configuration
-     * ipvx.dad-timeout override or zero).  A value greater than zero is a
-     * timeout in milliseconds.
+     * Maximum timeout in milliseconds used to check for the presence of duplicate
+     * IP addresses on the network.  If an address conflict is detected, the
+     * activation will fail. The property is currently implemented only for IPv4.
      *
-     * The property is currently implemented only for IPv4.
+     * A zero value means that no duplicate address detection is performed, -1 means
+     * the default value (either the value configured globally in NetworkManger.conf
+     * or 200ms).  A value greater than zero is a timeout in milliseconds.  Note that
+     * the time intervals are subject to randomization as per RFC 5227 and so the
+     * actual duration can be between half and the full time specified in this
+     * property.
      *
      * Since: 1.2
      **/
-    obj_properties[PROP_DAD_TIMEOUT] = g_param_spec_int(
-        NM_SETTING_IP_CONFIG_DAD_TIMEOUT,
-        "",
-        "",
-        -1,
-        NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
-        -1,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_DAD_TIMEOUT] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_DAD_TIMEOUT,
+                         "",
+                         "",
+                         -1,
+                         NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
+                         -1,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | NM_SETTING_PARAM_FUZZY_IGNORE
+                             | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-timeout:
@@ -6684,14 +6681,15 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      *
      * Set to 2147483647 (MAXINT32) for infinity.
      **/
-    obj_properties[PROP_DHCP_TIMEOUT] = g_param_spec_int(
-        NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
-        "",
-        "",
-        0,
-        G_MAXINT32,
-        0,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_DHCP_TIMEOUT] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_DHCP_TIMEOUT,
+                         "",
+                         "",
+                         0,
+                         G_MAXINT32,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | NM_SETTING_PARAM_FUZZY_IGNORE
+                             | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:required-timeout:
@@ -6716,14 +6714,15 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      *
      * Since: 1.34
      **/
-    obj_properties[PROP_REQUIRED_TIMEOUT] = g_param_spec_int(
-        NM_SETTING_IP_CONFIG_REQUIRED_TIMEOUT,
-        "",
-        "",
-        -1,
-        G_MAXINT32,
-        -1,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_REQUIRED_TIMEOUT] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_REQUIRED_TIMEOUT,
+                         "",
+                         "",
+                         -1,
+                         G_MAXINT32,
+                         -1,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | NM_SETTING_PARAM_FUZZY_IGNORE
+                             | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-iaid:
@@ -6758,7 +6757,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                             "",
                             "",
                             NULL,
-                            G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-hostname-flags:
@@ -6792,7 +6791,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                           0,
                           G_MAXUINT32,
                           NM_DHCP_HOSTNAME_FLAG_NONE,
-                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:dhcp-reject-servers:
@@ -6812,7 +6811,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                            "",
                            "",
                            G_TYPE_STRV,
-                           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:auto-route-ext-gw:
@@ -6831,7 +6830,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                           "",
                           NM_TYPE_TERNARY,
                           NM_TERNARY_DEFAULT,
-                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     /**
      * NMSettingIPConfig:replace-local-rule:
@@ -6847,7 +6846,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                           "",
                           NM_TYPE_TERNARY,
                           NM_TERNARY_DEFAULT,
-                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 }
diff --git a/src/libnm-core-impl/nm-setting-ip-tunnel.c b/src/libnm-core-impl/nm-setting-ip-tunnel.c
index 2deebf42..3512ff2e 100644
--- a/src/libnm-core-impl/nm-setting-ip-tunnel.c
+++ b/src/libnm-core-impl/nm-setting-ip-tunnel.c
@@ -55,20 +55,18 @@ typedef struct {
  * IP Tunneling Settings
  */
 struct _NMSettingIPTunnel {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                parent;
+    NMSettingIPTunnelPrivate _priv;
 };
 
 struct _NMSettingIPTunnelClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingIPTunnel, nm_setting_ip_tunnel, NM_TYPE_SETTING)
 
 #define NM_SETTING_IP_TUNNEL_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP_TUNNEL, NMSettingIPTunnelPrivate))
+    _NM_GET_PRIVATE(o, NMSettingIPTunnel, NM_IS_SETTING_IP_TUNNEL, NMSetting)
 
 /*****************************************************************************/
 
@@ -566,8 +564,6 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingIPTunnelPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -588,7 +584,8 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIPTunnelPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIPTunnel:mode:
@@ -634,7 +631,8 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                                               PROP_LOCAL,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIPTunnelPrivate,
-                                              local);
+                                              local,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIPTunnel:remote:
@@ -650,7 +648,8 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                                               PROP_REMOTE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIPTunnelPrivate,
-                                              remote);
+                                              remote,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIPTunnel:ttl
@@ -720,7 +719,8 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                                               PROP_INPUT_KEY,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIPTunnelPrivate,
-                                              input_key);
+                                              input_key,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIPTunnel:output-key:
@@ -736,7 +736,8 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                                               PROP_OUTPUT_KEY,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIPTunnelPrivate,
-                                              output_key);
+                                              output_key,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIPTunnel:encapsulation-limit:
@@ -861,5 +862,5 @@ nm_setting_ip_tunnel_class_init(NMSettingIPTunnelClass *klass)
                              NM_META_SETTING_TYPE_IP_TUNNEL,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingIPTunnel, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index 9ed39507..c79d0fdf 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -56,20 +56,18 @@ typedef struct {
  * IPv4 Settings
  */
 struct _NMSettingIP4Config {
-    NMSettingIPConfig parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSettingIPConfig         parent;
+    NMSettingIP4ConfigPrivate _priv;
 };
 
 struct _NMSettingIP4ConfigClass {
     NMSettingIPConfigClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingIP4Config, nm_setting_ip4_config, NM_TYPE_SETTING_IP_CONFIG)
 
 #define NM_SETTING_IP4_CONFIG_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP4_CONFIG, NMSettingIP4ConfigPrivate))
+    _NM_GET_PRIVATE(o, NMSettingIP4Config, NM_IS_SETTING_IP4_CONFIG, NMSettingIPConfig, NMSetting)
 
 /*****************************************************************************/
 
@@ -165,17 +163,17 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     g_assert(method);
 
     if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
-        if (nm_setting_ip_config_get_num_addresses(s_ip) == 0) {
+        if (nm_setting_ip_config_get_num_addresses(s_ip) == 0
+            && nm_setting_ip_config_get_num_routes(s_ip) == 0) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                        _("this property cannot be empty for '%s=%s'"),
-                        NM_SETTING_IP_CONFIG_METHOD,
+                        _("method '%s' requires at least an address or a route"),
                         method);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_IP4_CONFIG_SETTING_NAME,
-                           NM_SETTING_IP_CONFIG_ADDRESSES);
+                           NM_SETTING_IP_CONFIG_METHOD);
             return FALSE;
         }
     } else if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)
@@ -405,6 +403,8 @@ ip4_dns_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
     }
 
     strv = nm_utils_ip4_dns_from_variant(value);
+    nm_assert(strv);
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_DNS, strv, NULL);
     return TRUE;
 }
@@ -425,31 +425,24 @@ ip4_addresses_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *addrs   = NULL;
     gs_unref_variant GVariant   *s_ip4   = NULL;
-    gs_free const char         **labels  = NULL;
+    gs_unref_variant GVariant   *labels  = NULL;
     gs_free char                *gateway = NULL;
-    guint                        i;
-
-    /* FIXME: properly handle errors */
+    bool                         strict  = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     if (!_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) {
         *out_is_modified = FALSE;
         return TRUE;
     }
 
-    addrs = nm_utils_ip4_addresses_from_variant(value, &gateway);
-
     s_ip4 = g_variant_lookup_value(connection_dict,
                                    NM_SETTING_IP4_CONFIG_SETTING_NAME,
                                    NM_VARIANT_TYPE_SETTING);
-    if (g_variant_lookup(s_ip4, "address-labels", "^a&s", &labels)) {
-        for (i = 0; i < addrs->len && labels[i]; i++) {
-            if (*labels[i]) {
-                nm_ip_address_set_attribute(addrs->pdata[i],
-                                            NM_IP_ADDRESS_ATTRIBUTE_LABEL,
-                                            g_variant_new_string(labels[i]));
-            }
-        }
-    }
+
+    labels = g_variant_lookup_value(s_ip4, "address-labels", NULL);
+
+    addrs = _nm_utils_ip4_addresses_from_variant(value, labels, &gateway, strict, error);
+    if (!addrs)
+        return FALSE;
 
     g_object_set(setting,
                  NM_SETTING_IP_CONFIG_ADDRESSES,
@@ -514,9 +507,8 @@ ip4_address_data_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
 static gboolean
 ip4_address_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
-    gs_unref_ptrarray GPtrArray *addrs = NULL;
-
-    /* FIXME: properly handle errors */
+    gs_unref_ptrarray GPtrArray *addrs  = NULL;
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     /* Ignore 'address-data' if we're going to process 'addresses' */
     if (_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) {
@@ -524,7 +516,10 @@ ip4_address_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
         return TRUE;
     }
 
-    addrs = nm_utils_ip_addresses_from_variant(value, AF_INET);
+    addrs = _nm_utils_ip_addresses_from_variant(value, AF_INET, strict, error);
+    if (!addrs)
+        return FALSE;
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_ADDRESSES, addrs, NULL);
     return TRUE;
 }
@@ -542,15 +537,17 @@ static gboolean
 ip4_routes_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *routes = NULL;
-
-    /* FIXME: properly handle errors */
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     if (!_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) {
         *out_is_modified = FALSE;
         return TRUE;
     }
 
-    routes = nm_utils_ip4_routes_from_variant(value);
+    routes = _nm_utils_ip4_routes_from_variant(value, strict, error);
+    if (!routes)
+        return FALSE;
+
     g_object_set(setting, property_info->name, routes, NULL);
     return TRUE;
 }
@@ -571,8 +568,7 @@ static gboolean
 ip4_route_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *routes = NULL;
-
-    /* FIXME: properly handle errors */
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     /* Ignore 'route-data' if we're going to process 'routes' */
     if (_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) {
@@ -580,7 +576,10 @@ ip4_route_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
         return TRUE;
     }
 
-    routes = nm_utils_ip_routes_from_variant(value, AF_INET);
+    routes = _nm_utils_ip_routes_from_variant(value, AF_INET, strict, error);
+    if (!routes)
+        return FALSE;
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_ROUTES, routes, NULL);
     return TRUE;
 }
@@ -616,14 +615,12 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
     NMSettingIPConfigClass *setting_ip_config_class = NM_SETTING_IP_CONFIG_CLASS(klass);
     GArray *properties_override = _nm_sett_info_property_override_create_array_ip_config(AF_INET);
 
-    g_type_class_add_private(klass, sizeof(NMSettingIP4ConfigPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
     setting_class->verify = verify;
 
-    setting_ip_config_class->private_offset = g_type_class_get_instance_private_offset(klass);
+    setting_ip_config_class->private_offset = G_STRUCT_OFFSET(NMSettingIP4Config, _priv);
     setting_ip_config_class->is_ipv4        = TRUE;
     setting_ip_config_class->addr_family    = AF_INET;
 
@@ -703,6 +700,15 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      *   route2=7.7.0.0/16
      * ---end---
      */
+    /* ---keyfile---
+     * property: routes (attributes)
+     * variable: route1_options, route2_options, ...
+     * format: key=val[,key=val...]
+     * description: Attributes defined for the routes, if any. The supported
+     *   attributes are explained in ipv4.routes entry in `man nm-settings-nmcli`.
+     * example: route1_options=mtu=1000,onlink=true
+     * ---end---
+     */
     /* ---ifcfg-rh---
      * property: routes
      * variable: ADDRESS1, NETMASK1, GATEWAY1, METRIC1, OPTIONS1, ...
@@ -711,6 +717,16 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * ---end---
      */
 
+    /* ---keyfile---
+     * property: routing-rules
+     * variable: routing-rule1, routing-rule2, ...
+     * format: routing rule string
+     * description: Routing rules as defined with `ip rule add`, but with mandatory
+     *    fixed priority.
+     * example: routing-rule1=priority 5 from 192.167.4.0/24 table 45
+     * ---end---
+     */
+
     /* ---ifcfg-rh---
      * property: ignore-auto-routes
      * variable: PEERROUTES(+)
@@ -875,9 +891,20 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * stable-id, you may want to include the "${DEVICE}" or "${MAC}" specifier to get a
      * per-device key.
      *
-     * If unset, a globally configured default is used. If still unset, the default
-     * depends on the DHCP plugin.
+     * The special value "none" prevents any client identifier from being sent. Note that
+     * this is normally not recommended.
+     *
+     * If unset, a globally configured default from NetworkManager.conf is
+     * used. If still unset, the default depends on the DHCP plugin. The
+     * internal dhcp client will default to "mac" and the dhclient plugin will
+     * try to use one from its config file if present, or won't sent any
+     * client-id otherwise.
      **/
+    /* ---nmcli---
+     * property: dhcp-client-id
+     * special-values: mac, perm-mac, duid, ipv6-duid, stable, none
+     * ---end---
+     */
     /* ---ifcfg-rh---
      * property: dhcp-client-id
      * variable: DHCP_CLIENT_ID(+)
@@ -892,7 +919,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
                                               PROP_DHCP_CLIENT_ID,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingIP4ConfigPrivate,
-                                              dhcp_client_id);
+                                              dhcp_client_id,
+                                              .direct_string_allow_empty = TRUE);
 
     /* ---ifcfg-rh---
      * property: dad-timeout
@@ -945,7 +973,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
                                               PROP_DHCP_FQDN,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingIP4ConfigPrivate,
-                                              dhcp_fqdn);
+                                              dhcp_fqdn,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIP4Config:dhcp-vendor-class-identifier:
@@ -972,7 +1001,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
                                               PROP_DHCP_VENDOR_CLASS_IDENTIFIER,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingIP4ConfigPrivate,
-                                              dhcp_vendor_class_identifier);
+                                              dhcp_vendor_class_identifier,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIP4Config:link-local:
@@ -1288,5 +1318,5 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
                              NM_META_SETTING_TYPE_IP4_CONFIG,
                              NULL,
                              properties_override,
-                             setting_ip_config_class->private_offset);
+                             G_STRUCT_OFFSET(NMSettingIP4Config, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index 661a451e..4ad09932 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -65,20 +65,18 @@ typedef struct {
  * IPv6 Settings
  */
 struct _NMSettingIP6Config {
-    NMSettingIPConfig parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSettingIPConfig         parent;
+    NMSettingIP6ConfigPrivate _priv;
 };
 
 struct _NMSettingIP6ConfigClass {
     NMSettingIPConfigClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingIP6Config, nm_setting_ip6_config, NM_TYPE_SETTING_IP_CONFIG)
 
 #define NM_SETTING_IP6_CONFIG_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_IP6_CONFIG, NMSettingIP6ConfigPrivate))
+    _NM_GET_PRIVATE(o, NMSettingIP6Config, NM_IS_SETTING_IP6_CONFIG, NMSettingIPConfig, NMSetting)
 
 /*****************************************************************************/
 
@@ -230,17 +228,17 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     g_assert(method);
 
     if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL)) {
-        if (nm_setting_ip_config_get_num_addresses(s_ip) == 0) {
+        if (nm_setting_ip_config_get_num_addresses(s_ip) == 0
+            && nm_setting_ip_config_get_num_routes(s_ip) == 0) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                        _("this property cannot be empty for '%s=%s'"),
-                        NM_SETTING_IP_CONFIG_METHOD,
+                        _("method '%s' requires at least an address or a route"),
                         method);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_IP6_CONFIG_SETTING_NAME,
-                           NM_SETTING_IP_CONFIG_ADDRESSES);
+                           NM_SETTING_IP_CONFIG_METHOD);
             return FALSE;
         }
     } else if (NM_IN_STRSET(method,
@@ -437,14 +435,18 @@ ip6_dns_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
 static gboolean
 ip6_dns_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
-    gs_strfreev char **strv = NULL;
+    gs_strfreev char **strv   = NULL;
+    bool               strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     if (!_nm_setting_use_legacy_property(setting, connection_dict, "dns", "dns-data")) {
         *out_is_modified = FALSE;
         return TRUE;
     }
 
-    strv = nm_utils_ip6_dns_from_variant(value);
+    strv = _nm_utils_ip6_dns_from_variant(value, strict, error);
+    if (!strv)
+        return FALSE;
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_DNS, strv, NULL);
     return TRUE;
 }
@@ -465,13 +467,16 @@ ip6_addresses_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *addrs   = NULL;
     gs_free char                *gateway = NULL;
+    bool                         strict  = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     if (!_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) {
         *out_is_modified = FALSE;
         return TRUE;
     }
 
-    addrs = nm_utils_ip6_addresses_from_variant(value, &gateway);
+    addrs = _nm_utils_ip6_addresses_from_variant(value, &gateway, strict, error);
+    if (!addrs)
+        return FALSE;
 
     g_object_set(setting,
                  NM_SETTING_IP_CONFIG_ADDRESSES,
@@ -497,7 +502,8 @@ ip6_address_data_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil)
 static gboolean
 ip6_address_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
-    gs_unref_ptrarray GPtrArray *addrs = NULL;
+    gs_unref_ptrarray GPtrArray *addrs  = NULL;
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     /* Ignore 'address-data' if we're going to process 'addresses' */
     if (_nm_setting_use_legacy_property(setting, connection_dict, "addresses", "address-data")) {
@@ -505,7 +511,10 @@ ip6_address_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
         return TRUE;
     }
 
-    addrs = nm_utils_ip_addresses_from_variant(value, AF_INET6);
+    addrs = _nm_utils_ip_addresses_from_variant(value, AF_INET6, strict, error);
+    if (!addrs)
+        return FALSE;
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_ADDRESSES, addrs, NULL);
     return TRUE;
 }
@@ -523,13 +532,17 @@ static gboolean
 ip6_routes_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *routes = NULL;
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     if (!_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) {
         *out_is_modified = FALSE;
         return TRUE;
     }
 
-    routes = nm_utils_ip6_routes_from_variant(value);
+    routes = _nm_utils_ip6_routes_from_variant(value, strict, error);
+    if (!routes)
+        return FALSE;
+
     g_object_set(setting, property_info->name, routes, NULL);
     return TRUE;
 }
@@ -550,6 +563,7 @@ static gboolean
 ip6_route_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
 {
     gs_unref_ptrarray GPtrArray *routes = NULL;
+    bool                         strict = NM_FLAGS_HAS(parse_flags, NM_SETTING_PARSE_FLAGS_STRICT);
 
     /* Ignore 'route-data' if we're going to process 'routes' */
     if (_nm_setting_use_legacy_property(setting, connection_dict, "routes", "route-data")) {
@@ -557,7 +571,10 @@ ip6_route_data_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil)
         return TRUE;
     }
 
-    routes = nm_utils_ip_routes_from_variant(value, AF_INET6);
+    routes = _nm_utils_ip_routes_from_variant(value, AF_INET6, strict, error);
+    if (!routes)
+        return FALSE;
+
     g_object_set(setting, NM_SETTING_IP_CONFIG_ROUTES, routes, NULL);
     return TRUE;
 }
@@ -628,14 +645,12 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
     NMSettingIPConfigClass *setting_ip_config_class = NM_SETTING_IP_CONFIG_CLASS(klass);
     GArray *properties_override = _nm_sett_info_property_override_create_array_ip_config(AF_INET6);
 
-    g_type_class_add_private(klass, sizeof(NMSettingIP6ConfigPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
     setting_class->verify = verify;
 
-    setting_ip_config_class->private_offset = g_type_class_get_instance_private_offset(klass);
+    setting_ip_config_class->private_offset = G_STRUCT_OFFSET(NMSettingIP6Config, _priv);
     setting_ip_config_class->is_ipv4        = FALSE;
     setting_ip_config_class->addr_family    = AF_INET6;
 
@@ -714,6 +729,15 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * example: route1=2001:4860:4860::/64,2620:52:0:2219:222:68ff:fe11:5403
      * ---end---
      */
+    /* ---keyfile---
+     * property: routes (attributes)
+     * variable: route1_options, route2_options, ...
+     * format: key=val[,key=val...]
+     * description: Attributes defined for the routes, if any. The supported
+     *   attributes are explained in ipv6.routes entry in `man nm-settings-nmcli`.
+     * example: route1_options=mtu=1000,onlink=true
+     * ---end---
+     */
     /* ---ifcfg-rh---
      * property: routes
      * variable: (none)
@@ -722,6 +746,16 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * ---end---
      */
 
+    /* ---keyfile---
+     * property: routing-rules
+     * variable: routing-rule1, routing-rule2, ...
+     * format: routing rule string
+     * description: Routing rules as defined with `ip rule add`, but with mandatory
+     *    fixed priority.
+     * example: routing-rule1=priority 5 from 2001:4860:4860::/64 table 45
+     * ---end---
+     */
+
     /* ---ifcfg-rh---
      * property: ignore-auto-routes
      * variable: IPV6_PEERROUTES(+)
@@ -1035,7 +1069,8 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                                               PROP_TOKEN,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingIP6ConfigPrivate,
-                                              token);
+                                              token,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIP6Config:ra-timeout:
@@ -1138,7 +1173,8 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                                               PROP_DHCP_DUID,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingIP6ConfigPrivate,
-                                              dhcp_duid);
+                                              dhcp_duid,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingIP6Config:dhcp-pd-hint:
@@ -1166,8 +1202,9 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingIP6ConfigPrivate,
                                               dhcp_pd_hint,
-                                              .direct_hook.set_string_fcn =
-                                                  _set_string_fcn_dhcp_pd_hint);
+                                              .direct_set_fcn.set_string =
+                                                  _set_string_fcn_dhcp_pd_hint,
+                                              .direct_string_allow_empty = TRUE);
 
     /* IP6-specific property overrides */
 
@@ -1425,5 +1462,5 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
                              NM_META_SETTING_TYPE_IP6_CONFIG,
                              NULL,
                              properties_override,
-                             setting_ip_config_class->private_offset);
+                             G_STRUCT_OFFSET(NMSettingIP6Config, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-loopback.c b/src/libnm-core-impl/nm-setting-loopback.c
index 1451b411..b329c74a 100644
--- a/src/libnm-core-impl/nm-setting-loopback.c
+++ b/src/libnm-core-impl/nm-setting-loopback.c
@@ -125,7 +125,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
 
         if ((s_con = nm_connection_get_setting_connection(connection))) {
-            if (nm_setting_connection_get_slave_type(s_con)
+            if (nm_setting_connection_get_port_type(s_con)
                 || nm_setting_connection_get_master(s_con)) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
@@ -134,9 +134,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                 g_prefix_error(error,
                                "%s.%s: ",
                                NM_SETTING_CONNECTION_SETTING_NAME,
-                               nm_setting_connection_get_slave_type(s_con)
-                                   ? NM_SETTING_CONNECTION_SLAVE_TYPE
-                                   : NM_SETTING_CONNECTION_MASTER);
+                               nm_setting_connection_get_port_type(s_con)
+                                   ? NM_SETTING_CONNECTION_PORT_TYPE
+                                   : NM_SETTING_CONNECTION_CONTROLLER);
                 return FALSE;
             }
         }
diff --git a/src/libnm-core-impl/nm-setting-macsec.c b/src/libnm-core-impl/nm-setting-macsec.c
index bf48f049..f66fc52a 100644
--- a/src/libnm-core-impl/nm-setting-macsec.c
+++ b/src/libnm-core-impl/nm-setting-macsec.c
@@ -55,20 +55,18 @@ typedef struct {
  * MACSec Settings
  */
 struct _NMSettingMacsec {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting              parent;
+    NMSettingMacsecPrivate _priv;
 };
 
 struct _NMSettingMacsecClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingMacsec, nm_setting_macsec, NM_TYPE_SETTING)
 
 #define NM_SETTING_MACSEC_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_MACSEC, NMSettingMacsecPrivate))
+    _NM_GET_PRIVATE(o, NMSettingMacsec, NM_IS_SETTING_MACSEC, NMSetting)
 
 /*****************************************************************************/
 
@@ -306,7 +304,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             if (s_con) {
                 const char *master = NULL, *slave_type = NULL;
 
-                slave_type = nm_setting_connection_get_slave_type(s_con);
+                slave_type = nm_setting_connection_get_port_type(s_con);
                 if (!g_strcmp0(slave_type, NM_SETTING_MACSEC_SETTING_NAME))
                     master = nm_setting_connection_get_master(s_con);
 
@@ -316,7 +314,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                                 NM_CONNECTION_ERROR_INVALID_PROPERTY,
                                 _("'%s' value doesn't match '%s=%s'"),
                                 priv->parent,
-                                NM_SETTING_CONNECTION_MASTER,
+                                NM_SETTING_CONNECTION_CONTROLLER,
                                 master);
                     g_prefix_error(error,
                                    "%s.%s: ",
@@ -436,8 +434,6 @@ nm_setting_macsec_class_init(NMSettingMacsecClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingMacsecPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -460,7 +456,8 @@ nm_setting_macsec_class_init(NMSettingMacsecClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingMacsecPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingMacsec:mode:
@@ -511,7 +508,8 @@ nm_setting_macsec_class_init(NMSettingMacsecClass *klass)
                                               PROP_MKA_CAK,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingMacsecPrivate,
-                                              mka_cak);
+                                              mka_cak,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingMacsec:mka-cak-flags:
@@ -543,7 +541,8 @@ nm_setting_macsec_class_init(NMSettingMacsecClass *klass)
                                               PROP_MKA_CKN,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingMacsecPrivate,
-                                              mka_ckn);
+                                              mka_ckn,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingMacsec:port:
@@ -604,5 +603,5 @@ nm_setting_macsec_class_init(NMSettingMacsecClass *klass)
                              NM_META_SETTING_TYPE_MACSEC,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingMacsec, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-macvlan.c b/src/libnm-core-impl/nm-setting-macvlan.c
index d26a8086..2adecdfe 100644
--- a/src/libnm-core-impl/nm-setting-macvlan.c
+++ b/src/libnm-core-impl/nm-setting-macvlan.c
@@ -40,20 +40,18 @@ typedef struct {
  * MAC VLAN Settings
  */
 struct _NMSettingMacvlan {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting               parent;
+    NMSettingMacvlanPrivate _priv;
 };
 
 struct _NMSettingMacvlanClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingMacvlan, nm_setting_macvlan, NM_TYPE_SETTING)
 
 #define NM_SETTING_MACVLAN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_MACVLAN, NMSettingMacvlanPrivate))
+    _NM_GET_PRIVATE(o, NMSettingMacvlan, NM_IS_SETTING_MACVLAN, NMSetting)
 
 /*****************************************************************************/
 
@@ -207,8 +205,6 @@ nm_setting_macvlan_class_init(NMSettingMacvlanClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingMacvlanPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -230,7 +226,8 @@ nm_setting_macvlan_class_init(NMSettingMacvlanClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingMacvlanPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingMacvlan:mode:
@@ -289,5 +286,5 @@ nm_setting_macvlan_class_init(NMSettingMacvlanClass *klass)
                              NM_META_SETTING_TYPE_MACVLAN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingMacvlan, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-match.c b/src/libnm-core-impl/nm-setting-match.c
index 0ece65f1..7736b7c0 100644
--- a/src/libnm-core-impl/nm-setting-match.c
+++ b/src/libnm-core-impl/nm-setting-match.c
@@ -68,6 +68,8 @@ nm_setting_match_get_num_interface_names(NMSettingMatch *setting)
  * @setting: the #NMSettingMatch
  * @idx: index number of the DNS search domain to return
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the interface name at index @idx
  *
  * Since: 1.14
@@ -77,11 +79,7 @@ nm_setting_match_get_interface_name(NMSettingMatch *setting, int idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->interface_name.arr && idx >= 0
-                             && idx < setting->interface_name.arr->len,
-                         NULL);
-
-    return nm_strvarray_get_idx(setting->interface_name.arr, idx);
+    return nm_strvarray_get_idxnull_or_greturn(setting->interface_name.arr, idx);
 }
 
 /**
@@ -99,7 +97,7 @@ nm_setting_match_add_interface_name(NMSettingMatch *setting, const char *interfa
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(interface_name);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->interface_name.arr), interface_name);
+    nm_strvarray_ensure_and_add(&setting->interface_name.arr, interface_name);
     _notify(setting, PROP_INTERFACE_NAME);
 }
 
@@ -120,7 +118,7 @@ nm_setting_match_remove_interface_name(NMSettingMatch *setting, int idx)
     g_return_if_fail(setting->interface_name.arr && idx >= 0
                      && idx < setting->interface_name.arr->len);
 
-    g_array_remove_index(setting->interface_name.arr, idx);
+    nm_strvarray_remove_index(setting->interface_name.arr, idx);
     _notify(setting, PROP_INTERFACE_NAME);
 }
 
@@ -141,12 +139,11 @@ nm_setting_match_remove_interface_name_by_value(NMSettingMatch *setting, const c
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(interface_name, FALSE);
 
-    if (nm_strvarray_remove_first(setting->interface_name.arr, interface_name)) {
-        _notify(setting, PROP_INTERFACE_NAME);
-        return TRUE;
-    }
+    if (!nm_strvarray_remove_first(setting->interface_name.arr, interface_name))
+        return FALSE;
 
-    return FALSE;
+    _notify(setting, PROP_INTERFACE_NAME);
+    return TRUE;
 }
 
 /**
@@ -162,10 +159,8 @@ nm_setting_match_clear_interface_names(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->interface_name.arr) != 0) {
-        nm_clear_pointer(&setting->interface_name.arr, g_array_unref);
+    if (nm_strvarray_clear(&setting->interface_name.arr))
         _notify(setting, PROP_INTERFACE_NAME);
-    }
 }
 
 /**
@@ -187,7 +182,7 @@ nm_setting_match_get_interface_names(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->interface_name.arr, length);
+    return nm_strvarray_get_strv_notnull(setting->interface_name.arr, length);
 }
 
 /*****************************************************************************/
@@ -213,6 +208,8 @@ nm_setting_match_get_num_kernel_command_lines(NMSettingMatch *setting)
  * @setting: the #NMSettingMatch
  * @idx: index number of the kernel command line argument to return
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the kernel command line argument at index @idx
  *
  * Since: 1.26
@@ -222,11 +219,7 @@ nm_setting_match_get_kernel_command_line(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->kernel_command_line.arr
-                             && idx < setting->kernel_command_line.arr->len,
-                         NULL);
-
-    return nm_strvarray_get_idx(setting->kernel_command_line.arr, idx);
+    return nm_strvarray_get_idxnull_or_greturn(setting->kernel_command_line.arr, idx);
 }
 
 /**
@@ -244,7 +237,7 @@ nm_setting_match_add_kernel_command_line(NMSettingMatch *setting, const char *ke
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(kernel_command_line);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->kernel_command_line.arr), kernel_command_line);
+    nm_strvarray_ensure_and_add(&setting->kernel_command_line.arr, kernel_command_line);
     _notify(setting, PROP_KERNEL_COMMAND_LINE);
 }
 
@@ -265,7 +258,7 @@ nm_setting_match_remove_kernel_command_line(NMSettingMatch *setting, guint idx)
     g_return_if_fail(setting->kernel_command_line.arr
                      && idx < setting->kernel_command_line.arr->len);
 
-    g_array_remove_index(setting->kernel_command_line.arr, idx);
+    nm_strvarray_remove_index(setting->kernel_command_line.arr, idx);
     _notify(setting, PROP_KERNEL_COMMAND_LINE);
 }
 
@@ -287,12 +280,11 @@ nm_setting_match_remove_kernel_command_line_by_value(NMSettingMatch *setting,
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(kernel_command_line, FALSE);
 
-    if (nm_strvarray_remove_first(setting->kernel_command_line.arr, kernel_command_line)) {
-        _notify(setting, PROP_KERNEL_COMMAND_LINE);
-        return TRUE;
-    }
+    if (!nm_strvarray_remove_first(setting->kernel_command_line.arr, kernel_command_line))
+        return FALSE;
 
-    return FALSE;
+    _notify(setting, PROP_KERNEL_COMMAND_LINE);
+    return TRUE;
 }
 
 /**
@@ -308,10 +300,8 @@ nm_setting_match_clear_kernel_command_lines(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->kernel_command_line.arr) != 0) {
-        nm_clear_pointer(&setting->kernel_command_line.arr, g_array_unref);
+    if (nm_strvarray_clear(&setting->kernel_command_line.arr))
         _notify(setting, PROP_KERNEL_COMMAND_LINE);
-    }
 }
 
 /**
@@ -330,7 +320,7 @@ nm_setting_match_get_kernel_command_lines(NMSettingMatch *setting, guint *length
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->kernel_command_line.arr, length);
+    return nm_strvarray_get_strv_notnull(setting->kernel_command_line.arr, length);
 }
 
 /*****************************************************************************/
@@ -356,6 +346,8 @@ nm_setting_match_get_num_drivers(NMSettingMatch *setting)
  * @setting: the #NMSettingMatch
  * @idx: index number of the DNS search domain to return
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the driver at index @idx
  *
  * Since: 1.26
@@ -365,9 +357,7 @@ nm_setting_match_get_driver(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->driver.arr && idx < setting->driver.arr->len, NULL);
-
-    return nm_strvarray_get_idx(setting->driver.arr, idx);
+    return nm_strvarray_get_idxnull_or_greturn(setting->driver.arr, idx);
 }
 
 /**
@@ -385,7 +375,7 @@ nm_setting_match_add_driver(NMSettingMatch *setting, const char *driver)
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(driver);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->driver.arr), driver);
+    nm_strvarray_ensure_and_add(&setting->driver.arr, driver);
     _notify(setting, PROP_DRIVER);
 }
 
@@ -405,7 +395,7 @@ nm_setting_match_remove_driver(NMSettingMatch *setting, guint idx)
 
     g_return_if_fail(setting->driver.arr && idx < setting->driver.arr->len);
 
-    g_array_remove_index(setting->driver.arr, idx);
+    nm_strvarray_remove_index(setting->driver.arr, idx);
     _notify(setting, PROP_DRIVER);
 }
 
@@ -426,12 +416,11 @@ nm_setting_match_remove_driver_by_value(NMSettingMatch *setting, const char *dri
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(driver, FALSE);
 
-    if (nm_strvarray_remove_first(setting->driver.arr, driver)) {
-        _notify(setting, PROP_DRIVER);
-        return TRUE;
-    }
+    if (!nm_strvarray_remove_first(setting->driver.arr, driver))
+        return FALSE;
 
-    return FALSE;
+    _notify(setting, PROP_DRIVER);
+    return TRUE;
 }
 
 /**
@@ -447,10 +436,8 @@ nm_setting_match_clear_drivers(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->driver.arr) != 0) {
-        nm_clear_pointer(&setting->driver.arr, g_array_unref);
+    if (nm_strvarray_clear(&setting->driver.arr))
         _notify(setting, PROP_DRIVER);
-    }
 }
 
 /**
@@ -469,7 +456,7 @@ nm_setting_match_get_drivers(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->driver.arr, length);
+    return nm_strvarray_get_strv_notnull(setting->driver.arr, length);
 }
 
 /*****************************************************************************/
@@ -495,6 +482,8 @@ nm_setting_match_get_num_paths(NMSettingMatch *setting)
  * @setting: the #NMSettingMatch
  * @idx: index number of the path to return
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the path at index @idx
  *
  * Since: 1.26
@@ -504,9 +493,7 @@ nm_setting_match_get_path(NMSettingMatch *setting, guint idx)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    g_return_val_if_fail(setting->path.arr && idx < setting->path.arr->len, NULL);
-
-    return nm_strvarray_get_idx(setting->path.arr, idx);
+    return nm_strvarray_get_idxnull_or_greturn(setting->path.arr, idx);
 }
 
 /**
@@ -524,7 +511,7 @@ nm_setting_match_add_path(NMSettingMatch *setting, const char *path)
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
     g_return_if_fail(path);
 
-    nm_strvarray_add(nm_strvarray_ensure(&setting->path.arr), path);
+    nm_strvarray_ensure_and_add(&setting->path.arr, path);
     _notify(setting, PROP_PATH);
 }
 
@@ -544,7 +531,7 @@ nm_setting_match_remove_path(NMSettingMatch *setting, guint idx)
 
     g_return_if_fail(setting->path.arr && idx < setting->path.arr->len);
 
-    g_array_remove_index(setting->path.arr, idx);
+    nm_strvarray_remove_index(setting->path.arr, idx);
     _notify(setting, PROP_PATH);
 }
 
@@ -565,12 +552,11 @@ nm_setting_match_remove_path_by_value(NMSettingMatch *setting, const char *path)
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), FALSE);
     g_return_val_if_fail(path, FALSE);
 
-    if (nm_strvarray_remove_first(setting->path.arr, path)) {
-        _notify(setting, PROP_PATH);
-        return TRUE;
-    }
+    if (!nm_strvarray_remove_first(setting->path.arr, path))
+        return FALSE;
 
-    return FALSE;
+    _notify(setting, PROP_PATH);
+    return TRUE;
 }
 
 /**
@@ -586,10 +572,8 @@ nm_setting_match_clear_paths(NMSettingMatch *setting)
 {
     g_return_if_fail(NM_IS_SETTING_MATCH(setting));
 
-    if (nm_g_array_len(setting->path.arr) != 0) {
-        nm_clear_pointer(&setting->path.arr, g_array_unref);
+    if (nm_strvarray_clear(&setting->path.arr))
         _notify(setting, PROP_PATH);
-    }
 }
 
 /**
@@ -608,7 +592,7 @@ nm_setting_match_get_paths(NMSettingMatch *setting, guint *length)
 {
     g_return_val_if_fail(NM_IS_SETTING_MATCH(setting), NULL);
 
-    return nm_strvarray_get_strv(&setting->path.arr, length);
+    return nm_strvarray_get_strv_notnull(setting->path.arr, length);
 }
 
 /*****************************************************************************/
@@ -719,19 +703,6 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
 }
 
 static void
-finalize(GObject *object)
-{
-    NMSettingMatch *self = NM_SETTING_MATCH(object);
-
-    nm_clear_pointer(&self->interface_name.arr, g_array_unref);
-    nm_clear_pointer(&self->kernel_command_line.arr, g_array_unref);
-    nm_clear_pointer(&self->driver.arr, g_array_unref);
-    nm_clear_pointer(&self->path.arr, g_array_unref);
-
-    G_OBJECT_CLASS(nm_setting_match_parent_class)->finalize(object);
-}
-
-static void
 nm_setting_match_class_init(NMSettingMatchClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
@@ -740,7 +711,6 @@ nm_setting_match_class_init(NMSettingMatchClass *klass)
 
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
-    object_class->finalize     = finalize;
 
     setting_class->verify = verify;
 
diff --git a/src/libnm-core-impl/nm-setting-olpc-mesh.c b/src/libnm-core-impl/nm-setting-olpc-mesh.c
index fe6d3842..1b13d99a 100644
--- a/src/libnm-core-impl/nm-setting-olpc-mesh.c
+++ b/src/libnm-core-impl/nm-setting-olpc-mesh.c
@@ -39,20 +39,18 @@ typedef struct {
  * OLPC Wireless Mesh Settings
  */
 struct _NMSettingOlpcMesh {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                parent;
+    NMSettingOlpcMeshPrivate _priv;
 };
 
 struct _NMSettingOlpcMeshClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingOlpcMesh, nm_setting_olpc_mesh, NM_TYPE_SETTING)
 
 #define NM_SETTING_OLPC_MESH_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_OLPC_MESH, NMSettingOlpcMeshPrivate))
+    _NM_GET_PRIVATE(o, NMSettingOlpcMesh, NM_IS_SETTING_OLPC_MESH, NMSetting)
 
 /*****************************************************************************/
 
@@ -171,8 +169,6 @@ nm_setting_olpc_mesh_class_init(NMSettingOlpcMeshClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingOlpcMeshPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -231,5 +227,5 @@ nm_setting_olpc_mesh_class_init(NMSettingOlpcMeshClass *klass)
                              NM_META_SETTING_TYPE_OLPC_MESH,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingOlpcMesh, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-ovs-bridge.c b/src/libnm-core-impl/nm-setting-ovs-bridge.c
index 8e850157..bc5dd04a 100644
--- a/src/libnm-core-impl/nm-setting-ovs-bridge.c
+++ b/src/libnm-core-impl/nm-setting-ovs-bridge.c
@@ -162,7 +162,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_MASTER);
+                           NM_SETTING_CONNECTION_CONTROLLER);
             return FALSE;
         }
     }
@@ -242,7 +242,8 @@ nm_setting_ovs_bridge_class_init(NMSettingOvsBridgeClass *klass)
                                               PROP_FAIL_MODE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsBridge,
-                                              fail_mode);
+                                              fail_mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingOvsBridge:mcast-snooping-enable:
@@ -305,7 +306,8 @@ nm_setting_ovs_bridge_class_init(NMSettingOvsBridgeClass *klass)
                                               PROP_DATAPATH_TYPE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsBridge,
-                                              datapath_type);
+                                              datapath_type,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/libnm-core-impl/nm-setting-ovs-dpdk.c b/src/libnm-core-impl/nm-setting-ovs-dpdk.c
index 87e9a3e8..53931f46 100644
--- a/src/libnm-core-impl/nm-setting-ovs-dpdk.c
+++ b/src/libnm-core-impl/nm-setting-ovs-dpdk.c
@@ -190,7 +190,8 @@ nm_setting_ovs_dpdk_class_init(NMSettingOvsDpdkClass *klass)
                                               PROP_DEVARGS,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsDpdk,
-                                              devargs);
+                                              devargs,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingOvsDpdk:n-rxq:
diff --git a/src/libnm-core-impl/nm-setting-ovs-interface.c b/src/libnm-core-impl/nm-setting-ovs-interface.c
index 34e66480..88379087 100644
--- a/src/libnm-core-impl/nm-setting-ovs-interface.c
+++ b/src/libnm-core-impl/nm-setting-ovs-interface.c
@@ -312,16 +312,16 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("A connection with a '%s' setting must have a master."),
+                        _("A connection with a '%s' setting must have a controller."),
                         NM_SETTING_OVS_INTERFACE_SETTING_NAME);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_MASTER);
+                           NM_SETTING_CONNECTION_CONTROLLER);
             return FALSE;
         }
 
-        slave_type = nm_setting_connection_get_slave_type(s_con);
+        slave_type = nm_setting_connection_get_port_type(s_con);
         if (slave_type && !nm_streq(slave_type, NM_SETTING_OVS_PORT_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
@@ -334,7 +334,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
@@ -394,7 +394,8 @@ nm_setting_ovs_interface_class_init(NMSettingOvsInterfaceClass *klass)
                                               PROP_TYPE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsInterface,
-                                              type);
+                                              type,
+                                              .direct_string_allow_empty = TRUE);
     /**
      * NMSettingOvsInterface:ofport-request:
      *
diff --git a/src/libnm-core-impl/nm-setting-ovs-patch.c b/src/libnm-core-impl/nm-setting-ovs-patch.c
index 2781d435..e44646a6 100644
--- a/src/libnm-core-impl/nm-setting-ovs-patch.c
+++ b/src/libnm-core-impl/nm-setting-ovs-patch.c
@@ -138,7 +138,8 @@ nm_setting_ovs_patch_class_init(NMSettingOvsPatchClass *klass)
                                               PROP_PEER,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsPatch,
-                                              peer);
+                                              peer,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/libnm-core-impl/nm-setting-ovs-port.c b/src/libnm-core-impl/nm-setting-ovs-port.c
index 190f1e5d..f2071d43 100644
--- a/src/libnm-core-impl/nm-setting-ovs-port.c
+++ b/src/libnm-core-impl/nm-setting-ovs-port.c
@@ -349,7 +349,7 @@ verify_trunks(GPtrArray *ranges, GError **error)
         }
 
         for (vlan = range->start; vlan <= range->end; vlan++) {
-            if (!nm_g_hash_table_add(h, GUINT_TO_POINTER(vlan))) {
+            if (!g_hash_table_add(h, GUINT_TO_POINTER(vlan))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -422,16 +422,16 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_MASTER);
+                           NM_SETTING_CONNECTION_CONTROLLER);
             return FALSE;
         }
 
-        slave_type = nm_setting_connection_get_slave_type(s_con);
+        slave_type = nm_setting_connection_get_port_type(s_con);
         if (slave_type && strcmp(slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
                         NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                        _("A connection with a '%s' setting must have the slave-type set to '%s'. "
+                        _("A connection with a '%s' setting must have the port-type set to '%s'. "
                           "Instead it is '%s'"),
                         NM_SETTING_OVS_PORT_SETTING_NAME,
                         NM_SETTING_OVS_BRIDGE_SETTING_NAME,
@@ -439,7 +439,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
@@ -614,7 +614,8 @@ nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
                                               PROP_VLAN_MODE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsPort,
-                                              vlan_mode);
+                                              vlan_mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingOvsPort:tag:
@@ -672,7 +673,8 @@ nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
                                               PROP_LACP,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsPort,
-                                              lacp);
+                                              lacp,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingOvsPort:bond-mode:
@@ -687,7 +689,8 @@ nm_setting_ovs_port_class_init(NMSettingOvsPortClass *klass)
                                               PROP_BOND_MODE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingOvsPort,
-                                              bond_mode);
+                                              bond_mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingOvsPort:bond-updelay:
diff --git a/src/libnm-core-impl/nm-setting-ppp.c b/src/libnm-core-impl/nm-setting-ppp.c
index a9d2b9a2..2ac276b4 100644
--- a/src/libnm-core-impl/nm-setting-ppp.c
+++ b/src/libnm-core-impl/nm-setting-ppp.c
@@ -68,20 +68,17 @@ typedef struct {
  * Point-to-Point Protocol Settings
  */
 struct _NMSettingPpp {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting           parent;
+    NMSettingPppPrivate _priv;
 };
 
 struct _NMSettingPppClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingPpp, nm_setting_ppp, NM_TYPE_SETTING)
 
-#define NM_SETTING_PPP_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PPP, NMSettingPppPrivate))
+#define NM_SETTING_PPP_GET_PRIVATE(o) _NM_GET_PRIVATE(o, NMSettingPpp, NM_IS_SETTING_PPP, NMSetting)
 
 /*****************************************************************************/
 
@@ -402,8 +399,6 @@ nm_setting_ppp_class_init(NMSettingPppClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingPppPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -698,5 +693,5 @@ nm_setting_ppp_class_init(NMSettingPppClass *klass)
                              NM_META_SETTING_TYPE_PPP,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingPpp, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-pppoe.c b/src/libnm-core-impl/nm-setting-pppoe.c
index 5c4f3c28..48da78e7 100644
--- a/src/libnm-core-impl/nm-setting-pppoe.c
+++ b/src/libnm-core-impl/nm-setting-pppoe.c
@@ -43,20 +43,18 @@ typedef struct {
  * PPP-over-Ethernet Settings
  */
 struct _NMSettingPppoe {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting             parent;
+    NMSettingPppoePrivate _priv;
 };
 
 struct _NMSettingPppoeClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingPppoe, nm_setting_pppoe, NM_TYPE_SETTING)
 
 #define NM_SETTING_PPPOE_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PPPOE, NMSettingPppoePrivate))
+    _NM_GET_PRIVATE(o, NMSettingPppoe, NM_IS_SETTING_PPPOE, NMSetting)
 
 /*****************************************************************************/
 
@@ -220,8 +218,6 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingPppoePrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -244,7 +240,8 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingPppoePrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingPppoe:service:
@@ -260,7 +257,8 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
                                               PROP_SERVICE,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingPppoePrivate,
-                                              service);
+                                              service,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingPppoe:username:
@@ -273,7 +271,8 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
                                               PROP_USERNAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingPppoePrivate,
-                                              username);
+                                              username,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingPppoe:password:
@@ -286,7 +285,8 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
                                               PROP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingPppoePrivate,
-                                              password);
+                                              password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingPppoe:password-flags:
@@ -306,5 +306,5 @@ nm_setting_pppoe_class_init(NMSettingPppoeClass *klass)
                              NM_META_SETTING_TYPE_PPPOE,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingPppoe, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index fff6c1cc..9fe49de4 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -6,7 +6,7 @@
 #ifndef __NM_SETTING_PRIVATE_H__
 #define __NM_SETTING_PRIVATE_H__
 
-#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
 #error Cannot use this header.
 #endif
 
@@ -20,6 +20,14 @@
 
 /*****************************************************************************/
 
+/* This holds a property of type NM_VALUE_TYPE_STRV. You probably want
+ * to use nm_strvarray_*() API with this. */
+typedef struct {
+    GArray *arr;
+} NMValueStrv;
+
+/*****************************************************************************/
+
 struct _NMRefString;
 
 typedef struct {
@@ -80,6 +88,8 @@ void _nm_connection_private_clear(NMConnectionPrivate *priv);
 
 /*****************************************************************************/
 
+struct _NMSettingPrivate;
+
 /**
  * NMSetting:
  *
@@ -87,15 +97,13 @@ void _nm_connection_private_clear(NMConnectionPrivate *priv);
  * It should only be accessed through the functions described below.
  */
 struct _NMSetting {
-    GObject parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    GObject                   parent;
+    struct _NMSettingPrivate *_priv;
 };
 
 struct _NMSettingClass {
     GObjectClass parent;
 
-    /* In the past, this struct was public API. Preserve ABI! */
-
     int (*verify)(NMSetting *setting, NMConnection *connection, GError **error);
 
     gboolean (*verify_secrets)(NMSetting *setting, NMConnection *connection, GError **error);
@@ -120,8 +128,6 @@ struct _NMSettingClass {
                               NMSettingClearSecretsWithFlagsFn func,
                               gpointer                         user_data);
 
-    void (*padding_1)(void);
-
     void (*duplicate_copy_properties)(const struct _NMSettInfoSetting *sett_info,
                                       NMSetting                       *src,
                                       NMSetting                       *dst);
@@ -158,58 +164,41 @@ struct _NMSettingClass {
  */
 struct _NMSettingIPConfig {
     NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
 };
 
 struct _NMSettingIPConfigClass {
     NMSettingClass parent;
-
-    /* In the past, this struct was public API. Preserve ABI! */
-
-    union {
-        gpointer _dummy1;
-        int      private_offset;
-    };
-
-    union {
-        gpointer _dummy2;
-        gint8    addr_family;
-    };
-
-    union {
-        gpointer _dummy3;
-        bool     is_ipv4;
-    };
-
-    gpointer padding[5];
+    int            private_offset;
+    gint8          addr_family;
+    bool           is_ipv4;
 };
 
 typedef struct {
-    GPtrArray *dns;         /* array of IP address strings */
-    GPtrArray *dns_search;  /* array of domain name strings */
-    GPtrArray *dns_options; /* array of DNS options */
-    GPtrArray *addresses;   /* array of NMIPAddress */
-    GPtrArray *routes;      /* array of NMIPRoute */
-    GPtrArray *routing_rules;
-    GArray    *dhcp_reject_servers;
-    char      *method;
-    char      *gateway;
-    char      *dhcp_hostname;
-    char      *dhcp_iaid;
-    gint64     route_metric;
-    int        auto_route_ext_gw;
-    int        replace_local_rule;
-    gint32     required_timeout;
-    gint32     dad_timeout;
-    gint32     dhcp_timeout;
-    gint32     dns_priority;
-    guint32    route_table;
-    guint32    dhcp_hostname_flags;
-    bool       ignore_auto_routes;
-    bool       ignore_auto_dns;
-    bool       dhcp_send_hostname;
-    bool       never_default;
-    bool       may_fail;
+    NMValueStrv dns_search; /* array of domain name strings */
+    NMValueStrv dhcp_reject_servers;
+    NMValueStrv dns_options; /* array of DNS options */
+    GPtrArray  *dns;         /* array of IP address strings */
+    GPtrArray  *addresses;   /* array of NMIPAddress */
+    GPtrArray  *routes;      /* array of NMIPRoute */
+    GPtrArray  *routing_rules;
+    char       *method;
+    char       *gateway;
+    char       *dhcp_hostname;
+    char       *dhcp_iaid;
+    gint64      route_metric;
+    int         auto_route_ext_gw;
+    int         replace_local_rule;
+    gint32      required_timeout;
+    gint32      dad_timeout;
+    gint32      dhcp_timeout;
+    gint32      dns_priority;
+    guint32     route_table;
+    guint32     dhcp_hostname_flags;
+    bool        ignore_auto_routes;
+    bool        ignore_auto_dns;
+    bool        dhcp_send_hostname;
+    bool        never_default;
+    bool        may_fail;
 } NMSettingIPConfigPrivate;
 
 void _nm_setting_ip_config_private_init(gpointer self, NMSettingIPConfigPrivate *priv);
@@ -296,14 +285,6 @@ gboolean _nm_setting_clear_secrets(NMSetting                       *setting,
 
 /*****************************************************************************/
 
-/* This holds a property of type NM_VALUE_TYPE_STRV. You probably want
- * to use nm_strvarray_*() API with this. */
-typedef struct {
-    GArray *arr;
-} NMValueStrv;
-
-/*****************************************************************************/
-
 struct _NMRange {
     int     refcount;
     guint64 start;
@@ -350,6 +331,7 @@ extern const NMSettInfoPropertType nm_sett_info_propert_type_setting_name;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u;
+extern const NMSettInfoPropertType nm_sett_info_propert_type_gprop_strv_oldstyle;
 
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_boolean;
 extern const NMSettInfoPropertType nm_sett_info_propert_type_direct_int32;
@@ -417,6 +399,18 @@ gboolean _nm_setting_property_from_dbus_fcn_direct(_NM_SETT_INFO_PROP_FROM_DBUS_
 
 gboolean _nm_setting_property_from_dbus_fcn_gprop(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
 
+gboolean _nm_setting_connection_controller_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
+
+GVariant *_nm_setting_connection_controller_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil);
+
+gboolean _nm_setting_connection_master_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
+
+gboolean _nm_setting_connection_port_type_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
+
+GVariant *_nm_setting_connection_port_type_to_dbus(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_nil);
+
+gboolean _nm_setting_connection_slave_type_from_dbus(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS _nm_nil);
+
 GVariant *_nm_setting_to_dbus(NMSetting                              *setting,
                               NMConnection                           *connection,
                               NMConnectionSerializationFlags          flags,
@@ -438,11 +432,7 @@ const NMSettInfoProperty *
 _nm_sett_info_property_lookup_by_param_spec(const NMSettInfoSetting *sett_info,
                                             const GParamSpec        *param_spec);
 
-static inline GArray *
-_nm_sett_info_property_override_create_array_sized(guint reserved_size)
-{
-    return g_array_sized_new(FALSE, FALSE, sizeof(NMSettInfoProperty), reserved_size);
-}
+GArray *_nm_sett_info_property_override_create_array_sized(guint reserved_size);
 
 static inline GArray *
 _nm_sett_info_property_override_create_array(void)
@@ -497,23 +487,30 @@ void _nm_setting_class_commit(NMSettingClass             *setting_class,
 
 gboolean _nm_properties_override_assert(const NMSettInfoProperty *prop_info);
 
-static inline void
+static inline guint
 _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *prop_info)
 {
     nm_assert(properties_override);
     nm_assert(_nm_properties_override_assert(prop_info));
     g_array_append_vals(properties_override, prop_info, 1);
+    return properties_override->len - 1u;
 }
 
-#define _nm_properties_override_gobj(properties_override,                             \
-                                     p_param_spec,                                    \
-                                     p_property_type,                                 \
-                                     ... /* extra NMSettInfoProperty fields */)       \
-    _nm_properties_override((properties_override),                                    \
-                            NM_SETT_INFO_PROPERTY(.name          = NULL,              \
-                                                  .param_spec    = (p_param_spec),    \
-                                                  .property_type = (p_property_type), \
-                                                  __VA_ARGS__))
+#define _nm_properties_override_gobj(properties_override,                                     \
+                                     p_param_spec,                                            \
+                                     p_property_type,                                         \
+                                     ... /* extra NMSettInfoProperty fields */)               \
+    ({                                                                                        \
+        GParamSpec *const _p_param_spec_2 = (p_param_spec);                                   \
+                                                                                              \
+        nm_assert(_p_param_spec_2);                                                           \
+                                                                                              \
+        _nm_properties_override((properties_override),                                        \
+                                NM_SETT_INFO_PROPERTY(.name          = _p_param_spec_2->name, \
+                                                      .param_spec    = _p_param_spec_2,       \
+                                                      .property_type = (p_property_type),     \
+                                                      __VA_ARGS__));                          \
+    })
 
 #define _nm_properties_override_dbus(properties_override,                             \
                                      p_name,                                          \
@@ -557,12 +554,12 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                                                                                                \
         nm_assert(NM_IN_SET(_default_value, 0, 1));                                            \
                                                                                                \
-        _param_spec =                                                                          \
-            g_param_spec_boolean("" prop_name "",                                              \
-                                 "",                                                           \
-                                 "",                                                           \
-                                 _default_value,                                               \
-                                 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));  \
+        _param_spec = g_param_spec_boolean("" prop_name "",                                    \
+                                           "",                                                 \
+                                           "",                                                 \
+                                           _default_value,                                     \
+                                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY         \
+                                               | G_PARAM_STATIC_STRINGS | (param_flags));      \
                                                                                                \
         (obj_properties)[(prop_id)] = _param_spec;                                             \
                                                                                                \
@@ -600,14 +597,14 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         G_STATIC_ASSERT((default_value) <= (guint64) (max_value));                                \
         G_STATIC_ASSERT((max_value) <= (guint64) G_MAXUINT32);                                    \
                                                                                                   \
-        _param_spec =                                                                             \
-            g_param_spec_uint("" prop_name "",                                                    \
-                              "",                                                                 \
-                              "",                                                                 \
-                              (min_value),                                                        \
-                              (max_value),                                                        \
-                              (default_value),                                                    \
-                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));        \
+        _param_spec = g_param_spec_uint("" prop_name "",                                          \
+                                        "",                                                       \
+                                        "",                                                       \
+                                        (min_value),                                              \
+                                        (max_value),                                              \
+                                        (default_value),                                          \
+                                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY               \
+                                            | G_PARAM_STATIC_STRINGS | (param_flags));            \
                                                                                                   \
         (obj_properties)[(prop_id)] = _param_spec;                                                \
                                                                                                   \
@@ -646,14 +643,14 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         G_STATIC_ASSERT((default_value) <= (gint64) (max_value));                                \
         G_STATIC_ASSERT((max_value) <= (gint64) G_MAXUINT32);                                    \
                                                                                                  \
-        _param_spec =                                                                            \
-            g_param_spec_int("" prop_name "",                                                    \
-                             "",                                                                 \
-                             "",                                                                 \
-                             (min_value),                                                        \
-                             (max_value),                                                        \
-                             (default_value),                                                    \
-                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));        \
+        _param_spec = g_param_spec_int("" prop_name "",                                          \
+                                       "",                                                       \
+                                       "",                                                       \
+                                       (min_value),                                              \
+                                       (max_value),                                              \
+                                       (default_value),                                          \
+                                       G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY               \
+                                           | G_PARAM_STATIC_STRINGS | (param_flags));            \
                                                                                                  \
         (obj_properties)[(prop_id)] = _param_spec;                                               \
                                                                                                  \
@@ -692,14 +689,14 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         G_STATIC_ASSERT((default_value) <= (max_value));                                         \
         G_STATIC_ASSERT((max_value) <= G_MAXINT64);                                              \
                                                                                                  \
-        _param_spec =                                                                            \
-            g_param_spec_int64("" prop_name "",                                                  \
-                               "",                                                               \
-                               "",                                                               \
-                               (min_value),                                                      \
-                               (max_value),                                                      \
-                               (default_value),                                                  \
-                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));      \
+        _param_spec = g_param_spec_int64("" prop_name "",                                        \
+                                         "",                                                     \
+                                         "",                                                     \
+                                         (min_value),                                            \
+                                         (max_value),                                            \
+                                         (default_value),                                        \
+                                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY             \
+                                             | G_PARAM_STATIC_STRINGS | (param_flags));          \
                                                                                                  \
         (obj_properties)[(prop_id)] = _param_spec;                                               \
                                                                                                  \
@@ -737,14 +734,14 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         G_STATIC_ASSERT((default_value) == 0 || (default_value) -1u < (max_value));               \
         G_STATIC_ASSERT((max_value) <= G_MAXUINT64);                                              \
                                                                                                   \
-        _param_spec =                                                                             \
-            g_param_spec_uint64("" prop_name "",                                                  \
-                                "",                                                               \
-                                "",                                                               \
-                                (min_value),                                                      \
-                                (max_value),                                                      \
-                                (default_value),                                                  \
-                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));      \
+        _param_spec = g_param_spec_uint64("" prop_name "",                                        \
+                                          "",                                                     \
+                                          "",                                                     \
+                                          (min_value),                                            \
+                                          (max_value),                                            \
+                                          (default_value),                                        \
+                                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY             \
+                                              | G_PARAM_STATIC_STRINGS | (param_flags));          \
                                                                                                   \
         (obj_properties)[(prop_id)] = _param_spec;                                                \
                                                                                                   \
@@ -769,8 +766,7 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                                                        private_struct_type,                       \
                                                        private_struct_field,                      \
                                                        ... /* extra NMSettInfoProperty fields */) \
-    G_STMT_START                                                                                  \
-    {                                                                                             \
+    ({                                                                                            \
         GParamSpec                  *_param_spec;                                                 \
         const NMSettInfoPropertType *_property_type = (property_type);                            \
                                                                                                   \
@@ -782,14 +778,17 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         nm_assert(_property_type);                                                                \
         nm_assert(g_variant_type_equal(_property_type->dbus_type, "s"));                          \
         nm_assert(_property_type->direct_type == NM_VALUE_TYPE_STRING);                           \
-        nm_assert(_property_type->to_dbus_fcn == _nm_setting_property_to_dbus_fcn_direct);        \
+        nm_assert(NM_IN_SET(_property_type->to_dbus_fcn,                                          \
+                            _nm_setting_property_to_dbus_fcn_direct,                              \
+                            _nm_setting_connection_controller_to_dbus,                            \
+                            _nm_setting_connection_port_type_to_dbus));                           \
                                                                                                   \
-        _param_spec =                                                                             \
-            g_param_spec_string("" prop_name "",                                                  \
-                                "",                                                               \
-                                "",                                                               \
-                                NULL,                                                             \
-                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));      \
+        _param_spec = g_param_spec_string("" prop_name "",                                        \
+                                          "",                                                     \
+                                          "",                                                     \
+                                          NULL,                                                   \
+                                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY             \
+                                              | G_PARAM_STATIC_STRINGS | (param_flags));          \
                                                                                                   \
         (obj_properties)[(prop_id)] = _param_spec;                                                \
                                                                                                   \
@@ -800,8 +799,7 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
             .direct_offset =                                                                      \
                 NM_STRUCT_OFFSET_ENSURE_TYPE(char *, private_struct_type, private_struct_field),  \
             __VA_ARGS__);                                                                         \
-    }                                                                                             \
-    G_STMT_END
+    })
 
 #define _nm_setting_property_define_direct_string(properties_override,                       \
                                                   obj_properties,                            \
@@ -839,12 +837,12 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                                       ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_INFERRABLE      \
                                         | NM_SETTING_PARAM_FUZZY_IGNORE)));                        \
                                                                                                    \
-        _param_spec =                                                                              \
-            g_param_spec_boxed("" prop_name "",                                                    \
-                               "",                                                                 \
-                               "",                                                                 \
-                               G_TYPE_BYTES,                                                       \
-                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));        \
+        _param_spec = g_param_spec_boxed("" prop_name "",                                          \
+                                         "",                                                       \
+                                         "",                                                       \
+                                         G_TYPE_BYTES,                                             \
+                                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY               \
+                                             | G_PARAM_STATIC_STRINGS | (param_flags));            \
                                                                                                    \
         (obj_properties)[(prop_id)] = _param_spec;                                                 \
                                                                                                    \
@@ -874,12 +872,12 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                                                                                              \
         G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags), ~(NM_SETTING_PARAM_FUZZY_IGNORE)));     \
                                                                                              \
-        _param_spec =                                                                        \
-            g_param_spec_boxed("" prop_name "",                                              \
-                               "",                                                           \
-                               "",                                                           \
-                               G_TYPE_STRV,                                                  \
-                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));  \
+        _param_spec = g_param_spec_boxed("" prop_name "",                                    \
+                                         "",                                                 \
+                                         "",                                                 \
+                                         G_TYPE_STRV,                                        \
+                                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY         \
+                                             | G_PARAM_STATIC_STRINGS | (param_flags));      \
                                                                                              \
         (obj_properties)[(prop_id)] = _param_spec;                                           \
                                                                                              \
@@ -915,13 +913,13 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                           ~(NM_SETTING_PARAM_REAPPLY_IMMEDIATELY | NM_SETTING_PARAM_FUZZY_IGNORE \
                             | NM_SETTING_PARAM_INFERRABLE)));                                    \
                                                                                                  \
-        _param_spec =                                                                            \
-            g_param_spec_enum("" prop_name "",                                                   \
-                              "",                                                                \
-                              "",                                                                \
-                              (gtype_enum),                                                      \
-                              (default_value),                                                   \
-                              G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));       \
+        _param_spec = g_param_spec_enum("" prop_name "",                                         \
+                                        "",                                                      \
+                                        "",                                                      \
+                                        (gtype_enum),                                            \
+                                        (default_value),                                         \
+                                        G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY              \
+                                            | G_PARAM_STATIC_STRINGS | (param_flags));           \
                                                                                                  \
         (obj_properties)[(prop_id)] = _param_spec;                                               \
                                                                                                  \
@@ -976,13 +974,13 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
             !NM_FLAGS_ANY((param_flags),                                                        \
                           ~(NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_INFERRABLE)));     \
                                                                                                 \
-        _param_spec =                                                                           \
-            g_param_spec_flags("" prop_name "",                                                 \
-                               "",                                                              \
-                               "",                                                              \
-                               (gtype_flags),                                                   \
-                               (default_value),                                                 \
-                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));     \
+        _param_spec = g_param_spec_flags("" prop_name "",                                       \
+                                         "",                                                    \
+                                         "",                                                    \
+                                         (gtype_flags),                                         \
+                                         (default_value),                                       \
+                                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY            \
+                                             | G_PARAM_STATIC_STRINGS | (param_flags));         \
                                                                                                 \
         (obj_properties)[(prop_id)] = _param_spec;                                              \
                                                                                                 \
@@ -1035,12 +1033,12 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
                                         | NM_SETTING_PARAM_INFERRABLE                             \
                                         | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY)));                \
                                                                                                   \
-        _param_spec =                                                                             \
-            g_param_spec_string("" prop_name "",                                                  \
-                                "",                                                               \
-                                "",                                                               \
-                                NULL,                                                             \
-                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));      \
+        _param_spec = g_param_spec_string("" prop_name "",                                        \
+                                          "",                                                     \
+                                          "",                                                     \
+                                          NULL,                                                   \
+                                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY             \
+                                              | G_PARAM_STATIC_STRINGS | (param_flags));          \
                                                                                                   \
         (obj_properties)[(prop_id)] = _param_spec;                                                \
                                                                                                   \
@@ -1054,6 +1052,38 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
     }                                                                                             \
     G_STMT_END
 
+/* This should not be used for new strv properties. Use _nm_setting_property_define_direct_strv().
+ *
+ * FIXME: existing properties should migrate to _nm_setting_property_define_direct_strv(). */
+#define _nm_setting_property_define_gprop_strv_oldstyle(properties_override,                    \
+                                                        obj_properties,                         \
+                                                        prop_name,                              \
+                                                        prop_id,                                \
+                                                        param_flags)                            \
+    G_STMT_START                                                                                \
+    {                                                                                           \
+        GParamSpec *_param_spec;                                                                \
+                                                                                                \
+        G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags),                                            \
+                                      ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_FUZZY_IGNORE \
+                                        | NM_SETTING_PARAM_INFERRABLE                           \
+                                        | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY)));              \
+                                                                                                \
+        _param_spec =                                                                           \
+            g_param_spec_boxed("" prop_name "",                                                 \
+                               "",                                                              \
+                               "",                                                              \
+                               G_TYPE_STRV,                                                     \
+                               G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | (param_flags));     \
+                                                                                                \
+        (obj_properties)[(prop_id)] = _param_spec;                                              \
+                                                                                                \
+        _nm_properties_override_gobj((properties_override),                                     \
+                                     _param_spec,                                               \
+                                     &nm_sett_info_propert_type_gprop_strv_oldstyle);           \
+    }                                                                                           \
+    G_STMT_END
+
 /*****************************************************************************/
 
 gboolean _nm_setting_use_legacy_property(NMSetting  *setting,
@@ -1098,4 +1128,11 @@ _nm_sett_info_prop_from_dbus_fcn_cloned_mac_address(_NM_SETT_INFO_PROP_FROM_DBUS
 
 /*****************************************************************************/
 
+void _nm_setting_wireless_normalize_mac_address_randomization(
+    NMSettingWireless         *s_wifi,
+    const char               **out_cloned_mac_address,
+    NMSettingMacRandomization *out_mac_address_randomization);
+
+/*****************************************************************************/
+
 #endif /* NM_SETTING_PRIVATE_H */
diff --git a/src/libnm-core-impl/nm-setting-proxy.c b/src/libnm-core-impl/nm-setting-proxy.c
index 8b91210b..e50721df 100644
--- a/src/libnm-core-impl/nm-setting-proxy.c
+++ b/src/libnm-core-impl/nm-setting-proxy.c
@@ -41,19 +41,18 @@ typedef struct {
  * WWW Proxy Settings
  */
 struct _NMSettingProxy {
-    NMSetting parent;
+    NMSetting             parent;
+    NMSettingProxyPrivate _priv;
 };
 
 struct _NMSettingProxyClass {
     NMSettingClass parent;
-
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingProxy, nm_setting_proxy, NM_TYPE_SETTING)
 
 #define NM_SETTING_PROXY_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_PROXY, NMSettingProxyPrivate))
+    _NM_GET_PRIVATE(o, NMSettingProxy, NM_IS_SETTING_PROXY, NMSetting)
 
 /*****************************************************************************/
 
@@ -233,8 +232,6 @@ nm_setting_proxy_class_init(NMSettingProxyClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingProxyPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -310,7 +307,8 @@ nm_setting_proxy_class_init(NMSettingProxyClass *klass)
                                               PROP_PAC_URL,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingProxyPrivate,
-                                              pac_url);
+                                              pac_url,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingProxy:pac-script:
@@ -342,7 +340,8 @@ nm_setting_proxy_class_init(NMSettingProxyClass *klass)
                                               PROP_PAC_SCRIPT,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingProxyPrivate,
-                                              pac_script);
+                                              pac_script,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
@@ -350,5 +349,5 @@ nm_setting_proxy_class_init(NMSettingProxyClass *klass)
                              NM_META_SETTING_TYPE_PROXY,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingProxy, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-serial.c b/src/libnm-core-impl/nm-setting-serial.c
index 6fe3e487..ab2a8319 100644
--- a/src/libnm-core-impl/nm-setting-serial.c
+++ b/src/libnm-core-impl/nm-setting-serial.c
@@ -42,20 +42,18 @@ typedef struct {
  * Serial Link Settings
  */
 struct _NMSettingSerial {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting              parent;
+    NMSettingSerialPrivate _priv;
 };
 
 struct _NMSettingSerialClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingSerial, nm_setting_serial, NM_TYPE_SETTING)
 
 #define NM_SETTING_SERIAL_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_SERIAL, NMSettingSerialPrivate))
+    _NM_GET_PRIVATE(o, NMSettingSerial, NM_IS_SETTING_SERIAL, NMSetting)
 
 /*****************************************************************************/
 
@@ -226,8 +224,6 @@ nm_setting_serial_class_init(NMSettingSerialClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingSerialPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
 
@@ -341,5 +337,5 @@ nm_setting_serial_class_init(NMSettingSerialClass *klass)
                              NM_META_SETTING_TYPE_SERIAL,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingSerial, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-team-port.c b/src/libnm-core-impl/nm-setting-team-port.c
index 816d85d9..a881ec5f 100644
--- a/src/libnm-core-impl/nm-setting-team-port.c
+++ b/src/libnm-core-impl/nm-setting-team-port.c
@@ -41,20 +41,18 @@ typedef struct {
  * Team Port Settings
  */
 struct _NMSettingTeamPort {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                parent;
+    NMSettingTeamPortPrivate _priv;
 };
 
 struct _NMSettingTeamPortClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingTeamPort, nm_setting_team_port, NM_TYPE_SETTING)
 
 #define NM_SETTING_TEAM_PORT_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TEAM_PORT, NMSettingTeamPortPrivate))
+    _NM_GET_PRIVATE(o, NMSettingTeamPort, NM_IS_SETTING_TEAM_PORT, NMSetting)
 
 /*****************************************************************************/
 
@@ -325,7 +323,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             return FALSE;
         }
 
-        slave_type = nm_setting_connection_get_slave_type(s_con);
+        slave_type = nm_setting_connection_get_port_type(s_con);
         if (slave_type && strcmp(slave_type, NM_SETTING_TEAM_SETTING_NAME)) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
@@ -338,7 +336,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_CONNECTION_SETTING_NAME,
-                           NM_SETTING_CONNECTION_SLAVE_TYPE);
+                           NM_SETTING_CONNECTION_PORT_TYPE);
             return FALSE;
         }
     }
@@ -532,8 +530,6 @@ nm_setting_team_port_class_init(NMSettingTeamPortClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingTeamPortPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -702,5 +698,5 @@ nm_setting_team_port_class_init(NMSettingTeamPortClass *klass)
                              NM_META_SETTING_TYPE_TEAM_PORT,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingTeamPort, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-team.c b/src/libnm-core-impl/nm-setting-team.c
index 83de8b6f..2195757f 100644
--- a/src/libnm-core-impl/nm-setting-team.c
+++ b/src/libnm-core-impl/nm-setting-team.c
@@ -745,20 +745,18 @@ typedef struct {
  * Teaming Settings
  */
 struct _NMSettingTeam {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting            parent;
+    NMSettingTeamPrivate _priv;
 };
 
 struct _NMSettingTeamClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingTeam, nm_setting_team, NM_TYPE_SETTING)
 
 #define NM_SETTING_TEAM_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TEAM, NMSettingTeamPrivate))
+    _NM_GET_PRIVATE(o, NMSettingTeam, NM_IS_SETTING_TEAM, NMSetting)
 
 /*****************************************************************************/
 
@@ -1386,8 +1384,7 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
         break;
     case NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH:
         v_ptrarr = priv->team_setting->d.master.runner_tx_hash;
-        g_value_take_boxed(value,
-                           v_ptrarr ? _nm_utils_ptrarray_to_strv((GPtrArray *) v_ptrarr) : NULL);
+        g_value_take_boxed(value, nm_strv_ptrarray_to_strv_full(v_ptrarr, FALSE));
         break;
     case NM_TEAM_ATTRIBUTE_LINK_WATCHERS:
         g_value_take_boxed(value,
@@ -1498,8 +1495,6 @@ nm_setting_team_class_init(NMSettingTeamClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingTeamPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -1842,5 +1837,5 @@ nm_setting_team_class_init(NMSettingTeamClass *klass)
                              NM_META_SETTING_TYPE_TEAM,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingTeam, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-tun.c b/src/libnm-core-impl/nm-setting-tun.c
index ec095155..05460104 100644
--- a/src/libnm-core-impl/nm-setting-tun.c
+++ b/src/libnm-core-impl/nm-setting-tun.c
@@ -46,20 +46,17 @@ typedef struct {
  * Tunnel Settings
  */
 struct _NMSettingTun {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting           parent;
+    NMSettingTunPrivate _priv;
 };
 
 struct _NMSettingTunClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingTun, nm_setting_tun, NM_TYPE_SETTING)
 
-#define NM_SETTING_TUN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_TUN, NMSettingTunPrivate))
+#define NM_SETTING_TUN_GET_PRIVATE(o) _NM_GET_PRIVATE(o, NMSettingTun, NM_IS_SETTING_TUN, NMSetting)
 
 /*****************************************************************************/
 
@@ -223,8 +220,6 @@ nm_setting_tun_class_init(NMSettingTunClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingTunPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -265,7 +260,8 @@ nm_setting_tun_class_init(NMSettingTunClass *klass)
                                               PROP_OWNER,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingTunPrivate,
-                                              owner);
+                                              owner,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingTun:group:
@@ -281,7 +277,8 @@ nm_setting_tun_class_init(NMSettingTunClass *klass)
                                               PROP_GROUP,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingTunPrivate,
-                                              group);
+                                              group,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingTun:pi:
@@ -342,5 +339,5 @@ nm_setting_tun_class_init(NMSettingTunClass *klass)
                              NM_META_SETTING_TYPE_TUN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingTun, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-veth.c b/src/libnm-core-impl/nm-setting-veth.c
index 40e3c961..6b84233d 100644
--- a/src/libnm-core-impl/nm-setting-veth.c
+++ b/src/libnm-core-impl/nm-setting-veth.c
@@ -146,7 +146,8 @@ nm_setting_veth_class_init(NMSettingVethClass *klass)
                                               PROP_PEER,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingVeth,
-                                              _priv.peer);
+                                              _priv.peer,
+                                              .direct_string_allow_empty = TRUE);
 
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
diff --git a/src/libnm-core-impl/nm-setting-vlan.c b/src/libnm-core-impl/nm-setting-vlan.c
index e5a99ce9..534ed713 100644
--- a/src/libnm-core-impl/nm-setting-vlan.c
+++ b/src/libnm-core-impl/nm-setting-vlan.c
@@ -49,20 +49,18 @@ typedef struct {
  * VLAN Settings
  */
 struct _NMSettingVlan {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting            parent;
+    NMSettingVlanPrivate _priv;
 };
 
 struct _NMSettingVlanClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingVlan, nm_setting_vlan, NM_TYPE_SETTING)
 
 #define NM_SETTING_VLAN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VLAN, NMSettingVlanPrivate))
+    _NM_GET_PRIVATE(o, NMSettingVlan, NM_IS_SETTING_VLAN, NMSetting)
 
 /*****************************************************************************/
 
@@ -616,7 +614,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             if (s_con) {
                 const char *master = NULL, *slave_type = NULL;
 
-                slave_type = nm_setting_connection_get_slave_type(s_con);
+                slave_type = nm_setting_connection_get_port_type(s_con);
                 if (!g_strcmp0(slave_type, NM_SETTING_VLAN_SETTING_NAME))
                     master = nm_setting_connection_get_master(s_con);
 
@@ -626,7 +624,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                                 NM_CONNECTION_ERROR_INVALID_PROPERTY,
                                 _("'%s' value doesn't match '%s=%s'"),
                                 priv->parent,
-                                NM_SETTING_CONNECTION_MASTER,
+                                NM_SETTING_CONNECTION_CONTROLLER,
                                 master);
                     g_prefix_error(error,
                                    "%s.%s: ",
@@ -848,8 +846,6 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingVlanPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -876,7 +872,8 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingVlanPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVlan:id:
@@ -967,7 +964,8 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingVlanPrivate,
                                               protocol,
-                                              .direct_string_is_refstr = TRUE);
+                                              .direct_string_is_refstr   = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVlan:ingress-priority-map:
@@ -983,12 +981,11 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
      * example: VLAN_INGRESS_PRIORITY_MAP=4:2,3:5
      * ---end---
      */
-    obj_properties[PROP_INGRESS_PRIORITY_MAP] = g_param_spec_boxed(
-        NM_SETTING_VLAN_INGRESS_PRIORITY_MAP,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_VLAN_INGRESS_PRIORITY_MAP,
+                                                    PROP_INGRESS_PRIORITY_MAP,
+                                                    NM_SETTING_PARAM_INFERRABLE);
 
     /**
      * NMSettingVlan:egress-priority-map:
@@ -1004,12 +1001,11 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
      * example: VLAN_EGRESS_PRIORITY_MAP=5:4,4:1,3:7
      * ---end---
      */
-    obj_properties[PROP_EGRESS_PRIORITY_MAP] = g_param_spec_boxed(
-        NM_SETTING_VLAN_EGRESS_PRIORITY_MAP,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_VLAN_EGRESS_PRIORITY_MAP,
+                                                    PROP_EGRESS_PRIORITY_MAP,
+                                                    NM_SETTING_PARAM_INFERRABLE);
 
     /* ---ifcfg-rh---
      * property: interface-name
@@ -1039,5 +1035,5 @@ nm_setting_vlan_class_init(NMSettingVlanClass *klass)
                              NM_META_SETTING_TYPE_VLAN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingVlan, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-vpn.c b/src/libnm-core-impl/nm-setting-vpn.c
index 5984f773..b867d018 100644
--- a/src/libnm-core-impl/nm-setting-vpn.c
+++ b/src/libnm-core-impl/nm-setting-vpn.c
@@ -80,20 +80,17 @@ typedef struct {
  * VPN Settings
  */
 struct _NMSettingVpn {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting           parent;
+    NMSettingVpnPrivate _priv;
 };
 
 struct _NMSettingVpnClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingVpn, nm_setting_vpn, NM_TYPE_SETTING)
 
-#define NM_SETTING_VPN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VPN, NMSettingVpnPrivate))
+#define NM_SETTING_VPN_GET_PRIVATE(o) _NM_GET_PRIVATE(o, NMSettingVpn, NM_IS_SETTING_VPN, NMSetting)
 
 /*****************************************************************************/
 
@@ -1067,8 +1064,6 @@ nm_setting_vpn_class_init(NMSettingVpnClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingVpnPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -1095,7 +1090,8 @@ nm_setting_vpn_class_init(NMSettingVpnClass *klass)
                                               PROP_SERVICE_TYPE,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingVpnPrivate,
-                                              service_type);
+                                              service_type,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVpn:user-name:
@@ -1113,7 +1109,8 @@ nm_setting_vpn_class_init(NMSettingVpnClass *klass)
                                               PROP_USER_NAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingVpnPrivate,
-                                              user_name);
+                                              user_name,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVpn:persistent:
@@ -1211,5 +1208,5 @@ nm_setting_vpn_class_init(NMSettingVpnClass *klass)
                              NM_META_SETTING_TYPE_VPN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingVpn, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-vxlan.c b/src/libnm-core-impl/nm-setting-vxlan.c
index 3fb4c48d..50abd883 100644
--- a/src/libnm-core-impl/nm-setting-vxlan.c
+++ b/src/libnm-core-impl/nm-setting-vxlan.c
@@ -66,20 +66,18 @@ typedef struct {
  * VXLAN Settings
  */
 struct _NMSettingVxlan {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting             parent;
+    NMSettingVxlanPrivate _priv;
 };
 
 struct _NMSettingVxlanClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingVxlan, nm_setting_vxlan, NM_TYPE_SETTING)
 
 #define NM_SETTING_VXLAN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_VXLAN, NMSettingVxlanPrivate))
+    _NM_GET_PRIVATE(o, NMSettingVxlan, NM_IS_SETTING_VXLAN, NMSetting)
 
 /*****************************************************************************/
 
@@ -428,8 +426,6 @@ nm_setting_vxlan_class_init(NMSettingVxlanClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingVxlanPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -448,7 +444,8 @@ nm_setting_vxlan_class_init(NMSettingVxlanClass *klass)
                                               PROP_PARENT,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingVxlanPrivate,
-                                              parent);
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVxlan:id:
@@ -484,7 +481,8 @@ nm_setting_vxlan_class_init(NMSettingVxlanClass *klass)
                                               NMSettingVxlanPrivate,
                                               local,
                                               .direct_set_string_ip_address_addr_family =
-                                                  AF_UNSPEC + 1);
+                                                  AF_UNSPEC + 1,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVxlan:remote:
@@ -503,7 +501,8 @@ nm_setting_vxlan_class_init(NMSettingVxlanClass *klass)
                                               NMSettingVxlanPrivate,
                                               remote,
                                               .direct_set_string_ip_address_addr_family =
-                                                  AF_UNSPEC + 1);
+                                                  AF_UNSPEC + 1,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingVxlan:source-port-min:
@@ -722,5 +721,5 @@ nm_setting_vxlan_class_init(NMSettingVxlanClass *klass)
                              NM_META_SETTING_TYPE_VXLAN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingVxlan, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-wifi-p2p.c b/src/libnm-core-impl/nm-setting-wifi-p2p.c
index e8ce4bdc..31dbea5c 100644
--- a/src/libnm-core-impl/nm-setting-wifi-p2p.c
+++ b/src/libnm-core-impl/nm-setting-wifi-p2p.c
@@ -201,7 +201,8 @@ nm_setting_wifi_p2p_class_init(NMSettingWifiP2PClass *setting_wifi_p2p_class)
                                               PROP_PEER,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWifiP2P,
-                                              _priv.peer);
+                                              _priv.peer,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWifiP2P:wps-method:
diff --git a/src/libnm-core-impl/nm-setting-wimax.c b/src/libnm-core-impl/nm-setting-wimax.c
index c33ad3ea..e68aeb3d 100644
--- a/src/libnm-core-impl/nm-setting-wimax.c
+++ b/src/libnm-core-impl/nm-setting-wimax.c
@@ -41,20 +41,18 @@ typedef struct {
  * WiMax Settings
  */
 struct _NMSettingWimax {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting             parent;
+    NMSettingWimaxPrivate _priv;
 };
 
 struct _NMSettingWimaxClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingWimax, nm_setting_wimax, NM_TYPE_SETTING)
 
 #define NM_SETTING_WIMAX_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIMAX, NMSettingWimaxPrivate))
+    _NM_GET_PRIVATE(o, NMSettingWimax, NM_IS_SETTING_WIMAX, NMSetting)
 
 /*****************************************************************************/
 
@@ -157,8 +155,6 @@ nm_setting_wimax_class_init(NMSettingWimaxClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingWimaxPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -179,7 +175,8 @@ nm_setting_wimax_class_init(NMSettingWimaxClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWimaxPrivate,
                                               network_name,
-                                              .is_deprecated = TRUE, );
+                                              .is_deprecated             = TRUE,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWimax:mac-address:
@@ -206,5 +203,5 @@ nm_setting_wimax_class_init(NMSettingWimaxClass *klass)
                              NM_META_SETTING_TYPE_WIMAX,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingWimax, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-wired.c b/src/libnm-core-impl/nm-setting-wired.c
index 1ee16df5..e09fd70d 100644
--- a/src/libnm-core-impl/nm-setting-wired.c
+++ b/src/libnm-core-impl/nm-setting-wired.c
@@ -75,20 +75,18 @@ typedef struct {
  * Wired Ethernet Settings
  */
 struct _NMSettingWired {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting             parent;
+    NMSettingWiredPrivate _priv;
 };
 
 struct _NMSettingWiredClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingWired, nm_setting_wired, NM_TYPE_SETTING)
 
 #define NM_SETTING_WIRED_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIRED, NMSettingWiredPrivate))
+    _NM_GET_PRIVATE(o, NMSettingWired, NM_IS_SETTING_WIRED, NMSetting)
 
 /*****************************************************************************/
 
@@ -314,6 +312,8 @@ nm_setting_wired_get_num_mac_blacklist_items(NMSettingWired *setting)
  * @setting: the #NMSettingWired
  * @idx: the zero-based index of the MAC address entry
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the blacklisted MAC address string (hex-digits-and-colons notation)
  * at index @idx
  **/
@@ -325,7 +325,12 @@ nm_setting_wired_get_mac_blacklist_item(NMSettingWired *setting, guint32 idx)
     g_return_val_if_fail(NM_IS_SETTING_WIRED(setting), NULL);
 
     priv = NM_SETTING_WIRED_GET_PRIVATE(setting);
-    g_return_val_if_fail(idx <= priv->mac_address_blacklist->len, NULL);
+
+    if (idx == priv->mac_address_blacklist->len) {
+        return NULL;
+    }
+
+    g_return_val_if_fail(idx < priv->mac_address_blacklist->len, NULL);
 
     return nm_g_array_index(priv->mac_address_blacklist, const char *, idx);
 }
@@ -886,7 +891,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
-    if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address)
+    if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address, FALSE)
         && !nm_utils_hwaddr_valid(priv->cloned_mac_address, ETH_ALEN)) {
         g_set_error(error,
                     NM_CONNECTION_ERROR,
@@ -1172,8 +1177,6 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(klass, sizeof(NMSettingWiredPrivate));
-
     object_class->get_property = get_property;
     object_class->set_property = set_property;
     object_class->finalize     = finalize;
@@ -1200,7 +1203,8 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                                               PROP_PORT,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWiredPrivate,
-                                              port);
+                                              port,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWired:speed:
@@ -1266,7 +1270,8 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                                               PROP_DUPLEX,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWiredPrivate,
-                                              duplex);
+                                              duplex,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWired:auto-negotiate:
@@ -1455,7 +1460,8 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                                               PROP_GENERATE_MAC_ADDRESS_MASK,
                                               NM_SETTING_PARAM_FUZZY_IGNORE,
                                               NMSettingWiredPrivate,
-                                              generate_mac_address_mask);
+                                              generate_mac_address_mask,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWired:mac-address-blacklist:
@@ -1480,12 +1486,11 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
      * example: HWADDR_BLACKLIST="00:22:68:11:69:08 00:11:22:11:44:55"
      * ---end---
      */
-    obj_properties[PROP_MAC_ADDRESS_BLACKLIST] = g_param_spec_boxed(
-        NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST,
+                                                    PROP_MAC_ADDRESS_BLACKLIST,
+                                                    NM_SETTING_PARAM_FUZZY_IGNORE);
 
     /**
      * NMSettingWired:mtu:
@@ -1528,12 +1533,11 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
      * example: SUBCHANNELS=0.0.b00a,0.0.b00b,0.0.b00c
      * ---end---
      */
-    obj_properties[PROP_S390_SUBCHANNELS] = g_param_spec_boxed(
-        NM_SETTING_WIRED_S390_SUBCHANNELS,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRED_S390_SUBCHANNELS,
+                                                    PROP_S390_SUBCHANNELS,
+                                                    NM_SETTING_PARAM_INFERRABLE);
 
     /**
      * NMSettingWired:s390-nettype:
@@ -1555,7 +1559,8 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                                               PROP_S390_NETTYPE,
                                               NM_SETTING_PARAM_INFERRABLE,
                                               NMSettingWiredPrivate,
-                                              s390_nettype);
+                                              s390_nettype,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWired:s390-options: (type GHashTable(utf8,utf8))
@@ -1643,7 +1648,8 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                                               PROP_WAKE_ON_LAN_PASSWORD,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWiredPrivate,
-                                              wol_password);
+                                              wol_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWired:accept-all-mac-addresses:
@@ -1675,5 +1681,5 @@ nm_setting_wired_class_init(NMSettingWiredClass *klass)
                              NM_META_SETTING_TYPE_WIRED,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingWired, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c
index b0e3f66c..1e7ed05b 100644
--- a/src/libnm-core-impl/nm-setting-wireguard.c
+++ b/src/libnm-core-impl/nm-setting-wireguard.c
@@ -1318,7 +1318,7 @@ _peers_set(NMSettingWireGuardPrivate *priv,
     };
 
     g_ptr_array_add(priv->peers_arr, pd_same_key);
-    if (!nm_g_hash_table_add(priv->peers_hash, pd_same_key))
+    if (!g_hash_table_add(priv->peers_hash, pd_same_key))
         nm_assert_not_reached();
 
     nm_assert(_peers_get(priv, pd_same_key->idx) == pd_same_key);
@@ -2362,8 +2362,9 @@ nm_setting_wireguard_class_init(NMSettingWireGuardClass *klass)
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWireGuard,
                                               _priv.private_key,
-                                              .direct_hook.set_string_fcn =
-                                                  _set_string_fcn_public_key);
+                                              .direct_set_fcn.set_string =
+                                                  _set_string_fcn_public_key,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWireGuard:private-key-flags:
diff --git a/src/libnm-core-impl/nm-setting-wireless-security.c b/src/libnm-core-impl/nm-setting-wireless-security.c
index a7d12df4..e420b32b 100644
--- a/src/libnm-core-impl/nm-setting-wireless-security.c
+++ b/src/libnm-core-impl/nm-setting-wireless-security.c
@@ -90,22 +90,18 @@ typedef struct {
  * Wi-Fi Security Settings
  */
 struct _NMSettingWirelessSecurity {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                        parent;
+    NMSettingWirelessSecurityPrivate _priv;
 };
 
 struct _NMSettingWirelessSecurityClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingWirelessSecurity, nm_setting_wireless_security, NM_TYPE_SETTING)
 
-#define NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(o)                 \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o),                               \
-                                 NM_TYPE_SETTING_WIRELESS_SECURITY, \
-                                 NMSettingWirelessSecurityPrivate))
+#define NM_SETTING_WIRELESS_SECURITY_GET_PRIVATE(o) \
+    _NM_GET_PRIVATE(o, NMSettingWirelessSecurity, NM_IS_SETTING_WIRELESS_SECURITY, NMSetting)
 
 /*****************************************************************************/
 
@@ -1387,9 +1383,7 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
-    GArray         *properties_override = _nm_sett_info_property_override_create_array();
-
-    g_type_class_add_private(klass, sizeof(NMSettingWirelessSecurityPrivate));
+    GArray         *properties_override = _nm_sett_info_property_override_create_array_sized(25);
 
     object_class->get_property = get_property;
     object_class->set_property = set_property;
@@ -1426,7 +1420,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               NM_SETTING_PARAM_REQUIRED,
                                               NMSettingWirelessSecurityPrivate,
                                               key_mgmt,
-                                              .direct_set_string_ascii_strdown = TRUE);
+                                              .direct_set_string_ascii_strdown = TRUE,
+                                              .direct_string_allow_empty       = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-tx-keyidx:
@@ -1479,7 +1474,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWirelessSecurityPrivate,
                                               auth_alg,
-                                              .direct_set_string_ascii_strdown = TRUE);
+                                              .direct_set_string_ascii_strdown = TRUE,
+                                              .direct_string_allow_empty       = TRUE);
 
     /**
      * NMSettingWirelessSecurity:proto:
@@ -1496,11 +1492,11 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
      * description: Allowed WPA protocols, WPA and WPA2 (RSN).
      * ---end---
      */
-    obj_properties[PROP_PROTO] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_PROTO,
-                                                    "",
-                                                    "",
-                                                    G_TYPE_STRV,
-                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRELESS_SECURITY_PROTO,
+                                                    PROP_PROTO,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSettingWirelessSecurity:pairwise:
@@ -1518,11 +1514,11 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
      *   separated list.
      * ---end---
      */
-    obj_properties[PROP_PAIRWISE] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_PAIRWISE,
-                                                       "",
-                                                       "",
-                                                       G_TYPE_STRV,
-                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRELESS_SECURITY_PAIRWISE,
+                                                    PROP_PAIRWISE,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSettingWirelessSecurity:group:
@@ -1540,11 +1536,11 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
      *   separated list.
      * ---end---
      */
-    obj_properties[PROP_GROUP] = g_param_spec_boxed(NM_SETTING_WIRELESS_SECURITY_GROUP,
-                                                    "",
-                                                    "",
-                                                    G_TYPE_STRV,
-                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRELESS_SECURITY_GROUP,
+                                                    PROP_GROUP,
+                                                    NM_SETTING_PARAM_NONE);
 
     /**
      * NMSettingWirelessSecurity:pmf:
@@ -1597,7 +1593,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_LEAP_USERNAME,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWirelessSecurityPrivate,
-                                              leap_username);
+                                              leap_username,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-key0:
@@ -1617,7 +1614,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_WEP_KEY0,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              wep_key0);
+                                              wep_key0,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-key1:
@@ -1637,7 +1635,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_WEP_KEY1,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              wep_key1);
+                                              wep_key1,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-key2:
@@ -1657,7 +1656,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_WEP_KEY2,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              wep_key2);
+                                              wep_key2,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-key3:
@@ -1677,7 +1677,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_WEP_KEY3,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              wep_key3);
+                                              wep_key3,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:wep-key-flags:
@@ -1721,7 +1722,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_PSK,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              psk);
+                                              psk,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:psk-flags:
@@ -1763,7 +1765,8 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                                               PROP_LEAP_PASSWORD,
                                               NM_SETTING_PARAM_SECRET,
                                               NMSettingWirelessSecurityPrivate,
-                                              leap_password);
+                                              leap_password,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWirelessSecurity:leap-password-flags:
@@ -1894,5 +1897,5 @@ nm_setting_wireless_security_class_init(NMSettingWirelessSecurityClass *klass)
                              NM_META_SETTING_TYPE_WIRELESS_SECURITY,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingWirelessSecurity, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-wireless.c b/src/libnm-core-impl/nm-setting-wireless.c
index e2cd1f49..152bbde3 100644
--- a/src/libnm-core-impl/nm-setting-wireless.c
+++ b/src/libnm-core-impl/nm-setting-wireless.c
@@ -72,20 +72,18 @@ typedef struct {
  * Wi-Fi Settings
  */
 struct _NMSettingWireless {
-    NMSetting parent;
-    /* In the past, this struct was public API. Preserve ABI! */
+    NMSetting                parent;
+    NMSettingWirelessPrivate _priv;
 };
 
 struct _NMSettingWirelessClass {
     NMSettingClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 G_DEFINE_TYPE(NMSettingWireless, nm_setting_wireless, NM_TYPE_SETTING)
 
 #define NM_SETTING_WIRELESS_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WIRELESS, NMSettingWirelessPrivate))
+    _NM_GET_PRIVATE(o, NMSettingWireless, NM_IS_SETTING_WIRELESS, NMSetting)
 
 /*****************************************************************************/
 
@@ -499,6 +497,8 @@ nm_setting_wireless_get_num_mac_blacklist_items(NMSettingWireless *setting)
  * @setting: the #NMSettingWireless
  * @idx: the zero-based index of the MAC address entry
  *
+ * Since 1.46, access at index "len" is allowed and returns NULL.
+ *
  * Returns: the blacklisted MAC address string (hex-digits-and-colons notation)
  * at index @idx
  **/
@@ -510,7 +510,12 @@ nm_setting_wireless_get_mac_blacklist_item(NMSettingWireless *setting, guint32 i
     g_return_val_if_fail(NM_IS_SETTING_WIRELESS(setting), NULL);
 
     priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting);
-    g_return_val_if_fail(idx <= priv->mac_address_blacklist->len, NULL);
+
+    if (idx == priv->mac_address_blacklist->len) {
+        return NULL;
+    }
+
+    g_return_val_if_fail(idx < priv->mac_address_blacklist->len, NULL);
 
     return nm_g_array_index(priv->mac_address_blacklist, const char *, idx);
 }
@@ -824,6 +829,49 @@ nm_setting_wireless_get_ap_isolation(NMSettingWireless *setting)
 
 /*****************************************************************************/
 
+void
+_nm_setting_wireless_normalize_mac_address_randomization(
+    NMSettingWireless         *s_wifi,
+    const char               **out_cloned_mac_address,
+    NMSettingMacRandomization *out_mac_address_randomization)
+{
+    NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(s_wifi);
+    guint32                   mac_address_randomization;
+    const char               *cloned_mac_address;
+
+    mac_address_randomization = priv->mac_address_randomization;
+    cloned_mac_address        = priv->cloned_mac_address;
+
+    if (cloned_mac_address) {
+        /* If cloned_mac_address is set, it takes precedence and determines
+         * mac_address_randomization. */
+        if (nm_streq(cloned_mac_address, "random"))
+            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_ALWAYS;
+        else if (nm_streq(cloned_mac_address, "permanent"))
+            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_NEVER;
+        else
+            mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
+    } else if (!NM_IN_SET(mac_address_randomization,
+                          NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
+                          NM_SETTING_MAC_RANDOMIZATION_NEVER,
+                          NM_SETTING_MAC_RANDOMIZATION_ALWAYS)) {
+        /* cloned_mac_address is NULL and mac_address_randomization is invalid. Normalize
+         * mac_address_randomization to the default. */
+        mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
+    } else if (mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) {
+        /* mac_address_randomization is not (guint32)set to the default. cloned_mac_address gets
+         * overwritten. */
+        cloned_mac_address = mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS
+                                 ? "random"
+                                 : "permanent";
+    }
+
+    *out_cloned_mac_address        = cloned_mac_address;
+    *out_mac_address_randomization = mac_address_randomization;
+}
+
+/*****************************************************************************/
+
 static gboolean
 verify(NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -837,6 +885,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     guint                     i;
     gsize                     length;
     GError                   *local = NULL;
+    const char               *desired_cloned_mac_address;
+    NMSettingMacRandomization desired_mac_address_randomization;
 
     if (!priv->ssid) {
         g_set_error_literal(error,
@@ -958,7 +1008,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
-    if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address)
+    if (priv->cloned_mac_address && !NM_CLONED_MAC_IS_SPECIAL(priv->cloned_mac_address, TRUE)
         && !nm_utils_hwaddr_valid(priv->cloned_mac_address, ETH_ALEN)) {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
@@ -1082,27 +1132,21 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
 
     /* from here on, check for NM_SETTING_VERIFY_NORMALIZABLE conditions. */
 
-    if (priv->cloned_mac_address) {
-        if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_ALWAYS
-            && nm_streq(priv->cloned_mac_address, "random"))
-            goto mac_addr_rand_ok;
-        if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_NEVER
-            && nm_streq(priv->cloned_mac_address, "permanent"))
-            goto mac_addr_rand_ok;
-        if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
-            goto mac_addr_rand_ok;
-    } else if (priv->mac_address_randomization == NM_SETTING_MAC_RANDOMIZATION_DEFAULT)
-        goto mac_addr_rand_ok;
-    g_set_error(error,
-                NM_CONNECTION_ERROR,
-                NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                _("conflicting value of mac-address-randomization and cloned-mac-address"));
-    g_prefix_error(error,
-                   "%s.%s: ",
-                   NM_SETTING_WIRELESS_SETTING_NAME,
-                   NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS);
-    return NM_SETTING_VERIFY_NORMALIZABLE;
-mac_addr_rand_ok:
+    _nm_setting_wireless_normalize_mac_address_randomization(NM_SETTING_WIRELESS(setting),
+                                                             &desired_cloned_mac_address,
+                                                             &desired_mac_address_randomization);
+    if (desired_mac_address_randomization != priv->mac_address_randomization
+        || !nm_streq0(desired_cloned_mac_address, priv->cloned_mac_address)) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("conflicting value of mac-address-randomization and cloned-mac-address"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_WIRELESS_SETTING_NAME,
+                       NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS);
+        return NM_SETTING_VERIFY_NORMALIZABLE;
+    }
 
     if (priv->tx_power != 0 || priv->rate != 0) {
         g_set_error(error,
@@ -1209,25 +1253,33 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 static void
 set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
 {
-    NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(object);
+    NMSettingWireless        *self = NM_SETTING_WIRELESS(object);
+    NMSettingWirelessPrivate *priv = NM_SETTING_WIRELESS_GET_PRIVATE(self);
     const char *const        *blacklist;
     const char               *mac;
     gboolean                  bool_val;
+    _PropertyEnums            prop1 = PROP_0;
+    _PropertyEnums            prop2 = PROP_0;
 
     switch (prop_id) {
     case PROP_CLONED_MAC_ADDRESS:
         bool_val = !!priv->cloned_mac_address;
-        g_free(priv->cloned_mac_address);
-        priv->cloned_mac_address =
-            _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN);
+
+        if (nm_strdup_reset_take(
+                &priv->cloned_mac_address,
+                _nm_utils_hwaddr_canonical_or_invalid(g_value_get_string(value), ETH_ALEN)))
+            prop1 = prop_id;
+
         if (bool_val && !priv->cloned_mac_address) {
             /* cloned-mac-address was set before but was now explicitly cleared.
              * In this case, we also clear mac-address-randomization flag */
             if (priv->mac_address_randomization != NM_SETTING_MAC_RANDOMIZATION_DEFAULT) {
                 priv->mac_address_randomization = NM_SETTING_MAC_RANDOMIZATION_DEFAULT;
-                _notify(NM_SETTING_WIRELESS(object), PROP_MAC_ADDRESS_RANDOMIZATION);
+                prop2                           = PROP_MAC_ADDRESS_RANDOMIZATION;
             }
         }
+
+        nm_gobject_notify_together(self, prop1, prop2);
         break;
     case PROP_MAC_ADDRESS_BLACKLIST:
         blacklist = g_value_get_boxed(value);
@@ -1307,9 +1359,7 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
 {
     GObjectClass   *object_class        = G_OBJECT_CLASS(klass);
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
-    GArray         *properties_override = _nm_sett_info_property_override_create_array();
-
-    g_type_class_add_private(klass, sizeof(NMSettingWirelessPrivate));
+    GArray         *properties_override = _nm_sett_info_property_override_create_array_sized(25);
 
     object_class->set_property = set_property;
     object_class->get_property = get_property;
@@ -1363,7 +1413,8 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               PROP_MODE,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWirelessPrivate,
-                                              mode);
+                                              mode,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWireless:band:
@@ -1390,7 +1441,8 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               PROP_BAND,
                                               NM_SETTING_PARAM_NONE,
                                               NMSettingWirelessPrivate,
-                                              band);
+                                              band,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWireless:channel:
@@ -1539,12 +1591,14 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
      * This is known as MAC cloning or spoofing.
      *
      * Beside explicitly specifying a MAC address, the special values "preserve", "permanent",
-     * "random" and "stable" are supported.
+     * "random", "stable" and "stable-ssid" are supported.
      * "preserve" means not to touch the MAC address on activation.
      * "permanent" means to use the permanent hardware address of the device.
      * "random" creates a random MAC address on each connect.
      * "stable" creates a hashed MAC address based on connection.stable-id and a
      * machine dependent key.
+     * "stable-ssid" creates a hashed MAC address based on the SSID, the same as setting the
+     * stable-id to "${NETWORK_SSID}".
      *
      * If unspecified, the value can be overwritten via global defaults, see manual
      * of NetworkManager.conf. If still unspecified, it defaults to "preserve"
@@ -1576,12 +1630,13 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
      *    For libnm and nmcli, this field is called "cloned-mac-address".
      * ---end---
      */
-    obj_properties[PROP_CLONED_MAC_ADDRESS] = g_param_spec_string(
-        NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
-        "",
-        "",
-        NULL,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
+    obj_properties[PROP_CLONED_MAC_ADDRESS] =
+        g_param_spec_string(NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY
+                                | NM_SETTING_PARAM_INFERRABLE | G_PARAM_STATIC_STRINGS);
     _nm_properties_override_gobj(
         properties_override,
         obj_properties[PROP_CLONED_MAC_ADDRESS],
@@ -1654,7 +1709,8 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                                               PROP_GENERATE_MAC_ADDRESS_MASK,
                                               NM_SETTING_PARAM_FUZZY_IGNORE,
                                               NMSettingWirelessPrivate,
-                                              generate_mac_address_mask);
+                                              generate_mac_address_mask,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWireless:mac-address-blacklist:
@@ -1677,12 +1733,11 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
      *   is listed.
      * ---end---
      */
-    obj_properties[PROP_MAC_ADDRESS_BLACKLIST] = g_param_spec_boxed(
-        NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST,
-        "",
-        "",
-        G_TYPE_STRV,
-        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+    _nm_setting_property_define_gprop_strv_oldstyle(properties_override,
+                                                    obj_properties,
+                                                    NM_SETTING_WIRELESS_MAC_ADDRESS_BLACKLIST,
+                                                    PROP_MAC_ADDRESS_BLACKLIST,
+                                                    NM_SETTING_PARAM_FUZZY_IGNORE);
 
     /**
      * NMSettingWireless:seen-bssids:
@@ -1925,5 +1980,5 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
                              NM_META_SETTING_TYPE_WIRELESS,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingWireless, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting-wpan.c b/src/libnm-core-impl/nm-setting-wpan.c
index bc84b01e..ba947a86 100644
--- a/src/libnm-core-impl/nm-setting-wpan.c
+++ b/src/libnm-core-impl/nm-setting-wpan.c
@@ -57,7 +57,8 @@ typedef struct {
  * Since: 1.14
  */
 struct _NMSettingWpan {
-    NMSetting parent;
+    NMSetting            parent;
+    NMSettingWpanPrivate _priv;
 };
 
 struct _NMSettingWpanClass {
@@ -67,7 +68,7 @@ struct _NMSettingWpanClass {
 G_DEFINE_TYPE(NMSettingWpan, nm_setting_wpan, NM_TYPE_SETTING)
 
 #define NM_SETTING_WPAN_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING_WPAN, NMSettingWpanPrivate))
+    _NM_GET_PRIVATE(o, NMSettingWpan, NM_IS_SETTING_WPAN, NMSetting)
 
 /*****************************************************************************/
 
@@ -228,8 +229,6 @@ nm_setting_wpan_class_init(NMSettingWpanClass *klass)
     NMSettingClass *setting_class       = NM_SETTING_CLASS(klass);
     GArray         *properties_override = _nm_sett_info_property_override_create_array();
 
-    g_type_class_add_private(setting_class, sizeof(NMSettingWpanPrivate));
-
     object_class->get_property = _nm_setting_property_get_property_direct;
     object_class->set_property = _nm_setting_property_set_property_direct;
 
@@ -256,7 +255,8 @@ nm_setting_wpan_class_init(NMSettingWpanClass *klass)
                                               NMSettingWpanPrivate,
                                               mac_address,
                                               .direct_set_string_mac_address_len =
-                                                  IEEE802154_ADDR_LEN);
+                                                  IEEE802154_ADDR_LEN,
+                                              .direct_string_allow_empty = TRUE);
 
     /**
      * NMSettingWpan:pan-id:
@@ -334,5 +334,5 @@ nm_setting_wpan_class_init(NMSettingWpanClass *klass)
                              NM_META_SETTING_TYPE_WPAN,
                              NULL,
                              properties_override,
-                             NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
+                             G_STRUCT_OFFSET(NMSettingWpan, _priv));
 }
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index 276dfd34..758338e5 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -11,7 +11,6 @@
 #include "libnm-core-intern/nm-core-internal.h"
 #include "libnm-glib-aux/nm-ref-string.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
-#include "nm-property-compare.h"
 #include "nm-setting-private.h"
 #include "nm-utils-private.h"
 #include "nm-utils.h"
@@ -58,14 +57,13 @@ typedef struct {
 
 NM_GOBJECT_PROPERTIES_DEFINE(NMSetting, PROP_NAME, );
 
-typedef struct {
+typedef struct _NMSettingPrivate {
     GenData *gendata;
 } NMSettingPrivate;
 
 G_DEFINE_ABSTRACT_TYPE(NMSetting, nm_setting, G_TYPE_OBJECT)
 
-#define NM_SETTING_GET_PRIVATE(o) \
-    (G_TYPE_INSTANCE_GET_PRIVATE((o), NM_TYPE_SETTING, NMSettingPrivate))
+#define NM_SETTING_GET_PRIVATE(o) _NM_GET_PRIVATE_PTR(o, NMSetting, NM_IS_SETTING)
 
 /*****************************************************************************/
 
@@ -165,7 +163,7 @@ _nm_setting_slave_type_is_valid(const char *slave_type, const char **out_port_ty
 
 /*****************************************************************************/
 
-static const NMSettInfoProperty *
+_nm_unused static const NMSettInfoProperty *
 _nm_sett_info_property_find_in_array(const NMSettInfoProperty *properties,
                                      guint                     len,
                                      const char               *name)
@@ -184,9 +182,8 @@ _nm_properties_override_assert(const NMSettInfoProperty *prop_info)
 {
 #if NM_MORE_ASSERTS
     nm_assert(prop_info);
-    nm_assert((!!prop_info->name) != (!!prop_info->param_spec));
-    nm_assert(!prop_info->param_spec || !prop_info->name
-              || nm_streq0(prop_info->name, prop_info->param_spec->name));
+    nm_assert(prop_info->name);
+    nm_assert(!prop_info->param_spec || prop_info->name == prop_info->param_spec->name);
 
     if (prop_info->property_type) {
         const NMSettInfoPropertType *property_type = prop_info->property_type;
@@ -209,6 +206,34 @@ _nm_properties_override_assert(const NMSettInfoProperty *prop_info)
     return TRUE;
 }
 
+GArray *
+_nm_sett_info_property_override_create_array_sized(guint reserved_size)
+{
+    GArray     *properties_override;
+    GParamSpec *param_spec;
+
+    /* pre-allocate a relatively large buffer to avoid frequent re-allocations.
+     * Note that the buffer is only short-lived and will be destroyed by
+     * _nm_setting_class_commit(). */
+    properties_override =
+        g_array_sized_new(FALSE, FALSE, sizeof(NMSettInfoProperty), reserved_size);
+
+    /* The "name" property is special because it's defined in the
+     * parent class NMSetting. We add the property info right here.
+     */
+    param_spec = obj_properties[PROP_NAME];
+    nm_assert(param_spec);
+    nm_assert(nm_streq0(param_spec->name, NM_SETTING_NAME));
+    nm_assert(param_spec
+              == g_object_class_find_property(g_type_class_peek(NM_TYPE_SETTING), NM_SETTING_NAME));
+    nm_assert(!NM_FLAGS_HAS(param_spec->flags, G_PARAM_WRITABLE));
+    _nm_properties_override_gobj(properties_override,
+                                 param_spec,
+                                 &nm_sett_info_propert_type_setting_name);
+
+    return properties_override;
+}
+
 static NMSettInfoSetting _sett_info_settings[_NM_META_SETTING_TYPE_NUM];
 
 const NMSettInfoSetting *
@@ -224,10 +249,8 @@ _property_infos_sort_cmp_setting_connection(gconstpointer p_a,
 {
     const NMSettInfoProperty *a = *((const NMSettInfoProperty *const *) p_a);
     const NMSettInfoProperty *b = *((const NMSettInfoProperty *const *) p_b);
-    int                       c_name;
 
-    c_name = strcmp(a->name, b->name);
-    nm_assert(c_name != 0);
+    nm_assert(a->name && b->name && !nm_streq(a->name, b->name));
 
 #define CMP_AND_RETURN(n_a, n_b, name)         \
     G_STMT_START                               \
@@ -240,15 +263,14 @@ _property_infos_sort_cmp_setting_connection(gconstpointer p_a,
     G_STMT_END
 
     /* for [connection], report first id, uuid, type in that order. */
-    if (c_name != 0) {
-        CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_ID);
-        CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_UUID);
-        CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_TYPE);
-    }
+    CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_ID);
+    CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_UUID);
+    CMP_AND_RETURN(a->name, b->name, NM_SETTING_CONNECTION_TYPE);
 
 #undef CMP_AND_RETURN
 
-    return c_name;
+    NM_CMP_FIELD_STR(a, b, name);
+    return nm_assert_unreachable_val(0);
 }
 
 static const NMSettInfoProperty *const *
@@ -306,12 +328,14 @@ _nm_setting_class_commit(NMSettingClass             *setting_class,
                          GArray                     *properties_override,
                          gint16                      private_offset)
 {
-    NMSettInfoSetting                  *sett_info;
-    gs_free GParamSpec                **property_specs = NULL;
-    guint                               n_property_specs;
+    NMSettInfoSetting *sett_info;
+#if NM_MORE_ASSERTS > 10
+    gs_free GParamSpec **property_specs = NULL;
+    guint                n_property_specs;
+    guint                i;
+#endif
     NMSettInfoPropertLookupByParamSpec *lookup_by_iter;
     guint                               override_len;
-    guint                               i;
     guint16                             j;
 
     nm_assert(NM_IS_SETTING_CLASS(setting_class));
@@ -325,37 +349,28 @@ _nm_setting_class_commit(NMSettingClass             *setting_class,
     nm_assert(!sett_info->property_infos_len);
     nm_assert(!sett_info->property_infos);
 
-    property_specs =
-        g_object_class_list_properties(G_OBJECT_CLASS(setting_class), &n_property_specs);
-
-    if (!properties_override) {
-        override_len        = 0;
-        properties_override = _nm_sett_info_property_override_create_array_sized(n_property_specs);
-    } else {
-        override_len = properties_override->len;
+    if (!properties_override)
+        properties_override = _nm_sett_info_property_override_create_array_sized(1);
 
-        for (i = 0; i < override_len; i++) {
-            NMSettInfoProperty *p = &nm_g_array_index(properties_override, NMSettInfoProperty, i);
+    override_len = properties_override->len;
 
-            nm_assert((!!p->name) != (!!p->param_spec));
-
-            if (!p->name) {
-                nm_assert(p->param_spec);
-                p->name = p->param_spec->name;
-            } else
-                nm_assert(!p->param_spec);
-        }
-    }
+    nm_assert(override_len > 0);
 
 #if NM_MORE_ASSERTS > 10
+    property_specs =
+        g_object_class_list_properties(G_OBJECT_CLASS(setting_class), &n_property_specs);
+
     /* assert that properties_override is constructed consistently. */
     for (i = 0; i < override_len; i++) {
         const NMSettInfoProperty *p = &nm_g_array_index(properties_override, NMSettInfoProperty, i);
         gboolean                  found = FALSE;
         guint                     k;
 
+        nm_assert(p->name);
+        nm_assert(!p->param_spec || p->name == p->param_spec->name);
+
         nm_assert(!_nm_sett_info_property_find_in_array(
-            nm_g_array_index_p(properties_override, NMSettInfoProperty, 0),
+            nm_g_array_first_p(properties_override, NMSettInfoProperty),
             i,
             p->name));
         for (k = 0; k < n_property_specs; k++) {
@@ -367,79 +382,38 @@ _nm_setting_class_commit(NMSettingClass             *setting_class,
         }
         nm_assert(found == (p->param_spec != NULL));
     }
-#endif
 
     for (i = 0; i < n_property_specs; i++) {
-        const char         *name = property_specs[i]->name;
-        NMSettInfoProperty *p;
-
-        if (_nm_sett_info_property_find_in_array(
-                nm_g_array_index_p(properties_override, NMSettInfoProperty, 0),
-                override_len,
-                name))
-            continue;
-
-        p = nm_g_array_append_new(properties_override, NMSettInfoProperty);
-        memset(p, 0, sizeof(*p));
-        p->name       = name;
-        p->param_spec = property_specs[i];
+        const GParamSpec         *param_spec = property_specs[i];
+        const char               *name       = param_spec->name;
+        const NMSettInfoProperty *p;
+
+        p = _nm_sett_info_property_find_in_array(
+            nm_g_array_first_p(properties_override, NMSettInfoProperty),
+            override_len,
+            name);
+        nm_assert(p);
+        nm_assert(p->param_spec);
+        nm_assert(p->param_spec == param_spec);
     }
 
     for (i = 0; i < properties_override->len; i++) {
         NMSettInfoProperty *p = &nm_g_array_index(properties_override, NMSettInfoProperty, i);
-        GType               vtype;
-
-        if (p->property_type)
-            goto has_property_type;
-
-        nm_assert(p->param_spec);
-
-        vtype = p->param_spec->value_type;
 
-        if (vtype == G_TYPE_STRING) {
-            /* The "name" property is a bit special because it's defined in the
-             * parent class NMSetting. We set the property_type here, because
-             * it's more convenient (albeit a bit ugly).
-             *
-             * FIXME: let _nm_sett_info_property_override_create_array() always add
-             *   the handling of the name property.*/
-            nm_assert(nm_streq(p->name, NM_SETTING_NAME));
-            nm_assert(!NM_FLAGS_HAS(p->param_spec->flags, G_PARAM_WRITABLE));
-            p->property_type = &nm_sett_info_propert_type_setting_name;
-            goto has_property_type;
-        }
-
-        if (vtype == G_TYPE_STRV)
-            p->property_type = NM_SETT_INFO_PROPERT_TYPE_GPROP(
-                G_VARIANT_TYPE_STRING_ARRAY,
-                .compare_fcn       = _nm_setting_property_compare_fcn_default,
-                .from_dbus_fcn     = _nm_setting_property_from_dbus_fcn_gprop,
-                .from_dbus_is_full = TRUE);
-        else
-            nm_assert_not_reached();
-
-has_property_type:
         nm_assert(p->property_type);
         nm_assert(p->property_type->dbus_type);
         nm_assert(g_variant_type_string_is_valid((const char *) p->property_type->dbus_type));
+        nm_assert(p->name);
+        nm_assert(!p->param_spec || nm_streq0(p->name, p->param_spec->name));
     }
+#endif
 
     G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NMSettInfoProperty, name) == 0);
     g_array_sort(properties_override, nm_strcmp_p);
 
     setting_class->setting_info = &nm_meta_setting_infos[meta_type];
     sett_info->setting_class    = setting_class;
-
-    if (private_offset == NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS) {
-        int o;
-
-        o = g_type_class_get_instance_private_offset(setting_class);
-        nm_assert(o != NM_SETT_INFO_PRIVATE_OFFSET_FROM_CLASS);
-        nm_assert(o > G_MININT16);
-        nm_assert(o < 0);
-        private_offset = o;
-    }
-    sett_info->private_offset = private_offset;
+    sett_info->private_offset   = private_offset;
 
     if (detail)
         sett_info->detail = *detail;
@@ -708,10 +682,10 @@ _property_direct_set_string(const NMSettInfoSetting  *sett_info,
                + (!!property_info->direct_string_is_refstr)
                + (property_info->direct_set_string_mac_address_len > 0)
                + (property_info->direct_set_string_ip_address_addr_family != 0))
-              <= (property_info->direct_hook.set_string_fcn ? 0 : 1));
+              <= (property_info->direct_set_fcn.set_string ? 0 : 1));
 
-    if (property_info->direct_hook.set_string_fcn) {
-        return property_info->direct_hook.set_string_fcn(sett_info, property_info, setting, src);
+    if (property_info->direct_set_fcn.set_string) {
+        return property_info->direct_set_fcn.set_string(sett_info, property_info, setting, src);
     }
 
     dst = _nm_setting_get_private_field(setting, sett_info, property_info);
@@ -754,6 +728,24 @@ out_take:
     return nm_strdup_reset_take(dst, s);
 }
 
+static gboolean
+_property_direct_set_strv(const NMSettInfoSetting  *sett_info,
+                          const NMSettInfoProperty *property_info,
+                          NMSetting                *setting,
+                          const char *const        *strv)
+{
+    NMValueStrv *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
+
+    if (!property_info->direct_strv_preserve_empty && strv && !strv[0])
+        strv = NULL;
+
+    if (nm_strvarray_equal_strv(p_val->arr, strv, -1))
+        return FALSE;
+
+    nm_strvarray_set_strv_full(&p_val->arr, strv, property_info->direct_strv_preserve_empty);
+    return TRUE;
+}
+
 void
 _nm_setting_property_get_property_direct(GObject    *object,
                                          guint       prop_id,
@@ -842,14 +834,19 @@ _nm_setting_property_get_property_direct(GObject    *object,
     {
         const NMValueStrv *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
 
-        g_value_take_boxed(value, nm_strvarray_get_strv_non_empty_dup(p_val->arr, NULL));
+        g_value_take_boxed(
+            value,
+            nm_strvarray_get_strv_full_dup(p_val->arr,
+                                           NULL,
+                                           FALSE,
+                                           property_info->direct_strv_preserve_empty));
         return;
     }
     default:
         goto out_fail;
     }
 
-    return;
+    nm_assert_not_reached();
 
 out_fail:
     G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
@@ -981,28 +978,22 @@ _nm_setting_property_set_property_direct(GObject      *object,
         goto out_notify;
     }
     case NM_VALUE_TYPE_STRV:
-    {
-        NMValueStrv       *p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
-        const char *const *v;
-
-        v = g_value_get_boxed(value);
-        if (nm_strvarray_equal_strv(p_val->arr, v, -1))
+        if (!_property_direct_set_strv(sett_info, property_info, setting, g_value_get_boxed(value)))
             return;
-
-        nm_strvarray_set_strv(&p_val->arr, v);
         goto out_notify;
-    }
     default:
         goto out_fail;
     }
 
-    return;
+    nm_assert_not_reached();
 
 out_notify:
-    /* If explicit-notify would be set, we would need to emit g_object_notify_by_pspec().
-     *
-     * Currently we never set that, also because we still support glib 2.40. */
-    nm_assert(!NM_FLAGS_HAS(pspec->flags, 1 << 30 /* G_PARAM_EXPLICIT_NOTIFY */));
+    nm_assert(NM_FLAGS_HAS(pspec->flags, G_PARAM_EXPLICIT_NOTIFY));
+
+    nm_gobject_notify_together_by_pspec(object,
+                                        property_info->param_spec,
+                                        property_info->direct_also_notify);
+
     return;
 
 out_fail:
@@ -1306,6 +1297,11 @@ _nm_setting_property_to_dbus_fcn_direct(_NM_SETT_INFO_PROP_TO_DBUS_FCN_ARGS _nm_
             (const NMValueStrv *) _nm_setting_get_private_field(setting, sett_info, property_info);
         if (!val->arr)
             return NULL;
+        if (!property_info->direct_strv_preserve_empty && val->arr->len == 0) {
+            /* This property does not treat empty strv arrays special. No need
+             * to export the value on D-Bus. */
+            return NULL;
+        }
         return g_variant_new_strv(nm_g_array_data(val->arr), val->arr->len);
     }
     default:
@@ -1393,7 +1389,9 @@ _nm_setting_property_from_dbus_fcn_direct_mac_address(_NM_SETT_INFO_PROP_FROM_DB
 
     if (nm_strdup_reset_take(_nm_setting_get_private_field(setting, sett_info, property_info),
                              length > 0 ? nm_utils_hwaddr_ntoa(array, length) : NULL)) {
-        g_object_notify_by_pspec(G_OBJECT(setting), property_info->param_spec);
+        nm_gobject_notify_together_by_pspec(setting,
+                                            property_info->param_spec,
+                                            property_info->direct_also_notify);
     } else
         *out_is_modified = FALSE;
 
@@ -1670,7 +1668,6 @@ _nm_setting_property_from_dbus_fcn_direct(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS
     }
     case NM_VALUE_TYPE_STRV:
     {
-        NMValueStrv         *p_val;
         gs_free const char **ss = NULL;
         gsize                ss_len;
 
@@ -1681,13 +1678,10 @@ _nm_setting_property_from_dbus_fcn_direct(_NM_SETT_INFO_PROP_FROM_DBUS_FCN_ARGS
 
         ss = g_variant_get_strv(value, &ss_len);
         nm_assert(ss_len <= G_MAXUINT);
+        nm_assert(NM_PTRARRAY_LEN(ss) == ss_len);
 
-        p_val = _nm_setting_get_private_field(setting, sett_info, property_info);
-
-        if (nm_strvarray_equal_strv(p_val->arr, ss, ss_len))
+        if (!_property_direct_set_strv(sett_info, property_info, setting, ss))
             goto out_unchanged;
-
-        nm_strvarray_set_strv(&p_val->arr, ss);
         goto out_notify;
     }
     default:
@@ -1701,7 +1695,9 @@ out_unchanged:
 
 out_notify:
     *out_is_modified = TRUE;
-    g_object_notify_by_pspec(G_OBJECT(setting), property_info->param_spec);
+    nm_gobject_notify_together_by_pspec(setting,
+                                        property_info->param_spec,
+                                        property_info->direct_also_notify);
     return TRUE;
 
 out_error_wrong_dbus_type:
@@ -2409,17 +2405,87 @@ nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error)
     return result == NM_SETTING_VERIFY_SUCCESS || result == NM_SETTING_VERIFY_NORMALIZABLE;
 }
 
+static gboolean
+_verify_properties(NMSetting *setting, GError **error)
+{
+    NMSettingClass          *klass     = NM_SETTING_GET_CLASS(setting);
+    const NMSettInfoSetting *sett_info = _nm_setting_class_get_sett_info(klass);
+    guint16                  i;
+
+    if (!sett_info)
+        return TRUE;
+
+    for (i = 0; i < sett_info->property_infos_len; i++) {
+        const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
+
+        switch (property_info->property_type->direct_type) {
+        case NM_VALUE_TYPE_BOOL:
+        case NM_VALUE_TYPE_BYTES:
+        case NM_VALUE_TYPE_STRV:
+        case NM_VALUE_TYPE_ENUM:
+        case NM_VALUE_TYPE_FLAGS:
+        case NM_VALUE_TYPE_INT32:
+        case NM_VALUE_TYPE_INT64:
+        case NM_VALUE_TYPE_NONE:
+        case NM_VALUE_TYPE_UINT32:
+        case NM_VALUE_TYPE_UINT64:
+            break;
+        case NM_VALUE_TYPE_STRING:
+        {
+            const char *val;
+
+            if (!property_info->direct_string_allow_empty
+                && (val = *((const char *const *)
+                                _nm_setting_get_private_field(setting, sett_info, property_info)))
+                && val[0] == '\0') {
+                g_set_error_literal(error,
+                                    NM_CONNECTION_ERROR,
+                                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                    _("cannot be empty"));
+                g_prefix_error(error,
+                               "%s.%s: ",
+                               klass->setting_info->setting_name,
+                               property_info->name);
+                return FALSE;
+            }
+            break;
+        }
+        default:
+            nm_assert_not_reached();
+        }
+    }
+
+    return TRUE;
+}
+
 NMSettingVerifyResult
 _nm_setting_verify(NMSetting *setting, NMConnection *connection, GError **error)
 {
+    NMSettingClass       *klass;
+    NMSettingVerifyResult r;
+
     g_return_val_if_fail(NM_IS_SETTING(setting), NM_SETTING_VERIFY_ERROR);
     g_return_val_if_fail(!connection || NM_IS_CONNECTION(connection), NM_SETTING_VERIFY_ERROR);
     g_return_val_if_fail(!error || *error == NULL, NM_SETTING_VERIFY_ERROR);
 
-    if (NM_SETTING_GET_CLASS(setting)->verify)
-        return NM_SETTING_GET_CLASS(setting)->verify(setting, connection, error);
+    klass = NM_SETTING_GET_CLASS(setting);
 
-    return NM_SETTING_VERIFY_SUCCESS;
+    if (!klass->verify)
+        return NM_SETTING_VERIFY_SUCCESS;
+
+    r = klass->verify(setting, connection, error);
+
+    if (r != NM_SETTING_VERIFY_ERROR) {
+        gs_free_error GError *local = NULL;
+
+        if (!_verify_properties(setting, &local)) {
+            g_clear_error(error);
+            g_propagate_error(error, g_steal_pointer(&local));
+            r = NM_SETTING_VERIFY_ERROR;
+        }
+    }
+
+    return r;
 }
 
 /**
@@ -2570,7 +2636,9 @@ _nm_setting_property_compare_fcn_direct(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_
 
     nm_assert(NM_IN_SET(property_info->property_type->to_dbus_fcn,
                         _nm_setting_property_to_dbus_fcn_direct,
-                        _nm_setting_property_to_dbus_fcn_direct_mac_address));
+                        _nm_setting_property_to_dbus_fcn_direct_mac_address,
+                        _nm_setting_connection_controller_to_dbus,
+                        _nm_setting_connection_port_type_to_dbus));
 
     if (!property_info->param_spec)
         return nm_assert_unreachable_val(NM_TERNARY_DEFAULT);
@@ -2604,8 +2672,21 @@ _nm_setting_property_compare_fcn_direct(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm_
     case NM_VALUE_TYPE_BYTES:
         return nm_g_bytes_equal0(*((const GBytes *const *) p_a), *((const GBytes *const *) p_b));
     case NM_VALUE_TYPE_STRV:
-        return nm_strvarray_equal(((const NMValueStrv *) p_a)->arr,
-                                  ((const NMValueStrv *) p_b)->arr);
+    {
+        const NMValueStrv *v_a = p_a;
+        const NMValueStrv *v_b = p_b;
+        const GArray      *a   = v_a->arr;
+        const GArray      *b   = v_b->arr;
+
+        if (!property_info->direct_strv_preserve_empty) {
+            /* NULL and empty are treated identical. Coerce to NULL. */
+            if (a && a->len == 0)
+                a = NULL;
+            if (b && b->len == 0)
+                b = NULL;
+        }
+        return nm_strvarray_equal(a, b);
+    }
     default:
         return nm_assert_unreachable_val(TRUE);
     }
@@ -2643,7 +2724,7 @@ _nm_setting_property_compare_fcn_default(_NM_SETT_INFO_PROP_COMPARE_FCN_ARGS _nm
                                   NM_CONNECTION_SERIALIZE_ALL,
                                   NULL,
                                   TRUE);
-        return nm_property_compare(value1, value2) == 0;
+        return nm_g_variant_equal(value1, value2);
     }
 }
 
@@ -3617,6 +3698,15 @@ const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u =
         /* No functions set. This property type is to silently ignore the value on D-Bus. */
         .compare_fcn = _nm_setting_property_compare_fcn_ignore);
 
+/* This should not be used for new strv properties. Use nm_sett_info_propert_type_direct_strv.
+ *
+ * FIXME: existing properties should migrate to nm_sett_info_propert_type_direct_strv. */
+const NMSettInfoPropertType nm_sett_info_propert_type_gprop_strv_oldstyle =
+    NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(G_VARIANT_TYPE_STRING_ARRAY,
+                                         .compare_fcn   = _nm_setting_property_compare_fcn_default,
+                                         .from_dbus_fcn = _nm_setting_property_from_dbus_fcn_gprop,
+                                         .from_dbus_is_full = TRUE);
+
 const NMSettInfoPropertType nm_sett_info_propert_type_direct_boolean =
     NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(G_VARIANT_TYPE_BOOLEAN,
                                         .direct_type   = NM_VALUE_TYPE_BOOL,
@@ -4374,7 +4464,13 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
 
 static void
 nm_setting_init(NMSetting *setting)
-{}
+{
+    NMSettingPrivate *priv;
+
+    priv = G_TYPE_INSTANCE_GET_PRIVATE(setting, NM_TYPE_SETTING, NMSettingPrivate);
+
+    setting->_priv = priv;
+}
 
 static void
 constructed(GObject *object)
diff --git a/src/libnm-core-impl/nm-simple-connection.c b/src/libnm-core-impl/nm-simple-connection.c
index a9c66dc7..7d4395ff 100644
--- a/src/libnm-core-impl/nm-simple-connection.c
+++ b/src/libnm-core-impl/nm-simple-connection.c
@@ -30,13 +30,10 @@ int         _nm_simple_connection_private_offset;
  */
 struct _NMSimpleConnection {
     GObject parent;
-    /* In the past, this struct was public API. Preserve ABI! */
 };
 
 struct _NMSimpleConnectionClass {
     GObjectClass parent;
-    /* In the past, this struct was public API. Preserve ABI! */
-    gpointer padding[4];
 };
 
 static void nm_simple_connection_interface_init(NMConnectionInterface *iface);
diff --git a/src/libnm-core-impl/nm-team-utils.c b/src/libnm-core-impl/nm-team-utils.c
index 2f62f6c6..6f2f5dd2 100644
--- a/src/libnm-core-impl/nm-team-utils.c
+++ b/src/libnm-core-impl/nm-team-utils.c
@@ -2244,7 +2244,7 @@ _team_setting_verify_properties(const NMTeamSetting *self, GError **error)
             } else if (attr_data->value_type == NM_VALUE_TYPE_STRING) {
                 const char *v = *((const char *const *) p_field);
 
-                if (nm_strv_find_first(attr_data->range.r_string.valid_names, -1, v) < 0) {
+                if (!nm_strv_contains(attr_data->range.r_string.valid_names, -1, v)) {
                     g_set_error(error,
                                 NM_CONNECTION_ERROR,
                                 NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -2263,7 +2263,7 @@ _team_setting_verify_properties(const NMTeamSetting *self, GError **error)
                 for (i = 0; i < self->d.master.runner_tx_hash->len; i++) {
                     const char *val = self->d.master.runner_tx_hash->pdata[i];
 
-                    if (!val || (nm_strv_find_first(_valid_names_runner_tx_hash, -1, val) < 0)) {
+                    if (!val || !nm_strv_contains(_valid_names_runner_tx_hash, -1, val)) {
                         g_set_error(error,
                                     NM_CONNECTION_ERROR,
                                     NM_CONNECTION_ERROR_INVALID_SETTING,
@@ -2289,7 +2289,7 @@ _team_setting_verify_properties(const NMTeamSetting *self, GError **error)
             if (!_team_setting_has_field(self, attr_data))
                 continue;
             if (self->d.master.runner
-                && (nm_strv_find_first(e->valid_runners, -1, self->d.master.runner) >= 0))
+                && nm_strv_contains(e->valid_runners, -1, self->d.master.runner))
                 continue;
             if (e->valid_runners[1] == NULL) {
                 g_set_error(error,
@@ -2812,9 +2812,9 @@ nm_team_setting_new(gboolean is_port, const char *js_str)
     gsize          l;
 
     G_STATIC_ASSERT_EXPR(sizeof(*self) == sizeof(self->_data_priv));
-    G_STATIC_ASSERT_EXPR(sizeof(*self)
-                         == NM_CONST_MAX(nm_offsetofend(NMTeamSetting, d.master),
-                                         nm_offsetofend(NMTeamSetting, d.port)));
+    G_STATIC_ASSERT_EXPR(
+        sizeof(*self)
+        == NM_MAX(nm_offsetofend(NMTeamSetting, d.master), nm_offsetofend(NMTeamSetting, d.port)));
 
     l = is_port ? nm_offsetofend(NMTeamSetting, d.port) : nm_offsetofend(NMTeamSetting, d.master);
 
diff --git a/src/libnm-core-impl/nm-team-utils.h b/src/libnm-core-impl/nm-team-utils.h
index 06fa6cc8..0210cb26 100644
--- a/src/libnm-core-impl/nm-team-utils.h
+++ b/src/libnm-core-impl/nm-team-utils.h
@@ -6,7 +6,7 @@
 #ifndef __NM_TEAM_UITLS_H__
 #define __NM_TEAM_UITLS_H__
 
-#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
 #error Cannot use this header.
 #endif
 
@@ -48,7 +48,8 @@ typedef enum {
     NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,
     _NM_TEAM_ATTRIBUTE_PORT_NUM,
 
-    _NM_TEAM_ATTRIBUTE_NUM = MAX(_NM_TEAM_ATTRIBUTE_MASTER_NUM, _NM_TEAM_ATTRIBUTE_PORT_NUM),
+    _NM_TEAM_ATTRIBUTE_NUM =
+        NM_MAX_CONST(_NM_TEAM_ATTRIBUTE_MASTER_NUM, _NM_TEAM_ATTRIBUTE_PORT_NUM),
 
 } NMTeamAttribute;
 
diff --git a/src/libnm-core-impl/nm-utils-private.h b/src/libnm-core-impl/nm-utils-private.h
index e2e1aff7..1521c0f9 100644
--- a/src/libnm-core-impl/nm-utils-private.h
+++ b/src/libnm-core-impl/nm-utils-private.h
@@ -6,7 +6,7 @@
 #ifndef __NM_UTILS_PRIVATE_H__
 #define __NM_UTILS_PRIVATE_H__
 
-#if !((NETWORKMANAGER_COMPILATION) &NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
+#if !((NETWORKMANAGER_COMPILATION) & NM_NETWORKMANAGER_COMPILATION_WITH_LIBNM_CORE_PRIVATE)
 #error Cannot use this header.
 #endif
 
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 2bd8936d..88df3291 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -36,6 +36,7 @@
 #include "nm-setting-vlan.h"
 #include "nm-setting-wired.h"
 #include "nm-setting-wireless.h"
+#include "nm-errors.h"
 
 /**
  * SECTION:nm-utils
@@ -654,8 +655,13 @@ nm_utils_is_empty_ssid(const guint8 *ssid, gsize len)
  * representation of that character.  Intended for debugging only, should not
  * be used for display of SSIDs.
  *
+ * Warning: this function uses a static buffer. It is not thread-safe. Don't
+ *   use this function.
+ *
  * Returns: pointer to the escaped SSID, which uses an internal static buffer
  * and will be overwritten by subsequent calls to this function
+ *
+ * Deprecated: 1.46: use nm_utils_ssid_to_utf8() or nm_utils_bin2hexstr().
  **/
 const char *
 nm_utils_escape_ssid(const guint8 *ssid, gsize len)
@@ -669,7 +675,7 @@ nm_utils_escape_ssid(const guint8 *ssid, gsize len)
         return escaped;
     }
 
-    len = MIN(len, (guint32) NM_IW_ESSID_MAX_SIZE);
+    len = NM_MIN(len, (guint32) NM_IW_ESSID_MAX_SIZE);
     while (len--) {
         if (*s == '\0') {
             *d++ = '\\';
@@ -897,42 +903,6 @@ _nm_utils_slist_to_strv(const GSList *slist, gboolean deep_copy)
     return strv;
 }
 
-GPtrArray *
-nm_strv_to_ptrarray(char **strv)
-{
-    GPtrArray *ptrarray;
-    gsize      i, l;
-
-    l = NM_PTRARRAY_LEN(strv);
-
-    ptrarray = g_ptr_array_new_full(l, g_free);
-
-    if (strv) {
-        for (i = 0; strv[i]; i++)
-            g_ptr_array_add(ptrarray, g_strdup(strv[i]));
-    }
-
-    return ptrarray;
-}
-
-char **
-_nm_utils_ptrarray_to_strv(const GPtrArray *ptrarray)
-{
-    char **strv;
-    guint  i;
-
-    if (!ptrarray)
-        return g_new0(char *, 1);
-
-    strv = g_new(char *, ptrarray->len + 1);
-
-    for (i = 0; i < ptrarray->len; i++)
-        strv[i] = g_strdup(ptrarray->pdata[i]);
-    strv[i] = NULL;
-
-    return strv;
-}
-
 /*****************************************************************************/
 
 static gboolean
@@ -1356,17 +1326,49 @@ nm_utils_dns_to_variant(int addr_family, const char *const *dns, gssize len)
  * Utility function to convert a #GVariant of type 'au' representing a list of
  * IPv4 addresses into an array of IP address strings.
  *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
+ *
  * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address strings.
  **/
 char **
 nm_utils_ip4_dns_from_variant(GVariant *value)
 {
+    return _nm_utils_ip4_dns_from_variant(value, FALSE, NULL);
+}
+
+/**
+ * _nm_utils_ip4_dns_from_variant:
+ * @value: a #GVariant of type 'au'
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip4_dns_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address
+ *   strings. In strict mode, %NULL is returned on error.
+ */
+char **
+_nm_utils_ip4_dns_from_variant(GVariant *value, bool strict, GError **error)
+{
     const guint32 *array;
     gsize          length;
     char         **dns;
     gsize          i;
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("au")), NULL);
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("au"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"au\""));
+            return NULL;
+        }
+        dns    = g_new(char *, 1);
+        dns[0] = NULL;
+        return dns;
+    }
 
     array = g_variant_get_fixed_array(value, &length, sizeof(guint32));
     dns   = g_new(char *, length + 1u);
@@ -1437,7 +1439,11 @@ nm_utils_ip4_addresses_to_variant(GPtrArray *addresses, const char *gateway)
  * NetworkManager IPv4 addresses (which are tuples of address, prefix, and
  * gateway) into a #GPtrArray of #NMIPAddress objects. The "gateway" field of
  * the first address (if set) will be returned in @out_gateway; the "gateway" fields
- * of the other addresses are ignored.
+ * of the other addresses are ignored. Note that invalid addresses are discarded
+ * but the valid addresses are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
  *   #GPtrArray of #NMIPAddress objects
@@ -1445,46 +1451,132 @@ nm_utils_ip4_addresses_to_variant(GPtrArray *addresses, const char *gateway)
 GPtrArray *
 nm_utils_ip4_addresses_from_variant(GVariant *value, char **out_gateway)
 {
-    GPtrArray   *addresses;
-    GVariantIter iter;
-    GVariant    *addr_var;
+    return _nm_utils_ip4_addresses_from_variant(value, NULL, out_gateway, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aau")), NULL);
+/**
+ * _nm_utils_ip4_addresses_from_variant:
+ * @value: a #GVariant of type 'aau'
+ * @labels: (optional) (nullable): a #GVariant of the type 'as'. If not-NULL,
+ *   each element must contain a string with the labels that corresponds to each
+ *   IP address in @value.
+ * @out_gateway: (out) (optional) (nullable) (transfer full): on return, will
+ *   contain the IP gateway
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip4_addresses_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned. It also
+ * allows to parse the address-labels at the same time than the addresses.
+ *
+ * The labels need to be processed at the same time than the addresses, inside
+ * this function, because if there are invalid addresses they are filtered out,
+ * and the returned array of addresses contains less elements than the original
+ * array. If that happens, the caller don't know what label corresponds to what
+ * address, because they are matched by position in the array. If you are not
+ * interested in the labels, just set @labels to NULL.
+ *
+ * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
+ *   #GPtrArray of #NMIPAddress objects. In strict mode, %NULL is returned on error.
+ */
+GPtrArray *
+_nm_utils_ip4_addresses_from_variant(GVariant *value,
+                                     GVariant *labels,
+                                     char    **out_gateway,
+                                     bool      strict,
+                                     GError  **error)
+{
+    gs_unref_ptrarray GPtrArray *addresses = NULL;
+    GVariantIter                 iter;
+    GVariant                    *item;
+    const guint32               *addr_array;
+    gsize                        length;
+    NMIPAddress                 *addr;
+    const char                  *label;
+    gsize                        n_labels = 0;
+    guint                        i;
 
+    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
     if (out_gateway)
         *out_gateway = NULL;
 
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aau"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"aau\""));
+            return NULL;
+        }
+        return g_steal_pointer(&addresses);
+    }
+
+    if (labels && !g_variant_is_of_type(labels, G_VARIANT_TYPE("as"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected \"address-labels\" of type \"as\""));
+            return NULL;
+        }
+        /* We still can parse the addresses, without the labels */
+        labels = NULL;
+    }
+
+    if (labels)
+        n_labels = g_variant_n_children(labels);
+
     g_variant_iter_init(&iter, value);
-    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
 
-    while (g_variant_iter_next(&iter, "@au", &addr_var)) {
-        const guint32 *addr_array;
-        gsize          length;
-        NMIPAddress   *addr;
-        GError        *error = NULL;
+    for (i = 0; g_variant_iter_next(&iter, "@au", &item); i++) {
+        gs_unref_variant GVariant *addr_var    = item;
+        gs_free_error GError      *local_error = NULL;
 
         addr_array = g_variant_get_fixed_array(addr_var, &length, sizeof(guint32));
         if (length < 3) {
-            g_warning("Ignoring invalid IP4 address");
-            g_variant_unref(addr_var);
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Incomplete IPv4 address (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
+        }
+
+        addr = nm_ip_address_new_binary(AF_INET, &addr_array[0], addr_array[1], &local_error);
+        if (!addr) {
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
             continue;
         }
 
-        addr = nm_ip_address_new_binary(AF_INET, &addr_array[0], addr_array[1], &error);
-        if (addr) {
-            g_ptr_array_add(addresses, addr);
+        /* We were accepting address-labels to be shorter than addresses, so
+         * let's continue doing so and not consider it as an error */
+        if (labels && i < n_labels) {
+            g_variant_get_child(labels, i, "&s", &label);
 
-            if (addr_array[2] && out_gateway && !*out_gateway)
-                *out_gateway = nm_inet4_ntop_dup(addr_array[2]);
-        } else {
-            g_warning("Ignoring invalid IP4 address: %s", error->message);
-            g_clear_error(&error);
+            if (label && label[0]) {
+                nm_ip_address_set_attribute(addr,
+                                            NM_IP_ADDRESS_ATTRIBUTE_LABEL,
+                                            g_variant_new_string(label));
+            }
         }
 
-        g_variant_unref(addr_var);
+        g_ptr_array_add(addresses, addr);
+        if (addr_array[2] && out_gateway && !*out_gateway)
+            *out_gateway = nm_inet4_ntop_dup(addr_array[2]);
     }
 
-    return addresses;
+    return g_steal_pointer(&addresses);
 }
 
 /**
@@ -1518,7 +1610,7 @@ nm_utils_ip4_routes_to_variant(GPtrArray *routes)
             array[1] = nm_ip_route_get_prefix(route);
             nm_ip_route_get_next_hop_binary(route, &array[2]);
             /* The old routes format uses "0" for default, not "-1" */
-            array[3] = MAX(0, nm_ip_route_get_metric(route));
+            array[3] = NM_MAX(0, nm_ip_route_get_metric(route));
 
             g_variant_builder_add(&builder, "@au", nm_g_variant_new_au(array, 4));
         }
@@ -1533,7 +1625,11 @@ nm_utils_ip4_routes_to_variant(GPtrArray *routes)
  *
  * Utility function to convert a #GVariant of type 'aau' representing an array
  * of NetworkManager IPv4 routes (which are tuples of route, prefix, next hop,
- * and metric) into a #GPtrArray of #NMIPRoute objects.
+ * and metric) into a #GPtrArray of #NMIPRoute objects. Note that invalid routes
+ * are discarded but the valid routes are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
  *   #GPtrArray of #NMIPRoute objects
@@ -1541,25 +1637,61 @@ nm_utils_ip4_routes_to_variant(GPtrArray *routes)
 GPtrArray *
 nm_utils_ip4_routes_from_variant(GVariant *value)
 {
-    GVariantIter iter;
-    GVariant    *route_var;
-    GPtrArray   *routes;
+    return _nm_utils_ip4_routes_from_variant(value, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aau")), NULL);
+/**
+ * _nm_utils_ip4_routes_from_variant:
+ * @value: #GVariant of type 'aau'
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip4_routes_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
+ *   #GPtrArray of #NMIPRoute objects. In strict mode, NULL is returned on error.
+ */
+GPtrArray *
+_nm_utils_ip4_routes_from_variant(GVariant *value, bool strict, GError **error)
+{
+    gs_unref_ptrarray GPtrArray *routes = NULL;
+    GVariantIter                 iter;
+    GVariant                    *item;
+    const guint32               *route_array;
+    gsize                        length;
+    NMIPRoute                   *route;
+    guint                        i;
 
-    g_variant_iter_init(&iter, value);
     routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
 
-    while (g_variant_iter_next(&iter, "@au", &route_var)) {
-        const guint32 *route_array;
-        gsize          length;
-        NMIPRoute     *route;
-        GError        *error = NULL;
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aau"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"aau\""));
+            return NULL;
+        }
+        return g_steal_pointer(&routes);
+    }
+
+    g_variant_iter_init(&iter, value);
+
+    for (i = 0; g_variant_iter_next(&iter, "@au", &item); i++) {
+        gs_unref_variant GVariant *route_var   = item;
+        gs_free_error GError      *local_error = NULL;
 
         route_array = g_variant_get_fixed_array(route_var, &length, sizeof(guint32));
         if (length < 4) {
-            g_warning("Ignoring invalid IP4 route");
-            g_variant_unref(route_var);
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Incomplete IPv4 route (idx=%u)"),
+                            i);
+                return NULL;
+            }
             continue;
         }
 
@@ -1569,17 +1701,24 @@ nm_utils_ip4_routes_from_variant(GVariant *value)
                                        &route_array[2],
                                        /* The old routes format uses "0" for default, not "-1" */
                                        route_array[3] ? (gint64) route_array[3] : -1,
-                                       &error);
-        if (route)
-            g_ptr_array_add(routes, route);
-        else {
-            g_warning("Ignoring invalid IP4 route: %s", error->message);
-            g_clear_error(&error);
+                                       &local_error);
+        if (!route) {
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
+            continue;
         }
-        g_variant_unref(route_var);
+
+        g_ptr_array_add(routes, route);
     }
 
-    return routes;
+    return g_steal_pointer(&routes);
 }
 
 /**
@@ -1641,34 +1780,79 @@ nm_utils_ip4_get_default_prefix(guint32 ip)
  * a IPv6 address in binary form (16 bytes long). Invalid entries are silently
  * ignored.
  *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
+ *
  * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address strings.
  **/
 char **
 nm_utils_ip6_dns_from_variant(GVariant *value)
 {
-    GVariantIter iter;
-    GVariant    *ip_var;
-    char       **dns;
-    gsize        i;
+    return _nm_utils_ip6_dns_from_variant(value, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aay")), NULL);
+/**
+ * _nm_utils_ip6_dns_from_variant:
+ * @value: a #GVariant of type 'aay'
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip6_dns_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (type utf8): a %NULL-terminated array of IP address
+ *   strings. In strict mode, %NULL is returned on error.
+ **/
+char **
+_nm_utils_ip6_dns_from_variant(GVariant *value, bool strict, GError **error)
+{
+    gs_strfreev char **dns = NULL;
+    GVariantIter       iter;
+    GVariant          *item;
+    guint              i, j;
+
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aay"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"aay\""));
+            return NULL;
+        }
+        dns    = g_new(char *, 1);
+        dns[0] = NULL;
+        return g_steal_pointer(&dns);
+    }
 
-    dns = g_new(char *, g_variant_n_children(value) + 1);
+    dns    = g_new(char *, g_variant_n_children(value) + 1);
+    dns[0] = NULL;
 
     g_variant_iter_init(&iter, value);
-    i = 0;
-    while (g_variant_iter_next(&iter, "@ay", &ip_var)) {
-        gsize                  length;
-        const struct in6_addr *ip = g_variant_get_fixed_array(ip_var, &length, 1);
 
-        if (length == sizeof(struct in6_addr))
-            dns[i++] = nm_inet6_ntop_dup(ip);
+    for (i = 0, j = 0; g_variant_iter_next(&iter, "@ay", &item); i++) {
+        gs_unref_variant GVariant *ip_var = item;
+        const struct in6_addr     *ip;
+        gsize                      length;
 
-        g_variant_unref(ip_var);
+        ip = g_variant_get_fixed_array(ip_var, &length, 1);
+
+        if (length != sizeof(struct in6_addr)) {
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Invalid IPv6 DNS address length (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
+        }
+
+        dns[j]   = nm_inet6_ntop_dup(ip);
+        dns[++j] = NULL;
     }
-    dns[i] = NULL;
 
-    return dns;
+    return g_steal_pointer(&dns);
 }
 
 /**
@@ -1733,7 +1917,11 @@ nm_utils_ip6_addresses_to_variant(GPtrArray *addresses, const char *gateway)
  * list of NetworkManager IPv6 addresses (which are tuples of address, prefix,
  * and gateway) into a #GPtrArray of #NMIPAddress objects. The "gateway" field
  * of the first address (if set) will be returned in @out_gateway; the "gateway"
- * fields of the other addresses are ignored.
+ * fields of the other addresses are ignored. Note that invalid addresses are
+ * discarded but the valid addresses are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
  *   #GPtrArray of #NMIPAddress objects
@@ -1741,63 +1929,123 @@ nm_utils_ip6_addresses_to_variant(GPtrArray *addresses, const char *gateway)
 GPtrArray *
 nm_utils_ip6_addresses_from_variant(GVariant *value, char **out_gateway)
 {
-    GVariantIter iter;
-    GVariant    *addr_var, *gateway_var;
-    guint32      prefix;
-    GPtrArray   *addresses;
+    return _nm_utils_ip6_addresses_from_variant(value, out_gateway, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuay)")), NULL);
+/**
+ * _nm_utils_ip6_addresses_from_variant:
+ * @value: a #GVariant of type 'a(ayuay)'
+ * @out_gateway: (out) (optional) (nullable) (transfer full): on return, will
+ *   contain the IP gateway
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip6_addresses_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
+ *   #GPtrArray of #NMIPAddress objects. In strict mode, %NULL is returned on error.
+ **/
+GPtrArray *
+_nm_utils_ip6_addresses_from_variant(GVariant *value,
+                                     char    **out_gateway,
+                                     bool      strict,
+                                     GError  **error)
+{
+    gs_unref_ptrarray GPtrArray *addresses = NULL;
+    GVariantIter                 iter;
+    GVariant                    *addr_item;
+    GVariant                    *gateway_item;
+    guint32                      prefix;
+    guint                        i;
 
+    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
     if (out_gateway)
         *out_gateway = NULL;
 
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuay)"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"a(ayuay)\""));
+            return NULL;
+        }
+        return g_steal_pointer(&addresses);
+    }
+
     g_variant_iter_init(&iter, value);
-    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
 
-    while (g_variant_iter_next(&iter, "(@ayu@ay)", &addr_var, &prefix, &gateway_var)) {
-        NMIPAddress           *addr;
-        const struct in6_addr *addr_bytes, *gateway_bytes;
-        gsize                  addr_len, gateway_len;
-        GError                *error = NULL;
+    for (i = 0; g_variant_iter_next(&iter, "(@ayu@ay)", &addr_item, &prefix, &gateway_item); i++) {
+        gs_unref_variant GVariant *addr_var    = addr_item;
+        gs_unref_variant GVariant *gateway_var = gateway_item;
+        gs_free_error GError      *local_error = NULL;
+        NMIPAddress               *addr;
+        const struct in6_addr     *addr_bytes, *gateway_bytes;
+        gsize                      addr_len, gateway_len;
 
         if (!g_variant_is_of_type(addr_var, G_VARIANT_TYPE_BYTESTRING)
             || !g_variant_is_of_type(gateway_var, G_VARIANT_TYPE_BYTESTRING)) {
-            g_warning("%s: ignoring invalid IP6 address structure", __func__);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Expected value of type \"(ayuay)\" (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
         addr_bytes = g_variant_get_fixed_array(addr_var, &addr_len, 1);
         if (addr_len != 16) {
-            g_warning("%s: ignoring invalid IP6 address of length %d", __func__, (int) addr_len);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("IPv6 address with invalid length (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
+        }
+
+        addr = nm_ip_address_new_binary(AF_INET6, addr_bytes, prefix, &local_error);
+        if (!addr) {
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
-        addr = nm_ip_address_new_binary(AF_INET6, addr_bytes, prefix, &error);
-        if (addr) {
-            g_ptr_array_add(addresses, addr);
+        g_ptr_array_add(addresses, addr);
 
-            if (out_gateway && !*out_gateway) {
-                gateway_bytes = g_variant_get_fixed_array(gateway_var, &gateway_len, 1);
-                if (gateway_len != 16) {
-                    g_warning("%s: ignoring invalid IP6 address of length %d",
-                              __func__,
-                              (int) gateway_len);
-                    goto next;
+        if (out_gateway && !*out_gateway) {
+            gateway_bytes = g_variant_get_fixed_array(gateway_var, &gateway_len, 1);
+            if (gateway_len != 16) {
+                if (strict) {
+                    g_set_error(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("IPv6 gateway with invalid length (idx=%u)"),
+                                i);
+                    return NULL;
                 }
-                if (!IN6_IS_ADDR_UNSPECIFIED(gateway_bytes))
-                    *out_gateway = nm_inet6_ntop_dup(gateway_bytes);
+                continue;
             }
-        } else {
-            g_warning("Ignoring invalid IP6 address: %s", error->message);
-            g_clear_error(&error);
-        }
 
-next:
-        g_variant_unref(addr_var);
-        g_variant_unref(gateway_var);
+            if (!IN6_IS_ADDR_UNSPECIFIED(gateway_bytes))
+                NM_SET_OUT(out_gateway, nm_inet6_ntop_dup(gateway_bytes));
+        }
     }
 
-    return addresses;
+    return g_steal_pointer(&addresses);
 }
 
 /**
@@ -1853,7 +2101,11 @@ nm_utils_ip6_routes_to_variant(GPtrArray *routes)
  *
  * Utility function to convert a #GVariant of type 'a(ayuayu)' representing an
  * array of NetworkManager IPv6 routes (which are tuples of route, prefix, next
- * hop, and metric) into a #GPtrArray of #NMIPRoute objects.
+ * hop, and metric) into a #GPtrArray of #NMIPRoute objects. Note that invalid
+ * routes are ignored but the valid ones are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
  *   #GPtrArray of #NMIPRoute objects
@@ -1861,40 +2113,93 @@ nm_utils_ip6_routes_to_variant(GPtrArray *routes)
 GPtrArray *
 nm_utils_ip6_routes_from_variant(GVariant *value)
 {
-    GPtrArray             *routes;
-    GVariantIter           iter;
-    GVariant              *dest_var, *next_hop_var;
-    const struct in6_addr *dest, *next_hop;
-    gsize                  dest_len, next_hop_len;
-    guint32                prefix, metric;
+    return _nm_utils_ip6_routes_from_variant(value, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuayu)")), NULL);
+/**
+ * _nm_utils_ip6_routes_from_variant:
+ * @value: #GVariant of type 'a(ayuayu)'
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip6_routes_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
+ *   #GPtrArray of #NMIPRoute objects. In strict mode, %NULL is returned on error.
+ **/
+GPtrArray *
+_nm_utils_ip6_routes_from_variant(GVariant *value, bool strict, GError **error)
+{
+    gs_unref_ptrarray GPtrArray *routes = NULL;
+    GVariantIter                 iter;
+    GVariant                    *dest_item;
+    GVariant                    *next_hop_item;
+    guint32                      prefix, metric;
+    guint                        i;
 
     routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
 
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("a(ayuayu)"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"a(ayuayu)\""));
+            return NULL;
+        }
+        return g_steal_pointer(&routes);
+    }
+
     g_variant_iter_init(&iter, value);
-    while (g_variant_iter_next(&iter, "(@ayu@ayu)", &dest_var, &prefix, &next_hop_var, &metric)) {
-        NMIPRoute *route;
-        GError    *error = NULL;
+
+    for (i = 0;
+         g_variant_iter_next(&iter, "(@ayu@ayu)", &dest_item, &prefix, &next_hop_item, &metric);
+         i++) {
+        gs_unref_variant GVariant *dest_var     = dest_item;
+        gs_unref_variant GVariant *next_hop_var = next_hop_item;
+        gs_free_error GError      *local_error  = NULL;
+        NMIPRoute                 *route;
+        const struct in6_addr     *dest, *next_hop;
+        gsize                      dest_len, next_hop_len;
 
         if (!g_variant_is_of_type(dest_var, G_VARIANT_TYPE_BYTESTRING)
             || !g_variant_is_of_type(next_hop_var, G_VARIANT_TYPE_BYTESTRING)) {
-            g_warning("%s: ignoring invalid IP6 address structure", __func__);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Expected value of type \"(ayuayu)\" (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
         dest = g_variant_get_fixed_array(dest_var, &dest_len, 1);
         if (dest_len != 16) {
-            g_warning("%s: ignoring invalid IP6 address of length %d", __func__, (int) dest_len);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("IPv6 dest address with invalid length (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
         next_hop = g_variant_get_fixed_array(next_hop_var, &next_hop_len, 1);
         if (next_hop_len != 16) {
-            g_warning("%s: ignoring invalid IP6 address of length %d",
-                      __func__,
-                      (int) next_hop_len);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("IPv6 next-hop address with invalid length (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
         route = nm_ip_route_new_binary(AF_INET6,
@@ -1902,20 +2207,24 @@ nm_utils_ip6_routes_from_variant(GVariant *value)
                                        prefix,
                                        next_hop,
                                        metric ? (gint64) metric : -1,
-                                       &error);
-        if (route)
-            g_ptr_array_add(routes, route);
-        else {
-            g_warning("Ignoring invalid IP6 route: %s", error->message);
-            g_clear_error(&error);
+                                       &local_error);
+        if (!route) {
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
-next:
-        g_variant_unref(dest_var);
-        g_variant_unref(next_hop_var);
+        g_ptr_array_add(routes, route);
     }
 
-    return routes;
+    return g_steal_pointer(&routes);
 }
 
 /**
@@ -1980,7 +2289,11 @@ nm_utils_ip_addresses_to_variant(GPtrArray *addresses)
  * Utility function to convert a #GVariant representing a list of new-style
  * NetworkManager IPv4 or IPv6 addresses (as described in the documentation for
  * nm_utils_ip_addresses_to_variant()) into a #GPtrArray of #NMIPAddress
- * objects.
+ * objects. Note that invalid addresses are discarded but the valid addresses
+ * are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
  *   #GPtrArray of #NMIPAddress objects
@@ -1990,49 +2303,92 @@ nm_utils_ip_addresses_to_variant(GPtrArray *addresses)
 GPtrArray *
 nm_utils_ip_addresses_from_variant(GVariant *value, int family)
 {
-    GPtrArray   *addresses;
-    GVariantIter iter, attrs_iter;
-    GVariant    *addr_var;
-    const char  *ip;
-    guint32      prefix;
-    const char  *attr_name;
-    GVariant    *attr_val;
-    NMIPAddress *addr;
-    GError      *error = NULL;
+    return _nm_utils_ip_addresses_from_variant(value, family, FALSE, NULL);
+}
+
+/**
+ * _nm_utils_ip_addresses_from_variant:
+ * @value: a #GVariant of type 'aa{sv}'
+ * @family: an IP address family
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip_addresses_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (element-type NMIPAddress): a newly allocated
+ *   #GPtrArray of #NMIPAddress objects. In strict mode, %NULL is returned on error.
+ */
+GPtrArray *
+_nm_utils_ip_addresses_from_variant(GVariant *value, int family, bool strict, GError **error)
+{
+    gs_unref_ptrarray GPtrArray *addresses = NULL;
+    GVariantIter                 iter, attrs_iter;
+    GVariant                    *item;
+    const char                  *ip;
+    guint32                      prefix;
+    NMIPAddress                 *addr;
+    const char                  *attr_name;
+    guint                        i;
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL);
+    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
+
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"aa{sv}\""));
+            return NULL;
+        }
+        return g_steal_pointer(&addresses);
+    }
 
     g_variant_iter_init(&iter, value);
-    addresses = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_address_unref);
 
-    while (g_variant_iter_next(&iter, "@a{sv}", &addr_var)) {
+    for (i = 0; g_variant_iter_next(&iter, "@a{sv}", &item); i++) {
+        gs_unref_variant GVariant *addr_var    = item;
+        gs_free_error GError      *local_error = NULL;
+
         if (!g_variant_lookup(addr_var, "address", "&s", &ip)
             || !g_variant_lookup(addr_var, "prefix", "u", &prefix)) {
-            g_warning("Ignoring invalid address");
-            g_variant_unref(addr_var);
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("IP address requires fields \"dest\" and \"prefix\" (idx=%u)"),
+                            i);
+                return NULL;
+            }
             continue;
         }
 
-        addr = nm_ip_address_new(family, ip, prefix, &error);
+        addr = nm_ip_address_new(family, ip, prefix, &local_error);
         if (!addr) {
-            g_warning("Ignoring invalid address: %s", error->message);
-            g_clear_error(&error);
-            g_variant_unref(addr_var);
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
             continue;
         }
 
         g_variant_iter_init(&attrs_iter, addr_var);
-        while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &attr_val)) {
+        while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &item)) {
+            gs_unref_variant GVariant *attr_val = item;
+
             if (!NM_IN_STRSET(attr_name, "address", "prefix"))
                 nm_ip_address_set_attribute(addr, attr_name, attr_val);
-            g_variant_unref(attr_val);
         }
 
-        g_variant_unref(addr_var);
         g_ptr_array_add(addresses, addr);
     }
 
-    return addresses;
+    return g_steal_pointer(&addresses);
 }
 
 /**
@@ -2041,8 +2397,11 @@ nm_utils_ip_addresses_from_variant(GVariant *value, int family)
  *
  * Utility function to convert a #GPtrArray of #NMIPRoute objects representing
  * IPv4 or IPv6 routes into a #GVariant of type 'aa{sv}' representing an array
- * of new-style NetworkManager IP routes (which are tuples of destination,
- * prefix, next hop, metric, and additional attributes).
+ * of new-style NetworkManager IP routes. All routes will include "dest" (an IP
+ * address string), "prefix" (an uint) and optionally "next-hop" (an IP address
+ * string) and "metric" (an uint). Some routes may include additional attributes.
+ * Note that invalid routes are discarded and only a warning is emitted, but the
+ * valid routes are still returned.
  *
  * Returns: (transfer none): a new floating #GVariant representing @routes.
  *
@@ -2107,9 +2466,12 @@ nm_utils_ip_routes_to_variant(GPtrArray *routes)
  * @family: an IP address family
  *
  * Utility function to convert a #GVariant representing a list of new-style
- * NetworkManager IPv4 or IPv6 addresses (which are tuples of destination,
- * prefix, next hop, metric, and additional attributes) into a #GPtrArray of
- * #NMIPRoute objects.
+ * NetworkManager IPv4 or IPv6 addresses (as described in the documentation for
+ * nm_utils_ip_routes_to_variant()) into a #GPtrArray of #NMIPRoute objects.
+ * Invalid routes are discarded but the valid routes are still returned.
+ *
+ * Since 1.46, an empty list is returned if the variant type is not valid
+ * (before it was checked as assertion)
  *
  * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
  *   #GPtrArray of #NMIPRoute objects
@@ -2119,27 +2481,65 @@ nm_utils_ip_routes_to_variant(GPtrArray *routes)
 GPtrArray *
 nm_utils_ip_routes_from_variant(GVariant *value, int family)
 {
-    GPtrArray   *routes;
-    GVariantIter iter, attrs_iter;
-    GVariant    *route_var;
-    const char  *dest, *next_hop;
-    guint32      prefix, metric32;
-    gint64       metric;
-    const char  *attr_name;
-    GVariant    *attr_val;
-    NMIPRoute   *route;
-    GError      *error = NULL;
+    return _nm_utils_ip_routes_from_variant(value, family, FALSE, NULL);
+}
 
-    g_return_val_if_fail(g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}")), NULL);
+/**
+ * _nm_utils_ip_routes_from_variant:
+ * @value: a #GVariant of type 'aa{sv}'
+ * @family: an IP address family
+ * @strict: whether to parse in strict mode or best-effort mode
+ * @error: the error location
+ *
+ * Like #nm_utils_ip_routes_from_variant, but allows to parse in strict mode. In
+ * strict mode, parsing is aborted on first error and %NULL is returned.
+ *
+ * Returns: (transfer full) (element-type NMIPRoute): a newly allocated
+ *   #GPtrArray of #NMIPRoute objects. In strict mode %NULL is returned on error.
+ */
+GPtrArray *
+_nm_utils_ip_routes_from_variant(GVariant *value, int family, bool strict, GError **error)
+{
+    gs_unref_ptrarray GPtrArray *routes = NULL;
+    GVariantIter                 iter, attrs_iter;
+    GVariant                    *item;
+    const char                  *dest, *next_hop;
+    guint32                      prefix, metric32;
+    gint64                       metric;
+    const char                  *attr_name;
+    NMIPRoute                   *route;
+    guint                        i;
 
-    g_variant_iter_init(&iter, value);
     routes = g_ptr_array_new_with_free_func((GDestroyNotify) nm_ip_route_unref);
 
-    while (g_variant_iter_next(&iter, "@a{sv}", &route_var)) {
+    if (!g_variant_is_of_type(value, G_VARIANT_TYPE("aa{sv}"))) {
+        if (strict) {
+            g_set_error_literal(error,
+                                NM_CONNECTION_ERROR,
+                                NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                _("Expected value of type \"aa{sv}\""));
+            return NULL;
+        }
+        return g_steal_pointer(&routes);
+    }
+
+    g_variant_iter_init(&iter, value);
+
+    for (i = 0; g_variant_iter_next(&iter, "@a{sv}", &item); i++) {
+        gs_unref_variant GVariant *route_var   = item;
+        gs_free_error GError      *local_error = NULL;
+
         if (!g_variant_lookup(route_var, "dest", "&s", &dest)
             || !g_variant_lookup(route_var, "prefix", "u", &prefix)) {
-            g_warning("Ignoring invalid address");
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Route requires fields \"dest\" and \"prefix\" (idx=%u)"),
+                            i);
+                return NULL;
+            }
+            continue;
         }
         if (!g_variant_lookup(route_var, "next-hop", "&s", &next_hop))
             next_hop = NULL;
@@ -2148,26 +2548,32 @@ nm_utils_ip_routes_from_variant(GVariant *value, int family)
         else
             metric = -1;
 
-        route = nm_ip_route_new(family, dest, prefix, next_hop, metric, &error);
+        route = nm_ip_route_new(family, dest, prefix, next_hop, metric, &local_error);
         if (!route) {
-            g_warning("Ignoring invalid route: %s", error->message);
-            g_clear_error(&error);
-            goto next;
+            if (strict) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("%s (idx=%u)"),
+                            local_error->message,
+                            i);
+                return NULL;
+            }
+            continue;
         }
 
         g_variant_iter_init(&attrs_iter, route_var);
-        while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &attr_val)) {
+        while (g_variant_iter_next(&attrs_iter, "{&sv}", &attr_name, &item)) {
+            gs_unref_variant GVariant *attr_val = item;
+
             if (!NM_IN_STRSET(attr_name, "dest", "prefix", "next-hop", "metric"))
                 nm_ip_route_set_attribute(route, attr_name, attr_val);
-            g_variant_unref(attr_val);
         }
 
         g_ptr_array_add(routes, route);
-next:
-        g_variant_unref(route_var);
     }
 
-    return routes;
+    return g_steal_pointer(&routes);
 }
 
 /*****************************************************************************/
@@ -4694,7 +5100,7 @@ _nm_utils_strstrdictkey_create(const char *v1, const char *v2)
 static gboolean
 validate_dns_option(const char                 *name,
                     gboolean                    numeric,
-                    gboolean                    ipv6,
+                    int                         addr_family,
                     const NMUtilsDNSOptionDesc *option_descs)
 {
     const NMUtilsDNSOptionDesc *desc;
@@ -4703,8 +5109,15 @@ validate_dns_option(const char                 *name,
         return !!*name;
 
     for (desc = option_descs; desc->name; desc++) {
-        if (nm_streq(name, desc->name) && numeric == desc->numeric && (!desc->ipv6_only || ipv6))
-            return TRUE;
+        if (!nm_streq(name, desc->name))
+            continue;
+        if ((!!numeric) != (!!desc->numeric))
+            continue;
+        if (addr_family != AF_UNSPEC) {
+            if (desc->ipv6_only && addr_family != AF_INET6)
+                continue;
+        }
+        return TRUE;
     }
 
     return FALSE;
@@ -4715,7 +5128,9 @@ validate_dns_option(const char                 *name,
  * @option: option string
  * @out_name: (out) (optional) (nullable): the option name
  * @out_value: (out) (optional): the option value
- * @ipv6: whether the option refers to a IPv6 configuration
+ * @addr_family: AF_INET/AF_INET6 to only allow options for the specified address
+ *   family. AF_UNSPEC to allow either. This argument is ignored, if @option_descs
+ *   is NULL.
  * @option_descs: (nullable): an array of NMUtilsDNSOptionDesc which describes the
  * valid options
  *
@@ -4731,7 +5146,7 @@ gboolean
 _nm_utils_dns_option_validate(const char                 *option,
                               char                      **out_name,
                               long                       *out_value,
-                              gboolean                    ipv6,
+                              int                         addr_family,
                               const NMUtilsDNSOptionDesc *option_descs)
 {
     gs_free char *option0_free = NULL;
@@ -4742,6 +5157,8 @@ _nm_utils_dns_option_validate(const char                 *option,
 
     g_return_val_if_fail(option != NULL, FALSE);
 
+    nm_assert_addr_family_or_unspec(addr_family);
+
     NM_SET_OUT(out_name, NULL);
     NM_SET_OUT(out_value, -1);
 
@@ -4750,7 +5167,7 @@ _nm_utils_dns_option_validate(const char                 *option,
 
     delim = strchr(option, ':');
     if (!delim) {
-        if (!validate_dns_option(option, FALSE, ipv6, option_descs))
+        if (!validate_dns_option(option, FALSE, addr_family, option_descs))
             return FALSE;
         NM_SET_OUT(out_name, g_strdup(option));
         return TRUE;
@@ -4765,7 +5182,7 @@ _nm_utils_dns_option_validate(const char                 *option,
 
     option0 = nm_strndup_a(300, option, delim - option, &option0_free);
 
-    if (!validate_dns_option(option0, TRUE, ipv6, option_descs))
+    if (!validate_dns_option(option0, TRUE, addr_family, option_descs))
         return FALSE;
 
     option1_num = _nm_utils_ascii_str_to_int64(option1, 10, 0, G_MAXINT32, -1);
@@ -4779,7 +5196,8 @@ _nm_utils_dns_option_validate(const char                 *option,
 
 /**
  * _nm_utils_dns_option_find_idx:
- * @array: an array of strings
+ * @strv: an array of strings of length @strv_len
+ * @strv_len: the length of @strv, or -1 for a NULL terminated strv array.
  * @option: a dns option string
  *
  * Searches for an option in an array of strings. The match is
@@ -4789,18 +5207,28 @@ _nm_utils_dns_option_validate(const char                 *option,
  * found.
  */
 gssize
-_nm_utils_dns_option_find_idx(GPtrArray *array, const char *option)
+_nm_utils_dns_option_find_idx(const char *const *strv, gssize strv_len, const char *option)
 {
     gs_free char *option_name = NULL;
-    guint         i;
+    gsize         l;
+    gsize         i;
 
-    if (!_nm_utils_dns_option_validate(option, &option_name, NULL, FALSE, NULL))
+    if (strv_len >= 0)
+        l = strv_len;
+    else
+        l = NM_PTRARRAY_LEN(strv);
+
+    if (l == 0)
         return -1;
 
-    for (i = 0; i < array->len; i++) {
+    if (!_nm_utils_dns_option_validate(option, &option_name, NULL, AF_UNSPEC, NULL))
+        return -1;
+
+    for (i = 0; i < l; i++) {
+        const char   *str      = strv[i];
         gs_free char *tmp_name = NULL;
 
-        if (_nm_utils_dns_option_validate(array->pdata[i], &tmp_name, NULL, FALSE, NULL)) {
+        if (_nm_utils_dns_option_validate(str, &tmp_name, NULL, AF_UNSPEC, NULL)) {
             if (nm_streq(tmp_name, option_name))
                 return i;
         }
@@ -5548,7 +5976,7 @@ _nm_utils_bridge_vlan_verify_list(GPtrArray  *vlans,
         nm_bridge_vlan_get_vid_range(vlan, &vid_start, &vid_end);
 
         for (v = vid_start; v <= vid_end; v++) {
-            if (!nm_g_hash_table_add(h, GUINT_TO_POINTER(v))) {
+            if (!g_hash_table_add(h, GUINT_TO_POINTER(v))) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
diff --git a/src/libnm-core-impl/nm-vpn-editor-plugin.c b/src/libnm-core-impl/nm-vpn-editor-plugin.c
index 6c9aff43..6181368a 100644
--- a/src/libnm-core-impl/nm-vpn-editor-plugin.c
+++ b/src/libnm-core-impl/nm-vpn-editor-plugin.c
@@ -209,7 +209,7 @@ nm_vpn_editor_plugin_get_vt(NMVpnEditorPlugin *plugin, NMVpnEditorPluginVT *vt,
         if (!p_vt)
             p_vt_size = 0;
         g_return_val_if_fail(p_vt_size, 0);
-        memcpy(vt, p_vt, MIN(vt_size, p_vt_size));
+        memcpy(vt, p_vt, NM_MIN(vt_size, p_vt_size));
     }
 
     return p_vt_size;
diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c
index d2ce9ed8..223d8ab3 100644
--- a/src/libnm-core-impl/nm-vpn-plugin-info.c
+++ b/src/libnm-core-impl/nm-vpn-plugin-info.c
@@ -181,8 +181,8 @@ _sort_files(LoadDirInfo *a, LoadDirInfo *b)
 {
     time_t ta, tb;
 
-    ta = MAX(a->stat.st_mtime, a->stat.st_ctime);
-    tb = MAX(b->stat.st_mtime, b->stat.st_ctime);
+    ta = NM_MAX(a->stat.st_mtime, a->stat.st_ctime);
+    tb = NM_MAX(b->stat.st_mtime, b->stat.st_ctime);
     if (ta < tb)
         return 1;
     if (ta > tb)
@@ -337,7 +337,7 @@ nm_vpn_plugin_info_list_load(void)
     uid = getuid();
 
     for (i = 0; i < G_N_ELEMENTS(dir); i++) {
-        if (!dir[i] || nm_strv_find_first(dir, i, dir[i]) >= 0)
+        if (!dir[i] || nm_strv_contains(dir, i, dir[i]))
             continue;
 
         infos = _nm_vpn_plugin_info_list_load_dir(dir[i], TRUE, uid, NULL, NULL);
@@ -552,7 +552,7 @@ _list_find_by_service(GSList *list, const char *name, const char *service)
         if (name && !nm_streq(name, priv->name))
             continue;
         if (service && !nm_streq(priv->service, service)
-            && (nm_strv_find_first(priv->aliases, -1, service) < 0))
+            && !nm_strv_contains(priv->aliases, -1, service))
             continue;
 
         return list->data;
@@ -639,7 +639,7 @@ nm_vpn_plugin_info_list_find_service_type(GSList *list, const char *name)
 
     /* check the hard-coded list of short-names. They all have the same
      * well-known prefix org.freedesktop.NetworkManager and the name. */
-    if (nm_strv_find_first(known_names, G_N_ELEMENTS(known_names), name) >= 0)
+    if (nm_strv_contains(known_names, G_N_ELEMENTS(known_names), name))
         return g_strdup_printf("%s.%s", NM_DBUS_INTERFACE, name);
 
     /* try, if there exists a plugin with @name under org.freedesktop.NetworkManager.
@@ -728,7 +728,7 @@ nm_vpn_plugin_info_list_get_service_types(GSList  *list,
 
     if (l->len <= 0) {
         g_ptr_array_free(l, TRUE);
-        return g_new0(char *, 1);
+        return nm_strv_empty_new();
     }
 
     /* sort the result and remove duplicates. */
diff --git a/src/libnm-core-impl/tests/meson.build b/src/libnm-core-impl/tests/meson.build
index 988c60db..80b58827 100644
--- a/src/libnm-core-impl/tests/meson.build
+++ b/src/libnm-core-impl/tests/meson.build
@@ -8,7 +8,6 @@ enum_sources = gnome.mkenums_simple(
 )
 
 test_units = [
-  'test-compare',
   'test-crypto',
   'test-general',
   'test-keyfile',
diff --git a/src/libnm-core-impl/tests/test-compare.c b/src/libnm-core-impl/tests/test-compare.c
deleted file mode 100644
index 77d2e17b..00000000
--- a/src/libnm-core-impl/tests/test-compare.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/* SPDX-License-Identifier: LGPL-2.1-or-later */
-/*
- * Copyright (C) 2007 - 2014 Red Hat, Inc.
- * Copyright (C) 2007 - 2008 Novell, Inc.
- */
-
-#include "libnm-core-impl/nm-default-libnm-core.h"
-
-#include <arpa/inet.h>
-#include <netinet/in.h>
-
-#include "nm-property-compare.h"
-
-#include "libnm-glib-aux/nm-test-utils.h"
-
-static void
-compare_ints(void)
-{
-    GVariant *value1, *value2;
-
-    value1 = g_variant_new_int32(5);
-    value2 = g_variant_new_int32(5);
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_int32(10);
-    g_assert(nm_property_compare(value1, value2) < 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_int32(-1);
-    g_assert(nm_property_compare(value1, value2) > 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-static void
-compare_strings(void)
-{
-    GVariant   *value1, *value2;
-    const char *str1 = "hello";
-    const char *str2 = "world";
-
-    value1 = g_variant_new_string(str1);
-    value2 = g_variant_new_string(str1);
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_string(str2);
-    g_assert(nm_property_compare(value1, value2) < 0);
-
-    g_assert(nm_property_compare(value2, value1) > 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-static void
-compare_strv(void)
-{
-    GVariant         *value1, *value2;
-    const char *const strv1[] = {"foo", "bar", "baz", NULL};
-    const char *const strv2[] = {"foo", "bar", "bar", NULL};
-    const char *const strv3[] = {"foo", "bar", NULL};
-    const char *const strv4[] = {"foo", "bar", "baz", "bam", NULL};
-
-    value1 = g_variant_new_strv(strv1, -1);
-    value2 = g_variant_new_strv(strv1, -1);
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_strv(strv2, -1);
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_strv(strv3, -1);
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_strv(strv4, -1);
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-static void
-compare_arrays(void)
-{
-    GVariant *value1, *value2;
-    guint32   array[] = {0, 1, 2, 3, 4};
-
-    value1 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
-                                       array,
-                                       G_N_ELEMENTS(array),
-                                       sizeof(guint32));
-    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
-                                       array,
-                                       G_N_ELEMENTS(array),
-                                       sizeof(guint32));
-
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
-                                       array + 1,
-                                       G_N_ELEMENTS(array) - 1,
-                                       sizeof(guint32));
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    array[0] = 7;
-    g_variant_unref(value2);
-    value2 = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32,
-                                       array,
-                                       G_N_ELEMENTS(array),
-                                       sizeof(guint32));
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-static void
-compare_str_hash(void)
-{
-    GVariant       *value1, *value2;
-    GVariantBuilder builder;
-
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
-    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
-    g_variant_builder_add(&builder, "{ss}", "key2", "world");
-    g_variant_builder_add(&builder, "{ss}", "key3", "!");
-    value1 = g_variant_builder_end(&builder);
-
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
-    g_variant_builder_add(&builder, "{ss}", "key3", "!");
-    g_variant_builder_add(&builder, "{ss}", "key2", "world");
-    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
-    value2 = g_variant_builder_end(&builder);
-
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
-    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
-    g_variant_builder_add(&builder, "{ss}", "key3", "!");
-    value2 = g_variant_builder_end(&builder);
-
-    g_assert(nm_property_compare(value1, value2) != 0);
-    g_assert(nm_property_compare(value2, value1) != 0);
-
-    g_variant_unref(value2);
-    g_variant_builder_init(&builder, G_VARIANT_TYPE("a{ss}"));
-    g_variant_builder_add(&builder, "{ss}", "key1", "hello");
-    g_variant_builder_add(&builder, "{ss}", "key2", "moon");
-    g_variant_builder_add(&builder, "{ss}", "key3", "!");
-    value2 = g_variant_builder_end(&builder);
-
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-static void
-compare_ip6_addresses(void)
-{
-    GVariant       *value1, *value2;
-    struct in6_addr addr1;
-    struct in6_addr addr2;
-    struct in6_addr addr3;
-    guint32         prefix1 = 64;
-    guint32         prefix2 = 64;
-    guint32         prefix3 = 0;
-
-    inet_pton(AF_INET6, "1:2:3:4:5:6:7:8", &addr1);
-    inet_pton(AF_INET6, "ffff:2:3:4:5:6:7:8", &addr2);
-    inet_pton(AF_INET6, "::", &addr3);
-
-    value1 = g_variant_new(
-        "(@ayu@ay)",
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
-        prefix1,
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
-
-    value2 = g_variant_new(
-        "(@ayu@ay)",
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr1.s6_addr, 16, 1),
-        prefix1,
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
-
-    g_assert(nm_property_compare(value1, value2) == 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new(
-        "(@ayu@ay)",
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr2.s6_addr, 16, 1),
-        prefix2,
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
-
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value2);
-    value2 = g_variant_new(
-        "(@ayu@ay)",
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1),
-        prefix3,
-        g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, (guint8 *) addr3.s6_addr, 16, 1));
-
-    g_assert(nm_property_compare(value1, value2) != 0);
-
-    g_variant_unref(value1);
-    g_variant_unref(value2);
-}
-
-NMTST_DEFINE();
-
-int
-main(int argc, char *argv[])
-{
-    nmtst_init(&argc, &argv, TRUE);
-
-    g_test_add_func("/libnm/compare/ints", compare_ints);
-    g_test_add_func("/libnm/compare/strings", compare_strings);
-    g_test_add_func("/libnm/compare/strv", compare_strv);
-    g_test_add_func("/libnm/compare/arrays", compare_arrays);
-    g_test_add_func("/libnm/compare/str_hash", compare_str_hash);
-    g_test_add_func("/libnm/compare/ip6_addresses", compare_ip6_addresses);
-
-    return g_test_run();
-}
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index 30b1ca5d..4acf33e0 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -292,6 +292,7 @@ test_device_wifi_capabilities(void)
     _E(NM_WIFI_DEVICE_CAP_FREQ_VALID);
     _E(NM_WIFI_DEVICE_CAP_FREQ_2GHZ);
     _E(NM_WIFI_DEVICE_CAP_FREQ_5GHZ);
+    _E(NM_WIFI_DEVICE_CAP_FREQ_6GHZ);
     _E(NM_WIFI_DEVICE_CAP_MESH);
     _E(NM_WIFI_DEVICE_CAP_IBSS_RSN);
 #undef _E
@@ -556,6 +557,21 @@ test_nm_hash(void)
 #endif
 
     NM_STATIC_ASSERT_EXPR_VOID(NM_HASH_COMBINE_BOOLS(int, 1, 0, 1) == 5);
+
+    g_assert_cmpmem(NM_HASH_SEED_16(55, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
+                    16,
+                    ((guint8[16]){55, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}),
+                    16);
+
+    g_assert_cmpmem(NM_HASH_SEED_16_U64(1), 16, ((guint8[16]){0, 0, 0, 0, 0, 0, 0, 1, 0}), 16);
+    g_assert_cmpmem(NM_HASH_SEED_16_U64(0x1234567890ABCDEFu),
+                    16,
+                    ((guint8[16]){0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF, 0}),
+                    16);
+
+    g_assert_cmpint(c_siphash_hash(NM_HASH_SEED_16_U64(0x780E21E45489CC6Fu), (guint8 *) "foo", 3),
+                    ==,
+                    0XA5A41E5C1B4153BFu);
 }
 
 /*****************************************************************************/
@@ -2627,7 +2643,7 @@ test_setting_gsm_apn_bad_chars(void)
 
     /* Make sure a valid APN works */
     g_object_set(s_gsm, NM_SETTING_GSM_APN, "foobar123.-baz", NULL);
-    g_assert(nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL));
+    nmtst_assert_setting_verifies(s_gsm);
 
     /* Random invalid chars */
     g_object_set(s_gsm, NM_SETTING_GSM_APN, "@#%$@#%@#%", NULL);
@@ -2639,7 +2655,7 @@ test_setting_gsm_apn_bad_chars(void)
 
     /* 0 characters long */
     g_object_set(s_gsm, NM_SETTING_GSM_APN, "", NULL);
-    g_assert(nm_setting_verify(NM_SETTING(s_gsm), NULL, NULL));
+    nmtst_assert_setting_verifies(s_gsm);
 
     /* 65-character long */
     g_object_set(s_gsm,
@@ -3946,7 +3962,7 @@ typedef struct {
 
 typedef struct {
     const char *name;
-    DiffKey     keys[30];
+    DiffKey     keys[31];
 } DiffSetting;
 
 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
@@ -4003,7 +4019,9 @@ test_connection_diff_a_only(void)
           {NM_SETTING_CONNECTION_PERMISSIONS, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_ZONE, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_MASTER, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_CONTROLLER, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_SLAVE_TYPE, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_PORT_TYPE, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_AUTOCONNECT_SLAVES, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_SECONDARIES, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
@@ -5295,6 +5313,7 @@ test_setting_ip4_changed_signal(void)
     NMIPAddress       *addr;
     NMIPRoute         *route;
     GError            *error = NULL;
+    gs_strfreev char **strv  = NULL;
 
     connection = nm_simple_connection_new();
     g_signal_connect(connection,
@@ -5319,7 +5338,8 @@ test_setting_ip4_changed_signal(void)
     ASSERT_CHANGED(nm_setting_ip_config_add_dns_search(s_ip4, "foobar.com"));
     ASSERT_CHANGED(nm_setting_ip_config_remove_dns_search(s_ip4, 0));
 
-    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_search->len));
+    NMTST_EXPECT_LIBNM_CRITICAL(
+        NMTST_G_RETURN_MSG(idx >= 0 && idx < nm_g_array_len(priv->dns_search.arr)));
     ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_search(s_ip4, 1));
     g_test_assert_expected_messages();
 
@@ -5351,10 +5371,51 @@ test_setting_ip4_changed_signal(void)
     nm_setting_ip_config_add_route(s_ip4, route);
     ASSERT_CHANGED(nm_setting_ip_config_clear_routes(s_ip4));
 
+    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);
+
+    g_object_get(s_ip4, NM_SETTING_IP_CONFIG_DNS_OPTIONS, &strv, NULL);
+    g_assert_null(strv);
+
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 0));
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(_idx <= _len));
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 1));
+    g_test_assert_expected_messages();
+
     ASSERT_CHANGED(nm_setting_ip_config_add_dns_option(s_ip4, "debug"));
+
+    g_assert(nm_setting_ip_config_has_dns_options(s_ip4));
+    g_assert_cmpint(nm_setting_ip_config_get_num_dns_options(s_ip4), ==, 1);
+
+    g_object_get(s_ip4, NM_SETTING_IP_CONFIG_DNS_OPTIONS, &strv, NULL);
+    g_assert_nonnull(strv);
+    g_assert_cmpstr(strv[0], ==, "debug");
+    g_assert_cmpstr(strv[1], ==, NULL);
+    nm_clear_pointer(&strv, g_strfreev);
+
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_option(s_ip4, 0), ==, "debug");
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 1));
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(_idx <= _len));
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 2));
+    g_test_assert_expected_messages();
+
     ASSERT_CHANGED(nm_setting_ip_config_remove_dns_option(s_ip4, 0));
 
-    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_options->len));
+    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);
+
+    g_object_get(s_ip4, NM_SETTING_IP_CONFIG_DNS_OPTIONS, &strv, NULL);
+    g_assert_nonnull(strv);
+    g_assert_cmpstr(strv[0], ==, NULL);
+    nm_clear_pointer(&strv, g_strfreev);
+
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 0));
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(_idx <= _len));
+    g_assert_null(nm_setting_ip_config_get_dns_option(s_ip4, 1));
+    g_test_assert_expected_messages();
+
+    NMTST_EXPECT_LIBNM_CRITICAL(
+        NMTST_G_RETURN_MSG(idx >= 0 && idx < nm_g_array_len(priv->dns_options.arr)));
     ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_option(s_ip4, 1));
     g_test_assert_expected_messages();
 
@@ -5366,6 +5427,7 @@ test_setting_ip4_changed_signal(void)
 static void
 test_setting_ip6_changed_signal(void)
 {
+    gs_strfreev char **strv = NULL;
     NMConnection      *connection;
     gboolean           changed = FALSE;
     NMSettingIPConfig *s_ip6;
@@ -5393,10 +5455,35 @@ test_setting_ip6_changed_signal(void)
     nm_setting_ip_config_add_dns(s_ip6, "1:2:3::4:5:6");
     ASSERT_CHANGED(nm_setting_ip_config_clear_dns(s_ip6));
 
+    g_object_get(s_ip6, NM_SETTING_IP_CONFIG_DNS_SEARCH, &strv, NULL);
+    g_assert_null(strv);
+
     ASSERT_CHANGED(nm_setting_ip_config_add_dns_search(s_ip6, "foobar.com"));
+
+    g_object_get(s_ip6, NM_SETTING_IP_CONFIG_DNS_SEARCH, &strv, NULL);
+    g_assert_nonnull(strv);
+    g_assert_cmpstr(strv[0], ==, "foobar.com");
+    g_assert_cmpstr(strv[1], ==, NULL);
+    nm_clear_pointer(&strv, g_strfreev);
+
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 0), ==, "foobar.com");
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 1), ==, NULL);
+
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(_idx <= _len));
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, -1), ==, NULL);
+    g_test_assert_expected_messages();
+
+    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(_idx <= _len));
+    g_assert_cmpstr(nm_setting_ip_config_get_dns_search(s_ip6, 2), ==, NULL);
+    g_test_assert_expected_messages();
+
     ASSERT_CHANGED(nm_setting_ip_config_remove_dns_search(s_ip6, 0));
 
-    NMTST_EXPECT_LIBNM_CRITICAL(NMTST_G_RETURN_MSG(idx >= 0 && idx < priv->dns_search->len));
+    g_object_get(s_ip6, NM_SETTING_IP_CONFIG_DNS_SEARCH, &strv, NULL);
+    g_assert_null(strv);
+
+    NMTST_EXPECT_LIBNM_CRITICAL(
+        NMTST_G_RETURN_MSG(idx >= 0 && idx < nm_g_array_len(priv->dns_search.arr)));
     ASSERT_UNCHANGED(nm_setting_ip_config_remove_dns_search(s_ip6, 1));
     g_test_assert_expected_messages();
 
@@ -5591,9 +5678,13 @@ test_setting_wireless_security_changed_signal(void)
                                                         "wep-key0",
                                                         NM_SETTING_SECRET_FLAG_AGENT_OWNED,
                                                         NULL)));
+    ASSERT_UNCHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec),
+                                                          "wep-key1",
+                                                          NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                                                          NULL)));
     ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec),
                                                         "wep-key1",
-                                                        NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                                                        NM_SETTING_SECRET_FLAG_NOT_SAVED,
                                                         NULL)));
     ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec),
                                                         "wep-key2",
@@ -5601,7 +5692,7 @@ test_setting_wireless_security_changed_signal(void)
                                                         NULL)));
     ASSERT_CHANGED(g_assert(nm_setting_set_secret_flags(NM_SETTING(s_wsec),
                                                         "wep-key3",
-                                                        NM_SETTING_SECRET_FLAG_AGENT_OWNED,
+                                                        NM_SETTING_SECRET_FLAG_NOT_SAVED,
                                                         NULL)));
 
     g_object_unref(connection);
@@ -6146,9 +6237,9 @@ test_connection_normalize_slave_type_1(void)
                                           &s_con);
 
     g_object_set(s_con,
-                 NM_SETTING_CONNECTION_MASTER,
+                 NM_SETTING_CONNECTION_CONTROLLER,
                  "master0",
-                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_CONNECTION_PORT_TYPE,
                  "invalid-type",
                  NULL);
 
@@ -6157,7 +6248,7 @@ test_connection_normalize_slave_type_1(void)
                                            NM_CONNECTION_ERROR_INVALID_PROPERTY);
     g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
 
-    g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, "bridge", NULL);
+    g_object_set(s_con, NM_SETTING_CONNECTION_PORT_TYPE, "bridge", NULL);
 
     g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
     nmtst_assert_connection_verifies_after_normalization(con,
@@ -6165,9 +6256,7 @@ test_connection_normalize_slave_type_1(void)
                                                          NM_CONNECTION_ERROR_MISSING_SETTING);
     nmtst_connection_normalize(con);
     g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
-    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
-                    ==,
-                    NM_SETTING_BRIDGE_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_port_type(s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
 }
 
 static void
@@ -6182,9 +6271,9 @@ test_connection_normalize_slave_type_2(void)
                                           &s_con);
 
     g_object_set(s_con,
-                 NM_SETTING_CONNECTION_MASTER,
+                 NM_SETTING_CONNECTION_CONTROLLER,
                  "master0",
-                 NM_SETTING_CONNECTION_SLAVE_TYPE,
+                 NM_SETTING_CONNECTION_PORT_TYPE,
                  "invalid-type",
                  NULL);
 
@@ -6193,19 +6282,17 @@ test_connection_normalize_slave_type_2(void)
                                            NM_CONNECTION_ERROR_INVALID_PROPERTY);
     g_assert(!nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
 
-    g_object_set(s_con, NM_SETTING_CONNECTION_SLAVE_TYPE, NULL, NULL);
+    g_object_set(s_con, NM_SETTING_CONNECTION_PORT_TYPE, NULL, NULL);
     nm_connection_add_setting(con, nm_setting_bridge_port_new());
 
     g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
-    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con), ==, NULL);
+    g_assert_cmpstr(nm_setting_connection_get_port_type(s_con), ==, NULL);
     nmtst_assert_connection_verifies_after_normalization(con,
                                                          NM_CONNECTION_ERROR,
                                                          NM_CONNECTION_ERROR_MISSING_PROPERTY);
     nmtst_connection_normalize(con);
     g_assert(nm_connection_get_setting_by_name(con, NM_SETTING_BRIDGE_PORT_SETTING_NAME));
-    g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
-                    ==,
-                    NM_SETTING_BRIDGE_SETTING_NAME);
+    g_assert_cmpstr(nm_setting_connection_get_port_type(s_con), ==, NM_SETTING_BRIDGE_SETTING_NAME);
 }
 
 static void
@@ -6483,9 +6570,9 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
     switch (TEST_CASE) {
     case 1:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -6504,9 +6591,9 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
         break;
     case 2:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -6527,9 +6614,9 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
         break;
     case 3:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -6545,9 +6632,9 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
         break;
     case 4:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -6561,7 +6648,7 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
                                                NM_CONNECTION_ERROR_INVALID_PROPERTY);
         break;
     case 5:
-        g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL);
+        g_object_set(s_con, NM_SETTING_CONNECTION_CONTROLLER, "master0", NULL);
 
         s_ovs_if = NM_SETTING_OVS_INTERFACE(nm_setting_ovs_interface_new());
         nm_connection_add_setting(con, NM_SETTING(s_ovs_if));
@@ -6576,15 +6663,15 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
                                              NM_SETTING_WIRED_SETTING_NAME,
                                              NM_SETTING_OVS_INTERFACE_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         break;
     case 6:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_BRIDGE_SETTING_NAME,
                      NULL);
 
@@ -6598,9 +6685,9 @@ test_connection_normalize_ovs_interface_type_system(gconstpointer test_data)
         break;
     case 7:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_BRIDGE_SETTING_NAME,
                      NULL);
 
@@ -6646,7 +6733,7 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                                NM_CONNECTION_ERROR_INVALID_PROPERTY);
         break;
     case 2:
-        g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL);
+        g_object_set(s_con, NM_SETTING_CONNECTION_CONTROLLER, "master0", NULL);
         nmtst_assert_connection_verifies_after_normalization(con,
                                                              NM_CONNECTION_ERROR,
                                                              NM_CONNECTION_ERROR_MISSING_PROPERTY);
@@ -6659,16 +6746,16 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                              NM_SETTING_OVS_INTERFACE_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
         g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal");
         break;
     case 3:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         nmtst_assert_connection_verifies_after_normalization(con,
@@ -6683,13 +6770,13 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                              NM_SETTING_OVS_INTERFACE_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
         g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal");
         break;
     case 4:
-        g_object_set(s_con, NM_SETTING_CONNECTION_MASTER, "master0", NULL);
+        g_object_set(s_con, NM_SETTING_CONNECTION_CONTROLLER, "master0", NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL);
         nmtst_assert_connection_verifies_after_normalization(con,
                                                              NM_CONNECTION_ERROR,
@@ -6703,16 +6790,16 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                              NM_SETTING_OVS_INTERFACE_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
         g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal");
         break;
     case 5:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL);
@@ -6733,9 +6820,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 6:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "internal", NULL);
@@ -6749,16 +6836,16 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                              NM_SETTING_OVS_INTERFACE_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
         g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "internal");
         break;
     case 7:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "system", NULL);
@@ -6768,9 +6855,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 8:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "bogus", NULL);
@@ -6780,9 +6867,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 9:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL);
@@ -6792,9 +6879,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 10:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
         g_object_set(s_ovs_if, NM_SETTING_OVS_INTERFACE_TYPE, "patch", NULL);
@@ -6805,9 +6892,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 11:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NM_SETTING_CONNECTION_INTERFACE_NAME,
                      "adsf",
@@ -6820,9 +6907,9 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
         break;
     case 12:
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NM_SETTING_CONNECTION_INTERFACE_NAME,
                      "adsf",
@@ -6842,7 +6929,7 @@ test_connection_normalize_ovs_interface_type_ovs_interface(gconstpointer test_da
                                              NM_SETTING_OVS_PATCH_SETTING_NAME);
         g_assert(s_con == nm_connection_get_setting_connection(con));
         g_assert(s_ovs_if == nm_connection_get_setting_ovs_interface(con));
-        g_assert_cmpstr(nm_setting_connection_get_slave_type(s_con),
+        g_assert_cmpstr(nm_setting_connection_get_port_type(s_con),
                         ==,
                         NM_SETTING_OVS_PORT_SETTING_NAME);
         g_assert_cmpstr(nm_setting_ovs_interface_get_interface_type(s_ovs_if), ==, "patch");
@@ -8713,23 +8800,35 @@ test_nm_ptrarray_len(void)
 
 static void
 test_nm_utils_dns_option_validate_do(char                       *option,
-                                     gboolean                    ipv6,
+                                     int                         addr_family,
                                      const NMUtilsDNSOptionDesc *descs,
                                      gboolean                    exp_result,
                                      char                       *exp_name,
                                      gboolean                    exp_value)
 {
-    char    *name;
-    long     value = 0;
-    gboolean result;
+    gs_free char *name  = NULL;
+    long          value = 0;
+    gboolean      result;
+
+    if (!descs) {
+        g_assert(addr_family == AF_UNSPEC);
+        addr_family = nmtst_rand_select(AF_UNSPEC, AF_INET, AF_INET6);
+    }
 
-    result = _nm_utils_dns_option_validate(option, &name, &value, ipv6, descs);
+    result = _nm_utils_dns_option_validate(option, &name, &value, addr_family, descs);
 
     g_assert(result == exp_result);
     g_assert_cmpstr(name, ==, exp_name);
     g_assert(value == exp_value);
 
-    g_free(name);
+    nm_clear_g_free(&name);
+
+    if (result && descs) {
+        result = _nm_utils_dns_option_validate(option, &name, &value, AF_UNSPEC, descs);
+        g_assert(result == exp_result);
+        g_assert_cmpstr(name, ==, exp_name);
+        g_assert(value == exp_value);
+    }
 }
 
 static const NMUtilsDNSOptionDesc opt_descs[] = {
@@ -8743,57 +8842,56 @@ static const NMUtilsDNSOptionDesc opt_descs[] = {
 static void
 test_nm_utils_dns_option_validate(void)
 {
-    /*                                    opt            ipv6    descs        result name       value */
-    test_nm_utils_dns_option_validate_do("", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do(":", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do(":1", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do(":val", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt", FALSE, NULL, TRUE, "opt", -1);
-    test_nm_utils_dns_option_validate_do("opt:", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt:12", FALSE, NULL, TRUE, "opt", 12);
-    test_nm_utils_dns_option_validate_do("opt:12 ", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt:val", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt:2val", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt:2:3", FALSE, NULL, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt-6", FALSE, NULL, TRUE, "opt-6", -1);
+    /*                                  (opt, addr_family, descs, result, name, value) */
+    test_nm_utils_dns_option_validate_do("", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do(":", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do(":1", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do(":val", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt", AF_UNSPEC, NULL, TRUE, "opt", -1);
+    test_nm_utils_dns_option_validate_do("opt:", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt:12", AF_UNSPEC, NULL, TRUE, "opt", 12);
+    test_nm_utils_dns_option_validate_do("opt:12 ", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt:val", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt:2val", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt:2:3", AF_UNSPEC, NULL, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt-6", AF_UNSPEC, NULL, TRUE, "opt-6", -1);
 
-    test_nm_utils_dns_option_validate_do("opt1", FALSE, opt_descs, TRUE, "opt1", -1);
-    test_nm_utils_dns_option_validate_do("opt1", TRUE, opt_descs, TRUE, "opt1", -1);
-    test_nm_utils_dns_option_validate_do("opt1:3", FALSE, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt1", AF_INET, opt_descs, TRUE, "opt1", -1);
+    test_nm_utils_dns_option_validate_do("opt1", AF_INET6, opt_descs, TRUE, "opt1", -1);
+    test_nm_utils_dns_option_validate_do("opt1:3", AF_INET, opt_descs, FALSE, NULL, -1);
 
-    test_nm_utils_dns_option_validate_do("opt2", FALSE, opt_descs, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt2:5", FALSE, opt_descs, TRUE, "opt2", 5);
+    test_nm_utils_dns_option_validate_do("opt2", AF_INET, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt2:5", AF_INET, opt_descs, TRUE, "opt2", 5);
 
-    test_nm_utils_dns_option_validate_do("opt3", FALSE, opt_descs, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt3", TRUE, opt_descs, TRUE, "opt3", -1);
+    test_nm_utils_dns_option_validate_do("opt3", AF_INET, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt3", AF_INET6, opt_descs, TRUE, "opt3", -1);
 
-    test_nm_utils_dns_option_validate_do("opt4", FALSE, opt_descs, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt4", TRUE, opt_descs, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt4:40", FALSE, opt_descs, FALSE, NULL, -1);
-    test_nm_utils_dns_option_validate_do("opt4:40", TRUE, opt_descs, TRUE, "opt4", 40);
+    test_nm_utils_dns_option_validate_do("opt4", AF_INET, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt4", AF_INET6, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt4:40", AF_INET, opt_descs, FALSE, NULL, -1);
+    test_nm_utils_dns_option_validate_do("opt4:40", AF_INET6, opt_descs, TRUE, "opt4", 40);
 }
 
 static void
 test_nm_utils_dns_option_find_idx(void)
 {
-    GPtrArray *options;
-
-    options = g_ptr_array_new();
-
-    g_ptr_array_add(options, "debug");
-    g_ptr_array_add(options, "timeout:5");
-    g_ptr_array_add(options, "edns0");
+    const char *const options[] = {
+        "debug",
+        "timeout:5",
+        "edns0",
+    };
 
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "debug"), ==, 0);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "debug:1"), ==, 0);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout"), ==, 1);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout:5"), ==, 1);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "timeout:2"), ==, 1);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "edns0"), ==, 2);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, "rotate"), ==, -1);
-    g_assert_cmpint(_nm_utils_dns_option_find_idx(options, ""), ==, -1);
+#define _find_idx(options, option) \
+    _nm_utils_dns_option_find_idx((options), G_N_ELEMENTS(options), ("" option ""))
 
-    g_ptr_array_free(options, TRUE);
+    g_assert_cmpint(_find_idx(options, "debug"), ==, 0);
+    g_assert_cmpint(_find_idx(options, "debug:1"), ==, 0);
+    g_assert_cmpint(_find_idx(options, "timeout"), ==, 1);
+    g_assert_cmpint(_find_idx(options, "timeout:5"), ==, 1);
+    g_assert_cmpint(_find_idx(options, "timeout:2"), ==, 1);
+    g_assert_cmpint(_find_idx(options, "edns0"), ==, 2);
+    g_assert_cmpint(_find_idx(options, "rotate"), ==, -1);
+    g_assert_cmpint(_find_idx(options, ""), ==, -1);
 }
 
 /*****************************************************************************/
@@ -10601,9 +10699,9 @@ test_connection_ovs_ifname(gconstpointer test_data)
                                               &s_con);
 
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_BRIDGE_SETTING_NAME,
                      NULL);
 
@@ -10619,9 +10717,9 @@ test_connection_ovs_ifname(gconstpointer test_data)
         g_assert(s_ovs_iface);
 
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -10646,9 +10744,9 @@ test_connection_ovs_ifname(gconstpointer test_data)
         g_assert(s_ovs_iface);
 
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -10662,9 +10760,9 @@ test_connection_ovs_ifname(gconstpointer test_data)
                                               &s_con);
 
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
@@ -10688,9 +10786,9 @@ test_connection_ovs_ifname(gconstpointer test_data)
         g_assert(s_ovs_iface);
 
         g_object_set(s_con,
-                     NM_SETTING_CONNECTION_MASTER,
+                     NM_SETTING_CONNECTION_CONTROLLER,
                      "master0",
-                     NM_SETTING_CONNECTION_SLAVE_TYPE,
+                     NM_SETTING_CONNECTION_PORT_TYPE,
                      NM_SETTING_OVS_PORT_SETTING_NAME,
                      NULL);
 
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index b30758ff..4b1aa2c1 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -2303,6 +2303,79 @@ test_ethtool_pause(void)
     g_assert_true(out_value);
 }
 
+static void
+test_ethtool_eee(void)
+{
+    gs_unref_object NMConnection   *con     = NULL;
+    gs_unref_object NMConnection   *con2    = NULL;
+    gs_unref_object NMConnection   *con3    = NULL;
+    gs_unref_variant GVariant      *variant = NULL;
+    gs_free_error GError           *error   = NULL;
+    nm_auto_unref_keyfile GKeyFile *keyfile = NULL;
+    NMSettingConnection            *s_con;
+    NMSettingEthtool               *s_ethtool;
+    NMSettingEthtool               *s_ethtool2;
+    NMSettingEthtool               *s_ethtool3;
+    gboolean                        out_value;
+
+    con =
+        nmtst_create_minimal_connection("ethtool-eee", NULL, NM_SETTING_WIRED_SETTING_NAME, &s_con);
+    s_ethtool = NM_SETTING_ETHTOOL(nm_setting_ethtool_new());
+    nm_connection_add_setting(con, NM_SETTING(s_ethtool));
+
+    nm_setting_option_set_boolean(NM_SETTING(s_ethtool), NM_ETHTOOL_OPTNAME_EEE_ENABLED, FALSE);
+
+    g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool),
+                                                NM_ETHTOOL_OPTNAME_EEE_ENABLED,
+                                                &out_value));
+    g_assert_true(!out_value);
+
+    nmtst_connection_normalize(con);
+
+    variant = nm_connection_to_dbus(con, NM_CONNECTION_SERIALIZE_ALL);
+
+    con2 = nm_simple_connection_new_from_dbus(variant, &error);
+    nmtst_assert_success(con2, error);
+
+    s_ethtool2 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con2, NM_TYPE_SETTING_ETHTOOL));
+
+    g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool2),
+                                                NM_ETHTOOL_OPTNAME_EEE_ENABLED,
+                                                &out_value));
+    g_assert_true(!out_value);
+
+    nmtst_assert_connection_verifies_without_normalization(con2);
+
+    nmtst_assert_connection_equals(con, FALSE, con2, FALSE);
+
+    con2 = nm_simple_connection_new_from_dbus(variant, &error);
+    nmtst_assert_success(con2, error);
+
+    keyfile = nm_keyfile_write(con, NM_KEYFILE_HANDLER_FLAGS_NONE, NULL, NULL, &error);
+    nmtst_assert_success(keyfile, error);
+
+    con3 = nm_keyfile_read(keyfile,
+                           "/ignored/current/working/directory/for/loading/relative/paths",
+                           NM_KEYFILE_HANDLER_FLAGS_NONE,
+                           NULL,
+                           NULL,
+                           &error);
+    nmtst_assert_success(con3, error);
+
+    nm_keyfile_read_ensure_id(con3, "unused-because-already-has-id");
+    nm_keyfile_read_ensure_uuid(con3, "unused-because-already-has-uuid");
+
+    nmtst_connection_normalize(con3);
+
+    nmtst_assert_connection_equals(con, FALSE, con3, FALSE);
+
+    s_ethtool3 = NM_SETTING_ETHTOOL(nm_connection_get_setting(con3, NM_TYPE_SETTING_ETHTOOL));
+
+    g_assert_true(nm_setting_option_get_boolean(NM_SETTING(s_ethtool3),
+                                                NM_ETHTOOL_OPTNAME_EEE_ENABLED,
+                                                &out_value));
+    g_assert_true(!out_value);
+}
 /*****************************************************************************/
 
 static void
@@ -4492,7 +4565,7 @@ test_setting_metadata(void)
             GArray                   *property_types_data;
             guint                     prop_idx_val;
             gboolean                  can_set_including_default = FALSE;
-            gboolean                  can_have_direct_hook      = FALSE;
+            gboolean                  can_have_direct_set_fcn   = FALSE;
             int                       n_special_options;
 
             g_assert(sip->name);
@@ -4633,9 +4706,11 @@ test_setting_metadata(void)
                                        INFINIBAND_ALEN));
                 } else {
                     g_assert(g_variant_type_equal(sip->property_type->dbus_type, "s"));
-                    g_assert(sip->property_type->to_dbus_fcn
-                             == _nm_setting_property_to_dbus_fcn_direct);
-                    can_have_direct_hook = TRUE;
+                    g_assert(NM_IN_SET(sip->property_type->to_dbus_fcn,
+                                       _nm_setting_property_to_dbus_fcn_direct,
+                                       _nm_setting_connection_controller_to_dbus,
+                                       _nm_setting_connection_port_type_to_dbus));
+                    can_have_direct_set_fcn = TRUE;
                 }
                 g_assert(sip->param_spec);
                 g_assert(sip->param_spec->value_type == G_TYPE_STRING);
@@ -4663,6 +4738,11 @@ test_setting_metadata(void)
                 g_assert(sip->param_spec);
                 g_assert(!NM_FLAGS_HAS(sip->param_spec->flags, NM_SETTING_PARAM_SECRET));
             }
+            if (sip->direct_strv_preserve_empty)
+                g_assert(sip->property_type->direct_type == NM_VALUE_TYPE_STRV);
+            if (sip->direct_string_allow_empty) {
+                g_assert(sip->property_type->direct_type == NM_VALUE_TYPE_STRING);
+            }
 
             if (sip->direct_set_string_mac_address_len != 0) {
                 g_assert(NM_IN_SET(sip->property_type,
@@ -4671,8 +4751,26 @@ test_setting_metadata(void)
                 g_assert(sip->property_type->direct_type == NM_VALUE_TYPE_STRING);
             }
 
-            if (!can_have_direct_hook)
-                g_assert(!sip->direct_hook.set_string_fcn);
+            if (!can_have_direct_set_fcn)
+                g_assert(!sip->direct_set_fcn.set_string);
+
+            if (sip->property_type->direct_type == NM_VALUE_TYPE_NONE)
+                g_assert(!sip->direct_also_notify);
+            else {
+                if (sip->direct_also_notify) {
+                    guint prop_idx2;
+                    guint cnt = 0;
+
+                    for (prop_idx2 = 0; prop_idx2 < sis->property_infos_len; prop_idx2++) {
+                        const NMSettInfoProperty *sip2 = &sis->property_infos[prop_idx2];
+
+                        if (sip2->param_spec == sip->direct_also_notify)
+                            cnt++;
+                    }
+                    g_assert_cmpint(cnt, ==, 1u);
+                    g_assert(sip->param_spec != sip->direct_also_notify);
+                }
+            }
 
             n_special_options = (sip->direct_set_string_mac_address_len != 0)
                                 + (!!sip->direct_set_string_strip)
@@ -4754,6 +4852,10 @@ check_done:;
                     g_assert(NM_IN_SET(sip->property_type->from_dbus_fcn,
                                        _nm_setting_property_from_dbus_fcn_direct_ip_config_gateway,
                                        _nm_setting_property_from_dbus_fcn_direct_mac_address,
+                                       _nm_setting_connection_controller_from_dbus,
+                                       _nm_setting_connection_master_from_dbus,
+                                       _nm_setting_connection_slave_type_from_dbus,
+                                       _nm_setting_connection_port_type_from_dbus,
                                        _nm_setting_property_from_dbus_fcn_direct));
                 }
             }
@@ -4775,7 +4877,9 @@ check_done:;
                 g_assert(sip->property_type->direct_type != NM_VALUE_TYPE_NONE);
                 g_assert(NM_IN_SET(sip->property_type->to_dbus_fcn,
                                    _nm_setting_property_to_dbus_fcn_direct,
-                                   _nm_setting_property_to_dbus_fcn_direct_mac_address));
+                                   _nm_setting_property_to_dbus_fcn_direct_mac_address,
+                                   _nm_setting_connection_controller_to_dbus,
+                                   _nm_setting_connection_port_type_to_dbus));
             } else if (sip->property_type->compare_fcn == _nm_setting_property_compare_fcn_ignore) {
                 if (NM_IN_SET(sip->property_type,
                               &nm_sett_info_propert_type_deprecated_ignore_i,
@@ -4810,6 +4914,17 @@ check_done:;
             g_array_append_val(property_types_data, prop_idx_val);
 
             if (sip->param_spec) {
+                /* All "direct" properties use G_PARAM_EXPLICIT_NOTIFY.
+                 *
+                 * Warning: this is potentially dangerous, because implementations MUST remember
+                 * to notify the property change in set_property(). Optimally, the property uses
+                 * _nm_setting_property_set_property_direct(), which takes care of that.
+                 */
+                if (sip->property_type->direct_type != NM_VALUE_TYPE_NONE)
+                    g_assert(NM_FLAGS_HAS(sip->param_spec->flags, G_PARAM_EXPLICIT_NOTIFY));
+            }
+
+            if (sip->param_spec) {
                 nm_auto_unset_gvalue GValue val = G_VALUE_INIT;
 
                 g_assert_cmpstr(sip->name, ==, sip->param_spec->name);
@@ -4998,6 +5113,50 @@ check_done:;
 /*****************************************************************************/
 
 static void
+test_setting_connection_empty_address_and_route(void)
+{
+    NMSettingIPConfig            *s_ip4;
+    NMIPRoute                    *route;
+    NMIPAddress                  *addr;
+    gs_unref_object NMConnection *con   = NULL;
+    gs_free_error GError         *error = NULL;
+    gboolean                      success;
+
+    /* IP4 setting */
+    con   = nmtst_create_minimal_connection("wired", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+    s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+    nm_connection_add_setting(con, NM_SETTING(s_ip4));
+    g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+    g_assert(s_ip4 != NULL);
+    g_assert(NM_IS_SETTING_IP4_CONFIG(s_ip4));
+    success = nm_setting_verify((NMSetting *) s_ip4, con, &error);
+    nmtst_assert_no_success(success, error);
+    nm_clear_error(&error);
+
+    route = nm_ip_route_new(AF_INET, "192.168.12.0", 24, NULL, 0, NULL);
+    nm_setting_ip_config_add_route(s_ip4, route);
+    success = nm_setting_verify((NMSetting *) s_ip4, con, &error);
+    nmtst_assert_success(success, error);
+    nm_clear_error(&error);
+
+    nm_setting_ip_config_clear_routes(s_ip4);
+    addr = nm_ip_address_new(AF_INET, "1.1.1.3", 24, NULL);
+    nm_setting_ip_config_add_address(s_ip4, addr);
+    success = nm_setting_verify((NMSetting *) s_ip4, con, &error);
+    nmtst_assert_success(success, error);
+    nm_clear_error(&error);
+
+    nm_setting_ip_config_add_route(s_ip4, route);
+    success = nm_setting_verify((NMSetting *) s_ip4, con, &error);
+    nmtst_assert_success(success, error);
+    nm_ip_address_unref(addr);
+    nm_ip_route_unref(route);
+    nm_clear_error(&error);
+}
+
+/*****************************************************************************/
+
+static void
 test_setting_connection_secondaries_verify(void)
 {
     gs_unref_object NMConnection *con = NULL;
@@ -5035,44 +5194,42 @@ test_setting_connection_secondaries_verify(void)
 
         g_object_set(s_con, NM_SETTING_CONNECTION_SECONDARIES, arr->pdata, NULL);
 
-#define _assert_secondaries(s_con, expected)                                                   \
-    G_STMT_START                                                                               \
-    {                                                                                          \
-        NMSettingConnection *const _s_con    = (s_con);                                        \
-        const char *const         *_expected = (expected);                                     \
-        GArray                    *_secondaries;                                               \
-        const guint                _expected_len = NM_PTRARRAY_LEN(_expected);                 \
-        gs_strfreev char         **_sec_strv     = NULL;                                       \
-        guint                      _i;                                                         \
-                                                                                               \
-        g_assert(_expected);                                                                   \
-                                                                                               \
-        if (nmtst_get_rand_bool()) {                                                           \
-            _secondaries = _nm_setting_connection_get_secondaries(_s_con);                     \
-            g_assert_cmpint(_expected_len, ==, nm_g_array_len(_secondaries));                  \
-            g_assert((_expected_len == 0) == (!_secondaries));                                 \
-            g_assert(nm_strv_equal(_expected,                                                  \
-                                   _secondaries ? nm_strvarray_get_strv(&_secondaries, NULL)   \
-                                                : NM_PTRARRAY_EMPTY(const char *)));           \
-        }                                                                                      \
-                                                                                               \
-        if (nmtst_get_rand_bool()) {                                                           \
-            g_object_get(_s_con, NM_SETTING_CONNECTION_SECONDARIES, &_sec_strv, NULL);         \
-            g_assert_cmpint(_expected_len, ==, NM_PTRARRAY_LEN(_sec_strv));                    \
-            g_assert((_expected_len == 0) == (!_sec_strv));                                    \
-            g_assert(nm_strv_equal(_expected, _sec_strv ?: NM_STRV_EMPTY()));                  \
-        }                                                                                      \
-                                                                                               \
-        g_assert_cmpint(nm_setting_connection_get_num_secondaries(_s_con), ==, _expected_len); \
-        if (nmtst_get_rand_bool()) {                                                           \
-            for (_i = 0; _i < _expected_len; _i++) {                                           \
-                g_assert_cmpstr(nm_setting_connection_get_secondary(_s_con, _i),               \
-                                ==,                                                            \
-                                _expected[_i]);                                                \
-            }                                                                                  \
-            g_assert_null(nm_setting_connection_get_secondary(_s_con, _expected_len));         \
-        }                                                                                      \
-    }                                                                                          \
+#define _assert_secondaries(s_con, expected)                                                       \
+    G_STMT_START                                                                                   \
+    {                                                                                              \
+        NMSettingConnection *const _s_con    = (s_con);                                            \
+        const char *const         *_expected = (expected);                                         \
+        GArray                    *_secondaries;                                                   \
+        const guint                _expected_len = NM_PTRARRAY_LEN(_expected);                     \
+        gs_strfreev char         **_sec_strv     = NULL;                                           \
+        guint                      _i;                                                             \
+                                                                                                   \
+        g_assert(_expected);                                                                       \
+                                                                                                   \
+        if (nmtst_get_rand_bool()) {                                                               \
+            _secondaries = _nm_setting_connection_get_secondaries(_s_con);                         \
+            g_assert_cmpint(_expected_len, ==, nm_g_array_len(_secondaries));                      \
+            g_assert((_expected_len == 0) == (!_secondaries));                                     \
+            g_assert(nm_strv_equal(_expected, nm_strvarray_get_strv_notnull(_secondaries, NULL))); \
+        }                                                                                          \
+                                                                                                   \
+        if (nmtst_get_rand_bool()) {                                                               \
+            g_object_get(_s_con, NM_SETTING_CONNECTION_SECONDARIES, &_sec_strv, NULL);             \
+            g_assert_cmpint(_expected_len, ==, NM_PTRARRAY_LEN(_sec_strv));                        \
+            g_assert((_expected_len == 0) == (!_sec_strv));                                        \
+            g_assert(nm_strv_equal(_expected, _sec_strv ?: NM_STRV_EMPTY()));                      \
+        }                                                                                          \
+                                                                                                   \
+        g_assert_cmpint(nm_setting_connection_get_num_secondaries(_s_con), ==, _expected_len);     \
+        if (nmtst_get_rand_bool()) {                                                               \
+            for (_i = 0; _i < _expected_len; _i++) {                                               \
+                g_assert_cmpstr(nm_setting_connection_get_secondary(_s_con, _i),                   \
+                                ==,                                                                \
+                                _expected[_i]);                                                    \
+            }                                                                                      \
+            g_assert_null(nm_setting_connection_get_secondary(_s_con, _expected_len));             \
+        }                                                                                          \
+    }                                                                                              \
     G_STMT_END
 
         _assert_secondaries(s_con, (const char *const *) arr->pdata);
@@ -5307,6 +5464,8 @@ main(int argc, char **argv)
                          test_8021x);
     g_test_add_data_func("/libnm/setting-8021x/pkcs12", "test-cert.p12, test", test_8021x);
 
+    g_test_add_func("/libnm/settings/test_setting_connection_empty_address_and_route",
+                    test_setting_connection_empty_address_and_route);
     g_test_add_func("/libnm/settings/test_setting_connection_secondaries_verify",
                     test_setting_connection_secondaries_verify);
 
@@ -5326,6 +5485,7 @@ main(int argc, char **argv)
     g_test_add_func("/libnm/settings/ethtool/coalesce", test_ethtool_coalesce);
     g_test_add_func("/libnm/settings/ethtool/ring", test_ethtool_ring);
     g_test_add_func("/libnm/settings/ethtool/pause", test_ethtool_pause);
+    g_test_add_func("/libnm/settings/ethtool/eee", test_ethtool_eee);
 
     g_test_add_func("/libnm/settings/6lowpan/1", test_6lowpan_1);