zephyr/subsys/net/ip/icmp.c
Jukka Rissanen 31a25da8c2 net: icmp: Introduce new API to send ICMP messages
Allow user to send ICMP Echo Request message a.k.a pings.
The same ICMP API and framework is used for network stack
internal needs in later commits. One benefit for this new
API is that it allows sending Echo Requests to offlined
network devices.

Signed-off-by: Jukka Rissanen <jukka.rissanen@nordicsemi.no>
2023-09-28 09:34:20 +02:00

439 lines
9.9 KiB
C

/** @file
* @brief ICMP related functions
*/
/*
* Copyright (c) 2023 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/* Use highest log level if both IPv4 and IPv6 are defined */
#if defined(CONFIG_NET_IPV4) && defined(CONFIG_NET_IPV6)
#if CONFIG_NET_ICMPV4_LOG_LEVEL > CONFIG_NET_ICMPV6_LOG_LEVEL
#define ICMP_LOG_LEVEL CONFIG_NET_ICMPV4_LOG_LEVEL
#else
#define ICMP_LOG_LEVEL CONFIG_NET_ICMPV6_LOG_LEVEL
#endif
#elif defined(CONFIG_NET_IPV4)
#define ICMP_LOG_LEVEL CONFIG_NET_ICMPV4_LOG_LEVEL
#elif defined(CONFIG_NET_IPV6)
#define ICMP_LOG_LEVEL CONFIG_NET_ICMPV6_LOG_LEVEL
#else
#define ICMP_LOG_LEVEL LOG_LEVEL_INF
#endif
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_icmp, ICMP_LOG_LEVEL);
#include <errno.h>
#include <zephyr/random/rand32.h>
#include <zephyr/sys/slist.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/icmp.h>
#include "net_private.h"
#include "icmpv6.h"
#include "icmpv4.h"
#include "ipv4.h"
#include "ipv6.h"
#include "net_stats.h"
static K_MUTEX_DEFINE(lock);
static sys_slist_t handlers = SYS_SLIST_STATIC_INIT(&handlers);
#define PKT_WAIT_TIME K_SECONDS(1)
int net_icmp_init_ctx(struct net_icmp_ctx *ctx, uint8_t type, uint8_t code,
net_icmp_handler_t handler)
{
if (ctx == NULL || handler == NULL) {
return -EINVAL;
}
memset(ctx, 0, sizeof(struct net_icmp_ctx));
ctx->handler = handler;
ctx->type = type;
ctx->code = code;
k_mutex_lock(&lock, K_FOREVER);
sys_slist_prepend(&handlers, &ctx->node);
k_mutex_unlock(&lock);
return 0;
}
int net_icmp_cleanup_ctx(struct net_icmp_ctx *ctx)
{
if (ctx == NULL) {
return -EINVAL;
}
k_mutex_lock(&lock, K_FOREVER);
sys_slist_find_and_remove(&handlers, &ctx->node);
k_mutex_unlock(&lock);
memset(ctx, 0, sizeof(struct net_icmp_ctx));
return 0;
}
#if defined(CONFIG_NET_IPV4)
static int send_icmpv4_echo_request(struct net_icmp_ctx *ctx,
struct net_if *iface,
struct in_addr *dst,
struct net_icmp_ping_params *params,
void *user_data)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv4_access,
struct net_icmpv4_echo_req);
int ret = -ENOBUFS;
struct net_icmpv4_echo_req *echo_req;
const struct in_addr *src;
struct net_pkt *pkt;
if (IS_ENABLED(CONFIG_NET_OFFLOAD) && net_if_is_ip_offloaded(iface)) {
/* XXX: fixme so that we can send offloaded messages too */
return -ENOTSUP;
}
if (!iface->config.ip.ipv4) {
return -ENETUNREACH;
}
src = net_if_ipv4_select_src_addr(iface, dst);
pkt = net_pkt_alloc_with_buffer(iface,
sizeof(struct net_icmpv4_echo_req)
+ params->data_size,
AF_INET, IPPROTO_ICMP,
PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
if (!IS_ENABLED(CONFIG_NET_ALLOW_ANY_PRIORITY) &&
params->priority >= NET_MAX_PRIORITIES) {
NET_ERR("Priority %d is too large, maximum allowed is %d",
params->priority, NET_MAX_PRIORITIES - 1);
return -EINVAL;
}
if (params->priority < 0) {
net_pkt_set_ip_dscp(pkt, net_ipv4_get_dscp(params->tc_tos));
net_pkt_set_ip_ecn(pkt, net_ipv4_get_ecn(params->tc_tos));
} else {
net_pkt_set_priority(pkt, params->priority);
}
if (net_ipv4_create(pkt, src, dst) ||
net_icmpv4_create(pkt, NET_ICMPV4_ECHO_REQUEST, 0)) {
goto drop;
}
echo_req = (struct net_icmpv4_echo_req *)net_pkt_get_data(
pkt, &icmpv4_access);
if (!echo_req) {
goto drop;
}
echo_req->identifier = htons(params->identifier);
echo_req->sequence = htons(params->sequence);
net_pkt_set_data(pkt, &icmpv4_access);
if (params->data != NULL && params->data_size > 0) {
net_pkt_write(pkt, params->data, params->data_size);
} else if (params->data == NULL && params->data_size > 0) {
/* Generate payload. */
if (params->data_size >= sizeof(uint32_t)) {
uint32_t time_stamp = htonl(k_cycle_get_32());
net_pkt_write(pkt, &time_stamp, sizeof(time_stamp));
params->data_size -= sizeof(time_stamp);
}
for (size_t i = 0; i < params->data_size; i++) {
net_pkt_write_u8(pkt, (uint8_t)i);
}
} else {
/* No payload. */
}
net_pkt_cursor_init(pkt);
net_ipv4_finalize(pkt, IPPROTO_ICMP);
NET_DBG("Sending ICMPv4 Echo Request type %d from %s to %s",
NET_ICMPV4_ECHO_REQUEST,
net_sprint_ipv4_addr(src),
net_sprint_ipv4_addr(dst));
ctx->user_data = user_data;
ctx->iface = iface;
if (net_send_data(pkt) >= 0) {
net_stats_update_icmp_sent(iface);
return 0;
}
net_stats_update_icmp_drop(iface);
ret = -EIO;
drop:
net_pkt_unref(pkt);
return ret;
}
#else
static int send_icmpv4_echo_request(struct net_icmp_ctx *ctx,
struct net_if *iface,
struct in_addr *dst,
struct net_icmp_ping_params *params,
void *user_data)
{
ARG_UNUSED(ctx);
ARG_UNUSED(iface);
ARG_UNUSED(dst);
ARG_UNUSED(params);
return -ENOTSUP;
}
#endif
#if defined(CONFIG_NET_IPV6)
static int send_icmpv6_echo_request(struct net_icmp_ctx *ctx,
struct net_if *iface,
struct in6_addr *dst,
struct net_icmp_ping_params *params,
void *user_data)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv6_access,
struct net_icmpv6_echo_req);
int ret = -ENOBUFS;
struct net_icmpv6_echo_req *echo_req;
const struct in6_addr *src;
struct net_pkt *pkt;
if (!iface->config.ip.ipv6) {
return -ENETUNREACH;
}
src = net_if_ipv6_select_src_addr(iface, dst);
pkt = net_pkt_alloc_with_buffer(iface,
sizeof(struct net_icmpv6_echo_req)
+ params->data_size,
AF_INET6, IPPROTO_ICMPV6,
PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
if (!IS_ENABLED(CONFIG_NET_ALLOW_ANY_PRIORITY) &&
params->priority >= NET_MAX_PRIORITIES) {
NET_ERR("Priority %d is too large, maximum allowed is %d",
params->priority, NET_MAX_PRIORITIES - 1);
return -EINVAL;
}
if (params->priority < 0) {
net_pkt_set_ip_dscp(pkt, net_ipv6_get_dscp(params->tc_tos));
net_pkt_set_ip_ecn(pkt, net_ipv6_get_ecn(params->tc_tos));
} else {
net_pkt_set_priority(pkt, params->priority);
}
if (net_ipv6_create(pkt, src, dst) ||
net_icmpv6_create(pkt, NET_ICMPV6_ECHO_REQUEST, 0)) {
goto drop;
}
echo_req = (struct net_icmpv6_echo_req *)net_pkt_get_data(
pkt, &icmpv6_access);
if (!echo_req) {
goto drop;
}
echo_req->identifier = htons(params->identifier);
echo_req->sequence = htons(params->sequence);
net_pkt_set_data(pkt, &icmpv6_access);
if (params->data != NULL && params->data_size > 0) {
net_pkt_write(pkt, params->data, params->data_size);
} else if (params->data == NULL && params->data_size > 0) {
/* Generate payload. */
if (params->data_size >= sizeof(uint32_t)) {
uint32_t time_stamp = htonl(k_cycle_get_32());
net_pkt_write(pkt, &time_stamp, sizeof(time_stamp));
params->data_size -= sizeof(time_stamp);
}
for (size_t i = 0; i < params->data_size; i++) {
net_pkt_write_u8(pkt, (uint8_t)i);
}
} else {
/* No payload. */
}
net_pkt_cursor_init(pkt);
net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
NET_DBG("Sending ICMPv6 Echo Request type %d from %s to %s",
NET_ICMPV6_ECHO_REQUEST,
net_sprint_ipv6_addr(src),
net_sprint_ipv6_addr(dst));
ctx->user_data = user_data;
ctx->iface = iface;
if (net_send_data(pkt) >= 0) {
net_stats_update_icmp_sent(iface);
return 0;
}
net_stats_update_icmp_drop(iface);
ret = -EIO;
drop:
net_pkt_unref(pkt);
return ret;
}
#else
static int send_icmpv6_echo_request(struct net_icmp_ctx *ctx,
struct net_if *iface,
struct in6_addr *dst,
struct net_icmp_ping_params *params,
void *user_data)
{
ARG_UNUSED(ctx);
ARG_UNUSED(iface);
ARG_UNUSED(dst);
ARG_UNUSED(params);
return -ENOTSUP;
}
#endif
static struct net_icmp_ping_params *get_default_params(void)
{
static struct net_icmp_ping_params params = { 0 };
params.identifier = sys_rand32_get();
return &params;
}
int net_icmp_send_echo_request(struct net_icmp_ctx *ctx,
struct net_if *iface,
struct sockaddr *dst,
struct net_icmp_ping_params *params,
void *user_data)
{
if (ctx == NULL || dst == NULL) {
return -EINVAL;
}
if (iface == NULL) {
if (IS_ENABLED(CONFIG_NET_IPV4) && dst->sa_family == AF_INET) {
iface = net_if_ipv4_select_src_iface(&net_sin(dst)->sin_addr);
} else if (IS_ENABLED(CONFIG_NET_IPV6) && dst->sa_family == AF_INET6) {
iface = net_if_ipv6_select_src_iface(&net_sin6(dst)->sin6_addr);
}
if (iface == NULL) {
return -ENOENT;
}
}
if (IS_ENABLED(CONFIG_NET_IPV4) && dst->sa_family == AF_INET) {
if (params == NULL) {
params = get_default_params();
}
return send_icmpv4_echo_request(ctx, iface, &net_sin(dst)->sin_addr,
params, user_data);
}
if (IS_ENABLED(CONFIG_NET_IPV6) && dst->sa_family == AF_INET6) {
if (params == NULL) {
params = get_default_params();
}
return send_icmpv6_echo_request(ctx, iface, &net_sin6(dst)->sin6_addr,
params, user_data);
}
return -ENOENT;
}
static int icmp_call_handlers(struct net_pkt *pkt,
struct net_icmp_ip_hdr *ip_hdr,
struct net_icmp_hdr *icmp_hdr)
{
struct net_icmp_ctx *ctx;
int ret = -ENOENT;
k_mutex_lock(&lock, K_FOREVER);
SYS_SLIST_FOR_EACH_CONTAINER(&handlers, ctx, node) {
if (ctx->type == icmp_hdr->type &&
(ctx->code == icmp_hdr->code || ctx->code == 0U)) {
/* Do not use a handler that is expecting data from different
* network interface we sent the request.
*/
if (ctx->iface != NULL && ctx->iface != net_pkt_iface(pkt)) {
continue;
}
ret = ctx->handler(ctx, pkt, ip_hdr, icmp_hdr, ctx->user_data);
if (ret < 0) {
goto out;
}
}
}
out:
k_mutex_unlock(&lock);
return ret;
}
int net_icmp_call_ipv4_handlers(struct net_pkt *pkt,
struct net_ipv4_hdr *ipv4_hdr,
struct net_icmp_hdr *icmp_hdr)
{
struct net_icmp_ip_hdr ip_hdr;
ip_hdr.ipv4 = ipv4_hdr;
ip_hdr.family = AF_INET;
return icmp_call_handlers(pkt, &ip_hdr, icmp_hdr);
}
int net_icmp_call_ipv6_handlers(struct net_pkt *pkt,
struct net_ipv6_hdr *ipv6_hdr,
struct net_icmp_hdr *icmp_hdr)
{
struct net_icmp_ip_hdr ip_hdr;
ip_hdr.ipv6 = ipv6_hdr;
ip_hdr.family = AF_INET6;
return icmp_call_handlers(pkt, &ip_hdr, icmp_hdr);
}