about summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in110
-rw-r--r--src/libnm-core-impl/meson.build1
-rw-r--r--src/libnm-core-impl/nm-connection-private.h4
-rw-r--r--src/libnm-core-impl/nm-connection.c101
-rw-r--r--src/libnm-core-impl/nm-keyfile-utils.c19
-rw-r--r--src/libnm-core-impl/nm-keyfile.c143
-rw-r--r--src/libnm-core-impl/nm-meta-setting-base-impl.c11
-rw-r--r--src/libnm-core-impl/nm-setting-bridge.c2
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c422
-rw-r--r--src/libnm-core-impl/nm-setting-ethtool.c45
-rw-r--r--src/libnm-core-impl/nm-setting-gsm.c304
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c305
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c63
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c24
-rw-r--r--src/libnm-core-impl/nm-setting-ipvlan.c290
-rw-r--r--src/libnm-core-impl/nm-setting-private.h8
-rw-r--r--src/libnm-core-impl/nm-setting-sriov.c4
-rw-r--r--src/libnm-core-impl/nm-setting-vlan.c2
-rw-r--r--src/libnm-core-impl/nm-setting-wired.c4
-rw-r--r--src/libnm-core-impl/nm-setting-wireguard.c6
-rw-r--r--src/libnm-core-impl/nm-setting.c4
-rw-r--r--src/libnm-core-impl/nm-team-utils.c6
-rw-r--r--src/libnm-core-impl/nm-utils-private.h2
-rw-r--r--src/libnm-core-impl/nm-utils.c16
-rw-r--r--src/libnm-core-impl/nm-vpn-editor-plugin.c3
-rw-r--r--src/libnm-core-impl/tests/test-general.c351
-rw-r--r--src/libnm-core-impl/tests/test-keyfile.c6
-rw-r--r--src/libnm-core-impl/tests/test-setting.c135
28 files changed, 2095 insertions, 296 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 6d2b9254..e0ce4ac4 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
@@ -826,6 +826,18 @@
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
+        <property name="ip-ping-addresses"
+                  dbus-type="as"
+                  gprop-type="GStrv"
+                  />
+        <property name="ip-ping-addresses-require-all"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
+        <property name="ip-ping-timeout"
+                  dbus-type="u"
+                  gprop-type="guint"
+                  />
         <property name="lldp"
                   dbus-type="i"
                   gprop-type="gint"
@@ -1349,6 +1361,10 @@
                   dbus-type="u"
                   is-setting-option="1"
                   />
+        <property name="fec-mode"
+                  dbus-type="u"
+                  is-setting-option="1"
+                  />
     </setting>
     <setting name="generic"
              gtype="NMSettingGeneric"
@@ -1389,6 +1405,44 @@
                   dbus-type="b"
                   gprop-type="gboolean"
                   />
+        <property name="initial-eps-bearer-noauth"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-password"
+                  is-secret="1"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
+        <property name="initial-eps-bearer-password-flags"
+                  is-secret-flags="1"
+                  dbus-type="u"
+                  gprop-type="NMSettingSecretFlags"
+                  />
+        <property name="initial-eps-bearer-refuse-chap"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-refuse-eap"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-refuse-mschap"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-refuse-mschapv2"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-refuse-pap"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="initial-eps-bearer-username"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="mtu"
                   dbus-type="u"
                   gprop-type="guint"
@@ -1610,6 +1664,10 @@
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
+        <property name="dhcp-ipv6-only-preferred"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="dhcp-reject-servers"
                   dbus-type="as"
                   gprop-type="GStrv"
@@ -1618,6 +1676,10 @@
                   dbus-type="b"
                   gprop-type="gboolean"
                   />
+        <property name="dhcp-send-hostname-v2"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="dhcp-send-release"
                   dbus-type="i"
                   gprop-type="NMTernary"
@@ -1697,6 +1759,10 @@
                   dbus-type="u"
                   gprop-type="guint"
                   />
+        <property name="routed-dns"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="routes"
                   dbus-type="aau"
                   dbus-deprecated="1"
@@ -1705,6 +1771,14 @@
         <property name="routing-rules"
                   dbus-type="aa{sv}"
                   />
+        <property name="shared-dhcp-lease-time"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
+        <property name="shared-dhcp-range"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
     </setting>
     <setting name="ipv6"
              gtype="NMSettingIP6Config"
@@ -1761,6 +1835,10 @@
                   dbus-type="b"
                   gprop-type="gboolean"
                   />
+        <property name="dhcp-send-hostname-v2"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="dhcp-send-release"
                   dbus-type="i"
                   gprop-type="NMTernary"
@@ -1844,6 +1922,10 @@
                   dbus-type="u"
                   gprop-type="guint"
                   />
+        <property name="routed-dns"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="routes"
                   dbus-type="a(ayuayu)"
                   dbus-deprecated="1"
@@ -1852,6 +1934,14 @@
         <property name="routing-rules"
                   dbus-type="aa{sv}"
                   />
+        <property name="shared-dhcp-lease-time"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
+        <property name="shared-dhcp-range"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="temp-preferred-lifetime"
                   dbus-type="i"
                   gprop-type="gint"
@@ -1865,6 +1955,26 @@
                   gprop-type="gchararray"
                   />
     </setting>
+    <setting name="ipvlan"
+             gtype="NMSettingIpvlan"
+             >
+        <property name="mode"
+                  dbus-type="u"
+                  gprop-type="guint"
+                  />
+        <property name="parent"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
+        <property name="private"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+        <property name="vepa"
+                  dbus-type="b"
+                  gprop-type="gboolean"
+                  />
+    </setting>
     <setting name="link"
              gtype="NMSettingLink"
              >
diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build
index e1f11f32..e068d6bd 100644
--- a/src/libnm-core-impl/meson.build
+++ b/src/libnm-core-impl/meson.build
@@ -25,6 +25,7 @@ libnm_core_settings_sources = files(
   'nm-setting-ip-tunnel.c',
   'nm-setting-ip4-config.c',
   'nm-setting-ip6-config.c',
+  'nm-setting-ipvlan.c',
   'nm-setting-link.c',
   'nm-setting-loopback.c',
   'nm-setting-macsec.c',
diff --git a/src/libnm-core-impl/nm-connection-private.h b/src/libnm-core-impl/nm-connection-private.h
index 9f9007b6..38619bd7 100644
--- a/src/libnm-core-impl/nm-connection-private.h
+++ b/src/libnm-core-impl/nm-connection-private.h
@@ -29,6 +29,10 @@ const char *_nm_connection_detect_bluetooth_type(NMConnection *self);
 
 gboolean _nm_setting_connection_verify_secondaries(GArray *secondaries, GError **error);
 
+gboolean _nm_setting_connection_verify_no_duplicate_addresses(GArray *secondaries, GError **error);
+
+int _get_ip_address_family(const char *ip_address);
+
 gboolean _nm_connection_verify_required_interface_name(NMConnection *connection, GError **error);
 
 int _nm_setting_ovs_interface_verify_interface_type(NMSettingOvsInterface *self,
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index 6dace2b7..61a20a13 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -89,7 +89,7 @@ _nm_connection_get_private_from_qdata(NMConnection *connection)
     priv = g_object_get_qdata((GObject *) connection, key);
     if (G_UNLIKELY(!priv)) {
         priv  = g_slice_new(NMConnectionPrivate);
-        *priv = (NMConnectionPrivate){
+        *priv = (NMConnectionPrivate) {
             .self = connection,
         };
         g_object_set_qdata_full((GObject *) connection, key, priv, _nm_connection_private_free);
@@ -959,6 +959,40 @@ out:
     return FALSE;
 }
 
+gboolean
+_nm_setting_connection_verify_no_duplicate_addresses(GArray *addresses, GError **error)
+{
+    guint i, j;
+
+    if (addresses->len <= 1) {
+        return TRUE;
+    } else {
+        for (i = 0; i < addresses->len - 1; i++) {
+            for (j = i + 1; j < addresses->len; j++) {
+                if (nm_streq0(nm_g_array_index(addresses, const char *, i),
+                              nm_g_array_index(addresses, const char *, j)))
+                    return FALSE;
+            }
+        }
+    }
+
+    return TRUE;
+}
+
+int
+_get_ip_address_family(const char *ip_address)
+{
+    struct in_addr  ipv4_addr;
+    struct in6_addr ipv6_addr;
+
+    if (inet_pton(AF_INET, ip_address, &ipv4_addr))
+        return AF_INET;
+    else if (inet_pton(AF_INET6, ip_address, &ipv6_addr))
+        return AF_INET6;
+    else
+        return -1;
+}
+
 static gboolean
 _normalize_connection_secondaries(NMConnection *self)
 {
@@ -998,6 +1032,48 @@ _normalize_connection_secondaries(NMConnection *self)
 }
 
 static gboolean
+_normalize_connection_ip_ping_addresses(NMConnection *self)
+{
+    NMSettingConnection *s_con = nm_connection_get_setting_connection(self);
+    GArray              *addresses;
+    gs_strfreev char   **strv = NULL;
+    guint                i, j, k;
+
+    nm_assert(s_con);
+
+    addresses = _nm_setting_connection_get_ip_ping_addresses(s_con);
+    if (nm_g_array_len(addresses) == 0)
+        return FALSE;
+
+    if (_nm_setting_connection_verify_no_duplicate_addresses(addresses, NULL))
+        return FALSE;
+
+    strv = nm_strvarray_get_strv_notempty_dup(addresses, NULL);
+
+    for (i = 0, j = 0; strv[i]; i++) {
+        gboolean found = FALSE;
+
+        for (k = 0; k < j; k++) {
+            if (nm_streq0(strv[i], strv[k])) {
+                found = TRUE;
+                break;
+            }
+        }
+
+        if (found) {
+            continue;
+        }
+
+        strv[j++] = strv[i];
+    }
+    strv[j] = NULL;
+
+    g_object_set(s_con, NM_SETTING_CONNECTION_IP_PING_ADDRESSES, strv, NULL);
+
+    return TRUE;
+}
+
+static gboolean
 _normalize_connection_type(NMConnection *self)
 {
     NMSettingConnection *s_con  = nm_connection_get_setting_connection(self);
@@ -1186,6 +1262,7 @@ _normalize_ip_config(NMConnection *self, GHashTable *parameters)
     NMSetting         *setting;
     gboolean           changed = FALSE;
     guint              num, i;
+    int                dhcp_send_hostname_v2;
 
     s_ip4   = nm_connection_get_setting_ip4_config(self);
     s_ip6   = nm_connection_get_setting_ip6_config(self);
@@ -1241,6 +1318,16 @@ _normalize_ip_config(NMConnection *self, GHashTable *parameters)
                     nm_setting_ip_config_remove_address(s_ip4, i);
                 changed = TRUE;
             }
+
+            dhcp_send_hostname_v2 = nm_setting_ip_config_get_dhcp_send_hostname_v2(s_ip4);
+            if (dhcp_send_hostname_v2 != NM_TERNARY_DEFAULT
+                && dhcp_send_hostname_v2 != nm_setting_ip_config_get_dhcp_send_hostname(s_ip4)) {
+                g_object_set(s_ip4,
+                             NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
+                             dhcp_send_hostname_v2,
+                             NULL);
+                changed = TRUE;
+            }
         }
     } else {
         if (s_ip4) {
@@ -1315,6 +1402,16 @@ _normalize_ip_config(NMConnection *self, GHashTable *parameters)
                 g_object_set(s_ip6, NM_SETTING_IP_CONFIG_MAY_FAIL, TRUE, NULL);
                 changed = TRUE;
             }
+
+            dhcp_send_hostname_v2 = nm_setting_ip_config_get_dhcp_send_hostname_v2(s_ip6);
+            if (dhcp_send_hostname_v2 != NM_TERNARY_DEFAULT
+                && dhcp_send_hostname_v2 != nm_setting_ip_config_get_dhcp_send_hostname(s_ip6)) {
+                g_object_set(s_ip6,
+                             NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
+                             dhcp_send_hostname_v2,
+                             NULL);
+                changed = TRUE;
+            }
         }
     } else {
         if (s_ip6) {
@@ -2028,6 +2125,7 @@ _connection_normalize(NMConnection *connection,
     was_modified |= _normalize_connection_type(connection);
     was_modified |= _normalize_connection_port_type(connection);
     was_modified |= _normalize_connection_secondaries(connection);
+    was_modified |= _normalize_connection_ip_ping_addresses(connection);
     was_modified |= _normalize_connection(connection);
     was_modified |= _normalize_required_settings(connection);
     was_modified |= _normalize_invalid_port_port_settings(connection);
@@ -3176,6 +3274,7 @@ nm_connection_is_virtual(NMConnection *connection)
                      NM_SETTING_DUMMY_SETTING_NAME,
                      NM_SETTING_HSR_SETTING_NAME,
                      NM_SETTING_IP_TUNNEL_SETTING_NAME,
+                     NM_SETTING_IPVLAN_SETTING_NAME,
                      NM_SETTING_MACSEC_SETTING_NAME,
                      NM_SETTING_MACVLAN_SETTING_NAME,
                      NM_SETTING_OVS_BRIDGE_SETTING_NAME,
diff --git a/src/libnm-core-impl/nm-keyfile-utils.c b/src/libnm-core-impl/nm-keyfile-utils.c
index 95073bcc..fdf00677 100644
--- a/src/libnm-core-impl/nm-keyfile-utils.c
+++ b/src/libnm-core-impl/nm-keyfile-utils.c
@@ -682,22 +682,3 @@ nm_keyfile_key_decode(const char *key, char **out_to_free)
 #endif
     return name;
 }
-
-void
-nm_keyfile_add_group(GKeyFile *keyfile, const char *group)
-{
-    nm_assert(keyfile);
-    nm_assert(group);
-
-    /* You can only call this function if the group doesn't exist yet.
-     * Because, we are about to add a dummy key, so we would have to
-     * be sure that the key doesn't exist. */
-    nm_assert(!g_key_file_has_group(keyfile, group));
-
-    /* Ensure the group is present.
-     * There is no API for that, so add and remove a dummy key.
-     * For a profile it matters whether a setting is present or not,
-     * and we need to ensure that we persist the presence of the setting to keyfile*/
-    g_key_file_set_value(keyfile, group, ".X", "1");
-    g_key_file_remove_key(keyfile, group, ".X", NULL);
-}
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index 8dadf3cd..e4559e3c 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -18,6 +18,7 @@
 #include <linux/if_ether.h>
 #include <linux/if_infiniband.h>
 
+#include "libnm-glib-aux/nm-keyfile-aux.h"
 #include "libnm-glib-aux/nm-uuid.h"
 #include "libnm-glib-aux/nm-str-buf.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
@@ -132,7 +133,7 @@ _nm_printf(5, 6) static void _read_handle_warn(KeyfileReaderInfo    *info,
                                      info,
                                      kf_key,
                                      cur_property);
-    handler_data.warn = (NMKeyfileHandlerDataWarn){
+    handler_data.warn = (NMKeyfileHandlerDataWarn) {
         .severity = severity,
         .message  = NULL,
         .fmt      = fmt,
@@ -186,7 +187,7 @@ _nm_printf(6, 7) static void _write_handle_warn(KeyfileWriterInfo    *info,
                                       cur_property,
                                       setting,
                                       kf_key);
-    handler_data.warn = (NMKeyfileHandlerDataWarn){
+    handler_data.warn = (NMKeyfileHandlerDataWarn) {
         .severity = severity,
         .message  = NULL,
         .fmt      = fmt,
@@ -915,7 +916,7 @@ _build_list_create(GKeyFile     *keyfile,
         if (G_UNLIKELY(!build_list))
             build_list = g_new(BuildListData, n_keys - i_keys);
 
-        build_list[build_list_len++] = (BuildListData){
+        build_list[build_list_len++] = (BuildListData) {
             .s_key    = s_key,
             .key_idx  = key_idx,
             .key_type = key_type,
@@ -939,6 +940,31 @@ _build_list_create(GKeyFile     *keyfile,
 }
 
 static void
+gateway_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+    const char   *setting_name = nm_setting_get_name(setting);
+    gs_free char *gateway      = NULL;
+    const char   *old_gateway;
+
+    gateway = nm_keyfile_plugin_kf_get_string(info->keyfile, setting_name, key, NULL);
+    if (!gateway)
+        return;
+
+    old_gateway = nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(setting));
+    if (old_gateway && !nm_streq0(gateway, old_gateway)) {
+        read_handle_warn(info,
+                         key,
+                         NM_SETTING_IP_CONFIG_GATEWAY,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("ignoring gateway \"%s\" from \"address*\" keys because the "
+                           "\"gateway\" key is set"),
+                         old_gateway);
+    }
+
+    g_object_set(setting, NM_SETTING_IP_CONFIG_GATEWAY, gateway, NULL);
+}
+
+static void
 ip_address_or_route_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *setting_key)
 {
     const char                  *setting_name = nm_setting_get_name(setting);
@@ -1132,7 +1158,7 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
     addr_family = NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting);
 
     for (i = 0, n = 0; i < length; i++) {
-        if (!nm_utils_dnsname_parse(addr_family, list[i], NULL, NULL, NULL)) {
+        if (!nm_dns_uri_parse(addr_family, list[i], NULL)) {
             if (!read_handle_warn(info,
                                   key,
                                   key,
@@ -2263,11 +2289,7 @@ ip6_addr_gen_mode_writer(KeyfileWriterInfo *info,
 }
 
 static void
-write_ip_values(GKeyFile   *file,
-                const char *setting_name,
-                GPtrArray  *array,
-                const char *gateway,
-                gboolean    is_route)
+write_ip_values(GKeyFile *file, const char *setting_name, GPtrArray *array, gboolean is_route)
 {
     if (array->len > 0) {
         nm_auto_str_buf NMStrBuf output = NM_STR_BUF_INIT(2 * INET_ADDRSTRLEN + 10, FALSE);
@@ -2300,7 +2322,7 @@ write_ip_values(GKeyFile   *file,
 
                 addr = nm_ip_address_get_address(address);
                 plen = nm_ip_address_get_prefix(address);
-                gw   = (i == 0) ? gateway : NULL;
+                gw   = NULL;
             }
 
             nm_str_buf_set_size(&output, 0, FALSE, FALSE);
@@ -2351,11 +2373,10 @@ addr_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const
 {
     GPtrArray  *array;
     const char *setting_name = nm_setting_get_name(setting);
-    const char *gateway      = nm_setting_ip_config_get_gateway(NM_SETTING_IP_CONFIG(setting));
 
     array = (GPtrArray *) g_value_get_boxed(value);
     if (array && array->len)
-        write_ip_values(info->keyfile, setting_name, array, gateway, FALSE);
+        write_ip_values(info->keyfile, setting_name, array, FALSE);
 }
 
 static void
@@ -2366,7 +2387,7 @@ route_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const
 
     array = (GPtrArray *) g_value_get_boxed(value);
     if (array && array->len)
-        write_ip_values(info->keyfile, setting_name, array, NULL, TRUE);
+        write_ip_values(info->keyfile, setting_name, array, TRUE);
 }
 
 static void
@@ -2482,7 +2503,7 @@ wired_s390_options_writer_full(KeyfileWriterInfo        *info,
         /* groups in the keyfile are ordered. When we are about to add [ethernet-s390-options],
          * we want to also have an [ethernet] group, first. */
 
-        nm_keyfile_add_group(info->keyfile, setting_alias ?: NM_SETTING_WIRED_SETTING_NAME);
+        nm_key_file_add_group(info->keyfile, setting_alias ?: NM_SETTING_WIRED_SETTING_NAME);
     }
 
     for (i = 0; i < n; i++) {
@@ -2882,7 +2903,7 @@ cert_writer(KeyfileWriterInfo *info, NMSetting *setting, const char *key, const
                                           vtable->setting_key,
                                           setting,
                                           key);
-        handler_data.write_cert = (NMKeyfileHandlerDataWriteCert){
+        handler_data.write_cert = (NMKeyfileHandlerDataWriteCert) {
             .vtable = vtable,
         };
 
@@ -2944,11 +2965,11 @@ struct _ParseInfoProperty {
 };
 
 #define PARSE_INFO_PROPERTY(_property_name, ...) \
-    (&((const ParseInfoProperty){.property_name = _property_name, __VA_ARGS__}))
+    (&((const ParseInfoProperty) {.property_name = _property_name, __VA_ARGS__}))
 
-#define PARSE_INFO_PROPERTIES(...)                     \
-    .properties = ((const ParseInfoProperty *const[]){ \
-        __VA_ARGS__ NULL,                              \
+#define PARSE_INFO_PROPERTIES(...)                      \
+    .properties = ((const ParseInfoProperty *const[]) { \
+        __VA_ARGS__ NULL,                               \
     })
 
 typedef struct {
@@ -2956,7 +2977,7 @@ typedef struct {
 } ParseInfoSetting;
 
 #define PARSE_INFO_SETTING(setting_type, ...) \
-    [setting_type] = (&((const ParseInfoSetting){__VA_ARGS__}))
+    [setting_type] = (&((const ParseInfoSetting) {__VA_ARGS__}))
 
 static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
     PARSE_INFO_SETTING(
@@ -3060,7 +3081,7 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
                                 .parser              = ip_dns_parser,
                                 .writer              = dns_writer, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS_OPTIONS, .always_write = TRUE, ),
-            PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ),
+            PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .parser = gateway_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES,
                                 .parser_no_check_key = TRUE,
                                 .parser              = ip_address_or_route_parser,
@@ -3088,7 +3109,7 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
                                 .parser              = ip_dns_parser,
                                 .writer              = dns_writer, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_DNS_OPTIONS, .always_write = TRUE, ),
-            PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .writer_skip = TRUE, ),
+            PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .parser = gateway_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES,
                                 .parser_no_check_key = TRUE,
                                 .parser              = ip_address_or_route_parser,
@@ -3549,6 +3570,62 @@ read_one_setting_value(KeyfileReaderInfo        *info,
 }
 
 static void
+_read_handle_renamed_properties(KeyfileReaderInfo *info)
+{
+    GKeyFile             *kf    = info->keyfile;
+    const char           *group = info->group;
+    gs_free_error GError *error = NULL;
+
+    if (NM_IN_STRSET(group, "ipv4", "ipv6")) {
+        /* dhcp-send-hostname is stored as dhcp-send-hostname-deprecated
+         * dhcp-send-hostname-v2 is stored as dhcp-send-hostname
+         * Do the conversion back. Also, accept boolean values for -v2 to
+         * maintain backwards compatibility with keyfiles written with the
+         * deprecated property in mind
+         */
+        if (g_key_file_has_key(kf, group, "dhcp-send-hostname", NULL)) {
+            gboolean val_bool;
+            int      val;
+
+            val = g_key_file_get_integer(kf, group, "dhcp-send-hostname", &error);
+            if (error) {
+                g_clear_error(&error);
+                val_bool = g_key_file_get_boolean(kf, group, "dhcp-send-hostname", &error);
+                if (!error)
+                    val = val_bool ? 1 : 0;
+                else
+                    read_handle_warn(info,
+                                     NULL,
+                                     NULL,
+                                     NM_KEYFILE_WARN_SEVERITY_WARN,
+                                     _("invalid value for '%s.dhcp-send-hostname'"),
+                                     info->group);
+            }
+
+            g_key_file_remove_key(kf, group, "dhcp-send-hostname", NULL);
+            if (!error)
+                g_key_file_set_integer(kf, group, "dhcp-send-hostname-v2", val);
+        }
+
+        if (g_key_file_has_key(kf, group, "dhcp-send-hostname-deprecated", NULL)) {
+            gs_free char *val = NULL;
+
+            val = g_key_file_get_value(kf, group, "dhcp-send-hostname-deprecated", NULL);
+            g_key_file_remove_key(kf, group, "dhcp-send-hostname-deprecated", NULL);
+            if (val)
+                g_key_file_set_value(kf, group, "dhcp-send-hostname", val);
+            else
+                read_handle_warn(info,
+                                 NULL,
+                                 NULL,
+                                 NM_KEYFILE_WARN_SEVERITY_WARN,
+                                 _("invalid value for '%s.dhcp-send-hostname-deprecated'"),
+                                 info->group);
+        }
+    }
+}
+
+static void
 _read_setting(KeyfileReaderInfo *info)
 {
     const NMSettInfoSetting   *sett_info;
@@ -3576,6 +3653,8 @@ _read_setting(KeyfileReaderInfo *info)
 
     info->setting = setting;
 
+    _read_handle_renamed_properties(info);
+
     sett_info = _nm_setting_class_get_sett_info(NM_SETTING_GET_CLASS(setting));
 
     if (sett_info->detail.gendata_info) {
@@ -3950,7 +4029,7 @@ nm_keyfile_read(GKeyFile             *keyfile,
 
     connection = nm_simple_connection_new();
 
-    info = (KeyfileReaderInfo){
+    info = (KeyfileReaderInfo) {
         .connection   = connection,
         .keyfile      = keyfile,
         .base_dir     = base_dir,
@@ -4084,11 +4163,23 @@ write_setting_value(KeyfileWriterInfo        *info,
                      NM_SETTING_WIRED_MAC_ADDRESS_BLACKLIST))
         return;
 
-    value = (GValue){0};
+    value = (GValue) {0};
 
     g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(property_info->param_spec));
     g_object_get_property(G_OBJECT(setting), property_info->param_spec->name, &value);
 
+    /* To prevent any confusion from the user regarding the v2 suffix,
+     * dhcp-send-hostname is stored as dhcp-send-hostname-deprecated
+     * and dhcp-send-hostname-v2 is stored as dhcp-send-hostname
+     * in the keyfile.
+     */
+    if (NM_IS_SETTING_IP4_CONFIG(setting) || NM_IS_SETTING_IP6_CONFIG(setting)) {
+        if (nm_streq(key, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME_V2))
+            key = "dhcp-send-hostname";
+        else if (nm_streq(key, NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME))
+            key = "dhcp-send-hostname-deprecated";
+    }
+
     if ((!pip || !pip->writer_persist_default)
         && g_param_value_defaults(property_info->param_spec, &value)) {
         nm_assert(!g_key_file_has_key(info->keyfile, setting_info->setting_name, key, NULL));
@@ -4304,7 +4395,7 @@ nm_keyfile_write(NMConnection         *connection,
 
     keyfile = g_key_file_new();
 
-    info = (KeyfileWriterInfo){
+    info = (KeyfileWriterInfo) {
         .connection    = connection,
         .keyfile       = keyfile,
         .error         = NULL,
@@ -4385,7 +4476,7 @@ nm_keyfile_write(NMConnection         *connection,
             || g_key_file_has_group(info.keyfile, setting_name)) {
             /* we have a section for the setting. Nothing to do. */
         } else {
-            nm_keyfile_add_group(info.keyfile, setting_alias ?: setting_name);
+            nm_key_file_add_group(info.keyfile, setting_alias ?: setting_name);
         }
 
         if (NM_IS_SETTING_WIREGUARD(setting)) {
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 34a7d22e..37cb61f1 100644
--- a/src/libnm-core-impl/nm-meta-setting-base-impl.c
+++ b/src/libnm-core-impl/nm-meta-setting-base-impl.c
@@ -35,6 +35,7 @@
 #include "nm-setting-ip-tunnel.h"
 #include "nm-setting-ip4-config.h"
 #include "nm-setting-ip6-config.h"
+#include "nm-setting-ipvlan.h"
 #include "nm-setting-link.h"
 #include "nm-setting-loopback.h"
 #include "nm-setting-macsec.h"
@@ -371,6 +372,13 @@ const NMMetaSettingInfo nm_meta_setting_infos[] = {
             .setting_name      = NM_SETTING_IP_TUNNEL_SETTING_NAME,
             .get_setting_gtype = nm_setting_ip_tunnel_get_type,
         },
+    [NM_META_SETTING_TYPE_IPVLAN] =
+        {
+            .meta_type         = NM_META_SETTING_TYPE_IPVLAN,
+            .setting_priority  = NM_SETTING_PRIORITY_HW_BASE,
+            .setting_name      = NM_SETTING_IPVLAN_SETTING_NAME,
+            .get_setting_gtype = nm_setting_ipvlan_get_type,
+        },
     [NM_META_SETTING_TYPE_LINK] =
         {
             .meta_type         = NM_META_SETTING_TYPE_LINK,
@@ -643,6 +651,7 @@ const NMMetaSettingType nm_meta_setting_types_by_priority[] = {
     NM_META_SETTING_TYPE_HSR,
     NM_META_SETTING_TYPE_INFINIBAND,
     NM_META_SETTING_TYPE_IP_TUNNEL,
+    NM_META_SETTING_TYPE_IPVLAN,
     NM_META_SETTING_TYPE_LOOPBACK,
     NM_META_SETTING_TYPE_MACSEC,
     NM_META_SETTING_TYPE_MACVLAN,
@@ -822,7 +831,7 @@ again:
         for (i = 0; i < _NM_META_SETTING_TYPE_NUM; i++) {
             const NMMetaSettingInfo *m = &nm_meta_setting_infos[i];
 
-            static_array[i] = (LookupData){
+            static_array[i] = (LookupData) {
                 .gtype        = m->get_setting_gtype(),
                 .setting_info = m,
             };
diff --git a/src/libnm-core-impl/nm-setting-bridge.c b/src/libnm-core-impl/nm-setting-bridge.c
index 7e9a0964..c1618434 100644
--- a/src/libnm-core-impl/nm-setting-bridge.c
+++ b/src/libnm-core-impl/nm-setting-bridge.c
@@ -151,7 +151,7 @@ nm_bridge_vlan_new(guint16 vid_start, guint16 vid_end)
     g_return_val_if_fail(vid_start <= vid_end, NULL);
 
     vlan  = g_slice_new(NMBridgeVlan);
-    *vlan = (NMBridgeVlan){
+    *vlan = (NMBridgeVlan) {
         .refcount  = 1,
         .vid_start = vid_start,
         .vid_end   = vid_end,
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 33d088bb..4a910c7f 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -63,6 +63,9 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingConnection,
                              PROP_AUTOCONNECT_PORTS,
                              PROP_SECONDARIES,
                              PROP_GATEWAY_PING_TIMEOUT,
+                             PROP_IP_PING_TIMEOUT,
+                             PROP_IP_PING_ADDRESSES,
+                             PROP_IP_PING_ADDRESSES_REQUIRE_ALL,
                              PROP_METERED,
                              PROP_LLDP,
                              PROP_MDNS,
@@ -91,6 +94,7 @@ typedef struct {
     guint64     timestamp;
     int         autoconnect_ports;
     int         down_on_poweroff;
+    int         ip_ping_addresses_require_all;
     int         metered;
     gint32      autoconnect_priority;
     gint32      autoconnect_retries;
@@ -104,6 +108,8 @@ typedef struct {
     gint32      wait_activation_delay;
     guint32     mptcp_flags;
     guint32     gateway_ping_timeout;
+    NMValueStrv ip_ping_addresses;
+    guint32     ip_ping_timeout;
     bool        autoconnect;
     bool        read_only;
 } NMSettingConnectionPrivate;
@@ -139,7 +145,7 @@ _permission_set_stale(Permission *permission, PermType ptype, char *item_take)
 
     /* we don't inspect (clear) permission before setting. It takes a
      * stale instance. */
-    *permission = (Permission){
+    *permission = (Permission) {
         .ptype = ptype,
         .item  = item_take,
     };
@@ -1037,6 +1043,172 @@ nm_setting_connection_get_gateway_ping_timeout(NMSettingConnection *setting)
     return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->gateway_ping_timeout;
 }
 
+GArray *
+_nm_setting_connection_get_ip_ping_addresses(NMSettingConnection *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
+
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->ip_ping_addresses.arr;
+}
+
+/**
+ * nm_setting_connection_get_ip_ping_address:
+ * @setting: the #NMSettingConnection
+ * @idx: the zero-based index of the ip-ping-addresses entry.
+ *
+ * Returns: the ip address string at index @idx or
+ *   %NULL if @idx is the number of ip-ping-addresses.
+ *
+ * Since: 1.52
+ **/
+const char *
+nm_setting_connection_get_ip_ping_address(NMSettingConnection *setting, guint32 idx)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NULL);
+
+    return nm_strvarray_get_idxnull_or_greturn(
+        NM_SETTING_CONNECTION_GET_PRIVATE(setting)->ip_ping_addresses.arr,
+        idx);
+}
+
+/**
+ * nm_setting_connection_add_ip_ping_address:
+ * @setting: the #NMSettingConnection
+ * @address: the IP address string to add
+ *
+ * Adds a new IP address string to the ip-ping-addresses.
+ *
+ * Returns: %TRUE if the new IP address was added; %FALSE if the IP address
+ * was already present
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_connection_add_ip_ping_address(NMSettingConnection *setting, const char *address)
+{
+    NMSettingConnectionPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
+    g_return_val_if_fail(address, FALSE);
+
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    if (!nm_strvarray_ensure_and_add_unique(&priv->ip_ping_addresses.arr, address))
+        return FALSE;
+
+    _notify(setting, PROP_IP_PING_ADDRESSES);
+    return TRUE;
+}
+
+/**
+ * nm_setting_connection_remove_ip_ping_address:
+ * @setting: the #NMSettingConnection
+ * @idx: index number of the IP address
+ *
+ * Removes the IP address at index @idx.
+ *
+ * Since: 1.52
+ **/
+void
+nm_setting_connection_remove_ip_ping_address(NMSettingConnection *setting, guint32 idx)
+{
+    NMSettingConnectionPrivate *priv;
+
+    g_return_if_fail(NM_IS_SETTING_CONNECTION(setting));
+
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    g_return_if_fail(idx < nm_g_array_len(priv->ip_ping_addresses.arr));
+
+    nm_strvarray_remove_index(priv->ip_ping_addresses.arr, idx);
+    _notify(setting, PROP_IP_PING_ADDRESSES);
+}
+
+/**
+ * nm_setting_connection_remove_ip_ping_address_by_value:
+ * @setting: the #NMSettingConnection
+ * @address: the IP address to remove
+ *
+ * Removes the IP address @address from ip-ping-addresses.
+ *
+ * Returns: %TRUE if the IP address was found and removed; %FALSE if it was not.
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_connection_remove_ip_ping_address_by_value(NMSettingConnection *setting,
+                                                      const char          *address)
+{
+    NMSettingConnectionPrivate *priv;
+
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), FALSE);
+    g_return_val_if_fail(address, FALSE);
+
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    if (!nm_strvarray_remove_first(priv->ip_ping_addresses.arr, address))
+        return FALSE;
+
+    _notify(setting, PROP_IP_PING_ADDRESSES);
+    return TRUE;
+}
+
+/**
+ * nm_setting_connection_clear_ip_ping_addresses:
+ * @setting: the #NMSettingConnection
+ *
+ * Removes all configured ip-ping-addresses.
+ *
+ * Since: 1.52
+ **/
+void
+nm_setting_connection_clear_ip_ping_addresses(NMSettingConnection *setting)
+{
+    NMSettingConnectionPrivate *priv;
+
+    g_return_if_fail(NM_IS_SETTING_CONNECTION(setting));
+
+    priv = NM_SETTING_CONNECTION_GET_PRIVATE(setting);
+
+    if (nm_strvarray_clear(&priv->ip_ping_addresses.arr))
+        _notify(setting, PROP_IP_PING_ADDRESSES);
+}
+
+/**
+ * nm_setting_connection_get_ip_ping_timeout:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns: the value contained in the #NMSettingConnection:ip-ping-timeout
+ * property.
+ *
+ * Since: 1.52
+ **/
+guint32
+nm_setting_connection_get_ip_ping_timeout(NMSettingConnection *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), 0);
+
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->ip_ping_timeout;
+}
+
+/**
+ * nm_setting_connection_get_ip_ping_addresses_require_all:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns the #NMSettingConnection:ip-ping-addresses-require-all property of the connection.
+ *
+ * Returns: whether all the ip ping addresses pass the connectivity check.
+ *
+ * Since: 1.52
+ **/
+NMTernary
+nm_setting_connection_get_ip_ping_addresses_require_all(NMSettingConnection *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_TERNARY_DEFAULT);
+
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->ip_ping_addresses_require_all;
+}
+
 /**
  * nm_setting_connection_get_metered:
  * @setting: the #NMSettingConnection
@@ -1610,6 +1782,176 @@ after_interface_name:
         }
     }
 
+    if (priv->ip_ping_timeout != 0
+        && (!priv->ip_ping_addresses.arr || priv->ip_ping_addresses.arr->len == 0)) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("can only be set if %s.%s is set"),
+                    NM_SETTING_CONNECTION_SETTING_NAME,
+                    NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_CONNECTION_SETTING_NAME,
+                       NM_SETTING_CONNECTION_IP_PING_TIMEOUT);
+        return FALSE;
+    }
+
+    if (priv->ip_ping_addresses.arr && priv->ip_ping_addresses.arr->len > 0) {
+        guint i;
+
+        if (priv->ip_ping_timeout == 0) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("can only be set if %s.%s is set"),
+                        NM_SETTING_CONNECTION_SETTING_NAME,
+                        NM_SETTING_CONNECTION_IP_PING_TIMEOUT);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_CONNECTION_SETTING_NAME,
+                           NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+            return FALSE;
+        }
+
+        if (priv->gateway_ping_timeout != 0 && priv->ip_ping_timeout != 0) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("is incompatible with '%s'"),
+                        NM_SETTING_CONNECTION_IP_PING_TIMEOUT);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_CONNECTION_SETTING_NAME,
+                           NM_SETTING_CONNECTION_GATEWAY_PING_TIMEOUT);
+            return FALSE;
+        }
+
+        for (i = 0; i < priv->ip_ping_addresses.arr->len; i++) {
+            const char *address = nm_g_array_index(priv->ip_ping_addresses.arr, const char *, i);
+            int         addr_family = _get_ip_address_family(address);
+
+            if (addr_family == AF_INET) {
+                NMSettingIPConfig *s_ip4;
+
+                if (connection) {
+                    s_ip4 = nm_connection_get_setting_ip4_config(connection);
+
+                    if (s_ip4) {
+                        const char *method = nm_setting_ip_config_get_method(s_ip4);
+                        if (nm_streq0(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
+                            g_set_error(error,
+                                        NM_CONNECTION_ERROR,
+                                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                        _("contains IPv4 address '%s', %s.%s cannot be 'disabled'"),
+                                        address,
+                                        NM_SETTING_IP4_CONFIG_SETTING_NAME,
+                                        NM_SETTING_IP_CONFIG_METHOD);
+                            g_prefix_error(error,
+                                           "%s.%s: ",
+                                           NM_SETTING_CONNECTION_SETTING_NAME,
+                                           NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                            return FALSE;
+                        }
+                        if (nm_setting_ip_config_get_may_fail(s_ip4)) {
+                            g_set_error(error,
+                                        NM_CONNECTION_ERROR,
+                                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                        _("contains IPv4 address '%s', %s.%s cannot be 'true'"),
+                                        address,
+                                        NM_SETTING_IP4_CONFIG_SETTING_NAME,
+                                        NM_SETTING_IP_CONFIG_MAY_FAIL);
+                            g_prefix_error(error,
+                                           "%s.%s: ",
+                                           NM_SETTING_CONNECTION_SETTING_NAME,
+                                           NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                            return FALSE;
+                        }
+                    } else {
+                        g_set_error(error,
+                                    NM_CONNECTION_ERROR,
+                                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                    _("contains IPv4 address '%s', %s.%s must be set to 'false' "
+                                      "explicitly"),
+                                    address,
+                                    NM_SETTING_IP4_CONFIG_SETTING_NAME,
+                                    NM_SETTING_IP_CONFIG_MAY_FAIL);
+                        g_prefix_error(error,
+                                       "%s.%s: ",
+                                       NM_SETTING_CONNECTION_SETTING_NAME,
+                                       NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                        return FALSE;
+                    }
+                }
+            } else if (addr_family == AF_INET6) {
+                NMSettingIPConfig *s_ip6;
+
+                if (connection) {
+                    s_ip6 = nm_connection_get_setting_ip6_config(connection);
+                    if (s_ip6) {
+                        const char *method = nm_setting_ip_config_get_method(s_ip6);
+                        if (NM_IN_STRSET(method,
+                                         NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
+                                         NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
+                            g_set_error(error,
+                                        NM_CONNECTION_ERROR,
+                                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                        _("contains IPv6 address '%s', %s.%s cannot be '%s'"),
+                                        address,
+                                        NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                                        NM_SETTING_IP_CONFIG_METHOD,
+                                        method);
+                            g_prefix_error(error,
+                                           "%s.%s: ",
+                                           NM_SETTING_CONNECTION_SETTING_NAME,
+                                           NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                            return FALSE;
+                        }
+                        if (nm_setting_ip_config_get_may_fail(s_ip6)) {
+                            g_set_error(error,
+                                        NM_CONNECTION_ERROR,
+                                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                        _("contains IPv6 address '%s', %s.%s cannot be 'true'"),
+                                        address,
+                                        NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                                        NM_SETTING_IP_CONFIG_MAY_FAIL);
+                            g_prefix_error(error,
+                                           "%s.%s: ",
+                                           NM_SETTING_CONNECTION_SETTING_NAME,
+                                           NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                            return FALSE;
+                        }
+                    } else {
+                        g_set_error(error,
+                                    NM_CONNECTION_ERROR,
+                                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                                    _("contains IPv6 address '%s', %s.%s must be set to 'false' "
+                                      "explicitly"),
+                                    address,
+                                    NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                                    NM_SETTING_IP_CONFIG_MAY_FAIL);
+                        g_prefix_error(error,
+                                       "%s.%s: ",
+                                       NM_SETTING_CONNECTION_SETTING_NAME,
+                                       NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                        return FALSE;
+                    }
+                }
+            } else {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("has an invalid IP address '%s'"),
+                            address);
+                g_prefix_error(error,
+                               "%s.%s: ",
+                               NM_SETTING_CONNECTION_SETTING_NAME,
+                               NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+                return FALSE;
+            }
+        }
+    }
+
     /* *** errors above here should be always fatal, below NORMALIZABLE_ERROR *** */
 
     if (!priv->uuid) {
@@ -1711,6 +2053,20 @@ after_interface_name:
     if (!_nm_setting_connection_verify_secondaries(priv->secondaries.arr, error))
         return NM_SETTING_VERIFY_NORMALIZABLE;
 
+    if (priv->ip_ping_addresses.arr && priv->ip_ping_addresses.arr->len > 0
+        && !_nm_setting_connection_verify_no_duplicate_addresses(priv->ip_ping_addresses.arr,
+                                                                 error)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("has duplicate addresses"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_CONNECTION_SETTING_NAME,
+                       NM_SETTING_CONNECTION_IP_PING_ADDRESSES);
+        return NM_SETTING_VERIFY_NORMALIZABLE;
+    }
+
     if (priv->read_only) {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
@@ -2763,6 +3119,70 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                             secondaries);
 
     /**
+     * NMSettingConnection:ip-ping-addresses:
+     *
+     * The property specifies a list of target IP addresses for pinging.
+     * When multiple targets are set, NetworkManager will start multiple ping processes
+     * in parallel. This property can only be set if connection.ip-ping-timeout is
+     * set. The ip-ping-timeout is used to delay the success of IP addressing until
+     * either the specified timeout (in seconds) is reached, or an target IP address replies
+     * to a ping. Configuring #NMSettingConnection:ip-ping-addresses may delay reaching the
+     * systemd's network-online.target due to waiting for the ping operations to complete or timeout.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_strv(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_CONNECTION_IP_PING_ADDRESSES,
+                                            PROP_IP_PING_ADDRESSES,
+                                            NM_SETTING_PARAM_FUZZY_IGNORE,
+                                            NULL,
+                                            NMSettingConnectionPrivate,
+                                            ip_ping_addresses);
+
+    /**
+     * NMSettingConnection:ip-ping-addresses-require-all:
+     *
+     * The property determines whether it is sufficient for any ping check
+     * to succeed among #NMSettingConnection:ip-ping-addresses, or if all
+     * ping checks must succeed for #NMSettingConnection:ip-ping-addresses.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_enum(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_CONNECTION_IP_PING_ADDRESSES_REQUIRE_ALL,
+                                            PROP_IP_PING_ADDRESSES_REQUIRE_ALL,
+                                            NM_TYPE_TERNARY,
+                                            NM_TERNARY_DEFAULT,
+                                            NM_SETTING_PARAM_NONE,
+                                            NULL,
+                                            NMSettingConnectionPrivate,
+                                            ip_ping_addresses_require_all);
+
+    /**
+     * NMSettingConnection:ip-ping-timeout:
+     *
+     * If greater than zero, delay success of IP addressing until either the specified
+     * timeout (in seconds) is reached, or a target IP address replies to a ping. The
+     * property specifies the timeout for the #NMSettingConnection:ip-ping-addresses.
+     * This property is incompatible with #NMSettingConnection:gateway-ping-timeout,
+     * you cannot set these two properties at the same time.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_uint32(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_CONNECTION_IP_PING_TIMEOUT,
+                                              PROP_IP_PING_TIMEOUT,
+                                              0,
+                                              600,
+                                              0,
+                                              NM_SETTING_PARAM_NONE,
+                                              NMSettingConnectionPrivate,
+                                              ip_ping_timeout);
+
+    /**
      * NMSettingConnection:gateway-ping-timeout:
      *
      * If greater than zero, delay success of IP addressing until either the
diff --git a/src/libnm-core-impl/nm-setting-ethtool.c b/src/libnm-core-impl/nm-setting-ethtool.c
index 06c9c12b..9080076c 100644
--- a/src/libnm-core-impl/nm-setting-ethtool.c
+++ b/src/libnm-core-impl/nm-setting-ethtool.c
@@ -10,6 +10,7 @@
 #include "nm-setting-private.h"
 #include "libnm-base/nm-ethtool-base.h"
 #include "libnm-base/nm-ethtool-utils-base.h"
+#include "libnm-glib-aux/nm-enum-utils.h"
 
 /*****************************************************************************/
 
@@ -121,6 +122,21 @@ nm_ethtool_optname_is_pause(const char *optname)
 {
     return optname && nm_ethtool_id_is_pause(nm_ethtool_id_get_by_name(optname));
 }
+/**
+ * nm_ethtool_optname_is_fec:
+ * @optname: (nullable): the option name to check
+ *
+ * Checks whether @optname is a valid option name for a fec setting.
+ *
+ * Returns: %TRUE, if @optname is valid
+ *
+ * Since: 1.52
+ */
+gboolean
+nm_ethtool_optname_is_fec(const char *optname)
+{
+    return optname && nm_ethtool_id_is_fec(nm_ethtool_id_get_by_name(optname));
+}
 
 /*****************************************************************************/
 
@@ -309,6 +325,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     NMTernary          pause_autoneg = NM_TERNARY_DEFAULT;
     NMTernary          pause_tx      = NM_TERNARY_DEFAULT;
     NMTernary          pause_rx      = NM_TERNARY_DEFAULT;
+    guint32            fec_mode      = 0;
 
     len = _nm_setting_option_get_all(setting, &optnames, &variants);
 
@@ -356,6 +373,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             pause_rx = g_variant_get_boolean(variant);
         else if (NM_IN_SET(ethtool_id, NM_ETHTOOL_ID_PAUSE_TX))
             pause_tx = g_variant_get_boolean(variant);
+        else if (NM_IN_SET(ethtool_id, NM_ETHTOOL_ID_FEC_MODE))
+            fec_mode = g_variant_get_uint32(variant);
     }
 
     if (pause_rx != NM_TERNARY_DEFAULT || pause_tx != NM_TERNARY_DEFAULT) {
@@ -372,6 +391,32 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
+    if (fec_mode == NM_SETTING_ETHTOOL_FEC_MODE_NONE
+        || fec_mode >= (_NM_SETTING_ETHTOOL_FEC_MODE_LAST << 1)) {
+        gs_free const char  *cur_fec_mode = NULL;
+        gs_free const char **valid_all    = NULL;
+        gs_free const char  *valid_str    = NULL;
+
+        cur_fec_mode = _nm_utils_enum_to_str_full(nm_setting_ethtool_fec_mode_get_type(),
+                                                  (int) (fec_mode & INT_MAX),
+                                                  ", ",
+                                                  NULL);
+        valid_all = nm_utils_enum_get_values(nm_setting_ethtool_fec_mode_get_type(), 0, G_MAXUINT);
+        valid_str = g_strjoinv(",", (char **) valid_all);
+
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("'%s' is not valid FEC modes, valid modes are combinations of %s"),
+                    cur_fec_mode,
+                    valid_str);
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_ETHTOOL_SETTING_NAME,
+                       NM_ETHTOOL_OPTNAME_FEC_MODE);
+        return FALSE;
+    }
+
     return TRUE;
 }
 
diff --git a/src/libnm-core-impl/nm-setting-gsm.c b/src/libnm-core-impl/nm-setting-gsm.c
index 632b0ccc..02e0e236 100644
--- a/src/libnm-core-impl/nm-setting-gsm.c
+++ b/src/libnm-core-impl/nm-setting-gsm.c
@@ -38,7 +38,16 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_AUTO_CONFIG,
                                   PROP_SIM_OPERATOR_ID,
                                   PROP_MTU,
                                   PROP_INITIAL_EPS_CONFIG,
-                                  PROP_INITIAL_EPS_APN, );
+                                  PROP_INITIAL_EPS_APN,
+                                  PROP_INITIAL_EPS_USERNAME,
+                                  PROP_INITIAL_EPS_PASSWORD,
+                                  PROP_INITIAL_EPS_PASSWORD_FLAGS,
+                                  PROP_INITIAL_EPS_NOAUTH,
+                                  PROP_INITIAL_EPS_REFUSE_EAP,
+                                  PROP_INITIAL_EPS_REFUSE_PAP,
+                                  PROP_INITIAL_EPS_REFUSE_CHAP,
+                                  PROP_INITIAL_EPS_REFUSE_MSCHAP,
+                                  PROP_INITIAL_EPS_REFUSE_MSCHAPV2, );
 
 typedef struct {
     char   *number;
@@ -51,7 +60,16 @@ typedef struct {
     char   *network_id;
     char   *pin;
     char   *initial_eps_apn;
+    char   *initial_eps_username;
+    char   *initial_eps_password;
+    bool    initial_eps_noauth;
+    bool    initial_eps_refuse_eap;
+    bool    initial_eps_refuse_pap;
+    bool    initial_eps_refuse_chap;
+    bool    initial_eps_refuse_mschap;
+    bool    initial_eps_refuse_mschapv2;
     guint   password_flags;
+    guint   initial_eps_password_flags;
     guint   pin_flags;
     guint32 mtu;
     bool    auto_config;
@@ -319,6 +337,134 @@ nm_setting_gsm_get_initial_eps_apn(NMSettingGsm *setting)
     return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_apn;
 }
 
+/**
+ * nm_setting_gsm_get_initial_eps_username:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:initial-eps-bearer-username property of the setting
+ *
+ * Since: 1.52
+ **/
+const char *
+nm_setting_gsm_get_initial_eps_username(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_username;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_password:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:initial-eps-bearer-password property of the setting
+ *
+ * Since: 1.52
+ **/
+const char *
+nm_setting_gsm_get_initial_eps_password(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_password;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_noauth:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-noauth property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_noauth(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_noauth;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_refuse_eap:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-refuse-eap property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_refuse_eap(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_eap;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_refuse_pap:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-refuse-pap property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_refuse_pap(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_pap;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_refuse_chap:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-refuse-chap property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_refuse_chap(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_chap;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_refuse_mschap:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-refuse-mschap property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_refuse_mschap(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_mschap;
+}
+
+/**
+ * nm_setting_gsm_get_initial_eps_refuse_mschapv2:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: For LTE modems, the #NMSettingGsm:initial-eps-refuse-mschapv2 property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_gsm_get_initial_eps_refuse_mschapv2(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), FALSE);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_mschapv2;
+}
+
 static gboolean
 _verify_apn(const char *apn, gboolean allow_empty, const char *property_name, GError **error)
 {
@@ -847,6 +993,162 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                               initial_eps_apn,
                                               .direct_string_allow_empty = TRUE);
 
+    /**
+     * NMSettingGsm:initial-eps-bearer-username:
+     *
+     * For LTE modems, this sets the username for the initial EPS bearer that is set
+     * up when attaching to the network.  Setting this parameter implies
+     * initial-eps-bearer-configure to be TRUE.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_GSM_INITIAL_EPS_BEARER_USERNAME,
+                                              PROP_INITIAL_EPS_USERNAME,
+                                              NM_SETTING_PARAM_NONE,
+                                              NMSettingGsmPrivate,
+                                              initial_eps_username,
+                                              .direct_string_allow_empty = TRUE);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-password:
+     *
+     * For LTE modems, this sets the password for the initial EPS bearer that is set
+     * up when attaching to the network.  Setting this parameter implies
+     * initial-eps-bearer-configure to be TRUE.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_GSM_INITIAL_EPS_BEARER_PASSWORD,
+                                              PROP_INITIAL_EPS_PASSWORD,
+                                              NM_SETTING_PARAM_SECRET,
+                                              NMSettingGsmPrivate,
+                                              initial_eps_password,
+                                              .direct_string_allow_empty = TRUE);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-password-flags:
+     *
+     * Flags indicating how to handle the #NMSettingGsm:initial-eps-bearer-password property.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_secret_flags(
+        properties_override,
+        obj_properties,
+        NM_SETTING_GSM_INITIAL_EPS_BEARER_PASSWORD_FLAGS,
+        PROP_INITIAL_EPS_PASSWORD_FLAGS,
+        NMSettingGsmPrivate,
+        initial_eps_password_flags);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-noauth:
+     *
+     * For LTE modems, this sets NOAUTH authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     * If %TRUE, do not require the other side to authenticate itself to the client.
+     * If %FALSE, require authentication from the remote side.  In almost all cases,
+     * this should be %TRUE.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_NOAUTH,
+                                               PROP_INITIAL_EPS_NOAUTH,
+                                               TRUE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_noauth);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-refuse-eap:
+     *
+     * For LTE modems, this disables EAP authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_REFUSE_EAP,
+                                               PROP_INITIAL_EPS_REFUSE_EAP,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_refuse_eap);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-refuse-pap:
+     *
+     * For LTE modems, this disables PAP authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_REFUSE_PAP,
+                                               PROP_INITIAL_EPS_REFUSE_PAP,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_refuse_pap);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-refuse-chap:
+     *
+     * For LTE modems, this disables CHAP authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_REFUSE_CHAP,
+                                               PROP_INITIAL_EPS_REFUSE_CHAP,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_refuse_chap);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-refuse-mschap:
+     *
+     * For LTE modems, this disables MSCHAP authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_REFUSE_MSCHAP,
+                                               PROP_INITIAL_EPS_REFUSE_MSCHAP,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_refuse_mschap);
+
+    /**
+     * NMSettingGsm:initial-eps-bearer-refuse-mschapv2:
+     *
+     * For LTE modems, this disables MSCHAPV2 authentication method for the initial EPS bearer that is set
+     * up when attaching to the network.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_GSM_INITIAL_EPS_BEARER_REFUSE_MSCHAPV2,
+                                               PROP_INITIAL_EPS_REFUSE_MSCHAPV2,
+                                               FALSE,
+                                               NM_SETTING_PARAM_NONE,
+                                               NMSettingGsmPrivate,
+                                               initial_eps_refuse_mschapv2);
+
     /* Ignore incoming deprecated properties */
     _nm_properties_override_dbus(properties_override,
                                  "allowed-bands",
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index e79f25a8..1ec97029 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -175,7 +175,7 @@ nm_ip_address_new(int family, const char *addr, guint prefix, GError **error)
         return NULL;
 
     address  = g_slice_new(NMIPAddress);
-    *address = (NMIPAddress){
+    *address = (NMIPAddress) {
         .refcount = 1,
         .family   = family,
         .address  = canonicalize_ip_binary(family, &addr_bin, FALSE),
@@ -210,7 +210,7 @@ nm_ip_address_new_binary(int family, gconstpointer addr, guint prefix, GError **
         return NULL;
 
     address  = g_slice_new(NMIPAddress);
-    *address = (NMIPAddress){
+    *address = (NMIPAddress) {
         .refcount = 1,
         .family   = family,
         .address  = nm_inet_ntop_dup(family, addr),
@@ -637,7 +637,7 @@ nm_ip_route_new(int         family,
         return NULL;
 
     route  = g_slice_new(NMIPRoute);
-    *route = (NMIPRoute){
+    *route = (NMIPRoute) {
         .refcount = 1,
         .family   = family,
         .dest     = canonicalize_ip_binary(family, &dest_bin, FALSE),
@@ -683,7 +683,7 @@ nm_ip_route_new_binary(int           family,
         return NULL;
 
     route  = g_slice_new0(NMIPRoute);
-    *route = (NMIPRoute){
+    *route = (NMIPRoute) {
         .refcount = 1,
         .family   = family,
         .dest     = canonicalize_ip_binary(family, dest, FALSE),
@@ -1655,7 +1655,7 @@ nm_ip_routing_rule_new(int addr_family)
     g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NULL);
 
     self  = g_slice_new(NMIPRoutingRule);
-    *self = (NMIPRoutingRule){
+    *self = (NMIPRoutingRule) {
         .ref_count             = 1,
         .is_v4                 = (addr_family == AF_INET),
         .action                = FR_ACT_TO_TBL,
@@ -1685,7 +1685,7 @@ nm_ip_routing_rule_new_clone(const NMIPRoutingRule *rule)
     g_return_val_if_fail(NM_IS_IP_ROUTING_RULE(rule, TRUE), NULL);
 
     self  = g_slice_new(NMIPRoutingRule);
-    *self = (NMIPRoutingRule){
+    *self = (NMIPRoutingRule) {
         .ref_count = 1,
         .sealed    = FALSE,
         .is_v4     = rule->is_v4,
@@ -3996,6 +3996,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingIPConfig,
                              PROP_DHCP_DSCP,
                              PROP_DHCP_HOSTNAME_FLAGS,
                              PROP_DHCP_SEND_HOSTNAME,
+                             PROP_DHCP_SEND_HOSTNAME_V2,
                              PROP_NEVER_DEFAULT,
                              PROP_MAY_FAIL,
                              PROP_DAD_TIMEOUT,
@@ -4005,7 +4006,10 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingIPConfig,
                              PROP_DHCP_REJECT_SERVERS,
                              PROP_AUTO_ROUTE_EXT_GW,
                              PROP_REPLACE_LOCAL_RULE,
-                             PROP_DHCP_SEND_RELEASE, );
+                             PROP_DHCP_SEND_RELEASE,
+                             PROP_ROUTED_DNS,
+                             PROP_SHARED_DHCP_RANGE,
+                             PROP_SHARED_DHCP_LEASE_TIME, );
 
 G_DEFINE_ABSTRACT_TYPE(NMSettingIPConfig, nm_setting_ip_config, NM_TYPE_SETTING)
 
@@ -4091,7 +4095,7 @@ _ip_config_add_dns(NMSettingIPConfig *setting, const char *dns)
 
     priv = NM_SETTING_IP_CONFIG_GET_PRIVATE(setting);
 
-    s = nm_utils_dnsname_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
+    s = nm_dns_uri_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
     if (!s)
         s = dns;
 
@@ -4182,7 +4186,7 @@ nm_setting_ip_config_remove_dns_by_value(NMSettingIPConfig *setting, const char
         gs_free char *s_free = NULL;
         const char   *s;
 
-        s = nm_utils_dnsname_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
+        s = nm_dns_uri_normalize(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, &s_free);
         if (s && !nm_streq(dns, s))
             idx = nm_strv_ptrarray_find_first(priv->dns, dns);
     }
@@ -5193,6 +5197,8 @@ nm_setting_ip_config_get_dhcp_hostname(NMSettingIPConfig *setting)
  * Returns: %TRUE if NetworkManager should send the machine hostname to the
  * DHCP server when requesting addresses to allow the server to automatically
  * update DNS information for this machine.
+ *
+ * Deprecated: 1.52. Use nm_setting_ip_config_get_dhcp_send_hostname_v2() instead.
  **/
 gboolean
 nm_setting_ip_config_get_dhcp_send_hostname(NMSettingIPConfig *setting)
@@ -5203,6 +5209,25 @@ nm_setting_ip_config_get_dhcp_send_hostname(NMSettingIPConfig *setting)
 }
 
 /**
+ * nm_setting_ip_config_get_dhcp_send_hostname_v2:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns the value contained in the #NMSettingIPConfig:dhcp-send-hostname-v2
+ * property.
+ *
+ * Returns: the #NMSettingIPConfig:dhcp-send-hostname-v2 property of the setting
+ *
+ * Since: 1.52
+ **/
+NMTernary
+nm_setting_ip_config_get_dhcp_send_hostname_v2(NMSettingIPConfig *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NM_TERNARY_DEFAULT);
+
+    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_send_hostname_v2;
+}
+
+/**
  * nm_setting_ip_config_get_dhcp_dscp:
  * @setting: the #NMSettingIPConfig
  *
@@ -5480,6 +5505,60 @@ nm_setting_ip_config_get_dhcp_send_release(NMSettingIPConfig *setting)
     return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->dhcp_send_release;
 }
 
+/**
+ * nm_setting_ip_config_get_routed_dns:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns: the #NMSettingIPConfig:routed-dns property of the setting
+ *
+ * Since: 1.52
+ **/
+NMSettingIPConfigRoutedDns
+nm_setting_ip_config_get_routed_dns(NMSettingIPConfig *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT);
+
+    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->routed_dns;
+}
+
+/**
+ * nm_setting_ip_config_get_shared_dhcp_range:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns the value contained in the #NMSettingIPConfig:shared-dhcp-range
+ * property.
+ *
+ * Returns: the configured DHCP server range
+ *
+ * Since: 1.52
+ **/
+const char *
+nm_setting_ip_config_get_shared_dhcp_range(NMSettingIPConfig *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), NULL);
+
+    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->shared_dhcp_range;
+}
+
+/**
+ * nm_setting_ip_config_get_shared_dhcp_lease_time:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns the value contained in the #NMSettingIPConfig:shared-dhcp-lease-time
+ * property.
+ *
+ * Returns: the configured DHCP server lease time
+ *
+ * Since: 1.52
+ **/
+int
+nm_setting_ip_config_get_shared_dhcp_lease_time(NMSettingIPConfig *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), 0);
+
+    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->shared_dhcp_lease_time;
+}
+
 static gboolean
 verify_label(const char *label)
 {
@@ -5536,11 +5615,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         for (i = 0; i < priv->dns->len; i++) {
             const char *dns = priv->dns->pdata[i];
 
-            if (!nm_utils_dnsname_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
-                                        dns,
-                                        NULL,
-                                        NULL,
-                                        NULL)) {
+            if (!nm_dns_uri_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, NULL)) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -5776,6 +5851,26 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
+    /* Validate DHCP range served in the shared mode  */
+    if (priv->shared_dhcp_range
+        && !nm_utils_validate_shared_dhcp_range(priv->shared_dhcp_range, priv->addresses, error)) {
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       nm_setting_get_name(setting),
+                       NM_SETTING_IP_CONFIG_SHARED_DHCP_RANGE);
+        return FALSE;
+    }
+
+    /* Validate DHCP lease time */
+    if (priv->shared_dhcp_lease_time
+        && !nm_utils_validate_shared_dhcp_lease_time(priv->shared_dhcp_lease_time, error)) {
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       nm_setting_get_name(setting),
+                       NM_SETTING_IP_CONFIG_SHARED_DHCP_LEASE_TIME);
+        return FALSE;
+    }
+
     /* Normalizable errors */
     if (priv->gateway && priv->never_default) {
         g_set_error(error,
@@ -5790,6 +5885,20 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
     }
 
+    if (priv->dhcp_send_hostname_v2 != NM_TERNARY_DEFAULT
+        && priv->dhcp_send_hostname != priv->dhcp_send_hostname_v2) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("the value is inconsistent with '%s'"),
+                    NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME_V2);
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       nm_setting_get_name(setting),
+                       NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME);
+        return NM_SETTING_VERIFY_NORMALIZABLE_ERROR;
+    }
+
     return TRUE;
 }
 
@@ -6133,6 +6242,14 @@ _nm_sett_info_property_override_create_array_ip_config(int addr_family)
 
     _nm_properties_override_gobj(
         properties_override,
+        obj_properties[PROP_DHCP_SEND_HOSTNAME_V2],
+        &nm_sett_info_propert_type_direct_enum,
+        .direct_offset =
+            NM_STRUCT_OFFSET_ENSURE_TYPE(int, NMSettingIPConfigPrivate, dhcp_send_hostname_v2),
+        .direct_data.enum_gtype = NM_TYPE_TERNARY);
+
+    _nm_properties_override_gobj(
+        properties_override,
         obj_properties[PROP_DHCP_HOSTNAME_FLAGS],
         &nm_sett_info_propert_type_direct_uint32,
         .direct_offset =
@@ -6198,6 +6315,28 @@ _nm_sett_info_property_override_create_array_ip_config(int addr_family)
                                                                   NMSettingIPConfigPrivate,
                                                                   dhcp_reject_servers));
 
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_ROUTED_DNS],
+        &nm_sett_info_propert_type_direct_enum,
+        .direct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE(int, NMSettingIPConfigPrivate, routed_dns),
+        .direct_data.enum_gtype = NM_TYPE_SETTING_IP_CONFIG_ROUTED_DNS);
+
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_SHARED_DHCP_RANGE],
+        &nm_sett_info_propert_type_direct_string,
+        .direct_offset =
+            NM_STRUCT_OFFSET_ENSURE_TYPE(char *, NMSettingIPConfigPrivate, shared_dhcp_range),
+        .direct_string_allow_empty = TRUE);
+
+    _nm_properties_override_gobj(
+        properties_override,
+        obj_properties[PROP_SHARED_DHCP_LEASE_TIME],
+        &nm_sett_info_propert_type_direct_int32,
+        .direct_offset =
+            NM_STRUCT_OFFSET_ENSURE_TYPE(gint32, NMSettingIPConfigPrivate, shared_dhcp_lease_time));
+
     return properties_override;
 }
 
@@ -6367,11 +6506,16 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
     /**
      * NMSettingIPConfig:dns:
      *
-     * Array of IP addresses of DNS servers.
+     * Array of DNS servers.
+     *
+     * Each server can be specified either as a plain IP address (optionally followed
+     * by a "#" and the SNI server name for DNS over TLS) or with a URI syntax.
+     *
+     * When it is specified as an URI, the following forms are supported:
+     * dns+udp://ADDRESS[:PORT], dns+tls://ADDRESS[:PORT][#SERVERNAME] .
      *
-     * For DoT (DNS over TLS), the SNI server name can be specified by appending
-     * "#example.com" to the IP address of the DNS server. This currently only has
-     * effect when using systemd-resolved.
+     * When using the URI syntax, IPv6 addresses must be enclosed in square
+     * brackets ('[', ']').
      **/
     obj_properties[PROP_DNS] =
         g_param_spec_boxed(NM_SETTING_IP_CONFIG_DNS,
@@ -6662,12 +6806,21 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
     /**
      * NMSettingIPConfig:dhcp-send-hostname:
      *
-     * If %TRUE, a hostname is sent to the DHCP server when acquiring a lease.
-     * Some DHCP servers use this hostname to update DNS databases, essentially
-     * providing a static hostname for the computer.  If the
-     * #NMSettingIPConfig:dhcp-hostname property is %NULL and this property is
-     * %TRUE, the current persistent hostname of the computer is sent.
+     * Since 1.52 this property is deprecated and is only used as fallback value
+     * for #NMSettingIPConfig:dhcp-send-hostname-v2 if it's set to 'default'.
+     * This is only done to avoid breaking existing configurations, the new
+     * property should be used from now on.
+     *
+     * Deprecated: 1.52: use the new version of dhcp-send-hostname instead.
      **/
+    /* ---nmcli---
+     * property: dhcp-send-hostname
+     * rename: dhcp-send-hostname-deprecated
+     * description: Since 1.52 this property is deprecated and is only used as fallback value
+     *    for dhcp-send-hostname if it's set to 'default'. This is only done to avoid
+     *    breaking existing configurations, the new property should be used from now on.
+     * ---end---
+     */
     obj_properties[PROP_DHCP_SEND_HOSTNAME] =
         g_param_spec_boolean(NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME,
                              "",
@@ -6946,5 +7099,111 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
                           NM_TERNARY_DEFAULT,
                           G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
 
+    /**
+     * NMSettingIPConfig:routed-dns:
+     *
+     * Whether to add routes for DNS servers. When enabled, NetworkManager adds a route
+     * for each DNS server that is associated with this connection either statically
+     * (defined in the connection profile) or dynamically (for example, retrieved via
+     * DHCP). The route guarantees that the DNS server is reached via this interface. When
+     * set to %NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT, the value from global
+     * configuration is used; if no global default is defined, this feature is disabled.
+     *
+     * Since: 1.52
+     */
+    obj_properties[PROP_ROUTED_DNS] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_ROUTED_DNS,
+                         "",
+                         "",
+                         NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT,
+                         NM_SETTING_IP_CONFIG_ROUTED_DNS_YES,
+                         NM_SETTING_IP_CONFIG_ROUTED_DNS_DEFAULT,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+    /**                                                  
+     * NMSettingIPConfig:dhcp-send-hostname-v2:
+     *
+     * If %TRUE, a hostname is sent to the DHCP server when acquiring a lease.
+     * Some DHCP servers use this hostname to update DNS databases, essentially
+     * providing a static hostname for the computer.  If the
+     * #NMSettingIPConfig:dhcp-hostname property is %NULL and this property is
+     * %TRUE, the current persistent hostname of the computer is sent.
+     *
+     * The default value is %NM_TERNARY_DEFAULT. In this case the global value
+     * from NetworkManager configuration is looked up. If it's not set, the value
+     * from #NMSettingIPConfig:dhcp-send-hostname, which defaults to %TRUE, is
+     * used for backwards compatibility. In the future this will change and, in
+     * absence of a global default, it will always fallback to %TRUE.
+     *
+     * Since: 1.52
+     **/
+    /* ---nmcli---
+     * property: dhcp-send-hostname-v2
+     * rename: dhcp-send-hostname
+     * description: If %TRUE, a hostname is sent to the DHCP server when acquiring a lease.
+     *    Some DHCP servers use this hostname to update DNS databases, essentially
+     *    providing a static hostname for the computer.  If the dhcp-hostname
+     *    property is %NULL and this property is %TRUE, the current persistent
+     *    hostname of the computer is sent.
+     *
+     *    The default value is %NM_TERNARY_DEFAULT. In this case the global value
+     *    from NetworkManager configuration is looked up. If it's not set, the value
+     *    from dhcp-send-hostname-deprecated, which defaults to %TRUE, is
+     *    used for backwards compatibility. In the future this will change and, in
+     *    absence of a global default, it will always fallback to %TRUE.
+     * ---end---
+     */
+    obj_properties[PROP_DHCP_SEND_HOSTNAME_V2] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME_V2,
+                         "",
+                         "",
+                         G_MININT,
+                         G_MAXINT,
+                         NM_TERNARY_DEFAULT,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingIPConfig:shared-dhcp-range:
+     *
+     * This option allows you to specify a custom DHCP range for the shared connection
+     * method. The value is expected to be in `<START_ADDRESS>,<END_ADDRESS>` format.
+     * The range should be part of network set by ipv4.address option and it should
+     * not contain network address or broadcast address. If this option is not specified,
+     * the DHCP range will be automatically determined based on the interface address.
+     * The range will be selected to be adjacent to the interface address, either before
+     * or after it, with the larger possible range being preferred. The range will be
+     * adjusted to fill the available address space, except for networks with a prefix
+     * length greater than 24, which will be treated as if they have a prefix length of 24.
+     *
+     * Since: 1.52
+     */
+    obj_properties[PROP_SHARED_DHCP_RANGE] =
+        g_param_spec_string(NM_SETTING_IP_CONFIG_SHARED_DHCP_RANGE,
+                            "",
+                            "",
+                            NULL,
+                            G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+    /**
+     * NMSettingIPConfig:shared-dhcp-lease-time:
+     *
+     * This option allows you to specify a custom DHCP lease time for the shared connection
+     * method in seconds. The value should be either a number between 120 and 31536000 (one year)
+     * If this option is not specified, 3600 (one hour) is used.
+     *
+     * Special values are 0 for default value of 1 hour and 2147483647 (MAXINT32) for infinite lease time.
+     *
+     * Since: 1.52
+     */
+    obj_properties[PROP_SHARED_DHCP_LEASE_TIME] =
+        g_param_spec_int(NM_SETTING_IP_CONFIG_SHARED_DHCP_LEASE_TIME,
+                         "",
+                         "",
+                         0,
+                         G_MAXINT32,
+                         0,
+                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | NM_SETTING_PARAM_FUZZY_IGNORE
+                             | 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-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index 6112137f..a4fccc6d 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -39,7 +39,8 @@
 NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DHCP_CLIENT_ID,
                                   PROP_DHCP_FQDN,
                                   PROP_DHCP_VENDOR_CLASS_IDENTIFIER,
-                                  PROP_LINK_LOCAL, );
+                                  PROP_LINK_LOCAL,
+                                  PROP_DHCP_IPV6_ONLY_PREFERRED, );
 
 typedef struct {
     NMSettingIPConfigPrivate parent;
@@ -48,6 +49,7 @@ typedef struct {
     char  *dhcp_fqdn;
     char  *dhcp_vendor_class_identifier;
     gint32 link_local;
+    gint32 dhcp_ipv6_only_preferred;
 } NMSettingIP4ConfigPrivate;
 
 /**
@@ -146,6 +148,26 @@ nm_setting_ip4_config_get_link_local(NMSettingIP4Config *setting)
     return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->link_local;
 }
 
+/**
+ * nm_setting_ip4_config_get_dhcp_ipv6_only_preferred:
+ * @setting: the #NMSettingIP4Config
+ *
+ * Returns the value in the #NMSettingIP4Config:dhcp-ipv6-only-preferred
+ * property.
+ *
+ * Returns: the DHCP IPv6-only preferred property value
+ *
+ * Since: 1.52
+ **/
+NMSettingIP4DhcpIpv6OnlyPreferred
+nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(NMSettingIP4Config *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting),
+                         NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT);
+
+    return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_ipv6_only_preferred;
+}
+
 static gboolean
 verify(NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -241,7 +263,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                    NM_SETTING_IP4_LL_AUTO,
                    NM_SETTING_IP4_LL_DEFAULT,
                    NM_SETTING_IP4_LL_DISABLED,
-                   NM_SETTING_IP4_LL_ENABLED)) {
+                   NM_SETTING_IP4_LL_ENABLED,
+                   NM_SETTING_IP4_LL_FALLBACK)) {
         g_set_error(error,
                     NM_CONNECTION_ERROR,
                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
@@ -252,7 +275,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                        NM_SETTING_IP4_CONFIG_LINK_LOCAL);
         return FALSE;
     }
-    if (priv->link_local == NM_SETTING_IP4_LL_ENABLED
+    if (NM_IN_SET(priv->link_local, NM_SETTING_IP4_LL_ENABLED, NM_SETTING_IP4_LL_FALLBACK)
         && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
         g_set_error_literal(error,
                             NM_CONNECTION_ERROR,
@@ -1028,6 +1051,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * When set to "default", it honors the global connection default, before
      * falling back to "auto". Note that if "ipv4.method" is "disabled", then
      * link local addressing is always disabled too. The default is "default".
+     * Since 1.52, when set to "fallback", a link-local address is obtained
+     * if no other IPv4 address is set.
      *
      * Since: 1.40
      */
@@ -1325,6 +1350,38 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * ---end---
      */
 
+    /**
+     * NMSettingIP4Config:dhcp-ipv6-only-preferred
+     *
+     * Controls the "IPv6-Only Preferred" DHCPv4 option (RFC 8925).
+     *
+     * When set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES, the host adds the
+     * option to the parameter request list; if the DHCP server sends the option back,
+     * the host stops the DHCP client for the time interval specified in the option.
+     *
+     * Enable this feature if the host supports an IPv6-only mode, i.e. either all
+     * applications are IPv6-only capable or there is a form of 464XLAT deployed.
+     *
+     * When set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT, the actual value
+     * is looked up in the global configuration; if not specified, it defaults to
+     * %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO.
+     *
+     * If the connection has IPv6 method set to "disabled", this property does not
+     * have effect and the "IPv6-Only Preferred" option is always disabled.
+     *
+     * Since: 1.52
+     */
+    _nm_setting_property_define_direct_enum(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED,
+                                            PROP_DHCP_IPV6_ONLY_PREFERRED,
+                                            NM_TYPE_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED,
+                                            NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT,
+                                            NM_SETTING_PARAM_NONE,
+                                            NULL,
+                                            NMSettingIP4ConfigPrivate,
+                                            dhcp_ipv6_only_preferred);
+
     g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
 
     _nm_setting_class_commit(setting_class,
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index 32fb295f..c68be991 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -440,6 +440,30 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         return FALSE;
     }
 
+    if (nm_setting_ip_config_get_shared_dhcp_range(s_ip)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Shared DHCP range is not supported for IPv6"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                       NM_SETTING_IP_CONFIG_SHARED_DHCP_RANGE);
+        return FALSE;
+    }
+
+    if (nm_setting_ip_config_get_shared_dhcp_lease_time(s_ip)) {
+        g_set_error_literal(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("Shared DHCP lease time is not supported for IPv6"));
+        g_prefix_error(error,
+                       "%s.%s: ",
+                       NM_SETTING_IP6_CONFIG_SETTING_NAME,
+                       NM_SETTING_IP_CONFIG_SHARED_DHCP_LEASE_TIME);
+        return FALSE;
+    }
+
     /* Failures from here on, are NORMALIZABLE_ERROR... */
 
     if (token_needs_normalization) {
diff --git a/src/libnm-core-impl/nm-setting-ipvlan.c b/src/libnm-core-impl/nm-setting-ipvlan.c
new file mode 100644
index 00000000..fafa37b6
--- /dev/null
+++ b/src/libnm-core-impl/nm-setting-ipvlan.c
@@ -0,0 +1,290 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024 Red Hat, Inc.
+ */
+
+#include "libnm-core-impl/nm-default-libnm-core.h"
+
+#include "nm-setting-ipvlan.h"
+
+#include "nm-connection-private.h"
+#include "nm-utils.h"
+#include "nm-utils-private.h"
+
+/**
+ * SECTION:nm-setting-ipvlan
+ * @short_description: Describes connection properties for IPVLAN interfaces
+ *
+ * The #NMSettingIpvlan object is a #NMSetting subclass that describes properties
+ * necessary for connection to IPVLAN interfaces.
+ **/
+
+/*****************************************************************************/
+
+NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PARENT, PROP_MODE, PROP_PRIVATE, PROP_VEPA, );
+
+typedef struct {
+    char   *parent;
+    guint32 mode;
+    bool    private_flag;
+    bool    vepa;
+} NMSettingIpvlanPrivate;
+
+/**
+ * NMSettingIpvlan:
+ *
+ * IPVLAN Settings
+ */
+struct _NMSettingIpvlan {
+    NMSetting              parent;
+    NMSettingIpvlanPrivate _priv;
+};
+
+struct _NMSettingIpvlanClass {
+    NMSettingClass parent;
+};
+
+G_DEFINE_TYPE(NMSettingIpvlan, nm_setting_ipvlan, NM_TYPE_SETTING)
+
+#define NM_SETTING_IPVLAN_GET_PRIVATE(o) \
+    _NM_GET_PRIVATE(o, NMSettingIpvlan, NM_IS_SETTING_IPVLAN, NMSetting)
+
+/*****************************************************************************/
+
+/**
+ * nm_setting_ipvlan_get_parent:
+ * @setting: the #NMSettingIpvlan
+ *
+ * Returns: the #NMSettingIpvlan:parent property of the setting
+ *
+ * Since: 1.52
+ **/
+const char *
+nm_setting_ipvlan_get_parent(NMSettingIpvlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IPVLAN(setting), NULL);
+
+    return NM_SETTING_IPVLAN_GET_PRIVATE(setting)->parent;
+}
+
+/**
+ * nm_setting_ipvlan_get_mode:
+ * @setting: the #NMSettingIpvlan
+ *
+ * Returns: the #NMSettingIpvlan:mode property of the setting
+ *
+ * Since: 1.52
+ **/
+NMSettingIpvlanMode
+nm_setting_ipvlan_get_mode(NMSettingIpvlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IPVLAN(setting), NM_SETTING_IPVLAN_MODE_UNKNOWN);
+
+    return NM_SETTING_IPVLAN_GET_PRIVATE(setting)->mode;
+}
+
+/**
+ * nm_setting_ipvlan_get_private:
+ * @setting: the #NMSettingIpvlan
+ *
+ * Returns: the #NMSettingIpvlan:private property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_ipvlan_get_private(NMSettingIpvlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IPVLAN(setting), FALSE);
+
+    return NM_SETTING_IPVLAN_GET_PRIVATE(setting)->private_flag;
+}
+
+/**
+ * nm_setting_ipvlan_get_vepa:
+ * @setting: the #NMSettingIpvlan
+ *
+ * Returns: the #NMSettingIpvlan:vepa property of the setting
+ *
+ * Since: 1.52
+ **/
+gboolean
+nm_setting_ipvlan_get_vepa(NMSettingIpvlan *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IPVLAN(setting), FALSE);
+
+    return NM_SETTING_IPVLAN_GET_PRIVATE(setting)->vepa;
+}
+
+/*****************************************************************************/
+
+static gboolean
+verify(NMSetting *setting, NMConnection *connection, GError **error)
+{
+    NMSettingIpvlanPrivate *priv    = NM_SETTING_IPVLAN_GET_PRIVATE(setting);
+    NMSettingWired         *s_wired = NULL;
+
+    if (connection)
+        s_wired = nm_connection_get_setting_wired(connection);
+
+    if (priv->parent) {
+        if (!nm_utils_is_uuid(priv->parent) && !nm_utils_ifname_valid_kernel(priv->parent, NULL)) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' is neither an UUID nor an interface name"),
+                        priv->parent);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_IPVLAN_SETTING_NAME,
+                           NM_SETTING_IPVLAN_PARENT);
+            return FALSE;
+        }
+    } else {
+        /* If parent is NULL, the parent must be specified via NMSettingWired:mac-address. */
+        if (connection && (!s_wired || !nm_setting_wired_get_mac_address(s_wired))) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                        _("property is not specified and neither is '%s:%s'"),
+                        NM_SETTING_WIRED_SETTING_NAME,
+                        NM_SETTING_WIRED_MAC_ADDRESS);
+            g_prefix_error(error,
+                           "%s.%s: ",
+                           NM_SETTING_IPVLAN_SETTING_NAME,
+                           NM_SETTING_IPVLAN_PARENT);
+            return FALSE;
+        }
+    }
+
+    if (priv->private_flag && priv->vepa) {
+        g_set_error(error,
+                    NM_CONNECTION_ERROR,
+                    NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                    _("private and VEPA cannot be enabled at the same time"));
+        g_prefix_error(error, "%s: ", NM_SETTING_IPVLAN_SETTING_NAME);
+        return FALSE;
+    }
+
+    if (!_nm_connection_verify_required_interface_name(connection, error))
+        return FALSE;
+
+    return TRUE;
+}
+
+/*****************************************************************************/
+
+static void
+nm_setting_ipvlan_init(NMSettingIpvlan *self)
+{}
+
+/**
+ * nm_setting_ipvlan_new:
+ *
+ * Creates a new #NMSettingIpvlan object with default values.
+ *
+ * Returns: (transfer full): the new empty #NMSettingIpvlan object
+ *
+ * Since: 1.52
+ **/
+NMSetting *
+nm_setting_ipvlan_new(void)
+{
+    return g_object_new(NM_TYPE_SETTING_IPVLAN, NULL);
+}
+
+static void
+nm_setting_ipvlan_class_init(NMSettingIpvlanClass *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;
+
+    /**
+     * NMSettingIpvlan:parent:
+     *
+     * If given, specifies the parent interface name or parent connection UUID
+     * from which this IPVLAN interface should be created. If this property is
+     * not specified, the connection must contain an #NMSettingWired setting
+     * with a #NMSettingWired:mac-address property.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_IPVLAN_PARENT,
+                                              PROP_PARENT,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingIpvlanPrivate,
+                                              parent,
+                                              .direct_string_allow_empty = TRUE);
+
+    /**
+     * NMSettingIpvlan:mode:
+     *
+     * The IPVLAN mode. Valid values: %NM_SETTING_IPVLAN_MODE_L2,
+     * %NM_SETTING_IPVLAN_MODE_L3 and %NM_SETTING_IPVLAN_MODE_L3S.
+     *
+     * Since: 1.52
+     **/
+    /* ---nmcli---
+     * property: mode
+     * description:
+     *   The IPVLAN mode. Valid values: l2 (1), l3 (2), l3s (3)
+     * ---end---
+     */
+    _nm_setting_property_define_direct_uint32(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_IPVLAN_MODE,
+                                              PROP_MODE,
+                                              0,
+                                              G_MAXUINT32,
+                                              NM_SETTING_IPVLAN_MODE_UNKNOWN,
+                                              NM_SETTING_PARAM_INFERRABLE,
+                                              NMSettingIpvlanPrivate,
+                                              mode);
+
+    /**
+     * NMSettingIpvlan:private:
+     *
+     * Whether the interface should be put in private mode.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_IPVLAN_PRIVATE,
+                                               PROP_PRIVATE,
+                                               FALSE,
+                                               NM_SETTING_PARAM_INFERRABLE,
+                                               NMSettingIpvlanPrivate,
+                                               private_flag);
+
+    /**
+     * NMSettingIpvlan:vepa:
+     *
+     * Whether the interface should be put in VEPA mode.
+     *
+     * Since: 1.52
+     **/
+    _nm_setting_property_define_direct_boolean(properties_override,
+                                               obj_properties,
+                                               NM_SETTING_IPVLAN_VEPA,
+                                               PROP_VEPA,
+                                               FALSE,
+                                               NM_SETTING_PARAM_INFERRABLE,
+                                               NMSettingIpvlanPrivate,
+                                               vepa);
+
+    g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
+
+    _nm_setting_class_commit(setting_class,
+                             NM_META_SETTING_TYPE_IPVLAN,
+                             NULL,
+                             properties_override,
+                             G_STRUCT_OFFSET(NMSettingIpvlan, _priv));
+}
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index da7fdbb7..ba838364 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -186,10 +186,14 @@ typedef struct {
     char       *dhcp_hostname;
     char       *dhcp_iaid;
     char       *dhcp_dscp;
+    char       *shared_dhcp_range;
+    int         shared_dhcp_lease_time;
     gint64      route_metric;
     int         auto_route_ext_gw;
     int         replace_local_rule;
     int         dhcp_send_release;
+    int         routed_dns;
+    int         dhcp_send_hostname_v2;
     gint32      required_timeout;
     gint32      dad_timeout;
     gint32      dhcp_timeout;
@@ -482,7 +486,7 @@ void _nm_setting_class_commit(NMSettingClass             *setting_class,
         &_g;                                                   \
     })
 
-#define NM_SETT_INFO_SETT_DETAIL(...) (&((const NMSettInfoSettDetail){__VA_ARGS__}))
+#define NM_SETT_INFO_SETT_DETAIL(...) (&((const NMSettInfoSettDetail) {__VA_ARGS__}))
 
 #define NM_SETT_INFO_PROPERT_TYPE_DBUS_INIT(_dbus_type, ...) {.dbus_type = _dbus_type, __VA_ARGS__}
 
@@ -504,7 +508,7 @@ void _nm_setting_class_commit(NMSettingClass             *setting_class,
 #define NM_SETT_INFO_PROPERT_TYPE_GPROP(_dbus_type, ...) \
     NM_SETT_INFO_PROPERT_TYPE(NM_SETT_INFO_PROPERT_TYPE_GPROP_INIT(_dbus_type, __VA_ARGS__))
 
-#define NM_SETT_INFO_PROPERTY(...) (&((const NMSettInfoProperty){__VA_ARGS__}))
+#define NM_SETT_INFO_PROPERTY(...) (&((const NMSettInfoProperty) {__VA_ARGS__}))
 
 gboolean _nm_properties_override_assert(const NMSettInfoProperty *prop_info);
 
diff --git a/src/libnm-core-impl/nm-setting-sriov.c b/src/libnm-core-impl/nm-setting-sriov.c
index 145c2b14..4dc61d82 100644
--- a/src/libnm-core-impl/nm-setting-sriov.c
+++ b/src/libnm-core-impl/nm-setting-sriov.c
@@ -103,7 +103,7 @@ nm_sriov_vf_new(guint index)
     NMSriovVF *vf;
 
     vf  = g_slice_new(NMSriovVF);
-    *vf = (NMSriovVF){
+    *vf = (NMSriovVF) {
         .refcount   = 1,
         .index      = index,
         .attributes = g_hash_table_new_full(nm_str_hash,
@@ -223,7 +223,7 @@ vf_add_vlan(NMSriovVF *vf, guint vlan_id, guint qos, NMSriovVFVlanProtocol proto
     VFVlan *vlan;
 
     vlan  = g_slice_new(VFVlan);
-    *vlan = (VFVlan){
+    *vlan = (VFVlan) {
         .id       = vlan_id,
         .qos      = qos,
         .protocol = protocol,
diff --git a/src/libnm-core-impl/nm-setting-vlan.c b/src/libnm-core-impl/nm-setting-vlan.c
index 3a1f0930..5cb2470c 100644
--- a/src/libnm-core-impl/nm-setting-vlan.c
+++ b/src/libnm-core-impl/nm-setting-vlan.c
@@ -130,7 +130,7 @@ priority_map_new(guint32 from, guint32 to)
     NMVlanQosMapping *mapping;
 
     mapping  = g_new(NMVlanQosMapping, 1);
-    *mapping = (NMVlanQosMapping){
+    *mapping = (NMVlanQosMapping) {
         .from = from,
         .to   = to,
     };
diff --git a/src/libnm-core-impl/nm-setting-wired.c b/src/libnm-core-impl/nm-setting-wired.c
index 2c8562d3..e02c0a06 100644
--- a/src/libnm-core-impl/nm-setting-wired.c
+++ b/src/libnm-core-impl/nm-setting-wired.c
@@ -810,7 +810,7 @@ nm_setting_wired_add_s390_option(NMSettingWired *setting, const char *key, const
                     &priv->s390_options.arr[dst_idx],
                     (priv->s390_options.len - dst_idx) * sizeof(NMUtilsNamedValue));
         }
-        priv->s390_options.arr[dst_idx] = (NMUtilsNamedValue){
+        priv->s390_options.arr[dst_idx] = (NMUtilsNamedValue) {
             .name      = g_strdup(key),
             .value_str = g_strdup(value),
         };
@@ -1253,7 +1253,7 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
 
                 nm_assert(priv->s390_options.len < priv->s390_options.n_alloc);
 
-                priv->s390_options.arr[priv->s390_options.len] = (NMUtilsNamedValue){
+                priv->s390_options.arr[priv->s390_options.len] = (NMUtilsNamedValue) {
                     .name      = g_strdup(key),
                     .value_str = g_strdup(val),
                 };
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c
index 4f96f742..668af1f6 100644
--- a/src/libnm-core-impl/nm-setting-wireguard.c
+++ b/src/libnm-core-impl/nm-setting-wireguard.c
@@ -77,7 +77,7 @@ nm_wireguard_peer_new(void)
     NMWireGuardPeer *self;
 
     self  = g_slice_new(NMWireGuardPeer);
-    *self = (NMWireGuardPeer){
+    *self = (NMWireGuardPeer) {
         .refcount            = 1,
         .preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
     };
@@ -104,7 +104,7 @@ nm_wireguard_peer_new_clone(const NMWireGuardPeer *self, gboolean with_secrets)
     g_return_val_if_fail(NM_IS_WIREGUARD_PEER(self, TRUE), NULL);
 
     new  = g_slice_new(NMWireGuardPeer);
-    *new = (NMWireGuardPeer){
+    *new = (NMWireGuardPeer) {
         .refcount             = 1,
         .public_key           = g_strdup(self->public_key),
         .public_key_valid     = self->public_key_valid,
@@ -1310,7 +1310,7 @@ _peers_set(NMSettingWireGuardPrivate *priv,
     if (!pd_same_key)
         pd_same_key = g_slice_new(PeerData);
 
-    *pd_same_key = (PeerData){
+    *pd_same_key = (PeerData) {
         .peer       = peer,
         .public_key = public_key,
         .idx        = priv->peers_arr->len,
diff --git a/src/libnm-core-impl/nm-setting.c b/src/libnm-core-impl/nm-setting.c
index ed34db76..98424c76 100644
--- a/src/libnm-core-impl/nm-setting.c
+++ b/src/libnm-core-impl/nm-setting.c
@@ -446,7 +446,7 @@ _nm_setting_class_commit(NMSettingClass             *setting_class,
         const NMSettInfoProperty *property_info = &sett_info->property_infos[j];
 
         if (property_info->param_spec) {
-            *(lookup_by_iter++) = (NMSettInfoPropertLookupByParamSpec){
+            *(lookup_by_iter++) = (NMSettInfoPropertLookupByParamSpec) {
                 .param_spec_as_uint = (uintptr_t) ((gpointer) property_info->param_spec),
                 .property_info      = property_info,
             };
@@ -4346,7 +4346,7 @@ nm_range_new(guint64 start, guint64 end)
     g_return_val_if_fail(start <= end, NULL);
 
     range  = g_slice_new(NMRange);
-    *range = (NMRange){
+    *range = (NMRange) {
         .refcount = 1,
         .start    = start,
         .end      = end,
diff --git a/src/libnm-core-impl/nm-team-utils.c b/src/libnm-core-impl/nm-team-utils.c
index 6e7f85c7..8e1cdd64 100644
--- a/src/libnm-core-impl/nm-team-utils.c
+++ b/src/libnm-core-impl/nm-team-utils.c
@@ -692,9 +692,9 @@ _team_setting_has_fields_any_v(const NMTeamSetting   *self,
     return FALSE;
 }
 
-#define _team_setting_has_fields_any(self, ...)                              \
-    _team_setting_has_fields_any_v((self),                                   \
-                                   ((const NMTeamAttribute[]){__VA_ARGS__}), \
+#define _team_setting_has_fields_any(self, ...)                               \
+    _team_setting_has_fields_any_v((self),                                    \
+                                   ((const NMTeamAttribute[]) {__VA_ARGS__}), \
                                    NM_NARG(__VA_ARGS__))
 
 static void
diff --git a/src/libnm-core-impl/nm-utils-private.h b/src/libnm-core-impl/nm-utils-private.h
index 1521c0f9..da442511 100644
--- a/src/libnm-core-impl/nm-utils-private.h
+++ b/src/libnm-core-impl/nm-utils-private.h
@@ -14,7 +14,7 @@
 #include "nm-setting-ip-config.h"
 
 #define NM_VARIANT_ATTRIBUTE_SPEC_DEFINE(_name, _type, ...) \
-    (&((const NMVariantAttributeSpec){.name = _name, .type = _type, __VA_ARGS__}))
+    (&((const NMVariantAttributeSpec) {.name = _name, .type = _type, __VA_ARGS__}))
 
 gboolean _nm_utils_string_slist_validate(GSList *list, const char **valid_values);
 
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index fea13a95..6528b5fb 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -305,14 +305,14 @@ nm_sock_addr_endpoint_get_fixed_sockaddr(NMSockAddrEndpoint *self, gpointer sock
 good:
     switch (addr_family) {
     case AF_INET:
-        *((struct sockaddr_in *) sockaddr) = (struct sockaddr_in){
+        *((struct sockaddr_in *) sockaddr) = (struct sockaddr_in) {
             .sin_family = AF_INET,
             .sin_addr   = addrbin.addr4_struct,
             .sin_port   = htons(self->port),
         };
         return TRUE;
     case AF_INET6:
-        *((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6){
+        *((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6) {
             .sin6_family   = AF_INET6,
             .sin6_addr     = addrbin.addr6,
             .sin6_port     = htons(self->port),
@@ -1309,11 +1309,11 @@ nm_utils_dns_to_variant(int addr_family, const char *const *dns, gssize len)
 
         /* We can only represent the IP address on the legacy property "ipv[46].dns".
          * Expose what we can. */
-        if (!nm_utils_dnsname_parse(addr_family, dns[i], NULL, &ip, NULL))
+        if (!nm_dns_uri_parse_plain(addr_family, dns[i], NULL, &ip))
             continue;
 
         if (IS_IPv4)
-            g_variant_builder_add(&builder, "u", ip);
+            g_variant_builder_add(&builder, "u", ip.addr4);
         else
             g_variant_builder_add(&builder, "@ay", nm_g_variant_new_ay_in6addr(&ip.addr6));
     }
@@ -2358,7 +2358,7 @@ _nm_utils_ip_addresses_from_variant(GVariant *value, int family, bool strict, GE
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("IP address requires fields \"dest\" and \"prefix\" (idx=%u)"),
+                            _("IP address requires fields \"address\" and \"prefix\" (idx=%u)"),
                             i);
                 return NULL;
             }
@@ -2719,9 +2719,9 @@ typedef struct {
 } NMQdiscAttributeSpec;
 
 static const NMQdiscAttributeSpec *const tc_qdisc_attribute_spec[] = {
-    &(const NMQdiscAttributeSpec){"fq_codel", tc_qdisc_fq_codel_spec},
-    &(const NMQdiscAttributeSpec){"sfq", tc_qdisc_sfq_spec},
-    &(const NMQdiscAttributeSpec){"tbf", tc_qdisc_tbf_spec},
+    &(const NMQdiscAttributeSpec) {"fq_codel", tc_qdisc_fq_codel_spec},
+    &(const NMQdiscAttributeSpec) {"sfq", tc_qdisc_sfq_spec},
+    &(const NMQdiscAttributeSpec) {"tbf", tc_qdisc_tbf_spec},
     NULL,
 };
 
diff --git a/src/libnm-core-impl/nm-vpn-editor-plugin.c b/src/libnm-core-impl/nm-vpn-editor-plugin.c
index 6181368a..fc998b57 100644
--- a/src/libnm-core-impl/nm-vpn-editor-plugin.c
+++ b/src/libnm-core-impl/nm-vpn-editor-plugin.c
@@ -299,6 +299,9 @@ _nm_vpn_editor_plugin_load(const char               *plugin_name,
         return NULL;
     }
 
+    /* Note that factory() shouldn't be returning errors or failing.
+     * We can't change its prototype as it would consistute an ABI break,
+     * however it returning a failure would indicate a bug in the plugin. */
     editor_plugin = factory(&factory_error);
 
     if (loaded_before) {
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index 8d4ea069..d581fc79 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -560,13 +560,13 @@ test_nm_hash(void)
 
     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}),
+                    ((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(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}),
+                    ((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),
@@ -1702,7 +1702,7 @@ static const NMDedupMultiObjClass dedup_obj_class = {
 };
 
 #define DEDUP_OBJ_INIT(val_val, other_other)              \
-    (&((DedupObj){                                        \
+    (&((DedupObj) {                                       \
         .parent =                                         \
             {                                             \
                 .klass      = &dedup_obj_class,           \
@@ -1904,7 +1904,7 @@ _dedup_entry_assert_all(const NMDedupMultiEntry *entry,
     }
 }
 #define _dedup_entry_assert_all(entry, expected_idx, ...) \
-    _dedup_entry_assert_all(entry, expected_idx, (const DedupObj *const[]){__VA_ARGS__, NULL})
+    _dedup_entry_assert_all(entry, expected_idx, (const DedupObj *const[]) {__VA_ARGS__, NULL})
 
 static void
 test_dedup_multi(void)
@@ -3962,7 +3962,7 @@ typedef struct {
 
 typedef struct {
     const char *name;
-    DiffKey     keys[33];
+    DiffKey     keys[41];
 } DiffSetting;
 
 #define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
@@ -4037,6 +4037,9 @@ test_connection_diff_a_only(void)
           {NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_WAIT_ACTIVATION_DELAY, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_DOWN_ON_POWEROFF, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_IP_PING_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_IP_PING_ADDRESSES, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_IP_PING_ADDRESSES_REQUIRE_ALL, NM_SETTING_DIFF_RESULT_IN_A},
           {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN}}},
         {NM_SETTING_WIRED_SETTING_NAME,
          {
@@ -4075,6 +4078,7 @@ test_connection_diff_a_only(void)
              {NM_SETTING_IP4_CONFIG_DHCP_CLIENT_ID, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP_CONFIG_DHCP_SEND_HOSTNAME_V2, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_HOSTNAME, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP4_CONFIG_DHCP_FQDN, NM_SETTING_DIFF_RESULT_IN_A},
@@ -4088,9 +4092,13 @@ test_connection_diff_a_only(void)
              {NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_REJECT_SERVERS, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP4_CONFIG_LINK_LOCAL, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP4_CONFIG_DHCP_IPV6_ONLY_PREFERRED, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_AUTO_ROUTE_EXT_GW, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_REPLACE_LOCAL_RULE, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_SEND_RELEASE, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP_CONFIG_ROUTED_DNS, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP_CONFIG_SHARED_DHCP_RANGE, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP_CONFIG_SHARED_DHCP_LEASE_TIME, NM_SETTING_DIFF_RESULT_IN_A},
              {NULL, NM_SETTING_DIFF_RESULT_UNKNOWN},
          }},
     };
@@ -8671,9 +8679,11 @@ test_nm_utils_ascii_str_to_int64(void)
 static void
 test_nm_utils_strstrdictkey(void)
 {
-#define _VALUES_STATIC(_v1, _v2)                                                    \
-    {                                                                               \
-        .v1 = _v1, .v2 = _v2, .v_static = _nm_utils_strstrdictkey_static(_v1, _v2), \
+#define _VALUES_STATIC(_v1, _v2)                              \
+    {                                                         \
+        .v1       = _v1,                                      \
+        .v2       = _v2,                                      \
+        .v_static = _nm_utils_strstrdictkey_static(_v1, _v2), \
     }
     const struct {
         const char           *v1;
@@ -10613,7 +10623,7 @@ test_integrate_maincontext(gconstpointer test_data)
 
         loop1 = g_main_loop_new(c1, FALSE);
 
-        d = (IntegData){
+        d = (IntegData) {
             .loop1 = loop1,
             .c2    = c2,
         };
@@ -11389,176 +11399,179 @@ test_connection_path(void)
 /*****************************************************************************/
 
 static void
-_t_dnsname_1(const char *str, const char *exp_addr, const char *exp_server_name)
-{
-    int           addr_family;
-    NMIPAddr      exp_addr_bin;
-    gboolean      addr_family_request;
-    gboolean      r;
-    int           detect_addr_family;
-    NMIPAddr      detect_addr;
-    const char   *detect_server_name;
-    int          *p_detect_addr_family = &detect_addr_family;
-    NMIPAddr     *p_detect_addr        = &detect_addr;
-    const char  **p_detect_server_name = &detect_server_name;
-    char          str_construct_buf[100];
-    char          str_construct_buf2[100];
-    const char   *str_construct;
-    const char   *str_construct2;
-    gsize         l;
-    const char   *str_normalized;
-    gs_free char *str_normalized_alloc = NULL;
+t_dns_0(const char *str)
+{
+    NMDnsServer server = {};
+    gboolean    ret;
 
-    g_assert(str);
-    g_assert(exp_addr);
+    ret = nm_dns_uri_parse(AF_UNSPEC, str, &server);
 
-    r = nm_inet_parse_bin(AF_UNSPEC, exp_addr, &addr_family, &exp_addr_bin);
-    g_assert(r);
-    g_assert(NM_IN_SET(addr_family, AF_INET, AF_INET6));
+    g_assert(!ret);
+}
 
-    addr_family_request = nmtst_get_rand_bool();
-    if (nmtst_get_rand_bool())
-        p_detect_addr = NULL;
-    if ((addr_family_request || !p_detect_addr) && nmtst_get_rand_bool())
-        p_detect_addr_family = NULL;
-    if (nmtst_get_rand_bool())
-        p_detect_server_name = NULL;
-
-    r = nm_utils_dnsname_parse(addr_family_request ? addr_family : AF_UNSPEC,
-                               str,
-                               p_detect_addr_family,
-                               p_detect_addr,
-                               p_detect_server_name);
-    g_assert(r);
-
-    if (p_detect_addr_family)
-        g_assert_cmpint(addr_family, ==, detect_addr_family);
-    if (p_detect_addr)
-        g_assert_cmpstr(nmtst_inet_to_string(addr_family, &detect_addr), ==, exp_addr);
-    if (p_detect_server_name)
-        g_assert_cmpstr(detect_server_name, ==, exp_server_name);
-
-    r = nm_utils_dnsname_parse(addr_family == AF_INET ? AF_INET6 : AF_INET,
-                               str,
-                               p_detect_addr_family,
-                               p_detect_addr,
-                               p_detect_server_name);
-    g_assert(!r);
-
-    /* Construct the expected value. */
-    str_construct = nm_utils_dnsname_construct(addr_family,
-                                               &exp_addr_bin,
-                                               exp_server_name,
-                                               str_construct_buf,
-                                               sizeof(str_construct_buf));
-    g_assert(str_construct);
-    g_assert(str_construct == str_construct_buf);
-    g_assert(strlen(str_construct) < sizeof(str_construct_buf));
-
-    /* Check that a too short buffer causes truncation. */
-    l              = nmtst_get_rand_uint32() % (strlen(str_construct) + 10);
-    str_construct2 = nm_utils_dnsname_construct(addr_family,
-                                                &exp_addr_bin,
-                                                exp_server_name,
-                                                str_construct_buf2,
-                                                l);
-    if (str_construct2) {
-        g_assert(str_construct2 == str_construct_buf2);
-        g_assert_cmpstr(str_construct2, ==, str_construct);
-        g_assert(l > strlen(str_construct));
-    } else
-        g_assert(l <= strlen(str_construct));
+static void
+dns_uri_parse_ok(const char    *str,
+                 int            addr_family,
+                 NMDnsUriScheme scheme,
+                 const char    *addr,
+                 int            port,
+                 const char    *sname,
+                 const char    *ifname)
+{
+    NMDnsServer dns = {};
+    char        addrstr[NM_INET_ADDRSTRLEN];
+    gboolean    ret;
 
-    if (!nm_streq(str_construct, str)) {
-        _t_dnsname_1(str_construct, exp_addr, exp_server_name);
-    }
+    for (int i = 0; i < 2; i++) {
+        gboolean af_unspec = i;
 
-    str_normalized = nm_utils_dnsname_normalize(nmtst_get_rand_bool() ? addr_family : AF_UNSPEC,
-                                                str,
-                                                &str_normalized_alloc);
-    g_assert(str_normalized);
-    if (str_normalized_alloc) {
-        g_assert(str_normalized == str_normalized_alloc);
-        g_assert_cmpstr(str_normalized, !=, str);
-    } else {
-        g_assert(str == str_normalized);
+        ret = nm_dns_uri_parse(af_unspec ? AF_UNSPEC : addr_family, str, &dns);
+        g_assert(ret);
+
+        g_assert_cmpint(addr_family, ==, dns.addr_family);
+        g_assert_cmpint(port, ==, dns.port);
+        g_assert_cmpstr(sname, ==, dns.servername);
+        g_assert_cmpstr(ifname ?: "", ==, dns.interface);
+
+        nm_inet_ntop(dns.addr_family, &dns.addr, addrstr);
+        g_assert_cmpstr(addrstr, ==, addr);
+
+        /* Parse with the wrong address family must fail */
+        ret = nm_dns_uri_parse(addr_family == AF_INET ? AF_INET6 : AF_INET, str, &dns);
+        g_assert(!ret);
     }
-    g_assert_cmpstr(str_normalized, ==, str_construct);
+}
+
+#define t_dns_1(str, af, scheme, addr, port, sname, ifname) \
+    dns_uri_parse_ok((str),                                 \
+                     (AF_##af),                             \
+                     (NM_DNS_URI_SCHEME_##scheme),          \
+                     (addr),                                \
+                     (port),                                \
+                     (sname),                               \
+                     (ifname))
+
+static void
+test_dns_uri_parse(void)
+{
+    /* clang-format off */
+    t_dns_1("dns+tls://8.8.8.8",                   INET,  TLS,  "8.8.8.8",            -1, NULL,      NULL);
+    t_dns_1("dns+tls://8.8.8.8",                   INET,  TLS,  "8.8.8.8",            -1, NULL,      NULL);
+    t_dns_1("dns+tls://1.2.3.4#name",              INET,  TLS,  "1.2.3.4",            -1, "name",    NULL);
+    t_dns_1("dns+tls://1.2.3.4#a.b.c",             INET,  TLS,  "1.2.3.4",            -1, "a.b.c",   NULL);
+    t_dns_1("dns+tls://1.2.3.4:53",                INET,  TLS,  "1.2.3.4",            53, NULL,      NULL);
+    t_dns_1("dns+tls://1.2.3.4:53#foobar",         INET,  TLS,  "1.2.3.4",            53, "foobar",  NULL);
+    t_dns_1("dns+tls://192.168.120.250:99",        INET,  TLS,  "192.168.120.250",    99, NULL,      NULL);
+    t_dns_1("dns+udp://8.8.8.8:65535",             INET,  UDP,  "8.8.8.8",         65535, NULL,      NULL);
+
+    t_dns_1("dns+udp://[fd01::1]",                 INET6, UDP,  "fd01::1",            -1, NULL,      NULL);
+    t_dns_1("dns+tls://[fd01::2]:5353",            INET6, UDP,  "fd01::2",          5353, NULL,      NULL);
+    t_dns_1("dns+tls://[::1]#name",                INET6, UDP,  "::1",                -1, "name",    NULL);
+    t_dns_1("dns+tls://[::2]:65535#name",          INET6, UDP,  "::2",             65535, "name",    NULL);
+    t_dns_1("dns+udp://[::ffff:1.2.3.4]",          INET6, UDP,  "::ffff:1.2.3.4",     -1, NULL,      NULL);
+    t_dns_1("dns+tls://[fe80::1%eth0]",            INET6, UDP,  "fe80::1",            -1, NULL,      "eth0");
+    t_dns_1("dns+tls://[fe80::2%en1]:53#a",        INET6, UDP,  "fe80::2",            53, "a",       "en1");
+    t_dns_1("dns+tls://[fe80::1%en3456789012345]", INET6, UDP,  "fe80::1",            -1, NULL, "en3456789012345");
+
+    t_dns_1("1.2.3.4",                             INET,  NONE, "1.2.3.4",            -1, NULL,      NULL);
+    t_dns_1("1.2.3.4#foo",                         INET,  NONE, "1.2.3.4",            -1, "foo",     NULL);
+    t_dns_1("1::#x",                               INET6, NONE, "1::",                -1, "x",       NULL);
+    t_dns_1("1::0#x",                              INET6, NONE, "1::",                -1, "x",       NULL);
+    t_dns_1("192.168.0.1",                         INET,  NONE, "192.168.0.1",        -1, NULL,      NULL);
+    t_dns_1("192.168.0.1#tst.com",                 INET,  NONE, "192.168.0.1",        -1, "tst.com", NULL);
+    t_dns_1("fe80::18",                            INET6, NONE, "fe80::18",           -1, NULL,      NULL);
+    t_dns_1("fe80::18#foo.com",                    INET6, NONE, "fe80::18",           -1, "foo.com", NULL);
+    /* clang-format on */
 
-    nm_clear_g_free(&str_normalized_alloc);
-    str_normalized = nm_utils_dnsname_normalize(addr_family == AF_INET ? AF_INET6 : AF_INET,
-                                                str,
-                                                &str_normalized_alloc);
-    g_assert(!str_normalized);
-    g_assert(!str_normalized_alloc);
+    t_dns_0("http://8.8.8.8");              /* unsupported schema */
+    t_dns_0("dns+udp://1.2.3.4#name");      /* servername not supported for plain UDP */
+    t_dns_0("dns+tls://1.2.3");             /* invalid address */
+    t_dns_0("dns+tls://fd01::1");           /* IPv6 requires brackets */
+    t_dns_0("dns+tls://[fd13:a:aaaa]");     /* invalid address */
+    t_dns_0("dns+tls://1.2.3.4:1:1");       /* invalid syntax */
+    t_dns_0("dns+tls://1.2.3.4#name#name"); /* invalid syntax */
+    t_dns_0("dns+tls://1.2.3.4%eth0");      /* interface only allowed for IPv6 */
+    t_dns_0("dns+tls://[2001::1%eth0]");    /* interface only allowed for IPv6 link-local */
+    t_dns_0("dns+tls://[fe80::1%en34567890123456]"); /* interface name too long */
+    t_dns_0("1.2.3.4#");
+    t_dns_0("1::0#");
+    t_dns_0("192.168.0.1:53");
+    t_dns_0("192.168.0.1:53#example.com");
+    t_dns_0("fe80::18%19");
+    t_dns_0("fe80::18%lo");
+    t_dns_0("[fe80::18]:53");
+    t_dns_0("[fe80::18]:53%19");
+    t_dns_0("[fe80::18]:53%lo");
+    t_dns_0("fe80::18%19#hoge.com");
+    t_dns_0("[fe80::18]:53#hoge.com");
+    t_dns_0("[fe80::18]:53%19");
+    t_dns_0("[fe80::18]:53%19#hoge.com");
+    t_dns_0("[fe80::18]:53%lo");
+    t_dns_0("[fe80::18]:53%lo#hoge.com");
+}
+
+static void
+test_dns_uri_parse_plain(void)
+{
+    struct {
+        const char *input;
+        int         input_af;
+        gboolean    result;
+        const char *addrstr;
+    } values[] = {
+        {"1.2.3.4", AF_INET, TRUE, "1.2.3.4"},
+        {"1.2.3.4", AF_INET6, FALSE, NULL},
+        {"1.2.3.4", AF_UNSPEC, TRUE, "1.2.3.4"},
+        {"1234:5555:ffff:dddd::4321", AF_INET, FALSE, NULL},
+        {"1234:5555:ffff:dddd::4321", AF_INET6, TRUE, "1234:5555:ffff:dddd::4321"},
+        {"1234:5555:ffff:dddd::4321", AF_UNSPEC, TRUE, "1234:5555:ffff:dddd::4321"},
+        {"192.0.2.1#example.com", AF_INET, TRUE, "192.0.2.1"},
+        {"192.0.2.1#example.com", AF_UNSPEC, TRUE, "192.0.2.1"},
+        {"192.0.2.1#example.com", AF_INET6, FALSE, NULL},
+        {"dns+tls://1.2.3.4", AF_INET, FALSE, NULL},
+        {"dns+tls://[fd01::1]", AF_INET, FALSE, NULL},
+        {"dns+udp://1.2.3.4:53", AF_INET, TRUE, "1.2.3.4"},
+        {"dns+udp://1.2.3.4:54", AF_INET, FALSE, NULL},
+        {"dns+udp://[fd01::1]", AF_INET6, TRUE, "fd01::1"},
+        {"dns+udp://[fd01::1]:53", AF_INET6, TRUE, "fd01::1"},
+        {"dns+udp://[fd01::1]:60000", AF_INET, FALSE, NULL},
+    };
+    guint i;
+
+    for (i = 0; i < G_N_ELEMENTS(values); i++) {
+        char     addrstr[NM_INET_ADDRSTRLEN];
+        gboolean result;
+        NMIPAddr addr;
+
+        result = nm_dns_uri_parse_plain(values[i].input_af, values[i].input, addrstr, &addr);
+        g_assert_cmpint(result, ==, values[i].result);
+        if (result) {
+            char buf[NM_INET_ADDRSTRLEN];
+
+            nm_inet_ntop(strchr(addrstr, ':') ? AF_INET6 : AF_INET, addr.addr_ptr, buf);
+            g_assert_cmpstr(buf, ==, addrstr);
+            g_assert_cmpstr(addrstr, ==, values[i].addrstr);
+        }
+    }
 }
 
 static void
-_t_dnsname_0(const char *str)
+t_dns_uri_normalize(const char *input, const char *expected)
 {
-    gboolean      addr_family_request;
-    int           detect_addr_family;
-    NMIPAddr      detect_addr;
-    const char   *detect_server_name;
-    int          *p_detect_addr_family = &detect_addr_family;
-    NMIPAddr     *p_detect_addr        = &detect_addr;
-    const char  **p_detect_server_name = &detect_server_name;
-    const char   *str_normalized;
-    gs_free char *str_normalized_alloc = NULL;
-    gboolean      r;
+    const char   *str;
+    gs_free char *str_free = NULL;
 
-    g_assert(str);
+    str = nm_dns_uri_normalize(AF_UNSPEC, input, &str_free);
+    g_assert_cmpstr(str, ==, expected);
+}
 
-    addr_family_request = nmtst_get_rand_bool();
-    if (nmtst_get_rand_bool())
-        p_detect_addr = NULL;
-    if ((addr_family_request || !p_detect_addr) && nmtst_get_rand_bool())
-        p_detect_addr_family = NULL;
-    if (nmtst_get_rand_bool())
-        p_detect_server_name = NULL;
-
-    r = nm_utils_dnsname_parse(addr_family_request ? nmtst_rand_select(AF_INET, AF_INET6)
-                                                   : AF_UNSPEC,
-                               str,
-                               p_detect_addr_family,
-                               p_detect_addr,
-                               p_detect_server_name);
-    g_assert(!r);
-
-    str_normalized = nm_utils_dnsname_normalize(nmtst_rand_select(AF_UNSPEC, AF_INET, AF_INET6),
-                                                str,
-                                                &str_normalized_alloc);
-    g_assert(!str_normalized);
-    g_assert(!str_normalized_alloc);
-}
-
-static void
-test_dnsname(void)
-{
-    _t_dnsname_1("1.2.3.4", "1.2.3.4", NULL);
-    _t_dnsname_1("1.2.3.4#foo", "1.2.3.4", "foo");
-    _t_dnsname_1("1::#x", "1::", "x");
-    _t_dnsname_1("1::0#x", "1::", "x");
-    _t_dnsname_1("192.168.0.1", "192.168.0.1", NULL);
-    _t_dnsname_1("192.168.0.1#test.com", "192.168.0.1", "test.com");
-    _t_dnsname_1("fe80::18", "fe80::18", NULL);
-    _t_dnsname_1("fe80::18#hoge.com", "fe80::18", "hoge.com");
-
-    _t_dnsname_0("1.2.3.4#");
-    _t_dnsname_0("1::0#");
-    _t_dnsname_0("192.168.0.1:53");
-    _t_dnsname_0("192.168.0.1:53#example.com");
-    _t_dnsname_0("fe80::18%19");
-    _t_dnsname_0("fe80::18%lo");
-    _t_dnsname_0("[fe80::18]:53");
-    _t_dnsname_0("[fe80::18]:53%19");
-    _t_dnsname_0("[fe80::18]:53%lo");
-    _t_dnsname_0("fe80::18%19#hoge.com");
-    _t_dnsname_0("[fe80::18]:53#hoge.com");
-    _t_dnsname_0("[fe80::18]:53%19");
-    _t_dnsname_0("[fe80::18]:53%19#hoge.com");
-    _t_dnsname_0("[fe80::18]:53%lo");
-    _t_dnsname_0("[fe80::18]:53%lo#hoge.com");
+static void
+test_dns_uri_normalize(void)
+{
+    t_dns_uri_normalize("8.8.8.8", "8.8.8.8");
+    t_dns_uri_normalize("dns+tls://[2001:0:0::1234]:999#name", "dns+tls://[2001::1234]:999#name");
+    t_dns_uri_normalize("dns+udp://[0::1]:0123", "dns+udp://[::1]:123");
+    t_dns_uri_normalize("8.8.8.888", NULL);
 }
 
 /*****************************************************************************/
@@ -11937,7 +11950,9 @@ main(int argc, char **argv)
     g_test_add_func("/core/general/test_system_encodings", test_system_encodings);
     g_test_add_func("/core/general/test_direct_string_is_refstr", test_direct_string_is_refstr);
     g_test_add_func("/core/general/test_connection_path", test_connection_path);
-    g_test_add_func("/core/general/test_dnsname", test_dnsname);
+    g_test_add_func("/core/general/test_dns_uri_parse", test_dns_uri_parse);
+    g_test_add_func("/core/general/test_dns_uri_get_legacy", test_dns_uri_parse_plain);
+    g_test_add_func("/core/general/test_dns_uri_normalize", test_dns_uri_normalize);
     g_test_add_func("/core/general/test_dhcp_iaid_hexstr", test_dhcp_iaid_hexstr);
 
     return g_test_run();
diff --git a/src/libnm-core-impl/tests/test-keyfile.c b/src/libnm-core-impl/tests/test-keyfile.c
index 1912782e..5c66196c 100644
--- a/src/libnm-core-impl/tests/test-keyfile.c
+++ b/src/libnm-core-impl/tests/test-keyfile.c
@@ -943,7 +943,7 @@ test_invalid_option(void)
 
     nm_setting_option_set_boolean(s_ethtool, NM_ETHTOOL_OPTNAME_PAUSE_RX, TRUE);
 
-    data = (InvalidOptionWriteData){};
+    data = (InvalidOptionWriteData) {};
     kf   = nm_keyfile_write(con,
                           NM_KEYFILE_HANDLER_FLAGS_NONE,
                           _invalid_option_write_handler,
@@ -956,7 +956,7 @@ test_invalid_option(void)
 
     nmtst_assert_connection_verifies_without_normalization(con);
 
-    data = (InvalidOptionWriteData){};
+    data = (InvalidOptionWriteData) {};
     kf   = nm_keyfile_write(con,
                           NM_KEYFILE_HANDLER_FLAGS_NONE,
                           _invalid_option_write_handler,
@@ -967,7 +967,7 @@ test_invalid_option(void)
 
     nm_setting_option_set(s_ethtool, "bogus", g_variant_new_int64(0));
 
-    data = (InvalidOptionWriteData){
+    data = (InvalidOptionWriteData) {
         .expect = TRUE,
     };
     kf = nm_keyfile_write(con,
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index f3309166..2064162a 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -799,29 +799,29 @@ static void
 test_bond_compare(void)
 {
     test_bond_compare_options(TRUE,
-                              ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL}),
-                              ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL}));
+                              ((const char *[]) {"mode", "balance-rr", "miimon", "1", NULL}),
+                              ((const char *[]) {"mode", "balance-rr", "miimon", "1", NULL}));
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"mode", "balance-rr", "miimon", "1", NULL}),
-                              ((const char *[]){"mode", "balance-rr", "miimon", "2", NULL}));
+                              ((const char *[]) {"mode", "balance-rr", "miimon", "1", NULL}),
+                              ((const char *[]) {"mode", "balance-rr", "miimon", "2", NULL}));
 
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"miimon", "1", NULL}),
-                              ((const char *[]){"miimon", "1", "updelay", "0", NULL}));
+                              ((const char *[]) {"miimon", "1", NULL}),
+                              ((const char *[]) {"miimon", "1", "updelay", "0", NULL}));
 
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"num_grat_arp", "2", NULL}),
-                              ((const char *[]){"num_grat_arp", "1", NULL}));
+                              ((const char *[]) {"num_grat_arp", "2", NULL}),
+                              ((const char *[]) {"num_grat_arp", "1", NULL}));
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"num_grat_arp", "3", NULL}),
-                              ((const char *[]){"num_unsol_na", "3", NULL}));
+                              ((const char *[]) {"num_grat_arp", "3", NULL}),
+                              ((const char *[]) {"num_unsol_na", "3", NULL}));
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"num_grat_arp", "4", NULL}),
-                              ((const char *[]){"num_unsol_na", "4", "num_grat_arp", "4", NULL}));
+                              ((const char *[]) {"num_grat_arp", "4", NULL}),
+                              ((const char *[]) {"num_unsol_na", "4", "num_grat_arp", "4", NULL}));
 
     test_bond_compare_options(FALSE,
-                              ((const char *[]){"mode", "balance-rr", "miimon", "100", NULL}),
-                              ((const char *[]){"mode", "balance-rr", NULL}));
+                              ((const char *[]) {"mode", "balance-rr", "miimon", "100", NULL}),
+                              ((const char *[]) {"mode", "balance-rr", NULL}));
 }
 
 static void
@@ -856,20 +856,25 @@ static void
 test_bond_normalize(void)
 {
     test_bond_normalize_options(
-        ((const char *[]){"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL}),
-        ((const char *[]){"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL}));
-    test_bond_normalize_options(((const char *[]){"mode", "1", "miimon", "1", NULL}),
-                                ((const char *[]){"mode", "active-backup", "miimon", "1", NULL}));
+        ((const char *[]) {"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL}),
+        ((const char *[]) {"mode", "802.3ad", "ad_actor_system", "00:02:03:04:05:06", NULL}));
+    test_bond_normalize_options(((const char *[]) {"mode", "1", "miimon", "1", NULL}),
+                                ((const char *[]) {"mode", "active-backup", "miimon", "1", NULL}));
     test_bond_normalize_options(
-        ((const char *[]){"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL}),
-        ((const char *[]){"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL}));
+        ((const char *[]) {"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL}),
+        ((const char *[]) {"mode", "balance-alb", "tlb_dynamic_lb", "1", NULL}));
     test_bond_normalize_options(
-        ((const char *[]){"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL}),
-        ((const char *[]){"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL}));
+        ((const char *[]) {"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL}),
+        ((const char *[]) {"mode", "balance-tlb", "tlb_dynamic_lb", "1", NULL}));
     test_bond_normalize_options(
-        ((const char
-              *[]){"mode", "balance-rr", "ad_actor_sys_prio", "4", "packets_per_slave", "3", NULL}),
-        ((const char *[]){"mode", "balance-rr", "packets_per_slave", "3", NULL}));
+        ((const char *[]) {"mode",
+                           "balance-rr",
+                           "ad_actor_sys_prio",
+                           "4",
+                           "packets_per_slave",
+                           "3",
+                           NULL}),
+        ((const char *[]) {"mode", "balance-rr", "packets_per_slave", "3", NULL}));
 }
 
 /*****************************************************************************/
@@ -2379,6 +2384,85 @@ test_ethtool_eee(void)
 /*****************************************************************************/
 
 static void
+test_ethtool_fec(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;
+    guint32                         out_value;
+    guint32                         expected_fec_mode =
+        NM_SETTING_ETHTOOL_FEC_MODE_AUTO | NM_SETTING_ETHTOOL_FEC_MODE_BASER;
+
+    con =
+        nmtst_create_minimal_connection("ethtool-fec", 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_uint32(NM_SETTING(s_ethtool),
+                                 NM_ETHTOOL_OPTNAME_FEC_MODE,
+                                 expected_fec_mode);
+
+    g_assert_true(nm_setting_option_get_uint32(NM_SETTING(s_ethtool),
+                                               NM_ETHTOOL_OPTNAME_FEC_MODE,
+                                               &out_value));
+    g_assert_true(out_value == expected_fec_mode);
+
+    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_uint32(NM_SETTING(s_ethtool2),
+                                               NM_ETHTOOL_OPTNAME_FEC_MODE,
+                                               &out_value));
+    g_assert_true(out_value == expected_fec_mode);
+
+    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_uint32(NM_SETTING(s_ethtool3),
+                                               NM_ETHTOOL_OPTNAME_FEC_MODE,
+                                               &out_value));
+    g_assert_true(out_value == expected_fec_mode);
+}
+/*****************************************************************************/
+
+static void
 test_sriov_vf(void)
 {
     NMSriovVF *vf1, *vf2;
@@ -5481,6 +5565,7 @@ main(int argc, char **argv)
     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/ethtool/fec", test_ethtool_fec);
 
     g_test_add_func("/libnm/settings/6lowpan/1", test_6lowpan_1);