/** @file * @brief ICMPv4 related functions */ /* * Copyright (c) 2016 Intel Corporation * * SPDX-License-Identifier: Apache-2.0 */ #include LOG_MODULE_REGISTER(net_icmpv4, CONFIG_NET_ICMPV4_LOG_LEVEL); #include #include #include #include #include #include #include "net_private.h" #include "ipv4.h" #include "icmpv4.h" #include "net_stats.h" #include "pmtu.h" #define PKT_WAIT_TIME K_SECONDS(1) struct net_icmpv4_hdr_opts_data { struct net_pkt *reply; const struct in_addr *src; }; int net_icmpv4_create(struct net_pkt *pkt, uint8_t icmp_type, uint8_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, bool force_chksum) { 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 = 0U; if (net_if_need_calc_tx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV4_ICMP) || force_chksum) { icmp_hdr->chksum = net_calc_chksum_icmpv4(pkt); net_pkt_set_chksum_done(pkt, true); } 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(uint8_t *opt_data, uint8_t opt_len, struct net_pkt *reply, const struct in_addr *src) { uint8_t len = net_pkt_ipv4_opts_len(reply); uint8_t addr_len = sizeof(struct in_addr); uint8_t ptr_offset = 4U; uint8_t offset = 0U; uint8_t skip; uint8_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(uint8_t *opt_data, uint8_t opt_len, struct net_pkt *reply, const struct in_addr *src) { uint8_t len = net_pkt_ipv4_opts_len(reply); uint8_t addr_len = sizeof(struct in_addr); uint8_t ptr_offset = 5U; uint8_t offset = 0U; uint8_t new_entry_len; uint8_t overflow; uint8_t flag; uint8_t skip; uint8_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(uint32_t); break; case NET_IPV4_TS_OPT_TS_ADDR: new_entry_len = addr_len + sizeof(uint32_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(uint32_t); offset += sizeof(uint32_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(uint32_t); offset += (addr_len + sizeof(uint32_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(uint8_t opt_type, uint8_t *opt_data, uint8_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; uint8_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) { uint8_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 int icmpv4_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_ipv4_hdr *ip_hdr = hdr->ipv4; const struct in_addr *src; int16_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((struct in_addr *)ip_hdr->src)) { NET_DBG("DROP: src addr is unspecified"); goto drop; } NET_DBG("Received Echo Request from %s to %s", net_sprint_ipv4_addr(&ip_hdr->src), 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((struct in_addr *)ip_hdr->dst) || net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)ip_hdr->dst)) { src = net_if_ipv4_select_src_addr(net_pkt_iface(pkt), (struct in_addr *)ip_hdr->src); if (net_ipv4_is_addr_unspecified(src)) { NET_DBG("DROP: No src address match"); goto drop; } } else { src = (struct in_addr *)ip_hdr->dst; } net_pkt_set_ip_dscp(reply, net_pkt_ip_dscp(pkt)); net_pkt_set_ip_ecn(reply, net_pkt_ip_ecn(pkt)); if (net_ipv4_create(reply, src, (struct in_addr *)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 (net_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", net_sprint_ipv4_addr(src), net_sprint_ipv4_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_icmpv4_send_error(struct net_pkt *orig, uint8_t type, uint8_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 (net_ipv4_is_addr_bcast(net_pkt_iface(orig), (struct in_addr *)ip_hdr->dst)) { /* We should not send an error to packet that * were sent to broadcast */ NET_DBG("Not sending error to bcast pkt from %s on proto %s", net_sprint_ipv4_addr(&ip_hdr->src), net_proto2str(AF_INET, ip_hdr->proto)); 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, (struct in_addr *)ip_hdr->dst, (struct in_addr *)ip_hdr->src) || net_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, net_sprint_ipv4_addr(&ip_hdr->dst), net_sprint_ipv4_addr(&ip_hdr->src)); 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; } 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; int ret; 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_if_need_calc_rx_checksum(net_pkt_iface(pkt), NET_IF_CHECKSUM_IPV4_ICMP) || net_pkt_is_ip_reassembled(pkt)) { if (net_calc_chksum_icmpv4(pkt) != 0U) { NET_DBG("DROP: Invalid checksum"); goto drop; } } if (net_ipv4_is_addr_bcast(net_pkt_iface(pkt), (struct in_addr *)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)); ret = net_icmp_call_ipv4_handlers(pkt, ip_hdr, icmp_hdr); if (ret < 0 && ret != -ENOENT) { NET_ERR("ICMPv4 handling failure (%d)", ret); } net_pkt_unref(pkt); return NET_OK; drop: net_stats_update_icmp_drop(net_pkt_iface(pkt)); return NET_DROP; } #if defined(CONFIG_NET_IPV4_PMTU) /* The RFC 1191 chapter 3 says the minimum MTU size is 68 octets. * This is way too small in modern world, so make the minimum 576 octets. */ #define MIN_IPV4_MTU NET_IPV4_MTU static int icmpv4_handle_dst_unreach(struct net_icmp_ctx *ctx, struct net_pkt *pkt, struct net_icmp_ip_hdr *hdr, struct net_icmp_hdr *icmp_hdr, void *user_data) { NET_PKT_DATA_ACCESS_CONTIGUOUS_DEFINE(dst_unreach_access, struct net_icmpv4_dest_unreach); struct net_icmpv4_dest_unreach *dest_unreach_hdr; struct net_ipv4_hdr *ip_hdr = hdr->ipv4; uint16_t length = net_pkt_get_len(pkt); struct net_pmtu_entry *entry; struct sockaddr_in sockaddr_src = { .sin_family = AF_INET, }; uint16_t mtu; int ret; ARG_UNUSED(user_data); dest_unreach_hdr = (struct net_icmpv4_dest_unreach *) net_pkt_get_data(pkt, &dst_unreach_access); if (dest_unreach_hdr == NULL) { NET_DBG("DROP: NULL ICMPv4 Destination Unreachable header"); goto drop; } net_stats_update_ipv4_pmtu_recv(net_pkt_iface(pkt)); NET_DBG("Received Destination Unreachable from %s to %s", net_sprint_ipv4_addr(&ip_hdr->src), net_sprint_ipv4_addr(&ip_hdr->dst)); if (length < (sizeof(struct net_ipv4_hdr) + sizeof(struct net_icmp_hdr) + sizeof(struct net_icmpv4_dest_unreach))) { NET_DBG("DROP: length %d too big %zd", length, sizeof(struct net_ipv4_hdr) + sizeof(struct net_icmp_hdr) + sizeof(struct net_icmpv4_dest_unreach)); goto drop; } net_pkt_acknowledge_data(pkt, &dst_unreach_access); mtu = ntohs(dest_unreach_hdr->mtu); if (mtu < MIN_IPV4_MTU) { NET_DBG("DROP: Unsupported MTU %u, min is %u", mtu, MIN_IPV4_MTU); goto drop; } net_ipaddr_copy(&sockaddr_src.sin_addr, (struct in_addr *)&ip_hdr->src); entry = net_pmtu_get_entry((struct sockaddr *)&sockaddr_src); if (entry == NULL) { NET_DBG("DROP: Cannot find PMTU entry for %s", net_sprint_ipv4_addr(&ip_hdr->src)); goto silent_drop; } /* We must not accept larger PMTU value than what we already know. * RFC 1191 chapter 3 page 5. */ if (entry->mtu > 0 && entry->mtu < mtu) { NET_DBG("DROP: PMTU for %s %u larger than %u", net_sprint_ipv4_addr(&ip_hdr->src), mtu, entry->mtu); goto silent_drop; } ret = net_pmtu_update_entry(entry, mtu); if (ret > 0) { NET_DBG("PMTU for %s changed from %u to %u", net_sprint_ipv4_addr(&ip_hdr->src), ret, mtu); } return 0; drop: net_stats_update_ipv4_pmtu_drop(net_pkt_iface(pkt)); return -EIO; silent_drop: /* If the event is not really an error then just ignore it and * return 0 so that icmpv4 module will not complain about it. */ net_stats_update_ipv4_pmtu_drop(net_pkt_iface(pkt)); return 0; } static struct net_icmp_ctx dst_unreach_ctx; #endif /* CONFIG_NET_IPV4_PMTU */ void net_icmpv4_init(void) { static struct net_icmp_ctx ctx; int ret; ret = net_icmp_init_ctx(&ctx, NET_ICMPV4_ECHO_REQUEST, 0, icmpv4_handle_echo_request); if (ret < 0) { NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV4_ECHO_REQUEST), ret); } #if defined(CONFIG_NET_IPV4_PMTU) ret = net_icmp_init_ctx(&dst_unreach_ctx, NET_ICMPV4_DST_UNREACH, 0, icmpv4_handle_dst_unreach); if (ret < 0) { NET_ERR("Cannot register %s handler (%d)", STRINGIFY(NET_ICMPV4_DST_UNREACH), ret); } #endif }