1 : /*
2 : * packets.c - helpers to send Teredo packet from relay/client
3 : * $Id: packets.c 1938 2007-02-22 20:55:28Z remi $
4 : *
5 : * See "Teredo: Tunneling IPv6 over UDP through NATs"
6 : * for more information
7 : */
8 :
9 : /***********************************************************************
10 : * Copyright © 2004-2007 Rémi Denis-Courmont. *
11 : * This program is free software; you can redistribute and/or modify *
12 : * it under the terms of the GNU General Public License as published *
13 : * by the Free Software Foundation; version 2 of the license. *
14 : * *
15 : * This program is distributed in the hope that it will be useful, *
16 : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
18 : * See the GNU General Public License for more details. *
19 : * *
20 : * You should have received a copy of the GNU General Public License *
21 : * along with this program; if not, you can get it from: *
22 : * http://www.gnu.org/copyleft/gpl.html *
23 : ***********************************************************************/
24 :
25 : #ifdef HAVE_CONFIG_H
26 : # include <config.h>
27 : #endif
28 :
29 : #include <gettext.h>
30 :
31 : #include <string.h>
32 : #include <stdbool.h>
33 : #include <inttypes.h>
34 :
35 : #include <sys/types.h>
36 : #include <netinet/in.h>
37 : #include <netinet/ip6.h> /* struct ip6_hdr */
38 : #include <netinet/icmp6.h> /* router solicication */
39 : #include <syslog.h>
40 : #include <sys/uio.h>
41 :
42 : #include "teredo.h"
43 : #include "v4global.h" // is_ipv4_global_unicast()
44 : #include "teredo-udp.h"
45 :
46 : #include <time.h>
47 : #include "security.h"
48 :
49 : #include "packets.h"
50 : #include "checksum.h"
51 :
52 :
53 : /**
54 : * Sends a Teredo Bubble.
55 : *
56 : * @param ip destination IPv4
57 : * @param port destination UDP port
58 : * @param src unaligned pointer to source IPv6 address
59 : * @param dst unaligned pointer to destination IPv6 address
60 : *
61 : * @return 0 on success, -1 on error.
62 : */
63 : int
64 : teredo_send_bubble (int fd, uint32_t ip, uint16_t port,
65 : const uint8_t *src, const uint8_t *dst)
66 0 : {
67 0 : if (is_ipv4_global_unicast (ip))
68 : {
69 : static const uint8_t head[] =
70 : "\x60\x00\x00\x00" /* flow */
71 : "\x00\x00" /* plen = 0 */
72 : "\x3b" /* nxt = IPPROTO_NONE */
73 : "\x00" /* hlim = 0 */;
74 : struct iovec iov[3] =
75 : {
76 : { (void *)head, 8 },
77 : { (void *)src, 16 },
78 : { (void *)dst, 16 }
79 0 : };
80 :
81 0 : return teredo_sendv (fd, iov, 3, ip, port) == 40 ? 0 : -1;
82 : }
83 :
84 0 : return 0;
85 : }
86 :
87 :
88 : /**
89 : * Sends a Teredo Bubble.
90 : *
91 : * @param dst Teredo destination address.
92 : * @param indirect determines whether the bubble is sent to the server (true)
93 : * or the client (if indirect is false) - as determined from dst.
94 : *
95 : * @return 0 on success, -1 on error.
96 : */
97 : int
98 : SendBubbleFromDst (int fd, const struct in6_addr *dst, bool indirect)
99 0 : {
100 0 : uint32_t ip = IN6_TEREDO_IPV4 (dst);
101 0 : uint16_t port = IN6_TEREDO_PORT (dst);
102 :
103 : struct in6_addr src;
104 0 : memcpy (src.s6_addr, "\xfe\x80\x00\x00\x00\x00\x00\x00", 8);
105 : /* TODO: use some time information */
106 0 : teredo_get_nonce (0, ip, port, src.s6_addr + 8);
107 0 : src.s6_addr[8] &= 0xfc; /* Modified EUI-64 */
108 :
109 0 : if (indirect)
110 : {
111 0 : ip = IN6_TEREDO_SERVER (dst);
112 0 : port = htons (IPPORT_TEREDO);
113 : }
114 :
115 0 : return teredo_send_bubble (fd, ip, port, src.s6_addr, dst->s6_addr);
116 : }
117 :
118 :
119 : int CheckBubble (const teredo_packet *packet)
120 0 : {
121 0 : const struct ip6_hdr *ip6 = (const struct ip6_hdr *)packet->ip6;
122 0 : const struct in6_addr *me = &ip6->ip6_dst, *it = &ip6->ip6_src;
123 :
124 : uint8_t hash[8];
125 : /* TODO: use some time information */
126 0 : teredo_get_nonce (0, IN6_TEREDO_IPV4 (it), IN6_TEREDO_PORT (it), hash);
127 0 : hash[0] &= 0xfc; /* Modified EUI-64: non-global, non-group address */
128 :
129 0 : return memcmp (hash, me->s6_addr + 8, 8) ? -1 : 0;
130 : }
131 :
132 :
133 : #ifdef MIREDO_TEREDO_CLIENT
134 : static const struct in6_addr in6addr_allrouters =
135 : { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2 } } };
136 :
137 : /**
138 : * Sends a router solication with an Authentication header.
139 : *
140 : * @param fd socket through which the RS will be sent
141 : * @param server_ip server IPv4 address toward which the solicitation should
142 : * be encapsulated (network byte order)
143 : * @param nonce pointer to the 8-bytes authentication nonce
144 : * @param cone whether to send a Teredo “cone” solicitation
145 : *
146 : * @return 0 on success, -1 on error.
147 : */
148 : int
149 : teredo_send_rs (int fd, uint32_t server_ip,
150 : const unsigned char *nonce, bool cone)
151 0 : {
152 : struct teredo_simple_auth auth;
153 : struct
154 : {
155 : struct ip6_hdr ip6;
156 : struct nd_router_solicit rs;
157 : } rs;
158 0 : struct iovec iov[2] = { { &auth, 13 }, { &rs, sizeof (rs) } };
159 :
160 : // Authentication header
161 : // TODO: secure qualification
162 :
163 0 : auth.hdr.hdr.zero = 0;
164 0 : auth.hdr.hdr.code = teredo_auth_hdr;
165 0 : auth.hdr.id_len = auth.hdr.au_len = 0;
166 0 : memcpy (auth.nonce, nonce, 8);
167 0 : auth.confirmation = 0;
168 :
169 0 : rs.ip6.ip6_flow = htonl (0x60000000);
170 0 : rs.ip6.ip6_plen = htons (sizeof (rs) - sizeof (rs.ip6));
171 0 : rs.ip6.ip6_nxt = IPPROTO_ICMPV6;
172 0 : rs.ip6.ip6_hlim = 255;
173 0 : memcpy (&rs.ip6.ip6_src, cone ? &teredo_cone : &teredo_restrict,
174 : sizeof (rs.ip6.ip6_src));
175 0 : memcpy (&rs.ip6.ip6_dst, &in6addr_allrouters, sizeof (rs.ip6.ip6_dst));
176 :
177 0 : rs.rs.nd_rs_type = ND_ROUTER_SOLICIT;
178 0 : rs.rs.nd_rs_code = 0;
179 : // Checksums are pre-computed
180 0 : rs.rs.nd_rs_cksum = cone ? htons (0x125d) : htons (0x7d37);
181 0 : rs.rs.nd_rs_reserved = 0;
182 :
183 0 : return teredo_sendv (fd, iov, 2, server_ip, htons (IPPORT_TEREDO)) > 0
184 : ? 0 : -1;
185 : }
186 :
187 :
188 : /**
189 : * Validates a router advertisement from the Teredo server.
190 : * The RA must be of type cone if and only if cone is true.
191 : * Prefix, flags, mapped port and IP are returned through newaddr.
192 : * If there is a MTU option in the packet, the specified MTU value will
193 : * be returned at mtu. If not, the value pointed to by mtu will not be
194 : * modified.
195 : *
196 : * Assumptions:
197 : * - newaddr must be 4-bytes aligned (NOT necessarily the packet).
198 : * - newaddr->teredo.server_ip must be set to the server's expected IP by the
199 : * caller.
200 : * - IPv6 header is valid (ie. version 6, plen matches packet's length, and
201 : * the full packet is at least 40 bytes long).
202 : *
203 : * @param packet Teredo packet to be checked
204 : * @param newaddr upon entry, see assumptions, upon succesful return, the
205 : * infered Teredo client address. In other words, the caller must set the
206 : * server IPv4 part, this function will set the 32 bits Teredo prefix, the
207 : * Teredo flags, mapped port and mapped IPv4 parts.
208 : * @param cone whether the RA should be a reply to “cone” RS
209 : * @param mtu [out] MTU parameter found in the RA, not modified if the RA
210 : * had no MTU option. Undefined on error.
211 : *
212 : * @return 0 on success, -1 on error.
213 : */
214 : int
215 : teredo_parse_ra (const teredo_packet *restrict packet,
216 : union teredo_addr *restrict newaddr, bool cone,
217 : uint16_t *restrict mtu)
218 0 : {
219 0 : if (packet->orig_ipv4 == 0)
220 0 : return -1;
221 :
222 : // Only read ip6_next (1 byte), so no need to align
223 0 : const struct ip6_hdr *ip6 = (const struct ip6_hdr *)packet->ip6;
224 0 : size_t length = packet->ip6_len;
225 :
226 0 : length -= sizeof (*ip6);
227 :
228 0 : if (memcmp (&ip6->ip6_dst, cone ? &teredo_cone : &teredo_restrict,
229 : sizeof (ip6->ip6_dst))
230 : || (ip6->ip6_nxt != IPPROTO_ICMPV6)
231 : || (length < sizeof (struct nd_router_advert)))
232 0 : return -1;
233 :
234 : // Only read bytes, so no need to align
235 : const struct nd_router_advert *ra =
236 : (const struct nd_router_advert *)
237 0 : (((const uint8_t *)ip6) + sizeof (struct ip6_hdr));
238 0 : length -= sizeof (struct nd_router_advert);
239 :
240 0 : if ((ra->nd_ra_type != ND_ROUTER_ADVERT)
241 : || (ra->nd_ra_code != 0)
242 : || (length < sizeof (struct nd_opt_prefix_info)))
243 : /*
244 : * We don't check checksum, because it is rather useless.
245 : * There were already (at least) two lower-level checksums.
246 : */
247 0 : return -1;
248 :
249 0 : uint32_t net_mtu = 0;
250 0 : newaddr->teredo.server_ip = 0;
251 :
252 : // Looks for a prefix information option
253 : const struct nd_opt_hdr *hdr;
254 0 : for (hdr = (const struct nd_opt_hdr *)(ra + 1); length >= 8;
255 : hdr = (const struct nd_opt_hdr *)
256 0 : (((const uint8_t *)hdr) + (hdr->nd_opt_len << 3)))
257 : {
258 0 : size_t optlen = (size_t)(hdr->nd_opt_len << 3);
259 :
260 0 : if ((length < optlen) /* too short */
261 : || (optlen == 0) /* invalid */)
262 0 : return -1;
263 :
264 0 : switch (hdr->nd_opt_type)
265 : {
266 : /* Prefix information option */
267 : case ND_OPT_PREFIX_INFORMATION:
268 : {
269 : const struct nd_opt_prefix_info *pi =
270 0 : (const struct nd_opt_prefix_info *)hdr;
271 :
272 0 : if ((optlen < sizeof (*pi)) /* option too short */
273 : || (pi->nd_opt_pi_prefix_len != 64) /* unsupp. prefix size */)
274 0 : return -1;
275 :
276 0 : if (newaddr->teredo.server_ip != 0)
277 : {
278 : /* The Teredo specification excludes multiple prefixes */
279 0 : syslog (LOG_ERR, _("Multiple Teredo prefixes received"));
280 0 : return -1;
281 : }
282 :
283 0 : memcpy (newaddr, &pi->nd_opt_pi_prefix, 8);
284 0 : break;
285 : }
286 :
287 : /* MTU option */
288 : case ND_OPT_MTU:
289 : {
290 0 : const struct nd_opt_mtu *mo = (const struct nd_opt_mtu *)hdr;
291 :
292 : /*if (optlen < sizeof (*mo)) -- not possible (optlen >= 8)
293 : return -1;*/
294 :
295 0 : memcpy (&net_mtu, &mo->nd_opt_mtu_mtu, sizeof (net_mtu));
296 0 : net_mtu = ntohl (net_mtu);
297 :
298 0 : if ((net_mtu < 1280) || (net_mtu > 65535))
299 0 : return -1; // invalid IPv6 MTU
300 :
301 : break;
302 : }
303 : }
304 :
305 0 : length -= optlen;
306 : }
307 :
308 : /*
309 : * NOTE: bug-to-bug-to-bug (sic!) compatibility with Microsoft servers.
310 : * These severs still advertise the obsolete broken experimental Teredo
311 : * prefix. That's obviously for backward (but kinda useless) compatibility
312 : * with Windows XP SP1/SP2 clients, that only accept that broken prefix,
313 : * unless a dedicated registry key is merged.
314 : *
315 : * This work-around works because Microsoft servers handles both the
316 : * standard and the experimental prefixes.
317 : */
318 0 : if (newaddr->teredo.prefix == htonl (TEREDO_PREFIX_OBSOLETE))
319 0 : newaddr->teredo.prefix = htonl (TEREDO_PREFIX);
320 :
321 0 : if (!is_valid_teredo_prefix (newaddr->teredo.prefix))
322 0 : return -1;
323 :
324 : // only accept the cone flag:
325 0 : newaddr->teredo.flags = cone ? htons (TEREDO_FLAG_CONE) : 0;
326 :
327 0 : newaddr->teredo.client_port = ~packet->orig_port;
328 0 : newaddr->teredo.client_ip = ~packet->orig_ipv4;
329 :
330 0 : if (net_mtu != 0)
331 0 : *mtu = (uint16_t)net_mtu;
332 :
333 0 : return 0;
334 : }
335 :
336 : #define PING_PAYLOAD (LIBTEREDO_HMAC_LEN - 4)
337 : /**
338 : * Sends an ICMPv6 Echo request toward an IPv6 node through the Teredo server.
339 : */
340 : int
341 : SendPing (int fd, const union teredo_addr *src, const struct in6_addr *dst)
342 0 : {
343 : struct
344 : {
345 : struct ip6_hdr ip6;
346 : struct icmp6_hdr icmp6;
347 : uint8_t payload[PING_PAYLOAD];
348 : } ping;
349 :
350 0 : ping.ip6.ip6_flow = htonl (0x60000000);
351 0 : ping.ip6.ip6_plen = htons (sizeof (ping.icmp6) + PING_PAYLOAD);
352 0 : ping.ip6.ip6_nxt = IPPROTO_ICMPV6;
353 0 : ping.ip6.ip6_hlim = 128;
354 0 : memcpy (&ping.ip6.ip6_src, src, sizeof (ping.ip6.ip6_src));
355 0 : memcpy (&ping.ip6.ip6_dst, dst, sizeof (ping.ip6.ip6_dst));
356 :
357 0 : ping.icmp6.icmp6_type = ICMP6_ECHO_REQUEST;
358 0 : ping.icmp6.icmp6_code = 0;
359 0 : ping.icmp6.icmp6_cksum = 0;
360 : /*
361 : ping.icmp6.icmp6_id = 0;
362 : ping.icmp6.icmp6_seq = 0;
363 : */
364 0 : teredo_get_pinghash ((uint32_t)time (NULL), &ping.ip6.ip6_src,
365 : &ping.ip6.ip6_dst, (uint8_t *)&ping.icmp6.icmp6_id);
366 :
367 0 : ping.icmp6.icmp6_cksum = icmp6_checksum (&ping.ip6, &ping.icmp6);
368 :
369 0 : return teredo_send (fd, &ping, sizeof (ping.ip6) + sizeof (ping.icmp6)
370 : + PING_PAYLOAD, IN6_TEREDO_SERVER (src),
371 : htons (IPPORT_TEREDO)) > 0 ? 0 : -1;
372 : }
373 :
374 :
375 : /**
376 : * Checks that the packet is an ICMPv6 Echo reply and authenticates it.
377 : *
378 : * @return 0 if that is the case, -1 otherwise.
379 : */
380 : int CheckPing (const teredo_packet *packet)
381 0 : {
382 0 : const struct ip6_hdr *ip6 = (const struct ip6_hdr *)packet->ip6;
383 0 : size_t length = packet->ip6_len;
384 :
385 : // Only read bytes, so no need to align
386 0 : if ((ip6->ip6_nxt != IPPROTO_ICMPV6)
387 : || (length < (sizeof (*ip6) + sizeof (struct icmp6_hdr) + PING_PAYLOAD)))
388 0 : return -1;
389 :
390 0 : const struct icmp6_hdr *icmp6 = (const struct icmp6_hdr *)(ip6 + 1);
391 0 : const struct in6_addr *me = &ip6->ip6_dst, *it = &ip6->ip6_src;
392 :
393 0 : if (icmp6->icmp6_type == ICMP6_DST_UNREACH)
394 : {
395 : /*
396 : * NOTE:
397 : * Some brain-dead IPv6 nodes/firewalls don't reply to pings (which is
398 : * as explicit breakage of the IPv6/ICMPv6 specifications, btw). Some
399 : * of these brain-dead hosts reply with ICMPv6 unreachable messages.
400 : * We can authenticate them by looking at the payload of the message
401 : * and see if it is an ICMPv6 Echo request with the matching nonce in
402 : * it (Yes, this is a nasty kludge).
403 : *
404 : * NOTE 2:
405 : * We don't check source and destination addresses there...
406 : */
407 0 : length -= sizeof (*ip6) + sizeof (*icmp6);
408 0 : ip6 = (const struct ip6_hdr *)(icmp6 + 1);
409 :
410 0 : if ((length < (sizeof (*ip6) + sizeof (*icmp6) + PING_PAYLOAD))
411 : || (ip6->ip6_nxt != IPPROTO_ICMPV6))
412 0 : return -1;
413 :
414 : uint16_t plen;
415 0 : memcpy (&plen, &ip6->ip6_plen, sizeof (plen));
416 0 : if (ntohs (plen) != (sizeof (*icmp6) + PING_PAYLOAD))
417 0 : return -1; // not a ping from us
418 :
419 0 : icmp6 = (const struct icmp6_hdr *)(ip6 + 1);
420 :
421 0 : if (memcmp (&ip6->ip6_src, me, sizeof (*me))
422 : || (icmp6->icmp6_type != ICMP6_ECHO_REQUEST))
423 0 : return -1;
424 :
425 : /*
426 : * The callers wants to check the ping as regards the Unreachable
427 : * error packet's source, not as regards the inner Echo Request
428 : * packet destination. Sometimes, firewalls send error with their
429 : * own address (as a router) as source. In that case, we must not
430 : * accept the packet. If we did, the core relaying logic would
431 : * believe that we have authenticated the firewall address, while
432 : * we really authenticated the inner packet destination address.
433 : * In practice, this would potentially be a way to bypass the HMAC
434 : * authentication of Teredo relays: once a relays catches one valid
435 : * HMAC authentified Echo reply from an IPv6 node, it could fake
436 : * error message and spoof any other non-Teredo IPv6 node, by setting
437 : * the ICMPv6 Destination Unreachable packet IPv6 source address to
438 : * that of a host to be spoofed.
439 : *
440 : * An alternive to simply dropping these packets would be to implement
441 : * a complex interaction between the caller and CheckPing(), so that
442 : * CheckPing() could notify the caller that the authenticated IPv6
443 : * node is not the packet's source. However, this is probably
444 : * overkill, and might not even work properly depending on the cuplrit
445 : * firewall rules.
446 : */
447 0 : if (memcmp (&ip6->ip6_dst, it, sizeof (*it)))
448 0 : return -1;
449 :
450 0 : me = &ip6->ip6_src;
451 0 : it = &ip6->ip6_dst;
452 : }
453 : else
454 0 : if (icmp6->icmp6_type != ICMP6_ECHO_REPLY)
455 0 : return -1;
456 :
457 0 : if (icmp6->icmp6_code != 0)
458 0 : return -1;
459 :
460 0 : return teredo_verify_pinghash ((uint32_t)time (NULL), me, it,
461 : (const uint8_t *)&icmp6->icmp6_id);
462 : /* TODO: check the sum(?) */
463 : }
464 : #endif
465 :
466 :
467 : /**
468 : * Builds an ICMPv6 error message with specified type and code from an IPv6
469 : * packet. The output buffer must be at least 1240 bytes long and have
470 : * adequate IPv6 packet alignment. The ICMPv6 checksum is not set as they are
471 : * not enough information for its computation.
472 : *
473 : * It is assumed that the output buffer is properly aligned. The input
474 : * buffer does not need to be aligned.
475 : *
476 : * @param out output buffer
477 : * @param type ICMPv6 error type
478 : * @param code ICMPv6 error code
479 : * @param in original IPv6 packet
480 : * @param inlen original IPv6 packet length (including IPv6 header)
481 : *
482 : * @return the actual size of the generated error message, or zero if no
483 : * ICMPv6 packet should be generated. Never fails.
484 : */
485 : int
486 : BuildICMPv6Error (struct icmp6_hdr *restrict out, uint8_t type, uint8_t code,
487 : const void *restrict in, size_t inlen)
488 0 : {
489 : const struct in6_addr *p;
490 :
491 : /* don't reply if the packet is too small */
492 0 : if (inlen < sizeof (struct ip6_hdr))
493 0 : return 0;
494 :
495 : /* don't reply to ICMPv6 error */
496 0 : if ((((const struct ip6_hdr *)in)->ip6_nxt == IPPROTO_ICMPV6)
497 : && ((((const struct icmp6_hdr *)(((const struct ip6_hdr *)in) + 1))
498 : ->icmp6_type & 0x80) == 0))
499 0 : return 0;
500 :
501 : /* don't reply to multicast */
502 0 : if (((const struct ip6_hdr *)in)->ip6_dst.s6_addr[0] == 0xff)
503 0 : return 0;
504 :
505 0 : p = &((const struct ip6_hdr *)in)->ip6_src;
506 : /* don't reply to incorrect source address (multicast, undefined) */
507 0 : if ((p->s6_addr[0] == 0xff) /* multicast */
508 : || (memcmp (p, &in6addr_any, sizeof (*p)) == 0))
509 0 : return 0;
510 :
511 0 : out->icmp6_type = type;
512 0 : out->icmp6_code = code;
513 0 : out->icmp6_cksum = 0;
514 0 : out->icmp6_data32[0] = 0;
515 :
516 0 : if (inlen > 1280 - (sizeof (struct ip6_hdr) + sizeof (struct icmp6_hdr)))
517 0 : inlen = 1280 - (sizeof (struct ip6_hdr) + sizeof (struct icmp6_hdr));
518 :
519 0 : memcpy (out + 1, in, inlen);
520 :
521 0 : return sizeof (struct icmp6_hdr) + inlen;
522 : }
523 :
524 :
525 : #if 0
526 : /**
527 : * Builds an ICMPv6/IPv6 error message with specified type and code from an
528 : * IPv6 packet. The output buffer must be at least 1280 bytes long and have
529 : * adequate IPv6 packet alignment.
530 : *
531 : * It is assumed that the output buffer is properly aligned. The input
532 : * buffer does not need to be aligned.
533 : *
534 : * @param out output buffer
535 : * @param type ICMPv6 error type
536 : * @param code ICMPv6 error code
537 : * @param src source IPv6 address for ICMPv6 message
538 : * @param in original IPv6 packet
539 : * @param len original IPv6 packet length (including IPv6 header)
540 : *
541 : * @return the actual size of the generated error message, or zero if no
542 : * ICMPv6/IPv6 packet should be sent. Never fails.
543 : */
544 : int
545 : BuildIPv6Error (struct ip6_hdr *out, const struct in6_addr *src,
546 : uint8_t type, uint8_t code, const void *in, uint16_t len)
547 : {
548 : struct icmp6_hdr *h;
549 :
550 : h = (struct icmp6_hdr *)(out + 1);
551 : len = BuildICMPv6Error (h, type, code, in, len);
552 : if (len == 0)
553 : return 0;
554 :
555 : out->ip6_flow = htonl (0x60000000);
556 : out->ip6_plen = htons (len);
557 : out->ip6_nxt = IPPROTO_ICMPV6;
558 : out->ip6_hlim = 255;
559 : memcpy (&out->ip6_src, src, sizeof (out->ip6_src));
560 : memcpy (&out->ip6_dst, &((const struct ip6_hdr *)in)->ip6_src,
561 : sizeof (out->ip6_dst));
562 :
563 : len += sizeof (struct ip6_hdr);
564 :
565 : h->icmp6_cksum = icmp6_checksum (out, h);
566 : return len;
567 : }
568 : #endif
|