summary refs log tree commit diff
path: root/src/core/devices/nm-device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/nm-device.c')
-rw-r--r--src/core/devices/nm-device.c671
1 files changed, 370 insertions, 301 deletions
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. */