about summary refs log tree commit diff
path: root/src/libnm-core-impl
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2026-07-03 19:53:18 +0200
committerMichael Biebl <biebl@debian.org>2026-07-03 19:53:18 +0200
commit537bfce2bda471c92caabd388589230200891509 (patch)
treeaedeccfaf0ba52c238ecf51fc009c0db5d4b60f0 /src/libnm-core-impl
parent869e9027026cdbb15d4e4a6327ff41d2697858eb (diff)
New upstream version 1.58~rc1 upstream/1.58_rc1
Diffstat (limited to 'src/libnm-core-impl')
-rw-r--r--src/libnm-core-impl/gen-metadata-nm-settings-libnm-core.xml.in4
-rw-r--r--src/libnm-core-impl/nm-connection.c2
-rw-r--r--src/libnm-core-impl/nm-setting-8021x.c9
-rw-r--r--src/libnm-core-impl/nm-setting-bond.c16
-rw-r--r--src/libnm-core-impl/nm-setting-connection.c5
-rw-r--r--src/libnm-core-impl/nm-setting-geneve.c26
-rw-r--r--src/libnm-core-impl/nm-setting-ip-config.c9
-rw-r--r--src/libnm-core-impl/nm-setting-ip4-config.c90
-rw-r--r--src/libnm-core-impl/nm-setting-ip6-config.c8
-rw-r--r--src/libnm-core-impl/nm-setting-private.h3
-rw-r--r--src/libnm-core-impl/nm-setting-wireless.c23
-rw-r--r--src/libnm-core-impl/nm-utils.c267
-rw-r--r--src/libnm-core-impl/tests/test-general.c327
-rw-r--r--src/libnm-core-impl/tests/test-setting.c15
14 files changed, 642 insertions, 162 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 8d5d3bc9..7308db4d 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
@@ -1680,6 +1680,10 @@
                   dbus-type="i"
                   gprop-type="NMTernary"
                   />
+        <property name="clat"
+                  dbus-type="i"
+                  gprop-type="gint"
+                  />
         <property name="dad-timeout"
                   dbus-type="i"
                   gprop-type="gint"
diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c
index 180fcbbc..9ce72b41 100644
--- a/src/libnm-core-impl/nm-connection.c
+++ b/src/libnm-core-impl/nm-connection.c
@@ -3535,7 +3535,7 @@ nm_connection_get_setting_generic(NMConnection *connection)
  *
  * Returns: (transfer none): an #NMSettingGeneve if the connection contains one, otherwise NULL
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 NMSettingGeneve *
 nm_connection_get_setting_geneve(NMConnection *connection)
diff --git a/src/libnm-core-impl/nm-setting-8021x.c b/src/libnm-core-impl/nm-setting-8021x.c
index f9333803..ac148efa 100644
--- a/src/libnm-core-impl/nm-setting-8021x.c
+++ b/src/libnm-core-impl/nm-setting-8021x.c
@@ -3133,6 +3133,12 @@ need_secrets(NMSetting *setting, gboolean check_rerequest)
 
 /*****************************************************************************/
 
+static NMSetting8021xCKScheme
+_ck_scheme_path(NMSetting8021x *_sett)
+{
+    return NM_SETTING_802_1X_CK_SCHEME_PATH;
+}
+
 static void
 get_private_files(NMSetting *setting, GPtrArray *files)
 {
@@ -3159,6 +3165,7 @@ get_private_files(NMSetting *setting, GPtrArray *files)
         {NM_SETTING_802_1X_PHASE2_PRIVATE_KEY,
          nm_setting_802_1x_get_phase2_private_key_scheme,
          nm_setting_802_1x_get_phase2_private_key_path},
+        {NM_SETTING_802_1X_PAC_FILE, _ck_scheme_path, nm_setting_802_1x_get_pac_file},
     };
     NMSetting8021x *s_8021x = NM_SETTING_802_1X(setting);
     const char     *path;
@@ -3390,7 +3397,7 @@ nm_setting_802_1x_class_init(NMSetting8021xClass *klass)
                                               obj_properties,
                                               NM_SETTING_802_1X_PAC_FILE,
                                               PROP_PAC_FILE,
-                                              NM_SETTING_PARAM_NONE,
+                                              NM_SETTING_PARAM_CERT_KEY_FILE,
                                               NMSetting8021xPrivate,
                                               pac_file,
                                               .direct_string_allow_empty = TRUE);
diff --git a/src/libnm-core-impl/nm-setting-bond.c b/src/libnm-core-impl/nm-setting-bond.c
index 7204df48..6e390159 100644
--- a/src/libnm-core-impl/nm-setting-bond.c
+++ b/src/libnm-core-impl/nm-setting-bond.c
@@ -197,7 +197,7 @@ static NM_UTILS_STRING_TABLE_LOOKUP_STRUCT_DEFINE(
      {"any", NM_BOND_OPTION_TYPE_BOTH, 0, 1, _option_default_strv_arp_all_targets}},
     {NM_SETTING_BOND_OPTION_ARP_INTERVAL, {"0", NM_BOND_OPTION_TYPE_INT, 0, G_MAXINT}},
     {NM_SETTING_BOND_OPTION_ARP_IP_TARGET, {"", NM_BOND_OPTION_TYPE_IP}},
-    {NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, {"0", NM_BOND_OPTION_TYPE_INT, 0, 255}},
+    {NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, {"2", NM_BOND_OPTION_TYPE_INT, 0, 255}},
     {NM_SETTING_BOND_OPTION_ARP_VALIDATE,
      {"none", NM_BOND_OPTION_TYPE_BOTH, 0, 6, _option_default_strv_arp_validate}},
     {NM_SETTING_BOND_OPTION_BALANCE_SLB, {"0", NM_BOND_OPTION_TYPE_INT, 0, 1}},
@@ -364,6 +364,10 @@ _bond_get_option_normalized(NMSettingBond *self, const char *option, gboolean ge
                 /* balance-slb implies vlan+srcmac */
                 return "5";
             }
+        } else if (nm_streq(option, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX)) {
+            value = _bond_get_option(self, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX) ?: "0";
+            if (nm_streq(value, "0"))
+                value = _bond_get_option_default(self, option);
         } else
             value = _bond_get_option(self, option);
 
@@ -894,13 +898,16 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
 
     miimon       = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_MIIMON));
     arp_interval = _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_INTERVAL));
-    arp_missed_max =
-        _atoi(_bond_get_option_or_default(self, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX));
     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));
 
+    /* "0" is an invalid value in the kernel, but we used to accept it to indicate "default value".
+     * Keep accepting "0" as a valid value, although we'll apply a different value, actually.
+     * Bond modes that don't accept arp_missed_max must just ignore the "0" value, too. */
+    arp_missed_max = _atoi(_bond_get_option(self, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX) ?: "0");
+
     /* Option restrictions:
      *
      * arp_interval conflicts [ alb, tlb ]
@@ -950,6 +957,9 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
             g_prefix_error(error, "%s.%s: ", NM_SETTING_BOND_SETTING_NAME, NM_SETTING_BOND_OPTIONS);
             return FALSE;
         }
+    }
+
+    if (NM_IN_SET(bond_mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB, NM_BOND_MODE_8023AD)) {
         if (arp_missed_max > 0) {
             g_set_error(error,
                         NM_CONNECTION_ERROR,
diff --git a/src/libnm-core-impl/nm-setting-connection.c b/src/libnm-core-impl/nm-setting-connection.c
index 0ad97846..96dff146 100644
--- a/src/libnm-core-impl/nm-setting-connection.c
+++ b/src/libnm-core-impl/nm-setting-connection.c
@@ -3328,9 +3328,8 @@ nm_setting_connection_class_init(NMSettingConnectionClass *klass)
      *
      * The number of retries for the authentication. Zero means to try indefinitely; -1 means
      * to use a global default. If the global default is not set, the authentication
-     * retries for 3 times before failing the connection.
-     *
-     * Currently, this only applies to 802-1x authentication.
+     * retries for 3 times before failing the connection. Connections using a pre-shared key
+     * to authenticate will only prompt for a new key during the last authentication attempt.
      *
      * Since: 1.10
      **/
diff --git a/src/libnm-core-impl/nm-setting-geneve.c b/src/libnm-core-impl/nm-setting-geneve.c
index b01fcd9a..d6426a06 100644
--- a/src/libnm-core-impl/nm-setting-geneve.c
+++ b/src/libnm-core-impl/nm-setting-geneve.c
@@ -65,7 +65,7 @@ G_DEFINE_TYPE(NMSettingGeneve, nm_setting_geneve, NM_TYPE_SETTING)
  *
  * Returns: the #NMSettingGeneve:id property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 guint
 nm_setting_geneve_get_id(NMSettingGeneve *setting)
@@ -81,7 +81,7 @@ nm_setting_geneve_get_id(NMSettingGeneve *setting)
  *
  * Returns: the #NMSettingGeneve:remote property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 const char *
 nm_setting_geneve_get_remote(NMSettingGeneve *setting)
@@ -97,7 +97,7 @@ nm_setting_geneve_get_remote(NMSettingGeneve *setting)
  *
  * Returns: the #NMSettingGeneve:destination-port property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 guint
 nm_setting_geneve_get_destination_port(NMSettingGeneve *setting)
@@ -113,7 +113,7 @@ nm_setting_geneve_get_destination_port(NMSettingGeneve *setting)
  *
  * Returns: the #NMSettingGeneve:tos property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 guint
 nm_setting_geneve_get_tos(NMSettingGeneve *setting)
@@ -129,7 +129,7 @@ nm_setting_geneve_get_tos(NMSettingGeneve *setting)
  *
  * Returns: the #NMSettingGeneve:ttl property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 guint
 nm_setting_geneve_get_ttl(NMSettingGeneve *setting)
@@ -145,7 +145,7 @@ nm_setting_geneve_get_ttl(NMSettingGeneve *setting)
  *
  * Returns: the #NMSettingGeneve:df property of the setting
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 NMSettingGeneveDf
 nm_setting_geneve_get_df(NMSettingGeneve *setting)
@@ -206,7 +206,7 @@ nm_setting_geneve_init(NMSettingGeneve *self)
  *
  * Returns: (transfer full): the new empty #NMSettingGeneve object
  *
- * Since: 1.58, 1.56.1
+ * Since: 1.58
  **/
 NMSetting *
 nm_setting_geneve_new(void)
@@ -232,7 +232,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * Specifies the GENEVE Network Identifier (or GENEVE Segment Identifier) to
      * use.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_uint32(properties_override,
                                               obj_properties,
@@ -251,7 +251,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * Specifies the unicast destination IP address to use in outgoing packets
      * when communicating with the remote GENEVE tunnel endpoint.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_string(properties_override,
                                               obj_properties,
@@ -270,7 +270,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * Specifies the UDP destination port to communicate to the remote GENEVE
      * tunnel endpoint.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_uint32(properties_override,
                                               obj_properties,
@@ -289,7 +289,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * Specifies the TOS value to use in outgoing packets.
      * The special value "inherit" (1) means inherit from outer packet.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_uint32(properties_override,
                                               obj_properties,
@@ -308,7 +308,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * Specifies the time-to-live value to use in outgoing packets.
      * The special value "inherit" (-1) means inherit from outer packet, 0 means auto, 1-255 are fixed values.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_int32(properties_override,
                                              obj_properties,
@@ -331,7 +331,7 @@ nm_setting_geneve_class_init(NMSettingGeneveClass *klass)
      * %NM_SETTING_GENEVE_DF_SET (1): Always set the DF flag, packets will not be fragmented.
      * %NM_SETTING_GENEVE_DF_INHERIT (2): Inherit the DF flag from the inner IP header.
      *
-     * Since: 1.58, 1.56.1
+     * Since: 1.58
      **/
     _nm_setting_property_define_direct_enum(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 f4ee094c..cee35159 100644
--- a/src/libnm-core-impl/nm-setting-ip-config.c
+++ b/src/libnm-core-impl/nm-setting-ip-config.c
@@ -1401,7 +1401,7 @@ _ip_route_attribute_validate(const char           *name,
                         NM_CONNECTION_ERROR_FAILED,
                         family == AF_INET ? _("'%s' is not a valid IPv4 address")
                                           : _("'%s' is not a valid IPv6 address"),
-                        string);
+                        addr);
             return FALSE;
         }
         break;
@@ -6960,7 +6960,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * activation will fail. The property is currently implemented only for IPv4.
      *
      * A zero value means that no duplicate address detection is performed, -1 means
-     * the default value (either the value configured globally in NetworkManger.conf
+     * the default value (either the value configured globally in NetworkManager.conf
      * or 200ms).  A value greater than zero is a timeout in milliseconds.  Note that
      * the time intervals are subject to randomization as per RFC 5227 and so the
      * actual duration can be between half and the full time specified in this
@@ -7006,7 +7006,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * 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,
+     * e.g. IPv4, one can ensure that even if IPv6 succeeds earlier than IPv4,
      * NetworkManager waits some time for IPv4 before the connection becomes
      * active.
      *
@@ -7050,8 +7050,7 @@ nm_setting_ip_config_class_init(NMSettingIPConfigClass *klass)
      * values "duid" and "ipv6-duid" to generate the client-id.
      *
      * For DHCPv6, note that at the moment this property is
-     * only supported by the "internal" DHCPv6 plugin. The "dhclient" DHCPv6
-     * plugin always derives the IAID from the MAC address.
+     * only supported by the "internal" DHCPv6 plugin.
      *
      * The actually used DHCPv6 IAID for a currently activated interface is
      * exposed in the lease information of the device.
diff --git a/src/libnm-core-impl/nm-setting-ip4-config.c b/src/libnm-core-impl/nm-setting-ip4-config.c
index a4fccc6d..2eea98f6 100644
--- a/src/libnm-core-impl/nm-setting-ip4-config.c
+++ b/src/libnm-core-impl/nm-setting-ip4-config.c
@@ -40,7 +40,8 @@ NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_DHCP_CLIENT_ID,
                                   PROP_DHCP_FQDN,
                                   PROP_DHCP_VENDOR_CLASS_IDENTIFIER,
                                   PROP_LINK_LOCAL,
-                                  PROP_DHCP_IPV6_ONLY_PREFERRED, );
+                                  PROP_DHCP_IPV6_ONLY_PREFERRED,
+                                  PROP_CLAT, );
 
 typedef struct {
     NMSettingIPConfigPrivate parent;
@@ -50,6 +51,7 @@ typedef struct {
     char  *dhcp_vendor_class_identifier;
     gint32 link_local;
     gint32 dhcp_ipv6_only_preferred;
+    gint32 clat;
 } NMSettingIP4ConfigPrivate;
 
 /**
@@ -168,6 +170,24 @@ nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(NMSettingIP4Config *setting)
     return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->dhcp_ipv6_only_preferred;
 }
 
+/**
+ * nm_setting_ip4_config_get_clat:
+ * @setting: the #NMSettingIP4Config
+ *
+ * Returns the value in the #NMSettingIP4Config:clat property.
+ *
+ * Returns: the CLAT property value
+ *
+ * Since: 1.58
+ */
+NMSettingIp4ConfigClat
+nm_setting_ip4_config_get_clat(NMSettingIP4Config *setting)
+{
+    g_return_val_if_fail(NM_IS_SETTING_IP4_CONFIG(setting), NM_SETTING_IP4_CONFIG_CLAT_DEFAULT);
+
+    return NM_SETTING_IP4_CONFIG_GET_PRIVATE(setting)->clat;
+}
+
 static gboolean
 verify(NMSetting *setting, NMConnection *connection, GError **error)
 {
@@ -186,12 +206,15 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
 
     if (!strcmp(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
         if (nm_setting_ip_config_get_num_addresses(s_ip) == 0
-            && nm_setting_ip_config_get_num_routes(s_ip) == 0) {
-            g_set_error(error,
-                        NM_CONNECTION_ERROR,
-                        NM_CONNECTION_ERROR_MISSING_PROPERTY,
-                        _("method '%s' requires at least an address or a route"),
-                        method);
+            && nm_setting_ip_config_get_num_routes(s_ip) == 0
+            && nm_setting_ip4_config_get_clat(NM_SETTING_IP4_CONFIG(s_ip))
+                   != NM_SETTING_IP4_CONFIG_CLAT_FORCE) {
+            g_set_error(
+                error,
+                NM_CONNECTION_ERROR,
+                NM_CONNECTION_ERROR_MISSING_PROPERTY,
+                _("method '%s' requires at least an address, a route, or CLAT set to 'force'"),
+                method);
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_IP4_CONFIG_SETTING_NAME,
@@ -930,10 +953,7 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      * this is normally not recommended.
      *
      * If unset, a globally configured default from NetworkManager.conf is
-     * used. If still unset, the default depends on the DHCP plugin. The
-     * internal dhcp client will default to "mac" and the dhclient plugin will
-     * try to use one from its config file if present, or won't sent any
-     * client-id otherwise.
+     * used. If still unset, internal dhcp client will default to "mac".
      **/
     /* ---nmcli---
      * property: dhcp-client-id
@@ -1259,7 +1279,7 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
      *        won't get merged. If the route finds no merge partner, it is
      *        configured as single hop route.</para> <para>Note that in
      *        NetworkManager, currently all nexthops of a ECMP route must share
-     *        the same "onlink" flag in order to be mergable.</para>
+     *        the same "onlink" flag in order to be mergeable.</para>
      *      </listitem>
      *      <listitem>
      *        <para><literal>"window"</literal> - an unsigned 32 bit integer.</para>
@@ -1353,18 +1373,25 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
     /**
      * NMSettingIP4Config:dhcp-ipv6-only-preferred
      *
-     * Controls the "IPv6-Only Preferred" DHCPv4 option (RFC 8925).
+     * Controls the "IPv6-Only Preferred" DHCPv4 option (option 108 - 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.
+     * applications are IPv6-only capable or there is a form of CLAT (464XLAT)
+     * deployed.
+     *
+     * If set to %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO, the option is
+     * automatically turned on when the IPv6 method is "auto" and the connection
+     * profile has ipv4.clat set to "yes" or "auto". If these two conditions are
+     * met, the host can operate in IPv6-only mode and therefore it is safe to
+     * disable DHCPv4 when the network also supports it.
      *
      * 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.
+     * %NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO.
      *
      * If the connection has IPv6 method set to "disabled", this property does not
      * have effect and the "IPv6-Only Preferred" option is always disabled.
@@ -1382,6 +1409,39 @@ nm_setting_ip4_config_class_init(NMSettingIP4ConfigClass *klass)
                                             NMSettingIP4ConfigPrivate,
                                             dhcp_ipv6_only_preferred);
 
+    /**
+     * NMSettingIP4Config:clat
+     *
+     * Controls the CLAT (Customer-side translator) functionality. CLAT is used to implement the
+     * client part of 464XLAT (RFC 6877), an architecture that provides IPv4 connectivity to hosts
+     * on IPv6-only networks.
+     *
+     * When CLAT is enabled, NetworkManager discovers the NAT64 prefix from IPv6 Router Advertisements;
+     * if a NAT64 prefix is announced, NetworkManager installs a BPF program to perform the stateless
+     * translation of packets between IPv4 and IPv6.
+     *
+     * Setting %NM_SETTING_IP4_CONFIG_CLAT_NO completely disables CLAT. %NM_SETTING_IP4_CONFIG_CLAT_AUTO
+     * enables CLAT only when the IPv4 method is 'auto' and the device doesn't have a native IPv4 gateway.
+     * %NM_SETTING_IP4_CONFIG_CLAT_FORCE enables CLAT even if the IPv4 method is not 'auto' and even if
+     * the device has a native IPv4 gateway.
+     *
+     * When set to %NM_SETTING_IP4_CONFIG_CLAT_DEFAULT, the actual value is looked up in the global
+     * configuration; if not specified it defaults to %NM_SETTING_IP4_CONFIG_CLAT_NO. In the future the
+     * default fall back value will change to %NM_SETTING_IP4_CONFIG_CLAT_AUTO.
+     *
+     * Since: 1.58
+     */
+    _nm_setting_property_define_direct_enum(properties_override,
+                                            obj_properties,
+                                            NM_SETTING_IP4_CONFIG_CLAT,
+                                            PROP_CLAT,
+                                            NM_TYPE_SETTING_IP4_CONFIG_CLAT,
+                                            NM_SETTING_IP4_CONFIG_CLAT_DEFAULT,
+                                            NM_SETTING_PARAM_NONE,
+                                            NULL,
+                                            NMSettingIP4ConfigPrivate,
+                                            clat);
+
     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 c68be991..25997f75 100644
--- a/src/libnm-core-impl/nm-setting-ip6-config.c
+++ b/src/libnm-core-impl/nm-setting-ip6-config.c
@@ -1265,11 +1265,9 @@ nm_setting_ip6_config_class_init(NMSettingIP6ConfigClass *klass)
      * DUID and filled as an opaque value in the Client Identifier option.
      *
      * The special value "lease" will retrieve the DUID previously used from the
-     * lease file belonging to the connection. If no DUID is found and "dhclient"
-     * is the configured dhcp client, the DUID is searched in the system-wide
-     * dhclient lease file. If still no DUID is found, or another dhcp client is
-     * used, a global and permanent DUID-UUID (RFC 6355) will be generated based
-     * on the machine-id.
+     * lease file belonging to the connection. If no DUID is found, a global
+     * and permanent DUID-UUID (RFC 6355) will be generated based on the
+     * machine-id.
      *
      * The special values "llt" and "ll" will generate a DUID of type LLT or LL
      * (see RFC 3315) based on the current MAC address of the device. In order to
diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h
index 61f96789..b3678d7c 100644
--- a/src/libnm-core-impl/nm-setting-private.h
+++ b/src/libnm-core-impl/nm-setting-private.h
@@ -809,7 +809,8 @@ _nm_properties_override(GArray *properties_override, const NMSettInfoProperty *p
         G_STATIC_ASSERT(!NM_FLAGS_ANY((param_flags),                                              \
                                       ~(NM_SETTING_PARAM_SECRET | NM_SETTING_PARAM_FUZZY_IGNORE   \
                                         | NM_SETTING_PARAM_INFERRABLE | NM_SETTING_PARAM_REQUIRED \
-                                        | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY)));                \
+                                        | NM_SETTING_PARAM_REAPPLY_IMMEDIATELY                    \
+                                        | NM_SETTING_PARAM_CERT_KEY_FILE)));                      \
                                                                                                   \
         nm_assert(_property_type);                                                                \
         nm_assert(g_variant_type_equal(_property_type->dbus_type, "s"));                          \
diff --git a/src/libnm-core-impl/nm-setting-wireless.c b/src/libnm-core-impl/nm-setting-wireless.c
index 1c5486bf..318e864b 100644
--- a/src/libnm-core-impl/nm-setting-wireless.c
+++ b/src/libnm-core-impl/nm-setting-wireless.c
@@ -1086,7 +1086,7 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
                                                NM_SETTING_WIRELESS_MODE_AP,
                                                NM_SETTING_WIRELESS_MODE_MESH,
                                                NULL};
-    const char               *valid_bands[] = {"a", "bg", NULL};
+    const char               *valid_bands[] = {"a", "bg", "6GHz", NULL};
     guint                     i;
     gsize                     length;
     GError                   *local = NULL;
@@ -1363,11 +1363,11 @@ verify(NMSetting *setting, NMConnection *connection, GError **error)
         }
 
         if (priv->channel_width == NM_SETTING_WIRELESS_CHANNEL_WIDTH_80MHZ
-            && !nm_streq0(priv->band, "a")) {
+            && !NM_IN_STRSET(priv->band, "a", "6GHz")) {
             g_set_error_literal(error,
                                 NM_CONNECTION_ERROR,
                                 NM_CONNECTION_ERROR_INVALID_PROPERTY,
-                                _("80MHz channels are only supported in the 5GHz band"));
+                                _("80MHz channels are only supported in the 5GHz and 6GHz bands"));
             g_prefix_error(error,
                            "%s.%s: ",
                            NM_SETTING_WIRELESS_SETTING_NAME,
@@ -1636,17 +1636,18 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
     /**
      * NMSettingWireless:band:
      *
-     * 802.11 frequency band of the network.  One of "a" for 5GHz 802.11a or
-     * "bg" for 2.4GHz 802.11.  This will lock associations to the Wi-Fi network
-     * to the specific band, i.e. if "a" is specified, the device will not
-     * associate with the same network in the 2.4GHz band even if the network's
-     * settings are compatible.  This setting depends on specific driver
-     * capability and may not work with all drivers.
+     * 802.11 frequency band of the network.  One of "a" for 5GHz,
+     * "bg" for 2.4GHz or "6GHz".  This will lock associations to the
+     * Wi-Fi network to the specific band, i.e. if "a" is specified,
+     * the device will not associate with the same network in the
+     * 2.4GHz (bg) or 6GHz bands even if the network's settings are
+     * compatible.  This setting depends on specific driver capability
+     * and may not work with all drivers.
      **/
     /* ---ifcfg-rh---
      * property: band
      * variable: BAND(+)
-     * values: a, bg
+     * values: a, bg, 6GHz
      * description: BAND alone is honored, but CHANNEL overrides BAND since it
      *   implies a band.
      * example: BAND=bg
@@ -1673,8 +1674,6 @@ nm_setting_wireless_class_init(NMSettingWirelessClass *klass)
      * property: channel
      * variable: CHANNEL
      * description: Channel used for the Wi-Fi communication.
-     *   Channels greater than 14 mean "a" band, otherwise the
-     *   band is "bg".
      * example: CHANNEL=6
      * ---end---
      */
diff --git a/src/libnm-core-impl/nm-utils.c b/src/libnm-core-impl/nm-utils.c
index 1bf00831..50862708 100644
--- a/src/libnm-core-impl/nm-utils.c
+++ b/src/libnm-core-impl/nm-utils.c
@@ -25,6 +25,7 @@
 #include "libnm-glib-aux/nm-time-utils.h"
 #include "libnm-glib-aux/nm-secret-utils.h"
 #include "libnm-core-aux-intern/nm-common-macros.h"
+#include "libnm-core-aux-intern/nm-libnm-core-utils.h"
 #include "nm-utils-private.h"
 #include "nm-setting-private.h"
 #include "nm-setting-bond.h"
@@ -3697,43 +3698,78 @@ struct cf_pair {
     guint32 freq;
 };
 
-static const struct cf_pair a_table[] = {
-    /* A band */
-    {7, 5035},   {8, 5040},   {9, 5045},   {11, 5055},  {12, 5060},  {16, 5080},  {34, 5170},
-    {36, 5180},  {38, 5190},  {40, 5200},  {42, 5210},  {44, 5220},  {46, 5230},  {48, 5240},
-    {50, 5250},  {52, 5260},  {56, 5280},  {58, 5290},  {60, 5300},  {64, 5320},  {100, 5500},
-    {104, 5520}, {108, 5540}, {112, 5560}, {116, 5580}, {120, 5600}, {124, 5620}, {128, 5640},
-    {132, 5660}, {136, 5680}, {140, 5700}, {149, 5745}, {152, 5760}, {153, 5765}, {157, 5785},
-    {160, 5800}, {161, 5805}, {165, 5825}, {183, 4915}, {184, 4920}, {185, 4925}, {187, 4935},
-    {188, 4945}, {192, 4960}, {196, 4980}, {0, 0}};
-
-static const guint a_table_freqs[G_N_ELEMENTS(a_table)] = {
-    /* A band */
-    5035, 5040, 5045, 5055, 5060, 5080, 5170, 5180, 5190, 5200, 5210, 5220, 5230, 5240, 5250, 5260,
-    5280, 5290, 5300, 5320, 5500, 5520, 5540, 5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700, 5745,
-    5760, 5765, 5785, 5800, 5805, 5825, 4915, 4920, 4925, 4935, 4945, 4960, 4980, 0,
-};
+static const struct cf_pair table_5ghz[] = {
+    /* Special, 20MHz only */
+    {32, 5160},
+
+    /* UNII-1 (Indoor) */
+    {36, 5180},
+    {40, 5200},
+    {44, 5220},
+    {48, 5240},
+
+    /* UNII-2 (DFS) */
+    {52, 5260},
+    {56, 5280},
+    {60, 5300},
+    {64, 5320},
+
+    /* UNII-2C (DFS) */
+    {100, 5500},
+    {104, 5520},
+    {108, 5540},
+    {112, 5560},
+    {116, 5580},
+    {120, 5600},
+    {124, 5620},
+    {128, 5640},
+    {132, 5660},
+    {136, 5680},
+    {140, 5700},
+    {144, 5720},
+
+    /* UNII-3 */
+    {149, 5745},
+    {153, 5765},
+    {157, 5785},
+    {161, 5805},
+    {165, 5825},
+
+    /* 4.9 GHz Public Safety Band (802.11y/j) */
+    {183, 4915},
+    {184, 4920},
+    {185, 4925},
+    {187, 4935},
+    {188, 4940},
+    {189, 4945},
+    {192, 4960},
+    {196, 4980},
 
-static const struct cf_pair bg_table[] = {
-    /* B/G band */
-    {1, 2412},
-    {2, 2417},
-    {3, 2422},
-    {4, 2427},
-    {5, 2432},
-    {6, 2437},
-    {7, 2442},
-    {8, 2447},
-    {9, 2452},
-    {10, 2457},
-    {11, 2462},
-    {12, 2467},
-    {13, 2472},
-    {14, 2484},
     {0, 0}};
 
-static const guint bg_table_freqs[G_N_ELEMENTS(bg_table)] = {
-    /* B/G band */
+static const guint table_5ghz_freqs[G_N_ELEMENTS(table_5ghz)] = {
+    5160, 5180, 5200, 5220, 5240, 5260, 5280, 5300, 5320, 5500, 5520, 5540,
+    5560, 5580, 5600, 5620, 5640, 5660, 5680, 5700, 5720, 5745, 5765, 5785,
+    5805, 5825, 4915, 4920, 4925, 4935, 4940, 4945, 4960, 4980, 0,
+};
+
+static const struct cf_pair table_2ghz[] = {{1, 2412},
+                                            {2, 2417},
+                                            {3, 2422},
+                                            {4, 2427},
+                                            {5, 2432},
+                                            {6, 2437},
+                                            {7, 2442},
+                                            {8, 2447},
+                                            {9, 2452},
+                                            {10, 2457},
+                                            {11, 2462},
+                                            {12, 2467},
+                                            {13, 2472},
+                                            {14, 2484},
+                                            {0, 0}};
+
+static const guint table_2ghz_freqs[G_N_ELEMENTS(table_2ghz)] = {
     2412,
     2417,
     2422,
@@ -3751,6 +3787,24 @@ static const guint bg_table_freqs[G_N_ELEMENTS(bg_table)] = {
     0,
 };
 
+static const struct cf_pair table_6ghz[] = {
+    {1, 5955},   {5, 5975},   {9, 5995},   {13, 6015},  {17, 6035},  {21, 6055},  {25, 6075},
+    {29, 6095},  {33, 6115},  {37, 6135},  {41, 6155},  {45, 6175},  {49, 6195},  {53, 6215},
+    {57, 6235},  {61, 6255},  {65, 6275},  {69, 6295},  {73, 6315},  {77, 6335},  {81, 6355},
+    {85, 6375},  {89, 6395},  {93, 6415},  {97, 6435},  {101, 6455}, {105, 6475}, {109, 6495},
+    {113, 6515}, {117, 6535}, {121, 6555}, {125, 6575}, {129, 6595}, {133, 6615}, {137, 6635},
+    {141, 6655}, {145, 6675}, {149, 6695}, {153, 6715}, {157, 6735}, {161, 6755}, {169, 6775},
+    {173, 6815}, {177, 6835}, {181, 6855}, {185, 6875}, {189, 6895}, {193, 6915}, {197, 6935},
+    {201, 6955}, {205, 6975}, {209, 6995}, {213, 7015}, {217, 7035}, {221, 7055}, {225, 7075},
+    {229, 7095}, {233, 7115}, {0, 0}};
+
+static const guint table_6ghz_freqs[G_N_ELEMENTS(table_6ghz)] = {
+    5955, 5975, 5995, 6015, 6035, 6055, 6075, 6095, 6115, 6135, 6155, 6175, 6195, 6215, 6235,
+    6255, 6275, 6295, 6315, 6335, 6355, 6375, 6395, 6415, 6435, 6455, 6475, 6495, 6515, 6535,
+    6555, 6575, 6595, 6615, 6635, 6655, 6675, 6695, 6715, 6735, 6755, 6775, 6815, 6835, 6855,
+    6875, 6895, 6915, 6935, 6955, 6975, 6995, 7015, 7035, 7055, 7075, 7095, 7115, 0,
+};
+
 /**
  * nm_utils_wifi_freq_to_channel:
  * @freq: frequency
@@ -3762,42 +3816,51 @@ static const guint bg_table_freqs[G_N_ELEMENTS(bg_table)] = {
 guint32
 nm_utils_wifi_freq_to_channel(guint32 freq)
 {
-    int i = 0;
+    const struct cf_pair *table;
+    int                   i = 0;
 
-    if (freq > 4900) {
-        while (a_table[i].freq && (a_table[i].freq != freq))
-            i++;
-        return a_table[i].chan;
+    if (freq >= _NM_WIFI_FREQ_MIN_6GHZ) {
+        table = table_6ghz;
+    } else if (freq >= _NM_WIFI_FREQ_MIN_5GHZ) {
+        table = table_5ghz;
+    } else {
+        table = table_2ghz;
     }
 
-    while (bg_table[i].freq && (bg_table[i].freq != freq))
+    while (table[i].freq && (table[i].freq != freq))
         i++;
-    return bg_table[i].chan;
+
+    return table[i].chan;
 }
 
 /**
  * nm_utils_wifi_freq_to_band:
  * @freq: frequency
  *
- * Utility function to translate a Wi-Fi frequency to its corresponding band.
+ * Translates a Wi-Fi frequency to its corresponding band.
  *
- * Returns: the band containing the frequency or NULL if freq is invalid
+ * Returns: the band containing the frequency or %NM_WIFI_BAND_UNKNOWN if
+ * the frequency does not belong to a known band.
+ *
+ * Since: 1.58
  **/
-const char *
+NMWifiBand
 nm_utils_wifi_freq_to_band(guint32 freq)
 {
-    if (freq >= 4915 && freq <= 5825)
-        return "a";
-    else if (freq >= 2412 && freq <= 2484)
-        return "bg";
+    if (freq >= _NM_WIFI_FREQ_MIN_2GHZ && freq <= _NM_WIFI_FREQ_MAX_2GHZ)
+        return NM_WIFI_BAND_2_4_GHZ;
+    else if (freq >= _NM_WIFI_FREQ_MIN_5GHZ && freq <= _NM_WIFI_FREQ_MAX_5GHZ)
+        return NM_WIFI_BAND_5_GHZ;
+    else if (freq >= _NM_WIFI_FREQ_MIN_6GHZ && freq <= _NM_WIFI_FREQ_MAX_6GHZ)
+        return NM_WIFI_BAND_6_GHZ;
 
-    return NULL;
+    return NM_WIFI_BAND_UNKNOWN;
 }
 
 /**
  * nm_utils_wifi_channel_to_freq:
  * @channel: channel
- * @band: frequency band for wireless ("a" or "bg")
+ * @band: frequency band for wireless ("a", "bg", "6GHz")
  *
  * Utility function to translate a Wi-Fi channel to its corresponding frequency.
  *
@@ -3808,34 +3871,33 @@ nm_utils_wifi_freq_to_band(guint32 freq)
 guint32
 nm_utils_wifi_channel_to_freq(guint32 channel, const char *band)
 {
-    int i;
+    const struct cf_pair *table;
+    int                   i;
 
     g_return_val_if_fail(band, 0);
 
-    if (nm_streq(band, "a")) {
-        for (i = 0; a_table[i].chan; i++) {
-            if (a_table[i].chan == channel)
-                return a_table[i].freq;
-        }
-        return ((guint32) -1);
+    if (nm_streq0(band, "a")) {
+        table = table_5ghz;
+    } else if (nm_streq0(band, "bg")) {
+        table = table_2ghz;
+    } else if (nm_streq0(band, "6GHz")) {
+        table = table_6ghz;
+    } else {
+        return 0;
     }
 
-    if (nm_streq(band, "bg")) {
-        for (i = 0; bg_table[i].chan; i++) {
-            if (bg_table[i].chan == channel)
-                return bg_table[i].freq;
-        }
-        return ((guint32) -1);
+    for (i = 0; table[i].chan; i++) {
+        if (table[i].chan == channel)
+            return table[i].freq;
     }
-
-    return 0;
+    return ((guint32) -1);
 }
 
 /**
  * nm_utils_wifi_find_next_channel:
  * @channel: current channel
  * @direction: whether going downward (0 or less) or upward (1 or more)
- * @band: frequency band for wireless ("a" or "bg")
+ * @band: frequency band for wireless ("a", "bg", "6GHz")
  *
  * Utility function to find out next/previous Wi-Fi channel for a channel.
  *
@@ -3844,35 +3906,36 @@ nm_utils_wifi_channel_to_freq(guint32 channel, const char *band)
 guint32
 nm_utils_wifi_find_next_channel(guint32 channel, int direction, char *band)
 {
-    size_t                a_size  = G_N_ELEMENTS(a_table);
-    size_t                bg_size = G_N_ELEMENTS(bg_table);
-    const struct cf_pair *pair;
-
-    if (nm_streq(band, "a")) {
-        if (channel < a_table[0].chan)
-            return a_table[0].chan;
-        if (channel > a_table[a_size - 2].chan)
-            return a_table[a_size - 2].chan;
-        pair = &a_table[0];
-    } else if (nm_streq(band, "bg")) {
-        if (channel < bg_table[0].chan)
-            return bg_table[0].chan;
-        if (channel > bg_table[bg_size - 2].chan)
-            return bg_table[bg_size - 2].chan;
-        pair = &bg_table[0];
+    const struct cf_pair *table;
+    guint                 table_size;
+
+    if (nm_streq0(band, "a")) {
+        table      = table_5ghz;
+        table_size = G_N_ELEMENTS(table_5ghz);
+    } else if (nm_streq0(band, "bg")) {
+        table      = table_2ghz;
+        table_size = G_N_ELEMENTS(table_2ghz);
+    } else if (nm_streq0(band, "6GHz")) {
+        table      = table_6ghz;
+        table_size = G_N_ELEMENTS(table_6ghz);
     } else
         g_return_val_if_reached(0);
 
-    while (pair->chan) {
-        if (channel == pair->chan)
+    if (channel < table[0].chan)
+        return table[0].chan;
+    if (channel > table[table_size - 2].chan)
+        return table[table_size - 2].chan;
+
+    while (table->chan) {
+        if (channel == table->chan)
             return channel;
-        if ((channel < (pair + 1)->chan) && (channel > pair->chan)) {
+        if ((channel < (table + 1)->chan) && (channel > table->chan)) {
             if (direction > 0)
-                return (pair + 1)->chan;
+                return (table + 1)->chan;
             else
-                return pair->chan;
+                return table->chan;
         }
-        pair++;
+        table++;
     }
     return 0;
 }
@@ -3880,7 +3943,7 @@ nm_utils_wifi_find_next_channel(guint32 channel, int direction, char *band)
 /**
  * nm_utils_wifi_is_channel_valid:
  * @channel: channel
- * @band: frequency band for wireless ("a" or "bg")
+ * @band: frequency band for wireless ("a", "bg", "6GHz")
  *
  * Utility function to verify Wi-Fi channel validity.
  *
@@ -3930,8 +3993,8 @@ nm_utils_wifi_is_channel_valid(guint32 channel, const char *band)
 const guint *
 nm_utils_wifi_2ghz_freqs(void)
 {
-    _nm_assert_wifi_freqs(bg_table, bg_table_freqs);
-    return bg_table_freqs;
+    _nm_assert_wifi_freqs(table_2ghz, table_2ghz_freqs);
+    return table_2ghz_freqs;
 }
 
 /**
@@ -3946,8 +4009,24 @@ nm_utils_wifi_2ghz_freqs(void)
 const guint *
 nm_utils_wifi_5ghz_freqs(void)
 {
-    _nm_assert_wifi_freqs(a_table, a_table_freqs);
-    return a_table_freqs;
+    _nm_assert_wifi_freqs(table_5ghz, table_5ghz_freqs);
+    return table_5ghz_freqs;
+}
+
+/**
+ * nm_utils_wifi_6ghz_freqs:
+ *
+ * Utility function to return 6 GHz Wi-Fi frequencies (802.11ax/be, Wi-Fi 6E).
+ *
+ * Returns: zero-terminated array of frequencies numbers (in MHz)
+ *
+ * Since: 1.58
+ **/
+const guint *
+nm_utils_wifi_6ghz_freqs(void)
+{
+    _nm_assert_wifi_freqs(table_6ghz, table_6ghz_freqs);
+    return table_6ghz_freqs;
 }
 
 /**
@@ -6427,7 +6506,7 @@ nm_utils_copy_cert_as_user(const char *filename, const char *user, GError **erro
     nm_str_buf_destroy(&info.output_buffer);
     nm_str_buf_destroy(&info.error_buffer);
 
-    mkdir(RUN_CERT_DIR, 0600);
+    mkdir(RUN_CERT_DIR, 0700);
     fd = mkstemp(dst_path);
     if (fd < 0) {
         g_set_error_literal(error,
diff --git a/src/libnm-core-impl/tests/test-general.c b/src/libnm-core-impl/tests/test-general.c
index cd480045..1e2904a1 100644
--- a/src/libnm-core-impl/tests/test-general.c
+++ b/src/libnm-core-impl/tests/test-general.c
@@ -4094,6 +4094,7 @@ test_connection_diff_a_only(void)
              {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_IP4_CONFIG_CLAT, 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},
@@ -11140,7 +11141,7 @@ _do_wifi_ghz_freqs(const guint *freqs, const char *band)
     int j;
     int i;
 
-    g_assert(NM_IN_STRSET(band, "a", "bg"));
+    g_assert(NM_IN_STRSET(band, "a", "bg", "6GHz"));
     g_assert(freqs);
     g_assert(freqs[0] != 0);
 
@@ -11178,6 +11179,7 @@ test_nm_utils_wifi_ghz_freqs(void)
 {
     _do_wifi_ghz_freqs(nm_utils_wifi_2ghz_freqs(), "bg");
     _do_wifi_ghz_freqs(nm_utils_wifi_5ghz_freqs(), "a");
+    _do_wifi_ghz_freqs(nm_utils_wifi_6ghz_freqs(), "6GHz");
 }
 
 /*****************************************************************************/
@@ -11611,6 +11613,328 @@ test_dhcp_iaid_hexstr(void)
 
 /*****************************************************************************/
 
+static void
+test_unreachable_gateways(void)
+{
+    NMConnection        *conn;
+    NMSettingIPConfig   *s_ip4;
+    NMSettingIPConfig   *s_ip6;
+    gs_free const char **result = NULL;
+
+    /* IPv4 gateway reachable via address prefix route */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "192.168.1.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* IPv4 gateway NOT reachable */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "10.0.0.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_address(s_ip4, "172.16.1.1", 16);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 1);
+        g_assert_cmpstr(result[0], ==, "10.0.0.1");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* IPv4 gateway NOT reachable. It's ignored because of method "auto" */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_AUTO,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "10.0.0.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_address(s_ip4, "172.16.1.1", 16);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* Route gateway reachable via address prefix route */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "192.168.1.254", 100);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* Route gateway NOT reachable, check sorting */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 16, "172.16.0.2", 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.1.0.0", 16, "172.16.0.4", 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.2.0.0", 16, "172.16.0.3", 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.3.0.0", 16, "172.16.0.1", 100);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 4);
+        g_assert_cmpstr(result[0], ==, "172.16.0.1");
+        g_assert_cmpstr(result[1], ==, "172.16.0.2");
+        g_assert_cmpstr(result[2], ==, "172.16.0.3");
+        g_assert_cmpstr(result[3], ==, "172.16.0.4");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* Route gateway reachable via a direct route in the setting */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_route(s_ip4, "172.16.0.0", 16, NULL, 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "172.16.0.1", 100);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* No gateways */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* Both default gateway and route gateway unreachable */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "10.0.0.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 8, "172.16.0.1", 100);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 2);
+        g_assert_cmpstr(result[0], ==, "10.0.0.1");
+        g_assert_cmpstr(result[1], ==, "172.16.0.1");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* Test deduplication */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "192.168.1.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.0.0.0", 16, "172.16.0.1", 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.1.0.0", 16, "172.16.0.1", 100);
+        nmtst_setting_ip_config_add_route(s_ip4, "10.2.0.0", 16, "172.16.0.1", 100);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 1);
+        g_assert_cmpstr(result[0], ==, "172.16.0.1");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* IPv6 gateway reachable via address prefix route */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+        g_object_set(s_ip6,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "fd01::1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip6));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* IPv6 gateway NOT reachable */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+        g_object_set(s_ip6,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "fd02::1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip6));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 1);
+        g_assert_cmpstr(result[0], ==, "fd02::1");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* Multiple addresses, gateway reachable via second address */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "10.0.0.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nmtst_setting_ip_config_add_address(s_ip4, "10.0.0.5", 24);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+
+    /* Unreachable gateways in both IPv4 and IPv6 */
+    {
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "10.0.0.1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+        g_object_set(s_ip6,
+                     NM_SETTING_IP_CONFIG_METHOD,
+                     NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
+                     NM_SETTING_IP_CONFIG_GATEWAY,
+                     "fd02::1",
+                     NULL);
+        nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip6));
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(result);
+        g_assert_cmpint(g_strv_length((char **) result), ==, 2);
+        g_assert_cmpstr(result[0], ==, "10.0.0.1");
+        g_assert_cmpstr(result[1], ==, "fd02::1");
+        nm_clear_g_free(&result);
+        g_object_unref(conn);
+    }
+
+    /* Onlink and IPv6-link-local routes */
+    {
+        NMIPRoute *route;
+
+        conn =
+            nmtst_create_minimal_connection("test-ugw", NULL, NM_SETTING_WIRED_SETTING_NAME, NULL);
+
+        s_ip4 = (NMSettingIPConfig *) nm_setting_ip4_config_new();
+        g_object_set(s_ip4, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP4_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip4, "192.168.1.5", 24);
+
+        route = nm_ip_route_new(AF_INET, "10.0.0.1", 8, "192.168.20.1", 100, NULL);
+        g_assert(route);
+        nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE));
+        g_assert(nm_setting_ip_config_add_route(s_ip4, route));
+        nm_ip_route_unref(route);
+
+        nm_connection_add_setting(conn, NM_SETTING(s_ip4));
+
+        s_ip6 = (NMSettingIPConfig *) nm_setting_ip6_config_new();
+        g_object_set(s_ip6, NM_SETTING_IP_CONFIG_METHOD, NM_SETTING_IP6_CONFIG_METHOD_MANUAL, NULL);
+        nmtst_setting_ip_config_add_address(s_ip6, "fd01::10", 64);
+        nm_connection_add_setting(conn, NM_SETTING(s_ip6));
+
+        route = nm_ip_route_new(AF_INET6, "fd02::", 64, "fd03::1111", 100, NULL);
+        g_assert(route);
+        nm_ip_route_set_attribute(route, NM_IP_ROUTE_ATTRIBUTE_ONLINK, g_variant_new_boolean(TRUE));
+        g_assert(nm_setting_ip_config_add_route(s_ip6, route));
+        nm_ip_route_unref(route);
+
+        nmtst_setting_ip_config_add_route(s_ip6, "fd04::", 64, "fe80::1111", 100);
+
+        result = nm_connection_get_unreachable_gateways(conn);
+        g_assert(!result);
+        g_object_unref(conn);
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -11961,6 +12285,7 @@ main(int argc, char **argv)
     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);
+    g_test_add_func("/core/general/test_unreachable_gateways", test_unreachable_gateways);
 
     return g_test_run();
 }
diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c
index f3070c17..b9cafe4a 100644
--- a/src/libnm-core-impl/tests/test-setting.c
+++ b/src/libnm-core-impl/tests/test-setting.c
@@ -4082,13 +4082,12 @@ test_roundtrip_conversion(gconstpointer test_data)
             if (flag == NM_CONNECTION_SERIALIZE_ALL) {
                 s_wg2 = NM_SETTING_WIREGUARD(
                     nm_connection_get_setting(con2, NM_TYPE_SETTING_WIREGUARD));
-
-                if (flag == NM_CONNECTION_SERIALIZE_ALL)
-                    _rndt_wg_peers_assert_equal(s_wg2, wg_peers, TRUE, TRUE, FALSE);
-                else if (flag == NM_CONNECTION_SERIALIZE_WITH_NON_SECRET)
-                    _rndt_wg_peers_assert_equal(s_wg2, wg_peers, FALSE, FALSE, TRUE);
-                else
-                    g_assert_not_reached();
+                _rndt_wg_peers_assert_equal(s_wg2, wg_peers, TRUE, TRUE, FALSE);
+            } else if (flag == NM_CONNECTION_SERIALIZE_WITH_NON_SECRET) {
+                con2  = _connection_new_from_dbus_strict(con_var, FALSE);
+                s_wg2 = NM_SETTING_WIREGUARD(
+                    nm_connection_get_setting(con2, NM_TYPE_SETTING_WIREGUARD));
+                _rndt_wg_peers_assert_equal(s_wg2, wg_peers, FALSE, FALSE, TRUE);
             }
             break;
         }
@@ -5533,7 +5532,7 @@ test_bond_meta(void)
     _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_ACTOR_SYS_PRIO, 0, EINVAL);
     _A(_nm_setting_bond_opt_value_as_u16, set, NM_SETTING_BOND_OPTION_AD_USER_PORT_KEY, 0, EINVAL);
     _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP, 1, 0);
-    _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, 0, 0);
+    _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX, 2, 0);
     _A(_nm_setting_bond_opt_value_as_u8, set, NM_SETTING_BOND_OPTION_ALL_SLAVES_ACTIVE, 0, 0);
     _A(_nm_setting_bond_opt_value_as_intbool, set, NM_SETTING_BOND_OPTION_USE_CARRIER, 1, 0);
     _A(_nm_setting_bond_opt_value_as_intbool,