blob: 4fd10216aed35b80bd931d5933143ec596edc804 [file] [log] [blame]
/**
* @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 */