blob: 048521e1c12beebcc85f85faa1657c8f29140a7b [file] [log] [blame]
/** @file
* @brief ICMPv6 related functions
*/
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net_icmpv6, CONFIG_NET_ICMPV6_LOG_LEVEL);
#include <errno.h>
#include <zephyr/sys/slist.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/icmp.h>
#include "net_private.h"
#include "icmpv6.h"
#include "ipv6.h"
#include "net_stats.h"
#define PKT_WAIT_TIME K_SECONDS(1)
const char *net_icmpv6_type2str(int icmpv6_type)
{
switch (icmpv6_type) {
case NET_ICMPV6_DST_UNREACH:
return "Destination Unreachable";
case NET_ICMPV6_PACKET_TOO_BIG:
return "Packet Too Big";
case NET_ICMPV6_TIME_EXCEEDED:
return "Time Exceeded";
case NET_ICMPV6_PARAM_PROBLEM:
return "IPv6 Bad Header";
case NET_ICMPV6_ECHO_REQUEST:
return "Echo Request";
case NET_ICMPV6_ECHO_REPLY:
return "Echo Reply";
case NET_ICMPV6_MLD_QUERY:
return "Multicast Listener Query";
case NET_ICMPV6_RS:
return "Router Solicitation";
case NET_ICMPV6_RA:
return "Router Advertisement";
case NET_ICMPV6_NS:
return "Neighbor Solicitation";
case NET_ICMPV6_NA:
return "Neighbor Advertisement";
case NET_ICMPV6_MLDv2:
return "Multicast Listener Report v2";
}
return "?";
}
int net_icmpv6_finalize(struct net_pkt *pkt, bool force_chksum)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
if (!icmp_hdr) {
return -ENOBUFS;
}
icmp_hdr->chksum = 0U;
if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
force_chksum) {
icmp_hdr->chksum = net_calc_chksum_icmpv6(pkt);
net_pkt_set_chksum_done(pkt, true);
}
return net_pkt_set_data(pkt, &icmp_access);
}
int net_icmpv6_create(struct net_pkt *pkt, uint8_t icmp_type, uint8_t icmp_code)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
if (!icmp_hdr) {
return -ENOBUFS;
}
icmp_hdr->type = icmp_type;
icmp_hdr->code = icmp_code;
icmp_hdr->chksum = 0U;
return net_pkt_set_data(pkt, &icmp_access);
}
static int icmpv6_handle_echo_request(struct net_icmp_ctx *ctx,
struct net_pkt *pkt,
struct net_icmp_ip_hdr *hdr,
struct net_icmp_hdr *icmp_hdr,
void *user_data)
{
struct net_pkt *reply = NULL;
struct net_ipv6_hdr *ip_hdr = hdr->ipv6;
const struct in6_addr *src;
int16_t payload_len;
ARG_UNUSED(user_data);
ARG_UNUSED(icmp_hdr);
NET_DBG("Received Echo Request from %s to %s",
net_sprint_ipv6_addr(&ip_hdr->src),
net_sprint_ipv6_addr(&ip_hdr->dst));
payload_len = ntohs(ip_hdr->len) -
net_pkt_ipv6_ext_len(pkt) - NET_ICMPH_LEN;
if (payload_len < NET_ICMPV6_UNUSED_LEN) {
/* No identifier or sequence number present */
goto drop;
}
reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt), payload_len,
AF_INET6, IPPROTO_ICMPV6,
PKT_WAIT_TIME);
if (!reply) {
NET_DBG("DROP: No buffer");
goto drop;
}
if (net_ipv6_is_addr_mcast((struct in6_addr *)ip_hdr->dst)) {
src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
(struct in6_addr *)ip_hdr->src);
if (net_ipv6_is_addr_unspecified(src)) {
NET_DBG("DROP: No src address match");
goto drop;
}
} else {
src = (struct in6_addr *)ip_hdr->dst;
}
/* We must not set the destination ll address here but trust
* that it is set properly using a value from neighbor cache.
* Same for source as it points to original pkt ll src address.
*/
net_pkt_lladdr_dst(reply)->addr = NULL;
net_pkt_lladdr_src(reply)->addr = NULL;
net_pkt_set_ip_dscp(reply, net_pkt_ip_dscp(pkt));
net_pkt_set_ip_ecn(reply, net_pkt_ip_ecn(pkt));
if (net_ipv6_create(reply, src, (struct in6_addr *)ip_hdr->src)) {
NET_DBG("DROP: wrong buffer");
goto drop;
}
if (net_icmpv6_create(reply, NET_ICMPV6_ECHO_REPLY, 0) ||
net_pkt_copy(reply, pkt, payload_len)) {
NET_DBG("DROP: wrong buffer");
goto drop;
}
net_pkt_cursor_init(reply);
net_ipv6_finalize(reply, IPPROTO_ICMPV6);
NET_DBG("Sending Echo Reply from %s to %s",
net_sprint_ipv6_addr(src),
net_sprint_ipv6_addr(&ip_hdr->src));
if (net_send_data(reply) < 0) {
goto drop;
}
net_stats_update_icmp_sent(net_pkt_iface(reply));
return 0;
drop:
if (reply) {
net_pkt_unref(reply);
}
net_stats_update_icmp_drop(net_pkt_iface(pkt));
return -EIO;
}
int net_icmpv6_send_error(struct net_pkt *orig, uint8_t type, uint8_t code,
uint32_t param)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv6_access, struct net_ipv6_hdr);
int err = -EIO;
struct net_ipv6_hdr *ip_hdr;
const struct in6_addr *src;
struct net_pkt *pkt;
size_t copy_len;
int ret;
net_pkt_cursor_init(orig);
ip_hdr = (struct net_ipv6_hdr *)net_pkt_get_data(orig, &ipv6_access);
if (!ip_hdr) {
goto drop_no_pkt;
}
if (ip_hdr->nexthdr == IPPROTO_ICMPV6) {
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv6_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
net_pkt_acknowledge_data(orig, &ipv6_access);
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(
orig, &icmpv6_access);
if (!icmp_hdr || icmp_hdr->type < 128) {
/* We must not send ICMP errors back */
err = -EINVAL;
goto drop_no_pkt;
}
net_pkt_cursor_init(orig);
}
if (ip_hdr->nexthdr == IPPROTO_UDP) {
copy_len = sizeof(struct net_ipv6_hdr) +
sizeof(struct net_udp_hdr);
} else if (ip_hdr->nexthdr == IPPROTO_TCP) {
copy_len = sizeof(struct net_ipv6_hdr) +
sizeof(struct net_tcp_hdr);
} else {
copy_len = net_pkt_get_len(orig);
}
pkt = net_pkt_alloc_with_buffer(net_pkt_iface(orig),
net_pkt_lladdr_src(orig)->len * 2 +
copy_len + NET_ICMPV6_UNUSED_LEN,
AF_INET6, IPPROTO_ICMPV6,
PKT_WAIT_TIME);
if (!pkt) {
err = -ENOMEM;
goto drop_no_pkt;
}
/* We created above a new packet that contains some extra space that we
* will use to store the destination and source link addresses. This is
* needed because we cannot use the original pkt, which contains the
* link address where the new packet will be sent, as that pkt might
* get re-used before we have managed to set the link addresses in L2
* as that (link address setting) happens in a different thread (TX)
* than this one.
* So we copy the destination and source link addresses here, then set
* the link address pointers correctly, and skip the needed space
* as the link address will be set in the pkt when the packet is
* constructed in L2. So basically all this for just to create some
* extra space for link addresses so that we can set the lladdr
* pointers in net_pkt.
*/
ret = net_pkt_write(pkt, net_pkt_lladdr_src(orig)->addr,
net_pkt_lladdr_src(orig)->len);
if (ret < 0) {
err = ret;
goto drop;
}
net_pkt_lladdr_dst(pkt)->addr = pkt->buffer->data;
ret = net_pkt_write(pkt, net_pkt_lladdr_dst(orig)->addr,
net_pkt_lladdr_dst(orig)->len);
if (ret < 0) {
err = ret;
goto drop;
}
net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_dst(orig)->len);
net_pkt_lladdr_src(pkt)->addr = pkt->buffer->data;
net_buf_pull_mem(pkt->buffer, net_pkt_lladdr_src(orig)->len);
net_pkt_lladdr_src(pkt)->len = net_pkt_lladdr_dst(orig)->len;
net_pkt_lladdr_dst(pkt)->len = net_pkt_lladdr_src(orig)->len;
if (net_ipv6_is_addr_mcast((struct in6_addr *)ip_hdr->dst)) {
src = net_if_ipv6_select_src_addr(net_pkt_iface(pkt),
(struct in6_addr *)ip_hdr->dst);
} else {
src = (struct in6_addr *)ip_hdr->dst;
}
if (net_ipv6_create(pkt, src, (struct in6_addr *)ip_hdr->src) ||
net_icmpv6_create(pkt, type, code)) {
goto drop;
}
/* Depending on error option, we store the param into the ICMP message.
*/
if (type == NET_ICMPV6_PARAM_PROBLEM) {
err = net_pkt_write_be32(pkt, param);
} else {
err = net_pkt_memset(pkt, 0, NET_ICMPV6_UNUSED_LEN);
}
/* Allocator might not have been able to allocate all requested space,
* so let's copy as much as we can.
*/
copy_len = net_pkt_available_buffer(pkt);
if (err || net_pkt_copy(pkt, orig, copy_len)) {
goto drop;
}
net_pkt_cursor_init(pkt);
net_ipv6_finalize(pkt, IPPROTO_ICMPV6);
NET_DBG("Sending ICMPv6 Error Message type %d code %d param %d"
" from %s to %s", type, code, param,
net_sprint_ipv6_addr(src),
net_sprint_ipv6_addr(&ip_hdr->src));
if (net_send_data(pkt) >= 0) {
net_stats_update_icmp_sent(net_pkt_iface(pkt));
return 0;
}
drop:
net_pkt_unref(pkt);
drop_no_pkt:
net_stats_update_icmp_drop(net_pkt_iface(orig));
return err;
}
enum net_verdict net_icmpv6_input(struct net_pkt *pkt,
struct net_ipv6_hdr *ip_hdr)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
int ret;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
if (!icmp_hdr) {
NET_DBG("DROP: NULL ICMPv6 header");
return NET_DROP;
}
if (net_if_need_calc_rx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV6_ICMP) ||
net_pkt_is_ip_reassembled(pkt)) {
if (net_calc_chksum_icmpv6(pkt) != 0U) {
NET_DBG("DROP: invalid checksum");
goto drop;
}
}
net_pkt_acknowledge_data(pkt, &icmp_access);
NET_DBG("ICMPv6 %s received type %d code %d",
net_icmpv6_type2str(icmp_hdr->type),
icmp_hdr->type, icmp_hdr->code);
net_stats_update_icmp_recv(net_pkt_iface(pkt));
ret = net_icmp_call_ipv6_handlers(pkt, ip_hdr, icmp_hdr);
if (ret < 0 && ret != -ENOENT) {
NET_ERR("ICMPv6 handling failure (%d)", ret);
}
net_pkt_unref(pkt);
return NET_OK;
drop:
net_stats_update_icmp_drop(net_pkt_iface(pkt));
return NET_DROP;
}
void net_icmpv6_init(void)
{
static struct net_icmp_ctx ctx;
int ret;
ret = net_icmp_init_ctx(&ctx, NET_ICMPV6_ECHO_REQUEST, 0, icmpv6_handle_echo_request);
if (ret < 0) {
NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV6_ECHO_REQUEST),
ret);
}
}