about summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
authorSebastien Bacher <seb128@ubuntu.com>2021-08-25 15:24:42 +0200
committerSebastien Bacher <seb128@ubuntu.com>2021-08-25 15:24:42 +0200
commitdbb91282fa488964fb20595f9494a9f0e4f36a58 (patch)
tree142bc942e5320b35514cdf03a5f9f47a9b89df0e /src/libnm-core-impl
parent5f2ede3a2813b0e9204befdcfc67509d34be71c6 (diff)
parentcfb80376641fa49137b9996130352697e7f8b436 (diff)
Update upstream source from tag 'upstream/1.32.10'
Update to upstream version '1.32.10'
with Debian dir fcf2778b50b013ede3e7375bc1d829e984175658
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/meson.build36
-rw-r--r--src/libnm-core-impl/nm-connection.c2
-rw-r--r--src/libnm-core-impl/nm-keyfile-utils.c4
-rw-r--r--src/libnm-core-impl/nm-keyfile.c3
-rw-r--r--src/libnm-core-impl/nm-setting-bond.c37
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c61
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c2
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c9
-rw-r--r--src/libnm-core-impl/nm-setting-wireless.c60
-rw-r--r--src/libnm-core-impl/tests/meson.build2
-rw-r--r--src/libnm-core-impl/tests/test-general.c14
11 files changed, 201 insertions, 29 deletions
diff --git a/src/libnm-core-impl/meson.build b/src/libnm-core-impl/meson.build
index 2b769616..f175ea3b 100644
--- a/src/libnm-core-impl/meson.build
+++ b/src/libnm-core-impl/meson.build
@@ -2,23 +2,27 @@
 
 libnm_core_impl_inc = include_directories('.')
 
-libnm_crypto_nss = static_library(
-  'nm-crypto-nss',
-  sources: 'nm-crypto-nss.c',
-  dependencies: [
-    libnm_core_public_dep,
-    crypto_nss_dep,
-  ],
-)
+if crypto_nss_dep.found()
+  libnm_crypto_nss = static_library(
+    'nm-crypto-nss',
+    sources: 'nm-crypto-nss.c',
+    dependencies: [
+      libnm_core_public_dep,
+      crypto_nss_dep,
+    ],
+  )
+endif
 
-libnm_crypto_gnutls = static_library(
-  'nm-crypto-gnutls',
-  sources: 'nm-crypto-gnutls.c',
-  dependencies: [
-    libnm_core_public_dep,
-    crypto_gnutls_dep,
-  ],
-)
+if crypto_gnutls_dep.found()
+  libnm_crypto_gnutls = static_library(
+    'nm-crypto-gnutls',
+    sources: 'nm-crypto-gnutls.c',
+    dependencies: [
+      libnm_core_public_dep,
+      crypto_gnutls_dep,
+    ],
+  )
+endif
 
 if crypto == 'nss'
   libnm_crypto = libnm_crypto_nss
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index ed864db8..4ac7b322 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -649,7 +649,7 @@ nm_connection_replace_settings_from_connection(NMConnection *connection,
 {
     NMConnectionPrivate *priv;
     NMConnectionPrivate *new_priv;
-    gboolean             changed;
+    gboolean             changed = FALSE;
     int                  i;
 
     g_return_if_fail(NM_IS_CONNECTION(connection));
diff --git a/src/libnm-core-impl/nm-keyfile-utils.c b/src/libnm-core-impl/nm-keyfile-utils.c
index 88f9c4f9..f8c2f387 100644
--- a/src/libnm-core-impl/nm-keyfile-utils.c
+++ b/src/libnm-core-impl/nm-keyfile-utils.c
@@ -531,7 +531,7 @@ _keyfile_key_encode(const char *name, char **out_to_free)
             if (ch == '\0')
                 return name;
 
-            if (ch < 0x20 || ch >= 127 || NM_IN_SET(ch, '=', '[', ']')
+            if (!nm_ascii_is_regular(ch) || NM_IN_SET(ch, '=', '[', ']')
                 || (ch == '\\' && g_ascii_isxdigit(name[i + 1]) && g_ascii_isxdigit(name[i + 2]))
                 || (ch == ' ' && name[i + 1] == '\0'))
                 break;
@@ -557,7 +557,7 @@ _keyfile_key_encode(const char *name, char **out_to_free)
         if (ch == '\0')
             break;
 
-        if (ch < 0x20 || ch >= 127 || NM_IN_SET(ch, '=', '[', ']')
+        if (!nm_ascii_is_regular(ch) || NM_IN_SET(ch, '=', '[', ']')
             || (ch == '\\' && g_ascii_isxdigit(name[i + 1]) && g_ascii_isxdigit(name[i + 2]))
             || (ch == ' ' && name[i + 1] == '\0')) {
             nm_str_buf_append_c(&str, '\\');
diff --git a/src/libnm-core-impl/nm-keyfile.c b/src/libnm-core-impl/nm-keyfile.c
index 88aa0d46..c6c4a2eb 100644
--- a/src/libnm-core-impl/nm-keyfile.c
+++ b/src/libnm-core-impl/nm-keyfile.c
@@ -2781,6 +2781,9 @@ static const ParseInfoSetting *const parse_infos[_NM_META_SETTING_TYPE_NUM] = {
                                 .parser = mac_address_parser_ETHER_cloned, ),
             PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_MAC_ADDRESS,
                                 .parser = mac_address_parser_ETHER, ),
+            PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_SEEN_BSSIDS,
+                                .parser_skip = TRUE,
+                                .writer_skip = TRUE, ),
             PARSE_INFO_PROPERTY(NM_SETTING_WIRELESS_SSID,
                                 .parser = ssid_parser,
                                 .writer = ssid_writer, ), ), ),
diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c
index 93baa9d6..252adc73 100644
--- a/src/libnm-core-impl/nm-setting-bond.c
+++ b/src/libnm-core-impl/nm-setting-bond.c
@@ -90,6 +90,7 @@ static const char *const valid_options_lst[] = {
     NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE,
     NM_SETTING_BOND_OPTION_TLB_DYNAMIC_LB,
     NM_SETTING_BOND_OPTION_LP_INTERVAL,
+    NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY,
     NULL,
 };
 
@@ -207,6 +208,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(
     {NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_NUM_UNSOL_NA, {"1", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, {"1", NM_BOND_OPTION_TYPE_INT, 0, 65535}},
+    {NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}},
     {NM_SETTING_BOND_OPTION_PRIMARY, {"", NM_BOND_OPTION_TYPE_IFNAME}},
     {NM_SETTING_BOND_OPTION_PRIMARY_RESELECT,
      {"always", NM_BOND_OPTION_TYPE_BOTH, 0, 2, _option_default_strv_primary_reselect}},
@@ -782,6 +784,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     int                      arp_interval;
     int                      num_grat_arp;
     int                      num_unsol_na;
+    int                      peer_notif_delay;
     const char *             mode_str;
     const char *             arp_ip_target = NULL;
     const char *             lacp_rate;
@@ -810,6 +813,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
     arp_interval = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_INTERVAL));
     num_grat_arp = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP));
     num_unsol_na = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_NUM_UNSOL_NA));
+    peer_notif_delay =
+        _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY));
 
     /* Option restrictions:
      *
@@ -818,6 +823,8 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
      * arp_validate does not work with [ BOND_MODE_8023AD, BOND_MODE_TLB, BOND_MODE_ALB ]
      * downdelay needs miimon
      * updelay needs miimon
+     * peer_notif_delay needs miimon enabled
+     * peer_notif_delay must be a miimon multiple
      * primary needs [ active-backup, tlb, alb ]
      */
 
@@ -926,6 +933,36 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
     }
 
+    if (peer_notif_delay) {
+        if (miimon == 0) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' option requires '%s' option to be enabled"),
+                        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY,
+                        NM_SETTING_BOND_OPTION_MIIMON);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+            return FALSE;
+        }
+
+        /* The code disables miimon when arp is set, so they never occur together.
+         * But this occurs after this verification, so this check can occur in
+         * an invalid state, when both arp and miimon are enabled. To assure not
+         * dealing with an invalid state, this arp_interval == 0 condition,
+         * that is implicit, was made explicit.
+         */
+        if ((peer_notif_delay % miimon) && (arp_interval == 0)) {
+            g_set_error(error,
+                        NM_CONNECTION_ERROR,
+                        NM_CONNECTION_ERROR_INVALID_PROPERTY,
+                        _("'%s' option needs to be a value multiple of '%s' value"),
+                        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY,
+                        NM_SETTING_BOND_OPTION_MIIMON);
+            g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
+            return FALSE;
+        }
+    }
+
     /* arp_ip_target can only be used with arp_interval, and must
      * contain a comma-separated list of IPv4 addresses.
      */
diff --git a/src/libnm-core-impl/nm-setting-ip-config.c b/src/libnm-core-impl/nm-setting-ip-config.c
index 59ab5ca2..f37e9d35 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -3905,6 +3905,7 @@ NM_GOBJECT_PROPERTIES_DEFINE(NMSettingIPConfig,
                              PROP_MAY_FAIL,
                              PROP_DAD_TIMEOUT,
                              PROP_DHCP_TIMEOUT,
+                             PROP_REQUIRED_TIMEOUT,
                              PROP_DHCP_IAID,
                              PROP_DHCP_REJECT_SERVERS, );
 
@@ -3925,6 +3926,7 @@ typedef struct {
     int        dns_priority;
     int        dad_timeout;
     int        dhcp_timeout;
+    int        required_timeout;
     guint32    route_table;
     bool       ignore_auto_routes : 1;
     bool       ignore_auto_dns : 1;
@@ -5206,6 +5208,25 @@ nm_setting_ip_config_get_dhcp_timeout(NMSettingIPConfig *setting)
 }
 
 /**
+ * nm_setting_ip_config_get_required_timeout:
+ * @setting: the #NMSettingIPConfig
+ *
+ * Returns the value contained in the #NMSettingIPConfig:required-timeout
+ * property.
+ *
+ * Returns: the required timeout for the address family
+ *
+ * Since: 1.34, 1.32.4
+ **/
+int
+nm_setting_ip_config_get_required_timeout(NMSettingIPConfig *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP_CONFIG(setting), -1);
+
+    return NM_SETTING_IP_CONFIG_GET_PRIVATE(setting)->required_timeout;
+}
+
+/**
  * nm_setting_ip_config_get_dhcp_iaid:
  * @setting: the #NMSettingIPConfig
  *
@@ -5908,6 +5929,9 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
     case PROP_DHCP_TIMEOUT:
         g_value_set_int(value, nm_setting_ip_config_get_dhcp_timeout(setting));
         break;
+    case PROP_REQUIRED_TIMEOUT:
+        g_value_set_int(value, nm_setting_ip_config_get_required_timeout(setting));
+        break;
     case PROP_DHCP_IAID:
         g_value_set_string(value, nm_setting_ip_config_get_dhcp_iaid(setting));
         break;
@@ -6018,7 +6042,11 @@ set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *ps
     case PROP_DHCP_TIMEOUT:
         priv->dhcp_timeout = g_value_get_int(value);
         break;
+    case PROP_REQUIRED_TIMEOUT:
+        priv->required_timeout = g_value_get_int(value);
+        break;
     case PROP_DHCP_IAID:
+        g_free(priv->dhcp_iaid);
         priv->dhcp_iaid = g_value_dup_string(value);
         break;
     case PROP_DHCP_HOSTNAME_FLAGS:
@@ -6048,6 +6076,7 @@ nm_setting_ip_config_init(NMSettingIPConfig *setting)
     priv->dhcp_send_hostname = TRUE;
     priv->may_fail           = TRUE;
     priv->dad_timeout        = -1;
+    priv->required_timeout   = -1;
 }
 
 static void
@@ -6483,6 +6512,38 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
         G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
 
     /**
+     * NMSettingIPConfig:required-timeout:
+     *
+     * The minimum time interval in milliseconds for which dynamic IP configuration
+     * should be tried before the connection succeeds.
+     *
+     * This property is useful for example if both IPv4 and IPv6 are enabled and
+     * are allowed to fail. Normally the connection succeeds as soon as one of
+     * the two address families completes; by setting a required timeout for
+     * e.g. IPv4, one can ensure that even if IP6 succeeds earlier than IPv4,
+     * NetworkManager waits some time for IPv4 before the connection becomes
+     * active.
+     *
+     * Note that if #NMSettingIPConfig:may-fail is FALSE for the same address
+     * family, this property has no effect as NetworkManager needs to wait for
+     * the full DHCP timeout.
+     *
+     * A zero value means that no required timeout is present, -1 means the
+     * default value (either configuration ipvx.required-timeout override or
+     * zero).
+     *
+     * Since: 1.34, 1.32.4
+     **/
+    obj_properties[PROP_REQUIRED_TIMEOUT] = g_param_spec_int(
+        NM_SETTING_IP_CONFIG_REQUIRED_TIMEOUT,
+        "",
+        "",
+        -1,
+        G_MAXINT32,
+        -1,
+        G_PARAM_READWRITE | NM_SETTING_PARAM_FUZZY_IGNORE | G_PARAM_STATIC_STRINGS);
+
+    /**
      * NMSettingIPConfig:dhcp-iaid:
      *
      * A string containing the "Identity Association Identifier" (IAID) used
diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index 15bae0cd..b3a18bca 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -967,6 +967,8 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * format: a comma separated list of addresses
      * description: A list of IPv4 addresses and their prefix length. Multiple addresses
      * can be separated by comma. For example "192.168.1.5/24, 10.1.0.5/24".
+     * The addresses are listed in increasing priority, meaning the last address will
+     * be the primary address.
      * ---end---
      */
     _nm_properties_override_gobj(
diff --git a/src/libnm-core-impl/nm-setting-ip6-config.c b/src/libnm-core-impl/nm-setting-ip6-config.c
index f6d59a46..ee72de57 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -1030,6 +1030,15 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      *   that subnet.
      * ---end---
      */
+    /* ---nmcli---
+     * property: addresses
+     * format: a comma separated list of addresses
+     * description: A list of IPv6 addresses and their prefix length. Multiple addresses
+     * can be separated by comma. For example "2001:db8:85a3::8a2e:370:7334/64, 2001:db8:85a3::5/64".
+     * The addresses are listed in increasing priority, meaning the last address will
+     * be the primary address.
+     * ---end---
+     */
     _nm_properties_override_gobj(
         properties_override,
         g_object_class_find_property(G_OBJECT_CLASS(setting_class), NM_SETTING_IP_CONFIG_ADDRESSES),
diff --git a/src/libnm-core-impl/nm-setting-wireless.c b/src/libnm-core-impl/nm-setting-wireless.c
index 11111696..5bdedf9b 100644
--- a/src/libnm-core-impl/nm-setting-wireless.c
+++ b/src/libnm-core-impl/nm-setting-wireless.c
@@ -750,19 +750,54 @@ _to_dbus_fcn_seen_bssids(const NMSettInfoSetting *               sett_info,
                          NMConnectionSerializationFlags          flags,
                          const NMConnectionSerializationOptions *options)
 {
+    if (options && options->seen_bssids)
+        return options->seen_bssids[0] ? g_variant_new_strv(options->seen_bssids, -1) : NULL;
+
+    /* The seen-bssid property is special. It cannot be converted to D-Bus
+     * like regular properties, only via the "options".
+     *
+     * This basically means, that only the daemon can provide seen-bssids as GVariant,
+     * while when a client converts the property to GVariant, it gets lost.
+     *
+     * This has the odd effect, that when the client converts the setting to GVariant
+     * and back, the seen-bssids gets lost. That is kinda desired here, because the to_dbus_fcn()
+     * and from_dbus_fcn() have the meaning of how a setting gets transferred via D-Bus,
+     * and not necessarily a loss-less conversion into another format and back. And when
+     * transferring via D-Bus, then the option makes only sense when sending it from
+     * the daemon to the client, not otherwise. */
+    return NULL;
+}
+
+static gboolean
+_from_dbus_fcn_seen_bssids(NMSetting *         setting,
+                           GVariant *          connection_dict,
+                           const char *        property,
+                           GVariant *          value,
+                           NMSettingParseFlags parse_flags,
+                           GError **           error)
+{
     NMSettingWirelessPrivate *priv;
+    gs_free const char **     s = NULL;
+    gsize                     len;
+    gsize                     i;
 
-    if (options && options->seen_bssids) {
-        return options->seen_bssids[0] ? g_variant_new_strv(options->seen_bssids, -1) : NULL;
+    if (_nm_utils_is_manager_process) {
+        /* in the manager process, we don't accept seen-bssid from the client.
+         * Do nothing.  */
+        return TRUE;
     }
 
     priv = NM_SETTING_WIRELESS_GET_PRIVATE(setting);
 
-    if (!priv->seen_bssids || priv->seen_bssids->len == 0)
-        return NULL;
+    nm_clear_pointer(&priv->seen_bssids, g_ptr_array_unref);
 
-    return g_variant_new_strv((const char *const *) priv->seen_bssids->pdata,
-                              priv->seen_bssids->len);
+    s = g_variant_get_strv(value, &len);
+    if (len > 0) {
+        priv->seen_bssids = g_ptr_array_new_full(len, g_free);
+        for (i = 0; i < len; i++)
+            g_ptr_array_add(priv->seen_bssids, g_strdup(s[i]));
+    }
+    return TRUE;
 }
 
 /**
@@ -1075,12 +1110,18 @@ compare_property(const NMSettInfoSetting *sett_info,
                  NMSetting *              set_b,
                  NMSettingCompareFlags    flags)
 {
-    if (nm_streq(sett_info->property_infos[property_idx].name,
-                 NM_SETTING_WIRELESS_CLONED_MAC_ADDRESS)) {
+    if (sett_info->property_infos[property_idx].param_spec
+        == obj_properties[PROP_CLONED_MAC_ADDRESS]) {
         return !set_b
                || nm_streq0(NM_SETTING_WIRELESS_GET_PRIVATE(set_a)->cloned_mac_address,
                             NM_SETTING_WIRELESS_GET_PRIVATE(set_b)->cloned_mac_address);
     }
+    if (sett_info->property_infos[property_idx].param_spec == obj_properties[PROP_SEEN_BSSIDS]) {
+        return !set_b
+               || (nm_strv_ptrarray_cmp(NM_SETTING_WIRELESS_GET_PRIVATE(set_a)->seen_bssids,
+                                        NM_SETTING_WIRELESS_GET_PRIVATE(set_b)->seen_bssids)
+                   == 0);
+    }
 
     return NM_SETTING_CLASS(nm_setting_wireless_parent_class)
         ->compare_property(sett_info, property_idx, con_a, set_a, con_b, set_b, flags);
@@ -1744,7 +1785,8 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
         properties_override,
         obj_properties[PROP_SEEN_BSSIDS],
         NM_SETT_INFO_PROPERT_TYPE_DBUS(G_VARIANT_TYPE_STRING_ARRAY,
-                                       .to_dbus_fcn = _to_dbus_fcn_seen_bssids, ));
+                                       .to_dbus_fcn   = _to_dbus_fcn_seen_bssids,
+                                       .from_dbus_fcn = _from_dbus_fcn_seen_bssids, ));
 
     /**
      * NMSettingWireless:mtu:
diff --git a/src/libnm-core-impl/tests/meson.build b/src/libnm-core-impl/tests/meson.build
index ca1d1c1a..988c60db 100644
--- a/src/libnm-core-impl/tests/meson.build
+++ b/src/libnm-core-impl/tests/meson.build
@@ -32,8 +32,8 @@ foreach test_unit: test_units
       libnm_core_impl,
       libnm_crypto,
       libnm_base,
-      libnm_log_null,
       libnm_systemd_shared,
+      libnm_log_null,
       libnm_glib_aux,
       libnm_std_aux,
       libc_siphash,
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index f9cc5e54..57864948 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -3868,6 +3868,7 @@ test_connection_diff_a_only(void)
              {NM_SETTING_IP_CONFIG_NEVER_DEFAULT, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_MAY_FAIL, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DAD_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
+             {NM_SETTING_IP_CONFIG_REQUIRED_TIMEOUT, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DNS_PRIORITY, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP_CONFIG_DHCP_IAID, NM_SETTING_DIFF_RESULT_IN_A},
              {NM_SETTING_IP4_CONFIG_DHCP_VENDOR_CLASS_IDENTIFIER, NM_SETTING_DIFF_RESULT_IN_A},
@@ -9136,6 +9137,12 @@ _do_test_utils_str_utf8safe(const char *            str,
     ((nmtst_get_rand_bool()) ? NM_UTILS_STR_UTF8_SAFE_FLAG_NONE \
                              : NM_UTILS_STR_UTF8_SAFE_FLAG_SECRET)
 
+    if (expected && strlen(expected) == str_len && memcmp(str, expected, str_len) == 0) {
+        g_error("Test error: pass expected as NULL (instead of \"%s\", if the escaping will "
+                "produce no difference.",
+                expected);
+    }
+
     buf_safe = nm_utils_buf_utf8safe_escape(str, str_len, flags | RND_FLAG, &str_free_1);
 
     str_safe = nm_utils_str_utf8safe_escape(str, flags | RND_FLAG, &str_free_2);
@@ -9272,6 +9279,13 @@ test_utils_str_utf8safe(void)
     do_test_utils_str_utf8safe_unescape("\n\\012", "\n\012");
     do_test_utils_str_utf8safe_unescape("\n\\.", "\n.");
     do_test_utils_str_utf8safe_unescape("\\n\\.3\\r", "\n.3\r");
+
+    do_test_utils_str_utf8safe("ab∞c", NULL, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL);
+    do_test_utils_str_utf8safe("ab\ab∞c", "ab\\007b∞c", NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL);
+    do_test_utils_str_utf8safe("ab\ab∞c",
+                               "ab\\007b\\342\\210\\236c",
+                               NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL
+                                   | NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_NON_ASCII);
 }
 
 /*****************************************************************************/