xref: /third_party/lwip/src/core/ipv6/icmp6.c (revision 195972f6)
1/**
2 * @file
3 *
4 * IPv6 version of ICMP, as per RFC 4443.
5 */
6
7/*
8 * Copyright (c) 2010 Inico Technologies Ltd.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without modification,
12 * are permitted provided that the following conditions are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright notice,
15 *    this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright notice,
17 *    this list of conditions and the following disclaimer in the documentation
18 *    and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote products
20 *    derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
23 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
25 * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
27 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
31 * OF SUCH DAMAGE.
32 *
33 * This file is part of the lwIP TCP/IP stack.
34 *
35 * Author: Ivan Delamer <delamer@inicotech.com>
36 *
37 *
38 * Please coordinate changes and requests with Ivan Delamer
39 * <delamer@inicotech.com>
40 */
41
42#include "lwip/opt.h"
43
44#if LWIP_ICMP6 && LWIP_IPV6 /* don't build if not configured for use in lwipopts.h */
45
46#include "lwip/icmp6.h"
47#include "lwip/prot/icmp6.h"
48#include "lwip/ip6.h"
49#include "lwip/ip6_addr.h"
50#include "lwip/inet_chksum.h"
51#include "lwip/pbuf.h"
52#include "lwip/netif.h"
53#include "lwip/nd6.h"
54#include "lwip/mld6.h"
55#include "lwip/ip.h"
56#include "lwip/stats.h"
57
58#include <string.h>
59
60#if !LWIP_ICMP6_DATASIZE || (LWIP_ICMP6_DATASIZE > (IP6_MIN_MTU_LENGTH - IP6_HLEN - ICMP6_HLEN))
61#undef LWIP_ICMP6_DATASIZE
62#define LWIP_ICMP6_DATASIZE   (IP6_MIN_MTU_LENGTH - IP6_HLEN - ICMP6_HLEN)
63#endif
64
65/* Forward declarations */
66static void icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type);
67static void icmp6_send_response_with_addrs(struct pbuf *p, u8_t code, u32_t data,
68    u8_t type, const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr);
69static void icmp6_send_response_with_addrs_and_netif(struct pbuf *p, u8_t code, u32_t data,
70    u8_t type, const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr, struct netif *netif);
71
72
73/**
74 * Process an input ICMPv6 message. Called by ip6_input.
75 *
76 * Will generate a reply for echo requests. Other messages are forwarded
77 * to nd6_input, or mld6_input.
78 *
79 * @param p the mld packet, p->payload pointing to the icmpv6 header
80 * @param inp the netif on which this packet was received
81 */
82void
83icmp6_input(struct pbuf *p, struct netif *inp)
84{
85  struct icmp6_hdr *icmp6hdr;
86  struct pbuf *r;
87  const ip6_addr_t *reply_src;
88
89  ICMP6_STATS_INC(icmp6.recv);
90
91  /* Check that ICMPv6 header fits in payload */
92  if (p->len < sizeof(struct icmp6_hdr)) {
93    /* drop short packets */
94    pbuf_free(p);
95    ICMP6_STATS_INC(icmp6.lenerr);
96    ICMP6_STATS_INC(icmp6.drop);
97    return;
98  }
99
100  icmp6hdr = (struct icmp6_hdr *)p->payload;
101
102#if CHECKSUM_CHECK_ICMP6
103  IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_CHECK_ICMP6) {
104    if (ip6_chksum_pseudo(p, IP6_NEXTH_ICMP6, p->tot_len, ip6_current_src_addr(),
105                          ip6_current_dest_addr()) != 0) {
106      /* Checksum failed */
107      pbuf_free(p);
108      ICMP6_STATS_INC(icmp6.chkerr);
109      ICMP6_STATS_INC(icmp6.drop);
110      return;
111    }
112  }
113#endif /* CHECKSUM_CHECK_ICMP6 */
114
115  switch (icmp6hdr->type) {
116  case ICMP6_TYPE_NA: /* Neighbor advertisement */
117  case ICMP6_TYPE_NS: /* Neighbor solicitation */
118  case ICMP6_TYPE_RA: /* Router advertisement */
119  case ICMP6_TYPE_RD: /* Redirect */
120  case ICMP6_TYPE_PTB: /* Packet too big */
121    nd6_input(p, inp);
122    return;
123  case ICMP6_TYPE_RS:
124#if LWIP_IPV6_FORWARD
125    /* @todo implement router functionality */
126#endif
127    break;
128#if LWIP_IPV6_MLD
129  case ICMP6_TYPE_MLQ:
130  case ICMP6_TYPE_MLR:
131  case ICMP6_TYPE_MLD:
132    mld6_input(p, inp);
133    return;
134#endif
135  case ICMP6_TYPE_EREQ:
136#if !LWIP_MULTICAST_PING
137    /* multicast destination address? */
138    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
139      /* drop */
140      pbuf_free(p);
141      ICMP6_STATS_INC(icmp6.drop);
142      return;
143    }
144#endif /* LWIP_MULTICAST_PING */
145
146    /* Allocate reply. */
147    r = pbuf_alloc(PBUF_IP, p->tot_len, PBUF_RAM);
148    if (r == NULL) {
149      /* drop */
150      pbuf_free(p);
151      ICMP6_STATS_INC(icmp6.memerr);
152      return;
153    }
154
155    /* Copy echo request. */
156    if (pbuf_copy(r, p) != ERR_OK) {
157      /* drop */
158      pbuf_free(p);
159      pbuf_free(r);
160      ICMP6_STATS_INC(icmp6.err);
161      return;
162    }
163
164    /* Determine reply source IPv6 address. */
165#if LWIP_MULTICAST_PING
166    if (ip6_addr_ismulticast(ip6_current_dest_addr())) {
167      reply_src = ip_2_ip6(ip6_select_source_address(inp, ip6_current_src_addr()));
168      if (reply_src == NULL) {
169        /* drop */
170        pbuf_free(p);
171        pbuf_free(r);
172        ICMP6_STATS_INC(icmp6.rterr);
173        return;
174      }
175    }
176    else
177#endif /* LWIP_MULTICAST_PING */
178    {
179      reply_src = ip6_current_dest_addr();
180    }
181
182    /* Set fields in reply. */
183    ((struct icmp6_echo_hdr *)(r->payload))->type = ICMP6_TYPE_EREP;
184    ((struct icmp6_echo_hdr *)(r->payload))->chksum = 0;
185#if CHECKSUM_GEN_ICMP6
186    IF__NETIF_CHECKSUM_ENABLED(inp, NETIF_CHECKSUM_GEN_ICMP6) {
187      ((struct icmp6_echo_hdr *)(r->payload))->chksum = ip6_chksum_pseudo(r,
188          IP6_NEXTH_ICMP6, r->tot_len, reply_src, ip6_current_src_addr());
189    }
190#endif /* CHECKSUM_GEN_ICMP6 */
191
192    /* Send reply. */
193    ICMP6_STATS_INC(icmp6.xmit);
194    ip6_output_if(r, reply_src, ip6_current_src_addr(),
195        LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, inp);
196    pbuf_free(r);
197
198    break;
199  default:
200    ICMP6_STATS_INC(icmp6.proterr);
201    ICMP6_STATS_INC(icmp6.drop);
202    break;
203  }
204
205  pbuf_free(p);
206}
207
208
209/**
210 * Send an icmpv6 'destination unreachable' packet.
211 *
212 * This function must be used only in direct response to a packet that is being
213 * received right now. Otherwise, address zones would be lost.
214 *
215 * @param p the input packet for which the 'unreachable' should be sent,
216 *          p->payload pointing to the IPv6 header
217 * @param c ICMPv6 code for the unreachable type
218 */
219void
220icmp6_dest_unreach(struct pbuf *p, enum icmp6_dur_code c)
221{
222  icmp6_send_response(p, c, 0, ICMP6_TYPE_DUR);
223}
224
225/**
226 * Send an icmpv6 'packet too big' packet.
227 *
228 * This function must be used only in direct response to a packet that is being
229 * received right now. Otherwise, address zones would be lost.
230 *
231 * @param p the input packet for which the 'packet too big' should be sent,
232 *          p->payload pointing to the IPv6 header
233 * @param mtu the maximum mtu that we can accept
234 */
235void
236icmp6_packet_too_big(struct pbuf *p, u32_t mtu)
237{
238  icmp6_send_response(p, 0, mtu, ICMP6_TYPE_PTB);
239}
240
241/**
242 * Send an icmpv6 'time exceeded' packet.
243 *
244 * This function must be used only in direct response to a packet that is being
245 * received right now. Otherwise, address zones would be lost.
246 *
247 * @param p the input packet for which the 'time exceeded' should be sent,
248 *          p->payload pointing to the IPv6 header
249 * @param c ICMPv6 code for the time exceeded type
250 */
251void
252icmp6_time_exceeded(struct pbuf *p, enum icmp6_te_code c)
253{
254  icmp6_send_response(p, c, 0, ICMP6_TYPE_TE);
255}
256
257/**
258 * Send an icmpv6 'time exceeded' packet, with explicit source and destination
259 * addresses.
260 *
261 * This function may be used to send a response sometime after receiving the
262 * packet for which this response is meant. The provided source and destination
263 * addresses are used primarily to retain their zone information.
264 *
265 * @param p the input packet for which the 'time exceeded' should be sent,
266 *          p->payload pointing to the IPv6 header
267 * @param c ICMPv6 code for the time exceeded type
268 * @param src_addr source address of the original packet, with zone information
269 * @param dest_addr destination address of the original packet, with zone
270 *                  information
271 */
272void
273icmp6_time_exceeded_with_addrs(struct pbuf *p, enum icmp6_te_code c,
274    const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr)
275{
276  icmp6_send_response_with_addrs(p, c, 0, ICMP6_TYPE_TE, src_addr, dest_addr);
277}
278
279/**
280 * Send an icmpv6 'parameter problem' packet.
281 *
282 * This function must be used only in direct response to a packet that is being
283 * received right now. Otherwise, address zones would be lost and the calculated
284 * offset would be wrong (calculated against ip6_current_header()).
285 *
286 * @param p the input packet for which the 'param problem' should be sent,
287 *          p->payload pointing to the IP header
288 * @param c ICMPv6 code for the param problem type
289 * @param pointer the pointer to the byte where the parameter is found
290 */
291void
292icmp6_param_problem(struct pbuf *p, enum icmp6_pp_code c, const void *pointer)
293{
294  u32_t pointer_u32 = (u32_t)((const u8_t *)pointer - (const u8_t *)ip6_current_header());
295  icmp6_send_response(p, c, pointer_u32, ICMP6_TYPE_PP);
296}
297
298/**
299 * Send an ICMPv6 packet in response to an incoming packet.
300 * The packet is sent *to* ip_current_src_addr() on ip_current_netif().
301 *
302 * @param p the input packet for which the response should be sent,
303 *          p->payload pointing to the IPv6 header
304 * @param code Code of the ICMPv6 header
305 * @param data Additional 32-bit parameter in the ICMPv6 header
306 * @param type Type of the ICMPv6 header
307 */
308static void
309icmp6_send_response(struct pbuf *p, u8_t code, u32_t data, u8_t type)
310{
311  const struct ip6_addr *reply_src, *reply_dest;
312  struct netif *netif = ip_current_netif();
313
314  LWIP_ASSERT("icmpv6 packet not a direct response", netif != NULL);
315  reply_dest = ip6_current_src_addr();
316
317  /* Select an address to use as source. */
318  reply_src = ip_2_ip6(ip6_select_source_address(netif, reply_dest));
319  if (reply_src == NULL) {
320    ICMP6_STATS_INC(icmp6.rterr);
321    return;
322  }
323  icmp6_send_response_with_addrs_and_netif(p, code, data, type, reply_src, reply_dest, netif);
324}
325
326/**
327 * Send an ICMPv6 packet in response to an incoming packet.
328 *
329 * Call this function if the packet is NOT sent as a direct response to an
330 * incoming packet, but rather sometime later (e.g. for a fragment reassembly
331 * timeout). The caller must provide the zoned source and destination addresses
332 * from the original packet with the src_addr and dest_addr parameters. The
333 * reason for this approach is that while the addresses themselves are part of
334 * the original packet, their zone information is not, thus possibly resulting
335 * in a link-local response being sent over the wrong link.
336 *
337 * @param p the input packet for which the response should be sent,
338 *          p->payload pointing to the IPv6 header
339 * @param code Code of the ICMPv6 header
340 * @param data Additional 32-bit parameter in the ICMPv6 header
341 * @param type Type of the ICMPv6 header
342 * @param src_addr original source address
343 * @param dest_addr original destination address
344 */
345static void
346icmp6_send_response_with_addrs(struct pbuf *p, u8_t code, u32_t data, u8_t type,
347    const ip6_addr_t *src_addr, const ip6_addr_t *dest_addr)
348{
349  const struct ip6_addr *reply_src, *reply_dest;
350  struct netif *netif;
351
352  /* Get the destination address and netif for this ICMP message. */
353  LWIP_ASSERT("must provide both source and destination", src_addr != NULL);
354  LWIP_ASSERT("must provide both source and destination", dest_addr != NULL);
355
356  /* Special case, as ip6_current_xxx is either NULL, or points
357     to a different packet than the one that expired. */
358  IP6_ADDR_ZONECHECK(src_addr);
359  IP6_ADDR_ZONECHECK(dest_addr);
360  /* Swap source and destination for the reply. */
361  reply_dest = src_addr;
362  reply_src = dest_addr;
363#ifdef LOSCFG_NET_CONTAINER
364  netif = ip6_route(reply_src, reply_dest, get_root_net_group());
365#else
366  netif = ip6_route(reply_src, reply_dest);
367#endif
368  if (netif == NULL) {
369    ICMP6_STATS_INC(icmp6.rterr);
370    return;
371  }
372  icmp6_send_response_with_addrs_and_netif(p, code, data, type, reply_src,
373    reply_dest, netif);
374}
375
376/**
377 * Send an ICMPv6 packet (with srd/dst address and netif given).
378 *
379 * @param p the input packet for which the response should be sent,
380 *          p->payload pointing to the IPv6 header
381 * @param code Code of the ICMPv6 header
382 * @param data Additional 32-bit parameter in the ICMPv6 header
383 * @param type Type of the ICMPv6 header
384 * @param reply_src source address of the packet to send
385 * @param reply_dest destination address of the packet to send
386 * @param netif netif to send the packet
387 */
388static void
389icmp6_send_response_with_addrs_and_netif(struct pbuf *p, u8_t code, u32_t data, u8_t type,
390    const ip6_addr_t *reply_src, const ip6_addr_t *reply_dest, struct netif *netif)
391{
392  struct pbuf *q;
393  struct icmp6_hdr *icmp6hdr;
394  u16_t datalen = LWIP_MIN(p->tot_len, LWIP_ICMP6_DATASIZE);
395  u16_t offset;
396
397  /* ICMPv6 header + datalen (as much of the offending packet as possible) */
398  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp6_hdr) + datalen,
399                 PBUF_RAM);
400  if (q == NULL) {
401    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMPv6 packet.\n"));
402    ICMP6_STATS_INC(icmp6.memerr);
403    return;
404  }
405  LWIP_ASSERT("check that first pbuf can hold icmp6 header",
406             (q->len >= (sizeof(struct icmp6_hdr))));
407
408  icmp6hdr = (struct icmp6_hdr *)q->payload;
409  icmp6hdr->type = type;
410  icmp6hdr->code = code;
411  icmp6hdr->data = lwip_htonl(data);
412
413  /* copy fields from original packet (which may be a chain of pbufs) */
414  offset = sizeof(struct icmp6_hdr);
415  while (p && datalen) {
416    u16_t len = LWIP_MIN(datalen, p->len);
417    err_t res = pbuf_take_at(q, p->payload, len, offset);
418    if (res != ERR_OK) break;
419    datalen -= len;
420    offset += len;
421    p = p->next;
422  }
423
424  /* calculate checksum */
425  icmp6hdr->chksum = 0;
426#if CHECKSUM_GEN_ICMP6
427  IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_ICMP6) {
428    icmp6hdr->chksum = ip6_chksum_pseudo(q, IP6_NEXTH_ICMP6, q->tot_len,
429      reply_src, reply_dest);
430  }
431#endif /* CHECKSUM_GEN_ICMP6 */
432
433  ICMP6_STATS_INC(icmp6.xmit);
434  ip6_output_if(q, reply_src, reply_dest, LWIP_ICMP6_HL, 0, IP6_NEXTH_ICMP6, netif);
435  pbuf_free(q);
436}
437
438#endif /* LWIP_ICMP6 && LWIP_IPV6 */
439