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.in8
-rw-r--r--src/libnm-core-impl/nm-keyfile.c51
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c50
-rw-r--r--src/libnm-core-impl/nm-setting-gsm.c40
-rw-r--r--src/libnm-core-impl/nm-setting-hsr.c8
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c56
-rw-r--r--src/libnm-core-impl/nm-setting-wireguard.c67
-rw-r--r--src/libnm-core-impl/nm-utils.c3
-rw-r--r--src/libnm-core-impl/nm-vpn-plugin-info.c2
-rw-r--r--src/libnm-core-impl/tests/test-general.c20
-rw-r--r--src/libnm-core-impl/tests/test-setting.c39
11 files changed, 321 insertions, 23 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 ce4832dc..c83deea9 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
@@ -810,6 +810,10 @@
                   dbus-type="i"
                   gprop-type="gint"
                   />
+        <property name="dnssec"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="down-on-poweroff"
                   dbus-type="i"
                   gprop-type="gint"
@@ -1393,6 +1397,10 @@
                   dbus-type="s"
                   gprop-type="gchararray"
                   />
+        <property name="device-uid"
+                  dbus-type="s"
+                  gprop-type="gchararray"
+                  />
         <property name="home-only"
                   dbus-type="b"
                   gprop-type="gboolean"
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index f8f735e9..70758f0f 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -1158,14 +1158,17 @@ 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_dns_uri_parse(addr_family, list[i], NULL)) {
+        gs_free_error GError *error = NULL;
+
+        if (!nm_dns_uri_parse(addr_family, list[i], NULL, &error)) {
             if (!read_handle_warn(info,
                                   key,
                                   key,
                                   NM_KEYFILE_WARN_SEVERITY_WARN,
-                                  _("ignoring invalid DNS server IPv%c address '%s'"),
+                                  _("ignoring invalid DNS server IPv%c address '%s': %s"),
                                   nm_utils_addr_family_to_char(addr_family),
-                                  list[i])) {
+                                  list[i],
+                                  error->message)) {
                 do {
                     nm_clear_g_free(&list[i]);
                 } while (++i < length);
@@ -1184,6 +1187,46 @@ ip_dns_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 }
 
 static void
+ip_dns_search_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
+{
+    gs_strfreev char **list = NULL;
+    gsize              length;
+
+    nm_assert(NM_IS_SETTING_IP4_CONFIG(setting) || NM_IS_SETTING_IP6_CONFIG(setting));
+
+    list = nm_keyfile_plugin_kf_get_string_list(info->keyfile,
+                                                nm_setting_get_name(setting),
+                                                key,
+                                                &length,
+                                                NULL);
+    nm_assert(length == NM_PTRARRAY_LEN(list));
+    if (length == 0)
+        return;
+
+    if (length == 1 && strpbrk(list[0], ", ")) {
+        /* By mistake, we accepted invalid characters like ',' in DNS search domains.
+         * Now we do some validation that would cause the connection to be rejected by
+         * the daemon. Let's continue accepting ',' and ' ' as separators but emit a
+         * warning */
+        char **list2;
+
+        read_handle_warn(info,
+                         key,
+                         key,
+                         NM_KEYFILE_WARN_SEVERITY_WARN,
+                         _("normalizing invalid separator ',' or ' ' in DNS search value '%s', "
+                           "only ';' will be valid separators in keyfiles in the future"),
+                         list[0]);
+
+        list2 = g_strsplit_set(list[0], ", ", -1);
+        g_strfreev(list);
+        list = list2;
+    }
+
+    g_object_set(setting, key, list, NULL);
+}
+
+static void
 ip6_addr_gen_mode_parser(KeyfileReaderInfo *info, NMSetting *setting, const char *key)
 {
     NMSettingIP6ConfigAddrGenMode addr_gen_mode;
@@ -3081,6 +3124,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_DNS_SEARCH, .parser = ip_dns_search_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .parser = gateway_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES,
                                 .parser_no_check_key = TRUE,
@@ -3109,6 +3153,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_DNS_SEARCH, .parser = ip_dns_search_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_GATEWAY, .parser = gateway_parser, ),
             PARSE_INFO_PROPERTY(NM_SETTING_IP_CONFIG_ROUTES,
                                 .parser_no_check_key = TRUE,
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index ec3a1fed..0ad97846 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -71,6 +71,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingConnection,
                              PROP_MDNS,
                              PROP_LLMNR,
                              PROP_DNS_OVER_TLS,
+                             PROP_DNSSEC,
                              PROP_MPTCP_FLAGS,
                              PROP_STABLE_ID,
                              PROP_AUTH_RETRIES,
@@ -103,6 +104,7 @@ typedef struct {
     gint32      mdns;
     gint32      llmnr;
     gint32      dns_over_tls;
+    gint32      dnssec;
     gint32      wait_device_timeout;
     gint32      lldp;
     gint32      wait_activation_delay;
@@ -1335,6 +1337,22 @@ nm_setting_connection_get_dns_over_tls(NMSettingConnection *setting)
 }
 
 /**
+ * nm_setting_connection_get_dnssec:
+ * @setting: the #NMSettingConnection
+ *
+ * Returns: the #NMSettingConnection:dnssec property of the setting.
+ *
+ * Since: 1.56
+ **/
+NMSettingConnectionDnssec
+nm_setting_connection_get_dnssec(NMSettingConnection *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_CONNECTION(setting), NM_SETTING_CONNECTION_DNSSEC_DEFAULT);
+
+    return NM_SETTING_CONNECTION_GET_PRIVATE(setting)->dnssec;
+}
+
+/**
  * nm_setting_connection_get_mptcp_flags:
  * @setting: the #NMSettingConnection
  *
@@ -3447,6 +3465,33 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
                                              NMSettingConnectionPrivate,
                                              dns_over_tls);
 
+    /**
+     * NMSettingConnection:dnssec:
+     *
+     * Whether DNSSEC (dnssec) is enabled for the connection.
+     *
+     * The permitted values are: "yes" (2) use DNSSEC and disable fallback,
+     * "allow-downgrade" (1) use DNSSEC but allow fallback if the server does not support it,
+     * "no" (0) don't ever use DNSSEC.
+     * The effect of "default" (-1) depends on the dns plugin used.
+     * Systemd-resolved uses its global setting in this case.
+     *
+     * This feature requires a plugin which supports DNSSEC. Otherwise, the
+     * setting has no effect. One such plugin is systemd-resolved.
+     *
+     * Since: 1.56
+     **/
+    _nm_setting_property_define_direct_enum(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_CONNECTION_DNSSEC,
+                                            PROP_DNSSEC,
+                                            NM_TYPE_SETTING_CONNECTION_DNSSEC,
+                                            NM_SETTING_CONNECTION_DNSSEC_DEFAULT,
+                                            NM_SETTING_PARAM_NONE,
+                                            NULL,
+                                            NMSettingConnectionPrivate,
+                                            dnssec);
+
     /* Notes about "mptcp-flags":
      *
      * It is a bit odd that NMMptcpFlags mixes flags with different purposes:
@@ -3454,7 +3499,7 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      * - "disabled", "disabled-on-local-iface", "enable": whether MPTCP handling
      *   is enabled. The flag "disabled-on-local-iface" enables it based on whether
      *   the interface has a default route.
-     * - "signal", "subflow", "backup", "fullmesh": the endpoint flags
+     * - "signal", "subflow", "backup", "fullmesh", "laminar": the endpoint flags
      *   that are used.
      *
      * The reason is, that it is useful to have one "connection.mptcp-flags"
@@ -3514,7 +3559,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *
      * When MPTCP handling is enabled then endpoints are configured with
      * the specified address flags "signal" (0x10), "subflow" (0x20), "backup" (0x40),
-     * "fullmesh" (0x80). See ip-mptcp(8) manual for additional information about the flags.
+     * "fullmesh" (0x80), "laminar" (0x100). See ip-mptcp(8) manual for
+     * additional information about the flags.
      *
      * If the flags are zero (0x0), the global connection default from NetworkManager.conf is
      * honored. If still unspecified, the fallback is "enabled,subflow".
diff --git a/src/libnm-core-impl/nm-setting-gsm.c b/src/libnm-core-impl/nm-setting-gsm.c
index 02e0e236..e6c43c96 100644
--- a/src/libnm-core-impl/nm-setting-gsm.c
+++ b/src/libnm-core-impl/nm-setting-gsm.c
@@ -47,7 +47,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_AUTO_CONFIG,
                                   PROP_INITIAL_EPS_REFUSE_PAP,
                                   PROP_INITIAL_EPS_REFUSE_CHAP,
                                   PROP_INITIAL_EPS_REFUSE_MSCHAP,
-                                  PROP_INITIAL_EPS_REFUSE_MSCHAPV2, );
+                                  PROP_INITIAL_EPS_REFUSE_MSCHAPV2,
+                                  PROP_DEVICE_UID, );
 
 typedef struct {
     char   *number;
@@ -75,6 +76,7 @@ typedef struct {
     bool    auto_config;
     bool    home_only;
     bool    initial_eps_config;
+    char   *device_uid;
 } NMSettingGsmPrivate;
 
 /**
@@ -465,6 +467,22 @@ nm_setting_gsm_get_initial_eps_refuse_mschapv2(NMSettingGsm *setting)
     return NM_SETTING_GSM_GET_PRIVATE(setting)->initial_eps_refuse_mschapv2;
 }
 
+/**
+ * nm_setting_gsm_get_device_uid:
+ * @setting: the #NMSettingGsm
+ *
+ * Returns: the #NMSettingGsm:device-uid property of the setting
+ *
+ * Since: 1.56
+ **/
+const char *
+nm_setting_gsm_get_device_uid(NMSettingGsm *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_GSM(setting), NULL);
+
+    return NM_SETTING_GSM_GET_PRIVATE(setting)->device_uid;
+}
+
 static gboolean
 _verify_apn(const char *apn, gboolean allow_empty, const char *property_name, GError **error)
 {
@@ -1149,6 +1167,26 @@ nm_setting_gsm_class_init(NMSettingGsmClass *klass)
                                                NMSettingGsmPrivate,
                                                initial_eps_refuse_mschapv2);
 
+    /**
+     * NMSettingGsm:device-uid:
+     *
+     * The device UID (as given by the WWAN management service) which this
+     * connection applies to. In contrast to #NMSettingGsm:device-id, which is
+     * an inherent property of the connected device, this setting refers to
+     * a property set by a UDEV-rule. Refer to the "Common udev tags" ->
+     * "ID_MM_PHYSDEV_UID" documentation of ModemManager. If given, the
+     * connection will only apply to the specified device.
+     *
+     * Since: 1.56
+     **/
+    _nm_setting_property_define_direct_string(properties_override,
+                                              obj_properties,
+                                              NM_SETTING_GSM_DEVICE_UID,
+                                              PROP_DEVICE_UID,
+                                              NM_SETTING_PARAM_NONE,
+                                              NMSettingGsmPrivate,
+                                              device_uid);
+
     /* Ignore incoming deprecated properties */
     _nm_properties_override_dbus(properties_override,
                                  "allowed-bands",
diff --git a/src/libnm-core-impl/nm-setting-hsr.c b/src/libnm-core-impl/nm-setting-hsr.c
index 1edf4a14..2f6bf9de 100644
--- a/src/libnm-core-impl/nm-setting-hsr.c
+++ b/src/libnm-core-impl/nm-setting-hsr.c
@@ -131,7 +131,7 @@ nm_setting_hsr_get_prp(NMSettingHsr *setting)
  *
  * Returns: the #NMSettingHsr:protocol-version property of the setting
  *
- * Since: 1.56, 1.54.2
+ * Since: 1.56
  **/
 NMSettingHsrProtocolVersion
 nm_setting_hsr_get_protocol_version(NMSettingHsr *setting)
@@ -147,7 +147,7 @@ nm_setting_hsr_get_protocol_version(NMSettingHsr *setting)
  *
  * Returns: the #NMSettingHsr:interlink property of the setting
  *
- * Since: 1.56, 1.54.2
+ * Since: 1.56
  **/
 const char *
 nm_setting_hsr_get_interlink(NMSettingHsr *setting)
@@ -320,7 +320,7 @@ nm_setting_hsr_class_init(NMSettingHsrClass *klass)
       * %NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2010 sets the protocol version to HSRv0 (IEC 62439-3:2010).
       * %NM_SETTING_HSR_PROTOCOL_VERSION_HSR_2012 sets the protocol version to HSRv1 (IEC 62439-3:2012).
       *
-      * Since: 1.56, 1.54.2
+      * Since: 1.56
       **/
     _nm_setting_property_define_direct_enum(properties_override,
                                             obj_properties,
@@ -338,7 +338,7 @@ nm_setting_hsr_class_init(NMSettingHsrClass *klass)
       *
       * The optional interlink port name of the HSR interface.
       *
-      * Since: 1.56, 1.54.2
+      * Since: 1.56
       **/
     _nm_setting_property_define_direct_string(properties_override,
                                               obj_properties,
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index ad95eb1a..1aecc20c 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -4221,6 +4221,25 @@ nm_setting_ip_config_clear_dns(NMSettingIPConfig *setting)
     }
 }
 
+/**
+ * nm_dns_server_validate:
+ * @str: the string containing the DNS server
+ * @family: the IP address family (%AF_INET for IPv4, %AF_INET6 for IPv6,
+ *   %AF_UNSPEC to accept both IPv4 and IPv6)
+ * @error: (nullable): a pointer to %NULL #GError, or %NULL
+ *
+ * Validates a DNS name server string.
+ *
+ * Return: %TRUE if the name server is valid, %FALSE otherwise
+ *
+ * Since: 1.56
+ */
+gboolean
+nm_dns_server_validate(const char *str, int family, GError **error)
+{
+    return nm_dns_uri_parse(family, str, NULL, error);
+}
+
 GPtrArray *
 _nm_setting_ip_config_get_dns_array(NMSettingIPConfig *setting)
 {
@@ -5630,14 +5649,19 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     /* Validate DNS */
     if (priv->dns) {
         for (i = 0; i < priv->dns->len; i++) {
-            const char *dns = priv->dns->pdata[i];
+            const char           *dns   = priv->dns->pdata[i];
+            gs_free_error GError *local = NULL;
 
-            if (!nm_dns_uri_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting), dns, NULL)) {
+            if (!nm_dns_uri_parse(NM_SETTING_IP_CONFIG_GET_ADDR_FAMILY(setting),
+                                  dns,
+                                  NULL,
+                                  &local)) {
                 g_set_error(error,
                             NM_CONNECTION_ERROR,
                             NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                            _("%u. DNS server address is invalid"),
-                            (i + 1u));
+                            _("%u. DNS server address is invalid: %s"),
+                            (i + 1u),
+                            local->message);
                 g_prefix_error(error,
                                "%s.%s: ",
                                nm_setting_get_name(setting),
@@ -5647,6 +5671,28 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
+    /* Validate DNS search domains */
+    if (nm_strvarray_get_strv_notempty(priv->dns_search.arr, NULL)) {
+        for (i = 0; i < priv->dns_search.arr->len; i++) {
+            const char *dns_search = nm_strvarray_get_idx(priv->dns_search.arr, i);
+
+            /* TODO: currently we only check that no wrong list separators have
+             * been used by mistake. Proper domain name validation would be better. */
+            if (strpbrk(dns_search, ",; ")) {
+                g_set_error(error,
+                            NM_CONNECTION_ERROR,
+                            NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                            _("DNS search domain '%s' is invalid"),
+                            dns_search);
+                g_prefix_error(error,
+                               "%s.%s: ",
+                               nm_setting_get_name(setting),
+                               NM_SETTING_IP_CONFIG_DNS_SEARCH);
+                return FALSE;
+            }
+        }
+    }
+
     /* Validate addresses */
     for (i = 0; i < priv->addresses->len; i++) {
         NMIPAddress *addr = (NMIPAddress *) priv->addresses->pdata[i];
@@ -6694,7 +6740,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * NMSettingIPConfig:gateway:
      *
      * The gateway associated with this configuration. This is only meaningful
-     * if #NMSettingIPConfig:addresses is also set.
+     * if addresses are also set on the device.
      *
      * Setting the gateway causes NetworkManager to configure a standard default route
      * with the gateway as next hop. This is ignored if #NMSettingIPConfig:never-default
diff --git a/src/libnm-core-impl/nm-setting-wireguard.c b/src/libnm-core-impl/nm-setting-wireguard.c
index 614be0f6..9a324188 100644
--- a/src/libnm-core-impl/nm-setting-wireguard.c
+++ b/src/libnm-core-impl/nm-setting-wireguard.c
@@ -2532,6 +2532,73 @@ nm_setting_wireguard_class_init(NMSettingWireGuardClass *klass)
                                                     NMSettingWireGuard,
                                                     _priv.ip6_auto_default_route);
 
+    /* ---nmcli---
+     * property: peers
+     * format: a comma-separated list of WireGuard peers
+     * description:
+     *  A comma-separated list of WireGuard peers. Each peer has the following syntax:
+     *
+     *   PUBLIC_KEY [ATTRIBUTE=VALUE [ATTRIBUTE=VALUE]...]
+     *
+     *   The supported attributes are: endpoint, allowed-ips, persistent-keepalive,
+     *   preshared-key, preshared-key-flags.
+     * description-docbook:
+     *   <para>
+     *   A comma-separated list of WireGuard peers. Each peer has the following syntax:
+     *   </para>
+     *   <para>
+     *   <literal>
+     *   <replaceable>public-key</replaceable>
+     *   [<replaceable>attribute</replaceable>=<replaceable>value</replaceable>
+     *   [<replaceable>attribute</replaceable>=<replaceable>value</replaceable>]...]
+     *   </literal>
+     *   </para>
+     *   <para>
+     *   The public key is required and must be encoded as base64; it can be
+     *   calculated by running <command>wg pubkey</command> on the private key,
+     *   and it is usually transmitted out of band to the author of the configuration
+     *   file.
+     *   </para>
+     *   <para>
+     *   The supported attributes are:
+     *   <variablelist>
+     *     <varlistentry>
+     *       <term><varname>endpoint</varname></term>
+     *       <listitem><para>An endpoint IP or hostname, followed by a colon, and then
+     *       a port number.</para></listitem>
+     *     </varlistentry>
+     *
+     *     <varlistentry>
+     *       <term><varname></varname></term>
+     *       <listitem><para></para></listitem>
+     *     </varlistentry>
+     *     <varlistentry>
+     *       <term><varname>allowed-ips</varname></term>
+     *       <listitem><para>A semicolon-separated list of IP (v4 or v6) addresses
+     *       with CIDR masks from which incoming traffic for this peer is allowed
+     *       and to which outgoing traffic for this peer is directed
+     *       </para></listitem>
+     *     </varlistentry>
+     *     <varlistentry>
+     *       <term><varname>persistent-keepalive</varname></term>
+     *       <listitem><para>An interval in seconds, between 1 and 65535, of
+     *       how often to send an authenticated empty packet to the peer for the
+     *       purpose of keeping a stateful firewall or NAT mapping valid persistently.
+     *       </para></listitem>
+     *     </varlistentry>
+     *     <varlistentry>
+     *       <term><varname>preshared-key</varname></term>
+     *       <listitem><para>A base64 preshared key generated by "wg genpsk". Optional,
+     *       and may be omitted.</para></listitem>
+     *     </varlistentry>
+     *     <varlistentry>
+     *       <term><varname>preshared-key-flags</varname></term>
+     *       <listitem><para>The secret flags for the preshared-key.</para></listitem>
+     *     </varlistentry>
+     *   </variablelist>
+     *   </para>
+     * ---end---
+     */
     /* ---dbus---
      * property: peers
      * format: array of 'a{sv}'
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 9a78e947..1bf00831 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -6336,7 +6336,7 @@ helper_child_terminated(GPid pid, int status, gpointer user_data)
  *   if an error occurred, including when the given user can't access the
  *   file.
  *
- * Since: 1.56, 1.54.3
+ * Since: 1.56
  */
 char *
 nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **error)
@@ -6444,6 +6444,7 @@ nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **erro
                                     0600,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     error)) {
         return NULL;
     }
diff --git a/src/libnm-core-impl/nm-vpn-plugin-info.c b/src/libnm-core-impl/nm-vpn-plugin-info.c
index 04b0fb16..47dc9e3b 100644
--- a/src/libnm-core-impl/nm-vpn-plugin-info.c
+++ b/src/libnm-core-impl/nm-vpn-plugin-info.c
@@ -921,7 +921,7 @@ nm_vpn_plugin_info_supports_multiple(NMVpnPluginInfo *self)
  *     private connections in a safe way (i.e. checking user permissions), or
        if the service doesn't need to read any file from disk.
  *
- * Since: 1.56, 1.54.3
+ * Since: 1.56
  */
 gboolean
 nm_vpn_plugin_info_supports_safe_private_file_access(NMVpnPluginInfo *self)
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index 43e73c3d..cd480045 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -4032,6 +4032,7 @@ test_connection_diff_a_only(void)
           {NM_SETTING_CONNECTION_MDNS, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_LLMNR, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_DNS_OVER_TLS, NM_SETTING_DIFF_RESULT_IN_A},
+          {NM_SETTING_CONNECTION_DNSSEC, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_MPTCP_FLAGS, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_MUD_URL, NM_SETTING_DIFF_RESULT_IN_A},
           {NM_SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
@@ -11400,12 +11401,16 @@ test_connection_path(void)
 static void
 t_dns_0(const char *str)
 {
-    NMDnsServer server = {};
-    gboolean    ret;
+    gs_free_error GError *error  = NULL;
+    NMDnsServer           server = {};
+    gboolean              ret;
 
-    ret = nm_dns_uri_parse(AF_UNSPEC, str, &server);
+    ret = nm_dns_uri_parse(AF_UNSPEC, str, &server, &error);
 
     g_assert(!ret);
+    g_assert(error);
+    g_assert(error->message);
+    g_assert(error->message[0] != '\0');
 }
 
 static void
@@ -11422,10 +11427,12 @@ dns_uri_parse_ok(const char    *str,
     gboolean    ret;
 
     for (int i = 0; i < 2; i++) {
-        gboolean af_unspec = i;
+        gs_free_error GError *error     = NULL;
+        gboolean              af_unspec = i;
 
-        ret = nm_dns_uri_parse(af_unspec ? AF_UNSPEC : addr_family, str, &dns);
+        ret = nm_dns_uri_parse(af_unspec ? AF_UNSPEC : addr_family, str, &dns, &error);
         g_assert(ret);
+        g_assert_no_error(error);
 
         g_assert_cmpint(addr_family, ==, dns.addr_family);
         g_assert_cmpint(port, ==, dns.port);
@@ -11436,8 +11443,9 @@ dns_uri_parse_ok(const char    *str,
         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);
+        ret = nm_dns_uri_parse(addr_family == AF_INET ? AF_INET6 : AF_INET, str, &dns, &error);
         g_assert(!ret);
+        g_assert(error);
     }
 }
 
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index 13d95189..f3070c17 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -5460,6 +5460,44 @@ test_settings_dns(void)
     }
 }
 
+static void
+_assert_dns_searches(gboolean valid, ...)
+{
+    NMConnection      *con;
+    NMSettingIPConfig *ip4, *ip6;
+    const char        *dns_search;
+    va_list            args;
+
+    con = nmtst_create_minimal_connection("test-dns-search",
+                                          NULL,
+                                          NM_SETTING_WIRED_SETTING_NAME,
+                                          NULL);
+    nmtst_connection_normalize(con);
+    ip4 = nm_connection_get_setting_ip4_config(con);
+    ip6 = nm_connection_get_setting_ip6_config(con);
+
+    va_start(args, valid);
+    while ((dns_search = va_arg(args, const char *))) {
+        nm_setting_ip_config_add_dns_search(ip4, dns_search);
+        nm_setting_ip_config_add_dns_search(ip6, dns_search);
+    }
+    va_end(args);
+
+    g_assert(valid == nm_setting_verify((NMSetting *) ip4, con, NULL));
+    g_assert(valid == nm_setting_verify((NMSetting *) ip6, con, NULL));
+}
+
+static void
+test_settings_dns_search_domains(void)
+{
+    _assert_dns_searches(TRUE, "example.com", NULL);
+    _assert_dns_searches(TRUE, "sub.example.com", NULL);
+    _assert_dns_searches(TRUE, "example.com", "sub.example.com", NULL);
+    _assert_dns_searches(FALSE, "example.com,sub.example.com", NULL);
+    _assert_dns_searches(FALSE, "example.com;sub.example.com", NULL);
+    _assert_dns_searches(FALSE, "example.com sub.example.com", NULL);
+}
+
 /*****************************************************************************/
 
 static void
@@ -5787,6 +5825,7 @@ main(int argc, char **argv)
     g_test_add_func("/libnm/settings/6lowpan/1", test_6lowpan_1);
 
     g_test_add_func("/libnm/settings/dns", test_settings_dns);
+    g_test_add_func("/libnm/settings/dns_search_domain", test_settings_dns_search_domains);
 
     g_test_add_func("/libnm/settings/sriov/vf", test_sriov_vf);
     g_test_add_func("/libnm/settings/sriov/vf-dup", test_sriov_vf_dup);