|  | /** @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, 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) | 
|  | { | 
|  | 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(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 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; | 
|  | 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(&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, | 
|  | uint16_t identifier, | 
|  | uint16_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, 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 (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); | 
|  | } |