zephyr/subsys/net/ip/icmpv4.c
Stephanos Ioannidis 2c733d5195 net: icmpv4: Return ENETUNREACH when IPv4 is unavailable
net_icmpv4_send_echo_request currently returns EINVAL (invalid
argument) when IPv4 is unavailable.

Since the availability of IPv4 has nothing to do with the arguments
provided to this function and the meaning of EINVAL in this case is
ambiguous, return the ENETUNREACH (network is unreachable) error
number instead.

Signed-off-by: Stephanos Ioannidis <root@stephanos.io>
2020-03-11 13:25:55 -05:00

719 lines
15 KiB
C

/** @file
* @brief ICMPv4 related functions
*/
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/log.h>
LOG_MODULE_REGISTER(net_icmpv4, CONFIG_NET_ICMPV4_LOG_LEVEL);
#include <errno.h>
#include <sys/slist.h>
#include <net/net_core.h>
#include <net/net_pkt.h>
#include <net/net_if.h>
#include "net_private.h"
#include "ipv4.h"
#include "icmpv4.h"
#include "net_stats.h"
#define PKT_WAIT_TIME K_SECONDS(1)
static sys_slist_t handlers;
struct net_icmpv4_hdr_opts_data {
struct net_pkt *reply;
const struct in_addr *src;
};
static int icmpv4_create(struct net_pkt *pkt, u8_t icmp_type, u8_t icmp_code)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv4_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmpv4_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, &icmpv4_access);
}
int net_icmpv4_finalize(struct net_pkt *pkt)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv4_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
if (IS_ENABLED(CONFIG_NET_IPV4_HDR_OPTIONS)) {
if (net_pkt_skip(pkt, net_pkt_ipv4_opts_len(pkt))) {
return -ENOBUFS;
}
}
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmpv4_access);
if (!icmp_hdr) {
return -ENOBUFS;
}
icmp_hdr->chksum = net_calc_chksum_icmpv4(pkt);
return net_pkt_set_data(pkt, &icmpv4_access);
}
#if defined(CONFIG_NET_IPV4_HDR_OPTIONS)
/* Parse Record Route and add our own IP address based on
* free entries.
*/
static int icmpv4_update_record_route(u8_t *opt_data,
u8_t opt_len,
struct net_pkt *reply,
const struct in_addr *src)
{
u8_t len = net_pkt_ipv4_opts_len(reply);
u8_t addr_len = sizeof(struct in_addr);
u8_t ptr_offset = 4U;
u8_t offset = 0U;
u8_t skip;
u8_t ptr;
if (net_pkt_write_u8(reply, NET_IPV4_OPTS_RR)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, opt_len + 2U)) {
goto drop;
}
len++;
/* The third octet is the pointer into the route data
* indicating the octet which begins the next area to
* store a route address. The pointer is relative to
* this option, and the smallest legal value for the
* pointer is 4.
*/
ptr = opt_data[offset++];
/* If the route data area is already full (the pointer exceeds
* the length) the datagram is forwarded without inserting the
* address into the recorded route.
*/
if (ptr >= opt_len) {
/* No free entry to update RecordRoute */
if (net_pkt_write_u8(reply, ptr)) {
goto drop;
}
len++;
if (net_pkt_write(reply, opt_data + offset, opt_len)) {
goto drop;
}
len += opt_len;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
/* If there is some room but not enough room for a full address
* to be inserted, the original datagram is considered to be in
* error and is discarded.
*/
if ((ptr + addr_len) > opt_len) {
goto drop;
}
/* So, there is a free entry to update Record Route */
if (net_pkt_write_u8(reply, ptr + addr_len)) {
goto drop;
}
len++;
skip = ptr - ptr_offset;
if (skip) {
/* Do not alter existed routes */
if (net_pkt_write(reply, opt_data + offset, skip)) {
goto drop;
}
offset += skip;
len += skip;
}
if (net_pkt_write(reply, (void *)src, addr_len)) {
goto drop;
}
len += addr_len;
offset += addr_len;
if (opt_len > offset) {
if (net_pkt_write(reply, opt_data + offset, opt_len - offset)) {
goto drop;
}
}
len += opt_len - offset;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
drop:
return -EINVAL;
}
/* TODO: Timestamp value should updated, as per RFC 791
* Internet Timestamp. Timestamp value : 32-bit timestamp
* in milliseconds since midnight UT.
*/
static int icmpv4_update_time_stamp(u8_t *opt_data,
u8_t opt_len,
struct net_pkt *reply,
const struct in_addr *src)
{
u8_t len = net_pkt_ipv4_opts_len(reply);
u8_t addr_len = sizeof(struct in_addr);
u8_t ptr_offset = 5U;
u8_t offset = 0U;
u8_t new_entry_len;
u8_t overflow;
u8_t flag;
u8_t skip;
u8_t ptr;
if (net_pkt_write_u8(reply, NET_IPV4_OPTS_TS)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, opt_len + 2U)) {
goto drop;
}
len++;
/* The Pointer is the number of octets from the beginning of
* this option to the end of timestamps plus one (i.e., it
* points to the octet beginning the space for next timestamp).
* The smallest legal value is 5. The timestamp area is full
* when the pointer is greater than the length.
*/
ptr = opt_data[offset++];
flag = opt_data[offset++];
flag = flag & 0x0F;
overflow = (flag & 0xF0) >> 4U;
/* If the timestamp data area is already full (the pointer
* exceeds the length) the datagram is forwarded without
* inserting the timestamp, but the overflow count is
* incremented by one.
*/
if (ptr >= opt_len) {
/* overflow count itself overflows, the original datagram
* is considered to be in error and is discarded.
*/
if (overflow == 0x0F) {
goto drop;
}
/* No free entry to update Timestamp data */
if (net_pkt_write_u8(reply, ptr)) {
goto drop;
}
len++;
overflow++;
flag = (overflow << 4U) | flag;
if (net_pkt_write_u8(reply, flag)) {
goto drop;
}
len++;
if (net_pkt_write(reply, opt_data + offset, opt_len)) {
goto drop;
}
len += opt_len;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
switch (flag) {
case NET_IPV4_TS_OPT_TS_ONLY:
new_entry_len = sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_ADDR:
new_entry_len = addr_len + sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_PRES: /* TODO */
default:
goto drop;
}
/* So, there is a free entry to update Timestamp */
if (net_pkt_write_u8(reply, ptr + new_entry_len)) {
goto drop;
}
len++;
if (net_pkt_write_u8(reply, (overflow << 4) | flag)) {
goto drop;
}
len++;
skip = ptr - ptr_offset;
if (skip) {
/* Do not alter existed routes */
if (net_pkt_write(reply, opt_data + offset, skip)) {
goto drop;
}
len += skip;
offset += skip;
}
switch (flag) {
case NET_IPV4_TS_OPT_TS_ONLY:
if (net_pkt_write_be32(reply, htons(k_uptime_get_32()))) {
goto drop;
}
len += sizeof(u32_t);
offset += sizeof(u32_t);
break;
case NET_IPV4_TS_OPT_TS_ADDR:
if (net_pkt_write(reply, (void *)src, addr_len)) {
goto drop;
}
len += addr_len;
if (net_pkt_write_be32(reply, htons(k_uptime_get_32()))) {
goto drop;
}
len += sizeof(u32_t);
offset += (addr_len + sizeof(u32_t));
break;
}
if (opt_len > offset) {
if (net_pkt_write(reply, opt_data + offset, opt_len - offset)) {
goto drop;
}
}
len += opt_len - offset;
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
drop:
return -EINVAL;
}
static int icmpv4_reply_to_options(u8_t opt_type,
u8_t *opt_data,
u8_t opt_len,
void *user_data)
{
struct net_icmpv4_hdr_opts_data *ud =
(struct net_icmpv4_hdr_opts_data *)user_data;
if (opt_type == NET_IPV4_OPTS_RR) {
return icmpv4_update_record_route(opt_data, opt_len,
ud->reply, ud->src);
} else if (opt_type == NET_IPV4_OPTS_TS) {
return icmpv4_update_time_stamp(opt_data, opt_len,
ud->reply, ud->src);
}
return 0;
}
static int icmpv4_handle_header_options(struct net_pkt *pkt,
struct net_pkt *reply,
const struct in_addr *src)
{
struct net_icmpv4_hdr_opts_data ud;
u8_t len;
ud.reply = reply;
ud.src = src;
if (net_ipv4_parse_hdr_options(pkt, icmpv4_reply_to_options, &ud)) {
return -EINVAL;
}
len = net_pkt_ipv4_opts_len(reply);
/* IPv4 optional header part should ends in 32 bit boundary */
if (len % 4U != 0U) {
u8_t i = 4U - (len % 4U);
if (net_pkt_memset(reply, NET_IPV4_OPTS_NOP, i)) {
return -EINVAL;
}
len += i;
}
/* Options are added now, update the header length. */
net_pkt_set_ipv4_opts_len(reply, len);
return 0;
}
#else
static int icmpv4_handle_header_options(struct net_pkt *pkt,
struct net_pkt *reply,
const struct in_addr *src)
{
ARG_UNUSED(pkt);
ARG_UNUSED(reply);
ARG_UNUSED(src);
return 0;
}
#endif
static enum net_verdict icmpv4_handle_echo_request(struct net_pkt *pkt,
struct net_ipv4_hdr *ip_hdr,
struct net_icmp_hdr *icmp_hdr)
{
struct net_pkt *reply = NULL;
const struct in_addr *src;
s16_t payload_len;
/* If interface can not select src address based on dst addr
* and src address is unspecified, drop the echo request.
*/
if (net_ipv4_is_addr_unspecified(&ip_hdr->src)) {
NET_DBG("DROP: src addr is unspecified");
goto drop;
}
NET_DBG("Received Echo Request from %s to %s",
log_strdup(net_sprint_ipv4_addr(&ip_hdr->src)),
log_strdup(net_sprint_ipv4_addr(&ip_hdr->dst)));
payload_len = net_pkt_get_len(pkt) -
net_pkt_ip_hdr_len(pkt) -
net_pkt_ipv4_opts_len(pkt) - NET_ICMPH_LEN;
if (payload_len < NET_ICMPV4_UNUSED_LEN) {
/* No identifier or sequence number present */
goto drop;
}
reply = net_pkt_alloc_with_buffer(net_pkt_iface(pkt),
net_pkt_ipv4_opts_len(pkt) +
payload_len,
AF_INET, IPPROTO_ICMP,
PKT_WAIT_TIME);
if (!reply) {
NET_DBG("DROP: No buffer");
goto drop;
}
if (net_ipv4_is_addr_mcast(&ip_hdr->dst)) {
src = net_if_ipv4_select_src_addr(net_pkt_iface(pkt),
&ip_hdr->dst);
} else {
src = &ip_hdr->dst;
}
if (net_ipv4_create(reply, src, &ip_hdr->src)) {
goto drop;
}
if (IS_ENABLED(CONFIG_NET_IPV4_HDR_OPTIONS)) {
if (net_pkt_ipv4_opts_len(pkt) &&
icmpv4_handle_header_options(pkt, reply, src)) {
goto drop;
}
}
if (icmpv4_create(reply, NET_ICMPV4_ECHO_REPLY, 0) ||
net_pkt_copy(reply, pkt, payload_len)) {
goto drop;
}
net_pkt_cursor_init(reply);
net_ipv4_finalize(reply, IPPROTO_ICMP);
NET_DBG("Sending Echo Reply from %s to %s",
log_strdup(net_sprint_ipv4_addr(src)),
log_strdup(net_sprint_ipv4_addr(&ip_hdr->src)));
if (net_send_data(reply) < 0) {
goto drop;
}
net_stats_update_icmp_sent(net_pkt_iface(reply));
net_pkt_unref(pkt);
return NET_OK;
drop:
if (reply) {
net_pkt_unref(reply);
}
net_stats_update_icmp_drop(net_pkt_iface(pkt));
return NET_DROP;
}
int net_icmpv4_send_echo_request(struct net_if *iface,
struct in_addr *dst,
u16_t identifier,
u16_t sequence,
const void *data,
size_t data_size)
{
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)) {
return -ENOTSUP;
}
if (!iface->config.ip.ipv4) {
return -ENETUNREACH;
}
/* Take the first address of the network interface */
src = &iface->config.ip.ipv4->unicast[0].address.in_addr;
pkt = net_pkt_alloc_with_buffer(iface,
sizeof(struct net_icmpv4_echo_req)
+ data_size,
AF_INET, IPPROTO_ICMP,
PKT_WAIT_TIME);
if (!pkt) {
return -ENOMEM;
}
if (net_ipv4_create(pkt, src, dst) ||
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(identifier);
echo_req->sequence = htons(sequence);
net_pkt_set_data(pkt, &icmpv4_access);
net_pkt_write(pkt, data, data_size);
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,
log_strdup(net_sprint_ipv4_addr(src)),
log_strdup(net_sprint_ipv4_addr(dst)));
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;
}
int net_icmpv4_send_error(struct net_pkt *orig, u8_t type, u8_t code)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(ipv4_access, struct net_ipv4_hdr);
int err = -EIO;
struct net_ipv4_hdr *ip_hdr;
struct net_pkt *pkt;
size_t copy_len;
net_pkt_cursor_init(orig);
ip_hdr = (struct net_ipv4_hdr *)net_pkt_get_data(orig, &ipv4_access);
if (!ip_hdr) {
goto drop_no_pkt;
}
if (ip_hdr->proto == IPPROTO_ICMP) {
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmpv4_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(
orig, &icmpv4_access);
if (!icmp_hdr || icmp_hdr->code < 8) {
/* We must not send ICMP errors back */
err = -EINVAL;
goto drop_no_pkt;
}
}
if (ip_hdr->proto == IPPROTO_UDP) {
copy_len = sizeof(struct net_ipv4_hdr) +
sizeof(struct net_udp_hdr);
} else if (ip_hdr->proto == IPPROTO_TCP) {
copy_len = sizeof(struct net_ipv4_hdr) +
sizeof(struct net_tcp_hdr);
} else {
copy_len = 0;
}
pkt = net_pkt_alloc_with_buffer(net_pkt_iface(orig),
copy_len + NET_ICMPV4_UNUSED_LEN,
AF_INET, IPPROTO_ICMP,
PKT_WAIT_TIME);
if (!pkt) {
err = -ENOMEM;
goto drop_no_pkt;
}
if (net_ipv4_create(pkt, &ip_hdr->dst, &ip_hdr->src) ||
icmpv4_create(pkt, type, code) ||
net_pkt_memset(pkt, 0, NET_ICMPV4_UNUSED_LEN) ||
net_pkt_copy(pkt, orig, copy_len)) {
goto drop;
}
net_pkt_cursor_init(pkt);
net_ipv4_finalize(pkt, IPPROTO_ICMP);
net_pkt_lladdr_dst(pkt)->addr = net_pkt_lladdr_src(orig)->addr;
net_pkt_lladdr_dst(pkt)->len = net_pkt_lladdr_src(orig)->len;
NET_DBG("Sending ICMPv4 Error Message type %d code %d from %s to %s",
type, code,
log_strdup(net_sprint_ipv4_addr(&ip_hdr->src)),
log_strdup(net_sprint_ipv4_addr(&ip_hdr->dst)));
if (net_send_data(pkt) >= 0) {
net_stats_update_icmp_sent(net_pkt_iface(orig));
return 0;
}
drop:
net_pkt_unref(pkt);
drop_no_pkt:
net_stats_update_icmp_drop(net_pkt_iface(orig));
return err;
}
void net_icmpv4_register_handler(struct net_icmpv4_handler *handler)
{
sys_slist_prepend(&handlers, &handler->node);
}
void net_icmpv4_unregister_handler(struct net_icmpv4_handler *handler)
{
sys_slist_find_and_remove(&handlers, &handler->node);
}
enum net_verdict net_icmpv4_input(struct net_pkt *pkt,
struct net_ipv4_hdr *ip_hdr)
{
NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(icmp_access,
struct net_icmp_hdr);
struct net_icmp_hdr *icmp_hdr;
struct net_icmpv4_handler *cb;
icmp_hdr = (struct net_icmp_hdr *)net_pkt_get_data(pkt, &icmp_access);
if (!icmp_hdr) {
NET_DBG("DROP: NULL ICMPv4 header");
return NET_DROP;
}
if (net_calc_chksum_icmpv4(pkt) != 0U) {
NET_DBG("DROP: Invalid checksum");
goto drop;
}
if (net_ipv4_is_addr_bcast(net_pkt_iface(pkt), &ip_hdr->dst) &&
(!IS_ENABLED(CONFIG_NET_ICMPV4_ACCEPT_BROADCAST) ||
icmp_hdr->type != NET_ICMPV4_ECHO_REQUEST)) {
NET_DBG("DROP: broadcast pkt");
goto drop;
}
net_pkt_acknowledge_data(pkt, &icmp_access);
NET_DBG("ICMPv4 packet received type %d code %d",
icmp_hdr->type, icmp_hdr->code);
net_stats_update_icmp_recv(net_pkt_iface(pkt));
SYS_SLIST_FOR_EACH_CONTAINER(&handlers, cb, node) {
if (cb->type == icmp_hdr->type &&
(cb->code == icmp_hdr->code || cb->code == 0U)) {
return cb->handler(pkt, ip_hdr, icmp_hdr);
}
}
drop:
net_stats_update_icmp_drop(net_pkt_iface(pkt));
return NET_DROP;
}
static struct net_icmpv4_handler echo_request_handler = {
.type = NET_ICMPV4_ECHO_REQUEST,
.code = 0,
.handler = icmpv4_handle_echo_request,
};
void net_icmpv4_init(void)
{
net_icmpv4_register_handler(&echo_request_handler);
}