summary refs log tree commit diff
path: root/src/core/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices')
-rw-r--r--src/core/devices/nm-device-bond.c13
-rw-r--r--src/core/devices/nm-device-ethernet.c12
-rw-r--r--src/core/devices/nm-device-macvlan.c7
-rw-r--r--src/core/devices/nm-device-veth.c2
-rw-r--r--src/core/devices/nm-device-vxlan.c4
-rw-r--r--src/core/devices/nm-device.c671
-rw-r--r--src/core/devices/nm-device.h1
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c33
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c174
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c18
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c40
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.h4
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c46
-rw-r--r--src/core/devices/wwan/nm-modem-manager.c11
14 files changed, 665 insertions, 371 deletions
diff --git a/src/core/devices/nm-device-bond.c b/src/core/devices/nm-device-bond.c
index 39e68e96..06d41a19 100644
--- a/src/core/devices/nm-device-bond.c
+++ b/src/core/devices/nm-device-bond.c
@@ -52,11 +52,12 @@
         NM_SETTING_BOND_OPTION_PACKETS_PER_SLAVE, NM_SETTING_BOND_OPTION_PRIMARY_RESELECT, \
         NM_SETTING_BOND_OPTION_RESEND_IGMP, NM_SETTING_BOND_OPTION_USE_CARRIER,            \
         NM_SETTING_BOND_OPTION_XMIT_HASH_POLICY, NM_SETTING_BOND_OPTION_NUM_GRAT_ARP,      \
-        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX
+        NM_SETTING_BOND_OPTION_PEER_NOTIF_DELAY
 
-#define OPTIONS_REAPPLY_FULL                                     \
-    OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE, \
-        NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET
+#define OPTIONS_REAPPLY_FULL                                                        \
+    OPTIONS_REAPPLY_SUBSET, NM_SETTING_BOND_OPTION_ACTIVE_SLAVE,                    \
+        NM_SETTING_BOND_OPTION_ARP_IP_TARGET, NM_SETTING_BOND_OPTION_NS_IP6_TARGET, \
+        NM_SETTING_BOND_OPTION_ARP_MISSED_MAX
 
 /*****************************************************************************/
 
@@ -501,6 +502,8 @@ _platform_lnk_bond_init_from_setting(NMSettingBond *s_bond, NMPlatformLnkBond *p
     props->lp_interval_has      = props->lp_interval != 1;
     props->tlb_dynamic_lb_has   = NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB);
     props->lacp_active_has      = NM_IN_SET(props->mode, NM_BOND_MODE_8023AD);
+    props->arp_missed_max_has =
+        !NM_IN_SET(props->mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB, NM_BOND_MODE_8023AD);
 }
 
 static void
@@ -907,6 +910,8 @@ reapply_connection(NMDevice *device, NMConnection *con_old, NMConnection *con_ne
     set_bond_arp_ip_targets(device, s_bond);
 
     set_bond_attrs_or_default(device, s_bond, NM_MAKE_STRV(OPTIONS_REAPPLY_SUBSET));
+    if (!NM_IN_SET(mode, NM_BOND_MODE_TLB, NM_BOND_MODE_ALB, NM_BOND_MODE_8023AD))
+        set_bond_attr_or_default(device, s_bond, NM_SETTING_BOND_OPTION_ARP_MISSED_MAX);
 
     _balance_slb_setup(self, con_new);
 }
diff --git a/src/core/devices/nm-device-ethernet.c b/src/core/devices/nm-device-ethernet.c
index 11f691de..b550b10a 100644
--- a/src/core/devices/nm-device-ethernet.c
+++ b/src/core/devices/nm-device-ethernet.c
@@ -1684,11 +1684,13 @@ complete_connection(NMDevice            *device,
 
         con_peer_name = nm_setting_veth_get_peer(s_veth);
         if (con_peer_name) {
-            nm_utils_error_set(error,
-                               NM_UTILS_ERROR_UNKNOWN,
-                               "mismatching veth peer \"%s\"",
-                               con_peer_name);
-            return FALSE;
+            if (!nm_streq(con_peer_name, peer_name)) {
+                nm_utils_error_set(error,
+                                   NM_UTILS_ERROR_UNKNOWN,
+                                   "mismatching veth peer \"%s\"",
+                                   con_peer_name);
+                return FALSE;
+            }
         } else
             g_object_set(s_veth, NM_SETTING_VETH_PEER, peer_name, NULL);
 
diff --git a/src/core/devices/nm-device-macvlan.c b/src/core/devices/nm-device-macvlan.c
index c5bcc91a..d3cbb663 100644
--- a/src/core/devices/nm-device-macvlan.c
+++ b/src/core/devices/nm-device-macvlan.c
@@ -468,7 +468,12 @@ static const NMDBusInterfaceInfoExtended interface_info_device_macvlan = {
             NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NoPromisc",
                                                            "b",
                                                            NM_DEVICE_MACVLAN_NO_PROMISC),
-            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tab", "b", NM_DEVICE_MACVLAN_TAP), ), ),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Tap", "b", NM_DEVICE_MACVLAN_TAP),
+            NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE(
+                "Tab",
+                "b",
+                NM_DEVICE_MACVLAN_TAP,
+                .annotations = NM_GDBUS_ANNOTATION_INFO_LIST_DEPRECATED(), ), ), ),
 };
 
 static void
diff --git a/src/core/devices/nm-device-veth.c b/src/core/devices/nm-device-veth.c
index c4b9e234..8be29fe9 100644
--- a/src/core/devices/nm-device-veth.c
+++ b/src/core/devices/nm-device-veth.c
@@ -53,7 +53,7 @@ update_properties(NMDevice *device)
     nm_device_parent_set_ifindex(device, peer_ifindex);
 
     peer = nm_device_parent_get_device(device);
-    if (peer && NM_IS_DEVICE_VETH(peer) && nm_device_parent_get_ifindex(peer) <= 0)
+    if (peer && NM_IS_DEVICE_VETH(peer) && !nm_device_parent_get_device(peer))
         update_properties(peer);
 }
 
diff --git a/src/core/devices/nm-device-vxlan.c b/src/core/devices/nm-device-vxlan.c
index 4058287c..87844e6f 100644
--- a/src/core/devices/nm-device-vxlan.c
+++ b/src/core/devices/nm-device-vxlan.c
@@ -176,14 +176,14 @@ create_and_realize(NMDevice              *device,
     if (str) {
         if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.local)
             && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.local6))
-            return FALSE;
+            return nm_assert_unreachable_val(FALSE);
     }
 
     str = nm_setting_vxlan_get_remote(s_vxlan);
     if (str) {
         if (!nm_inet_parse_bin(AF_INET, str, NULL, &props.group)
             && !nm_inet_parse_bin(AF_INET6, str, NULL, &props.group6))
-            return FALSE;
+            return nm_assert_unreachable_val(FALSE);
     }
 
     props.tos          = nm_setting_vxlan_get_tos(s_vxlan);
diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c
index a0cf1ee6..e3457e34 100644
--- a/src/core/devices/nm-device.c
+++ b/src/core/devices/nm-device.c
@@ -805,7 +805,7 @@ typedef struct _NMDevicePrivate {
 
     GVariant *ports_variant; /* Array of port devices D-Bus path */
     char     *prop_ip_iface; /* IP interface D-Bus property */
-    GList    *ping_operations;
+    CList     ping_ops_lst_head;
     GSource  *ping_timeout;
 } NMDevicePrivate;
 
@@ -850,7 +850,6 @@ static const char *_activation_func_to_string(ActivationHandleFunc func);
 static void
 _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting);
 static void queued_state_clear(NMDevice *device);
-static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data);
 static void nm_device_start_ip_check(NMDevice *self);
 static void realize_start_setup(NMDevice             *self,
                                 const NMPlatformLink *plink,
@@ -907,10 +906,11 @@ static void concheck_update_state(NMDevice           *self,
 static void sriov_op_cb(GError *error, gpointer user_data);
 
 static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
-static gboolean device_link_changed(gpointer user_data);
-static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
-static void     deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
-static void     carrier_disconnected_action_cancel(NMDevice *self);
+static gboolean    device_link_changed(gpointer user_data);
+static gboolean    _get_maybe_ipv6_disabled(NMDevice *self);
+static void        deactivate_ready(NMDevice *self, NMDeviceStateReason reason);
+static void        carrier_disconnected_action_cancel(NMDevice *self);
+static const char *nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family);
 
 /*****************************************************************************/
 
@@ -1294,7 +1294,7 @@ _prop_get_ipv6_dhcp_duid(NMDevice     *self,
             gint64  time;
             guint32 timestamp;
 
-#define EPOCH_DATETIME_THREE_YEARS (356 * 24 * 3600 * 3)
+#define EPOCH_DATETIME_THREE_YEARS (365 * 24 * 3600 * 3)
 
             /* We want a variable time between the host_id timestamp and three years
              * before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
@@ -1524,6 +1524,50 @@ _prop_get_connection_dnssec(NMDevice *self, NMConnection *connection)
                                                        NM_SETTING_CONNECTION_DNSSEC_DEFAULT);
 }
 
+static NMSettingIp4ConfigClat
+_prop_get_ipv4_clat(NMDevice *self, gboolean do_log)
+{
+    NMSettingIP4Config    *s_ip4 = NULL;
+    NMSettingIp4ConfigClat clat;
+    const char            *method;
+
+    s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
+    if (!s_ip4)
+        return NM_SETTING_IP4_CONFIG_CLAT_NO;
+
+    method = nm_device_get_effective_ip_config_method(self, AF_INET);
+    if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
+        return NM_SETTING_IP4_CONFIG_CLAT_NO;
+
+    clat = nm_setting_ip4_config_get_clat(s_ip4);
+    if (clat == NM_SETTING_IP4_CONFIG_CLAT_DEFAULT) {
+        clat = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                           NM_CON_DEFAULT("ipv4.clat"),
+                                                           self,
+                                                           NM_SETTING_IP4_CONFIG_CLAT_NO,
+                                                           NM_SETTING_IP4_CONFIG_CLAT_FORCE,
+                                                           NM_SETTING_IP4_CONFIG_CLAT_NO);
+    }
+
+    if (clat == NM_SETTING_IP4_CONFIG_CLAT_AUTO
+        && !nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
+        /* clat=auto enables CLAT only with method=auto */
+        clat = NM_SETTING_IP4_CONFIG_CLAT_NO;
+    }
+
+    if (!HAVE_CLAT
+        && NM_IN_SET(clat, NM_SETTING_IP4_CONFIG_CLAT_AUTO, NM_SETTING_IP4_CONFIG_CLAT_FORCE)) {
+        if (do_log) {
+            _NMLOG(clat == NM_SETTING_IP4_CONFIG_CLAT_FORCE ? LOGL_WARN : LOGL_TRACE,
+                   LOGD_DEVICE,
+                   "CLAT will not work because it is disabled at build time");
+        }
+        clat = NM_SETTING_IP4_CONFIG_CLAT_NO;
+    }
+
+    return clat;
+}
+
 static NMMptcpFlags
 _prop_get_connection_mptcp_flags(NMDevice *self, NMConnection *connection)
 {
@@ -1922,26 +1966,43 @@ _prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache
 }
 
 static gboolean
-_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self)
+_prop_get_ipv4_dhcp_ipv6_only_preferred(NMDevice *self, gboolean *out_is_auto)
 {
     NMSettingIP4Config               *s_ip4;
     NMSettingIP4DhcpIpv6OnlyPreferred ipv6_only;
 
+    NM_SET_OUT(out_is_auto, FALSE);
+
     s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
     if (!s_ip4)
         return FALSE;
 
     ipv6_only = nm_setting_ip4_config_get_dhcp_ipv6_only_preferred(s_ip4);
-    if (ipv6_only != NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT)
-        return ipv6_only;
+    if (ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_DEFAULT) {
+        ipv6_only = nm_config_data_get_connection_default_int64(
+            NM_CONFIG_GET_DATA,
+            NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"),
+            self,
+            NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO,
+            NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO,
+            NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_AUTO);
+    }
 
-    return nm_config_data_get_connection_default_int64(
-        NM_CONFIG_GET_DATA,
-        NM_CON_DEFAULT("ipv4.dhcp-ipv6-only-preferred"),
-        self,
-        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO,
-        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
-        NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO);
+    if (NM_IN_SET(ipv6_only,
+                  NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES,
+                  NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_NO))
+        return ipv6_only == NM_SETTING_IP4_DHCP_IPV6_ONLY_PREFERRED_YES;
+
+    /* auto */
+    NM_SET_OUT(out_is_auto, TRUE);
+
+    if (nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET6),
+                  NM_SETTING_IP6_CONFIG_METHOD_AUTO)
+        && _prop_get_ipv4_clat(self, FALSE) != NM_SETTING_IP4_CONFIG_CLAT_NO) {
+        return TRUE;
+    }
+
+    return FALSE;
 }
 
 /**
@@ -3623,6 +3684,7 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co
 {
     NML3ConfigData *l3cd;
     int             ifindex;
+    gs_free char   *gw_warning = NULL;
 
     nm_assert(NM_IS_DEVICE(self));
     nm_assert(!connection || NM_IS_CONNECTION(connection));
@@ -3642,6 +3704,11 @@ nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *co
     nm_l3_config_data_set_dnssec(l3cd, _prop_get_connection_dnssec(self, connection));
     nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self, connection));
     nm_l3_config_data_set_mptcp_flags(l3cd, _prop_get_connection_mptcp_flags(self, connection));
+
+    gw_warning = nm_connection_get_unreachable_gateways_warning(connection, FALSE);
+    if (gw_warning)
+        _LOGW(LOGD_IP, "%s", gw_warning);
+
     return l3cd;
 }
 
@@ -4924,7 +4991,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N
         if (state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
             /* FIXME(l3cfg): MTU handling should be moved to l3cfg. */
             if (l3cd)
-                priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd);
+                priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu_ra(l3cd);
             _commit_mtu(self);
         }
         _dev_ipll4_check_fallback(self, l3cd);
@@ -6345,6 +6412,14 @@ concheck_is_possible(NMDevice *self)
     if (priv->state == NM_DEVICE_STATE_UNKNOWN)
         return FALSE;
 
+    if (!nm_config_data_get_device_config_boolean_by_device(
+            NM_CONFIG_GET_DATA,
+            NM_CONFIG_KEYFILE_KEY_DEVICE_CHECK_CONNECTIVITY,
+            self,
+            TRUE,
+            TRUE))
+        return FALSE;
+
     return TRUE;
 }
 
@@ -6365,8 +6440,10 @@ concheck_periodic_schedule_do(NMDevice *self, int addr_family, gint64 now_ns)
         goto out;
     }
 
-    if (!concheck_is_possible(self))
+    if (!concheck_is_possible(self)) {
+        concheck_update_state(self, addr_family, NM_CONNECTIVITY_UNKNOWN, FALSE);
         goto out;
+    }
 
     nm_assert(now_ns > 0);
     nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0);
@@ -6589,7 +6666,11 @@ concheck_update_interval(NMDevice *self, int addr_family, gboolean check_now)
         concheck_periodic_schedule_do(self, addr_family, 0);
 
         /* also update the fake connectivity state. */
-        concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE);
+        if (concheck_is_possible(self))
+            concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE);
+        else
+            concheck_update_state(self, addr_family, NM_CONNECTIVITY_UNKNOWN, FALSE);
+
         return;
     }
 
@@ -6618,6 +6699,7 @@ concheck_update_state(NMDevice           *self,
     /* @state is a result of the connectivity check. We only expect a precise
      * number of possible values. */
     nm_assert(NM_IN_SET(state,
+                        NM_CONNECTIVITY_UNKNOWN,
                         NM_CONNECTIVITY_LIMITED,
                         NM_CONNECTIVITY_PORTAL,
                         NM_CONNECTIVITY_FULL,
@@ -6941,8 +7023,11 @@ nm_device_check_connectivity(NMDevice                    *self,
                              NMDeviceConnectivityCallback callback,
                              gpointer                     user_data)
 {
-    if (!concheck_is_possible(self))
+    if (!concheck_is_possible(self)) {
+        concheck_update_state(self, AF_INET, NM_CONNECTIVITY_UNKNOWN, FALSE);
+        concheck_update_state(self, AF_INET6, NM_CONNECTIVITY_UNKNOWN, FALSE);
         return NULL;
+    }
 
     concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
     return concheck_start(self, addr_family, callback, user_data, FALSE);
@@ -7149,7 +7234,9 @@ nm_device_controller_release_port(NMDevice           *self,
 
     info = find_port_info(self, port);
 
-    if (info->port_state == PORT_STATE_ATTACHED)
+    if (!info)
+        port_state_str = "(not registered)";
+    else if (info->port_state == PORT_STATE_ATTACHED)
         port_state_str = "(attached)";
     else if (info->port_state == PORT_STATE_NOT_ATTACHED)
         port_state_str = "(not attached)";
@@ -7162,7 +7249,7 @@ nm_device_controller_release_port(NMDevice           *self,
           "controller: release one port " NM_HASH_OBFUSCATE_PTR_FMT "/%s %s%s",
           NM_HASH_OBFUSCATE_PTR(port),
           nm_device_get_iface(port),
-          !info ? "(not registered)" : port_state_str,
+          port_state_str,
           release_type == RELEASE_PORT_TYPE_CONFIG_FORCE
               ? " (force-configure)"
               : (release_type == RELEASE_PORT_TYPE_CONFIG ? " (configure)" : "(no-config)"));
@@ -7191,6 +7278,8 @@ nm_device_controller_release_port(NMDevice           *self,
         if (ret == NM_TERNARY_DEFAULT) {
             port_priv->port_detach_count++;
             port_priv->port_detach_reason = reason;
+        } else {
+            g_object_unref(port);
         }
     }
 
@@ -7798,6 +7887,12 @@ device_link_changed(gpointer user_data)
                                                NM_UNMANAGED_PLATFORM_INIT,
                                                NM_UNMAN_FLAG_OP_SET_MANAGED,
                                                nm_device_get_manage_reason_external(self));
+
+        /* Now that we got UDEV's announcement we need to check ignore-carrier again.
+         * This is because the permanent MAC might have been set or changed. If we don't
+         * recheck we would ignore rules matching by MAC address. */
+        priv->ignore_carrier =
+            nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(nm_config_get()), self);
     }
 
     _dev_unmanaged_check_external_down(self, FALSE, FALSE);
@@ -8330,6 +8425,17 @@ config_changed(NMConfig           *config,
             && !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
             device_init_static_sriov_num_vfs(self);
     }
+
+    if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES) && concheck_is_possible(self)) {
+        /* restart (periodic) connectivity checks if they were previously disabled */
+        if (!nm_config_data_get_device_config_boolean_by_device(
+                old_data,
+                NM_CONFIG_KEYFILE_KEY_DEVICE_CHECK_CONNECTIVITY,
+                self,
+                TRUE,
+                TRUE))
+            nm_device_check_connectivity_update_interval(self);
+    }
 }
 
 static void
@@ -8463,10 +8569,13 @@ realize_start_setup(NMDevice             *self,
     nm_device_update_initial_hw_address(self);
     nm_device_update_permanent_hw_address(self, FALSE);
 
-    /* Note: initial hardware address must be read before calling get_ignore_carrier() */
+    /* Note: initial hardware address must be read before calling get_ignore_carrier(). We'll
+     * need to recheck ignore_carrier again after UDEV's announcement, as the permanent MAC
+     * address may be set by UDEV. */
     config = nm_config_get();
     priv->ignore_carrier =
         nm_config_data_get_ignore_carrier_by_device(nm_config_get_data(config), self);
+
     if (!priv->config_changed_id) {
         priv->config_changed_id = g_signal_connect(config,
                                                    NM_CONFIG_SIGNAL_CONFIG_CHANGED,
@@ -11440,6 +11549,8 @@ _dev_ipmanual_start(NMDevice *self)
         if (_prop_get_ipvx_routed_dns(self, AF_INET6) == NM_SETTING_IP_CONFIG_ROUTED_DNS_YES) {
             nm_l3_config_data_set_routed_dns(l3cd, AF_INET6, TRUE);
         }
+
+        nm_l3_config_data_set_clat_config(l3cd, _prop_get_ipv4_clat(self, TRUE));
     }
 
     if (!l3cd) {
@@ -11748,8 +11859,9 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
         gboolean               hostname_is_fqdn;
         gboolean               send_client_id;
         guint8                 dscp;
-        gboolean               dscp_explicit  = FALSE;
-        gboolean               ipv6_only_pref = FALSE;
+        gboolean               dscp_explicit       = FALSE;
+        gboolean               ipv6_only_pref      = FALSE;
+        gboolean               ipv6_only_pref_auto = FALSE;
 
         client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr, &send_client_id);
         dscp      = _prop_get_ipv4_dhcp_dscp(self, &dscp_explicit);
@@ -11768,13 +11880,15 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family)
             hostname         = nm_setting_ip_config_get_dhcp_hostname(s_ip);
         }
 
-        if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self)) {
+        if (_prop_get_ipv4_dhcp_ipv6_only_preferred(self, &ipv6_only_pref_auto)) {
             if (nm_streq0(priv->ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
                 _LOGI_ipdhcp(
                     addr_family,
                     "not requesting the \"IPv6-only preferred\" option because IPv6 is disabled");
             } else {
-                _LOGD_ipdhcp(addr_family, "requesting the \"IPv6-only preferred\" option");
+                _LOGD_ipdhcp(addr_family,
+                             "requesting the \"IPv6-only preferred\" option (%s enabled)",
+                             ipv6_only_pref_auto ? "automatically" : "explicitly");
                 ipv6_only_pref = TRUE;
             }
         }
@@ -13585,13 +13699,21 @@ activate_stage3_ip_config(NMDevice *self)
                   nm_device_get_ip_iface(self));
     }
 
-    /* We currently will attach ports in the state change NM_DEVICE_STATE_IP_CONFIG above.
-     * Note that kernel changes the MTU of bond ports, so we want to commit the MTU
-     * afterwards!
+    /*
+     * Let's make sure MTU matches what is configured. The reason it's done at this
+     * precise location is twofold:
+     *
+     * (1) Attaching ports above might affect the MTU.
+     *
+     * We currently will attach ports in the state change NM_DEVICE_STATE_IP_CONFIG
+     * above. This might reset the MTU to something different from the bond controller
+     * and it might not be a working configuration. But it's what the user asked for.
+     *
+     * (2) When MTU is under 1280 IPv6 can not work.
      *
-     * This might reset the MTU to something different from the bond controller and
-     * it might not be a working configuration. But it's what the user asked for, so
-     * let's do it! */
+     * Kernel will not expose sysctls, create or accept addresses that are needed for IPv6
+     * configuration when the MTU is too small (under 1280).
+     */
     _commit_mtu(self);
 
     if (!nm_device_managed_type_is_external(self)
@@ -13600,12 +13722,6 @@ activate_stage3_ip_config(NMDevice *self)
             && !NM_IN_STRSET(priv->ipv6_method,
                              NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
                              NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
-            /* Ensure the MTU makes sense. If it was below 1280 the kernel would not
-             * expose any ipv6 sysctls or allow presence of any addresses on the interface,
-             * including LL, which * would make it impossible to autoconfigure MTU to a
-             * correct value. */
-            _commit_mtu(self);
-
             /* Any method past this point requires an IPv6LL address. Use NM-controlled
              * IPv6LL if this is not an assumed connection, since assumed connections
              * will already have IPv6 set up.
@@ -14583,6 +14699,12 @@ check_and_reapply_connection(NMDevice            *self,
 
         reactivate_proxy_config(self);
 
+        /* Reapply may have changed the per-device L3 merge flags (ignore-auto-dns,
+         * ignore-auto-routes, never-default). Re-register the active l3cds so the
+         * refreshed flags reach l3cfg even for sources that are not restarted on
+         * reapply (e.g. DHCPv6 when the NDisc DHCP level is unchanged). */
+        _dev_l3_register_l3cds(self, priv->l3cfg, TRUE, FALSE);
+
         nm_device_l3cfg_commit(
             self,
             NM_FLAGS_HAS(reapply_flags, NM_DEVICE_REAPPLY_FLAGS_PRESERVE_EXTERNAL_IP)
@@ -15588,35 +15710,15 @@ _dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_da
 /*****************************************************************************/
 
 typedef struct {
-    NMLogDomain log_domain;
-    NMDevice   *device;
-    gboolean    ping_addresses_require_all;
-    GSource    *watch;
-    GPid        pid;
-    char       *binary;
-    char       *address;
-    guint       deadline;
-} PingOperation;
-
-static PingOperation *
-ping_operation_new(NMDevice   *self,
-                   NMLogDomain log_domain,
-                   const char *address,
-                   const char *ping_binary,
-                   guint       ping_timeout,
-                   gboolean    ip_ping_addresses_require_all)
-{
-    PingOperation *ping_op = g_new0(PingOperation, 1);
-
-    ping_op->device                     = self;
-    ping_op->log_domain                 = log_domain;
-    ping_op->address                    = g_strdup(address);
-    ping_op->binary                     = g_strdup(ping_binary);
-    ping_op->deadline                   = ping_timeout + 10;
-    ping_op->ping_addresses_require_all = ip_ping_addresses_require_all;
+    char         *addr_str;
+    NMIPAddrTyped addr_bin;
 
-    return ping_op;
-}
+    NMLogDomain   log_domain;
+    NMDevice     *device;
+    GCancellable *cancellable;
+    gboolean      require_all;
+    CList         ping_ops_lst;
+} PingOperation;
 
 static void
 ip_check_pre_up(NMDevice *self)
@@ -15640,188 +15742,154 @@ ip_check_pre_up(NMDevice *self)
 }
 
 static void
-cleanup_ping_operation(PingOperation *ping_op)
+ping_op_cleanup(PingOperation *ping_op)
 {
-    if (ping_op->watch) {
-        nm_clear_g_source_inst(&ping_op->watch);
-    }
-
-    if (ping_op->pid) {
-        nm_utils_kill_child_async(ping_op->pid,
-                                  SIGTERM,
-                                  ping_op->log_domain,
-                                  "ping",
-                                  1000,
-                                  NULL,
-                                  NULL);
-        ping_op->pid = 0;
-    }
-
-    nm_clear_g_free(&ping_op->binary);
-    nm_clear_g_free(&ping_op->address);
+    nm_clear_g_cancellable(&ping_op->cancellable);
+    nm_clear_g_free(&ping_op->addr_str);
+    c_list_unlink_stale(&ping_op->ping_ops_lst);
 
     g_free(ping_op);
 }
 
-static gboolean
-spawn_ping_for_operation(NMDevice *self, PingOperation *ping_op)
-{
-    gs_free char         *str_timeout = NULL;
-    gs_free char         *tmp_str     = NULL;
-    const char           *args[]      = {ping_op->binary,
-                                         "-I",
-                                         nm_device_get_ip_iface(self),
-                                         "-c",
-                                         "1",
-                                         "-w",
-                                         NULL,
-                                         ping_op->address,
-                                         NULL};
-    gs_free_error GError *error       = NULL;
-    gboolean              ret;
-
-    args[6] = str_timeout = g_strdup_printf("%u", ping_op->deadline);
-
-    tmp_str = g_strjoinv(" ", (char **) args);
-    _LOGD(ping_op->log_domain, "ping: running '%s'", tmp_str);
-
-    ret = g_spawn_async("/",
-                        (char **) args,
-                        NULL,
-                        G_SPAWN_DO_NOT_REAP_CHILD,
-                        NULL,
-                        NULL,
-                        &ping_op->pid,
-                        &error);
+static void
+ping_cleanup(NMDevice *self)
+{
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    PingOperation   *ping_op;
 
-    if (ret) {
-        ping_op->watch = nm_g_child_watch_add_source(ping_op->pid, ip_check_ping_watch_cb, ping_op);
-    } else {
-        _LOGD(ping_op->log_domain, "ping: could not spawn %s: %s", ping_op->binary, error->message);
+    while ((ping_op = c_list_first_entry(&priv->ping_ops_lst_head, PingOperation, ping_ops_lst))) {
+        ping_op_cleanup(ping_op);
     }
 
-    return ret;
+    nm_clear_g_source_inst(&priv->ping_timeout);
 }
 
-static gboolean
-respawn_ping_cb(gpointer user_data)
+static void
+ping_host_cb(GObject *source, GAsyncResult *result, gpointer user_data)
 {
-    PingOperation   *ping_op = (PingOperation *) user_data;
-    NMDevice        *self    = ping_op->device;
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
+    NMDevice             *self;
+    NMDevicePrivate      *priv;
+    PingOperation        *ping_op = user_data;
+    gs_free_error GError *error   = NULL;
+    gboolean              success;
+    NMLogDomain           log_domain;
+    gs_free char         *addr_str = NULL;
+
+    success = nm_utils_ping_host_finish(result, &error);
+    if (nm_utils_error_is_cancelled(error))
+        return;
 
-    nm_clear_g_source_inst(&ping_op->watch);
+    self       = NM_DEVICE(ping_op->device);
+    priv       = NM_DEVICE_GET_PRIVATE(self);
+    log_domain = ping_op->log_domain;
+    addr_str   = g_steal_pointer(&ping_op->addr_str);
 
-    if (!spawn_ping_for_operation(self, ping_op)) {
-        priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
-        cleanup_ping_operation(ping_op);
+    if (!success) {
+        /* it should never fail because we set an infinite timeout */
+        nm_assert_not_reached();
+        return;
+    }
 
-        if (g_list_length(priv->ping_operations) == 0) {
-            ip_check_pre_up(self);
+    if (ping_op->require_all) {
+        ping_op_cleanup(ping_op);
+        if (!c_list_is_empty(&priv->ping_ops_lst_head)) {
+            _LOGD(log_domain,
+                  "ping: check on address %s succeeded, waiting for other addresses",
+                  addr_str);
+            return;
         }
     }
 
-    return FALSE;
+    _LOGD(log_domain, "ping: check on address %s succeeded, continuing the activation", addr_str);
+
+    ping_cleanup(self);
+    ip_check_pre_up(self);
 }
 
 static void
-ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
+ping_operation_start(NMDevice      *self,
+                     const char    *addr_str,
+                     NMIPAddrTyped *addr_bin,
+                     gboolean       require_all)
 {
-    PingOperation   *ping_op = (PingOperation *) user_data;
-    NMDevice        *self    = ping_op->device;
-    NMDevicePrivate *priv    = NM_DEVICE_GET_PRIVATE(self);
-    gboolean         success = FALSE;
-
-    if (!ping_op->watch)
-        return;
-
-    nm_clear_g_source_inst(&ping_op->watch);
-    ping_op->pid = 0;
-
-    if (WIFEXITED(status)) {
-        if (WEXITSTATUS(status) == 0) {
-            _LOGD(ping_op->log_domain, "ping: ping succeeded on %s", ping_op->address);
-            success = TRUE;
-        } else {
-            _LOGD(ping_op->log_domain,
-                  "ping: ping failed with error code %d on %s",
-                  WEXITSTATUS(status),
-                  ping_op->address);
-        }
+    PingOperation   *ping_op;
+    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
+    NMIPAddrTyped    addr_bin_local;
+    char             buf[NM_INET_ADDRSTRLEN];
+    int              addr_family;
+    int              ret;
+
+    /* Exactly one of the two must be set */
+    nm_assert(!!addr_str ^ !!addr_bin);
+
+    /* Derive the string address from the binary and vice versa */
+    if (addr_str) {
+        ret = nm_inet_parse_bin_full(AF_UNSPEC,
+                                     FALSE,
+                                     addr_str,
+                                     &addr_family,
+                                     &addr_bin_local.addr.addr_ptr);
+        nm_assert(ret);
+        addr_bin_local.addr_family = addr_family;
+        addr_bin                   = &addr_bin_local;
     } else {
-        _LOGD(ping_op->log_domain,
-              "ping: stopped unexpectedly with status %d on %s",
-              status,
-              ping_op->address);
-    }
-
-    if (success) {
-        if (ping_op->ping_addresses_require_all) {
-            priv->ping_operations = g_list_remove(priv->ping_operations, ping_op);
-            if (g_list_length(priv->ping_operations) == 0) {
-                _LOGD(ping_op->log_domain,
-                      "ping: ip-ping-addresses requires all, all ping checks on ip-ping-addresses "
-                      "succeeded");
-                if (priv->ping_timeout)
-                    nm_clear_g_source_inst(&priv->ping_timeout);
-                ip_check_pre_up(self);
-            }
-            cleanup_ping_operation(ping_op);
-        } else {
-            nm_assert(priv->ping_operations);
-
-            g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
-            priv->ping_operations = NULL;
+        nm_inet_ntop(addr_bin->addr_family, addr_bin->addr.addr_ptr, buf);
+        addr_str = buf;
+    }
+
+    /* When pinging the gateway, the caller must ensure that the IP configuration is ready.
+     * For the ip-ping-addresses property, a valid connection always has may-fail=no for
+     * the families of all the target addresses. Thus, at this point the IP configuration
+     * must also be ready. */
+    nm_assert(priv->ip_data_x[NM_IS_IPv4(addr_bin->addr_family)].state == NM_DEVICE_IP_STATE_READY);
+
+    ping_op  = g_new(PingOperation, 1);
+    *ping_op = (PingOperation) {
+        .device      = self,
+        .cancellable = g_cancellable_new(),
+        .require_all = require_all,
+        .addr_bin    = *addr_bin,
+        .addr_str    = g_strdup(addr_str),
+        .log_domain  = (addr_bin->addr_family == AF_INET) ? LOGD_IP4 : LOGD_IP6,
+    };
 
-            if (priv->ping_timeout)
-                nm_clear_g_source_inst(&priv->ping_timeout);
+    /* Start the asynchronous ping operation */
+    nm_utils_ping_host(ping_op->addr_bin,
+                       nm_device_get_ip_ifindex(self),
+                       0, /* try forever */
+                       ping_op->cancellable,
+                       ping_host_cb,
+                       ping_op);
 
-            _LOGD(ping_op->log_domain,
-                  "ping: ip-ping-addresses requires any, one ping check on ip-ping-addresses "
-                  "succeeded");
-            ip_check_pre_up(self);
-        }
-    } else {
-        /* If ping exited with an error it may have returned early,
-         * wait 1 second and restart it */
-        ping_op->watch = nm_g_timeout_add_seconds_source(1, respawn_ping_cb, ping_op);
-    }
+    c_list_link_tail(&priv->ping_ops_lst_head, &ping_op->ping_ops_lst);
 }
 
 static gboolean
 ip_check_ping_timeout_cb(gpointer user_data)
 {
-    NMDevice        *self = NM_DEVICE(user_data);
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    _LOGW(LOGD_DEVICE, "ping timeout: unreachable gateway or ip-ping-addresses");
-
-    if (priv->ping_operations) {
-        g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
-        priv->ping_operations = NULL;
+    NMDevice                     *self = NM_DEVICE(user_data);
+    NMDevicePrivate              *priv = NM_DEVICE_GET_PRIVATE(self);
+    PingOperation                *ping_op;
+    nm_auto_free_gstring GString *str = NULL;
+
+    if (_LOGW_ENABLED(LOGD_DEVICE)) {
+        str = g_string_new("");
+        c_list_for_each_entry (ping_op, &priv->ping_ops_lst_head, ping_ops_lst) {
+            if (str->len != 0)
+                g_string_append(str, ", ");
+            g_string_append(str, ping_op->addr_str);
+        }
+        _LOGW(LOGD_DEVICE,
+              "ping: the following addresses were not reachable within the timeout: %s",
+              str->str);
     }
 
-    if (priv->ping_timeout)
-        nm_clear_g_source_inst(&priv->ping_timeout);
+    ping_cleanup(self);
     ip_check_pre_up(self);
 
     return FALSE;
 }
 
-static gboolean
-start_ping(NMDevice *self, PingOperation *ping_op)
-{
-    NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
-
-    if (spawn_ping_for_operation(self, ping_op)) {
-        priv->ping_operations = g_list_append(priv->ping_operations, ping_op);
-        return TRUE;
-    }
-
-    cleanup_ping_operation(ping_op);
-    return FALSE;
-}
-
 static void
 nm_device_start_ip_check(NMDevice *self)
 {
@@ -15830,17 +15898,13 @@ nm_device_start_ip_check(NMDevice *self)
     NMSettingConnection *s_con;
     guint                gw_ping_timeout = 0;
     guint                ip_ping_timeout = 0;
-    const char          *ping_binary     = NULL;
-    char                 buf[NM_INET_ADDRSTRLEN];
-    NMLogDomain          log_domain = LOGD_IP4;
-    gboolean             ip_ping_addresses_require_all;
-    gboolean             ping_started = FALSE;
+    gboolean             require_all;
+    NMIPAddrTyped        addr_bin = {};
 
     /* Shouldn't be any active ping here, since IP_CHECK happens after the
      * first IP method completes.  Any subsequently completing IP method doesn't
-     * get checked.
-     */
-    g_return_if_fail(priv->ping_operations == NULL);
+     * get checked. */
+    g_return_if_fail(c_list_is_empty(&priv->ping_ops_lst_head));
     g_return_if_fail(priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
                      || priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY);
 
@@ -15849,100 +15913,71 @@ nm_device_start_ip_check(NMDevice *self)
 
     s_con = nm_connection_get_setting_connection(connection);
     g_assert(s_con);
-    gw_ping_timeout               = nm_setting_connection_get_gateway_ping_timeout(s_con);
-    ip_ping_addresses_require_all = _prop_get_connection_ip_ping_addresses_require_all(self, s_con);
-    ip_ping_timeout               = nm_setting_connection_get_ip_ping_timeout(s_con);
+    gw_ping_timeout = nm_setting_connection_get_gateway_ping_timeout(s_con);
+    ip_ping_timeout = nm_setting_connection_get_ip_ping_timeout(s_con);
+    require_all     = _prop_get_connection_ip_ping_addresses_require_all(self, s_con);
 
-    buf[0] = '\0';
-    if (gw_ping_timeout != 0 && ip_ping_timeout == 0) {
+    /* the timeouts are mutually exclusive */
+    nm_assert(gw_ping_timeout == 0 || ip_ping_timeout == 0);
+
+    if (gw_ping_timeout > 0) {
         const NMPObject      *gw;
         const NML3ConfigData *l3cd;
 
-        _LOGD(LOGD_DEVICE, "starting ping gateway...");
-
         l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
         if (!l3cd) {
             /* pass */
         } else if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY) {
             gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET);
             if (gw) {
-                nm_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway, buf);
-                ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL);
-                log_domain  = LOGD_IP4;
+                addr_bin.addr_family = AF_INET;
+                addr_bin.addr.addr4  = NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway;
             }
         } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY) {
             gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET6);
             if (gw) {
-                nm_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway, buf);
-                ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL);
-                log_domain  = LOGD_IP6;
+                addr_bin.addr_family = AF_INET6;
+                addr_bin.addr.addr6  = NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway;
             }
         }
-    }
-
-    if (buf[0]) {
-        PingOperation *ping_op = ping_operation_new(self,
-                                                    log_domain,
-                                                    buf,
-                                                    ping_binary,
-                                                    gw_ping_timeout,
-                                                    ip_ping_addresses_require_all);
-
-        if (start_ping(self, ping_op))
-            ping_started = TRUE;
-    }
 
-    if (gw_ping_timeout == 0 && ip_ping_timeout != 0) {
+        if (addr_bin.addr_family != AF_UNSPEC) {
+            _LOGD(LOGD_DEVICE,
+                  "starting ping on the IPv%c gateway with a %u seconds timeout",
+                  nm_utils_addr_family_to_char(addr_bin.addr_family),
+                  gw_ping_timeout);
+            ping_operation_start(self, NULL, &addr_bin, require_all);
+        }
+    } else if (ip_ping_timeout > 0) {
         const NML3ConfigData *l3cd;
+        GArray               *ip_ping_addresses;
+        const char *const    *strv;
         guint                 i;
-        GArray            *ip_ping_addresses = _nm_setting_connection_get_ip_ping_addresses(s_con);
-        const char *const *strv = nm_strvarray_get_strv_notempty(ip_ping_addresses, NULL);
 
-        _LOGD(LOGD_DEVICE, "starting ping ip addresses...");
+        ip_ping_addresses = _nm_setting_connection_get_ip_ping_addresses(s_con);
+        strv              = nm_strvarray_get_strv_notnull(ip_ping_addresses, NULL);
 
-        l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
+        _LOGD(LOGD_DEVICE,
+              "starting ping on the ip-ping-addresses with a %u seconds timeout",
+              ip_ping_timeout);
 
+        l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
         if (l3cd) {
             for (i = 0; strv[i]; i++) {
-                const char     *s = strv[i];
-                struct in_addr  ipv4_addr;
-                struct in6_addr ipv6_addr;
-
-                if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
-                    && inet_pton(AF_INET, (const char *) s, &ipv4_addr)) {
-                    ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL);
-                    log_domain  = LOGD_IP4;
-                } else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY
-                           && inet_pton(AF_INET6, (const char *) s, &ipv6_addr)) {
-                    ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL);
-                    log_domain  = LOGD_IP6;
-                } else
-                    continue;
-
-                if (s[0]) {
-                    PingOperation *ping_op = ping_operation_new(self,
-                                                                log_domain,
-                                                                s,
-                                                                ping_binary,
-                                                                ip_ping_timeout,
-                                                                ip_ping_addresses_require_all);
-
-                    if (start_ping(self, ping_op))
-                        ping_started = TRUE;
-                }
+                ping_operation_start(self, strv[i], NULL, require_all);
             }
         }
     }
 
-    if (ping_started) {
+    if (c_list_is_empty(&priv->ping_ops_lst_head)) {
+        /* No ping operation in progress, advance to pre-up */
+        ip_check_pre_up(self);
+    } else {
         priv->ping_timeout =
-            nm_g_timeout_add_seconds_source(gw_ping_timeout ? gw_ping_timeout : ip_ping_timeout,
+            nm_g_timeout_add_seconds_source(ip_ping_timeout > 0 ? ip_ping_timeout : gw_ping_timeout,
                                             ip_check_ping_timeout_cb,
                                             self);
     }
-    /* If no ping was started, just advance to pre_up. */
-    else
-        ip_check_pre_up(self);
 }
 
 /*****************************************************************************/
@@ -17401,17 +17436,11 @@ _cancel_activation(NMDevice *self)
 
     _dispatcher_cleanup(self);
 
-    if (priv->ping_operations) {
-        g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
-        priv->ping_operations = NULL;
-    }
-
-    if (priv->ping_timeout)
-        nm_clear_g_source_inst(&priv->ping_timeout);
-
     _dev_ip_state_cleanup(self, AF_INET, FALSE);
     _dev_ip_state_cleanup(self, AF_INET6, FALSE);
 
+    ping_cleanup(self);
+
     /* Break the activation chain */
     activation_source_clear(self);
 }
@@ -17968,6 +17997,14 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
             nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
         }
         break;
+    case NM_DEVICE_STATE_DEACTIVATING:
+        /* When deactivating, certain devices are removed/disconnected after the
+         * STATE_CHANGED signal is sent and before the DHCP release packet
+         * can be sent. To ensure the release packet is sent, we cleanup DHCP
+         * before the signal is emitted*/
+        _dev_ipdhcpx_cleanup(self, AF_INET, TRUE, FALSE);
+        _dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, FALSE);
+        break;
     case NM_DEVICE_STATE_DISCONNECTED:
         if (old_state > NM_DEVICE_STATE_DISCONNECTED) {
             /* Ensure devices that previously assumed a connection now have
@@ -18017,6 +18054,7 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
                                (guint32) state,
                                (guint32) old_state,
                                (guint32) reason);
+
     g_signal_emit(self,
                   signals[STATE_CHANGED],
                   0,
@@ -18167,13 +18205,8 @@ _set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason,
         break;
     }
     case NM_DEVICE_STATE_SECONDARIES:
-        if (priv->ping_operations) {
-            g_list_free_full(priv->ping_operations, (GDestroyNotify) cleanup_ping_operation);
-            priv->ping_operations = NULL;
-        }
-        if (priv->ping_timeout)
-            nm_clear_g_source_inst(&priv->ping_timeout);
         _LOGD(LOGD_DEVICE, "device entered SECONDARIES state");
+        ping_cleanup(self);
         break;
     default:
         break;
@@ -19153,14 +19186,14 @@ nm_device_get_supplicant_timeout(NMDevice *self)
                                                        SUPPLICANT_DEFAULT_TIMEOUT);
 }
 
-gboolean
-nm_device_auth_retries_try_next(NMDevice *self)
+static int
+_device_get_auth_retries(NMDevice *self)
 {
     NMDevicePrivate     *priv;
     NMSettingConnection *s_con;
     int                  auth_retries;
 
-    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+    g_return_val_if_fail(NM_IS_DEVICE(self), 0);
 
     priv         = NM_DEVICE_GET_PRIVATE(self);
     auth_retries = priv->auth_retries;
@@ -19192,13 +19225,47 @@ nm_device_auth_retries_try_next(NMDevice *self)
         priv->auth_retries = auth_retries;
     }
 
+    return auth_retries;
+}
+
+gboolean
+nm_device_auth_retries_has_next(NMDevice *self)
+{
+    int auth_retries;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+
+    auth_retries = _device_get_auth_retries(self);
+
+    if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY)
+        return TRUE;
+
+    if (auth_retries > 0)
+        return TRUE;
+
+    return FALSE;
+}
+
+gboolean
+nm_device_auth_retries_try_next(NMDevice *self)
+{
+    NMDevicePrivate *priv;
+    int              auth_retries;
+
+    g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
+
+    priv         = NM_DEVICE_GET_PRIVATE(self);
+    auth_retries = _device_get_auth_retries(self);
+
     if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY)
         return TRUE;
     if (auth_retries <= 0) {
         nm_assert(auth_retries == 0);
         return FALSE;
     }
+
     priv->auth_retries--;
+
     return TRUE;
 }
 
@@ -19838,6 +19905,8 @@ nm_device_init(NMDevice *self)
     priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
     priv->ip6_saved_properties  = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
 
+    c_list_init(&priv->ping_ops_lst_head);
+
     priv->managed_type_ = NM_DEVICE_MANAGED_TYPE_EXTERNAL;
     /* If networking is already disabled at boot, we want to manage all devices
      * after re-enabling networking; hence, the initial state is MANAGED. */
diff --git a/src/core/devices/nm-device.h b/src/core/devices/nm-device.h
index 2f287953..c8069d7c 100644
--- a/src/core/devices/nm-device.h
+++ b/src/core/devices/nm-device.h
@@ -791,6 +791,7 @@ void     nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_fr
 void     nm_device_update_dynamic_ip_setup(NMDevice *self, const char *reason);
 guint    nm_device_get_supplicant_timeout(NMDevice *self);
 
+gboolean nm_device_auth_retries_has_next(NMDevice *self);
 gboolean nm_device_auth_retries_try_next(NMDevice *self);
 
 gboolean nm_device_hw_addr_get_cloned(NMDevice     *self,
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index fa6e2f9d..94b9a7d7 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -2270,6 +2270,37 @@ add_new:
     return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+static void
+set_powersave(NMDevice *device)
+{
+    NMDeviceIwd               *self = NM_DEVICE_IWD(device);
+    NMSettingWireless         *s_wireless;
+    NMSettingWirelessPowersave val;
+
+    s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
+
+    g_return_if_fail(s_wireless);
+
+    val = nm_setting_wireless_get_powersave(s_wireless);
+    if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) {
+        val = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                          "wifi.powersave",
+                                                          device,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_ENABLE,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE);
+    }
+
+    _LOGT(LOGD_WIFI, "powersave is set to %u", (unsigned) val);
+
+    if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE)
+        return;
+
+    nm_platform_wifi_set_powersave(nm_device_get_platform(device),
+                                   nm_device_get_ifindex(device),
+                                   val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE);
+}
+
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -2297,6 +2328,8 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             goto out_fail;
         }
 
+        set_powersave(device);
+
         /* With priv->iwd_autoconnect we have to let IWD handle retries for
          * infrastructure networks.  IWD will not necessarily retry the same
          * network after a failure but it will likely go into an autoconnect
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index b41ed5e1..b836c1e8 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -194,6 +194,9 @@ static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
 static void supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface,
                                                         NMDeviceWifi          *self);
 
+static void supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface,
+                                                        NMDeviceWifi          *self);
+
 static void periodic_update(NMDeviceWifi *self);
 
 static void ap_add_remove(NMDeviceWifi *self,
@@ -631,6 +634,10 @@ supplicant_interface_acquire_cb(NMSupplicantManager         *supplicant_manager,
                      NM_SUPPLICANT_INTERFACE_PSK_MISMATCH,
                      G_CALLBACK(supplicant_iface_notify_wpa_psk_mismatch_cb),
                      self);
+    g_signal_connect(priv->sup_iface,
+                     NM_SUPPLICANT_INTERFACE_SAE_MISMATCH,
+                     G_CALLBACK(supplicant_iface_notify_wpa_sae_mismatch_cb),
+                     self);
 
     _scan_notify_is_scanning(self);
 
@@ -2191,21 +2198,16 @@ supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
 
     val_key = g_variant_lookup_value(credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
     if (val_key) {
-        char psk[64];
+        char psk[65];
 
         array = g_variant_get_fixed_array(val_key, &psk_len, 1);
-        if (psk_len >= 8 && psk_len <= 63) {
-            memcpy(psk, array, psk_len);
-            psk[psk_len] = '\0';
-            if (g_utf8_validate(psk, psk_len, NULL)) {
-                secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]",
-                                               NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-                                               NM_SETTING_WIRELESS_SECURITY_PSK,
-                                               psk);
-                g_variant_ref_sink(secrets);
-            }
-        }
-        if (!secrets)
+        if (nm_wifi_utils_wps_key_to_psk((const guint8 *) array, psk_len, &psk)) {
+            secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]",
+                                           NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+                                           NM_SETTING_WIRELESS_SECURITY_PSK,
+                                           psk);
+            g_variant_ref_sink(secrets);
+        } else
             _LOGW(LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
     }
 
@@ -2244,6 +2246,26 @@ wps_timeout_cb(gpointer user_data)
     return G_SOURCE_REMOVE;
 }
 
+static gboolean
+wifi_connection_is_new(NMDeviceWifi *self)
+{
+    NMDevice             *device = NM_DEVICE(self);
+    NMActRequest         *req;
+    NMSettingsConnection *connection;
+    guint64               timestamp = 0;
+
+    req = nm_device_get_act_request(device);
+    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), TRUE);
+
+    connection = nm_act_request_get_settings_connection(req);
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(connection), TRUE);
+
+    if (nm_settings_connection_get_timestamp(connection, &timestamp) && timestamp != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
 static void
 wifi_secrets_get_secrets(NMDeviceWifi                *self,
                          const char                  *setting_name,
@@ -2398,10 +2420,11 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
                               NMSupplicantInterfaceState old_state,
                               int                        disconnect_reason)
 {
-    NMDevice     *device = NM_DEVICE(self);
-    NMActRequest *req;
-    const char   *setting_name = NULL;
-    gboolean      handled      = FALSE;
+    NMDevice                    *device = NM_DEVICE(self);
+    NMActRequest                *req;
+    const char                  *setting_name = NULL;
+    NMSecretAgentGetSecretsFlags secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                                | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
     g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE);
 
@@ -2411,8 +2434,7 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
     req = nm_device_get_act_request(NM_DEVICE(self));
     g_return_val_if_fail(req != NULL, FALSE);
 
-    if (need_new_8021x_secrets(self, old_state, &setting_name)
-        || need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) {
+    if (need_new_8021x_secrets(self, old_state, &setting_name)) {
         nm_act_request_clear_secrets(req);
 
         _LOGI(LOGD_DEVICE | LOGD_WIFI,
@@ -2422,14 +2444,54 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_NEED_AUTH,
                                 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
-        wifi_secrets_get_secrets(self,
-                                 setting_name,
-                                 NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-                                     | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
-        handled = TRUE;
+        wifi_secrets_get_secrets(self, setting_name, secret_flags);
+        return TRUE;
+    }
+
+    if (need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) {
+        nm_act_request_clear_secrets(req);
+        cleanup_association_attempt(self, TRUE);
+
+        if (wifi_connection_is_new(self)) {
+            _LOGI(LOGD_DEVICE | LOGD_WIFI,
+                  "Activation: (wifi) new connection disconnected during association, asking for "
+                  "new key");
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_NEED_AUTH,
+                                    NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+            wifi_secrets_get_secrets(self, setting_name, secret_flags);
+            return TRUE;
+        }
+
+        if (!nm_device_auth_retries_try_next(device)) {
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_FAILED,
+                                    NM_DEVICE_STATE_REASON_NO_SECRETS);
+            return TRUE;
+        }
+
+        if (nm_device_auth_retries_has_next(device)) {
+            secret_flags &= ~NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+            _LOGI(
+                LOGD_DEVICE | LOGD_WIFI,
+                "Activation: (wifi) disconnected during association, reauthenticating connection");
+        } else {
+            _LOGI(LOGD_DEVICE | LOGD_WIFI,
+                  "Activation: (wifi) disconnected during association, asking for new key");
+        }
+
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_NEED_AUTH,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+        wifi_secrets_get_secrets(self, setting_name, secret_flags);
+
+        return TRUE;
     }
 
-    return handled;
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) disconnected during association, retrying connection");
+
+    return FALSE;
 }
 
 static gboolean
@@ -2861,6 +2923,12 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi
     if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
         return;
 
+    if (!wifi_connection_is_new(self) && nm_device_auth_retries_has_next(device)) {
+        _LOGI(LOGD_DEVICE | LOGD_WIFI,
+              "Activation: (wifi) psk mismatch reported by supplicant, retrying connection");
+        return;
+    }
+
     _LOGI(LOGD_DEVICE | LOGD_WIFI,
           "Activation: (wifi) psk mismatch reported by supplicant, asking for new key");
 
@@ -2879,6 +2947,34 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi
                                  | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
 }
 
+static void
+supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self)
+{
+    NMDevice     *device = NM_DEVICE(self);
+    NMActRequest *req;
+    const char   *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+
+    if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
+        return;
+
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) SAE password mismatch reported by supplicant, asking for new key");
+
+    req = nm_device_get_act_request(NM_DEVICE(self));
+    g_return_if_fail(req != NULL);
+
+    nm_act_request_clear_secrets(req);
+
+    cleanup_association_attempt(self, TRUE);
+    nm_device_state_changed(device,
+                            NM_DEVICE_STATE_NEED_AUTH,
+                            NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+    wifi_secrets_get_secrets(self,
+                             setting_name,
+                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                 | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+}
+
 /*
  * supplicant_connection_timeout_cb
  *
@@ -3222,8 +3318,19 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap)
 {
-    guint32     a_freqs[]  = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0};
-    guint32     bg_freqs[] = {2412, 2437, 2462, 2472, 0};
+    guint32     freqs_a[]    = {5180, /* only U-NII-1 channels: non-DFS and available everywhere */
+                                5200,
+                                5220,
+                                5240,
+                                0};
+    guint32     freqs_bg[]   = {2412, 2437, 2462, 2472, 0};
+    guint32     freqs_6ghz[] = {5975, /* only U-NII-5 PSC channels, for better compatibility */
+                                6055,
+                                6135,
+                                6215,
+                                6295,
+                                6375,
+                                0};
     guint32    *rnd_freqs;
     guint       rnd_freqs_len;
     NMDevice   *device = NM_DEVICE(self);
@@ -3234,7 +3341,7 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
     guint       l;
 
     nm_assert(ap);
-    nm_assert(NM_IN_STRSET(band, NULL, "a", "bg"));
+    nm_assert(NM_IN_STRSET(band, NULL, "a", "bg", "6GHz"));
 
     if (nm_wifi_ap_get_freq(ap))
         return;
@@ -3268,11 +3375,14 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
     }
 
     if (nm_streq0(band, "a")) {
-        rnd_freqs     = a_freqs;
-        rnd_freqs_len = G_N_ELEMENTS(a_freqs) - 1;
+        rnd_freqs     = freqs_a;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_a) - 1;
+    } else if (nm_streq0(band, "6GHz")) {
+        rnd_freqs     = freqs_6ghz;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_6ghz) - 1;
     } else {
-        rnd_freqs     = bg_freqs;
-        rnd_freqs_len = G_N_ELEMENTS(bg_freqs) - 1;
+        rnd_freqs     = freqs_bg;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_bg) - 1;
     }
 
     /* shuffle the frequencies (inplace). The idea is to choose
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index ceb954b7..3e758972 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -574,16 +574,6 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64
     return str_buf;
 }
 
-static guint
-freq_to_band(guint32 freq)
-{
-    if (freq >= 4915 && freq <= 5825)
-        return 5;
-    else if (freq >= 2412 && freq <= 2484)
-        return 2;
-    return 0;
-}
-
 gboolean
 nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 {
@@ -631,12 +621,12 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 
     band = nm_setting_wireless_get_band(s_wireless);
     if (band) {
-        guint ap_band = freq_to_band(priv->freq);
+        const char *ap_band = nm_wifi_freq_to_band_prop(priv->freq);
 
-        if (!strcmp(band, "a") && ap_band != 5)
-            return FALSE;
-        else if (!strcmp(band, "bg") && ap_band != 2)
+        if (!nm_streq(band, ap_band))
             return FALSE;
+
+        return TRUE;
     }
 
     channel = nm_setting_wireless_get_channel(s_wireless);
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index 332352ab..2ee4ec2e 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -639,7 +639,7 @@ nm_wifi_utils_complete_connection(GBytes       *ap_ssid,
                 chan_valid = FALSE;
             }
 
-            band = nm_utils_wifi_freq_to_band(ap_freq);
+            band = nm_wifi_freq_to_band_prop(ap_freq);
             if (band) {
                 g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL);
             } else {
@@ -890,6 +890,28 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid)
     return FALSE;
 }
 
+/* Convert a WPS "Key" credential into a PSK string. The key is either an
+ * 8..63 character passphrase or a 64 character hexadecimal PSK. The actual
+ * WPA-PSK validity check is shared with nm_utils_wpa_psk_valid(). */
+gboolean
+nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65])
+{
+    if (key_len > 64)
+        return FALSE;
+    if (key_len < 64 && !g_utf8_validate((const char *) key, key_len, NULL))
+        return FALSE;
+
+    memcpy(*out_psk, key, key_len);
+    (*out_psk)[key_len] = '\0';
+
+    /* An embedded NUL would make nm_utils_wpa_psk_valid() see a truncated
+     * string, so reject it explicitly. */
+    if (strlen(*out_psk) != key_len)
+        return FALSE;
+
+    return nm_utils_wpa_psk_valid(*out_psk);
+}
+
 /* To be used for connections where the SSID has been validated before */
 gboolean
 nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
@@ -1929,3 +1951,19 @@ nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b)
     return a->source == b->source && a->sink == b->sink && a->port == b->port
            && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp;
 }
+
+const char *
+nm_wifi_freq_to_band_prop(guint32 freq)
+{
+    switch (nm_utils_wifi_freq_to_band(freq)) {
+    case NM_WIFI_BAND_2_4_GHZ:
+        return "bg";
+    case NM_WIFI_BAND_5_GHZ:
+        return "a";
+    case NM_WIFI_BAND_6_GHZ:
+        return "6GHz";
+    default:
+    case NM_WIFI_BAND_UNKNOWN:
+        return NULL;
+    }
+}
diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h
index 1d46a900..30920396 100644
--- a/src/core/devices/wifi/nm-wifi-utils.h
+++ b/src/core/devices/wifi/nm-wifi-utils.h
@@ -42,6 +42,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes       *ssid,
 
 gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid);
 
+gboolean nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65]);
+
 gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
                                                       char                **ssid,
                                                       NMIwdNetworkSecurity *security);
@@ -56,4 +58,6 @@ bool    nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd);
 GBytes *nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd);
 bool    nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b);
 
+const char *nm_wifi_freq_to_band_prop(guint32 freq);
+
 #endif /* __NM_WIFI_UTILS_H__ */
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index a52696ea..b50e2626 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -1459,6 +1459,50 @@ test_ssids_options_to_ptrarray(void)
 
 /*****************************************************************************/
 
+static void
+test_wps_key_to_psk(void)
+{
+    static const struct {
+        const char *key;
+        gsize       key_len;
+        gboolean    valid;
+    } cases[] = {
+        {"12345678", 8, TRUE},
+        {"supersecret123", 14, TRUE},
+        {"caf\xc3\xa9_key", 9, TRUE},
+        {"123456789012345678901234567890123456789012345678901234567890123", 63, TRUE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 64, TRUE},
+        {"0123456789ABCDEF0123456789abcdef0123456789abcdef0123456789abcdeF", 64, TRUE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg", 64, FALSE},
+        {"", 0, FALSE},
+        {"1234567", 7, FALSE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", 65, FALSE},
+        {"abc\xff"
+         "def123",
+         10,
+         FALSE},
+        {"0123456789abcdef0123456789abcdef\0"
+         "23456789abcdef0123456789abcdef0",
+         64,
+         FALSE},
+    };
+    gsize i;
+
+    for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+        char     psk[65];
+        gboolean ok;
+
+        ok = nm_wifi_utils_wps_key_to_psk((const guint8 *) cases[i].key, cases[i].key_len, &psk);
+        g_assert_cmpint(ok, ==, cases[i].valid);
+        if (ok) {
+            g_assert_cmpmem(psk, cases[i].key_len, cases[i].key, cases[i].key_len);
+            g_assert_cmpint(psk[cases[i].key_len], ==, '\0');
+        }
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -1605,5 +1649,7 @@ main(int argc, char **argv)
 
     g_test_add_func("/wifi/ssids_options_to_ptrarray", test_ssids_options_to_ptrarray);
 
+    g_test_add_func("/wifi/wps_key_to_psk", test_wps_key_to_psk);
+
     return g_test_run();
 }
diff --git a/src/core/devices/wwan/nm-modem-manager.c b/src/core/devices/wwan/nm-modem-manager.c
index 4a89f38e..b8438e3f 100644
--- a/src/core/devices/wwan/nm-modem-manager.c
+++ b/src/core/devices/wwan/nm-modem-manager.c
@@ -258,16 +258,7 @@ modm_handle_name_owner_changed(MMManager *modem_manager, GParamSpec *pspec, NMMo
     /* Available! */
     g_free(name_owner);
 
-    /* Hack alert: GDBusObjectManagerClient won't signal neither 'object-added'
-     * nor 'object-removed' if it was created while there was no ModemManager in
-     * the bus. This hack avoids this issue until we get a GIO with the fix
-     * included... */
-    modm_clear_manager(self);
-    modm_ensure_manager(self);
-
-    /* Whenever GDBusObjectManagerClient is fixed, we can just do the following:
-     * modm_manager_available (self);
-     */
+    modm_manager_available(self);
 }
 
 static void