| /** |
| * @file |
| * |
| * IPv6 version of ICMP, as per RFC 4443. |
| */ |
| |
| /* |
| * Copyright (c) 2010 Inico Technologies Ltd. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without modification, |
| * are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT |
| * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
| * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
| * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY |
| * OF SUCH DAMAGE. |
| * |
| * This file is part of the lwIP TCP/IP stack. |
| * |
| * Author: Ivan Delamer <delamer@inicotech.com> |
| * |
| * |
| * Please coordinate changes and requests with Ivan Delamer |
| * <delamer@inicotech.com> |
| */ |
| |
| #include "lwip/opt.h" |
| |
| #if LWIP_ICMP6 && LWIP_IPV6 /* don't build if not configured for use in lwipopts.h */ |
| |
| #include "lwip/icmp6.h" |
| #include "lwip/prot/icmp6.h" |
| #include "lwip/ip6.h" |
| #include "lwip/ip6_addr.h" |
| #include "lwip/inet_chksum.h" |
| #include "lwip/pbuf.h" |
| #include "lwip/netif.h" |
| #include "lwip/nd6.h" |
| #include "lwip/mld6.h" |
| #include "lwip/ip.h" |
| #include "lwip/stats.h" |
| |
| #include <string.h> |
| |
| #if !LWIP_ICMP6_DATASIZE || (LWIP_ICMP6_DATASIZE > (IP6_MIN_MTU_LENGTH - IP6_HLEN - ICMP6_HLEN)) |
| #undef LWIP_ICMP6_DATASIZE |
| #define LWIP_ICMP6_DATASIZE (IP6_MIN_MTU_LENGTH - IP6_HLEN - ICMP6_HLEN) |
| #endif |
| |
| /* Forward declarations */ |
| static void icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type); |
| static void icmp6_send_response_with_addrs(struct pbuf *p, u8_t code, u32_t data, |
| u8_t type, const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr); |
| static void icmp6_send_response_with_addrs_and_netif(struct pbuf *p, u8_t code, u32_t data, |
| u8_t type, const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr, struct netif *netif); |
| |
| |
| /** |
| * Process an input ICMPv6 message. Called by ip6_input. |
| * |
| * Will generate a reply for echo requests. Other messages are forwarded |
| * to nd6_input, or mld6_input. |
| * |
| * @param p the mld packet, p->payload pointing to the icmpv6 header |
| * @param inp the netif on which this packet was received |
| */ |
| void |
| icmp6_input(struct pbuf *p, struct netif *inp) |
| { |
| struct icmp6_hdr *icmp6hdr; |
| struct pbuf *r; |
| const ip6_addr_t *reply_src; |
| |
| ICMP6_STATS_INC(icmp6.recv); |
| |
| /* Check that ICMPv6 header fits in payload */ |
| if (p->len < sizeof(struct icmp6_hdr)) { |
| /* drop short packets */ |
| pbuf_free(p); |
| ICMP6_STATS_INC(icmp6.lenerr); |
| ICMP6_STATS_INC(icmp6.drop); |
| return; |
| } |
| |
| icmp6hdr = (struct icmp6_hdr *)p->payload; |
| |
| #if CHECKSUM_CHECK_ICMP6 |
| IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_ICMP6) { |
| if (ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len, ip6_current_src_addr(), |
| ip6_current_dest_addr()) != 0) { |
| /* Checksum failed */ |
| pbuf_free(p); |
| ICMP6_STATS_INC(icmp6.chkerr); |
| ICMP6_STATS_INC(icmp6.drop); |
| return; |
| } |
| } |
| #endif /* CHECKSUM_CHECK_ICMP6 */ |
| |
| switch (icmp6hdr->type) { |
| case ICMP6_TYPE_NA: /* Neighbor advertisement */ |
| case ICMP6_TYPE_NS: /* Neighbor solicitation */ |
| case ICMP6_TYPE_RA: /* Router advertisement */ |
| case ICMP6_TYPE_RD: /* Redirect */ |
| case ICMP6_TYPE_PTB: /* Packet too big */ |
| nd6_input(p, inp); |
| return; |
| case ICMP6_TYPE_RS: |
| #if LWIP_IPV6_FORWARD |
| /* @todo implement router functionality */ |
| #endif |
| break; |
| #if LWIP_IPV6_MLD |
| case ICMP6_TYPE_MLQ: |
| case ICMP6_TYPE_MLR: |
| case ICMP6_TYPE_MLD: |
| mld6_input(p, inp); |
| return; |
| #endif |
| case ICMP6_TYPE_EREQ: |
| #if !LWIP_MULTICAST_PING |
| /* multicast destination address? */ |
| if (ip6_addr_ismulticast(ip6_current_dest_addr())) { |
| /* drop */ |
| pbuf_free(p); |
| ICMP6_STATS_INC(icmp6.drop); |
| return; |
| } |
| #endif /* LWIP_MULTICAST_PING */ |
| |
| /* Allocate reply. */ |
| r = pbuf_alloc(PBUF_IP, p->tot_len, PBUF_RAM); |
| if (r == NULL) { |
| /* drop */ |
| pbuf_free(p); |
| ICMP6_STATS_INC(icmp6.memerr); |
| return; |
| } |
| |
| /* Copy echo request. */ |
| if (pbuf_copy(r, p) != ERR_OK) { |
| /* drop */ |
| pbuf_free(p); |
| pbuf_free(r); |
| ICMP6_STATS_INC(icmp6.err); |
| return; |
| } |
| |
| /* Determine reply source IPv6 address. */ |
| #if LWIP_MULTICAST_PING |
| if (ip6_addr_ismulticast(ip6_current_dest_addr())) { |
| reply_src = ip_2_ip6(ip6_select_source_address(inp, ip6_current_src_addr())); |
| if (reply_src == NULL) { |
| /* drop */ |
| pbuf_free(p); |
| pbuf_free(r); |
| ICMP6_STATS_INC(icmp6.rterr); |
| return; |
| } |
| } |
| else |
| #endif /* LWIP_MULTICAST_PING */ |
| { |
| reply_src = ip6_current_dest_addr(); |
| } |
| |
| /* Set fields in reply. */ |
| ((struct icmp6_echo_hdr *)(r->payload))->type = ICMP6_TYPE_EREP; |
| ((struct icmp6_echo_hdr *)(r->payload))->chksum = 0; |
| #if CHECKSUM_GEN_ICMP6 |
| IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP6) { |
| ((struct icmp6_echo_hdr *)(r->payload))->chksum = ip6_chksum_pseudo(r, |
| IP6_NEXTH_ICMP6, r->tot_len, reply_src, ip6_current_src_addr()); |
| } |
| #endif /* CHECKSUM_GEN_ICMP6 */ |
| |
| /* Send reply. */ |
| ICMP6_STATS_INC(icmp6.xmit); |
| ip6_output_if(r, reply_src, ip6_current_src_addr(), |
| LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, inp); |
| pbuf_free(r); |
| |
| break; |
| default: |
| ICMP6_STATS_INC(icmp6.proterr); |
| ICMP6_STATS_INC(icmp6.drop); |
| break; |
| } |
| |
| pbuf_free(p); |
| } |
| |
| |
| /** |
| * Send an icmpv6 'destination unreachable' packet. |
| * |
| * This function must be used only in direct response to a packet that is being |
| * received right now. Otherwise, address zones would be lost. |
| * |
| * @param p the input packet for which the 'unreachable' should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param c ICMPv6 code for the unreachable type |
| */ |
| void |
| icmp6_dest_unreach(struct pbuf *p, enum icmp6_dur_code c) |
| { |
| icmp6_send_response(p, c, 0, ICMP6_TYPE_DUR); |
| } |
| |
| /** |
| * Send an icmpv6 'packet too big' packet. |
| * |
| * This function must be used only in direct response to a packet that is being |
| * received right now. Otherwise, address zones would be lost. |
| * |
| * @param p the input packet for which the 'packet too big' should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param mtu the maximum mtu that we can accept |
| */ |
| void |
| icmp6_packet_too_big(struct pbuf *p, u32_t mtu) |
| { |
| icmp6_send_response(p, 0, mtu, ICMP6_TYPE_PTB); |
| } |
| |
| /** |
| * Send an icmpv6 'time exceeded' packet. |
| * |
| * This function must be used only in direct response to a packet that is being |
| * received right now. Otherwise, address zones would be lost. |
| * |
| * @param p the input packet for which the 'time exceeded' should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param c ICMPv6 code for the time exceeded type |
| */ |
| void |
| icmp6_time_exceeded(struct pbuf *p, enum icmp6_te_code c) |
| { |
| icmp6_send_response(p, c, 0, ICMP6_TYPE_TE); |
| } |
| |
| /** |
| * Send an icmpv6 'time exceeded' packet, with explicit source and destination |
| * addresses. |
| * |
| * This function may be used to send a response sometime after receiving the |
| * packet for which this response is meant. The provided source and destination |
| * addresses are used primarily to retain their zone information. |
| * |
| * @param p the input packet for which the 'time exceeded' should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param c ICMPv6 code for the time exceeded type |
| * @param src_addr source address of the original packet, with zone information |
| * @param dest_addr destination address of the original packet, with zone |
| * information |
| */ |
| void |
| icmp6_time_exceeded_with_addrs(struct pbuf *p, enum icmp6_te_code c, |
| const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr) |
| { |
| icmp6_send_response_with_addrs(p, c, 0, ICMP6_TYPE_TE, src_addr, dest_addr); |
| } |
| |
| /** |
| * Send an icmpv6 'parameter problem' packet. |
| * |
| * This function must be used only in direct response to a packet that is being |
| * received right now. Otherwise, address zones would be lost and the calculated |
| * offset would be wrong (calculated against ip6_current_header()). |
| * |
| * @param p the input packet for which the 'param problem' should be sent, |
| * p->payload pointing to the IP header |
| * @param c ICMPv6 code for the param problem type |
| * @param pointer the pointer to the byte where the parameter is found |
| */ |
| void |
| icmp6_param_problem(struct pbuf *p, enum icmp6_pp_code c, const void *pointer) |
| { |
| u32_t pointer_u32 = (u32_t)((const u8_t *)pointer - (const u8_t *)ip6_current_header()); |
| icmp6_send_response(p, c, pointer_u32, ICMP6_TYPE_PP); |
| } |
| |
| /** |
| * Send an ICMPv6 packet in response to an incoming packet. |
| * The packet is sent *to* ip_current_src_addr() on ip_current_netif(). |
| * |
| * @param p the input packet for which the response should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param code Code of the ICMPv6 header |
| * @param data Additional 32-bit parameter in the ICMPv6 header |
| * @param type Type of the ICMPv6 header |
| */ |
| static void |
| icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type) |
| { |
| const struct ip6_addr *reply_src, *reply_dest; |
| struct netif *netif = ip_current_netif(); |
| |
| LWIP_ASSERT("icmpv6 packet not a direct response", netif != NULL); |
| reply_dest = ip6_current_src_addr(); |
| |
| /* Select an address to use as source. */ |
| reply_src = ip_2_ip6(ip6_select_source_address(netif, reply_dest)); |
| if (reply_src == NULL) { |
| ICMP6_STATS_INC(icmp6.rterr); |
| return; |
| } |
| icmp6_send_response_with_addrs_and_netif(p, code, data, type, reply_src, reply_dest, netif); |
| } |
| |
| /** |
| * Send an ICMPv6 packet in response to an incoming packet. |
| * |
| * Call this function if the packet is NOT sent as a direct response to an |
| * incoming packet, but rather sometime later (e.g. for a fragment reassembly |
| * timeout). The caller must provide the zoned source and destination addresses |
| * from the original packet with the src_addr and dest_addr parameters. The |
| * reason for this approach is that while the addresses themselves are part of |
| * the original packet, their zone information is not, thus possibly resulting |
| * in a link-local response being sent over the wrong link. |
| * |
| * @param p the input packet for which the response should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param code Code of the ICMPv6 header |
| * @param data Additional 32-bit parameter in the ICMPv6 header |
| * @param type Type of the ICMPv6 header |
| * @param src_addr original source address |
| * @param dest_addr original destination address |
| */ |
| static void |
| icmp6_send_response_with_addrs(struct pbuf *p, u8_t code, u32_t data, u8_t type, |
| const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr) |
| { |
| const struct ip6_addr *reply_src, *reply_dest; |
| struct netif *netif; |
| |
| /* Get the destination address and netif for this ICMP message. */ |
| LWIP_ASSERT("must provide both source and destination", src_addr != NULL); |
| LWIP_ASSERT("must provide both source and destination", dest_addr != NULL); |
| |
| /* Special case, as ip6_current_xxx is either NULL, or points |
| to a different packet than the one that expired. */ |
| IP6_ADDR_ZONECHECK(src_addr); |
| IP6_ADDR_ZONECHECK(dest_addr); |
| /* Swap source and destination for the reply. */ |
| reply_dest = src_addr; |
| reply_src = dest_addr; |
| netif = ip6_route(reply_src, reply_dest); |
| if (netif == NULL) { |
| ICMP6_STATS_INC(icmp6.rterr); |
| return; |
| } |
| icmp6_send_response_with_addrs_and_netif(p, code, data, type, reply_src, |
| reply_dest, netif); |
| } |
| |
| /** |
| * Send an ICMPv6 packet (with srd/dst address and netif given). |
| * |
| * @param p the input packet for which the response should be sent, |
| * p->payload pointing to the IPv6 header |
| * @param code Code of the ICMPv6 header |
| * @param data Additional 32-bit parameter in the ICMPv6 header |
| * @param type Type of the ICMPv6 header |
| * @param reply_src source address of the packet to send |
| * @param reply_dest destination address of the packet to send |
| * @param netif netif to send the packet |
| */ |
| static void |
| icmp6_send_response_with_addrs_and_netif(struct pbuf *p, u8_t code, u32_t data, u8_t type, |
| const ip6_addr_t *reply_src, const ip6_addr_t *reply_dest, struct netif *netif) |
| { |
| struct pbuf *q; |
| struct icmp6_hdr *icmp6hdr; |
| u16_t datalen = LWIP_MIN(p->tot_len, LWIP_ICMP6_DATASIZE); |
| u16_t offset; |
| |
| /* ICMPv6 header + datalen (as much of the offending packet as possible) */ |
| q = pbuf_alloc(PBUF_IP, sizeof(struct icmp6_hdr) + datalen, |
| PBUF_RAM); |
| if (q == NULL) { |
| LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMPv6 packet.\n")); |
| ICMP6_STATS_INC(icmp6.memerr); |
| return; |
| } |
| LWIP_ASSERT("check that first pbuf can hold icmp6 header", |
| (q->len >= (sizeof(struct icmp6_hdr)))); |
| |
| icmp6hdr = (struct icmp6_hdr *)q->payload; |
| icmp6hdr->type = type; |
| icmp6hdr->code = code; |
| icmp6hdr->data = lwip_htonl(data); |
| |
| /* copy fields from original packet (which may be a chain of pbufs) */ |
| offset = sizeof(struct icmp6_hdr); |
| while (p && datalen) { |
| u16_t len = LWIP_MIN(datalen, p->len); |
| err_t res = pbuf_take_at(q, p->payload, len, offset); |
| if (res != ERR_OK) break; |
| datalen -= len; |
| offset += len; |
| p = p->next; |
| } |
| |
| /* calculate checksum */ |
| icmp6hdr->chksum = 0; |
| #if CHECKSUM_GEN_ICMP6 |
| IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) { |
| icmp6hdr->chksum = ip6_chksum_pseudo(q, IP6_NEXTH_ICMP6, q->tot_len, |
| reply_src, reply_dest); |
| } |
| #endif /* CHECKSUM_GEN_ICMP6 */ |
| |
| ICMP6_STATS_INC(icmp6.xmit); |
| ip6_output_if(q, reply_src, reply_dest, LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif); |
| pbuf_free(q); |
| } |
| |
| #endif /* LWIP_ICMP6 && LWIP_IPV6 */ |