From e77e846cb7b16c491da32370da7fac1c8aaf8242 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 11 Dec 2025 18:13:45 +0100 Subject: [PATCH 1/3] core: introduce nm_utils_ping_host() Introduce a function that pings a given host. It opens a "ping socket" (IPPROTO_ICMP), binds it to the given ifindex, connects it to the remote address, and keep sending ICMP echo-request packets until it receives a reply or the optional timeout is reached. By using this kind of socket, the kernel automatically sets the ICMP ID on outgoing packets and matches incoming packets by the same ID. --- src/core/nm-core-utils.c | 296 +++++++++++++++++++++++++++++++++++++++ src/core/nm-core-utils.h | 11 ++ 2 files changed, 307 insertions(+) diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 5404ecb9ce..2c2e282ff7 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "libnm-glib-aux/nm-uuid.h" #include "libnm-platform/nmp-base.h" @@ -5002,6 +5004,297 @@ NM_UTILS_LOOKUP_STR_DEFINE(nm_activation_type_to_string, /*****************************************************************************/ +typedef struct { + NMIPAddrTyped address; + char *addr_str; + GTask *task; + GSource *timeout_source; + GSource *retry_source; + GSource *input_source; + gulong cancellable_id; + int ifindex; + int socket; + guint16 seq; +} PingInfo; + +#define _NMLOG2_PREFIX_NAME "ping" +#define _NMLOG2_DOMAIN LOGD_CORE +#define _NMLOG2(level, info, ...) \ + G_STMT_START \ + { \ + if (nm_logging_enabled((level), (_NMLOG2_DOMAIN))) { \ + PingInfo *_info = (info); \ + \ + _nm_log((level), \ + (_NMLOG2_DOMAIN), \ + 0, \ + NULL, \ + NULL, \ + _NMLOG2_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",if=%d,%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(_info), \ + _info->ifindex, \ + _info->addr_str _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + } \ + G_STMT_END + +static void +ping_complete(PingInfo *info, GError *error) +{ + nm_clear_g_cancellable_disconnect(g_task_get_cancellable(info->task), &info->cancellable_id); + + if (error && !nm_utils_error_is_cancelled(error)) { + _LOG2T(info, "terminated with error: %s", error->message); + } + + if (error) + g_task_return_error(info->task, error); + else + g_task_return_boolean(info->task, TRUE); + + nm_clear_g_source_inst(&info->timeout_source); + nm_clear_g_source_inst(&info->retry_source); + nm_clear_g_source_inst(&info->input_source); + nm_clear_g_free(&info->addr_str); + nm_clear_fd(&info->socket); + g_object_unref(info->task); + + g_free(info); +} + +static gboolean +ping_socket_data_cb(int fd, GIOCondition condition, gpointer user_data) +{ + PingInfo *info = user_data; + ssize_t len; + union { + struct icmphdr icmph; + struct icmp6_hdr icmp6h; + } pkt; + + len = recv(fd, &pkt, sizeof(pkt), 0); + + if (len < 0) + return G_SOURCE_CONTINUE; + + if (info->address.addr_family == AF_INET) { + if (len >= sizeof(struct icmphdr) && pkt.icmph.type == ICMP_ECHOREPLY) { + _LOG2T(info, "received echo-reply with seq %hu", ntohs(pkt.icmph.un.echo.sequence)); + ping_complete(info, NULL); + return G_SOURCE_CONTINUE; + } + } else { + if (len >= sizeof(struct icmp6_hdr) && pkt.icmp6h.icmp6_type == ICMP6_ECHO_REPLY) { + _LOG2T(info, "received echo-reply with seq %hu", ntohs(pkt.icmp6h.icmp6_seq)); + ping_complete(info, NULL); + return G_SOURCE_CONTINUE; + } + } + + return G_SOURCE_CONTINUE; +} + +static void +ping_send(PingInfo *info) +{ + const bool IS_IPv4 = NM_IS_IPv4(info->address.addr_family); + union { + struct sockaddr_in6 sa6; + struct sockaddr_in sa4; + } sa; + union { + struct icmphdr icmph; + struct icmp6_hdr icmp6h; + } pkt; + socklen_t sa_len; + size_t pkt_len; + nm_be32_t ifindex_be; + int errsv; + + info->seq++; + + if (info->socket < 0) { + info->socket = socket(info->address.addr_family, + SOCK_DGRAM | SOCK_CLOEXEC, + IS_IPv4 ? IPPROTO_ICMP : IPPROTO_ICMPV6); + if (info->socket < 0) { + errsv = errno; + _LOG2T(info, "socket creation failed: %s", nm_strerror_native(errsv)); + /* Try again at the next iteration */ + return; + } + + memset(&sa, 0, sizeof(sa)); + if (IS_IPv4) { + sa.sa4.sin_family = AF_INET; + sa.sa4.sin_addr.s_addr = info->address.addr.addr4; + sa_len = sizeof(struct sockaddr_in); + } else { + sa.sa6.sin6_family = AF_INET6; + sa.sa6.sin6_addr = info->address.addr.addr6; + if (IN6_IS_ADDR_LINKLOCAL(&info->address.addr.addr6)) + sa.sa6.sin6_scope_id = info->ifindex; + sa_len = sizeof(struct sockaddr_in6); + } + + /* setsockopt(IP*_UNICAST_IF) must be called *before* connecting + * the socket, otherwise it doesn't have any effect */ + ifindex_be = htonl(info->ifindex); + if (setsockopt(info->socket, + IS_IPv4 ? IPPROTO_IP : IPPROTO_IPV6, + IS_IPv4 ? IP_UNICAST_IF : IPV6_UNICAST_IF, + &ifindex_be, + sizeof(ifindex_be))) { + errsv = errno; + _LOG2T(info, + "failed to bind the socket to the interface: %s", + nm_strerror_native(errsv)); + /* Try again at the next iteration */ + nm_clear_fd(&info->socket); + return; + } + + /* Connect the socket so that the kernel only delivers us packets + * coming from the given remote address */ + if (connect(info->socket, (struct sockaddr *) &sa, sa_len) < 0) { + errsv = errno; + _LOG2T(info, "failed to connect the socket: %s", nm_strerror_native(errsv)); + /* try again at the next iteration */ + nm_clear_fd(&info->socket); + return; + } + + info->input_source = nm_g_unix_fd_source_new(info->socket, + G_IO_IN, + G_PRIORITY_DEFAULT, + ping_socket_data_cb, + info, + NULL); + g_source_attach(info->input_source, g_task_get_context(info->task)); + } + + if (IS_IPv4) { + memset(&pkt.icmph, 0, sizeof(struct icmphdr)); + pkt.icmph.type = ICMP_ECHO; + pkt.icmph.un.echo.sequence = htons(info->seq); + pkt_len = sizeof(struct icmphdr); + } else { + memset(&pkt.icmp6h, 0, sizeof(struct icmp6_hdr)); + pkt.icmp6h.icmp6_type = ICMP6_ECHO_REQUEST; + pkt.icmp6h.icmp6_seq = htons(info->seq); + pkt_len = sizeof(struct icmp6_hdr); + } + /* The kernel will automatically set the ID ICMP field and filter + * incoming packets by the same ID */ + + if (send(info->socket, &pkt, pkt_len, 0) < 0) { + errsv = errno; + _LOG2T(info, "error sending echo-request #%u: %s", info->seq, nm_strerror_native(errsv)); + return; + } + + _LOG2T(info, "sent echo-request #%u", info->seq); +} + +static gboolean +ping_timeout_cb(gpointer user_data) +{ + PingInfo *info = user_data; + + _LOG2T(info, "timeout"); + + nm_clear_g_source_inst(&info->timeout_source); + ping_complete(info, g_error_new_literal(NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "timeout")); + + return G_SOURCE_CONTINUE; +} + +static gboolean +ping_retry_cb(gpointer user_data) +{ + PingInfo *info = user_data; + + ping_send(info); + + return G_SOURCE_CONTINUE; +} + +static void +ping_cancelled(GObject *object, gpointer user_data) +{ + PingInfo *info = user_data; + GError *error = NULL; + + nm_clear_g_signal_handler(g_task_get_cancellable(info->task), &info->cancellable_id); + nm_utils_error_set_cancelled(&error, FALSE, NULL); + ping_complete(info, error); +} + +void +nm_utils_ping_host(NMIPAddrTyped address, + int ifindex, + guint timeout_sec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer cb_data) +{ + PingInfo *info; + char buf[NM_INET_ADDRSTRLEN]; + gulong signal_id; + + nm_assert(ifindex > 0); + nm_assert(G_IS_CANCELLABLE(cancellable)); + nm_assert(callback); + nm_assert(cb_data); + + info = g_new0(PingInfo, 1); + info->address = address; + info->ifindex = ifindex; + info->task = nm_g_task_new(NULL, cancellable, nm_utils_ping_host, callback, cb_data); + info->socket = -1; + + nm_inet_ntop(address.addr_family, address.addr.addr_ptr, buf); + info->addr_str = g_strdup(buf); + + _LOG2T(info, "started"); + + if (timeout_sec > 0) { + info->timeout_source = nm_g_timeout_source_new_seconds(timeout_sec, + G_PRIORITY_DEFAULT, + ping_timeout_cb, + info, + NULL); + g_source_attach(info->timeout_source, g_task_get_context(info->task)); + } + + info->retry_source = + nm_g_timeout_source_new_seconds(1, G_PRIORITY_DEFAULT, ping_retry_cb, info, NULL); + g_source_attach(info->retry_source, g_task_get_context(info->task)); + + signal_id = g_cancellable_connect(cancellable, G_CALLBACK(ping_cancelled), info, NULL); + if (signal_id == 0) { + /* the callback was invoked synchronously, which destroyed @info. + * We must not touch it anymore. */ + return; + } + info->cancellable_id = signal_id; + + ping_send(info); +} + +gboolean +nm_utils_ping_host_finish(GAsyncResult *result, GError **error) +{ + GTask *task = G_TASK(result); + + nm_assert(nm_g_task_is_valid(result, NULL, nm_utils_ping_host)); + + return g_task_propagate_boolean(task, error); +} + +/*****************************************************************************/ + typedef struct { GPid pid; GTask *task; @@ -5023,6 +5316,9 @@ typedef struct { gsize out_buffer_offset; } HelperInfo; +#undef _NMLOG2_PREFIX_NAME +#undef _NMLOG2_DOMAIN +#undef _NMLOG2 #define _NMLOG2_PREFIX_NAME "nm-daemon-helper" #define _NMLOG2_DOMAIN LOGD_CORE #define _NMLOG2(level, info, ...) \ diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index cccccae636..7ba5c3801e 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -520,4 +520,15 @@ void nm_utils_read_private_files(const char *const *paths, gpointer cb_data); GHashTable *nm_utils_read_private_files_finish(GAsyncResult *result, GError **error); +/*****************************************************************************/ + +void nm_utils_ping_host(NMIPAddrTyped address, + int ifindex, + guint timeout_sec, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer cb_data); + +gboolean nm_utils_ping_host_finish(GAsyncResult *result, GError **error); + #endif /* __NM_CORE_UTILS_H__ */ From e426876c708c54032fc141ec46c688544f9591d7 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 11 Dec 2025 18:14:14 +0100 Subject: [PATCH 2/3] device: use the internal ping implementation Currently NetworkManager depends on the external ping binary to perform the reachability check on IP addresses. This means that the NM daemon package must depend on another package. On Fedora the iputils package is 800KiB. Implement the same functionality natively so that we can drop such dependency. --- NEWS | 3 + src/core/devices/nm-device.c | 427 ++++++++++++++--------------------- 2 files changed, 168 insertions(+), 262 deletions(-) diff --git a/NEWS b/NEWS index f720a1bda6..eb9210cf09 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE! the 802.1X certificates and keys set in the connection. * Introduce a libnm function that can be used by VPN plugins to check user permissions on certificate and keys. +* Use an internal implementation of the ping functionality when the + "connection.gateway-ping-timeout" or "connection.ip-ping-addresses" + properties are set, instead of relying on the "ping" tool. ============================================= NetworkManager-1.56 diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index f51aea53ae..de8edc8064 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, @@ -15381,36 +15380,16 @@ _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; + char *addr_str; + NMIPAddrTyped addr_bin; + + NMLogDomain log_domain; + NMDevice *device; + GCancellable *cancellable; + gboolean require_all; + CList ping_ops_lst; } 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; - - return ping_op; -} - static void ip_check_pre_up(NMDevice *self) { @@ -15433,188 +15412,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) +static void +ping_cleanup(NMDevice *self) { - 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; + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + PingOperation *ping_op; - 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); - - 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; -} - -static gboolean -respawn_ping_cb(gpointer user_data) -{ - PingOperation *ping_op = (PingOperation *) user_data; - NMDevice *self = ping_op->device; - NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); - - nm_clear_g_source_inst(&ping_op->watch); - - 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 (g_list_length(priv->ping_operations) == 0) { - ip_check_pre_up(self); - } - } - - return FALSE; + nm_clear_g_source_inst(&priv->ping_timeout); } static void -ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data) +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); - gboolean success = FALSE; + 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; - if (!ping_op->watch) + success = nm_utils_ping_host_finish(result, &error); + if (nm_utils_error_is_cancelled(error)) return; - nm_clear_g_source_inst(&ping_op->watch); - ping_op->pid = 0; + 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 (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); - } - } else { - _LOGD(ping_op->log_domain, - "ping: stopped unexpectedly with status %d on %s", - status, - ping_op->address); + if (!success) { + /* it should never fail because we set an infinite timeout */ + nm_assert_not_reached(); + return; } - 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; - - if (priv->ping_timeout) - nm_clear_g_source_inst(&priv->ping_timeout); - - _LOGD(ping_op->log_domain, - "ping: ip-ping-addresses requires any, one ping check on ip-ping-addresses " - "succeeded"); - 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; } - } 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); } + + _LOGD(log_domain, "ping: check on address %s succeeded, continuing the activation", addr_str); + + ping_cleanup(self); + ip_check_pre_up(self); +} + +static void +ping_operation_start(NMDevice *self, + const char *addr_str, + NMIPAddrTyped *addr_bin, + gboolean require_all) +{ + 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 { + 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, + }; + + /* 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); + + 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); + NMDevice *self = NM_DEVICE(user_data); + NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); + PingOperation *ping_op; + nm_auto_free_gstring GString *str = NULL; - _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; + 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) { @@ -15623,17 +15568,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); @@ -15642,100 +15583,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); + + _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); } /*****************************************************************************/ @@ -17194,17 +17106,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); } @@ -17960,13 +17866,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; @@ -19631,6 +19532,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. */ From fb697ce5bae0eee6960ae4f1c42fe77a39d46a28 Mon Sep 17 00:00:00 2001 From: Beniamino Galvani Date: Thu, 11 Dec 2025 18:15:03 +0100 Subject: [PATCH 3/3] rpm: drop weak dependency on iputils --- contrib/fedora/rpm/NetworkManager.spec | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/fedora/rpm/NetworkManager.spec b/contrib/fedora/rpm/NetworkManager.spec index cc952f213e..935fa4270e 100644 --- a/contrib/fedora/rpm/NetworkManager.spec +++ b/contrib/fedora/rpm/NetworkManager.spec @@ -199,8 +199,6 @@ Requires: dbus >= %{dbus_version} Requires: glib2 >= %{glib2_version} Requires: %{name}-libnm%{?_isa} = %{epoch}:%{version}-%{release} -Recommends: iputils - %if 0%{?rhel} == 8 # Older libndp versions use select() (rh#1933041). On well known distros, # choose a version that has the necessary fix.