| /** @file |
| * @brief ICMPv4 related functions |
| */ |
| |
| /* |
| * Copyright (c) 2016 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #if defined(CONFIG_NET_DEBUG_ICMPV4) |
| #define SYS_LOG_DOMAIN "net/icmpv4" |
| #define NET_LOG_ENABLED 1 |
| #endif |
| |
| #include <errno.h> |
| #include <misc/slist.h> |
| #include <net/net_core.h> |
| #include <net/nbuf.h> |
| #include <net/net_if.h> |
| #include "net_private.h" |
| #include "icmpv4.h" |
| #include "net_stats.h" |
| |
| #define BUF_WAIT_TIME K_SECONDS(1) |
| |
| static inline enum net_verdict handle_echo_request(struct net_buf *buf) |
| { |
| /* Note that we send the same data buffers back and just swap |
| * the addresses etc. |
| */ |
| struct in_addr addr; |
| |
| #if defined(CONFIG_NET_DEBUG_ICMPV4) |
| char out[sizeof("xxx.xxx.xxx.xxx")]; |
| |
| snprintk(out, sizeof(out), "%s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->dst)); |
| NET_DBG("Received Echo Request from %s to %s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->src), out); |
| #endif /* CONFIG_NET_DEBUG_ICMPV4 */ |
| |
| net_ipaddr_copy(&addr, &NET_IPV4_BUF(buf)->src); |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->src, |
| &NET_IPV4_BUF(buf)->dst); |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->dst, &addr); |
| |
| NET_ICMP_BUF(buf)->type = NET_ICMPV4_ECHO_REPLY; |
| NET_ICMP_BUF(buf)->code = 0; |
| NET_ICMP_BUF(buf)->chksum = 0; |
| NET_ICMP_BUF(buf)->chksum = ~net_calc_chksum_icmpv4(buf); |
| |
| #if defined(CONFIG_NET_DEBUG_ICMPV4) |
| snprintk(out, sizeof(out), "%s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->dst)); |
| NET_DBG("Sending Echo Reply from %s to %s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->src), out); |
| #endif /* CONFIG_NET_DEBUG_ICMPV4 */ |
| |
| if (net_send_data(buf) < 0) { |
| net_stats_update_icmp_drop(); |
| return NET_DROP; |
| } |
| |
| net_stats_update_icmp_sent(); |
| |
| return NET_OK; |
| } |
| |
| #define NET_ICMPV4_UNUSED_LEN 4 |
| |
| static inline void setup_ipv4_header(struct net_buf *buf, uint8_t extra_len, |
| uint8_t ttl, uint8_t icmp_type, |
| uint8_t icmp_code) |
| { |
| NET_IPV4_BUF(buf)->vhl = 0x45; |
| NET_IPV4_BUF(buf)->tos = 0x00; |
| NET_IPV4_BUF(buf)->len[0] = 0; |
| NET_IPV4_BUF(buf)->len[1] = sizeof(struct net_ipv4_hdr) + |
| NET_ICMPH_LEN + extra_len + NET_ICMPV4_UNUSED_LEN; |
| |
| NET_IPV4_BUF(buf)->proto = IPPROTO_ICMP; |
| NET_IPV4_BUF(buf)->ttl = ttl; |
| NET_IPV4_BUF(buf)->offset[0] = NET_IPV4_BUF(buf)->offset[1] = 0; |
| NET_IPV4_BUF(buf)->id[0] = NET_IPV4_BUF(buf)->id[1] = 0; |
| |
| net_nbuf_set_ip_hdr_len(buf, sizeof(struct net_ipv4_hdr)); |
| |
| NET_IPV4_BUF(buf)->chksum = 0; |
| NET_IPV4_BUF(buf)->chksum = ~net_calc_chksum_ipv4(buf); |
| |
| NET_ICMP_BUF(buf)->type = icmp_type; |
| NET_ICMP_BUF(buf)->code = icmp_code; |
| |
| memset(net_nbuf_icmp_data(buf) + sizeof(struct net_icmp_hdr), 0, |
| NET_ICMPV4_UNUSED_LEN); |
| } |
| |
| int net_icmpv4_send_echo_request(struct net_if *iface, |
| struct in_addr *dst, |
| uint16_t identifier, |
| uint16_t sequence) |
| { |
| const struct in_addr *src; |
| struct net_buf *buf, *frag; |
| |
| /* Take the first address of the network interface */ |
| src = &iface->ipv4.unicast[0].address.in_addr; |
| |
| /* We cast to IPv6 address but that should be ok in this case |
| * as IPv4 cannot be used in 802.15.4 where it is the reserve |
| * size can change depending on address. |
| */ |
| buf = net_nbuf_get_reserve_tx(net_if_get_ll_reserve(iface, |
| (const struct in6_addr *)dst), |
| K_FOREVER); |
| |
| frag = net_nbuf_get_frag(buf, K_FOREVER); |
| |
| net_buf_frag_add(buf, frag); |
| net_nbuf_set_family(buf, AF_INET); |
| net_nbuf_set_iface(buf, iface); |
| |
| setup_ipv4_header(buf, 0, net_if_ipv4_get_ttl(iface), |
| NET_ICMPV4_ECHO_REQUEST, 0); |
| |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->src, src); |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->dst, dst); |
| |
| NET_ICMPV4_ECHO_REQ_BUF(buf)->identifier = htons(identifier); |
| NET_ICMPV4_ECHO_REQ_BUF(buf)->sequence = htons(sequence); |
| |
| NET_ICMP_BUF(buf)->chksum = 0; |
| NET_ICMP_BUF(buf)->chksum = ~net_calc_chksum_icmpv4(buf); |
| |
| #if defined(CONFIG_NET_DEBUG_ICMPV4) |
| do { |
| char out[NET_IPV4_ADDR_LEN]; |
| |
| snprintk(out, sizeof(out), "%s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->dst)); |
| |
| NET_DBG("Sending ICMPv4 Echo Request type %d" |
| " from %s to %s", NET_ICMPV4_ECHO_REQUEST, |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->src), out); |
| } while (0); |
| #endif /* CONFIG_NET_DEBUG_ICMPV4 */ |
| |
| net_buf_add(buf->frags, sizeof(struct net_ipv4_hdr) + |
| sizeof(struct net_icmp_hdr) + |
| sizeof(struct net_icmpv4_echo_req)); |
| |
| if (net_send_data(buf) >= 0) { |
| net_stats_update_icmp_sent(); |
| return 0; |
| } |
| |
| net_nbuf_unref(buf); |
| net_stats_update_icmp_drop(); |
| |
| return -EIO; |
| } |
| |
| enum net_verdict net_icmpv4_input(struct net_buf *buf, uint16_t len, |
| uint8_t type, uint8_t code) |
| { |
| ARG_UNUSED(code); |
| ARG_UNUSED(len); |
| |
| net_stats_update_icmp_recv(); |
| |
| switch (type) { |
| case NET_ICMPV4_ECHO_REQUEST: |
| return handle_echo_request(buf); |
| } |
| |
| net_stats_update_icmp_drop(); |
| |
| return NET_DROP; |
| } |
| |
| int net_icmpv4_send_error(struct net_buf *orig, uint8_t type, uint8_t code) |
| { |
| struct net_buf *buf, *frag; |
| struct net_if *iface = net_nbuf_iface(orig); |
| size_t extra_len, reserve; |
| struct in_addr addr, *src, *dst; |
| int err = -EIO; |
| |
| if (NET_IPV4_BUF(orig)->proto == IPPROTO_ICMP) { |
| if (NET_ICMP_BUF(orig)->code < 8) { |
| /* We must not send ICMP errors back */ |
| err = -EINVAL; |
| goto drop_no_buf; |
| } |
| } |
| |
| iface = net_nbuf_iface(orig); |
| |
| buf = net_nbuf_get_reserve_tx(0, BUF_WAIT_TIME); |
| if (!buf) { |
| err = -ENOMEM; |
| goto drop_no_buf; |
| } |
| |
| reserve = sizeof(struct net_ipv4_hdr) + sizeof(struct net_icmp_hdr) + |
| NET_ICMPV4_UNUSED_LEN; |
| |
| if (NET_IPV4_BUF(orig)->proto == IPPROTO_UDP) { |
| extra_len = sizeof(struct net_ipv4_hdr) + |
| sizeof(struct net_udp_hdr); |
| } else if (NET_IPV4_BUF(orig)->proto == IPPROTO_TCP) { |
| extra_len = sizeof(struct net_ipv4_hdr); |
| /* FIXME, add TCP header length too */ |
| } else { |
| size_t space = CONFIG_NET_NBUF_DATA_SIZE - |
| net_if_get_ll_reserve(iface, NULL); |
| |
| if (reserve > space) { |
| extra_len = 0; |
| } else { |
| extra_len = space - reserve; |
| } |
| } |
| |
| /* We need to remember the original location of source and destination |
| * addresses as the net_nbuf_copy() will mangle the original buffer. |
| */ |
| src = &NET_IPV4_BUF(orig)->src; |
| dst = &NET_IPV4_BUF(orig)->dst; |
| |
| /* We only copy minimal IPv4 + next header from original message. |
| * This is so that the memory pressure is minimized. |
| */ |
| frag = net_nbuf_copy(orig, extra_len, reserve, BUF_WAIT_TIME); |
| if (!frag) { |
| err = -ENOMEM; |
| goto drop; |
| } |
| |
| net_buf_frag_add(buf, frag); |
| net_nbuf_set_family(buf, AF_INET); |
| net_nbuf_set_iface(buf, iface); |
| net_nbuf_set_ll_reserve(buf, net_buf_headroom(frag)); |
| |
| setup_ipv4_header(buf, extra_len, net_if_ipv4_get_ttl(iface), |
| type, code); |
| |
| net_ipaddr_copy(&addr, src); |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->src, dst); |
| net_ipaddr_copy(&NET_IPV4_BUF(buf)->dst, &addr); |
| |
| net_nbuf_ll_src(buf)->addr = net_nbuf_ll_dst(orig)->addr; |
| net_nbuf_ll_src(buf)->len = net_nbuf_ll_dst(orig)->len; |
| net_nbuf_ll_dst(buf)->addr = net_nbuf_ll_src(orig)->addr; |
| net_nbuf_ll_dst(buf)->len = net_nbuf_ll_src(orig)->len; |
| |
| NET_ICMP_BUF(buf)->chksum = 0; |
| NET_ICMP_BUF(buf)->chksum = ~net_calc_chksum_icmpv4(buf); |
| |
| #if defined(CONFIG_NET_DEBUG_ICMPV4) |
| do { |
| char out[sizeof("xxx.xxx.xxx.xxx")]; |
| |
| snprintk(out, sizeof(out), "%s", |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->dst)); |
| NET_DBG("Sending ICMPv4 Error Message type %d code %d " |
| "from %s to %s", type, code, |
| net_sprint_ipv4_addr(&NET_IPV4_BUF(buf)->src), out); |
| } while (0); |
| #endif /* CONFIG_NET_DEBUG_ICMPV4 */ |
| |
| if (net_send_data(buf) >= 0) { |
| net_stats_update_icmp_sent(); |
| return 0; |
| } |
| |
| drop: |
| net_nbuf_unref(buf); |
| |
| drop_no_buf: |
| net_stats_update_icmp_drop(); |
| |
| return err; |
| } |