merge: branch 'bg/builtin-ping'

Use an internal "ping" implementation

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2328
This commit is contained in:
Beniamino Galvani 2025-12-18 08:33:56 +00:00
commit f21f9eb172
5 changed files with 475 additions and 264 deletions

3
NEWS
View file

@ -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

View file

@ -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.

View file

@ -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. */

View file

@ -21,6 +21,8 @@
#include <linux/if_infiniband.h>
#include <net/if_arp.h>
#include <net/ethernet.h>
#include <netinet/ip_icmp.h>
#include <netinet/icmp6.h>
#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, ...) \

View file

@ -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__ */