diff options
Diffstat (limited to 'sys/netinet6')
68 files changed, 43534 insertions, 0 deletions
diff --git a/sys/netinet6/README b/sys/netinet6/README new file mode 100644 index 0000000..70cec5e --- /dev/null +++ b/sys/netinet6/README @@ -0,0 +1,81 @@ +a note to committers about KAME tree +$FreeBSD$ +KAME project + + +FreeBSD IPv6/IPsec tree is from KAMEproject (http://www.kame.net/). +To synchronize KAME tree and FreeBSD better today and in the future, +please understand the following: + +- DO NOT MAKE COSTMETIC CHANGES. + "Cosmetic changes" here includes tabify, untabify, removal of space at EOL, + minor KNF items, and whatever adds more output lines on "diff freebsd kame". + To make future synchronization easier. it is critical to preserve certain + statements in the code. Also, as KAME tree supports all 4 BSDs (Free, Open, + Net, BSD/OS) in single shared tree, it is not always possible to backport + FreeBSD changes into KAME tree. So again, please do not make cosmetic + changes. Even if you think it a right thing, that will bite KAME guys badly + during upgrade attempts, and prevent us from synchronizing two trees. + (you don't usually make cosmetic changes against third-party code, do you?) + +- REPORT CHANGES/BUGS TO KAME GUYS. + It is not always possible for KAME guys to watch all the freebsd mailing + list traffic, as the traffic is HUGE. So if possible, please, inform + kame guys of changes you made in IPv6/IPsec related portion. Contact + path would be snap-users@kame.net or KAME PR database on www.kame.net. + (or to core@kame.net if it is necessary to make it confidential) + +Thank you for your cooperation and have a happy IPv6 life! + + +Note: KAME-origin code is in the following locations. +The above notice applies to corresponding manpages too. +The list may not be complete. If you see $KAME$ in the code, it is from +KAME distribution. If you see some file that is IPv6/IPsec related, it is +highly possible that the file is from KAME distribution. + +include/ifaddrs.h +lib/libc/net +lib/libc/net/getaddrinfo.c +lib/libc/net/getifaddrs.c +lib/libc/net/getnameinfo.c +lib/libc/net/ifname.c +lib/libc/net/ip6opt.c +lib/libc/net/map_v4v6.c +lib/libc/net/name6.c +lib/libftpio +lib/libipsec +sbin/ip6fw +sbin/ping6 +sbin/rtsol +share/doc/IPv6 +share/man/man4/ip6.4 +share/man/man4/inet6.4 +sys/crypto (except sys/crypto/rc4) +sys/kern/uipc_mbuf2.c +sys/net/if_faith.[ch] +sys/net/if_gif.[ch] +sys/net/if_stf.[ch] +sys/net/pfkeyv2.h +sys/netinet/icmp6.h +sys/netinet/in_gif.[ch] +sys/netinet/ip6.h +sys/netinet/ip_encap.[ch] +sys/netinet6 +sys/netkey +usr.sbin/faithd +usr.sbin/gifconfig +usr.sbin/ifmcstat +usr.sbin/mld6query +usr.sbin/ndp +usr.sbin/pim6dd +usr.sbin/pim6sd +usr.sbin/prefix +usr.sbin/rip6query +usr.sbin/route6d +usr.sbin/rrenumd +usr.sbin/rtadvd +usr.sbin/rtsold +usr.sbin/scope6config +usr.sbin/setkey +usr.sbin/traceroute6 diff --git a/sys/netinet6/ah.h b/sys/netinet6/ah.h new file mode 100644 index 0000000..1934569 --- /dev/null +++ b/sys/netinet6/ah.h @@ -0,0 +1,94 @@ +/* $FreeBSD$ */ +/* $KAME: ah.h,v 1.16 2001/09/04 08:43:19 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1826/2402 authentication header. + */ + +#ifndef _NETINET6_AH_H_ +#define _NETINET6_AH_H_ + +#if defined(_KERNEL) && !defined(_LKM) +#include "opt_inet.h" +#endif + +struct ah { + u_int8_t ah_nxt; /* Next Header */ + u_int8_t ah_len; /* Length of data, in 32bit */ + u_int16_t ah_reserve; /* Reserved for future use */ + u_int32_t ah_spi; /* Security parameter index */ + /* variable size, 32bit bound*/ /* Authentication data */ +}; + +struct newah { + u_int8_t ah_nxt; /* Next Header */ + u_int8_t ah_len; /* Length of data + 1, in 32bit */ + u_int16_t ah_reserve; /* Reserved for future use */ + u_int32_t ah_spi; /* Security parameter index */ + u_int32_t ah_seq; /* Sequence number field */ + /* variable size, 32bit bound*/ /* Authentication data */ +}; + +#ifdef _KERNEL +struct secasvar; + +struct ah_algorithm_state { + struct secasvar *sav; + void* foo; /* per algorithm data - maybe */ +}; + +struct ah_algorithm { + int (*sumsiz) __P((struct secasvar *)); + int (*mature) __P((struct secasvar *)); + int keymin; /* in bits */ + int keymax; /* in bits */ + const char *name; + int (*init) __P((struct ah_algorithm_state *, struct secasvar *)); + void (*update) __P((struct ah_algorithm_state *, caddr_t, size_t)); + void (*result) __P((struct ah_algorithm_state *, caddr_t)); +}; + +#define AH_MAXSUMSIZE 16 + +extern const struct ah_algorithm *ah_algorithm_lookup __P((int)); + +/* cksum routines */ +extern int ah_hdrlen __P((struct secasvar *)); + +extern size_t ah_hdrsiz __P((struct ipsecrequest *)); +extern void ah4_input __P((struct mbuf *, int)); +extern int ah4_output __P((struct mbuf *, struct ipsecrequest *)); +extern int ah4_calccksum __P((struct mbuf *, caddr_t, size_t, + const struct ah_algorithm *, struct secasvar *)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_AH_H_ */ diff --git a/sys/netinet6/ah6.h b/sys/netinet6/ah6.h new file mode 100644 index 0000000..ead07bf --- /dev/null +++ b/sys/netinet6/ah6.h @@ -0,0 +1,52 @@ +/* $FreeBSD$ */ +/* $KAME: ah.h,v 1.13 2000/10/18 21:28:00 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1826/2402 authentication header. + */ + +#ifndef _NETINET6_AH6_H_ +#define _NETINET6_AH6_H_ + +#ifdef _KERNEL +struct secasvar; + +extern int ah6_input __P((struct mbuf **, int *, int)); +extern int ah6_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *)); +extern int ah6_calccksum __P((struct mbuf *, caddr_t, size_t, + const struct ah_algorithm *, struct secasvar *)); + +extern void ah6_ctlinput __P((int, struct sockaddr *, void *)); +#endif + +#endif /*_NETINET6_AH6_H_*/ diff --git a/sys/netinet6/ah_core.c b/sys/netinet6/ah_core.c new file mode 100644 index 0000000..cbb6206 --- /dev/null +++ b/sys/netinet6/ah_core.c @@ -0,0 +1,1644 @@ +/* $FreeBSD$ */ +/* $KAME: ah_core.c,v 1.44 2001/03/12 11:24:39 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1826/2402 authentication header. + */ + +/* TODO: have shared routines for hmac-* algorithms */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#ifdef IPSEC_ESP +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#endif +#include <net/pfkeyv2.h> +#include <netkey/keydb.h> +#include <sys/md5.h> +#include <crypto/sha1.h> +#include <crypto/sha2/sha2.h> + +#include <net/net_osdep.h> + +#define HMACSIZE 16 + +static int ah_sumsiz_1216 __P((struct secasvar *)); +static int ah_sumsiz_zero __P((struct secasvar *)); +static int ah_none_mature __P((struct secasvar *)); +static int ah_none_init __P((struct ah_algorithm_state *, struct secasvar *)); +static void ah_none_loop __P((struct ah_algorithm_state *, caddr_t, size_t)); +static void ah_none_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_keyed_md5_mature __P((struct secasvar *)); +static int ah_keyed_md5_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_keyed_md5_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_keyed_md5_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_keyed_sha1_mature __P((struct secasvar *)); +static int ah_keyed_sha1_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_keyed_sha1_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_keyed_sha1_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_hmac_md5_mature __P((struct secasvar *)); +static int ah_hmac_md5_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_hmac_md5_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_hmac_md5_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_hmac_sha1_mature __P((struct secasvar *)); +static int ah_hmac_sha1_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_hmac_sha1_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_hmac_sha1_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_hmac_sha2_256_mature __P((struct secasvar *)); +static int ah_hmac_sha2_256_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_hmac_sha2_256_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_hmac_sha2_256_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_hmac_sha2_384_mature __P((struct secasvar *)); +static int ah_hmac_sha2_384_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_hmac_sha2_384_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_hmac_sha2_384_result __P((struct ah_algorithm_state *, caddr_t)); +static int ah_hmac_sha2_512_mature __P((struct secasvar *)); +static int ah_hmac_sha2_512_init __P((struct ah_algorithm_state *, + struct secasvar *)); +static void ah_hmac_sha2_512_loop __P((struct ah_algorithm_state *, caddr_t, + size_t)); +static void ah_hmac_sha2_512_result __P((struct ah_algorithm_state *, caddr_t)); + +static void ah_update_mbuf __P((struct mbuf *, int, int, + const struct ah_algorithm *, struct ah_algorithm_state *)); + +const struct ah_algorithm * +ah_algorithm_lookup(idx) + int idx; +{ + /* checksum algorithms */ + static struct ah_algorithm ah_algorithms[] = { + { ah_sumsiz_1216, ah_hmac_md5_mature, 128, 128, "hmac-md5", + ah_hmac_md5_init, ah_hmac_md5_loop, + ah_hmac_md5_result, }, + { ah_sumsiz_1216, ah_hmac_sha1_mature, 160, 160, "hmac-sha1", + ah_hmac_sha1_init, ah_hmac_sha1_loop, + ah_hmac_sha1_result, }, + { ah_sumsiz_1216, ah_keyed_md5_mature, 128, 128, "keyed-md5", + ah_keyed_md5_init, ah_keyed_md5_loop, + ah_keyed_md5_result, }, + { ah_sumsiz_1216, ah_keyed_sha1_mature, 160, 160, "keyed-sha1", + ah_keyed_sha1_init, ah_keyed_sha1_loop, + ah_keyed_sha1_result, }, + { ah_sumsiz_zero, ah_none_mature, 0, 2048, "none", + ah_none_init, ah_none_loop, ah_none_result, }, + { ah_sumsiz_1216, ah_hmac_sha2_256_mature, 256, 256, + "hmac-sha2-256", + ah_hmac_sha2_256_init, ah_hmac_sha2_256_loop, + ah_hmac_sha2_256_result, }, + { ah_sumsiz_1216, ah_hmac_sha2_384_mature, 384, 384, + "hmac-sha2-384", + ah_hmac_sha2_384_init, ah_hmac_sha2_384_loop, + ah_hmac_sha2_384_result, }, + { ah_sumsiz_1216, ah_hmac_sha2_512_mature, 512, 512, + "hmac-sha2-512", + ah_hmac_sha2_512_init, ah_hmac_sha2_512_loop, + ah_hmac_sha2_512_result, }, + }; + + switch (idx) { + case SADB_AALG_MD5HMAC: + return &ah_algorithms[0]; + case SADB_AALG_SHA1HMAC: + return &ah_algorithms[1]; + case SADB_X_AALG_MD5: + return &ah_algorithms[2]; + case SADB_X_AALG_SHA: + return &ah_algorithms[3]; + case SADB_X_AALG_NULL: + return &ah_algorithms[4]; + case SADB_X_AALG_SHA2_256: + return &ah_algorithms[5]; + case SADB_X_AALG_SHA2_384: + return &ah_algorithms[6]; + case SADB_X_AALG_SHA2_512: + return &ah_algorithms[7]; + default: + return NULL; + } +} + + +static int +ah_sumsiz_1216(sav) + struct secasvar *sav; +{ + if (!sav) + return -1; + if (sav->flags & SADB_X_EXT_OLD) + return 16; + else + return 12; +} + +static int +ah_sumsiz_zero(sav) + struct secasvar *sav; +{ + if (!sav) + return -1; + return 0; +} + +static int +ah_none_mature(sav) + struct secasvar *sav; +{ + if (sav->sah->saidx.proto == IPPROTO_AH) { + ipseclog((LOG_ERR, + "ah_none_mature: protocol and algorithm mismatch.\n")); + return 1; + } + return 0; +} + +static int +ah_none_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + state->foo = NULL; + return 0; +} + +static void +ah_none_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ +} + +static void +ah_none_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ +} + +static int +ah_keyed_md5_mature(sav) + struct secasvar *sav; +{ + /* anything is okay */ + return 0; +} + +static int +ah_keyed_md5_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + size_t padlen; + size_t keybitlen; + u_int8_t buf[32]; + + if (!state) + panic("ah_keyed_md5_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(sizeof(MD5_CTX), M_TEMP, M_NOWAIT); + if (state->foo == NULL) + return ENOBUFS; + + MD5Init((MD5_CTX *)state->foo); + if (state->sav) { + MD5Update((MD5_CTX *)state->foo, + (u_int8_t *)_KEYBUF(state->sav->key_auth), + (u_int)_KEYLEN(state->sav->key_auth)); + + /* + * Pad after the key. + * We cannot simply use md5_pad() since the function + * won't update the total length. + */ + if (_KEYLEN(state->sav->key_auth) < 56) + padlen = 64 - 8 - _KEYLEN(state->sav->key_auth); + else + padlen = 64 + 64 - 8 - _KEYLEN(state->sav->key_auth); + keybitlen = _KEYLEN(state->sav->key_auth); + keybitlen *= 8; + + buf[0] = 0x80; + MD5Update((MD5_CTX *)state->foo, &buf[0], 1); + padlen--; + + bzero(buf, sizeof(buf)); + while (sizeof(buf) < padlen) { + MD5Update((MD5_CTX *)state->foo, &buf[0], sizeof(buf)); + padlen -= sizeof(buf); + } + if (padlen) { + MD5Update((MD5_CTX *)state->foo, &buf[0], padlen); + } + + buf[0] = (keybitlen >> 0) & 0xff; + buf[1] = (keybitlen >> 8) & 0xff; + buf[2] = (keybitlen >> 16) & 0xff; + buf[3] = (keybitlen >> 24) & 0xff; + MD5Update((MD5_CTX *)state->foo, buf, 8); + } + + return 0; +} + +static void +ah_keyed_md5_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + if (!state) + panic("ah_keyed_md5_loop: what?"); + + MD5Update((MD5_CTX *)state->foo, addr, len); +} + +static void +ah_keyed_md5_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[16]; + + if (!state) + panic("ah_keyed_md5_result: what?"); + + if (state->sav) { + MD5Update((MD5_CTX *)state->foo, + (u_int8_t *)_KEYBUF(state->sav->key_auth), + (u_int)_KEYLEN(state->sav->key_auth)); + } + MD5Final(&digest[0], (MD5_CTX *)state->foo); + free(state->foo, M_TEMP); + bcopy(&digest[0], (void *)addr, sizeof(digest)); +} + +static int +ah_keyed_sha1_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, "ah_keyed_sha1_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, "ah_keyed_sha1_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin + || algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_keyed_sha1_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_keyed_sha1_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + SHA1_CTX *ctxt; + size_t padlen; + size_t keybitlen; + u_int8_t buf[32]; + + if (!state) + panic("ah_keyed_sha1_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(sizeof(SHA1_CTX), M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + + ctxt = (SHA1_CTX *)state->foo; + SHA1Init(ctxt); + + if (state->sav) { + SHA1Update(ctxt, (u_int8_t *)_KEYBUF(state->sav->key_auth), + (u_int)_KEYLEN(state->sav->key_auth)); + + /* + * Pad after the key. + */ + if (_KEYLEN(state->sav->key_auth) < 56) + padlen = 64 - 8 - _KEYLEN(state->sav->key_auth); + else + padlen = 64 + 64 - 8 - _KEYLEN(state->sav->key_auth); + keybitlen = _KEYLEN(state->sav->key_auth); + keybitlen *= 8; + + buf[0] = 0x80; + SHA1Update(ctxt, &buf[0], 1); + padlen--; + + bzero(buf, sizeof(buf)); + while (sizeof(buf) < padlen) { + SHA1Update(ctxt, &buf[0], sizeof(buf)); + padlen -= sizeof(buf); + } + if (padlen) { + SHA1Update(ctxt, &buf[0], padlen); + } + + buf[0] = (keybitlen >> 0) & 0xff; + buf[1] = (keybitlen >> 8) & 0xff; + buf[2] = (keybitlen >> 16) & 0xff; + buf[3] = (keybitlen >> 24) & 0xff; + SHA1Update(ctxt, buf, 8); + } + + return 0; +} + +static void +ah_keyed_sha1_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + SHA1_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_keyed_sha1_loop: what?"); + ctxt = (SHA1_CTX *)state->foo; + + SHA1Update(ctxt, (caddr_t)addr, (size_t)len); +} + +static void +ah_keyed_sha1_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[SHA1_RESULTLEN]; /* SHA-1 generates 160 bits */ + SHA1_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_keyed_sha1_result: what?"); + ctxt = (SHA1_CTX *)state->foo; + + if (state->sav) { + SHA1Update(ctxt, (u_int8_t *)_KEYBUF(state->sav->key_auth), + (u_int)_KEYLEN(state->sav->key_auth)); + } + SHA1Final((caddr_t)&digest[0], ctxt); + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +static int +ah_hmac_md5_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, "ah_hmac_md5_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, "ah_hmac_md5_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin + || algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_hmac_md5_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_hmac_md5_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + u_char *ipad; + u_char *opad; + u_char tk[16]; + u_char *key; + size_t keylen; + size_t i; + MD5_CTX *ctxt; + + if (!state) + panic("ah_hmac_md5_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(64 + 64 + sizeof(MD5_CTX), M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (MD5_CTX *)(opad + 64); + + /* compress the key if necessery */ + if (64 < _KEYLEN(state->sav->key_auth)) { + MD5Init(ctxt); + MD5Update(ctxt, _KEYBUF(state->sav->key_auth), + _KEYLEN(state->sav->key_auth)); + MD5Final(&tk[0], ctxt); + key = &tk[0]; + keylen = 16; + } else { + key = _KEYBUF(state->sav->key_auth); + keylen = _KEYLEN(state->sav->key_auth); + } + + bzero(ipad, 64); + bzero(opad, 64); + bcopy(key, ipad, keylen); + bcopy(key, opad, keylen); + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + MD5Init(ctxt); + MD5Update(ctxt, ipad, 64); + + return 0; +} + +static void +ah_hmac_md5_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + MD5_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_md5_loop: what?"); + ctxt = (MD5_CTX *)(((caddr_t)state->foo) + 128); + MD5Update(ctxt, addr, len); +} + +static void +ah_hmac_md5_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[16]; + u_char *ipad; + u_char *opad; + MD5_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_md5_result: what?"); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (MD5_CTX *)(opad + 64); + + MD5Final(&digest[0], ctxt); + + MD5Init(ctxt); + MD5Update(ctxt, opad, 64); + MD5Update(ctxt, &digest[0], sizeof(digest)); + MD5Final(&digest[0], ctxt); + + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +static int +ah_hmac_sha1_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, "ah_hmac_sha1_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, "ah_hmac_sha1_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin + || algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_hmac_sha1_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_hmac_sha1_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + u_char *ipad; + u_char *opad; + SHA1_CTX *ctxt; + u_char tk[SHA1_RESULTLEN]; /* SHA-1 generates 160 bits */ + u_char *key; + size_t keylen; + size_t i; + + if (!state) + panic("ah_hmac_sha1_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(64 + 64 + sizeof(SHA1_CTX), + M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA1_CTX *)(opad + 64); + + /* compress the key if necessery */ + if (64 < _KEYLEN(state->sav->key_auth)) { + SHA1Init(ctxt); + SHA1Update(ctxt, _KEYBUF(state->sav->key_auth), + _KEYLEN(state->sav->key_auth)); + SHA1Final(&tk[0], ctxt); + key = &tk[0]; + keylen = SHA1_RESULTLEN; + } else { + key = _KEYBUF(state->sav->key_auth); + keylen = _KEYLEN(state->sav->key_auth); + } + + bzero(ipad, 64); + bzero(opad, 64); + bcopy(key, ipad, keylen); + bcopy(key, opad, keylen); + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + SHA1Init(ctxt); + SHA1Update(ctxt, ipad, 64); + + return 0; +} + +static void +ah_hmac_sha1_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + SHA1_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha1_loop: what?"); + + ctxt = (SHA1_CTX *)(((u_char *)state->foo) + 128); + SHA1Update(ctxt, (caddr_t)addr, (size_t)len); +} + +static void +ah_hmac_sha1_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[SHA1_RESULTLEN]; /* SHA-1 generates 160 bits */ + u_char *ipad; + u_char *opad; + SHA1_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha1_result: what?"); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA1_CTX *)(opad + 64); + + SHA1Final((caddr_t)&digest[0], ctxt); + + SHA1Init(ctxt); + SHA1Update(ctxt, opad, 64); + SHA1Update(ctxt, (caddr_t)&digest[0], sizeof(digest)); + SHA1Final((caddr_t)&digest[0], ctxt); + + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +static int +ah_hmac_sha2_256_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_256_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_256_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin || + algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_256_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_hmac_sha2_256_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + u_char *ipad; + u_char *opad; + SHA256_CTX *ctxt; + u_char tk[SHA256_DIGEST_LENGTH]; + u_char *key; + size_t keylen; + size_t i; + + if (!state) + panic("ah_hmac_sha2_256_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(64 + 64 + sizeof(SHA256_CTX), + M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA256_CTX *)(opad + 64); + + /* compress the key if necessery */ + if (64 < _KEYLEN(state->sav->key_auth)) { + bzero(tk, sizeof(tk)); + bzero(ctxt, sizeof(*ctxt)); + SHA256_Init(ctxt); + SHA256_Update(ctxt, _KEYBUF(state->sav->key_auth), + _KEYLEN(state->sav->key_auth)); + SHA256_Final(&tk[0], ctxt); + key = &tk[0]; + keylen = sizeof(tk) < 64 ? sizeof(tk) : 64; + } else { + key = _KEYBUF(state->sav->key_auth); + keylen = _KEYLEN(state->sav->key_auth); + } + + bzero(ipad, 64); + bzero(opad, 64); + bcopy(key, ipad, keylen); + bcopy(key, opad, keylen); + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + bzero(ctxt, sizeof(*ctxt)); + SHA256_Init(ctxt); + SHA256_Update(ctxt, ipad, 64); + + return 0; +} + +static void +ah_hmac_sha2_256_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + SHA256_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_256_loop: what?"); + + ctxt = (SHA256_CTX *)(((u_char *)state->foo) + 128); + SHA256_Update(ctxt, (caddr_t)addr, (size_t)len); +} + +static void +ah_hmac_sha2_256_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[SHA256_DIGEST_LENGTH]; + u_char *ipad; + u_char *opad; + SHA256_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_256_result: what?"); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA256_CTX *)(opad + 64); + + SHA256_Final((caddr_t)&digest[0], ctxt); + + bzero(ctxt, sizeof(*ctxt)); + SHA256_Init(ctxt); + SHA256_Update(ctxt, opad, 64); + SHA256_Update(ctxt, (caddr_t)&digest[0], sizeof(digest)); + SHA256_Final((caddr_t)&digest[0], ctxt); + + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +static int +ah_hmac_sha2_384_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_384_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_384_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin || + algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_384_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_hmac_sha2_384_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + u_char *ipad; + u_char *opad; + SHA384_CTX *ctxt; + u_char tk[SHA384_DIGEST_LENGTH]; + u_char *key; + size_t keylen; + size_t i; + + if (!state) + panic("ah_hmac_sha2_384_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(64 + 64 + sizeof(SHA384_CTX), + M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + bzero(state->foo, 64 + 64 + sizeof(SHA384_CTX)); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA384_CTX *)(opad + 64); + + /* compress the key if necessery */ + if (64 < _KEYLEN(state->sav->key_auth)) { + bzero(tk, sizeof(tk)); + bzero(ctxt, sizeof(*ctxt)); + SHA384_Init(ctxt); + SHA384_Update(ctxt, _KEYBUF(state->sav->key_auth), + _KEYLEN(state->sav->key_auth)); + SHA384_Final(&tk[0], ctxt); + key = &tk[0]; + keylen = sizeof(tk) < 64 ? sizeof(tk) : 64; + } else { + key = _KEYBUF(state->sav->key_auth); + keylen = _KEYLEN(state->sav->key_auth); + } + + bzero(ipad, 64); + bzero(opad, 64); + bcopy(key, ipad, keylen); + bcopy(key, opad, keylen); + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + bzero(ctxt, sizeof(*ctxt)); + SHA384_Init(ctxt); + SHA384_Update(ctxt, ipad, 64); + + return 0; +} + +static void +ah_hmac_sha2_384_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + SHA384_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_384_loop: what?"); + + ctxt = (SHA384_CTX *)(((u_char *)state->foo) + 128); + SHA384_Update(ctxt, (caddr_t)addr, (size_t)len); +} + +static void +ah_hmac_sha2_384_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[SHA384_DIGEST_LENGTH]; + u_char *ipad; + u_char *opad; + SHA384_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_384_result: what?"); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA384_CTX *)(opad + 64); + + SHA384_Final((caddr_t)&digest[0], ctxt); + + bzero(ctxt, sizeof(*ctxt)); + SHA384_Init(ctxt); + SHA384_Update(ctxt, opad, 64); + SHA384_Update(ctxt, (caddr_t)&digest[0], sizeof(digest)); + SHA384_Final((caddr_t)&digest[0], ctxt); + + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +static int +ah_hmac_sha2_512_mature(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + + if (!sav->key_auth) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_512_mature: no key is given.\n")); + return 1; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_512_mature: unsupported algorithm.\n")); + return 1; + } + + if (sav->key_auth->sadb_key_bits < algo->keymin || + algo->keymax < sav->key_auth->sadb_key_bits) { + ipseclog((LOG_ERR, + "ah_hmac_sha2_512_mature: invalid key length %d.\n", + sav->key_auth->sadb_key_bits)); + return 1; + } + + return 0; +} + +static int +ah_hmac_sha2_512_init(state, sav) + struct ah_algorithm_state *state; + struct secasvar *sav; +{ + u_char *ipad; + u_char *opad; + SHA512_CTX *ctxt; + u_char tk[SHA512_DIGEST_LENGTH]; + u_char *key; + size_t keylen; + size_t i; + + if (!state) + panic("ah_hmac_sha2_512_init: what?"); + + state->sav = sav; + state->foo = (void *)malloc(64 + 64 + sizeof(SHA512_CTX), + M_TEMP, M_NOWAIT); + if (!state->foo) + return ENOBUFS; + bzero(state->foo, 64 + 64 + sizeof(SHA512_CTX)); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA512_CTX *)(opad + 64); + + /* compress the key if necessery */ + if (64 < _KEYLEN(state->sav->key_auth)) { + bzero(tk, sizeof(tk)); + bzero(ctxt, sizeof(*ctxt)); + SHA512_Init(ctxt); + SHA512_Update(ctxt, _KEYBUF(state->sav->key_auth), + _KEYLEN(state->sav->key_auth)); + SHA512_Final(&tk[0], ctxt); + key = &tk[0]; + keylen = sizeof(tk) < 64 ? sizeof(tk) : 64; + } else { + key = _KEYBUF(state->sav->key_auth); + keylen = _KEYLEN(state->sav->key_auth); + } + + bzero(ipad, 64); + bzero(opad, 64); + bcopy(key, ipad, keylen); + bcopy(key, opad, keylen); + for (i = 0; i < 64; i++) { + ipad[i] ^= 0x36; + opad[i] ^= 0x5c; + } + + bzero(ctxt, sizeof(*ctxt)); + SHA512_Init(ctxt); + SHA512_Update(ctxt, ipad, 64); + + return 0; +} + +static void +ah_hmac_sha2_512_loop(state, addr, len) + struct ah_algorithm_state *state; + caddr_t addr; + size_t len; +{ + SHA512_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_512_loop: what?"); + + ctxt = (SHA512_CTX *)(((u_char *)state->foo) + 128); + SHA512_Update(ctxt, (caddr_t)addr, (size_t)len); +} + +static void +ah_hmac_sha2_512_result(state, addr) + struct ah_algorithm_state *state; + caddr_t addr; +{ + u_char digest[SHA512_DIGEST_LENGTH]; + u_char *ipad; + u_char *opad; + SHA512_CTX *ctxt; + + if (!state || !state->foo) + panic("ah_hmac_sha2_512_result: what?"); + + ipad = (u_char *)state->foo; + opad = (u_char *)(ipad + 64); + ctxt = (SHA512_CTX *)(opad + 64); + + SHA512_Final((caddr_t)&digest[0], ctxt); + + bzero(ctxt, sizeof(*ctxt)); + SHA512_Init(ctxt); + SHA512_Update(ctxt, opad, 64); + SHA512_Update(ctxt, (caddr_t)&digest[0], sizeof(digest)); + SHA512_Final((caddr_t)&digest[0], ctxt); + + bcopy(&digest[0], (void *)addr, HMACSIZE); + + free(state->foo, M_TEMP); +} + +/*------------------------------------------------------------*/ + +/* + * go generate the checksum. + */ +static void +ah_update_mbuf(m, off, len, algo, algos) + struct mbuf *m; + int off; + int len; + const struct ah_algorithm *algo; + struct ah_algorithm_state *algos; +{ + struct mbuf *n; + int tlen; + + /* easy case first */ + if (off + len <= m->m_len) { + (algo->update)(algos, mtod(m, caddr_t) + off, len); + return; + } + + for (n = m; n; n = n->m_next) { + if (off < n->m_len) + break; + + off -= n->m_len; + } + + if (!n) + panic("ah_update_mbuf: wrong offset specified"); + + for (/* nothing */; n && len > 0; n = n->m_next) { + if (n->m_len == 0) + continue; + if (n->m_len - off < len) + tlen = n->m_len - off; + else + tlen = len; + + (algo->update)(algos, mtod(n, caddr_t) + off, tlen); + + len -= tlen; + off = 0; + } +} + +#ifdef INET +/* + * Go generate the checksum. This function won't modify the mbuf chain + * except AH itself. + * + * NOTE: the function does not free mbuf on failure. + * Don't use m_copy(), it will try to share cluster mbuf by using refcnt. + */ +int +ah4_calccksum(m, ahdat, len, algo, sav) + struct mbuf *m; + caddr_t ahdat; + size_t len; + const struct ah_algorithm *algo; + struct secasvar *sav; +{ + int off; + int hdrtype; + size_t advancewidth; + struct ah_algorithm_state algos; + u_char sumbuf[AH_MAXSUMSIZE]; + int error = 0; + int ahseen; + struct mbuf *n = NULL; + + if ((m->m_flags & M_PKTHDR) == 0) + return EINVAL; + + ahseen = 0; + hdrtype = -1; /* dummy, it is called IPPROTO_IP */ + + off = 0; + + error = (algo->init)(&algos, sav); + if (error) + return error; + + advancewidth = 0; /* safety */ + +again: + /* gory. */ + switch (hdrtype) { + case -1: /* first one only */ + { + /* + * copy ip hdr, modify to fit the AH checksum rule, + * then take a checksum. + */ + struct ip iphdr; + size_t hlen; + + m_copydata(m, off, sizeof(iphdr), (caddr_t)&iphdr); +#ifdef _IP_VHL + hlen = IP_VHL_HL(iphdr.ip_vhl) << 2; +#else + hlen = iphdr.ip_hl << 2; +#endif + iphdr.ip_ttl = 0; + iphdr.ip_sum = htons(0); + if (ip4_ah_cleartos) + iphdr.ip_tos = 0; + iphdr.ip_off = htons(ntohs(iphdr.ip_off) & ip4_ah_offsetmask); + (algo->update)(&algos, (caddr_t)&iphdr, sizeof(struct ip)); + + if (hlen != sizeof(struct ip)) { + u_char *p; + int i, l, skip; + + if (hlen > MCLBYTES) { + error = EMSGSIZE; + goto fail; + } + MGET(n, M_DONTWAIT, MT_DATA); + if (n && hlen > MLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (n == NULL) { + error = ENOBUFS; + goto fail; + } + m_copydata(m, off, hlen, mtod(n, caddr_t)); + + /* + * IP options processing. + * See RFC2402 appendix A. + */ + p = mtod(n, u_char *); + i = sizeof(struct ip); + while (i < hlen) { + if (i + IPOPT_OPTVAL >= hlen) { + ipseclog((LOG_ERR, "ah4_calccksum: " + "invalid IP option\n")); + error = EINVAL; + goto fail; + } + if (p[i + IPOPT_OPTVAL] == IPOPT_EOL || + p[i + IPOPT_OPTVAL] == IPOPT_NOP || + i + IPOPT_OLEN < hlen) + ; + else { + ipseclog((LOG_ERR, + "ah4_calccksum: invalid IP option " + "(type=%02x)\n", + p[i + IPOPT_OPTVAL])); + error = EINVAL; + goto fail; + } + + skip = 1; + switch (p[i + IPOPT_OPTVAL]) { + case IPOPT_EOL: + case IPOPT_NOP: + l = 1; + skip = 0; + break; + case IPOPT_SECURITY: /* 0x82 */ + case 0x85: /* Extended security */ + case 0x86: /* Commercial security */ + case 0x94: /* Router alert */ + case 0x95: /* RFC1770 */ + l = p[i + IPOPT_OLEN]; + if (l < 2) + goto invalopt; + skip = 0; + break; + default: + l = p[i + IPOPT_OLEN]; + if (l < 2) + goto invalopt; + skip = 1; + break; + } + if (l < 1 || hlen - i < l) { + invalopt: + ipseclog((LOG_ERR, + "ah4_calccksum: invalid IP option " + "(type=%02x len=%02x)\n", + p[i + IPOPT_OPTVAL], + p[i + IPOPT_OLEN])); + error = EINVAL; + goto fail; + } + if (skip) + bzero(p + i, l); + if (p[i + IPOPT_OPTVAL] == IPOPT_EOL) + break; + i += l; + } + p = mtod(n, u_char *) + sizeof(struct ip); + (algo->update)(&algos, p, hlen - sizeof(struct ip)); + + m_free(n); + n = NULL; + } + + hdrtype = (iphdr.ip_p) & 0xff; + advancewidth = hlen; + break; + } + + case IPPROTO_AH: + { + struct ah ah; + int siz; + int hdrsiz; + int totlen; + + m_copydata(m, off, sizeof(ah), (caddr_t)&ah); + hdrsiz = (sav->flags & SADB_X_EXT_OLD) + ? sizeof(struct ah) + : sizeof(struct newah); + siz = (*algo->sumsiz)(sav); + totlen = (ah.ah_len + 2) << 2; + + /* + * special treatment is necessary for the first one, not others + */ + if (!ahseen) { + if (totlen > m->m_pkthdr.len - off || + totlen > MCLBYTES) { + error = EMSGSIZE; + goto fail; + } + MGET(n, M_DONTWAIT, MT_DATA); + if (n && totlen > MLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (n == NULL) { + error = ENOBUFS; + goto fail; + } + m_copydata(m, off, totlen, mtod(n, caddr_t)); + n->m_len = totlen; + bzero(mtod(n, caddr_t) + hdrsiz, siz); + (algo->update)(&algos, mtod(n, caddr_t), n->m_len); + m_free(n); + n = NULL; + } else + ah_update_mbuf(m, off, totlen, algo, &algos); + ahseen++; + + hdrtype = ah.ah_nxt; + advancewidth = totlen; + break; + } + + default: + ah_update_mbuf(m, off, m->m_pkthdr.len - off, algo, &algos); + advancewidth = m->m_pkthdr.len - off; + break; + } + + off += advancewidth; + if (off < m->m_pkthdr.len) + goto again; + + if (len < (*algo->sumsiz)(sav)) { + error = EINVAL; + goto fail; + } + + (algo->result)(&algos, &sumbuf[0]); + bcopy(&sumbuf[0], ahdat, (*algo->sumsiz)(sav)); + + if (n) + m_free(n); + return error; + +fail: + if (n) + m_free(n); + return error; +} +#endif + +#ifdef INET6 +/* + * Go generate the checksum. This function won't modify the mbuf chain + * except AH itself. + * + * NOTE: the function does not free mbuf on failure. + * Don't use m_copy(), it will try to share cluster mbuf by using refcnt. + */ +int +ah6_calccksum(m, ahdat, len, algo, sav) + struct mbuf *m; + caddr_t ahdat; + size_t len; + const struct ah_algorithm *algo; + struct secasvar *sav; +{ + int newoff, off; + int proto, nxt; + struct mbuf *n = NULL; + int error; + int ahseen; + struct ah_algorithm_state algos; + u_char sumbuf[AH_MAXSUMSIZE]; + + if ((m->m_flags & M_PKTHDR) == 0) + return EINVAL; + + error = (algo->init)(&algos, sav); + if (error) + return error; + + off = 0; + proto = IPPROTO_IPV6; + nxt = -1; + ahseen = 0; + + again: + newoff = ip6_nexthdr(m, off, proto, &nxt); + if (newoff < 0) + newoff = m->m_pkthdr.len; + else if (newoff <= off) { + error = EINVAL; + goto fail; + } + + switch (proto) { + case IPPROTO_IPV6: + /* + * special treatment is necessary for the first one, not others + */ + if (off == 0) { + struct ip6_hdr ip6copy; + + if (newoff - off != sizeof(struct ip6_hdr)) { + error = EINVAL; + goto fail; + } + + m_copydata(m, off, newoff - off, (caddr_t)&ip6copy); + /* RFC2402 */ + ip6copy.ip6_flow = 0; + ip6copy.ip6_vfc &= ~IPV6_VERSION_MASK; + ip6copy.ip6_vfc |= IPV6_VERSION; + ip6copy.ip6_hlim = 0; + if (IN6_IS_ADDR_LINKLOCAL(&ip6copy.ip6_src)) + ip6copy.ip6_src.s6_addr16[1] = 0x0000; + if (IN6_IS_ADDR_LINKLOCAL(&ip6copy.ip6_dst)) + ip6copy.ip6_dst.s6_addr16[1] = 0x0000; + (algo->update)(&algos, (caddr_t)&ip6copy, + sizeof(struct ip6_hdr)); + } else { + newoff = m->m_pkthdr.len; + ah_update_mbuf(m, off, m->m_pkthdr.len - off, algo, + &algos); + } + break; + + case IPPROTO_AH: + { + int siz; + int hdrsiz; + + hdrsiz = (sav->flags & SADB_X_EXT_OLD) + ? sizeof(struct ah) + : sizeof(struct newah); + siz = (*algo->sumsiz)(sav); + + /* + * special treatment is necessary for the first one, not others + */ + if (!ahseen) { + if (newoff - off > MCLBYTES) { + error = EMSGSIZE; + goto fail; + } + MGET(n, M_DONTWAIT, MT_DATA); + if (n && newoff - off > MLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (n == NULL) { + error = ENOBUFS; + goto fail; + } + m_copydata(m, off, newoff - off, mtod(n, caddr_t)); + n->m_len = newoff - off; + bzero(mtod(n, caddr_t) + hdrsiz, siz); + (algo->update)(&algos, mtod(n, caddr_t), n->m_len); + m_free(n); + n = NULL; + } else + ah_update_mbuf(m, off, newoff - off, algo, &algos); + ahseen++; + break; + } + + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + { + struct ip6_ext *ip6e; + int hdrlen, optlen; + u_int8_t *p, *optend, *optp; + + if (newoff - off > MCLBYTES) { + error = EMSGSIZE; + goto fail; + } + MGET(n, M_DONTWAIT, MT_DATA); + if (n && newoff - off > MLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (n == NULL) { + error = ENOBUFS; + goto fail; + } + m_copydata(m, off, newoff - off, mtod(n, caddr_t)); + n->m_len = newoff - off; + + ip6e = mtod(n, struct ip6_ext *); + hdrlen = (ip6e->ip6e_len + 1) << 3; + if (newoff - off < hdrlen) { + error = EINVAL; + m_free(n); + n = NULL; + goto fail; + } + p = mtod(n, u_int8_t *); + optend = p + hdrlen; + + /* + * ICV calculation for the options header including all + * options. This part is a little tricky since there are + * two type of options; mutable and immutable. We try to + * null-out mutable ones here. + */ + optp = p + 2; + while (optp < optend) { + if (optp[0] == IP6OPT_PAD1) + optlen = 1; + else { + if (optp + 2 > optend) { + error = EINVAL; + m_free(n); + n = NULL; + goto fail; + } + optlen = optp[1] + 2; + + if (optp[0] & IP6OPT_MUTABLE) + bzero(optp + 2, optlen - 2); + } + + optp += optlen; + } + + (algo->update)(&algos, mtod(n, caddr_t), n->m_len); + m_free(n); + n = NULL; + break; + } + + case IPPROTO_ROUTING: + /* + * For an input packet, we can just calculate `as is'. + * For an output packet, we assume ip6_output have already + * made packet how it will be received at the final + * destination. + */ + /* FALLTHROUGH */ + + default: + ah_update_mbuf(m, off, newoff - off, algo, &algos); + break; + } + + if (newoff < m->m_pkthdr.len) { + proto = nxt; + off = newoff; + goto again; + } + + if (len < (*algo->sumsiz)(sav)) { + error = EINVAL; + goto fail; + } + + (algo->result)(&algos, &sumbuf[0]); + bcopy(&sumbuf[0], ahdat, (*algo->sumsiz)(sav)); + + /* just in case */ + if (n) + m_free(n); + return 0; +fail: + /* just in case */ + if (n) + m_free(n); + return error; +} +#endif diff --git a/sys/netinet6/ah_input.c b/sys/netinet6/ah_input.c new file mode 100644 index 0000000..d7d3030 --- /dev/null +++ b/sys/netinet6/ah_input.c @@ -0,0 +1,1036 @@ +/* $FreeBSD$ */ +/* $KAME: ah_input.c,v 1.67 2002/01/07 11:39:56 kjc Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1826/2402 authentication header. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <machine/cpu.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_ecn.h> +#ifdef INET6 +#include <netinet6/ip6_ecn.h> +#endif + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/in_pcb.h> +#include <netinet6/in6_pcb.h> +#include <netinet/icmp6.h> +#include <netinet6/ip6protosw.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> +#ifdef IPSEC_DEBUG +#include <netkey/key_debug.h> +#else +#define KEYDEBUG(lev,arg) +#endif + +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +#define IPLEN_FLIPPED + +#ifdef INET +extern struct protosw inetsw[]; + +void +ah4_input(m, off) + struct mbuf *m; + int off; +{ + struct ip *ip; + struct ah *ah; + u_int32_t spi; + const struct ah_algorithm *algo; + size_t siz; + size_t siz1; + u_char *cksum; + struct secasvar *sav = NULL; + u_int16_t nxt; + size_t hlen; + int proto; + size_t stripsiz = 0; + +#ifndef PULLDOWN_TEST + if (m->m_len < off + sizeof(struct newah)) { + m = m_pullup(m, off + sizeof(struct newah)); + if (!m) { + ipseclog((LOG_DEBUG, "IPv4 AH input: can't pullup;" + "dropping the packet for simplicity\n")); + ipsecstat.in_inval++; + goto fail; + } + } + + ip = mtod(m, struct ip *); + proto = ip->ip_p; + ah = (struct ah *)(((caddr_t)ip) + off); +#else + ip = mtod(m, struct ip *); + proto = ip->ip_p; + IP6_EXTHDR_GET(ah, struct ah *, m, off, sizeof(struct newah)); + if (ah == NULL) { + ipseclog((LOG_DEBUG, "IPv4 AH input: can't pullup;" + "dropping the packet for simplicity\n")); + ipsecstat.in_inval++; + goto fail; + } +#endif + nxt = ah->ah_nxt; +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + + /* find the sassoc. */ + spi = ah->ah_spi; + + if ((sav = key_allocsa(AF_INET, + (caddr_t)&ip->ip_src, (caddr_t)&ip->ip_dst, + IPPROTO_AH, spi)) == 0) { + ipseclog((LOG_WARNING, + "IPv4 AH input: no key association found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_nosa++; + goto fail; + } + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah4_input called to allocate SA:%p\n", sav)); + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) { + ipseclog((LOG_DEBUG, + "IPv4 AH input: non-mature/dying SA found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_badspi++; + goto fail; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_DEBUG, "IPv4 AH input: " + "unsupported authentication algorithm for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_badspi++; + goto fail; + } + + siz = (*algo->sumsiz)(sav); + siz1 = ((siz + 3) & ~(4 - 1)); + + /* + * sanity checks for header, 1. + */ + { + int sizoff; + + sizoff = (sav->flags & SADB_X_EXT_OLD) ? 0 : 4; + + /* + * Here, we do not do "siz1 == siz". This is because the way + * RFC240[34] section 2 is written. They do not require truncation + * to 96 bits. + * For example, Microsoft IPsec stack attaches 160 bits of + * authentication data for both hmac-md5 and hmac-sha1. For hmac-sha1, + * 32 bits of padding is attached. + * + * There are two downsides to this specification. + * They have no real harm, however, they leave us fuzzy feeling. + * - if we attach more than 96 bits of authentication data onto AH, + * we will never notice about possible modification by rogue + * intermediate nodes. + * Since extra bits in AH checksum is never used, this constitutes + * no real issue, however, it is wacky. + * - even if the peer attaches big authentication data, we will never + * notice the difference, since longer authentication data will just + * work. + * + * We may need some clarification in the spec. + */ + if (siz1 < siz) { + ipseclog((LOG_NOTICE, "sum length too short in IPv4 AH input " + "(%lu, should be at least %lu): %s\n", + (u_long)siz1, (u_long)siz, + ipsec4_logpacketstr(ip, spi))); + ipsecstat.in_inval++; + goto fail; + } + if ((ah->ah_len << 2) - sizoff != siz1) { + ipseclog((LOG_NOTICE, "sum length mismatch in IPv4 AH input " + "(%d should be %lu): %s\n", + (ah->ah_len << 2) - sizoff, (u_long)siz1, + ipsec4_logpacketstr(ip, spi))); + ipsecstat.in_inval++; + goto fail; + } + +#ifndef PULLDOWN_TEST + if (m->m_len < off + sizeof(struct ah) + sizoff + siz1) { + m = m_pullup(m, off + sizeof(struct ah) + sizoff + siz1); + if (!m) { + ipseclog((LOG_DEBUG, "IPv4 AH input: can't pullup\n")); + ipsecstat.in_inval++; + goto fail; + } + + ip = mtod(m, struct ip *); + ah = (struct ah *)(((caddr_t)ip) + off); + } +#else + IP6_EXTHDR_GET(ah, struct ah *, m, off, + sizeof(struct ah) + sizoff + siz1); + if (ah == NULL) { + ipseclog((LOG_DEBUG, "IPv4 AH input: can't pullup\n")); + ipsecstat.in_inval++; + goto fail; + } +#endif + } + + /* + * check for sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_chkreplay(ntohl(((struct newah *)ah)->ah_seq), sav)) + ; /* okey */ + else { + ipsecstat.in_ahreplay++; + ipseclog((LOG_WARNING, + "replay packet in IPv4 AH input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + goto fail; + } + } + + /* + * alright, it seems sane. now we are going to check the + * cryptographic checksum. + */ + cksum = malloc(siz1, M_TEMP, M_NOWAIT); + if (!cksum) { + ipseclog((LOG_DEBUG, "IPv4 AH input: " + "couldn't alloc temporary region for cksum\n")); + ipsecstat.in_inval++; + goto fail; + } + + /* + * some of IP header fields are flipped to the host endian. + * convert them back to network endian. VERY stupid. + */ + ip->ip_len = htons(ip->ip_len + hlen); + ip->ip_off = htons(ip->ip_off); + if (ah4_calccksum(m, (caddr_t)cksum, siz1, algo, sav)) { + free(cksum, M_TEMP); + ipsecstat.in_inval++; + goto fail; + } + ipsecstat.in_ahhist[sav->alg_auth]++; + /* + * flip them back. + */ + ip->ip_len = ntohs(ip->ip_len) - hlen; + ip->ip_off = ntohs(ip->ip_off); + + { + caddr_t sumpos = NULL; + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + sumpos = (caddr_t)(ah + 1); + } else { + /* RFC 2402 */ + sumpos = (caddr_t)(((struct newah *)ah) + 1); + } + + if (bcmp(sumpos, cksum, siz) != 0) { + ipseclog((LOG_WARNING, + "checksum mismatch in IPv4 AH input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + free(cksum, M_TEMP); + ipsecstat.in_ahauthfail++; + goto fail; + } + } + + free(cksum, M_TEMP); + + m->m_flags |= M_AUTHIPHDR; + m->m_flags |= M_AUTHIPDGM; + +#if 0 + /* + * looks okey, but we need more sanity check. + * XXX should elaborate. + */ + if (ah->ah_nxt == IPPROTO_IPIP || ah->ah_nxt == IPPROTO_IP) { + struct ip *nip; + size_t sizoff; + + sizoff = (sav->flags & SADB_X_EXT_OLD) ? 0 : 4; + + if (m->m_len < off + sizeof(struct ah) + sizoff + siz1 + hlen) { + m = m_pullup(m, off + sizeof(struct ah) + + sizoff + siz1 + hlen); + if (!m) { + ipseclog((LOG_DEBUG, + "IPv4 AH input: can't pullup\n")); + ipsecstat.in_inval++; + goto fail; + } + } + + nip = (struct ip *)((u_char *)(ah + 1) + sizoff + siz1); + if (nip->ip_src.s_addr != ip->ip_src.s_addr + || nip->ip_dst.s_addr != ip->ip_dst.s_addr) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } + } +#ifdef INET6 + else if (ah->ah_nxt == IPPROTO_IPV6) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } +#endif /* INET6 */ +#endif /* 0 */ + + if (m->m_flags & M_AUTHIPHDR + && m->m_flags & M_AUTHIPDGM) { +#if 0 + ipseclog((LOG_DEBUG, + "IPv4 AH input: authentication succeess\n")); +#endif + ipsecstat.in_ahauthsucc++; + } else { + ipseclog((LOG_WARNING, + "authentication failed in IPv4 AH input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_ahauthfail++; + goto fail; + } + + /* + * update sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_updatereplay(ntohl(((struct newah *)ah)->ah_seq), sav)) { + ipsecstat.in_ahreplay++; + goto fail; + } + } + + /* was it transmitted over the IPsec tunnel SA? */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + stripsiz = sizeof(struct ah) + siz1; + } else { + /* RFC 2402 */ + stripsiz = sizeof(struct newah) + siz1; + } + if (ipsec4_tunnel_validate(m, off + stripsiz, nxt, sav)) { + /* + * strip off all the headers that precedes AH. + * IP xx AH IP' payload -> IP' payload + * + * XXX more sanity checks + * XXX relationship with gif? + */ + u_int8_t tos; + + tos = ip->ip_tos; + m_adj(m, off + stripsiz); + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (!m) { + ipsecstat.in_inval++; + goto fail; + } + } + ip = mtod(m, struct ip *); + /* ECN consideration. */ + ip_ecn_egress(ip4_ipsec_ecn, &tos, &ip->ip_tos); + if (!key_checktunnelsanity(sav, AF_INET, + (caddr_t)&ip->ip_src, (caddr_t)&ip->ip_dst)) { + ipseclog((LOG_NOTICE, "ipsec tunnel address mismatch " + "in IPv4 AH input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_inval++; + goto fail; + } + +#if 1 + /* + * Should the inner packet be considered authentic? + * My current answer is: NO. + * + * host1 -- gw1 === gw2 -- host2 + * In this case, gw2 can trust the authenticity of the + * outer packet, but NOT inner. Packet may be altered + * between host1 and gw1. + * + * host1 -- gw1 === host2 + * This case falls into the same scenario as above. + * + * host1 === host2 + * This case is the only case when we may be able to leave + * M_AUTHIPHDR and M_AUTHIPDGM set. + * However, if host1 is wrongly configured, and allows + * attacker to inject some packet with src=host1 and + * dst=host2, you are in risk. + */ + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; +#endif + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_AH, spi) != 0 || + ipsec_addhist(m, IPPROTO_IPV4, 0) != 0) { + ipsecstat.in_nomem++; + goto fail; + } + + if (! netisr_queue(NETISR_IP, m)) { + ipsecstat.in_inval++; + m = NULL; + goto fail; + } + m = NULL; + nxt = IPPROTO_DONE; + } else { + /* + * strip off AH. + */ + + ip = mtod(m, struct ip *); +#ifndef PULLDOWN_TEST + /* + * We do deep-copy since KAME requires that + * the packet is placed in a single external mbuf. + */ + ovbcopy((caddr_t)ip, (caddr_t)(((u_char *)ip) + stripsiz), off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; +#else + /* + * even in m_pulldown case, we need to strip off AH so that + * we can compute checksum for multiple AH correctly. + */ + if (m->m_len >= stripsiz + off) { + ovbcopy((caddr_t)ip, ((caddr_t)ip) + stripsiz, off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; + } else { + /* + * this comes with no copy if the boundary is on + * cluster + */ + struct mbuf *n; + + n = m_split(m, off, M_DONTWAIT); + if (n == NULL) { + /* m is retained by m_split */ + goto fail; + } + m_adj(n, stripsiz); + m_cat(m, n); + /* m_cat does not update m_pkthdr.len */ + m->m_pkthdr.len += n->m_pkthdr.len; + } +#endif + + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (m == NULL) { + ipsecstat.in_inval++; + goto fail; + } + } + ip = mtod(m, struct ip *); +#ifdef IPLEN_FLIPPED + ip->ip_len = ip->ip_len - stripsiz; +#else + ip->ip_len = htons(ntohs(ip->ip_len) - stripsiz); +#endif + ip->ip_p = nxt; + /* forget about IP hdr checksum, the check has already been passed */ + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_AH, spi) != 0) { + ipsecstat.in_nomem++; + goto fail; + } + + if (nxt != IPPROTO_DONE) { + if ((inetsw[ip_protox[nxt]].pr_flags & PR_LASTHDR) != 0 && + ipsec4_in_reject(m, NULL)) { + ipsecstat.in_polvio++; + goto fail; + } + (*inetsw[ip_protox[nxt]].pr_input)(m, off); + } else + m_freem(m); + m = NULL; + } + + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah4_input call free SA:%p\n", sav)); + key_freesav(sav); + } + ipsecstat.in_success++; + return; + +fail: + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah4_input call free SA:%p\n", sav)); + key_freesav(sav); + } + if (m) + m_freem(m); + return; +} +#endif /* INET */ + +#ifdef INET6 +int +ah6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + int off = *offp; + struct ip6_hdr *ip6; + struct ah *ah; + u_int32_t spi; + const struct ah_algorithm *algo; + size_t siz; + size_t siz1; + u_char *cksum; + struct secasvar *sav = NULL; + u_int16_t nxt; + size_t stripsiz = 0; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(struct ah), IPPROTO_DONE); + ah = (struct ah *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(ah, struct ah *, m, off, sizeof(struct newah)); + if (ah == NULL) { + ipseclog((LOG_DEBUG, "IPv6 AH input: can't pullup\n")); + ipsec6stat.in_inval++; + return IPPROTO_DONE; + } +#endif + ip6 = mtod(m, struct ip6_hdr *); + nxt = ah->ah_nxt; + + /* find the sassoc. */ + spi = ah->ah_spi; + + if (ntohs(ip6->ip6_plen) == 0) { + ipseclog((LOG_ERR, "IPv6 AH input: " + "AH with IPv6 jumbogram is not supported.\n")); + ipsec6stat.in_inval++; + goto fail; + } + + if ((sav = key_allocsa(AF_INET6, + (caddr_t)&ip6->ip6_src, (caddr_t)&ip6->ip6_dst, + IPPROTO_AH, spi)) == 0) { + ipseclog((LOG_WARNING, + "IPv6 AH input: no key association found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_nosa++; + goto fail; + } + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah6_input called to allocate SA:%p\n", sav)); + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) { + ipseclog((LOG_DEBUG, + "IPv6 AH input: non-mature/dying SA found for spi %u; ", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_badspi++; + goto fail; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_DEBUG, "IPv6 AH input: " + "unsupported authentication algorithm for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_badspi++; + goto fail; + } + + siz = (*algo->sumsiz)(sav); + siz1 = ((siz + 3) & ~(4 - 1)); + + /* + * sanity checks for header, 1. + */ + { + int sizoff; + + sizoff = (sav->flags & SADB_X_EXT_OLD) ? 0 : 4; + + /* + * Here, we do not do "siz1 == siz". See ah4_input() for complete + * description. + */ + if (siz1 < siz) { + ipseclog((LOG_NOTICE, "sum length too short in IPv6 AH input " + "(%lu, should be at least %lu): %s\n", + (u_long)siz1, (u_long)siz, + ipsec6_logpacketstr(ip6, spi))); + ipsec6stat.in_inval++; + goto fail; + } + if ((ah->ah_len << 2) - sizoff != siz1) { + ipseclog((LOG_NOTICE, "sum length mismatch in IPv6 AH input " + "(%d should be %lu): %s\n", + (ah->ah_len << 2) - sizoff, (u_long)siz1, + ipsec6_logpacketstr(ip6, spi))); + ipsec6stat.in_inval++; + goto fail; + } +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(struct ah) + sizoff + siz1, IPPROTO_DONE); +#else + IP6_EXTHDR_GET(ah, struct ah *, m, off, + sizeof(struct ah) + sizoff + siz1); + if (ah == NULL) { + ipseclog((LOG_NOTICE, "couldn't pullup gather IPv6 AH checksum part")); + ipsec6stat.in_inval++; + m = NULL; + goto fail; + } +#endif + } + + /* + * check for sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_chkreplay(ntohl(((struct newah *)ah)->ah_seq), sav)) + ; /* okey */ + else { + ipsec6stat.in_ahreplay++; + ipseclog((LOG_WARNING, + "replay packet in IPv6 AH input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), + ipsec_logsastr(sav))); + goto fail; + } + } + + /* + * alright, it seems sane. now we are going to check the + * cryptographic checksum. + */ + cksum = malloc(siz1, M_TEMP, M_NOWAIT); + if (!cksum) { + ipseclog((LOG_DEBUG, "IPv6 AH input: " + "couldn't alloc temporary region for cksum\n")); + ipsec6stat.in_inval++; + goto fail; + } + + if (ah6_calccksum(m, (caddr_t)cksum, siz1, algo, sav)) { + free(cksum, M_TEMP); + ipsec6stat.in_inval++; + goto fail; + } + ipsec6stat.in_ahhist[sav->alg_auth]++; + + { + caddr_t sumpos = NULL; + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + sumpos = (caddr_t)(ah + 1); + } else { + /* RFC 2402 */ + sumpos = (caddr_t)(((struct newah *)ah) + 1); + } + + if (bcmp(sumpos, cksum, siz) != 0) { + ipseclog((LOG_WARNING, + "checksum mismatch in IPv6 AH input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + free(cksum, M_TEMP); + ipsec6stat.in_ahauthfail++; + goto fail; + } + } + + free(cksum, M_TEMP); + + m->m_flags |= M_AUTHIPHDR; + m->m_flags |= M_AUTHIPDGM; + +#if 0 + /* + * looks okey, but we need more sanity check. + * XXX should elaborate. + */ + if (ah->ah_nxt == IPPROTO_IPV6) { + struct ip6_hdr *nip6; + size_t sizoff; + + sizoff = (sav->flags & SADB_X_EXT_OLD) ? 0 : 4; + + IP6_EXTHDR_CHECK(m, off, sizeof(struct ah) + sizoff + siz1 + + sizeof(struct ip6_hdr), IPPROTO_DONE); + + nip6 = (struct ip6_hdr *)((u_char *)(ah + 1) + sizoff + siz1); + if (!IN6_ARE_ADDR_EQUAL(&nip6->ip6_src, &ip6->ip6_src) + || !IN6_ARE_ADDR_EQUAL(&nip6->ip6_dst, &ip6->ip6_dst)) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } + } else if (ah->ah_nxt == IPPROTO_IPIP) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } else if (ah->ah_nxt == IPPROTO_IP) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } +#endif + + if (m->m_flags & M_AUTHIPHDR + && m->m_flags & M_AUTHIPDGM) { +#if 0 + ipseclog((LOG_DEBUG, + "IPv6 AH input: authentication succeess\n")); +#endif + ipsec6stat.in_ahauthsucc++; + } else { + ipseclog((LOG_WARNING, + "authentication failed in IPv6 AH input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + ipsec6stat.in_ahauthfail++; + goto fail; + } + + /* + * update sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_updatereplay(ntohl(((struct newah *)ah)->ah_seq), sav)) { + ipsec6stat.in_ahreplay++; + goto fail; + } + } + + /* was it transmitted over the IPsec tunnel SA? */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + stripsiz = sizeof(struct ah) + siz1; + } else { + /* RFC 2402 */ + stripsiz = sizeof(struct newah) + siz1; + } + if (ipsec6_tunnel_validate(m, off + stripsiz, nxt, sav)) { + /* + * strip off all the headers that precedes AH. + * IP6 xx AH IP6' payload -> IP6' payload + * + * XXX more sanity checks + * XXX relationship with gif? + */ + u_int32_t flowinfo; /* net endian */ + + flowinfo = ip6->ip6_flow; + m_adj(m, off + stripsiz); + if (m->m_len < sizeof(*ip6)) { + /* + * m_pullup is prohibited in KAME IPv6 input processing + * but there's no other way! + */ + m = m_pullup(m, sizeof(*ip6)); + if (!m) { + ipsec6stat.in_inval++; + goto fail; + } + } + ip6 = mtod(m, struct ip6_hdr *); + /* ECN consideration. */ + ip6_ecn_egress(ip6_ipsec_ecn, &flowinfo, &ip6->ip6_flow); + if (!key_checktunnelsanity(sav, AF_INET6, + (caddr_t)&ip6->ip6_src, (caddr_t)&ip6->ip6_dst)) { + ipseclog((LOG_NOTICE, "ipsec tunnel address mismatch " + "in IPv6 AH input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), + ipsec_logsastr(sav))); + ipsec6stat.in_inval++; + goto fail; + } + +#if 1 + /* + * should the inner packet be considered authentic? + * see comment in ah4_input(). + */ + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; +#endif + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_AH, spi) != 0 || + ipsec_addhist(m, IPPROTO_IPV6, 0) != 0) { + ipsec6stat.in_nomem++; + goto fail; + } + + if (! netisr_queue(NETISR_IPV6, m)) { + ipsec6stat.in_inval++; + m = NULL; + goto fail; + } + m = NULL; + nxt = IPPROTO_DONE; + } else { + /* + * strip off AH. + */ + char *prvnxtp; + + /* + * Copy the value of the next header field of AH to the + * next header field of the previous header. + * This is necessary because AH will be stripped off below. + */ + prvnxtp = ip6_get_prevhdr(m, off); /* XXX */ + *prvnxtp = nxt; + + ip6 = mtod(m, struct ip6_hdr *); +#ifndef PULLDOWN_TEST + /* + * We do deep-copy since KAME requires that + * the packet is placed in a single mbuf. + */ + ovbcopy((caddr_t)ip6, ((caddr_t)ip6) + stripsiz, off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; +#else + /* + * even in m_pulldown case, we need to strip off AH so that + * we can compute checksum for multiple AH correctly. + */ + if (m->m_len >= stripsiz + off) { + ovbcopy((caddr_t)ip6, ((caddr_t)ip6) + stripsiz, off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; + } else { + /* + * this comes with no copy if the boundary is on + * cluster + */ + struct mbuf *n; + + n = m_split(m, off, M_DONTWAIT); + if (n == NULL) { + /* m is retained by m_split */ + goto fail; + } + m_adj(n, stripsiz); + m_cat(m, n); + /* m_cat does not update m_pkthdr.len */ + m->m_pkthdr.len += n->m_pkthdr.len; + } +#endif + ip6 = mtod(m, struct ip6_hdr *); + /* XXX jumbogram */ + ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) - stripsiz); + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_AH, spi) != 0) { + ipsec6stat.in_nomem++; + goto fail; + } + } + + *offp = off; + *mp = m; + + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah6_input call free SA:%p\n", sav)); + key_freesav(sav); + } + ipsec6stat.in_success++; + return nxt; + +fail: + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ah6_input call free SA:%p\n", sav)); + key_freesav(sav); + } + if (m) + m_freem(m); + return IPPROTO_DONE; +} + +void +ah6_ctlinput(cmd, sa, d) + int cmd; + struct sockaddr *sa; + void *d; +{ + const struct newah *ahp; + struct newah ah; + struct secasvar *sav; + struct ip6_hdr *ip6; + struct mbuf *m; + struct ip6ctlparam *ip6cp = NULL; + int off; + struct sockaddr_in6 *sa6_src, *sa6_dst; + + if (sa->sa_family != AF_INET6 || + sa->sa_len != sizeof(struct sockaddr_in6)) + return; + if ((unsigned)cmd >= PRC_NCMDS) + return; + + /* if the parameter is from icmp6, decode it. */ + if (d != NULL) { + ip6cp = (struct ip6ctlparam *)d; + m = ip6cp->ip6c_m; + ip6 = ip6cp->ip6c_ip6; + off = ip6cp->ip6c_off; + } else { + m = NULL; + ip6 = NULL; + off = 0; /* calm gcc */ + } + + if (ip6) { + /* + * XXX: We assume that when ip6 is non NULL, + * M and OFF are valid. + */ + + /* check if we can safely examine src and dst ports */ + if (m->m_pkthdr.len < off + sizeof(ah)) + return; + + if (m->m_len < off + sizeof(ah)) { + /* + * this should be rare case, + * so we compromise on this copy... + */ + m_copydata(m, off, sizeof(ah), (caddr_t)&ah); + ahp = &ah; + } else + ahp = (struct newah *)(mtod(m, caddr_t) + off); + + if (cmd == PRC_MSGSIZE) { + int valid = 0; + + /* + * Check to see if we have a valid SA corresponding to + * the address in the ICMP message payload. + */ + sa6_src = ip6cp->ip6c_src; + sa6_dst = (struct sockaddr_in6 *)sa; + sav = key_allocsa(AF_INET6, + (caddr_t)&sa6_src->sin6_addr, + (caddr_t)&sa6_dst->sin6_addr, + IPPROTO_AH, ahp->ah_spi); + if (sav) { + if (sav->state == SADB_SASTATE_MATURE || + sav->state == SADB_SASTATE_DYING) + valid++; + key_freesav(sav); + } + + /* XXX Further validation? */ + + /* + * Depending on the value of "valid" and routing table + * size (mtudisc_{hi,lo}wat), we will: + * - recalcurate the new MTU and create the + * corresponding routing entry, or + * - ignore the MTU change notification. + */ + icmp6_mtudisc_update((struct ip6ctlparam *)d, valid); + } + + /* we normally notify single pcb here */ + } else { + /* we normally notify any pcb here */ + } +} +#endif /* INET6 */ diff --git a/sys/netinet6/ah_output.c b/sys/netinet6/ah_output.c new file mode 100644 index 0000000..0887c41 --- /dev/null +++ b/sys/netinet6/ah_output.c @@ -0,0 +1,588 @@ +/* $FreeBSD$ */ +/* $KAME: ah_output.c,v 1.31 2001/07/26 06:53:15 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1826/2402 authentication header. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> + +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> + +#include <net/net_osdep.h> + +#ifdef INET +static struct in_addr *ah4_finaldst __P((struct mbuf *)); +#endif + +/* + * compute AH header size. + * transport mode only. for tunnel mode, we should implement + * virtual interface, and control MTU/MSS by the interface MTU. + */ +size_t +ah_hdrsiz(isr) + struct ipsecrequest *isr; +{ + const struct ah_algorithm *algo; + size_t hdrsiz; + + /* sanity check */ + if (isr == NULL) + panic("ah_hdrsiz: NULL was passed."); + + if (isr->saidx.proto != IPPROTO_AH) + panic("unsupported mode passed to ah_hdrsiz"); + + if (isr->sav == NULL) + goto estimate; + if (isr->sav->state != SADB_SASTATE_MATURE + && isr->sav->state != SADB_SASTATE_DYING) + goto estimate; + + /* we need transport mode AH. */ + algo = ah_algorithm_lookup(isr->sav->alg_auth); + if (!algo) + goto estimate; + + /* + * XXX + * right now we don't calcurate the padding size. simply + * treat the padding size as constant, for simplicity. + * + * XXX variable size padding support + */ + hdrsiz = (((*algo->sumsiz)(isr->sav) + 3) & ~(4 - 1)); + if (isr->sav->flags & SADB_X_EXT_OLD) + hdrsiz += sizeof(struct ah); + else + hdrsiz += sizeof(struct newah); + + return hdrsiz; + + estimate: + /* ASSUMING: + * sizeof(struct newah) > sizeof(struct ah). + * 16 = (16 + 3) & ~(4 - 1). + */ + return sizeof(struct newah) + 16; +} + +#ifdef INET +/* + * Modify the packet so that it includes the authentication data. + * The mbuf passed must start with IPv4 header. + * + * assumes that the first mbuf contains IPv4 header + option only. + * the function does not modify m. + */ +int +ah4_output(m, isr) + struct mbuf *m; + struct ipsecrequest *isr; +{ + struct secasvar *sav = isr->sav; + const struct ah_algorithm *algo; + u_int32_t spi; + u_char *ahdrpos; + u_char *ahsumpos = NULL; + size_t hlen = 0; /* IP header+option in bytes */ + size_t plen = 0; /* AH payload size in bytes */ + size_t ahlen = 0; /* plen + sizeof(ah) */ + struct ip *ip; + struct in_addr dst; + struct in_addr *finaldst; + int error; + + /* sanity checks */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) { + struct ip *ip; + + ip = mtod(m, struct ip *); + ipseclog((LOG_DEBUG, "ah4_output: internal error: " + "sav->replay is null: %x->%x, SPI=%u\n", + (u_int32_t)ntohl(ip->ip_src.s_addr), + (u_int32_t)ntohl(ip->ip_dst.s_addr), + (u_int32_t)ntohl(sav->spi))); + ipsecstat.out_inval++; + m_freem(m); + return EINVAL; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, "ah4_output: unsupported algorithm: " + "SPI=%u\n", (u_int32_t)ntohl(sav->spi))); + ipsecstat.out_inval++; + m_freem(m); + return EINVAL; + } + spi = sav->spi; + + /* + * determine the size to grow. + */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */ + ahlen = plen + sizeof(struct ah); + } else { + /* RFC 2402 */ + plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */ + ahlen = plen + sizeof(struct newah); + } + + /* + * grow the mbuf to accomodate AH. + */ + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + + if (m->m_len != hlen) + panic("ah4_output: assumption failed (first mbuf length)"); + if (M_LEADINGSPACE(m->m_next) < ahlen) { + struct mbuf *n; + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + ipseclog((LOG_DEBUG, "ENOBUFS in ah4_output %d\n", + __LINE__)); + m_freem(m); + return ENOBUFS; + } + n->m_len = ahlen; + n->m_next = m->m_next; + m->m_next = n; + m->m_pkthdr.len += ahlen; + ahdrpos = mtod(n, u_char *); + } else { + m->m_next->m_len += ahlen; + m->m_next->m_data -= ahlen; + m->m_pkthdr.len += ahlen; + ahdrpos = mtod(m->m_next, u_char *); + } + + ip = mtod(m, struct ip *); /* just to be sure */ + + /* + * initialize AH. + */ + if (sav->flags & SADB_X_EXT_OLD) { + struct ah *ahdr; + + ahdr = (struct ah *)ahdrpos; + ahsumpos = (u_char *)(ahdr + 1); + ahdr->ah_len = plen >> 2; + ahdr->ah_nxt = ip->ip_p; + ahdr->ah_reserve = htons(0); + ahdr->ah_spi = spi; + bzero(ahdr + 1, plen); + } else { + struct newah *ahdr; + + ahdr = (struct newah *)ahdrpos; + ahsumpos = (u_char *)(ahdr + 1); + ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */ + ahdr->ah_nxt = ip->ip_p; + ahdr->ah_reserve = htons(0); + ahdr->ah_spi = spi; + if (sav->replay->count == ~0) { + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + /* XXX Is it noisy ? */ + ipseclog((LOG_WARNING, + "replay counter overflowed. %s\n", + ipsec_logsastr(sav))); + ipsecstat.out_inval++; + m_freem(m); + return EINVAL; + } + } + sav->replay->count++; + /* + * XXX sequence number must not be cycled, if the SA is + * installed by IKE daemon. + */ + ahdr->ah_seq = htonl(sav->replay->count); + bzero(ahdr + 1, plen); + } + + /* + * modify IPv4 header. + */ + ip->ip_p = IPPROTO_AH; + if (ahlen < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + ahlen); + else { + ipseclog((LOG_ERR, "IPv4 AH output: size exceeds limit\n")); + ipsecstat.out_inval++; + m_freem(m); + return EMSGSIZE; + } + + /* + * If there is source routing option, update destination field in + * the IPv4 header to the final destination. + * Note that we do not need to update source routing option itself + * (as done in IPv4 AH processing -- see ip6_output()), since + * source routing option is not part of the ICV computation. + */ + finaldst = ah4_finaldst(m); + if (finaldst) { + dst.s_addr = ip->ip_dst.s_addr; + ip->ip_dst.s_addr = finaldst->s_addr; + } + + /* + * calcurate the checksum, based on security association + * and the algorithm specified. + */ + error = ah4_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav); + if (error) { + ipseclog((LOG_ERR, + "error after ah4_calccksum, called from ah4_output")); + m_freem(m); + m = NULL; + ipsecstat.out_inval++; + return error; + } + + if (finaldst) { + ip = mtod(m, struct ip *); /* just to make sure */ + ip->ip_dst.s_addr = dst.s_addr; + } + ipsecstat.out_success++; + ipsecstat.out_ahhist[sav->alg_auth]++; + key_sa_recordxfer(sav, m); + + return 0; +} +#endif + +/* Calculate AH length */ +int +ah_hdrlen(sav) + struct secasvar *sav; +{ + const struct ah_algorithm *algo; + int plen, ahlen; + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) + return 0; + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1826 */ + plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */ + ahlen = plen + sizeof(struct ah); + } else { + /* RFC 2402 */ + plen = ((*algo->sumsiz)(sav) + 3) & ~(4 - 1); /* XXX pad to 8byte? */ + ahlen = plen + sizeof(struct newah); + } + + return(ahlen); +} + +#ifdef INET6 +/* + * Fill in the Authentication Header and calculate checksum. + */ +int +ah6_output(m, nexthdrp, md, isr) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; +{ + struct mbuf *mprev; + struct mbuf *mah; + struct secasvar *sav = isr->sav; + const struct ah_algorithm *algo; + u_int32_t spi; + u_char *ahsumpos = NULL; + size_t plen; /* AH payload size in bytes */ + int error = 0; + int ahlen; + struct ip6_hdr *ip6; + + if (m->m_len < sizeof(struct ip6_hdr)) { + ipseclog((LOG_DEBUG, "ah6_output: first mbuf too short\n")); + m_freem(m); + return EINVAL; + } + + ahlen = ah_hdrlen(sav); + if (ahlen == 0) + return 0; + + for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) + ; + if (!mprev || mprev->m_next != md) { + ipseclog((LOG_DEBUG, "ah6_output: md is not in chain\n")); + m_freem(m); + return EINVAL; + } + + MGET(mah, M_DONTWAIT, MT_DATA); + if (!mah) { + m_freem(m); + return ENOBUFS; + } + if (ahlen > MLEN) { + MCLGET(mah, M_DONTWAIT); + if ((mah->m_flags & M_EXT) == 0) { + m_free(mah); + m_freem(m); + return ENOBUFS; + } + } + mah->m_len = ahlen; + mah->m_next = md; + mprev->m_next = mah; + m->m_pkthdr.len += ahlen; + + /* fix plen */ + if (m->m_pkthdr.len - sizeof(struct ip6_hdr) > IPV6_MAXPACKET) { + ipseclog((LOG_ERR, + "ip6_output: AH with IPv6 jumbogram is not supported\n")); + m_freem(m); + return EINVAL; + } + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); + + if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) { + ipseclog((LOG_DEBUG, "ah6_output: internal error: " + "sav->replay is null: SPI=%u\n", + (u_int32_t)ntohl(sav->spi))); + ipsec6stat.out_inval++; + m_freem(m); + return EINVAL; + } + + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, "ah6_output: unsupported algorithm: " + "SPI=%u\n", (u_int32_t)ntohl(sav->spi))); + ipsec6stat.out_inval++; + m_freem(m); + return EINVAL; + } + spi = sav->spi; + + /* + * initialize AH. + */ + if (sav->flags & SADB_X_EXT_OLD) { + struct ah *ahdr = mtod(mah, struct ah *); + + plen = mah->m_len - sizeof(struct ah); + ahsumpos = (u_char *)(ahdr + 1); + ahdr->ah_nxt = *nexthdrp; + *nexthdrp = IPPROTO_AH; + ahdr->ah_len = plen >> 2; + ahdr->ah_reserve = htons(0); + ahdr->ah_spi = spi; + bzero(ahdr + 1, plen); + } else { + struct newah *ahdr = mtod(mah, struct newah *); + + plen = mah->m_len - sizeof(struct newah); + ahsumpos = (u_char *)(ahdr + 1); + ahdr->ah_nxt = *nexthdrp; + *nexthdrp = IPPROTO_AH; + ahdr->ah_len = (plen >> 2) + 1; /* plus one for seq# */ + ahdr->ah_reserve = htons(0); + ahdr->ah_spi = spi; + if (sav->replay->count == ~0) { + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + /* XXX Is it noisy ? */ + ipseclog((LOG_WARNING, + "replay counter overflowed. %s\n", + ipsec_logsastr(sav))); + ipsec6stat.out_inval++; + m_freem(m); + return EINVAL; + } + } + sav->replay->count++; + /* + * XXX sequence number must not be cycled, if the SA is + * installed by IKE daemon. + */ + ahdr->ah_seq = htonl(sav->replay->count); + bzero(ahdr + 1, plen); + } + + /* + * calcurate the checksum, based on security association + * and the algorithm specified. + */ + error = ah6_calccksum(m, (caddr_t)ahsumpos, plen, algo, sav); + if (error) { + ipsec6stat.out_inval++; + m_freem(m); + } else { + ipsec6stat.out_success++; + key_sa_recordxfer(sav, m); + } + ipsec6stat.out_ahhist[sav->alg_auth]++; + + return(error); +} +#endif + +#ifdef INET +/* + * Find the final destination if there is loose/strict source routing option. + * Returns NULL if there's no source routing options. + * Returns NULL on errors too. + * Note that this function will return a pointer INTO the given parameter, + * struct mbuf *m. + * The mbuf must be pulled up toward, at least, ip option part. + */ +static struct in_addr * +ah4_finaldst(m) + struct mbuf *m; +{ + struct ip *ip; + int optlen; + u_char *q; + int i; + int hlen; + + if (!m) + panic("ah4_finaldst: m == NULL"); + ip = mtod(m, struct ip *); + hlen = (ip->ip_hl << 2); + + if (m->m_len < hlen) { + ipseclog((LOG_DEBUG, + "ah4_finaldst: parameter mbuf wrong (not pulled up)\n")); + return NULL; + } + + if (hlen == sizeof(struct ip)) + return NULL; + + optlen = hlen - sizeof(struct ip); + if (optlen < 0) { + ipseclog((LOG_DEBUG, "ah4_finaldst: wrong optlen %d\n", + optlen)); + return NULL; + } + + q = (u_char *)(ip + 1); + i = 0; + while (i < optlen) { + if (i + IPOPT_OPTVAL >= optlen) + return NULL; + if (q[i + IPOPT_OPTVAL] == IPOPT_EOL || + q[i + IPOPT_OPTVAL] == IPOPT_NOP || + i + IPOPT_OLEN < optlen) + ; + else + return NULL; + + switch (q[i + IPOPT_OPTVAL]) { + case IPOPT_EOL: + i = optlen; /* bye */ + break; + case IPOPT_NOP: + i++; + break; + case IPOPT_LSRR: + case IPOPT_SSRR: + if (q[i + IPOPT_OLEN] < 2 + sizeof(struct in_addr) || + optlen - i < q[i + IPOPT_OLEN]) { + ipseclog((LOG_ERR, + "ip_finaldst: invalid IP option " + "(code=%02x len=%02x)\n", + q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN])); + return NULL; + } + i += q[i + IPOPT_OLEN] - sizeof(struct in_addr); + return (struct in_addr *)(q + i); + default: + if (q[i + IPOPT_OLEN] < 2 || + optlen - i < q[i + IPOPT_OLEN]) { + ipseclog((LOG_ERR, + "ip_finaldst: invalid IP option " + "(code=%02x len=%02x)\n", + q[i + IPOPT_OPTVAL], q[i + IPOPT_OLEN])); + return NULL; + } + i += q[i + IPOPT_OLEN]; + break; + } + } + return NULL; +} +#endif diff --git a/sys/netinet6/dest6.c b/sys/netinet6/dest6.c new file mode 100644 index 0000000..ced896f --- /dev/null +++ b/sys/netinet6/dest6.c @@ -0,0 +1,127 @@ +/* $FreeBSD$ */ +/* $KAME: dest6.c,v 1.34 2002/01/08 02:40:55 k-sugyou Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> + +/* + * Destination options header processing. + */ +int +dest6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + int off = *offp, dstoptlen, optlen; + struct ip6_dest *dstopts; + u_int8_t *opt; + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + + /* validation of the length of the header */ +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(*dstopts), IPPROTO_DONE); + dstopts = (struct ip6_dest *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(dstopts, struct ip6_dest *, m, off, sizeof(*dstopts)); + if (dstopts == NULL) + return IPPROTO_DONE; +#endif + dstoptlen = (dstopts->ip6d_len + 1) << 3; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, dstoptlen, IPPROTO_DONE); + dstopts = (struct ip6_dest *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(dstopts, struct ip6_dest *, m, off, dstoptlen); + if (dstopts == NULL) + return IPPROTO_DONE; +#endif + off += dstoptlen; + dstoptlen -= sizeof(struct ip6_dest); + opt = (u_int8_t *)dstopts + sizeof(struct ip6_dest); + + /* search header for all options. */ + for (optlen = 0; dstoptlen > 0; dstoptlen -= optlen, opt += optlen) { + if (*opt != IP6OPT_PAD1 && + (dstoptlen < IP6OPT_MINLEN || *(opt + 1) + 2 > dstoptlen)) { + ip6stat.ip6s_toosmall++; + goto bad; + } + + switch (*opt) { + case IP6OPT_PAD1: + optlen = 1; + break; + case IP6OPT_PADN: + optlen = *(opt + 1) + 2; + break; + + default: /* unknown option */ + optlen = ip6_unknown_opt(opt, m, + opt - mtod(m, u_int8_t *)); + if (optlen == -1) + return (IPPROTO_DONE); + optlen += 2; + break; + } + } + + *offp = off; + return (dstopts->ip6d_nxt); + + bad: + m_freem(m); + return (IPPROTO_DONE); +} diff --git a/sys/netinet6/esp.h b/sys/netinet6/esp.h new file mode 100644 index 0000000..28fe456 --- /dev/null +++ b/sys/netinet6/esp.h @@ -0,0 +1,109 @@ +/* $FreeBSD$ */ +/* $KAME: esp.h,v 1.19 2001/09/04 08:43:19 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#ifndef _NETINET6_ESP_H_ +#define _NETINET6_ESP_H_ + +#if defined(_KERNEL) && !defined(_LKM) +#include "opt_inet.h" +#endif + +struct esp { + u_int32_t esp_spi; /* ESP */ + /* variable size, 32bit bound */ /* Initialization Vector */ + /* variable size */ /* Payload data */ + /* variable size */ /* padding */ + /* 8bit */ /* pad size */ + /* 8bit */ /* next header */ + /* 8bit */ /* next header */ + /* variable size, 32bit bound */ /* Authentication data (new IPsec) */ +}; + +struct newesp { + u_int32_t esp_spi; /* ESP */ + u_int32_t esp_seq; /* Sequence number */ + /* variable size */ /* (IV and) Payload data */ + /* variable size */ /* padding */ + /* 8bit */ /* pad size */ + /* 8bit */ /* next header */ + /* 8bit */ /* next header */ + /* variable size, 32bit bound *//* Authentication data */ +}; + +struct esptail { + u_int8_t esp_padlen; /* pad length */ + u_int8_t esp_nxt; /* Next header */ + /* variable size, 32bit bound *//* Authentication data (new IPsec)*/ +}; + +#ifdef _KERNEL +struct secasvar; + +struct esp_algorithm { + size_t padbound; /* pad boundary, in byte */ + int ivlenval; /* iv length, in byte */ + int (*mature) __P((struct secasvar *)); + int keymin; /* in bits */ + int keymax; /* in bits */ + int (*schedlen) __P((const struct esp_algorithm *)); + const char *name; + int (*ivlen) __P((const struct esp_algorithm *, struct secasvar *)); + int (*decrypt) __P((struct mbuf *, size_t, + struct secasvar *, const struct esp_algorithm *, int)); + int (*encrypt) __P((struct mbuf *, size_t, size_t, + struct secasvar *, const struct esp_algorithm *, int)); + /* not supposed to be called directly */ + int (*schedule) __P((const struct esp_algorithm *, struct secasvar *)); + int (*blockdecrypt) __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); + int (*blockencrypt) __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +}; + +extern const struct esp_algorithm *esp_algorithm_lookup __P((int)); +extern int esp_max_ivlen __P((void)); + +/* crypt routines */ +extern int esp4_output __P((struct mbuf *, struct ipsecrequest *)); +extern void esp4_input __P((struct mbuf *, int)); +extern size_t esp_hdrsiz __P((struct ipsecrequest *)); + +extern int esp_schedule __P((const struct esp_algorithm *, struct secasvar *)); +extern int esp_auth __P((struct mbuf *, size_t, size_t, + struct secasvar *, u_char *)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_ESP_H_ */ diff --git a/sys/netinet6/esp6.h b/sys/netinet6/esp6.h new file mode 100644 index 0000000..d774d24 --- /dev/null +++ b/sys/netinet6/esp6.h @@ -0,0 +1,48 @@ +/* $FreeBSD$ */ +/* $KAME: esp.h,v 1.16 2000/10/18 21:28:00 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#ifndef _NETINET6_ESP6_H_ +#define _NETINET6_ESP6_H_ + +#ifdef _KERNEL +extern int esp6_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *)); +extern int esp6_input __P((struct mbuf **, int *, int)); + +extern void esp6_ctlinput __P((int, struct sockaddr *, void *)); +#endif /*_KERNEL*/ + +#endif /*_NETINET6_ESP6_H_*/ diff --git a/sys/netinet6/esp_core.c b/sys/netinet6/esp_core.c new file mode 100644 index 0000000..db09b94 --- /dev/null +++ b/sys/netinet6/esp_core.c @@ -0,0 +1,1136 @@ +/* $FreeBSD$ */ +/* $KAME: esp_core.c,v 1.50 2000/11/02 12:27:38 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#include <netinet6/esp_rijndael.h> +#include <net/pfkeyv2.h> +#include <netkey/keydb.h> +#include <netkey/key.h> +#include <crypto/des/des.h> +#include <crypto/blowfish/blowfish.h> +#include <crypto/cast128/cast128.h> + +#include <net/net_osdep.h> + +static int esp_null_mature __P((struct secasvar *)); +static int esp_null_decrypt __P((struct mbuf *, size_t, + struct secasvar *, const struct esp_algorithm *, int)); +static int esp_null_encrypt __P((struct mbuf *, size_t, size_t, + struct secasvar *, const struct esp_algorithm *, int)); +static int esp_descbc_mature __P((struct secasvar *)); +static int esp_descbc_ivlen __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_des_schedule __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_des_schedlen __P((const struct esp_algorithm *)); +static int esp_des_blockdecrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_des_blockencrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_cbc_mature __P((struct secasvar *)); +static int esp_blowfish_schedule __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_blowfish_schedlen __P((const struct esp_algorithm *)); +static int esp_blowfish_blockdecrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_blowfish_blockencrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_cast128_schedule __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_cast128_schedlen __P((const struct esp_algorithm *)); +static int esp_cast128_blockdecrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_cast128_blockencrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_3des_schedule __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_3des_schedlen __P((const struct esp_algorithm *)); +static int esp_3des_blockdecrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_3des_blockencrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +static int esp_common_ivlen __P((const struct esp_algorithm *, + struct secasvar *)); +static int esp_cbc_decrypt __P((struct mbuf *, size_t, + struct secasvar *, const struct esp_algorithm *, int)); +static int esp_cbc_encrypt __P((struct mbuf *, size_t, size_t, + struct secasvar *, const struct esp_algorithm *, int)); + +#define MAXIVLEN 16 + +static const struct esp_algorithm esp_algorithms[] = { + { 8, -1, esp_descbc_mature, 64, 64, esp_des_schedlen, + "des-cbc", + esp_descbc_ivlen, esp_cbc_decrypt, + esp_cbc_encrypt, esp_des_schedule, + esp_des_blockdecrypt, esp_des_blockencrypt, }, + { 8, 8, esp_cbc_mature, 192, 192, esp_3des_schedlen, + "3des-cbc", + esp_common_ivlen, esp_cbc_decrypt, + esp_cbc_encrypt, esp_3des_schedule, + esp_3des_blockdecrypt, esp_3des_blockencrypt, }, + { 1, 0, esp_null_mature, 0, 2048, 0, "null", + esp_common_ivlen, esp_null_decrypt, + esp_null_encrypt, NULL, }, + { 8, 8, esp_cbc_mature, 40, 448, esp_blowfish_schedlen, "blowfish-cbc", + esp_common_ivlen, esp_cbc_decrypt, + esp_cbc_encrypt, esp_blowfish_schedule, + esp_blowfish_blockdecrypt, esp_blowfish_blockencrypt, }, + { 8, 8, esp_cbc_mature, 40, 128, esp_cast128_schedlen, + "cast128-cbc", + esp_common_ivlen, esp_cbc_decrypt, + esp_cbc_encrypt, esp_cast128_schedule, + esp_cast128_blockdecrypt, esp_cast128_blockencrypt, }, + { 16, 16, esp_cbc_mature, 128, 256, esp_rijndael_schedlen, + "rijndael-cbc", + esp_common_ivlen, esp_cbc_decrypt, + esp_cbc_encrypt, esp_rijndael_schedule, + esp_rijndael_blockdecrypt, esp_rijndael_blockencrypt }, +}; + +const struct esp_algorithm * +esp_algorithm_lookup(idx) + int idx; +{ + + switch (idx) { + case SADB_EALG_DESCBC: + return &esp_algorithms[0]; + case SADB_EALG_3DESCBC: + return &esp_algorithms[1]; + case SADB_EALG_NULL: + return &esp_algorithms[2]; + case SADB_X_EALG_BLOWFISHCBC: + return &esp_algorithms[3]; + case SADB_X_EALG_CAST128CBC: + return &esp_algorithms[4]; + case SADB_X_EALG_RIJNDAELCBC: + return &esp_algorithms[5]; + default: + return NULL; + } +} + +int +esp_max_ivlen() +{ + int idx; + int ivlen; + + ivlen = 0; + for (idx = 0; idx < sizeof(esp_algorithms)/sizeof(esp_algorithms[0]); + idx++) { + if (esp_algorithms[idx].ivlenval > ivlen) + ivlen = esp_algorithms[idx].ivlenval; + } + + return ivlen; +} + +int +esp_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + int error; + + /* check for key length */ + if (_KEYBITS(sav->key_enc) < algo->keymin || + _KEYBITS(sav->key_enc) > algo->keymax) { + ipseclog((LOG_ERR, + "esp_schedule %s: unsupported key length %d: " + "needs %d to %d bits\n", algo->name, _KEYBITS(sav->key_enc), + algo->keymin, algo->keymax)); + return EINVAL; + } + + /* already allocated */ + if (sav->sched && sav->schedlen != 0) + return 0; + /* no schedule necessary */ + if (!algo->schedule || !algo->schedlen) + return 0; + + sav->schedlen = (*algo->schedlen)(algo); + if (sav->schedlen < 0) + return EINVAL; + sav->sched = malloc(sav->schedlen, M_SECA, M_NOWAIT); + if (!sav->sched) { + sav->schedlen = 0; + return ENOBUFS; + } + + error = (*algo->schedule)(algo, sav); + if (error) { + ipseclog((LOG_ERR, "esp_schedule %s: error %d\n", + algo->name, error)); + free(sav->sched, M_SECA); + sav->sched = NULL; + sav->schedlen = 0; + } + return error; +} + +static int +esp_null_mature(sav) + struct secasvar *sav; +{ + + /* anything is okay */ + return 0; +} + +static int +esp_null_decrypt(m, off, sav, algo, ivlen) + struct mbuf *m; + size_t off; /* offset to ESP header */ + struct secasvar *sav; + const struct esp_algorithm *algo; + int ivlen; +{ + + return 0; /* do nothing */ +} + +static int +esp_null_encrypt(m, off, plen, sav, algo, ivlen) + struct mbuf *m; + size_t off; /* offset to ESP header */ + size_t plen; /* payload length (to be encrypted) */ + struct secasvar *sav; + const struct esp_algorithm *algo; + int ivlen; +{ + + return 0; /* do nothing */ +} + +static int +esp_descbc_mature(sav) + struct secasvar *sav; +{ + const struct esp_algorithm *algo; + + if (!(sav->flags & SADB_X_EXT_OLD) && (sav->flags & SADB_X_EXT_IV4B)) { + ipseclog((LOG_ERR, "esp_cbc_mature: " + "algorithm incompatible with 4 octets IV length\n")); + return 1; + } + + if (!sav->key_enc) { + ipseclog((LOG_ERR, "esp_descbc_mature: no key is given.\n")); + return 1; + } + + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) { + ipseclog((LOG_ERR, + "esp_descbc_mature: unsupported algorithm.\n")); + return 1; + } + + if (_KEYBITS(sav->key_enc) < algo->keymin || + _KEYBITS(sav->key_enc) > algo->keymax) { + ipseclog((LOG_ERR, + "esp_descbc_mature: invalid key length %d.\n", + _KEYBITS(sav->key_enc))); + return 1; + } + + /* weak key check */ + if (des_is_weak_key((des_cblock *)_KEYBUF(sav->key_enc))) { + ipseclog((LOG_ERR, + "esp_descbc_mature: weak key was passed.\n")); + return 1; + } + + return 0; +} + +static int +esp_descbc_ivlen(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + + if (!sav) + return 8; + if ((sav->flags & SADB_X_EXT_OLD) && (sav->flags & SADB_X_EXT_IV4B)) + return 4; + if (!(sav->flags & SADB_X_EXT_OLD) && (sav->flags & SADB_X_EXT_DERIV)) + return 4; + return 8; +} + +static int +esp_des_schedlen(algo) + const struct esp_algorithm *algo; +{ + + return sizeof(des_key_schedule); +} + +static int +esp_des_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + + if (des_key_sched((des_cblock *)_KEYBUF(sav->key_enc), + *(des_key_schedule *)sav->sched)) + return EINVAL; + else + return 0; +} + +static int +esp_des_blockdecrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + + /* assumption: d has a good alignment */ + bcopy(s, d, sizeof(DES_LONG) * 2); + des_ecb_encrypt((des_cblock *)d, (des_cblock *)d, + *(des_key_schedule *)sav->sched, DES_DECRYPT); + return 0; +} + +static int +esp_des_blockencrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + + /* assumption: d has a good alignment */ + bcopy(s, d, sizeof(DES_LONG) * 2); + des_ecb_encrypt((des_cblock *)d, (des_cblock *)d, + *(des_key_schedule *)sav->sched, DES_ENCRYPT); + return 0; +} + +static int +esp_cbc_mature(sav) + struct secasvar *sav; +{ + int keylen; + const struct esp_algorithm *algo; + + if (sav->flags & SADB_X_EXT_OLD) { + ipseclog((LOG_ERR, + "esp_cbc_mature: algorithm incompatible with esp-old\n")); + return 1; + } + if (sav->flags & SADB_X_EXT_DERIV) { + ipseclog((LOG_ERR, + "esp_cbc_mature: algorithm incompatible with derived\n")); + return 1; + } + + if (!sav->key_enc) { + ipseclog((LOG_ERR, "esp_cbc_mature: no key is given.\n")); + return 1; + } + + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) { + ipseclog((LOG_ERR, + "esp_cbc_mature %s: unsupported algorithm.\n", algo->name)); + return 1; + } + + keylen = sav->key_enc->sadb_key_bits; + if (keylen < algo->keymin || algo->keymax < keylen) { + ipseclog((LOG_ERR, + "esp_cbc_mature %s: invalid key length %d.\n", + algo->name, sav->key_enc->sadb_key_bits)); + return 1; + } + switch (sav->alg_enc) { + case SADB_EALG_3DESCBC: + /* weak key check */ + if (des_is_weak_key((des_cblock *)_KEYBUF(sav->key_enc)) || + des_is_weak_key((des_cblock *)(_KEYBUF(sav->key_enc) + 8)) || + des_is_weak_key((des_cblock *)(_KEYBUF(sav->key_enc) + 16))) { + ipseclog((LOG_ERR, + "esp_cbc_mature %s: weak key was passed.\n", + algo->name)); + return 1; + } + break; + case SADB_X_EALG_BLOWFISHCBC: + case SADB_X_EALG_CAST128CBC: + break; + case SADB_X_EALG_RIJNDAELCBC: + /* allows specific key sizes only */ + if (!(keylen == 128 || keylen == 192 || keylen == 256)) { + ipseclog((LOG_ERR, + "esp_cbc_mature %s: invalid key length %d.\n", + algo->name, keylen)); + return 1; + } + break; + } + + return 0; +} + +static int +esp_blowfish_schedlen(algo) + const struct esp_algorithm *algo; +{ + + return sizeof(BF_KEY); +} + +static int +esp_blowfish_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + + BF_set_key((BF_KEY *)sav->sched, _KEYLEN(sav->key_enc), + _KEYBUF(sav->key_enc)); + return 0; +} + +static int +esp_blowfish_blockdecrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + /* HOLY COW! BF_decrypt() takes values in host byteorder */ + BF_LONG t[2]; + + bcopy(s, t, sizeof(t)); + t[0] = ntohl(t[0]); + t[1] = ntohl(t[1]); + BF_decrypt(t, (BF_KEY *)sav->sched); + t[0] = htonl(t[0]); + t[1] = htonl(t[1]); + bcopy(t, d, sizeof(t)); + return 0; +} + +static int +esp_blowfish_blockencrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + /* HOLY COW! BF_encrypt() takes values in host byteorder */ + BF_LONG t[2]; + + bcopy(s, t, sizeof(t)); + t[0] = ntohl(t[0]); + t[1] = ntohl(t[1]); + BF_encrypt(t, (BF_KEY *)sav->sched); + t[0] = htonl(t[0]); + t[1] = htonl(t[1]); + bcopy(t, d, sizeof(t)); + return 0; +} + +static int +esp_cast128_schedlen(algo) + const struct esp_algorithm *algo; +{ + + return sizeof(u_int32_t) * 32; +} + +static int +esp_cast128_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + + set_cast128_subkey((u_int32_t *)sav->sched, _KEYBUF(sav->key_enc), + _KEYLEN(sav->key_enc)); + return 0; +} + +static int +esp_cast128_blockdecrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + + if (_KEYLEN(sav->key_enc) <= 80 / 8) + cast128_decrypt_round12(d, s, (u_int32_t *)sav->sched); + else + cast128_decrypt_round16(d, s, (u_int32_t *)sav->sched); + return 0; +} + +static int +esp_cast128_blockencrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + + if (_KEYLEN(sav->key_enc) <= 80 / 8) + cast128_encrypt_round12(d, s, (u_int32_t *)sav->sched); + else + cast128_encrypt_round16(d, s, (u_int32_t *)sav->sched); + return 0; +} + +static int +esp_3des_schedlen(algo) + const struct esp_algorithm *algo; +{ + + return sizeof(des_key_schedule) * 3; +} + +static int +esp_3des_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + int error; + des_key_schedule *p; + int i; + char *k; + + p = (des_key_schedule *)sav->sched; + k = _KEYBUF(sav->key_enc); + for (i = 0; i < 3; i++) { + error = des_key_sched((des_cblock *)(k + 8 * i), p[i]); + if (error) + return EINVAL; + } + return 0; +} + +static int +esp_3des_blockdecrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + des_key_schedule *p; + + /* assumption: d has a good alignment */ + p = (des_key_schedule *)sav->sched; + bcopy(s, d, sizeof(DES_LONG) * 2); + des_ecb3_encrypt((des_cblock *)d, (des_cblock *)d, + p[0], p[1], p[2], DES_DECRYPT); + return 0; +} + +static int +esp_3des_blockencrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + des_key_schedule *p; + + /* assumption: d has a good alignment */ + p = (des_key_schedule *)sav->sched; + bcopy(s, d, sizeof(DES_LONG) * 2); + des_ecb3_encrypt((des_cblock *)d, (des_cblock *)d, + p[0], p[1], p[2], DES_ENCRYPT); + return 0; +} + +static int +esp_common_ivlen(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + + if (!algo) + panic("esp_common_ivlen: unknown algorithm"); + return algo->ivlenval; +} + +static int +esp_cbc_decrypt(m, off, sav, algo, ivlen) + struct mbuf *m; + size_t off; + struct secasvar *sav; + const struct esp_algorithm *algo; + int ivlen; +{ + struct mbuf *s; + struct mbuf *d, *d0, *dp; + int soff, doff; /* offset from the head of chain, to head of this mbuf */ + int sn, dn; /* offset from the head of the mbuf, to meat */ + size_t ivoff, bodyoff; + u_int8_t iv[MAXIVLEN], *ivp; + u_int8_t sbuf[MAXIVLEN], *sp; + u_int8_t *p, *q; + struct mbuf *scut; + int scutoff; + int i; + int blocklen; + int derived; + + if (ivlen != sav->ivlen || ivlen > sizeof(iv)) { + ipseclog((LOG_ERR, "esp_cbc_decrypt %s: " + "unsupported ivlen %d\n", algo->name, ivlen)); + m_freem(m); + return EINVAL; + } + + /* assumes blocklen == padbound */ + blocklen = algo->padbound; + +#ifdef DIAGNOSTIC + if (blocklen > sizeof(iv)) { + ipseclog((LOG_ERR, "esp_cbc_decrypt %s: " + "unsupported blocklen %d\n", algo->name, blocklen)); + m_freem(m); + return EINVAL; + } +#endif + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + ivoff = off + sizeof(struct esp); + bodyoff = off + sizeof(struct esp) + ivlen; + derived = 0; + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) { + /* + * draft-ietf-ipsec-ciph-des-derived-00.txt + * uses sequence number field as IV field. + */ + ivoff = off + sizeof(struct esp); + bodyoff = off + sizeof(struct esp) + sizeof(u_int32_t); + ivlen = sizeof(u_int32_t); + derived = 1; + } else { + ivoff = off + sizeof(struct newesp); + bodyoff = off + sizeof(struct newesp) + ivlen; + derived = 0; + } + } + + /* grab iv */ + m_copydata(m, ivoff, ivlen, iv); + + /* extend iv */ + if (ivlen == blocklen) + ; + else if (ivlen == 4 && blocklen == 8) { + bcopy(&iv[0], &iv[4], 4); + iv[4] ^= 0xff; + iv[5] ^= 0xff; + iv[6] ^= 0xff; + iv[7] ^= 0xff; + } else { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: " + "unsupported ivlen/blocklen: %d %d\n", + algo->name, ivlen, blocklen)); + m_freem(m); + return EINVAL; + } + + if (m->m_pkthdr.len < bodyoff) { + ipseclog((LOG_ERR, "esp_cbc_decrypt %s: bad len %d/%lu\n", + algo->name, m->m_pkthdr.len, (unsigned long)bodyoff)); + m_freem(m); + return EINVAL; + } + if ((m->m_pkthdr.len - bodyoff) % blocklen) { + ipseclog((LOG_ERR, "esp_cbc_decrypt %s: " + "payload length must be multiple of %d\n", + algo->name, blocklen)); + m_freem(m); + return EINVAL; + } + + s = m; + d = d0 = dp = NULL; + soff = doff = sn = dn = 0; + ivp = sp = NULL; + + /* skip bodyoff */ + while (soff < bodyoff) { + if (soff + s->m_len >= bodyoff) { + sn = bodyoff - soff; + break; + } + + soff += s->m_len; + s = s->m_next; + } + scut = s; + scutoff = sn; + + /* skip over empty mbuf */ + while (s && s->m_len == 0) + s = s->m_next; + + while (soff < m->m_pkthdr.len) { + /* source */ + if (sn + blocklen <= s->m_len) { + /* body is continuous */ + sp = mtod(s, u_int8_t *) + sn; + } else { + /* body is non-continuous */ + m_copydata(s, sn, blocklen, sbuf); + sp = sbuf; + } + + /* destination */ + if (!d || dn + blocklen > d->m_len) { + if (d) + dp = d; + MGET(d, M_DONTWAIT, MT_DATA); + i = m->m_pkthdr.len - (soff + sn); + if (d && i > MLEN) { + MCLGET(d, M_DONTWAIT); + if ((d->m_flags & M_EXT) == 0) { + m_free(d); + d = NULL; + } + } + if (!d) { + m_freem(m); + if (d0) + m_freem(d0); + return ENOBUFS; + } + if (!d0) + d0 = d; + if (dp) + dp->m_next = d; + d->m_len = 0; + d->m_len = (M_TRAILINGSPACE(d) / blocklen) * blocklen; + if (d->m_len > i) + d->m_len = i; + dn = 0; + } + + /* decrypt */ + (*algo->blockdecrypt)(algo, sav, sp, mtod(d, u_int8_t *) + dn); + + /* xor */ + p = ivp ? ivp : iv; + q = mtod(d, u_int8_t *) + dn; + for (i = 0; i < blocklen; i++) + q[i] ^= p[i]; + + /* next iv */ + if (sp == sbuf) { + bcopy(sbuf, iv, blocklen); + ivp = NULL; + } else + ivp = sp; + + sn += blocklen; + dn += blocklen; + + /* find the next source block */ + while (s && sn >= s->m_len) { + sn -= s->m_len; + soff += s->m_len; + s = s->m_next; + } + + /* skip over empty mbuf */ + while (s && s->m_len == 0) + s = s->m_next; + } + + m_freem(scut->m_next); + scut->m_len = scutoff; + scut->m_next = d0; + + /* just in case */ + bzero(iv, sizeof(iv)); + bzero(sbuf, sizeof(sbuf)); + + return 0; +} + +static int +esp_cbc_encrypt(m, off, plen, sav, algo, ivlen) + struct mbuf *m; + size_t off; + size_t plen; + struct secasvar *sav; + const struct esp_algorithm *algo; + int ivlen; +{ + struct mbuf *s; + struct mbuf *d, *d0, *dp; + int soff, doff; /* offset from the head of chain, to head of this mbuf */ + int sn, dn; /* offset from the head of the mbuf, to meat */ + size_t ivoff, bodyoff; + u_int8_t iv[MAXIVLEN], *ivp; + u_int8_t sbuf[MAXIVLEN], *sp; + u_int8_t *p, *q; + struct mbuf *scut; + int scutoff; + int i; + int blocklen; + int derived; + + if (ivlen != sav->ivlen || ivlen > sizeof(iv)) { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: " + "unsupported ivlen %d\n", algo->name, ivlen)); + m_freem(m); + return EINVAL; + } + + /* assumes blocklen == padbound */ + blocklen = algo->padbound; + +#ifdef DIAGNOSTIC + if (blocklen > sizeof(iv)) { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: " + "unsupported blocklen %d\n", algo->name, blocklen)); + m_freem(m); + return EINVAL; + } +#endif + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + ivoff = off + sizeof(struct esp); + bodyoff = off + sizeof(struct esp) + ivlen; + derived = 0; + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) { + /* + * draft-ietf-ipsec-ciph-des-derived-00.txt + * uses sequence number field as IV field. + */ + ivoff = off + sizeof(struct esp); + bodyoff = off + sizeof(struct esp) + sizeof(u_int32_t); + ivlen = sizeof(u_int32_t); + derived = 1; + } else { + ivoff = off + sizeof(struct newesp); + bodyoff = off + sizeof(struct newesp) + ivlen; + derived = 0; + } + } + + /* put iv into the packet. if we are in derived mode, use seqno. */ + if (derived) + m_copydata(m, ivoff, ivlen, iv); + else { + bcopy(sav->iv, iv, ivlen); + /* maybe it is better to overwrite dest, not source */ + m_copyback(m, ivoff, ivlen, iv); + } + + /* extend iv */ + if (ivlen == blocklen) + ; + else if (ivlen == 4 && blocklen == 8) { + bcopy(&iv[0], &iv[4], 4); + iv[4] ^= 0xff; + iv[5] ^= 0xff; + iv[6] ^= 0xff; + iv[7] ^= 0xff; + } else { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: " + "unsupported ivlen/blocklen: %d %d\n", + algo->name, ivlen, blocklen)); + m_freem(m); + return EINVAL; + } + + if (m->m_pkthdr.len < bodyoff) { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: bad len %d/%lu\n", + algo->name, m->m_pkthdr.len, (unsigned long)bodyoff)); + m_freem(m); + return EINVAL; + } + if ((m->m_pkthdr.len - bodyoff) % blocklen) { + ipseclog((LOG_ERR, "esp_cbc_encrypt %s: " + "payload length must be multiple of %lu\n", + algo->name, (unsigned long)algo->padbound)); + m_freem(m); + return EINVAL; + } + + s = m; + d = d0 = dp = NULL; + soff = doff = sn = dn = 0; + ivp = sp = NULL; + + /* skip bodyoff */ + while (soff < bodyoff) { + if (soff + s->m_len >= bodyoff) { + sn = bodyoff - soff; + break; + } + + soff += s->m_len; + s = s->m_next; + } + scut = s; + scutoff = sn; + + /* skip over empty mbuf */ + while (s && s->m_len == 0) + s = s->m_next; + + while (soff < m->m_pkthdr.len) { + /* source */ + if (sn + blocklen <= s->m_len) { + /* body is continuous */ + sp = mtod(s, u_int8_t *) + sn; + } else { + /* body is non-continuous */ + m_copydata(s, sn, blocklen, sbuf); + sp = sbuf; + } + + /* destination */ + if (!d || dn + blocklen > d->m_len) { + if (d) + dp = d; + MGET(d, M_DONTWAIT, MT_DATA); + i = m->m_pkthdr.len - (soff + sn); + if (d && i > MLEN) { + MCLGET(d, M_DONTWAIT); + if ((d->m_flags & M_EXT) == 0) { + m_free(d); + d = NULL; + } + } + if (!d) { + m_freem(m); + if (d0) + m_freem(d0); + return ENOBUFS; + } + if (!d0) + d0 = d; + if (dp) + dp->m_next = d; + d->m_len = 0; + d->m_len = (M_TRAILINGSPACE(d) / blocklen) * blocklen; + if (d->m_len > i) + d->m_len = i; + dn = 0; + } + + /* xor */ + p = ivp ? ivp : iv; + q = sp; + for (i = 0; i < blocklen; i++) + q[i] ^= p[i]; + + /* encrypt */ + (*algo->blockencrypt)(algo, sav, sp, mtod(d, u_int8_t *) + dn); + + /* next iv */ + ivp = mtod(d, u_int8_t *) + dn; + + sn += blocklen; + dn += blocklen; + + /* find the next source block */ + while (s && sn >= s->m_len) { + sn -= s->m_len; + soff += s->m_len; + s = s->m_next; + } + + /* skip over empty mbuf */ + while (s && s->m_len == 0) + s = s->m_next; + } + + m_freem(scut->m_next); + scut->m_len = scutoff; + scut->m_next = d0; + + /* just in case */ + bzero(iv, sizeof(iv)); + bzero(sbuf, sizeof(sbuf)); + + key_sa_stir_iv(sav); + + return 0; +} + +/*------------------------------------------------------------*/ + +/* does not free m0 on error */ +int +esp_auth(m0, skip, length, sav, sum) + struct mbuf *m0; + size_t skip; /* offset to ESP header */ + size_t length; /* payload length */ + struct secasvar *sav; + u_char *sum; +{ + struct mbuf *m; + size_t off; + struct ah_algorithm_state s; + u_char sumbuf[AH_MAXSUMSIZE]; + const struct ah_algorithm *algo; + size_t siz; + int error; + + /* sanity checks */ + if (m0->m_pkthdr.len < skip) { + ipseclog((LOG_DEBUG, "esp_auth: mbuf length < skip\n")); + return EINVAL; + } + if (m0->m_pkthdr.len < skip + length) { + ipseclog((LOG_DEBUG, + "esp_auth: mbuf length < skip + length\n")); + return EINVAL; + } + /* + * length of esp part (excluding authentication data) must be 4n, + * since nexthdr must be at offset 4n+3. + */ + if (length % 4) { + ipseclog((LOG_ERR, "esp_auth: length is not multiple of 4\n")); + return EINVAL; + } + if (!sav) { + ipseclog((LOG_DEBUG, "esp_auth: NULL SA passed\n")); + return EINVAL; + } + algo = ah_algorithm_lookup(sav->alg_auth); + if (!algo) { + ipseclog((LOG_ERR, + "esp_auth: bad ESP auth algorithm passed: %d\n", + sav->alg_auth)); + return EINVAL; + } + + m = m0; + off = 0; + + siz = (((*algo->sumsiz)(sav) + 3) & ~(4 - 1)); + if (sizeof(sumbuf) < siz) { + ipseclog((LOG_DEBUG, + "esp_auth: AH_MAXSUMSIZE is too small: siz=%lu\n", + (u_long)siz)); + return EINVAL; + } + + /* skip the header */ + while (skip) { + if (!m) + panic("mbuf chain?"); + if (m->m_len <= skip) { + skip -= m->m_len; + m = m->m_next; + off = 0; + } else { + off = skip; + skip = 0; + } + } + + error = (*algo->init)(&s, sav); + if (error) + return error; + + while (0 < length) { + if (!m) + panic("mbuf chain?"); + + if (m->m_len - off < length) { + (*algo->update)(&s, mtod(m, u_char *) + off, + m->m_len - off); + length -= m->m_len - off; + m = m->m_next; + off = 0; + } else { + (*algo->update)(&s, mtod(m, u_char *) + off, length); + break; + } + } + (*algo->result)(&s, sumbuf); + bcopy(sumbuf, sum, siz); /* XXX */ + + return 0; +} diff --git a/sys/netinet6/esp_input.c b/sys/netinet6/esp_input.c new file mode 100644 index 0000000..43f0a61 --- /dev/null +++ b/sys/netinet6/esp_input.c @@ -0,0 +1,981 @@ +/* $FreeBSD$ */ +/* $KAME: esp_input.c,v 1.62 2002/01/07 11:39:57 kjc Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <machine/cpu.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> +#include <netinet/ip_ecn.h> +#ifdef INET6 +#include <netinet6/ip6_ecn.h> +#endif + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet/in_pcb.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#include <netinet6/ip6protosw.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> +#include <netkey/key_debug.h> + +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +#define IPLEN_FLIPPED + +#define ESPMAXLEN \ + (sizeof(struct esp) < sizeof(struct newesp) \ + ? sizeof(struct newesp) : sizeof(struct esp)) + +#ifdef INET +extern struct protosw inetsw[]; + +void +esp4_input(m, off) + struct mbuf *m; + int off; +{ + struct ip *ip; + struct esp *esp; + struct esptail esptail; + u_int32_t spi; + struct secasvar *sav = NULL; + size_t taillen; + u_int16_t nxt; + const struct esp_algorithm *algo; + int ivlen; + size_t hlen; + size_t esplen; + int proto; + + /* sanity check for alignment. */ + if (off % 4 != 0 || m->m_pkthdr.len % 4 != 0) { + ipseclog((LOG_ERR, "IPv4 ESP input: packet alignment problem " + "(off=%d, pktlen=%d)\n", off, m->m_pkthdr.len)); + ipsecstat.in_inval++; + goto bad; + } + + if (m->m_len < off + ESPMAXLEN) { + m = m_pullup(m, off + ESPMAXLEN); + if (!m) { + ipseclog((LOG_DEBUG, + "IPv4 ESP input: can't pullup in esp4_input\n")); + ipsecstat.in_inval++; + goto bad; + } + } + + ip = mtod(m, struct ip *); + proto = ip->ip_p; + esp = (struct esp *)(((u_int8_t *)ip) + off); +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + + /* find the sassoc. */ + spi = esp->esp_spi; + + if ((sav = key_allocsa(AF_INET, + (caddr_t)&ip->ip_src, (caddr_t)&ip->ip_dst, + IPPROTO_ESP, spi)) == 0) { + ipseclog((LOG_WARNING, + "IPv4 ESP input: no key association found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_nosa++; + goto bad; + } + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp4_input called to allocate SA:%p\n", sav)); + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) { + ipseclog((LOG_DEBUG, + "IPv4 ESP input: non-mature/dying SA found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_badspi++; + goto bad; + } + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) { + ipseclog((LOG_DEBUG, "IPv4 ESP input: " + "unsupported encryption algorithm for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsecstat.in_badspi++; + goto bad; + } + + /* check if we have proper ivlen information */ + ivlen = sav->ivlen; + if (ivlen < 0) { + ipseclog((LOG_ERR, "inproper ivlen in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_inval++; + goto bad; + } + + if (!((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay + && (sav->alg_auth && sav->key_auth))) + goto noreplaycheck; + + if (sav->alg_auth == SADB_X_AALG_NULL || + sav->alg_auth == SADB_AALG_NONE) + goto noreplaycheck; + + /* + * check for sequence number. + */ + if (ipsec_chkreplay(ntohl(((struct newesp *)esp)->esp_seq), sav)) + ; /* okey */ + else { + ipsecstat.in_espreplay++; + ipseclog((LOG_WARNING, + "replay packet in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + goto bad; + } + + /* check ICV */ + { + u_char sum0[AH_MAXSUMSIZE]; + u_char sum[AH_MAXSUMSIZE]; + const struct ah_algorithm *sumalgo; + size_t siz; + + sumalgo = ah_algorithm_lookup(sav->alg_auth); + if (!sumalgo) + goto noreplaycheck; + siz = (((*sumalgo->sumsiz)(sav) + 3) & ~(4 - 1)); + if (m->m_pkthdr.len < off + ESPMAXLEN + siz) { + ipsecstat.in_inval++; + goto bad; + } + if (AH_MAXSUMSIZE < siz) { + ipseclog((LOG_DEBUG, + "internal error: AH_MAXSUMSIZE must be larger than %lu\n", + (u_long)siz)); + ipsecstat.in_inval++; + goto bad; + } + + m_copydata(m, m->m_pkthdr.len - siz, siz, &sum0[0]); + + if (esp_auth(m, off, m->m_pkthdr.len - off - siz, sav, sum)) { + ipseclog((LOG_WARNING, "auth fail in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_espauthfail++; + goto bad; + } + + if (bcmp(sum0, sum, siz) != 0) { + ipseclog((LOG_WARNING, "auth fail in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_espauthfail++; + goto bad; + } + + /* strip off the authentication data */ + m_adj(m, -siz); + ip = mtod(m, struct ip *); +#ifdef IPLEN_FLIPPED + ip->ip_len = ip->ip_len - siz; +#else + ip->ip_len = htons(ntohs(ip->ip_len) - siz); +#endif + m->m_flags |= M_AUTHIPDGM; + ipsecstat.in_espauthsucc++; + } + + /* + * update sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_updatereplay(ntohl(((struct newesp *)esp)->esp_seq), sav)) { + ipsecstat.in_espreplay++; + goto bad; + } + } + +noreplaycheck: + + /* process main esp header. */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + esplen = sizeof(struct esp); + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) + esplen = sizeof(struct esp); + else + esplen = sizeof(struct newesp); + } + + if (m->m_pkthdr.len < off + esplen + ivlen + sizeof(esptail)) { + ipseclog((LOG_WARNING, + "IPv4 ESP input: packet too short\n")); + ipsecstat.in_inval++; + goto bad; + } + + if (m->m_len < off + esplen + ivlen) { + m = m_pullup(m, off + esplen + ivlen); + if (!m) { + ipseclog((LOG_DEBUG, + "IPv4 ESP input: can't pullup in esp4_input\n")); + ipsecstat.in_inval++; + goto bad; + } + } + + /* + * pre-compute and cache intermediate key + */ + if (esp_schedule(algo, sav) != 0) { + ipsecstat.in_inval++; + goto bad; + } + + /* + * decrypt the packet. + */ + if (!algo->decrypt) + panic("internal error: no decrypt function"); + if ((*algo->decrypt)(m, off, sav, algo, ivlen)) { + /* m is already freed */ + m = NULL; + ipseclog((LOG_ERR, "decrypt fail in IPv4 ESP input: %s\n", + ipsec_logsastr(sav))); + ipsecstat.in_inval++; + goto bad; + } + ipsecstat.in_esphist[sav->alg_enc]++; + + m->m_flags |= M_DECRYPTED; + + /* + * find the trailer of the ESP. + */ + m_copydata(m, m->m_pkthdr.len - sizeof(esptail), sizeof(esptail), + (caddr_t)&esptail); + nxt = esptail.esp_nxt; + taillen = esptail.esp_padlen + sizeof(esptail); + + if (m->m_pkthdr.len < taillen + || m->m_pkthdr.len - taillen < hlen) { /* ? */ + ipseclog((LOG_WARNING, + "bad pad length in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_inval++; + goto bad; + } + + /* strip off the trailing pad area. */ + m_adj(m, -taillen); + +#ifdef IPLEN_FLIPPED + ip->ip_len = ip->ip_len - taillen; +#else + ip->ip_len = htons(ntohs(ip->ip_len) - taillen); +#endif + + /* was it transmitted over the IPsec tunnel SA? */ + if (ipsec4_tunnel_validate(m, off + esplen + ivlen, nxt, sav)) { + /* + * strip off all the headers that precedes ESP header. + * IP4 xx ESP IP4' payload -> IP4' payload + * + * XXX more sanity checks + * XXX relationship with gif? + */ + u_int8_t tos; + + tos = ip->ip_tos; + m_adj(m, off + esplen + ivlen); + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (!m) { + ipsecstat.in_inval++; + goto bad; + } + } + ip = mtod(m, struct ip *); + /* ECN consideration. */ + ip_ecn_egress(ip4_ipsec_ecn, &tos, &ip->ip_tos); + if (!key_checktunnelsanity(sav, AF_INET, + (caddr_t)&ip->ip_src, (caddr_t)&ip->ip_dst)) { + ipseclog((LOG_ERR, "ipsec tunnel address mismatch " + "in IPv4 ESP input: %s %s\n", + ipsec4_logpacketstr(ip, spi), ipsec_logsastr(sav))); + ipsecstat.in_inval++; + goto bad; + } + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_ESP, spi) != 0 || + ipsec_addhist(m, IPPROTO_IPV4, 0) != 0) { + ipsecstat.in_nomem++; + goto bad; + } + + if (! netisr_queue(NETISR_IP, m)) { + ipsecstat.in_inval++; + m = NULL; + goto bad; + } + m = NULL; + nxt = IPPROTO_DONE; + } else { + /* + * strip off ESP header and IV. + * even in m_pulldown case, we need to strip off ESP so that + * we can always compute checksum for AH correctly. + */ + size_t stripsiz; + + stripsiz = esplen + ivlen; + + ip = mtod(m, struct ip *); + ovbcopy((caddr_t)ip, (caddr_t)(((u_char *)ip) + stripsiz), off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; + + ip = mtod(m, struct ip *); +#ifdef IPLEN_FLIPPED + ip->ip_len = ip->ip_len - stripsiz; +#else + ip->ip_len = htons(ntohs(ip->ip_len) - stripsiz); +#endif + ip->ip_p = nxt; + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_ESP, spi) != 0) { + ipsecstat.in_nomem++; + goto bad; + } + + if (nxt != IPPROTO_DONE) { + if ((inetsw[ip_protox[nxt]].pr_flags & PR_LASTHDR) != 0 && + ipsec4_in_reject(m, NULL)) { + ipsecstat.in_polvio++; + goto bad; + } + (*inetsw[ip_protox[nxt]].pr_input)(m, off); + } else + m_freem(m); + m = NULL; + } + + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp4_input call free SA:%p\n", sav)); + key_freesav(sav); + } + ipsecstat.in_success++; + return; + +bad: + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp4_input call free SA:%p\n", sav)); + key_freesav(sav); + } + if (m) + m_freem(m); + return; +} +#endif /* INET */ + +#ifdef INET6 +int +esp6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + int off = *offp; + struct ip6_hdr *ip6; + struct esp *esp; + struct esptail esptail; + u_int32_t spi; + struct secasvar *sav = NULL; + size_t taillen; + u_int16_t nxt; + const struct esp_algorithm *algo; + int ivlen; + size_t esplen; + + /* sanity check for alignment. */ + if (off % 4 != 0 || m->m_pkthdr.len % 4 != 0) { + ipseclog((LOG_ERR, "IPv6 ESP input: packet alignment problem " + "(off=%d, pktlen=%d)\n", off, m->m_pkthdr.len)); + ipsec6stat.in_inval++; + goto bad; + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, ESPMAXLEN, IPPROTO_DONE); + esp = (struct esp *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(esp, struct esp *, m, off, ESPMAXLEN); + if (esp == NULL) { + ipsec6stat.in_inval++; + return IPPROTO_DONE; + } +#endif + ip6 = mtod(m, struct ip6_hdr *); + + if (ntohs(ip6->ip6_plen) == 0) { + ipseclog((LOG_ERR, "IPv6 ESP input: " + "ESP with IPv6 jumbogram is not supported.\n")); + ipsec6stat.in_inval++; + goto bad; + } + + /* find the sassoc. */ + spi = esp->esp_spi; + + if ((sav = key_allocsa(AF_INET6, + (caddr_t)&ip6->ip6_src, (caddr_t)&ip6->ip6_dst, + IPPROTO_ESP, spi)) == 0) { + ipseclog((LOG_WARNING, + "IPv6 ESP input: no key association found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_nosa++; + goto bad; + } + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp6_input called to allocate SA:%p\n", sav)); + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) { + ipseclog((LOG_DEBUG, + "IPv6 ESP input: non-mature/dying SA found for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_badspi++; + goto bad; + } + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) { + ipseclog((LOG_DEBUG, "IPv6 ESP input: " + "unsupported encryption algorithm for spi %u\n", + (u_int32_t)ntohl(spi))); + ipsec6stat.in_badspi++; + goto bad; + } + + /* check if we have proper ivlen information */ + ivlen = sav->ivlen; + if (ivlen < 0) { + ipseclog((LOG_ERR, "inproper ivlen in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + ipsec6stat.in_badspi++; + goto bad; + } + + if (!((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay + && (sav->alg_auth && sav->key_auth))) + goto noreplaycheck; + + if (sav->alg_auth == SADB_X_AALG_NULL || + sav->alg_auth == SADB_AALG_NONE) + goto noreplaycheck; + + /* + * check for sequence number. + */ + if (ipsec_chkreplay(ntohl(((struct newesp *)esp)->esp_seq), sav)) + ; /* okey */ + else { + ipsec6stat.in_espreplay++; + ipseclog((LOG_WARNING, + "replay packet in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + goto bad; + } + + /* check ICV */ + { + u_char sum0[AH_MAXSUMSIZE]; + u_char sum[AH_MAXSUMSIZE]; + const struct ah_algorithm *sumalgo; + size_t siz; + + sumalgo = ah_algorithm_lookup(sav->alg_auth); + if (!sumalgo) + goto noreplaycheck; + siz = (((*sumalgo->sumsiz)(sav) + 3) & ~(4 - 1)); + if (m->m_pkthdr.len < off + ESPMAXLEN + siz) { + ipsecstat.in_inval++; + goto bad; + } + if (AH_MAXSUMSIZE < siz) { + ipseclog((LOG_DEBUG, + "internal error: AH_MAXSUMSIZE must be larger than %lu\n", + (u_long)siz)); + ipsec6stat.in_inval++; + goto bad; + } + + m_copydata(m, m->m_pkthdr.len - siz, siz, &sum0[0]); + + if (esp_auth(m, off, m->m_pkthdr.len - off - siz, sav, sum)) { + ipseclog((LOG_WARNING, "auth fail in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + ipsec6stat.in_espauthfail++; + goto bad; + } + + if (bcmp(sum0, sum, siz) != 0) { + ipseclog((LOG_WARNING, "auth fail in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + ipsec6stat.in_espauthfail++; + goto bad; + } + + /* strip off the authentication data */ + m_adj(m, -siz); + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) - siz); + + m->m_flags |= M_AUTHIPDGM; + ipsec6stat.in_espauthsucc++; + } + + /* + * update sequence number. + */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && sav->replay) { + if (ipsec_updatereplay(ntohl(((struct newesp *)esp)->esp_seq), sav)) { + ipsec6stat.in_espreplay++; + goto bad; + } + } + +noreplaycheck: + + /* process main esp header. */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + esplen = sizeof(struct esp); + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) + esplen = sizeof(struct esp); + else + esplen = sizeof(struct newesp); + } + + if (m->m_pkthdr.len < off + esplen + ivlen + sizeof(esptail)) { + ipseclog((LOG_WARNING, + "IPv6 ESP input: packet too short\n")); + ipsec6stat.in_inval++; + goto bad; + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, esplen + ivlen, IPPROTO_DONE); /* XXX */ +#else + IP6_EXTHDR_GET(esp, struct esp *, m, off, esplen + ivlen); + if (esp == NULL) { + ipsec6stat.in_inval++; + m = NULL; + goto bad; + } +#endif + ip6 = mtod(m, struct ip6_hdr *); /* set it again just in case */ + + /* + * pre-compute and cache intermediate key + */ + if (esp_schedule(algo, sav) != 0) { + ipsec6stat.in_inval++; + goto bad; + } + + /* + * decrypt the packet. + */ + if (!algo->decrypt) + panic("internal error: no decrypt function"); + if ((*algo->decrypt)(m, off, sav, algo, ivlen)) { + /* m is already freed */ + m = NULL; + ipseclog((LOG_ERR, "decrypt fail in IPv6 ESP input: %s\n", + ipsec_logsastr(sav))); + ipsec6stat.in_inval++; + goto bad; + } + ipsec6stat.in_esphist[sav->alg_enc]++; + + m->m_flags |= M_DECRYPTED; + + /* + * find the trailer of the ESP. + */ + m_copydata(m, m->m_pkthdr.len - sizeof(esptail), sizeof(esptail), + (caddr_t)&esptail); + nxt = esptail.esp_nxt; + taillen = esptail.esp_padlen + sizeof(esptail); + + if (m->m_pkthdr.len < taillen + || m->m_pkthdr.len - taillen < sizeof(struct ip6_hdr)) { /* ? */ + ipseclog((LOG_WARNING, + "bad pad length in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), ipsec_logsastr(sav))); + ipsec6stat.in_inval++; + goto bad; + } + + /* strip off the trailing pad area. */ + m_adj(m, -taillen); + + ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) - taillen); + + /* was it transmitted over the IPsec tunnel SA? */ + if (ipsec6_tunnel_validate(m, off + esplen + ivlen, nxt, sav)) { + /* + * strip off all the headers that precedes ESP header. + * IP6 xx ESP IP6' payload -> IP6' payload + * + * XXX more sanity checks + * XXX relationship with gif? + */ + u_int32_t flowinfo; /* net endian */ + flowinfo = ip6->ip6_flow; + m_adj(m, off + esplen + ivlen); + if (m->m_len < sizeof(*ip6)) { +#ifndef PULLDOWN_TEST + /* + * m_pullup is prohibited in KAME IPv6 input processing + * but there's no other way! + */ +#else + /* okay to pullup in m_pulldown style */ +#endif + m = m_pullup(m, sizeof(*ip6)); + if (!m) { + ipsec6stat.in_inval++; + goto bad; + } + } + ip6 = mtod(m, struct ip6_hdr *); + /* ECN consideration. */ + ip6_ecn_egress(ip6_ipsec_ecn, &flowinfo, &ip6->ip6_flow); + if (!key_checktunnelsanity(sav, AF_INET6, + (caddr_t)&ip6->ip6_src, (caddr_t)&ip6->ip6_dst)) { + ipseclog((LOG_ERR, "ipsec tunnel address mismatch " + "in IPv6 ESP input: %s %s\n", + ipsec6_logpacketstr(ip6, spi), + ipsec_logsastr(sav))); + ipsec6stat.in_inval++; + goto bad; + } + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_ESP, spi) != 0 || + ipsec_addhist(m, IPPROTO_IPV6, 0) != 0) { + ipsec6stat.in_nomem++; + goto bad; + } + + if (! netisr_queue(NETISR_IPV6, m)) { + ipsec6stat.in_inval++; + m = NULL; + goto bad; + } + m = NULL; + nxt = IPPROTO_DONE; + } else { + /* + * strip off ESP header and IV. + * even in m_pulldown case, we need to strip off ESP so that + * we can always compute checksum for AH correctly. + */ + size_t stripsiz; + char *prvnxtp; + + /* + * Set the next header field of the previous header correctly. + */ + prvnxtp = ip6_get_prevhdr(m, off); /* XXX */ + *prvnxtp = nxt; + + stripsiz = esplen + ivlen; + + ip6 = mtod(m, struct ip6_hdr *); + if (m->m_len >= stripsiz + off) { + ovbcopy((caddr_t)ip6, ((caddr_t)ip6) + stripsiz, off); + m->m_data += stripsiz; + m->m_len -= stripsiz; + m->m_pkthdr.len -= stripsiz; + } else { + /* + * this comes with no copy if the boundary is on + * cluster + */ + struct mbuf *n; + + n = m_split(m, off, M_DONTWAIT); + if (n == NULL) { + /* m is retained by m_split */ + goto bad; + } + m_adj(n, stripsiz); + m_cat(m, n); + /* m_cat does not update m_pkthdr.len */ + m->m_pkthdr.len += n->m_pkthdr.len; + } + +#ifndef PULLDOWN_TEST + /* + * KAME requires that the packet to be contiguous on the + * mbuf. We need to make that sure. + * this kind of code should be avoided. + * XXX other conditions to avoid running this part? + */ + if (m->m_len != m->m_pkthdr.len) { + struct mbuf *n = NULL; + int maxlen; + + MGETHDR(n, M_DONTWAIT, MT_HEADER); + maxlen = MHLEN; + if (n) + M_MOVE_PKTHDR(n, m); + if (n && n->m_pkthdr.len > maxlen) { + MCLGET(n, M_DONTWAIT); + maxlen = MCLBYTES; + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (!n) { + printf("esp6_input: mbuf allocation failed\n"); + goto bad; + } + + if (n->m_pkthdr.len <= maxlen) { + m_copydata(m, 0, n->m_pkthdr.len, mtod(n, caddr_t)); + n->m_len = n->m_pkthdr.len; + n->m_next = NULL; + m_freem(m); + } else { + m_copydata(m, 0, maxlen, mtod(n, caddr_t)); + n->m_len = maxlen; + n->m_next = m; + m_adj(m, maxlen); + } + m = n; + } +#endif + + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons(ntohs(ip6->ip6_plen) - stripsiz); + + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_ESP, spi) != 0) { + ipsec6stat.in_nomem++; + goto bad; + } + } + + *offp = off; + *mp = m; + + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp6_input call free SA:%p\n", sav)); + key_freesav(sav); + } + ipsec6stat.in_success++; + return nxt; + +bad: + if (sav) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP esp6_input call free SA:%p\n", sav)); + key_freesav(sav); + } + if (m) + m_freem(m); + return IPPROTO_DONE; +} + +void +esp6_ctlinput(cmd, sa, d) + int cmd; + struct sockaddr *sa; + void *d; +{ + const struct newesp *espp; + struct newesp esp; + struct ip6ctlparam *ip6cp = NULL, ip6cp1; + struct secasvar *sav; + struct ip6_hdr *ip6; + struct mbuf *m; + int off; + struct sockaddr_in6 *sa6_src, *sa6_dst; + + if (sa->sa_family != AF_INET6 || + sa->sa_len != sizeof(struct sockaddr_in6)) + return; + if ((unsigned)cmd >= PRC_NCMDS) + return; + + /* if the parameter is from icmp6, decode it. */ + if (d != NULL) { + ip6cp = (struct ip6ctlparam *)d; + m = ip6cp->ip6c_m; + ip6 = ip6cp->ip6c_ip6; + off = ip6cp->ip6c_off; + } else { + m = NULL; + ip6 = NULL; + off = 0; /* calm gcc */ + } + + if (ip6) { + /* + * Notify the error to all possible sockets via pfctlinput2. + * Since the upper layer information (such as protocol type, + * source and destination ports) is embedded in the encrypted + * data and might have been cut, we can't directly call + * an upper layer ctlinput function. However, the pcbnotify + * function will consider source and destination addresses + * as well as the flow info value, and may be able to find + * some PCB that should be notified. + * Although pfctlinput2 will call esp6_ctlinput(), there is + * no possibility of an infinite loop of function calls, + * because we don't pass the inner IPv6 header. + */ + bzero(&ip6cp1, sizeof(ip6cp1)); + ip6cp1.ip6c_src = ip6cp->ip6c_src; + pfctlinput2(cmd, sa, (void *)&ip6cp1); + + /* + * Then go to special cases that need ESP header information. + * XXX: We assume that when ip6 is non NULL, + * M and OFF are valid. + */ + + /* check if we can safely examine src and dst ports */ + if (m->m_pkthdr.len < off + sizeof(esp)) + return; + + if (m->m_len < off + sizeof(esp)) { + /* + * this should be rare case, + * so we compromise on this copy... + */ + m_copydata(m, off, sizeof(esp), (caddr_t)&esp); + espp = &esp; + } else + espp = (struct newesp*)(mtod(m, caddr_t) + off); + + if (cmd == PRC_MSGSIZE) { + int valid = 0; + + /* + * Check to see if we have a valid SA corresponding to + * the address in the ICMP message payload. + */ + sa6_src = ip6cp->ip6c_src; + sa6_dst = (struct sockaddr_in6 *)sa; + sav = key_allocsa(AF_INET6, + (caddr_t)&sa6_src->sin6_addr, + (caddr_t)&sa6_dst->sin6_addr, + IPPROTO_ESP, espp->esp_spi); + if (sav) { + if (sav->state == SADB_SASTATE_MATURE || + sav->state == SADB_SASTATE_DYING) + valid++; + key_freesav(sav); + } + + /* XXX Further validation? */ + + /* + * Depending on the value of "valid" and routing table + * size (mtudisc_{hi,lo}wat), we will: + * - recalcurate the new MTU and create the + * corresponding routing entry, or + * - ignore the MTU change notification. + */ + icmp6_mtudisc_update((struct ip6ctlparam *)d, valid); + } + } else { + /* we normally notify any pcb here */ + } +} +#endif /* INET6 */ diff --git a/sys/netinet6/esp_output.c b/sys/netinet6/esp_output.c new file mode 100644 index 0000000..0365e20 --- /dev/null +++ b/sys/netinet6/esp_output.c @@ -0,0 +1,720 @@ +/* $FreeBSD$ */ +/* $KAME: esp_output.c,v 1.44 2001/07/26 06:53:15 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +/* + * RFC1827/2406 Encapsulated Security Payload. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_var.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> + +#include <net/net_osdep.h> + +static int esp_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *, int)); + +/* + * compute ESP header size. + */ +size_t +esp_hdrsiz(isr) + struct ipsecrequest *isr; +{ + struct secasvar *sav; + const struct esp_algorithm *algo; + const struct ah_algorithm *aalgo; + size_t ivlen; + size_t authlen; + size_t hdrsiz; + + /* sanity check */ + if (isr == NULL) + panic("esp_hdrsiz: NULL was passed."); + + sav = isr->sav; + + if (isr->saidx.proto != IPPROTO_ESP) + panic("unsupported mode passed to esp_hdrsiz"); + + if (sav == NULL) + goto estimate; + if (sav->state != SADB_SASTATE_MATURE + && sav->state != SADB_SASTATE_DYING) + goto estimate; + + /* we need transport mode ESP. */ + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) + goto estimate; + ivlen = sav->ivlen; + if (ivlen < 0) + goto estimate; + + /* + * XXX + * right now we don't calcurate the padding size. simply + * treat the padding size as constant, for simplicity. + * + * XXX variable size padding support + */ + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + hdrsiz = sizeof(struct esp) + ivlen + 9; + } else { + /* RFC 2406 */ + aalgo = ah_algorithm_lookup(sav->alg_auth); + if (aalgo && sav->replay && sav->key_auth) + authlen = (aalgo->sumsiz)(sav); + else + authlen = 0; + hdrsiz = sizeof(struct newesp) + ivlen + 9 + authlen; + } + + return hdrsiz; + + estimate: + /* + * ASSUMING: + * sizeof(struct newesp) > sizeof(struct esp). + * esp_max_ivlen() = max ivlen for CBC mode + * 9 = (maximum padding length without random padding length) + * + (Pad Length field) + (Next Header field). + * 16 = maximum ICV we support. + */ + return sizeof(struct newesp) + esp_max_ivlen() + 9 + 16; +} + +/* + * Modify the packet so that the payload is encrypted. + * The mbuf (m) must start with IPv4 or IPv6 header. + * On failure, free the given mbuf and return NULL. + * + * on invocation: + * m nexthdrp md + * v v v + * IP ......... payload + * during the encryption: + * m nexthdrp mprev md + * v v v v + * IP ............... esp iv payload pad padlen nxthdr + * <--><-><------><---------------> + * esplen plen extendsiz + * ivlen + * <-----> esphlen + * <-> hlen + * <-----------------> espoff + */ +static int +esp_output(m, nexthdrp, md, isr, af) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; + int af; +{ + struct mbuf *n; + struct mbuf *mprev; + struct esp *esp; + struct esptail *esptail; + struct secasvar *sav = isr->sav; + const struct esp_algorithm *algo; + u_int32_t spi; + u_int8_t nxt = 0; + size_t plen; /* payload length to be encrypted */ + size_t espoff; + int ivlen; + int afnumber; + size_t extendsiz; + int error = 0; + struct ipsecstat *stat; + + switch (af) { +#ifdef INET + case AF_INET: + afnumber = 4; + stat = &ipsecstat; + break; +#endif +#ifdef INET6 + case AF_INET6: + afnumber = 6; + stat = &ipsec6stat; + break; +#endif + default: + ipseclog((LOG_ERR, "esp_output: unsupported af %d\n", af)); + return 0; /* no change at all */ + } + + /* some sanity check */ + if ((sav->flags & SADB_X_EXT_OLD) == 0 && !sav->replay) { + switch (af) { +#ifdef INET + case AF_INET: + { + struct ip *ip; + + ip = mtod(m, struct ip *); + ipseclog((LOG_DEBUG, "esp4_output: internal error: " + "sav->replay is null: %x->%x, SPI=%u\n", + (u_int32_t)ntohl(ip->ip_src.s_addr), + (u_int32_t)ntohl(ip->ip_dst.s_addr), + (u_int32_t)ntohl(sav->spi))); + ipsecstat.out_inval++; + break; + } +#endif /* INET */ +#ifdef INET6 + case AF_INET6: + ipseclog((LOG_DEBUG, "esp6_output: internal error: " + "sav->replay is null: SPI=%u\n", + (u_int32_t)ntohl(sav->spi))); + ipsec6stat.out_inval++; + break; +#endif /* INET6 */ + default: + panic("esp_output: should not reach here"); + } + m_freem(m); + return EINVAL; + } + + algo = esp_algorithm_lookup(sav->alg_enc); + if (!algo) { + ipseclog((LOG_ERR, "esp_output: unsupported algorithm: " + "SPI=%u\n", (u_int32_t)ntohl(sav->spi))); + m_freem(m); + return EINVAL; + } + spi = sav->spi; + ivlen = sav->ivlen; + /* should be okey */ + if (ivlen < 0) { + panic("invalid ivlen"); + } + + { + /* + * insert ESP header. + * XXX inserts ESP header right after IPv4 header. should + * chase the header chain. + * XXX sequential number + */ +#ifdef INET + struct ip *ip = NULL; +#endif +#ifdef INET6 + struct ip6_hdr *ip6 = NULL; +#endif + size_t esplen; /* sizeof(struct esp/newesp) */ + size_t esphlen; /* sizeof(struct esp/newesp) + ivlen */ + size_t hlen = 0; /* ip header len */ + + if (sav->flags & SADB_X_EXT_OLD) { + /* RFC 1827 */ + esplen = sizeof(struct esp); + } else { + /* RFC 2406 */ + if (sav->flags & SADB_X_EXT_DERIV) + esplen = sizeof(struct esp); + else + esplen = sizeof(struct newesp); + } + esphlen = esplen + ivlen; + + for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) + ; + if (mprev == NULL || mprev->m_next != md) { + ipseclog((LOG_DEBUG, "esp%d_output: md is not in chain\n", + afnumber)); + m_freem(m); + return EINVAL; + } + + plen = 0; + for (n = md; n; n = n->m_next) + plen += n->m_len; + + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + break; +#endif +#ifdef INET6 + case AF_INET6: + ip6 = mtod(m, struct ip6_hdr *); + hlen = sizeof(*ip6); + break; +#endif + } + + /* make the packet over-writable */ + mprev->m_next = NULL; + if ((md = ipsec_copypkt(md)) == NULL) { + m_freem(m); + error = ENOBUFS; + goto fail; + } + mprev->m_next = md; + + espoff = m->m_pkthdr.len - plen; + + /* + * grow the mbuf to accomodate ESP header. + * before: IP ... payload + * after: IP ... ESP IV payload + */ + if (M_LEADINGSPACE(md) < esphlen || (md->m_flags & M_EXT) != 0) { + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + m_freem(m); + error = ENOBUFS; + goto fail; + } + n->m_len = esphlen; + mprev->m_next = n; + n->m_next = md; + m->m_pkthdr.len += esphlen; + esp = mtod(n, struct esp *); + } else { + md->m_len += esphlen; + md->m_data -= esphlen; + m->m_pkthdr.len += esphlen; + esp = mtod(md, struct esp *); + } + + nxt = *nexthdrp; + *nexthdrp = IPPROTO_ESP; + switch (af) { +#ifdef INET + case AF_INET: + if (esphlen < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + esphlen); + else { + ipseclog((LOG_ERR, + "IPv4 ESP output: size exceeds limit\n")); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + + /* initialize esp header. */ + esp->esp_spi = spi; + if ((sav->flags & SADB_X_EXT_OLD) == 0) { + struct newesp *nesp; + nesp = (struct newesp *)esp; + if (sav->replay->count == ~0) { + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) { + /* XXX Is it noisy ? */ + ipseclog((LOG_WARNING, + "replay counter overflowed. %s\n", + ipsec_logsastr(sav))); + stat->out_inval++; + m_freem(m); + return EINVAL; + } + } + sav->replay->count++; + /* + * XXX sequence number must not be cycled, if the SA is + * installed by IKE daemon. + */ + nesp->esp_seq = htonl(sav->replay->count); + } + + { + /* + * find the last mbuf. make some room for ESP trailer. + */ +#ifdef INET + struct ip *ip = NULL; +#endif + size_t padbound; + u_char *extend; + int i; + int randpadmax; + + if (algo->padbound) + padbound = algo->padbound; + else + padbound = 4; + /* ESP packet, including nxthdr field, must be length of 4n */ + if (padbound < 4) + padbound = 4; + + extendsiz = padbound - (plen % padbound); + if (extendsiz == 1) + extendsiz = padbound + 1; + + /* random padding */ + switch (af) { +#ifdef INET + case AF_INET: + randpadmax = ip4_esp_randpad; + break; +#endif +#ifdef INET6 + case AF_INET6: + randpadmax = ip6_esp_randpad; + break; +#endif + default: + randpadmax = -1; + break; + } + if (randpadmax < 0 || plen + extendsiz >= randpadmax) + ; + else { + int n; + + /* round */ + randpadmax = (randpadmax / padbound) * padbound; + n = (randpadmax - plen + extendsiz) / padbound; + + if (n > 0) + n = (random() % n) * padbound; + else + n = 0; + + /* + * make sure we do not pad too much. + * MLEN limitation comes from the trailer attachment + * code below. + * 256 limitation comes from sequential padding. + * also, the 1-octet length field in ESP trailer imposes + * limitation (but is less strict than sequential padding + * as length field do not count the last 2 octets). + */ + if (extendsiz + n <= MLEN && extendsiz + n < 256) + extendsiz += n; + } + +#ifdef DIAGNOSTIC + if (extendsiz > MLEN || extendsiz >= 256) + panic("extendsiz too big in esp_output"); +#endif + + n = m; + while (n->m_next) + n = n->m_next; + + /* + * if M_EXT, the external mbuf data may be shared among + * two consequtive TCP packets, and it may be unsafe to use the + * trailing space. + */ + if (!(n->m_flags & M_EXT) && extendsiz < M_TRAILINGSPACE(n)) { + extend = mtod(n, u_char *) + n->m_len; + n->m_len += extendsiz; + m->m_pkthdr.len += extendsiz; + } else { + struct mbuf *nn; + + MGET(nn, M_DONTWAIT, MT_DATA); + if (!nn) { + ipseclog((LOG_DEBUG, "esp%d_output: can't alloc mbuf", + afnumber)); + m_freem(m); + error = ENOBUFS; + goto fail; + } + extend = mtod(nn, u_char *); + nn->m_len = extendsiz; + nn->m_next = NULL; + n->m_next = nn; + n = nn; + m->m_pkthdr.len += extendsiz; + } + switch (sav->flags & SADB_X_EXT_PMASK) { + case SADB_X_EXT_PRAND: + key_randomfill(extend, extendsiz); + break; + case SADB_X_EXT_PZERO: + bzero(extend, extendsiz); + break; + case SADB_X_EXT_PSEQ: + for (i = 0; i < extendsiz; i++) + extend[i] = (i + 1) & 0xff; + break; + } + + /* initialize esp trailer. */ + esptail = (struct esptail *) + (mtod(n, u_int8_t *) + n->m_len - sizeof(struct esptail)); + esptail->esp_nxt = nxt; + esptail->esp_padlen = extendsiz - 2; + + /* modify IP header (for ESP header part only) */ + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); + if (extendsiz < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + extendsiz); + else { + ipseclog((LOG_ERR, + "IPv4 ESP output: size exceeds limit\n")); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + + /* + * pre-compute and cache intermediate key + */ + error = esp_schedule(algo, sav); + if (error) { + m_freem(m); + stat->out_inval++; + goto fail; + } + + /* + * encrypt the packet, based on security association + * and the algorithm specified. + */ + if (!algo->encrypt) + panic("internal error: no encrypt function"); + if ((*algo->encrypt)(m, espoff, plen + extendsiz, sav, algo, ivlen)) { + /* m is already freed */ + ipseclog((LOG_ERR, "packet encryption failure\n")); + stat->out_inval++; + error = EINVAL; + goto fail; + } + + /* + * calculate ICV if required. + */ + if (!sav->replay) + goto noantireplay; + if (!sav->key_auth) + goto noantireplay; + if (sav->key_auth == SADB_AALG_NONE) + goto noantireplay; + + { + const struct ah_algorithm *aalgo; + u_char authbuf[AH_MAXSUMSIZE]; + struct mbuf *n; + u_char *p; + size_t siz; +#ifdef INET + struct ip *ip; +#endif + + aalgo = ah_algorithm_lookup(sav->alg_auth); + if (!aalgo) + goto noantireplay; + siz = ((aalgo->sumsiz)(sav) + 3) & ~(4 - 1); + if (AH_MAXSUMSIZE < siz) + panic("assertion failed for AH_MAXSUMSIZE"); + + if (esp_auth(m, espoff, m->m_pkthdr.len - espoff, sav, authbuf)) { + ipseclog((LOG_ERR, "ESP checksum generation failure\n")); + m_freem(m); + error = EINVAL; + stat->out_inval++; + goto fail; + } + + n = m; + while (n->m_next) + n = n->m_next; + + if (!(n->m_flags & M_EXT) && siz < M_TRAILINGSPACE(n)) { /* XXX */ + n->m_len += siz; + m->m_pkthdr.len += siz; + p = mtod(n, u_char *) + n->m_len - siz; + } else { + struct mbuf *nn; + + MGET(nn, M_DONTWAIT, MT_DATA); + if (!nn) { + ipseclog((LOG_DEBUG, "can't alloc mbuf in esp%d_output", + afnumber)); + m_freem(m); + error = ENOBUFS; + goto fail; + } + nn->m_len = siz; + nn->m_next = NULL; + n->m_next = nn; + n = nn; + m->m_pkthdr.len += siz; + p = mtod(nn, u_char *); + } + bcopy(authbuf, p, siz); + + /* modify IP header (for ESP header part only) */ + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); + if (siz < (IP_MAXPACKET - ntohs(ip->ip_len))) + ip->ip_len = htons(ntohs(ip->ip_len) + siz); + else { + ipseclog((LOG_ERR, + "IPv4 ESP output: size exceeds limit\n")); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + +noantireplay: + if (!m) { + ipseclog((LOG_ERR, + "NULL mbuf after encryption in esp%d_output", afnumber)); + } else + stat->out_success++; + stat->out_esphist[sav->alg_enc]++; + key_sa_recordxfer(sav, m); + return 0; + +fail: +#if 1 + return error; +#else + panic("something bad in esp_output"); +#endif +} + +#ifdef INET +int +esp4_output(m, isr) + struct mbuf *m; + struct ipsecrequest *isr; +{ + struct ip *ip; + if (m->m_len < sizeof(struct ip)) { + ipseclog((LOG_DEBUG, "esp4_output: first mbuf too short\n")); + m_freem(m); + return 0; + } + ip = mtod(m, struct ip *); + /* XXX assumes that m->m_next points to payload */ + return esp_output(m, &ip->ip_p, m->m_next, isr, AF_INET); +} +#endif /* INET */ + +#ifdef INET6 +int +esp6_output(m, nexthdrp, md, isr) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; +{ + if (m->m_len < sizeof(struct ip6_hdr)) { + ipseclog((LOG_DEBUG, "esp6_output: first mbuf too short\n")); + m_freem(m); + return 0; + } + return esp_output(m, nexthdrp, md, isr, AF_INET6); +} +#endif /* INET6 */ diff --git a/sys/netinet6/esp_rijndael.c b/sys/netinet6/esp_rijndael.c new file mode 100644 index 0000000..950bbe8 --- /dev/null +++ b/sys/netinet6/esp_rijndael.c @@ -0,0 +1,117 @@ +/* $FreeBSD$ */ +/* $KAME: esp_rijndael.c,v 1.4 2001/03/02 05:53:05 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/socket.h> +#include <sys/queue.h> + +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> + +#include <netinet6/ipsec.h> +#include <netinet6/esp.h> +#include <netinet6/esp_rijndael.h> + +#include <crypto/rijndael/rijndael.h> + +#include <net/net_osdep.h> + +/* as rijndael uses assymetric scheduled keys, we need to do it twice. */ +int +esp_rijndael_schedlen(algo) + const struct esp_algorithm *algo; +{ + + return sizeof(keyInstance) * 2; +} + +int +esp_rijndael_schedule(algo, sav) + const struct esp_algorithm *algo; + struct secasvar *sav; +{ + keyInstance *k; + + k = (keyInstance *)sav->sched; + if (rijndael_makeKey(&k[0], DIR_DECRYPT, _KEYLEN(sav->key_enc) * 8, + _KEYBUF(sav->key_enc)) < 0) + return -1; + if (rijndael_makeKey(&k[1], DIR_ENCRYPT, _KEYLEN(sav->key_enc) * 8, + _KEYBUF(sav->key_enc)) < 0) + return -1; + return 0; +} + +int +esp_rijndael_blockdecrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + cipherInstance c; + keyInstance *p; + + /* does not take advantage of CBC mode support */ + bzero(&c, sizeof(c)); + if (rijndael_cipherInit(&c, MODE_ECB, NULL) < 0) + return -1; + p = (keyInstance *)sav->sched; + if (rijndael_blockDecrypt(&c, &p[0], s, algo->padbound * 8, d) < 0) + return -1; + return 0; +} + +int +esp_rijndael_blockencrypt(algo, sav, s, d) + const struct esp_algorithm *algo; + struct secasvar *sav; + u_int8_t *s; + u_int8_t *d; +{ + cipherInstance c; + keyInstance *p; + + /* does not take advantage of CBC mode support */ + bzero(&c, sizeof(c)); + if (rijndael_cipherInit(&c, MODE_ECB, NULL) < 0) + return -1; + p = (keyInstance *)sav->sched; + if (rijndael_blockEncrypt(&c, &p[1], s, algo->padbound * 8, d) < 0) + return -1; + return 0; +} diff --git a/sys/netinet6/esp_rijndael.h b/sys/netinet6/esp_rijndael.h new file mode 100644 index 0000000..0c40d78 --- /dev/null +++ b/sys/netinet6/esp_rijndael.h @@ -0,0 +1,39 @@ +/* $FreeBSD$ */ +/* $KAME: esp_rijndael.h,v 1.1 2000/09/20 18:15:22 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +int esp_rijndael_schedlen __P((const struct esp_algorithm *)); +int esp_rijndael_schedule __P((const struct esp_algorithm *, + struct secasvar *)); +int esp_rijndael_blockdecrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); +int esp_rijndael_blockencrypt __P((const struct esp_algorithm *, + struct secasvar *, u_int8_t *, u_int8_t *)); diff --git a/sys/netinet6/frag6.c b/sys/netinet6/frag6.c new file mode 100644 index 0000000..515a9fb --- /dev/null +++ b/sys/netinet6/frag6.c @@ -0,0 +1,695 @@ +/* $FreeBSD$ */ +/* $KAME: frag6.c,v 1.33 2002/01/07 11:34:48 kjc Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> + +#include <net/net_osdep.h> + +/* + * Define it to get a correct behavior on per-interface statistics. + * You will need to perform an extra routing table lookup, per fragment, + * to do it. This may, or may not be, a performance hit. + */ +#define IN6_IFSTAT_STRICT + +static void frag6_enq __P((struct ip6asfrag *, struct ip6asfrag *)); +static void frag6_deq __P((struct ip6asfrag *)); +static void frag6_insque __P((struct ip6q *, struct ip6q *)); +static void frag6_remque __P((struct ip6q *)); +static void frag6_freef __P((struct ip6q *)); + +/* XXX we eventually need splreass6, or some real semaphore */ +int frag6_doing_reass; +u_int frag6_nfragpackets; +struct ip6q ip6q; /* ip6 reassemble queue */ + +/* FreeBSD tweak */ +static MALLOC_DEFINE(M_FTABLE, "fragment", "fragment reassembly header"); + +/* + * Initialise reassembly queue and fragment identifier. + */ +void +frag6_init() +{ + struct timeval tv; + + ip6_maxfragpackets = nmbclusters / 4; + + /* + * in many cases, random() here does NOT return random number + * as initialization during bootstrap time occur in fixed order. + */ + microtime(&tv); + ip6_id = random() ^ tv.tv_usec; + ip6q.ip6q_next = ip6q.ip6q_prev = &ip6q; +} + +/* + * In RFC2460, fragment and reassembly rule do not agree with each other, + * in terms of next header field handling in fragment header. + * While the sender will use the same value for all of the fragmented packets, + * receiver is suggested not to check the consistency. + * + * fragment rule (p20): + * (2) A Fragment header containing: + * The Next Header value that identifies the first header of + * the Fragmentable Part of the original packet. + * -> next header field is same for all fragments + * + * reassembly rule (p21): + * The Next Header field of the last header of the Unfragmentable + * Part is obtained from the Next Header field of the first + * fragment's Fragment header. + * -> should grab it from the first fragment only + * + * The following note also contradicts with fragment rule - noone is going to + * send different fragment with different next header field. + * + * additional note (p22): + * The Next Header values in the Fragment headers of different + * fragments of the same original packet may differ. Only the value + * from the Offset zero fragment packet is used for reassembly. + * -> should grab it from the first fragment only + * + * There is no explicit reason given in the RFC. Historical reason maybe? + */ +/* + * Fragment input + */ +int +frag6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp, *t; + struct ip6_hdr *ip6; + struct ip6_frag *ip6f; + struct ip6q *q6; + struct ip6asfrag *af6, *ip6af, *af6dwn; + int offset = *offp, nxt, i, next; + int first_frag = 0; + int fragoff, frgpartlen; /* must be larger than u_int16_t */ + struct ifnet *dstifp; +#ifdef IN6_IFSTAT_STRICT + static struct route_in6 ro; + struct sockaddr_in6 *dst; +#endif + + ip6 = mtod(m, struct ip6_hdr *); +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, offset, sizeof(struct ip6_frag), IPPROTO_DONE); + ip6f = (struct ip6_frag *)((caddr_t)ip6 + offset); +#else + IP6_EXTHDR_GET(ip6f, struct ip6_frag *, m, offset, sizeof(*ip6f)); + if (ip6f == NULL) + return IPPROTO_DONE; +#endif + + dstifp = NULL; +#ifdef IN6_IFSTAT_STRICT + /* find the destination interface of the packet. */ + dst = (struct sockaddr_in6 *)&ro.ro_dst; + if (ro.ro_rt + && ((ro.ro_rt->rt_flags & RTF_UP) == 0 + || !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &ip6->ip6_dst))) { + RTFREE(ro.ro_rt); + ro.ro_rt = (struct rtentry *)0; + } + if (ro.ro_rt == NULL) { + bzero(dst, sizeof(*dst)); + dst->sin6_family = AF_INET6; + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_addr = ip6->ip6_dst; + } + rtalloc((struct route *)&ro); + if (ro.ro_rt != NULL && ro.ro_rt->rt_ifa != NULL) + dstifp = ((struct in6_ifaddr *)ro.ro_rt->rt_ifa)->ia_ifp; +#else + /* we are violating the spec, this is not the destination interface */ + if ((m->m_flags & M_PKTHDR) != 0) + dstifp = m->m_pkthdr.rcvif; +#endif + + /* jumbo payload can't contain a fragment header */ + if (ip6->ip6_plen == 0) { + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, offset); + in6_ifstat_inc(dstifp, ifs6_reass_fail); + return IPPROTO_DONE; + } + + /* + * check whether fragment packet's fragment length is + * multiple of 8 octets. + * sizeof(struct ip6_frag) == 8 + * sizeof(struct ip6_hdr) = 40 + */ + if ((ip6f->ip6f_offlg & IP6F_MORE_FRAG) && + (((ntohs(ip6->ip6_plen) - offset) & 0x7) != 0)) { + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + offsetof(struct ip6_hdr, ip6_plen)); + in6_ifstat_inc(dstifp, ifs6_reass_fail); + return IPPROTO_DONE; + } + + ip6stat.ip6s_fragments++; + in6_ifstat_inc(dstifp, ifs6_reass_reqd); + + /* offset now points to data portion */ + offset += sizeof(struct ip6_frag); + + frag6_doing_reass = 1; + + for (q6 = ip6q.ip6q_next; q6 != &ip6q; q6 = q6->ip6q_next) + if (ip6f->ip6f_ident == q6->ip6q_ident && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &q6->ip6q_src) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &q6->ip6q_dst)) + break; + + if (q6 == &ip6q) { + /* + * the first fragment to arrive, create a reassembly queue. + */ + first_frag = 1; + + /* + * Enforce upper bound on number of fragmented packets + * for which we attempt reassembly; + * If maxfrag is 0, never accept fragments. + * If maxfrag is -1, accept all fragments without limitation. + */ + if (ip6_maxfragpackets < 0) + ; + else if (frag6_nfragpackets >= (u_int)ip6_maxfragpackets) + goto dropfrag; + frag6_nfragpackets++; + q6 = (struct ip6q *)malloc(sizeof(struct ip6q), M_FTABLE, + M_DONTWAIT); + if (q6 == NULL) + goto dropfrag; + bzero(q6, sizeof(*q6)); + + frag6_insque(q6, &ip6q); + + /* ip6q_nxt will be filled afterwards, from 1st fragment */ + q6->ip6q_down = q6->ip6q_up = (struct ip6asfrag *)q6; +#ifdef notyet + q6->ip6q_nxtp = (u_char *)nxtp; +#endif + q6->ip6q_ident = ip6f->ip6f_ident; + q6->ip6q_arrive = 0; /* Is it used anywhere? */ + q6->ip6q_ttl = IPV6_FRAGTTL; + q6->ip6q_src = ip6->ip6_src; + q6->ip6q_dst = ip6->ip6_dst; + q6->ip6q_unfrglen = -1; /* The 1st fragment has not arrived. */ + } + + /* + * If it's the 1st fragment, record the length of the + * unfragmentable part and the next header of the fragment header. + */ + fragoff = ntohs(ip6f->ip6f_offlg & IP6F_OFF_MASK); + if (fragoff == 0) { + q6->ip6q_unfrglen = offset - sizeof(struct ip6_hdr) + - sizeof(struct ip6_frag); + q6->ip6q_nxt = ip6f->ip6f_nxt; + } + + /* + * Check that the reassembled packet would not exceed 65535 bytes + * in size. + * If it would exceed, discard the fragment and return an ICMP error. + */ + frgpartlen = sizeof(struct ip6_hdr) + ntohs(ip6->ip6_plen) - offset; + if (q6->ip6q_unfrglen >= 0) { + /* The 1st fragment has already arrived. */ + if (q6->ip6q_unfrglen + fragoff + frgpartlen > IPV6_MAXPACKET) { + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, + offset - sizeof(struct ip6_frag) + + offsetof(struct ip6_frag, ip6f_offlg)); + frag6_doing_reass = 0; + return(IPPROTO_DONE); + } + } + else if (fragoff + frgpartlen > IPV6_MAXPACKET) { + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, + offset - sizeof(struct ip6_frag) + + offsetof(struct ip6_frag, ip6f_offlg)); + frag6_doing_reass = 0; + return(IPPROTO_DONE); + } + /* + * If it's the first fragment, do the above check for each + * fragment already stored in the reassembly queue. + */ + if (fragoff == 0) { + for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6; + af6 = af6dwn) { + af6dwn = af6->ip6af_down; + + if (q6->ip6q_unfrglen + af6->ip6af_off + af6->ip6af_frglen > + IPV6_MAXPACKET) { + struct mbuf *merr = IP6_REASS_MBUF(af6); + struct ip6_hdr *ip6err; + int erroff = af6->ip6af_offset; + + /* dequeue the fragment. */ + frag6_deq(af6); + free(af6, M_FTABLE); + + /* adjust pointer. */ + ip6err = mtod(merr, struct ip6_hdr *); + + /* + * Restore source and destination addresses + * in the erroneous IPv6 header. + */ + ip6err->ip6_src = q6->ip6q_src; + ip6err->ip6_dst = q6->ip6q_dst; + + icmp6_error(merr, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff - sizeof(struct ip6_frag) + + offsetof(struct ip6_frag, ip6f_offlg)); + } + } + } + + ip6af = (struct ip6asfrag *)malloc(sizeof(struct ip6asfrag), M_FTABLE, + M_DONTWAIT); + if (ip6af == NULL) + goto dropfrag; + bzero(ip6af, sizeof(*ip6af)); + ip6af->ip6af_head = ip6->ip6_flow; + ip6af->ip6af_len = ip6->ip6_plen; + ip6af->ip6af_nxt = ip6->ip6_nxt; + ip6af->ip6af_hlim = ip6->ip6_hlim; + ip6af->ip6af_mff = ip6f->ip6f_offlg & IP6F_MORE_FRAG; + ip6af->ip6af_off = fragoff; + ip6af->ip6af_frglen = frgpartlen; + ip6af->ip6af_offset = offset; + IP6_REASS_MBUF(ip6af) = m; + + if (first_frag) { + af6 = (struct ip6asfrag *)q6; + goto insert; + } + + /* + * Find a segment which begins after this one does. + */ + for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6; + af6 = af6->ip6af_down) + if (af6->ip6af_off > ip6af->ip6af_off) + break; + +#if 0 + /* + * If there is a preceding segment, it may provide some of + * our data already. If so, drop the data from the incoming + * segment. If it provides all of our data, drop us. + */ + if (af6->ip6af_up != (struct ip6asfrag *)q6) { + i = af6->ip6af_up->ip6af_off + af6->ip6af_up->ip6af_frglen + - ip6af->ip6af_off; + if (i > 0) { + if (i >= ip6af->ip6af_frglen) + goto dropfrag; + m_adj(IP6_REASS_MBUF(ip6af), i); + ip6af->ip6af_off += i; + ip6af->ip6af_frglen -= i; + } + } + + /* + * While we overlap succeeding segments trim them or, + * if they are completely covered, dequeue them. + */ + while (af6 != (struct ip6asfrag *)q6 && + ip6af->ip6af_off + ip6af->ip6af_frglen > af6->ip6af_off) { + i = (ip6af->ip6af_off + ip6af->ip6af_frglen) - af6->ip6af_off; + if (i < af6->ip6af_frglen) { + af6->ip6af_frglen -= i; + af6->ip6af_off += i; + m_adj(IP6_REASS_MBUF(af6), i); + break; + } + af6 = af6->ip6af_down; + m_freem(IP6_REASS_MBUF(af6->ip6af_up)); + frag6_deq(af6->ip6af_up); + } +#else + /* + * If the incoming framgent overlaps some existing fragments in + * the reassembly queue, drop it, since it is dangerous to override + * existing fragments from a security point of view. + */ + if (af6->ip6af_up != (struct ip6asfrag *)q6) { + i = af6->ip6af_up->ip6af_off + af6->ip6af_up->ip6af_frglen + - ip6af->ip6af_off; + if (i > 0) { +#if 0 /* suppress the noisy log */ + log(LOG_ERR, "%d bytes of a fragment from %s " + "overlaps the previous fragment\n", + i, ip6_sprintf(&q6->ip6q_src)); +#endif + free(ip6af, M_FTABLE); + goto dropfrag; + } + } + if (af6 != (struct ip6asfrag *)q6) { + i = (ip6af->ip6af_off + ip6af->ip6af_frglen) - af6->ip6af_off; + if (i > 0) { +#if 0 /* suppress the noisy log */ + log(LOG_ERR, "%d bytes of a fragment from %s " + "overlaps the succeeding fragment", + i, ip6_sprintf(&q6->ip6q_src)); +#endif + free(ip6af, M_FTABLE); + goto dropfrag; + } + } +#endif + +insert: + + /* + * Stick new segment in its place; + * check for complete reassembly. + * Move to front of packet queue, as we are + * the most recently active fragmented packet. + */ + frag6_enq(ip6af, af6->ip6af_up); +#if 0 /* xxx */ + if (q6 != ip6q.ip6q_next) { + frag6_remque(q6); + frag6_insque(q6, &ip6q); + } +#endif + next = 0; + for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6; + af6 = af6->ip6af_down) { + if (af6->ip6af_off != next) { + frag6_doing_reass = 0; + return IPPROTO_DONE; + } + next += af6->ip6af_frglen; + } + if (af6->ip6af_up->ip6af_mff) { + frag6_doing_reass = 0; + return IPPROTO_DONE; + } + + /* + * Reassembly is complete; concatenate fragments. + */ + ip6af = q6->ip6q_down; + t = m = IP6_REASS_MBUF(ip6af); + af6 = ip6af->ip6af_down; + frag6_deq(ip6af); + while (af6 != (struct ip6asfrag *)q6) { + af6dwn = af6->ip6af_down; + frag6_deq(af6); + while (t->m_next) + t = t->m_next; + t->m_next = IP6_REASS_MBUF(af6); + m_adj(t->m_next, af6->ip6af_offset); + free(af6, M_FTABLE); + af6 = af6dwn; + } + + /* adjust offset to point where the original next header starts */ + offset = ip6af->ip6af_offset - sizeof(struct ip6_frag); + free(ip6af, M_FTABLE); + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_plen = htons((u_short)next + offset - sizeof(struct ip6_hdr)); + ip6->ip6_src = q6->ip6q_src; + ip6->ip6_dst = q6->ip6q_dst; + nxt = q6->ip6q_nxt; +#ifdef notyet + *q6->ip6q_nxtp = (u_char)(nxt & 0xff); +#endif + + /* + * Delete frag6 header with as a few cost as possible. + */ + if (offset < m->m_len) { + ovbcopy((caddr_t)ip6, (caddr_t)ip6 + sizeof(struct ip6_frag), + offset); + m->m_data += sizeof(struct ip6_frag); + m->m_len -= sizeof(struct ip6_frag); + } else { + /* this comes with no copy if the boundary is on cluster */ + if ((t = m_split(m, offset, M_DONTWAIT)) == NULL) { + frag6_remque(q6); + free(q6, M_FTABLE); + frag6_nfragpackets--; + goto dropfrag; + } + m_adj(t, sizeof(struct ip6_frag)); + m_cat(m, t); + } + + /* + * Store NXT to the original. + */ + { + char *prvnxtp = ip6_get_prevhdr(m, offset); /* XXX */ + *prvnxtp = nxt; + } + + frag6_remque(q6); + free(q6, M_FTABLE); + frag6_nfragpackets--; + + if (m->m_flags & M_PKTHDR) { /* Isn't it always true? */ + int plen = 0; + for (t = m; t; t = t->m_next) + plen += t->m_len; + m->m_pkthdr.len = plen; + } + + ip6stat.ip6s_reassembled++; + in6_ifstat_inc(dstifp, ifs6_reass_ok); + + /* + * Tell launch routine the next header + */ + + *mp = m; + *offp = offset; + + frag6_doing_reass = 0; + return nxt; + + dropfrag: + in6_ifstat_inc(dstifp, ifs6_reass_fail); + ip6stat.ip6s_fragdropped++; + m_freem(m); + frag6_doing_reass = 0; + return IPPROTO_DONE; +} + +/* + * Free a fragment reassembly header and all + * associated datagrams. + */ +void +frag6_freef(q6) + struct ip6q *q6; +{ + struct ip6asfrag *af6, *down6; + + for (af6 = q6->ip6q_down; af6 != (struct ip6asfrag *)q6; + af6 = down6) { + struct mbuf *m = IP6_REASS_MBUF(af6); + + down6 = af6->ip6af_down; + frag6_deq(af6); + + /* + * Return ICMP time exceeded error for the 1st fragment. + * Just free other fragments. + */ + if (af6->ip6af_off == 0) { + struct ip6_hdr *ip6; + + /* adjust pointer */ + ip6 = mtod(m, struct ip6_hdr *); + + /* restoure source and destination addresses */ + ip6->ip6_src = q6->ip6q_src; + ip6->ip6_dst = q6->ip6q_dst; + + icmp6_error(m, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_REASSEMBLY, 0); + } else + m_freem(m); + free(af6, M_FTABLE); + } + frag6_remque(q6); + free(q6, M_FTABLE); + frag6_nfragpackets--; +} + +/* + * Put an ip fragment on a reassembly chain. + * Like insque, but pointers in middle of structure. + */ +void +frag6_enq(af6, up6) + struct ip6asfrag *af6, *up6; +{ + af6->ip6af_up = up6; + af6->ip6af_down = up6->ip6af_down; + up6->ip6af_down->ip6af_up = af6; + up6->ip6af_down = af6; +} + +/* + * To frag6_enq as remque is to insque. + */ +void +frag6_deq(af6) + struct ip6asfrag *af6; +{ + af6->ip6af_up->ip6af_down = af6->ip6af_down; + af6->ip6af_down->ip6af_up = af6->ip6af_up; +} + +void +frag6_insque(new, old) + struct ip6q *new, *old; +{ + new->ip6q_prev = old; + new->ip6q_next = old->ip6q_next; + old->ip6q_next->ip6q_prev= new; + old->ip6q_next = new; +} + +void +frag6_remque(p6) + struct ip6q *p6; +{ + p6->ip6q_prev->ip6q_next = p6->ip6q_next; + p6->ip6q_next->ip6q_prev = p6->ip6q_prev; +} + +/* + * IPv6 reassembling timer processing; + * if a timer expires on a reassembly + * queue, discard it. + */ +void +frag6_slowtimo() +{ + struct ip6q *q6; + int s = splnet(); + + frag6_doing_reass = 1; + q6 = ip6q.ip6q_next; + if (q6) + while (q6 != &ip6q) { + --q6->ip6q_ttl; + q6 = q6->ip6q_next; + if (q6->ip6q_prev->ip6q_ttl == 0) { + ip6stat.ip6s_fragtimeout++; + /* XXX in6_ifstat_inc(ifp, ifs6_reass_fail) */ + frag6_freef(q6->ip6q_prev); + } + } + /* + * If we are over the maximum number of fragments + * (due to the limit being lowered), drain off + * enough to get down to the new limit. + */ + while (frag6_nfragpackets > (u_int)ip6_maxfragpackets && + ip6q.ip6q_prev) { + ip6stat.ip6s_fragoverflow++; + /* XXX in6_ifstat_inc(ifp, ifs6_reass_fail) */ + frag6_freef(ip6q.ip6q_prev); + } + frag6_doing_reass = 0; + +#if 0 + /* + * Routing changes might produce a better route than we last used; + * make sure we notice eventually, even if forwarding only for one + * destination and the cache is never replaced. + */ + if (ip6_forward_rt.ro_rt) { + RTFREE(ip6_forward_rt.ro_rt); + ip6_forward_rt.ro_rt = 0; + } + if (ipsrcchk_rt.ro_rt) { + RTFREE(ipsrcchk_rt.ro_rt); + ipsrcchk_rt.ro_rt = 0; + } +#endif + + splx(s); +} + +/* + * Drain off all datagram fragments. + */ +void +frag6_drain() +{ + if (frag6_doing_reass) + return; + while (ip6q.ip6q_next != &ip6q) { + ip6stat.ip6s_fragdropped++; + /* XXX in6_ifstat_inc(ifp, ifs6_reass_fail) */ + frag6_freef(ip6q.ip6q_next); + } +} diff --git a/sys/netinet6/icmp6.c b/sys/netinet6/icmp6.c new file mode 100644 index 0000000..d964b58 --- /dev/null +++ b/sys/netinet6/icmp6.c @@ -0,0 +1,2894 @@ +/* $FreeBSD$ */ +/* $KAME: icmp6.c,v 1.211 2001/04/04 05:56:20 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)ip_icmp.c 8.2 (Berkeley) 1/4/94 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/domain.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/signalvar.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sx.h> +#include <sys/syslog.h> +#include <sys/systm.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#include <netinet/in_var.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> +#include <netinet6/in6_ifattach.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/ip6protosw.h> +#include <netinet6/ip6_var.h> +#include <netinet6/mld6_var.h> +#include <netinet6/nd6.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#include <netkey/key.h> +#endif + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/key.h> +#define IPSEC +#endif + +#include <net/net_osdep.h> + +#ifdef HAVE_NRL_INPCB +/* inpcb members */ +#define in6pcb inpcb +#define in6p_laddr inp_laddr6 +#define in6p_faddr inp_faddr6 +#define in6p_icmp6filt inp_icmp6filt +#define in6p_route inp_route +#define in6p_socket inp_socket +#define in6p_flags inp_flags +#define in6p_moptions inp_moptions6 +#define in6p_outputopts inp_outputopts6 +#define in6p_ip6 inp_ipv6 +#define in6p_flowinfo inp_flowinfo +#define in6p_sp inp_sp +#define in6p_next inp_next +#define in6p_prev inp_prev +/* macro names */ +#define sotoin6pcb sotoinpcb +/* function names */ +#define in6_pcbdetach in_pcbdetach +#define in6_rtchange in_rtchange + +/* + * for KAME src sync over BSD*'s. XXX: FreeBSD (>=3) are VERY different from + * others... + */ +#define in6p_ip6_nxt inp_ipv6.ip6_nxt +#endif + +extern struct domain inet6domain; + +struct icmp6stat icmp6stat; + +extern struct inpcbhead ripcb; +extern int icmp6errppslim; +static int icmp6errpps_count = 0; +static struct timeval icmp6errppslim_last; +extern int icmp6_nodeinfo; + +static void icmp6_errcount __P((struct icmp6errstat *, int, int)); +static int icmp6_rip6_input __P((struct mbuf **, int)); +static int icmp6_ratelimit __P((const struct in6_addr *, const int, const int)); +static const char *icmp6_redirect_diag __P((struct in6_addr *, + struct in6_addr *, struct in6_addr *)); +#define HAVE_PPSRATECHECK +#ifndef HAVE_PPSRATECHECK +static int ppsratecheck __P((struct timeval *, int *, int)); +#endif +static struct mbuf *ni6_input __P((struct mbuf *, int)); +static struct mbuf *ni6_nametodns __P((const char *, int, int)); +static int ni6_dnsmatch __P((const char *, int, const char *, int)); +static int ni6_addrs __P((struct icmp6_nodeinfo *, struct mbuf *, + struct ifnet **, char *)); +static int ni6_store_addrs __P((struct icmp6_nodeinfo *, struct icmp6_nodeinfo *, + struct ifnet *, int)); +static int icmp6_notify_error __P((struct mbuf *, int, int, int)); + +#ifdef COMPAT_RFC1885 +static struct route_in6 icmp6_reflect_rt; +#endif + + +void +icmp6_init() +{ + mld6_init(); +} + +static void +icmp6_errcount(stat, type, code) + struct icmp6errstat *stat; + int type, code; +{ + switch (type) { + case ICMP6_DST_UNREACH: + switch (code) { + case ICMP6_DST_UNREACH_NOROUTE: + stat->icp6errs_dst_unreach_noroute++; + return; + case ICMP6_DST_UNREACH_ADMIN: + stat->icp6errs_dst_unreach_admin++; + return; + case ICMP6_DST_UNREACH_BEYONDSCOPE: + stat->icp6errs_dst_unreach_beyondscope++; + return; + case ICMP6_DST_UNREACH_ADDR: + stat->icp6errs_dst_unreach_addr++; + return; + case ICMP6_DST_UNREACH_NOPORT: + stat->icp6errs_dst_unreach_noport++; + return; + } + break; + case ICMP6_PACKET_TOO_BIG: + stat->icp6errs_packet_too_big++; + return; + case ICMP6_TIME_EXCEEDED: + switch (code) { + case ICMP6_TIME_EXCEED_TRANSIT: + stat->icp6errs_time_exceed_transit++; + return; + case ICMP6_TIME_EXCEED_REASSEMBLY: + stat->icp6errs_time_exceed_reassembly++; + return; + } + break; + case ICMP6_PARAM_PROB: + switch (code) { + case ICMP6_PARAMPROB_HEADER: + stat->icp6errs_paramprob_header++; + return; + case ICMP6_PARAMPROB_NEXTHEADER: + stat->icp6errs_paramprob_nextheader++; + return; + case ICMP6_PARAMPROB_OPTION: + stat->icp6errs_paramprob_option++; + return; + } + break; + case ND_REDIRECT: + stat->icp6errs_redirect++; + return; + } + stat->icp6errs_unknown++; +} + +/* + * Generate an error packet of type error in response to bad IP6 packet. + */ +void +icmp6_error(m, type, code, param) + struct mbuf *m; + int type, code, param; +{ + struct ip6_hdr *oip6, *nip6; + struct icmp6_hdr *icmp6; + u_int preplen; + int off; + int nxt; + + icmp6stat.icp6s_error++; + + /* count per-type-code statistics */ + icmp6_errcount(&icmp6stat.icp6s_outerrhist, type, code); + +#ifdef M_DECRYPTED /*not openbsd*/ + if (m->m_flags & M_DECRYPTED) { + icmp6stat.icp6s_canterror++; + goto freeit; + } +#endif + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, sizeof(struct ip6_hdr), ); +#else + if (m->m_len < sizeof(struct ip6_hdr)) { + m = m_pullup(m, sizeof(struct ip6_hdr)); + if (m == NULL) + return; + } +#endif + oip6 = mtod(m, struct ip6_hdr *); + + /* + * Multicast destination check. For unrecognized option errors, + * this check has already done in ip6_unknown_opt(), so we can + * check only for other errors. + */ + if ((m->m_flags & (M_BCAST|M_MCAST) || + IN6_IS_ADDR_MULTICAST(&oip6->ip6_dst)) && + (type != ICMP6_PACKET_TOO_BIG && + (type != ICMP6_PARAM_PROB || + code != ICMP6_PARAMPROB_OPTION))) + goto freeit; + + /* Source address check. XXX: the case of anycast source? */ + if (IN6_IS_ADDR_UNSPECIFIED(&oip6->ip6_src) || + IN6_IS_ADDR_MULTICAST(&oip6->ip6_src)) + goto freeit; + + /* + * If we are about to send ICMPv6 against ICMPv6 error/redirect, + * don't do it. + */ + nxt = -1; + off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); + if (off >= 0 && nxt == IPPROTO_ICMPV6) { + struct icmp6_hdr *icp; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, off + sizeof(struct icmp6_hdr), ); + icp = (struct icmp6_hdr *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(icp, struct icmp6_hdr *, m, off, + sizeof(*icp)); + if (icp == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + if (icp->icmp6_type < ICMP6_ECHO_REQUEST || + icp->icmp6_type == ND_REDIRECT) { + /* + * ICMPv6 error + * Special case: for redirect (which is + * informational) we must not send icmp6 error. + */ + icmp6stat.icp6s_canterror++; + goto freeit; + } else { + /* ICMPv6 informational - send the error */ + } + } else { + /* non-ICMPv6 - send the error */ + } + + oip6 = mtod(m, struct ip6_hdr *); /* adjust pointer */ + + /* Finally, do rate limitation check. */ + if (icmp6_ratelimit(&oip6->ip6_src, type, code)) { + icmp6stat.icp6s_toofreq++; + goto freeit; + } + + /* + * OK, ICMP6 can be generated. + */ + + if (m->m_pkthdr.len >= ICMPV6_PLD_MAXLEN) + m_adj(m, ICMPV6_PLD_MAXLEN - m->m_pkthdr.len); + + preplen = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr); + M_PREPEND(m, preplen, M_DONTWAIT); + if (m && m->m_len < preplen) + m = m_pullup(m, preplen); + if (m == NULL) { + nd6log((LOG_DEBUG, "ENOBUFS in icmp6_error %d\n", __LINE__)); + return; + } + + nip6 = mtod(m, struct ip6_hdr *); + nip6->ip6_src = oip6->ip6_src; + nip6->ip6_dst = oip6->ip6_dst; + + if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_src)) + oip6->ip6_src.s6_addr16[1] = 0; + if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_dst)) + oip6->ip6_dst.s6_addr16[1] = 0; + + icmp6 = (struct icmp6_hdr *)(nip6 + 1); + icmp6->icmp6_type = type; + icmp6->icmp6_code = code; + icmp6->icmp6_pptr = htonl((u_int32_t)param); + + /* + * icmp6_reflect() is designed to be in the input path. + * icmp6_error() can be called from both input and outut path, + * and if we are in output path rcvif could contain bogus value. + * clear m->m_pkthdr.rcvif for safety, we should have enough scope + * information in ip header (nip6). + */ + m->m_pkthdr.rcvif = NULL; + + icmp6stat.icp6s_outhist[type]++; + icmp6_reflect(m, sizeof(struct ip6_hdr)); /* header order: IPv6 - ICMPv6 */ + + return; + + freeit: + /* + * If we can't tell wheter or not we can generate ICMP6, free it. + */ + m_freem(m); +} + +/* + * Process a received ICMP6 message. + */ +int +icmp6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp, *n; + struct ip6_hdr *ip6, *nip6; + struct icmp6_hdr *icmp6, *nicmp6; + int off = *offp; + int icmp6len = m->m_pkthdr.len - *offp; + int code, sum, noff; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(struct icmp6_hdr), IPPROTO_DONE); + /* m might change if M_LOOP. So, call mtod after this */ +#endif + + /* + * Locate icmp6 structure in mbuf, and check + * that not corrupted and of at least minimum length + */ + + ip6 = mtod(m, struct ip6_hdr *); + if (icmp6len < sizeof(struct icmp6_hdr)) { + icmp6stat.icp6s_tooshort++; + goto freeit; + } + + /* + * calculate the checksum + */ +#ifndef PULLDOWN_TEST + icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(icmp6, struct icmp6_hdr *, m, off, sizeof(*icmp6)); + if (icmp6 == NULL) { + icmp6stat.icp6s_tooshort++; + return IPPROTO_DONE; + } +#endif + code = icmp6->icmp6_code; + + if ((sum = in6_cksum(m, IPPROTO_ICMPV6, off, icmp6len)) != 0) { + nd6log((LOG_ERR, + "ICMP6 checksum error(%d|%x) %s\n", + icmp6->icmp6_type, sum, ip6_sprintf(&ip6->ip6_src))); + icmp6stat.icp6s_checksum++; + goto freeit; + } + + if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { + /* + * Deliver very specific ICMP6 type only. + * This is important to deilver TOOBIG. Otherwise PMTUD + * will not work. + */ + switch (icmp6->icmp6_type) { + case ICMP6_DST_UNREACH: + case ICMP6_PACKET_TOO_BIG: + case ICMP6_TIME_EXCEEDED: + break; + default: + goto freeit; + } + } + + icmp6stat.icp6s_inhist[icmp6->icmp6_type]++; + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_msg); + if (icmp6->icmp6_type < ICMP6_INFOMSG_MASK) + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_error); + + switch (icmp6->icmp6_type) { + case ICMP6_DST_UNREACH: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_dstunreach); + switch (code) { + case ICMP6_DST_UNREACH_NOROUTE: + code = PRC_UNREACH_NET; + break; + case ICMP6_DST_UNREACH_ADMIN: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_adminprohib); + code = PRC_UNREACH_PROTOCOL; /* is this a good code? */ + break; + case ICMP6_DST_UNREACH_ADDR: + code = PRC_HOSTDEAD; + break; +#ifdef COMPAT_RFC1885 + case ICMP6_DST_UNREACH_NOTNEIGHBOR: + code = PRC_UNREACH_SRCFAIL; + break; +#else + case ICMP6_DST_UNREACH_BEYONDSCOPE: + /* I mean "source address was incorrect." */ + code = PRC_PARAMPROB; + break; +#endif + case ICMP6_DST_UNREACH_NOPORT: + code = PRC_UNREACH_PORT; + break; + default: + goto badcode; + } + goto deliver; + break; + + case ICMP6_PACKET_TOO_BIG: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_pkttoobig); + if (code != 0) + goto badcode; + + code = PRC_MSGSIZE; + + /* + * Updating the path MTU will be done after examining + * intermediate extension headers. + */ + goto deliver; + break; + + case ICMP6_TIME_EXCEEDED: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_timeexceed); + switch (code) { + case ICMP6_TIME_EXCEED_TRANSIT: + case ICMP6_TIME_EXCEED_REASSEMBLY: + code += PRC_TIMXCEED_INTRANS; + break; + default: + goto badcode; + } + goto deliver; + break; + + case ICMP6_PARAM_PROB: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_paramprob); + switch (code) { + case ICMP6_PARAMPROB_NEXTHEADER: + code = PRC_UNREACH_PROTOCOL; + break; + case ICMP6_PARAMPROB_HEADER: + case ICMP6_PARAMPROB_OPTION: + code = PRC_PARAMPROB; + break; + default: + goto badcode; + } + goto deliver; + break; + + case ICMP6_ECHO_REQUEST: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_echo); + if (code != 0) + goto badcode; + if ((n = m_copy(m, 0, M_COPYALL)) == NULL) { + /* Give up remote */ + break; + } + if ((n->m_flags & M_EXT) != 0 + || n->m_len < off + sizeof(struct icmp6_hdr)) { + struct mbuf *n0 = n; + const int maxlen = sizeof(*nip6) + sizeof(*nicmp6); + int n0len; + + /* + * Prepare an internal mbuf. m_pullup() doesn't + * always copy the length we specified. + */ + if (maxlen >= MCLBYTES) { + /* Give up remote */ + m_freem(n0); + break; + } + MGETHDR(n, M_DONTWAIT, n0->m_type); + n0len = n0->m_pkthdr.len; /* save for use below */ + if (n) + M_MOVE_PKTHDR(n, n0); + if (n && maxlen >= MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (n == NULL) { + /* Give up remote */ + m_freem(n0); + break; + } + /* + * Copy IPv6 and ICMPv6 only. + */ + nip6 = mtod(n, struct ip6_hdr *); + bcopy(ip6, nip6, sizeof(struct ip6_hdr)); + nicmp6 = (struct icmp6_hdr *)(nip6 + 1); + bcopy(icmp6, nicmp6, sizeof(struct icmp6_hdr)); + noff = sizeof(struct ip6_hdr); + /* new mbuf contains only ipv6+icmpv6 headers */ + n->m_len = noff + sizeof(struct icmp6_hdr); + /* + * Adjust mbuf. ip6_plen will be adjusted in + * ip6_output(). + */ + m_adj(n0, off + sizeof(struct icmp6_hdr)); + /* recalculate complete packet size */ + n->m_pkthdr.len = n0len + (noff - off); + n->m_next = n0; + } else { + nip6 = mtod(n, struct ip6_hdr *); + nicmp6 = (struct icmp6_hdr *)((caddr_t)nip6 + off); + noff = off; + } + nicmp6->icmp6_type = ICMP6_ECHO_REPLY; + nicmp6->icmp6_code = 0; + if (n) { + icmp6stat.icp6s_reflect++; + icmp6stat.icp6s_outhist[ICMP6_ECHO_REPLY]++; + icmp6_reflect(n, noff); + } + break; + + case ICMP6_ECHO_REPLY: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_echoreply); + if (code != 0) + goto badcode; + break; + + case MLD_LISTENER_QUERY: + case MLD_LISTENER_REPORT: + if (icmp6len < sizeof(struct mld_hdr)) + goto badlen; + if (icmp6->icmp6_type == MLD_LISTENER_QUERY) /* XXX: ugly... */ + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mldquery); + else + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mldreport); + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + mld6_input(m, off); + m = NULL; + goto freeit; + } + mld6_input(n, off); + /* m stays. */ + break; + + case MLD_LISTENER_DONE: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mlddone); + if (icmp6len < sizeof(struct mld_hdr)) /* necessary? */ + goto badlen; + break; /* nothing to be done in kernel */ + + case MLD_MTRACE_RESP: + case MLD_MTRACE: + /* XXX: these two are experimental. not officially defind. */ + /* XXX: per-interface statistics? */ + break; /* just pass it to applications */ + + case ICMP6_WRUREQUEST: /* ICMP6_FQDN_QUERY */ + { + enum { WRU, FQDN } mode; + + if (!icmp6_nodeinfo) + break; + + if (icmp6len == sizeof(struct icmp6_hdr) + 4) + mode = WRU; + else if (icmp6len >= sizeof(struct icmp6_nodeinfo)) + mode = FQDN; + else + goto badlen; + +#define hostnamelen strlen(hostname) + if (mode == FQDN) { +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(struct icmp6_nodeinfo), + IPPROTO_DONE); +#endif + n = m_copy(m, 0, M_COPYALL); + if (n) + n = ni6_input(n, off); + /* XXX meaningless if n == NULL */ + noff = sizeof(struct ip6_hdr); + } else { + u_char *p; + int maxlen, maxhlen; + + if ((icmp6_nodeinfo & 5) != 5) + break; + + if (code != 0) + goto badcode; + maxlen = sizeof(*nip6) + sizeof(*nicmp6) + 4; + if (maxlen >= MCLBYTES) { + /* Give up remote */ + break; + } + MGETHDR(n, M_DONTWAIT, m->m_type); + if (n && maxlen > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (!m_dup_pkthdr(n, m, M_DONTWAIT)) { + /* + * Previous code did a blind M_COPY_PKTHDR + * and said "just for rcvif". If true, then + * we could tolerate the dup failing (due to + * the deep copy of the tag chain). For now + * be conservative and just fail. + */ + m_free(n); + n = NULL; + } + if (n == NULL) { + /* Give up remote */ + break; + } + n->m_pkthdr.rcvif = NULL; + n->m_len = 0; + maxhlen = M_TRAILINGSPACE(n) - maxlen; + if (maxhlen > hostnamelen) + maxhlen = hostnamelen; + /* + * Copy IPv6 and ICMPv6 only. + */ + nip6 = mtod(n, struct ip6_hdr *); + bcopy(ip6, nip6, sizeof(struct ip6_hdr)); + nicmp6 = (struct icmp6_hdr *)(nip6 + 1); + bcopy(icmp6, nicmp6, sizeof(struct icmp6_hdr)); + p = (u_char *)(nicmp6 + 1); + bzero(p, 4); + bcopy(hostname, p + 4, maxhlen); /* meaningless TTL */ + noff = sizeof(struct ip6_hdr); + n->m_pkthdr.len = n->m_len = sizeof(struct ip6_hdr) + + sizeof(struct icmp6_hdr) + 4 + maxhlen; + nicmp6->icmp6_type = ICMP6_WRUREPLY; + nicmp6->icmp6_code = 0; + } +#undef hostnamelen + if (n) { + icmp6stat.icp6s_reflect++; + icmp6stat.icp6s_outhist[ICMP6_WRUREPLY]++; + icmp6_reflect(n, noff); + } + break; + } + + case ICMP6_WRUREPLY: + if (code != 0) + goto badcode; + break; + + case ND_ROUTER_SOLICIT: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_routersolicit); + if (code != 0) + goto badcode; + if (icmp6len < sizeof(struct nd_router_solicit)) + goto badlen; + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + nd6_rs_input(m, off, icmp6len); + m = NULL; + goto freeit; + } + nd6_rs_input(n, off, icmp6len); + /* m stays. */ + break; + + case ND_ROUTER_ADVERT: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_routeradvert); + if (code != 0) + goto badcode; + if (icmp6len < sizeof(struct nd_router_advert)) + goto badlen; + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + nd6_ra_input(m, off, icmp6len); + m = NULL; + goto freeit; + } + nd6_ra_input(n, off, icmp6len); + /* m stays. */ + break; + + case ND_NEIGHBOR_SOLICIT: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_neighborsolicit); + if (code != 0) + goto badcode; + if (icmp6len < sizeof(struct nd_neighbor_solicit)) + goto badlen; + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + nd6_ns_input(m, off, icmp6len); + m = NULL; + goto freeit; + } + nd6_ns_input(n, off, icmp6len); + /* m stays. */ + break; + + case ND_NEIGHBOR_ADVERT: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_neighboradvert); + if (code != 0) + goto badcode; + if (icmp6len < sizeof(struct nd_neighbor_advert)) + goto badlen; + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + nd6_na_input(m, off, icmp6len); + m = NULL; + goto freeit; + } + nd6_na_input(n, off, icmp6len); + /* m stays. */ + break; + + case ND_REDIRECT: + icmp6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_redirect); + if (code != 0) + goto badcode; + if (icmp6len < sizeof(struct nd_redirect)) + goto badlen; + if ((n = m_copym(m, 0, M_COPYALL, M_DONTWAIT)) == NULL) { + /* give up local */ + icmp6_redirect_input(m, off); + m = NULL; + goto freeit; + } + icmp6_redirect_input(n, off); + /* m stays. */ + break; + + case ICMP6_ROUTER_RENUMBERING: + if (code != ICMP6_ROUTER_RENUMBERING_COMMAND && + code != ICMP6_ROUTER_RENUMBERING_RESULT) + goto badcode; + if (icmp6len < sizeof(struct icmp6_router_renum)) + goto badlen; + break; + + default: + nd6log((LOG_DEBUG, + "icmp6_input: unknown type %d(src=%s, dst=%s, ifid=%d)\n", + icmp6->icmp6_type, ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + m->m_pkthdr.rcvif ? m->m_pkthdr.rcvif->if_index : 0)); + if (icmp6->icmp6_type < ICMP6_ECHO_REQUEST) { + /* ICMPv6 error: MUST deliver it by spec... */ + code = PRC_NCMDS; + /* deliver */ + } else { + /* ICMPv6 informational: MUST not deliver */ + break; + } + deliver: + if (icmp6_notify_error(m, off, icmp6len, code)) { + /* In this case, m should've been freed. */ + return(IPPROTO_DONE); + } + break; + + badcode: + icmp6stat.icp6s_badcode++; + break; + + badlen: + icmp6stat.icp6s_badlen++; + break; + } + + /* deliver the packet to appropriate sockets */ + icmp6_rip6_input(&m, *offp); + + return IPPROTO_DONE; + + freeit: + m_freem(m); + return IPPROTO_DONE; +} + +static int +icmp6_notify_error(m, off, icmp6len, code) + struct mbuf *m; + int off, icmp6len, code; +{ + struct icmp6_hdr *icmp6; + struct ip6_hdr *eip6; + u_int32_t notifymtu; + struct sockaddr_in6 icmp6src, icmp6dst; + + if (icmp6len < sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr)) { + icmp6stat.icp6s_tooshort++; + goto freeit; + } +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, + sizeof(struct icmp6_hdr) + sizeof(struct ip6_hdr), + -1); + icmp6 = (struct icmp6_hdr *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(icmp6, struct icmp6_hdr *, m, off, + sizeof(*icmp6) + sizeof(struct ip6_hdr)); + if (icmp6 == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + eip6 = (struct ip6_hdr *)(icmp6 + 1); + + /* Detect the upper level protocol */ + { + void (*ctlfunc) __P((int, struct sockaddr *, void *)); + u_int8_t nxt = eip6->ip6_nxt; + int eoff = off + sizeof(struct icmp6_hdr) + + sizeof(struct ip6_hdr); + struct ip6ctlparam ip6cp; + struct in6_addr *finaldst = NULL; + int icmp6type = icmp6->icmp6_type; + struct ip6_frag *fh; + struct ip6_rthdr *rth; + struct ip6_rthdr0 *rth0; + int rthlen; + + while (1) { /* XXX: should avoid infinite loop explicitly? */ + struct ip6_ext *eh; + + switch (nxt) { + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + case IPPROTO_AH: +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, eoff + + sizeof(struct ip6_ext), + -1); + eh = (struct ip6_ext *)(mtod(m, caddr_t) + + eoff); +#else + IP6_EXTHDR_GET(eh, struct ip6_ext *, m, + eoff, sizeof(*eh)); + if (eh == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + + if (nxt == IPPROTO_AH) + eoff += (eh->ip6e_len + 2) << 2; + else + eoff += (eh->ip6e_len + 1) << 3; + nxt = eh->ip6e_nxt; + break; + case IPPROTO_ROUTING: + /* + * When the erroneous packet contains a + * routing header, we should examine the + * header to determine the final destination. + * Otherwise, we can't properly update + * information that depends on the final + * destination (e.g. path MTU). + */ +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, eoff + sizeof(*rth), + -1); + rth = (struct ip6_rthdr *)(mtod(m, caddr_t) + + eoff); +#else + IP6_EXTHDR_GET(rth, struct ip6_rthdr *, m, + eoff, sizeof(*rth)); + if (rth == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + rthlen = (rth->ip6r_len + 1) << 3; + /* + * XXX: currently there is no + * officially defined type other + * than type-0. + * Note that if the segment left field + * is 0, all intermediate hops must + * have been passed. + */ + if (rth->ip6r_segleft && + rth->ip6r_type == IPV6_RTHDR_TYPE_0) { + int hops; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, eoff + rthlen, + -1); + rth0 = (struct ip6_rthdr0 *)(mtod(m, caddr_t) + eoff); +#else + IP6_EXTHDR_GET(rth0, + struct ip6_rthdr0 *, m, + eoff, rthlen); + if (rth0 == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + /* just ignore a bogus header */ + if ((rth0->ip6r0_len % 2) == 0 && + (hops = rth0->ip6r0_len/2)) + finaldst = (struct in6_addr *)(rth0 + 1) + (hops - 1); + } + eoff += rthlen; + nxt = rth->ip6r_nxt; + break; + case IPPROTO_FRAGMENT: +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, 0, eoff + + sizeof(struct ip6_frag), + -1); + fh = (struct ip6_frag *)(mtod(m, caddr_t) + + eoff); +#else + IP6_EXTHDR_GET(fh, struct ip6_frag *, m, + eoff, sizeof(*fh)); + if (fh == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + /* + * Data after a fragment header is meaningless + * unless it is the first fragment, but + * we'll go to the notify label for path MTU + * discovery. + */ + if (fh->ip6f_offlg & IP6F_OFF_MASK) + goto notify; + + eoff += sizeof(struct ip6_frag); + nxt = fh->ip6f_nxt; + break; + default: + /* + * This case includes ESP and the No Next + * Header. In such cases going to the notify + * label does not have any meaning + * (i.e. ctlfunc will be NULL), but we go + * anyway since we might have to update + * path MTU information. + */ + goto notify; + } + } + notify: +#ifndef PULLDOWN_TEST + icmp6 = (struct icmp6_hdr *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(icmp6, struct icmp6_hdr *, m, off, + sizeof(*icmp6) + sizeof(struct ip6_hdr)); + if (icmp6 == NULL) { + icmp6stat.icp6s_tooshort++; + return(-1); + } +#endif + + eip6 = (struct ip6_hdr *)(icmp6 + 1); + bzero(&icmp6dst, sizeof(icmp6dst)); + icmp6dst.sin6_len = sizeof(struct sockaddr_in6); + icmp6dst.sin6_family = AF_INET6; + if (finaldst == NULL) + icmp6dst.sin6_addr = eip6->ip6_dst; + else + icmp6dst.sin6_addr = *finaldst; + icmp6dst.sin6_scope_id = in6_addr2scopeid(m->m_pkthdr.rcvif, + &icmp6dst.sin6_addr); +#ifndef SCOPEDROUTING + if (in6_embedscope(&icmp6dst.sin6_addr, &icmp6dst, + NULL, NULL)) { + /* should be impossbile */ + nd6log((LOG_DEBUG, + "icmp6_notify_error: in6_embedscope failed\n")); + goto freeit; + } +#endif + + /* + * retrieve parameters from the inner IPv6 header, and convert + * them into sockaddr structures. + */ + bzero(&icmp6src, sizeof(icmp6src)); + icmp6src.sin6_len = sizeof(struct sockaddr_in6); + icmp6src.sin6_family = AF_INET6; + icmp6src.sin6_addr = eip6->ip6_src; + icmp6src.sin6_scope_id = in6_addr2scopeid(m->m_pkthdr.rcvif, + &icmp6src.sin6_addr); +#ifndef SCOPEDROUTING + if (in6_embedscope(&icmp6src.sin6_addr, &icmp6src, + NULL, NULL)) { + /* should be impossbile */ + nd6log((LOG_DEBUG, + "icmp6_notify_error: in6_embedscope failed\n")); + goto freeit; + } +#endif + icmp6src.sin6_flowinfo = + (eip6->ip6_flow & IPV6_FLOWLABEL_MASK); + + if (finaldst == NULL) + finaldst = &eip6->ip6_dst; + ip6cp.ip6c_m = m; + ip6cp.ip6c_icmp6 = icmp6; + ip6cp.ip6c_ip6 = (struct ip6_hdr *)(icmp6 + 1); + ip6cp.ip6c_off = eoff; + ip6cp.ip6c_finaldst = finaldst; + ip6cp.ip6c_src = &icmp6src; + ip6cp.ip6c_nxt = nxt; + + if (icmp6type == ICMP6_PACKET_TOO_BIG) { + notifymtu = ntohl(icmp6->icmp6_mtu); + ip6cp.ip6c_cmdarg = (void *)¬ifymtu; + icmp6_mtudisc_update(&ip6cp, 1); /*XXX*/ + } + + ctlfunc = (void (*) __P((int, struct sockaddr *, void *))) + (inet6sw[ip6_protox[nxt]].pr_ctlinput); + if (ctlfunc) { + (void) (*ctlfunc)(code, (struct sockaddr *)&icmp6dst, + &ip6cp); + } + } + return(0); + + freeit: + m_freem(m); + return(-1); +} + +void +icmp6_mtudisc_update(ip6cp, validated) + struct ip6ctlparam *ip6cp; + int validated; +{ + struct in6_addr *dst = ip6cp->ip6c_finaldst; + struct icmp6_hdr *icmp6 = ip6cp->ip6c_icmp6; + struct mbuf *m = ip6cp->ip6c_m; /* will be necessary for scope issue */ + u_int mtu = ntohl(icmp6->icmp6_mtu); + struct rtentry *rt = NULL; + struct sockaddr_in6 sin6; + + if (!validated) + return; + + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = PF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr = *dst; + /* XXX normally, this won't happen */ + if (IN6_IS_ADDR_LINKLOCAL(dst)) { + sin6.sin6_addr.s6_addr16[1] = + htons(m->m_pkthdr.rcvif->if_index); + } + /* sin6.sin6_scope_id = XXX: should be set if DST is a scoped addr */ + rt = rtalloc1((struct sockaddr *)&sin6, 0, + RTF_CLONING | RTF_PRCLONING); + + if (rt && (rt->rt_flags & RTF_HOST) + && !(rt->rt_rmx.rmx_locks & RTV_MTU)) { + if (mtu < IPV6_MMTU) { + /* xxx */ + rt->rt_rmx.rmx_locks |= RTV_MTU; + } else if (mtu < rt->rt_ifp->if_mtu && + rt->rt_rmx.rmx_mtu > mtu) { + icmp6stat.icp6s_pmtuchg++; + rt->rt_rmx.rmx_mtu = mtu; + } + } + if (rt) { /* XXX: need braces to avoid conflict with else in RTFREE. */ + RTFREE(rt); + } +} + +/* + * Process a Node Information Query packet, based on + * draft-ietf-ipngwg-icmp-name-lookups-07. + * + * Spec incompatibilities: + * - IPv6 Subject address handling + * - IPv4 Subject address handling support missing + * - Proxy reply (answer even if it's not for me) + * - joins NI group address at in6_ifattach() time only, does not cope + * with hostname changes by sethostname(3) + */ +#define hostnamelen strlen(hostname) +static struct mbuf * +ni6_input(m, off) + struct mbuf *m; + int off; +{ + struct icmp6_nodeinfo *ni6, *nni6; + struct mbuf *n = NULL; + u_int16_t qtype; + int subjlen; + int replylen = sizeof(struct ip6_hdr) + sizeof(struct icmp6_nodeinfo); + struct ni_reply_fqdn *fqdn; + int addrs; /* for NI_QTYPE_NODEADDR */ + struct ifnet *ifp = NULL; /* for NI_QTYPE_NODEADDR */ + struct sockaddr_in6 sin6; /* double meaning; ip6_dst and subjectaddr */ + struct sockaddr_in6 sin6_d; /* XXX: we should retrieve this from m_aux */ + struct ip6_hdr *ip6; + int oldfqdn = 0; /* if 1, return pascal string (03 draft) */ + char *subj = NULL; + struct in6_ifaddr *ia6 = NULL; + + ip6 = mtod(m, struct ip6_hdr *); +#ifndef PULLDOWN_TEST + ni6 = (struct icmp6_nodeinfo *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(ni6, struct icmp6_nodeinfo *, m, off, sizeof(*ni6)); + if (ni6 == NULL) { + /* m is already reclaimed */ + return NULL; + } +#endif + + /* + * Validate IPv6 destination address. + * + * The Responder must discard the Query without further processing + * unless it is one of the Responder's unicast or anycast addresses, or + * a link-local scope multicast address which the Responder has joined. + * [icmp-name-lookups-07, Section 4.] + */ + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_dst, &sin6.sin6_addr, sizeof(sin6.sin6_addr)); + /* XXX scopeid */ + if ((ia6 = (struct in6_ifaddr *)ifa_ifwithaddr((struct sockaddr *)&sin6)) != NULL) { + /* unicast/anycast, fine */ + if ((ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0 && + (icmp6_nodeinfo & 4) == 0) { + nd6log((LOG_DEBUG, "ni6_input: ignore node info to " + "a temporary address in %s:%d", + __FILE__, __LINE__)); + goto bad; + } + } else if (IN6_IS_ADDR_MC_LINKLOCAL(&sin6.sin6_addr)) + ; /* link-local multicast, fine */ + else + goto bad; + + /* validate query Subject field. */ + qtype = ntohs(ni6->ni_qtype); + subjlen = m->m_pkthdr.len - off - sizeof(struct icmp6_nodeinfo); + switch (qtype) { + case NI_QTYPE_NOOP: + case NI_QTYPE_SUPTYPES: + /* 07 draft */ + if (ni6->ni_code == ICMP6_NI_SUBJ_FQDN && subjlen == 0) + break; + /* FALLTHROUGH */ + case NI_QTYPE_FQDN: + case NI_QTYPE_NODEADDR: + switch (ni6->ni_code) { + case ICMP6_NI_SUBJ_IPV6: +#if ICMP6_NI_SUBJ_IPV6 != 0 + case 0: +#endif + /* + * backward compatibility - try to accept 03 draft + * format, where no Subject is present. + */ + if (qtype == NI_QTYPE_FQDN && ni6->ni_code == 0 && + subjlen == 0) { + oldfqdn++; + break; + } +#if ICMP6_NI_SUBJ_IPV6 != 0 + if (ni6->ni_code != ICMP6_NI_SUBJ_IPV6) + goto bad; +#endif + + if (subjlen != sizeof(sin6.sin6_addr)) + goto bad; + + /* + * Validate Subject address. + * + * Not sure what exactly "address belongs to the node" + * means in the spec, is it just unicast, or what? + * + * At this moment we consider Subject address as + * "belong to the node" if the Subject address equals + * to the IPv6 destination address; validation for + * IPv6 destination address should have done enough + * check for us. + * + * We do not do proxy at this moment. + */ + /* m_pulldown instead of copy? */ + m_copydata(m, off + sizeof(struct icmp6_nodeinfo), + subjlen, (caddr_t)&sin6.sin6_addr); + sin6.sin6_scope_id = in6_addr2scopeid(m->m_pkthdr.rcvif, + &sin6.sin6_addr); +#ifndef SCOPEDROUTING + in6_embedscope(&sin6.sin6_addr, &sin6, NULL, NULL); +#endif + bzero(&sin6_d, sizeof(sin6_d)); + sin6_d.sin6_family = AF_INET6; /* not used, actually */ + sin6_d.sin6_len = sizeof(sin6_d); /* ditto */ + sin6_d.sin6_addr = ip6->ip6_dst; + sin6_d.sin6_scope_id = in6_addr2scopeid(m->m_pkthdr.rcvif, + &ip6->ip6_dst); +#ifndef SCOPEDROUTING + in6_embedscope(&sin6_d.sin6_addr, &sin6_d, NULL, NULL); +#endif + subj = (char *)&sin6; + if (SA6_ARE_ADDR_EQUAL(&sin6, &sin6_d)) + break; + + /* + * XXX if we are to allow other cases, we should really + * be careful about scope here. + * basically, we should disallow queries toward IPv6 + * destination X with subject Y, if scope(X) > scope(Y). + * if we allow scope(X) > scope(Y), it will result in + * information leakage across scope boundary. + */ + goto bad; + + case ICMP6_NI_SUBJ_FQDN: + /* + * Validate Subject name with gethostname(3). + * + * The behavior may need some debate, since: + * - we are not sure if the node has FQDN as + * hostname (returned by gethostname(3)). + * - the code does wildcard match for truncated names. + * however, we are not sure if we want to perform + * wildcard match, if gethostname(3) side has + * truncated hostname. + */ + n = ni6_nametodns(hostname, hostnamelen, 0); + if (!n || n->m_next || n->m_len == 0) + goto bad; + IP6_EXTHDR_GET(subj, char *, m, + off + sizeof(struct icmp6_nodeinfo), subjlen); + if (subj == NULL) + goto bad; + if (!ni6_dnsmatch(subj, subjlen, mtod(n, const char *), + n->m_len)) { + goto bad; + } + m_freem(n); + n = NULL; + break; + + case ICMP6_NI_SUBJ_IPV4: /* XXX: to be implemented? */ + default: + goto bad; + } + break; + } + + /* refuse based on configuration. XXX ICMP6_NI_REFUSED? */ + switch (qtype) { + case NI_QTYPE_FQDN: + if ((icmp6_nodeinfo & 1) == 0) + goto bad; + break; + case NI_QTYPE_NODEADDR: + if ((icmp6_nodeinfo & 2) == 0) + goto bad; + break; + } + + /* guess reply length */ + switch (qtype) { + case NI_QTYPE_NOOP: + break; /* no reply data */ + case NI_QTYPE_SUPTYPES: + replylen += sizeof(u_int32_t); + break; + case NI_QTYPE_FQDN: + /* XXX will append an mbuf */ + replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen); + break; + case NI_QTYPE_NODEADDR: + addrs = ni6_addrs(ni6, m, &ifp, subj); + if ((replylen += addrs * (sizeof(struct in6_addr) + + sizeof(u_int32_t))) > MCLBYTES) + replylen = MCLBYTES; /* XXX: will truncate pkt later */ + break; + default: + /* + * XXX: We must return a reply with the ICMP6 code + * `unknown Qtype' in this case. However we regard the case + * as an FQDN query for backward compatibility. + * Older versions set a random value to this field, + * so it rarely varies in the defined qtypes. + * But the mechanism is not reliable... + * maybe we should obsolete older versions. + */ + qtype = NI_QTYPE_FQDN; + /* XXX will append an mbuf */ + replylen += offsetof(struct ni_reply_fqdn, ni_fqdn_namelen); + oldfqdn++; + break; + } + + /* allocate an mbuf to reply. */ + MGETHDR(n, M_DONTWAIT, m->m_type); + if (n == NULL) { + m_freem(m); + return(NULL); + } + M_MOVE_PKTHDR(n, m); /* just for recvif */ + if (replylen > MHLEN) { + if (replylen > MCLBYTES) { + /* + * XXX: should we try to allocate more? But MCLBYTES + * is probably much larger than IPV6_MMTU... + */ + goto bad; + } + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + goto bad; + } + } + n->m_pkthdr.len = n->m_len = replylen; + + /* copy mbuf header and IPv6 + Node Information base headers */ + bcopy(mtod(m, caddr_t), mtod(n, caddr_t), sizeof(struct ip6_hdr)); + nni6 = (struct icmp6_nodeinfo *)(mtod(n, struct ip6_hdr *) + 1); + bcopy((caddr_t)ni6, (caddr_t)nni6, sizeof(struct icmp6_nodeinfo)); + + /* qtype dependent procedure */ + switch (qtype) { + case NI_QTYPE_NOOP: + nni6->ni_code = ICMP6_NI_SUCCESS; + nni6->ni_flags = 0; + break; + case NI_QTYPE_SUPTYPES: + { + u_int32_t v; + nni6->ni_code = ICMP6_NI_SUCCESS; + nni6->ni_flags = htons(0x0000); /* raw bitmap */ + /* supports NOOP, SUPTYPES, FQDN, and NODEADDR */ + v = (u_int32_t)htonl(0x0000000f); + bcopy(&v, nni6 + 1, sizeof(u_int32_t)); + break; + } + case NI_QTYPE_FQDN: + nni6->ni_code = ICMP6_NI_SUCCESS; + fqdn = (struct ni_reply_fqdn *)(mtod(n, caddr_t) + + sizeof(struct ip6_hdr) + + sizeof(struct icmp6_nodeinfo)); + nni6->ni_flags = 0; /* XXX: meaningless TTL */ + fqdn->ni_fqdn_ttl = 0; /* ditto. */ + /* + * XXX do we really have FQDN in variable "hostname"? + */ + n->m_next = ni6_nametodns(hostname, hostnamelen, oldfqdn); + if (n->m_next == NULL) + goto bad; + /* XXX we assume that n->m_next is not a chain */ + if (n->m_next->m_next != NULL) + goto bad; + n->m_pkthdr.len += n->m_next->m_len; + break; + case NI_QTYPE_NODEADDR: + { + int lenlim, copied; + + nni6->ni_code = ICMP6_NI_SUCCESS; + n->m_pkthdr.len = n->m_len = + sizeof(struct ip6_hdr) + sizeof(struct icmp6_nodeinfo); + lenlim = M_TRAILINGSPACE(n); + copied = ni6_store_addrs(ni6, nni6, ifp, lenlim); + /* XXX: reset mbuf length */ + n->m_pkthdr.len = n->m_len = sizeof(struct ip6_hdr) + + sizeof(struct icmp6_nodeinfo) + copied; + break; + } + default: + break; /* XXX impossible! */ + } + + nni6->ni_type = ICMP6_NI_REPLY; + m_freem(m); + return(n); + + bad: + m_freem(m); + if (n) + m_freem(n); + return(NULL); +} +#undef hostnamelen + +/* + * make a mbuf with DNS-encoded string. no compression support. + * + * XXX names with less than 2 dots (like "foo" or "foo.section") will be + * treated as truncated name (two \0 at the end). this is a wild guess. + */ +static struct mbuf * +ni6_nametodns(name, namelen, old) + const char *name; + int namelen; + int old; /* return pascal string if non-zero */ +{ + struct mbuf *m; + char *cp, *ep; + const char *p, *q; + int i, len, nterm; + + if (old) + len = namelen + 1; + else + len = MCLBYTES; + + /* because MAXHOSTNAMELEN is usually 256, we use cluster mbuf */ + MGET(m, M_DONTWAIT, MT_DATA); + if (m && len > MLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) + goto fail; + } + if (!m) + goto fail; + m->m_next = NULL; + + if (old) { + m->m_len = len; + *mtod(m, char *) = namelen; + bcopy(name, mtod(m, char *) + 1, namelen); + return m; + } else { + m->m_len = 0; + cp = mtod(m, char *); + ep = mtod(m, char *) + M_TRAILINGSPACE(m); + + /* if not certain about my name, return empty buffer */ + if (namelen == 0) + return m; + + /* + * guess if it looks like shortened hostname, or FQDN. + * shortened hostname needs two trailing "\0". + */ + i = 0; + for (p = name; p < name + namelen; p++) { + if (*p && *p == '.') + i++; + } + if (i < 2) + nterm = 2; + else + nterm = 1; + + p = name; + while (cp < ep && p < name + namelen) { + i = 0; + for (q = p; q < name + namelen && *q && *q != '.'; q++) + i++; + /* result does not fit into mbuf */ + if (cp + i + 1 >= ep) + goto fail; + /* + * DNS label length restriction, RFC1035 page 8. + * "i == 0" case is included here to avoid returning + * 0-length label on "foo..bar". + */ + if (i <= 0 || i >= 64) + goto fail; + *cp++ = i; + bcopy(p, cp, i); + cp += i; + p = q; + if (p < name + namelen && *p == '.') + p++; + } + /* termination */ + if (cp + nterm >= ep) + goto fail; + while (nterm-- > 0) + *cp++ = '\0'; + m->m_len = cp - mtod(m, char *); + return m; + } + + panic("should not reach here"); + /* NOTREACHED */ + + fail: + if (m) + m_freem(m); + return NULL; +} + +/* + * check if two DNS-encoded string matches. takes care of truncated + * form (with \0\0 at the end). no compression support. + * XXX upper/lowercase match (see RFC2065) + */ +static int +ni6_dnsmatch(a, alen, b, blen) + const char *a; + int alen; + const char *b; + int blen; +{ + const char *a0, *b0; + int l; + + /* simplest case - need validation? */ + if (alen == blen && bcmp(a, b, alen) == 0) + return 1; + + a0 = a; + b0 = b; + + /* termination is mandatory */ + if (alen < 2 || blen < 2) + return 0; + if (a0[alen - 1] != '\0' || b0[blen - 1] != '\0') + return 0; + alen--; + blen--; + + while (a - a0 < alen && b - b0 < blen) { + if (a - a0 + 1 > alen || b - b0 + 1 > blen) + return 0; + + if ((signed char)a[0] < 0 || (signed char)b[0] < 0) + return 0; + /* we don't support compression yet */ + if (a[0] >= 64 || b[0] >= 64) + return 0; + + /* truncated case */ + if (a[0] == 0 && a - a0 == alen - 1) + return 1; + if (b[0] == 0 && b - b0 == blen - 1) + return 1; + if (a[0] == 0 || b[0] == 0) + return 0; + + if (a[0] != b[0]) + return 0; + l = a[0]; + if (a - a0 + 1 + l > alen || b - b0 + 1 + l > blen) + return 0; + if (bcmp(a + 1, b + 1, l) != 0) + return 0; + + a += 1 + l; + b += 1 + l; + } + + if (a - a0 == alen && b - b0 == blen) + return 1; + else + return 0; +} + +/* + * calculate the number of addresses to be returned in the node info reply. + */ +static int +ni6_addrs(ni6, m, ifpp, subj) + struct icmp6_nodeinfo *ni6; + struct mbuf *m; + struct ifnet **ifpp; + char *subj; +{ + struct ifnet *ifp; + struct in6_ifaddr *ifa6; + struct ifaddr *ifa; + struct sockaddr_in6 *subj_ip6 = NULL; /* XXX pedant */ + int addrs = 0, addrsofif, iffound = 0; + int niflags = ni6->ni_flags; + + if ((niflags & NI_NODEADDR_FLAG_ALL) == 0) { + switch (ni6->ni_code) { + case ICMP6_NI_SUBJ_IPV6: + if (subj == NULL) /* must be impossible... */ + return(0); + subj_ip6 = (struct sockaddr_in6 *)subj; + break; + default: + /* + * XXX: we only support IPv6 subject address for + * this Qtype. + */ + return(0); + } + } + + IFNET_RLOCK(); + for (ifp = TAILQ_FIRST(&ifnet); ifp; ifp = TAILQ_NEXT(ifp, if_list)) + { + addrsofif = 0; + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ifa6 = (struct in6_ifaddr *)ifa; + + if ((niflags & NI_NODEADDR_FLAG_ALL) == 0 && + IN6_ARE_ADDR_EQUAL(&subj_ip6->sin6_addr, + &ifa6->ia_addr.sin6_addr)) + iffound = 1; + + /* + * IPv4-mapped addresses can only be returned by a + * Node Information proxy, since they represent + * addresses of IPv4-only nodes, which perforce do + * not implement this protocol. + * [icmp-name-lookups-07, Section 5.4] + * So we don't support NI_NODEADDR_FLAG_COMPAT in + * this function at this moment. + */ + + /* What do we have to do about ::1? */ + switch (in6_addrscope(&ifa6->ia_addr.sin6_addr)) { + case IPV6_ADDR_SCOPE_LINKLOCAL: + if ((niflags & NI_NODEADDR_FLAG_LINKLOCAL) == 0) + continue; + break; + case IPV6_ADDR_SCOPE_SITELOCAL: + if ((niflags & NI_NODEADDR_FLAG_SITELOCAL) == 0) + continue; + break; + case IPV6_ADDR_SCOPE_GLOBAL: + if ((niflags & NI_NODEADDR_FLAG_GLOBAL) == 0) + continue; + break; + default: + continue; + } + + /* + * check if anycast is okay. + * XXX: just experimental. not in the spec. + */ + if ((ifa6->ia6_flags & IN6_IFF_ANYCAST) != 0 && + (niflags & NI_NODEADDR_FLAG_ANYCAST) == 0) + continue; /* we need only unicast addresses */ + if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0 && + (icmp6_nodeinfo & 4) == 0) { + continue; + } + addrsofif++; /* count the address */ + } + if (iffound) { + *ifpp = ifp; + IFNET_RUNLOCK(); + return(addrsofif); + } + + addrs += addrsofif; + } + IFNET_RUNLOCK(); + + return(addrs); +} + +static int +ni6_store_addrs(ni6, nni6, ifp0, resid) + struct icmp6_nodeinfo *ni6, *nni6; + struct ifnet *ifp0; + int resid; +{ + struct ifnet *ifp = ifp0 ? ifp0 : TAILQ_FIRST(&ifnet); + struct in6_ifaddr *ifa6; + struct ifaddr *ifa; + struct ifnet *ifp_dep = NULL; + int copied = 0, allow_deprecated = 0; + u_char *cp = (u_char *)(nni6 + 1); + int niflags = ni6->ni_flags; + u_int32_t ltime; + + if (ifp0 == NULL && !(niflags & NI_NODEADDR_FLAG_ALL)) + return(0); /* needless to copy */ + + IFNET_RLOCK(); + again: + for (; ifp; ifp = TAILQ_NEXT(ifp, if_list)) + { + for (ifa = ifp->if_addrlist.tqh_first; ifa; + ifa = ifa->ifa_list.tqe_next) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ifa6 = (struct in6_ifaddr *)ifa; + + if ((ifa6->ia6_flags & IN6_IFF_DEPRECATED) != 0 && + allow_deprecated == 0) { + /* + * prefererred address should be put before + * deprecated addresses. + */ + + /* record the interface for later search */ + if (ifp_dep == NULL) + ifp_dep = ifp; + + continue; + } + else if ((ifa6->ia6_flags & IN6_IFF_DEPRECATED) == 0 && + allow_deprecated != 0) + continue; /* we now collect deprecated addrs */ + + /* What do we have to do about ::1? */ + switch (in6_addrscope(&ifa6->ia_addr.sin6_addr)) { + case IPV6_ADDR_SCOPE_LINKLOCAL: + if ((niflags & NI_NODEADDR_FLAG_LINKLOCAL) == 0) + continue; + break; + case IPV6_ADDR_SCOPE_SITELOCAL: + if ((niflags & NI_NODEADDR_FLAG_SITELOCAL) == 0) + continue; + break; + case IPV6_ADDR_SCOPE_GLOBAL: + if ((niflags & NI_NODEADDR_FLAG_GLOBAL) == 0) + continue; + break; + default: + continue; + } + + /* + * check if anycast is okay. + * XXX: just experimental. not in the spec. + */ + if ((ifa6->ia6_flags & IN6_IFF_ANYCAST) != 0 && + (niflags & NI_NODEADDR_FLAG_ANYCAST) == 0) + continue; + if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0 && + (icmp6_nodeinfo & 4) == 0) { + continue; + } + + /* now we can copy the address */ + if (resid < sizeof(struct in6_addr) + + sizeof(u_int32_t)) { + /* + * We give up much more copy. + * Set the truncate flag and return. + */ + nni6->ni_flags |= + NI_NODEADDR_FLAG_TRUNCATE; + IFNET_RUNLOCK(); + return(copied); + } + + /* + * Set the TTL of the address. + * The TTL value should be one of the following + * according to the specification: + * + * 1. The remaining lifetime of a DHCP lease on the + * address, or + * 2. The remaining Valid Lifetime of a prefix from + * which the address was derived through Stateless + * Autoconfiguration. + * + * Note that we currently do not support stateful + * address configuration by DHCPv6, so the former + * case can't happen. + */ + if (ifa6->ia6_lifetime.ia6t_expire == 0) + ltime = ND6_INFINITE_LIFETIME; + else { + if (ifa6->ia6_lifetime.ia6t_expire > + time_second) + ltime = htonl(ifa6->ia6_lifetime.ia6t_expire - time_second); + else + ltime = 0; + } + + bcopy(<ime, cp, sizeof(u_int32_t)); + cp += sizeof(u_int32_t); + + /* copy the address itself */ + bcopy(&ifa6->ia_addr.sin6_addr, cp, + sizeof(struct in6_addr)); + /* XXX: KAME link-local hack; remove ifindex */ + if (IN6_IS_ADDR_LINKLOCAL(&ifa6->ia_addr.sin6_addr)) + ((struct in6_addr *)cp)->s6_addr16[1] = 0; + cp += sizeof(struct in6_addr); + + resid -= (sizeof(struct in6_addr) + sizeof(u_int32_t)); + copied += (sizeof(struct in6_addr) + + sizeof(u_int32_t)); + } + if (ifp0) /* we need search only on the specified IF */ + break; + } + + if (allow_deprecated == 0 && ifp_dep != NULL) { + ifp = ifp_dep; + allow_deprecated = 1; + + goto again; + } + + IFNET_RUNLOCK(); + + return(copied); +} + +/* + * XXX almost dup'ed code with rip6_input. + */ +static int +icmp6_rip6_input(mp, off) + struct mbuf **mp; + int off; +{ + struct mbuf *m = *mp; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct in6pcb *in6p; + struct in6pcb *last = NULL; + struct sockaddr_in6 rip6src; + struct icmp6_hdr *icmp6; + struct mbuf *opts = NULL; + +#ifndef PULLDOWN_TEST + /* this is assumed to be safe. */ + icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(icmp6, struct icmp6_hdr *, m, off, sizeof(*icmp6)); + if (icmp6 == NULL) { + /* m is already reclaimed */ + return IPPROTO_DONE; + } +#endif + + bzero(&rip6src, sizeof(rip6src)); + rip6src.sin6_len = sizeof(struct sockaddr_in6); + rip6src.sin6_family = AF_INET6; + /* KAME hack: recover scopeid */ + (void)in6_recoverscope(&rip6src, &ip6->ip6_src, m->m_pkthdr.rcvif); + + LIST_FOREACH(in6p, &ripcb, inp_list) + { + if ((in6p->inp_vflag & INP_IPV6) == 0) + continue; +#ifdef HAVE_NRL_INPCB + if (!(in6p->in6p_flags & INP_IPV6)) + continue; +#endif + if (in6p->in6p_ip6_nxt != IPPROTO_ICMPV6) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) && + !IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, &ip6->ip6_dst)) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) && + !IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src)) + continue; + if (in6p->in6p_icmp6filt + && ICMP6_FILTER_WILLBLOCK(icmp6->icmp6_type, + in6p->in6p_icmp6filt)) + continue; + if (last) { + struct mbuf *n; + if ((n = m_copy(m, 0, (int)M_COPYALL)) != NULL) { + if (last->in6p_flags & IN6P_CONTROLOPTS) + ip6_savecontrol(last, &opts, ip6, n); + /* strip intermediate headers */ + m_adj(n, off); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&rip6src, + n, opts) == 0) { + /* should notify about lost packet */ + m_freem(n); + if (opts) { + m_freem(opts); + } + } else + sorwakeup(last->in6p_socket); + opts = NULL; + } + } + last = in6p; + } + if (last) { + if (last->in6p_flags & IN6P_CONTROLOPTS) + ip6_savecontrol(last, &opts, ip6, m); + /* strip intermediate headers */ + m_adj(m, off); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&rip6src, m, opts) == 0) { + m_freem(m); + if (opts) + m_freem(opts); + } else + sorwakeup(last->in6p_socket); + } else { + m_freem(m); + ip6stat.ip6s_delivered--; + } + return IPPROTO_DONE; +} + +/* + * Reflect the ip6 packet back to the source. + * OFF points to the icmp6 header, counted from the top of the mbuf. + */ +void +icmp6_reflect(m, off) + struct mbuf *m; + size_t off; +{ + struct ip6_hdr *ip6; + struct icmp6_hdr *icmp6; + struct in6_ifaddr *ia; + struct in6_addr t, *src = 0; + int plen; + int type, code; + struct ifnet *outif = NULL; + struct sockaddr_in6 sa6_src, sa6_dst; +#ifdef COMPAT_RFC1885 + int mtu = IPV6_MMTU; + struct sockaddr_in6 *sin6 = &icmp6_reflect_rt.ro_dst; +#endif + + /* too short to reflect */ + if (off < sizeof(struct ip6_hdr)) { + nd6log((LOG_DEBUG, + "sanity fail: off=%lx, sizeof(ip6)=%lx in %s:%d\n", + (u_long)off, (u_long)sizeof(struct ip6_hdr), + __FILE__, __LINE__)); + goto bad; + } + + /* + * If there are extra headers between IPv6 and ICMPv6, strip + * off that header first. + */ +#ifdef DIAGNOSTIC + if (sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr) > MHLEN) + panic("assumption failed in icmp6_reflect"); +#endif + if (off > sizeof(struct ip6_hdr)) { + size_t l; + struct ip6_hdr nip6; + + l = off - sizeof(struct ip6_hdr); + m_copydata(m, 0, sizeof(nip6), (caddr_t)&nip6); + m_adj(m, l); + l = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr); + if (m->m_len < l) { + if ((m = m_pullup(m, l)) == NULL) + return; + } + bcopy((caddr_t)&nip6, mtod(m, caddr_t), sizeof(nip6)); + } else /* off == sizeof(struct ip6_hdr) */ { + size_t l; + l = sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr); + if (m->m_len < l) { + if ((m = m_pullup(m, l)) == NULL) + return; + } + } + plen = m->m_pkthdr.len - sizeof(struct ip6_hdr); + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_nxt = IPPROTO_ICMPV6; + icmp6 = (struct icmp6_hdr *)(ip6 + 1); + type = icmp6->icmp6_type; /* keep type for statistics */ + code = icmp6->icmp6_code; /* ditto. */ + + t = ip6->ip6_dst; + /* + * ip6_input() drops a packet if its src is multicast. + * So, the src is never multicast. + */ + ip6->ip6_dst = ip6->ip6_src; + + /* + * XXX: make sure to embed scope zone information, using + * already embedded IDs or the received interface (if any). + * Note that rcvif may be NULL. + * TODO: scoped routing case (XXX). + */ + bzero(&sa6_src, sizeof(sa6_src)); + sa6_src.sin6_family = AF_INET6; + sa6_src.sin6_len = sizeof(sa6_src); + sa6_src.sin6_addr = ip6->ip6_dst; + in6_recoverscope(&sa6_src, &ip6->ip6_dst, m->m_pkthdr.rcvif); + in6_embedscope(&ip6->ip6_dst, &sa6_src, NULL, NULL); + bzero(&sa6_dst, sizeof(sa6_dst)); + sa6_dst.sin6_family = AF_INET6; + sa6_dst.sin6_len = sizeof(sa6_dst); + sa6_dst.sin6_addr = t; + in6_recoverscope(&sa6_dst, &t, m->m_pkthdr.rcvif); + in6_embedscope(&t, &sa6_dst, NULL, NULL); + +#ifdef COMPAT_RFC1885 + /* + * xxx guess MTU + * RFC 1885 requires that echo reply should be truncated if it + * does not fit in with (return) path MTU, but the description was + * removed in the new spec. + */ + if (icmp6_reflect_rt.ro_rt == 0 || + ! (IN6_ARE_ADDR_EQUAL(&sin6->sin6_addr, &ip6->ip6_dst))) { + if (icmp6_reflect_rt.ro_rt) { + RTFREE(icmp6_reflect_rt.ro_rt); + icmp6_reflect_rt.ro_rt = 0; + } + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = PF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_addr = ip6->ip6_dst; + + rtalloc_ign((struct route *)&icmp6_reflect_rt.ro_rt, + RTF_PRCLONING); + } + + if (icmp6_reflect_rt.ro_rt == 0) + goto bad; + + if ((icmp6_reflect_rt.ro_rt->rt_flags & RTF_HOST) + && mtu < icmp6_reflect_rt.ro_rt->rt_ifp->if_mtu) + mtu = icmp6_reflect_rt.ro_rt->rt_rmx.rmx_mtu; + + if (mtu < m->m_pkthdr.len) { + plen -= (m->m_pkthdr.len - mtu); + m_adj(m, mtu - m->m_pkthdr.len); + } +#endif + /* + * If the incoming packet was addressed directly to us(i.e. unicast), + * use dst as the src for the reply. + * The IN6_IFF_NOTREADY case would be VERY rare, but is possible + * (for example) when we encounter an error while forwarding procedure + * destined to a duplicated address of ours. + */ + for (ia = in6_ifaddr; ia; ia = ia->ia_next) + if (IN6_ARE_ADDR_EQUAL(&t, &ia->ia_addr.sin6_addr) && + (ia->ia6_flags & (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY)) == 0) { + src = &t; + break; + } + if (ia == NULL && IN6_IS_ADDR_LINKLOCAL(&t) && (m->m_flags & M_LOOP)) { + /* + * This is the case if the dst is our link-local address + * and the sender is also ourselves. + */ + src = &t; + } + + if (src == 0) { + int e; + struct route_in6 ro; + + /* + * This case matches to multicasts, our anycast, or unicasts + * that we do not own. Select a source address based on the + * source address of the erroneous packet. + */ + bzero(&ro, sizeof(ro)); + src = in6_selectsrc(&sa6_src, NULL, NULL, &ro, NULL, &e); + if (ro.ro_rt) + RTFREE(ro.ro_rt); /* XXX: we could use this */ + if (src == NULL) { + nd6log((LOG_DEBUG, + "icmp6_reflect: source can't be determined: " + "dst=%s, error=%d\n", + ip6_sprintf(&sa6_src.sin6_addr), e)); + goto bad; + } + } + + ip6->ip6_src = *src; + + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + ip6->ip6_nxt = IPPROTO_ICMPV6; + if (m->m_pkthdr.rcvif) { + /* XXX: This may not be the outgoing interface */ + ip6->ip6_hlim = nd_ifinfo[m->m_pkthdr.rcvif->if_index].chlim; + } else + ip6->ip6_hlim = ip6_defhlim; + + icmp6->icmp6_cksum = 0; + icmp6->icmp6_cksum = in6_cksum(m, IPPROTO_ICMPV6, + sizeof(struct ip6_hdr), plen); + + /* + * XXX option handling + */ + + m->m_flags &= ~(M_BCAST|M_MCAST); + +#ifdef COMPAT_RFC1885 + ip6_output(m, NULL, &icmp6_reflect_rt, 0, NULL, &outif, NULL); +#else + ip6_output(m, NULL, NULL, 0, NULL, &outif, NULL); +#endif + if (outif) + icmp6_ifoutstat_inc(outif, type, code); + + return; + + bad: + m_freem(m); + return; +} + +void +icmp6_fasttimo() +{ + + mld6_fasttimeo(); +} + +static const char * +icmp6_redirect_diag(src6, dst6, tgt6) + struct in6_addr *src6; + struct in6_addr *dst6; + struct in6_addr *tgt6; +{ + static char buf[1024]; + snprintf(buf, sizeof(buf), "(src=%s dst=%s tgt=%s)", + ip6_sprintf(src6), ip6_sprintf(dst6), ip6_sprintf(tgt6)); + return buf; +} + +void +icmp6_redirect_input(m, off) + struct mbuf *m; + int off; +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct nd_redirect *nd_rd; + int icmp6len = ntohs(ip6->ip6_plen); + char *lladdr = NULL; + int lladdrlen = 0; + u_char *redirhdr = NULL; + int redirhdrlen = 0; + struct rtentry *rt = NULL; + int is_router; + int is_onlink; + struct in6_addr src6 = ip6->ip6_src; + struct in6_addr redtgt6; + struct in6_addr reddst6; + union nd_opts ndopts; + + if (!m || !ifp) + return; + + /* XXX if we are router, we don't update route by icmp6 redirect */ + if (ip6_forwarding) + goto freeit; + if (!icmp6_rediraccept) + goto freeit; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, icmp6len,); + nd_rd = (struct nd_redirect *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(nd_rd, struct nd_redirect *, m, off, icmp6len); + if (nd_rd == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + redtgt6 = nd_rd->nd_rd_target; + reddst6 = nd_rd->nd_rd_dst; + + if (IN6_IS_ADDR_LINKLOCAL(&redtgt6)) + redtgt6.s6_addr16[1] = htons(ifp->if_index); + if (IN6_IS_ADDR_LINKLOCAL(&reddst6)) + reddst6.s6_addr16[1] = htons(ifp->if_index); + + /* validation */ + if (!IN6_IS_ADDR_LINKLOCAL(&src6)) { + nd6log((LOG_ERR, + "ICMP6 redirect sent from %s rejected; " + "must be from linklocal\n", ip6_sprintf(&src6))); + goto bad; + } + if (ip6->ip6_hlim != 255) { + nd6log((LOG_ERR, + "ICMP6 redirect sent from %s rejected; " + "hlim=%d (must be 255)\n", + ip6_sprintf(&src6), ip6->ip6_hlim)); + goto bad; + } + { + /* ip6->ip6_src must be equal to gw for icmp6->icmp6_reddst */ + struct sockaddr_in6 sin6; + struct in6_addr *gw6; + + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&reddst6, &sin6.sin6_addr, sizeof(reddst6)); + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); + if (rt) { + if (rt->rt_gateway == NULL || + rt->rt_gateway->sa_family != AF_INET6) { + nd6log((LOG_ERR, + "ICMP6 redirect rejected; no route " + "with inet6 gateway found for redirect dst: %s\n", + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + RTFREE(rt); + goto bad; + } + + gw6 = &(((struct sockaddr_in6 *)rt->rt_gateway)->sin6_addr); + if (bcmp(&src6, gw6, sizeof(struct in6_addr)) != 0) { + nd6log((LOG_ERR, + "ICMP6 redirect rejected; " + "not equal to gw-for-src=%s (must be same): " + "%s\n", + ip6_sprintf(gw6), + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + RTFREE(rt); + goto bad; + } + } else { + nd6log((LOG_ERR, + "ICMP6 redirect rejected; " + "no route found for redirect dst: %s\n", + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + goto bad; + } + RTFREE(rt); + rt = NULL; + } + if (IN6_IS_ADDR_MULTICAST(&reddst6)) { + nd6log((LOG_ERR, + "ICMP6 redirect rejected; " + "redirect dst must be unicast: %s\n", + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + goto bad; + } + + is_router = is_onlink = 0; + if (IN6_IS_ADDR_LINKLOCAL(&redtgt6)) + is_router = 1; /* router case */ + if (bcmp(&redtgt6, &reddst6, sizeof(redtgt6)) == 0) + is_onlink = 1; /* on-link destination case */ + if (!is_router && !is_onlink) { + nd6log((LOG_ERR, + "ICMP6 redirect rejected; " + "neither router case nor onlink case: %s\n", + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + goto bad; + } + /* validation passed */ + + icmp6len -= sizeof(*nd_rd); + nd6_option_init(nd_rd + 1, icmp6len, &ndopts); + if (nd6_options(&ndopts) < 0) { + nd6log((LOG_INFO, "icmp6_redirect_input: " + "invalid ND option, rejected: %s\n", + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + /* nd6_options have incremented stats */ + goto freeit; + } + + if (ndopts.nd_opts_tgt_lladdr) { + lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1); + lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3; + } + + if (ndopts.nd_opts_rh) { + redirhdrlen = ndopts.nd_opts_rh->nd_opt_rh_len; + redirhdr = (u_char *)(ndopts.nd_opts_rh + 1); /* xxx */ + } + + if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) { + nd6log((LOG_INFO, + "icmp6_redirect_input: lladdrlen mismatch for %s " + "(if %d, icmp6 packet %d): %s\n", + ip6_sprintf(&redtgt6), ifp->if_addrlen, lladdrlen - 2, + icmp6_redirect_diag(&src6, &reddst6, &redtgt6))); + goto bad; + } + + /* RFC 2461 8.3 */ + nd6_cache_lladdr(ifp, &redtgt6, lladdr, lladdrlen, ND_REDIRECT, + is_onlink ? ND_REDIRECT_ONLINK : ND_REDIRECT_ROUTER); + + if (!is_onlink) { /* better router case. perform rtredirect. */ + /* perform rtredirect */ + struct sockaddr_in6 sdst; + struct sockaddr_in6 sgw; + struct sockaddr_in6 ssrc; + + bzero(&sdst, sizeof(sdst)); + bzero(&sgw, sizeof(sgw)); + bzero(&ssrc, sizeof(ssrc)); + sdst.sin6_family = sgw.sin6_family = ssrc.sin6_family = AF_INET6; + sdst.sin6_len = sgw.sin6_len = ssrc.sin6_len = + sizeof(struct sockaddr_in6); + bcopy(&redtgt6, &sgw.sin6_addr, sizeof(struct in6_addr)); + bcopy(&reddst6, &sdst.sin6_addr, sizeof(struct in6_addr)); + bcopy(&src6, &ssrc.sin6_addr, sizeof(struct in6_addr)); + rtredirect((struct sockaddr *)&sdst, (struct sockaddr *)&sgw, + (struct sockaddr *)NULL, RTF_GATEWAY | RTF_HOST, + (struct sockaddr *)&ssrc, + (struct rtentry **)NULL); + } + /* finally update cached route in each socket via pfctlinput */ + { + struct sockaddr_in6 sdst; + + bzero(&sdst, sizeof(sdst)); + sdst.sin6_family = AF_INET6; + sdst.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&reddst6, &sdst.sin6_addr, sizeof(struct in6_addr)); + pfctlinput(PRC_REDIRECT_HOST, (struct sockaddr *)&sdst); +#ifdef IPSEC + key_sa_routechange((struct sockaddr *)&sdst); +#endif + } + + freeit: + m_freem(m); + return; + + bad: + icmp6stat.icp6s_badredirect++; + m_freem(m); +} + +void +icmp6_redirect_output(m0, rt) + struct mbuf *m0; + struct rtentry *rt; +{ + struct ifnet *ifp; /* my outgoing interface */ + struct in6_addr *ifp_ll6; + struct in6_addr *router_ll6; + struct ip6_hdr *sip6; /* m0 as struct ip6_hdr */ + struct mbuf *m = NULL; /* newly allocated one */ + struct ip6_hdr *ip6; /* m as struct ip6_hdr */ + struct nd_redirect *nd_rd; + size_t maxlen; + u_char *p; + struct ifnet *outif = NULL; + struct sockaddr_in6 src_sa; + + icmp6_errcount(&icmp6stat.icp6s_outerrhist, ND_REDIRECT, 0); + + /* if we are not router, we don't send icmp6 redirect */ + if (!ip6_forwarding || ip6_accept_rtadv) + goto fail; + + /* sanity check */ + if (!m0 || !rt || !(rt->rt_flags & RTF_UP) || !(ifp = rt->rt_ifp)) + goto fail; + + /* + * Address check: + * the source address must identify a neighbor, and + * the destination address must not be a multicast address + * [RFC 2461, sec 8.2] + */ + sip6 = mtod(m0, struct ip6_hdr *); + bzero(&src_sa, sizeof(src_sa)); + src_sa.sin6_family = AF_INET6; + src_sa.sin6_len = sizeof(src_sa); + src_sa.sin6_addr = sip6->ip6_src; + /* we don't currently use sin6_scope_id, but eventually use it */ + src_sa.sin6_scope_id = in6_addr2scopeid(ifp, &sip6->ip6_src); + if (nd6_is_addr_neighbor(&src_sa, ifp) == 0) + goto fail; + if (IN6_IS_ADDR_MULTICAST(&sip6->ip6_dst)) + goto fail; /* what should we do here? */ + + /* rate limit */ + if (icmp6_ratelimit(&sip6->ip6_src, ND_REDIRECT, 0)) + goto fail; + + /* + * Since we are going to append up to 1280 bytes (= IPV6_MMTU), + * we almost always ask for an mbuf cluster for simplicity. + * (MHLEN < IPV6_MMTU is almost always true) + */ +#if IPV6_MMTU >= MCLBYTES +# error assumption failed about IPV6_MMTU and MCLBYTES +#endif + MGETHDR(m, M_DONTWAIT, MT_HEADER); + if (m && IPV6_MMTU >= MHLEN) + MCLGET(m, M_DONTWAIT); + if (!m) + goto fail; + m->m_pkthdr.rcvif = NULL; + m->m_len = 0; + maxlen = M_TRAILINGSPACE(m); + maxlen = min(IPV6_MMTU, maxlen); + /* just for safety */ + if (maxlen < sizeof(struct ip6_hdr) + sizeof(struct icmp6_hdr) + + ((sizeof(struct nd_opt_hdr) + ifp->if_addrlen + 7) & ~7)) { + goto fail; + } + + { + /* get ip6 linklocal address for ifp(my outgoing interface). */ + struct in6_ifaddr *ia; + if ((ia = in6ifa_ifpforlinklocal(ifp, + IN6_IFF_NOTREADY| + IN6_IFF_ANYCAST)) == NULL) + goto fail; + ifp_ll6 = &ia->ia_addr.sin6_addr; + } + + /* get ip6 linklocal address for the router. */ + if (rt->rt_gateway && (rt->rt_flags & RTF_GATEWAY)) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)rt->rt_gateway; + router_ll6 = &sin6->sin6_addr; + if (!IN6_IS_ADDR_LINKLOCAL(router_ll6)) + router_ll6 = (struct in6_addr *)NULL; + } else + router_ll6 = (struct in6_addr *)NULL; + + /* ip6 */ + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + /* ip6->ip6_plen will be set later */ + ip6->ip6_nxt = IPPROTO_ICMPV6; + ip6->ip6_hlim = 255; + /* ip6->ip6_src must be linklocal addr for my outgoing if. */ + bcopy(ifp_ll6, &ip6->ip6_src, sizeof(struct in6_addr)); + bcopy(&sip6->ip6_src, &ip6->ip6_dst, sizeof(struct in6_addr)); + + /* ND Redirect */ + nd_rd = (struct nd_redirect *)(ip6 + 1); + nd_rd->nd_rd_type = ND_REDIRECT; + nd_rd->nd_rd_code = 0; + nd_rd->nd_rd_reserved = 0; + if (rt->rt_flags & RTF_GATEWAY) { + /* + * nd_rd->nd_rd_target must be a link-local address in + * better router cases. + */ + if (!router_ll6) + goto fail; + bcopy(router_ll6, &nd_rd->nd_rd_target, + sizeof(nd_rd->nd_rd_target)); + bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_dst, + sizeof(nd_rd->nd_rd_dst)); + } else { + /* make sure redtgt == reddst */ + bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_target, + sizeof(nd_rd->nd_rd_target)); + bcopy(&sip6->ip6_dst, &nd_rd->nd_rd_dst, + sizeof(nd_rd->nd_rd_dst)); + } + + p = (u_char *)(nd_rd + 1); + + if (!router_ll6) + goto nolladdropt; + + { + /* target lladdr option */ + struct rtentry *rt_router = NULL; + int len; + struct sockaddr_dl *sdl; + struct nd_opt_hdr *nd_opt; + char *lladdr; + + rt_router = nd6_lookup(router_ll6, 0, ifp); + if (!rt_router) + goto nolladdropt; + len = sizeof(*nd_opt) + ifp->if_addrlen; + len = (len + 7) & ~7; /* round by 8 */ + /* safety check */ + if (len + (p - (u_char *)ip6) > maxlen) + goto nolladdropt; + if (!(rt_router->rt_flags & RTF_GATEWAY) && + (rt_router->rt_flags & RTF_LLINFO) && + (rt_router->rt_gateway->sa_family == AF_LINK) && + (sdl = (struct sockaddr_dl *)rt_router->rt_gateway) && + sdl->sdl_alen) { + nd_opt = (struct nd_opt_hdr *)p; + nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + nd_opt->nd_opt_len = len >> 3; + lladdr = (char *)(nd_opt + 1); + bcopy(LLADDR(sdl), lladdr, ifp->if_addrlen); + p += len; + } + } +nolladdropt:; + + m->m_pkthdr.len = m->m_len = p - (u_char *)ip6; + + /* just to be safe */ +#ifdef M_DECRYPTED /*not openbsd*/ + if (m0->m_flags & M_DECRYPTED) + goto noredhdropt; +#endif + if (p - (u_char *)ip6 > maxlen) + goto noredhdropt; + + { + /* redirected header option */ + int len; + struct nd_opt_rd_hdr *nd_opt_rh; + + /* + * compute the maximum size for icmp6 redirect header option. + * XXX room for auth header? + */ + len = maxlen - (p - (u_char *)ip6); + len &= ~7; + + /* This is just for simplicity. */ + if (m0->m_pkthdr.len != m0->m_len) { + if (m0->m_next) { + m_freem(m0->m_next); + m0->m_next = NULL; + } + m0->m_pkthdr.len = m0->m_len; + } + + /* + * Redirected header option spec (RFC2461 4.6.3) talks nothing + * about padding/truncate rule for the original IP packet. + * From the discussion on IPv6imp in Feb 1999, the consensus was: + * - "attach as much as possible" is the goal + * - pad if not aligned (original size can be guessed by original + * ip6 header) + * Following code adds the padding if it is simple enough, + * and truncates if not. + */ + if (m0->m_next || m0->m_pkthdr.len != m0->m_len) + panic("assumption failed in %s:%d", __FILE__, __LINE__); + + if (len - sizeof(*nd_opt_rh) < m0->m_pkthdr.len) { + /* not enough room, truncate */ + m0->m_pkthdr.len = m0->m_len = len - sizeof(*nd_opt_rh); + } else { + /* enough room, pad or truncate */ + size_t extra; + + extra = m0->m_pkthdr.len % 8; + if (extra) { + /* pad if easy enough, truncate if not */ + if (8 - extra <= M_TRAILINGSPACE(m0)) { + /* pad */ + m0->m_len += (8 - extra); + m0->m_pkthdr.len += (8 - extra); + } else { + /* truncate */ + m0->m_pkthdr.len -= extra; + m0->m_len -= extra; + } + } + len = m0->m_pkthdr.len + sizeof(*nd_opt_rh); + m0->m_pkthdr.len = m0->m_len = len - sizeof(*nd_opt_rh); + } + + nd_opt_rh = (struct nd_opt_rd_hdr *)p; + bzero(nd_opt_rh, sizeof(*nd_opt_rh)); + nd_opt_rh->nd_opt_rh_type = ND_OPT_REDIRECTED_HEADER; + nd_opt_rh->nd_opt_rh_len = len >> 3; + p += sizeof(*nd_opt_rh); + m->m_pkthdr.len = m->m_len = p - (u_char *)ip6; + + /* connect m0 to m */ + m_tag_delete_chain(m0, NULL); + m0->m_flags &= ~M_PKTHDR; + m->m_next = m0; + m->m_pkthdr.len = m->m_len + m0->m_len; + m0 = NULL; + } +noredhdropt:; + if (m0) { + m_freem(m0); + m0 = NULL; + } + + if (IN6_IS_ADDR_LINKLOCAL(&sip6->ip6_src)) + sip6->ip6_src.s6_addr16[1] = 0; + if (IN6_IS_ADDR_LINKLOCAL(&sip6->ip6_dst)) + sip6->ip6_dst.s6_addr16[1] = 0; +#if 0 + if (IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) + ip6->ip6_src.s6_addr16[1] = 0; + if (IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst)) + ip6->ip6_dst.s6_addr16[1] = 0; +#endif + if (IN6_IS_ADDR_LINKLOCAL(&nd_rd->nd_rd_target)) + nd_rd->nd_rd_target.s6_addr16[1] = 0; + if (IN6_IS_ADDR_LINKLOCAL(&nd_rd->nd_rd_dst)) + nd_rd->nd_rd_dst.s6_addr16[1] = 0; + + ip6->ip6_plen = htons(m->m_pkthdr.len - sizeof(struct ip6_hdr)); + + nd_rd->nd_rd_cksum = 0; + nd_rd->nd_rd_cksum + = in6_cksum(m, IPPROTO_ICMPV6, sizeof(*ip6), ntohs(ip6->ip6_plen)); + + /* send the packet to outside... */ + ip6_output(m, NULL, NULL, 0, NULL, &outif, NULL); + if (outif) { + icmp6_ifstat_inc(outif, ifs6_out_msg); + icmp6_ifstat_inc(outif, ifs6_out_redirect); + } + icmp6stat.icp6s_outhist[ND_REDIRECT]++; + + return; + +fail: + if (m) + m_freem(m); + if (m0) + m_freem(m0); +} + +#ifdef HAVE_NRL_INPCB +#define sotoin6pcb sotoinpcb +#define in6pcb inpcb +#define in6p_icmp6filt inp_icmp6filt +#endif +/* + * ICMPv6 socket option processing. + */ +int +icmp6_ctloutput(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + int error = 0; + int optlen; + struct inpcb *inp = sotoinpcb(so); + int level, op, optname; + + if (sopt) { + level = sopt->sopt_level; + op = sopt->sopt_dir; + optname = sopt->sopt_name; + optlen = sopt->sopt_valsize; + } else + level = op = optname = optlen = 0; + + if (level != IPPROTO_ICMPV6) { + return EINVAL; + } + + switch (op) { + case PRCO_SETOPT: + switch (optname) { + case ICMP6_FILTER: + { + struct icmp6_filter *p; + + if (optlen != sizeof(*p)) { + error = EMSGSIZE; + break; + } + if (inp->in6p_icmp6filt == NULL) { + error = EINVAL; + break; + } + error = sooptcopyin(sopt, inp->in6p_icmp6filt, optlen, + optlen); + break; + } + + default: + error = ENOPROTOOPT; + break; + } + break; + + case PRCO_GETOPT: + switch (optname) { + case ICMP6_FILTER: + { + if (inp->in6p_icmp6filt == NULL) { + error = EINVAL; + break; + } + error = sooptcopyout(sopt, inp->in6p_icmp6filt, + sizeof(struct icmp6_filter)); + break; + } + + default: + error = ENOPROTOOPT; + break; + } + break; + } + + return(error); +} +#ifdef HAVE_NRL_INPCB +#undef sotoin6pcb +#undef in6pcb +#undef in6p_icmp6filt +#endif + +#ifndef HAVE_PPSRATECHECK +#ifndef timersub +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) +#endif + +/* + * ppsratecheck(): packets (or events) per second limitation. + */ +static int +ppsratecheck(lasttime, curpps, maxpps) + struct timeval *lasttime; + int *curpps; + int maxpps; /* maximum pps allowed */ +{ + struct timeval tv, delta; + int s, rv; + + s = splclock(); + microtime(&tv); + splx(s); + + timersub(&tv, lasttime, &delta); + + /* + * Check for 0,0 so that the message will be seen at least once. + * If more than one second has passed since the last update of + * lasttime, reset the counter. + * + * We do increment *curpps even in *curpps < maxpps case, as some may + * try to use *curpps for stat purposes as well. + */ + if ((lasttime->tv_sec == 0 && lasttime->tv_usec == 0) || + delta.tv_sec >= 1) { + *lasttime = tv; + *curpps = 0; + rv = 1; + } else if (maxpps < 0) + rv = 1; + else if (*curpps < maxpps) + rv = 1; + else + rv = 0; + +#if 1 /* DIAGNOSTIC? */ + /* be careful about wrap-around */ + if (*curpps + 1 > *curpps) + *curpps = *curpps + 1; +#else + /* + * assume that there's not too many calls to this function. + * not sure if the assumption holds, as it depends on *caller's* + * behavior, not the behavior of this function. + * IMHO it is wrong to make assumption on the caller's behavior, + * so the above #if is #if 1, not #ifdef DIAGNOSTIC. + */ + *curpps = *curpps + 1; +#endif + + return (rv); +} +#endif + +/* + * Perform rate limit check. + * Returns 0 if it is okay to send the icmp6 packet. + * Returns 1 if the router SHOULD NOT send this icmp6 packet due to rate + * limitation. + * + * XXX per-destination/type check necessary? + */ +static int +icmp6_ratelimit(dst, type, code) + const struct in6_addr *dst; /* not used at this moment */ + const int type; /* not used at this moment */ + const int code; /* not used at this moment */ +{ + int ret; + + ret = 0; /* okay to send */ + + /* PPS limit */ + if (!ppsratecheck(&icmp6errppslim_last, &icmp6errpps_count, + icmp6errppslim)) { + /* The packet is subject to rate limit */ + ret++; + } + + return ret; +} diff --git a/sys/netinet6/icmp6.h b/sys/netinet6/icmp6.h new file mode 100644 index 0000000..a6414ef --- /dev/null +++ b/sys/netinet6/icmp6.h @@ -0,0 +1,4 @@ +/* $FreeBSD$ */ +/* $KAME: icmp6.h,v 1.17 2000/06/11 17:23:40 jinmei Exp $ */ + +#error "netinet6/icmp6.h is obsolete. use netinet/icmp6.h" diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c new file mode 100644 index 0000000..945ab65 --- /dev/null +++ b/sys/netinet6/in6.c @@ -0,0 +1,2455 @@ +/* $FreeBSD$ */ +/* $KAME: in6.c,v 1.259 2002/01/21 11:37:50 keiichi Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1991, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in.c 8.2 (Berkeley) 11/15/93 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sockio.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/route.h> +#include <net/if_dl.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#ifndef SCOPEDROUTING +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> +#endif + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/mld6_var.h> +#include <netinet6/ip6_mroute.h> +#include <netinet6/in6_ifattach.h> +#include <netinet6/scope6_var.h> +#ifndef SCOPEDROUTING +#include <netinet6/in6_pcb.h> +#endif + +#include <net/net_osdep.h> + +MALLOC_DEFINE(M_IPMADDR, "in6_multi", "internet multicast address"); + +/* + * Definitions of some costant IP6 addresses. + */ +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; +const struct in6_addr in6addr_nodelocal_allnodes = + IN6ADDR_NODELOCAL_ALLNODES_INIT; +const struct in6_addr in6addr_linklocal_allnodes = + IN6ADDR_LINKLOCAL_ALLNODES_INIT; +const struct in6_addr in6addr_linklocal_allrouters = + IN6ADDR_LINKLOCAL_ALLROUTERS_INIT; + +const struct in6_addr in6mask0 = IN6MASK0; +const struct in6_addr in6mask32 = IN6MASK32; +const struct in6_addr in6mask64 = IN6MASK64; +const struct in6_addr in6mask96 = IN6MASK96; +const struct in6_addr in6mask128 = IN6MASK128; + +const struct sockaddr_in6 sa6_any = {sizeof(sa6_any), AF_INET6, + 0, 0, IN6ADDR_ANY_INIT, 0}; + +static int in6_lifaddr_ioctl __P((struct socket *, u_long, caddr_t, + struct ifnet *, struct thread *)); +static int in6_ifinit __P((struct ifnet *, struct in6_ifaddr *, + struct sockaddr_in6 *, int)); +static void in6_unlink_ifa __P((struct in6_ifaddr *, struct ifnet *)); + +struct in6_multihead in6_multihead; /* XXX BSS initialization */ + +int (*faithprefix_p)(struct in6_addr *); + +/* + * Subroutine for in6_ifaddloop() and in6_ifremloop(). + * This routine does actual work. + */ +static void +in6_ifloop_request(int cmd, struct ifaddr *ifa) +{ + struct sockaddr_in6 all1_sa; + struct rtentry *nrt = NULL; + int e; + + bzero(&all1_sa, sizeof(all1_sa)); + all1_sa.sin6_family = AF_INET6; + all1_sa.sin6_len = sizeof(struct sockaddr_in6); + all1_sa.sin6_addr = in6mask128; + + /* + * We specify the address itself as the gateway, and set the + * RTF_LLINFO flag, so that the corresponding host route would have + * the flag, and thus applications that assume traditional behavior + * would be happy. Note that we assume the caller of the function + * (probably implicitly) set nd6_rtrequest() to ifa->ifa_rtrequest, + * which changes the outgoing interface to the loopback interface. + */ + e = rtrequest(cmd, ifa->ifa_addr, ifa->ifa_addr, + (struct sockaddr *)&all1_sa, + RTF_UP|RTF_HOST|RTF_LLINFO, &nrt); + if (e != 0) { + log(LOG_ERR, "in6_ifloop_request: " + "%s operation failed for %s (errno=%d)\n", + cmd == RTM_ADD ? "ADD" : "DELETE", + ip6_sprintf(&((struct in6_ifaddr *)ifa)->ia_addr.sin6_addr), + e); + } + + /* + * Make sure rt_ifa be equal to IFA, the second argument of the + * function. + * We need this because when we refer to rt_ifa->ia6_flags in + * ip6_input, we assume that the rt_ifa points to the address instead + * of the loopback address. + */ + if (cmd == RTM_ADD && nrt && ifa != nrt->rt_ifa) { + IFAFREE(nrt->rt_ifa); + IFAREF(ifa); + nrt->rt_ifa = ifa; + } + + /* + * Report the addition/removal of the address to the routing socket. + * XXX: since we called rtinit for a p2p interface with a destination, + * we end up reporting twice in such a case. Should we rather + * omit the second report? + */ + if (nrt) { + rt_newaddrmsg(cmd, ifa, e, nrt); + if (cmd == RTM_DELETE) { + RTFREE(nrt); + } else { + /* the cmd must be RTM_ADD here */ + nrt->rt_refcnt--; + } + } +} + +/* + * Add ownaddr as loopback rtentry. We previously add the route only if + * necessary (ex. on a p2p link). However, since we now manage addresses + * separately from prefixes, we should always add the route. We can't + * rely on the cloning mechanism from the corresponding interface route + * any more. + */ +static void +in6_ifaddloop(struct ifaddr *ifa) +{ + struct rtentry *rt; + + /* If there is no loopback entry, allocate one. */ + rt = rtalloc1(ifa->ifa_addr, 0, 0); + if (rt == NULL || (rt->rt_flags & RTF_HOST) == 0 || + (rt->rt_ifp->if_flags & IFF_LOOPBACK) == 0) + in6_ifloop_request(RTM_ADD, ifa); + if (rt) + rt->rt_refcnt--; +} + +/* + * Remove loopback rtentry of ownaddr generated by in6_ifaddloop(), + * if it exists. + */ +static void +in6_ifremloop(struct ifaddr *ifa) +{ + struct in6_ifaddr *ia; + struct rtentry *rt; + int ia_count = 0; + + /* + * Some of BSD variants do not remove cloned routes + * from an interface direct route, when removing the direct route + * (see comments in net/net_osdep.h). Even for variants that do remove + * cloned routes, they could fail to remove the cloned routes when + * we handle multple addresses that share a common prefix. + * So, we should remove the route corresponding to the deleted address + * regardless of the result of in6_is_ifloop_auto(). + */ + + /* + * Delete the entry only if exact one ifa exists. More than one ifa + * can exist if we assign a same single address to multiple + * (probably p2p) interfaces. + * XXX: we should avoid such a configuration in IPv6... + */ + for (ia = in6_ifaddr; ia; ia = ia->ia_next) { + if (IN6_ARE_ADDR_EQUAL(IFA_IN6(ifa), &ia->ia_addr.sin6_addr)) { + ia_count++; + if (ia_count > 1) + break; + } + } + + if (ia_count == 1) { + /* + * Before deleting, check if a corresponding loopbacked host + * route surely exists. With this check, we can avoid to + * delete an interface direct route whose destination is same + * as the address being removed. This can happen when remofing + * a subnet-router anycast address on an interface attahced + * to a shared medium. + */ + rt = rtalloc1(ifa->ifa_addr, 0, 0); + if (rt != NULL && (rt->rt_flags & RTF_HOST) != 0 && + (rt->rt_ifp->if_flags & IFF_LOOPBACK) != 0) { + rt->rt_refcnt--; + in6_ifloop_request(RTM_DELETE, ifa); + } + } +} + +int +in6_ifindex2scopeid(idx) + int idx; +{ + struct ifnet *ifp; + struct ifaddr *ifa; + struct sockaddr_in6 *sin6; + + if (idx < 0 || if_index < idx) + return -1; + ifp = ifnet_byindex(idx); + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) + return sin6->sin6_scope_id & 0xffff; + } + + return -1; +} + +int +in6_mask2len(mask, lim0) + struct in6_addr *mask; + u_char *lim0; +{ + int x = 0, y; + u_char *lim = lim0, *p; + + if (lim0 == NULL || + lim0 - (u_char *)mask > sizeof(*mask)) /* ignore the scope_id part */ + lim = (u_char *)mask + sizeof(*mask); + for (p = (u_char *)mask; p < lim; x++, p++) { + if (*p != 0xff) + break; + } + y = 0; + if (p < lim) { + for (y = 0; y < 8; y++) { + if ((*p & (0x80 >> y)) == 0) + break; + } + } + + /* + * when the limit pointer is given, do a stricter check on the + * remaining bits. + */ + if (p < lim) { + if (y != 0 && (*p & (0x00ff >> y)) != 0) + return(-1); + for (p = p + 1; p < lim; p++) + if (*p != 0) + return(-1); + } + + return x * 8 + y; +} + +void +in6_len2mask(mask, len) + struct in6_addr *mask; + int len; +{ + int i; + + bzero(mask, sizeof(*mask)); + for (i = 0; i < len / 8; i++) + mask->s6_addr8[i] = 0xff; + if (len % 8) + mask->s6_addr8[i] = (0xff00 >> (len % 8)) & 0xff; +} + +#define ifa2ia6(ifa) ((struct in6_ifaddr *)(ifa)) +#define ia62ifa(ia6) (&((ia6)->ia_ifa)) + +int +in6_control(so, cmd, data, ifp, td) + struct socket *so; + u_long cmd; + caddr_t data; + struct ifnet *ifp; + struct thread *td; +{ + struct in6_ifreq *ifr = (struct in6_ifreq *)data; + struct in6_ifaddr *ia = NULL; + struct in6_aliasreq *ifra = (struct in6_aliasreq *)data; + int privileged; + + privileged = 0; + if (td == NULL || !suser(td)) + privileged++; + + switch (cmd) { + case SIOCGETSGCNT_IN6: + case SIOCGETMIFCNT_IN6: + return (mrt6_ioctl(cmd, data)); + } + + if (ifp == NULL) + return(EOPNOTSUPP); + + switch (cmd) { + case SIOCSNDFLUSH_IN6: + case SIOCSPFXFLUSH_IN6: + case SIOCSRTRFLUSH_IN6: + case SIOCSDEFIFACE_IN6: + case SIOCSIFINFO_FLAGS: + if (!privileged) + return(EPERM); + /* fall through */ + case OSIOCGIFINFO_IN6: + case SIOCGIFINFO_IN6: + case SIOCGDRLST_IN6: + case SIOCGPRLST_IN6: + case SIOCGNBRINFO_IN6: + case SIOCGDEFIFACE_IN6: + return(nd6_ioctl(cmd, data, ifp)); + } + + switch (cmd) { + case SIOCSIFPREFIX_IN6: + case SIOCDIFPREFIX_IN6: + case SIOCAIFPREFIX_IN6: + case SIOCCIFPREFIX_IN6: + case SIOCSGIFPREFIX_IN6: + case SIOCGIFPREFIX_IN6: + log(LOG_NOTICE, + "prefix ioctls are now invalidated. " + "please use ifconfig.\n"); + return(EOPNOTSUPP); + } + + switch (cmd) { + case SIOCSSCOPE6: + if (!privileged) + return(EPERM); + return(scope6_set(ifp, ifr->ifr_ifru.ifru_scope_id)); + break; + case SIOCGSCOPE6: + return(scope6_get(ifp, ifr->ifr_ifru.ifru_scope_id)); + break; + case SIOCGSCOPE6DEF: + return(scope6_get_default(ifr->ifr_ifru.ifru_scope_id)); + break; + } + + switch (cmd) { + case SIOCALIFADDR: + case SIOCDLIFADDR: + if (!privileged) + return(EPERM); + /* fall through */ + case SIOCGLIFADDR: + return in6_lifaddr_ioctl(so, cmd, data, ifp, td); + } + + /* + * Find address for this interface, if it exists. + */ + if (ifra->ifra_addr.sin6_family == AF_INET6) { /* XXX */ + struct sockaddr_in6 *sa6 = + (struct sockaddr_in6 *)&ifra->ifra_addr; + + if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) { + if (sa6->sin6_addr.s6_addr16[1] == 0) { + /* link ID is not embedded by the user */ + sa6->sin6_addr.s6_addr16[1] = + htons(ifp->if_index); + } else if (sa6->sin6_addr.s6_addr16[1] != + htons(ifp->if_index)) { + return(EINVAL); /* link ID contradicts */ + } + if (sa6->sin6_scope_id) { + if (sa6->sin6_scope_id != + (u_int32_t)ifp->if_index) + return(EINVAL); + sa6->sin6_scope_id = 0; /* XXX: good way? */ + } + } + ia = in6ifa_ifpwithaddr(ifp, &ifra->ifra_addr.sin6_addr); + } + + switch (cmd) { + case SIOCSIFADDR_IN6: + case SIOCSIFDSTADDR_IN6: + case SIOCSIFNETMASK_IN6: + /* + * Since IPv6 allows a node to assign multiple addresses + * on a single interface, SIOCSIFxxx ioctls are not suitable + * and should be unused. + */ + /* we decided to obsolete this command (20000704) */ + return(EINVAL); + + case SIOCDIFADDR_IN6: + /* + * for IPv4, we look for existing in_ifaddr here to allow + * "ifconfig if0 delete" to remove first IPv4 address on the + * interface. For IPv6, as the spec allow multiple interface + * address from the day one, we consider "remove the first one" + * semantics to be not preferable. + */ + if (ia == NULL) + return(EADDRNOTAVAIL); + /* FALLTHROUGH */ + case SIOCAIFADDR_IN6: + /* + * We always require users to specify a valid IPv6 address for + * the corresponding operation. + */ + if (ifra->ifra_addr.sin6_family != AF_INET6 || + ifra->ifra_addr.sin6_len != sizeof(struct sockaddr_in6)) + return(EAFNOSUPPORT); + if (!privileged) + return(EPERM); + + break; + + case SIOCGIFADDR_IN6: + /* This interface is basically deprecated. use SIOCGIFCONF. */ + /* fall through */ + case SIOCGIFAFLAG_IN6: + case SIOCGIFNETMASK_IN6: + case SIOCGIFDSTADDR_IN6: + case SIOCGIFALIFETIME_IN6: + /* must think again about its semantics */ + if (ia == NULL) + return(EADDRNOTAVAIL); + break; + case SIOCSIFALIFETIME_IN6: + { + struct in6_addrlifetime *lt; + + if (!privileged) + return(EPERM); + if (ia == NULL) + return(EADDRNOTAVAIL); + /* sanity for overflow - beware unsigned */ + lt = &ifr->ifr_ifru.ifru_lifetime; + if (lt->ia6t_vltime != ND6_INFINITE_LIFETIME + && lt->ia6t_vltime + time_second < time_second) { + return EINVAL; + } + if (lt->ia6t_pltime != ND6_INFINITE_LIFETIME + && lt->ia6t_pltime + time_second < time_second) { + return EINVAL; + } + break; + } + } + + switch (cmd) { + + case SIOCGIFADDR_IN6: + ifr->ifr_addr = ia->ia_addr; + break; + + case SIOCGIFDSTADDR_IN6: + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + return(EINVAL); + /* + * XXX: should we check if ifa_dstaddr is NULL and return + * an error? + */ + ifr->ifr_dstaddr = ia->ia_dstaddr; + break; + + case SIOCGIFNETMASK_IN6: + ifr->ifr_addr = ia->ia_prefixmask; + break; + + case SIOCGIFAFLAG_IN6: + ifr->ifr_ifru.ifru_flags6 = ia->ia6_flags; + break; + + case SIOCGIFSTAT_IN6: + if (ifp == NULL) + return EINVAL; + if (in6_ifstat == NULL || ifp->if_index >= in6_ifstatmax + || in6_ifstat[ifp->if_index] == NULL) { + /* return EAFNOSUPPORT? */ + bzero(&ifr->ifr_ifru.ifru_stat, + sizeof(ifr->ifr_ifru.ifru_stat)); + } else + ifr->ifr_ifru.ifru_stat = *in6_ifstat[ifp->if_index]; + break; + + case SIOCGIFSTAT_ICMP6: + if (ifp == NULL) + return EINVAL; + if (icmp6_ifstat == NULL || ifp->if_index >= icmp6_ifstatmax || + icmp6_ifstat[ifp->if_index] == NULL) { + /* return EAFNOSUPPORT? */ + bzero(&ifr->ifr_ifru.ifru_stat, + sizeof(ifr->ifr_ifru.ifru_icmp6stat)); + } else + ifr->ifr_ifru.ifru_icmp6stat = + *icmp6_ifstat[ifp->if_index]; + break; + + case SIOCGIFALIFETIME_IN6: + ifr->ifr_ifru.ifru_lifetime = ia->ia6_lifetime; + break; + + case SIOCSIFALIFETIME_IN6: + ia->ia6_lifetime = ifr->ifr_ifru.ifru_lifetime; + /* for sanity */ + if (ia->ia6_lifetime.ia6t_vltime != ND6_INFINITE_LIFETIME) { + ia->ia6_lifetime.ia6t_expire = + time_second + ia->ia6_lifetime.ia6t_vltime; + } else + ia->ia6_lifetime.ia6t_expire = 0; + if (ia->ia6_lifetime.ia6t_pltime != ND6_INFINITE_LIFETIME) { + ia->ia6_lifetime.ia6t_preferred = + time_second + ia->ia6_lifetime.ia6t_pltime; + } else + ia->ia6_lifetime.ia6t_preferred = 0; + break; + + case SIOCAIFADDR_IN6: + { + int i, error = 0; + struct nd_prefix pr0, *pr; + + /* + * first, make or update the interface address structure, + * and link it to the list. + */ + if ((error = in6_update_ifa(ifp, ifra, ia)) != 0) + return(error); + + /* + * then, make the prefix on-link on the interface. + * XXX: we'd rather create the prefix before the address, but + * we need at least one address to install the corresponding + * interface route, so we configure the address first. + */ + + /* + * convert mask to prefix length (prefixmask has already + * been validated in in6_update_ifa(). + */ + bzero(&pr0, sizeof(pr0)); + pr0.ndpr_ifp = ifp; + pr0.ndpr_plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr, + NULL); + if (pr0.ndpr_plen == 128) + break; /* we don't need to install a host route. */ + pr0.ndpr_prefix = ifra->ifra_addr; + pr0.ndpr_mask = ifra->ifra_prefixmask.sin6_addr; + /* apply the mask for safety. */ + for (i = 0; i < 4; i++) { + pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= + ifra->ifra_prefixmask.sin6_addr.s6_addr32[i]; + } + /* + * XXX: since we don't have an API to set prefix (not address) + * lifetimes, we just use the same lifetimes as addresses. + * The (temporarily) installed lifetimes can be overridden by + * later advertised RAs (when accept_rtadv is non 0), which is + * an intended behavior. + */ + pr0.ndpr_raf_onlink = 1; /* should be configurable? */ + pr0.ndpr_raf_auto = + ((ifra->ifra_flags & IN6_IFF_AUTOCONF) != 0); + pr0.ndpr_vltime = ifra->ifra_lifetime.ia6t_vltime; + pr0.ndpr_pltime = ifra->ifra_lifetime.ia6t_pltime; + + /* add the prefix if there's one. */ + if ((pr = nd6_prefix_lookup(&pr0)) == NULL) { + /* + * nd6_prelist_add will install the corresponding + * interface route. + */ + if ((error = nd6_prelist_add(&pr0, NULL, &pr)) != 0) + return(error); + if (pr == NULL) { + log(LOG_ERR, "nd6_prelist_add succedded but " + "no prefix\n"); + return(EINVAL); /* XXX panic here? */ + } + } + if ((ia = in6ifa_ifpwithaddr(ifp, &ifra->ifra_addr.sin6_addr)) + == NULL) { + /* XXX: this should not happen! */ + log(LOG_ERR, "in6_control: addition succeeded, but" + " no ifaddr\n"); + } else { + if ((ia->ia6_flags & IN6_IFF_AUTOCONF) != 0 && + ia->ia6_ndpr == NULL) { /* new autoconfed addr */ + ia->ia6_ndpr = pr; + pr->ndpr_refcnt++; + + /* + * If this is the first autoconf address from + * the prefix, create a temporary address + * as well (when specified). + */ + if (ip6_use_tempaddr && + pr->ndpr_refcnt == 1) { + int e; + if ((e = in6_tmpifadd(ia, 1)) != 0) { + log(LOG_NOTICE, "in6_control: " + "failed to create a " + "temporary address, " + "errno=%d\n", + e); + } + } + } + + /* + * this might affect the status of autoconfigured + * addresses, that is, this address might make + * other addresses detached. + */ + pfxlist_onlink_check(); + } + break; + } + + case SIOCDIFADDR_IN6: + { + int i = 0; + struct nd_prefix pr0, *pr; + + /* + * If the address being deleted is the only one that owns + * the corresponding prefix, expire the prefix as well. + * XXX: theoretically, we don't have to warry about such + * relationship, since we separate the address management + * and the prefix management. We do this, however, to provide + * as much backward compatibility as possible in terms of + * the ioctl operation. + */ + bzero(&pr0, sizeof(pr0)); + pr0.ndpr_ifp = ifp; + pr0.ndpr_plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, + NULL); + if (pr0.ndpr_plen == 128) + goto purgeaddr; + pr0.ndpr_prefix = ia->ia_addr; + pr0.ndpr_mask = ia->ia_prefixmask.sin6_addr; + for (i = 0; i < 4; i++) { + pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= + ia->ia_prefixmask.sin6_addr.s6_addr32[i]; + } + /* + * The logic of the following condition is a bit complicated. + * We expire the prefix when + * 1. the address obeys autoconfiguration and it is the + * only owner of the associated prefix, or + * 2. the address does not obey autoconf and there is no + * other owner of the prefix. + */ + if ((pr = nd6_prefix_lookup(&pr0)) != NULL && + (((ia->ia6_flags & IN6_IFF_AUTOCONF) != 0 && + pr->ndpr_refcnt == 1) || + ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0 && + pr->ndpr_refcnt == 0))) { + pr->ndpr_expire = 1; /* XXX: just for expiration */ + } + + purgeaddr: + in6_purgeaddr(&ia->ia_ifa); + break; + } + + default: + if (ifp == NULL || ifp->if_ioctl == 0) + return(EOPNOTSUPP); + return((*ifp->if_ioctl)(ifp, cmd, data)); + } + + return(0); +} + +/* + * Update parameters of an IPv6 interface address. + * If necessary, a new entry is created and linked into address chains. + * This function is separated from in6_control(). + * XXX: should this be performed under splnet()? + */ +int +in6_update_ifa(ifp, ifra, ia) + struct ifnet *ifp; + struct in6_aliasreq *ifra; + struct in6_ifaddr *ia; +{ + int error = 0, hostIsNew = 0, plen = -1; + struct in6_ifaddr *oia; + struct sockaddr_in6 dst6; + struct in6_addrlifetime *lt; + + /* Validate parameters */ + if (ifp == NULL || ifra == NULL) /* this maybe redundant */ + return(EINVAL); + + /* + * The destination address for a p2p link must have a family + * of AF_UNSPEC or AF_INET6. + */ + if ((ifp->if_flags & IFF_POINTOPOINT) != 0 && + ifra->ifra_dstaddr.sin6_family != AF_INET6 && + ifra->ifra_dstaddr.sin6_family != AF_UNSPEC) + return(EAFNOSUPPORT); + /* + * validate ifra_prefixmask. don't check sin6_family, netmask + * does not carry fields other than sin6_len. + */ + if (ifra->ifra_prefixmask.sin6_len > sizeof(struct sockaddr_in6)) + return(EINVAL); + /* + * Because the IPv6 address architecture is classless, we require + * users to specify a (non 0) prefix length (mask) for a new address. + * We also require the prefix (when specified) mask is valid, and thus + * reject a non-consecutive mask. + */ + if (ia == NULL && ifra->ifra_prefixmask.sin6_len == 0) + return(EINVAL); + if (ifra->ifra_prefixmask.sin6_len != 0) { + plen = in6_mask2len(&ifra->ifra_prefixmask.sin6_addr, + (u_char *)&ifra->ifra_prefixmask + + ifra->ifra_prefixmask.sin6_len); + if (plen <= 0) + return(EINVAL); + } + else { + /* + * In this case, ia must not be NULL. We just use its prefix + * length. + */ + plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL); + } + /* + * If the destination address on a p2p interface is specified, + * and the address is a scoped one, validate/set the scope + * zone identifier. + */ + dst6 = ifra->ifra_dstaddr; + if ((ifp->if_flags & (IFF_POINTOPOINT|IFF_LOOPBACK)) && + (dst6.sin6_family == AF_INET6)) { + int scopeid; + +#ifndef SCOPEDROUTING + if ((error = in6_recoverscope(&dst6, + &ifra->ifra_dstaddr.sin6_addr, + ifp)) != 0) + return(error); +#endif + scopeid = in6_addr2scopeid(ifp, &dst6.sin6_addr); + if (dst6.sin6_scope_id == 0) /* user omit to specify the ID. */ + dst6.sin6_scope_id = scopeid; + else if (dst6.sin6_scope_id != scopeid) + return(EINVAL); /* scope ID mismatch. */ +#ifndef SCOPEDROUTING + if ((error = in6_embedscope(&dst6.sin6_addr, &dst6, NULL, NULL)) + != 0) + return(error); + dst6.sin6_scope_id = 0; /* XXX */ +#endif + } + /* + * The destination address can be specified only for a p2p or a + * loopback interface. If specified, the corresponding prefix length + * must be 128. + */ + if (ifra->ifra_dstaddr.sin6_family == AF_INET6) { + if ((ifp->if_flags & (IFF_POINTOPOINT|IFF_LOOPBACK)) == 0) { + /* XXX: noisy message */ + log(LOG_INFO, "in6_update_ifa: a destination can be " + "specified for a p2p or a loopback IF only\n"); + return(EINVAL); + } + if (plen != 128) { + /* + * The following message seems noisy, but we dare to + * add it for diagnosis. + */ + log(LOG_INFO, "in6_update_ifa: prefixlen must be 128 " + "when dstaddr is specified\n"); + return(EINVAL); + } + } + /* lifetime consistency check */ + lt = &ifra->ifra_lifetime; + if (lt->ia6t_vltime != ND6_INFINITE_LIFETIME + && lt->ia6t_vltime + time_second < time_second) { + return EINVAL; + } + if (lt->ia6t_vltime == 0) { + /* + * the following log might be noisy, but this is a typical + * configuration mistake or a tool's bug. + */ + log(LOG_INFO, + "in6_update_ifa: valid lifetime is 0 for %s\n", + ip6_sprintf(&ifra->ifra_addr.sin6_addr)); + } + if (lt->ia6t_pltime != ND6_INFINITE_LIFETIME + && lt->ia6t_pltime + time_second < time_second) { + return EINVAL; + } + + /* + * If this is a new address, allocate a new ifaddr and link it + * into chains. + */ + if (ia == NULL) { + hostIsNew = 1; + /* + * When in6_update_ifa() is called in a process of a received + * RA, it is called under splnet(). So, we should call malloc + * with M_NOWAIT. + */ + ia = (struct in6_ifaddr *) + malloc(sizeof(*ia), M_IFADDR, M_NOWAIT); + if (ia == NULL) + return (ENOBUFS); + bzero((caddr_t)ia, sizeof(*ia)); + /* Initialize the address and masks */ + IFA_LOCK_INIT(&ia->ia_ifa); + ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; + ia->ia_addr.sin6_family = AF_INET6; + ia->ia_addr.sin6_len = sizeof(ia->ia_addr); + if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) != 0) { + /* + * XXX: some functions expect that ifa_dstaddr is not + * NULL for p2p interfaces. + */ + ia->ia_ifa.ifa_dstaddr + = (struct sockaddr *)&ia->ia_dstaddr; + } else { + ia->ia_ifa.ifa_dstaddr = NULL; + } + ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; + + ia->ia_ifp = ifp; + if ((oia = in6_ifaddr) != NULL) { + for ( ; oia->ia_next; oia = oia->ia_next) + continue; + oia->ia_next = ia; + } else + in6_ifaddr = ia; + + ia->ia_ifa.ifa_refcnt = 1; + TAILQ_INSERT_TAIL(&ifp->if_addrlist, &ia->ia_ifa, ifa_list); + } + + /* set prefix mask */ + if (ifra->ifra_prefixmask.sin6_len) { + /* + * We prohibit changing the prefix length of an existing + * address, because + * + such an operation should be rare in IPv6, and + * + the operation would confuse prefix management. + */ + if (ia->ia_prefixmask.sin6_len && + in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL) != plen) { + log(LOG_INFO, "in6_update_ifa: the prefix length of an" + " existing (%s) address should not be changed\n", + ip6_sprintf(&ia->ia_addr.sin6_addr)); + error = EINVAL; + goto unlink; + } + ia->ia_prefixmask = ifra->ifra_prefixmask; + } + + /* + * If a new destination address is specified, scrub the old one and + * install the new destination. Note that the interface must be + * p2p or loopback (see the check above.) + */ + if (dst6.sin6_family == AF_INET6 && + !IN6_ARE_ADDR_EQUAL(&dst6.sin6_addr, + &ia->ia_dstaddr.sin6_addr)) { + int e; + + if ((ia->ia_flags & IFA_ROUTE) != 0 && + (e = rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST)) + != 0) { + log(LOG_ERR, "in6_update_ifa: failed to remove " + "a route to the old destination: %s\n", + ip6_sprintf(&ia->ia_addr.sin6_addr)); + /* proceed anyway... */ + } + else + ia->ia_flags &= ~IFA_ROUTE; + ia->ia_dstaddr = dst6; + } + + /* reset the interface and routing table appropriately. */ + if ((error = in6_ifinit(ifp, ia, &ifra->ifra_addr, hostIsNew)) != 0) + goto unlink; + + /* + * Beyond this point, we should call in6_purgeaddr upon an error, + * not just go to unlink. + */ + +#if 0 /* disable this mechanism for now */ + /* update prefix list */ + if (hostIsNew && + (ifra->ifra_flags & IN6_IFF_NOPFX) == 0) { /* XXX */ + int iilen; + + iilen = (sizeof(ia->ia_prefixmask.sin6_addr) << 3) - plen; + if ((error = in6_prefix_add_ifid(iilen, ia)) != 0) { + in6_purgeaddr((struct ifaddr *)ia); + return(error); + } + } +#endif + + if ((ifp->if_flags & IFF_MULTICAST) != 0) { + struct sockaddr_in6 mltaddr, mltmask; + struct in6_multi *in6m; + + if (hostIsNew) { + /* + * join solicited multicast addr for new host id + */ + struct in6_addr llsol; + bzero(&llsol, sizeof(struct in6_addr)); + llsol.s6_addr16[0] = htons(0xff02); + llsol.s6_addr16[1] = htons(ifp->if_index); + llsol.s6_addr32[1] = 0; + llsol.s6_addr32[2] = htonl(1); + llsol.s6_addr32[3] = + ifra->ifra_addr.sin6_addr.s6_addr32[3]; + llsol.s6_addr8[12] = 0xff; + (void)in6_addmulti(&llsol, ifp, &error); + if (error != 0) { + log(LOG_WARNING, + "in6_update_ifa: addmulti failed for " + "%s on %s (errno=%d)\n", + ip6_sprintf(&llsol), if_name(ifp), + error); + in6_purgeaddr((struct ifaddr *)ia); + return(error); + } + } + + bzero(&mltmask, sizeof(mltmask)); + mltmask.sin6_len = sizeof(struct sockaddr_in6); + mltmask.sin6_family = AF_INET6; + mltmask.sin6_addr = in6mask32; + + /* + * join link-local all-nodes address + */ + bzero(&mltaddr, sizeof(mltaddr)); + mltaddr.sin6_len = sizeof(struct sockaddr_in6); + mltaddr.sin6_family = AF_INET6; + mltaddr.sin6_addr = in6addr_linklocal_allnodes; + mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL) { + rtrequest(RTM_ADD, + (struct sockaddr *)&mltaddr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&mltmask, + RTF_UP|RTF_CLONING, /* xxx */ + (struct rtentry **)0); + (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); + if (error != 0) { + log(LOG_WARNING, + "in6_update_ifa: addmulti failed for " + "%s on %s (errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error); + } + } + + /* + * join node information group address + */ +#define hostnamelen strlen(hostname) + if (in6_nigroup(ifp, hostname, hostnamelen, &mltaddr.sin6_addr) + == 0) { + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL && ia != NULL) { + (void)in6_addmulti(&mltaddr.sin6_addr, + ifp, &error); + if (error != 0) { + log(LOG_WARNING, "in6_update_ifa: " + "addmulti failed for " + "%s on %s (errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error); + } + } + } +#undef hostnamelen + + /* + * join node-local all-nodes address, on loopback. + * XXX: since "node-local" is obsoleted by interface-local, + * we have to join the group on every interface with + * some interface-boundary restriction. + */ + if (ifp->if_flags & IFF_LOOPBACK) { + struct in6_ifaddr *ia_loop; + + struct in6_addr loop6 = in6addr_loopback; + ia_loop = in6ifa_ifpwithaddr(ifp, &loop6); + + mltaddr.sin6_addr = in6addr_nodelocal_allnodes; + + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL && ia_loop != NULL) { + rtrequest(RTM_ADD, + (struct sockaddr *)&mltaddr, + (struct sockaddr *)&ia_loop->ia_addr, + (struct sockaddr *)&mltmask, + RTF_UP, + (struct rtentry **)0); + (void)in6_addmulti(&mltaddr.sin6_addr, ifp, + &error); + if (error != 0) { + log(LOG_WARNING, "in6_update_ifa: " + "addmulti failed for %s on %s " + "(errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error); + } + } + } + } + + ia->ia6_flags = ifra->ifra_flags; + ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /*safety*/ + ia->ia6_flags &= ~IN6_IFF_NODAD; /* Mobile IPv6 */ + + ia->ia6_lifetime = ifra->ifra_lifetime; + /* for sanity */ + if (ia->ia6_lifetime.ia6t_vltime != ND6_INFINITE_LIFETIME) { + ia->ia6_lifetime.ia6t_expire = + time_second + ia->ia6_lifetime.ia6t_vltime; + } else + ia->ia6_lifetime.ia6t_expire = 0; + if (ia->ia6_lifetime.ia6t_pltime != ND6_INFINITE_LIFETIME) { + ia->ia6_lifetime.ia6t_preferred = + time_second + ia->ia6_lifetime.ia6t_pltime; + } else + ia->ia6_lifetime.ia6t_preferred = 0; + + /* + * make sure to initialize ND6 information. this is to workaround + * issues with interfaces with IPv6 addresses, which have never brought + * up. We are assuming that it is safe to nd6_ifattach multiple times. + */ + nd6_ifattach(ifp); + + /* + * Perform DAD, if needed. + * XXX It may be of use, if we can administratively + * disable DAD. + */ + if (in6if_do_dad(ifp) && (ifra->ifra_flags & IN6_IFF_NODAD) == 0) { + ia->ia6_flags |= IN6_IFF_TENTATIVE; + nd6_dad_start((struct ifaddr *)ia, NULL); + } + + return(error); + + unlink: + /* + * XXX: if a change of an existing address failed, keep the entry + * anyway. + */ + if (hostIsNew) + in6_unlink_ifa(ia, ifp); + return(error); +} + +void +in6_purgeaddr(ifa) + struct ifaddr *ifa; +{ + struct ifnet *ifp = ifa->ifa_ifp; + struct in6_ifaddr *ia = (struct in6_ifaddr *) ifa; + + /* stop DAD processing */ + nd6_dad_stop(ifa); + + /* + * delete route to the destination of the address being purged. + * The interface must be p2p or loopback in this case. + */ + if ((ia->ia_flags & IFA_ROUTE) != 0 && ia->ia_dstaddr.sin6_len != 0) { + int e; + + if ((e = rtinit(&(ia->ia_ifa), (int)RTM_DELETE, RTF_HOST)) + != 0) { + log(LOG_ERR, "in6_purgeaddr: failed to remove " + "a route to the p2p destination: %s on %s, " + "errno=%d\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), if_name(ifp), + e); + /* proceed anyway... */ + } + else + ia->ia_flags &= ~IFA_ROUTE; + } + + /* Remove ownaddr's loopback rtentry, if it exists. */ + in6_ifremloop(&(ia->ia_ifa)); + + if (ifp->if_flags & IFF_MULTICAST) { + /* + * delete solicited multicast addr for deleting host id + */ + struct in6_multi *in6m; + struct in6_addr llsol; + bzero(&llsol, sizeof(struct in6_addr)); + llsol.s6_addr16[0] = htons(0xff02); + llsol.s6_addr16[1] = htons(ifp->if_index); + llsol.s6_addr32[1] = 0; + llsol.s6_addr32[2] = htonl(1); + llsol.s6_addr32[3] = + ia->ia_addr.sin6_addr.s6_addr32[3]; + llsol.s6_addr8[12] = 0xff; + + IN6_LOOKUP_MULTI(llsol, ifp, in6m); + if (in6m) + in6_delmulti(in6m); + } + + in6_unlink_ifa(ia, ifp); +} + +static void +in6_unlink_ifa(ia, ifp) + struct in6_ifaddr *ia; + struct ifnet *ifp; +{ + int plen, iilen; + struct in6_ifaddr *oia; + int s = splnet(); + + TAILQ_REMOVE(&ifp->if_addrlist, &ia->ia_ifa, ifa_list); + + oia = ia; + if (oia == (ia = in6_ifaddr)) + in6_ifaddr = ia->ia_next; + else { + while (ia->ia_next && (ia->ia_next != oia)) + ia = ia->ia_next; + if (ia->ia_next) + ia->ia_next = oia->ia_next; + else { + /* search failed */ + printf("Couldn't unlink in6_ifaddr from in6_ifaddr\n"); + } + } + + if (oia->ia6_ifpr) { /* check for safety */ + plen = in6_mask2len(&oia->ia_prefixmask.sin6_addr, NULL); + iilen = (sizeof(oia->ia_prefixmask.sin6_addr) << 3) - plen; + in6_prefix_remove_ifid(iilen, oia); + } + + /* + * When an autoconfigured address is being removed, release the + * reference to the base prefix. Also, since the release might + * affect the status of other (detached) addresses, call + * pfxlist_onlink_check(). + */ + if ((oia->ia6_flags & IN6_IFF_AUTOCONF) != 0) { + if (oia->ia6_ndpr == NULL) { + log(LOG_NOTICE, "in6_unlink_ifa: autoconf'ed address " + "%p has no prefix\n", oia); + } else { + oia->ia6_ndpr->ndpr_refcnt--; + oia->ia6_flags &= ~IN6_IFF_AUTOCONF; + oia->ia6_ndpr = NULL; + } + + pfxlist_onlink_check(); + } + + /* + * release another refcnt for the link from in6_ifaddr. + * Note that we should decrement the refcnt at least once for all *BSD. + */ + IFAFREE(&oia->ia_ifa); + + splx(s); +} + +void +in6_purgeif(ifp) + struct ifnet *ifp; +{ + struct ifaddr *ifa, *nifa; + + for (ifa = TAILQ_FIRST(&ifp->if_addrlist); ifa != NULL; ifa = nifa) + { + nifa = TAILQ_NEXT(ifa, ifa_list); + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + in6_purgeaddr(ifa); + } + + in6_ifdetach(ifp); +} + +/* + * SIOC[GAD]LIFADDR. + * SIOCGLIFADDR: get first address. (?) + * SIOCGLIFADDR with IFLR_PREFIX: + * get first address that matches the specified prefix. + * SIOCALIFADDR: add the specified address. + * SIOCALIFADDR with IFLR_PREFIX: + * add the specified prefix, filling hostid part from + * the first link-local address. prefixlen must be <= 64. + * SIOCDLIFADDR: delete the specified address. + * SIOCDLIFADDR with IFLR_PREFIX: + * delete the first address that matches the specified prefix. + * return values: + * EINVAL on invalid parameters + * EADDRNOTAVAIL on prefix match failed/specified address not found + * other values may be returned from in6_ioctl() + * + * NOTE: SIOCALIFADDR(with IFLR_PREFIX set) allows prefixlen less than 64. + * this is to accomodate address naming scheme other than RFC2374, + * in the future. + * RFC2373 defines interface id to be 64bit, but it allows non-RFC2374 + * address encoding scheme. (see figure on page 8) + */ +static int +in6_lifaddr_ioctl(so, cmd, data, ifp, td) + struct socket *so; + u_long cmd; + caddr_t data; + struct ifnet *ifp; + struct thread *td; +{ + struct if_laddrreq *iflr = (struct if_laddrreq *)data; + struct ifaddr *ifa; + struct sockaddr *sa; + + /* sanity checks */ + if (!data || !ifp) { + panic("invalid argument to in6_lifaddr_ioctl"); + /*NOTRECHED*/ + } + + switch (cmd) { + case SIOCGLIFADDR: + /* address must be specified on GET with IFLR_PREFIX */ + if ((iflr->flags & IFLR_PREFIX) == 0) + break; + /* FALLTHROUGH */ + case SIOCALIFADDR: + case SIOCDLIFADDR: + /* address must be specified on ADD and DELETE */ + sa = (struct sockaddr *)&iflr->addr; + if (sa->sa_family != AF_INET6) + return EINVAL; + if (sa->sa_len != sizeof(struct sockaddr_in6)) + return EINVAL; + /* XXX need improvement */ + sa = (struct sockaddr *)&iflr->dstaddr; + if (sa->sa_family && sa->sa_family != AF_INET6) + return EINVAL; + if (sa->sa_len && sa->sa_len != sizeof(struct sockaddr_in6)) + return EINVAL; + break; + default: /* shouldn't happen */ +#if 0 + panic("invalid cmd to in6_lifaddr_ioctl"); + /* NOTREACHED */ +#else + return EOPNOTSUPP; +#endif + } + if (sizeof(struct in6_addr) * 8 < iflr->prefixlen) + return EINVAL; + + switch (cmd) { + case SIOCALIFADDR: + { + struct in6_aliasreq ifra; + struct in6_addr *hostid = NULL; + int prefixlen; + + if ((iflr->flags & IFLR_PREFIX) != 0) { + struct sockaddr_in6 *sin6; + + /* + * hostid is to fill in the hostid part of the + * address. hostid points to the first link-local + * address attached to the interface. + */ + ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, 0); + if (!ifa) + return EADDRNOTAVAIL; + hostid = IFA_IN6(ifa); + + /* prefixlen must be <= 64. */ + if (64 < iflr->prefixlen) + return EINVAL; + prefixlen = iflr->prefixlen; + + /* hostid part must be zero. */ + sin6 = (struct sockaddr_in6 *)&iflr->addr; + if (sin6->sin6_addr.s6_addr32[2] != 0 + || sin6->sin6_addr.s6_addr32[3] != 0) { + return EINVAL; + } + } else + prefixlen = iflr->prefixlen; + + /* copy args to in6_aliasreq, perform ioctl(SIOCAIFADDR_IN6). */ + bzero(&ifra, sizeof(ifra)); + bcopy(iflr->iflr_name, ifra.ifra_name, + sizeof(ifra.ifra_name)); + + bcopy(&iflr->addr, &ifra.ifra_addr, + ((struct sockaddr *)&iflr->addr)->sa_len); + if (hostid) { + /* fill in hostid part */ + ifra.ifra_addr.sin6_addr.s6_addr32[2] = + hostid->s6_addr32[2]; + ifra.ifra_addr.sin6_addr.s6_addr32[3] = + hostid->s6_addr32[3]; + } + + if (((struct sockaddr *)&iflr->dstaddr)->sa_family) { /*XXX*/ + bcopy(&iflr->dstaddr, &ifra.ifra_dstaddr, + ((struct sockaddr *)&iflr->dstaddr)->sa_len); + if (hostid) { + ifra.ifra_dstaddr.sin6_addr.s6_addr32[2] = + hostid->s6_addr32[2]; + ifra.ifra_dstaddr.sin6_addr.s6_addr32[3] = + hostid->s6_addr32[3]; + } + } + + ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + in6_len2mask(&ifra.ifra_prefixmask.sin6_addr, prefixlen); + + ifra.ifra_flags = iflr->flags & ~IFLR_PREFIX; + return in6_control(so, SIOCAIFADDR_IN6, (caddr_t)&ifra, ifp, td); + } + case SIOCGLIFADDR: + case SIOCDLIFADDR: + { + struct in6_ifaddr *ia; + struct in6_addr mask, candidate, match; + struct sockaddr_in6 *sin6; + int cmp; + + bzero(&mask, sizeof(mask)); + if (iflr->flags & IFLR_PREFIX) { + /* lookup a prefix rather than address. */ + in6_len2mask(&mask, iflr->prefixlen); + + sin6 = (struct sockaddr_in6 *)&iflr->addr; + bcopy(&sin6->sin6_addr, &match, sizeof(match)); + match.s6_addr32[0] &= mask.s6_addr32[0]; + match.s6_addr32[1] &= mask.s6_addr32[1]; + match.s6_addr32[2] &= mask.s6_addr32[2]; + match.s6_addr32[3] &= mask.s6_addr32[3]; + + /* if you set extra bits, that's wrong */ + if (bcmp(&match, &sin6->sin6_addr, sizeof(match))) + return EINVAL; + + cmp = 1; + } else { + if (cmd == SIOCGLIFADDR) { + /* on getting an address, take the 1st match */ + cmp = 0; /* XXX */ + } else { + /* on deleting an address, do exact match */ + in6_len2mask(&mask, 128); + sin6 = (struct sockaddr_in6 *)&iflr->addr; + bcopy(&sin6->sin6_addr, &match, sizeof(match)); + + cmp = 1; + } + } + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (!cmp) + break; + + bcopy(IFA_IN6(ifa), &candidate, sizeof(candidate)); +#ifndef SCOPEDROUTING + /* + * XXX: this is adhoc, but is necessary to allow + * a user to specify fe80::/64 (not /10) for a + * link-local address. + */ + if (IN6_IS_ADDR_LINKLOCAL(&candidate)) + candidate.s6_addr16[1] = 0; +#endif + candidate.s6_addr32[0] &= mask.s6_addr32[0]; + candidate.s6_addr32[1] &= mask.s6_addr32[1]; + candidate.s6_addr32[2] &= mask.s6_addr32[2]; + candidate.s6_addr32[3] &= mask.s6_addr32[3]; + if (IN6_ARE_ADDR_EQUAL(&candidate, &match)) + break; + } + if (!ifa) + return EADDRNOTAVAIL; + ia = ifa2ia6(ifa); + + if (cmd == SIOCGLIFADDR) { +#ifndef SCOPEDROUTING + struct sockaddr_in6 *s6; +#endif + + /* fill in the if_laddrreq structure */ + bcopy(&ia->ia_addr, &iflr->addr, ia->ia_addr.sin6_len); +#ifndef SCOPEDROUTING /* XXX see above */ + s6 = (struct sockaddr_in6 *)&iflr->addr; + if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) { + s6->sin6_addr.s6_addr16[1] = 0; + s6->sin6_scope_id = + in6_addr2scopeid(ifp, &s6->sin6_addr); + } +#endif + if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { + bcopy(&ia->ia_dstaddr, &iflr->dstaddr, + ia->ia_dstaddr.sin6_len); +#ifndef SCOPEDROUTING /* XXX see above */ + s6 = (struct sockaddr_in6 *)&iflr->dstaddr; + if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) { + s6->sin6_addr.s6_addr16[1] = 0; + s6->sin6_scope_id = + in6_addr2scopeid(ifp, + &s6->sin6_addr); + } +#endif + } else + bzero(&iflr->dstaddr, sizeof(iflr->dstaddr)); + + iflr->prefixlen = + in6_mask2len(&ia->ia_prefixmask.sin6_addr, + NULL); + + iflr->flags = ia->ia6_flags; /* XXX */ + + return 0; + } else { + struct in6_aliasreq ifra; + + /* fill in6_aliasreq and do ioctl(SIOCDIFADDR_IN6) */ + bzero(&ifra, sizeof(ifra)); + bcopy(iflr->iflr_name, ifra.ifra_name, + sizeof(ifra.ifra_name)); + + bcopy(&ia->ia_addr, &ifra.ifra_addr, + ia->ia_addr.sin6_len); + if ((ifp->if_flags & IFF_POINTOPOINT) != 0) { + bcopy(&ia->ia_dstaddr, &ifra.ifra_dstaddr, + ia->ia_dstaddr.sin6_len); + } else { + bzero(&ifra.ifra_dstaddr, + sizeof(ifra.ifra_dstaddr)); + } + bcopy(&ia->ia_prefixmask, &ifra.ifra_dstaddr, + ia->ia_prefixmask.sin6_len); + + ifra.ifra_flags = ia->ia6_flags; + return in6_control(so, SIOCDIFADDR_IN6, (caddr_t)&ifra, + ifp, td); + } + } + } + + return EOPNOTSUPP; /* just for safety */ +} + +/* + * Initialize an interface's intetnet6 address + * and routing table entry. + */ +static int +in6_ifinit(ifp, ia, sin6, newhost) + struct ifnet *ifp; + struct in6_ifaddr *ia; + struct sockaddr_in6 *sin6; + int newhost; +{ + int error = 0, plen, ifacount = 0; + int s = splimp(); + struct ifaddr *ifa; + + /* + * Give the interface a chance to initialize + * if this is its first address, + * and to validate the address if necessary. + */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr == NULL) + continue; /* just for safety */ + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ifacount++; + } + + ia->ia_addr = *sin6; + + if (ifacount <= 1 && ifp->if_ioctl && + (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia))) { + splx(s); + return(error); + } + splx(s); + + ia->ia_ifa.ifa_metric = ifp->if_metric; + + /* we could do in(6)_socktrim here, but just omit it at this moment. */ + + /* + * Special case: + * If the destination address is specified for a point-to-point + * interface, install a route to the destination as an interface + * direct route. + */ + plen = in6_mask2len(&ia->ia_prefixmask.sin6_addr, NULL); /* XXX */ + if (plen == 128 && ia->ia_dstaddr.sin6_family == AF_INET6) { + if ((error = rtinit(&(ia->ia_ifa), (int)RTM_ADD, + RTF_UP | RTF_HOST)) != 0) + return(error); + ia->ia_flags |= IFA_ROUTE; + } + if (plen < 128) { + /* + * The RTF_CLONING flag is necessary for in6_is_ifloop_auto(). + */ + ia->ia_ifa.ifa_flags |= RTF_CLONING; + } + + /* Add ownaddr as loopback rtentry, if necessary (ex. on p2p link). */ + if (newhost) { + /* set the rtrequest function to create llinfo */ + ia->ia_ifa.ifa_rtrequest = nd6_rtrequest; + in6_ifaddloop(&(ia->ia_ifa)); + } + + return(error); +} + +/* + * Add an address to the list of IP6 multicast addresses for a + * given interface. + */ +struct in6_multi * +in6_addmulti(maddr6, ifp, errorp) + struct in6_addr *maddr6; + struct ifnet *ifp; + int *errorp; +{ + struct in6_multi *in6m; + struct sockaddr_in6 sin6; + struct ifmultiaddr *ifma; + int s = splnet(); + + *errorp = 0; + + /* + * Call generic routine to add membership or increment + * refcount. It wants addresses in the form of a sockaddr, + * so we build one here (being careful to zero the unused bytes). + */ + bzero(&sin6, sizeof sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof sin6; + sin6.sin6_addr = *maddr6; + *errorp = if_addmulti(ifp, (struct sockaddr *)&sin6, &ifma); + if (*errorp) { + splx(s); + return 0; + } + + /* + * If ifma->ifma_protospec is null, then if_addmulti() created + * a new record. Otherwise, we are done. + */ + if (ifma->ifma_protospec != 0) + return ifma->ifma_protospec; + + /* XXX - if_addmulti uses M_WAITOK. Can this really be called + at interrupt time? If so, need to fix if_addmulti. XXX */ + in6m = (struct in6_multi *)malloc(sizeof(*in6m), M_IPMADDR, M_NOWAIT); + if (in6m == NULL) { + splx(s); + return (NULL); + } + + bzero(in6m, sizeof *in6m); + in6m->in6m_addr = *maddr6; + in6m->in6m_ifp = ifp; + in6m->in6m_ifma = ifma; + ifma->ifma_protospec = in6m; + LIST_INSERT_HEAD(&in6_multihead, in6m, in6m_entry); + + /* + * Let MLD6 know that we have joined a new IP6 multicast + * group. + */ + mld6_start_listening(in6m); + splx(s); + return(in6m); +} + +/* + * Delete a multicast address record. + */ +void +in6_delmulti(in6m) + struct in6_multi *in6m; +{ + struct ifmultiaddr *ifma = in6m->in6m_ifma; + int s = splnet(); + + if (ifma->ifma_refcount == 1) { + /* + * No remaining claims to this record; let MLD6 know + * that we are leaving the multicast group. + */ + mld6_stop_listening(in6m); + ifma->ifma_protospec = 0; + LIST_REMOVE(in6m, in6m_entry); + free(in6m, M_IPMADDR); + } + /* XXX - should be separate API for when we have an ifma? */ + if_delmulti(ifma->ifma_ifp, ifma->ifma_addr); + splx(s); +} + +/* + * Find an IPv6 interface link-local address specific to an interface. + */ +struct in6_ifaddr * +in6ifa_ifpforlinklocal(ifp, ignoreflags) + struct ifnet *ifp; + int ignoreflags; +{ + struct ifaddr *ifa; + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr == NULL) + continue; /* just for safety */ + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (IN6_IS_ADDR_LINKLOCAL(IFA_IN6(ifa))) { + if ((((struct in6_ifaddr *)ifa)->ia6_flags & + ignoreflags) != 0) + continue; + break; + } + } + + return((struct in6_ifaddr *)ifa); +} + + +/* + * find the internet address corresponding to a given interface and address. + */ +struct in6_ifaddr * +in6ifa_ifpwithaddr(ifp, addr) + struct ifnet *ifp; + struct in6_addr *addr; +{ + struct ifaddr *ifa; + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr == NULL) + continue; /* just for safety */ + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (IN6_ARE_ADDR_EQUAL(addr, IFA_IN6(ifa))) + break; + } + + return((struct in6_ifaddr *)ifa); +} + +/* + * Convert IP6 address to printable (loggable) representation. + */ +static char digits[] = "0123456789abcdef"; +static int ip6round = 0; +char * +ip6_sprintf(addr) + const struct in6_addr *addr; +{ + static char ip6buf[8][48]; + int i; + char *cp; + const u_short *a = (const u_short *)addr; + const u_char *d; + int dcolon = 0; + + ip6round = (ip6round + 1) & 7; + cp = ip6buf[ip6round]; + + for (i = 0; i < 8; i++) { + if (dcolon == 1) { + if (*a == 0) { + if (i == 7) + *cp++ = ':'; + a++; + continue; + } else + dcolon = 2; + } + if (*a == 0) { + if (dcolon == 0 && *(a + 1) == 0) { + if (i == 0) + *cp++ = ':'; + *cp++ = ':'; + dcolon = 1; + } else { + *cp++ = '0'; + *cp++ = ':'; + } + a++; + continue; + } + d = (const u_char *)a; + *cp++ = digits[*d >> 4]; + *cp++ = digits[*d++ & 0xf]; + *cp++ = digits[*d >> 4]; + *cp++ = digits[*d & 0xf]; + *cp++ = ':'; + a++; + } + *--cp = 0; + return(ip6buf[ip6round]); +} + +int +in6_localaddr(in6) + struct in6_addr *in6; +{ + struct in6_ifaddr *ia; + + if (IN6_IS_ADDR_LOOPBACK(in6) || IN6_IS_ADDR_LINKLOCAL(in6)) + return 1; + + for (ia = in6_ifaddr; ia; ia = ia->ia_next) + if (IN6_ARE_MASKED_ADDR_EQUAL(in6, &ia->ia_addr.sin6_addr, + &ia->ia_prefixmask.sin6_addr)) + return 1; + + return (0); +} + +int +in6_is_addr_deprecated(sa6) + struct sockaddr_in6 *sa6; +{ + struct in6_ifaddr *ia; + + for (ia = in6_ifaddr; ia; ia = ia->ia_next) { + if (IN6_ARE_ADDR_EQUAL(&ia->ia_addr.sin6_addr, + &sa6->sin6_addr) && +#ifdef SCOPEDROUTING + ia->ia_addr.sin6_scope_id == sa6->sin6_scope_id && +#endif + (ia->ia6_flags & IN6_IFF_DEPRECATED) != 0) + return(1); /* true */ + + /* XXX: do we still have to go thru the rest of the list? */ + } + + return(0); /* false */ +} + +/* + * return length of part which dst and src are equal + * hard coding... + */ +int +in6_matchlen(src, dst) +struct in6_addr *src, *dst; +{ + int match = 0; + u_char *s = (u_char *)src, *d = (u_char *)dst; + u_char *lim = s + 16, r; + + while (s < lim) + if ((r = (*d++ ^ *s++)) != 0) { + while (r < 128) { + match++; + r <<= 1; + } + break; + } else + match += 8; + return match; +} + +/* XXX: to be scope conscious */ +int +in6_are_prefix_equal(p1, p2, len) + struct in6_addr *p1, *p2; + int len; +{ + int bytelen, bitlen; + + /* sanity check */ + if (0 > len || len > 128) { + log(LOG_ERR, "in6_are_prefix_equal: invalid prefix length(%d)\n", + len); + return(0); + } + + bytelen = len / 8; + bitlen = len % 8; + + if (bcmp(&p1->s6_addr, &p2->s6_addr, bytelen)) + return(0); + if (p1->s6_addr[bytelen] >> (8 - bitlen) != + p2->s6_addr[bytelen] >> (8 - bitlen)) + return(0); + + return(1); +} + +void +in6_prefixlen2mask(maskp, len) + struct in6_addr *maskp; + int len; +{ + u_char maskarray[8] = {0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff}; + int bytelen, bitlen, i; + + /* sanity check */ + if (0 > len || len > 128) { + log(LOG_ERR, "in6_prefixlen2mask: invalid prefix length(%d)\n", + len); + return; + } + + bzero(maskp, sizeof(*maskp)); + bytelen = len / 8; + bitlen = len % 8; + for (i = 0; i < bytelen; i++) + maskp->s6_addr[i] = 0xff; + if (bitlen) + maskp->s6_addr[bytelen] = maskarray[bitlen - 1]; +} + +/* + * return the best address out of the same scope + */ +struct in6_ifaddr * +in6_ifawithscope(oifp, dst) + struct ifnet *oifp; + struct in6_addr *dst; +{ + int dst_scope = in6_addrscope(dst), src_scope, best_scope = 0; + int blen = -1; + struct ifaddr *ifa; + struct ifnet *ifp; + struct in6_ifaddr *ifa_best = NULL; + + if (oifp == NULL) { +#if 0 + printf("in6_ifawithscope: output interface is not specified\n"); +#endif + return(NULL); + } + + /* + * We search for all addresses on all interfaces from the beginning. + * Comparing an interface with the outgoing interface will be done + * only at the final stage of tiebreaking. + */ + IFNET_RLOCK(); + for (ifp = TAILQ_FIRST(&ifnet); ifp; ifp = TAILQ_NEXT(ifp, if_list)) + { + /* + * We can never take an address that breaks the scope zone + * of the destination. + */ + if (in6_addr2scopeid(ifp, dst) != in6_addr2scopeid(oifp, dst)) + continue; + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + int tlen = -1, dscopecmp, bscopecmp, matchcmp; + + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + src_scope = in6_addrscope(IFA_IN6(ifa)); + + /* + * Don't use an address before completing DAD + * nor a duplicated address. + */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & + IN6_IFF_NOTREADY) + continue; + + /* XXX: is there any case to allow anycasts? */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & + IN6_IFF_ANYCAST) + continue; + + if (((struct in6_ifaddr *)ifa)->ia6_flags & + IN6_IFF_DETACHED) + continue; + + /* + * If this is the first address we find, + * keep it anyway. + */ + if (ifa_best == NULL) + goto replace; + + /* + * ifa_best is never NULL beyond this line except + * within the block labeled "replace". + */ + + /* + * If ifa_best has a smaller scope than dst and + * the current address has a larger one than + * (or equal to) dst, always replace ifa_best. + * Also, if the current address has a smaller scope + * than dst, ignore it unless ifa_best also has a + * smaller scope. + * Consequently, after the two if-clause below, + * the followings must be satisfied: + * (scope(src) < scope(dst) && + * scope(best) < scope(dst)) + * OR + * (scope(best) >= scope(dst) && + * scope(src) >= scope(dst)) + */ + if (IN6_ARE_SCOPE_CMP(best_scope, dst_scope) < 0 && + IN6_ARE_SCOPE_CMP(src_scope, dst_scope) >= 0) + goto replace; /* (A) */ + if (IN6_ARE_SCOPE_CMP(src_scope, dst_scope) < 0 && + IN6_ARE_SCOPE_CMP(best_scope, dst_scope) >= 0) + continue; /* (B) */ + + /* + * A deprecated address SHOULD NOT be used in new + * communications if an alternate (non-deprecated) + * address is available and has sufficient scope. + * RFC 2462, Section 5.5.4. + */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & + IN6_IFF_DEPRECATED) { + /* + * Ignore any deprecated addresses if + * specified by configuration. + */ + if (!ip6_use_deprecated) + continue; + + /* + * If we have already found a non-deprecated + * candidate, just ignore deprecated addresses. + */ + if ((ifa_best->ia6_flags & IN6_IFF_DEPRECATED) + == 0) + continue; + } + + /* + * A non-deprecated address is always preferred + * to a deprecated one regardless of scopes and + * address matching (Note invariants ensured by the + * conditions (A) and (B) above.) + */ + if ((ifa_best->ia6_flags & IN6_IFF_DEPRECATED) && + (((struct in6_ifaddr *)ifa)->ia6_flags & + IN6_IFF_DEPRECATED) == 0) + goto replace; + + /* + * When we use temporary addresses described in + * RFC 3041, we prefer temporary addresses to + * public autoconf addresses. Again, note the + * invariants from (A) and (B). Also note that we + * don't have any preference between static addresses + * and autoconf addresses (despite of whether or not + * the latter is temporary or public.) + */ + if (ip6_use_tempaddr) { + struct in6_ifaddr *ifat; + + ifat = (struct in6_ifaddr *)ifa; + if ((ifa_best->ia6_flags & + (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY)) + == IN6_IFF_AUTOCONF && + (ifat->ia6_flags & + (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY)) + == (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY)) { + goto replace; + } + if ((ifa_best->ia6_flags & + (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY)) + == (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY) && + (ifat->ia6_flags & + (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY)) + == IN6_IFF_AUTOCONF) { + continue; + } + } + + /* + * At this point, we have two cases: + * 1. we are looking at a non-deprecated address, + * and ifa_best is also non-deprecated. + * 2. we are looking at a deprecated address, + * and ifa_best is also deprecated. + * Also, we do not have to consider a case where + * the scope of if_best is larger(smaller) than dst and + * the scope of the current address is smaller(larger) + * than dst. Such a case has already been covered. + * Tiebreaking is done according to the following + * items: + * - the scope comparison between the address and + * dst (dscopecmp) + * - the scope comparison between the address and + * ifa_best (bscopecmp) + * - if the address match dst longer than ifa_best + * (matchcmp) + * - if the address is on the outgoing I/F (outI/F) + * + * Roughly speaking, the selection policy is + * - the most important item is scope. The same scope + * is best. Then search for a larger scope. + * Smaller scopes are the last resort. + * - A deprecated address is chosen only when we have + * no address that has an enough scope, but is + * prefered to any addresses of smaller scopes + * (this must be already done above.) + * - addresses on the outgoing I/F are preferred to + * ones on other interfaces if none of above + * tiebreaks. In the table below, the column "bI" + * means if the best_ifa is on the outgoing + * interface, and the column "sI" means if the ifa + * is on the outgoing interface. + * - If there is no other reasons to choose one, + * longest address match against dst is considered. + * + * The precise decision table is as follows: + * dscopecmp bscopecmp match bI oI | replace? + * N/A equal N/A Y N | No (1) + * N/A equal N/A N Y | Yes (2) + * N/A equal larger N/A | Yes (3) + * N/A equal !larger N/A | No (4) + * larger larger N/A N/A | No (5) + * larger smaller N/A N/A | Yes (6) + * smaller larger N/A N/A | Yes (7) + * smaller smaller N/A N/A | No (8) + * equal smaller N/A N/A | Yes (9) + * equal larger (already done at A above) + */ + dscopecmp = IN6_ARE_SCOPE_CMP(src_scope, dst_scope); + bscopecmp = IN6_ARE_SCOPE_CMP(src_scope, best_scope); + + if (bscopecmp == 0) { + struct ifnet *bifp = ifa_best->ia_ifp; + + if (bifp == oifp && ifp != oifp) /* (1) */ + continue; + if (bifp != oifp && ifp == oifp) /* (2) */ + goto replace; + + /* + * Both bifp and ifp are on the outgoing + * interface, or both two are on a different + * interface from the outgoing I/F. + * now we need address matching against dst + * for tiebreaking. + */ + tlen = in6_matchlen(IFA_IN6(ifa), dst); + matchcmp = tlen - blen; + if (matchcmp > 0) /* (3) */ + goto replace; + continue; /* (4) */ + } + if (dscopecmp > 0) { + if (bscopecmp > 0) /* (5) */ + continue; + goto replace; /* (6) */ + } + if (dscopecmp < 0) { + if (bscopecmp > 0) /* (7) */ + goto replace; + continue; /* (8) */ + } + + /* now dscopecmp must be 0 */ + if (bscopecmp < 0) + goto replace; /* (9) */ + + replace: + ifa_best = (struct in6_ifaddr *)ifa; + blen = tlen >= 0 ? tlen : + in6_matchlen(IFA_IN6(ifa), dst); + best_scope = in6_addrscope(&ifa_best->ia_addr.sin6_addr); + } + } + IFNET_RUNLOCK(); + + /* count statistics for future improvements */ + if (ifa_best == NULL) + ip6stat.ip6s_sources_none++; + else { + if (oifp == ifa_best->ia_ifp) + ip6stat.ip6s_sources_sameif[best_scope]++; + else + ip6stat.ip6s_sources_otherif[best_scope]++; + + if (best_scope == dst_scope) + ip6stat.ip6s_sources_samescope[best_scope]++; + else + ip6stat.ip6s_sources_otherscope[best_scope]++; + + if ((ifa_best->ia6_flags & IN6_IFF_DEPRECATED) != 0) + ip6stat.ip6s_sources_deprecated[best_scope]++; + } + + return(ifa_best); +} + +/* + * return the best address out of the same scope. if no address was + * found, return the first valid address from designated IF. + */ +struct in6_ifaddr * +in6_ifawithifp(ifp, dst) + struct ifnet *ifp; + struct in6_addr *dst; +{ + int dst_scope = in6_addrscope(dst), blen = -1, tlen; + struct ifaddr *ifa; + struct in6_ifaddr *besta = 0; + struct in6_ifaddr *dep[2]; /* last-resort: deprecated */ + + dep[0] = dep[1] = NULL; + + /* + * We first look for addresses in the same scope. + * If there is one, return it. + * If two or more, return one which matches the dst longest. + * If none, return one of global addresses assigned other ifs. + */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_ANYCAST) + continue; /* XXX: is there any case to allow anycast? */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_NOTREADY) + continue; /* don't use this interface */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_DETACHED) + continue; + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_DEPRECATED) { + if (ip6_use_deprecated) + dep[0] = (struct in6_ifaddr *)ifa; + continue; + } + + if (dst_scope == in6_addrscope(IFA_IN6(ifa))) { + /* + * call in6_matchlen() as few as possible + */ + if (besta) { + if (blen == -1) + blen = in6_matchlen(&besta->ia_addr.sin6_addr, dst); + tlen = in6_matchlen(IFA_IN6(ifa), dst); + if (tlen > blen) { + blen = tlen; + besta = (struct in6_ifaddr *)ifa; + } + } else + besta = (struct in6_ifaddr *)ifa; + } + } + if (besta) + return(besta); + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_ANYCAST) + continue; /* XXX: is there any case to allow anycast? */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_NOTREADY) + continue; /* don't use this interface */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_DETACHED) + continue; + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_DEPRECATED) { + if (ip6_use_deprecated) + dep[1] = (struct in6_ifaddr *)ifa; + continue; + } + + return (struct in6_ifaddr *)ifa; + } + + /* use the last-resort values, that are, deprecated addresses */ + if (dep[0]) + return dep[0]; + if (dep[1]) + return dep[1]; + + return NULL; +} + +/* + * perform DAD when interface becomes IFF_UP. + */ +void +in6_if_up(ifp) + struct ifnet *ifp; +{ + struct ifaddr *ifa; + struct in6_ifaddr *ia; + int dad_delay; /* delay ticks before DAD output */ + + /* + * special cases, like 6to4, are handled in in6_ifattach + */ + in6_ifattach(ifp, NULL); + + dad_delay = 0; + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + ia = (struct in6_ifaddr *)ifa; + if (ia->ia6_flags & IN6_IFF_TENTATIVE) + nd6_dad_start(ifa, &dad_delay); + } +} + +int +in6if_do_dad(ifp) + struct ifnet *ifp; +{ + if ((ifp->if_flags & IFF_LOOPBACK) != 0) + return(0); + + switch (ifp->if_type) { +#ifdef IFT_DUMMY + case IFT_DUMMY: +#endif + case IFT_FAITH: + /* + * These interfaces do not have the IFF_LOOPBACK flag, + * but loop packets back. We do not have to do DAD on such + * interfaces. We should even omit it, because loop-backed + * NS would confuse the DAD procedure. + */ + return(0); + default: + /* + * Our DAD routine requires the interface up and running. + * However, some interfaces can be up before the RUNNING + * status. Additionaly, users may try to assign addresses + * before the interface becomes up (or running). + * We simply skip DAD in such a case as a work around. + * XXX: we should rather mark "tentative" on such addresses, + * and do DAD after the interface becomes ready. + */ + if ((ifp->if_flags & (IFF_UP|IFF_RUNNING)) != + (IFF_UP|IFF_RUNNING)) + return(0); + + return(1); + } +} + +/* + * Calculate max IPv6 MTU through all the interfaces and store it + * to in6_maxmtu. + */ +void +in6_setmaxmtu() +{ + unsigned long maxmtu = 0; + struct ifnet *ifp; + + IFNET_RLOCK(); + for (ifp = TAILQ_FIRST(&ifnet); ifp; ifp = TAILQ_NEXT(ifp, if_list)) + { + if ((ifp->if_flags & IFF_LOOPBACK) == 0 && + nd_ifinfo[ifp->if_index].linkmtu > maxmtu) + maxmtu = nd_ifinfo[ifp->if_index].linkmtu; + } + IFNET_RUNLOCK(); + if (maxmtu) /* update only when maxmtu is positive */ + in6_maxmtu = maxmtu; +} + +/* + * Convert sockaddr_in6 to sockaddr_in. Original sockaddr_in6 must be + * v4 mapped addr or v4 compat addr + */ +void +in6_sin6_2_sin(struct sockaddr_in *sin, struct sockaddr_in6 *sin6) +{ + bzero(sin, sizeof(*sin)); + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_port = sin6->sin6_port; + sin->sin_addr.s_addr = sin6->sin6_addr.s6_addr32[3]; +} + +/* Convert sockaddr_in to sockaddr_in6 in v4 mapped addr format. */ +void +in6_sin_2_v4mapsin6(struct sockaddr_in *sin, struct sockaddr_in6 *sin6) +{ + bzero(sin6, sizeof(*sin6)); + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = sin->sin_port; + sin6->sin6_addr.s6_addr32[0] = 0; + sin6->sin6_addr.s6_addr32[1] = 0; + sin6->sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_SMP; + sin6->sin6_addr.s6_addr32[3] = sin->sin_addr.s_addr; +} + +/* Convert sockaddr_in6 into sockaddr_in. */ +void +in6_sin6_2_sin_in_sock(struct sockaddr *nam) +{ + struct sockaddr_in *sin_p; + struct sockaddr_in6 sin6; + + /* + * Save original sockaddr_in6 addr and convert it + * to sockaddr_in. + */ + sin6 = *(struct sockaddr_in6 *)nam; + sin_p = (struct sockaddr_in *)nam; + in6_sin6_2_sin(sin_p, &sin6); +} + +/* Convert sockaddr_in into sockaddr_in6 in v4 mapped addr format. */ +void +in6_sin_2_v4mapsin6_in_sock(struct sockaddr **nam) +{ + struct sockaddr_in *sin_p; + struct sockaddr_in6 *sin6_p; + + MALLOC(sin6_p, struct sockaddr_in6 *, sizeof *sin6_p, M_SONAME, + M_WAITOK); + sin_p = (struct sockaddr_in *)*nam; + in6_sin_2_v4mapsin6(sin_p, sin6_p); + FREE(*nam, M_SONAME); + *nam = (struct sockaddr *)sin6_p; +} diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h new file mode 100644 index 0000000..a178371 --- /dev/null +++ b/sys/netinet6/in6.h @@ -0,0 +1,671 @@ +/* $FreeBSD$ */ +/* $KAME: in6.h,v 1.89 2001/05/27 13:28:35 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1990, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in.h 8.3 (Berkeley) 1/3/94 + */ + +#ifndef __KAME_NETINET_IN_H_INCLUDED_ +#error "do not include netinet6/in6.h directly, include netinet/in.h. see RFC2553" +#endif + +#ifndef _NETINET6_IN6_H_ +#define _NETINET6_IN6_H_ + +/* + * Identification of the network protocol stack + * for *BSD-current/release: http://www.kame.net/dev/cvsweb.cgi/kame/COVERAGE + * has the table of implementation/integration differences. + */ +#define __KAME__ +#define __KAME_VERSION "20010528/FreeBSD" + +/* + * Local port number conventions: + * + * Ports < IPPORT_RESERVED are reserved for privileged processes (e.g. root), + * unless a kernel is compiled with IPNOPRIVPORTS defined. + * + * When a user does a bind(2) or connect(2) with a port number of zero, + * a non-conflicting local port address is chosen. + * + * The default range is IPPORT_ANONMIN to IPPORT_ANONMAX, although + * that is settable by sysctl(3); net.inet.ip.anonportmin and + * net.inet.ip.anonportmax respectively. + * + * A user may set the IPPROTO_IP option IP_PORTRANGE to change this + * default assignment range. + * + * The value IP_PORTRANGE_DEFAULT causes the default behavior. + * + * The value IP_PORTRANGE_HIGH is the same as IP_PORTRANGE_DEFAULT, + * and exists only for FreeBSD compatibility purposes. + * + * The value IP_PORTRANGE_LOW changes the range to the "low" are + * that is (by convention) restricted to privileged processes. + * This convention is based on "vouchsafe" principles only. + * It is only secure if you trust the remote host to restrict these ports. + * The range is IPPORT_RESERVEDMIN to IPPORT_RESERVEDMAX. + */ +#if __BSD_VISIBLE +#define IPV6PORT_RESERVED 1024 +#define IPV6PORT_ANONMIN 49152 +#define IPV6PORT_ANONMAX 65535 +#define IPV6PORT_RESERVEDMIN 600 +#define IPV6PORT_RESERVEDMAX (IPV6PORT_RESERVED-1) +#endif + +/* + * IPv6 address + */ +struct in6_addr { + union { + uint8_t __u6_addr8[16]; + uint16_t __u6_addr16[8]; + uint32_t __u6_addr32[4]; + } __u6_addr; /* 128-bit IP6 address */ +}; + +#define s6_addr __u6_addr.__u6_addr8 +#ifdef _KERNEL /* XXX nonstandard */ +#define s6_addr8 __u6_addr.__u6_addr8 +#define s6_addr16 __u6_addr.__u6_addr16 +#define s6_addr32 __u6_addr.__u6_addr32 +#endif + +#define INET6_ADDRSTRLEN 46 + +/* + * XXX missing POSIX.1-2001 macro IPPROTO_IPV6. + */ + +/* + * Socket address for IPv6 + */ +#if __BSD_VISIBLE +#define SIN6_LEN +#endif + +struct sockaddr_in6 { + uint8_t sin6_len; /* length of this struct */ + sa_family_t sin6_family; /* AF_INET6 */ + in_port_t sin6_port; /* Transport layer port # */ + uint32_t sin6_flowinfo; /* IP6 flow information */ + struct in6_addr sin6_addr; /* IP6 address */ + uint32_t sin6_scope_id; /* scope zone index */ +}; + +/* + * Local definition for masks + */ +#ifdef _KERNEL /* XXX nonstandard */ +#define IN6MASK0 {{{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}} +#define IN6MASK32 {{{ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}} +#define IN6MASK64 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}} +#define IN6MASK96 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}} +#define IN6MASK128 {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, \ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}} +#endif + +#ifdef _KERNEL +extern const struct sockaddr_in6 sa6_any; + +extern const struct in6_addr in6mask0; +extern const struct in6_addr in6mask32; +extern const struct in6_addr in6mask64; +extern const struct in6_addr in6mask96; +extern const struct in6_addr in6mask128; +#endif /* _KERNEL */ + +/* + * Macros started with IPV6_ADDR is KAME local + */ +#ifdef _KERNEL /* XXX nonstandard */ +#if _BYTE_ORDER == _BIG_ENDIAN +#define IPV6_ADDR_INT32_ONE 1 +#define IPV6_ADDR_INT32_TWO 2 +#define IPV6_ADDR_INT32_MNL 0xff010000 +#define IPV6_ADDR_INT32_MLL 0xff020000 +#define IPV6_ADDR_INT32_SMP 0x0000ffff +#define IPV6_ADDR_INT16_ULL 0xfe80 +#define IPV6_ADDR_INT16_USL 0xfec0 +#define IPV6_ADDR_INT16_MLL 0xff02 +#elif _BYTE_ORDER == _LITTLE_ENDIAN +#define IPV6_ADDR_INT32_ONE 0x01000000 +#define IPV6_ADDR_INT32_TWO 0x02000000 +#define IPV6_ADDR_INT32_MNL 0x000001ff +#define IPV6_ADDR_INT32_MLL 0x000002ff +#define IPV6_ADDR_INT32_SMP 0xffff0000 +#define IPV6_ADDR_INT16_ULL 0x80fe +#define IPV6_ADDR_INT16_USL 0xc0fe +#define IPV6_ADDR_INT16_MLL 0x02ff +#endif +#endif + +/* + * Definition of some useful macros to handle IP6 addresses + */ +#if __BSD_VISIBLE +#define IN6ADDR_ANY_INIT \ + {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}} +#define IN6ADDR_LOOPBACK_INIT \ + {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}} +#define IN6ADDR_NODELOCAL_ALLNODES_INIT \ + {{{ 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}} +#define IN6ADDR_LINKLOCAL_ALLNODES_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}} +#define IN6ADDR_LINKLOCAL_ALLROUTERS_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}} +#define IN6ADDR_LINKLOCAL_ALLMDNS_INIT \ + {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb }}} +#endif + +extern const struct in6_addr in6addr_any; +extern const struct in6_addr in6addr_loopback; +#if __BSD_VISIBLE +extern const struct in6_addr in6addr_nodelocal_allnodes; +extern const struct in6_addr in6addr_linklocal_allnodes; +extern const struct in6_addr in6addr_linklocal_allrouters; +#endif + +/* + * Equality + * NOTE: Some of kernel programming environment (for example, openbsd/sparc) + * does not supply memcmp(). For userland memcmp() is preferred as it is + * in ANSI standard. + */ +#ifdef _KERNEL +#define IN6_ARE_ADDR_EQUAL(a, b) \ + (bcmp(&(a)->s6_addr[0], &(b)->s6_addr[0], sizeof(struct in6_addr)) == 0) +#else +#if __BSD_VISIBLE +#define IN6_ARE_ADDR_EQUAL(a, b) \ + (memcmp(&(a)->s6_addr[0], &(b)->s6_addr[0], sizeof(struct in6_addr)) == 0) +#endif +#endif + +#ifdef _KERNEL /* non standard */ +/* see if two addresses are equal in a scope-conscious manner. */ +#define SA6_ARE_ADDR_EQUAL(a, b) \ + (((a)->sin6_scope_id == 0 || (b)->sin6_scope_id == 0 || \ + ((a)->sin6_scope_id == (b)->sin6_scope_id)) && \ + (bcmp(&(a)->sin6_addr, &(b)->sin6_addr, sizeof(struct in6_addr)) == 0)) +#endif + +/* + * Unspecified + */ +#define IN6_IS_ADDR_UNSPECIFIED(a) \ + ((*(const u_int32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[8]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[12]) == 0)) + +/* + * Loopback + */ +#define IN6_IS_ADDR_LOOPBACK(a) \ + ((*(const u_int32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[8]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[12]) == ntohl(1))) + +/* + * IPv4 compatible + */ +#define IN6_IS_ADDR_V4COMPAT(a) \ + ((*(const u_int32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[8]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[12]) != 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[12]) != ntohl(1))) + +/* + * Mapped + */ +#define IN6_IS_ADDR_V4MAPPED(a) \ + ((*(const u_int32_t *)(const void *)(&(a)->s6_addr[0]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[4]) == 0) && \ + (*(const u_int32_t *)(const void *)(&(a)->s6_addr[8]) == ntohl(0x0000ffff))) + +/* + * KAME Scope Values + */ + +#ifdef _KERNEL /* XXX nonstandard */ +#define IPV6_ADDR_SCOPE_NODELOCAL 0x01 +#define IPV6_ADDR_SCOPE_LINKLOCAL 0x02 +#define IPV6_ADDR_SCOPE_SITELOCAL 0x05 +#define IPV6_ADDR_SCOPE_ORGLOCAL 0x08 /* just used in this file */ +#define IPV6_ADDR_SCOPE_GLOBAL 0x0e +#else +#define __IPV6_ADDR_SCOPE_NODELOCAL 0x01 +#define __IPV6_ADDR_SCOPE_LINKLOCAL 0x02 +#define __IPV6_ADDR_SCOPE_SITELOCAL 0x05 +#define __IPV6_ADDR_SCOPE_ORGLOCAL 0x08 /* just used in this file */ +#define __IPV6_ADDR_SCOPE_GLOBAL 0x0e +#endif + +/* + * Unicast Scope + * Note that we must check topmost 10 bits only, not 16 bits (see RFC2373). + */ +#define IN6_IS_ADDR_LINKLOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0x80)) +#define IN6_IS_ADDR_SITELOCAL(a) \ + (((a)->s6_addr[0] == 0xfe) && (((a)->s6_addr[1] & 0xc0) == 0xc0)) + +/* + * Multicast + */ +#define IN6_IS_ADDR_MULTICAST(a) ((a)->s6_addr[0] == 0xff) + +#ifdef _KERNEL /* XXX nonstandard */ +#define IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f) +#else +#define __IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f) +#endif + +/* + * Multicast Scope + */ +#ifdef _KERNEL /* refers nonstandard items */ +#define IN6_IS_ADDR_MC_NODELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (IPV6_ADDR_MC_SCOPE(a) == IPV6_ADDR_SCOPE_NODELOCAL)) +#define IN6_IS_ADDR_MC_LINKLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (IPV6_ADDR_MC_SCOPE(a) == IPV6_ADDR_SCOPE_LINKLOCAL)) +#define IN6_IS_ADDR_MC_SITELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (IPV6_ADDR_MC_SCOPE(a) == IPV6_ADDR_SCOPE_SITELOCAL)) +#define IN6_IS_ADDR_MC_ORGLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (IPV6_ADDR_MC_SCOPE(a) == IPV6_ADDR_SCOPE_ORGLOCAL)) +#define IN6_IS_ADDR_MC_GLOBAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (IPV6_ADDR_MC_SCOPE(a) == IPV6_ADDR_SCOPE_GLOBAL)) +#else +#define IN6_IS_ADDR_MC_NODELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_NODELOCAL)) +#define IN6_IS_ADDR_MC_LINKLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_LINKLOCAL)) +#define IN6_IS_ADDR_MC_SITELOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_SITELOCAL)) +#define IN6_IS_ADDR_MC_ORGLOCAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_ORGLOCAL)) +#define IN6_IS_ADDR_MC_GLOBAL(a) \ + (IN6_IS_ADDR_MULTICAST(a) && \ + (__IPV6_ADDR_MC_SCOPE(a) == __IPV6_ADDR_SCOPE_GLOBAL)) +#endif + +#ifdef _KERNEL /* nonstandard */ +/* + * KAME Scope + */ +#define IN6_IS_SCOPE_LINKLOCAL(a) \ + ((IN6_IS_ADDR_LINKLOCAL(a)) || \ + (IN6_IS_ADDR_MC_LINKLOCAL(a))) + +#define IFA6_IS_DEPRECATED(a) \ + ((a)->ia6_lifetime.ia6t_preferred != 0 && \ + (a)->ia6_lifetime.ia6t_preferred < time_second) +#define IFA6_IS_INVALID(a) \ + ((a)->ia6_lifetime.ia6t_expire != 0 && \ + (a)->ia6_lifetime.ia6t_expire < time_second) +#endif /* _KERNEL */ + +/* + * IP6 route structure + */ +#if __BSD_VISIBLE +struct route_in6 { + struct rtentry *ro_rt; + struct sockaddr_in6 ro_dst; +}; +#endif + +/* + * Options for use with [gs]etsockopt at the IPV6 level. + * First word of comment is data type; bool is stored in int. + */ +#define IPV6_JOIN_GROUP 12 /* ip6_mreq; join a group membership */ +#define IPV6_LEAVE_GROUP 13 /* ip6_mreq; leave a group membership */ +#define IPV6_MULTICAST_HOPS 10 /* int; set/get IP6 multicast hops */ +#define IPV6_MULTICAST_IF 9 /* u_int; set/get IP6 multicast i/f */ +#define IPV6_MULTICAST_LOOP 11 /* u_int; set/get IP6 multicast loopback */ +#define IPV6_UNICAST_HOPS 4 /* int; IP6 hops */ +#define IPV6_V6ONLY 27 /* bool; only bind INET6 at wildcard bind */ + +#if __BSD_VISIBLE +/* no hdrincl */ +#if 0 /* the followings are relic in IPv4 and hence are disabled */ +#define IPV6_OPTIONS 1 /* buf/ip6_opts; set/get IP6 options */ +#define IPV6_RECVOPTS 5 /* bool; receive all IP6 opts w/dgram */ +#define IPV6_RECVRETOPTS 6 /* bool; receive IP6 opts for response */ +#define IPV6_RECVDSTADDR 7 /* bool; receive IP6 dst addr w/dgram */ +#define IPV6_RETOPTS 8 /* ip6_opts; set/get IP6 options */ +#endif +#define IPV6_SOCKOPT_RESERVED1 3 /* reserved for future use */ +#define IPV6_PORTRANGE 14 /* int; range to choose for unspec port */ +#define ICMP6_FILTER 18 /* icmp6_filter; icmp6 filter */ +/* RFC2292 options */ +#define IPV6_PKTINFO 19 /* bool; send/recv if, src/dst addr */ +#define IPV6_HOPLIMIT 20 /* bool; hop limit */ +#define IPV6_NEXTHOP 21 /* bool; next hop addr */ +#define IPV6_HOPOPTS 22 /* bool; hop-by-hop option */ +#define IPV6_DSTOPTS 23 /* bool; destination option */ +#define IPV6_RTHDR 24 /* bool; routing header */ +#define IPV6_PKTOPTIONS 25 /* buf/cmsghdr; set/get IPv6 options */ + +#define IPV6_CHECKSUM 26 /* int; checksum offset for raw socket */ +#ifndef _KERNEL +#define IPV6_BINDV6ONLY IPV6_V6ONLY +#endif + +#if 1 /* IPSEC */ +#define IPV6_IPSEC_POLICY 28 /* struct; get/set security policy */ +#endif +#define IPV6_FAITH 29 /* bool; accept FAITH'ed connections */ + +#if 1 /* IPV6FIREWALL */ +#define IPV6_FW_ADD 30 /* add a firewall rule to chain */ +#define IPV6_FW_DEL 31 /* delete a firewall rule from chain */ +#define IPV6_FW_FLUSH 32 /* flush firewall rule chain */ +#define IPV6_FW_ZERO 33 /* clear single/all firewall counter(s) */ +#define IPV6_FW_GET 34 /* get entire firewall rule chain */ +#endif + +/* to define items, should talk with KAME guys first, for *BSD compatibility */ + +#define IPV6_RTHDR_LOOSE 0 /* this hop need not be a neighbor. XXX old spec */ +#define IPV6_RTHDR_STRICT 1 /* this hop must be a neighbor. XXX old spec */ +#define IPV6_RTHDR_TYPE_0 0 /* IPv6 routing header type 0 */ + +/* + * Defaults and limits for options + */ +#define IPV6_DEFAULT_MULTICAST_HOPS 1 /* normally limit m'casts to 1 hop */ +#define IPV6_DEFAULT_MULTICAST_LOOP 1 /* normally hear sends if a member */ + +/* + * Argument structure for IPV6_JOIN_GROUP and IPV6_LEAVE_GROUP. + */ +struct ipv6_mreq { + struct in6_addr ipv6mr_multiaddr; + unsigned int ipv6mr_interface; +}; + +/* + * IPV6_PKTINFO: Packet information(RFC2292 sec 5) + */ +struct in6_pktinfo { + struct in6_addr ipi6_addr; /* src/dst IPv6 address */ + unsigned int ipi6_ifindex; /* send/recv interface index */ +}; + +/* + * Argument for IPV6_PORTRANGE: + * - which range to search when port is unspecified at bind() or connect() + */ +#define IPV6_PORTRANGE_DEFAULT 0 /* default range */ +#define IPV6_PORTRANGE_HIGH 1 /* "high" - request firewall bypass */ +#define IPV6_PORTRANGE_LOW 2 /* "low" - vouchsafe security */ + +/* + * Definitions for inet6 sysctl operations. + * + * Third level is protocol number. + * Fourth level is desired variable within that protocol. + */ +#define IPV6PROTO_MAXID (IPPROTO_PIM + 1) /* don't list to IPV6PROTO_MAX */ + +#define CTL_IPV6PROTO_NAMES { \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, \ + { "tcp6", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { "udp6", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, \ + { "ip6", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, \ + { "ipsec6", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { "icmp6", CTLTYPE_NODE }, \ + { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { "pim6", CTLTYPE_NODE }, \ +} + +/* + * Names for IP sysctl objects + */ +#define IPV6CTL_FORWARDING 1 /* act as router */ +#define IPV6CTL_SENDREDIRECTS 2 /* may send redirects when forwarding*/ +#define IPV6CTL_DEFHLIM 3 /* default Hop-Limit */ +#ifdef notyet +#define IPV6CTL_DEFMTU 4 /* default MTU */ +#endif +#define IPV6CTL_FORWSRCRT 5 /* forward source-routed dgrams */ +#define IPV6CTL_STATS 6 /* stats */ +#define IPV6CTL_MRTSTATS 7 /* multicast forwarding stats */ +#define IPV6CTL_MRTPROTO 8 /* multicast routing protocol */ +#define IPV6CTL_MAXFRAGPACKETS 9 /* max packets reassembly queue */ +#define IPV6CTL_SOURCECHECK 10 /* verify source route and intf */ +#define IPV6CTL_SOURCECHECK_LOGINT 11 /* minimume logging interval */ +#define IPV6CTL_ACCEPT_RTADV 12 +#define IPV6CTL_KEEPFAITH 13 +#define IPV6CTL_LOG_INTERVAL 14 +#define IPV6CTL_HDRNESTLIMIT 15 +#define IPV6CTL_DAD_COUNT 16 +#define IPV6CTL_AUTO_FLOWLABEL 17 +#define IPV6CTL_DEFMCASTHLIM 18 +#define IPV6CTL_GIF_HLIM 19 /* default HLIM for gif encap packet */ +#define IPV6CTL_KAME_VERSION 20 +#define IPV6CTL_USE_DEPRECATED 21 /* use deprecated addr (RFC2462 5.5.4) */ +#define IPV6CTL_RR_PRUNE 22 /* walk timer for router renumbering */ +#if 0 /* obsolete */ +#define IPV6CTL_MAPPED_ADDR 23 +#endif +#define IPV6CTL_V6ONLY 24 +#define IPV6CTL_RTEXPIRE 25 /* cloned route expiration time */ +#define IPV6CTL_RTMINEXPIRE 26 /* min value for expiration time */ +#define IPV6CTL_RTMAXCACHE 27 /* trigger level for dynamic expire */ + +#define IPV6CTL_USETEMPADDR 32 /* use temporary addresses (RFC3041) */ +#define IPV6CTL_TEMPPLTIME 33 /* preferred lifetime for tmpaddrs */ +#define IPV6CTL_TEMPVLTIME 34 /* valid lifetime for tmpaddrs */ +#define IPV6CTL_AUTO_LINKLOCAL 35 /* automatic link-local addr assign */ +#define IPV6CTL_RIP6STATS 36 /* raw_ip6 stats */ + +/* New entries should be added here from current IPV6CTL_MAXID value. */ +/* to define items, should talk with KAME guys first, for *BSD compatibility */ +#define IPV6CTL_MAXID 37 + +/* + * Redefinition of mbuf flags + */ +#define M_AUTHIPHDR M_PROTO2 +#define M_DECRYPTED M_PROTO3 +#define M_LOOP M_PROTO4 +#define M_AUTHIPDGM M_PROTO5 + +#ifdef _KERNEL +struct cmsghdr; + +int in6_cksum __P((struct mbuf *, u_int8_t, u_int32_t, u_int32_t)); +int in6_localaddr __P((struct in6_addr *)); +int in6_addrscope __P((struct in6_addr *)); +struct in6_ifaddr *in6_ifawithscope __P((struct ifnet *, struct in6_addr *)); +struct in6_ifaddr *in6_ifawithifp __P((struct ifnet *, struct in6_addr *)); +extern void in6_if_up __P((struct ifnet *)); +struct sockaddr; +extern u_char ip6_protox[]; + +void in6_sin6_2_sin __P((struct sockaddr_in *sin, + struct sockaddr_in6 *sin6)); +void in6_sin_2_v4mapsin6 __P((struct sockaddr_in *sin, + struct sockaddr_in6 *sin6)); +void in6_sin6_2_sin_in_sock __P((struct sockaddr *nam)); +void in6_sin_2_v4mapsin6_in_sock __P((struct sockaddr **nam)); + +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) +#define sin6tosa(sin6) ((struct sockaddr *)(sin6)) +#define ifatoia6(ifa) ((struct in6_ifaddr *)(ifa)) + +extern int (*faithprefix_p)(struct in6_addr *); +#endif /* _KERNEL */ + +#ifndef _SIZE_T_DECLARED +typedef __size_t size_t; +#define _SIZE_T_DECLARED +#endif + +__BEGIN_DECLS +struct cmsghdr; + +extern int inet6_option_space __P((int)); +extern int inet6_option_init __P((void *, struct cmsghdr **, int)); +extern int inet6_option_append __P((struct cmsghdr *, const uint8_t *, + int, int)); +extern uint8_t *inet6_option_alloc __P((struct cmsghdr *, int, int, int)); +extern int inet6_option_next __P((const struct cmsghdr *, uint8_t **)); +extern int inet6_option_find __P((const struct cmsghdr *, uint8_t **, int)); + +extern size_t inet6_rthdr_space __P((int, int)); +extern struct cmsghdr *inet6_rthdr_init __P((void *, int)); +extern int inet6_rthdr_add __P((struct cmsghdr *, const struct in6_addr *, + unsigned int)); +extern int inet6_rthdr_lasthop __P((struct cmsghdr *, unsigned int)); +#if 0 /* not implemented yet */ +extern int inet6_rthdr_reverse __P((const struct cmsghdr *, struct cmsghdr *)); +#endif +extern int inet6_rthdr_segments __P((const struct cmsghdr *)); +extern struct in6_addr *inet6_rthdr_getaddr __P((struct cmsghdr *, int)); +extern int inet6_rthdr_getflags __P((const struct cmsghdr *, int)); + +extern int inet6_opt_init __P((void *, size_t)); +extern int inet6_opt_append __P((void *, size_t, int, uint8_t, + size_t, uint8_t, void **)); +extern int inet6_opt_finish __P((void *, size_t, int)); +extern int inet6_opt_set_val __P((void *, size_t, void *, int)); + +extern int inet6_opt_next __P((void *, size_t, int, uint8_t *, + size_t *, void **)); +extern int inet6_opt_find __P((void *, size_t, int, uint8_t, + size_t *, void **)); +extern int inet6_opt_get_val __P((void *, size_t, void *, int)); +extern size_t inet6_rth_space __P((int, int)); +extern void *inet6_rth_init __P((void *, int, int, int)); +extern int inet6_rth_add __P((void *, const struct in6_addr *)); +extern int inet6_rth_reverse __P((const void *, void *)); +extern int inet6_rth_segments __P((const void *)); +extern struct in6_addr *inet6_rth_getaddr __P((const void *, int)); +__END_DECLS + +#endif /* __BSD_VISIBLE */ + +#endif /* !_NETINET6_IN6_H_ */ diff --git a/sys/netinet6/in6_cksum.c b/sys/netinet6/in6_cksum.c new file mode 100644 index 0000000..049c60d --- /dev/null +++ b/sys/netinet6/in6_cksum.c @@ -0,0 +1,321 @@ +/* $FreeBSD$ */ +/* $KAME: in6_cksum.c,v 1.10 2000/12/03 00:53:59 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1988, 1992, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_cksum.c 8.1 (Berkeley) 6/10/93 + */ + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/systm.h> +#include <netinet/in.h> +#include <netinet/ip6.h> + +#include <net/net_osdep.h> + +/* + * Checksum routine for Internet Protocol family headers (Portable Version). + * + * This routine is very heavily used in the network + * code and should be modified for each CPU to be as fast as possible. + */ + +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE {l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);} + +/* + * m MUST contain a continuous IP6 header. + * off is an offset where TCP/UDP/ICMP6 header starts. + * len is a total length of a transport segment. + * (e.g. TCP header + TCP payload) + */ + +int +in6_cksum(m, nxt, off, len) + struct mbuf *m; + u_int8_t nxt; + u_int32_t off, len; +{ + u_int16_t *w; + int sum = 0; + int mlen = 0; + int byte_swapped = 0; +#if 0 + int srcifid = 0, dstifid = 0; +#endif + struct ip6_hdr *ip6; + union { + u_int16_t phs[4]; + struct { + u_int32_t ph_len; + u_int8_t ph_zero[3]; + u_int8_t ph_nxt; + } ph __packed; + } uph; + union { + u_int8_t c[2]; + u_int16_t s; + } s_util; + union { + u_int16_t s[2]; + u_int32_t l; + } l_util; + + /* sanity check */ + if (m->m_pkthdr.len < off + len) { + panic("in6_cksum: mbuf len (%d) < off+len (%d+%d)", + m->m_pkthdr.len, off, len); + } + + bzero(&uph, sizeof(uph)); + + /* + * First create IP6 pseudo header and calculate a summary. + */ + ip6 = mtod(m, struct ip6_hdr *); +#if 0 + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + srcifid = ip6->ip6_src.s6_addr16[1]; + ip6->ip6_src.s6_addr16[1] = 0; + } + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + dstifid = ip6->ip6_dst.s6_addr16[1]; + ip6->ip6_dst.s6_addr16[1] = 0; + } +#endif + w = (u_int16_t *)&ip6->ip6_src; + uph.ph.ph_len = htonl(len); + uph.ph.ph_nxt = nxt; + + /* IPv6 source address */ + sum += w[0]; + if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) + sum += w[1]; + sum += w[2]; sum += w[3]; sum += w[4]; sum += w[5]; + sum += w[6]; sum += w[7]; + /* IPv6 destination address */ + sum += w[8]; + if (!IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) + sum += w[9]; + sum += w[10]; sum += w[11]; sum += w[12]; sum += w[13]; + sum += w[14]; sum += w[15]; + /* Payload length and upper layer identifier */ + sum += uph.phs[0]; sum += uph.phs[1]; + sum += uph.phs[2]; sum += uph.phs[3]; + +#if 0 + if (srcifid) + ip6->ip6_src.s6_addr16[1] = srcifid; + if (dstifid) + ip6->ip6_dst.s6_addr16[1] = dstifid; +#endif + /* + * Secondly calculate a summary of the first mbuf excluding offset. + */ + while (m != NULL && off > 0) { + if (m->m_len <= off) + off -= m->m_len; + else + break; + m = m->m_next; + } + w = (u_int16_t *)(mtod(m, u_char *) + off); + mlen = m->m_len - off; + if (len < mlen) + mlen = len; + len -= mlen; + /* + * Force to even boundary. + */ + if ((1 & (long) w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(u_char *)w; + w = (u_int16_t *)((char *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7]; + sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11]; + sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + goto next; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + if (byte_swapped) { + REDUCE; + sum <<= 8; + byte_swapped = 0; + if (mlen == -1) { + s_util.c[1] = *(char *)w; + sum += s_util.s; + mlen = 0; + } else + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(char *)w; + next: + m = m->m_next; + + /* + * Lastly calculate a summary of the rest of mbufs. + */ + + for (;m && len; m = m->m_next) { + if (m->m_len == 0) + continue; + w = mtod(m, u_int16_t *); + if (mlen == -1) { + /* + * The first byte of this mbuf is the continuation + * of a word spanning between this mbuf and the + * last mbuf. + * + * s_util.c[0] is already saved when scanning previous + * mbuf. + */ + s_util.c[1] = *(char *)w; + sum += s_util.s; + w = (u_int16_t *)((char *)w + 1); + mlen = m->m_len - 1; + len--; + } else + mlen = m->m_len; + if (len < mlen) + mlen = len; + len -= mlen; + /* + * Force to even boundary. + */ + if ((1 & (long) w) && (mlen > 0)) { + REDUCE; + sum <<= 8; + s_util.c[0] = *(u_char *)w; + w = (u_int16_t *)((char *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches &c small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7]; + sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11]; + sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + continue; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + if (byte_swapped) { + REDUCE; + sum <<= 8; + byte_swapped = 0; + if (mlen == -1) { + s_util.c[1] = *(char *)w; + sum += s_util.s; + mlen = 0; + } else + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(char *)w; + } + if (len) + panic("in6_cksum: out of data"); + if (mlen == -1) { + /* The last mbuf has odd # of bytes. Follow the + standard (the odd byte may be shifted left by 8 bits + or not as determined by endian-ness of the machine) */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + return (~sum & 0xffff); +} diff --git a/sys/netinet6/in6_gif.c b/sys/netinet6/in6_gif.c new file mode 100644 index 0000000..b560075 --- /dev/null +++ b/sys/netinet6/in6_gif.c @@ -0,0 +1,398 @@ +/* $FreeBSD$ */ +/* $KAME: in6_gif.c,v 1.49 2001/05/14 14:02:17 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/errno.h> +#include <sys/queue.h> +#include <sys/syslog.h> +#include <sys/protosw.h> + +#include <sys/malloc.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#ifdef INET +#include <netinet/ip.h> +#endif +#include <netinet/ip_encap.h> +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_gif.h> +#include <netinet6/in6_var.h> +#endif +#include <netinet6/ip6protosw.h> +#include <netinet/ip_ecn.h> +#ifdef INET6 +#include <netinet6/ip6_ecn.h> +#endif + +#include <net/if_gif.h> + +#include <net/net_osdep.h> + +static int gif_validate6(const struct ip6_hdr *, struct gif_softc *, + struct ifnet *); + +extern struct domain inet6domain; +struct ip6protosw in6_gif_protosw = +{ SOCK_RAW, &inet6domain, 0/*IPPROTO_IPV[46]*/, PR_ATOMIC|PR_ADDR, + in6_gif_input, rip6_output, 0, rip6_ctloutput, + 0, + 0, 0, 0, 0, + &rip6_usrreqs +}; + +int +in6_gif_output(ifp, family, m) + struct ifnet *ifp; + int family; /* family of the packet to be encapsulate. */ + struct mbuf *m; +{ + struct gif_softc *sc = (struct gif_softc*)ifp; + struct sockaddr_in6 *dst = (struct sockaddr_in6 *)&sc->gif_ro6.ro_dst; + struct sockaddr_in6 *sin6_src = (struct sockaddr_in6 *)sc->gif_psrc; + struct sockaddr_in6 *sin6_dst = (struct sockaddr_in6 *)sc->gif_pdst; + struct ip6_hdr *ip6; + int proto; + u_int8_t itos, otos; + + if (sin6_src == NULL || sin6_dst == NULL || + sin6_src->sin6_family != AF_INET6 || + sin6_dst->sin6_family != AF_INET6) { + m_freem(m); + return EAFNOSUPPORT; + } + + switch (family) { +#ifdef INET + case AF_INET: + { + struct ip *ip; + + proto = IPPROTO_IPV4; + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (!m) + return ENOBUFS; + } + ip = mtod(m, struct ip *); + itos = ip->ip_tos; + break; + } +#endif +#ifdef INET6 + case AF_INET6: + { + struct ip6_hdr *ip6; + proto = IPPROTO_IPV6; + if (m->m_len < sizeof(*ip6)) { + m = m_pullup(m, sizeof(*ip6)); + if (!m) + return ENOBUFS; + } + ip6 = mtod(m, struct ip6_hdr *); + itos = (ntohl(ip6->ip6_flow) >> 20) & 0xff; + break; + } +#endif + default: +#ifdef DEBUG + printf("in6_gif_output: warning: unknown family %d passed\n", + family); +#endif + m_freem(m); + return EAFNOSUPPORT; + } + + /* prepend new IP header */ + M_PREPEND(m, sizeof(struct ip6_hdr), M_DONTWAIT); + if (m && m->m_len < sizeof(struct ip6_hdr)) + m = m_pullup(m, sizeof(struct ip6_hdr)); + if (m == NULL) { + printf("ENOBUFS in in6_gif_output %d\n", __LINE__); + return ENOBUFS; + } + + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + ip6->ip6_plen = htons((u_short)m->m_pkthdr.len); + ip6->ip6_nxt = proto; + ip6->ip6_hlim = ip6_gif_hlim; + ip6->ip6_src = sin6_src->sin6_addr; + /* bidirectional configured tunnel mode */ + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6_dst->sin6_addr)) + ip6->ip6_dst = sin6_dst->sin6_addr; + else { + m_freem(m); + return ENETUNREACH; + } + if (ifp->if_flags & IFF_LINK1) + ip_ecn_ingress(ECN_ALLOWED, &otos, &itos); + else + ip_ecn_ingress(ECN_NOCARE, &otos, &itos); + ip6->ip6_flow &= ~ntohl(0xff00000); + ip6->ip6_flow |= htonl((u_int32_t)otos << 20); + + if (dst->sin6_family != sin6_dst->sin6_family || + !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &sin6_dst->sin6_addr)) { + /* cache route doesn't match */ + bzero(dst, sizeof(*dst)); + dst->sin6_family = sin6_dst->sin6_family; + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_addr = sin6_dst->sin6_addr; + if (sc->gif_ro6.ro_rt) { + RTFREE(sc->gif_ro6.ro_rt); + sc->gif_ro6.ro_rt = NULL; + } +#if 0 + sc->gif_if.if_mtu = GIF_MTU; +#endif + } + + if (sc->gif_ro6.ro_rt == NULL) { + rtalloc((struct route *)&sc->gif_ro6); + if (sc->gif_ro6.ro_rt == NULL) { + m_freem(m); + return ENETUNREACH; + } + + /* if it constitutes infinite encapsulation, punt. */ + if (sc->gif_ro.ro_rt->rt_ifp == ifp) { + m_freem(m); + return ENETUNREACH; /*XXX*/ + } +#if 0 + ifp->if_mtu = sc->gif_ro6.ro_rt->rt_ifp->if_mtu + - sizeof(struct ip6_hdr); +#endif + } + +#ifdef IPV6_MINMTU + /* + * force fragmentation to minimum MTU, to avoid path MTU discovery. + * it is too painful to ask for resend of inner packet, to achieve + * path MTU discovery for encapsulated packets. + */ + return(ip6_output(m, 0, &sc->gif_ro6, IPV6_MINMTU, 0, NULL, NULL)); +#else + return(ip6_output(m, 0, &sc->gif_ro6, 0, 0, NULL, NULL)); +#endif +} + +int in6_gif_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + struct ifnet *gifp = NULL; + struct ip6_hdr *ip6; + int af = 0; + u_int32_t otos; + + ip6 = mtod(m, struct ip6_hdr *); + + gifp = (struct ifnet *)encap_getarg(m); + + if (gifp == NULL || (gifp->if_flags & IFF_UP) == 0) { + m_freem(m); + ip6stat.ip6s_nogif++; + return IPPROTO_DONE; + } + + otos = ip6->ip6_flow; + m_adj(m, *offp); + + switch (proto) { +#ifdef INET + case IPPROTO_IPV4: + { + struct ip *ip; + u_int8_t otos8; + af = AF_INET; + otos8 = (ntohl(otos) >> 20) & 0xff; + if (m->m_len < sizeof(*ip)) { + m = m_pullup(m, sizeof(*ip)); + if (!m) + return IPPROTO_DONE; + } + ip = mtod(m, struct ip *); + if (gifp->if_flags & IFF_LINK1) + ip_ecn_egress(ECN_ALLOWED, &otos8, &ip->ip_tos); + else + ip_ecn_egress(ECN_NOCARE, &otos8, &ip->ip_tos); + break; + } +#endif /* INET */ +#ifdef INET6 + case IPPROTO_IPV6: + { + struct ip6_hdr *ip6; + af = AF_INET6; + if (m->m_len < sizeof(*ip6)) { + m = m_pullup(m, sizeof(*ip6)); + if (!m) + return IPPROTO_DONE; + } + ip6 = mtod(m, struct ip6_hdr *); + if (gifp->if_flags & IFF_LINK1) + ip6_ecn_egress(ECN_ALLOWED, &otos, &ip6->ip6_flow); + else + ip6_ecn_egress(ECN_NOCARE, &otos, &ip6->ip6_flow); + break; + } +#endif + default: + ip6stat.ip6s_nogif++; + m_freem(m); + return IPPROTO_DONE; + } + + gif_input(m, af, gifp); + return IPPROTO_DONE; +} + +/* + * validate outer address. + */ +static int +gif_validate6(ip6, sc, ifp) + const struct ip6_hdr *ip6; + struct gif_softc *sc; + struct ifnet *ifp; +{ + struct sockaddr_in6 *src, *dst; + + src = (struct sockaddr_in6 *)sc->gif_psrc; + dst = (struct sockaddr_in6 *)sc->gif_pdst; + + /* + * Check for address match. Note that the check is for an incoming + * packet. We should compare the *source* address in our configuration + * and the *destination* address of the packet, and vice versa. + */ + if (!IN6_ARE_ADDR_EQUAL(&src->sin6_addr, &ip6->ip6_dst) || + !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &ip6->ip6_src)) + return 0; + + /* martian filters on outer source - done in ip6_input */ + + /* ingress filters on outer source */ + if ((sc->gif_if.if_flags & IFF_LINK2) == 0 && ifp) { + struct sockaddr_in6 sin6; + struct rtentry *rt; + + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr = ip6->ip6_src; +#ifndef SCOPEDROUTING + sin6.sin6_scope_id = 0; /* XXX */ +#endif + + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); + if (!rt || rt->rt_ifp != ifp) { +#if 0 + log(LOG_WARNING, "%s: packet from %s dropped " + "due to ingress filter\n", if_name(&sc->gif_if), + ip6_sprintf(&sin6.sin6_addr)); +#endif + if (rt) + rtfree(rt); + return 0; + } + rtfree(rt); + } + + return 128 * 2; +} + +/* + * we know that we are in IFF_UP, outer address available, and outer family + * matched the physical addr family. see gif_encapcheck(). + * sanity check for arg should have been done in the caller. + */ +int +gif_encapcheck6(m, off, proto, arg) + const struct mbuf *m; + int off; + int proto; + void *arg; +{ + struct ip6_hdr ip6; + struct gif_softc *sc; + struct ifnet *ifp; + + /* sanity check done in caller */ + sc = (struct gif_softc *)arg; + + /* LINTED const cast */ + m_copydata(m, 0, sizeof(ip6), (caddr_t)&ip6); + ifp = ((m->m_flags & M_PKTHDR) != 0) ? m->m_pkthdr.rcvif : NULL; + + return gif_validate6(&ip6, sc, ifp); +} + +int +in6_gif_attach(sc) + struct gif_softc *sc; +{ + sc->encap_cookie6 = encap_attach_func(AF_INET6, -1, gif_encapcheck, + (struct protosw *)&in6_gif_protosw, sc); + if (sc->encap_cookie6 == NULL) + return EEXIST; + return 0; +} + +int +in6_gif_detach(sc) + struct gif_softc *sc; +{ + int error; + + error = encap_detach(sc->encap_cookie6); + if (error == 0) + sc->encap_cookie6 = NULL; + return error; +} diff --git a/sys/netinet6/in6_gif.h b/sys/netinet6/in6_gif.h new file mode 100644 index 0000000..83625e4 --- /dev/null +++ b/sys/netinet6/in6_gif.h @@ -0,0 +1,45 @@ +/* $FreeBSD$ */ +/* $KAME: in6_gif.h,v 1.5 2000/04/14 08:36:03 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_IN6_GIF_H_ +#define _NETINET6_IN6_GIF_H_ + +#define GIF_HLIM 30 + +struct gif_softc; +int in6_gif_input __P((struct mbuf **, int *, int)); +int in6_gif_output __P((struct ifnet *, int, struct mbuf *)); +int gif_encapcheck6 __P((const struct mbuf *, int, int, void *)); +int in6_gif_attach __P((struct gif_softc *)); +int in6_gif_detach __P((struct gif_softc *)); + +#endif /*_NETINET6_IN6_GIF_H_*/ diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c new file mode 100644 index 0000000..5abcca1 --- /dev/null +++ b/sys/netinet6/in6_ifattach.c @@ -0,0 +1,1050 @@ +/* $FreeBSD$ */ +/* $KAME: in6_ifattach.c,v 1.118 2001/05/24 07:44:00 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/kernel.h> +#include <sys/syslog.h> +#include <sys/md5.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/if_ether.h> +#include <netinet/in_pcb.h> + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_var.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/in6_ifattach.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/scope6_var.h> + +#include <net/net_osdep.h> + +struct in6_ifstat **in6_ifstat = NULL; +struct icmp6_ifstat **icmp6_ifstat = NULL; +size_t in6_ifstatmax = 0; +size_t icmp6_ifstatmax = 0; +unsigned long in6_maxmtu = 0; + +#ifdef IP6_AUTO_LINKLOCAL +int ip6_auto_linklocal = IP6_AUTO_LINKLOCAL; +#else +int ip6_auto_linklocal = 1; /* enable by default */ +#endif + +struct callout in6_tmpaddrtimer_ch; + +extern struct inpcbinfo udbinfo; +extern struct inpcbinfo ripcbinfo; + +static int get_rand_ifid __P((struct ifnet *, struct in6_addr *)); +static int generate_tmp_ifid __P((u_int8_t *, const u_int8_t *, u_int8_t *)); +static int get_hw_ifid __P((struct ifnet *, struct in6_addr *)); +static int get_ifid __P((struct ifnet *, struct ifnet *, struct in6_addr *)); +static int in6_ifattach_linklocal __P((struct ifnet *, struct ifnet *)); +static int in6_ifattach_loopback __P((struct ifnet *)); + +#define EUI64_GBIT 0x01 +#define EUI64_UBIT 0x02 +#define EUI64_TO_IFID(in6) do {(in6)->s6_addr[8] ^= EUI64_UBIT; } while (0) +#define EUI64_GROUP(in6) ((in6)->s6_addr[8] & EUI64_GBIT) +#define EUI64_INDIVIDUAL(in6) (!EUI64_GROUP(in6)) +#define EUI64_LOCAL(in6) ((in6)->s6_addr[8] & EUI64_UBIT) +#define EUI64_UNIVERSAL(in6) (!EUI64_LOCAL(in6)) + +#define IFID_LOCAL(in6) (!EUI64_LOCAL(in6)) +#define IFID_UNIVERSAL(in6) (!EUI64_UNIVERSAL(in6)) + +/* + * Generate a last-resort interface identifier, when the machine has no + * IEEE802/EUI64 address sources. + * The goal here is to get an interface identifier that is + * (1) random enough and (2) does not change across reboot. + * We currently use MD5(hostname) for it. + */ +static int +get_rand_ifid(ifp, in6) + struct ifnet *ifp; + struct in6_addr *in6; /* upper 64bits are preserved */ +{ + MD5_CTX ctxt; + u_int8_t digest[16]; + int hostnamelen = strlen(hostname); + +#if 0 + /* we need at least several letters as seed for ifid */ + if (hostnamelen < 3) + return -1; +#endif + + /* generate 8 bytes of pseudo-random value. */ + bzero(&ctxt, sizeof(ctxt)); + MD5Init(&ctxt); + MD5Update(&ctxt, hostname, hostnamelen); + MD5Final(digest, &ctxt); + + /* assumes sizeof(digest) > sizeof(ifid) */ + bcopy(digest, &in6->s6_addr[8], 8); + + /* make sure to set "u" bit to local, and "g" bit to individual. */ + in6->s6_addr[8] &= ~EUI64_GBIT; /* g bit to "individual" */ + in6->s6_addr[8] |= EUI64_UBIT; /* u bit to "local" */ + + /* convert EUI64 into IPv6 interface identifier */ + EUI64_TO_IFID(in6); + + return 0; +} + +static int +generate_tmp_ifid(seed0, seed1, ret) + u_int8_t *seed0, *ret; + const u_int8_t *seed1; +{ + MD5_CTX ctxt; + u_int8_t seed[16], digest[16], nullbuf[8]; + u_int32_t val32; + struct timeval tv; + + /* If there's no hisotry, start with a random seed. */ + bzero(nullbuf, sizeof(nullbuf)); + if (bcmp(nullbuf, seed0, sizeof(nullbuf)) == 0) { + int i; + + for (i = 0; i < 2; i++) { + microtime(&tv); + val32 = random() ^ tv.tv_usec; + bcopy(&val32, seed + sizeof(val32) * i, sizeof(val32)); + } + } else { + bcopy(seed0, seed, 8); + } + + /* copy the right-most 64-bits of the given address */ + /* XXX assumption on the size of IFID */ + bcopy(seed1, &seed[8], 8); + + if (0) { /* for debugging purposes only */ + int i; + + printf("generate_tmp_ifid: new randomized ID from: "); + for (i = 0; i < 16; i++) + printf("%02x", seed[i]); + printf(" "); + } + + /* generate 16 bytes of pseudo-random value. */ + bzero(&ctxt, sizeof(ctxt)); + MD5Init(&ctxt); + MD5Update(&ctxt, seed, sizeof(seed)); + MD5Final(digest, &ctxt); + + /* + * RFC 3041 3.2.1. (3) + * Take the left-most 64-bits of the MD5 digest and set bit 6 (the + * left-most bit is numbered 0) to zero. + */ + bcopy(digest, ret, 8); + ret[0] &= ~EUI64_UBIT; + + /* + * XXX: we'd like to ensure that the generated value is not zero + * for simplicity. If the caclculated digest happens to be zero, + * use a random non-zero value as the last resort. + */ + if (bcmp(nullbuf, ret, sizeof(nullbuf)) == 0) { + log(LOG_INFO, + "generate_tmp_ifid: computed MD5 value is zero.\n"); + + microtime(&tv); + val32 = random() ^ tv.tv_usec; + val32 = 1 + (val32 % (0xffffffff - 1)); + } + + /* + * RFC 3041 3.2.1. (4) + * Take the rightmost 64-bits of the MD5 digest and save them in + * stable storage as the history value to be used in the next + * iteration of the algorithm. + */ + bcopy(&digest[8], seed0, 8); + + if (0) { /* for debugging purposes only */ + int i; + + printf("to: "); + for (i = 0; i < 16; i++) + printf("%02x", digest[i]); + printf("\n"); + } + + return 0; +} + +/* + * Get interface identifier for the specified interface. + * XXX assumes single sockaddr_dl (AF_LINK address) per an interface + */ +static int +get_hw_ifid(ifp, in6) + struct ifnet *ifp; + struct in6_addr *in6; /* upper 64bits are preserved */ +{ + struct ifaddr *ifa; + struct sockaddr_dl *sdl; + u_int8_t *addr; + size_t addrlen; + static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + static u_int8_t allone[8] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + for (ifa = ifp->if_addrlist.tqh_first; + ifa; + ifa = ifa->ifa_list.tqe_next) + { + if (ifa->ifa_addr->sa_family != AF_LINK) + continue; + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if (sdl == NULL) + continue; + if (sdl->sdl_alen == 0) + continue; + + goto found; + } + + return -1; + +found: + addr = LLADDR(sdl); + addrlen = sdl->sdl_alen; + + /* get EUI64 */ + switch (ifp->if_type) { + case IFT_ETHER: + case IFT_FDDI: + case IFT_ATM: + case IFT_IEEE1394: +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: +#endif + /* IEEE802/EUI64 cases - what others? */ + /* IEEE1394 uses 16byte length address starting with EUI64 */ + if (addrlen > 8) + addrlen = 8; + + /* look at IEEE802/EUI64 only */ + if (addrlen != 8 && addrlen != 6) + return -1; + + /* + * check for invalid MAC address - on bsdi, we see it a lot + * since wildboar configures all-zero MAC on pccard before + * card insertion. + */ + if (bcmp(addr, allzero, addrlen) == 0) + return -1; + if (bcmp(addr, allone, addrlen) == 0) + return -1; + + /* make EUI64 address */ + if (addrlen == 8) + bcopy(addr, &in6->s6_addr[8], 8); + else if (addrlen == 6) { + in6->s6_addr[8] = addr[0]; + in6->s6_addr[9] = addr[1]; + in6->s6_addr[10] = addr[2]; + in6->s6_addr[11] = 0xff; + in6->s6_addr[12] = 0xfe; + in6->s6_addr[13] = addr[3]; + in6->s6_addr[14] = addr[4]; + in6->s6_addr[15] = addr[5]; + } + break; + + case IFT_ARCNET: + if (addrlen != 1) + return -1; + if (!addr[0]) + return -1; + + bzero(&in6->s6_addr[8], 8); + in6->s6_addr[15] = addr[0]; + + /* + * due to insufficient bitwidth, we mark it local. + */ + in6->s6_addr[8] &= ~EUI64_GBIT; /* g bit to "individual" */ + in6->s6_addr[8] |= EUI64_UBIT; /* u bit to "local" */ + break; + + case IFT_GIF: +#ifdef IFT_STF + case IFT_STF: +#endif + /* + * RFC2893 says: "SHOULD use IPv4 address as ifid source". + * however, IPv4 address is not very suitable as unique + * identifier source (can be renumbered). + * we don't do this. + */ + return -1; + + default: + return -1; + } + + /* sanity check: g bit must not indicate "group" */ + if (EUI64_GROUP(in6)) + return -1; + + /* convert EUI64 into IPv6 interface identifier */ + EUI64_TO_IFID(in6); + + /* + * sanity check: ifid must not be all zero, avoid conflict with + * subnet router anycast + */ + if ((in6->s6_addr[8] & ~(EUI64_GBIT | EUI64_UBIT)) == 0x00 && + bcmp(&in6->s6_addr[9], allzero, 7) == 0) { + return -1; + } + + return 0; +} + +/* + * Get interface identifier for the specified interface. If it is not + * available on ifp0, borrow interface identifier from other information + * sources. + */ +static int +get_ifid(ifp0, altifp, in6) + struct ifnet *ifp0; + struct ifnet *altifp; /* secondary EUI64 source */ + struct in6_addr *in6; +{ + struct ifnet *ifp; + + /* first, try to get it from the interface itself */ + if (get_hw_ifid(ifp0, in6) == 0) { + nd6log((LOG_DEBUG, "%s: got interface identifier from itself\n", + if_name(ifp0))); + goto success; + } + + /* try secondary EUI64 source. this basically is for ATM PVC */ + if (altifp && get_hw_ifid(altifp, in6) == 0) { + nd6log((LOG_DEBUG, "%s: got interface identifier from %s\n", + if_name(ifp0), if_name(altifp))); + goto success; + } + + /* next, try to get it from some other hardware interface */ + IFNET_RLOCK(); + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) + { + if (ifp == ifp0) + continue; + if (get_hw_ifid(ifp, in6) != 0) + continue; + + /* + * to borrow ifid from other interface, ifid needs to be + * globally unique + */ + if (IFID_UNIVERSAL(in6)) { + nd6log((LOG_DEBUG, + "%s: borrow interface identifier from %s\n", + if_name(ifp0), if_name(ifp))); + IFNET_RUNLOCK(); + goto success; + } + } + IFNET_RUNLOCK(); + + /* last resort: get from random number source */ + if (get_rand_ifid(ifp, in6) == 0) { + nd6log((LOG_DEBUG, + "%s: interface identifier generated by random number\n", + if_name(ifp0))); + goto success; + } + + printf("%s: failed to get interface identifier\n", if_name(ifp0)); + return -1; + +success: + nd6log((LOG_INFO, "%s: ifid: " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + if_name(ifp0), + in6->s6_addr[8], in6->s6_addr[9], + in6->s6_addr[10], in6->s6_addr[11], + in6->s6_addr[12], in6->s6_addr[13], + in6->s6_addr[14], in6->s6_addr[15])); + return 0; +} + +static int +in6_ifattach_linklocal(ifp, altifp) + struct ifnet *ifp; + struct ifnet *altifp; /* secondary EUI64 source */ +{ + struct in6_ifaddr *ia; + struct in6_aliasreq ifra; + struct nd_prefix pr0; + int i, error; + + /* + * configure link-local address. + */ + bzero(&ifra, sizeof(ifra)); + + /* + * in6_update_ifa() does not use ifra_name, but we accurately set it + * for safety. + */ + strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name)); + + ifra.ifra_addr.sin6_family = AF_INET6; + ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_addr.sin6_addr.s6_addr16[0] = htons(0xfe80); +#ifdef SCOPEDROUTING + ifra.ifra_addr.sin6_addr.s6_addr16[1] = 0 +#else + ifra.ifra_addr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); /* XXX */ +#endif + ifra.ifra_addr.sin6_addr.s6_addr32[1] = 0; + if ((ifp->if_flags & IFF_LOOPBACK) != 0) { + ifra.ifra_addr.sin6_addr.s6_addr32[2] = 0; + ifra.ifra_addr.sin6_addr.s6_addr32[3] = htonl(1); + } else { + if (get_ifid(ifp, altifp, &ifra.ifra_addr.sin6_addr) != 0) { + nd6log((LOG_ERR, + "%s: no ifid available\n", if_name(ifp))); + return -1; + } + } +#ifdef SCOPEDROUTING + ifra.ifra_addr.sin6_scope_id = + in6_addr2scopeid(ifp, &ifra.ifra_addr.sin6_addr); +#endif + + ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_prefixmask.sin6_family = AF_INET6; + ifra.ifra_prefixmask.sin6_addr = in6mask64; +#ifdef SCOPEDROUTING + /* take into accound the sin6_scope_id field for routing */ + ifra.ifra_prefixmask.sin6_scope_id = 0xffffffff; +#endif + /* link-local addresses should NEVER expire. */ + ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + + /* + * Do not let in6_update_ifa() do DAD, since we need a random delay + * before sending an NS at the first time the interface becomes up. + * Instead, in6_if_up() will start DAD with a proper random delay. + */ + ifra.ifra_flags |= IN6_IFF_NODAD; + + /* + * Now call in6_update_ifa() to do a bunch of procedures to configure + * a link-local address. We can set NULL to the 3rd argument, because + * we know there's no other link-local address on the interface + * and therefore we are adding one (instead of updating one). + */ + if ((error = in6_update_ifa(ifp, &ifra, NULL)) != 0) { + /* + * XXX: When the interface does not support IPv6, this call + * would fail in the SIOCSIFADDR ioctl. I believe the + * notification is rather confusing in this case, so just + * supress it. (jinmei@kame.net 20010130) + */ + if (error != EAFNOSUPPORT) + log(LOG_NOTICE, "in6_ifattach_linklocal: failed to " + "configure a link-local address on %s " + "(errno=%d)\n", + if_name(ifp), error); + return(-1); + } + + /* + * Adjust ia6_flags so that in6_if_up will perform DAD. + * XXX: Some P2P interfaces seem not to send packets just after + * becoming up, so we skip p2p interfaces for safety. + */ + ia = in6ifa_ifpforlinklocal(ifp, 0); /* ia must not be NULL */ +#ifdef DIAGNOSTIC + if (!ia) { + panic("ia == NULL in in6_ifattach_linklocal"); + /* NOTREACHED */ + } +#endif + if (in6if_do_dad(ifp) && (ifp->if_flags & IFF_POINTOPOINT) == 0) { + ia->ia6_flags &= ~IN6_IFF_NODAD; + ia->ia6_flags |= IN6_IFF_TENTATIVE; + } + + /* + * Make the link-local prefix (fe80::/64%link) as on-link. + * Since we'd like to manage prefixes separately from addresses, + * we make an ND6 prefix structure for the link-local prefix, + * and add it to the prefix list as a never-expire prefix. + * XXX: this change might affect some existing code base... + */ + bzero(&pr0, sizeof(pr0)); + pr0.ndpr_ifp = ifp; + /* this should be 64 at this moment. */ + pr0.ndpr_plen = in6_mask2len(&ifra.ifra_prefixmask.sin6_addr, NULL); + pr0.ndpr_mask = ifra.ifra_prefixmask.sin6_addr; + pr0.ndpr_prefix = ifra.ifra_addr; + /* apply the mask for safety. (nd6_prelist_add will apply it again) */ + for (i = 0; i < 4; i++) { + pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= + in6mask64.s6_addr32[i]; + } + /* + * Initialize parameters. The link-local prefix must always be + * on-link, and its lifetimes never expire. + */ + pr0.ndpr_raf_onlink = 1; + pr0.ndpr_raf_auto = 1; /* probably meaningless */ + pr0.ndpr_vltime = ND6_INFINITE_LIFETIME; + pr0.ndpr_pltime = ND6_INFINITE_LIFETIME; + /* + * Since there is no other link-local addresses, nd6_prefix_lookup() + * probably returns NULL. However, we cannot always expect the result. + * For example, if we first remove the (only) existing link-local + * address, and then reconfigure another one, the prefix is still + * valid with referring to the old link-local address. + */ + if (nd6_prefix_lookup(&pr0) == NULL) { + if ((error = nd6_prelist_add(&pr0, NULL, NULL)) != 0) + return(error); + } + + return 0; +} + +static int +in6_ifattach_loopback(ifp) + struct ifnet *ifp; /* must be IFT_LOOP */ +{ + struct in6_aliasreq ifra; + int error; + + bzero(&ifra, sizeof(ifra)); + + /* + * in6_update_ifa() does not use ifra_name, but we accurately set it + * for safety. + */ + strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name)); + + ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_prefixmask.sin6_family = AF_INET6; + ifra.ifra_prefixmask.sin6_addr = in6mask128; + + /* + * Always initialize ia_dstaddr (= broadcast address) to loopback + * address. Follows IPv4 practice - see in_ifinit(). + */ + ifra.ifra_dstaddr.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_dstaddr.sin6_family = AF_INET6; + ifra.ifra_dstaddr.sin6_addr = in6addr_loopback; + + ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_addr.sin6_family = AF_INET6; + ifra.ifra_addr.sin6_addr = in6addr_loopback; + + /* the loopback address should NEVER expire. */ + ifra.ifra_lifetime.ia6t_vltime = ND6_INFINITE_LIFETIME; + ifra.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; + + /* we don't need to perform DAD on loopback interfaces. */ + ifra.ifra_flags |= IN6_IFF_NODAD; + + /* skip registration to the prefix list. XXX should be temporary. */ + ifra.ifra_flags |= IN6_IFF_NOPFX; + + /* + * We are sure that this is a newly assigned address, so we can set + * NULL to the 3rd arg. + */ + if ((error = in6_update_ifa(ifp, &ifra, NULL)) != 0) { + log(LOG_ERR, "in6_ifattach_loopback: failed to configure " + "the loopback address on %s (errno=%d)\n", + if_name(ifp), error); + return(-1); + } + + return 0; +} + +/* + * compute NI group address, based on the current hostname setting. + * see draft-ietf-ipngwg-icmp-name-lookup-* (04 and later). + * + * when ifp == NULL, the caller is responsible for filling scopeid. + */ +int +in6_nigroup(ifp, name, namelen, in6) + struct ifnet *ifp; + const char *name; + int namelen; + struct in6_addr *in6; +{ + const char *p; + u_char *q; + MD5_CTX ctxt; + u_int8_t digest[16]; + char l; + char n[64]; /* a single label must not exceed 63 chars */ + + if (!namelen || !name) + return -1; + + p = name; + while (p && *p && *p != '.' && p - name < namelen) + p++; + if (p - name > sizeof(n) - 1) + return -1; /* label too long */ + l = p - name; + strncpy(n, name, l); + n[(int)l] = '\0'; + for (q = n; *q; q++) { + if ('A' <= *q && *q <= 'Z') + *q = *q - 'A' + 'a'; + } + + /* generate 8 bytes of pseudo-random value. */ + bzero(&ctxt, sizeof(ctxt)); + MD5Init(&ctxt); + MD5Update(&ctxt, &l, sizeof(l)); + MD5Update(&ctxt, n, l); + MD5Final(digest, &ctxt); + + bzero(in6, sizeof(*in6)); + in6->s6_addr16[0] = htons(0xff02); + if (ifp) + in6->s6_addr16[1] = htons(ifp->if_index); + in6->s6_addr8[11] = 2; + bcopy(digest, &in6->s6_addr32[3], sizeof(in6->s6_addr32[3])); + + return 0; +} + +void +in6_nigroup_attach(name, namelen) + const char *name; + int namelen; +{ + struct ifnet *ifp; + struct sockaddr_in6 mltaddr; + struct in6_multi *in6m; + int error; + + bzero(&mltaddr, sizeof(mltaddr)); + mltaddr.sin6_family = AF_INET6; + mltaddr.sin6_len = sizeof(struct sockaddr_in6); + if (in6_nigroup(NULL, name, namelen, &mltaddr.sin6_addr) != 0) + return; + + IFNET_RLOCK(); + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) + { + mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (!in6m) { + if (!in6_addmulti(&mltaddr.sin6_addr, ifp, &error)) { + nd6log((LOG_ERR, "%s: failed to join %s " + "(errno=%d)\n", if_name(ifp), + ip6_sprintf(&mltaddr.sin6_addr), + error)); + } + } + } + IFNET_RUNLOCK(); +} + +void +in6_nigroup_detach(name, namelen) + const char *name; + int namelen; +{ + struct ifnet *ifp; + struct sockaddr_in6 mltaddr; + struct in6_multi *in6m; + + bzero(&mltaddr, sizeof(mltaddr)); + mltaddr.sin6_family = AF_INET6; + mltaddr.sin6_len = sizeof(struct sockaddr_in6); + if (in6_nigroup(NULL, name, namelen, &mltaddr.sin6_addr) != 0) + return; + + IFNET_RLOCK(); + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) + { + mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m) + in6_delmulti(in6m); + } + IFNET_RUNLOCK(); +} + +/* + * XXX multiple loopback interface needs more care. for instance, + * nodelocal address needs to be configured onto only one of them. + * XXX multiple link-local address case + */ +void +in6_ifattach(ifp, altifp) + struct ifnet *ifp; + struct ifnet *altifp; /* secondary EUI64 source */ +{ + static size_t if_indexlim = 8; + struct in6_ifaddr *ia; + struct in6_addr in6; + + /* some of the interfaces are inherently not IPv6 capable */ + switch (ifp->if_type) { +#ifdef IFT_BRIDGE /*OpenBSD 2.8*/ + case IFT_BRIDGE: + return; +#endif + } + + /* + * We have some arrays that should be indexed by if_index. + * since if_index will grow dynamically, they should grow too. + * struct in6_ifstat **in6_ifstat + * struct icmp6_ifstat **icmp6_ifstat + */ + if (in6_ifstat == NULL || icmp6_ifstat == NULL || + if_index >= if_indexlim) { + size_t n; + caddr_t q; + size_t olim; + + olim = if_indexlim; + while (if_index >= if_indexlim) + if_indexlim <<= 1; + + /* grow in6_ifstat */ + n = if_indexlim * sizeof(struct in6_ifstat *); + q = (caddr_t)malloc(n, M_IFADDR, M_WAITOK); + bzero(q, n); + if (in6_ifstat) { + bcopy((caddr_t)in6_ifstat, q, + olim * sizeof(struct in6_ifstat *)); + free((caddr_t)in6_ifstat, M_IFADDR); + } + in6_ifstat = (struct in6_ifstat **)q; + in6_ifstatmax = if_indexlim; + + /* grow icmp6_ifstat */ + n = if_indexlim * sizeof(struct icmp6_ifstat *); + q = (caddr_t)malloc(n, M_IFADDR, M_WAITOK); + bzero(q, n); + if (icmp6_ifstat) { + bcopy((caddr_t)icmp6_ifstat, q, + olim * sizeof(struct icmp6_ifstat *)); + free((caddr_t)icmp6_ifstat, M_IFADDR); + } + icmp6_ifstat = (struct icmp6_ifstat **)q; + icmp6_ifstatmax = if_indexlim; + } + + /* initialize scope identifiers */ + scope6_ifattach(ifp); + + /* + * quirks based on interface type + */ + switch (ifp->if_type) { +#ifdef IFT_STF + case IFT_STF: + /* + * 6to4 interface is a very special kind of beast. + * no multicast, no linklocal. RFC2529 specifies how to make + * linklocals for 6to4 interface, but there's no use and + * it is rather harmful to have one. + */ + goto statinit; +#endif + default: + break; + } + + /* + * usually, we require multicast capability to the interface + */ + if ((ifp->if_flags & IFF_MULTICAST) == 0) { + log(LOG_INFO, "in6_ifattach: " + "%s is not multicast capable, IPv6 not enabled\n", + if_name(ifp)); + return; + } + + /* + * assign loopback address for loopback interface. + * XXX multiple loopback interface case. + */ + if ((ifp->if_flags & IFF_LOOPBACK) != 0) { + in6 = in6addr_loopback; + if (in6ifa_ifpwithaddr(ifp, &in6) == NULL) { + if (in6_ifattach_loopback(ifp) != 0) + return; + } + } + + /* + * assign a link-local address, if there's none. + */ + if (ip6_auto_linklocal) { + ia = in6ifa_ifpforlinklocal(ifp, 0); + if (ia == NULL) { + if (in6_ifattach_linklocal(ifp, altifp) == 0) { + /* linklocal address assigned */ + } else { + /* failed to assign linklocal address. bark? */ + } + } + } + +#ifdef IFT_STF /* XXX */ +statinit: +#endif + + /* update dynamically. */ + if (in6_maxmtu < ifp->if_mtu) + in6_maxmtu = ifp->if_mtu; + + if (in6_ifstat[ifp->if_index] == NULL) { + in6_ifstat[ifp->if_index] = (struct in6_ifstat *) + malloc(sizeof(struct in6_ifstat), M_IFADDR, M_WAITOK); + bzero(in6_ifstat[ifp->if_index], sizeof(struct in6_ifstat)); + } + if (icmp6_ifstat[ifp->if_index] == NULL) { + icmp6_ifstat[ifp->if_index] = (struct icmp6_ifstat *) + malloc(sizeof(struct icmp6_ifstat), M_IFADDR, M_WAITOK); + bzero(icmp6_ifstat[ifp->if_index], sizeof(struct icmp6_ifstat)); + } + + /* initialize NDP variables */ + nd6_ifattach(ifp); +} + +/* + * NOTE: in6_ifdetach() does not support loopback if at this moment. + * We don't need this function in bsdi, because interfaces are never removed + * from the ifnet list in bsdi. + */ +void +in6_ifdetach(ifp) + struct ifnet *ifp; +{ + struct in6_ifaddr *ia, *oia; + struct ifaddr *ifa, *next; + struct rtentry *rt; + short rtflags; + struct sockaddr_in6 sin6; + struct in6_multi *in6m; + struct in6_multi *in6m_next; + + /* nuke prefix list. this may try to remove some of ifaddrs as well */ + in6_purgeprefix(ifp); + + /* remove neighbor management table */ + nd6_purge(ifp); + + /* nuke any of IPv6 addresses we have */ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = next) + { + next = ifa->ifa_list.tqe_next; + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + in6_purgeaddr(ifa); + } + + /* undo everything done by in6_ifattach(), just in case */ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = next) + { + next = ifa->ifa_list.tqe_next; + + + if (ifa->ifa_addr->sa_family != AF_INET6 + || !IN6_IS_ADDR_LINKLOCAL(&satosin6(&ifa->ifa_addr)->sin6_addr)) { + continue; + } + + ia = (struct in6_ifaddr *)ifa; + + /* remove from the routing table */ + if ((ia->ia_flags & IFA_ROUTE) + && (rt = rtalloc1((struct sockaddr *)&ia->ia_addr, 0, 0UL))) { + rtflags = rt->rt_flags; + rtfree(rt); + rtrequest(RTM_DELETE, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&ia->ia_prefixmask, + rtflags, (struct rtentry **)0); + } + + /* remove from the linked list */ + TAILQ_REMOVE(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); + IFAFREE(&ia->ia_ifa); + + /* also remove from the IPv6 address chain(itojun&jinmei) */ + oia = ia; + if (oia == (ia = in6_ifaddr)) + in6_ifaddr = ia->ia_next; + else { + while (ia->ia_next && (ia->ia_next != oia)) + ia = ia->ia_next; + if (ia->ia_next) + ia->ia_next = oia->ia_next; + else { + nd6log((LOG_ERR, + "%s: didn't unlink in6ifaddr from " + "list\n", if_name(ifp))); + } + } + + IFAFREE(&oia->ia_ifa); + } + + /* leave from all multicast groups joined */ + in6_pcbpurgeif0(LIST_FIRST(udbinfo.listhead), ifp); + in6_pcbpurgeif0(LIST_FIRST(ripcbinfo.listhead), ifp); + for (in6m = LIST_FIRST(&in6_multihead); in6m; in6m = in6m_next) { + in6m_next = LIST_NEXT(in6m, in6m_entry); + if (in6m->in6m_ifp != ifp) + continue; + in6_delmulti(in6m); + in6m = NULL; + } + + /* + * remove neighbor management table. we call it twice just to make + * sure we nuke everything. maybe we need just one call. + * XXX: since the first call did not release addresses, some prefixes + * might remain. We should call nd6_purge() again to release the + * prefixes after removing all addresses above. + * (Or can we just delay calling nd6_purge until at this point?) + */ + nd6_purge(ifp); + + /* remove route to link-local allnodes multicast (ff02::1) */ + bzero(&sin6, sizeof(sin6)); + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_linklocal_allnodes; + sin6.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); + if (rt && rt->rt_ifp == ifp) { + rtrequest(RTM_DELETE, (struct sockaddr *)rt_key(rt), + rt->rt_gateway, rt_mask(rt), rt->rt_flags, 0); + rtfree(rt); + } +} + +void +in6_get_tmpifid(ifp, retbuf, baseid, generate) + struct ifnet *ifp; + u_int8_t *retbuf; + const u_int8_t *baseid; + int generate; +{ + u_int8_t nullbuf[8]; + struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index]; + + bzero(nullbuf, sizeof(nullbuf)); + if (bcmp(ndi->randomid, nullbuf, sizeof(nullbuf)) == 0) { + /* we've never created a random ID. Create a new one. */ + generate = 1; + } + + if (generate) { + bcopy(baseid, ndi->randomseed1, sizeof(ndi->randomseed1)); + + /* generate_tmp_ifid will update seedn and buf */ + (void)generate_tmp_ifid(ndi->randomseed0, ndi->randomseed1, + ndi->randomid); + } + bcopy(ndi->randomid, retbuf, 8); +} + +void +in6_tmpaddrtimer(ignored_arg) + void *ignored_arg; +{ + int i; + struct nd_ifinfo *ndi; + u_int8_t nullbuf[8]; + int s = splnet(); + + callout_reset(&in6_tmpaddrtimer_ch, + (ip6_temp_preferred_lifetime - ip6_desync_factor - + ip6_temp_regen_advance) * hz, + in6_tmpaddrtimer, NULL); + + bzero(nullbuf, sizeof(nullbuf)); + for (i = 1; i < if_index + 1; i++) { + ndi = &nd_ifinfo[i]; + if (bcmp(ndi->randomid, nullbuf, sizeof(nullbuf)) != 0) { + /* + * We've been generating a random ID on this interface. + * Create a new one. + */ + (void)generate_tmp_ifid(ndi->randomseed0, + ndi->randomseed1, + ndi->randomid); + } + } + + splx(s); +} diff --git a/sys/netinet6/in6_ifattach.h b/sys/netinet6/in6_ifattach.h new file mode 100644 index 0000000..fa4434f --- /dev/null +++ b/sys/netinet6/in6_ifattach.h @@ -0,0 +1,46 @@ +/* $FreeBSD$ */ +/* $KAME: in6_ifattach.h,v 1.14 2001/02/08 12:48:39 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_IN6_IFATTACH_H_ +#define _NETINET6_IN6_IFATTACH_H_ + +#ifdef _KERNEL +void in6_nigroup_attach __P((const char *, int)); +void in6_nigroup_detach __P((const char *, int)); +void in6_ifattach __P((struct ifnet *, struct ifnet *)); +void in6_ifdetach __P((struct ifnet *)); +void in6_get_tmpifid __P((struct ifnet *, u_int8_t *, const u_int8_t *, int)); +void in6_tmpaddrtimer __P((void *)); +int in6_nigroup __P((struct ifnet *, const char *, int, struct in6_addr *)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_IN6_IFATTACH_H_ */ diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c new file mode 100644 index 0000000..84ea6a9 --- /dev/null +++ b/sys/netinet6/in6_pcb.c @@ -0,0 +1,1158 @@ +/* $FreeBSD$ */ +/* $KAME: in6_pcb.c,v 1.31 2001/05/21 05:45:10 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + */ + +/* + * Copyright (c) 1982, 1986, 1991, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_pcb.c 8.2 (Berkeley) 1/4/94 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sockio.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/proc.h> +#include <sys/jail.h> + +#include <vm/uma.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/tcp_var.h> +#include <netinet/ip6.h> +#include <netinet/ip_var.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet/in_pcb.h> +#include <netinet6/in6_pcb.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#include <netkey/key.h> +#endif /* IPSEC */ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/key.h> +#define IPSEC +#endif /* FAST_IPSEC */ + +struct in6_addr zeroin6_addr; + +int +in6_pcbbind(inp, nam, td) + register struct inpcb *inp; + struct sockaddr *nam; + struct thread *td; +{ + struct socket *so = inp->inp_socket; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)NULL; + struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; + u_short lport = 0; + int wild = 0, reuseport = (so->so_options & SO_REUSEPORT); + + if (!in6_ifaddr) /* XXX broken! */ + return (EADDRNOTAVAIL); + if (inp->inp_lport || !IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) + return(EINVAL); + if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0) + wild = 1; + if (nam) { + sin6 = (struct sockaddr_in6 *)nam; + if (nam->sa_len != sizeof(*sin6)) + return(EINVAL); + /* + * family check. + */ + if (nam->sa_family != AF_INET6) + return(EAFNOSUPPORT); + + /* KAME hack: embed scopeid */ + if (in6_embedscope(&sin6->sin6_addr, sin6, inp, NULL) != 0) + return EINVAL; + /* this must be cleared for ifa_ifwithaddr() */ + sin6->sin6_scope_id = 0; + + lport = sin6->sin6_port; + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) { + /* + * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; + * allow compepte duplication of binding if + * SO_REUSEPORT is set, or if SO_REUSEADDR is set + * and a multicast address is bound on both + * new and duplicated sockets. + */ + if (so->so_options & SO_REUSEADDR) + reuseport = SO_REUSEADDR|SO_REUSEPORT; + } else if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + struct ifaddr *ia = NULL; + + sin6->sin6_port = 0; /* yech... */ + if ((ia = ifa_ifwithaddr((struct sockaddr *)sin6)) == 0) + return(EADDRNOTAVAIL); + + /* + * XXX: bind to an anycast address might accidentally + * cause sending a packet with anycast source address. + * We should allow to bind to a deprecated address, since + * the application dare to use it. + */ + if (ia && + ((struct in6_ifaddr *)ia)->ia6_flags & + (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY|IN6_IFF_DETACHED)) { + return(EADDRNOTAVAIL); + } + } + if (lport) { + struct inpcb *t; + + /* GROSS */ + if (ntohs(lport) < IPV6PORT_RESERVED && td && + suser_cred(td->td_ucred, PRISON_ROOT)) + return(EACCES); + if (so->so_cred->cr_uid != 0 && + !IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) { + t = in6_pcblookup_local(pcbinfo, + &sin6->sin6_addr, lport, + INPLOOKUP_WILDCARD); + if (t && (t->inp_vflag & INP_TIMEWAIT)) { + if ((!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || + !IN6_IS_ADDR_UNSPECIFIED(&t->in6p_laddr) || + !(intotw(t)->tw_so_options & SO_REUSEPORT)) + && so->so_cred->cr_uid != + intotw(t)->tw_cred->cr_uid) + return (EADDRINUSE); + } else if (t && + (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || + !IN6_IS_ADDR_UNSPECIFIED(&t->in6p_laddr) || + (t->inp_socket->so_options & SO_REUSEPORT) + == 0) && (so->so_cred->cr_uid != + t->inp_socket->so_cred->cr_uid)) + return (EADDRINUSE); + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 && + IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + struct sockaddr_in sin; + + in6_sin6_2_sin(&sin, sin6); + t = in_pcblookup_local(pcbinfo, + sin.sin_addr, lport, + INPLOOKUP_WILDCARD); + if (t && (t->inp_vflag & INP_TIMEWAIT)) { + if (so->so_cred->cr_uid != + intotw(t)->tw_cred->cr_uid && + (ntohl(t->inp_laddr.s_addr) != + INADDR_ANY || + ((inp->inp_vflag & + INP_IPV6PROTO) == + (t->inp_vflag & + INP_IPV6PROTO)))) + return (EADDRINUSE); + } else if (t && + (so->so_cred->cr_uid != + t->inp_socket->so_cred->cr_uid) && + (ntohl(t->inp_laddr.s_addr) != + INADDR_ANY || + INP_SOCKAF(so) == + INP_SOCKAF(t->inp_socket))) + return (EADDRINUSE); + } + } + t = in6_pcblookup_local(pcbinfo, &sin6->sin6_addr, + lport, wild); + if (t && (reuseport & ((t->inp_vflag & INP_TIMEWAIT) ? + intotw(t)->tw_so_options : + t->inp_socket->so_options)) == 0) + return(EADDRINUSE); + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0 && + IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + struct sockaddr_in sin; + + in6_sin6_2_sin(&sin, sin6); + t = in_pcblookup_local(pcbinfo, sin.sin_addr, + lport, wild); + if (t && t->inp_vflag & INP_TIMEWAIT) { + if ((reuseport & + intotw(t)->tw_so_options) == 0 && + (ntohl(t->inp_laddr.s_addr) != + INADDR_ANY || ((inp->inp_vflag & + INP_IPV6PROTO) == + (t->inp_vflag & INP_IPV6PROTO)))) + return (EADDRINUSE); + } + else if (t && + (reuseport & t->inp_socket->so_options) + == 0 && (ntohl(t->inp_laddr.s_addr) != + INADDR_ANY || INP_SOCKAF(so) == + INP_SOCKAF(t->inp_socket))) + return (EADDRINUSE); + } + } + inp->in6p_laddr = sin6->sin6_addr; + } + if (lport == 0) { + int e; + if ((e = in6_pcbsetport(&inp->in6p_laddr, inp, td)) != 0) + return(e); + } + else { + inp->inp_lport = lport; + if (in_pcbinshash(inp) != 0) { + inp->in6p_laddr = in6addr_any; + inp->inp_lport = 0; + return (EAGAIN); + } + } + return(0); +} + +/* + * Transform old in6_pcbconnect() into an inner subroutine for new + * in6_pcbconnect(): Do some validity-checking on the remote + * address (in mbuf 'nam') and then determine local host address + * (i.e., which interface) to use to access that remote host. + * + * This preserves definition of in6_pcbconnect(), while supporting a + * slightly different version for T/TCP. (This is more than + * a bit of a kludge, but cleaning up the internal interfaces would + * have forced minor changes in every protocol). + */ + +int +in6_pcbladdr(inp, nam, plocal_addr6) + register struct inpcb *inp; + struct sockaddr *nam; + struct in6_addr **plocal_addr6; +{ + register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; + struct ifnet *ifp = NULL; + int error = 0; + + if (nam->sa_len != sizeof (*sin6)) + return (EINVAL); + if (sin6->sin6_family != AF_INET6) + return (EAFNOSUPPORT); + if (sin6->sin6_port == 0) + return (EADDRNOTAVAIL); + + /* KAME hack: embed scopeid */ + if (in6_embedscope(&sin6->sin6_addr, sin6, inp, &ifp) != 0) + return EINVAL; + + if (in6_ifaddr) { + /* + * If the destination address is UNSPECIFIED addr, + * use the loopback addr, e.g ::1. + */ + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + sin6->sin6_addr = in6addr_loopback; + } + { + /* + * XXX: in6_selectsrc might replace the bound local address + * with the address specified by setsockopt(IPV6_PKTINFO). + * Is it the intended behavior? + */ + *plocal_addr6 = in6_selectsrc(sin6, inp->in6p_outputopts, + inp->in6p_moptions, + &inp->in6p_route, + &inp->in6p_laddr, &error); + if (*plocal_addr6 == 0) { + if (error == 0) + error = EADDRNOTAVAIL; + return(error); + } + /* + * Don't do pcblookup call here; return interface in + * plocal_addr6 + * and exit to caller, that will do the lookup. + */ + } + + if (inp->in6p_route.ro_rt) + ifp = inp->in6p_route.ro_rt->rt_ifp; + + return(0); +} + +/* + * Outer subroutine: + * Connect from a socket to a specified address. + * Both address and port must be specified in argument sin. + * If don't have a local address for this socket yet, + * then pick one. + */ +int +in6_pcbconnect(inp, nam, td) + register struct inpcb *inp; + struct sockaddr *nam; + struct thread *td; +{ + struct in6_addr *addr6; + register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; + int error; + + /* + * Call inner routine, to assign local interface address. + * in6_pcbladdr() may automatically fill in sin6_scope_id. + */ + if ((error = in6_pcbladdr(inp, nam, &addr6)) != 0) + return(error); + + if (in6_pcblookup_hash(inp->inp_pcbinfo, &sin6->sin6_addr, + sin6->sin6_port, + IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr) + ? addr6 : &inp->in6p_laddr, + inp->inp_lport, 0, NULL) != NULL) { + return (EADDRINUSE); + } + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) { + if (inp->inp_lport == 0) { + error = in6_pcbbind(inp, (struct sockaddr *)0, td); + if (error) + return (error); + } + inp->in6p_laddr = *addr6; + } + inp->in6p_faddr = sin6->sin6_addr; + inp->inp_fport = sin6->sin6_port; + /* update flowinfo - draft-itojun-ipv6-flowlabel-api-00 */ + inp->in6p_flowinfo &= ~IPV6_FLOWLABEL_MASK; + if (inp->in6p_flags & IN6P_AUTOFLOWLABEL) + inp->in6p_flowinfo |= + (htonl(ip6_flow_seq++) & IPV6_FLOWLABEL_MASK); + + in_pcbrehash(inp); + return (0); +} + +#if 0 +/* + * Return an IPv6 address, which is the most appropriate for given + * destination and user specified options. + * If necessary, this function lookups the routing table and return + * an entry to the caller for later use. + */ +struct in6_addr * +in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp) + struct sockaddr_in6 *dstsock; + struct ip6_pktopts *opts; + struct ip6_moptions *mopts; + struct route_in6 *ro; + struct in6_addr *laddr; + int *errorp; +{ + struct in6_addr *dst; + struct in6_ifaddr *ia6 = 0; + struct in6_pktinfo *pi = NULL; + + dst = &dstsock->sin6_addr; + *errorp = 0; + + /* + * If the source address is explicitly specified by the caller, + * use it. + */ + if (opts && (pi = opts->ip6po_pktinfo) && + !IN6_IS_ADDR_UNSPECIFIED(&pi->ipi6_addr)) + return(&pi->ipi6_addr); + + /* + * If the source address is not specified but the socket(if any) + * is already bound, use the bound address. + */ + if (laddr && !IN6_IS_ADDR_UNSPECIFIED(laddr)) + return(laddr); + + /* + * If the caller doesn't specify the source address but + * the outgoing interface, use an address associated with + * the interface. + */ + if (pi && pi->ipi6_ifindex) { + /* XXX boundary check is assumed to be already done. */ + ia6 = in6_ifawithscope(ifnet_byindex(pi->ipi6_ifindex), dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + /* + * If the destination address is a link-local unicast address or + * a multicast address, and if the outgoing interface is specified + * by the sin6_scope_id filed, use an address associated with the + * interface. + * XXX: We're now trying to define more specific semantics of + * sin6_scope_id field, so this part will be rewritten in + * the near future. + */ + if ((IN6_IS_ADDR_LINKLOCAL(dst) || IN6_IS_ADDR_MULTICAST(dst)) && + dstsock->sin6_scope_id) { + /* + * I'm not sure if boundary check for scope_id is done + * somewhere... + */ + if (dstsock->sin6_scope_id < 0 || + if_index < dstsock->sin6_scope_id) { + *errorp = ENXIO; /* XXX: better error? */ + return(0); + } + ia6 = in6_ifawithscope(ifnet_byindex(dstsock->sin6_scope_id), + dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + /* + * If the destination address is a multicast address and + * the outgoing interface for the address is specified + * by the caller, use an address associated with the interface. + * There is a sanity check here; if the destination has node-local + * scope, the outgoing interfacde should be a loopback address. + * Even if the outgoing interface is not specified, we also + * choose a loopback interface as the outgoing interface. + */ + if (IN6_IS_ADDR_MULTICAST(dst)) { + struct ifnet *ifp = mopts ? mopts->im6o_multicast_ifp : NULL; + + if (ifp == NULL && IN6_IS_ADDR_MC_NODELOCAL(dst)) { + ifp = &loif[0]; + } + + if (ifp) { + ia6 = in6_ifawithscope(ifp, dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&ia6->ia_addr.sin6_addr); + } + } + + /* + * If the next hop address for the packet is specified + * by caller, use an address associated with the route + * to the next hop. + */ + { + struct sockaddr_in6 *sin6_next; + struct rtentry *rt; + + if (opts && opts->ip6po_nexthop) { + sin6_next = satosin6(opts->ip6po_nexthop); + rt = nd6_lookup(&sin6_next->sin6_addr, 1, NULL); + if (rt) { + ia6 = in6_ifawithscope(rt->rt_ifp, dst); + if (ia6 == 0) + ia6 = ifatoia6(rt->rt_ifa); + } + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + } + + /* + * If route is known or can be allocated now, + * our src addr is taken from the i/f, else punt. + */ + if (ro) { + if (ro->ro_rt && + !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr, dst)) { + RTFREE(ro->ro_rt); + ro->ro_rt = (struct rtentry *)0; + } + if (ro->ro_rt == (struct rtentry *)0 || + ro->ro_rt->rt_ifp == (struct ifnet *)0) { + struct sockaddr_in6 *dst6; + + /* No route yet, so try to acquire one */ + bzero(&ro->ro_dst, sizeof(struct sockaddr_in6)); + dst6 = (struct sockaddr_in6 *)&ro->ro_dst; + dst6->sin6_family = AF_INET6; + dst6->sin6_len = sizeof(struct sockaddr_in6); + dst6->sin6_addr = *dst; + if (IN6_IS_ADDR_MULTICAST(dst)) { + ro->ro_rt = rtalloc1(&((struct route *)ro) + ->ro_dst, 0, 0UL); + } else { + rtalloc((struct route *)ro); + } + } + + /* + * in_pcbconnect() checks out IFF_LOOPBACK to skip using + * the address. But we don't know why it does so. + * It is necessary to ensure the scope even for lo0 + * so doesn't check out IFF_LOOPBACK. + */ + + if (ro->ro_rt) { + ia6 = in6_ifawithscope(ro->ro_rt->rt_ifa->ifa_ifp, dst); + if (ia6 == 0) /* xxx scope error ?*/ + ia6 = ifatoia6(ro->ro_rt->rt_ifa); + } + if (ia6 == 0) { + *errorp = EHOSTUNREACH; /* no route */ + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + *errorp = EADDRNOTAVAIL; + return(0); +} + +/* + * Default hop limit selection. The precedence is as follows: + * 1. Hoplimit valued specified via ioctl. + * 2. (If the outgoing interface is detected) the current + * hop limit of the interface specified by router advertisement. + * 3. The system default hoplimit. +*/ +int +in6_selecthlim(in6p, ifp) + struct in6pcb *in6p; + struct ifnet *ifp; +{ + if (in6p && in6p->in6p_hops >= 0) + return(in6p->in6p_hops); + else if (ifp) + return(nd_ifinfo[ifp->if_index].chlim); + else + return(ip6_defhlim); +} +#endif + +void +in6_pcbdisconnect(inp) + struct inpcb *inp; +{ + bzero((caddr_t)&inp->in6p_faddr, sizeof(inp->in6p_faddr)); + inp->inp_fport = 0; + /* clear flowinfo - draft-itojun-ipv6-flowlabel-api-00 */ + inp->in6p_flowinfo &= ~IPV6_FLOWLABEL_MASK; + in_pcbrehash(inp); + if (inp->inp_socket->so_state & SS_NOFDREF) + in6_pcbdetach(inp); +} + +void +in6_pcbdetach(inp) + struct inpcb *inp; +{ + struct socket *so = inp->inp_socket; + struct inpcbinfo *ipi = inp->inp_pcbinfo; + +#ifdef IPSEC + if (inp->in6p_sp != NULL) + ipsec6_delete_pcbpolicy(inp); +#endif /* IPSEC */ + inp->inp_gencnt = ++ipi->ipi_gencnt; + in_pcbremlists(inp); + if (so) { + so->so_pcb = NULL; + sotryfree(so); + } + if (inp->in6p_options) + m_freem(inp->in6p_options); + ip6_freepcbopts(inp->in6p_outputopts); + ip6_freemoptions(inp->in6p_moptions); + if (inp->in6p_route.ro_rt) + rtfree(inp->in6p_route.ro_rt); + /* Check and free IPv4 related resources in case of mapped addr */ + if (inp->inp_options) + (void)m_free(inp->inp_options); + ip_freemoptions(inp->inp_moptions); + inp->inp_vflag = 0; + INP_LOCK_DESTROY(inp); + uma_zfree(ipi->ipi_zone, inp); +} + +struct sockaddr * +in6_sockaddr(port, addr_p) + in_port_t port; + struct in6_addr *addr_p; +{ + struct sockaddr_in6 *sin6; + + MALLOC(sin6, struct sockaddr_in6 *, sizeof *sin6, M_SONAME, M_WAITOK); + bzero(sin6, sizeof *sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_port = port; + sin6->sin6_addr = *addr_p; + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + sin6->sin6_scope_id = ntohs(sin6->sin6_addr.s6_addr16[1]); + else + sin6->sin6_scope_id = 0; /*XXX*/ + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + sin6->sin6_addr.s6_addr16[1] = 0; + + return (struct sockaddr *)sin6; +} + +struct sockaddr * +in6_v4mapsin6_sockaddr(port, addr_p) + in_port_t port; + struct in_addr *addr_p; +{ + struct sockaddr_in sin; + struct sockaddr_in6 *sin6_p; + + bzero(&sin, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_port = port; + sin.sin_addr = *addr_p; + + MALLOC(sin6_p, struct sockaddr_in6 *, sizeof *sin6_p, M_SONAME, + M_WAITOK); + in6_sin_2_v4mapsin6(&sin, sin6_p); + + return (struct sockaddr *)sin6_p; +} + +/* + * The calling convention of in6_setsockaddr() and in6_setpeeraddr() was + * modified to match the pru_sockaddr() and pru_peeraddr() entry points + * in struct pr_usrreqs, so that protocols can just reference then directly + * without the need for a wrapper function. The socket must have a valid + * (i.e., non-nil) PCB, but it should be impossible to get an invalid one + * except through a kernel programming error, so it is acceptable to panic + * (or in this case trap) if the PCB is invalid. (Actually, we don't trap + * because there actually /is/ a programming error somewhere... XXX) + */ +int +in6_setsockaddr(so, nam) + struct socket *so; + struct sockaddr **nam; +{ + int s; + register struct inpcb *inp; + struct in6_addr addr; + in_port_t port; + + s = splnet(); + inp = sotoinpcb(so); + if (!inp) { + splx(s); + return EINVAL; + } + port = inp->inp_lport; + addr = inp->in6p_laddr; + splx(s); + + *nam = in6_sockaddr(port, &addr); + return 0; +} + +int +in6_setpeeraddr(so, nam) + struct socket *so; + struct sockaddr **nam; +{ + int s; + struct inpcb *inp; + struct in6_addr addr; + in_port_t port; + + s = splnet(); + inp = sotoinpcb(so); + if (!inp) { + splx(s); + return EINVAL; + } + port = inp->inp_fport; + addr = inp->in6p_faddr; + splx(s); + + *nam = in6_sockaddr(port, &addr); + return 0; +} + +int +in6_mapped_sockaddr(struct socket *so, struct sockaddr **nam) +{ + struct inpcb *inp = sotoinpcb(so); + int error; + + if (inp == NULL) + return EINVAL; + if (inp->inp_vflag & INP_IPV4) { + error = in_setsockaddr(so, nam, &tcbinfo); + if (error == 0) + in6_sin_2_v4mapsin6_in_sock(nam); + } else + /* scope issues will be handled in in6_setsockaddr(). */ + error = in6_setsockaddr(so, nam); + + return error; +} + +int +in6_mapped_peeraddr(struct socket *so, struct sockaddr **nam) +{ + struct inpcb *inp = sotoinpcb(so); + int error; + + if (inp == NULL) + return EINVAL; + if (inp->inp_vflag & INP_IPV4) { + error = in_setpeeraddr(so, nam, &tcbinfo); + if (error == 0) + in6_sin_2_v4mapsin6_in_sock(nam); + } else + /* scope issues will be handled in in6_setpeeraddr(). */ + error = in6_setpeeraddr(so, nam); + + return error; +} + +/* + * Pass some notification to all connections of a protocol + * associated with address dst. The local address and/or port numbers + * may be specified to limit the search. The "usual action" will be + * taken, depending on the ctlinput cmd. The caller must filter any + * cmds that are uninteresting (e.g., no error in the map). + * Call the protocol specific routine (if any) to report + * any errors for each matching socket. + * + * Must be called at splnet. + */ +void +in6_pcbnotify(head, dst, fport_arg, src, lport_arg, cmd, notify) + struct inpcbhead *head; + struct sockaddr *dst; + const struct sockaddr *src; + u_int fport_arg, lport_arg; + int cmd; + struct inpcb *(*notify) __P((struct inpcb *, int)); +{ + struct inpcb *inp, *ninp; + struct sockaddr_in6 sa6_src, *sa6_dst; + u_short fport = fport_arg, lport = lport_arg; + u_int32_t flowinfo; + int errno, s; + + if ((unsigned)cmd > PRC_NCMDS || dst->sa_family != AF_INET6) + return; + + sa6_dst = (struct sockaddr_in6 *)dst; + if (IN6_IS_ADDR_UNSPECIFIED(&sa6_dst->sin6_addr)) + return; + + /* + * note that src can be NULL when we get notify by local fragmentation. + */ + sa6_src = (src == NULL) ? sa6_any : *(const struct sockaddr_in6 *)src; + flowinfo = sa6_src.sin6_flowinfo; + + /* + * Redirects go to all references to the destination, + * and use in6_rtchange to invalidate the route cache. + * Dead host indications: also use in6_rtchange to invalidate + * the cache, and deliver the error to all the sockets. + * Otherwise, if we have knowledge of the local port and address, + * deliver only to that socket. + */ + if (PRC_IS_REDIRECT(cmd) || cmd == PRC_HOSTDEAD) { + fport = 0; + lport = 0; + bzero((caddr_t)&sa6_src.sin6_addr, sizeof(sa6_src.sin6_addr)); + + if (cmd != PRC_HOSTDEAD) + notify = in6_rtchange; + } + errno = inet6ctlerrmap[cmd]; + s = splnet(); + for (inp = LIST_FIRST(head); inp != NULL; inp = ninp) { + ninp = LIST_NEXT(inp, inp_list); + + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + + /* + * Detect if we should notify the error. If no source and + * destination ports are specifed, but non-zero flowinfo and + * local address match, notify the error. This is the case + * when the error is delivered with an encrypted buffer + * by ESP. Otherwise, just compare addresses and ports + * as usual. + */ + if (lport == 0 && fport == 0 && flowinfo && + inp->inp_socket != NULL && + flowinfo == (inp->in6p_flowinfo & IPV6_FLOWLABEL_MASK) && + IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, &sa6_src.sin6_addr)) + goto do_notify; + else if (!IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, + &sa6_dst->sin6_addr) || + inp->inp_socket == 0 || + (lport && inp->inp_lport != lport) || + (!IN6_IS_ADDR_UNSPECIFIED(&sa6_src.sin6_addr) && + !IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, + &sa6_src.sin6_addr)) || + (fport && inp->inp_fport != fport)) + continue; + + do_notify: + if (notify) + (*notify)(inp, errno); + } + splx(s); +} + +/* + * Lookup a PCB based on the local address and port. + */ +struct inpcb * +in6_pcblookup_local(pcbinfo, laddr, lport_arg, wild_okay) + struct inpcbinfo *pcbinfo; + struct in6_addr *laddr; + u_int lport_arg; + int wild_okay; +{ + register struct inpcb *inp; + int matchwild = 3, wildcard; + u_short lport = lport_arg; + + if (!wild_okay) { + struct inpcbhead *head; + /* + * Look for an unconnected (wildcard foreign addr) PCB that + * matches the local address and port we're looking for. + */ + head = &pcbinfo->hashbase[INP_PCBHASH(INADDR_ANY, lport, 0, + pcbinfo->hashmask)]; + LIST_FOREACH(inp, head, inp_hash) { + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) && + IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && + inp->inp_lport == lport) { + /* + * Found. + */ + return (inp); + } + } + /* + * Not found. + */ + return (NULL); + } else { + struct inpcbporthead *porthash; + struct inpcbport *phd; + struct inpcb *match = NULL; + /* + * Best fit PCB lookup. + * + * First see if this local port is in use by looking on the + * port hash list. + */ + porthash = &pcbinfo->porthashbase[INP_PCBPORTHASH(lport, + pcbinfo->porthashmask)]; + LIST_FOREACH(phd, porthash, phd_hash) { + if (phd->phd_port == lport) + break; + } + if (phd != NULL) { + /* + * Port is in use by one or more PCBs. Look for best + * fit. + */ + LIST_FOREACH(inp, &phd->phd_pcblist, inp_portlist) { + wildcard = 0; + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + wildcard++; + if (!IN6_IS_ADDR_UNSPECIFIED( + &inp->in6p_laddr)) { + if (IN6_IS_ADDR_UNSPECIFIED(laddr)) + wildcard++; + else if (!IN6_ARE_ADDR_EQUAL( + &inp->in6p_laddr, laddr)) + continue; + } else { + if (!IN6_IS_ADDR_UNSPECIFIED(laddr)) + wildcard++; + } + if (wildcard < matchwild) { + match = inp; + matchwild = wildcard; + if (matchwild == 0) { + break; + } + } + } + } + return (match); + } +} + +void +in6_pcbpurgeif0(head, ifp) + struct in6pcb *head; + struct ifnet *ifp; +{ + struct in6pcb *in6p; + struct ip6_moptions *im6o; + struct in6_multi_mship *imm, *nimm; + + for (in6p = head; in6p != NULL; in6p = LIST_NEXT(in6p, inp_list)) { + im6o = in6p->in6p_moptions; + if ((in6p->inp_vflag & INP_IPV6) && + im6o) { + /* + * Unselect the outgoing interface if it is being + * detached. + */ + if (im6o->im6o_multicast_ifp == ifp) + im6o->im6o_multicast_ifp = NULL; + + /* + * Drop multicast group membership if we joined + * through the interface being detached. + * XXX controversial - is it really legal for kernel + * to force this? + */ + for (imm = im6o->im6o_memberships.lh_first; + imm != NULL; imm = nimm) { + nimm = imm->i6mm_chain.le_next; + if (imm->i6mm_maddr->in6m_ifp == ifp) { + LIST_REMOVE(imm, i6mm_chain); + in6_delmulti(imm->i6mm_maddr); + free(imm, M_IPMADDR); + } + } + } + } +} + +/* + * Check for alternatives when higher level complains + * about service problems. For now, invalidate cached + * routing information. If the route was created dynamically + * (by a redirect), time to try a default gateway again. + */ +void +in6_losing(in6p) + struct inpcb *in6p; +{ + struct rtentry *rt; + struct rt_addrinfo info; + + if ((rt = in6p->in6p_route.ro_rt) != NULL) { + bzero((caddr_t)&info, sizeof(info)); + info.rti_flags = rt->rt_flags; + info.rti_info[RTAX_DST] = rt_key(rt); + info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; + info.rti_info[RTAX_NETMASK] = rt_mask(rt); + rt_missmsg(RTM_LOSING, &info, rt->rt_flags, 0); + if (rt->rt_flags & RTF_DYNAMIC) + (void)rtrequest1(RTM_DELETE, &info, NULL); + in6p->in6p_route.ro_rt = NULL; + rtfree(rt); + /* + * A new route can be allocated + * the next time output is attempted. + */ + } +} + +/* + * After a routing change, flush old routing + * and allocate a (hopefully) better one. + */ +struct inpcb * +in6_rtchange(inp, errno) + struct inpcb *inp; + int errno; +{ + if (inp->in6p_route.ro_rt) { + rtfree(inp->in6p_route.ro_rt); + inp->in6p_route.ro_rt = 0; + /* + * A new route can be allocated the next time + * output is attempted. + */ + } + return inp; +} + +/* + * Lookup PCB in hash list. + */ +struct inpcb * +in6_pcblookup_hash(pcbinfo, faddr, fport_arg, laddr, lport_arg, wildcard, ifp) + struct inpcbinfo *pcbinfo; + struct in6_addr *faddr, *laddr; + u_int fport_arg, lport_arg; + int wildcard; + struct ifnet *ifp; +{ + struct inpcbhead *head; + register struct inpcb *inp; + u_short fport = fport_arg, lport = lport_arg; + int faith; + + if (faithprefix_p != NULL) + faith = (*faithprefix_p)(laddr); + else + faith = 0; + + /* + * First look for an exact match. + */ + head = &pcbinfo->hashbase[INP_PCBHASH(faddr->s6_addr32[3] /* XXX */, + lport, fport, + pcbinfo->hashmask)]; + LIST_FOREACH(inp, head, inp_hash) { + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + if (IN6_ARE_ADDR_EQUAL(&inp->in6p_faddr, faddr) && + IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, laddr) && + inp->inp_fport == fport && + inp->inp_lport == lport) { + /* + * Found. + */ + return (inp); + } + } + if (wildcard) { + struct inpcb *local_wild = NULL; + + head = &pcbinfo->hashbase[INP_PCBHASH(INADDR_ANY, lport, 0, + pcbinfo->hashmask)]; + LIST_FOREACH(inp, head, inp_hash) { + if ((inp->inp_vflag & INP_IPV6) == 0) + continue; + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr) && + inp->inp_lport == lport) { + if (faith && (inp->inp_flags & INP_FAITH) == 0) + continue; + if (IN6_ARE_ADDR_EQUAL(&inp->in6p_laddr, + laddr)) + return (inp); + else if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_laddr)) + local_wild = inp; + } + } + return (local_wild); + } + + /* + * Not found. + */ + return (NULL); +} + +void +init_sin6(struct sockaddr_in6 *sin6, struct mbuf *m) +{ + struct ip6_hdr *ip; + + ip = mtod(m, struct ip6_hdr *); + bzero(sin6, sizeof(*sin6)); + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr = ip->ip6_src; + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = + (m->m_pkthdr.rcvif && IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + ? m->m_pkthdr.rcvif->if_index : 0; + + return; +} diff --git a/sys/netinet6/in6_pcb.h b/sys/netinet6/in6_pcb.h new file mode 100644 index 0000000..6d5c609 --- /dev/null +++ b/sys/netinet6/in6_pcb.h @@ -0,0 +1,116 @@ +/* $FreeBSD$ */ +/* $KAME: in6_pcb.h,v 1.13 2001/02/06 09:16:53 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + */ + +/* + * Copyright (c) 1982, 1986, 1990, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_pcb.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET6_IN6_PCB_H_ +#define _NETINET6_IN6_PCB_H_ + +#ifdef _KERNEL +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) +#define sin6tosa(sin6) ((struct sockaddr *)(sin6)) +#define ifatoia6(ifa) ((struct in6_ifaddr *)(ifa)) + +void in6_pcbpurgeif0 __P((struct in6pcb *, struct ifnet *)); +void in6_losing __P((struct inpcb *)); +int in6_pcballoc __P((struct socket *, struct inpcbinfo *, struct thread *)); +int in6_pcbbind __P((struct inpcb *, struct sockaddr *, struct thread *)); +int in6_pcbconnect __P((struct inpcb *, struct sockaddr *, struct thread *)); +void in6_pcbdetach __P((struct inpcb *)); +void in6_pcbdisconnect __P((struct inpcb *)); +int in6_pcbladdr __P((struct inpcb *, struct sockaddr *, + struct in6_addr **)); +struct inpcb * + in6_pcblookup_local __P((struct inpcbinfo *, + struct in6_addr *, u_int, int)); +struct inpcb * + in6_pcblookup_hash __P((struct inpcbinfo *, + struct in6_addr *, u_int, struct in6_addr *, + u_int, int, struct ifnet *)); +void in6_pcbnotify __P((struct inpcbhead *, struct sockaddr *, + u_int, const struct sockaddr *, u_int, int, + struct inpcb *(*)(struct inpcb *, int))); +struct inpcb * + in6_rtchange __P((struct inpcb *, int)); +struct sockaddr * + in6_sockaddr __P((in_port_t port, struct in6_addr *addr_p)); +struct sockaddr * + in6_v4mapsin6_sockaddr __P((in_port_t port, struct in_addr *addr_p)); +int in6_setpeeraddr __P((struct socket *so, struct sockaddr **nam)); +int in6_setsockaddr __P((struct socket *so, struct sockaddr **nam)); +int in6_mapped_sockaddr __P((struct socket *so, struct sockaddr **nam)); +int in6_mapped_peeraddr __P((struct socket *so, struct sockaddr **nam)); +struct in6_addr *in6_selectsrc __P((struct sockaddr_in6 *, + struct ip6_pktopts *, + struct ip6_moptions *, + struct route_in6 *, + struct in6_addr *, int *)); +int in6_selecthlim __P((struct in6pcb *, struct ifnet *)); +int in6_pcbsetport __P((struct in6_addr *, struct inpcb *, struct thread *)); +void init_sin6 __P((struct sockaddr_in6 *sin6, struct mbuf *m)); +#endif /* _KERNEL */ + +#endif /* !_NETINET6_IN6_PCB_H_ */ diff --git a/sys/netinet6/in6_prefix.c b/sys/netinet6/in6_prefix.c new file mode 100644 index 0000000..4292cd4 --- /dev/null +++ b/sys/netinet6/in6_prefix.c @@ -0,0 +1,1203 @@ +/* $FreeBSD$ */ +/* $KAME: in6_prefix.c,v 1.47 2001/03/25 08:41:39 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1991, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in.c 8.2 (Berkeley) 11/15/93 + */ + +#include <sys/param.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sockio.h> +#include <sys/systm.h> +#include <sys/syslog.h> +#include <sys/proc.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/ip6.h> +#include <netinet6/in6_prefix.h> +#include <netinet6/ip6_var.h> + +static MALLOC_DEFINE(M_IP6RR, "ip6rr", "IPv6 Router Renumbering Prefix"); +static MALLOC_DEFINE(M_RR_ADDR, "rp_addr", "IPv6 Router Renumbering Ifid"); + +struct rr_prhead rr_prefix; + +struct callout in6_rr_timer_ch; + +#include <net/net_osdep.h> + +static void add_each_addr __P((struct socket *so, struct rr_prefix *rpp, + struct rp_addr *rap)); +static int create_ra_entry __P((struct rp_addr **rapp)); +static int add_each_prefix __P((struct socket *so, struct rr_prefix *rpp)); +static void free_rp_entries __P((struct rr_prefix *rpp)); +static int link_stray_ia6s __P((struct rr_prefix *rpp)); +static void rp_remove __P((struct rr_prefix *rpp)); + +/* + * Copy bits from src to tgt, from off bit for len bits. + * Caller must specify collect tgtsize and srcsize. + */ +static void +bit_copy(char *tgt, u_int tgtsize, char *src, u_int srcsize, + u_int off, u_int len) +{ + char *sp, *tp; + + /* arg values check */ + if (srcsize < off || srcsize < (off + len) || + tgtsize < off || tgtsize < (off + len)) { + log(LOG_ERR, + "in6_prefix.c: bit_copy: invalid args: srcsize %d,\n" + "tgtsize %d, off %d, len %d\n", srcsize, tgtsize, off, + len); + return; + } + + /* search start point */ + for (sp = src, tp = tgt; off >= 8; sp++, tp++) + off-=8; + /* copy starting bits */ + if (off) { + char setbit; + int startbits; + + startbits = min((8 - off), len); + + for (setbit = (0x80 >> off); startbits; + setbit >>= 1, startbits--, len--) + *tp |= (setbit & *sp); + tp++; + sp++; + } + /* copy midium bits */ + for (; len >= 8; sp++, tp++) { + *tp = *sp; + len-=8; + } + /* copy ending bits */ + if (len) { + char setbit; + + for (setbit = 0x80; len; setbit >>= 1, len--) + *tp |= (setbit & *sp); + } +} + +static struct ifprefix * +in6_prefixwithifp(struct ifnet *ifp, int plen, struct in6_addr *dst) +{ + struct ifprefix *ifpr; + + /* search matched prefix */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + if (plen <= in6_matchlen(dst, IFPR_IN6(ifpr))) + break; + } + return (ifpr); +} + +/* + * Search prefix which matches arg prefix as specified in + * draft-ietf-ipngwg-router-renum-08.txt + */ +static struct rr_prefix * +search_matched_prefix(struct ifnet *ifp, struct in6_prefixreq *ipr) +{ + struct ifprefix *ifpr; + struct ifaddr *ifa; + struct rr_prefix *rpp; + + /* search matched prefix */ + ifpr = in6_prefixwithifp(ifp, ipr->ipr_plen, + &ipr->ipr_prefix.sin6_addr); + if (ifpr != NULL) + return ifpr2rp(ifpr); + + /* + * search matched addr, and then search prefix + * which matches the addr + */ + + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (ipr->ipr_plen <= + in6_matchlen(&ipr->ipr_prefix.sin6_addr, IFA_IN6(ifa))) + break; + } + if (ifa == NULL) + return NULL; + + rpp = ifpr2rp(((struct in6_ifaddr *)ifa)->ia6_ifpr); + if (rpp != 0) + return rpp; + + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + if (ifpr->ifpr_plen <= in6_matchlen(IFA_IN6(ifa), + IFPR_IN6(ifpr))) + break; + } + if (ifpr != NULL) + log(LOG_ERR, "in6_prefix.c: search_matched_prefix: addr %s" + "has no pointer to prefix %s\n", ip6_sprintf(IFA_IN6(ifa)), + ip6_sprintf(IFPR_IN6(ifpr))); + return ifpr2rp(ifpr); +} + +/* + * Search prefix which matches arg prefix as specified in + * draft-ietf-ipngwg-router-renum-08.txt, and mark it if exists. + * Return 1 if anything matched, and 0 if nothing matched. + */ +static int +mark_matched_prefixes(u_long cmd, struct ifnet *ifp, struct in6_rrenumreq *irr) +{ + struct ifprefix *ifpr; + struct ifaddr *ifa; + int matchlen, matched = 0; + + /* search matched prefixes */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + matchlen = in6_matchlen(&irr->irr_matchprefix.sin6_addr, + IFPR_IN6(ifpr)); + if (irr->irr_m_minlen > ifpr->ifpr_plen || + irr->irr_m_maxlen < ifpr->ifpr_plen || + irr->irr_m_len > matchlen) + continue; + matched = 1; + ifpr2rp(ifpr)->rp_statef_addmark = 1; + if (cmd == SIOCCIFPREFIX_IN6) + ifpr2rp(ifpr)->rp_statef_delmark = 1; + } + + /* + * search matched addr, and then search prefixes + * which matche the addr + */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + struct rr_prefix *rpp; + + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + matchlen = in6_matchlen(&irr->irr_matchprefix.sin6_addr, + IFA_IN6(ifa)); + if (irr->irr_m_minlen > matchlen || + irr->irr_m_maxlen < matchlen || irr->irr_m_len > matchlen) + continue; + rpp = ifpr2rp(((struct in6_ifaddr *)ifa)->ia6_ifpr); + if (rpp != 0) { + matched = 1; + rpp->rp_statef_addmark = 1; + if (cmd == SIOCCIFPREFIX_IN6) + rpp->rp_statef_delmark = 1; + } else + log(LOG_WARNING, "in6_prefix.c: mark_matched_prefixes:" + "no back pointer to ifprefix for %s. " + "ND autoconfigured addr?\n", + ip6_sprintf(IFA_IN6(ifa))); + } + return matched; +} + +/* + * Mark global prefixes as to be deleted. + */ +static void +delmark_global_prefixes(struct ifnet *ifp, struct in6_rrenumreq *irr) +{ + struct ifprefix *ifpr; + + /* search matched prefixes */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + /* mark delete global prefix */ + if (in6_addrscope(RP_IN6(ifpr2rp(ifpr))) == + IPV6_ADDR_SCOPE_GLOBAL) + ifpr2rp(ifpr)->rp_statef_delmark = 1; + } +} + +/* Unmark prefixes */ +static void +unmark_prefixes(struct ifnet *ifp) +{ + struct ifprefix *ifpr; + + /* unmark all prefix */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + /* unmark prefix */ + ifpr2rp(ifpr)->rp_statef_addmark = 0; + ifpr2rp(ifpr)->rp_statef_delmark = 0; + } +} + +static void +init_prefix_ltimes(struct rr_prefix *rpp) +{ + + if (rpp->rp_pltime == RR_INFINITE_LIFETIME || + rpp->rp_rrf_decrprefd == 0) + rpp->rp_preferred = 0; + else + rpp->rp_preferred = time_second + rpp->rp_pltime; + if (rpp->rp_vltime == RR_INFINITE_LIFETIME || + rpp->rp_rrf_decrvalid == 0) + rpp->rp_expire = 0; + else + rpp->rp_expire = time_second + rpp->rp_vltime; +} + +static int +rr_are_ifid_equal(struct in6_addr *ii1, struct in6_addr *ii2, int ii_len) +{ + int ii_bytelen, ii_bitlen; + int p_bytelen, p_bitlen; + + /* sanity check */ + if (1 > ii_len || + ii_len > 124) { /* as RFC2373, prefix is at least 4 bit */ + log(LOG_ERR, "rr_are_ifid_equal: invalid ifid length(%d)\n", + ii_len); + return(0); + } + + ii_bytelen = ii_len / 8; + ii_bitlen = ii_len % 8; + + p_bytelen = sizeof(struct in6_addr) - ii_bytelen - 1; + p_bitlen = 8 - ii_bitlen; + + if (bcmp(ii1->s6_addr + p_bytelen + 1, ii2->s6_addr + p_bytelen + 1, + ii_bytelen)) + return(0); + if (((ii1->s6_addr[p_bytelen] << p_bitlen) & 0xff) != + ((ii2->s6_addr[p_bytelen] << p_bitlen) & 0xff)) + return(0); + + return(1); +} + +static struct rp_addr * +search_ifidwithprefix(struct rr_prefix *rpp, struct in6_addr *ifid) +{ + struct rp_addr *rap; + + LIST_FOREACH(rap, &rpp->rp_addrhead, ra_entry) + { + if (rr_are_ifid_equal(ifid, &rap->ra_ifid, + (sizeof(struct in6_addr) << 3) - + rpp->rp_plen)) + break; + } + return rap; +} + +static int +assign_ra_entry(struct rr_prefix *rpp, int iilen, struct in6_ifaddr *ia) +{ + int error = 0; + struct rp_addr *rap; + int s; + + if ((error = create_ra_entry(&rap)) != 0) + return error; + + /* copy interface id part */ + bit_copy((caddr_t)&rap->ra_ifid, sizeof(rap->ra_ifid) << 3, + (caddr_t)IA6_IN6(ia), + sizeof(*IA6_IN6(ia)) << 3, rpp->rp_plen, iilen); + /* link to ia, and put into list */ + rap->ra_addr = ia; + IFAREF(&rap->ra_addr->ia_ifa); +#if 0 /* Can't do this now, because rpp may be on th stack. should fix it? */ + ia->ia6_ifpr = rp2ifpr(rpp); +#endif + s = splnet(); + LIST_INSERT_HEAD(&rpp->rp_addrhead, rap, ra_entry); + splx(s); + + return 0; +} + +/* + * add a link-local address to an interface. we will add new interface address + * (prefix database + new interface id). + */ +static int +in6_prefix_add_llifid(int iilen, struct in6_ifaddr *ia) +{ + struct rr_prefix *rpp; + struct rp_addr *rap; + struct socket so; + int error, s; + + if ((error = create_ra_entry(&rap)) != 0) + return(error); + /* copy interface id part */ + bit_copy((caddr_t)&rap->ra_ifid, sizeof(rap->ra_ifid) << 3, + (caddr_t)IA6_IN6(ia), sizeof(*IA6_IN6(ia)) << 3, + 64, (sizeof(rap->ra_ifid) << 3) - 64); + /* XXX: init dummy so */ + bzero(&so, sizeof(so)); + /* insert into list */ + LIST_FOREACH(rpp, &rr_prefix, rp_entry) + { + /* + * do not attempt to add an address, if ifp does not match + */ + if (rpp->rp_ifp != ia->ia_ifp) + continue; + + s = splnet(); + LIST_INSERT_HEAD(&rpp->rp_addrhead, rap, ra_entry); + splx(s); + add_each_addr(&so, rpp, rap); + } + return 0; +} + +/* + * add an address to an interface. if the interface id portion is new, + * we will add new interface address (prefix database + new interface id). + */ +int +in6_prefix_add_ifid(int iilen, struct in6_ifaddr *ia) +{ + int plen = (sizeof(*IA6_IN6(ia)) << 3) - iilen; + struct ifprefix *ifpr; + struct rp_addr *rap; + int error = 0; + + if (IN6_IS_ADDR_LINKLOCAL(IA6_IN6(ia))) + return(in6_prefix_add_llifid(iilen, ia)); + ifpr = in6_prefixwithifp(ia->ia_ifp, plen, IA6_IN6(ia)); + if (ifpr == NULL) { + struct rr_prefix rp; + struct socket so; + int pplen = (plen == 128) ? 64 : plen; /* XXX hardcoded 64 is bad */ + + /* allocate a prefix for ia, with default properties */ + + /* init rp */ + bzero(&rp, sizeof(rp)); + rp.rp_type = IN6_PREFIX_RR; + rp.rp_ifp = ia->ia_ifp; + rp.rp_plen = pplen; + rp.rp_prefix.sin6_len = sizeof(rp.rp_prefix); + rp.rp_prefix.sin6_family = AF_INET6; + bit_copy((char *)RP_IN6(&rp), sizeof(*RP_IN6(&rp)) << 3, + (char *)&ia->ia_addr.sin6_addr, + sizeof(ia->ia_addr.sin6_addr) << 3, + 0, pplen); + rp.rp_vltime = rp.rp_pltime = RR_INFINITE_LIFETIME; + rp.rp_raf_onlink = 1; + rp.rp_raf_auto = 1; + /* Is some FlagMasks for rrf necessary? */ + rp.rp_rrf_decrvalid = rp.rp_rrf_decrprefd = 0; + rp.rp_origin = PR_ORIG_RR; /* can be renumbered */ + + /* create ra_entry */ + error = link_stray_ia6s(&rp); + if (error != 0) { + free_rp_entries(&rp); + return error; + } + + /* XXX: init dummy so */ + bzero(&so, sizeof(so)); + + error = add_each_prefix(&so, &rp); + + /* free each rp_addr entry */ + free_rp_entries(&rp); + + if (error != 0) + return error; + + /* search again */ + ifpr = in6_prefixwithifp(ia->ia_ifp, pplen, IA6_IN6(ia)); + if (ifpr == NULL) + return 0; + } + rap = search_ifidwithprefix(ifpr2rp(ifpr), IA6_IN6(ia)); + if (rap != NULL) { + if (rap->ra_addr == NULL) { + rap->ra_addr = ia; + IFAREF(&rap->ra_addr->ia_ifa); + } else if (rap->ra_addr != ia) { + /* There may be some inconsistencies between addrs. */ + log(LOG_ERR, "ip6_prefix.c: addr %s/%d matched prefix" + " already has another ia %p(%s) on its ifid list\n", + ip6_sprintf(IA6_IN6(ia)), plen, + rap->ra_addr, + ip6_sprintf(IA6_IN6(rap->ra_addr))); + return EADDRINUSE /* XXX */; + } + ia->ia6_ifpr = ifpr; + return 0; + } + error = assign_ra_entry(ifpr2rp(ifpr), iilen, ia); + if (error == 0) + ia->ia6_ifpr = ifpr; + return (error); +} + +void +in6_prefix_remove_ifid(int iilen, struct in6_ifaddr *ia) +{ + struct rp_addr *rap; + + if (ia->ia6_ifpr == NULL) + return; + rap = search_ifidwithprefix(ifpr2rp(ia->ia6_ifpr), IA6_IN6(ia)); + if (rap != NULL) { + int s = splnet(); + LIST_REMOVE(rap, ra_entry); + splx(s); + if (rap->ra_addr) + IFAFREE(&rap->ra_addr->ia_ifa); + free(rap, M_RR_ADDR); + } + + if (LIST_EMPTY(&ifpr2rp(ia->ia6_ifpr)->rp_addrhead)) + rp_remove(ifpr2rp(ia->ia6_ifpr)); +} + +void +in6_purgeprefix(ifp) + struct ifnet *ifp; +{ + struct ifprefix *ifpr, *nextifpr; + + /* delete prefixes before ifnet goes away */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; + ifpr = nextifpr) + { + nextifpr = TAILQ_NEXT(ifpr, ifpr_list); + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + (void)delete_each_prefix(ifpr2rp(ifpr), PR_ORIG_KERNEL); + } +} + +static void +add_each_addr(struct socket *so, struct rr_prefix *rpp, struct rp_addr *rap) +{ + struct in6_ifaddr *ia6; + struct in6_aliasreq ifra; + int error; + + /* init ifra */ + bzero(&ifra, sizeof(ifra)); + strncpy(ifra.ifra_name, if_name(rpp->rp_ifp), sizeof(ifra.ifra_name)); + ifra.ifra_addr.sin6_family = ifra.ifra_prefixmask.sin6_family = + AF_INET6; + ifra.ifra_addr.sin6_len = ifra.ifra_prefixmask.sin6_len = + sizeof(ifra.ifra_addr); + /* copy prefix part */ + bit_copy((char *)&ifra.ifra_addr.sin6_addr, + sizeof(ifra.ifra_addr.sin6_addr) << 3, + (char *)RP_IN6(rpp), sizeof(*RP_IN6(rpp)) << 3, + 0, rpp->rp_plen); + /* copy interface id part */ + bit_copy((char *)&ifra.ifra_addr.sin6_addr, + sizeof(ifra.ifra_addr.sin6_addr) << 3, + (char *)&rap->ra_ifid, sizeof(rap->ra_ifid) << 3, + rpp->rp_plen, (sizeof(rap->ra_ifid) << 3) - rpp->rp_plen); + in6_prefixlen2mask(&ifra.ifra_prefixmask.sin6_addr, rpp->rp_plen); + /* don't care ifra_flags for now */ + + /* + * XXX: if we did this with finite lifetime values, the lifetimes would + * decrese in time and never incremented. + * we should need more clarifications on the prefix mechanism... + */ + ifra.ifra_lifetime.ia6t_vltime = rpp->rp_vltime; + ifra.ifra_lifetime.ia6t_pltime = rpp->rp_pltime; + + ia6 = in6ifa_ifpwithaddr(rpp->rp_ifp, &ifra.ifra_addr.sin6_addr); + if (ia6 != NULL) { + if (ia6->ia6_ifpr == NULL) { + /* link this addr and the prefix each other */ + if (rap->ra_addr) + IFAFREE(&rap->ra_addr->ia_ifa); + rap->ra_addr = ia6; + IFAREF(&rap->ra_addr->ia_ifa); + ia6->ia6_ifpr = rp2ifpr(rpp); + return; + } + if (ia6->ia6_ifpr == rp2ifpr(rpp)) { + if (rap->ra_addr) + IFAFREE(&rap->ra_addr->ia_ifa); + rap->ra_addr = ia6; + IFAREF(&rap->ra_addr->ia_ifa); + return; + } + /* + * The addr is already assigned to other + * prefix. + * There may be some inconsistencies between + * prefixes. + * e.g. overraped prefixes with common starting + * part and different plefixlen. + * Or, completely duplicated prefixes? + * log it and return. + */ + log(LOG_ERR, + "in6_prefix.c: add_each_addr: addition of an addr %s/%d " + "failed because there is already another addr %s/%d\n", + ip6_sprintf(&ifra.ifra_addr.sin6_addr), rpp->rp_plen, + ip6_sprintf(IA6_IN6(ia6)), + in6_mask2len(&ia6->ia_prefixmask.sin6_addr, NULL)); + return; + } + /* propagate ANYCAST flag if it is set for ancestor addr */ + if (rap->ra_flags.anycast != 0) + ifra.ifra_flags |= IN6_IFF_ANYCAST; + error = in6_control(so, SIOCAIFADDR_IN6, (caddr_t)&ifra, rpp->rp_ifp, + curthread); + if (error != 0) { + log(LOG_ERR, "in6_prefix.c: add_each_addr: addition of an addr" + "%s/%d failed because in6_control failed for error %d\n", + ip6_sprintf(&ifra.ifra_addr.sin6_addr), rpp->rp_plen, + error); + return; + } + + /* + * link beween this addr and the prefix will be done + * in in6_prefix_add_ifid + */ +} + +static int +rrpr_update(struct socket *so, struct rr_prefix *new) +{ + struct rr_prefix *rpp; + struct ifprefix *ifpr; + struct rp_addr *rap; + int s; + + /* search existing prefix */ + for (ifpr = TAILQ_FIRST(&new->rp_ifp->if_prefixhead); ifpr; + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + { + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + if (ifpr->ifpr_plen == new->rp_plen && + in6_are_prefix_equal(IFPR_IN6(ifpr), RP_IN6(new), + ifpr->ifpr_plen)) + break; + } + rpp = ifpr2rp(ifpr); + if (rpp != NULL) { + /* + * We got a prefix which we have seen in the past. + */ + /* + * If the origin of the already-installed prefix is more + * preferable than the new one, ignore installation request. + */ + if (rpp->rp_origin > new->rp_origin) + return(EPERM); + + /* update prefix information */ + rpp->rp_flags.prf_ra = new->rp_flags.prf_ra; + if (rpp->rp_origin >= PR_ORIG_RR) + rpp->rp_flags.prf_rr = new->rp_flags.prf_rr; + rpp->rp_vltime = new->rp_vltime; + rpp->rp_pltime = new->rp_pltime; + rpp->rp_expire = new->rp_expire; + rpp->rp_preferred = new->rp_preferred; + rpp->rp_statef_delmark = 0; /* cancel deletion */ + /* + * Interface id related update. + * add rp_addr entries in new into rpp, if they have not + * been already included in rpp. + */ + while (!LIST_EMPTY(&new->rp_addrhead)) + { + rap = LIST_FIRST(&new->rp_addrhead); + LIST_REMOVE(rap, ra_entry); + if (search_ifidwithprefix(rpp, &rap->ra_ifid) + != NULL) { + if (rap->ra_addr) + IFAFREE(&rap->ra_addr->ia_ifa); + free(rap, M_RR_ADDR); + continue; + } + s = splnet(); + LIST_INSERT_HEAD(&rpp->rp_addrhead, rap, ra_entry); + splx(s); + } + } else { + /* + * We got a fresh prefix. + */ + /* create new prefix */ + rpp = (struct rr_prefix *)malloc(sizeof(*rpp), M_IP6RR, + M_NOWAIT); + if (rpp == NULL) { + log(LOG_ERR, "in6_prefix.c: rrpr_update:%d" + ": ENOBUFS for rr_prefix\n", __LINE__); + return(ENOBUFS); + } + /* initilization */ + *rpp = *new; + LIST_INIT(&rpp->rp_addrhead); + /* move rp_addr entries of new to rpp */ + while (!LIST_EMPTY(&new->rp_addrhead)) + { + rap = LIST_FIRST(&new->rp_addrhead); + LIST_REMOVE(rap, ra_entry); + LIST_INSERT_HEAD(&rpp->rp_addrhead, rap, ra_entry); + } + + /* let rp_ifpr.ifpr_prefix point rr_prefix. */ + rpp->rp_ifpr.ifpr_prefix = (struct sockaddr *)&rpp->rp_prefix; + /* link rr_prefix entry to if_prefixlist */ + { + struct ifnet *ifp = rpp->rp_ifp; + struct ifprefix *ifpr; + + if ((ifpr = TAILQ_FIRST(&ifp->if_prefixhead)) + != NULL) { + for ( ; TAILQ_NEXT(ifpr, ifpr_list); + ifpr = TAILQ_NEXT(ifpr, ifpr_list)) + continue; + TAILQ_NEXT(ifpr, ifpr_list) = rp2ifpr(rpp); + } else + TAILQ_FIRST(&ifp->if_prefixhead) = + rp2ifpr(rpp); + rp2ifpr(rpp)->ifpr_type = IN6_PREFIX_RR; + } + /* link rr_prefix entry to rr_prefix list */ + s = splnet(); + LIST_INSERT_HEAD(&rr_prefix, rpp, rp_entry); + splx(s); + } + + if (!new->rp_raf_auto) + return 0; + + /* + * Add an address for each interface id, if it is not yet + * If it existed but not pointing to the prefix yet, + * init the prefix pointer. + */ + LIST_FOREACH(rap, &rpp->rp_addrhead, ra_entry) + { + if (rap->ra_addr != NULL) { + if (rap->ra_addr->ia6_ifpr == NULL) + rap->ra_addr->ia6_ifpr = rp2ifpr(rpp); + continue; + } + add_each_addr(so, rpp, rap); + } + return 0; +} + +static int +add_each_prefix(struct socket *so, struct rr_prefix *rpp) +{ + init_prefix_ltimes(rpp); + return(rrpr_update(so, rpp)); +} + +static void +rp_remove(struct rr_prefix *rpp) +{ + int s; + + s = splnet(); + /* unlink rp_entry from if_prefixlist */ + { + struct ifnet *ifp = rpp->rp_ifp; + struct ifprefix *ifpr; + + if ((ifpr = TAILQ_FIRST(&ifp->if_prefixhead)) == rp2ifpr(rpp)) + TAILQ_FIRST(&ifp->if_prefixhead) = + TAILQ_NEXT(ifpr, ifpr_list); + else { + while (TAILQ_NEXT(ifpr, ifpr_list) != NULL && + (TAILQ_NEXT(ifpr, ifpr_list) != rp2ifpr(rpp))) + ifpr = TAILQ_NEXT(ifpr, ifpr_list); + if (TAILQ_NEXT(ifpr, ifpr_list)) + TAILQ_NEXT(ifpr, ifpr_list) = + TAILQ_NEXT(rp2ifpr(rpp), ifpr_list); + else + printf("Couldn't unlink rr_prefix from ifp\n"); + } + } + /* unlink rp_entry from rr_prefix list */ + LIST_REMOVE(rpp, rp_entry); + splx(s); + free(rpp, M_IP6RR); +} + +static int +create_ra_entry(struct rp_addr **rapp) +{ + *rapp = (struct rp_addr *)malloc(sizeof(struct rp_addr), M_RR_ADDR, + M_NOWAIT); + if (*rapp == NULL) { + log(LOG_ERR, "in6_prefix.c: init_newprefix:%d: ENOBUFS" + "for rp_addr\n", __LINE__); + return ENOBUFS; + } + bzero(*rapp, sizeof(*(*rapp))); + + return 0; +} + +static int +init_newprefix(struct in6_rrenumreq *irr, struct ifprefix *ifpr, + struct rr_prefix *rpp) +{ + struct rp_addr *orap; + + /* init rp */ + bzero(rpp, sizeof(*rpp)); + rpp->rp_type = IN6_PREFIX_RR; + rpp->rp_ifp = ifpr->ifpr_ifp; + rpp->rp_plen = ifpr->ifpr_plen; + rpp->rp_prefix.sin6_len = sizeof(rpp->rp_prefix); + rpp->rp_prefix.sin6_family = AF_INET6; + bit_copy((char *)RP_IN6(rpp), sizeof(*RP_IN6(rpp)) << 3, + (char *)&irr->irr_useprefix.sin6_addr, + sizeof(irr->irr_useprefix.sin6_addr) << 3, + 0, irr->irr_u_uselen); + /* copy keeplen part if necessary as necessary len */ + if (irr->irr_u_uselen < ifpr->ifpr_plen) + bit_copy((char *)RP_IN6(rpp), sizeof(*RP_IN6(rpp)) << 3, + (char *)IFPR_IN6(ifpr), sizeof(*IFPR_IN6(ifpr)) << 3, + irr->irr_u_uselen, + min(ifpr->ifpr_plen - irr->irr_u_uselen, + irr->irr_u_keeplen)); + LIST_FOREACH(orap, &(ifpr2rp(ifpr)->rp_addrhead), ra_entry) + { + struct rp_addr *rap; + int error = 0; + + if ((error = create_ra_entry(&rap)) != 0) + return error; + rap->ra_ifid = orap->ra_ifid; + rap->ra_flags.anycast = (orap->ra_addr != NULL && + (orap->ra_addr->ia6_flags & + IN6_IFF_ANYCAST) != 0) ? 1 : 0; + LIST_INSERT_HEAD(&rpp->rp_addrhead, rap, ra_entry); + } + rpp->rp_vltime = irr->irr_vltime; + rpp->rp_pltime = irr->irr_pltime; + rpp->rp_raf_onlink = irr->irr_raf_mask_onlink ? irr->irr_raf_onlink : + ifpr2rp(ifpr)->rp_raf_onlink; + rpp->rp_raf_auto = irr->irr_raf_mask_auto ? irr->irr_raf_auto : + ifpr2rp(ifpr)->rp_raf_auto; + /* Is some FlagMasks for rrf necessary? */ + rpp->rp_rrf = irr->irr_rrf; + rpp->rp_origin = irr->irr_origin; + + return 0; +} + +static void +free_rp_entries(struct rr_prefix *rpp) +{ + /* + * This func is only called with rpp on stack(not on list). + * So no splnet() here + */ + while (!LIST_EMPTY(&rpp->rp_addrhead)) + { + struct rp_addr *rap; + + rap = LIST_FIRST(&rpp->rp_addrhead); + LIST_REMOVE(rap, ra_entry); + if (rap->ra_addr) + IFAFREE(&rap->ra_addr->ia_ifa); + free(rap, M_RR_ADDR); + } +} + +static int +add_useprefixes(struct socket *so, struct ifnet *ifp, + struct in6_rrenumreq *irr) +{ + struct ifprefix *ifpr, *nextifpr; + struct rr_prefix rp; + int error = 0; + + /* add prefixes to each of marked prefix */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; ifpr = nextifpr) + { + nextifpr = TAILQ_NEXT(ifpr, ifpr_list); + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + if (ifpr2rp(ifpr)->rp_statef_addmark) { + if ((error = init_newprefix(irr, ifpr, &rp)) != 0) + break; + error = add_each_prefix(so, &rp); + } + } + /* free each rp_addr entry */ + free_rp_entries(&rp); + + return error; +} + +static void +unprefer_prefix(struct rr_prefix *rpp) +{ + struct rp_addr *rap; + + for (rap = rpp->rp_addrhead.lh_first; rap != NULL; + rap = rap->ra_entry.le_next) { + if (rap->ra_addr == NULL) + continue; + rap->ra_addr->ia6_lifetime.ia6t_preferred = time_second; + rap->ra_addr->ia6_lifetime.ia6t_pltime = 0; + } +} + +int +delete_each_prefix(struct rr_prefix *rpp, u_char origin) +{ + int error = 0; + + if (rpp->rp_origin > origin) + return(EPERM); + + while (rpp->rp_addrhead.lh_first != NULL) { + struct rp_addr *rap; + int s; + + s = splnet(); + rap = LIST_FIRST(&rpp->rp_addrhead); + if (rap == NULL) { + splx(s); + break; + } + LIST_REMOVE(rap, ra_entry); + splx(s); + if (rap->ra_addr == NULL) { + free(rap, M_RR_ADDR); + continue; + } + rap->ra_addr->ia6_ifpr = NULL; + + in6_purgeaddr(&rap->ra_addr->ia_ifa); + IFAFREE(&rap->ra_addr->ia_ifa); + free(rap, M_RR_ADDR); + } + rp_remove(rpp); + + return error; +} + +static void +delete_prefixes(struct ifnet *ifp, u_char origin) +{ + struct ifprefix *ifpr, *nextifpr; + + /* delete prefixes marked as tobe deleted */ + for (ifpr = TAILQ_FIRST(&ifp->if_prefixhead); ifpr; ifpr = nextifpr) + { + nextifpr = TAILQ_NEXT(ifpr, ifpr_list); + if (ifpr->ifpr_prefix->sa_family != AF_INET6 || + ifpr->ifpr_type != IN6_PREFIX_RR) + continue; + if (ifpr2rp(ifpr)->rp_statef_delmark) + (void)delete_each_prefix(ifpr2rp(ifpr), origin); + } +} + +static int +link_stray_ia6s(struct rr_prefix *rpp) +{ + struct ifaddr *ifa; + + for (ifa = rpp->rp_ifp->if_addrlist.tqh_first; ifa; + ifa = ifa->ifa_list.tqe_next) + { + struct rp_addr *rap; + struct rr_prefix *orpp; + int error = 0; + + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (rpp->rp_plen > in6_matchlen(RP_IN6(rpp), IFA_IN6(ifa))) + continue; + + orpp = ifpr2rp(((struct in6_ifaddr *)ifa)->ia6_ifpr); + if (orpp != NULL) { + if (!in6_are_prefix_equal(RP_IN6(orpp), RP_IN6(rpp), + rpp->rp_plen)) + log(LOG_ERR, "in6_prefix.c: link_stray_ia6s:" + "addr %s/%d already linked to a prefix" + "and it matches also %s/%d\n", + ip6_sprintf(IFA_IN6(ifa)), orpp->rp_plen, + ip6_sprintf(RP_IN6(rpp)), + rpp->rp_plen); + continue; + } + if ((error = assign_ra_entry(rpp, + (sizeof(rap->ra_ifid) << 3) - + rpp->rp_plen, + (struct in6_ifaddr *)ifa)) != 0) + return error; + } + return 0; +} + +/* XXX assumes that permission is already checked by the caller */ +int +in6_prefix_ioctl(struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp) +{ + struct rr_prefix *rpp, rp_tmp; + struct rp_addr *rap; + struct in6_prefixreq *ipr = (struct in6_prefixreq *)data; + struct in6_rrenumreq *irr = (struct in6_rrenumreq *)data; + struct ifaddr *ifa; + int error = 0; + + /* + * Failsafe for errneous address config program. + * Let's hope rrenumd don't make a mistakes. + */ + if (ipr->ipr_origin <= PR_ORIG_RA) + ipr->ipr_origin = PR_ORIG_STATIC; + + switch (cmd) { + case SIOCSGIFPREFIX_IN6: + delmark_global_prefixes(ifp, irr); + /* FALL THROUGH */ + case SIOCAIFPREFIX_IN6: + case SIOCCIFPREFIX_IN6: + /* check if preferred lifetime > valid lifetime */ + if (irr->irr_pltime > irr->irr_vltime) { + log(LOG_NOTICE, + "in6_prefix_ioctl: preferred lifetime" + "(%ld) is greater than valid lifetime(%ld)\n", + (u_long)irr->irr_pltime, (u_long)irr->irr_vltime); + error = EINVAL; + break; + } + if (mark_matched_prefixes(cmd, ifp, irr)) { + if (irr->irr_u_uselen != 0) + if ((error = add_useprefixes(so, ifp, irr)) + != 0) + goto failed; + if (cmd != SIOCAIFPREFIX_IN6) + delete_prefixes(ifp, irr->irr_origin); + } else + return (EADDRNOTAVAIL); + failed: + unmark_prefixes(ifp); + break; + case SIOCGIFPREFIX_IN6: + rpp = search_matched_prefix(ifp, ipr); + if (rpp == NULL || ifp != rpp->rp_ifp) + return (EADDRNOTAVAIL); + + ipr->ipr_origin = rpp->rp_origin; + ipr->ipr_plen = rpp->rp_plen; + ipr->ipr_vltime = rpp->rp_vltime; + ipr->ipr_pltime = rpp->rp_pltime; + ipr->ipr_flags = rpp->rp_flags; + ipr->ipr_prefix = rpp->rp_prefix; + + break; + case SIOCSIFPREFIX_IN6: + /* check if preferred lifetime > valid lifetime */ + if (ipr->ipr_pltime > ipr->ipr_vltime) { + log(LOG_NOTICE, + "in6_prefix_ioctl: preferred lifetime" + "(%ld) is greater than valid lifetime(%ld)\n", + (u_long)ipr->ipr_pltime, (u_long)ipr->ipr_vltime); + error = EINVAL; + break; + } + + /* init rp_tmp */ + bzero((caddr_t)&rp_tmp, sizeof(rp_tmp)); + rp_tmp.rp_ifp = ifp; + rp_tmp.rp_plen = ipr->ipr_plen; + rp_tmp.rp_prefix = ipr->ipr_prefix; + rp_tmp.rp_vltime = ipr->ipr_vltime; + rp_tmp.rp_pltime = ipr->ipr_pltime; + rp_tmp.rp_flags = ipr->ipr_flags; + rp_tmp.rp_origin = ipr->ipr_origin; + + /* create rp_addr entries, usually at least for lladdr */ + if ((error = link_stray_ia6s(&rp_tmp)) != 0) { + free_rp_entries(&rp_tmp); + break; + } + for (ifa = ifp->if_addrlist.tqh_first; + ifa; + ifa = ifa->ifa_list.tqe_next) + { + if (ifa->ifa_addr == NULL) + continue; /* just for safety */ + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + if (IN6_IS_ADDR_LINKLOCAL(IFA_IN6(ifa)) == 0) + continue; + + if ((error = create_ra_entry(&rap)) != 0) { + free_rp_entries(&rp_tmp); + goto bad; + } + /* copy interface id part */ + bit_copy((caddr_t)&rap->ra_ifid, + sizeof(rap->ra_ifid) << 3, + (caddr_t)IFA_IN6(ifa), + sizeof(*IFA_IN6(ifa)) << 3, + rp_tmp.rp_plen, + (sizeof(rap->ra_ifid) << 3) - rp_tmp.rp_plen); + /* insert into list */ + LIST_INSERT_HEAD(&rp_tmp.rp_addrhead, rap, ra_entry); + } + + error = add_each_prefix(so, &rp_tmp); + + /* free each rp_addr entry */ + free_rp_entries(&rp_tmp); + + break; + case SIOCDIFPREFIX_IN6: + rpp = search_matched_prefix(ifp, ipr); + if (rpp == NULL || ifp != rpp->rp_ifp) + return (EADDRNOTAVAIL); + + error = delete_each_prefix(rpp, ipr->ipr_origin); + break; + } + bad: + return error; +} + +void +in6_rr_timer(void *ignored_arg) +{ + int s; + struct rr_prefix *rpp; + + callout_reset(&in6_rr_timer_ch, ip6_rr_prune * hz, + in6_rr_timer, NULL); + + s = splnet(); + /* expire */ + rpp = LIST_FIRST(&rr_prefix); + while (rpp) { + if (rpp->rp_expire && rpp->rp_expire < time_second) { + struct rr_prefix *next_rpp; + + next_rpp = LIST_NEXT(rpp, rp_entry); + delete_each_prefix(rpp, PR_ORIG_KERNEL); + rpp = next_rpp; + continue; + } + if (rpp->rp_preferred && rpp->rp_preferred < time_second) + unprefer_prefix(rpp); + rpp = LIST_NEXT(rpp, rp_entry); + } + splx(s); +} diff --git a/sys/netinet6/in6_prefix.h b/sys/netinet6/in6_prefix.h new file mode 100644 index 0000000..3ae6a63 --- /dev/null +++ b/sys/netinet6/in6_prefix.h @@ -0,0 +1,91 @@ +/* $FreeBSD$ */ +/* $KAME: in6_prefix.h,v 1.10 2001/02/08 16:30:30 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, 1998 and 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include <sys/callout.h> + +struct rr_prefix { + struct ifprefix rp_ifpr; + LIST_ENTRY(rr_prefix) rp_entry; + LIST_HEAD(rp_addrhead, rp_addr) rp_addrhead; + struct sockaddr_in6 rp_prefix; /* prefix */ + u_int32_t rp_vltime; /* advertised valid lifetime */ + u_int32_t rp_pltime; /* advertised preferred lifetime */ + time_t rp_expire; /* expiration time of the prefix */ + time_t rp_preferred; /* preferred time of the prefix */ + struct in6_prflags rp_flags; + u_char rp_origin; /* from where this prefix info is obtained */ + struct rp_stateflags { + /* if some prefix should be added to this prefix */ + u_char addmark : 1; + u_char delmark : 1; /* if this prefix will be deleted */ + } rp_stateflags; +}; + +#define rp_type rp_ifpr.ifpr_type +#define rp_ifp rp_ifpr.ifpr_ifp +#define rp_plen rp_ifpr.ifpr_plen + +#define rp_raf rp_flags.prf_ra +#define rp_raf_onlink rp_flags.prf_ra.onlink +#define rp_raf_auto rp_flags.prf_ra.autonomous + +#define rp_statef_addmark rp_stateflags.addmark +#define rp_statef_delmark rp_stateflags.delmark + +#define rp_rrf rp_flags.prf_rr +#define rp_rrf_decrvalid rp_flags.prf_rr.decrvalid +#define rp_rrf_decrprefd rp_flags.prf_rr.decrprefd + +struct rp_addr { + LIST_ENTRY(rp_addr) ra_entry; + struct in6_addr ra_ifid; + struct in6_ifaddr *ra_addr; + struct ra_flags { + u_char anycast : 1; + } ra_flags; +}; + +#define ifpr2rp(ifpr) ((struct rr_prefix *)(ifpr)) +#define rp2ifpr(rp) ((struct ifprefix *)(rp)) + +#define RP_IN6(rp) (&(rp)->rp_prefix.sin6_addr) + +#define RR_INFINITE_LIFETIME 0xffffffff + + +LIST_HEAD(rr_prhead, rr_prefix); + +extern struct rr_prhead rr_prefix; + +void in6_rr_timer __P((void *)); +extern struct callout in6_rr_timer_ch; +int delete_each_prefix __P((struct rr_prefix *rpp, u_char origin)); diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c new file mode 100644 index 0000000..1e8aeac --- /dev/null +++ b/sys/netinet6/in6_proto.c @@ -0,0 +1,460 @@ +/* $FreeBSD$ */ +/* $KAME: in6_proto.c,v 1.91 2001/05/27 13:28:35 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_proto.c 8.1 (Berkeley) 6/10/93 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/protosw.h> +#include <sys/kernel.h> +#include <sys/domain.h> +#include <sys/mbuf.h> +#include <sys/systm.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/radix.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip_encap.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> + +#include <netinet/tcp.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet6/tcp6_var.h> +#include <netinet6/raw_ip6.h> +#include <netinet6/udp6_var.h> +#include <netinet6/pim6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/in6_prefix.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#ifdef IPSEC_ESP +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#endif +#include <netinet6/ipcomp.h> +#ifdef INET6 +#include <netinet6/ipcomp6.h> +#endif +#endif /* IPSEC */ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec6.h> +#define IPSEC +#define IPSEC_ESP +#define ah6_input ipsec6_common_input +#define esp6_input ipsec6_common_input +#define ipcomp6_input ipsec6_common_input +#endif /* FAST_IPSEC */ + +#include <netinet6/ip6protosw.h> + +#include <net/net_osdep.h> + +/* + * TCP/IP protocol family: IP6, ICMP6, UDP, TCP. + */ + +extern struct domain inet6domain; +static struct pr_usrreqs nousrreqs; + +#define PR_LISTEN 0 +#define PR_ABRTACPTDIS 0 + +struct ip6protosw inet6sw[] = { +{ 0, &inet6domain, IPPROTO_IPV6, 0, + 0, 0, 0, 0, + 0, + ip6_init, 0, frag6_slowtimo, frag6_drain, + &nousrreqs, +}, +{ SOCK_DGRAM, &inet6domain, IPPROTO_UDP, PR_ATOMIC|PR_ADDR, + udp6_input, 0, udp6_ctlinput, ip6_ctloutput, + 0, + 0, 0, 0, 0, + &udp6_usrreqs, +}, +{ SOCK_STREAM, &inet6domain, IPPROTO_TCP, PR_CONNREQUIRED|PR_WANTRCVD|PR_LISTEN, + tcp6_input, 0, tcp6_ctlinput, tcp_ctloutput, + 0, +#ifdef INET /* don't call initialization and timeout routines twice */ + 0, 0, 0, tcp_drain, +#else + tcp_init, tcp_fasttimo, tcp_slowtimo, tcp_drain, +#endif + &tcp6_usrreqs, +}, +{ SOCK_RAW, &inet6domain, IPPROTO_RAW, PR_ATOMIC|PR_ADDR, + rip6_input, rip6_output, rip6_ctlinput, rip6_ctloutput, + 0, + 0, 0, 0, 0, + &rip6_usrreqs +}, +{ SOCK_RAW, &inet6domain, IPPROTO_ICMPV6, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + icmp6_input, rip6_output, rip6_ctlinput, rip6_ctloutput, + 0, + icmp6_init, icmp6_fasttimo, 0, 0, + &rip6_usrreqs +}, +{ SOCK_RAW, &inet6domain, IPPROTO_DSTOPTS,PR_ATOMIC|PR_ADDR, + dest6_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs +}, +{ SOCK_RAW, &inet6domain, IPPROTO_ROUTING,PR_ATOMIC|PR_ADDR, + route6_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs +}, +{ SOCK_RAW, &inet6domain, IPPROTO_FRAGMENT,PR_ATOMIC|PR_ADDR, + frag6_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs +}, +#ifdef IPSEC +{ SOCK_RAW, &inet6domain, IPPROTO_AH, PR_ATOMIC|PR_ADDR, + ah6_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs, +}, +#ifdef IPSEC_ESP +{ SOCK_RAW, &inet6domain, IPPROTO_ESP, PR_ATOMIC|PR_ADDR, + esp6_input, 0, + esp6_ctlinput, + 0, + 0, + 0, 0, 0, 0, + &nousrreqs, +}, +#endif +{ SOCK_RAW, &inet6domain, IPPROTO_IPCOMP, PR_ATOMIC|PR_ADDR, + ipcomp6_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs, +}, +#endif /* IPSEC */ +#ifdef INET +{ SOCK_RAW, &inet6domain, IPPROTO_IPV4, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + encap6_input, rip6_output, 0, rip6_ctloutput, + 0, + encap_init, 0, 0, 0, + &rip6_usrreqs +}, +#endif /* INET */ +{ SOCK_RAW, &inet6domain, IPPROTO_IPV6, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + encap6_input, rip6_output, 0, rip6_ctloutput, + 0, + encap_init, 0, 0, 0, + &rip6_usrreqs +}, +{ SOCK_RAW, &inet6domain, IPPROTO_PIM, PR_ATOMIC|PR_ADDR|PR_LASTHDR, + pim6_input, rip6_output, 0, rip6_ctloutput, + 0, + 0, 0, 0, 0, + &rip6_usrreqs +}, +/* raw wildcard */ +{ SOCK_RAW, &inet6domain, 0, PR_ATOMIC|PR_ADDR, + rip6_input, rip6_output, 0, rip6_ctloutput, + 0, + 0, 0, 0, 0, + &rip6_usrreqs +}, +}; + +extern int in6_inithead __P((void **, int)); + +struct domain inet6domain = + { AF_INET6, "internet6", 0, 0, 0, + (struct protosw *)inet6sw, + (struct protosw *)&inet6sw[sizeof(inet6sw)/sizeof(inet6sw[0])], 0, + in6_inithead, + offsetof(struct sockaddr_in6, sin6_addr) << 3, + sizeof(struct sockaddr_in6) }; + +DOMAIN_SET(inet6); + +/* + * Internet configuration info + */ +#ifndef IPV6FORWARDING +#ifdef GATEWAY6 +#define IPV6FORWARDING 1 /* forward IP6 packets not for us */ +#else +#define IPV6FORWARDING 0 /* don't forward IP6 packets not for us */ +#endif /* GATEWAY6 */ +#endif /* !IPV6FORWARDING */ + +#ifndef IPV6_SENDREDIRECTS +#define IPV6_SENDREDIRECTS 1 +#endif + +int ip6_forwarding = IPV6FORWARDING; /* act as router? */ +int ip6_sendredirects = IPV6_SENDREDIRECTS; +int ip6_defhlim = IPV6_DEFHLIM; +int ip6_defmcasthlim = IPV6_DEFAULT_MULTICAST_HOPS; +int ip6_accept_rtadv = 0; /* "IPV6FORWARDING ? 0 : 1" is dangerous */ +int ip6_maxfragpackets; /* initialized in frag6.c:frag6_init() */ +int ip6_log_interval = 5; +int ip6_hdrnestlimit = 50; /* appropriate? */ +int ip6_dad_count = 1; /* DupAddrDetectionTransmits */ +u_int32_t ip6_flow_seq; +int ip6_auto_flowlabel = 1; +int ip6_gif_hlim = 0; +int ip6_use_deprecated = 1; /* allow deprecated addr (RFC2462 5.5.4) */ +int ip6_rr_prune = 5; /* router renumbering prefix + * walk list every 5 sec. */ +int ip6_v6only = 1; + +u_int32_t ip6_id = 0UL; +int ip6_keepfaith = 0; +time_t ip6_log_time = (time_t)0L; + +/* icmp6 */ +/* + * BSDI4 defines these variables in in_proto.c... + * XXX: what if we don't define INET? Should we define pmtu6_expire + * or so? (jinmei@kame.net 19990310) + */ +int pmtu_expire = 60*10; +int pmtu_probe = 60*2; + +/* raw IP6 parameters */ +/* + * Nominal space allocated to a raw ip socket. + */ +#define RIPV6SNDQ 8192 +#define RIPV6RCVQ 8192 + +u_long rip6_sendspace = RIPV6SNDQ; +u_long rip6_recvspace = RIPV6RCVQ; + +/* ICMPV6 parameters */ +int icmp6_rediraccept = 1; /* accept and process redirects */ +int icmp6_redirtimeout = 10 * 60; /* 10 minutes */ +int icmp6errppslim = 100; /* 100pps */ +int icmp6_nodeinfo = 3; /* enable/disable NI response */ + +/* UDP on IP6 parameters */ +int udp6_sendspace = 9216; /* really max datagram size */ +int udp6_recvspace = 40 * (1024 + sizeof(struct sockaddr_in6)); + /* 40 1K datagrams */ + +/* + * sysctl related items. + */ +SYSCTL_NODE(_net, PF_INET6, inet6, CTLFLAG_RW, 0, + "Internet6 Family"); + +/* net.inet6 */ +SYSCTL_NODE(_net_inet6, IPPROTO_IPV6, ip6, CTLFLAG_RW, 0, "IP6"); +SYSCTL_NODE(_net_inet6, IPPROTO_ICMPV6, icmp6, CTLFLAG_RW, 0, "ICMP6"); +SYSCTL_NODE(_net_inet6, IPPROTO_UDP, udp6, CTLFLAG_RW, 0, "UDP6"); +SYSCTL_NODE(_net_inet6, IPPROTO_TCP, tcp6, CTLFLAG_RW, 0, "TCP6"); +#ifdef IPSEC +SYSCTL_NODE(_net_inet6, IPPROTO_ESP, ipsec6, CTLFLAG_RW, 0, "IPSEC6"); +#endif /* IPSEC */ + +/* net.inet6.ip6 */ +static int +sysctl_ip6_temppltime(SYSCTL_HANDLER_ARGS) +{ + int error = 0; + int old; + + error = SYSCTL_OUT(req, arg1, sizeof(int)); + if (error || !req->newptr) + return (error); + old = ip6_temp_preferred_lifetime; + error = SYSCTL_IN(req, arg1, sizeof(int)); + if (ip6_temp_preferred_lifetime < + ip6_desync_factor + ip6_temp_regen_advance) { + ip6_temp_preferred_lifetime = old; + return(EINVAL); + } + return(error); +} + +static int +sysctl_ip6_tempvltime(SYSCTL_HANDLER_ARGS) +{ + int error = 0; + int old; + + error = SYSCTL_OUT(req, arg1, sizeof(int)); + if (error || !req->newptr) + return (error); + old = ip6_temp_valid_lifetime; + error = SYSCTL_IN(req, arg1, sizeof(int)); + if (ip6_temp_valid_lifetime < ip6_temp_preferred_lifetime) { + ip6_temp_preferred_lifetime = old; + return(EINVAL); + } + return(error); +} + +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_FORWARDING, + forwarding, CTLFLAG_RW, &ip6_forwarding, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_SENDREDIRECTS, + redirect, CTLFLAG_RW, &ip6_sendredirects, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_DEFHLIM, + hlim, CTLFLAG_RW, &ip6_defhlim, 0, ""); +SYSCTL_STRUCT(_net_inet6_ip6, IPV6CTL_STATS, stats, CTLFLAG_RD, + &ip6stat, ip6stat, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_MAXFRAGPACKETS, + maxfragpackets, CTLFLAG_RW, &ip6_maxfragpackets, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_ACCEPT_RTADV, + accept_rtadv, CTLFLAG_RW, &ip6_accept_rtadv, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_KEEPFAITH, + keepfaith, CTLFLAG_RW, &ip6_keepfaith, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_LOG_INTERVAL, + log_interval, CTLFLAG_RW, &ip6_log_interval, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_HDRNESTLIMIT, + hdrnestlimit, CTLFLAG_RW, &ip6_hdrnestlimit, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_DAD_COUNT, + dad_count, CTLFLAG_RW, &ip6_dad_count, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_AUTO_FLOWLABEL, + auto_flowlabel, CTLFLAG_RW, &ip6_auto_flowlabel, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_DEFMCASTHLIM, + defmcasthlim, CTLFLAG_RW, &ip6_defmcasthlim, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_GIF_HLIM, + gifhlim, CTLFLAG_RW, &ip6_gif_hlim, 0, ""); +SYSCTL_STRING(_net_inet6_ip6, IPV6CTL_KAME_VERSION, + kame_version, CTLFLAG_RD, __KAME_VERSION, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_USE_DEPRECATED, + use_deprecated, CTLFLAG_RW, &ip6_use_deprecated, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RR_PRUNE, + rr_prune, CTLFLAG_RW, &ip6_rr_prune, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_USETEMPADDR, + use_tempaddr, CTLFLAG_RW, &ip6_use_tempaddr, 0, ""); +SYSCTL_OID(_net_inet6_ip6, IPV6CTL_TEMPPLTIME, temppltime, + CTLTYPE_INT|CTLFLAG_RW, &ip6_temp_preferred_lifetime, 0, + sysctl_ip6_temppltime, "I", ""); +SYSCTL_OID(_net_inet6_ip6, IPV6CTL_TEMPVLTIME, tempvltime, + CTLTYPE_INT|CTLFLAG_RW, &ip6_temp_valid_lifetime, 0, + sysctl_ip6_tempvltime, "I", ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_V6ONLY, + v6only, CTLFLAG_RW, &ip6_v6only, 0, ""); +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_AUTO_LINKLOCAL, + auto_linklocal, CTLFLAG_RW, &ip6_auto_linklocal, 0, ""); +SYSCTL_STRUCT(_net_inet6_ip6, IPV6CTL_RIP6STATS, rip6stats, CTLFLAG_RD, + &rip6stat, rip6stat, ""); + +/* net.inet6.icmp6 */ +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRACCEPT, + rediraccept, CTLFLAG_RW, &icmp6_rediraccept, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_REDIRTIMEOUT, + redirtimeout, CTLFLAG_RW, &icmp6_redirtimeout, 0, ""); +SYSCTL_STRUCT(_net_inet6_icmp6, ICMPV6CTL_STATS, stats, CTLFLAG_RD, + &icmp6stat, icmp6stat, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_PRUNE, + nd6_prune, CTLFLAG_RW, &nd6_prune, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_DELAY, + nd6_delay, CTLFLAG_RW, &nd6_delay, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_UMAXTRIES, + nd6_umaxtries, CTLFLAG_RW, &nd6_umaxtries, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_MMAXTRIES, + nd6_mmaxtries, CTLFLAG_RW, &nd6_mmaxtries, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_USELOOPBACK, + nd6_useloopback, CTLFLAG_RW, &nd6_useloopback, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_NODEINFO, + nodeinfo, CTLFLAG_RW, &icmp6_nodeinfo, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ERRPPSLIMIT, + errppslimit, CTLFLAG_RW, &icmp6errppslim, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_MAXNUDHINT, + nd6_maxnudhint, CTLFLAG_RW, &nd6_maxnudhint, 0, ""); +SYSCTL_INT(_net_inet6_icmp6, ICMPV6CTL_ND6_DEBUG, + nd6_debug, CTLFLAG_RW, &nd6_debug, 0, ""); diff --git a/sys/netinet6/in6_rmx.c b/sys/netinet6/in6_rmx.c new file mode 100644 index 0000000..14587bd --- /dev/null +++ b/sys/netinet6/in6_rmx.c @@ -0,0 +1,487 @@ +/* $FreeBSD$ */ +/* $KAME: in6_rmx.c,v 1.11 2001/07/26 06:53:16 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright 1994, 1995 Massachusetts Institute of Technology + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose and without fee is hereby + * granted, provided that both the above copyright notice and this + * permission notice appear in all copies, that both the above + * copyright notice and this permission notice appear in all + * supporting documentation, and that the name of M.I.T. not be used + * in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. M.I.T. makes + * no representations about the suitability of this software for any + * purpose. It is provided "as is" without express or implied + * warranty. + * + * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS + * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT + * SHALL M.I.T. 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 code does two things necessary for the enhanced TCP metrics to + * function in a useful manner: + * 1) It marks all non-host routes as `cloning', thus ensuring that + * every actual reference to such a route actually gets turned + * into a reference to a host route to the specific destination + * requested. + * 2) When such routes lose all their references, it arranges for them + * to be deleted in some random collection of circumstances, so that + * a large quantity of stale routing data is not kept in kernel memory + * indefinitely. See in6_rtqtimo() below for the exact mechanism. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/sysctl.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/mbuf.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> + +#include <netinet/icmp6.h> + +#include <netinet/tcp.h> +#include <netinet/tcp_seq.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> + +extern int in6_inithead __P((void **head, int off)); + +#define RTPRF_OURS RTF_PROTO3 /* set on routes we manage */ + +/* + * Do what we need to do when inserting a route. + */ +static struct radix_node * +in6_addroute(void *v_arg, void *n_arg, struct radix_node_head *head, + struct radix_node *treenodes) +{ + struct rtentry *rt = (struct rtentry *)treenodes; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)rt_key(rt); + struct radix_node *ret; + + /* + * For IPv6, all unicast non-host routes are automatically cloning. + */ + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + rt->rt_flags |= RTF_MULTICAST; + + if (!(rt->rt_flags & (RTF_HOST | RTF_CLONING | RTF_MULTICAST))) { + rt->rt_flags |= RTF_PRCLONING; + } + + /* + * A little bit of help for both IPv6 output and input: + * For local addresses, we make sure that RTF_LOCAL is set, + * with the thought that this might one day be used to speed up + * ip_input(). + * + * We also mark routes to multicast addresses as such, because + * it's easy to do and might be useful (but this is much more + * dubious since it's so easy to inspect the address). (This + * is done above.) + * + * XXX + * should elaborate the code. + */ + if (rt->rt_flags & RTF_HOST) { + if (IN6_ARE_ADDR_EQUAL(&satosin6(rt->rt_ifa->ifa_addr) + ->sin6_addr, + &sin6->sin6_addr)) { + rt->rt_flags |= RTF_LOCAL; + } + } + + if (!rt->rt_rmx.rmx_mtu && !(rt->rt_rmx.rmx_locks & RTV_MTU) + && rt->rt_ifp) + rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu; + + ret = rn_addroute(v_arg, n_arg, head, treenodes); + if (ret == NULL && rt->rt_flags & RTF_HOST) { + struct rtentry *rt2; + /* + * We are trying to add a host route, but can't. + * Find out if it is because of an + * ARP entry and delete it if so. + */ + rt2 = rtalloc1((struct sockaddr *)sin6, 0, + RTF_CLONING | RTF_PRCLONING); + if (rt2) { + if (rt2->rt_flags & RTF_LLINFO && + rt2->rt_flags & RTF_HOST && + rt2->rt_gateway && + rt2->rt_gateway->sa_family == AF_LINK) { + rtrequest(RTM_DELETE, + (struct sockaddr *)rt_key(rt2), + rt2->rt_gateway, + rt_mask(rt2), rt2->rt_flags, 0); + ret = rn_addroute(v_arg, n_arg, head, + treenodes); + } + RTFREE(rt2); + } + } else if (ret == NULL && rt->rt_flags & RTF_CLONING) { + struct rtentry *rt2; + /* + * We are trying to add a net route, but can't. + * The following case should be allowed, so we'll make a + * special check for this: + * Two IPv6 addresses with the same prefix is assigned + * to a single interrface. + * # ifconfig if0 inet6 3ffe:0501::1 prefix 64 alias (*1) + * # ifconfig if0 inet6 3ffe:0501::2 prefix 64 alias (*2) + * In this case, (*1) and (*2) want to add the same + * net route entry, 3ffe:0501:: -> if0. + * This case should not raise an error. + */ + rt2 = rtalloc1((struct sockaddr *)sin6, 0, + RTF_CLONING | RTF_PRCLONING); + if (rt2) { + if ((rt2->rt_flags & (RTF_CLONING|RTF_HOST|RTF_GATEWAY)) + == RTF_CLONING + && rt2->rt_gateway + && rt2->rt_gateway->sa_family == AF_LINK + && rt2->rt_ifp == rt->rt_ifp) { + ret = rt2->rt_nodes; + } + RTFREE(rt2); + } + } + return ret; +} + +/* + * This code is the inverse of in6_clsroute: on first reference, if we + * were managing the route, stop doing so and set the expiration timer + * back off again. + */ +static struct radix_node * +in6_matroute(void *v_arg, struct radix_node_head *head) +{ + struct radix_node *rn = rn_match(v_arg, head); + struct rtentry *rt = (struct rtentry *)rn; + + if (rt && rt->rt_refcnt == 0) { /* this is first reference */ + if (rt->rt_flags & RTPRF_OURS) { + rt->rt_flags &= ~RTPRF_OURS; + rt->rt_rmx.rmx_expire = 0; + } + } + return rn; +} + +SYSCTL_DECL(_net_inet6_ip6); + +static int rtq_reallyold = 60*60; + /* one hour is ``really old'' */ +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTEXPIRE, rtexpire, + CTLFLAG_RW, &rtq_reallyold , 0, ""); + +static int rtq_minreallyold = 10; + /* never automatically crank down to less */ +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTMINEXPIRE, rtminexpire, + CTLFLAG_RW, &rtq_minreallyold , 0, ""); + +static int rtq_toomany = 128; + /* 128 cached routes is ``too many'' */ +SYSCTL_INT(_net_inet6_ip6, IPV6CTL_RTMAXCACHE, rtmaxcache, + CTLFLAG_RW, &rtq_toomany , 0, ""); + + +/* + * On last reference drop, mark the route as belong to us so that it can be + * timed out. + */ +static void +in6_clsroute(struct radix_node *rn, struct radix_node_head *head) +{ + struct rtentry *rt = (struct rtentry *)rn; + + if (!(rt->rt_flags & RTF_UP)) + return; /* prophylactic measures */ + + if ((rt->rt_flags & (RTF_LLINFO | RTF_HOST)) != RTF_HOST) + return; + + if ((rt->rt_flags & (RTF_WASCLONED | RTPRF_OURS)) != RTF_WASCLONED) + return; + + /* + * As requested by David Greenman: + * If rtq_reallyold is 0, just delete the route without + * waiting for a timeout cycle to kill it. + */ + if (rtq_reallyold != 0) { + rt->rt_flags |= RTPRF_OURS; + rt->rt_rmx.rmx_expire = time_second + rtq_reallyold; + } else { + rtrequest(RTM_DELETE, + (struct sockaddr *)rt_key(rt), + rt->rt_gateway, rt_mask(rt), + rt->rt_flags, 0); + } +} + +struct rtqk_arg { + struct radix_node_head *rnh; + int mode; + int updating; + int draining; + int killed; + int found; + time_t nextstop; +}; + +/* + * Get rid of old routes. When draining, this deletes everything, even when + * the timeout is not expired yet. When updating, this makes sure that + * nothing has a timeout longer than the current value of rtq_reallyold. + */ +static int +in6_rtqkill(struct radix_node *rn, void *rock) +{ + struct rtqk_arg *ap = rock; + struct rtentry *rt = (struct rtentry *)rn; + int err; + + if (rt->rt_flags & RTPRF_OURS) { + ap->found++; + + if (ap->draining || rt->rt_rmx.rmx_expire <= time_second) { + if (rt->rt_refcnt > 0) + panic("rtqkill route really not free"); + + err = rtrequest(RTM_DELETE, + (struct sockaddr *)rt_key(rt), + rt->rt_gateway, rt_mask(rt), + rt->rt_flags, 0); + if (err) { + log(LOG_WARNING, "in6_rtqkill: error %d", err); + } else { + ap->killed++; + } + } else { + if (ap->updating + && (rt->rt_rmx.rmx_expire - time_second + > rtq_reallyold)) { + rt->rt_rmx.rmx_expire = time_second + + rtq_reallyold; + } + ap->nextstop = lmin(ap->nextstop, + rt->rt_rmx.rmx_expire); + } + } + + return 0; +} + +#define RTQ_TIMEOUT 60*10 /* run no less than once every ten minutes */ +static int rtq_timeout = RTQ_TIMEOUT; + +static void +in6_rtqtimo(void *rock) +{ + struct radix_node_head *rnh = rock; + struct rtqk_arg arg; + struct timeval atv; + static time_t last_adjusted_timeout = 0; + int s; + + arg.found = arg.killed = 0; + arg.rnh = rnh; + arg.nextstop = time_second + rtq_timeout; + arg.draining = arg.updating = 0; + s = splnet(); + RADIX_NODE_HEAD_LOCK(rnh); + rnh->rnh_walktree(rnh, in6_rtqkill, &arg); + RADIX_NODE_HEAD_UNLOCK(rnh); + splx(s); + + /* + * Attempt to be somewhat dynamic about this: + * If there are ``too many'' routes sitting around taking up space, + * then crank down the timeout, and see if we can't make some more + * go away. However, we make sure that we will never adjust more + * than once in rtq_timeout seconds, to keep from cranking down too + * hard. + */ + if ((arg.found - arg.killed > rtq_toomany) + && (time_second - last_adjusted_timeout >= rtq_timeout) + && rtq_reallyold > rtq_minreallyold) { + rtq_reallyold = 2*rtq_reallyold / 3; + if (rtq_reallyold < rtq_minreallyold) { + rtq_reallyold = rtq_minreallyold; + } + + last_adjusted_timeout = time_second; +#ifdef DIAGNOSTIC + log(LOG_DEBUG, "in6_rtqtimo: adjusted rtq_reallyold to %d", + rtq_reallyold); +#endif + arg.found = arg.killed = 0; + arg.updating = 1; + s = splnet(); + RADIX_NODE_HEAD_LOCK(rnh); + rnh->rnh_walktree(rnh, in6_rtqkill, &arg); + RADIX_NODE_HEAD_UNLOCK(rnh); + splx(s); + } + + atv.tv_usec = 0; + atv.tv_sec = arg.nextstop; + timeout(in6_rtqtimo, rock, tvtohz(&atv)); +} + +/* + * Age old PMTUs. + */ +struct mtuex_arg { + struct radix_node_head *rnh; + time_t nextstop; +}; + +static int +in6_mtuexpire(struct radix_node *rn, void *rock) +{ + struct rtentry *rt = (struct rtentry *)rn; + struct mtuex_arg *ap = rock; + + /* sanity */ + if (!rt) + panic("rt == NULL in in6_mtuexpire"); + + if (rt->rt_rmx.rmx_expire && !(rt->rt_flags & RTF_PROBEMTU)) { + if (rt->rt_rmx.rmx_expire <= time_second) { + rt->rt_flags |= RTF_PROBEMTU; + } else { + ap->nextstop = lmin(ap->nextstop, + rt->rt_rmx.rmx_expire); + } + } + + return 0; +} + +#define MTUTIMO_DEFAULT (60*1) + +static void +in6_mtutimo(void *rock) +{ + struct radix_node_head *rnh = rock; + struct mtuex_arg arg; + struct timeval atv; + int s; + + arg.rnh = rnh; + arg.nextstop = time_second + MTUTIMO_DEFAULT; + s = splnet(); + RADIX_NODE_HEAD_LOCK(rnh); + rnh->rnh_walktree(rnh, in6_mtuexpire, &arg); + RADIX_NODE_HEAD_UNLOCK(rnh); + splx(s); + + atv.tv_usec = 0; + atv.tv_sec = arg.nextstop; + if (atv.tv_sec < time_second) { + printf("invalid mtu expiration time on routing table\n"); + arg.nextstop = time_second + 30; /* last resort */ + } + timeout(in6_mtutimo, rock, tvtohz(&atv)); +} + +#if 0 +void +in6_rtqdrain() +{ + struct radix_node_head *rnh = rt_tables[AF_INET6]; + struct rtqk_arg arg; + int s; + arg.found = arg.killed = 0; + arg.rnh = rnh; + arg.nextstop = 0; + arg.draining = 1; + arg.updating = 0; + s = splnet(); + RADIX_NODE_HEAD_LOCK(rnh); + rnh->rnh_walktree(rnh, in6_rtqkill, &arg); + RADIX_NODE_HEAD_UNLOCK(rnh); + splx(s); +} +#endif + +/* + * Initialize our routing tree. + */ +int +in6_inithead(void **head, int off) +{ + struct radix_node_head *rnh; + + if (!rn_inithead(head, off)) + return 0; + + if (head != (void **)&rt_tables[AF_INET6]) /* BOGUS! */ + return 1; /* only do this for the real routing table */ + + rnh = *head; + rnh->rnh_addaddr = in6_addroute; + rnh->rnh_matchaddr = in6_matroute; + rnh->rnh_close = in6_clsroute; + in6_rtqtimo(rnh); /* kick off timeout first time */ + in6_mtutimo(rnh); /* kick off timeout first time */ + return 1; +} diff --git a/sys/netinet6/in6_src.c b/sys/netinet6/in6_src.c new file mode 100644 index 0000000..3dd2212 --- /dev/null +++ b/sys/netinet6/in6_src.c @@ -0,0 +1,558 @@ +/* $FreeBSD$ */ +/* $KAME: in6_src.c,v 1.37 2001/03/29 05:34:31 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1991, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_pcb.c 8.2 (Berkeley) 1/4/94 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#ifdef ENABLE_DEFAULT_SCOPE +#include <netinet6/scope6_var.h> +#endif + +#include <net/net_osdep.h> + +/* + * Return an IPv6 address, which is the most appropriate for a given + * destination and user specified options. + * If necessary, this function lookups the routing table and returns + * an entry to the caller for later use. + */ +struct in6_addr * +in6_selectsrc(dstsock, opts, mopts, ro, laddr, errorp) + struct sockaddr_in6 *dstsock; + struct ip6_pktopts *opts; + struct ip6_moptions *mopts; + struct route_in6 *ro; + struct in6_addr *laddr; + int *errorp; +{ + struct in6_addr *dst; + struct in6_ifaddr *ia6 = 0; + struct in6_pktinfo *pi = NULL; + + dst = &dstsock->sin6_addr; + *errorp = 0; + + /* + * If the source address is explicitly specified by the caller, + * use it. + */ + if (opts && (pi = opts->ip6po_pktinfo) && + !IN6_IS_ADDR_UNSPECIFIED(&pi->ipi6_addr)) + return(&pi->ipi6_addr); + + /* + * If the source address is not specified but the socket(if any) + * is already bound, use the bound address. + */ + if (laddr && !IN6_IS_ADDR_UNSPECIFIED(laddr)) + return(laddr); + + /* + * If the caller doesn't specify the source address but + * the outgoing interface, use an address associated with + * the interface. + */ + if (pi && pi->ipi6_ifindex) { + /* XXX boundary check is assumed to be already done. */ + ia6 = in6_ifawithscope(ifnet_byindex(pi->ipi6_ifindex), dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + /* + * If the destination address is a link-local unicast address or + * a multicast address, and if the outgoing interface is specified + * by the sin6_scope_id filed, use an address associated with the + * interface. + * XXX: We're now trying to define more specific semantics of + * sin6_scope_id field, so this part will be rewritten in + * the near future. + */ + if ((IN6_IS_ADDR_LINKLOCAL(dst) || IN6_IS_ADDR_MULTICAST(dst)) && + dstsock->sin6_scope_id) { + /* + * I'm not sure if boundary check for scope_id is done + * somewhere... + */ + if (dstsock->sin6_scope_id < 0 || + if_index < dstsock->sin6_scope_id) { + *errorp = ENXIO; /* XXX: better error? */ + return(0); + } + ia6 = in6_ifawithscope(ifnet_byindex(dstsock->sin6_scope_id), + dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + /* + * If the destination address is a multicast address and + * the outgoing interface for the address is specified + * by the caller, use an address associated with the interface. + * There is a sanity check here; if the destination has node-local + * scope, the outgoing interfacde should be a loopback address. + * Even if the outgoing interface is not specified, we also + * choose a loopback interface as the outgoing interface. + */ + if (IN6_IS_ADDR_MULTICAST(dst)) { + struct ifnet *ifp = mopts ? mopts->im6o_multicast_ifp : NULL; + + if (ifp == NULL && IN6_IS_ADDR_MC_NODELOCAL(dst)) { + ifp = &loif[0]; + } + + if (ifp) { + ia6 = in6_ifawithscope(ifp, dst); + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + } + + /* + * If the next hop address for the packet is specified + * by caller, use an address associated with the route + * to the next hop. + */ + { + struct sockaddr_in6 *sin6_next; + struct rtentry *rt; + + if (opts && opts->ip6po_nexthop) { + sin6_next = satosin6(opts->ip6po_nexthop); + rt = nd6_lookup(&sin6_next->sin6_addr, 1, NULL); + if (rt) { + ia6 = in6_ifawithscope(rt->rt_ifp, dst); + if (ia6 == 0) + ia6 = ifatoia6(rt->rt_ifa); + } + if (ia6 == 0) { + *errorp = EADDRNOTAVAIL; + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + } + + /* + * If route is known or can be allocated now, + * our src addr is taken from the i/f, else punt. + */ + if (ro) { + if (ro->ro_rt && + (!(ro->ro_rt->rt_flags & RTF_UP) || + satosin6(&ro->ro_dst)->sin6_family != AF_INET6 || + !IN6_ARE_ADDR_EQUAL(&satosin6(&ro->ro_dst)->sin6_addr, + dst))) { + RTFREE(ro->ro_rt); + ro->ro_rt = (struct rtentry *)0; + } + if (ro->ro_rt == (struct rtentry *)0 || + ro->ro_rt->rt_ifp == (struct ifnet *)0) { + struct sockaddr_in6 *sa6; + + /* No route yet, so try to acquire one */ + bzero(&ro->ro_dst, sizeof(struct sockaddr_in6)); + sa6 = (struct sockaddr_in6 *)&ro->ro_dst; + sa6->sin6_family = AF_INET6; + sa6->sin6_len = sizeof(struct sockaddr_in6); + sa6->sin6_addr = *dst; + sa6->sin6_scope_id = dstsock->sin6_scope_id; + if (IN6_IS_ADDR_MULTICAST(dst)) { + ro->ro_rt = rtalloc1(&((struct route *)ro) + ->ro_dst, 0, 0UL); + } else { + rtalloc((struct route *)ro); + } + } + + /* + * in_pcbconnect() checks out IFF_LOOPBACK to skip using + * the address. But we don't know why it does so. + * It is necessary to ensure the scope even for lo0 + * so doesn't check out IFF_LOOPBACK. + */ + + if (ro->ro_rt) { + ia6 = in6_ifawithscope(ro->ro_rt->rt_ifa->ifa_ifp, dst); + if (ia6 == 0) /* xxx scope error ?*/ + ia6 = ifatoia6(ro->ro_rt->rt_ifa); + } +#if 0 + /* + * xxx The followings are necessary? (kazu) + * I don't think so. + * It's for SO_DONTROUTE option in IPv4.(jinmei) + */ + if (ia6 == 0) { + struct sockaddr_in6 sin6 = {sizeof(sin6), AF_INET6, 0}; + + sin6->sin6_addr = *dst; + + ia6 = ifatoia6(ifa_ifwithdstaddr(sin6tosa(&sin6))); + if (ia6 == 0) + ia6 = ifatoia6(ifa_ifwithnet(sin6tosa(&sin6))); + if (ia6 == 0) + return(0); + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } +#endif /* 0 */ + if (ia6 == 0) { + *errorp = EHOSTUNREACH; /* no route */ + return(0); + } + return(&satosin6(&ia6->ia_addr)->sin6_addr); + } + + *errorp = EADDRNOTAVAIL; + return(0); +} + +/* + * Default hop limit selection. The precedence is as follows: + * 1. Hoplimit value specified via ioctl. + * 2. (If the outgoing interface is detected) the current + * hop limit of the interface specified by router advertisement. + * 3. The system default hoplimit. +*/ +int +in6_selecthlim(in6p, ifp) + struct in6pcb *in6p; + struct ifnet *ifp; +{ + if (in6p && in6p->in6p_hops >= 0) + return(in6p->in6p_hops); + else if (ifp) + return(nd_ifinfo[ifp->if_index].chlim); + else + return(ip6_defhlim); +} + +/* + * XXX: this is borrowed from in6_pcbbind(). If possible, we should + * share this function by all *bsd*... + */ +int +in6_pcbsetport(laddr, inp, td) + struct in6_addr *laddr; + struct inpcb *inp; + struct thread *td; +{ + struct socket *so = inp->inp_socket; + u_int16_t lport = 0, first, last, *lastport; + int count, error = 0, wild = 0; + struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; + + /* XXX: this is redundant when called from in6_pcbbind */ + if ((so->so_options & (SO_REUSEADDR|SO_REUSEPORT)) == 0) + wild = INPLOOKUP_WILDCARD; + + inp->inp_flags |= INP_ANONPORT; + + if (inp->inp_flags & INP_HIGHPORT) { + first = ipport_hifirstauto; /* sysctl */ + last = ipport_hilastauto; + lastport = &pcbinfo->lasthi; + } else if (inp->inp_flags & INP_LOWPORT) { + if (td && (error = suser(td))) + return error; + first = ipport_lowfirstauto; /* 1023 */ + last = ipport_lowlastauto; /* 600 */ + lastport = &pcbinfo->lastlow; + } else { + first = ipport_firstauto; /* sysctl */ + last = ipport_lastauto; + lastport = &pcbinfo->lastport; + } + /* + * Simple check to ensure all ports are not used up causing + * a deadlock here. + * + * We split the two cases (up and down) so that the direction + * is not being tested on each round of the loop. + */ + if (first > last) { + /* + * counting down + */ + count = first - last; + + do { + if (count-- < 0) { /* completely used? */ + /* + * Undo any address bind that may have + * occurred above. + */ + inp->in6p_laddr = in6addr_any; + return (EAGAIN); + } + --*lastport; + if (*lastport > first || *lastport < last) + *lastport = first; + lport = htons(*lastport); + } while (in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild)); + } else { + /* + * counting up + */ + count = last - first; + + do { + if (count-- < 0) { /* completely used? */ + /* + * Undo any address bind that may have + * occurred above. + */ + inp->in6p_laddr = in6addr_any; + return (EAGAIN); + } + ++*lastport; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); + } while (in6_pcblookup_local(pcbinfo, + &inp->in6p_laddr, lport, wild)); + } + + inp->inp_lport = lport; + if (in_pcbinshash(inp) != 0) { + inp->in6p_laddr = in6addr_any; + inp->inp_lport = 0; + return (EAGAIN); + } + + return(0); +} + +/* + * generate kernel-internal form (scopeid embedded into s6_addr16[1]). + * If the address scope of is link-local, embed the interface index in the + * address. The routine determines our precedence + * between advanced API scope/interface specification and basic API + * specification. + * + * this function should be nuked in the future, when we get rid of + * embedded scopeid thing. + * + * XXX actually, it is over-specification to return ifp against sin6_scope_id. + * there can be multiple interfaces that belong to a particular scope zone + * (in specification, we have 1:N mapping between a scope zone and interfaces). + * we may want to change the function to return something other than ifp. + */ +int +in6_embedscope(in6, sin6, in6p, ifpp) + struct in6_addr *in6; + const struct sockaddr_in6 *sin6; +#ifdef HAVE_NRL_INPCB + struct inpcb *in6p; +#define in6p_outputopts inp_outputopts6 +#define in6p_moptions inp_moptions6 +#else + struct in6pcb *in6p; +#endif + struct ifnet **ifpp; +{ + struct ifnet *ifp = NULL; + u_int32_t scopeid; + + *in6 = sin6->sin6_addr; + scopeid = sin6->sin6_scope_id; + if (ifpp) + *ifpp = NULL; + + /* + * don't try to read sin6->sin6_addr beyond here, since the caller may + * ask us to overwrite existing sockaddr_in6 + */ + +#ifdef ENABLE_DEFAULT_SCOPE + if (scopeid == 0) + scopeid = scope6_addr2default(in6); +#endif + + if (IN6_IS_SCOPE_LINKLOCAL(in6)) { + struct in6_pktinfo *pi; + + /* + * KAME assumption: link id == interface id + */ + + if (in6p && in6p->in6p_outputopts && + (pi = in6p->in6p_outputopts->ip6po_pktinfo) && + pi->ipi6_ifindex) { + ifp = ifnet_byindex(pi->ipi6_ifindex); + in6->s6_addr16[1] = htons(pi->ipi6_ifindex); + } else if (in6p && IN6_IS_ADDR_MULTICAST(in6) && + in6p->in6p_moptions && + in6p->in6p_moptions->im6o_multicast_ifp) { + ifp = in6p->in6p_moptions->im6o_multicast_ifp; + in6->s6_addr16[1] = htons(ifp->if_index); + } else if (scopeid) { + /* boundary check */ + if (scopeid < 0 || if_index < scopeid) + return ENXIO; /* XXX EINVAL? */ + ifp = ifnet_byindex(scopeid); + /*XXX assignment to 16bit from 32bit variable */ + in6->s6_addr16[1] = htons(scopeid & 0xffff); + } + + if (ifpp) + *ifpp = ifp; + } + + return 0; +} +#ifdef HAVE_NRL_INPCB +#undef in6p_outputopts +#undef in6p_moptions +#endif + +/* + * generate standard sockaddr_in6 from embedded form. + * touches sin6_addr and sin6_scope_id only. + * + * this function should be nuked in the future, when we get rid of + * embedded scopeid thing. + */ +int +in6_recoverscope(sin6, in6, ifp) + struct sockaddr_in6 *sin6; + const struct in6_addr *in6; + struct ifnet *ifp; +{ + u_int32_t scopeid; + + sin6->sin6_addr = *in6; + + /* + * don't try to read *in6 beyond here, since the caller may + * ask us to overwrite existing sockaddr_in6 + */ + + sin6->sin6_scope_id = 0; + if (IN6_IS_SCOPE_LINKLOCAL(in6)) { + /* + * KAME assumption: link id == interface id + */ + scopeid = ntohs(sin6->sin6_addr.s6_addr16[1]); + if (scopeid) { + /* sanity check */ + if (scopeid < 0 || if_index < scopeid) + return ENXIO; + if (ifp && ifp->if_index != scopeid) + return ENXIO; + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = scopeid; + } + } + + return 0; +} + +/* + * just clear the embedded scope identifer. + * XXX: currently used for bsdi4 only as a supplement function. + */ +void +in6_clearscope(addr) + struct in6_addr *addr; +{ + if (IN6_IS_SCOPE_LINKLOCAL(addr)) + addr->s6_addr16[1] = 0; +} diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h new file mode 100644 index 0000000..35f9e5b --- /dev/null +++ b/sys/netinet6/in6_var.h @@ -0,0 +1,607 @@ +/* $FreeBSD$ */ +/* $KAME: in6_var.h,v 1.56 2001/03/29 05:34:31 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1985, 1986, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)in_var.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET6_IN6_VAR_H_ +#define _NETINET6_IN6_VAR_H_ + +/* + * Interface address, Internet version. One of these structures + * is allocated for each interface with an Internet address. + * The ifaddr structure contains the protocol-independent part + * of the structure and is assumed to be first. + */ + +/* + * pltime/vltime are just for future reference (required to implements 2 + * hour rule for hosts). they should never be modified by nd6_timeout or + * anywhere else. + * userland -> kernel: accept pltime/vltime + * kernel -> userland: throw up everything + * in kernel: modify preferred/expire only + */ +struct in6_addrlifetime { + time_t ia6t_expire; /* valid lifetime expiration time */ + time_t ia6t_preferred; /* preferred lifetime expiration time */ + u_int32_t ia6t_vltime; /* valid lifetime */ + u_int32_t ia6t_pltime; /* prefix lifetime */ +}; + +struct in6_ifaddr { + struct ifaddr ia_ifa; /* protocol-independent info */ +#define ia_ifp ia_ifa.ifa_ifp +#define ia_flags ia_ifa.ifa_flags + struct sockaddr_in6 ia_addr; /* interface address */ + struct sockaddr_in6 ia_net; /* network number of interface */ + struct sockaddr_in6 ia_dstaddr; /* space for destination addr */ + struct sockaddr_in6 ia_prefixmask; /* prefix mask */ + u_int32_t ia_plen; /* prefix length */ + struct in6_ifaddr *ia_next; /* next in6 list of IP6 addresses */ + int ia6_flags; + + struct in6_addrlifetime ia6_lifetime; + struct ifprefix *ia6_ifpr; /* back pointer to ifprefix */ + + struct nd_prefix *ia6_ndpr; /* back pointer to the ND prefix + * (for autoconfigured addresses only) + */ +}; + +/* + * IPv6 interface statistics, as defined in RFC2465 Ipv6IfStatsEntry (p12). + */ +struct in6_ifstat { + u_quad_t ifs6_in_receive; /* # of total input datagram */ + u_quad_t ifs6_in_hdrerr; /* # of datagrams with invalid hdr */ + u_quad_t ifs6_in_toobig; /* # of datagrams exceeded MTU */ + u_quad_t ifs6_in_noroute; /* # of datagrams with no route */ + u_quad_t ifs6_in_addrerr; /* # of datagrams with invalid dst */ + u_quad_t ifs6_in_protounknown; /* # of datagrams with unknown proto */ + /* NOTE: increment on final dst if */ + u_quad_t ifs6_in_truncated; /* # of truncated datagrams */ + u_quad_t ifs6_in_discard; /* # of discarded datagrams */ + /* NOTE: fragment timeout is not here */ + u_quad_t ifs6_in_deliver; /* # of datagrams delivered to ULP */ + /* NOTE: increment on final dst if */ + u_quad_t ifs6_out_forward; /* # of datagrams forwarded */ + /* NOTE: increment on outgoing if */ + u_quad_t ifs6_out_request; /* # of outgoing datagrams from ULP */ + /* NOTE: does not include forwrads */ + u_quad_t ifs6_out_discard; /* # of discarded datagrams */ + u_quad_t ifs6_out_fragok; /* # of datagrams fragmented */ + u_quad_t ifs6_out_fragfail; /* # of datagrams failed on fragment */ + u_quad_t ifs6_out_fragcreat; /* # of fragment datagrams */ + /* NOTE: this is # after fragment */ + u_quad_t ifs6_reass_reqd; /* # of incoming fragmented packets */ + /* NOTE: increment on final dst if */ + u_quad_t ifs6_reass_ok; /* # of reassembled packets */ + /* NOTE: this is # after reass */ + /* NOTE: increment on final dst if */ + u_quad_t ifs6_reass_fail; /* # of reass failures */ + /* NOTE: may not be packet count */ + /* NOTE: increment on final dst if */ + u_quad_t ifs6_in_mcast; /* # of inbound multicast datagrams */ + u_quad_t ifs6_out_mcast; /* # of outbound multicast datagrams */ +}; + +/* + * ICMPv6 interface statistics, as defined in RFC2466 Ipv6IfIcmpEntry. + * XXX: I'm not sure if this file is the right place for this structure... + */ +struct icmp6_ifstat { + /* + * Input statistics + */ + /* ipv6IfIcmpInMsgs, total # of input messages */ + u_quad_t ifs6_in_msg; + /* ipv6IfIcmpInErrors, # of input error messages */ + u_quad_t ifs6_in_error; + /* ipv6IfIcmpInDestUnreachs, # of input dest unreach errors */ + u_quad_t ifs6_in_dstunreach; + /* ipv6IfIcmpInAdminProhibs, # of input administratively prohibited errs */ + u_quad_t ifs6_in_adminprohib; + /* ipv6IfIcmpInTimeExcds, # of input time exceeded errors */ + u_quad_t ifs6_in_timeexceed; + /* ipv6IfIcmpInParmProblems, # of input parameter problem errors */ + u_quad_t ifs6_in_paramprob; + /* ipv6IfIcmpInPktTooBigs, # of input packet too big errors */ + u_quad_t ifs6_in_pkttoobig; + /* ipv6IfIcmpInEchos, # of input echo requests */ + u_quad_t ifs6_in_echo; + /* ipv6IfIcmpInEchoReplies, # of input echo replies */ + u_quad_t ifs6_in_echoreply; + /* ipv6IfIcmpInRouterSolicits, # of input router solicitations */ + u_quad_t ifs6_in_routersolicit; + /* ipv6IfIcmpInRouterAdvertisements, # of input router advertisements */ + u_quad_t ifs6_in_routeradvert; + /* ipv6IfIcmpInNeighborSolicits, # of input neighbor solicitations */ + u_quad_t ifs6_in_neighborsolicit; + /* ipv6IfIcmpInNeighborAdvertisements, # of input neighbor advertisements */ + u_quad_t ifs6_in_neighboradvert; + /* ipv6IfIcmpInRedirects, # of input redirects */ + u_quad_t ifs6_in_redirect; + /* ipv6IfIcmpInGroupMembQueries, # of input MLD queries */ + u_quad_t ifs6_in_mldquery; + /* ipv6IfIcmpInGroupMembResponses, # of input MLD reports */ + u_quad_t ifs6_in_mldreport; + /* ipv6IfIcmpInGroupMembReductions, # of input MLD done */ + u_quad_t ifs6_in_mlddone; + + /* + * Output statistics. We should solve unresolved routing problem... + */ + /* ipv6IfIcmpOutMsgs, total # of output messages */ + u_quad_t ifs6_out_msg; + /* ipv6IfIcmpOutErrors, # of output error messages */ + u_quad_t ifs6_out_error; + /* ipv6IfIcmpOutDestUnreachs, # of output dest unreach errors */ + u_quad_t ifs6_out_dstunreach; + /* ipv6IfIcmpOutAdminProhibs, # of output administratively prohibited errs */ + u_quad_t ifs6_out_adminprohib; + /* ipv6IfIcmpOutTimeExcds, # of output time exceeded errors */ + u_quad_t ifs6_out_timeexceed; + /* ipv6IfIcmpOutParmProblems, # of output parameter problem errors */ + u_quad_t ifs6_out_paramprob; + /* ipv6IfIcmpOutPktTooBigs, # of output packet too big errors */ + u_quad_t ifs6_out_pkttoobig; + /* ipv6IfIcmpOutEchos, # of output echo requests */ + u_quad_t ifs6_out_echo; + /* ipv6IfIcmpOutEchoReplies, # of output echo replies */ + u_quad_t ifs6_out_echoreply; + /* ipv6IfIcmpOutRouterSolicits, # of output router solicitations */ + u_quad_t ifs6_out_routersolicit; + /* ipv6IfIcmpOutRouterAdvertisements, # of output router advertisements */ + u_quad_t ifs6_out_routeradvert; + /* ipv6IfIcmpOutNeighborSolicits, # of output neighbor solicitations */ + u_quad_t ifs6_out_neighborsolicit; + /* ipv6IfIcmpOutNeighborAdvertisements, # of output neighbor advertisements */ + u_quad_t ifs6_out_neighboradvert; + /* ipv6IfIcmpOutRedirects, # of output redirects */ + u_quad_t ifs6_out_redirect; + /* ipv6IfIcmpOutGroupMembQueries, # of output MLD queries */ + u_quad_t ifs6_out_mldquery; + /* ipv6IfIcmpOutGroupMembResponses, # of output MLD reports */ + u_quad_t ifs6_out_mldreport; + /* ipv6IfIcmpOutGroupMembReductions, # of output MLD done */ + u_quad_t ifs6_out_mlddone; +}; + +struct in6_ifreq { + char ifr_name[IFNAMSIZ]; + union { + struct sockaddr_in6 ifru_addr; + struct sockaddr_in6 ifru_dstaddr; + int ifru_flags; + int ifru_flags6; + int ifru_metric; + caddr_t ifru_data; + struct in6_addrlifetime ifru_lifetime; + struct in6_ifstat ifru_stat; + struct icmp6_ifstat ifru_icmp6stat; + u_int32_t ifru_scope_id[16]; + } ifr_ifru; +}; + +struct in6_aliasreq { + char ifra_name[IFNAMSIZ]; + struct sockaddr_in6 ifra_addr; + struct sockaddr_in6 ifra_dstaddr; + struct sockaddr_in6 ifra_prefixmask; + int ifra_flags; + struct in6_addrlifetime ifra_lifetime; +}; + +/* prefix type macro */ +#define IN6_PREFIX_ND 1 +#define IN6_PREFIX_RR 2 + +/* + * prefix related flags passed between kernel(NDP related part) and + * user land command(ifconfig) and daemon(rtadvd). + */ +struct in6_prflags { + struct prf_ra { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; + } prf_ra; + u_char prf_reserved1; + u_short prf_reserved2; + /* want to put this on 4byte offset */ + struct prf_rr { + u_char decrvalid : 1; + u_char decrprefd : 1; + u_char reserved : 6; + } prf_rr; + u_char prf_reserved3; + u_short prf_reserved4; +}; + +struct in6_prefixreq { + char ipr_name[IFNAMSIZ]; + u_char ipr_origin; + u_char ipr_plen; + u_int32_t ipr_vltime; + u_int32_t ipr_pltime; + struct in6_prflags ipr_flags; + struct sockaddr_in6 ipr_prefix; +}; + +#define PR_ORIG_RA 0 +#define PR_ORIG_RR 1 +#define PR_ORIG_STATIC 2 +#define PR_ORIG_KERNEL 3 + +#define ipr_raf_onlink ipr_flags.prf_ra.onlink +#define ipr_raf_auto ipr_flags.prf_ra.autonomous + +#define ipr_statef_onlink ipr_flags.prf_state.onlink + +#define ipr_rrf_decrvalid ipr_flags.prf_rr.decrvalid +#define ipr_rrf_decrprefd ipr_flags.prf_rr.decrprefd + +struct in6_rrenumreq { + char irr_name[IFNAMSIZ]; + u_char irr_origin; + u_char irr_m_len; /* match len for matchprefix */ + u_char irr_m_minlen; /* minlen for matching prefix */ + u_char irr_m_maxlen; /* maxlen for matching prefix */ + u_char irr_u_uselen; /* uselen for adding prefix */ + u_char irr_u_keeplen; /* keeplen from matching prefix */ + struct irr_raflagmask { + u_char onlink : 1; + u_char autonomous : 1; + u_char reserved : 6; + } irr_raflagmask; + u_int32_t irr_vltime; + u_int32_t irr_pltime; + struct in6_prflags irr_flags; + struct sockaddr_in6 irr_matchprefix; + struct sockaddr_in6 irr_useprefix; +}; + +#define irr_raf_mask_onlink irr_raflagmask.onlink +#define irr_raf_mask_auto irr_raflagmask.autonomous +#define irr_raf_mask_reserved irr_raflagmask.reserved + +#define irr_raf_onlink irr_flags.prf_ra.onlink +#define irr_raf_auto irr_flags.prf_ra.autonomous + +#define irr_statef_onlink irr_flags.prf_state.onlink + +#define irr_rrf irr_flags.prf_rr +#define irr_rrf_decrvalid irr_flags.prf_rr.decrvalid +#define irr_rrf_decrprefd irr_flags.prf_rr.decrprefd + +/* + * Given a pointer to an in6_ifaddr (ifaddr), + * return a pointer to the addr as a sockaddr_in6 + */ +#define IA6_IN6(ia) (&((ia)->ia_addr.sin6_addr)) +#define IA6_DSTIN6(ia) (&((ia)->ia_dstaddr.sin6_addr)) +#define IA6_MASKIN6(ia) (&((ia)->ia_prefixmask.sin6_addr)) +#define IA6_SIN6(ia) (&((ia)->ia_addr)) +#define IA6_DSTSIN6(ia) (&((ia)->ia_dstaddr)) +#define IFA_IN6(x) (&((struct sockaddr_in6 *)((x)->ifa_addr))->sin6_addr) +#define IFA_DSTIN6(x) (&((struct sockaddr_in6 *)((x)->ifa_dstaddr))->sin6_addr) + +#define IFPR_IN6(x) (&((struct sockaddr_in6 *)((x)->ifpr_prefix))->sin6_addr) + +#ifdef _KERNEL +#define IN6_ARE_MASKED_ADDR_EQUAL(d, a, m) ( \ + (((d)->s6_addr32[0] ^ (a)->s6_addr32[0]) & (m)->s6_addr32[0]) == 0 && \ + (((d)->s6_addr32[1] ^ (a)->s6_addr32[1]) & (m)->s6_addr32[1]) == 0 && \ + (((d)->s6_addr32[2] ^ (a)->s6_addr32[2]) & (m)->s6_addr32[2]) == 0 && \ + (((d)->s6_addr32[3] ^ (a)->s6_addr32[3]) & (m)->s6_addr32[3]) == 0 ) +#endif + +#define SIOCSIFADDR_IN6 _IOW('i', 12, struct in6_ifreq) +#define SIOCGIFADDR_IN6 _IOWR('i', 33, struct in6_ifreq) + +#ifdef _KERNEL +/* + * SIOCSxxx ioctls should be unused (see comments in in6.c), but + * we do not shift numbers for binary compatibility. + */ +#define SIOCSIFDSTADDR_IN6 _IOW('i', 14, struct in6_ifreq) +#define SIOCSIFNETMASK_IN6 _IOW('i', 22, struct in6_ifreq) +#endif + +#define SIOCGIFDSTADDR_IN6 _IOWR('i', 34, struct in6_ifreq) +#define SIOCGIFNETMASK_IN6 _IOWR('i', 37, struct in6_ifreq) + +#define SIOCDIFADDR_IN6 _IOW('i', 25, struct in6_ifreq) +#define SIOCAIFADDR_IN6 _IOW('i', 26, struct in6_aliasreq) + +#define SIOCSIFPHYADDR_IN6 _IOW('i', 70, struct in6_aliasreq) +#define SIOCGIFPSRCADDR_IN6 _IOWR('i', 71, struct in6_ifreq) +#define SIOCGIFPDSTADDR_IN6 _IOWR('i', 72, struct in6_ifreq) + +#define SIOCGIFAFLAG_IN6 _IOWR('i', 73, struct in6_ifreq) + +#define SIOCGDRLST_IN6 _IOWR('i', 74, struct in6_drlist) +#define SIOCGPRLST_IN6 _IOWR('i', 75, struct in6_prlist) +#ifdef _KERNEL +#define OSIOCGIFINFO_IN6 _IOWR('i', 76, struct in6_ondireq) +#endif +#define SIOCGIFINFO_IN6 _IOWR('i', 108, struct in6_ndireq) +#define SIOCSNDFLUSH_IN6 _IOWR('i', 77, struct in6_ifreq) +#define SIOCGNBRINFO_IN6 _IOWR('i', 78, struct in6_nbrinfo) +#define SIOCSPFXFLUSH_IN6 _IOWR('i', 79, struct in6_ifreq) +#define SIOCSRTRFLUSH_IN6 _IOWR('i', 80, struct in6_ifreq) + +#define SIOCGIFALIFETIME_IN6 _IOWR('i', 81, struct in6_ifreq) +#define SIOCSIFALIFETIME_IN6 _IOWR('i', 82, struct in6_ifreq) +#define SIOCGIFSTAT_IN6 _IOWR('i', 83, struct in6_ifreq) +#define SIOCGIFSTAT_ICMP6 _IOWR('i', 84, struct in6_ifreq) + +#define SIOCSDEFIFACE_IN6 _IOWR('i', 85, struct in6_ndifreq) +#define SIOCGDEFIFACE_IN6 _IOWR('i', 86, struct in6_ndifreq) + +#define SIOCSIFINFO_FLAGS _IOWR('i', 87, struct in6_ndireq) /* XXX */ + +#define SIOCSSCOPE6 _IOW('i', 88, struct in6_ifreq) +#define SIOCGSCOPE6 _IOWR('i', 89, struct in6_ifreq) +#define SIOCGSCOPE6DEF _IOWR('i', 90, struct in6_ifreq) + +#define SIOCSIFPREFIX_IN6 _IOW('i', 100, struct in6_prefixreq) /* set */ +#define SIOCGIFPREFIX_IN6 _IOWR('i', 101, struct in6_prefixreq) /* get */ +#define SIOCDIFPREFIX_IN6 _IOW('i', 102, struct in6_prefixreq) /* del */ +#define SIOCAIFPREFIX_IN6 _IOW('i', 103, struct in6_rrenumreq) /* add */ +#define SIOCCIFPREFIX_IN6 _IOW('i', 104, \ + struct in6_rrenumreq) /* change */ +#define SIOCSGIFPREFIX_IN6 _IOW('i', 105, \ + struct in6_rrenumreq) /* set global */ + +#define SIOCGETSGCNT_IN6 _IOWR('u', 106, \ + struct sioc_sg_req6) /* get s,g pkt cnt */ +#define SIOCGETMIFCNT_IN6 _IOWR('u', 107, \ + struct sioc_mif_req6) /* get pkt cnt per if */ + +#define IN6_IFF_ANYCAST 0x01 /* anycast address */ +#define IN6_IFF_TENTATIVE 0x02 /* tentative address */ +#define IN6_IFF_DUPLICATED 0x04 /* DAD detected duplicate */ +#define IN6_IFF_DETACHED 0x08 /* may be detached from the link */ +#define IN6_IFF_DEPRECATED 0x10 /* deprecated address */ +#define IN6_IFF_NODAD 0x20 /* don't perform DAD on this address + * (used only at first SIOC* call) + */ +#define IN6_IFF_AUTOCONF 0x40 /* autoconfigurable address. */ +#define IN6_IFF_TEMPORARY 0x80 /* temporary (anonymous) address. */ +#define IN6_IFF_NOPFX 0x8000 /* skip kernel prefix management. + * XXX: this should be temporary. + */ + +/* do not input/output */ +#define IN6_IFF_NOTREADY (IN6_IFF_TENTATIVE|IN6_IFF_DUPLICATED) + +#ifdef _KERNEL +#define IN6_ARE_SCOPE_CMP(a,b) ((a)-(b)) +#define IN6_ARE_SCOPE_EQUAL(a,b) ((a)==(b)) +#endif + +#ifdef _KERNEL +extern struct in6_ifaddr *in6_ifaddr; + +extern struct in6_ifstat **in6_ifstat; +extern size_t in6_ifstatmax; +extern struct icmp6stat icmp6stat; +extern struct icmp6_ifstat **icmp6_ifstat; +extern size_t icmp6_ifstatmax; +#define in6_ifstat_inc(ifp, tag) \ +do { \ + if ((ifp) && (ifp)->if_index <= if_index \ + && (ifp)->if_index < in6_ifstatmax \ + && in6_ifstat && in6_ifstat[(ifp)->if_index]) { \ + in6_ifstat[(ifp)->if_index]->tag++; \ + } \ +} while (0) + +extern struct in6_addr zeroin6_addr; +extern u_char inet6ctlerrmap[]; +extern unsigned long in6_maxmtu; +#ifdef MALLOC_DECLARE +MALLOC_DECLARE(M_IPMADDR); +#endif + +/* + * Macro for finding the internet address structure (in6_ifaddr) corresponding + * to a given interface (ifnet structure). + */ + +#define IFP_TO_IA6(ifp, ia) \ +/* struct ifnet *ifp; */ \ +/* struct in6_ifaddr *ia; */ \ +do { \ + struct ifaddr *ifa; \ + for (ifa = (ifp)->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) { \ + if (!ifa->ifa_addr) \ + continue; \ + if (ifa->ifa_addr->sa_family == AF_INET6) \ + break; \ + } \ + (ia) = (struct in6_ifaddr *)ifa; \ +} while (0) + +#endif /* _KERNEL */ + +/* + * Multi-cast membership entry. One for each group/ifp that a PCB + * belongs to. + */ +struct in6_multi_mship { + struct in6_multi *i6mm_maddr; /* Multicast address pointer */ + LIST_ENTRY(in6_multi_mship) i6mm_chain; /* multicast options chain */ +}; + +struct in6_multi { + LIST_ENTRY(in6_multi) in6m_entry; /* list glue */ + struct in6_addr in6m_addr; /* IP6 multicast address */ + struct ifnet *in6m_ifp; /* back pointer to ifnet */ + struct ifmultiaddr *in6m_ifma; /* back pointer to ifmultiaddr */ + u_int in6m_refcount; /* # membership claims by sockets */ + u_int in6m_state; /* state of the membership */ + u_int in6m_timer; /* MLD6 listener report timer */ +}; + +#ifdef _KERNEL +extern LIST_HEAD(in6_multihead, in6_multi) in6_multihead; + +/* + * Structure used by macros below to remember position when stepping through + * all of the in6_multi records. + */ +struct in6_multistep { + struct in6_ifaddr *i_ia; + struct in6_multi *i_in6m; +}; + +/* + * Macros for looking up the in6_multi record for a given IP6 multicast + * address on a given interface. If no matching record is found, "in6m" + * returns NLL. + */ + +#define IN6_LOOKUP_MULTI(addr, ifp, in6m) \ +/* struct in6_addr addr; */ \ +/* struct ifnet *ifp; */ \ +/* struct in6_multi *in6m; */ \ +do { \ + struct ifmultiaddr *ifma; \ + TAILQ_FOREACH(ifma, &(ifp)->if_multiaddrs, ifma_link) { \ + if (ifma->ifma_addr->sa_family == AF_INET6 \ + && IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)ifma->ifma_addr)->sin6_addr, \ + &(addr))) \ + break; \ + } \ + (in6m) = (struct in6_multi *)(ifma ? ifma->ifma_protospec : 0); \ +} while(0) + +/* + * Macro to step through all of the in6_multi records, one at a time. + * The current position is remembered in "step", which the caller must + * provide. IN6_FIRST_MULTI(), below, must be called to initialize "step" + * and get the first record. Both macros return a NULL "in6m" when there + * are no remaining records. + */ +#define IN6_NEXT_MULTI(step, in6m) \ +/* struct in6_multistep step; */ \ +/* struct in6_multi *in6m; */ \ +do { \ + if (((in6m) = (step).i_in6m) != NULL) \ + (step).i_in6m = (step).i_in6m->in6m_entry.le_next; \ +} while(0) + +#define IN6_FIRST_MULTI(step, in6m) \ +/* struct in6_multistep step; */ \ +/* struct in6_multi *in6m */ \ +do { \ + (step).i_in6m = in6_multihead.lh_first; \ + IN6_NEXT_MULTI((step), (in6m)); \ +} while(0) + +struct in6_multi *in6_addmulti __P((struct in6_addr *, struct ifnet *, + int *)); +void in6_delmulti __P((struct in6_multi *)); +extern int in6_ifindex2scopeid __P((int)); +extern int in6_mask2len __P((struct in6_addr *, u_char *)); +extern void in6_len2mask __P((struct in6_addr *, int)); +int in6_control __P((struct socket *, + u_long, caddr_t, struct ifnet *, struct thread *)); +int in6_update_ifa __P((struct ifnet *, struct in6_aliasreq *, + struct in6_ifaddr *)); +void in6_purgeaddr __P((struct ifaddr *)); +int in6if_do_dad __P((struct ifnet *)); +void in6_purgeif __P((struct ifnet *)); +void in6_savemkludge __P((struct in6_ifaddr *)); +void in6_setmaxmtu __P((void)); +void in6_restoremkludge __P((struct in6_ifaddr *, struct ifnet *)); +void in6_purgemkludge __P((struct ifnet *)); +struct in6_ifaddr *in6ifa_ifpforlinklocal __P((struct ifnet *, int)); +struct in6_ifaddr *in6ifa_ifpwithaddr __P((struct ifnet *, + struct in6_addr *)); +char *ip6_sprintf __P((const struct in6_addr *)); +int in6_addr2scopeid __P((struct ifnet *, struct in6_addr *)); +int in6_matchlen __P((struct in6_addr *, struct in6_addr *)); +int in6_are_prefix_equal __P((struct in6_addr *p1, struct in6_addr *p2, + int len)); +void in6_prefixlen2mask __P((struct in6_addr *maskp, int len)); +int in6_prefix_ioctl __P((struct socket *so, u_long cmd, caddr_t data, + struct ifnet *ifp)); +int in6_prefix_add_ifid __P((int iilen, struct in6_ifaddr *ia)); +void in6_prefix_remove_ifid __P((int iilen, struct in6_ifaddr *ia)); +void in6_purgeprefix __P((struct ifnet *)); + +int in6_is_addr_deprecated __P((struct sockaddr_in6 *)); +struct inpcb; +int in6_embedscope __P((struct in6_addr *, const struct sockaddr_in6 *, + struct inpcb *, struct ifnet **)); +int in6_recoverscope __P((struct sockaddr_in6 *, const struct in6_addr *, + struct ifnet *)); +void in6_clearscope __P((struct in6_addr *)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_IN6_VAR_H_ */ diff --git a/sys/netinet6/ip6.h b/sys/netinet6/ip6.h new file mode 100644 index 0000000..9eec13f --- /dev/null +++ b/sys/netinet6/ip6.h @@ -0,0 +1,4 @@ +/* $FreeBSD$ */ +/* $KAME: ip6.h,v 1.7 2000/03/25 07:23:36 sumikawa Exp $ */ + +#error "netinet6/ip6.h is obsolete. use netinet/ip6.h" diff --git a/sys/netinet6/ip6_ecn.h b/sys/netinet6/ip6_ecn.h new file mode 100644 index 0000000..4107cf0 --- /dev/null +++ b/sys/netinet6/ip6_ecn.h @@ -0,0 +1,41 @@ +/* $FreeBSD$ */ +/* $KAME: ip_ecn.h,v 1.5 2000/03/27 04:58:38 sumikawa Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + */ +/* + * ECN consideration on tunnel ingress/egress operation. + * http://www.aciri.org/floyd/papers/draft-ipsec-ecn-00.txt + */ + +#ifdef _KERNEL +extern void ip6_ecn_ingress __P((int, u_int32_t *, const u_int32_t *)); +extern void ip6_ecn_egress __P((int, const u_int32_t *, u_int32_t *)); +#endif diff --git a/sys/netinet6/ip6_forward.c b/sys/netinet6/ip6_forward.c new file mode 100644 index 0000000..1e6dde8 --- /dev/null +++ b/sys/netinet6/ip6_forward.c @@ -0,0 +1,595 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_forward.c,v 1.69 2001/05/17 03:48:30 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_ip6fw.h" +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#ifdef PFIL_HOOKS +#include <net/pfil.h> +#endif + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#include <netinet6/nd6.h> + +#include <netinet/in_pcb.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netkey/key.h> +#endif /* IPSEC */ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/key.h> +#define IPSEC +#endif /* FAST_IPSEC */ + +#include <netinet6/ip6_fw.h> + +#include <net/net_osdep.h> + +#include <netinet6/ip6protosw.h> + +struct route_in6 ip6_forward_rt; + +/* + * Forward a packet. If some error occurs return the sender + * an icmp packet. Note we can't always generate a meaningful + * icmp message because icmp doesn't have a large enough repertoire + * of codes and types. + * + * If not forwarding, just drop the packet. This could be confusing + * if ipforwarding was zero but some routing protocol was advancing + * us as a gateway to somewhere. However, we must let the routing + * protocol deal with that. + * + */ + +void +ip6_forward(m, srcrt) + struct mbuf *m; + int srcrt; +{ + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct sockaddr_in6 *dst; + struct rtentry *rt; + int error, type = 0, code = 0; + struct mbuf *mcopy = NULL; + struct ifnet *origifp; /* maybe unnecessary */ +#ifdef PFIL_HOOKS + struct packet_filter_hook *pfh; + struct mbuf *m1; + int rv; +#endif /* PFIL_HOOKS */ +#ifdef IPSEC + struct secpolicy *sp = NULL; +#endif + +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + /* + * Don't increment ip6s_cantforward because this is the check + * before forwarding packet actually. + */ + if (ipsec6_in_reject(m, NULL)) { +#if !defined(FAST_IPSEC) + ipsec6stat.in_polvio++; +#endif + m_freem(m); + return; + } +#endif /* IPSEC */ + + /* + * Do not forward packets to multicast destination (should be handled + * by ip6_mforward(). + * Do not forward packets with unspecified source. It was discussed + * in July 2000, on ipngwg mailing list. + */ + if ((m->m_flags & (M_BCAST|M_MCAST)) != 0 || + IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) { + ip6stat.ip6s_cantforward++; + /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */ + if (ip6_log_time + ip6_log_interval < time_second) { + ip6_log_time = time_second; + log(LOG_DEBUG, + "cannot forward " + "from %s to %s nxt %d received on %s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + ip6->ip6_nxt, + if_name(m->m_pkthdr.rcvif)); + } + m_freem(m); + return; + } + + if (ip6->ip6_hlim <= IPV6_HLIMDEC) { + /* XXX in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard) */ + icmp6_error(m, ICMP6_TIME_EXCEEDED, + ICMP6_TIME_EXCEED_TRANSIT, 0); + return; + } + ip6->ip6_hlim -= IPV6_HLIMDEC; + + /* + * Save at most ICMPV6_PLD_MAXLEN (= the min IPv6 MTU - + * size of IPv6 + ICMPv6 headers) bytes of the packet in case + * we need to generate an ICMP6 message to the src. + * Thanks to M_EXT, in most cases copy will not occur. + * + * It is important to save it before IPsec processing as IPsec + * processing may modify the mbuf. + */ + mcopy = m_copy(m, 0, imin(m->m_pkthdr.len, ICMPV6_PLD_MAXLEN)); + +#ifdef IPSEC + /* get a security policy for this packet */ + sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, IP_FORWARDING, + &error); + if (sp == NULL) { + ipsec6stat.out_inval++; + ip6stat.ip6s_cantforward++; + if (mcopy) { +#if 0 + /* XXX: what icmp ? */ +#else + m_freem(mcopy); +#endif + } + m_freem(m); + return; + } + + error = 0; + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + /* + * This packet is just discarded. + */ + ipsec6stat.out_polvio++; + ip6stat.ip6s_cantforward++; + key_freesp(sp); + if (mcopy) { +#if 0 + /* XXX: what icmp ? */ +#else + m_freem(mcopy); +#endif + } + m_freem(m); + return; + + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + /* no need to do IPsec. */ + key_freesp(sp); + goto skip_ipsec; + + case IPSEC_POLICY_IPSEC: + if (sp->req == NULL) { + /* XXX should be panic ? */ + printf("ip6_forward: No IPsec request specified.\n"); + ip6stat.ip6s_cantforward++; + key_freesp(sp); + if (mcopy) { +#if 0 + /* XXX: what icmp ? */ +#else + m_freem(mcopy); +#endif + } + m_freem(m); + return; + } + /* do IPsec */ + break; + + case IPSEC_POLICY_ENTRUST: + default: + /* should be panic ?? */ + printf("ip6_forward: Invalid policy found. %d\n", sp->policy); + key_freesp(sp); + goto skip_ipsec; + } + + { + struct ipsec_output_state state; + + /* + * All the extension headers will become inaccessible + * (since they can be encrypted). + * Don't panic, we need no more updates to extension headers + * on inner IPv6 packet (since they are now encapsulated). + * + * IPv6 [ESP|AH] IPv6 [extension headers] payload + */ + bzero(&state, sizeof(state)); + state.m = m; + state.ro = NULL; /* update at ipsec6_output_tunnel() */ + state.dst = NULL; /* update at ipsec6_output_tunnel() */ + + error = ipsec6_output_tunnel(&state, sp, 0); + + m = state.m; + key_freesp(sp); + + if (error) { + /* mbuf is already reclaimed in ipsec6_output_tunnel. */ + switch (error) { + case EHOSTUNREACH: + case ENETUNREACH: + case EMSGSIZE: + case ENOBUFS: + case ENOMEM: + break; + default: + printf("ip6_output (ipsec): error code %d\n", error); + /* fall through */ + case ENOENT: + /* don't show these error codes to the user */ + break; + } + ip6stat.ip6s_cantforward++; + if (mcopy) { +#if 0 + /* XXX: what icmp ? */ +#else + m_freem(mcopy); +#endif + } + m_freem(m); + return; + } + } + skip_ipsec: +#endif /* IPSEC */ + + dst = (struct sockaddr_in6 *)&ip6_forward_rt.ro_dst; + if (!srcrt) { + /* + * ip6_forward_rt.ro_dst.sin6_addr is equal to ip6->ip6_dst + */ + if (ip6_forward_rt.ro_rt == 0 || + (ip6_forward_rt.ro_rt->rt_flags & RTF_UP) == 0) { + if (ip6_forward_rt.ro_rt) { + RTFREE(ip6_forward_rt.ro_rt); + ip6_forward_rt.ro_rt = 0; + } + /* this probably fails but give it a try again */ + rtalloc_ign((struct route *)&ip6_forward_rt, + RTF_PRCLONING); + } + + if (ip6_forward_rt.ro_rt == 0) { + ip6stat.ip6s_noroute++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); + if (mcopy) { + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_NOROUTE, 0); + } + m_freem(m); + return; + } + } else if ((rt = ip6_forward_rt.ro_rt) == 0 || + !IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &dst->sin6_addr)) { + if (ip6_forward_rt.ro_rt) { + RTFREE(ip6_forward_rt.ro_rt); + ip6_forward_rt.ro_rt = 0; + } + bzero(dst, sizeof(*dst)); + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_family = AF_INET6; + dst->sin6_addr = ip6->ip6_dst; + + rtalloc_ign((struct route *)&ip6_forward_rt, RTF_PRCLONING); + if (ip6_forward_rt.ro_rt == 0) { + ip6stat.ip6s_noroute++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_noroute); + if (mcopy) { + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_NOROUTE, 0); + } + m_freem(m); + return; + } + } + rt = ip6_forward_rt.ro_rt; + + /* + * Scope check: if a packet can't be delivered to its destination + * for the reason that the destination is beyond the scope of the + * source address, discard the packet and return an icmp6 destination + * unreachable error with Code 2 (beyond scope of source address). + * [draft-ietf-ipngwg-icmp-v3-02.txt, Section 3.1] + */ + if (in6_addr2scopeid(m->m_pkthdr.rcvif, &ip6->ip6_src) != + in6_addr2scopeid(rt->rt_ifp, &ip6->ip6_src)) { + ip6stat.ip6s_cantforward++; + ip6stat.ip6s_badscope++; + in6_ifstat_inc(rt->rt_ifp, ifs6_in_discard); + + if (ip6_log_time + ip6_log_interval < time_second) { + ip6_log_time = time_second; + log(LOG_DEBUG, + "cannot forward " + "src %s, dst %s, nxt %d, rcvif %s, outif %s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + ip6->ip6_nxt, + if_name(m->m_pkthdr.rcvif), if_name(rt->rt_ifp)); + } + if (mcopy) + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_BEYONDSCOPE, 0); + m_freem(m); + return; + } + + if (m->m_pkthdr.len > rt->rt_ifp->if_mtu) { + in6_ifstat_inc(rt->rt_ifp, ifs6_in_toobig); + if (mcopy) { + u_long mtu; +#ifdef IPSEC + struct secpolicy *sp; + int ipsecerror; + size_t ipsechdrsiz; +#endif + + mtu = rt->rt_ifp->if_mtu; +#ifdef IPSEC + /* + * When we do IPsec tunnel ingress, we need to play + * with if_mtu value (decrement IPsec header size + * from mtu value). The code is much simpler than v4 + * case, as we have the outgoing interface for + * encapsulated packet as "rt->rt_ifp". + */ + sp = ipsec6_getpolicybyaddr(mcopy, IPSEC_DIR_OUTBOUND, + IP_FORWARDING, &ipsecerror); + if (sp) { + ipsechdrsiz = ipsec6_hdrsiz(mcopy, + IPSEC_DIR_OUTBOUND, NULL); + if (ipsechdrsiz < mtu) + mtu -= ipsechdrsiz; + } + + /* + * if mtu becomes less than minimum MTU, + * tell minimum MTU (and I'll need to fragment it). + */ + if (mtu < IPV6_MMTU) + mtu = IPV6_MMTU; +#endif + icmp6_error(mcopy, ICMP6_PACKET_TOO_BIG, 0, mtu); + } + m_freem(m); + return; + } + + if (rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in6 *)rt->rt_gateway; + + /* + * If we are to forward the packet using the same interface + * as one we got the packet from, perhaps we should send a redirect + * to sender to shortcut a hop. + * Only send redirect if source is sending directly to us, + * and if packet was not source routed (or has any options). + * Also, don't send redirect if forwarding using a route + * modified by a redirect. + */ + if (rt->rt_ifp == m->m_pkthdr.rcvif && !srcrt && + (rt->rt_flags & (RTF_DYNAMIC|RTF_MODIFIED)) == 0) { + if ((rt->rt_ifp->if_flags & IFF_POINTOPOINT) != 0) { + /* + * If the incoming interface is equal to the outgoing + * one, and the link attached to the interface is + * point-to-point, then it will be highly probable + * that a routing loop occurs. Thus, we immediately + * drop the packet and send an ICMPv6 error message. + * + * type/code is based on suggestion by Rich Draves. + * not sure if it is the best pick. + */ + icmp6_error(mcopy, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0); + m_freem(m); + return; + } + type = ND_REDIRECT; + } + + /* + * Check with the firewall... + */ + if (ip6_fw_enable && ip6_fw_chk_ptr) { + u_short port = 0; + /* If ipfw says divert, we have to just drop packet */ + if ((*ip6_fw_chk_ptr)(&ip6, rt->rt_ifp, &port, &m)) { + m_freem(m); + goto freecopy; + } + if (!m) + goto freecopy; + } + + /* + * Fake scoped addresses. Note that even link-local source or + * destinaion can appear, if the originating node just sends the + * packet to us (without address resolution for the destination). + * Since both icmp6_error and icmp6_redirect_output fill the embedded + * link identifiers, we can do this stuff after making a copy for + * returning an error. + */ + if ((rt->rt_ifp->if_flags & IFF_LOOPBACK) != 0) { + /* + * See corresponding comments in ip6_output. + * XXX: but is it possible that ip6_forward() sends a packet + * to a loopback interface? I don't think so, and thus + * I bark here. (jinmei@kame.net) + * XXX: it is common to route invalid packets to loopback. + * also, the codepath will be visited on use of ::1 in + * rthdr. (itojun) + */ +#if 1 + if (0) +#else + if ((rt->rt_flags & (RTF_BLACKHOLE|RTF_REJECT)) == 0) +#endif + { + printf("ip6_forward: outgoing interface is loopback. " + "src %s, dst %s, nxt %d, rcvif %s, outif %s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + ip6->ip6_nxt, if_name(m->m_pkthdr.rcvif), + if_name(rt->rt_ifp)); + } + + /* we can just use rcvif in forwarding. */ + origifp = m->m_pkthdr.rcvif; + } + else + origifp = rt->rt_ifp; +#ifndef SCOPEDROUTING + /* + * clear embedded scope identifiers if necessary. + * in6_clearscope will touch the addresses only when necessary. + */ + in6_clearscope(&ip6->ip6_src); + in6_clearscope(&ip6->ip6_dst); +#endif + +#ifdef PFIL_HOOKS + /* + * Run through list of hooks for output packets. + */ + m1 = m; + pfh = pfil_hook_get(PFIL_OUT, &inet6sw[ip6_protox[IPPROTO_IPV6]].pr_pfh); + for (; pfh; pfh = pfh->pfil_link.tqe_next) + if (pfh->pfil_func) { + rv = pfh->pfil_func(ip6, sizeof(*ip6), + rt->rt_ifp, 1, &m1); + if (rv) { + error = EHOSTUNREACH; + goto freecopy; + } + m = m1; + if (m == NULL) + goto freecopy; + ip6 = mtod(m, struct ip6_hdr *); + } +#endif /* PFIL_HOOKS */ + + error = nd6_output(rt->rt_ifp, origifp, m, dst, rt); + if (error) { + in6_ifstat_inc(rt->rt_ifp, ifs6_out_discard); + ip6stat.ip6s_cantforward++; + } else { + ip6stat.ip6s_forward++; + in6_ifstat_inc(rt->rt_ifp, ifs6_out_forward); + if (type) + ip6stat.ip6s_redirectsent++; + else { + if (mcopy) + goto freecopy; + } + } + if (mcopy == NULL) + return; + switch (error) { + case 0: +#if 1 + if (type == ND_REDIRECT) { + icmp6_redirect_output(mcopy, rt); + return; + } +#endif + goto freecopy; + + case EMSGSIZE: + /* xxx MTU is constant in PPP? */ + goto freecopy; + + case ENOBUFS: + /* Tell source to slow down like source quench in IP? */ + goto freecopy; + + case ENETUNREACH: /* shouldn't happen, checked above */ + case EHOSTUNREACH: + case ENETDOWN: + case EHOSTDOWN: + default: + type = ICMP6_DST_UNREACH; + code = ICMP6_DST_UNREACH_ADDR; + break; + } + icmp6_error(mcopy, type, code, 0); + return; + + freecopy: + m_freem(mcopy); + return; +} diff --git a/sys/netinet6/ip6_fw.c b/sys/netinet6/ip6_fw.c new file mode 100644 index 0000000..0ef2c52 --- /dev/null +++ b/sys/netinet6/ip6_fw.c @@ -0,0 +1,1299 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_fw.c,v 1.21 2001/01/24 01:25:32 itojun Exp $ */ + +/* + * Copyright (C) 1998, 1999, 2000 and 2001 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1993 Daniel Boulet + * Copyright (c) 1994 Ugen J.S.Antsilevich + * Copyright (c) 1996 Alex Nash + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + */ + +/* + * Implement IPv6 packet firewall + */ + +#if !defined(KLD_MODULE) +#include "opt_ip6fw.h" +#include "opt_inet.h" +#include "opt_inet6.h" +#endif + +#ifdef IP6DIVERT +#error "NOT SUPPORTED IPV6 DIVERT" +#endif +#ifdef IP6FW_DIVERT_RESTART +#error "NOT SUPPORTED IPV6 DIVERT" +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/queue.h> +#include <sys/kernel.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/syslog.h> +#include <sys/time.h> +#include <net/if.h> +#include <net/route.h> +#include <netinet/in_systm.h> +#include <netinet/in.h> +#include <netinet/ip.h> + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_var.h> +#include <netinet/icmp6.h> + +#include <netinet/in_pcb.h> + +#include <netinet6/ip6_fw.h> +#include <netinet/ip_var.h> +#include <netinet/tcp.h> +#include <netinet/tcp_seq.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcp_var.h> +#include <netinet/udp.h> + +#include <sys/sysctl.h> + +#include <net/net_osdep.h> + +MALLOC_DEFINE(M_IP6FW, "Ip6Fw/Ip6Acct", "Ip6Fw/Ip6Acct chain's"); + +static int fw6_debug = 1; +#ifdef IPV6FIREWALL_VERBOSE +static int fw6_verbose = 1; +#else +static int fw6_verbose = 0; +#endif +#ifdef IPV6FIREWALL_VERBOSE_LIMIT +static int fw6_verbose_limit = IPV6FIREWALL_VERBOSE_LIMIT; +#else +static int fw6_verbose_limit = 0; +#endif + +static LIST_HEAD (ip6_fw_head, ip6_fw_chain) ip6_fw_chain; + +#ifdef SYSCTL_NODE +SYSCTL_DECL(_net_inet6_ip6); +SYSCTL_NODE(_net_inet6_ip6, OID_AUTO, fw, CTLFLAG_RW | CTLFLAG_SECURE, + 0, "Firewall"); +SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, enable, CTLFLAG_RW | CTLFLAG_SECURE, + &ip6_fw_enable, 0, "Enable ip6fw"); +SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, debug, CTLFLAG_RW, &fw6_debug, 0, ""); +SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, verbose, CTLFLAG_RW | CTLFLAG_SECURE, + &fw6_verbose, 0, ""); +SYSCTL_INT(_net_inet6_ip6_fw, OID_AUTO, verbose_limit, CTLFLAG_RW, &fw6_verbose_limit, 0, ""); +#endif + +#define dprintf(a) do { \ + if (fw6_debug) \ + printf a; \ + } while (0) +#define SNPARGS(buf, len) buf + len, sizeof(buf) > len ? sizeof(buf) - len : 0 + +static int add_entry6 __P((struct ip6_fw_head *chainptr, struct ip6_fw *frwl)); +static int del_entry6 __P((struct ip6_fw_head *chainptr, u_short number)); +static int zero_entry6 __P((struct mbuf *m)); +static struct ip6_fw *check_ip6fw_struct __P((struct ip6_fw *m)); +static struct ip6_fw *check_ip6fw_mbuf __P((struct mbuf *fw)); +static int ip6opts_match __P((struct ip6_hdr **ip6, struct ip6_fw *f, + struct mbuf **m, + int *off, int *nxt, u_short *offset)); +static int port_match6 __P((u_short *portptr, int nports, u_short port, + int range_flag)); +static int tcp6flg_match __P((struct tcphdr *tcp6, struct ip6_fw *f)); +static int icmp6type_match __P((struct icmp6_hdr * icmp, struct ip6_fw * f)); +static void ip6fw_report __P((struct ip6_fw *f, struct ip6_hdr *ip6, + struct ifnet *rif, struct ifnet *oif, int off, int nxt)); + +static int ip6_fw_chk __P((struct ip6_hdr **pip6, + struct ifnet *oif, u_int16_t *cookie, struct mbuf **m)); +static int ip6_fw_ctl __P((int stage, struct mbuf **mm)); + +static char err_prefix[] = "ip6_fw_ctl:"; + +/* + * Returns 1 if the port is matched by the vector, 0 otherwise + */ +static +__inline int +port_match6(u_short *portptr, int nports, u_short port, int range_flag) +{ + if (!nports) + return 1; + if (range_flag) { + if (portptr[0] <= port && port <= portptr[1]) { + return 1; + } + nports -= 2; + portptr += 2; + } + while (nports-- > 0) { + if (*portptr++ == port) { + return 1; + } + } + return 0; +} + +static int +tcp6flg_match(struct tcphdr *tcp6, struct ip6_fw *f) +{ + u_char flg_set, flg_clr; + + /* + * If an established connection is required, reject packets that + * have only SYN of RST|ACK|SYN set. Otherwise, fall through to + * other flag requirements. + */ + if ((f->fw_ipflg & IPV6_FW_IF_TCPEST) && + ((tcp6->th_flags & (IPV6_FW_TCPF_RST | IPV6_FW_TCPF_ACK | + IPV6_FW_TCPF_SYN)) == IPV6_FW_TCPF_SYN)) + return 0; + + flg_set = tcp6->th_flags & f->fw_tcpf; + flg_clr = tcp6->th_flags & f->fw_tcpnf; + + if (flg_set != f->fw_tcpf) + return 0; + if (flg_clr) + return 0; + + return 1; +} + +static int +icmp6type_match(struct icmp6_hdr *icmp6, struct ip6_fw *f) +{ + int type; + + if (!(f->fw_flg & IPV6_FW_F_ICMPBIT)) + return(1); + + type = icmp6->icmp6_type; + + /* check for matching type in the bitmap */ + if (type < IPV6_FW_ICMPTYPES_DIM * sizeof(unsigned) * 8 && + (f->fw_icmp6types[type / (sizeof(unsigned) * 8)] & + (1U << (type % (8 * sizeof(unsigned)))))) + return(1); + + return(0); /* no match */ +} + +static int +is_icmp6_query(struct ip6_hdr *ip6, int off) +{ + const struct icmp6_hdr *icmp6; + int icmp6_type; + + icmp6 = (struct icmp6_hdr *)((caddr_t)ip6 + off); + icmp6_type = icmp6->icmp6_type; + + if (icmp6_type == ICMP6_ECHO_REQUEST || + icmp6_type == ICMP6_MEMBERSHIP_QUERY || + icmp6_type == ICMP6_WRUREQUEST || + icmp6_type == ICMP6_FQDN_QUERY || + icmp6_type == ICMP6_NI_QUERY) + return(1); + + return(0); +} + +static int +ip6opts_match(struct ip6_hdr **pip6, struct ip6_fw *f, struct mbuf **m, + int *off, int *nxt, u_short *offset) +{ + int len; + struct ip6_hdr *ip6 = *pip6; + struct ip6_ext *ip6e; + u_char opts, nopts, nopts_sve; + + opts = f->fw_ip6opt; + nopts = nopts_sve = f->fw_ip6nopt; + + *nxt = ip6->ip6_nxt; + *off = sizeof(struct ip6_hdr); + len = ntohs(ip6->ip6_plen) + sizeof(struct ip6_hdr); + while (*off < len) { + ip6e = (struct ip6_ext *)((caddr_t) ip6 + *off); + if ((*m)->m_len < *off + sizeof(*ip6e)) + goto opts_check; /* XXX */ + + switch(*nxt) { + case IPPROTO_FRAGMENT: + if ((*m)->m_len >= *off + sizeof(struct ip6_frag)) { + struct ip6_frag *ip6f; + + ip6f = (struct ip6_frag *) ((caddr_t)ip6 + *off); + *offset = ip6f->ip6f_offlg & IP6F_OFF_MASK; + } + opts &= ~IPV6_FW_IP6OPT_FRAG; + nopts &= ~IPV6_FW_IP6OPT_FRAG; + *off += sizeof(struct ip6_frag); + break; + case IPPROTO_AH: + opts &= ~IPV6_FW_IP6OPT_AH; + nopts &= ~IPV6_FW_IP6OPT_AH; + *off += (ip6e->ip6e_len + 2) << 2; + break; + default: + switch (*nxt) { + case IPPROTO_HOPOPTS: + opts &= ~IPV6_FW_IP6OPT_HOPOPT; + nopts &= ~IPV6_FW_IP6OPT_HOPOPT; + break; + case IPPROTO_ROUTING: + opts &= ~IPV6_FW_IP6OPT_ROUTE; + nopts &= ~IPV6_FW_IP6OPT_ROUTE; + break; + case IPPROTO_ESP: + opts &= ~IPV6_FW_IP6OPT_ESP; + nopts &= ~IPV6_FW_IP6OPT_ESP; + break; + case IPPROTO_NONE: + opts &= ~IPV6_FW_IP6OPT_NONXT; + nopts &= ~IPV6_FW_IP6OPT_NONXT; + goto opts_check; + break; + case IPPROTO_DSTOPTS: + opts &= ~IPV6_FW_IP6OPT_OPTS; + nopts &= ~IPV6_FW_IP6OPT_OPTS; + break; + default: + goto opts_check; + break; + } + *off += (ip6e->ip6e_len + 1) << 3; + break; + } + *nxt = ip6e->ip6e_nxt; + + } + opts_check: + if (f->fw_ip6opt == f->fw_ip6nopt) /* XXX */ + return 1; + + if (opts == 0 && nopts == nopts_sve) + return 1; + else + return 0; +} + +static +__inline int +iface_match(struct ifnet *ifp, union ip6_fw_if *ifu, int byname) +{ + /* Check by name or by IP address */ + if (byname) { + /* Check unit number (-1 is wildcard) */ + if (ifu->fu_via_if.unit != -1 + && ifp->if_unit != ifu->fu_via_if.unit) + return(0); + /* Check name */ + if (strncmp(ifp->if_name, ifu->fu_via_if.name, IP6FW_IFNLEN)) + return(0); + return(1); + } else if (!IN6_IS_ADDR_UNSPECIFIED(&ifu->fu_via_ip6)) { /* Zero == wildcard */ + struct ifaddr *ia; + + for (ia = ifp->if_addrlist.tqh_first; ia; ia = ia->ifa_list.tqe_next) + { + + if (ia->ifa_addr == NULL) + continue; + if (ia->ifa_addr->sa_family != AF_INET6) + continue; + if (!IN6_ARE_ADDR_EQUAL(&ifu->fu_via_ip6, + &(((struct sockaddr_in6 *) + (ia->ifa_addr))->sin6_addr))) + continue; + return(1); + } + return(0); + } + return(1); +} + +static void +ip6fw_report(struct ip6_fw *f, struct ip6_hdr *ip6, + struct ifnet *rif, struct ifnet *oif, int off, int nxt) +{ + static int counter; + struct tcphdr *const tcp6 = (struct tcphdr *) ((caddr_t) ip6+ off); + struct udphdr *const udp = (struct udphdr *) ((caddr_t) ip6+ off); + struct icmp6_hdr *const icmp6 = (struct icmp6_hdr *) ((caddr_t) ip6+ off); + int count; + char *action; + char action2[32], proto[102], name[18]; + int len; + + count = f ? f->fw_pcnt : ++counter; + if (fw6_verbose_limit != 0 && count > fw6_verbose_limit) + return; + + /* Print command name */ + snprintf(SNPARGS(name, 0), "ip6fw: %d", f ? f->fw_number : -1); + + action = action2; + if (!f) + action = "Refuse"; + else { + switch (f->fw_flg & IPV6_FW_F_COMMAND) { + case IPV6_FW_F_DENY: + action = "Deny"; + break; + case IPV6_FW_F_REJECT: + if (f->fw_reject_code == IPV6_FW_REJECT_RST) + action = "Reset"; + else + action = "Unreach"; + break; + case IPV6_FW_F_ACCEPT: + action = "Accept"; + break; + case IPV6_FW_F_COUNT: + action = "Count"; + break; + case IPV6_FW_F_DIVERT: + snprintf(SNPARGS(action2, 0), "Divert %d", + f->fw_divert_port); + break; + case IPV6_FW_F_TEE: + snprintf(SNPARGS(action2, 0), "Tee %d", + f->fw_divert_port); + break; + case IPV6_FW_F_SKIPTO: + snprintf(SNPARGS(action2, 0), "SkipTo %d", + f->fw_skipto_rule); + break; + default: + action = "UNKNOWN"; + break; + } + } + + switch (nxt) { + case IPPROTO_TCP: + len = snprintf(SNPARGS(proto, 0), "TCP [%s]", + ip6_sprintf(&ip6->ip6_src)); + if (off > 0) + len += snprintf(SNPARGS(proto, len), ":%d ", + ntohs(tcp6->th_sport)); + else + len += snprintf(SNPARGS(proto, len), " "); + len += snprintf(SNPARGS(proto, len), "[%s]", + ip6_sprintf(&ip6->ip6_dst)); + if (off > 0) + snprintf(SNPARGS(proto, len), ":%d", + ntohs(tcp6->th_dport)); + break; + case IPPROTO_UDP: + len = snprintf(SNPARGS(proto, 0), "UDP [%s]", + ip6_sprintf(&ip6->ip6_src)); + if (off > 0) + len += snprintf(SNPARGS(proto, len), ":%d ", + ntohs(udp->uh_sport)); + else + len += snprintf(SNPARGS(proto, len), " "); + len += snprintf(SNPARGS(proto, len), "[%s]", + ip6_sprintf(&ip6->ip6_dst)); + if (off > 0) + snprintf(SNPARGS(proto, len), ":%d", + ntohs(udp->uh_dport)); + break; + case IPPROTO_ICMPV6: + if (off > 0) + len = snprintf(SNPARGS(proto, 0), "IPV6-ICMP:%u.%u ", + icmp6->icmp6_type, icmp6->icmp6_code); + else + len = snprintf(SNPARGS(proto, 0), "IPV6-ICMP "); + len += snprintf(SNPARGS(proto, len), "[%s]", + ip6_sprintf(&ip6->ip6_src)); + snprintf(SNPARGS(proto, len), " [%s]", + ip6_sprintf(&ip6->ip6_dst)); + break; + default: + len = snprintf(SNPARGS(proto, 0), "P:%d [%s]", nxt, + ip6_sprintf(&ip6->ip6_src)); + snprintf(SNPARGS(proto, len), " [%s]", + ip6_sprintf(&ip6->ip6_dst)); + break; + } + + if (oif) + log(LOG_SECURITY | LOG_INFO, "%s %s %s out via %s\n", + name, action, proto, if_name(oif)); + else if (rif) + log(LOG_SECURITY | LOG_INFO, "%s %s %s in via %s\n", + name, action, proto, if_name(rif)); + else + log(LOG_SECURITY | LOG_INFO, "%s %s %s", + name, action, proto); + if (fw6_verbose_limit != 0 && count == fw6_verbose_limit) + log(LOG_SECURITY | LOG_INFO, "ip6fw: limit reached on entry %d\n", + f ? f->fw_number : -1); +} + +/* + * Parameters: + * + * ip Pointer to packet header (struct ip6_hdr *) + * hlen Packet header length + * oif Outgoing interface, or NULL if packet is incoming + * #ifndef IP6FW_DIVERT_RESTART + * *cookie Ignore all divert/tee rules to this port (if non-zero) + * #else + * *cookie Skip up to the first rule past this rule number; + * #endif + * *m The packet; we set to NULL when/if we nuke it. + * + * Return value: + * + * 0 The packet is to be accepted and routed normally OR + * the packet was denied/rejected and has been dropped; + * in the latter case, *m is equal to NULL upon return. + * port Divert the packet to port. + */ + +static int +ip6_fw_chk(struct ip6_hdr **pip6, + struct ifnet *oif, u_int16_t *cookie, struct mbuf **m) +{ + struct ip6_fw_chain *chain; + struct ip6_fw *rule = NULL; + struct ip6_hdr *ip6 = *pip6; + struct ifnet *const rif = (*m)->m_pkthdr.rcvif; + u_short offset = 0; + int off = sizeof(struct ip6_hdr), nxt = ip6->ip6_nxt; + u_short src_port, dst_port; +#ifdef IP6FW_DIVERT_RESTART + u_int16_t skipto = *cookie; +#else + u_int16_t ignport = ntohs(*cookie); +#endif + + *cookie = 0; + /* + * Go down the chain, looking for enlightment + * #ifdef IP6FW_DIVERT_RESTART + * If we've been asked to start at a given rule immediatly, do so. + * #endif + */ + chain = LIST_FIRST(&ip6_fw_chain); +#ifdef IP6FW_DIVERT_RESTART + if (skipto) { + if (skipto >= 65535) + goto dropit; + while (chain && (chain->rule->fw_number <= skipto)) { + chain = LIST_NEXT(chain, chain); + } + if (! chain) goto dropit; + } +#endif /* IP6FW_DIVERT_RESTART */ + for (; chain; chain = LIST_NEXT(chain, chain)) { + struct ip6_fw *const f = chain->rule; + + if (oif) { + /* Check direction outbound */ + if (!(f->fw_flg & IPV6_FW_F_OUT)) + continue; + } else { + /* Check direction inbound */ + if (!(f->fw_flg & IPV6_FW_F_IN)) + continue; + } + +#define IN6_ARE_ADDR_MASKEQUAL(x,y,z) (\ + (((x)->s6_addr32[0] & (y)->s6_addr32[0]) == (z)->s6_addr32[0]) && \ + (((x)->s6_addr32[1] & (y)->s6_addr32[1]) == (z)->s6_addr32[1]) && \ + (((x)->s6_addr32[2] & (y)->s6_addr32[2]) == (z)->s6_addr32[2]) && \ + (((x)->s6_addr32[3] & (y)->s6_addr32[3]) == (z)->s6_addr32[3])) + + /* If src-addr doesn't match, not this rule. */ + if (((f->fw_flg & IPV6_FW_F_INVSRC) != 0) ^ + (!IN6_ARE_ADDR_MASKEQUAL(&ip6->ip6_src,&f->fw_smsk,&f->fw_src))) + continue; + + /* If dest-addr doesn't match, not this rule. */ + if (((f->fw_flg & IPV6_FW_F_INVDST) != 0) ^ + (!IN6_ARE_ADDR_MASKEQUAL(&ip6->ip6_dst,&f->fw_dmsk,&f->fw_dst))) + continue; + +#undef IN6_ARE_ADDR_MASKEQUAL + /* Interface check */ + if ((f->fw_flg & IF6_FW_F_VIAHACK) == IF6_FW_F_VIAHACK) { + struct ifnet *const iface = oif ? oif : rif; + + /* Backwards compatibility hack for "via" */ + if (!iface || !iface_match(iface, + &f->fw_in_if, f->fw_flg & IPV6_FW_F_OIFNAME)) + continue; + } else { + /* Check receive interface */ + if ((f->fw_flg & IPV6_FW_F_IIFACE) + && (!rif || !iface_match(rif, + &f->fw_in_if, f->fw_flg & IPV6_FW_F_IIFNAME))) + continue; + /* Check outgoing interface */ + if ((f->fw_flg & IPV6_FW_F_OIFACE) + && (!oif || !iface_match(oif, + &f->fw_out_if, f->fw_flg & IPV6_FW_F_OIFNAME))) + continue; + } + + /* Check IP options */ + if (!ip6opts_match(&ip6, f, m, &off, &nxt, &offset)) + continue; + + /* Fragments */ + if ((f->fw_flg & IPV6_FW_F_FRAG) && !offset) + continue; + + /* Check protocol; if wildcard, match */ + if (f->fw_prot == IPPROTO_IPV6) + goto got_match; + + /* If different, don't match */ + if (nxt != f->fw_prot) + continue; + +#define PULLUP_TO(len) do { \ + if ((*m)->m_len < (len) \ + && (*m = m_pullup(*m, (len))) == 0) { \ + goto dropit; \ + } \ + *pip6 = ip6 = mtod(*m, struct ip6_hdr *); \ + } while (0) + + /* Protocol specific checks */ + switch (nxt) { + case IPPROTO_TCP: + { + struct tcphdr *tcp6; + + if (offset == 1) { /* cf. RFC 1858 */ + PULLUP_TO(off + 4); /* XXX ? */ + goto bogusfrag; + } + if (offset != 0) { + /* + * TCP flags and ports aren't available in this + * packet -- if this rule specified either one, + * we consider the rule a non-match. + */ + if (f->fw_nports != 0 || + f->fw_tcpf != f->fw_tcpnf) + continue; + + break; + } + PULLUP_TO(off + 14); + tcp6 = (struct tcphdr *) ((caddr_t)ip6 + off); + if (((f->fw_tcpf != f->fw_tcpnf) || + (f->fw_ipflg & IPV6_FW_IF_TCPEST)) && + !tcp6flg_match(tcp6, f)) + continue; + src_port = ntohs(tcp6->th_sport); + dst_port = ntohs(tcp6->th_dport); + goto check_ports; + } + + case IPPROTO_UDP: + { + struct udphdr *udp; + + if (offset != 0) { + /* + * Port specification is unavailable -- if this + * rule specifies a port, we consider the rule + * a non-match. + */ + if (f->fw_nports != 0) + continue; + + break; + } + PULLUP_TO(off + 4); + udp = (struct udphdr *) ((caddr_t)ip6 + off); + src_port = ntohs(udp->uh_sport); + dst_port = ntohs(udp->uh_dport); +check_ports: + if (!port_match6(&f->fw_pts[0], + IPV6_FW_GETNSRCP(f), src_port, + f->fw_flg & IPV6_FW_F_SRNG)) + continue; + if (!port_match6(&f->fw_pts[IPV6_FW_GETNSRCP(f)], + IPV6_FW_GETNDSTP(f), dst_port, + f->fw_flg & IPV6_FW_F_DRNG)) + continue; + break; + } + + case IPPROTO_ICMPV6: + { + struct icmp6_hdr *icmp; + + if (offset != 0) /* Type isn't valid */ + break; + PULLUP_TO(off + 2); + icmp = (struct icmp6_hdr *) ((caddr_t)ip6 + off); + if (!icmp6type_match(icmp, f)) + continue; + break; + } +#undef PULLUP_TO + +bogusfrag: + if (fw6_verbose) + ip6fw_report(NULL, ip6, rif, oif, off, nxt); + goto dropit; + } + +got_match: +#ifndef IP6FW_DIVERT_RESTART + /* Ignore divert/tee rule if socket port is "ignport" */ + switch (f->fw_flg & IPV6_FW_F_COMMAND) { + case IPV6_FW_F_DIVERT: + case IPV6_FW_F_TEE: + if (f->fw_divert_port == ignport) + continue; /* ignore this rule */ + break; + } + +#endif /* IP6FW_DIVERT_RESTART */ + /* Update statistics */ + f->fw_pcnt += 1; + f->fw_bcnt += ntohs(ip6->ip6_plen); + f->timestamp = time_second; + + /* Log to console if desired */ + if ((f->fw_flg & IPV6_FW_F_PRN) && fw6_verbose) + ip6fw_report(f, ip6, rif, oif, off, nxt); + + /* Take appropriate action */ + switch (f->fw_flg & IPV6_FW_F_COMMAND) { + case IPV6_FW_F_ACCEPT: + return(0); + case IPV6_FW_F_COUNT: + continue; + case IPV6_FW_F_DIVERT: +#ifdef IP6FW_DIVERT_RESTART + *cookie = f->fw_number; +#else + *cookie = htons(f->fw_divert_port); +#endif /* IP6FW_DIVERT_RESTART */ + return(f->fw_divert_port); + case IPV6_FW_F_TEE: + /* + * XXX someday tee packet here, but beware that you + * can't use m_copym() or m_copypacket() because + * the divert input routine modifies the mbuf + * (and these routines only increment reference + * counts in the case of mbuf clusters), so need + * to write custom routine. + */ + continue; + case IPV6_FW_F_SKIPTO: +#ifdef DIAGNOSTIC + while (chain->chain.le_next + && chain->chain.le_next->rule->fw_number + < f->fw_skipto_rule) +#else + while (chain->chain.le_next->rule->fw_number + < f->fw_skipto_rule) +#endif + chain = chain->chain.le_next; + continue; + } + + /* Deny/reject this packet using this rule */ + rule = f; + break; + } + +#ifdef DIAGNOSTIC + /* Rule 65535 should always be there and should always match */ + if (!chain) + panic("ip6_fw: chain"); +#endif + + /* + * At this point, we're going to drop the packet. + * Send a reject notice if all of the following are true: + * + * - The packet matched a reject rule + * - The packet is not an ICMP packet, or is an ICMP query packet + * - The packet is not a multicast or broadcast packet + */ + if ((rule->fw_flg & IPV6_FW_F_COMMAND) == IPV6_FW_F_REJECT + && (nxt != IPPROTO_ICMPV6 || is_icmp6_query(ip6, off)) + && !((*m)->m_flags & (M_BCAST|M_MCAST)) + && !IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + switch (rule->fw_reject_code) { + case IPV6_FW_REJECT_RST: + { + struct tcphdr *const tcp = + (struct tcphdr *) ((caddr_t)ip6 + off); + struct { + struct ip6_hdr ip6; + struct tcphdr th; + } ti; + tcp_seq ack, seq; + int flags; + + if (offset != 0 || (tcp->th_flags & TH_RST)) + break; + + ti.ip6 = *ip6; + ti.th = *tcp; + ti.th.th_seq = ntohl(ti.th.th_seq); + ti.th.th_ack = ntohl(ti.th.th_ack); + ti.ip6.ip6_nxt = IPPROTO_TCP; + if (ti.th.th_flags & TH_ACK) { + ack = 0; + seq = ti.th.th_ack; + flags = TH_RST; + } else { + ack = ti.th.th_seq; + if (((*m)->m_flags & M_PKTHDR) != 0) { + ack += (*m)->m_pkthdr.len - off + - (ti.th.th_off << 2); + } else if (ip6->ip6_plen) { + ack += ntohs(ip6->ip6_plen) + sizeof(*ip6) + - off - (ti.th.th_off << 2); + } else { + m_freem(*m); + *m = 0; + break; + } + seq = 0; + flags = TH_RST|TH_ACK; + } + bcopy(&ti, ip6, sizeof(ti)); + tcp_respond(NULL, ip6, (struct tcphdr *)(ip6 + 1), + *m, ack, seq, flags); + *m = NULL; + break; + } + default: /* Send an ICMP unreachable using code */ + if (oif) + (*m)->m_pkthdr.rcvif = oif; + icmp6_error(*m, ICMP6_DST_UNREACH, + rule->fw_reject_code, 0); + *m = NULL; + break; + } + } + +dropit: + /* + * Finally, drop the packet. + */ + if (*m) { + m_freem(*m); + *m = NULL; + } + return(0); +} + +static int +add_entry6(struct ip6_fw_head *chainptr, struct ip6_fw *frwl) +{ + struct ip6_fw *ftmp = 0; + struct ip6_fw_chain *fwc = 0, *fcp, *fcpl = 0; + u_short nbr = 0; + int s; + + fwc = malloc(sizeof *fwc, M_IP6FW, M_NOWAIT); + ftmp = malloc(sizeof *ftmp, M_IP6FW, M_NOWAIT); + if (!fwc || !ftmp) { + dprintf(("%s malloc said no\n", err_prefix)); + if (fwc) free(fwc, M_IP6FW); + if (ftmp) free(ftmp, M_IP6FW); + return (ENOSPC); + } + + bcopy(frwl, ftmp, sizeof(struct ip6_fw)); + ftmp->fw_in_if.fu_via_if.name[IP6FW_IFNLEN - 1] = '\0'; + ftmp->fw_pcnt = 0L; + ftmp->fw_bcnt = 0L; + fwc->rule = ftmp; + + s = splnet(); + + if (!chainptr->lh_first) { + LIST_INSERT_HEAD(chainptr, fwc, chain); + splx(s); + return(0); + } else if (ftmp->fw_number == (u_short)-1) { + if (fwc) free(fwc, M_IP6FW); + if (ftmp) free(ftmp, M_IP6FW); + splx(s); + dprintf(("%s bad rule number\n", err_prefix)); + return (EINVAL); + } + + /* If entry number is 0, find highest numbered rule and add 100 */ + if (ftmp->fw_number == 0) { + for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { + if (fcp->rule->fw_number != (u_short)-1) + nbr = fcp->rule->fw_number; + else + break; + } + if (nbr < (u_short)-1 - 100) + nbr += 100; + ftmp->fw_number = nbr; + } + + /* Got a valid number; now insert it, keeping the list ordered */ + for (fcp = chainptr->lh_first; fcp; fcp = fcp->chain.le_next) { + if (fcp->rule->fw_number > ftmp->fw_number) { + if (fcpl) { + LIST_INSERT_AFTER(fcpl, fwc, chain); + } else { + LIST_INSERT_HEAD(chainptr, fwc, chain); + } + break; + } else { + fcpl = fcp; + } + } + + splx(s); + return (0); +} + +static int +del_entry6(struct ip6_fw_head *chainptr, u_short number) +{ + struct ip6_fw_chain *fcp; + int s; + + s = splnet(); + + fcp = chainptr->lh_first; + if (number != (u_short)-1) { + for (; fcp; fcp = fcp->chain.le_next) { + if (fcp->rule->fw_number == number) { + LIST_REMOVE(fcp, chain); + splx(s); + free(fcp->rule, M_IP6FW); + free(fcp, M_IP6FW); + return 0; + } + } + } + + splx(s); + return (EINVAL); +} + +static int +zero_entry6(struct mbuf *m) +{ + struct ip6_fw *frwl; + struct ip6_fw_chain *fcp; + int s; + + if (m && m->m_len != 0) { + if (m->m_len != sizeof(struct ip6_fw)) + return(EINVAL); + frwl = mtod(m, struct ip6_fw *); + } + else + frwl = NULL; + + /* + * It's possible to insert multiple chain entries with the + * same number, so we don't stop after finding the first + * match if zeroing a specific entry. + */ + s = splnet(); + for (fcp = ip6_fw_chain.lh_first; fcp; fcp = fcp->chain.le_next) + if (!frwl || frwl->fw_number == fcp->rule->fw_number) { + fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; + fcp->rule->timestamp = 0; + } + splx(s); + + if (fw6_verbose) { + if (frwl) + log(LOG_SECURITY | LOG_NOTICE, + "ip6fw: Entry %d cleared.\n", frwl->fw_number); + else + log(LOG_SECURITY | LOG_NOTICE, + "ip6fw: Accounting cleared.\n"); + } + + return(0); +} + +static struct ip6_fw * +check_ip6fw_mbuf(struct mbuf *m) +{ + /* Check length */ + if (m->m_len != sizeof(struct ip6_fw)) { + dprintf(("%s len=%d, want %zu\n", err_prefix, m->m_len, + sizeof(struct ip6_fw))); + return (NULL); + } + return(check_ip6fw_struct(mtod(m, struct ip6_fw *))); +} + +static struct ip6_fw * +check_ip6fw_struct(struct ip6_fw *frwl) +{ + /* Check for invalid flag bits */ + if ((frwl->fw_flg & ~IPV6_FW_F_MASK) != 0) { + dprintf(("%s undefined flag bits set (flags=%x)\n", + err_prefix, frwl->fw_flg)); + return (NULL); + } + /* Must apply to incoming or outgoing (or both) */ + if (!(frwl->fw_flg & (IPV6_FW_F_IN | IPV6_FW_F_OUT))) { + dprintf(("%s neither in nor out\n", err_prefix)); + return (NULL); + } + /* Empty interface name is no good */ + if (((frwl->fw_flg & IPV6_FW_F_IIFNAME) + && !*frwl->fw_in_if.fu_via_if.name) + || ((frwl->fw_flg & IPV6_FW_F_OIFNAME) + && !*frwl->fw_out_if.fu_via_if.name)) { + dprintf(("%s empty interface name\n", err_prefix)); + return (NULL); + } + /* Sanity check interface matching */ + if ((frwl->fw_flg & IF6_FW_F_VIAHACK) == IF6_FW_F_VIAHACK) { + ; /* allow "via" backwards compatibility */ + } else if ((frwl->fw_flg & IPV6_FW_F_IN) + && (frwl->fw_flg & IPV6_FW_F_OIFACE)) { + dprintf(("%s outgoing interface check on incoming\n", + err_prefix)); + return (NULL); + } + /* Sanity check port ranges */ + if ((frwl->fw_flg & IPV6_FW_F_SRNG) && IPV6_FW_GETNSRCP(frwl) < 2) { + dprintf(("%s src range set but n_src_p=%d\n", + err_prefix, IPV6_FW_GETNSRCP(frwl))); + return (NULL); + } + if ((frwl->fw_flg & IPV6_FW_F_DRNG) && IPV6_FW_GETNDSTP(frwl) < 2) { + dprintf(("%s dst range set but n_dst_p=%d\n", + err_prefix, IPV6_FW_GETNDSTP(frwl))); + return (NULL); + } + if (IPV6_FW_GETNSRCP(frwl) + IPV6_FW_GETNDSTP(frwl) > IPV6_FW_MAX_PORTS) { + dprintf(("%s too many ports (%d+%d)\n", + err_prefix, IPV6_FW_GETNSRCP(frwl), IPV6_FW_GETNDSTP(frwl))); + return (NULL); + } + /* + * Protocols other than TCP/UDP don't use port range + */ + if ((frwl->fw_prot != IPPROTO_TCP) && + (frwl->fw_prot != IPPROTO_UDP) && + (IPV6_FW_GETNSRCP(frwl) || IPV6_FW_GETNDSTP(frwl))) { + dprintf(("%s port(s) specified for non TCP/UDP rule\n", + err_prefix)); + return(NULL); + } + + /* + * Rather than modify the entry to make such entries work, + * we reject this rule and require user level utilities + * to enforce whatever policy they deem appropriate. + */ + if ((frwl->fw_src.s6_addr32[0] & (~frwl->fw_smsk.s6_addr32[0])) || + (frwl->fw_src.s6_addr32[1] & (~frwl->fw_smsk.s6_addr32[1])) || + (frwl->fw_src.s6_addr32[2] & (~frwl->fw_smsk.s6_addr32[2])) || + (frwl->fw_src.s6_addr32[3] & (~frwl->fw_smsk.s6_addr32[3])) || + (frwl->fw_dst.s6_addr32[0] & (~frwl->fw_dmsk.s6_addr32[0])) || + (frwl->fw_dst.s6_addr32[1] & (~frwl->fw_dmsk.s6_addr32[1])) || + (frwl->fw_dst.s6_addr32[2] & (~frwl->fw_dmsk.s6_addr32[2])) || + (frwl->fw_dst.s6_addr32[3] & (~frwl->fw_dmsk.s6_addr32[3]))) { + dprintf(("%s rule never matches\n", err_prefix)); + return(NULL); + } + + if ((frwl->fw_flg & IPV6_FW_F_FRAG) && + (frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) { + if (frwl->fw_nports) { + dprintf(("%s cannot mix 'frag' and ports\n", err_prefix)); + return(NULL); + } + if (frwl->fw_prot == IPPROTO_TCP && + frwl->fw_tcpf != frwl->fw_tcpnf) { + dprintf(("%s cannot mix 'frag' with TCP flags\n", err_prefix)); + return(NULL); + } + } + + /* Check command specific stuff */ + switch (frwl->fw_flg & IPV6_FW_F_COMMAND) + { + case IPV6_FW_F_REJECT: + if (frwl->fw_reject_code >= 0x100 + && !(frwl->fw_prot == IPPROTO_TCP + && frwl->fw_reject_code == IPV6_FW_REJECT_RST)) { + dprintf(("%s unknown reject code\n", err_prefix)); + return(NULL); + } + break; + case IPV6_FW_F_DIVERT: /* Diverting to port zero is invalid */ + case IPV6_FW_F_TEE: + if (frwl->fw_divert_port == 0) { + dprintf(("%s can't divert to port 0\n", err_prefix)); + return (NULL); + } + break; + case IPV6_FW_F_DENY: + case IPV6_FW_F_ACCEPT: + case IPV6_FW_F_COUNT: + case IPV6_FW_F_SKIPTO: + break; + default: + dprintf(("%s invalid command\n", err_prefix)); + return(NULL); + } + + return frwl; +} + +static int +ip6_fw_ctl(int stage, struct mbuf **mm) +{ + int error; + struct mbuf *m; + + if (stage == IPV6_FW_GET) { + struct ip6_fw_chain *fcp = ip6_fw_chain.lh_first; + *mm = m = m_get(M_TRYWAIT, MT_DATA); /* XXX */ + if (!m) + return(ENOBUFS); + if (sizeof *(fcp->rule) > MLEN) { + MCLGET(m, M_TRYWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + return(ENOBUFS); + } + } + for (; fcp; fcp = fcp->chain.le_next) { + bcopy(fcp->rule, m->m_data, sizeof *(fcp->rule)); + m->m_len = sizeof *(fcp->rule); + m->m_next = m_get(M_TRYWAIT, MT_DATA); /* XXX */ + if (!m->m_next) { + m_freem(*mm); + return(ENOBUFS); + } + m = m->m_next; + if (sizeof *(fcp->rule) > MLEN) { + MCLGET(m, M_TRYWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_freem(*mm); + return(ENOBUFS); + } + } + m->m_len = 0; + } + return (0); + } + m = *mm; + /* only allow get calls if secure mode > 2 */ + if (securelevel > 2) { + if (m) { + (void)m_freem(m); + *mm = 0; + } + return(EPERM); + } + if (stage == IPV6_FW_FLUSH) { + while (ip6_fw_chain.lh_first != NULL && + ip6_fw_chain.lh_first->rule->fw_number != (u_short)-1) { + struct ip6_fw_chain *fcp = ip6_fw_chain.lh_first; + int s = splnet(); + LIST_REMOVE(ip6_fw_chain.lh_first, chain); + splx(s); + free(fcp->rule, M_IP6FW); + free(fcp, M_IP6FW); + } + if (m) { + (void)m_freem(m); + *mm = 0; + } + return (0); + } + if (stage == IPV6_FW_ZERO) { + error = zero_entry6(m); + if (m) { + (void)m_freem(m); + *mm = 0; + } + return (error); + } + if (m == NULL) { + printf("%s NULL mbuf ptr\n", err_prefix); + return (EINVAL); + } + + if (stage == IPV6_FW_ADD) { + struct ip6_fw *frwl = check_ip6fw_mbuf(m); + + if (!frwl) + error = EINVAL; + else + error = add_entry6(&ip6_fw_chain, frwl); + if (m) { + (void)m_freem(m); + *mm = 0; + } + return error; + } + if (stage == IPV6_FW_DEL) { + if (m->m_len != sizeof(struct ip6_fw)) { + dprintf(("%s len=%d, want %zu\n", err_prefix, m->m_len, + sizeof(struct ip6_fw))); + error = EINVAL; + } else if (mtod(m, struct ip6_fw *)->fw_number == (u_short)-1) { + dprintf(("%s can't delete rule 65535\n", err_prefix)); + error = EINVAL; + } else + error = del_entry6(&ip6_fw_chain, + mtod(m, struct ip6_fw *)->fw_number); + if (m) { + (void)m_freem(m); + *mm = 0; + } + return error; + } + + dprintf(("%s unknown request %d\n", err_prefix, stage)); + if (m) { + (void)m_freem(m); + *mm = 0; + } + return (EINVAL); +} + +void +ip6_fw_init(void) +{ + struct ip6_fw default_rule; + + ip6_fw_chk_ptr = ip6_fw_chk; + ip6_fw_ctl_ptr = ip6_fw_ctl; + LIST_INIT(&ip6_fw_chain); + + bzero(&default_rule, sizeof default_rule); + default_rule.fw_prot = IPPROTO_IPV6; + default_rule.fw_number = (u_short)-1; +#ifdef IPV6FIREWALL_DEFAULT_TO_ACCEPT + default_rule.fw_flg |= IPV6_FW_F_ACCEPT; +#else + default_rule.fw_flg |= IPV6_FW_F_DENY; +#endif + default_rule.fw_flg |= IPV6_FW_F_IN | IPV6_FW_F_OUT; + if (check_ip6fw_struct(&default_rule) == NULL || + add_entry6(&ip6_fw_chain, &default_rule)) + panic(__func__); + + printf("IPv6 packet filtering initialized, "); +#ifdef IPV6FIREWALL_DEFAULT_TO_ACCEPT + printf("default to accept, "); +#endif +#ifndef IPV6FIREWALL_VERBOSE + printf("logging disabled\n"); +#else + if (fw6_verbose_limit == 0) + printf("unlimited logging\n"); + else + printf("logging limited to %d packets/entry\n", + fw6_verbose_limit); +#endif +} + +static ip6_fw_chk_t *old_chk_ptr; +static ip6_fw_ctl_t *old_ctl_ptr; + +static int +ip6fw_modevent(module_t mod, int type, void *unused) +{ + int s; + + switch (type) { + case MOD_LOAD: + s = splnet(); + + old_chk_ptr = ip6_fw_chk_ptr; + old_ctl_ptr = ip6_fw_ctl_ptr; + + ip6_fw_init(); + splx(s); + return 0; + case MOD_UNLOAD: + s = splnet(); + ip6_fw_chk_ptr = old_chk_ptr; + ip6_fw_ctl_ptr = old_ctl_ptr; + while (LIST_FIRST(&ip6_fw_chain) != NULL) { + struct ip6_fw_chain *fcp = LIST_FIRST(&ip6_fw_chain); + LIST_REMOVE(LIST_FIRST(&ip6_fw_chain), chain); + free(fcp->rule, M_IP6FW); + free(fcp, M_IP6FW); + } + + splx(s); + printf("IPv6 firewall unloaded\n"); + return 0; + default: + break; + } + return 0; +} + +static moduledata_t ip6fwmod = { + "ip6fw", + ip6fw_modevent, + 0 +}; +DECLARE_MODULE(ip6fw, ip6fwmod, SI_SUB_PSEUDO, SI_ORDER_ANY); diff --git a/sys/netinet6/ip6_fw.h b/sys/netinet6/ip6_fw.h new file mode 100644 index 0000000..3c1dcd0 --- /dev/null +++ b/sys/netinet6/ip6_fw.h @@ -0,0 +1,231 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_fw.h,v 1.9 2001/08/01 04:29:57 sumikawa Exp $ */ + +/* + * Copyright (C) 1998, 1999, 2000 and 2001 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1993 Daniel Boulet + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + */ + +#ifndef _IP6_FW_H +#define _IP6_FW_H + +#include <net/if.h> + +/* + * This union structure identifies an interface, either explicitly + * by name or implicitly by IP address. The flags IP_FW_F_IIFNAME + * and IP_FW_F_OIFNAME say how to interpret this structure. An + * interface unit number of -1 matches any unit number, while an + * IP address of 0.0.0.0 indicates matches any interface. + * + * The receive and transmit interfaces are only compared against the + * the packet if the corresponding bit (IP_FW_F_IIFACE or IP_FW_F_OIFACE) + * is set. Note some packets lack a receive or transmit interface + * (in which case the missing "interface" never matches). + */ + +union ip6_fw_if { + struct in6_addr fu_via_ip6; /* Specified by IPv6 address */ + struct { /* Specified by interface name */ +#define IP6FW_IFNLEN IFNAMSIZ + char name[IP6FW_IFNLEN]; + short unit; /* -1 means match any unit */ + } fu_via_if; +}; + +/* + * Format of an IP firewall descriptor + * + * fw_src, fw_dst, fw_smsk, fw_dmsk are always stored in network byte order. + * fw_flg and fw_n*p are stored in host byte order (of course). + * Port numbers are stored in HOST byte order. + * Warning: setsockopt() will fail if sizeof(struct ip_fw) > MLEN (108) + */ + +struct ip6_fw { + u_long fw_pcnt,fw_bcnt; /* Packet and byte counters */ + struct in6_addr fw_src, fw_dst; /* Source and destination IPv6 addr */ + struct in6_addr fw_smsk, fw_dmsk; /* Mask for src and dest IPv6 addr */ + u_short fw_number; /* Rule number */ + u_short fw_flg; /* Flags word */ +#define IPV6_FW_MAX_PORTS 10 /* A reasonable maximum */ + u_int fw_ipflg; /* IP flags word */ + u_short fw_pts[IPV6_FW_MAX_PORTS]; /* Array of port numbers to match */ + u_char fw_ip6opt,fw_ip6nopt; /* IPv6 options set/unset */ + u_char fw_tcpf,fw_tcpnf; /* TCP flags set/unset */ +#define IPV6_FW_ICMPTYPES_DIM (256 / (sizeof(unsigned) * 8)) + unsigned fw_icmp6types[IPV6_FW_ICMPTYPES_DIM]; /* ICMP types bitmap */ + long timestamp; /* timestamp (tv_sec) of last match */ + union ip6_fw_if fw_in_if, fw_out_if;/* Incoming and outgoing interfaces */ + union { + u_short fu_divert_port; /* Divert/tee port (options IP6DIVERT) */ + u_short fu_skipto_rule; /* SKIPTO command rule number */ + u_short fu_reject_code; /* REJECT response code */ + } fw_un; + u_char fw_prot; /* IPv6 protocol */ + u_char fw_nports; /* N'of src ports and # of dst ports */ + /* in ports array (dst ports follow */ + /* src ports; max of 10 ports in all; */ + /* count of 0 means match all ports) */ +}; + +#define IPV6_FW_GETNSRCP(rule) ((rule)->fw_nports & 0x0f) +#define IPV6_FW_SETNSRCP(rule, n) do { \ + (rule)->fw_nports &= ~0x0f; \ + (rule)->fw_nports |= (n); \ + } while (0) +#define IPV6_FW_GETNDSTP(rule) ((rule)->fw_nports >> 4) +#define IPV6_FW_SETNDSTP(rule, n) do { \ + (rule)->fw_nports &= ~0xf0; \ + (rule)->fw_nports |= (n) << 4;\ + } while (0) + +#define fw_divert_port fw_un.fu_divert_port +#define fw_skipto_rule fw_un.fu_skipto_rule +#define fw_reject_code fw_un.fu_reject_code + +struct ip6_fw_chain { + LIST_ENTRY(ip6_fw_chain) chain; + struct ip6_fw *rule; +}; + +/* + * Values for "flags" field . + */ +#define IPV6_FW_F_IN 0x0001 /* Check inbound packets */ +#define IPV6_FW_F_OUT 0x0002 /* Check outbound packets */ +#define IPV6_FW_F_IIFACE 0x0004 /* Apply inbound interface test */ +#define IPV6_FW_F_OIFACE 0x0008 /* Apply outbound interface test */ + +#define IPV6_FW_F_COMMAND 0x0070 /* Mask for type of chain entry: */ +#define IPV6_FW_F_DENY 0x0000 /* This is a deny rule */ +#define IPV6_FW_F_REJECT 0x0010 /* Deny and send a response packet */ +#define IPV6_FW_F_ACCEPT 0x0020 /* This is an accept rule */ +#define IPV6_FW_F_COUNT 0x0030 /* This is a count rule */ +#define IPV6_FW_F_DIVERT 0x0040 /* This is a divert rule */ +#define IPV6_FW_F_TEE 0x0050 /* This is a tee rule */ +#define IPV6_FW_F_SKIPTO 0x0060 /* This is a skipto rule */ + +#define IPV6_FW_F_PRN 0x0080 /* Print if this rule matches */ + +#define IPV6_FW_F_SRNG 0x0100 /* The first two src ports are a min * + * and max range (stored in host byte * + * order). */ + +#define IPV6_FW_F_DRNG 0x0200 /* The first two dst ports are a min * + * and max range (stored in host byte * + * order). */ + +#define IPV6_FW_F_IIFNAME 0x0400 /* In interface by name/unit (not IP) */ +#define IPV6_FW_F_OIFNAME 0x0800 /* Out interface by name/unit (not IP) */ + +#define IPV6_FW_F_INVSRC 0x1000 /* Invert sense of src check */ +#define IPV6_FW_F_INVDST 0x2000 /* Invert sense of dst check */ + +#define IPV6_FW_F_FRAG 0x4000 /* Fragment */ + +#define IPV6_FW_F_ICMPBIT 0x8000 /* ICMP type bitmap is valid */ + +#define IPV6_FW_F_MASK 0xFFFF /* All possible flag bits mask */ + +/* + * Flags for the 'fw_ipflg' field, for comparing values of ip and its protocols. */ +#define IPV6_FW_IF_TCPEST 0x00000020 /* established TCP connection */ +#define IPV6_FW_IF_TCPMSK 0x00000020 /* mask of all TCP values */ + +/* + * For backwards compatibility with rules specifying "via iface" but + * not restricted to only "in" or "out" packets, we define this combination + * of bits to represent this configuration. + */ + +#define IF6_FW_F_VIAHACK (IPV6_FW_F_IN|IPV6_FW_F_OUT|IPV6_FW_F_IIFACE|IPV6_FW_F_OIFACE) + +/* + * Definitions for REJECT response codes. + * Values less than 256 correspond to ICMP unreachable codes. + */ +#define IPV6_FW_REJECT_RST 0x0100 /* TCP packets: send RST */ + +/* + * Definitions for IPv6 option names. + */ +#define IPV6_FW_IP6OPT_HOPOPT 0x01 +#define IPV6_FW_IP6OPT_ROUTE 0x02 +#define IPV6_FW_IP6OPT_FRAG 0x04 +#define IPV6_FW_IP6OPT_ESP 0x08 +#define IPV6_FW_IP6OPT_AH 0x10 +#define IPV6_FW_IP6OPT_NONXT 0x20 +#define IPV6_FW_IP6OPT_OPTS 0x40 + +/* + * Definitions for TCP flags. + */ +#define IPV6_FW_TCPF_FIN TH_FIN +#define IPV6_FW_TCPF_SYN TH_SYN +#define IPV6_FW_TCPF_RST TH_RST +#define IPV6_FW_TCPF_PSH TH_PUSH +#define IPV6_FW_TCPF_ACK TH_ACK +#define IPV6_FW_TCPF_URG TH_URG + +/* + * Main firewall chains definitions and global var's definitions. + */ +#ifdef _KERNEL + +/* + * Function definitions. + */ +void ip6_fw_init(void); + +/* Firewall hooks */ +struct ip6_hdr; +typedef int ip6_fw_chk_t __P((struct ip6_hdr**, struct ifnet*, + u_short *, struct mbuf**)); +typedef int ip6_fw_ctl_t __P((int, struct mbuf**)); +extern ip6_fw_chk_t *ip6_fw_chk_ptr; +extern ip6_fw_ctl_t *ip6_fw_ctl_ptr; +extern int ip6_fw_enable; + +#endif /* _KERNEL */ + +#endif /* _IP6_FW_H */ diff --git a/sys/netinet6/ip6_input.c b/sys/netinet6/ip6_input.c new file mode 100644 index 0000000..dcb74fb --- /dev/null +++ b/sys/netinet6/ip6_input.c @@ -0,0 +1,1643 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_input.c,v 1.259 2002/01/21 04:58:09 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)ip_input.c 8.2 (Berkeley) 1/4/94 + */ + +#include "opt_ip6fw.h" +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" +#include "opt_pfil_hooks.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <net/netisr.h> +#ifdef PFIL_HOOKS +#include <net/pfil.h> +#endif + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#ifdef INET +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#endif /* INET */ +#include <netinet/ip6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ip6_var.h> +#include <netinet/in_pcb.h> +#include <netinet/icmp6.h> +#include <netinet6/in6_ifattach.h> +#include <netinet6/nd6.h> +#include <netinet6/in6_prefix.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#endif + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#define IPSEC +#endif /* FAST_IPSEC */ + +#include <netinet6/ip6_fw.h> + +#include <netinet6/ip6protosw.h> + +#include <net/net_osdep.h> + +extern struct domain inet6domain; + +u_char ip6_protox[IPPROTO_MAX]; +static struct ifqueue ip6intrq; +static int ip6qmaxlen = IFQ_MAXLEN; +struct in6_ifaddr *in6_ifaddr; + +extern struct callout in6_tmpaddrtimer_ch; + +int ip6_forward_srcrt; /* XXX */ +int ip6_sourcecheck; /* XXX */ +int ip6_sourcecheck_interval; /* XXX */ + +int ip6_ours_check_algorithm; + + +/* firewall hooks */ +ip6_fw_chk_t *ip6_fw_chk_ptr; +ip6_fw_ctl_t *ip6_fw_ctl_ptr; +int ip6_fw_enable = 1; + +struct ip6stat ip6stat; + +static void ip6_init2 __P((void *)); +static struct ip6aux *ip6_setdstifaddr __P((struct mbuf *, struct in6_ifaddr *)); +static int ip6_hopopts_input __P((u_int32_t *, u_int32_t *, struct mbuf **, int *)); +#ifdef PULLDOWN_TEST +static struct mbuf *ip6_pullexthdr __P((struct mbuf *, size_t, int)); +#endif + + +/* + * IP6 initialization: fill in IP6 protocol switch table. + * All protocols not implemented in kernel go to raw IP6 protocol handler. + */ +void +ip6_init() +{ + struct ip6protosw *pr; + int i; + struct timeval tv; + +#ifdef DIAGNOSTIC + if (sizeof(struct protosw) != sizeof(struct ip6protosw)) + panic("sizeof(protosw) != sizeof(ip6protosw)"); +#endif + pr = (struct ip6protosw *)pffindproto(PF_INET6, IPPROTO_RAW, SOCK_RAW); + if (pr == 0) + panic("ip6_init"); + for (i = 0; i < IPPROTO_MAX; i++) + ip6_protox[i] = pr - inet6sw; + for (pr = (struct ip6protosw *)inet6domain.dom_protosw; + pr < (struct ip6protosw *)inet6domain.dom_protoswNPROTOSW; pr++) + if (pr->pr_domain->dom_family == PF_INET6 && + pr->pr_protocol && pr->pr_protocol != IPPROTO_RAW) + ip6_protox[pr->pr_protocol] = pr - inet6sw; + ip6intrq.ifq_maxlen = ip6qmaxlen; + mtx_init(&ip6intrq.ifq_mtx, "ip6_inq", NULL, MTX_DEF); + netisr_register(NETISR_IPV6, ip6_input, &ip6intrq); + nd6_init(); + frag6_init(); + /* + * in many cases, random() here does NOT return random number + * as initialization during bootstrap time occur in fixed order. + */ + microtime(&tv); + ip6_flow_seq = random() ^ tv.tv_usec; + microtime(&tv); + ip6_desync_factor = (random() ^ tv.tv_usec) % MAX_TEMP_DESYNC_FACTOR; +} + +static void +ip6_init2(dummy) + void *dummy; +{ + + /* + * to route local address of p2p link to loopback, + * assign loopback address first. + */ + in6_ifattach(&loif[0], NULL); + + /* nd6_timer_init */ + callout_init(&nd6_timer_ch, 0); + callout_reset(&nd6_timer_ch, hz, nd6_timer, NULL); + + /* router renumbering prefix list maintenance */ + callout_init(&in6_rr_timer_ch, 0); + callout_reset(&in6_rr_timer_ch, hz, in6_rr_timer, NULL); + + /* timer for regeneranation of temporary addresses randomize ID */ + callout_reset(&in6_tmpaddrtimer_ch, + (ip6_temp_preferred_lifetime - ip6_desync_factor - + ip6_temp_regen_advance) * hz, + in6_tmpaddrtimer, NULL); +} + +/* cheat */ +/* This must be after route_init(), which is now SI_ORDER_THIRD */ +SYSINIT(netinet6init2, SI_SUB_PROTO_DOMAIN, SI_ORDER_MIDDLE, ip6_init2, NULL); + +extern struct route_in6 ip6_forward_rt; + +void +ip6_input(m) + struct mbuf *m; +{ + struct ip6_hdr *ip6; + int off = sizeof(struct ip6_hdr), nest; + u_int32_t plen; + u_int32_t rtalert = ~0; + int nxt, ours = 0; + struct ifnet *deliverifp = NULL; +#ifdef PFIL_HOOKS + struct packet_filter_hook *pfh; + struct mbuf *m0; + int rv; +#endif /* PFIL_HOOKS */ + +#ifdef IPSEC + /* + * should the inner packet be considered authentic? + * see comment in ah4_input(). + */ + if (m) { + m->m_flags &= ~M_AUTHIPHDR; + m->m_flags &= ~M_AUTHIPDGM; + } +#endif + + /* + * make sure we don't have onion peering information into m_aux. + */ + ip6_delaux(m); + + /* + * mbuf statistics + */ + if (m->m_flags & M_EXT) { + if (m->m_next) + ip6stat.ip6s_mext2m++; + else + ip6stat.ip6s_mext1++; + } else { +#define M2MMAX (sizeof(ip6stat.ip6s_m2m)/sizeof(ip6stat.ip6s_m2m[0])) + if (m->m_next) { + if (m->m_flags & M_LOOP) { + ip6stat.ip6s_m2m[loif[0].if_index]++; /* XXX */ + } else if (m->m_pkthdr.rcvif->if_index < M2MMAX) + ip6stat.ip6s_m2m[m->m_pkthdr.rcvif->if_index]++; + else + ip6stat.ip6s_m2m[0]++; + } else + ip6stat.ip6s_m1++; +#undef M2MMAX + } + + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_receive); + ip6stat.ip6s_total++; + +#ifndef PULLDOWN_TEST + /* + * L2 bridge code and some other code can return mbuf chain + * that does not conform to KAME requirement. too bad. + * XXX: fails to join if interface MTU > MCLBYTES. jumbogram? + */ + if (m && m->m_next != NULL && m->m_pkthdr.len < MCLBYTES) { + struct mbuf *n; + + MGETHDR(n, M_DONTWAIT, MT_HEADER); + if (n) + M_MOVE_PKTHDR(n, m); + if (n && n->m_pkthdr.len > MHLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (n == NULL) { + m_freem(m); + return; /*ENOBUFS*/ + } + + m_copydata(m, 0, n->m_pkthdr.len, mtod(n, caddr_t)); + n->m_len = n->m_pkthdr.len; + m_freem(m); + m = n; + } + IP6_EXTHDR_CHECK(m, 0, sizeof(struct ip6_hdr), /*nothing*/); +#endif + + if (m->m_len < sizeof(struct ip6_hdr)) { + struct ifnet *inifp; + inifp = m->m_pkthdr.rcvif; + if ((m = m_pullup(m, sizeof(struct ip6_hdr))) == 0) { + ip6stat.ip6s_toosmall++; + in6_ifstat_inc(inifp, ifs6_in_hdrerr); + return; + } + } + + ip6 = mtod(m, struct ip6_hdr *); + + if ((ip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) { + ip6stat.ip6s_badvers++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_hdrerr); + goto bad; + } + +#ifdef PFIL_HOOKS + /* + * Run through list of hooks for input packets. If there are any + * filters which require that additional packets in the flow are + * not fast-forwarded, they must clear the M_CANFASTFWD flag. + * Note that filters must _never_ set this flag, as another filter + * in the list may have previously cleared it. + */ + m0 = m; + pfh = pfil_hook_get(PFIL_IN, &inet6sw[ip6_protox[IPPROTO_IPV6]].pr_pfh); + for (; pfh; pfh = pfh->pfil_link.tqe_next) + if (pfh->pfil_func) { + rv = pfh->pfil_func(ip6, sizeof(*ip6), + m->m_pkthdr.rcvif, 0, &m0); + if (rv) + return; + m = m0; + if (m == NULL) + return; + ip6 = mtod(m, struct ip6_hdr *); + } +#endif /* PFIL_HOOKS */ + + ip6stat.ip6s_nxthist[ip6->ip6_nxt]++; + + /* + * Check with the firewall... + */ + if (ip6_fw_enable && ip6_fw_chk_ptr) { + u_short port = 0; + /* If ipfw says divert, we have to just drop packet */ + /* use port as a dummy argument */ + if ((*ip6_fw_chk_ptr)(&ip6, NULL, &port, &m)) { + m_freem(m); + m = NULL; + } + if (!m) + return; + } + + /* + * Check against address spoofing/corruption. + */ + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst)) { + /* + * XXX: "badscope" is not very suitable for a multicast source. + */ + ip6stat.ip6s_badscope++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); + goto bad; + } + if ((IN6_IS_ADDR_LOOPBACK(&ip6->ip6_src) || + IN6_IS_ADDR_LOOPBACK(&ip6->ip6_dst)) && + (m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) == 0) { + ip6stat.ip6s_badscope++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); + goto bad; + } + + /* + * The following check is not documented in specs. A malicious + * party may be able to use IPv4 mapped addr to confuse tcp/udp stack + * and bypass security checks (act as if it was from 127.0.0.1 by using + * IPv6 src ::ffff:127.0.0.1). Be cautious. + * + * This check chokes if we are in an SIIT cloud. As none of BSDs + * support IPv4-less kernel compilation, we cannot support SIIT + * environment at all. So, it makes more sense for us to reject any + * malicious packets for non-SIIT environment, than try to do a + * partical support for SIIT environment. + */ + if (IN6_IS_ADDR_V4MAPPED(&ip6->ip6_src) || + IN6_IS_ADDR_V4MAPPED(&ip6->ip6_dst)) { + ip6stat.ip6s_badscope++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); + goto bad; + } +#if 0 + /* + * Reject packets with IPv4 compatible addresses (auto tunnel). + * + * The code forbids auto tunnel relay case in RFC1933 (the check is + * stronger than RFC1933). We may want to re-enable it if mech-xx + * is revised to forbid relaying case. + */ + if (IN6_IS_ADDR_V4COMPAT(&ip6->ip6_src) || + IN6_IS_ADDR_V4COMPAT(&ip6->ip6_dst)) { + ip6stat.ip6s_badscope++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); + goto bad; + } +#endif + + /* drop packets if interface ID portion is already filled */ + if ((m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) == 0) { + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src) && + ip6->ip6_src.s6_addr16[1]) { + ip6stat.ip6s_badscope++; + goto bad; + } + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst) && + ip6->ip6_dst.s6_addr16[1]) { + ip6stat.ip6s_badscope++; + goto bad; + } + } + + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) + ip6->ip6_src.s6_addr16[1] + = htons(m->m_pkthdr.rcvif->if_index); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) + ip6->ip6_dst.s6_addr16[1] + = htons(m->m_pkthdr.rcvif->if_index); + +#if 0 /* this case seems to be unnecessary. (jinmei, 20010401) */ + /* + * We use rt->rt_ifp to determine if the address is ours or not. + * If rt_ifp is lo0, the address is ours. + * The problem here is, rt->rt_ifp for fe80::%lo0/64 is set to lo0, + * so any address under fe80::%lo0/64 will be mistakenly considered + * local. The special case is supplied to handle the case properly + * by actually looking at interface addresses + * (using in6ifa_ifpwithaddr). + */ + if ((m->m_pkthdr.rcvif->if_flags & IFF_LOOPBACK) != 0 && + IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst)) { + if (!in6ifa_ifpwithaddr(m->m_pkthdr.rcvif, &ip6->ip6_dst)) { + icmp6_error(m, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0); + /* m is already freed */ + return; + } + + ours = 1; + deliverifp = m->m_pkthdr.rcvif; + goto hbhcheck; + } +#endif + + /* + * Multicast check + */ + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + struct in6_multi *in6m = 0; + + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_mcast); + /* + * See if we belong to the destination multicast group on the + * arrival interface. + */ + IN6_LOOKUP_MULTI(ip6->ip6_dst, m->m_pkthdr.rcvif, in6m); + if (in6m) + ours = 1; + else if (!ip6_mrouter) { + ip6stat.ip6s_notmember++; + ip6stat.ip6s_cantforward++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard); + goto bad; + } + deliverifp = m->m_pkthdr.rcvif; + goto hbhcheck; + } + + /* + * Unicast check + */ + switch (ip6_ours_check_algorithm) { + default: + /* + * XXX: I intentionally broke our indentation rule here, + * since this switch-case is just for measurement and + * therefore should soon be removed. + */ + if (ip6_forward_rt.ro_rt != NULL && + (ip6_forward_rt.ro_rt->rt_flags & RTF_UP) != 0 && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, + &((struct sockaddr_in6 *)(&ip6_forward_rt.ro_dst))->sin6_addr)) + ip6stat.ip6s_forward_cachehit++; + else { + struct sockaddr_in6 *dst6; + + if (ip6_forward_rt.ro_rt) { + /* route is down or destination is different */ + ip6stat.ip6s_forward_cachemiss++; + RTFREE(ip6_forward_rt.ro_rt); + ip6_forward_rt.ro_rt = 0; + } + + bzero(&ip6_forward_rt.ro_dst, sizeof(struct sockaddr_in6)); + dst6 = (struct sockaddr_in6 *)&ip6_forward_rt.ro_dst; + dst6->sin6_len = sizeof(struct sockaddr_in6); + dst6->sin6_family = AF_INET6; + dst6->sin6_addr = ip6->ip6_dst; +#ifdef SCOPEDROUTING + ip6_forward_rt.ro_dst.sin6_scope_id = + in6_addr2scopeid(m->m_pkthdr.rcvif, &ip6->ip6_dst); +#endif + + rtalloc_ign((struct route *)&ip6_forward_rt, RTF_PRCLONING); + } + +#define rt6_key(r) ((struct sockaddr_in6 *)((r)->rt_nodes->rn_key)) + + /* + * Accept the packet if the forwarding interface to the destination + * according to the routing table is the loopback interface, + * unless the associated route has a gateway. + * Note that this approach causes to accept a packet if there is a + * route to the loopback interface for the destination of the packet. + * But we think it's even useful in some situations, e.g. when using + * a special daemon which wants to intercept the packet. + * + * XXX: some OSes automatically make a cloned route for the destination + * of an outgoing packet. If the outgoing interface of the packet + * is a loopback one, the kernel would consider the packet to be + * accepted, even if we have no such address assinged on the interface. + * We check the cloned flag of the route entry to reject such cases, + * assuming that route entries for our own addresses are not made by + * cloning (it should be true because in6_addloop explicitly installs + * the host route). However, we might have to do an explicit check + * while it would be less efficient. Or, should we rather install a + * reject route for such a case? + */ + if (ip6_forward_rt.ro_rt && + (ip6_forward_rt.ro_rt->rt_flags & + (RTF_HOST|RTF_GATEWAY)) == RTF_HOST && +#ifdef RTF_WASCLONED + !(ip6_forward_rt.ro_rt->rt_flags & RTF_WASCLONED) && +#endif +#ifdef RTF_CLONED + !(ip6_forward_rt.ro_rt->rt_flags & RTF_CLONED) && +#endif +#if 0 + /* + * The check below is redundant since the comparison of + * the destination and the key of the rtentry has + * already done through looking up the routing table. + */ + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, + &rt6_key(ip6_forward_rt.ro_rt)->sin6_addr) +#endif + ip6_forward_rt.ro_rt->rt_ifp->if_type == IFT_LOOP) { + struct in6_ifaddr *ia6 = + (struct in6_ifaddr *)ip6_forward_rt.ro_rt->rt_ifa; + + /* + * record address information into m_aux. + */ + (void)ip6_setdstifaddr(m, ia6); + + /* + * packets to a tentative, duplicated, or somehow invalid + * address must not be accepted. + */ + if (!(ia6->ia6_flags & IN6_IFF_NOTREADY)) { + /* this address is ready */ + ours = 1; + deliverifp = ia6->ia_ifp; /* correct? */ + /* Count the packet in the ip address stats */ + ia6->ia_ifa.if_ipackets++; + ia6->ia_ifa.if_ibytes += m->m_pkthdr.len; + goto hbhcheck; + } else { + /* address is not ready, so discard the packet. */ + nd6log((LOG_INFO, + "ip6_input: packet to an unready address %s->%s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst))); + + goto bad; + } + } + } /* XXX indentation (see above) */ + + /* + * FAITH(Firewall Aided Internet Translator) + */ + if (ip6_keepfaith) { + if (ip6_forward_rt.ro_rt && ip6_forward_rt.ro_rt->rt_ifp + && ip6_forward_rt.ro_rt->rt_ifp->if_type == IFT_FAITH) { + /* XXX do we need more sanity checks? */ + ours = 1; + deliverifp = ip6_forward_rt.ro_rt->rt_ifp; /* faith */ + goto hbhcheck; + } + } + + /* + * Now there is no reason to process the packet if it's not our own + * and we're not a router. + */ + if (!ip6_forwarding) { + ip6stat.ip6s_cantforward++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard); + goto bad; + } + + hbhcheck: + /* + * record address information into m_aux, if we don't have one yet. + * note that we are unable to record it, if the address is not listed + * as our interface address (e.g. multicast addresses, addresses + * within FAITH prefixes and such). + */ + if (deliverifp && !ip6_getdstifaddr(m)) { + struct in6_ifaddr *ia6; + + ia6 = in6_ifawithifp(deliverifp, &ip6->ip6_dst); + if (ia6) { + if (!ip6_setdstifaddr(m, ia6)) { + /* + * XXX maybe we should drop the packet here, + * as we could not provide enough information + * to the upper layers. + */ + } + } + } + + /* + * Process Hop-by-Hop options header if it's contained. + * m may be modified in ip6_hopopts_input(). + * If a JumboPayload option is included, plen will also be modified. + */ + plen = (u_int32_t)ntohs(ip6->ip6_plen); + if (ip6->ip6_nxt == IPPROTO_HOPOPTS) { + struct ip6_hbh *hbh; + + if (ip6_hopopts_input(&plen, &rtalert, &m, &off)) { +#if 0 /*touches NULL pointer*/ + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard); +#endif + return; /* m have already been freed */ + } + + /* adjust pointer */ + ip6 = mtod(m, struct ip6_hdr *); + + /* + * if the payload length field is 0 and the next header field + * indicates Hop-by-Hop Options header, then a Jumbo Payload + * option MUST be included. + */ + if (ip6->ip6_plen == 0 && plen == 0) { + /* + * Note that if a valid jumbo payload option is + * contained, ip6_hoptops_input() must set a valid + * (non-zero) payload length to the variable plen. + */ + ip6stat.ip6s_badoptions++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_discard); + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_hdrerr); + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + (caddr_t)&ip6->ip6_plen - (caddr_t)ip6); + return; + } +#ifndef PULLDOWN_TEST + /* ip6_hopopts_input() ensures that mbuf is contiguous */ + hbh = (struct ip6_hbh *)(ip6 + 1); +#else + IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, sizeof(struct ip6_hdr), + sizeof(struct ip6_hbh)); + if (hbh == NULL) { + ip6stat.ip6s_tooshort++; + return; + } +#endif + nxt = hbh->ip6h_nxt; + + /* + * accept the packet if a router alert option is included + * and we act as an IPv6 router. + */ + if (rtalert != ~0 && ip6_forwarding) + ours = 1; + } else + nxt = ip6->ip6_nxt; + + /* + * Check that the amount of data in the buffers + * is as at least much as the IPv6 header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + if (m->m_pkthdr.len - sizeof(struct ip6_hdr) < plen) { + ip6stat.ip6s_tooshort++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_truncated); + goto bad; + } + if (m->m_pkthdr.len > sizeof(struct ip6_hdr) + plen) { + if (m->m_len == m->m_pkthdr.len) { + m->m_len = sizeof(struct ip6_hdr) + plen; + m->m_pkthdr.len = sizeof(struct ip6_hdr) + plen; + } else + m_adj(m, sizeof(struct ip6_hdr) + plen - m->m_pkthdr.len); + } + + /* + * Forward if desirable. + */ + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + /* + * If we are acting as a multicast router, all + * incoming multicast packets are passed to the + * kernel-level multicast forwarding function. + * The packet is returned (relatively) intact; if + * ip6_mforward() returns a non-zero value, the packet + * must be discarded, else it may be accepted below. + */ + if (ip6_mrouter && ip6_mforward(ip6, m->m_pkthdr.rcvif, m)) { + ip6stat.ip6s_cantforward++; + m_freem(m); + return; + } + if (!ours) { + m_freem(m); + return; + } + } else if (!ours) { + ip6_forward(m, 0); + return; + } + + ip6 = mtod(m, struct ip6_hdr *); + + /* + * Malicious party may be able to use IPv4 mapped addr to confuse + * tcp/udp stack and bypass security checks (act as if it was from + * 127.0.0.1 by using IPv6 src ::ffff:127.0.0.1). Be cautious. + * + * For SIIT end node behavior, you may want to disable the check. + * However, you will become vulnerable to attacks using IPv4 mapped + * source. + */ + if (IN6_IS_ADDR_V4MAPPED(&ip6->ip6_src) || + IN6_IS_ADDR_V4MAPPED(&ip6->ip6_dst)) { + ip6stat.ip6s_badscope++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_addrerr); + goto bad; + } + + /* + * Tell launch routine the next header + */ + ip6stat.ip6s_delivered++; + in6_ifstat_inc(deliverifp, ifs6_in_deliver); + nest = 0; + + while (nxt != IPPROTO_DONE) { + if (ip6_hdrnestlimit && (++nest > ip6_hdrnestlimit)) { + ip6stat.ip6s_toomanyhdr++; + goto bad; + } + + /* + * protection against faulty packet - there should be + * more sanity checks in header chain processing. + */ + if (m->m_pkthdr.len < off) { + ip6stat.ip6s_tooshort++; + in6_ifstat_inc(m->m_pkthdr.rcvif, ifs6_in_truncated); + goto bad; + } + +#if 0 + /* + * do we need to do it for every header? yeah, other + * functions can play with it (like re-allocate and copy). + */ + mhist = ip6_addaux(m); + if (mhist && M_TRAILINGSPACE(mhist) >= sizeof(nxt)) { + hist = mtod(mhist, caddr_t) + mhist->m_len; + bcopy(&nxt, hist, sizeof(nxt)); + mhist->m_len += sizeof(nxt); + } else { + ip6stat.ip6s_toomanyhdr++; + goto bad; + } +#endif + +#ifdef IPSEC + /* + * enforce IPsec policy checking if we are seeing last header. + * note that we do not visit this with protocols with pcb layer + * code - like udp/tcp/raw ip. + */ + if ((inet6sw[ip6_protox[nxt]].pr_flags & PR_LASTHDR) != 0 && + ipsec6_in_reject(m, NULL)) { + ipsec6stat.in_polvio++; + goto bad; + } +#endif + + nxt = (*inet6sw[ip6_protox[nxt]].pr_input)(&m, &off, nxt); + } + return; + bad: + m_freem(m); +} + +/* + * set/grab in6_ifaddr correspond to IPv6 destination address. + * XXX backward compatibility wrapper + */ +static struct ip6aux * +ip6_setdstifaddr(m, ia6) + struct mbuf *m; + struct in6_ifaddr *ia6; +{ + struct ip6aux *n; + + n = ip6_addaux(m); + if (n) + n->ip6a_dstia6 = ia6; + return n; /* NULL if failed to set */ +} + +struct in6_ifaddr * +ip6_getdstifaddr(m) + struct mbuf *m; +{ + struct ip6aux *n; + + n = ip6_findaux(m); + if (n) + return n->ip6a_dstia6; + else + return NULL; +} + +/* + * Hop-by-Hop options header processing. If a valid jumbo payload option is + * included, the real payload length will be stored in plenp. + */ +static int +ip6_hopopts_input(plenp, rtalertp, mp, offp) + u_int32_t *plenp; + u_int32_t *rtalertp; /* XXX: should be stored more smart way */ + struct mbuf **mp; + int *offp; +{ + struct mbuf *m = *mp; + int off = *offp, hbhlen; + struct ip6_hbh *hbh; + u_int8_t *opt; + + /* validation of the length of the header */ +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(*hbh), -1); + hbh = (struct ip6_hbh *)(mtod(m, caddr_t) + off); + hbhlen = (hbh->ip6h_len + 1) << 3; + + IP6_EXTHDR_CHECK(m, off, hbhlen, -1); + hbh = (struct ip6_hbh *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, + sizeof(struct ip6_hdr), sizeof(struct ip6_hbh)); + if (hbh == NULL) { + ip6stat.ip6s_tooshort++; + return -1; + } + hbhlen = (hbh->ip6h_len + 1) << 3; + IP6_EXTHDR_GET(hbh, struct ip6_hbh *, m, sizeof(struct ip6_hdr), + hbhlen); + if (hbh == NULL) { + ip6stat.ip6s_tooshort++; + return -1; + } +#endif + off += hbhlen; + hbhlen -= sizeof(struct ip6_hbh); + opt = (u_int8_t *)hbh + sizeof(struct ip6_hbh); + + if (ip6_process_hopopts(m, (u_int8_t *)hbh + sizeof(struct ip6_hbh), + hbhlen, rtalertp, plenp) < 0) + return(-1); + + *offp = off; + *mp = m; + return(0); +} + +/* + * Search header for all Hop-by-hop options and process each option. + * This function is separate from ip6_hopopts_input() in order to + * handle a case where the sending node itself process its hop-by-hop + * options header. In such a case, the function is called from ip6_output(). + * + * The function assumes that hbh header is located right after the IPv6 header + * (RFC2460 p7), opthead is pointer into data content in m, and opthead to + * opthead + hbhlen is located in continuous memory region. + */ +int +ip6_process_hopopts(m, opthead, hbhlen, rtalertp, plenp) + struct mbuf *m; + u_int8_t *opthead; + int hbhlen; + u_int32_t *rtalertp; + u_int32_t *plenp; +{ + struct ip6_hdr *ip6; + int optlen = 0; + u_int8_t *opt = opthead; + u_int16_t rtalert_val; + u_int32_t jumboplen; + const int erroff = sizeof(struct ip6_hdr) + sizeof(struct ip6_hbh); + + for (; hbhlen > 0; hbhlen -= optlen, opt += optlen) { + switch (*opt) { + case IP6OPT_PAD1: + optlen = 1; + break; + case IP6OPT_PADN: + if (hbhlen < IP6OPT_MINLEN) { + ip6stat.ip6s_toosmall++; + goto bad; + } + optlen = *(opt + 1) + 2; + break; + case IP6OPT_RTALERT: + /* XXX may need check for alignment */ + if (hbhlen < IP6OPT_RTALERT_LEN) { + ip6stat.ip6s_toosmall++; + goto bad; + } + if (*(opt + 1) != IP6OPT_RTALERT_LEN - 2) { + /* XXX stat */ + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff + opt + 1 - opthead); + return(-1); + } + optlen = IP6OPT_RTALERT_LEN; + bcopy((caddr_t)(opt + 2), (caddr_t)&rtalert_val, 2); + *rtalertp = ntohs(rtalert_val); + break; + case IP6OPT_JUMBO: + /* XXX may need check for alignment */ + if (hbhlen < IP6OPT_JUMBO_LEN) { + ip6stat.ip6s_toosmall++; + goto bad; + } + if (*(opt + 1) != IP6OPT_JUMBO_LEN - 2) { + /* XXX stat */ + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff + opt + 1 - opthead); + return(-1); + } + optlen = IP6OPT_JUMBO_LEN; + + /* + * IPv6 packets that have non 0 payload length + * must not contain a jumbo payload option. + */ + ip6 = mtod(m, struct ip6_hdr *); + if (ip6->ip6_plen) { + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff + opt - opthead); + return(-1); + } + + /* + * We may see jumbolen in unaligned location, so + * we'd need to perform bcopy(). + */ + bcopy(opt + 2, &jumboplen, sizeof(jumboplen)); + jumboplen = (u_int32_t)htonl(jumboplen); + +#if 1 + /* + * if there are multiple jumbo payload options, + * *plenp will be non-zero and the packet will be + * rejected. + * the behavior may need some debate in ipngwg - + * multiple options does not make sense, however, + * there's no explicit mention in specification. + */ + if (*plenp != 0) { + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff + opt + 2 - opthead); + return(-1); + } +#endif + + /* + * jumbo payload length must be larger than 65535. + */ + if (jumboplen <= IPV6_MAXPACKET) { + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_HEADER, + erroff + opt + 2 - opthead); + return(-1); + } + *plenp = jumboplen; + + break; + default: /* unknown option */ + if (hbhlen < IP6OPT_MINLEN) { + ip6stat.ip6s_toosmall++; + goto bad; + } + optlen = ip6_unknown_opt(opt, m, + erroff + opt - opthead); + if (optlen == -1) + return(-1); + optlen += 2; + break; + } + } + + return(0); + + bad: + m_freem(m); + return(-1); +} + +/* + * Unknown option processing. + * The third argument `off' is the offset from the IPv6 header to the option, + * which is necessary if the IPv6 header the and option header and IPv6 header + * is not continuous in order to return an ICMPv6 error. + */ +int +ip6_unknown_opt(optp, m, off) + u_int8_t *optp; + struct mbuf *m; + int off; +{ + struct ip6_hdr *ip6; + + switch (IP6OPT_TYPE(*optp)) { + case IP6OPT_TYPE_SKIP: /* ignore the option */ + return((int)*(optp + 1)); + case IP6OPT_TYPE_DISCARD: /* silently discard */ + m_freem(m); + return(-1); + case IP6OPT_TYPE_FORCEICMP: /* send ICMP even if multicasted */ + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_OPTION, off); + return(-1); + case IP6OPT_TYPE_ICMP: /* send ICMP if not multicasted */ + ip6stat.ip6s_badoptions++; + ip6 = mtod(m, struct ip6_hdr *); + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + (m->m_flags & (M_BCAST|M_MCAST))) + m_freem(m); + else + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_OPTION, off); + return(-1); + } + + m_freem(m); /* XXX: NOTREACHED */ + return(-1); +} + +/* + * Create the "control" list for this pcb. + * The function will not modify mbuf chain at all. + * + * with KAME mbuf chain restriction: + * The routine will be called from upper layer handlers like tcp6_input(). + * Thus the routine assumes that the caller (tcp6_input) have already + * called IP6_EXTHDR_CHECK() and all the extension headers are located in the + * very first mbuf on the mbuf chain. + */ +void +ip6_savecontrol(in6p, mp, ip6, m) + struct inpcb *in6p; + struct mbuf **mp; + struct ip6_hdr *ip6; + struct mbuf *m; +{ +#if __FreeBSD_version >= 500000 + struct thread *td = curthread; /* XXX */ +#else + struct proc *td = curproc; /* XXX */ +#endif + int privileged = 0; + int rthdr_exist = 0; + + + if (td && !suser(td)) + privileged++; + +#ifdef SO_TIMESTAMP + if ((in6p->in6p_socket->so_options & SO_TIMESTAMP) != 0) { + struct timeval tv; + + microtime(&tv); + *mp = sbcreatecontrol((caddr_t) &tv, sizeof(tv), + SCM_TIMESTAMP, SOL_SOCKET); + if (*mp) { + mp = &(*mp)->m_next; + } + } +#endif + + /* RFC 2292 sec. 5 */ + if ((in6p->in6p_flags & IN6P_PKTINFO) != 0) { + struct in6_pktinfo pi6; + bcopy(&ip6->ip6_dst, &pi6.ipi6_addr, sizeof(struct in6_addr)); + if (IN6_IS_SCOPE_LINKLOCAL(&pi6.ipi6_addr)) + pi6.ipi6_addr.s6_addr16[1] = 0; + pi6.ipi6_ifindex = (m && m->m_pkthdr.rcvif) + ? m->m_pkthdr.rcvif->if_index + : 0; + *mp = sbcreatecontrol((caddr_t) &pi6, + sizeof(struct in6_pktinfo), IPV6_PKTINFO, + IPPROTO_IPV6); + if (*mp) + mp = &(*mp)->m_next; + } + + if ((in6p->in6p_flags & IN6P_HOPLIMIT) != 0) { + int hlim = ip6->ip6_hlim & 0xff; + *mp = sbcreatecontrol((caddr_t) &hlim, + sizeof(int), IPV6_HOPLIMIT, IPPROTO_IPV6); + if (*mp) + mp = &(*mp)->m_next; + } + + /* + * IPV6_HOPOPTS socket option. We require super-user privilege + * for the option, but it might be too strict, since there might + * be some hop-by-hop options which can be returned to normal user. + * See RFC 2292 section 6. + */ + if ((in6p->in6p_flags & IN6P_HOPOPTS) != 0 && privileged) { + /* + * Check if a hop-by-hop options header is contatined in the + * received packet, and if so, store the options as ancillary + * data. Note that a hop-by-hop options header must be + * just after the IPv6 header, which fact is assured through + * the IPv6 input processing. + */ + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + if (ip6->ip6_nxt == IPPROTO_HOPOPTS) { + struct ip6_hbh *hbh; + int hbhlen = 0; +#ifdef PULLDOWN_TEST + struct mbuf *ext; +#endif + +#ifndef PULLDOWN_TEST + hbh = (struct ip6_hbh *)(ip6 + 1); + hbhlen = (hbh->ip6h_len + 1) << 3; +#else + ext = ip6_pullexthdr(m, sizeof(struct ip6_hdr), + ip6->ip6_nxt); + if (ext == NULL) { + ip6stat.ip6s_tooshort++; + return; + } + hbh = mtod(ext, struct ip6_hbh *); + hbhlen = (hbh->ip6h_len + 1) << 3; + if (hbhlen != ext->m_len) { + m_freem(ext); + ip6stat.ip6s_tooshort++; + return; + } +#endif + + /* + * XXX: We copy whole the header even if a jumbo + * payload option is included, which option is to + * be removed before returning in the RFC 2292. + * Note: this constraint is removed in 2292bis. + */ + *mp = sbcreatecontrol((caddr_t)hbh, hbhlen, + IPV6_HOPOPTS, IPPROTO_IPV6); + if (*mp) + mp = &(*mp)->m_next; +#ifdef PULLDOWN_TEST + m_freem(ext); +#endif + } + } + + /* IPV6_DSTOPTS and IPV6_RTHDR socket options */ + if ((in6p->in6p_flags & (IN6P_DSTOPTS | IN6P_RTHDRDSTOPTS)) != 0) { + int proto, off, nxt; + + /* + * go through the header chain to see if a routing header is + * contained in the packet. We need this information to store + * destination options headers (if any) properly. + * XXX: performance issue. We should record this info when + * processing extension headers in incoming routine. + * (todo) use m_aux? + */ + proto = IPPROTO_IPV6; + off = 0; + nxt = -1; + while (1) { + int newoff; + + newoff = ip6_nexthdr(m, off, proto, &nxt); + if (newoff < 0) + break; + if (newoff < off) /* invalid, check for safety */ + break; + if ((proto = nxt) == IPPROTO_ROUTING) { + rthdr_exist = 1; + break; + } + off = newoff; + } + } + + if ((in6p->in6p_flags & + (IN6P_RTHDR | IN6P_DSTOPTS | IN6P_RTHDRDSTOPTS)) != 0) { + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + int nxt = ip6->ip6_nxt, off = sizeof(struct ip6_hdr); + + /* + * Search for destination options headers or routing + * header(s) through the header chain, and stores each + * header as ancillary data. + * Note that the order of the headers remains in + * the chain of ancillary data. + */ + while (1) { /* is explicit loop prevention necessary? */ + struct ip6_ext *ip6e = NULL; + int elen; +#ifdef PULLDOWN_TEST + struct mbuf *ext = NULL; +#endif + + /* + * if it is not an extension header, don't try to + * pull it from the chain. + */ + switch (nxt) { + case IPPROTO_DSTOPTS: + case IPPROTO_ROUTING: + case IPPROTO_HOPOPTS: + case IPPROTO_AH: /* is it possible? */ + break; + default: + goto loopend; + } + +#ifndef PULLDOWN_TEST + if (off + sizeof(*ip6e) > m->m_len) + goto loopend; + ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + off); + if (nxt == IPPROTO_AH) + elen = (ip6e->ip6e_len + 2) << 2; + else + elen = (ip6e->ip6e_len + 1) << 3; + if (off + elen > m->m_len) + goto loopend; +#else + ext = ip6_pullexthdr(m, off, nxt); + if (ext == NULL) { + ip6stat.ip6s_tooshort++; + return; + } + ip6e = mtod(ext, struct ip6_ext *); + if (nxt == IPPROTO_AH) + elen = (ip6e->ip6e_len + 2) << 2; + else + elen = (ip6e->ip6e_len + 1) << 3; + if (elen != ext->m_len) { + m_freem(ext); + ip6stat.ip6s_tooshort++; + return; + } +#endif + + switch (nxt) { + case IPPROTO_DSTOPTS: + if ((in6p->in6p_flags & IN6P_DSTOPTS) == 0) + break; + + /* + * We also require super-user privilege for + * the option. + * See the comments on IN6_HOPOPTS. + */ + if (!privileged) + break; + + *mp = sbcreatecontrol((caddr_t)ip6e, elen, + IPV6_DSTOPTS, + IPPROTO_IPV6); + if (*mp) + mp = &(*mp)->m_next; + break; + case IPPROTO_ROUTING: + if (!in6p->in6p_flags & IN6P_RTHDR) + break; + + *mp = sbcreatecontrol((caddr_t)ip6e, elen, + IPV6_RTHDR, + IPPROTO_IPV6); + if (*mp) + mp = &(*mp)->m_next; + break; + case IPPROTO_HOPOPTS: + case IPPROTO_AH: /* is it possible? */ + break; + + default: + /* + * other cases have been filtered in the above. + * none will visit this case. here we supply + * the code just in case (nxt overwritten or + * other cases). + */ +#ifdef PULLDOWN_TEST + m_freem(ext); +#endif + goto loopend; + + } + + /* proceed with the next header. */ + off += elen; + nxt = ip6e->ip6e_nxt; + ip6e = NULL; +#ifdef PULLDOWN_TEST + m_freem(ext); + ext = NULL; +#endif + } + loopend: + ; + } + +} + +#ifdef PULLDOWN_TEST +/* + * pull single extension header from mbuf chain. returns single mbuf that + * contains the result, or NULL on error. + */ +static struct mbuf * +ip6_pullexthdr(m, off, nxt) + struct mbuf *m; + size_t off; + int nxt; +{ + struct ip6_ext ip6e; + size_t elen; + struct mbuf *n; + +#ifdef DIAGNOSTIC + switch (nxt) { + case IPPROTO_DSTOPTS: + case IPPROTO_ROUTING: + case IPPROTO_HOPOPTS: + case IPPROTO_AH: /* is it possible? */ + break; + default: + printf("ip6_pullexthdr: invalid nxt=%d\n", nxt); + } +#endif + + m_copydata(m, off, sizeof(ip6e), (caddr_t)&ip6e); + if (nxt == IPPROTO_AH) + elen = (ip6e.ip6e_len + 2) << 2; + else + elen = (ip6e.ip6e_len + 1) << 3; + + MGET(n, M_DONTWAIT, MT_DATA); + if (n && elen >= MLEN) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_free(n); + n = NULL; + } + } + if (!n) + return NULL; + + n->m_len = 0; + if (elen >= M_TRAILINGSPACE(n)) { + m_free(n); + return NULL; + } + + m_copydata(m, off, elen, mtod(n, caddr_t)); + n->m_len = elen; + return n; +} +#endif + +/* + * Get pointer to the previous header followed by the header + * currently processed. + * XXX: This function supposes that + * M includes all headers, + * the next header field and the header length field of each header + * are valid, and + * the sum of each header length equals to OFF. + * Because of these assumptions, this function must be called very + * carefully. Moreover, it will not be used in the near future when + * we develop `neater' mechanism to process extension headers. + */ +char * +ip6_get_prevhdr(m, off) + struct mbuf *m; + int off; +{ + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + + if (off == sizeof(struct ip6_hdr)) + return(&ip6->ip6_nxt); + else { + int len, nxt; + struct ip6_ext *ip6e = NULL; + + nxt = ip6->ip6_nxt; + len = sizeof(struct ip6_hdr); + while (len < off) { + ip6e = (struct ip6_ext *)(mtod(m, caddr_t) + len); + + switch (nxt) { + case IPPROTO_FRAGMENT: + len += sizeof(struct ip6_frag); + break; + case IPPROTO_AH: + len += (ip6e->ip6e_len + 2) << 2; + break; + default: + len += (ip6e->ip6e_len + 1) << 3; + break; + } + nxt = ip6e->ip6e_nxt; + } + if (ip6e) + return(&ip6e->ip6e_nxt); + else + return NULL; + } +} + +/* + * get next header offset. m will be retained. + */ +int +ip6_nexthdr(m, off, proto, nxtp) + struct mbuf *m; + int off; + int proto; + int *nxtp; +{ + struct ip6_hdr ip6; + struct ip6_ext ip6e; + struct ip6_frag fh; + + /* just in case */ + if (m == NULL) + panic("ip6_nexthdr: m == NULL"); + if ((m->m_flags & M_PKTHDR) == 0 || m->m_pkthdr.len < off) + return -1; + + switch (proto) { + case IPPROTO_IPV6: + if (m->m_pkthdr.len < off + sizeof(ip6)) + return -1; + m_copydata(m, off, sizeof(ip6), (caddr_t)&ip6); + if (nxtp) + *nxtp = ip6.ip6_nxt; + off += sizeof(ip6); + return off; + + case IPPROTO_FRAGMENT: + /* + * terminate parsing if it is not the first fragment, + * it does not make sense to parse through it. + */ + if (m->m_pkthdr.len < off + sizeof(fh)) + return -1; + m_copydata(m, off, sizeof(fh), (caddr_t)&fh); + if ((ntohs(fh.ip6f_offlg) & IP6F_OFF_MASK) != 0) + return -1; + if (nxtp) + *nxtp = fh.ip6f_nxt; + off += sizeof(struct ip6_frag); + return off; + + case IPPROTO_AH: + if (m->m_pkthdr.len < off + sizeof(ip6e)) + return -1; + m_copydata(m, off, sizeof(ip6e), (caddr_t)&ip6e); + if (nxtp) + *nxtp = ip6e.ip6e_nxt; + off += (ip6e.ip6e_len + 2) << 2; + return off; + + case IPPROTO_HOPOPTS: + case IPPROTO_ROUTING: + case IPPROTO_DSTOPTS: + if (m->m_pkthdr.len < off + sizeof(ip6e)) + return -1; + m_copydata(m, off, sizeof(ip6e), (caddr_t)&ip6e); + if (nxtp) + *nxtp = ip6e.ip6e_nxt; + off += (ip6e.ip6e_len + 1) << 3; + return off; + + case IPPROTO_NONE: + case IPPROTO_ESP: + case IPPROTO_IPCOMP: + /* give up */ + return -1; + + default: + return -1; + } + + return -1; +} + +/* + * get offset for the last header in the chain. m will be kept untainted. + */ +int +ip6_lasthdr(m, off, proto, nxtp) + struct mbuf *m; + int off; + int proto; + int *nxtp; +{ + int newoff; + int nxt; + + if (!nxtp) { + nxt = -1; + nxtp = &nxt; + } + while (1) { + newoff = ip6_nexthdr(m, off, proto, nxtp); + if (newoff < 0) + return off; + else if (newoff < off) + return -1; /* invalid */ + else if (newoff == off) + return newoff; + + off = newoff; + proto = *nxtp; + } +} + +struct ip6aux * +ip6_addaux(m) + struct mbuf *m; +{ + struct m_tag *tag = m_tag_find(m, PACKET_TAG_IPV6_INPUT, NULL); + if (!tag) { + tag = m_tag_get(PACKET_TAG_IPV6_INPUT, + sizeof (struct ip6aux), + M_NOWAIT); + if (tag) + m_tag_prepend(m, tag); + } + if (tag) + bzero(tag+1, sizeof (struct ip6aux)); + return tag ? (struct ip6aux*)(tag+1) : NULL; +} + +struct ip6aux * +ip6_findaux(m) + struct mbuf *m; +{ + struct m_tag *tag = m_tag_find(m, PACKET_TAG_IPV6_INPUT, NULL); + return tag ? (struct ip6aux*)(tag+1) : NULL; +} + +void +ip6_delaux(m) + struct mbuf *m; +{ + struct m_tag *tag = m_tag_find(m, PACKET_TAG_IPV6_INPUT, NULL); + if (tag) + m_tag_delete(m, tag); +} + +/* + * System control for IP6 + */ + +u_char inet6ctlerrmap[PRC_NCMDS] = { + 0, 0, 0, 0, + 0, EMSGSIZE, EHOSTDOWN, EHOSTUNREACH, + EHOSTUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, + EMSGSIZE, EHOSTUNREACH, 0, 0, + 0, 0, 0, 0, + ENOPROTOOPT +}; diff --git a/sys/netinet6/ip6_mroute.c b/sys/netinet6/ip6_mroute.c new file mode 100644 index 0000000..f1257d3 --- /dev/null +++ b/sys/netinet6/ip6_mroute.c @@ -0,0 +1,1814 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_mroute.c,v 1.58 2001/12/18 02:36:31 itojun Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* BSDI ip_mroute.c,v 2.10 1996/11/14 00:29:52 jch Exp */ + +/* + * IP multicast forwarding procedures + * + * Written by David Waitzman, BBN Labs, August 1988. + * Modified by Steve Deering, Stanford, February 1989. + * Modified by Mark J. Steiglitz, Stanford, May, 1991 + * Modified by Van Jacobson, LBL, January 1993 + * Modified by Ajit Thyagarajan, PARC, August 1993 + * Modified by Bill Fenenr, PARC, April 1994 + * + * MROUTING Revision: 3.5.1.2 + PIM-SMv2 (pimd) Support + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/callout.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/signalvar.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sockio.h> +#include <sys/sx.h> +#include <sys/syslog.h> +#include <sys/systm.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/raw_cb.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> + +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/ip6_mroute.h> +#include <netinet6/pim6.h> +#include <netinet6/pim6_var.h> + +#include <net/net_osdep.h> + +static MALLOC_DEFINE(M_MRTABLE, "mf6c", "multicast forwarding cache entry"); + +#define M_HASCL(m) ((m)->m_flags & M_EXT) + +static int ip6_mdq __P((struct mbuf *, struct ifnet *, struct mf6c *)); +static void phyint_send __P((struct ip6_hdr *, struct mif6 *, struct mbuf *)); + +static int set_pim6 __P((int *)); +static int socket_send __P((struct socket *, struct mbuf *, + struct sockaddr_in6 *)); +static int register_send __P((struct ip6_hdr *, struct mif6 *, + struct mbuf *)); + +/* + * Globals. All but ip6_mrouter, ip6_mrtproto and mrt6stat could be static, + * except for netstat or debugging purposes. + */ +struct socket *ip6_mrouter = NULL; +int ip6_mrouter_ver = 0; +int ip6_mrtproto = IPPROTO_PIM; /* for netstat only */ +struct mrt6stat mrt6stat; + +#define NO_RTE_FOUND 0x1 +#define RTE_FOUND 0x2 + +struct mf6c *mf6ctable[MF6CTBLSIZ]; +u_char n6expire[MF6CTBLSIZ]; +static struct mif6 mif6table[MAXMIFS]; +#ifdef MRT6DEBUG +u_int mrt6debug = 0; /* debug level */ +#define DEBUG_MFC 0x02 +#define DEBUG_FORWARD 0x04 +#define DEBUG_EXPIRE 0x08 +#define DEBUG_XMIT 0x10 +#define DEBUG_REG 0x20 +#define DEBUG_PIM 0x40 +#endif + +static void expire_upcalls __P((void *)); +#define EXPIRE_TIMEOUT (hz / 4) /* 4x / second */ +#define UPCALL_EXPIRE 6 /* number of timeouts */ + +#ifdef INET +#ifdef MROUTING +extern struct socket *ip_mrouter; +#endif +#endif + +/* + * 'Interfaces' associated with decapsulator (so we can tell + * packets that went through it from ones that get reflected + * by a broken gateway). These interfaces are never linked into + * the system ifnet list & no routes point to them. I.e., packets + * can't be sent this way. They only exist as a placeholder for + * multicast source verification. + */ +struct ifnet multicast_register_if; + +#define ENCAP_HOPS 64 + +/* + * Private variables. + */ +static mifi_t nummifs = 0; +static mifi_t reg_mif_num = (mifi_t)-1; + +static struct pim6stat pim6stat; +static int pim6; + +/* + * Hash function for a source, group entry + */ +#define MF6CHASH(a, g) MF6CHASHMOD((a).s6_addr32[0] ^ (a).s6_addr32[1] ^ \ + (a).s6_addr32[2] ^ (a).s6_addr32[3] ^ \ + (g).s6_addr32[0] ^ (g).s6_addr32[1] ^ \ + (g).s6_addr32[2] ^ (g).s6_addr32[3]) + +/* + * Find a route for a given origin IPv6 address and Multicast group address. + * Quality of service parameter to be added in the future!!! + */ + +#define MF6CFIND(o, g, rt) do { \ + struct mf6c *_rt = mf6ctable[MF6CHASH(o,g)]; \ + rt = NULL; \ + mrt6stat.mrt6s_mfc_lookups++; \ + while (_rt) { \ + if (IN6_ARE_ADDR_EQUAL(&_rt->mf6c_origin.sin6_addr, &(o)) && \ + IN6_ARE_ADDR_EQUAL(&_rt->mf6c_mcastgrp.sin6_addr, &(g)) && \ + (_rt->mf6c_stall == NULL)) { \ + rt = _rt; \ + break; \ + } \ + _rt = _rt->mf6c_next; \ + } \ + if (rt == NULL) { \ + mrt6stat.mrt6s_mfc_misses++; \ + } \ +} while (0) + +/* + * Macros to compute elapsed time efficiently + * Borrowed from Van Jacobson's scheduling code + */ +#define TV_DELTA(a, b, delta) do { \ + int xxs; \ + \ + delta = (a).tv_usec - (b).tv_usec; \ + if ((xxs = (a).tv_sec - (b).tv_sec)) { \ + switch (xxs) { \ + case 2: \ + delta += 1000000; \ + /* fall through */ \ + case 1: \ + delta += 1000000; \ + break; \ + default: \ + delta += (1000000 * xxs); \ + } \ + } \ +} while (0) + +#define TV_LT(a, b) (((a).tv_usec < (b).tv_usec && \ + (a).tv_sec <= (b).tv_sec) || (a).tv_sec < (b).tv_sec) + +#ifdef UPCALL_TIMING +#define UPCALL_MAX 50 +u_long upcall_data[UPCALL_MAX + 1]; +static void collate(); +#endif /* UPCALL_TIMING */ + +static int get_sg_cnt __P((struct sioc_sg_req6 *)); +static int get_mif6_cnt __P((struct sioc_mif_req6 *)); +static int ip6_mrouter_init __P((struct socket *, struct mbuf *, int)); +static int add_m6if __P((struct mif6ctl *)); +static int del_m6if __P((mifi_t *)); +static int add_m6fc __P((struct mf6cctl *)); +static int del_m6fc __P((struct mf6cctl *)); + +static struct callout expire_upcalls_ch; + +/* + * Handle MRT setsockopt commands to modify the multicast routing tables. + */ +int +ip6_mrouter_set(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + int error = 0; + struct mbuf *m; + + if (so != ip6_mrouter && sopt->sopt_name != MRT6_INIT) + return (EACCES); + + if ((error = soopt_getm(sopt, &m)) != 0) /* XXX */ + return (error); + if ((error = soopt_mcopyin(sopt, m)) != 0) /* XXX */ + return (error); + + switch (sopt->sopt_name) { + case MRT6_INIT: +#ifdef MRT6_OINIT + case MRT6_OINIT: +#endif + error = ip6_mrouter_init(so, m, sopt->sopt_name); + break; + case MRT6_DONE: + error = ip6_mrouter_done(); + break; + case MRT6_ADD_MIF: + error = add_m6if(mtod(m, struct mif6ctl *)); + break; + case MRT6_DEL_MIF: + error = del_m6if(mtod(m, mifi_t *)); + break; + case MRT6_ADD_MFC: + error = add_m6fc(mtod(m, struct mf6cctl *)); + break; + case MRT6_DEL_MFC: + error = del_m6fc(mtod(m, struct mf6cctl *)); + break; + case MRT6_PIM: + error = set_pim6(mtod(m, int *)); + break; + default: + error = EOPNOTSUPP; + break; + } + + (void)m_freem(m); + return(error); +} + +/* + * Handle MRT getsockopt commands + */ +int +ip6_mrouter_get(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + int error = 0; + + if (so != ip6_mrouter) return EACCES; + + switch (sopt->sopt_name) { + case MRT6_PIM: + error = sooptcopyout(sopt, &pim6, sizeof(pim6)); + break; + } + return (error); +} + +/* + * Handle ioctl commands to obtain information from the cache + */ +int +mrt6_ioctl(cmd, data) + int cmd; + caddr_t data; +{ + int error = 0; + + switch (cmd) { + case SIOCGETSGCNT_IN6: + return(get_sg_cnt((struct sioc_sg_req6 *)data)); + break; /* for safety */ + case SIOCGETMIFCNT_IN6: + return(get_mif6_cnt((struct sioc_mif_req6 *)data)); + break; /* for safety */ + default: + return (EINVAL); + break; + } + return error; +} + +/* + * returns the packet, byte, rpf-failure count for the source group provided + */ +static int +get_sg_cnt(req) + struct sioc_sg_req6 *req; +{ + struct mf6c *rt; + int s; + + s = splnet(); + MF6CFIND(req->src.sin6_addr, req->grp.sin6_addr, rt); + splx(s); + if (rt != NULL) { + req->pktcnt = rt->mf6c_pkt_cnt; + req->bytecnt = rt->mf6c_byte_cnt; + req->wrong_if = rt->mf6c_wrong_if; + } else + return(ESRCH); +#if 0 + req->pktcnt = req->bytecnt = req->wrong_if = 0xffffffff; +#endif + + return 0; +} + +/* + * returns the input and output packet and byte counts on the mif provided + */ +static int +get_mif6_cnt(req) + struct sioc_mif_req6 *req; +{ + mifi_t mifi = req->mifi; + + if (mifi >= nummifs) + return EINVAL; + + req->icount = mif6table[mifi].m6_pkt_in; + req->ocount = mif6table[mifi].m6_pkt_out; + req->ibytes = mif6table[mifi].m6_bytes_in; + req->obytes = mif6table[mifi].m6_bytes_out; + + return 0; +} + +static int +set_pim6(i) + int *i; +{ + if ((*i != 1) && (*i != 0)) + return EINVAL; + + pim6 = *i; + + return 0; +} + +/* + * Enable multicast routing + */ +static int +ip6_mrouter_init(so, m, cmd) + struct socket *so; + struct mbuf *m; + int cmd; +{ + int *v; + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, + "ip6_mrouter_init: so_type = %d, pr_protocol = %d\n", + so->so_type, so->so_proto->pr_protocol); +#endif + + if (so->so_type != SOCK_RAW || + so->so_proto->pr_protocol != IPPROTO_ICMPV6) + return EOPNOTSUPP; + + if (!m || (m->m_len != sizeof(int *))) + return ENOPROTOOPT; + + v = mtod(m, int *); + if (*v != 1) + return ENOPROTOOPT; + + if (ip6_mrouter != NULL) return EADDRINUSE; + + ip6_mrouter = so; + ip6_mrouter_ver = cmd; + + bzero((caddr_t)mf6ctable, sizeof(mf6ctable)); + bzero((caddr_t)n6expire, sizeof(n6expire)); + + pim6 = 0;/* used for stubbing out/in pim stuff */ + + callout_reset(&expire_upcalls_ch, EXPIRE_TIMEOUT, + expire_upcalls, NULL); + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, "ip6_mrouter_init\n"); +#endif + + return 0; +} + +/* + * Disable multicast routing + */ +int +ip6_mrouter_done() +{ + mifi_t mifi; + int i; + struct ifnet *ifp; + struct in6_ifreq ifr; + struct mf6c *rt; + struct rtdetq *rte; + int s; + + s = splnet(); + + /* + * For each phyint in use, disable promiscuous reception of all IPv6 + * multicasts. + */ +#ifdef INET +#ifdef MROUTING + /* + * If there is still IPv4 multicast routing daemon, + * we remain interfaces to receive all muliticasted packets. + * XXX: there may be an interface in which the IPv4 multicast + * daemon is not interested... + */ + if (!ip_mrouter) +#endif +#endif + { + for (mifi = 0; mifi < nummifs; mifi++) { + if (mif6table[mifi].m6_ifp && + !(mif6table[mifi].m6_flags & MIFF_REGISTER)) { + ifr.ifr_addr.sin6_family = AF_INET6; + ifr.ifr_addr.sin6_addr= in6addr_any; + ifp = mif6table[mifi].m6_ifp; + (*ifp->if_ioctl)(ifp, SIOCDELMULTI, + (caddr_t)&ifr); + } + } + } +#ifdef notyet + bzero((caddr_t)qtable, sizeof(qtable)); + bzero((caddr_t)tbftable, sizeof(tbftable)); +#endif + bzero((caddr_t)mif6table, sizeof(mif6table)); + nummifs = 0; + + pim6 = 0; /* used to stub out/in pim specific code */ + + callout_stop(&expire_upcalls_ch); + + /* + * Free all multicast forwarding cache entries. + */ + for (i = 0; i < MF6CTBLSIZ; i++) { + rt = mf6ctable[i]; + while (rt) { + struct mf6c *frt; + + for (rte = rt->mf6c_stall; rte != NULL; ) { + struct rtdetq *n = rte->next; + + m_free(rte->m); + free(rte, M_MRTABLE); + rte = n; + } + frt = rt; + rt = rt->mf6c_next; + free(frt, M_MRTABLE); + } + } + + bzero((caddr_t)mf6ctable, sizeof(mf6ctable)); + + /* + * Reset de-encapsulation cache + */ + reg_mif_num = -1; + + ip6_mrouter = NULL; + ip6_mrouter_ver = 0; + + splx(s); + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, "ip6_mrouter_done\n"); +#endif + + return 0; +} + +static struct sockaddr_in6 sin6 = { sizeof(sin6), AF_INET6 }; + +/* + * Add a mif to the mif table + */ +static int +add_m6if(mifcp) + struct mif6ctl *mifcp; +{ + struct mif6 *mifp; + struct ifnet *ifp; + int error, s; +#ifdef notyet + struct tbf *m_tbf = tbftable + mifcp->mif6c_mifi; +#endif + + if (mifcp->mif6c_mifi >= MAXMIFS) + return EINVAL; + mifp = mif6table + mifcp->mif6c_mifi; + if (mifp->m6_ifp) + return EADDRINUSE; /* XXX: is it appropriate? */ + if (mifcp->mif6c_pifi == 0 || mifcp->mif6c_pifi > if_index) + return ENXIO; + ifp = ifnet_byindex(mifcp->mif6c_pifi); + + if (mifcp->mif6c_flags & MIFF_REGISTER) { + if (reg_mif_num == (mifi_t)-1) { + multicast_register_if.if_name = "register_mif"; + multicast_register_if.if_flags |= IFF_LOOPBACK; + multicast_register_if.if_index = mifcp->mif6c_mifi; + reg_mif_num = mifcp->mif6c_mifi; + } + + ifp = &multicast_register_if; + + } /* if REGISTER */ + else { + /* Make sure the interface supports multicast */ + if ((ifp->if_flags & IFF_MULTICAST) == 0) + return EOPNOTSUPP; + + s = splnet(); + error = if_allmulti(ifp, 1); + splx(s); + if (error) + return error; + } + + s = splnet(); + mifp->m6_flags = mifcp->mif6c_flags; + mifp->m6_ifp = ifp; +#ifdef notyet + /* scaling up here allows division by 1024 in critical code */ + mifp->m6_rate_limit = mifcp->mif6c_rate_limit * 1024 / 1000; +#endif + /* initialize per mif pkt counters */ + mifp->m6_pkt_in = 0; + mifp->m6_pkt_out = 0; + mifp->m6_bytes_in = 0; + mifp->m6_bytes_out = 0; + splx(s); + + /* Adjust nummifs up if the mifi is higher than nummifs */ + if (nummifs <= mifcp->mif6c_mifi) + nummifs = mifcp->mif6c_mifi + 1; + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, + "add_mif #%d, phyint %s%d\n", + mifcp->mif6c_mifi, + ifp->if_name, ifp->if_unit); +#endif + + return 0; +} + +/* + * Delete a mif from the mif table + */ +static int +del_m6if(mifip) + mifi_t *mifip; +{ + struct mif6 *mifp = mif6table + *mifip; + mifi_t mifi; + struct ifnet *ifp; + int s; + + if (*mifip >= nummifs) + return EINVAL; + if (mifp->m6_ifp == NULL) + return EINVAL; + + s = splnet(); + + if (!(mifp->m6_flags & MIFF_REGISTER)) { + /* + * XXX: what if there is yet IPv4 multicast daemon + * using the interface? + */ + ifp = mifp->m6_ifp; + + if_allmulti(ifp, 0); + } + +#ifdef notyet + bzero((caddr_t)qtable[*mifip], sizeof(qtable[*mifip])); + bzero((caddr_t)mifp->m6_tbf, sizeof(*(mifp->m6_tbf))); +#endif + bzero((caddr_t)mifp, sizeof (*mifp)); + + /* Adjust nummifs down */ + for (mifi = nummifs; mifi > 0; mifi--) + if (mif6table[mifi - 1].m6_ifp) + break; + nummifs = mifi; + + splx(s); + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, "del_m6if %d, nummifs %d\n", *mifip, nummifs); +#endif + + return 0; +} + +/* + * Add an mfc entry + */ +static int +add_m6fc(mfccp) + struct mf6cctl *mfccp; +{ + struct mf6c *rt; + u_long hash; + struct rtdetq *rte; + u_short nstl; + int s; + + MF6CFIND(mfccp->mf6cc_origin.sin6_addr, + mfccp->mf6cc_mcastgrp.sin6_addr, rt); + + /* If an entry already exists, just update the fields */ + if (rt) { +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_MFC) + log(LOG_DEBUG, + "add_m6fc no upcall h %d o %s g %s p %x\n", + ip6_sprintf(&mfccp->mf6cc_origin.sin6_addr), + ip6_sprintf(&mfccp->mf6cc_mcastgrp.sin6_addr), + mfccp->mf6cc_parent); +#endif + + s = splnet(); + rt->mf6c_parent = mfccp->mf6cc_parent; + rt->mf6c_ifset = mfccp->mf6cc_ifset; + splx(s); + return 0; + } + + /* + * Find the entry for which the upcall was made and update + */ + s = splnet(); + hash = MF6CHASH(mfccp->mf6cc_origin.sin6_addr, + mfccp->mf6cc_mcastgrp.sin6_addr); + for (rt = mf6ctable[hash], nstl = 0; rt; rt = rt->mf6c_next) { + if (IN6_ARE_ADDR_EQUAL(&rt->mf6c_origin.sin6_addr, + &mfccp->mf6cc_origin.sin6_addr) && + IN6_ARE_ADDR_EQUAL(&rt->mf6c_mcastgrp.sin6_addr, + &mfccp->mf6cc_mcastgrp.sin6_addr) && + (rt->mf6c_stall != NULL)) { + + if (nstl++) + log(LOG_ERR, + "add_m6fc: %s o %s g %s p %x dbx %p\n", + "multiple kernel entries", + ip6_sprintf(&mfccp->mf6cc_origin.sin6_addr), + ip6_sprintf(&mfccp->mf6cc_mcastgrp.sin6_addr), + mfccp->mf6cc_parent, rt->mf6c_stall); + +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_MFC) + log(LOG_DEBUG, + "add_m6fc o %s g %s p %x dbg %x\n", + ip6_sprintf(&mfccp->mf6cc_origin.sin6_addr), + ip6_sprintf(&mfccp->mf6cc_mcastgrp.sin6_addr), + mfccp->mf6cc_parent, rt->mf6c_stall); +#endif + + rt->mf6c_origin = mfccp->mf6cc_origin; + rt->mf6c_mcastgrp = mfccp->mf6cc_mcastgrp; + rt->mf6c_parent = mfccp->mf6cc_parent; + rt->mf6c_ifset = mfccp->mf6cc_ifset; + /* initialize pkt counters per src-grp */ + rt->mf6c_pkt_cnt = 0; + rt->mf6c_byte_cnt = 0; + rt->mf6c_wrong_if = 0; + + rt->mf6c_expire = 0; /* Don't clean this guy up */ + n6expire[hash]--; + + /* free packets Qed at the end of this entry */ + for (rte = rt->mf6c_stall; rte != NULL; ) { + struct rtdetq *n = rte->next; + ip6_mdq(rte->m, rte->ifp, rt); + m_freem(rte->m); +#ifdef UPCALL_TIMING + collate(&(rte->t)); +#endif /* UPCALL_TIMING */ + free(rte, M_MRTABLE); + rte = n; + } + rt->mf6c_stall = NULL; + } + } + + /* + * It is possible that an entry is being inserted without an upcall + */ + if (nstl == 0) { +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_MFC) + log(LOG_DEBUG,"add_mfc no upcall h %d o %s g %s p %x\n", + hash, + ip6_sprintf(&mfccp->mf6cc_origin.sin6_addr), + ip6_sprintf(&mfccp->mf6cc_mcastgrp.sin6_addr), + mfccp->mf6cc_parent); +#endif + + for (rt = mf6ctable[hash]; rt; rt = rt->mf6c_next) { + + if (IN6_ARE_ADDR_EQUAL(&rt->mf6c_origin.sin6_addr, + &mfccp->mf6cc_origin.sin6_addr)&& + IN6_ARE_ADDR_EQUAL(&rt->mf6c_mcastgrp.sin6_addr, + &mfccp->mf6cc_mcastgrp.sin6_addr)) { + + rt->mf6c_origin = mfccp->mf6cc_origin; + rt->mf6c_mcastgrp = mfccp->mf6cc_mcastgrp; + rt->mf6c_parent = mfccp->mf6cc_parent; + rt->mf6c_ifset = mfccp->mf6cc_ifset; + /* initialize pkt counters per src-grp */ + rt->mf6c_pkt_cnt = 0; + rt->mf6c_byte_cnt = 0; + rt->mf6c_wrong_if = 0; + + if (rt->mf6c_expire) + n6expire[hash]--; + rt->mf6c_expire = 0; + } + } + if (rt == NULL) { + /* no upcall, so make a new entry */ + rt = (struct mf6c *)malloc(sizeof(*rt), M_MRTABLE, + M_NOWAIT); + if (rt == NULL) { + splx(s); + return ENOBUFS; + } + + /* insert new entry at head of hash chain */ + rt->mf6c_origin = mfccp->mf6cc_origin; + rt->mf6c_mcastgrp = mfccp->mf6cc_mcastgrp; + rt->mf6c_parent = mfccp->mf6cc_parent; + rt->mf6c_ifset = mfccp->mf6cc_ifset; + /* initialize pkt counters per src-grp */ + rt->mf6c_pkt_cnt = 0; + rt->mf6c_byte_cnt = 0; + rt->mf6c_wrong_if = 0; + rt->mf6c_expire = 0; + rt->mf6c_stall = NULL; + + /* link into table */ + rt->mf6c_next = mf6ctable[hash]; + mf6ctable[hash] = rt; + } + } + splx(s); + return 0; +} + +#ifdef UPCALL_TIMING +/* + * collect delay statistics on the upcalls + */ +static void +collate(t) + struct timeval *t; +{ + u_long d; + struct timeval tp; + u_long delta; + + GET_TIME(tp); + + if (TV_LT(*t, tp)) + { + TV_DELTA(tp, *t, delta); + + d = delta >> 10; + if (d > UPCALL_MAX) + d = UPCALL_MAX; + + ++upcall_data[d]; + } +} +#endif /* UPCALL_TIMING */ + +/* + * Delete an mfc entry + */ +static int +del_m6fc(mfccp) + struct mf6cctl *mfccp; +{ + struct sockaddr_in6 origin; + struct sockaddr_in6 mcastgrp; + struct mf6c *rt; + struct mf6c **nptr; + u_long hash; + int s; + + origin = mfccp->mf6cc_origin; + mcastgrp = mfccp->mf6cc_mcastgrp; + hash = MF6CHASH(origin.sin6_addr, mcastgrp.sin6_addr); + +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_MFC) + log(LOG_DEBUG,"del_m6fc orig %s mcastgrp %s\n", + ip6_sprintf(&origin.sin6_addr), + ip6_sprintf(&mcastgrp.sin6_addr)); +#endif + + s = splnet(); + + nptr = &mf6ctable[hash]; + while ((rt = *nptr) != NULL) { + if (IN6_ARE_ADDR_EQUAL(&origin.sin6_addr, + &rt->mf6c_origin.sin6_addr) && + IN6_ARE_ADDR_EQUAL(&mcastgrp.sin6_addr, + &rt->mf6c_mcastgrp.sin6_addr) && + rt->mf6c_stall == NULL) + break; + + nptr = &rt->mf6c_next; + } + if (rt == NULL) { + splx(s); + return EADDRNOTAVAIL; + } + + *nptr = rt->mf6c_next; + free(rt, M_MRTABLE); + + splx(s); + + return 0; +} + +static int +socket_send(s, mm, src) + struct socket *s; + struct mbuf *mm; + struct sockaddr_in6 *src; +{ + if (s) { + if (sbappendaddr(&s->so_rcv, + (struct sockaddr *)src, + mm, (struct mbuf *)0) != 0) { + sorwakeup(s); + return 0; + } + } + m_freem(mm); + return -1; +} + +/* + * IPv6 multicast forwarding function. This function assumes that the packet + * pointed to by "ip6" has arrived on (or is about to be sent to) the interface + * pointed to by "ifp", and the packet is to be relayed to other networks + * that have members of the packet's destination IPv6 multicast group. + * + * The packet is returned unscathed to the caller, unless it is + * erroneous, in which case a non-zero return value tells the caller to + * discard it. + */ + +int +ip6_mforward(ip6, ifp, m) + struct ip6_hdr *ip6; + struct ifnet *ifp; + struct mbuf *m; +{ + struct mf6c *rt; + struct mif6 *mifp; + struct mbuf *mm; + int s; + mifi_t mifi; + +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_FORWARD) + log(LOG_DEBUG, "ip6_mforward: src %s, dst %s, ifindex %d\n", + ip6_sprintf(&ip6->ip6_src), ip6_sprintf(&ip6->ip6_dst), + ifp->if_index); +#endif + + /* + * Don't forward a packet with Hop limit of zero or one, + * or a packet destined to a local-only group. + */ + if (ip6->ip6_hlim <= 1 || IN6_IS_ADDR_MC_NODELOCAL(&ip6->ip6_dst) || + IN6_IS_ADDR_MC_LINKLOCAL(&ip6->ip6_dst)) + return 0; + ip6->ip6_hlim--; + + /* + * Source address check: do not forward packets with unspecified + * source. It was discussed in July 2000, on ipngwg mailing list. + * This is rather more serious than unicast cases, because some + * MLD packets can be sent with the unspecified source address + * (although such packets must normally set 1 to the hop limit field). + */ + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src)) { + ip6stat.ip6s_cantforward++; + if (ip6_log_time + ip6_log_interval < time_second) { + ip6_log_time = time_second; + log(LOG_DEBUG, + "cannot forward " + "from %s to %s nxt %d received on %s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + ip6->ip6_nxt, + if_name(m->m_pkthdr.rcvif)); + } + return 0; + } + + /* + * Determine forwarding mifs from the forwarding cache table + */ + s = splnet(); + MF6CFIND(ip6->ip6_src, ip6->ip6_dst, rt); + + /* Entry exists, so forward if necessary */ + if (rt) { + splx(s); + return (ip6_mdq(m, ifp, rt)); + } else { + /* + * If we don't have a route for packet's origin, + * Make a copy of the packet & + * send message to routing daemon + */ + + struct mbuf *mb0; + struct rtdetq *rte; + u_long hash; +/* int i, npkts;*/ +#ifdef UPCALL_TIMING + struct timeval tp; + + GET_TIME(tp); +#endif /* UPCALL_TIMING */ + + mrt6stat.mrt6s_no_route++; +#ifdef MRT6DEBUG + if (mrt6debug & (DEBUG_FORWARD | DEBUG_MFC)) + log(LOG_DEBUG, "ip6_mforward: no rte s %s g %s\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst)); +#endif + + /* + * Allocate mbufs early so that we don't do extra work if we + * are just going to fail anyway. + */ + rte = (struct rtdetq *)malloc(sizeof(*rte), M_MRTABLE, + M_NOWAIT); + if (rte == NULL) { + splx(s); + return ENOBUFS; + } + mb0 = m_copy(m, 0, M_COPYALL); + /* + * Pullup packet header if needed before storing it, + * as other references may modify it in the meantime. + */ + if (mb0 && + (M_HASCL(mb0) || mb0->m_len < sizeof(struct ip6_hdr))) + mb0 = m_pullup(mb0, sizeof(struct ip6_hdr)); + if (mb0 == NULL) { + free(rte, M_MRTABLE); + splx(s); + return ENOBUFS; + } + + /* is there an upcall waiting for this packet? */ + hash = MF6CHASH(ip6->ip6_src, ip6->ip6_dst); + for (rt = mf6ctable[hash]; rt; rt = rt->mf6c_next) { + if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, + &rt->mf6c_origin.sin6_addr) && + IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, + &rt->mf6c_mcastgrp.sin6_addr) && + (rt->mf6c_stall != NULL)) + break; + } + + if (rt == NULL) { + struct mrt6msg *im; +#ifdef MRT6_OINIT + struct omrt6msg *oim; +#endif + + /* no upcall, so make a new entry */ + rt = (struct mf6c *)malloc(sizeof(*rt), M_MRTABLE, + M_NOWAIT); + if (rt == NULL) { + free(rte, M_MRTABLE); + m_freem(mb0); + splx(s); + return ENOBUFS; + } + /* + * Make a copy of the header to send to the user + * level process + */ + mm = m_copy(mb0, 0, sizeof(struct ip6_hdr)); + + if (mm == NULL) { + free(rte, M_MRTABLE); + m_freem(mb0); + free(rt, M_MRTABLE); + splx(s); + return ENOBUFS; + } + + /* + * Send message to routing daemon + */ + sin6.sin6_addr = ip6->ip6_src; + + im = NULL; +#ifdef MRT6_OINIT + oim = NULL; +#endif + switch (ip6_mrouter_ver) { +#ifdef MRT6_OINIT + case MRT6_OINIT: + oim = mtod(mm, struct omrt6msg *); + oim->im6_msgtype = MRT6MSG_NOCACHE; + oim->im6_mbz = 0; + break; +#endif + case MRT6_INIT: + im = mtod(mm, struct mrt6msg *); + im->im6_msgtype = MRT6MSG_NOCACHE; + im->im6_mbz = 0; + break; + default: + free(rte, M_MRTABLE); + m_freem(mb0); + free(rt, M_MRTABLE); + splx(s); + return EINVAL; + } + +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_FORWARD) + log(LOG_DEBUG, + "getting the iif info in the kernel\n"); +#endif + + for (mifp = mif6table, mifi = 0; + mifi < nummifs && mifp->m6_ifp != ifp; + mifp++, mifi++) + ; + + switch (ip6_mrouter_ver) { +#ifdef MRT6_OINIT + case MRT6_OINIT: + oim->im6_mif = mifi; + break; +#endif + case MRT6_INIT: + im->im6_mif = mifi; + break; + } + + if (socket_send(ip6_mrouter, mm, &sin6) < 0) { + log(LOG_WARNING, "ip6_mforward: ip6_mrouter " + "socket queue full\n"); + mrt6stat.mrt6s_upq_sockfull++; + free(rte, M_MRTABLE); + m_freem(mb0); + free(rt, M_MRTABLE); + splx(s); + return ENOBUFS; + } + + mrt6stat.mrt6s_upcalls++; + + /* insert new entry at head of hash chain */ + bzero(rt, sizeof(*rt)); + rt->mf6c_origin.sin6_family = AF_INET6; + rt->mf6c_origin.sin6_len = sizeof(struct sockaddr_in6); + rt->mf6c_origin.sin6_addr = ip6->ip6_src; + rt->mf6c_mcastgrp.sin6_family = AF_INET6; + rt->mf6c_mcastgrp.sin6_len = sizeof(struct sockaddr_in6); + rt->mf6c_mcastgrp.sin6_addr = ip6->ip6_dst; + rt->mf6c_expire = UPCALL_EXPIRE; + n6expire[hash]++; + rt->mf6c_parent = MF6C_INCOMPLETE_PARENT; + + /* link into table */ + rt->mf6c_next = mf6ctable[hash]; + mf6ctable[hash] = rt; + /* Add this entry to the end of the queue */ + rt->mf6c_stall = rte; + } else { + /* determine if q has overflowed */ + struct rtdetq **p; + int npkts = 0; + + for (p = &rt->mf6c_stall; *p != NULL; p = &(*p)->next) + if (++npkts > MAX_UPQ6) { + mrt6stat.mrt6s_upq_ovflw++; + free(rte, M_MRTABLE); + m_freem(mb0); + splx(s); + return 0; + } + + /* Add this entry to the end of the queue */ + *p = rte; + } + + rte->next = NULL; + rte->m = mb0; + rte->ifp = ifp; +#ifdef UPCALL_TIMING + rte->t = tp; +#endif /* UPCALL_TIMING */ + + splx(s); + + return 0; + } +} + +/* + * Clean up cache entries if upcalls are not serviced + * Call from the Slow Timeout mechanism, every half second. + */ +static void +expire_upcalls(unused) + void *unused; +{ + struct rtdetq *rte; + struct mf6c *mfc, **nptr; + int i; + int s; + + s = splnet(); + for (i = 0; i < MF6CTBLSIZ; i++) { + if (n6expire[i] == 0) + continue; + nptr = &mf6ctable[i]; + while ((mfc = *nptr) != NULL) { + rte = mfc->mf6c_stall; + /* + * Skip real cache entries + * Make sure it wasn't marked to not expire (shouldn't happen) + * If it expires now + */ + if (rte != NULL && + mfc->mf6c_expire != 0 && + --mfc->mf6c_expire == 0) { +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_EXPIRE) + log(LOG_DEBUG, "expire_upcalls: expiring (%s %s)\n", + ip6_sprintf(&mfc->mf6c_origin.sin6_addr), + ip6_sprintf(&mfc->mf6c_mcastgrp.sin6_addr)); +#endif + /* + * drop all the packets + * free the mbuf with the pkt, if, timing info + */ + do { + struct rtdetq *n = rte->next; + m_freem(rte->m); + free(rte, M_MRTABLE); + rte = n; + } while (rte != NULL); + mrt6stat.mrt6s_cache_cleanups++; + n6expire[i]--; + + *nptr = mfc->mf6c_next; + free(mfc, M_MRTABLE); + } else { + nptr = &mfc->mf6c_next; + } + } + } + splx(s); + callout_reset(&expire_upcalls_ch, EXPIRE_TIMEOUT, + expire_upcalls, NULL); +} + +/* + * Packet forwarding routine once entry in the cache is made + */ +static int +ip6_mdq(m, ifp, rt) + struct mbuf *m; + struct ifnet *ifp; + struct mf6c *rt; +{ + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + mifi_t mifi, iif; + struct mif6 *mifp; + int plen = m->m_pkthdr.len; + +/* + * Macro to send packet on mif. Since RSVP packets don't get counted on + * input, they shouldn't get counted on output, so statistics keeping is + * separate. + */ + +#define MC6_SEND(ip6, mifp, m) do { \ + if ((mifp)->m6_flags & MIFF_REGISTER) \ + register_send((ip6), (mifp), (m)); \ + else \ + phyint_send((ip6), (mifp), (m)); \ +} while (0) + + /* + * Don't forward if it didn't arrive from the parent mif + * for its origin. + */ + mifi = rt->mf6c_parent; + if ((mifi >= nummifs) || (mif6table[mifi].m6_ifp != ifp)) { + /* came in the wrong interface */ +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_FORWARD) + log(LOG_DEBUG, + "wrong if: ifid %d mifi %d mififid %x\n", + ifp->if_index, mifi, + mif6table[mifi].m6_ifp->if_index); +#endif + mrt6stat.mrt6s_wrong_if++; + rt->mf6c_wrong_if++; + /* + * If we are doing PIM processing, and we are forwarding + * packets on this interface, send a message to the + * routing daemon. + */ + /* have to make sure this is a valid mif */ + if (mifi < nummifs && mif6table[mifi].m6_ifp) + if (pim6 && (m->m_flags & M_LOOP) == 0) { + /* + * Check the M_LOOP flag to avoid an + * unnecessary PIM assert. + * XXX: M_LOOP is an ad-hoc hack... + */ + static struct sockaddr_in6 sin6 = + { sizeof(sin6), AF_INET6 }; + + struct mbuf *mm; + struct mrt6msg *im; +#ifdef MRT6_OINIT + struct omrt6msg *oim; +#endif + + mm = m_copy(m, 0, sizeof(struct ip6_hdr)); + if (mm && + (M_HASCL(mm) || + mm->m_len < sizeof(struct ip6_hdr))) + mm = m_pullup(mm, sizeof(struct ip6_hdr)); + if (mm == NULL) + return ENOBUFS; + +#ifdef MRT6_OINIT + oim = NULL; +#endif + im = NULL; + switch (ip6_mrouter_ver) { +#ifdef MRT6_OINIT + case MRT6_OINIT: + oim = mtod(mm, struct omrt6msg *); + oim->im6_msgtype = MRT6MSG_WRONGMIF; + oim->im6_mbz = 0; + break; +#endif + case MRT6_INIT: + im = mtod(mm, struct mrt6msg *); + im->im6_msgtype = MRT6MSG_WRONGMIF; + im->im6_mbz = 0; + break; + default: + m_freem(mm); + return EINVAL; + } + + for (mifp = mif6table, iif = 0; + iif < nummifs && mifp && + mifp->m6_ifp != ifp; + mifp++, iif++) + ; + + switch (ip6_mrouter_ver) { +#ifdef MRT6_OINIT + case MRT6_OINIT: + oim->im6_mif = iif; + sin6.sin6_addr = oim->im6_src; + break; +#endif + case MRT6_INIT: + im->im6_mif = iif; + sin6.sin6_addr = im->im6_src; + break; + } + + mrt6stat.mrt6s_upcalls++; + + if (socket_send(ip6_mrouter, mm, &sin6) < 0) { +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_WARNING, "mdq, ip6_mrouter socket queue full\n"); +#endif + ++mrt6stat.mrt6s_upq_sockfull; + return ENOBUFS; + } /* if socket Q full */ + } /* if PIM */ + return 0; + } /* if wrong iif */ + + /* If I sourced this packet, it counts as output, else it was input. */ + if (m->m_pkthdr.rcvif == NULL) { + /* XXX: is rcvif really NULL when output?? */ + mif6table[mifi].m6_pkt_out++; + mif6table[mifi].m6_bytes_out += plen; + } else { + mif6table[mifi].m6_pkt_in++; + mif6table[mifi].m6_bytes_in += plen; + } + rt->mf6c_pkt_cnt++; + rt->mf6c_byte_cnt += plen; + + /* + * For each mif, forward a copy of the packet if there are group + * members downstream on the interface. + */ + for (mifp = mif6table, mifi = 0; mifi < nummifs; mifp++, mifi++) + if (IF_ISSET(mifi, &rt->mf6c_ifset)) { + /* + * check if the outgoing packet is going to break + * a scope boundary. + * XXX For packets through PIM register tunnel + * interface, we believe a routing daemon. + */ + if ((mif6table[rt->mf6c_parent].m6_flags & + MIFF_REGISTER) == 0 && + (mif6table[mifi].m6_flags & MIFF_REGISTER) == 0 && + (in6_addr2scopeid(ifp, &ip6->ip6_dst) != + in6_addr2scopeid(mif6table[mifi].m6_ifp, + &ip6->ip6_dst) || + in6_addr2scopeid(ifp, &ip6->ip6_src) != + in6_addr2scopeid(mif6table[mifi].m6_ifp, + &ip6->ip6_src))) { + ip6stat.ip6s_badscope++; + continue; + } + + mifp->m6_pkt_out++; + mifp->m6_bytes_out += plen; + MC6_SEND(ip6, mifp, m); + } + return 0; +} + +static void +phyint_send(ip6, mifp, m) + struct ip6_hdr *ip6; + struct mif6 *mifp; + struct mbuf *m; +{ + struct mbuf *mb_copy; + struct ifnet *ifp = mifp->m6_ifp; + int error = 0; + int s = splnet(); /* needs to protect static "ro" below. */ + static struct route_in6 ro; + struct in6_multi *in6m; + struct sockaddr_in6 *dst6; + + /* + * Make a new reference to the packet; make sure that + * the IPv6 header is actually copied, not just referenced, + * so that ip6_output() only scribbles on the copy. + */ + mb_copy = m_copy(m, 0, M_COPYALL); + if (mb_copy && + (M_HASCL(mb_copy) || mb_copy->m_len < sizeof(struct ip6_hdr))) + mb_copy = m_pullup(mb_copy, sizeof(struct ip6_hdr)); + if (mb_copy == NULL) { + splx(s); + return; + } + /* set MCAST flag to the outgoing packet */ + mb_copy->m_flags |= M_MCAST; + + /* + * If we sourced the packet, call ip6_output since we may devide + * the packet into fragments when the packet is too big for the + * outgoing interface. + * Otherwise, we can simply send the packet to the interface + * sending queue. + */ + if (m->m_pkthdr.rcvif == NULL) { + struct ip6_moptions im6o; + + im6o.im6o_multicast_ifp = ifp; + /* XXX: ip6_output will override ip6->ip6_hlim */ + im6o.im6o_multicast_hlim = ip6->ip6_hlim; + im6o.im6o_multicast_loop = 1; + error = ip6_output(mb_copy, NULL, &ro, + IPV6_FORWARDING, &im6o, NULL, NULL); + +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_XMIT) + log(LOG_DEBUG, "phyint_send on mif %d err %d\n", + mifp - mif6table, error); +#endif + splx(s); + return; + } + + /* + * If we belong to the destination multicast group + * on the outgoing interface, loop back a copy. + */ + dst6 = (struct sockaddr_in6 *)&ro.ro_dst; + IN6_LOOKUP_MULTI(ip6->ip6_dst, ifp, in6m); + if (in6m != NULL) { + dst6->sin6_len = sizeof(struct sockaddr_in6); + dst6->sin6_family = AF_INET6; + dst6->sin6_addr = ip6->ip6_dst; + ip6_mloopback(ifp, m, (struct sockaddr_in6 *)&ro.ro_dst); + } + /* + * Put the packet into the sending queue of the outgoing interface + * if it would fit in the MTU of the interface. + */ + if (mb_copy->m_pkthdr.len <= ifp->if_mtu || ifp->if_mtu < IPV6_MMTU) { + dst6->sin6_len = sizeof(struct sockaddr_in6); + dst6->sin6_family = AF_INET6; + dst6->sin6_addr = ip6->ip6_dst; + /* + * We just call if_output instead of nd6_output here, since + * we need no ND for a multicast forwarded packet...right? + */ + error = (*ifp->if_output)(ifp, mb_copy, + (struct sockaddr *)&ro.ro_dst, NULL); +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_XMIT) + log(LOG_DEBUG, "phyint_send on mif %d err %d\n", + mifp - mif6table, error); +#endif + } else { +#ifdef MULTICAST_PMTUD + icmp6_error(mb_copy, ICMP6_PACKET_TOO_BIG, 0, ifp->if_mtu); +#else +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_XMIT) + log(LOG_DEBUG, + "phyint_send: packet too big on %s o %s g %s" + " size %d(discarded)\n", + if_name(ifp), + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), + mb_copy->m_pkthdr.len); +#endif /* MRT6DEBUG */ + m_freem(mb_copy); /* simply discard the packet */ +#endif + } + + splx(s); +} + +static int +register_send(ip6, mif, m) + struct ip6_hdr *ip6; + struct mif6 *mif; + struct mbuf *m; +{ + struct mbuf *mm; + int i, len = m->m_pkthdr.len; + static struct sockaddr_in6 sin6 = { sizeof(sin6), AF_INET6 }; + struct mrt6msg *im6; + +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_DEBUG, "** IPv6 register_send **\n src %s dst %s\n", + ip6_sprintf(&ip6->ip6_src), ip6_sprintf(&ip6->ip6_dst)); +#endif + ++pim6stat.pim6s_snd_registers; + + /* Make a copy of the packet to send to the user level process */ + MGETHDR(mm, M_DONTWAIT, MT_HEADER); + if (mm == NULL) + return ENOBUFS; + mm->m_pkthdr.rcvif = NULL; + mm->m_data += max_linkhdr; + mm->m_len = sizeof(struct ip6_hdr); + + if ((mm->m_next = m_copy(m, 0, M_COPYALL)) == NULL) { + m_freem(mm); + return ENOBUFS; + } + i = MHLEN - M_LEADINGSPACE(mm); + if (i > len) + i = len; + mm = m_pullup(mm, i); + if (mm == NULL) + return ENOBUFS; +/* TODO: check it! */ + mm->m_pkthdr.len = len + sizeof(struct ip6_hdr); + + /* + * Send message to routing daemon + */ + sin6.sin6_addr = ip6->ip6_src; + + im6 = mtod(mm, struct mrt6msg *); + im6->im6_msgtype = MRT6MSG_WHOLEPKT; + im6->im6_mbz = 0; + + im6->im6_mif = mif - mif6table; + + /* iif info is not given for reg. encap.n */ + mrt6stat.mrt6s_upcalls++; + + if (socket_send(ip6_mrouter, mm, &sin6) < 0) { +#ifdef MRT6DEBUG + if (mrt6debug) + log(LOG_WARNING, + "register_send: ip6_mrouter socket queue full\n"); +#endif + ++mrt6stat.mrt6s_upq_sockfull; + return ENOBUFS; + } + return 0; +} + +/* + * PIM sparse mode hook + * Receives the pim control messages, and passes them up to the listening + * socket, using rip6_input. + * The only message processed is the REGISTER pim message; the pim header + * is stripped off, and the inner packet is passed to register_mforward. + */ +int +pim6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct pim *pim; /* pointer to a pim struct */ + struct ip6_hdr *ip6; + int pimlen; + struct mbuf *m = *mp; + int minlen; + int off = *offp; + + ++pim6stat.pim6s_rcv_total; + + ip6 = mtod(m, struct ip6_hdr *); + pimlen = m->m_pkthdr.len - *offp; + + /* + * Validate lengths + */ + if (pimlen < PIM_MINLEN) { + ++pim6stat.pim6s_rcv_tooshort; +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) + log(LOG_DEBUG,"pim6_input: PIM packet too short\n"); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + /* + * if the packet is at least as big as a REGISTER, go ahead + * and grab the PIM REGISTER header size, to avoid another + * possible m_pullup() later. + * + * PIM_MINLEN == pimhdr + u_int32 == 8 + * PIM6_REG_MINLEN == pimhdr + reghdr + eip6hdr == 4 + 4 + 40 + */ + minlen = (pimlen >= PIM6_REG_MINLEN) ? PIM6_REG_MINLEN : PIM_MINLEN; + + /* + * Make sure that the IP6 and PIM headers in contiguous memory, and + * possibly the PIM REGISTER header + */ +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, minlen, IPPROTO_DONE); + /* adjust pointer */ + ip6 = mtod(m, struct ip6_hdr *); + + /* adjust mbuf to point to the PIM header */ + pim = (struct pim *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(pim, struct pim *, m, off, minlen); + if (pim == NULL) { + pim6stat.pim6s_rcv_tooshort++; + return IPPROTO_DONE; + } +#endif + +#define PIM6_CHECKSUM +#ifdef PIM6_CHECKSUM + { + int cksumlen; + + /* + * Validate checksum. + * If PIM REGISTER, exclude the data packet + */ + if (pim->pim_type == PIM_REGISTER) + cksumlen = PIM_MINLEN; + else + cksumlen = pimlen; + + if (in6_cksum(m, IPPROTO_PIM, off, cksumlen)) { + ++pim6stat.pim6s_rcv_badsum; +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) + log(LOG_DEBUG, + "pim6_input: invalid checksum\n"); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + } +#endif /* PIM_CHECKSUM */ + + /* PIM version check */ + if (pim->pim_ver != PIM_VERSION) { + ++pim6stat.pim6s_rcv_badversion; +#ifdef MRT6DEBUG + log(LOG_ERR, + "pim6_input: incorrect version %d, expecting %d\n", + pim->pim_ver, PIM_VERSION); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + if (pim->pim_type == PIM_REGISTER) { + /* + * since this is a REGISTER, we'll make a copy of the register + * headers ip6+pim+u_int32_t+encap_ip6, to be passed up to the + * routing daemon. + */ + static struct sockaddr_in6 dst = { sizeof(dst), AF_INET6 }; + + struct mbuf *mcp; + struct ip6_hdr *eip6; + u_int32_t *reghdr; + int rc; + + ++pim6stat.pim6s_rcv_registers; + + if ((reg_mif_num >= nummifs) || (reg_mif_num == (mifi_t) -1)) { +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) + log(LOG_DEBUG, + "pim6_input: register mif not set: %d\n", + reg_mif_num); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + reghdr = (u_int32_t *)(pim + 1); + + if ((ntohl(*reghdr) & PIM_NULL_REGISTER)) + goto pim6_input_to_daemon; + + /* + * Validate length + */ + if (pimlen < PIM6_REG_MINLEN) { + ++pim6stat.pim6s_rcv_tooshort; + ++pim6stat.pim6s_rcv_badregisters; +#ifdef MRT6DEBUG + log(LOG_ERR, + "pim6_input: register packet size too " + "small %d from %s\n", + pimlen, ip6_sprintf(&ip6->ip6_src)); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + eip6 = (struct ip6_hdr *) (reghdr + 1); +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) + log(LOG_DEBUG, + "pim6_input[register], eip6: %s -> %s, " + "eip6 plen %d\n", + ip6_sprintf(&eip6->ip6_src), + ip6_sprintf(&eip6->ip6_dst), + ntohs(eip6->ip6_plen)); +#endif + + /* verify the version number of the inner packet */ + if ((eip6->ip6_vfc & IPV6_VERSION_MASK) != IPV6_VERSION) { + ++pim6stat.pim6s_rcv_badregisters; +#ifdef MRT6DEBUG + log(LOG_DEBUG, "pim6_input: invalid IP version (%d) " + "of the inner packet\n", + (eip6->ip6_vfc & IPV6_VERSION)); +#endif + m_freem(m); + return(IPPROTO_NONE); + } + + /* verify the inner packet is destined to a mcast group */ + if (!IN6_IS_ADDR_MULTICAST(&eip6->ip6_dst)) { + ++pim6stat.pim6s_rcv_badregisters; +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) + log(LOG_DEBUG, + "pim6_input: inner packet of register " + "is not multicast %s\n", + ip6_sprintf(&eip6->ip6_dst)); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + /* + * make a copy of the whole header to pass to the daemon later. + */ + mcp = m_copy(m, 0, off + PIM6_REG_MINLEN); + if (mcp == NULL) { +#ifdef MRT6DEBUG + log(LOG_ERR, + "pim6_input: pim register: " + "could not copy register head\n"); +#endif + m_freem(m); + return(IPPROTO_DONE); + } + + /* + * forward the inner ip6 packet; point m_data at the inner ip6. + */ + m_adj(m, off + PIM_MINLEN); +#ifdef MRT6DEBUG + if (mrt6debug & DEBUG_PIM) { + log(LOG_DEBUG, + "pim6_input: forwarding decapsulated register: " + "src %s, dst %s, mif %d\n", + ip6_sprintf(&eip6->ip6_src), + ip6_sprintf(&eip6->ip6_dst), + reg_mif_num); + } +#endif + + rc = if_simloop(mif6table[reg_mif_num].m6_ifp, m, + dst.sin6_family, 0); + + /* prepare the register head to send to the mrouting daemon */ + m = mcp; + } + + /* + * Pass the PIM message up to the daemon; if it is a register message + * pass the 'head' only up to the daemon. This includes the + * encapsulator ip6 header, pim header, register header and the + * encapsulated ip6 header. + */ + pim6_input_to_daemon: + rip6_input(&m, offp, proto); + return(IPPROTO_DONE); +} diff --git a/sys/netinet6/ip6_mroute.h b/sys/netinet6/ip6_mroute.h new file mode 100644 index 0000000..4c75298 --- /dev/null +++ b/sys/netinet6/ip6_mroute.h @@ -0,0 +1,277 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_mroute.h,v 1.19 2001/06/14 06:12:55 suz Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* BSDI ip_mroute.h,v 2.5 1996/10/11 16:01:48 pjd Exp */ + +/* + * Definitions for IP multicast forwarding. + * + * Written by David Waitzman, BBN Labs, August 1988. + * Modified by Steve Deering, Stanford, February 1989. + * Modified by Ajit Thyagarajan, PARC, August 1993. + * Modified by Ajit Thyagarajan, PARC, August 1994. + * Modified by Ahmed Helmy, USC, September 1996. + * + * MROUTING Revision: 1.2 + */ + +#ifndef _NETINET6_IP6_MROUTE_H_ +#define _NETINET6_IP6_MROUTE_H_ + +/* + * Multicast Routing set/getsockopt commands. + */ +#ifdef _KERNEL +#define MRT6_OINIT 100 /* initialize forwarder (omrt6msg) */ +#endif +#define MRT6_DONE 101 /* shut down forwarder */ +#define MRT6_ADD_MIF 102 /* add multicast interface */ +#define MRT6_DEL_MIF 103 /* delete multicast interface */ +#define MRT6_ADD_MFC 104 /* insert forwarding cache entry */ +#define MRT6_DEL_MFC 105 /* delete forwarding cache entry */ +#define MRT6_PIM 107 /* enable pim code */ +#define MRT6_INIT 108 /* initialize forwarder (mrt6msg) */ + +#if BSD >= 199103 +#define GET_TIME(t) microtime(&t) +#elif defined(sun) +#define GET_TIME(t) uniqtime(&t) +#else +#define GET_TIME(t) ((t) = time) +#endif + +/* + * Types and macros for handling bitmaps with one bit per multicast interface. + */ +typedef u_short mifi_t; /* type of a mif index */ +#define MAXMIFS 64 + +#ifndef IF_SETSIZE +#define IF_SETSIZE 256 +#endif + +typedef u_int32_t if_mask; +#define NIFBITS (sizeof(if_mask) * NBBY) /* bits per mask */ + +#ifndef howmany +#define howmany(x, y) (((x) + ((y) - 1)) / (y)) +#endif + +typedef struct if_set { + if_mask ifs_bits[howmany(IF_SETSIZE, NIFBITS)]; +} if_set; + +#define IF_SET(n, p) ((p)->ifs_bits[(n)/NIFBITS] |= (1 << ((n) % NIFBITS))) +#define IF_CLR(n, p) ((p)->ifs_bits[(n)/NIFBITS] &= ~(1 << ((n) % NIFBITS))) +#define IF_ISSET(n, p) ((p)->ifs_bits[(n)/NIFBITS] & (1 << ((n) % NIFBITS))) +#define IF_COPY(f, t) bcopy(f, t, sizeof(*(f))) +#define IF_ZERO(p) bzero(p, sizeof(*(p))) + +/* + * Argument structure for MRT6_ADD_IF. + */ +struct mif6ctl { + mifi_t mif6c_mifi; /* the index of the mif to be added */ + u_char mif6c_flags; /* MIFF_ flags defined below */ + u_short mif6c_pifi; /* the index of the physical IF */ +#ifdef notyet + u_int mif6c_rate_limit; /* max rate */ +#endif +}; + +#define MIFF_REGISTER 0x1 /* mif represents a register end-point */ + +/* + * Argument structure for MRT6_ADD_MFC and MRT6_DEL_MFC + */ +struct mf6cctl { + struct sockaddr_in6 mf6cc_origin; /* IPv6 origin of mcasts */ + struct sockaddr_in6 mf6cc_mcastgrp; /* multicast group associated */ + mifi_t mf6cc_parent; /* incoming ifindex */ + struct if_set mf6cc_ifset; /* set of forwarding ifs */ +}; + +/* + * The kernel's multicast routing statistics. + */ +struct mrt6stat { + u_quad_t mrt6s_mfc_lookups; /* # forw. cache hash table hits */ + u_quad_t mrt6s_mfc_misses; /* # forw. cache hash table misses */ + u_quad_t mrt6s_upcalls; /* # calls to mrouted */ + u_quad_t mrt6s_no_route; /* no route for packet's origin */ + u_quad_t mrt6s_bad_tunnel; /* malformed tunnel options */ + u_quad_t mrt6s_cant_tunnel; /* no room for tunnel options */ + u_quad_t mrt6s_wrong_if; /* arrived on wrong interface */ + u_quad_t mrt6s_upq_ovflw; /* upcall Q overflow */ + u_quad_t mrt6s_cache_cleanups; /* # entries with no upcalls */ + u_quad_t mrt6s_drop_sel; /* pkts dropped selectively */ + u_quad_t mrt6s_q_overflow; /* pkts dropped - Q overflow */ + u_quad_t mrt6s_pkt2large; /* pkts dropped - size > BKT SIZE */ + u_quad_t mrt6s_upq_sockfull; /* upcalls dropped - socket full */ +}; + +#ifdef MRT6_OINIT +/* + * Struct used to communicate from kernel to multicast router + * note the convenient similarity to an IPv6 header. + * XXX old version, superseded by mrt6msg. + */ +struct omrt6msg { + u_long unused1; + u_char im6_msgtype; /* what type of message */ +#if 0 +#define MRT6MSG_NOCACHE 1 +#define MRT6MSG_WRONGMIF 2 +#define MRT6MSG_WHOLEPKT 3 /* used for user level encap*/ +#endif + u_char im6_mbz; /* must be zero */ + u_char im6_mif; /* mif rec'd on */ + u_char unused2; + struct in6_addr im6_src, im6_dst; +}; +#endif + +/* + * Structure used to communicate from kernel to multicast router. + * We'll overlay the structure onto an MLD header (not an IPv6 header + * like igmpmsg{} used for IPv4 implementation). This is because this + * structure will be passed via an IPv6 raw socket, on which an application + * will only receive the payload i.e. the data after the IPv6 header and all + * the extension headers. (see Section 3 of draft-ietf-ipngwg-2292bis-01) + */ +struct mrt6msg { +#define MRT6MSG_NOCACHE 1 +#define MRT6MSG_WRONGMIF 2 +#define MRT6MSG_WHOLEPKT 3 /* used for user level encap*/ + u_char im6_mbz; /* must be zero */ + u_char im6_msgtype; /* what type of message */ + u_int16_t im6_mif; /* mif rec'd on */ + u_int32_t im6_pad; /* padding for 64bit arch */ + struct in6_addr im6_src, im6_dst; +}; + +/* + * Argument structure used by multicast routing daemon to get src-grp + * packet counts + */ +struct sioc_sg_req6 { + struct sockaddr_in6 src; + struct sockaddr_in6 grp; + u_quad_t pktcnt; + u_quad_t bytecnt; + u_quad_t wrong_if; +}; + +/* + * Argument structure used by mrouted to get mif pkt counts + */ +struct sioc_mif_req6 { + mifi_t mifi; /* mif number */ + u_quad_t icount; /* Input packet count on mif */ + u_quad_t ocount; /* Output packet count on mif */ + u_quad_t ibytes; /* Input byte count on mif */ + u_quad_t obytes; /* Output byte count on mif */ +}; + +#if defined(_KERNEL) || defined(KERNEL) +/* + * The kernel's multicast-interface structure. + */ +struct mif6 { + u_char m6_flags; /* MIFF_ flags defined above */ + u_int m6_rate_limit; /* max rate */ +#ifdef notyet + struct tbf *m6_tbf; /* token bucket structure at intf. */ +#endif + struct in6_addr m6_lcl_addr; /* local interface address */ + struct ifnet *m6_ifp; /* pointer to interface */ + u_quad_t m6_pkt_in; /* # pkts in on interface */ + u_quad_t m6_pkt_out; /* # pkts out on interface */ + u_quad_t m6_bytes_in; /* # bytes in on interface */ + u_quad_t m6_bytes_out; /* # bytes out on interface */ + struct route_in6 m6_route;/* cached route if this is a tunnel */ +#ifdef notyet + u_int m6_rsvp_on; /* RSVP listening on this vif */ + struct socket *m6_rsvpd; /* RSVP daemon socket */ +#endif +}; + +/* + * The kernel's multicast forwarding cache entry structure + */ +struct mf6c { + struct sockaddr_in6 mf6c_origin; /* IPv6 origin of mcasts */ + struct sockaddr_in6 mf6c_mcastgrp; /* multicast group associated*/ + mifi_t mf6c_parent; /* incoming IF */ + struct if_set mf6c_ifset; /* set of outgoing IFs */ + + u_quad_t mf6c_pkt_cnt; /* pkt count for src-grp */ + u_quad_t mf6c_byte_cnt; /* byte count for src-grp */ + u_quad_t mf6c_wrong_if; /* wrong if for src-grp */ + int mf6c_expire; /* time to clean entry up */ + struct timeval mf6c_last_assert; /* last time I sent an assert*/ + struct rtdetq *mf6c_stall; /* pkts waiting for route */ + struct mf6c *mf6c_next; /* hash table linkage */ +}; + +#define MF6C_INCOMPLETE_PARENT ((mifi_t)-1) + +/* + * Argument structure used for pkt info. while upcall is made + */ +#ifndef _NETINET_IP_MROUTE_H_ +struct rtdetq { /* XXX: rtdetq is also defined in ip_mroute.h */ + struct mbuf *m; /* A copy of the packet */ + struct ifnet *ifp; /* Interface pkt came in on */ +#ifdef UPCALL_TIMING + struct timeval t; /* Timestamp */ +#endif /* UPCALL_TIMING */ + struct rtdetq *next; +}; +#endif /* _NETINET_IP_MROUTE_H_ */ + +#define MF6CTBLSIZ 256 +#if (MF6CTBLSIZ & (MF6CTBLSIZ - 1)) == 0 /* from sys:route.h */ +#define MF6CHASHMOD(h) ((h) & (MF6CTBLSIZ - 1)) +#else +#define MF6CHASHMOD(h) ((h) % MF6CTBLSIZ) +#endif + +#define MAX_UPQ6 4 /* max. no of pkts in upcall Q */ + +int ip6_mrouter_set __P((struct socket *so, struct sockopt *sopt)); +int ip6_mrouter_get __P((struct socket *so, struct sockopt *sopt)); +int ip6_mrouter_done __P((void)); +int mrt6_ioctl __P((int, caddr_t)); +#endif /* _KERNEL */ + +#endif /* !_NETINET6_IP6_MROUTE_H_ */ diff --git a/sys/netinet6/ip6_output.c b/sys/netinet6/ip6_output.c new file mode 100644 index 0000000..b18f9a7 --- /dev/null +++ b/sys/netinet6/ip6_output.c @@ -0,0 +1,2614 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_output.c,v 1.279 2002/01/26 06:12:30 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1988, 1990, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)ip_output.c 8.3 (Berkeley) 1/21/94 + */ + +#include "opt_ip6fw.h" +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" +#include "opt_pfil_hooks.h" + +#include <sys/param.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/errno.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/systm.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> +#ifdef PFIL_HOOKS +#include <net/pfil.h> +#endif + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> +#include <netinet6/ip6_var.h> +#include <netinet/in_pcb.h> +#include <netinet6/nd6.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netkey/key.h> +#endif /* IPSEC */ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#include <netipsec/key.h> +#endif /* FAST_IPSEC */ + +#include <netinet6/ip6_fw.h> + +#include <net/net_osdep.h> + +#include <netinet6/ip6protosw.h> + +static MALLOC_DEFINE(M_IPMOPTS, "ip6_moptions", "internet multicast options"); + +struct ip6_exthdrs { + struct mbuf *ip6e_ip6; + struct mbuf *ip6e_hbh; + struct mbuf *ip6e_dest1; + struct mbuf *ip6e_rthdr; + struct mbuf *ip6e_dest2; +}; + +static int ip6_pcbopts __P((struct ip6_pktopts **, struct mbuf *, + struct socket *, struct sockopt *sopt)); +static int ip6_setmoptions __P((int, struct ip6_moptions **, struct mbuf *)); +static int ip6_getmoptions __P((int, struct ip6_moptions *, struct mbuf **)); +static int ip6_copyexthdr __P((struct mbuf **, caddr_t, int)); +static int ip6_insertfraghdr __P((struct mbuf *, struct mbuf *, int, + struct ip6_frag **)); +static int ip6_insert_jumboopt __P((struct ip6_exthdrs *, u_int32_t)); +static int ip6_splithdr __P((struct mbuf *, struct ip6_exthdrs *)); + +/* + * IP6 output. The packet in mbuf chain m contains a skeletal IP6 + * header (with pri, len, nxt, hlim, src, dst). + * This function may modify ver and hlim only. + * The mbuf chain containing the packet will be freed. + * The mbuf opt, if present, will not be freed. + * + * type of "mtu": rt_rmx.rmx_mtu is u_long, ifnet.ifr_mtu is int, and + * nd_ifinfo.linkmtu is u_int32_t. so we use u_long to hold largest one, + * which is rt_rmx.rmx_mtu. + */ +int +ip6_output(m0, opt, ro, flags, im6o, ifpp, inp) + struct mbuf *m0; + struct ip6_pktopts *opt; + struct route_in6 *ro; + int flags; + struct ip6_moptions *im6o; + struct ifnet **ifpp; /* XXX: just for statistics */ + struct inpcb *inp; +{ + struct ip6_hdr *ip6, *mhip6; + struct ifnet *ifp, *origifp; + struct mbuf *m = m0; + int hlen, tlen, len, off; + struct route_in6 ip6route; + struct sockaddr_in6 *dst; + int error = 0; + struct in6_ifaddr *ia = NULL; + u_long mtu; + u_int32_t optlen = 0, plen = 0, unfragpartlen = 0; + struct ip6_exthdrs exthdrs; + struct in6_addr finaldst; + struct route_in6 *ro_pmtu = NULL; + int hdrsplit = 0; + int needipsec = 0; +#ifdef PFIL_HOOKS + struct packet_filter_hook *pfh; + struct mbuf *m1; + int rv; +#endif /* PFIL_HOOKS */ +#ifdef IPSEC + int needipsectun = 0; + struct secpolicy *sp = NULL; + + ip6 = mtod(m, struct ip6_hdr *); +#endif /* IPSEC */ +#ifdef FAST_IPSEC + int needipsectun = 0; + struct secpolicy *sp = NULL; + + ip6 = mtod(m, struct ip6_hdr *); +#endif /* FAST_IPSEC */ + +#define MAKE_EXTHDR(hp, mp) \ + do { \ + if (hp) { \ + struct ip6_ext *eh = (struct ip6_ext *)(hp); \ + error = ip6_copyexthdr((mp), (caddr_t)(hp), \ + ((eh)->ip6e_len + 1) << 3); \ + if (error) \ + goto freehdrs; \ + } \ + } while (0) + + bzero(&exthdrs, sizeof(exthdrs)); + + if (opt) { + /* Hop-by-Hop options header */ + MAKE_EXTHDR(opt->ip6po_hbh, &exthdrs.ip6e_hbh); + /* Destination options header(1st part) */ + MAKE_EXTHDR(opt->ip6po_dest1, &exthdrs.ip6e_dest1); + /* Routing header */ + MAKE_EXTHDR(opt->ip6po_rthdr, &exthdrs.ip6e_rthdr); + /* Destination options header(2nd part) */ + MAKE_EXTHDR(opt->ip6po_dest2, &exthdrs.ip6e_dest2); + } + +#ifdef IPSEC + /* get a security policy for this packet */ + if (inp == NULL) + sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, 0, &error); + else + sp = ipsec6_getpolicybypcb(m, IPSEC_DIR_OUTBOUND, inp, &error); + + if (sp == NULL) { + ipsec6stat.out_inval++; + goto freehdrs; + } + + error = 0; + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + /* + * This packet is just discarded. + */ + ipsec6stat.out_polvio++; + goto freehdrs; + + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + /* no need to do IPsec. */ + needipsec = 0; + break; + + case IPSEC_POLICY_IPSEC: + if (sp->req == NULL) { + /* acquire a policy */ + error = key_spdacquire(sp); + goto freehdrs; + } + needipsec = 1; + break; + + case IPSEC_POLICY_ENTRUST: + default: + printf("ip6_output: Invalid policy found. %d\n", sp->policy); + } +#endif /* IPSEC */ +#ifdef FAST_IPSEC + /* get a security policy for this packet */ + if (inp == NULL) + sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_OUTBOUND, 0, &error); + else + sp = ipsec_getpolicybysock(m, IPSEC_DIR_OUTBOUND, inp, &error); + + if (sp == NULL) { + newipsecstat.ips_out_inval++; + goto freehdrs; + } + + error = 0; + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + /* + * This packet is just discarded. + */ + newipsecstat.ips_out_polvio++; + goto freehdrs; + + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + /* no need to do IPsec. */ + needipsec = 0; + break; + + case IPSEC_POLICY_IPSEC: + if (sp->req == NULL) { + /* acquire a policy */ + error = key_spdacquire(sp); + goto freehdrs; + } + needipsec = 1; + break; + + case IPSEC_POLICY_ENTRUST: + default: + printf("ip6_output: Invalid policy found. %d\n", sp->policy); + } +#endif /* FAST_IPSEC */ + + /* + * Calculate the total length of the extension header chain. + * Keep the length of the unfragmentable part for fragmentation. + */ + optlen = 0; + if (exthdrs.ip6e_hbh) optlen += exthdrs.ip6e_hbh->m_len; + if (exthdrs.ip6e_dest1) optlen += exthdrs.ip6e_dest1->m_len; + if (exthdrs.ip6e_rthdr) optlen += exthdrs.ip6e_rthdr->m_len; + unfragpartlen = optlen + sizeof(struct ip6_hdr); + /* NOTE: we don't add AH/ESP length here. do that later. */ + if (exthdrs.ip6e_dest2) optlen += exthdrs.ip6e_dest2->m_len; + + /* + * If we need IPsec, or there is at least one extension header, + * separate IP6 header from the payload. + */ + if ((needipsec || optlen) && !hdrsplit) { + if ((error = ip6_splithdr(m, &exthdrs)) != 0) { + m = NULL; + goto freehdrs; + } + m = exthdrs.ip6e_ip6; + hdrsplit++; + } + + /* adjust pointer */ + ip6 = mtod(m, struct ip6_hdr *); + + /* adjust mbuf packet header length */ + m->m_pkthdr.len += optlen; + plen = m->m_pkthdr.len - sizeof(*ip6); + + /* If this is a jumbo payload, insert a jumbo payload option. */ + if (plen > IPV6_MAXPACKET) { + if (!hdrsplit) { + if ((error = ip6_splithdr(m, &exthdrs)) != 0) { + m = NULL; + goto freehdrs; + } + m = exthdrs.ip6e_ip6; + hdrsplit++; + } + /* adjust pointer */ + ip6 = mtod(m, struct ip6_hdr *); + if ((error = ip6_insert_jumboopt(&exthdrs, plen)) != 0) + goto freehdrs; + ip6->ip6_plen = 0; + } else + ip6->ip6_plen = htons(plen); + + /* + * Concatenate headers and fill in next header fields. + * Here we have, on "m" + * IPv6 payload + * and we insert headers accordingly. Finally, we should be getting: + * IPv6 hbh dest1 rthdr ah* [esp* dest2 payload] + * + * during the header composing process, "m" points to IPv6 header. + * "mprev" points to an extension header prior to esp. + */ + { + u_char *nexthdrp = &ip6->ip6_nxt; + struct mbuf *mprev = m; + + /* + * we treat dest2 specially. this makes IPsec processing + * much easier. the goal here is to make mprev point the + * mbuf prior to dest2. + * + * result: IPv6 dest2 payload + * m and mprev will point to IPv6 header. + */ + if (exthdrs.ip6e_dest2) { + if (!hdrsplit) + panic("assumption failed: hdr not split"); + exthdrs.ip6e_dest2->m_next = m->m_next; + m->m_next = exthdrs.ip6e_dest2; + *mtod(exthdrs.ip6e_dest2, u_char *) = ip6->ip6_nxt; + ip6->ip6_nxt = IPPROTO_DSTOPTS; + } + +#define MAKE_CHAIN(m, mp, p, i)\ + do {\ + if (m) {\ + if (!hdrsplit) \ + panic("assumption failed: hdr not split"); \ + *mtod((m), u_char *) = *(p);\ + *(p) = (i);\ + p = mtod((m), u_char *);\ + (m)->m_next = (mp)->m_next;\ + (mp)->m_next = (m);\ + (mp) = (m);\ + }\ + } while (0) + /* + * result: IPv6 hbh dest1 rthdr dest2 payload + * m will point to IPv6 header. mprev will point to the + * extension header prior to dest2 (rthdr in the above case). + */ + MAKE_CHAIN(exthdrs.ip6e_hbh, mprev, + nexthdrp, IPPROTO_HOPOPTS); + MAKE_CHAIN(exthdrs.ip6e_dest1, mprev, + nexthdrp, IPPROTO_DSTOPTS); + MAKE_CHAIN(exthdrs.ip6e_rthdr, mprev, + nexthdrp, IPPROTO_ROUTING); + +#if defined(IPSEC) || defined(FAST_IPSEC) + if (!needipsec) + goto skip_ipsec2; + + /* + * pointers after IPsec headers are not valid any more. + * other pointers need a great care too. + * (IPsec routines should not mangle mbufs prior to AH/ESP) + */ + exthdrs.ip6e_dest2 = NULL; + + { + struct ip6_rthdr *rh = NULL; + int segleft_org = 0; + struct ipsec_output_state state; + + if (exthdrs.ip6e_rthdr) { + rh = mtod(exthdrs.ip6e_rthdr, struct ip6_rthdr *); + segleft_org = rh->ip6r_segleft; + rh->ip6r_segleft = 0; + } + + bzero(&state, sizeof(state)); + state.m = m; + error = ipsec6_output_trans(&state, nexthdrp, mprev, sp, flags, + &needipsectun); + m = state.m; + if (error) { + /* mbuf is already reclaimed in ipsec6_output_trans. */ + m = NULL; + switch (error) { + case EHOSTUNREACH: + case ENETUNREACH: + case EMSGSIZE: + case ENOBUFS: + case ENOMEM: + break; + default: + printf("ip6_output (ipsec): error code %d\n", error); + /* fall through */ + case ENOENT: + /* don't show these error codes to the user */ + error = 0; + break; + } + goto bad; + } + if (exthdrs.ip6e_rthdr) { + /* ah6_output doesn't modify mbuf chain */ + rh->ip6r_segleft = segleft_org; + } + } +skip_ipsec2:; +#endif + } + + /* + * If there is a routing header, replace destination address field + * with the first hop of the routing header. + */ + if (exthdrs.ip6e_rthdr) { + struct ip6_rthdr *rh = + (struct ip6_rthdr *)(mtod(exthdrs.ip6e_rthdr, + struct ip6_rthdr *)); + struct ip6_rthdr0 *rh0; + + finaldst = ip6->ip6_dst; + switch (rh->ip6r_type) { + case IPV6_RTHDR_TYPE_0: + rh0 = (struct ip6_rthdr0 *)rh; + ip6->ip6_dst = rh0->ip6r0_addr[0]; + bcopy((caddr_t)&rh0->ip6r0_addr[1], + (caddr_t)&rh0->ip6r0_addr[0], + sizeof(struct in6_addr)*(rh0->ip6r0_segleft - 1) + ); + rh0->ip6r0_addr[rh0->ip6r0_segleft - 1] = finaldst; + break; + default: /* is it possible? */ + error = EINVAL; + goto bad; + } + } + + /* Source address validation */ + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src) && + (flags & IPV6_DADOUTPUT) == 0) { + error = EOPNOTSUPP; + ip6stat.ip6s_badscope++; + goto bad; + } + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src)) { + error = EOPNOTSUPP; + ip6stat.ip6s_badscope++; + goto bad; + } + + ip6stat.ip6s_localout++; + + /* + * Route packet. + */ + if (ro == 0) { + ro = &ip6route; + bzero((caddr_t)ro, sizeof(*ro)); + } + ro_pmtu = ro; + if (opt && opt->ip6po_rthdr) + ro = &opt->ip6po_route; + dst = (struct sockaddr_in6 *)&ro->ro_dst; + /* + * If there is a cached route, + * check that it is to the same destination + * and is still up. If not, free it and try again. + */ + if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 || + dst->sin6_family != AF_INET6 || + !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &ip6->ip6_dst))) { + RTFREE(ro->ro_rt); + ro->ro_rt = (struct rtentry *)0; + } + if (ro->ro_rt == 0) { + bzero(dst, sizeof(*dst)); + dst->sin6_family = AF_INET6; + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_addr = ip6->ip6_dst; +#ifdef SCOPEDROUTING + /* XXX: sin6_scope_id should already be fixed at this point */ + if (IN6_IS_SCOPE_LINKLOCAL(&dst->sin6_addr)) + dst->sin6_scope_id = ntohs(dst->sin6_addr.s6_addr16[1]); +#endif + } +#if defined(IPSEC) || defined(FAST_IPSEC) + if (needipsec && needipsectun) { + struct ipsec_output_state state; + + /* + * All the extension headers will become inaccessible + * (since they can be encrypted). + * Don't panic, we need no more updates to extension headers + * on inner IPv6 packet (since they are now encapsulated). + * + * IPv6 [ESP|AH] IPv6 [extension headers] payload + */ + bzero(&exthdrs, sizeof(exthdrs)); + exthdrs.ip6e_ip6 = m; + + bzero(&state, sizeof(state)); + state.m = m; + state.ro = (struct route *)ro; + state.dst = (struct sockaddr *)dst; + + error = ipsec6_output_tunnel(&state, sp, flags); + + m = state.m; + ro = (struct route_in6 *)state.ro; + dst = (struct sockaddr_in6 *)state.dst; + if (error) { + /* mbuf is already reclaimed in ipsec6_output_tunnel. */ + m0 = m = NULL; + m = NULL; + switch (error) { + case EHOSTUNREACH: + case ENETUNREACH: + case EMSGSIZE: + case ENOBUFS: + case ENOMEM: + break; + default: + printf("ip6_output (ipsec): error code %d\n", error); + /* fall through */ + case ENOENT: + /* don't show these error codes to the user */ + error = 0; + break; + } + goto bad; + } + + exthdrs.ip6e_ip6 = m; + } +#endif /* IPSEC */ + + if (!IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + /* Unicast */ + +#define ifatoia6(ifa) ((struct in6_ifaddr *)(ifa)) +#define sin6tosa(sin6) ((struct sockaddr *)(sin6)) + /* xxx + * interface selection comes here + * if an interface is specified from an upper layer, + * ifp must point it. + */ + if (ro->ro_rt == 0) { + /* + * non-bsdi always clone routes, if parent is + * PRF_CLONING. + */ + rtalloc((struct route *)ro); + } + if (ro->ro_rt == 0) { + ip6stat.ip6s_noroute++; + error = EHOSTUNREACH; + /* XXX in6_ifstat_inc(ifp, ifs6_out_discard); */ + goto bad; + } + ia = ifatoia6(ro->ro_rt->rt_ifa); + ifp = ro->ro_rt->rt_ifp; + ro->ro_rt->rt_use++; + if (ro->ro_rt->rt_flags & RTF_GATEWAY) + dst = (struct sockaddr_in6 *)ro->ro_rt->rt_gateway; + m->m_flags &= ~(M_BCAST | M_MCAST); /* just in case */ + + in6_ifstat_inc(ifp, ifs6_out_request); + + /* + * Check if the outgoing interface conflicts with + * the interface specified by ifi6_ifindex (if specified). + * Note that loopback interface is always okay. + * (this may happen when we are sending a packet to one of + * our own addresses.) + */ + if (opt && opt->ip6po_pktinfo + && opt->ip6po_pktinfo->ipi6_ifindex) { + if (!(ifp->if_flags & IFF_LOOPBACK) + && ifp->if_index != opt->ip6po_pktinfo->ipi6_ifindex) { + ip6stat.ip6s_noroute++; + in6_ifstat_inc(ifp, ifs6_out_discard); + error = EHOSTUNREACH; + goto bad; + } + } + + if (opt && opt->ip6po_hlim != -1) + ip6->ip6_hlim = opt->ip6po_hlim & 0xff; + } else { + /* Multicast */ + struct in6_multi *in6m; + + m->m_flags = (m->m_flags & ~M_BCAST) | M_MCAST; + + /* + * See if the caller provided any multicast options + */ + ifp = NULL; + if (im6o != NULL) { + ip6->ip6_hlim = im6o->im6o_multicast_hlim; + if (im6o->im6o_multicast_ifp != NULL) + ifp = im6o->im6o_multicast_ifp; + } else + ip6->ip6_hlim = ip6_defmcasthlim; + + /* + * See if the caller provided the outgoing interface + * as an ancillary data. + * Boundary check for ifindex is assumed to be already done. + */ + if (opt && opt->ip6po_pktinfo && opt->ip6po_pktinfo->ipi6_ifindex) + ifp = ifnet_byindex(opt->ip6po_pktinfo->ipi6_ifindex); + + /* + * If the destination is a node-local scope multicast, + * the packet should be loop-backed only. + */ + if (IN6_IS_ADDR_MC_NODELOCAL(&ip6->ip6_dst)) { + /* + * If the outgoing interface is already specified, + * it should be a loopback interface. + */ + if (ifp && (ifp->if_flags & IFF_LOOPBACK) == 0) { + ip6stat.ip6s_badscope++; + error = ENETUNREACH; /* XXX: better error? */ + /* XXX correct ifp? */ + in6_ifstat_inc(ifp, ifs6_out_discard); + goto bad; + } else { + ifp = &loif[0]; + } + } + + if (opt && opt->ip6po_hlim != -1) + ip6->ip6_hlim = opt->ip6po_hlim & 0xff; + + /* + * If caller did not provide an interface lookup a + * default in the routing table. This is either a + * default for the speicfied group (i.e. a host + * route), or a multicast default (a route for the + * ``net'' ff00::/8). + */ + if (ifp == NULL) { + if (ro->ro_rt == 0) { + ro->ro_rt = rtalloc1((struct sockaddr *) + &ro->ro_dst, 0, 0UL); + } + if (ro->ro_rt == 0) { + ip6stat.ip6s_noroute++; + error = EHOSTUNREACH; + /* XXX in6_ifstat_inc(ifp, ifs6_out_discard) */ + goto bad; + } + ia = ifatoia6(ro->ro_rt->rt_ifa); + ifp = ro->ro_rt->rt_ifp; + ro->ro_rt->rt_use++; + } + + if ((flags & IPV6_FORWARDING) == 0) + in6_ifstat_inc(ifp, ifs6_out_request); + in6_ifstat_inc(ifp, ifs6_out_mcast); + + /* + * Confirm that the outgoing interface supports multicast. + */ + if ((ifp->if_flags & IFF_MULTICAST) == 0) { + ip6stat.ip6s_noroute++; + in6_ifstat_inc(ifp, ifs6_out_discard); + error = ENETUNREACH; + goto bad; + } + IN6_LOOKUP_MULTI(ip6->ip6_dst, ifp, in6m); + if (in6m != NULL && + (im6o == NULL || im6o->im6o_multicast_loop)) { + /* + * If we belong to the destination multicast group + * on the outgoing interface, and the caller did not + * forbid loopback, loop back a copy. + */ + ip6_mloopback(ifp, m, dst); + } else { + /* + * If we are acting as a multicast router, perform + * multicast forwarding as if the packet had just + * arrived on the interface to which we are about + * to send. The multicast forwarding function + * recursively calls this function, using the + * IPV6_FORWARDING flag to prevent infinite recursion. + * + * Multicasts that are looped back by ip6_mloopback(), + * above, will be forwarded by the ip6_input() routine, + * if necessary. + */ + if (ip6_mrouter && (flags & IPV6_FORWARDING) == 0) { + if (ip6_mforward(ip6, ifp, m) != 0) { + m_freem(m); + goto done; + } + } + } + /* + * Multicasts with a hoplimit of zero may be looped back, + * above, but must not be transmitted on a network. + * Also, multicasts addressed to the loopback interface + * are not sent -- the above call to ip6_mloopback() will + * loop back a copy if this host actually belongs to the + * destination group on the loopback interface. + */ + if (ip6->ip6_hlim == 0 || (ifp->if_flags & IFF_LOOPBACK)) { + m_freem(m); + goto done; + } + } + + /* + * Fill the outgoing inteface to tell the upper layer + * to increment per-interface statistics. + */ + if (ifpp) + *ifpp = ifp; + + /* + * Determine path MTU. + */ + if (ro_pmtu != ro) { + /* The first hop and the final destination may differ. */ + struct sockaddr_in6 *sin6_fin = + (struct sockaddr_in6 *)&ro_pmtu->ro_dst; + if (ro_pmtu->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 || + !IN6_ARE_ADDR_EQUAL(&sin6_fin->sin6_addr, + &finaldst))) { + RTFREE(ro_pmtu->ro_rt); + ro_pmtu->ro_rt = (struct rtentry *)0; + } + if (ro_pmtu->ro_rt == 0) { + bzero(sin6_fin, sizeof(*sin6_fin)); + sin6_fin->sin6_family = AF_INET6; + sin6_fin->sin6_len = sizeof(struct sockaddr_in6); + sin6_fin->sin6_addr = finaldst; + + rtalloc((struct route *)ro_pmtu); + } + } + if (ro_pmtu->ro_rt != NULL) { + u_int32_t ifmtu = nd_ifinfo[ifp->if_index].linkmtu; + + mtu = ro_pmtu->ro_rt->rt_rmx.rmx_mtu; + if (mtu > ifmtu || mtu == 0) { + /* + * The MTU on the route is larger than the MTU on + * the interface! This shouldn't happen, unless the + * MTU of the interface has been changed after the + * interface was brought up. Change the MTU in the + * route to match the interface MTU (as long as the + * field isn't locked). + * + * if MTU on the route is 0, we need to fix the MTU. + * this case happens with path MTU discovery timeouts. + */ + mtu = ifmtu; + if ((ro_pmtu->ro_rt->rt_rmx.rmx_locks & RTV_MTU) == 0) + ro_pmtu->ro_rt->rt_rmx.rmx_mtu = mtu; /* XXX */ + } + } else { + mtu = nd_ifinfo[ifp->if_index].linkmtu; + } + + /* + * advanced API (IPV6_USE_MIN_MTU) overrides mtu setting + */ + if ((flags & IPV6_MINMTU) != 0 && mtu > IPV6_MMTU) + mtu = IPV6_MMTU; + + /* Fake scoped addresses */ + if ((ifp->if_flags & IFF_LOOPBACK) != 0) { + /* + * If source or destination address is a scoped address, and + * the packet is going to be sent to a loopback interface, + * we should keep the original interface. + */ + + /* + * XXX: this is a very experimental and temporary solution. + * We eventually have sockaddr_in6 and use the sin6_scope_id + * field of the structure here. + * We rely on the consistency between two scope zone ids + * of source and destination, which should already be assured. + * Larger scopes than link will be supported in the future. + */ + origifp = NULL; + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) + origifp = ifnet_byindex(ntohs(ip6->ip6_src.s6_addr16[1])); + else if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) + origifp = ifnet_byindex(ntohs(ip6->ip6_dst.s6_addr16[1])); + /* + * XXX: origifp can be NULL even in those two cases above. + * For example, if we remove the (only) link-local address + * from the loopback interface, and try to send a link-local + * address without link-id information. Then the source + * address is ::1, and the destination address is the + * link-local address with its s6_addr16[1] being zero. + * What is worse, if the packet goes to the loopback interface + * by a default rejected route, the null pointer would be + * passed to looutput, and the kernel would hang. + * The following last resort would prevent such disaster. + */ + if (origifp == NULL) + origifp = ifp; + } + else + origifp = ifp; +#ifndef SCOPEDROUTING + /* + * clear embedded scope identifiers if necessary. + * in6_clearscope will touch the addresses only when necessary. + */ + in6_clearscope(&ip6->ip6_src); + in6_clearscope(&ip6->ip6_dst); +#endif + + /* + * Check with the firewall... + */ + if (ip6_fw_enable && ip6_fw_chk_ptr) { + u_short port = 0; + m->m_pkthdr.rcvif = NULL; /* XXX */ + /* If ipfw says divert, we have to just drop packet */ + if ((*ip6_fw_chk_ptr)(&ip6, ifp, &port, &m)) { + m_freem(m); + goto done; + } + if (!m) { + error = EACCES; + goto done; + } + } + + /* + * If the outgoing packet contains a hop-by-hop options header, + * it must be examined and processed even by the source node. + * (RFC 2460, section 4.) + */ + if (exthdrs.ip6e_hbh) { + struct ip6_hbh *hbh = mtod(exthdrs.ip6e_hbh, struct ip6_hbh *); + u_int32_t dummy1; /* XXX unused */ + u_int32_t dummy2; /* XXX unused */ + +#ifdef DIAGNOSTIC + if ((hbh->ip6h_len + 1) << 3 > exthdrs.ip6e_hbh->m_len) + panic("ip6e_hbh is not continuous"); +#endif + /* + * XXX: if we have to send an ICMPv6 error to the sender, + * we need the M_LOOP flag since icmp6_error() expects + * the IPv6 and the hop-by-hop options header are + * continuous unless the flag is set. + */ + m->m_flags |= M_LOOP; + m->m_pkthdr.rcvif = ifp; + if (ip6_process_hopopts(m, + (u_int8_t *)(hbh + 1), + ((hbh->ip6h_len + 1) << 3) - + sizeof(struct ip6_hbh), + &dummy1, &dummy2) < 0) { + /* m was already freed at this point */ + error = EINVAL;/* better error? */ + goto done; + } + m->m_flags &= ~M_LOOP; /* XXX */ + m->m_pkthdr.rcvif = NULL; + } + +#ifdef PFIL_HOOKS + /* + * Run through list of hooks for output packets. + */ + m1 = m; + pfh = pfil_hook_get(PFIL_OUT, &inet6sw[ip6_protox[IPPROTO_IPV6]].pr_pfh); + for (; pfh; pfh = pfh->pfil_link.tqe_next) + if (pfh->pfil_func) { + rv = pfh->pfil_func(ip6, sizeof(*ip6), ifp, 1, &m1); + if (rv) { + error = EHOSTUNREACH; + goto done; + } + m = m1; + if (m == NULL) + goto done; + ip6 = mtod(m, struct ip6_hdr *); + } +#endif /* PFIL_HOOKS */ + /* + * Send the packet to the outgoing interface. + * If necessary, do IPv6 fragmentation before sending. + */ + tlen = m->m_pkthdr.len; + if (tlen <= mtu +#ifdef notyet + /* + * On any link that cannot convey a 1280-octet packet in one piece, + * link-specific fragmentation and reassembly must be provided at + * a layer below IPv6. [RFC 2460, sec.5] + * Thus if the interface has ability of link-level fragmentation, + * we can just send the packet even if the packet size is + * larger than the link's MTU. + * XXX: IFF_FRAGMENTABLE (or such) flag has not been defined yet... + */ + + || ifp->if_flags & IFF_FRAGMENTABLE +#endif + ) + { + /* Record statistics for this interface address. */ + if (ia && !(flags & IPV6_FORWARDING)) { + ia->ia_ifa.if_opackets++; + ia->ia_ifa.if_obytes += m->m_pkthdr.len; + } +#ifdef IPSEC + /* clean ipsec history once it goes out of the node */ + ipsec_delaux(m); +#endif + error = nd6_output(ifp, origifp, m, dst, ro->ro_rt); + goto done; + } else if (mtu < IPV6_MMTU) { + /* + * note that path MTU is never less than IPV6_MMTU + * (see icmp6_input). + */ + error = EMSGSIZE; + in6_ifstat_inc(ifp, ifs6_out_fragfail); + goto bad; + } else if (ip6->ip6_plen == 0) { /* jumbo payload cannot be fragmented */ + error = EMSGSIZE; + in6_ifstat_inc(ifp, ifs6_out_fragfail); + goto bad; + } else { + struct mbuf **mnext, *m_frgpart; + struct ip6_frag *ip6f; + u_int32_t id = htonl(ip6_id++); + u_char nextproto; + + /* + * Too large for the destination or interface; + * fragment if possible. + * Must be able to put at least 8 bytes per fragment. + */ + hlen = unfragpartlen; + if (mtu > IPV6_MAXPACKET) + mtu = IPV6_MAXPACKET; + + len = (mtu - hlen - sizeof(struct ip6_frag)) & ~7; + if (len < 8) { + error = EMSGSIZE; + in6_ifstat_inc(ifp, ifs6_out_fragfail); + goto bad; + } + + mnext = &m->m_nextpkt; + + /* + * Change the next header field of the last header in the + * unfragmentable part. + */ + if (exthdrs.ip6e_rthdr) { + nextproto = *mtod(exthdrs.ip6e_rthdr, u_char *); + *mtod(exthdrs.ip6e_rthdr, u_char *) = IPPROTO_FRAGMENT; + } else if (exthdrs.ip6e_dest1) { + nextproto = *mtod(exthdrs.ip6e_dest1, u_char *); + *mtod(exthdrs.ip6e_dest1, u_char *) = IPPROTO_FRAGMENT; + } else if (exthdrs.ip6e_hbh) { + nextproto = *mtod(exthdrs.ip6e_hbh, u_char *); + *mtod(exthdrs.ip6e_hbh, u_char *) = IPPROTO_FRAGMENT; + } else { + nextproto = ip6->ip6_nxt; + ip6->ip6_nxt = IPPROTO_FRAGMENT; + } + + /* + * Loop through length of segment after first fragment, + * make new header and copy data of each part and link onto + * chain. + */ + m0 = m; + for (off = hlen; off < tlen; off += len) { + MGETHDR(m, M_DONTWAIT, MT_HEADER); + if (!m) { + error = ENOBUFS; + ip6stat.ip6s_odropped++; + goto sendorfree; + } + m->m_pkthdr.rcvif = NULL; + m->m_flags = m0->m_flags & M_COPYFLAGS; + *mnext = m; + mnext = &m->m_nextpkt; + m->m_data += max_linkhdr; + mhip6 = mtod(m, struct ip6_hdr *); + *mhip6 = *ip6; + m->m_len = sizeof(*mhip6); + error = ip6_insertfraghdr(m0, m, hlen, &ip6f); + if (error) { + ip6stat.ip6s_odropped++; + goto sendorfree; + } + ip6f->ip6f_offlg = htons((u_short)((off - hlen) & ~7)); + if (off + len >= tlen) + len = tlen - off; + else + ip6f->ip6f_offlg |= IP6F_MORE_FRAG; + mhip6->ip6_plen = htons((u_short)(len + hlen + + sizeof(*ip6f) - + sizeof(struct ip6_hdr))); + if ((m_frgpart = m_copy(m0, off, len)) == 0) { + error = ENOBUFS; + ip6stat.ip6s_odropped++; + goto sendorfree; + } + m_cat(m, m_frgpart); + m->m_pkthdr.len = len + hlen + sizeof(*ip6f); + m->m_pkthdr.rcvif = (struct ifnet *)0; + ip6f->ip6f_reserved = 0; + ip6f->ip6f_ident = id; + ip6f->ip6f_nxt = nextproto; + ip6stat.ip6s_ofragments++; + in6_ifstat_inc(ifp, ifs6_out_fragcreat); + } + + in6_ifstat_inc(ifp, ifs6_out_fragok); + } + + /* + * Remove leading garbages. + */ +sendorfree: + m = m0->m_nextpkt; + m0->m_nextpkt = 0; + m_freem(m0); + for (m0 = m; m; m = m0) { + m0 = m->m_nextpkt; + m->m_nextpkt = 0; + if (error == 0) { + /* Record statistics for this interface address. */ + if (ia) { + ia->ia_ifa.if_opackets++; + ia->ia_ifa.if_obytes += m->m_pkthdr.len; + } +#ifdef IPSEC + /* clean ipsec history once it goes out of the node */ + ipsec_delaux(m); +#endif + error = nd6_output(ifp, origifp, m, dst, ro->ro_rt); + } else + m_freem(m); + } + + if (error == 0) + ip6stat.ip6s_fragmented++; + +done: + if (ro == &ip6route && ro->ro_rt) { /* brace necessary for RTFREE */ + RTFREE(ro->ro_rt); + } else if (ro_pmtu == &ip6route && ro_pmtu->ro_rt) { + RTFREE(ro_pmtu->ro_rt); + } + +#ifdef IPSEC + if (sp != NULL) + key_freesp(sp); +#endif /* IPSEC */ +#ifdef FAST_IPSEC + if (sp != NULL) + KEY_FREESP(&sp); +#endif /* FAST_IPSEC */ + + return(error); + +freehdrs: + m_freem(exthdrs.ip6e_hbh); /* m_freem will check if mbuf is 0 */ + m_freem(exthdrs.ip6e_dest1); + m_freem(exthdrs.ip6e_rthdr); + m_freem(exthdrs.ip6e_dest2); + /* fall through */ +bad: + m_freem(m); + goto done; +} + +static int +ip6_copyexthdr(mp, hdr, hlen) + struct mbuf **mp; + caddr_t hdr; + int hlen; +{ + struct mbuf *m; + + if (hlen > MCLBYTES) + return(ENOBUFS); /* XXX */ + + MGET(m, M_DONTWAIT, MT_DATA); + if (!m) + return(ENOBUFS); + + if (hlen > MLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + return(ENOBUFS); + } + } + m->m_len = hlen; + if (hdr) + bcopy(hdr, mtod(m, caddr_t), hlen); + + *mp = m; + return(0); +} + +/* + * Insert jumbo payload option. + */ +static int +ip6_insert_jumboopt(exthdrs, plen) + struct ip6_exthdrs *exthdrs; + u_int32_t plen; +{ + struct mbuf *mopt; + u_char *optbuf; + u_int32_t v; + +#define JUMBOOPTLEN 8 /* length of jumbo payload option and padding */ + + /* + * If there is no hop-by-hop options header, allocate new one. + * If there is one but it doesn't have enough space to store the + * jumbo payload option, allocate a cluster to store the whole options. + * Otherwise, use it to store the options. + */ + if (exthdrs->ip6e_hbh == 0) { + MGET(mopt, M_DONTWAIT, MT_DATA); + if (mopt == 0) + return(ENOBUFS); + mopt->m_len = JUMBOOPTLEN; + optbuf = mtod(mopt, u_char *); + optbuf[1] = 0; /* = ((JUMBOOPTLEN) >> 3) - 1 */ + exthdrs->ip6e_hbh = mopt; + } else { + struct ip6_hbh *hbh; + + mopt = exthdrs->ip6e_hbh; + if (M_TRAILINGSPACE(mopt) < JUMBOOPTLEN) { + /* + * XXX assumption: + * - exthdrs->ip6e_hbh is not referenced from places + * other than exthdrs. + * - exthdrs->ip6e_hbh is not an mbuf chain. + */ + int oldoptlen = mopt->m_len; + struct mbuf *n; + + /* + * XXX: give up if the whole (new) hbh header does + * not fit even in an mbuf cluster. + */ + if (oldoptlen + JUMBOOPTLEN > MCLBYTES) + return(ENOBUFS); + + /* + * As a consequence, we must always prepare a cluster + * at this point. + */ + MGET(n, M_DONTWAIT, MT_DATA); + if (n) { + MCLGET(n, M_DONTWAIT); + if ((n->m_flags & M_EXT) == 0) { + m_freem(n); + n = NULL; + } + } + if (!n) + return(ENOBUFS); + n->m_len = oldoptlen + JUMBOOPTLEN; + bcopy(mtod(mopt, caddr_t), mtod(n, caddr_t), + oldoptlen); + optbuf = mtod(n, caddr_t) + oldoptlen; + m_freem(mopt); + mopt = exthdrs->ip6e_hbh = n; + } else { + optbuf = mtod(mopt, u_char *) + mopt->m_len; + mopt->m_len += JUMBOOPTLEN; + } + optbuf[0] = IP6OPT_PADN; + optbuf[1] = 1; + + /* + * Adjust the header length according to the pad and + * the jumbo payload option. + */ + hbh = mtod(mopt, struct ip6_hbh *); + hbh->ip6h_len += (JUMBOOPTLEN >> 3); + } + + /* fill in the option. */ + optbuf[2] = IP6OPT_JUMBO; + optbuf[3] = 4; + v = (u_int32_t)htonl(plen + JUMBOOPTLEN); + bcopy(&v, &optbuf[4], sizeof(u_int32_t)); + + /* finally, adjust the packet header length */ + exthdrs->ip6e_ip6->m_pkthdr.len += JUMBOOPTLEN; + + return(0); +#undef JUMBOOPTLEN +} + +/* + * Insert fragment header and copy unfragmentable header portions. + */ +static int +ip6_insertfraghdr(m0, m, hlen, frghdrp) + struct mbuf *m0, *m; + int hlen; + struct ip6_frag **frghdrp; +{ + struct mbuf *n, *mlast; + + if (hlen > sizeof(struct ip6_hdr)) { + n = m_copym(m0, sizeof(struct ip6_hdr), + hlen - sizeof(struct ip6_hdr), M_DONTWAIT); + if (n == 0) + return(ENOBUFS); + m->m_next = n; + } else + n = m; + + /* Search for the last mbuf of unfragmentable part. */ + for (mlast = n; mlast->m_next; mlast = mlast->m_next) + ; + + if ((mlast->m_flags & M_EXT) == 0 && + M_TRAILINGSPACE(mlast) >= sizeof(struct ip6_frag)) { + /* use the trailing space of the last mbuf for the fragment hdr */ + *frghdrp = + (struct ip6_frag *)(mtod(mlast, caddr_t) + mlast->m_len); + mlast->m_len += sizeof(struct ip6_frag); + m->m_pkthdr.len += sizeof(struct ip6_frag); + } else { + /* allocate a new mbuf for the fragment header */ + struct mbuf *mfrg; + + MGET(mfrg, M_DONTWAIT, MT_DATA); + if (mfrg == 0) + return(ENOBUFS); + mfrg->m_len = sizeof(struct ip6_frag); + *frghdrp = mtod(mfrg, struct ip6_frag *); + mlast->m_next = mfrg; + } + + return(0); +} + +/* + * IP6 socket option processing. + */ +int +ip6_ctloutput(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + int privileged; + struct inpcb *in6p = sotoinpcb(so); + int error, optval; + int level, op, optname; + int optlen; + struct thread *td; + + if (sopt) { + level = sopt->sopt_level; + op = sopt->sopt_dir; + optname = sopt->sopt_name; + optlen = sopt->sopt_valsize; + td = sopt->sopt_td; + } else { + panic("ip6_ctloutput: arg soopt is NULL"); + } + error = optval = 0; + + privileged = (td == 0 || suser(td)) ? 0 : 1; + + if (level == IPPROTO_IPV6) { + switch (op) { + + case SOPT_SET: + switch (optname) { + case IPV6_PKTOPTIONS: + { + struct mbuf *m; + + error = soopt_getm(sopt, &m); /* XXX */ + if (error != 0) + break; + error = soopt_mcopyin(sopt, m); /* XXX */ + if (error != 0) + break; + error = ip6_pcbopts(&in6p->in6p_outputopts, + m, so, sopt); + m_freem(m); /* XXX */ + break; + } + + /* + * Use of some Hop-by-Hop options or some + * Destination options, might require special + * privilege. That is, normal applications + * (without special privilege) might be forbidden + * from setting certain options in outgoing packets, + * and might never see certain options in received + * packets. [RFC 2292 Section 6] + * KAME specific note: + * KAME prevents non-privileged users from sending or + * receiving ANY hbh/dst options in order to avoid + * overhead of parsing options in the kernel. + */ + case IPV6_UNICAST_HOPS: + case IPV6_CHECKSUM: + case IPV6_FAITH: + + case IPV6_V6ONLY: + if (optlen != sizeof(int)) { + error = EINVAL; + break; + } + error = sooptcopyin(sopt, &optval, + sizeof optval, sizeof optval); + if (error) + break; + switch (optname) { + + case IPV6_UNICAST_HOPS: + if (optval < -1 || optval >= 256) + error = EINVAL; + else { + /* -1 = kernel default */ + in6p->in6p_hops = optval; + + if ((in6p->in6p_vflag & + INP_IPV4) != 0) + in6p->inp_ip_ttl = optval; + } + break; +#define OPTSET(bit) \ +do { \ + if (optval) \ + in6p->in6p_flags |= (bit); \ + else \ + in6p->in6p_flags &= ~(bit); \ +} while (0) +#define OPTBIT(bit) (in6p->in6p_flags & (bit) ? 1 : 0) + + case IPV6_CHECKSUM: + in6p->in6p_cksum = optval; + break; + + case IPV6_FAITH: + OPTSET(IN6P_FAITH); + break; + + case IPV6_V6ONLY: + /* + * make setsockopt(IPV6_V6ONLY) + * available only prior to bind(2). + * see ipng mailing list, Jun 22 2001. + */ + if (in6p->in6p_lport || + !IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) + { + error = EINVAL; + break; + } + OPTSET(IN6P_IPV6_V6ONLY); + if (optval) + in6p->in6p_vflag &= ~INP_IPV4; + else + in6p->in6p_vflag |= INP_IPV4; + break; + } + break; + + case IPV6_PKTINFO: + case IPV6_HOPLIMIT: + case IPV6_HOPOPTS: + case IPV6_DSTOPTS: + case IPV6_RTHDR: + /* RFC 2292 */ + if (optlen != sizeof(int)) { + error = EINVAL; + break; + } + error = sooptcopyin(sopt, &optval, + sizeof optval, sizeof optval); + if (error) + break; + switch (optname) { + case IPV6_PKTINFO: + OPTSET(IN6P_PKTINFO); + break; + case IPV6_HOPLIMIT: + OPTSET(IN6P_HOPLIMIT); + break; + case IPV6_HOPOPTS: + /* + * Check super-user privilege. + * See comments for IPV6_RECVHOPOPTS. + */ + if (!privileged) + return(EPERM); + OPTSET(IN6P_HOPOPTS); + break; + case IPV6_DSTOPTS: + if (!privileged) + return(EPERM); + OPTSET(IN6P_DSTOPTS|IN6P_RTHDRDSTOPTS); /* XXX */ + break; + case IPV6_RTHDR: + OPTSET(IN6P_RTHDR); + break; + } + break; +#undef OPTSET + + case IPV6_MULTICAST_IF: + case IPV6_MULTICAST_HOPS: + case IPV6_MULTICAST_LOOP: + case IPV6_JOIN_GROUP: + case IPV6_LEAVE_GROUP: + { + struct mbuf *m; + if (sopt->sopt_valsize > MLEN) { + error = EMSGSIZE; + break; + } + /* XXX */ + MGET(m, sopt->sopt_td ? M_TRYWAIT : M_DONTWAIT, MT_HEADER); + if (m == 0) { + error = ENOBUFS; + break; + } + m->m_len = sopt->sopt_valsize; + error = sooptcopyin(sopt, mtod(m, char *), + m->m_len, m->m_len); + error = ip6_setmoptions(sopt->sopt_name, + &in6p->in6p_moptions, + m); + (void)m_free(m); + } + break; + + case IPV6_PORTRANGE: + error = sooptcopyin(sopt, &optval, + sizeof optval, sizeof optval); + if (error) + break; + + switch (optval) { + case IPV6_PORTRANGE_DEFAULT: + in6p->in6p_flags &= ~(IN6P_LOWPORT); + in6p->in6p_flags &= ~(IN6P_HIGHPORT); + break; + + case IPV6_PORTRANGE_HIGH: + in6p->in6p_flags &= ~(IN6P_LOWPORT); + in6p->in6p_flags |= IN6P_HIGHPORT; + break; + + case IPV6_PORTRANGE_LOW: + in6p->in6p_flags &= ~(IN6P_HIGHPORT); + in6p->in6p_flags |= IN6P_LOWPORT; + break; + + default: + error = EINVAL; + break; + } + break; + +#if defined(IPSEC) || defined(FAST_IPSEC) + case IPV6_IPSEC_POLICY: + { + caddr_t req = NULL; + size_t len = 0; + struct mbuf *m; + + if ((error = soopt_getm(sopt, &m)) != 0) /* XXX */ + break; + if ((error = soopt_mcopyin(sopt, m)) != 0) /* XXX */ + break; + if (m) { + req = mtod(m, caddr_t); + len = m->m_len; + } + error = ipsec6_set_policy(in6p, optname, req, + len, privileged); + m_freem(m); + } + break; +#endif /* KAME IPSEC */ + + case IPV6_FW_ADD: + case IPV6_FW_DEL: + case IPV6_FW_FLUSH: + case IPV6_FW_ZERO: + { + struct mbuf *m; + struct mbuf **mp = &m; + + if (ip6_fw_ctl_ptr == NULL) + return EINVAL; + /* XXX */ + if ((error = soopt_getm(sopt, &m)) != 0) + break; + /* XXX */ + if ((error = soopt_mcopyin(sopt, m)) != 0) + break; + error = (*ip6_fw_ctl_ptr)(optname, mp); + m = *mp; + } + break; + + default: + error = ENOPROTOOPT; + break; + } + break; + + case SOPT_GET: + switch (optname) { + + case IPV6_PKTOPTIONS: + if (in6p->in6p_options) { + struct mbuf *m; + m = m_copym(in6p->in6p_options, + 0, M_COPYALL, M_TRYWAIT); + error = soopt_mcopyout(sopt, m); + if (error == 0) + m_freem(m); + } else + sopt->sopt_valsize = 0; + break; + + case IPV6_UNICAST_HOPS: + case IPV6_CHECKSUM: + + case IPV6_FAITH: + case IPV6_V6ONLY: + case IPV6_PORTRANGE: + switch (optname) { + + case IPV6_UNICAST_HOPS: + optval = in6p->in6p_hops; + break; + + case IPV6_CHECKSUM: + optval = in6p->in6p_cksum; + break; + + case IPV6_FAITH: + optval = OPTBIT(IN6P_FAITH); + break; + + case IPV6_V6ONLY: + optval = OPTBIT(IN6P_IPV6_V6ONLY); + break; + + case IPV6_PORTRANGE: + { + int flags; + flags = in6p->in6p_flags; + if (flags & IN6P_HIGHPORT) + optval = IPV6_PORTRANGE_HIGH; + else if (flags & IN6P_LOWPORT) + optval = IPV6_PORTRANGE_LOW; + else + optval = 0; + break; + } + } + error = sooptcopyout(sopt, &optval, + sizeof optval); + break; + + case IPV6_PKTINFO: + case IPV6_HOPLIMIT: + case IPV6_HOPOPTS: + case IPV6_RTHDR: + case IPV6_DSTOPTS: + if (optname == IPV6_HOPOPTS || + optname == IPV6_DSTOPTS || + !privileged) + return(EPERM); + switch (optname) { + case IPV6_PKTINFO: + optval = OPTBIT(IN6P_PKTINFO); + break; + case IPV6_HOPLIMIT: + optval = OPTBIT(IN6P_HOPLIMIT); + break; + case IPV6_HOPOPTS: + if (!privileged) + return(EPERM); + optval = OPTBIT(IN6P_HOPOPTS); + break; + case IPV6_RTHDR: + optval = OPTBIT(IN6P_RTHDR); + break; + case IPV6_DSTOPTS: + if (!privileged) + return(EPERM); + optval = OPTBIT(IN6P_DSTOPTS|IN6P_RTHDRDSTOPTS); + break; + } + error = sooptcopyout(sopt, &optval, + sizeof optval); + break; + + case IPV6_MULTICAST_IF: + case IPV6_MULTICAST_HOPS: + case IPV6_MULTICAST_LOOP: + case IPV6_JOIN_GROUP: + case IPV6_LEAVE_GROUP: + { + struct mbuf *m; + error = ip6_getmoptions(sopt->sopt_name, + in6p->in6p_moptions, &m); + if (error == 0) + error = sooptcopyout(sopt, + mtod(m, char *), m->m_len); + m_freem(m); + } + break; + +#if defined(IPSEC) || defined(FAST_IPSEC) + case IPV6_IPSEC_POLICY: + { + caddr_t req = NULL; + size_t len = 0; + struct mbuf *m = NULL; + struct mbuf **mp = &m; + + error = soopt_getm(sopt, &m); /* XXX */ + if (error != 0) + break; + error = soopt_mcopyin(sopt, m); /* XXX */ + if (error != 0) + break; + if (m) { + req = mtod(m, caddr_t); + len = m->m_len; + } + error = ipsec6_get_policy(in6p, req, len, mp); + if (error == 0) + error = soopt_mcopyout(sopt, m); /*XXX*/ + if (error == 0 && m) + m_freem(m); + break; + } +#endif /* KAME IPSEC */ + + case IPV6_FW_GET: + { + struct mbuf *m; + struct mbuf **mp = &m; + + if (ip6_fw_ctl_ptr == NULL) + { + return EINVAL; + } + error = (*ip6_fw_ctl_ptr)(optname, mp); + if (error == 0) + error = soopt_mcopyout(sopt, m); /* XXX */ + if (error == 0 && m) + m_freem(m); + } + break; + + default: + error = ENOPROTOOPT; + break; + } + break; + } + } else { + error = EINVAL; + } + return(error); +} + +/* + * Set up IP6 options in pcb for insertion in output packets or + * specifying behavior of outgoing packets. + */ +static int +ip6_pcbopts(pktopt, m, so, sopt) + struct ip6_pktopts **pktopt; + struct mbuf *m; + struct socket *so; + struct sockopt *sopt; +{ + struct ip6_pktopts *opt = *pktopt; + int error = 0; + struct thread *td = sopt->sopt_td; + int priv = 0; + + /* turn off any old options. */ + if (opt) { +#ifdef DIAGNOSTIC + if (opt->ip6po_pktinfo || opt->ip6po_nexthop || + opt->ip6po_hbh || opt->ip6po_dest1 || opt->ip6po_dest2 || + opt->ip6po_rhinfo.ip6po_rhi_rthdr) + printf("ip6_pcbopts: all specified options are cleared.\n"); +#endif + ip6_clearpktopts(opt, 1, -1); + } else + opt = malloc(sizeof(*opt), M_IP6OPT, M_WAITOK); + *pktopt = NULL; + + if (!m || m->m_len == 0) { + /* + * Only turning off any previous options, regardless of + * whether the opt is just created or given. + */ + free(opt, M_IP6OPT); + return(0); + } + + /* set options specified by user. */ + if (td && !suser(td)) + priv = 1; + if ((error = ip6_setpktoptions(m, opt, priv, 1)) != 0) { + ip6_clearpktopts(opt, 1, -1); /* XXX: discard all options */ + free(opt, M_IP6OPT); + return(error); + } + *pktopt = opt; + return(0); +} + +/* + * initialize ip6_pktopts. beware that there are non-zero default values in + * the struct. + */ +void +init_ip6pktopts(opt) + struct ip6_pktopts *opt; +{ + + bzero(opt, sizeof(*opt)); + opt->ip6po_hlim = -1; /* -1 means default hop limit */ +} + +void +ip6_clearpktopts(pktopt, needfree, optname) + struct ip6_pktopts *pktopt; + int needfree, optname; +{ + if (pktopt == NULL) + return; + + if (optname == -1) { + if (needfree && pktopt->ip6po_pktinfo) + free(pktopt->ip6po_pktinfo, M_IP6OPT); + pktopt->ip6po_pktinfo = NULL; + } + if (optname == -1) + pktopt->ip6po_hlim = -1; + if (optname == -1) { + if (needfree && pktopt->ip6po_nexthop) + free(pktopt->ip6po_nexthop, M_IP6OPT); + pktopt->ip6po_nexthop = NULL; + } + if (optname == -1) { + if (needfree && pktopt->ip6po_hbh) + free(pktopt->ip6po_hbh, M_IP6OPT); + pktopt->ip6po_hbh = NULL; + } + if (optname == -1) { + if (needfree && pktopt->ip6po_dest1) + free(pktopt->ip6po_dest1, M_IP6OPT); + pktopt->ip6po_dest1 = NULL; + } + if (optname == -1) { + if (needfree && pktopt->ip6po_rhinfo.ip6po_rhi_rthdr) + free(pktopt->ip6po_rhinfo.ip6po_rhi_rthdr, M_IP6OPT); + pktopt->ip6po_rhinfo.ip6po_rhi_rthdr = NULL; + if (pktopt->ip6po_route.ro_rt) { + RTFREE(pktopt->ip6po_route.ro_rt); + pktopt->ip6po_route.ro_rt = NULL; + } + } + if (optname == -1) { + if (needfree && pktopt->ip6po_dest2) + free(pktopt->ip6po_dest2, M_IP6OPT); + pktopt->ip6po_dest2 = NULL; + } +} + +#define PKTOPT_EXTHDRCPY(type) \ +do {\ + if (src->type) {\ + int hlen =\ + (((struct ip6_ext *)src->type)->ip6e_len + 1) << 3;\ + dst->type = malloc(hlen, M_IP6OPT, canwait);\ + if (dst->type == NULL && canwait == M_NOWAIT)\ + goto bad;\ + bcopy(src->type, dst->type, hlen);\ + }\ +} while (0) + +struct ip6_pktopts * +ip6_copypktopts(src, canwait) + struct ip6_pktopts *src; + int canwait; +{ + struct ip6_pktopts *dst; + + if (src == NULL) { + printf("ip6_clearpktopts: invalid argument\n"); + return(NULL); + } + + dst = malloc(sizeof(*dst), M_IP6OPT, canwait); + if (dst == NULL && canwait == M_NOWAIT) + return (NULL); + bzero(dst, sizeof(*dst)); + + dst->ip6po_hlim = src->ip6po_hlim; + if (src->ip6po_pktinfo) { + dst->ip6po_pktinfo = malloc(sizeof(*dst->ip6po_pktinfo), + M_IP6OPT, canwait); + if (dst->ip6po_pktinfo == NULL && canwait == M_NOWAIT) + goto bad; + *dst->ip6po_pktinfo = *src->ip6po_pktinfo; + } + if (src->ip6po_nexthop) { + dst->ip6po_nexthop = malloc(src->ip6po_nexthop->sa_len, + M_IP6OPT, canwait); + if (dst->ip6po_nexthop == NULL && canwait == M_NOWAIT) + goto bad; + bcopy(src->ip6po_nexthop, dst->ip6po_nexthop, + src->ip6po_nexthop->sa_len); + } + PKTOPT_EXTHDRCPY(ip6po_hbh); + PKTOPT_EXTHDRCPY(ip6po_dest1); + PKTOPT_EXTHDRCPY(ip6po_dest2); + PKTOPT_EXTHDRCPY(ip6po_rthdr); /* not copy the cached route */ + return(dst); + + bad: + if (dst->ip6po_pktinfo) free(dst->ip6po_pktinfo, M_IP6OPT); + if (dst->ip6po_nexthop) free(dst->ip6po_nexthop, M_IP6OPT); + if (dst->ip6po_hbh) free(dst->ip6po_hbh, M_IP6OPT); + if (dst->ip6po_dest1) free(dst->ip6po_dest1, M_IP6OPT); + if (dst->ip6po_dest2) free(dst->ip6po_dest2, M_IP6OPT); + if (dst->ip6po_rthdr) free(dst->ip6po_rthdr, M_IP6OPT); + free(dst, M_IP6OPT); + return(NULL); +} +#undef PKTOPT_EXTHDRCPY + +void +ip6_freepcbopts(pktopt) + struct ip6_pktopts *pktopt; +{ + if (pktopt == NULL) + return; + + ip6_clearpktopts(pktopt, 1, -1); + + free(pktopt, M_IP6OPT); +} + +/* + * Set the IP6 multicast options in response to user setsockopt(). + */ +static int +ip6_setmoptions(optname, im6op, m) + int optname; + struct ip6_moptions **im6op; + struct mbuf *m; +{ + int error = 0; + u_int loop, ifindex; + struct ipv6_mreq *mreq; + struct ifnet *ifp; + struct ip6_moptions *im6o = *im6op; + struct route_in6 ro; + struct sockaddr_in6 *dst; + struct in6_multi_mship *imm; + struct thread *td = curthread; /* XXX */ + + if (im6o == NULL) { + /* + * No multicast option buffer attached to the pcb; + * allocate one and initialize to default values. + */ + im6o = (struct ip6_moptions *) + malloc(sizeof(*im6o), M_IPMOPTS, M_WAITOK); + + if (im6o == NULL) + return(ENOBUFS); + *im6op = im6o; + im6o->im6o_multicast_ifp = NULL; + im6o->im6o_multicast_hlim = ip6_defmcasthlim; + im6o->im6o_multicast_loop = IPV6_DEFAULT_MULTICAST_LOOP; + LIST_INIT(&im6o->im6o_memberships); + } + + switch (optname) { + + case IPV6_MULTICAST_IF: + /* + * Select the interface for outgoing multicast packets. + */ + if (m == NULL || m->m_len != sizeof(u_int)) { + error = EINVAL; + break; + } + bcopy(mtod(m, u_int *), &ifindex, sizeof(ifindex)); + if (ifindex < 0 || if_index < ifindex) { + error = ENXIO; /* XXX EINVAL? */ + break; + } + ifp = ifnet_byindex(ifindex); + if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { + error = EADDRNOTAVAIL; + break; + } + im6o->im6o_multicast_ifp = ifp; + break; + + case IPV6_MULTICAST_HOPS: + { + /* + * Set the IP6 hoplimit for outgoing multicast packets. + */ + int optval; + if (m == NULL || m->m_len != sizeof(int)) { + error = EINVAL; + break; + } + bcopy(mtod(m, u_int *), &optval, sizeof(optval)); + if (optval < -1 || optval >= 256) + error = EINVAL; + else if (optval == -1) + im6o->im6o_multicast_hlim = ip6_defmcasthlim; + else + im6o->im6o_multicast_hlim = optval; + break; + } + + case IPV6_MULTICAST_LOOP: + /* + * Set the loopback flag for outgoing multicast packets. + * Must be zero or one. + */ + if (m == NULL || m->m_len != sizeof(u_int)) { + error = EINVAL; + break; + } + bcopy(mtod(m, u_int *), &loop, sizeof(loop)); + if (loop > 1) { + error = EINVAL; + break; + } + im6o->im6o_multicast_loop = loop; + break; + + case IPV6_JOIN_GROUP: + /* + * Add a multicast group membership. + * Group must be a valid IP6 multicast address. + */ + if (m == NULL || m->m_len != sizeof(struct ipv6_mreq)) { + error = EINVAL; + break; + } + mreq = mtod(m, struct ipv6_mreq *); + if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) { + /* + * We use the unspecified address to specify to accept + * all multicast addresses. Only super user is allowed + * to do this. + */ + if (suser(td)) + { + error = EACCES; + break; + } + } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) { + error = EINVAL; + break; + } + + /* + * If the interface is specified, validate it. + */ + if (mreq->ipv6mr_interface < 0 + || if_index < mreq->ipv6mr_interface) { + error = ENXIO; /* XXX EINVAL? */ + break; + } + /* + * If no interface was explicitly specified, choose an + * appropriate one according to the given multicast address. + */ + if (mreq->ipv6mr_interface == 0) { + /* + * If the multicast address is in node-local scope, + * the interface should be a loopback interface. + * Otherwise, look up the routing table for the + * address, and choose the outgoing interface. + * XXX: is it a good approach? + */ + if (IN6_IS_ADDR_MC_NODELOCAL(&mreq->ipv6mr_multiaddr)) { + ifp = &loif[0]; + } else { + ro.ro_rt = NULL; + dst = (struct sockaddr_in6 *)&ro.ro_dst; + bzero(dst, sizeof(*dst)); + dst->sin6_len = sizeof(struct sockaddr_in6); + dst->sin6_family = AF_INET6; + dst->sin6_addr = mreq->ipv6mr_multiaddr; + rtalloc((struct route *)&ro); + if (ro.ro_rt == NULL) { + error = EADDRNOTAVAIL; + break; + } + ifp = ro.ro_rt->rt_ifp; + rtfree(ro.ro_rt); + } + } else + ifp = ifnet_byindex(mreq->ipv6mr_interface); + + /* + * See if we found an interface, and confirm that it + * supports multicast + */ + if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) { + error = EADDRNOTAVAIL; + break; + } + /* + * Put interface index into the multicast address, + * if the address has link-local scope. + */ + if (IN6_IS_ADDR_MC_LINKLOCAL(&mreq->ipv6mr_multiaddr)) { + mreq->ipv6mr_multiaddr.s6_addr16[1] + = htons(mreq->ipv6mr_interface); + } + /* + * See if the membership already exists. + */ + for (imm = im6o->im6o_memberships.lh_first; + imm != NULL; imm = imm->i6mm_chain.le_next) + if (imm->i6mm_maddr->in6m_ifp == ifp && + IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr, + &mreq->ipv6mr_multiaddr)) + break; + if (imm != NULL) { + error = EADDRINUSE; + break; + } + /* + * Everything looks good; add a new record to the multicast + * address list for the given interface. + */ + imm = malloc(sizeof(*imm), M_IPMADDR, M_WAITOK); + if (imm == NULL) { + error = ENOBUFS; + break; + } + if ((imm->i6mm_maddr = + in6_addmulti(&mreq->ipv6mr_multiaddr, ifp, &error)) == NULL) { + free(imm, M_IPMADDR); + break; + } + LIST_INSERT_HEAD(&im6o->im6o_memberships, imm, i6mm_chain); + break; + + case IPV6_LEAVE_GROUP: + /* + * Drop a multicast group membership. + * Group must be a valid IP6 multicast address. + */ + if (m == NULL || m->m_len != sizeof(struct ipv6_mreq)) { + error = EINVAL; + break; + } + mreq = mtod(m, struct ipv6_mreq *); + if (IN6_IS_ADDR_UNSPECIFIED(&mreq->ipv6mr_multiaddr)) { + if (suser(td)) { + error = EACCES; + break; + } + } else if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) { + error = EINVAL; + break; + } + /* + * If an interface address was specified, get a pointer + * to its ifnet structure. + */ + if (mreq->ipv6mr_interface < 0 + || if_index < mreq->ipv6mr_interface) { + error = ENXIO; /* XXX EINVAL? */ + break; + } + ifp = ifnet_byindex(mreq->ipv6mr_interface); + /* + * Put interface index into the multicast address, + * if the address has link-local scope. + */ + if (IN6_IS_ADDR_MC_LINKLOCAL(&mreq->ipv6mr_multiaddr)) { + mreq->ipv6mr_multiaddr.s6_addr16[1] + = htons(mreq->ipv6mr_interface); + } + /* + * Find the membership in the membership list. + */ + for (imm = im6o->im6o_memberships.lh_first; + imm != NULL; imm = imm->i6mm_chain.le_next) { + if ((ifp == NULL || + imm->i6mm_maddr->in6m_ifp == ifp) && + IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr, + &mreq->ipv6mr_multiaddr)) + break; + } + if (imm == NULL) { + /* Unable to resolve interface */ + error = EADDRNOTAVAIL; + break; + } + /* + * Give up the multicast address record to which the + * membership points. + */ + LIST_REMOVE(imm, i6mm_chain); + in6_delmulti(imm->i6mm_maddr); + free(imm, M_IPMADDR); + break; + + default: + error = EOPNOTSUPP; + break; + } + + /* + * If all options have default values, no need to keep the mbuf. + */ + if (im6o->im6o_multicast_ifp == NULL && + im6o->im6o_multicast_hlim == ip6_defmcasthlim && + im6o->im6o_multicast_loop == IPV6_DEFAULT_MULTICAST_LOOP && + im6o->im6o_memberships.lh_first == NULL) { + free(*im6op, M_IPMOPTS); + *im6op = NULL; + } + + return(error); +} + +/* + * Return the IP6 multicast options in response to user getsockopt(). + */ +static int +ip6_getmoptions(optname, im6o, mp) + int optname; + struct ip6_moptions *im6o; + struct mbuf **mp; +{ + u_int *hlim, *loop, *ifindex; + + *mp = m_get(M_TRYWAIT, MT_HEADER); /* XXX */ + + switch (optname) { + + case IPV6_MULTICAST_IF: + ifindex = mtod(*mp, u_int *); + (*mp)->m_len = sizeof(u_int); + if (im6o == NULL || im6o->im6o_multicast_ifp == NULL) + *ifindex = 0; + else + *ifindex = im6o->im6o_multicast_ifp->if_index; + return(0); + + case IPV6_MULTICAST_HOPS: + hlim = mtod(*mp, u_int *); + (*mp)->m_len = sizeof(u_int); + if (im6o == NULL) + *hlim = ip6_defmcasthlim; + else + *hlim = im6o->im6o_multicast_hlim; + return(0); + + case IPV6_MULTICAST_LOOP: + loop = mtod(*mp, u_int *); + (*mp)->m_len = sizeof(u_int); + if (im6o == NULL) + *loop = ip6_defmcasthlim; + else + *loop = im6o->im6o_multicast_loop; + return(0); + + default: + return(EOPNOTSUPP); + } +} + +/* + * Discard the IP6 multicast options. + */ +void +ip6_freemoptions(im6o) + struct ip6_moptions *im6o; +{ + struct in6_multi_mship *imm; + + if (im6o == NULL) + return; + + while ((imm = im6o->im6o_memberships.lh_first) != NULL) { + LIST_REMOVE(imm, i6mm_chain); + if (imm->i6mm_maddr) + in6_delmulti(imm->i6mm_maddr); + free(imm, M_IPMADDR); + } + free(im6o, M_IPMOPTS); +} + +/* + * Set IPv6 outgoing packet options based on advanced API. + */ +int +ip6_setpktoptions(control, opt, priv, needcopy) + struct mbuf *control; + struct ip6_pktopts *opt; + int priv, needcopy; +{ + struct cmsghdr *cm = 0; + + if (control == 0 || opt == 0) + return(EINVAL); + + init_ip6pktopts(opt); + + /* + * XXX: Currently, we assume all the optional information is stored + * in a single mbuf. + */ + if (control->m_next) + return(EINVAL); + + for (; control->m_len; control->m_data += CMSG_ALIGN(cm->cmsg_len), + control->m_len -= CMSG_ALIGN(cm->cmsg_len)) { + cm = mtod(control, struct cmsghdr *); + if (cm->cmsg_len == 0 || cm->cmsg_len > control->m_len) + return(EINVAL); + if (cm->cmsg_level != IPPROTO_IPV6) + continue; + + /* + * XXX should check if RFC2292 API is mixed with 2292bis API + */ + switch (cm->cmsg_type) { + case IPV6_PKTINFO: + if (cm->cmsg_len != CMSG_LEN(sizeof(struct in6_pktinfo))) + return(EINVAL); + if (needcopy) { + /* XXX: Is it really WAITOK? */ + opt->ip6po_pktinfo = + malloc(sizeof(struct in6_pktinfo), + M_IP6OPT, M_WAITOK); + bcopy(CMSG_DATA(cm), opt->ip6po_pktinfo, + sizeof(struct in6_pktinfo)); + } else + opt->ip6po_pktinfo = + (struct in6_pktinfo *)CMSG_DATA(cm); + if (opt->ip6po_pktinfo->ipi6_ifindex && + IN6_IS_ADDR_LINKLOCAL(&opt->ip6po_pktinfo->ipi6_addr)) + opt->ip6po_pktinfo->ipi6_addr.s6_addr16[1] = + htons(opt->ip6po_pktinfo->ipi6_ifindex); + + if (opt->ip6po_pktinfo->ipi6_ifindex > if_index + || opt->ip6po_pktinfo->ipi6_ifindex < 0) { + return(ENXIO); + } + + /* + * Check if the requested source address is indeed a + * unicast address assigned to the node, and can be + * used as the packet's source address. + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&opt->ip6po_pktinfo->ipi6_addr)) { + struct in6_ifaddr *ia6; + struct sockaddr_in6 sin6; + + bzero(&sin6, sizeof(sin6)); + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = + opt->ip6po_pktinfo->ipi6_addr; + ia6 = (struct in6_ifaddr *)ifa_ifwithaddr(sin6tosa(&sin6)); + if (ia6 == NULL || + (ia6->ia6_flags & (IN6_IFF_ANYCAST | + IN6_IFF_NOTREADY)) != 0) + return(EADDRNOTAVAIL); + } + break; + + case IPV6_HOPLIMIT: + if (cm->cmsg_len != CMSG_LEN(sizeof(int))) + return(EINVAL); + + opt->ip6po_hlim = *(int *)CMSG_DATA(cm); + if (opt->ip6po_hlim < -1 || opt->ip6po_hlim > 255) + return(EINVAL); + break; + + case IPV6_NEXTHOP: + if (!priv) + return(EPERM); + + if (cm->cmsg_len < sizeof(u_char) || + /* check if cmsg_len is large enough for sa_len */ + cm->cmsg_len < CMSG_LEN(*CMSG_DATA(cm))) + return(EINVAL); + + if (needcopy) { + opt->ip6po_nexthop = + malloc(*CMSG_DATA(cm), + M_IP6OPT, M_WAITOK); + bcopy(CMSG_DATA(cm), + opt->ip6po_nexthop, + *CMSG_DATA(cm)); + } else + opt->ip6po_nexthop = + (struct sockaddr *)CMSG_DATA(cm); + break; + + case IPV6_HOPOPTS: + { + struct ip6_hbh *hbh; + int hbhlen; + + if (cm->cmsg_len < CMSG_LEN(sizeof(struct ip6_hbh))) + return(EINVAL); + hbh = (struct ip6_hbh *)CMSG_DATA(cm); + hbhlen = (hbh->ip6h_len + 1) << 3; + if (cm->cmsg_len != CMSG_LEN(hbhlen)) + return(EINVAL); + + if (needcopy) { + opt->ip6po_hbh = + malloc(hbhlen, M_IP6OPT, M_WAITOK); + bcopy(hbh, opt->ip6po_hbh, hbhlen); + } else + opt->ip6po_hbh = hbh; + break; + } + + case IPV6_DSTOPTS: + { + struct ip6_dest *dest, **newdest; + int destlen; + + if (cm->cmsg_len < CMSG_LEN(sizeof(struct ip6_dest))) + return(EINVAL); + dest = (struct ip6_dest *)CMSG_DATA(cm); + destlen = (dest->ip6d_len + 1) << 3; + if (cm->cmsg_len != CMSG_LEN(destlen)) + return(EINVAL); + + /* + * The old advacned API is ambiguous on this + * point. Our approach is to determine the + * position based according to the existence + * of a routing header. Note, however, that + * this depends on the order of the extension + * headers in the ancillary data; the 1st part + * of the destination options header must + * appear before the routing header in the + * ancillary data, too. + * RFC2292bis solved the ambiguity by + * introducing separate cmsg types. + */ + if (opt->ip6po_rthdr == NULL) + newdest = &opt->ip6po_dest1; + else + newdest = &opt->ip6po_dest2; + + if (needcopy) { + *newdest = malloc(destlen, M_IP6OPT, M_WAITOK); + bcopy(dest, *newdest, destlen); + } else + *newdest = dest; + + break; + } + + case IPV6_RTHDR: + { + struct ip6_rthdr *rth; + int rthlen; + + if (cm->cmsg_len < CMSG_LEN(sizeof(struct ip6_rthdr))) + return(EINVAL); + rth = (struct ip6_rthdr *)CMSG_DATA(cm); + rthlen = (rth->ip6r_len + 1) << 3; + if (cm->cmsg_len != CMSG_LEN(rthlen)) + return(EINVAL); + + switch (rth->ip6r_type) { + case IPV6_RTHDR_TYPE_0: + /* must contain one addr */ + if (rth->ip6r_len == 0) + return(EINVAL); + /* length must be even */ + if (rth->ip6r_len % 2) + return(EINVAL); + if (rth->ip6r_len / 2 != rth->ip6r_segleft) + return(EINVAL); + break; + default: + return(EINVAL); /* not supported */ + } + + if (needcopy) { + opt->ip6po_rthdr = malloc(rthlen, M_IP6OPT, + M_WAITOK); + bcopy(rth, opt->ip6po_rthdr, rthlen); + } else + opt->ip6po_rthdr = rth; + + break; + } + + default: + return(ENOPROTOOPT); + } + } + + return(0); +} + +/* + * Routine called from ip6_output() to loop back a copy of an IP6 multicast + * packet to the input queue of a specified interface. Note that this + * calls the output routine of the loopback "driver", but with an interface + * pointer that might NOT be &loif -- easier than replicating that code here. + */ +void +ip6_mloopback(ifp, m, dst) + struct ifnet *ifp; + struct mbuf *m; + struct sockaddr_in6 *dst; +{ + struct mbuf *copym; + struct ip6_hdr *ip6; + + copym = m_copy(m, 0, M_COPYALL); + if (copym == NULL) + return; + + /* + * Make sure to deep-copy IPv6 header portion in case the data + * is in an mbuf cluster, so that we can safely override the IPv6 + * header portion later. + */ + if ((copym->m_flags & M_EXT) != 0 || + copym->m_len < sizeof(struct ip6_hdr)) { + copym = m_pullup(copym, sizeof(struct ip6_hdr)); + if (copym == NULL) + return; + } + +#ifdef DIAGNOSTIC + if (copym->m_len < sizeof(*ip6)) { + m_freem(copym); + return; + } +#endif + + ip6 = mtod(copym, struct ip6_hdr *); +#ifndef SCOPEDROUTING + /* + * clear embedded scope identifiers if necessary. + * in6_clearscope will touch the addresses only when necessary. + */ + in6_clearscope(&ip6->ip6_src); + in6_clearscope(&ip6->ip6_dst); +#endif + + (void)if_simloop(ifp, copym, dst->sin6_family, 0); +} + +/* + * Chop IPv6 header off from the payload. + */ +static int +ip6_splithdr(m, exthdrs) + struct mbuf *m; + struct ip6_exthdrs *exthdrs; +{ + struct mbuf *mh; + struct ip6_hdr *ip6; + + ip6 = mtod(m, struct ip6_hdr *); + if (m->m_len > sizeof(*ip6)) { + MGETHDR(mh, M_DONTWAIT, MT_HEADER); + if (mh == 0) { + m_freem(m); + return ENOBUFS; + } + M_MOVE_PKTHDR(mh, m); + MH_ALIGN(mh, sizeof(*ip6)); + m->m_len -= sizeof(*ip6); + m->m_data += sizeof(*ip6); + mh->m_next = m; + m = mh; + m->m_len = sizeof(*ip6); + bcopy((caddr_t)ip6, mtod(m, caddr_t), sizeof(*ip6)); + } + exthdrs->ip6e_ip6 = m; + return 0; +} + +/* + * Compute IPv6 extension header length. + */ +int +ip6_optlen(in6p) + struct in6pcb *in6p; +{ + int len; + + if (!in6p->in6p_outputopts) + return 0; + + len = 0; +#define elen(x) \ + (((struct ip6_ext *)(x)) ? (((struct ip6_ext *)(x))->ip6e_len + 1) << 3 : 0) + + len += elen(in6p->in6p_outputopts->ip6po_hbh); + if (in6p->in6p_outputopts->ip6po_rthdr) + /* dest1 is valid with rthdr only */ + len += elen(in6p->in6p_outputopts->ip6po_dest1); + len += elen(in6p->in6p_outputopts->ip6po_rthdr); + len += elen(in6p->in6p_outputopts->ip6po_dest2); + return len; +#undef elen +} diff --git a/sys/netinet6/ip6_var.h b/sys/netinet6/ip6_var.h new file mode 100644 index 0000000..7ffdf1e --- /dev/null +++ b/sys/netinet6/ip6_var.h @@ -0,0 +1,353 @@ +/* $FreeBSD$ */ +/* $KAME: ip6_var.h,v 1.62 2001/05/03 14:51:48 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)ip_var.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET6_IP6_VAR_H_ +#define _NETINET6_IP6_VAR_H_ + +/* + * IP6 reassembly queue structure. Each fragment + * being reassembled is attached to one of these structures. + */ +struct ip6q { + u_int32_t ip6q_head; + u_int16_t ip6q_len; + u_int8_t ip6q_nxt; /* ip6f_nxt in first fragment */ + u_int8_t ip6q_hlim; + struct ip6asfrag *ip6q_down; + struct ip6asfrag *ip6q_up; + u_int32_t ip6q_ident; + u_int8_t ip6q_arrive; + u_int8_t ip6q_ttl; + struct in6_addr ip6q_src, ip6q_dst; + struct ip6q *ip6q_next; + struct ip6q *ip6q_prev; + int ip6q_unfrglen; /* len of unfragmentable part */ +#ifdef notyet + u_char *ip6q_nxtp; +#endif +}; + +struct ip6asfrag { + u_int32_t ip6af_head; + u_int16_t ip6af_len; + u_int8_t ip6af_nxt; + u_int8_t ip6af_hlim; + /* must not override the above members during reassembling */ + struct ip6asfrag *ip6af_down; + struct ip6asfrag *ip6af_up; + struct mbuf *ip6af_m; + int ip6af_offset; /* offset in ip6af_m to next header */ + int ip6af_frglen; /* fragmentable part length */ + int ip6af_off; /* fragment offset */ + u_int16_t ip6af_mff; /* more fragment bit in frag off */ +}; + +#define IP6_REASS_MBUF(ip6af) (*(struct mbuf **)&((ip6af)->ip6af_m)) + +struct ip6_moptions { + struct ifnet *im6o_multicast_ifp; /* ifp for outgoing multicasts */ + u_char im6o_multicast_hlim; /* hoplimit for outgoing multicasts */ + u_char im6o_multicast_loop; /* 1 >= hear sends if a member */ + LIST_HEAD(, in6_multi_mship) im6o_memberships; +}; + +/* + * Control options for outgoing packets + */ + +/* Routing header related info */ +struct ip6po_rhinfo { + struct ip6_rthdr *ip6po_rhi_rthdr; /* Routing header */ + struct route_in6 ip6po_rhi_route; /* Route to the 1st hop */ +}; +#define ip6po_rthdr ip6po_rhinfo.ip6po_rhi_rthdr +#define ip6po_route ip6po_rhinfo.ip6po_rhi_route + +struct ip6_pktopts { + struct mbuf *ip6po_m; /* Pointer to mbuf storing the data */ + int ip6po_hlim; /* Hoplimit for outgoing packets */ + + /* Outgoing IF/address information */ + struct in6_pktinfo *ip6po_pktinfo; + + struct sockaddr *ip6po_nexthop; /* Next-hop address */ + + struct ip6_hbh *ip6po_hbh; /* Hop-by-Hop options header */ + + /* Destination options header (before a routing header) */ + struct ip6_dest *ip6po_dest1; + + /* Routing header related info. */ + struct ip6po_rhinfo ip6po_rhinfo; + + /* Destination options header (after a routing header) */ + struct ip6_dest *ip6po_dest2; +}; + +/* + * Control options for incoming packets + */ + +struct ip6stat { + u_quad_t ip6s_total; /* total packets received */ + u_quad_t ip6s_tooshort; /* packet too short */ + u_quad_t ip6s_toosmall; /* not enough data */ + u_quad_t ip6s_fragments; /* fragments received */ + u_quad_t ip6s_fragdropped; /* frags dropped(dups, out of space) */ + u_quad_t ip6s_fragtimeout; /* fragments timed out */ + u_quad_t ip6s_fragoverflow; /* fragments that exceeded limit */ + u_quad_t ip6s_forward; /* packets forwarded */ + u_quad_t ip6s_cantforward; /* packets rcvd for unreachable dest */ + u_quad_t ip6s_redirectsent; /* packets forwarded on same net */ + u_quad_t ip6s_delivered; /* datagrams delivered to upper level*/ + u_quad_t ip6s_localout; /* total ip packets generated here */ + u_quad_t ip6s_odropped; /* lost packets due to nobufs, etc. */ + u_quad_t ip6s_reassembled; /* total packets reassembled ok */ + u_quad_t ip6s_fragmented; /* datagrams sucessfully fragmented */ + u_quad_t ip6s_ofragments; /* output fragments created */ + u_quad_t ip6s_cantfrag; /* don't fragment flag was set, etc. */ + u_quad_t ip6s_badoptions; /* error in option processing */ + u_quad_t ip6s_noroute; /* packets discarded due to no route */ + u_quad_t ip6s_badvers; /* ip6 version != 6 */ + u_quad_t ip6s_rawout; /* total raw ip packets generated */ + u_quad_t ip6s_badscope; /* scope error */ + u_quad_t ip6s_notmember; /* don't join this multicast group */ + u_quad_t ip6s_nxthist[256]; /* next header history */ + u_quad_t ip6s_m1; /* one mbuf */ + u_quad_t ip6s_m2m[32]; /* two or more mbuf */ + u_quad_t ip6s_mext1; /* one ext mbuf */ + u_quad_t ip6s_mext2m; /* two or more ext mbuf */ + u_quad_t ip6s_exthdrtoolong; /* ext hdr are not continuous */ + u_quad_t ip6s_nogif; /* no match gif found */ + u_quad_t ip6s_toomanyhdr; /* discarded due to too many headers */ + + /* + * statistics for improvement of the source address selection + * algorithm: + * XXX: hardcoded 16 = # of ip6 multicast scope types + 1 + */ + /* number of times that address selection fails */ + u_quad_t ip6s_sources_none; + /* number of times that an address on the outgoing I/F is chosen */ + u_quad_t ip6s_sources_sameif[16]; + /* number of times that an address on a non-outgoing I/F is chosen */ + u_quad_t ip6s_sources_otherif[16]; + /* + * number of times that an address that has the same scope + * from the destination is chosen. + */ + u_quad_t ip6s_sources_samescope[16]; + /* + * number of times that an address that has a different scope + * from the destination is chosen. + */ + u_quad_t ip6s_sources_otherscope[16]; + /* number of times that a deprecated address is chosen */ + u_quad_t ip6s_sources_deprecated[16]; + + u_quad_t ip6s_forward_cachehit; + u_quad_t ip6s_forward_cachemiss; +}; + +#ifdef _KERNEL +/* + * IPv6 onion peeling state. + * it will be initialized when we come into ip6_input(). + * XXX do not make it a kitchen sink! + */ +struct ip6aux { + u_int32_t ip6a_flags; +#define IP6A_SWAP 0x01 /* swapped home/care-of on packet */ +#define IP6A_HASEEN 0x02 /* HA was present */ +#define IP6A_BRUID 0x04 /* BR Unique Identifier was present */ +#define IP6A_RTALERTSEEN 0x08 /* rtalert present */ + + /* ip6.ip6_src */ + struct in6_addr ip6a_careof; /* care-of address of the peer */ + struct in6_addr ip6a_home; /* home address of the peer */ + u_int16_t ip6a_bruid; /* BR unique identifier */ + + /* ip6.ip6_dst */ + struct in6_ifaddr *ip6a_dstia6; /* my ifaddr that matches ip6_dst */ + + /* rtalert */ + u_int16_t ip6a_rtalert; /* rtalert option value */ + + /* + * decapsulation history will be here. + * with IPsec it may not be accurate. + */ +}; +#endif + +#ifdef _KERNEL +/* flags passed to ip6_output as last parameter */ +#define IPV6_DADOUTPUT 0x01 /* DAD */ +#define IPV6_FORWARDING 0x02 /* most of IPv6 header exists */ +#define IPV6_MINMTU 0x04 /* use minimum MTU (IPV6_USE_MIN_MTU) */ + +extern struct ip6stat ip6stat; /* statistics */ +extern u_int32_t ip6_id; /* fragment identifier */ +extern int ip6_defhlim; /* default hop limit */ +extern int ip6_defmcasthlim; /* default multicast hop limit */ +extern int ip6_forwarding; /* act as router? */ +extern int ip6_forward_srcrt; /* forward src-routed? */ +extern int ip6_gif_hlim; /* Hop limit for gif encap packet */ +extern int ip6_use_deprecated; /* allow deprecated addr as source */ +extern int ip6_rr_prune; /* router renumbering prefix + * walk list every 5 sec. */ +extern int ip6_v6only; + +extern struct socket *ip6_mrouter; /* multicast routing daemon */ +extern int ip6_sendredirects; /* send IP redirects when forwarding? */ +extern int ip6_maxfragpackets; /* Maximum packets in reassembly queue */ +extern int ip6_sourcecheck; /* Verify source interface */ +extern int ip6_sourcecheck_interval; /* Interval between log messages */ +extern int ip6_accept_rtadv; /* Acts as a host not a router */ +extern int ip6_keepfaith; /* Firewall Aided Internet Translator */ +extern int ip6_log_interval; +extern time_t ip6_log_time; +extern int ip6_hdrnestlimit; /* upper limit of # of extension headers */ +extern int ip6_dad_count; /* DupAddrDetectionTransmits */ + +extern u_int32_t ip6_flow_seq; +extern int ip6_auto_flowlabel; +extern int ip6_auto_linklocal; + +extern int ip6_anonportmin; /* minimum ephemeral port */ +extern int ip6_anonportmax; /* maximum ephemeral port */ +extern int ip6_lowportmin; /* minimum reserved port */ +extern int ip6_lowportmax; /* maximum reserved port */ + +extern int ip6_use_tempaddr; /* whether to use temporary addresses. */ + +extern struct pr_usrreqs rip6_usrreqs; +struct sockopt; + +struct inpcb; + +int icmp6_ctloutput __P((struct socket *, struct sockopt *sopt)); + +struct in6_ifaddr; +void ip6_init __P((void)); +void ip6_input __P((struct mbuf *)); +struct in6_ifaddr *ip6_getdstifaddr __P((struct mbuf *)); +void ip6_freepcbopts __P((struct ip6_pktopts *)); +void ip6_freemoptions __P((struct ip6_moptions *)); +int ip6_unknown_opt __P((u_int8_t *, struct mbuf *, int)); +char * ip6_get_prevhdr __P((struct mbuf *, int)); +int ip6_nexthdr __P((struct mbuf *, int, int, int *)); +int ip6_lasthdr __P((struct mbuf *, int, int, int *)); + +struct ip6aux *ip6_addaux __P((struct mbuf *)); +struct ip6aux *ip6_findaux __P((struct mbuf *)); +void ip6_delaux __P((struct mbuf *)); + +int ip6_mforward __P((struct ip6_hdr *, struct ifnet *, struct mbuf *)); +int ip6_process_hopopts __P((struct mbuf *, u_int8_t *, int, u_int32_t *, + u_int32_t *)); +void ip6_savecontrol __P((struct inpcb *, struct mbuf **, struct ip6_hdr *, + struct mbuf *)); +void ip6_notify_pmtu __P((struct inpcb *, struct sockaddr_in6 *, + u_int32_t *)); +int ip6_sysctl __P((int *, u_int, void *, size_t *, void *, size_t)); + +void ip6_forward __P((struct mbuf *, int)); + +void ip6_mloopback __P((struct ifnet *, struct mbuf *, struct sockaddr_in6 *)); +int ip6_output __P((struct mbuf *, struct ip6_pktopts *, + struct route_in6 *, + int, + struct ip6_moptions *, struct ifnet **, + struct inpcb *)); +int ip6_ctloutput __P((struct socket *, struct sockopt *sopt)); +void init_ip6pktopts __P((struct ip6_pktopts *)); +int ip6_setpktoptions __P((struct mbuf *, struct ip6_pktopts *, int, int)); +void ip6_clearpktopts __P((struct ip6_pktopts *, int, int)); +struct ip6_pktopts *ip6_copypktopts __P((struct ip6_pktopts *, int)); +int ip6_optlen __P((struct inpcb *)); + +int route6_input __P((struct mbuf **, int *, int)); + +void frag6_init __P((void)); +int frag6_input __P((struct mbuf **, int *, int)); +void frag6_slowtimo __P((void)); +void frag6_drain __P((void)); + +void rip6_init __P((void)); +int rip6_input __P((struct mbuf **mp, int *offp, int proto)); +void rip6_ctlinput __P((int, struct sockaddr *, void *)); +int rip6_ctloutput __P((struct socket *so, struct sockopt *sopt)); +int rip6_output __P((struct mbuf *, ...)); +int rip6_usrreq __P((struct socket *, + int, struct mbuf *, struct mbuf *, struct mbuf *, struct thread *)); + +int dest6_input __P((struct mbuf **, int *, int)); +int none_input __P((struct mbuf **, int *, int)); +#endif /* _KERNEL */ + +#endif /* !_NETINET6_IP6_VAR_H_ */ diff --git a/sys/netinet6/ip6protosw.h b/sys/netinet6/ip6protosw.h new file mode 100644 index 0000000..9730121 --- /dev/null +++ b/sys/netinet6/ip6protosw.h @@ -0,0 +1,163 @@ +/* $FreeBSD$ */ +/* $KAME: ip6protosw.h,v 1.25 2001/09/26 06:13:03 keiichi Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + */ + +/* BSDI protosw.h,v 2.3 1996/10/11 16:02:40 pjd Exp */ + +/*- + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)protosw.h 8.1 (Berkeley) 6/2/93 + */ + +#ifndef _NETINET6_IP6PROTOSW_H_ +#define _NETINET6_IP6PROTOSW_H_ + +/* + * For pfil_head structure. + */ +#include <net/pfil.h> + +/* + * Protocol switch table for IPv6. + * All other definitions should refer to sys/protosw.h + */ + +struct mbuf; +struct sockaddr; +struct socket; +struct domain; +struct thread; +struct ip6_hdr; +struct icmp6_hdr; +struct in6_addr; +struct pr_usrreqs; + +/* + * argument type for the last arg of pr_ctlinput(). + * should be consulted only with AF_INET6 family. + * + * IPv6 ICMP IPv6 [exthdrs] finalhdr paylaod + * ^ ^ ^ ^ + * | | ip6c_ip6 ip6c_off + * | ip6c_icmp6 + * ip6c_m + * + * ip6c_finaldst usually points to ip6c_ip6->ip6_dst. if the original + * (internal) packet carries a routing header, it may point the final + * dstination address in the routing header. + * + * ip6c_src: ip6c_ip6->ip6_src + scope info + flowlabel in ip6c_ip6 + * (beware of flowlabel, if you try to compare it against others) + * ip6c_dst: ip6c_finaldst + scope info + */ +struct ip6ctlparam { + struct mbuf *ip6c_m; /* start of mbuf chain */ + struct icmp6_hdr *ip6c_icmp6; /* icmp6 header of target packet */ + struct ip6_hdr *ip6c_ip6; /* ip6 header of target packet */ + int ip6c_off; /* offset of the target proto header */ + struct sockaddr_in6 *ip6c_src; /* srcaddr w/ additional info */ + struct sockaddr_in6 *ip6c_dst; /* (final) dstaddr w/ additional info */ + struct in6_addr *ip6c_finaldst; /* final destination address */ + void *ip6c_cmdarg; /* control command dependent data */ + u_int8_t ip6c_nxt; /* final next header field */ +}; + +struct ip6protosw { + short pr_type; /* socket type used for */ + struct domain *pr_domain; /* domain protocol a member of */ + short pr_protocol; /* protocol number */ + short pr_flags; /* see below */ + +/* protocol-protocol hooks */ + int (*pr_input) /* input to protocol (from below) */ + __P((struct mbuf **, int *, int)); + int (*pr_output) /* output to protocol (from above) */ + __P((struct mbuf *, ...)); + void (*pr_ctlinput) /* control input (from below) */ + __P((int, struct sockaddr *, void *)); + int (*pr_ctloutput) /* control output (from above) */ + __P((struct socket *, struct sockopt *)); + +/* user-protocol hook */ + int (*pr_usrreq) /* user request: see list below */ + __P((struct socket *, int, struct mbuf *, + struct mbuf *, struct mbuf *, struct thread *)); + +/* utility hooks */ + void (*pr_init) /* initialization hook */ + __P((void)); + + void (*pr_fasttimo) /* fast timeout (200ms) */ + __P((void)); + void (*pr_slowtimo) /* slow timeout (500ms) */ + __P((void)); + void (*pr_drain) /* flush any excess space possible */ + __P((void)); + struct pr_usrreqs *pr_usrreqs; /* supersedes pr_usrreq() */ + struct pfil_head pr_pfh; +}; + +#ifdef _KERNEL +extern struct ip6protosw inet6sw[]; +#endif + +#endif /* !_NETINET6_IP6PROTOSW_H_ */ diff --git a/sys/netinet6/ipcomp.h b/sys/netinet6/ipcomp.h new file mode 100644 index 0000000..ed58768 --- /dev/null +++ b/sys/netinet6/ipcomp.h @@ -0,0 +1,71 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp.h,v 1.11 2001/09/04 08:43:19 itojun Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#ifndef _NETINET6_IPCOMP_H_ +#define _NETINET6_IPCOMP_H_ + +#if defined(_KERNEL) && !defined(_LKM) +#include "opt_inet.h" +#endif + +struct ipcomp { + u_int8_t comp_nxt; /* Next Header */ + u_int8_t comp_flags; /* reserved, must be zero */ + u_int16_t comp_cpi; /* Compression parameter index */ +}; + +/* well-known algorithm number (in CPI), from RFC2409 */ +#define IPCOMP_OUI 1 /* vendor specific */ +#define IPCOMP_DEFLATE 2 /* RFC2394 */ +#define IPCOMP_LZS 3 /* RFC2395 */ +#define IPCOMP_MAX 4 + +#define IPCOMP_CPI_NEGOTIATE_MIN 256 + +#ifdef _KERNEL +struct ipcomp_algorithm { + int (*compress) __P((struct mbuf *, struct mbuf *, size_t *)); + int (*decompress) __P((struct mbuf *, struct mbuf *, size_t *)); + size_t minplen; /* minimum required length for compression */ +}; + +struct ipsecrequest; +extern const struct ipcomp_algorithm *ipcomp_algorithm_lookup __P((int)); +extern void ipcomp4_input __P((struct mbuf *, int)); +extern int ipcomp4_output __P((struct mbuf *, struct ipsecrequest *)); +#endif /* KERNEL */ + +#endif /* _NETINET6_IPCOMP_H_ */ diff --git a/sys/netinet6/ipcomp6.h b/sys/netinet6/ipcomp6.h new file mode 100644 index 0000000..4a1046a --- /dev/null +++ b/sys/netinet6/ipcomp6.h @@ -0,0 +1,46 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp.h,v 1.8 2000/09/26 07:55:14 itojun Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#ifndef _NETINET6_IPCOMP6_H_ +#define _NETINET6_IPCOMP6_H_ + +#ifdef _KERNEL +extern int ipcomp6_input __P((struct mbuf **, int *, int)); +extern int ipcomp6_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *)); +#endif /*KERNEL*/ + +#endif /*_NETINET6_IPCOMP6_H_*/ diff --git a/sys/netinet6/ipcomp_core.c b/sys/netinet6/ipcomp_core.c new file mode 100644 index 0000000..a6cdea5 --- /dev/null +++ b/sys/netinet6/ipcomp_core.c @@ -0,0 +1,358 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp_core.c,v 1.25 2001/07/26 06:53:17 jinmei Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> +#include <sys/queue.h> + +#include <net/if.h> +#include <net/route.h> +#include <netinet/in.h> +#include <net/netisr.h> +#include <net/zlib.h> +#include <machine/cpu.h> + +#include <netinet6/ipcomp.h> +#ifdef INET6 +#include <netinet6/ipcomp6.h> +#endif +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif + +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +static void *deflate_alloc __P((void *, u_int, u_int)); +static void deflate_free __P((void *, void *)); +static int deflate_common __P((struct mbuf *, struct mbuf *, size_t *, int)); +static int deflate_compress __P((struct mbuf *, struct mbuf *, size_t *)); +static int deflate_decompress __P((struct mbuf *, struct mbuf *, size_t *)); + +/* + * We need to use default window size (2^15 = 32Kbytes as of writing) for + * inbound case. Otherwise we get interop problem. + * Use negative value to avoid Adler32 checksum. This is an undocumented + * feature in zlib (see ipsec wg mailing list archive in January 2000). + */ +static int deflate_policy = Z_DEFAULT_COMPRESSION; +static int deflate_window_out = -12; +static const int deflate_window_in = -1 * MAX_WBITS; /* don't change it */ +static int deflate_memlevel = MAX_MEM_LEVEL; + +static const struct ipcomp_algorithm ipcomp_algorithms[] = { + { deflate_compress, deflate_decompress, 90 }, +}; + +const struct ipcomp_algorithm * +ipcomp_algorithm_lookup(idx) + int idx; +{ + + if (idx == SADB_X_CALG_DEFLATE) + return &ipcomp_algorithms[0]; + return NULL; +} + +static void * +deflate_alloc(aux, items, siz) + void *aux; + u_int items; + u_int siz; +{ + void *ptr; + ptr = malloc(items * siz, M_TEMP, M_NOWAIT); + return ptr; +} + +static void +deflate_free(aux, ptr) + void *aux; + void *ptr; +{ + free(ptr, M_TEMP); +} + +static int +deflate_common(m, md, lenp, mode) + struct mbuf *m; + struct mbuf *md; + size_t *lenp; + int mode; /* 0: compress 1: decompress */ +{ + struct mbuf *mprev; + struct mbuf *p; + struct mbuf *n = NULL, *n0 = NULL, **np; + z_stream zs; + int error = 0; + int zerror; + size_t offset; + +#define MOREBLOCK() \ +do { \ + /* keep the reply buffer into our chain */ \ + if (n) { \ + n->m_len = zs.total_out - offset; \ + offset = zs.total_out; \ + *np = n; \ + np = &n->m_next; \ + n = NULL; \ + } \ + \ + /* get a fresh reply buffer */ \ + MGET(n, M_DONTWAIT, MT_DATA); \ + if (n) { \ + MCLGET(n, M_DONTWAIT); \ + } \ + if (!n) { \ + error = ENOBUFS; \ + goto fail; \ + } \ + n->m_len = 0; \ + n->m_len = M_TRAILINGSPACE(n); \ + n->m_next = NULL; \ + /* \ + * if this is the first reply buffer, reserve \ + * region for ipcomp header. \ + */ \ + if (*np == NULL) { \ + n->m_len -= sizeof(struct ipcomp); \ + n->m_data += sizeof(struct ipcomp); \ + } \ + \ + zs.next_out = mtod(n, u_int8_t *); \ + zs.avail_out = n->m_len; \ +} while (0) + + for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) + ; + if (!mprev) + panic("md is not in m in deflate_common"); + + bzero(&zs, sizeof(zs)); + zs.zalloc = deflate_alloc; + zs.zfree = deflate_free; + + zerror = mode ? inflateInit2(&zs, deflate_window_in) + : deflateInit2(&zs, deflate_policy, Z_DEFLATED, + deflate_window_out, deflate_memlevel, + Z_DEFAULT_STRATEGY); + if (zerror != Z_OK) { + error = ENOBUFS; + goto fail; + } + + n0 = n = NULL; + np = &n0; + offset = 0; + zerror = 0; + p = md; + while (p && p->m_len == 0) { + p = p->m_next; + } + + /* input stream and output stream are available */ + while (p && zs.avail_in == 0) { + /* get input buffer */ + if (p && zs.avail_in == 0) { + zs.next_in = mtod(p, u_int8_t *); + zs.avail_in = p->m_len; + p = p->m_next; + while (p && p->m_len == 0) { + p = p->m_next; + } + } + + /* get output buffer */ + if (zs.next_out == NULL || zs.avail_out == 0) { + MOREBLOCK(); + } + + zerror = mode ? inflate(&zs, Z_NO_FLUSH) + : deflate(&zs, Z_NO_FLUSH); + + if (zerror == Z_STREAM_END) + ; /* once more. */ + else if (zerror == Z_OK) { + /* inflate: Z_OK can indicate the end of decode */ + if (mode && !p && zs.avail_out != 0) + goto terminate; + else + ; /* once more. */ + } else { + if (zs.msg) { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflate(Z_NO_FLUSH): %s\n", + mode ? "de" : "", mode ? "in" : "de", + zs.msg)); + } else { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflate(Z_NO_FLUSH): unknown error (%d)\n", + mode ? "de" : "", mode ? "in" : "de", + zerror)); + } + mode ? inflateEnd(&zs) : deflateEnd(&zs); + error = EINVAL; + goto fail; + } + } + + if (zerror == Z_STREAM_END) + goto terminate; + + /* termination */ + while (1) { + /* get output buffer */ + if (zs.next_out == NULL || zs.avail_out == 0) { + MOREBLOCK(); + } + + zerror = mode ? inflate(&zs, Z_SYNC_FLUSH) + : deflate(&zs, Z_FINISH); + + if (zerror == Z_STREAM_END) + break; + else if (zerror == Z_OK) { + if (mode && zs.avail_out != 0) + goto terminate; + else + ; /* once more. */ + } else { + if (zs.msg) { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflate(Z_FINISH): %s\n", + mode ? "de" : "", mode ? "in" : "de", + zs.msg)); + } else { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflate(Z_FINISH): unknown error (%d)\n", + mode ? "de" : "", mode ? "in" : "de", + zerror)); + } + mode ? inflateEnd(&zs) : deflateEnd(&zs); + error = EINVAL; + goto fail; + } + } + +terminate: + zerror = mode ? inflateEnd(&zs) : deflateEnd(&zs); + if (zerror != Z_OK) { + if (zs.msg) { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflateEnd: %s\n", + mode ? "de" : "", mode ? "in" : "de", + zs.msg)); + } else { + ipseclog((LOG_ERR, "ipcomp_%scompress: " + "%sflateEnd: unknown error (%d)\n", + mode ? "de" : "", mode ? "in" : "de", + zerror)); + } + error = EINVAL; + goto fail; + } + /* keep the final reply buffer into our chain */ + if (n) { + n->m_len = zs.total_out - offset; + offset = zs.total_out; + *np = n; + np = &n->m_next; + n = NULL; + } + + /* switch the mbuf to the new one */ + mprev->m_next = n0; + m_freem(md); + *lenp = zs.total_out; + + return 0; + +fail: + if (m) + m_freem(m); + if (n) + m_freem(n); + if (n0) + m_freem(n0); + return error; +#undef MOREBLOCK +} + +static int +deflate_compress(m, md, lenp) + struct mbuf *m; + struct mbuf *md; + size_t *lenp; +{ + if (!m) + panic("m == NULL in deflate_compress"); + if (!md) + panic("md == NULL in deflate_compress"); + if (!lenp) + panic("lenp == NULL in deflate_compress"); + + return deflate_common(m, md, lenp, 0); +} + +static int +deflate_decompress(m, md, lenp) + struct mbuf *m; + struct mbuf *md; + size_t *lenp; +{ + if (!m) + panic("m == NULL in deflate_decompress"); + if (!md) + panic("md == NULL in deflate_decompress"); + if (!lenp) + panic("lenp == NULL in deflate_decompress"); + + return deflate_common(m, md, lenp, 1); +} diff --git a/sys/netinet6/ipcomp_input.c b/sys/netinet6/ipcomp_input.c new file mode 100644 index 0000000..65be602 --- /dev/null +++ b/sys/netinet6/ipcomp_input.c @@ -0,0 +1,349 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp_input.c,v 1.25 2001/03/01 09:12:09 itojun Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <net/zlib.h> +#include <machine/cpu.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_ecn.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#endif +#include <netinet6/ipcomp.h> +#ifdef INET6 +#include <netinet6/ipcomp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> + +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +#define IPLEN_FLIPPED + +#ifdef INET +extern struct protosw inetsw[]; + +void +ipcomp4_input(m, off) + struct mbuf *m; + int off; +{ + struct mbuf *md; + struct ip *ip; + struct ipcomp *ipcomp; + const struct ipcomp_algorithm *algo; + u_int16_t cpi; /* host order */ + u_int16_t nxt; + size_t hlen; + int error; + size_t newlen, olen; + struct secasvar *sav = NULL; + int proto; + + if (m->m_pkthdr.len < off + sizeof(struct ipcomp)) { + ipseclog((LOG_DEBUG, "IPv4 IPComp input: assumption failed " + "(packet too short)\n")); + ipsecstat.in_inval++; + goto fail; + } + + md = m_pulldown(m, off, sizeof(*ipcomp), NULL); + if (!m) { + m = NULL; /* already freed */ + ipseclog((LOG_DEBUG, "IPv4 IPComp input: assumption failed " + "(pulldown failure)\n")); + ipsecstat.in_inval++; + goto fail; + } + ipcomp = mtod(md, struct ipcomp *); + ip = mtod(m, struct ip *); + proto = ip->ip_p; + nxt = ipcomp->comp_nxt; +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + + cpi = ntohs(ipcomp->comp_cpi); + + if (cpi >= IPCOMP_CPI_NEGOTIATE_MIN) { + sav = key_allocsa(AF_INET, (caddr_t)&ip->ip_src, + (caddr_t)&ip->ip_dst, IPPROTO_IPCOMP, htonl(cpi)); + if (sav != NULL + && (sav->state == SADB_SASTATE_MATURE + || sav->state == SADB_SASTATE_DYING)) { + cpi = sav->alg_enc; /* XXX */ + /* other parameters to look at? */ + } + } + algo = ipcomp_algorithm_lookup(cpi); + if (!algo) { + ipseclog((LOG_WARNING, "IPv4 IPComp input: unknown cpi %u\n", + cpi)); + ipsecstat.in_nosa++; + goto fail; + } + + /* chop ipcomp header */ + ipcomp = NULL; + md->m_data += sizeof(struct ipcomp); + md->m_len -= sizeof(struct ipcomp); + m->m_pkthdr.len -= sizeof(struct ipcomp); +#ifdef IPLEN_FLIPPED + ip->ip_len -= sizeof(struct ipcomp); +#else + ip->ip_len = htons(ntohs(ip->ip_len) - sizeof(struct ipcomp)); +#endif + + olen = m->m_pkthdr.len; + newlen = m->m_pkthdr.len - off; + error = (*algo->decompress)(m, m->m_next, &newlen); + if (error != 0) { + if (error == EINVAL) + ipsecstat.in_inval++; + else if (error == ENOBUFS) + ipsecstat.in_nomem++; + m = NULL; + goto fail; + } + ipsecstat.in_comphist[cpi]++; + + /* + * returning decompressed packet onto icmp is meaningless. + * mark it decrypted to prevent icmp from attaching original packet. + */ + m->m_flags |= M_DECRYPTED; + + m->m_pkthdr.len = off + newlen; + ip = mtod(m, struct ip *); + { + size_t len; +#ifdef IPLEN_FLIPPED + len = ip->ip_len; +#else + len = ntohs(ip->ip_len); +#endif + /* + * be careful about underflow. also, do not assign exact value + * as ip_len is manipulated differently on *BSDs. + */ + len += m->m_pkthdr.len; + len -= olen; + if (len & ~0xffff) { + /* packet too big after decompress */ + ipsecstat.in_inval++; + goto fail; + } +#ifdef IPLEN_FLIPPED + ip->ip_len = len & 0xffff; +#else + ip->ip_len = htons(len & 0xffff); +#endif + ip->ip_p = nxt; + } + + if (sav) { + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_IPCOMP, (u_int32_t)cpi) != 0) { + ipsecstat.in_nomem++; + goto fail; + } + key_freesav(sav); + sav = NULL; + } + + if (nxt != IPPROTO_DONE) { + if ((inetsw[ip_protox[nxt]].pr_flags & PR_LASTHDR) != 0 && + ipsec4_in_reject(m, NULL)) { + ipsecstat.in_polvio++; + goto fail; + } + (*inetsw[ip_protox[nxt]].pr_input)(m, off); + } else + m_freem(m); + m = NULL; + + ipsecstat.in_success++; + return; + +fail: + if (sav) + key_freesav(sav); + if (m) + m_freem(m); + return; +} +#endif /* INET */ + +#ifdef INET6 +int +ipcomp6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m, *md; + int off; + struct ip6_hdr *ip6; + struct ipcomp *ipcomp; + const struct ipcomp_algorithm *algo; + u_int16_t cpi; /* host order */ + u_int16_t nxt; + int error; + size_t newlen; + struct secasvar *sav = NULL; + char *prvnxtp; + + m = *mp; + off = *offp; + + md = m_pulldown(m, off, sizeof(*ipcomp), NULL); + if (!m) { + m = NULL; /* already freed */ + ipseclog((LOG_DEBUG, "IPv6 IPComp input: assumption failed " + "(pulldown failure)\n")); + ipsec6stat.in_inval++; + goto fail; + } + ipcomp = mtod(md, struct ipcomp *); + ip6 = mtod(m, struct ip6_hdr *); + nxt = ipcomp->comp_nxt; + + cpi = ntohs(ipcomp->comp_cpi); + + if (cpi >= IPCOMP_CPI_NEGOTIATE_MIN) { + sav = key_allocsa(AF_INET6, (caddr_t)&ip6->ip6_src, + (caddr_t)&ip6->ip6_dst, IPPROTO_IPCOMP, htonl(cpi)); + if (sav != NULL + && (sav->state == SADB_SASTATE_MATURE + || sav->state == SADB_SASTATE_DYING)) { + cpi = sav->alg_enc; /* XXX */ + /* other parameters to look at? */ + } + } + algo = ipcomp_algorithm_lookup(cpi); + if (!algo) { + ipseclog((LOG_WARNING, "IPv6 IPComp input: unknown cpi %u; " + "dropping the packet for simplicity\n", cpi)); + ipsec6stat.in_nosa++; + goto fail; + } + + /* chop ipcomp header */ + ipcomp = NULL; + md->m_data += sizeof(struct ipcomp); + md->m_len -= sizeof(struct ipcomp); + m->m_pkthdr.len -= sizeof(struct ipcomp); + + newlen = m->m_pkthdr.len - off; + error = (*algo->decompress)(m, md, &newlen); + if (error != 0) { + if (error == EINVAL) + ipsec6stat.in_inval++; + else if (error == ENOBUFS) + ipsec6stat.in_nomem++; + m = NULL; + goto fail; + } + ipsec6stat.in_comphist[cpi]++; + m->m_pkthdr.len = off + newlen; + + /* + * returning decompressed packet onto icmp is meaningless. + * mark it decrypted to prevent icmp from attaching original packet. + */ + m->m_flags |= M_DECRYPTED; + + /* update next header field */ + prvnxtp = ip6_get_prevhdr(m, off); + *prvnxtp = nxt; + + /* + * no need to adjust payload length, as all the IPv6 protocols + * look at m->m_pkthdr.len + */ + + if (sav) { + key_sa_recordxfer(sav, m); + if (ipsec_addhist(m, IPPROTO_IPCOMP, (u_int32_t)cpi) != 0) { + ipsec6stat.in_nomem++; + goto fail; + } + key_freesav(sav); + sav = NULL; + } + *offp = off; + *mp = m; + ipsec6stat.in_success++; + return nxt; + +fail: + if (m) + m_freem(m); + if (sav) + key_freesav(sav); + return IPPROTO_DONE; +} +#endif /* INET6 */ diff --git a/sys/netinet6/ipcomp_output.c b/sys/netinet6/ipcomp_output.c new file mode 100644 index 0000000..f07e06c --- /dev/null +++ b/sys/netinet6/ipcomp_output.c @@ -0,0 +1,384 @@ +/* $FreeBSD$ */ +/* $KAME: ipcomp_output.c,v 1.25 2002/06/09 14:44:00 itojun Exp $ */ + +/* + * Copyright (C) 1999 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * RFC2393 IP payload compression protocol (IPComp). + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/netisr.h> +#include <net/zlib.h> +#include <machine/cpu.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_ecn.h> + +#ifdef INET6 +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#endif +#include <netinet6/ipcomp.h> +#ifdef INET6 +#include <netinet6/ipcomp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> + +#include <machine/stdarg.h> + +#include <net/net_osdep.h> + +static int ipcomp_output __P((struct mbuf *, u_char *, struct mbuf *, + struct ipsecrequest *, int)); + +/* + * Modify the packet so that the payload is compressed. + * The mbuf (m) must start with IPv4 or IPv6 header. + * On failure, free the given mbuf and return non-zero. + * + * on invocation: + * m nexthdrp md + * v v v + * IP ......... payload + * during the encryption: + * m nexthdrp mprev md + * v v v v + * IP ............... ipcomp payload + * <-----><-----> + * complen plen + * <-> hlen + * <-----------------> compoff + */ +static int +ipcomp_output(m, nexthdrp, md, isr, af) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; + int af; +{ + struct mbuf *n; + struct mbuf *md0; + struct mbuf *mcopy; + struct mbuf *mprev; + struct ipcomp *ipcomp; + struct secasvar *sav = isr->sav; + const struct ipcomp_algorithm *algo; + u_int16_t cpi; /* host order */ + size_t plen0, plen; /* payload length to be compressed */ + size_t compoff; + int afnumber; + int error = 0; + struct ipsecstat *stat; + + switch (af) { +#ifdef INET + case AF_INET: + afnumber = 4; + stat = &ipsecstat; + break; +#endif +#ifdef INET6 + case AF_INET6: + afnumber = 6; + stat = &ipsec6stat; + break; +#endif + default: + ipseclog((LOG_ERR, "ipcomp_output: unsupported af %d\n", af)); + return 0; /* no change at all */ + } + + /* grab parameters */ + algo = ipcomp_algorithm_lookup(sav->alg_enc); + if ((ntohl(sav->spi) & ~0xffff) != 0 || !algo) { + stat->out_inval++; + m_freem(m); + return EINVAL; + } + if ((sav->flags & SADB_X_EXT_RAWCPI) == 0) + cpi = sav->alg_enc; + else + cpi = ntohl(sav->spi) & 0xffff; + + /* compute original payload length */ + plen = 0; + for (n = md; n; n = n->m_next) + plen += n->m_len; + + /* if the payload is short enough, we don't need to compress */ + if (plen < algo->minplen) + return 0; + + /* + * retain the original packet for two purposes: + * (1) we need to backout our changes when compression is not necessary. + * (2) byte lifetime computation should use the original packet. + * see RFC2401 page 23. + * compromise two m_copym(). we will be going through every byte of + * the payload during compression process anyways. + */ + mcopy = m_copym(m, 0, M_COPYALL, M_DONTWAIT); + if (mcopy == NULL) { + error = ENOBUFS; + return 0; + } + md0 = m_copym(md, 0, M_COPYALL, M_DONTWAIT); + if (md0 == NULL) { + m_freem(mcopy); + error = ENOBUFS; + return 0; + } + plen0 = plen; + + /* make the packet over-writable */ + for (mprev = m; mprev && mprev->m_next != md; mprev = mprev->m_next) + ; + if (mprev == NULL || mprev->m_next != md) { + ipseclog((LOG_DEBUG, "ipcomp%d_output: md is not in chain\n", + afnumber)); + stat->out_inval++; + m_freem(m); + m_freem(md0); + m_freem(mcopy); + return EINVAL; + } + mprev->m_next = NULL; + if ((md = ipsec_copypkt(md)) == NULL) { + m_freem(m); + m_freem(md0); + m_freem(mcopy); + error = ENOBUFS; + goto fail; + } + mprev->m_next = md; + + /* compress data part */ + if ((*algo->compress)(m, md, &plen) || mprev->m_next == NULL) { + ipseclog((LOG_ERR, "packet compression failure\n")); + m = NULL; + m_freem(md0); + m_freem(mcopy); + stat->out_inval++; + error = EINVAL; + goto fail; + } + stat->out_comphist[sav->alg_enc]++; + md = mprev->m_next; + + /* + * if the packet became bigger, meaningless to use IPComp. + * we've only wasted our cpu time. + */ + if (plen0 < plen) { + m_freem(md); + m_freem(mcopy); + mprev->m_next = md0; + return 0; + } + + /* + * no need to backout change beyond here. + */ + m_freem(md0); + md0 = NULL; + + m->m_pkthdr.len -= plen0; + m->m_pkthdr.len += plen; + + { + /* + * insert IPComp header. + */ +#ifdef INET + struct ip *ip = NULL; +#endif +#ifdef INET6 + struct ip6_hdr *ip6 = NULL; +#endif + size_t hlen = 0; /* ip header len */ + size_t complen = sizeof(struct ipcomp); + + switch (af) { +#ifdef INET + case AF_INET: + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + break; +#endif +#ifdef INET6 + case AF_INET6: + ip6 = mtod(m, struct ip6_hdr *); + hlen = sizeof(*ip6); + break; +#endif + } + + compoff = m->m_pkthdr.len - plen; + + /* + * grow the mbuf to accomodate ipcomp header. + * before: IP ... payload + * after: IP ... ipcomp payload + */ + if (M_LEADINGSPACE(md) < complen) { + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + m_freem(m); + error = ENOBUFS; + goto fail; + } + n->m_len = complen; + mprev->m_next = n; + n->m_next = md; + m->m_pkthdr.len += complen; + ipcomp = mtod(n, struct ipcomp *); + } else { + md->m_len += complen; + md->m_data -= complen; + m->m_pkthdr.len += complen; + ipcomp = mtod(md, struct ipcomp *); + } + + bzero(ipcomp, sizeof(*ipcomp)); + ipcomp->comp_nxt = *nexthdrp; + *nexthdrp = IPPROTO_IPCOMP; + ipcomp->comp_cpi = htons(cpi); + switch (af) { +#ifdef INET + case AF_INET: + if (compoff + complen + plen < IP_MAXPACKET) + ip->ip_len = htons(compoff + complen + plen); + else { + ipseclog((LOG_ERR, + "IPv4 ESP output: size exceeds limit\n")); + ipsecstat.out_inval++; + m_freem(m); + error = EMSGSIZE; + goto fail; + } + break; +#endif +#ifdef INET6 + case AF_INET6: + /* total packet length will be computed in ip6_output() */ + break; +#endif + } + } + + if (!m) { + ipseclog((LOG_DEBUG, + "NULL mbuf after compression in ipcomp%d_output", + afnumber)); + stat->out_inval++; + } + stat->out_success++; + + /* compute byte lifetime against original packet */ + key_sa_recordxfer(sav, mcopy); + m_freem(mcopy); + + return 0; + +fail: +#if 1 + return error; +#else + panic("something bad in ipcomp_output"); +#endif +} + +#ifdef INET +int +ipcomp4_output(m, isr) + struct mbuf *m; + struct ipsecrequest *isr; +{ + struct ip *ip; + if (m->m_len < sizeof(struct ip)) { + ipseclog((LOG_DEBUG, "ipcomp4_output: first mbuf too short\n")); + ipsecstat.out_inval++; + m_freem(m); + return 0; + } + ip = mtod(m, struct ip *); + /* XXX assumes that m->m_next points to payload */ + return ipcomp_output(m, &ip->ip_p, m->m_next, isr, AF_INET); +} +#endif /* INET */ + +#ifdef INET6 +int +ipcomp6_output(m, nexthdrp, md, isr) + struct mbuf *m; + u_char *nexthdrp; + struct mbuf *md; + struct ipsecrequest *isr; +{ + if (m->m_len < sizeof(struct ip6_hdr)) { + ipseclog((LOG_DEBUG, "ipcomp6_output: first mbuf too short\n")); + ipsec6stat.out_inval++; + m_freem(m); + return 0; + } + return ipcomp_output(m, nexthdrp, md, isr, AF_INET6); +} +#endif /* INET6 */ diff --git a/sys/netinet6/ipsec.c b/sys/netinet6/ipsec.c new file mode 100644 index 0000000..6ae18f0 --- /dev/null +++ b/sys/netinet6/ipsec.c @@ -0,0 +1,3504 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.c,v 1.103 2001/05/24 07:14:18 sakane Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * IPsec controller part. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/syslog.h> +#include <sys/sysctl.h> +#include <sys/proc.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_var.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet/ip_ecn.h> +#ifdef INET6 +#include <netinet6/ip6_ecn.h> +#endif +#include <netinet/tcp.h> +#include <netinet/udp.h> + +#include <netinet/ip6.h> +#ifdef INET6 +#include <netinet6/ip6_var.h> +#endif +#include <netinet/in_pcb.h> +#ifdef INET6 +#include <netinet/icmp6.h> +#endif + +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#include <netinet6/ah.h> +#ifdef INET6 +#include <netinet6/ah6.h> +#endif +#ifdef IPSEC_ESP +#include <netinet6/esp.h> +#ifdef INET6 +#include <netinet6/esp6.h> +#endif +#endif +#include <netinet6/ipcomp.h> +#ifdef INET6 +#include <netinet6/ipcomp6.h> +#endif +#include <netkey/key.h> +#include <netkey/keydb.h> +#include <netkey/key_debug.h> + +#include <machine/in_cksum.h> + +#include <net/net_osdep.h> + +#ifdef IPSEC_DEBUG +int ipsec_debug = 1; +#else +int ipsec_debug = 0; +#endif + +struct ipsecstat ipsecstat; +int ip4_ah_cleartos = 1; +int ip4_ah_offsetmask = 0; /* maybe IP_DF? */ +int ip4_ipsec_dfbit = 0; /* DF bit on encap. 0: clear 1: set 2: copy */ +int ip4_esp_trans_deflev = IPSEC_LEVEL_USE; +int ip4_esp_net_deflev = IPSEC_LEVEL_USE; +int ip4_ah_trans_deflev = IPSEC_LEVEL_USE; +int ip4_ah_net_deflev = IPSEC_LEVEL_USE; +struct secpolicy ip4_def_policy; +int ip4_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */ +int ip4_esp_randpad = -1; + +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_inet_ipsec); +#ifdef INET6 +SYSCTL_DECL(_net_inet6_ipsec6); +#endif +#endif + +/* net.inet.ipsec */ +SYSCTL_STRUCT(_net_inet_ipsec, IPSECCTL_STATS, + stats, CTLFLAG_RD, &ipsecstat, ipsecstat, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_POLICY, + def_policy, CTLFLAG_RW, &ip4_def_policy.policy, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_RW, &ip4_esp_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_RW, &ip4_esp_net_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_RW, &ip4_ah_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_RW, &ip4_ah_net_deflev, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_CLEARTOS, + ah_cleartos, CTLFLAG_RW, &ip4_ah_cleartos, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_AH_OFFSETMASK, + ah_offsetmask, CTLFLAG_RW, &ip4_ah_offsetmask, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DFBIT, + dfbit, CTLFLAG_RW, &ip4_ipsec_dfbit, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ECN, + ecn, CTLFLAG_RW, &ip4_ipsec_ecn, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_DEBUG, + debug, CTLFLAG_RW, &ipsec_debug, 0, ""); +SYSCTL_INT(_net_inet_ipsec, IPSECCTL_ESP_RANDPAD, + esp_randpad, CTLFLAG_RW, &ip4_esp_randpad, 0, ""); + +#ifdef INET6 +struct ipsecstat ipsec6stat; +int ip6_esp_trans_deflev = IPSEC_LEVEL_USE; +int ip6_esp_net_deflev = IPSEC_LEVEL_USE; +int ip6_ah_trans_deflev = IPSEC_LEVEL_USE; +int ip6_ah_net_deflev = IPSEC_LEVEL_USE; +struct secpolicy ip6_def_policy; +int ip6_ipsec_ecn = 0; /* ECN ignore(-1)/forbidden(0)/allowed(1) */ +int ip6_esp_randpad = -1; + +/* net.inet6.ipsec6 */ +SYSCTL_STRUCT(_net_inet6_ipsec6, IPSECCTL_STATS, + stats, CTLFLAG_RD, &ipsec6stat, ipsecstat, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_POLICY, + def_policy, CTLFLAG_RW, &ip6_def_policy.policy, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_TRANSLEV, esp_trans_deflev, + CTLFLAG_RW, &ip6_esp_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_ESP_NETLEV, esp_net_deflev, + CTLFLAG_RW, &ip6_esp_net_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_TRANSLEV, ah_trans_deflev, + CTLFLAG_RW, &ip6_ah_trans_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEF_AH_NETLEV, ah_net_deflev, + CTLFLAG_RW, &ip6_ah_net_deflev, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ECN, + ecn, CTLFLAG_RW, &ip6_ipsec_ecn, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_DEBUG, + debug, CTLFLAG_RW, &ipsec_debug, 0, ""); +SYSCTL_INT(_net_inet6_ipsec6, IPSECCTL_ESP_RANDPAD, + esp_randpad, CTLFLAG_RW, &ip6_esp_randpad, 0, ""); +#endif /* INET6 */ + +static int ipsec_setspidx_mbuf + __P((struct secpolicyindex *, u_int, u_int, struct mbuf *, int)); +static int ipsec4_setspidx_inpcb __P((struct mbuf *, struct inpcb *pcb)); +#ifdef INET6 +static int ipsec6_setspidx_in6pcb __P((struct mbuf *, struct in6pcb *pcb)); +#endif +static int ipsec_setspidx __P((struct mbuf *, struct secpolicyindex *, int)); +static void ipsec4_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); +static int ipsec4_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +#ifdef INET6 +static void ipsec6_get_ulp __P((struct mbuf *m, struct secpolicyindex *, int)); +static int ipsec6_setspidx_ipaddr __P((struct mbuf *, struct secpolicyindex *)); +#endif +static struct inpcbpolicy *ipsec_newpcbpolicy __P((void)); +static void ipsec_delpcbpolicy __P((struct inpcbpolicy *)); +static struct secpolicy *ipsec_deepcopy_policy __P((struct secpolicy *src)); +static int ipsec_set_policy __P((struct secpolicy **pcb_sp, + int optname, caddr_t request, size_t len, int priv)); +static int ipsec_get_policy __P((struct secpolicy *pcb_sp, struct mbuf **mp)); +static void vshiftl __P((unsigned char *, int, int)); +static int ipsec_in_reject __P((struct secpolicy *, struct mbuf *)); +static size_t ipsec_hdrsiz __P((struct secpolicy *)); +#ifdef INET +static struct mbuf *ipsec4_splithdr __P((struct mbuf *)); +#endif +#ifdef INET6 +static struct mbuf *ipsec6_splithdr __P((struct mbuf *)); +#endif +#ifdef INET +static int ipsec4_encapsulate __P((struct mbuf *, struct secasvar *)); +#endif +#ifdef INET6 +static int ipsec6_encapsulate __P((struct mbuf *, struct secasvar *)); +#endif + +/* + * For OUTBOUND packet having a socket. Searching SPD for packet, + * and return a pointer to SP. + * OUT: NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error occured. + * others: a pointer to SP + * + * NOTE: IPv6 mapped adddress concern is implemented here. + */ +struct secpolicy * +ipsec4_getpolicybypcb(m, dir, inp, error) + struct mbuf *m; + u_int dir; + struct inpcb *inp; + int *error; +{ + struct inpcbpolicy *pcbsp = NULL; + struct secpolicy *currsp = NULL; /* policy on socket */ + struct secpolicy *kernsp = NULL; /* policy on kernel */ + + /* sanity check */ + if (m == NULL || inp == NULL || error == NULL) + panic("ipsec4_getpolicybysock: NULL pointer was passed."); + + /* set spidx in pcb */ +#ifdef INET6 + if (inp->inp_vflag & INP_IPV6PROTO) + *error = ipsec6_setspidx_in6pcb(m, inp); + else +#endif + *error = ipsec4_setspidx_inpcb(m, inp); + if (*error) + return NULL; + pcbsp = inp->inp_sp; + + /* sanity check */ + if (pcbsp == NULL) + panic("ipsec4_getpolicybysock: pcbsp is NULL."); + + switch (dir) { + case IPSEC_DIR_INBOUND: + currsp = pcbsp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + currsp = pcbsp->sp_out; + break; + default: + panic("ipsec4_getpolicybysock: illegal direction."); + } + + /* sanity check */ + if (currsp == NULL) + panic("ipsec4_getpolicybysock: currsp is NULL."); + + /* when privilieged socket */ + if (pcbsp->priv) { + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + currsp->refcnt++; + *error = 0; + return currsp; + + case IPSEC_POLICY_ENTRUST: + /* look for a policy in SPD */ + kernsp = key_allocsp(&currsp->spidx, dir); + + /* SP found */ + if (kernsp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec4_getpolicybysock called " + "to allocate SP:%p\n", kernsp)); + *error = 0; + return kernsp; + } + + /* no SP found */ + if (ip4_def_policy.policy != IPSEC_POLICY_DISCARD + && ip4_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, + "fixed system default policy: %d->%d\n", + ip4_def_policy.policy, IPSEC_POLICY_NONE)); + ip4_def_policy.policy = IPSEC_POLICY_NONE; + } + ip4_def_policy.refcnt++; + *error = 0; + return &ip4_def_policy; + + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + *error = 0; + return currsp; + + default: + ipseclog((LOG_ERR, "ipsec4_getpolicybysock: " + "Invalid policy for PCB %d\n", currsp->policy)); + *error = EINVAL; + return NULL; + } + /* NOTREACHED */ + } + + /* when non-privilieged socket */ + /* look for a policy in SPD */ + kernsp = key_allocsp(&currsp->spidx, dir); + + /* SP found */ + if (kernsp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec4_getpolicybysock called " + "to allocate SP:%p\n", kernsp)); + *error = 0; + return kernsp; + } + + /* no SP found */ + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + ipseclog((LOG_ERR, "ipsec4_getpolicybysock: " + "Illegal policy for non-priviliged defined %d\n", + currsp->policy)); + *error = EINVAL; + return NULL; + + case IPSEC_POLICY_ENTRUST: + if (ip4_def_policy.policy != IPSEC_POLICY_DISCARD + && ip4_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, + "fixed system default policy: %d->%d\n", + ip4_def_policy.policy, IPSEC_POLICY_NONE)); + ip4_def_policy.policy = IPSEC_POLICY_NONE; + } + ip4_def_policy.refcnt++; + *error = 0; + return &ip4_def_policy; + + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + *error = 0; + return currsp; + + default: + ipseclog((LOG_ERR, "ipsec4_getpolicybysock: " + "Invalid policy for PCB %d\n", currsp->policy)); + *error = EINVAL; + return NULL; + } + /* NOTREACHED */ +} + +struct secpolicy * +ipsec4_getpolicybysock(m, dir, so, error) + struct mbuf *m; + u_int dir; + struct socket *so; + int *error; +{ + + if (so == NULL) + panic("ipsec4_getpolicybysock: NULL pointer was passed."); + return (ipsec4_getpolicybypcb(m, dir, sotoinpcb(so), error)); +} + +/* + * For FORWADING packet or OUTBOUND without a socket. Searching SPD for packet, + * and return a pointer to SP. + * OUT: positive: a pointer to the entry for security policy leaf matched. + * NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error occured. + */ +struct secpolicy * +ipsec4_getpolicybyaddr(m, dir, flag, error) + struct mbuf *m; + u_int dir; + int flag; + int *error; +{ + struct secpolicy *sp = NULL; + + /* sanity check */ + if (m == NULL || error == NULL) + panic("ipsec4_getpolicybyaddr: NULL pointer was passed."); + + { + struct secpolicyindex spidx; + + bzero(&spidx, sizeof(spidx)); + + /* Make an index to look for a policy. */ + *error = ipsec_setspidx_mbuf(&spidx, dir, AF_INET, m, + (flag & IP_FORWARDING) ? 0 : 1); + + if (*error != 0) + return NULL; + + sp = key_allocsp(&spidx, dir); + } + + /* SP found */ + if (sp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec4_getpolicybyaddr called " + "to allocate SP:%p\n", sp)); + *error = 0; + return sp; + } + + /* no SP found */ + if (ip4_def_policy.policy != IPSEC_POLICY_DISCARD + && ip4_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, "fixed system default policy:%d->%d\n", + ip4_def_policy.policy, + IPSEC_POLICY_NONE)); + ip4_def_policy.policy = IPSEC_POLICY_NONE; + } + ip4_def_policy.refcnt++; + *error = 0; + return &ip4_def_policy; +} + +#ifdef INET6 +/* + * For OUTBOUND packet having a socket. Searching SPD for packet, + * and return a pointer to SP. + * OUT: NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error occured. + * others: a pointer to SP + */ +struct secpolicy * +ipsec6_getpolicybypcb(m, dir, inp, error) + struct mbuf *m; + u_int dir; + struct inpcb *inp; + int *error; +{ + struct inpcbpolicy *pcbsp = NULL; + struct secpolicy *currsp = NULL; /* policy on socket */ + struct secpolicy *kernsp = NULL; /* policy on kernel */ + + /* sanity check */ + if (m == NULL || inp == NULL || error == NULL) + panic("ipsec6_getpolicybysock: NULL pointer was passed."); + +#ifdef DIAGNOSTIC + if ((inp->inp_vflag & INP_IPV6PROTO) == 0) + panic("ipsec6_getpolicybysock: socket domain != inet6"); +#endif + + /* set spidx in pcb */ + ipsec6_setspidx_in6pcb(m, inp); + pcbsp = inp->in6p_sp; + + /* sanity check */ + if (pcbsp == NULL) + panic("ipsec6_getpolicybysock: pcbsp is NULL."); + + switch (dir) { + case IPSEC_DIR_INBOUND: + currsp = pcbsp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + currsp = pcbsp->sp_out; + break; + default: + panic("ipsec6_getpolicybysock: illegal direction."); + } + + /* sanity check */ + if (currsp == NULL) + panic("ipsec6_getpolicybysock: currsp is NULL."); + + /* when privilieged socket */ + if (pcbsp->priv) { + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + currsp->refcnt++; + *error = 0; + return currsp; + + case IPSEC_POLICY_ENTRUST: + /* look for a policy in SPD */ + kernsp = key_allocsp(&currsp->spidx, dir); + + /* SP found */ + if (kernsp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec6_getpolicybysock called " + "to allocate SP:%p\n", kernsp)); + *error = 0; + return kernsp; + } + + /* no SP found */ + if (ip6_def_policy.policy != IPSEC_POLICY_DISCARD + && ip6_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, + "fixed system default policy: %d->%d\n", + ip6_def_policy.policy, IPSEC_POLICY_NONE)); + ip6_def_policy.policy = IPSEC_POLICY_NONE; + } + ip6_def_policy.refcnt++; + *error = 0; + return &ip6_def_policy; + + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + *error = 0; + return currsp; + + default: + ipseclog((LOG_ERR, "ipsec6_getpolicybysock: " + "Invalid policy for PCB %d\n", currsp->policy)); + *error = EINVAL; + return NULL; + } + /* NOTREACHED */ + } + + /* when non-privilieged socket */ + /* look for a policy in SPD */ + kernsp = key_allocsp(&currsp->spidx, dir); + + /* SP found */ + if (kernsp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec6_getpolicybysock called " + "to allocate SP:%p\n", kernsp)); + *error = 0; + return kernsp; + } + + /* no SP found */ + switch (currsp->policy) { + case IPSEC_POLICY_BYPASS: + ipseclog((LOG_ERR, "ipsec6_getpolicybysock: " + "Illegal policy for non-priviliged defined %d\n", + currsp->policy)); + *error = EINVAL; + return NULL; + + case IPSEC_POLICY_ENTRUST: + if (ip6_def_policy.policy != IPSEC_POLICY_DISCARD + && ip6_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, + "fixed system default policy: %d->%d\n", + ip6_def_policy.policy, IPSEC_POLICY_NONE)); + ip6_def_policy.policy = IPSEC_POLICY_NONE; + } + ip6_def_policy.refcnt++; + *error = 0; + return &ip6_def_policy; + + case IPSEC_POLICY_IPSEC: + currsp->refcnt++; + *error = 0; + return currsp; + + default: + ipseclog((LOG_ERR, + "ipsec6_policybysock: Invalid policy for PCB %d\n", + currsp->policy)); + *error = EINVAL; + return NULL; + } + /* NOTREACHED */ +} + +struct secpolicy * +ipsec6_getpolicybysock(m, dir, so, error) + struct mbuf *m; + u_int dir; + struct socket *so; + int *error; +{ + + if (so == NULL) + panic("ipsec6_getpolicybysock: NULL pointer was passed."); + return (ipsec6_getpolicybypcb(m, dir, sotoin6pcb(so), error)); +} + +/* + * For FORWADING packet or OUTBOUND without a socket. Searching SPD for packet, + * and return a pointer to SP. + * `flag' means that packet is to be forwarded whether or not. + * flag = 1: forwad + * OUT: positive: a pointer to the entry for security policy leaf matched. + * NULL: no apropreate SP found, the following value is set to error. + * 0 : bypass + * EACCES : discard packet. + * ENOENT : ipsec_acquire() in progress, maybe. + * others : error occured. + */ +#ifndef IP_FORWARDING +#define IP_FORWARDING 1 +#endif + +struct secpolicy * +ipsec6_getpolicybyaddr(m, dir, flag, error) + struct mbuf *m; + u_int dir; + int flag; + int *error; +{ + struct secpolicy *sp = NULL; + + /* sanity check */ + if (m == NULL || error == NULL) + panic("ipsec6_getpolicybyaddr: NULL pointer was passed."); + + { + struct secpolicyindex spidx; + + bzero(&spidx, sizeof(spidx)); + + /* Make an index to look for a policy. */ + *error = ipsec_setspidx_mbuf(&spidx, dir, AF_INET6, m, + (flag & IP_FORWARDING) ? 0 : 1); + + if (*error != 0) + return NULL; + + sp = key_allocsp(&spidx, dir); + } + + /* SP found */ + if (sp != NULL) { + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec6_getpolicybyaddr called " + "to allocate SP:%p\n", sp)); + *error = 0; + return sp; + } + + /* no SP found */ + if (ip6_def_policy.policy != IPSEC_POLICY_DISCARD + && ip6_def_policy.policy != IPSEC_POLICY_NONE) { + ipseclog((LOG_INFO, "fixed system default policy: %d->%d\n", + ip6_def_policy.policy, IPSEC_POLICY_NONE)); + ip6_def_policy.policy = IPSEC_POLICY_NONE; + } + ip6_def_policy.refcnt++; + *error = 0; + return &ip6_def_policy; +} +#endif /* INET6 */ + +/* + * set IP address into spidx from mbuf. + * When Forwarding packet and ICMP echo reply, this function is used. + * + * IN: get the followings from mbuf. + * protocol family, src, dst, next protocol + * OUT: + * 0: success. + * other: failure, and set errno. + */ +int +ipsec_setspidx_mbuf(spidx, dir, family, m, needport) + struct secpolicyindex *spidx; + u_int dir, family; + struct mbuf *m; + int needport; +{ + int error; + + /* sanity check */ + if (spidx == NULL || m == NULL) + panic("ipsec_setspidx_mbuf: NULL pointer was passed."); + + bzero(spidx, sizeof(*spidx)); + + error = ipsec_setspidx(m, spidx, needport); + if (error) + goto bad; + spidx->dir = dir; + + return 0; + + bad: + /* XXX initialize */ + bzero(spidx, sizeof(*spidx)); + return EINVAL; +} + +static int +ipsec4_setspidx_inpcb(m, pcb) + struct mbuf *m; + struct inpcb *pcb; +{ + struct secpolicyindex *spidx; + int error; + + /* sanity check */ + if (pcb == NULL) + panic("ipsec4_setspidx_inpcb: no PCB found."); + if (pcb->inp_sp == NULL) + panic("ipsec4_setspidx_inpcb: no inp_sp found."); + if (pcb->inp_sp->sp_out == NULL || pcb->inp_sp->sp_in == NULL) + panic("ipsec4_setspidx_inpcb: no sp_in/out found."); + + bzero(&pcb->inp_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->inp_sp->sp_out->spidx, sizeof(*spidx)); + + spidx = &pcb->inp_sp->sp_in->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_INBOUND; + + spidx = &pcb->inp_sp->sp_out->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_OUTBOUND; + + return 0; + +bad: + bzero(&pcb->inp_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->inp_sp->sp_out->spidx, sizeof(*spidx)); + return error; +} + +#ifdef INET6 +static int +ipsec6_setspidx_in6pcb(m, pcb) + struct mbuf *m; + struct in6pcb *pcb; +{ + struct secpolicyindex *spidx; + int error; + + /* sanity check */ + if (pcb == NULL) + panic("ipsec6_setspidx_in6pcb: no PCB found."); + if (pcb->in6p_sp == NULL) + panic("ipsec6_setspidx_in6pcb: no in6p_sp found."); + if (pcb->in6p_sp->sp_out == NULL || pcb->in6p_sp->sp_in == NULL) + panic("ipsec6_setspidx_in6pcb: no sp_in/out found."); + + bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx)); + + spidx = &pcb->in6p_sp->sp_in->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_INBOUND; + + spidx = &pcb->in6p_sp->sp_out->spidx; + error = ipsec_setspidx(m, spidx, 1); + if (error) + goto bad; + spidx->dir = IPSEC_DIR_OUTBOUND; + + return 0; + +bad: + bzero(&pcb->in6p_sp->sp_in->spidx, sizeof(*spidx)); + bzero(&pcb->in6p_sp->sp_out->spidx, sizeof(*spidx)); + return error; +} +#endif + +/* + * configure security policy index (src/dst/proto/sport/dport) + * by looking at the content of mbuf. + * the caller is responsible for error recovery (like clearing up spidx). + */ +static int +ipsec_setspidx(m, spidx, needport) + struct mbuf *m; + struct secpolicyindex *spidx; + int needport; +{ + struct ip *ip = NULL; + struct ip ipbuf; + u_int v; + struct mbuf *n; + int len; + int error; + + if (m == NULL) + panic("ipsec_setspidx: m == 0 passed."); + + /* + * validate m->m_pkthdr.len. we see incorrect length if we + * mistakenly call this function with inconsistent mbuf chain + * (like 4.4BSD tcp/udp processing). XXX should we panic here? + */ + len = 0; + for (n = m; n; n = n->m_next) + len += n->m_len; + if (m->m_pkthdr.len != len) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "total of m_len(%d) != pkthdr.len(%d), " + "ignored.\n", + len, m->m_pkthdr.len)); + return EINVAL; + } + + if (m->m_pkthdr.len < sizeof(struct ip)) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "pkthdr.len(%d) < sizeof(struct ip), ignored.\n", + m->m_pkthdr.len)); + return EINVAL; + } + + if (m->m_len >= sizeof(*ip)) + ip = mtod(m, struct ip *); + else { + m_copydata(m, 0, sizeof(ipbuf), (caddr_t)&ipbuf); + ip = &ipbuf; + } +#ifdef _IP_VHL + v = _IP_VHL_V(ip->ip_vhl); +#else + v = ip->ip_v; +#endif + switch (v) { + case 4: + error = ipsec4_setspidx_ipaddr(m, spidx); + if (error) + return error; + ipsec4_get_ulp(m, spidx, needport); + return 0; +#ifdef INET6 + case 6: + if (m->m_pkthdr.len < sizeof(struct ip6_hdr)) { + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "pkthdr.len(%d) < sizeof(struct ip6_hdr), " + "ignored.\n", m->m_pkthdr.len)); + return EINVAL; + } + error = ipsec6_setspidx_ipaddr(m, spidx); + if (error) + return error; + ipsec6_get_ulp(m, spidx, needport); + return 0; +#endif + default: + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_setspidx: " + "unknown IP version %u, ignored.\n", v)); + return EINVAL; + } +} + +static void +ipsec4_get_ulp(m, spidx, needport) + struct mbuf *m; + struct secpolicyindex *spidx; + int needport; +{ + struct ip ip; + struct ip6_ext ip6e; + u_int8_t nxt; + int off; + struct tcphdr th; + struct udphdr uh; + + /* sanity check */ + if (m == NULL) + panic("ipsec4_get_ulp: NULL pointer was passed."); + if (m->m_pkthdr.len < sizeof(ip)) + panic("ipsec4_get_ulp: too short"); + + /* set default */ + spidx->ul_proto = IPSEC_ULPROTO_ANY; + ((struct sockaddr_in *)&spidx->src)->sin_port = IPSEC_PORT_ANY; + ((struct sockaddr_in *)&spidx->dst)->sin_port = IPSEC_PORT_ANY; + + m_copydata(m, 0, sizeof(ip), (caddr_t)&ip); + /* ip_input() flips it into host endian XXX need more checking */ + if (ip.ip_off & (IP_MF | IP_OFFMASK)) + return; + + nxt = ip.ip_p; +#ifdef _IP_VHL + off = _IP_VHL_HL(ip->ip_vhl) << 2; +#else + off = ip.ip_hl << 2; +#endif + while (off < m->m_pkthdr.len) { + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + return; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + return; + m_copydata(m, off, sizeof(th), (caddr_t)&th); + ((struct sockaddr_in *)&spidx->src)->sin_port = + th.th_sport; + ((struct sockaddr_in *)&spidx->dst)->sin_port = + th.th_dport; + return; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + return; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + return; + m_copydata(m, off, sizeof(uh), (caddr_t)&uh); + ((struct sockaddr_in *)&spidx->src)->sin_port = + uh.uh_sport; + ((struct sockaddr_in *)&spidx->dst)->sin_port = + uh.uh_dport; + return; + case IPPROTO_AH: + if (m->m_pkthdr.len > off + sizeof(ip6e)) + return; + m_copydata(m, off, sizeof(ip6e), (caddr_t)&ip6e); + off += (ip6e.ip6e_len + 2) << 2; + nxt = ip6e.ip6e_nxt; + break; + case IPPROTO_ICMP: + default: + /* XXX intermediate headers??? */ + spidx->ul_proto = nxt; + return; + } + } +} + +/* assumes that m is sane */ +static int +ipsec4_setspidx_ipaddr(m, spidx) + struct mbuf *m; + struct secpolicyindex *spidx; +{ + struct ip *ip = NULL; + struct ip ipbuf; + struct sockaddr_in *sin; + + if (m->m_len >= sizeof(*ip)) + ip = mtod(m, struct ip *); + else { + m_copydata(m, 0, sizeof(ipbuf), (caddr_t)&ipbuf); + ip = &ipbuf; + } + + sin = (struct sockaddr_in *)&spidx->src; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + bcopy(&ip->ip_src, &sin->sin_addr, sizeof(ip->ip_src)); + spidx->prefs = sizeof(struct in_addr) << 3; + + sin = (struct sockaddr_in *)&spidx->dst; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + bcopy(&ip->ip_dst, &sin->sin_addr, sizeof(ip->ip_dst)); + spidx->prefd = sizeof(struct in_addr) << 3; + return 0; +} + +#ifdef INET6 +static void +ipsec6_get_ulp(m, spidx, needport) + struct mbuf *m; + struct secpolicyindex *spidx; + int needport; +{ + int off, nxt; + struct tcphdr th; + struct udphdr uh; + + /* sanity check */ + if (m == NULL) + panic("ipsec6_get_ulp: NULL pointer was passed."); + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec6_get_ulp:\n"); kdebug_mbuf(m)); + + /* set default */ + spidx->ul_proto = IPSEC_ULPROTO_ANY; + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = IPSEC_PORT_ANY; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = IPSEC_PORT_ANY; + + nxt = -1; + off = ip6_lasthdr(m, 0, IPPROTO_IPV6, &nxt); + if (off < 0 || m->m_pkthdr.len < off) + return; + + switch (nxt) { + case IPPROTO_TCP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct tcphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(th), (caddr_t)&th); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = th.th_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = th.th_dport; + break; + case IPPROTO_UDP: + spidx->ul_proto = nxt; + if (!needport) + break; + if (off + sizeof(struct udphdr) > m->m_pkthdr.len) + break; + m_copydata(m, off, sizeof(uh), (caddr_t)&uh); + ((struct sockaddr_in6 *)&spidx->src)->sin6_port = uh.uh_sport; + ((struct sockaddr_in6 *)&spidx->dst)->sin6_port = uh.uh_dport; + break; + case IPPROTO_ICMPV6: + default: + /* XXX intermediate headers??? */ + spidx->ul_proto = nxt; + break; + } +} + +/* assumes that m is sane */ +static int +ipsec6_setspidx_ipaddr(m, spidx) + struct mbuf *m; + struct secpolicyindex *spidx; +{ + struct ip6_hdr *ip6 = NULL; + struct ip6_hdr ip6buf; + struct sockaddr_in6 *sin6; + + if (m->m_len >= sizeof(*ip6)) + ip6 = mtod(m, struct ip6_hdr *); + else { + m_copydata(m, 0, sizeof(ip6buf), (caddr_t)&ip6buf); + ip6 = &ip6buf; + } + + sin6 = (struct sockaddr_in6 *)&spidx->src; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_src, &sin6->sin6_addr, sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + spidx->prefs = sizeof(struct in6_addr) << 3; + + sin6 = (struct sockaddr_in6 *)&spidx->dst; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + bcopy(&ip6->ip6_dst, &sin6->sin6_addr, sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } + spidx->prefd = sizeof(struct in6_addr) << 3; + + return 0; +} +#endif + +static struct inpcbpolicy * +ipsec_newpcbpolicy() +{ + struct inpcbpolicy *p; + + p = (struct inpcbpolicy *)malloc(sizeof(*p), M_SECA, M_NOWAIT); + return p; +} + +static void +ipsec_delpcbpolicy(p) + struct inpcbpolicy *p; +{ + free(p, M_SECA); +} + +/* initialize policy in PCB */ +int +ipsec_init_policy(so, pcb_sp) + struct socket *so; + struct inpcbpolicy **pcb_sp; +{ + struct inpcbpolicy *new; + + /* sanity check. */ + if (so == NULL || pcb_sp == NULL) + panic("ipsec_init_policy: NULL pointer was passed."); + + new = ipsec_newpcbpolicy(); + if (new == NULL) { + ipseclog((LOG_DEBUG, "ipsec_init_policy: No more memory.\n")); + return ENOBUFS; + } + bzero(new, sizeof(*new)); + + if (so->so_cred != 0 && so->so_cred->cr_uid == 0) + new->priv = 1; + else + new->priv = 0; + + if ((new->sp_in = key_newsp()) == NULL) { + ipsec_delpcbpolicy(new); + return ENOBUFS; + } + new->sp_in->state = IPSEC_SPSTATE_ALIVE; + new->sp_in->policy = IPSEC_POLICY_ENTRUST; + + if ((new->sp_out = key_newsp()) == NULL) { + key_freesp(new->sp_in); + ipsec_delpcbpolicy(new); + return ENOBUFS; + } + new->sp_out->state = IPSEC_SPSTATE_ALIVE; + new->sp_out->policy = IPSEC_POLICY_ENTRUST; + + *pcb_sp = new; + + return 0; +} + +/* copy old ipsec policy into new */ +int +ipsec_copy_policy(old, new) + struct inpcbpolicy *old, *new; +{ + struct secpolicy *sp; + + sp = ipsec_deepcopy_policy(old->sp_in); + if (sp) { + key_freesp(new->sp_in); + new->sp_in = sp; + } else + return ENOBUFS; + + sp = ipsec_deepcopy_policy(old->sp_out); + if (sp) { + key_freesp(new->sp_out); + new->sp_out = sp; + } else + return ENOBUFS; + + new->priv = old->priv; + + return 0; +} + +/* deep-copy a policy in PCB */ +static struct secpolicy * +ipsec_deepcopy_policy(src) + struct secpolicy *src; +{ + struct ipsecrequest *newchain = NULL; + struct ipsecrequest *p; + struct ipsecrequest **q; + struct ipsecrequest *r; + struct secpolicy *dst; + + dst = key_newsp(); + if (src == NULL || dst == NULL) + return NULL; + + /* + * deep-copy IPsec request chain. This is required since struct + * ipsecrequest is not reference counted. + */ + q = &newchain; + for (p = src->req; p; p = p->next) { + *q = (struct ipsecrequest *)malloc(sizeof(struct ipsecrequest), + M_SECA, M_NOWAIT); + if (*q == NULL) + goto fail; + bzero(*q, sizeof(**q)); + (*q)->next = NULL; + + (*q)->saidx.proto = p->saidx.proto; + (*q)->saidx.mode = p->saidx.mode; + (*q)->level = p->level; + (*q)->saidx.reqid = p->saidx.reqid; + + bcopy(&p->saidx.src, &(*q)->saidx.src, sizeof((*q)->saidx.src)); + bcopy(&p->saidx.dst, &(*q)->saidx.dst, sizeof((*q)->saidx.dst)); + + (*q)->sav = NULL; + (*q)->sp = dst; + + q = &((*q)->next); + } + + dst->req = newchain; + dst->state = src->state; + dst->policy = src->policy; + /* do not touch the refcnt fields */ + + return dst; + +fail: + for (p = newchain; p; p = r) { + r = p->next; + free(p, M_SECA); + p = NULL; + } + return NULL; +} + +/* set policy and ipsec request if present. */ +static int +ipsec_set_policy(pcb_sp, optname, request, len, priv) + struct secpolicy **pcb_sp; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy *newsp = NULL; + int error; + + /* sanity check. */ + if (pcb_sp == NULL || *pcb_sp == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_set_policy: passed policy\n"); + kdebug_sadb_x_policy((struct sadb_ext *)xpl)); + + /* check policy type */ + /* ipsec_set_policy() accepts IPSEC, ENTRUST and BYPASS. */ + if (xpl->sadb_x_policy_type == IPSEC_POLICY_DISCARD + || xpl->sadb_x_policy_type == IPSEC_POLICY_NONE) + return EINVAL; + + /* check privileged socket */ + if (priv == 0 && xpl->sadb_x_policy_type == IPSEC_POLICY_BYPASS) + return EACCES; + + /* allocation new SP entry */ + if ((newsp = key_msg2sp(xpl, len, &error)) == NULL) + return error; + + newsp->state = IPSEC_SPSTATE_ALIVE; + + /* clear old SP and set new SP */ + key_freesp(*pcb_sp); + *pcb_sp = newsp; + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_set_policy: new policy\n"); + kdebug_secpolicy(newsp)); + + return 0; +} + +static int +ipsec_get_policy(pcb_sp, mp) + struct secpolicy *pcb_sp; + struct mbuf **mp; +{ + + /* sanity check. */ + if (pcb_sp == NULL || mp == NULL) + return EINVAL; + + *mp = key_sp2msg(pcb_sp); + if (!*mp) { + ipseclog((LOG_DEBUG, "ipsec_get_policy: No more memory.\n")); + return ENOBUFS; + } + + (*mp)->m_type = MT_DATA; + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_get_policy:\n"); + kdebug_mbuf(*mp)); + + return 0; +} + +int +ipsec4_set_policy(inp, optname, request, len, priv) + struct inpcb *inp; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy **pcb_sp; + + /* sanity check. */ + if (inp == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = &inp->inp_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = &inp->inp_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_set_policy(pcb_sp, optname, request, len, priv); +} + +int +ipsec4_get_policy(inp, request, len, mp) + struct inpcb *inp; + caddr_t request; + size_t len; + struct mbuf **mp; +{ + struct sadb_x_policy *xpl; + struct secpolicy *pcb_sp; + + /* sanity check. */ + if (inp == NULL || request == NULL || mp == NULL) + return EINVAL; + if (inp->inp_sp == NULL) + panic("policy in PCB is NULL"); + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = inp->inp_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = inp->inp_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec4_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_get_policy(pcb_sp, mp); +} + +/* delete policy in PCB */ +int +ipsec4_delete_pcbpolicy(inp) + struct inpcb *inp; +{ + /* sanity check. */ + if (inp == NULL) + panic("ipsec4_delete_pcbpolicy: NULL pointer was passed."); + + if (inp->inp_sp == NULL) + return 0; + + if (inp->inp_sp->sp_in != NULL) { + key_freesp(inp->inp_sp->sp_in); + inp->inp_sp->sp_in = NULL; + } + + if (inp->inp_sp->sp_out != NULL) { + key_freesp(inp->inp_sp->sp_out); + inp->inp_sp->sp_out = NULL; + } + + ipsec_delpcbpolicy(inp->inp_sp); + inp->inp_sp = NULL; + + return 0; +} + +#ifdef INET6 +int +ipsec6_set_policy(in6p, optname, request, len, priv) + struct in6pcb *in6p; + int optname; + caddr_t request; + size_t len; + int priv; +{ + struct sadb_x_policy *xpl; + struct secpolicy **pcb_sp; + + /* sanity check. */ + if (in6p == NULL || request == NULL) + return EINVAL; + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = &in6p->in6p_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = &in6p->in6p_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_set_policy(pcb_sp, optname, request, len, priv); +} + +int +ipsec6_get_policy(in6p, request, len, mp) + struct in6pcb *in6p; + caddr_t request; + size_t len; + struct mbuf **mp; +{ + struct sadb_x_policy *xpl; + struct secpolicy *pcb_sp; + + /* sanity check. */ + if (in6p == NULL || request == NULL || mp == NULL) + return EINVAL; + if (in6p->in6p_sp == NULL) + panic("policy in PCB is NULL"); + if (len < sizeof(*xpl)) + return EINVAL; + xpl = (struct sadb_x_policy *)request; + + /* select direction */ + switch (xpl->sadb_x_policy_dir) { + case IPSEC_DIR_INBOUND: + pcb_sp = in6p->in6p_sp->sp_in; + break; + case IPSEC_DIR_OUTBOUND: + pcb_sp = in6p->in6p_sp->sp_out; + break; + default: + ipseclog((LOG_ERR, "ipsec6_set_policy: invalid direction=%u\n", + xpl->sadb_x_policy_dir)); + return EINVAL; + } + + return ipsec_get_policy(pcb_sp, mp); +} + +int +ipsec6_delete_pcbpolicy(in6p) + struct in6pcb *in6p; +{ + /* sanity check. */ + if (in6p == NULL) + panic("ipsec6_delete_pcbpolicy: NULL pointer was passed."); + + if (in6p->in6p_sp == NULL) + return 0; + + if (in6p->in6p_sp->sp_in != NULL) { + key_freesp(in6p->in6p_sp->sp_in); + in6p->in6p_sp->sp_in = NULL; + } + + if (in6p->in6p_sp->sp_out != NULL) { + key_freesp(in6p->in6p_sp->sp_out); + in6p->in6p_sp->sp_out = NULL; + } + + ipsec_delpcbpolicy(in6p->in6p_sp); + in6p->in6p_sp = NULL; + + return 0; +} +#endif + +/* + * return current level. + * Either IPSEC_LEVEL_USE or IPSEC_LEVEL_REQUIRE are always returned. + */ +u_int +ipsec_get_reqlevel(isr) + struct ipsecrequest *isr; +{ + u_int level = 0; + u_int esp_trans_deflev, esp_net_deflev, ah_trans_deflev, ah_net_deflev; + + /* sanity check */ + if (isr == NULL || isr->sp == NULL) + panic("ipsec_get_reqlevel: NULL pointer is passed."); + if (((struct sockaddr *)&isr->sp->spidx.src)->sa_family + != ((struct sockaddr *)&isr->sp->spidx.dst)->sa_family) + panic("ipsec_get_reqlevel: family mismatched."); + +/* XXX note that we have ipseclog() expanded here - code sync issue */ +#define IPSEC_CHECK_DEFAULT(lev) \ + (((lev) != IPSEC_LEVEL_USE && (lev) != IPSEC_LEVEL_REQUIRE \ + && (lev) != IPSEC_LEVEL_UNIQUE) \ + ? (ipsec_debug \ + ? log(LOG_INFO, "fixed system default level " #lev ":%d->%d\n",\ + (lev), IPSEC_LEVEL_REQUIRE) \ + : 0), \ + (lev) = IPSEC_LEVEL_REQUIRE, \ + (lev) \ + : (lev)) + + /* set default level */ + switch (((struct sockaddr *)&isr->sp->spidx.src)->sa_family) { +#ifdef INET + case AF_INET: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(ip4_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(ip4_ah_net_deflev); + break; +#endif +#ifdef INET6 + case AF_INET6: + esp_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_trans_deflev); + esp_net_deflev = IPSEC_CHECK_DEFAULT(ip6_esp_net_deflev); + ah_trans_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_trans_deflev); + ah_net_deflev = IPSEC_CHECK_DEFAULT(ip6_ah_net_deflev); + break; +#endif /* INET6 */ + default: + panic("key_get_reqlevel: Unknown family. %d", + ((struct sockaddr *)&isr->sp->spidx.src)->sa_family); + } + +#undef IPSEC_CHECK_DEFAULT + + /* set level */ + switch (isr->level) { + case IPSEC_LEVEL_DEFAULT: + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = esp_net_deflev; + else + level = esp_trans_deflev; + break; + case IPPROTO_AH: + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + level = ah_net_deflev; + else + level = ah_trans_deflev; + case IPPROTO_IPCOMP: + /* + * we don't really care, as IPcomp document says that + * we shouldn't compress small packets + */ + level = IPSEC_LEVEL_USE; + break; + default: + panic("ipsec_get_reqlevel: " + "Illegal protocol defined %u", + isr->saidx.proto); + } + break; + + case IPSEC_LEVEL_USE: + case IPSEC_LEVEL_REQUIRE: + level = isr->level; + break; + case IPSEC_LEVEL_UNIQUE: + level = IPSEC_LEVEL_REQUIRE; + break; + + default: + panic("ipsec_get_reqlevel: Illegal IPsec level %u", + isr->level); + } + + return level; +} + +/* + * Check AH/ESP integrity. + * OUT: + * 0: valid + * 1: invalid + */ +static int +ipsec_in_reject(sp, m) + struct secpolicy *sp; + struct mbuf *m; +{ + struct ipsecrequest *isr; + u_int level; + int need_auth, need_conf, need_icv; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec_in_reject: using SP\n"); + kdebug_secpolicy(sp)); + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + return 1; + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return 0; + + case IPSEC_POLICY_IPSEC: + break; + + case IPSEC_POLICY_ENTRUST: + default: + panic("ipsec_hdrsiz: Invalid policy found. %d", sp->policy); + } + + need_auth = 0; + need_conf = 0; + need_icv = 0; + + /* XXX should compare policy against ipsec header history */ + + for (isr = sp->req; isr != NULL; isr = isr->next) { + + /* get current level */ + level = ipsec_get_reqlevel(isr); + + switch (isr->saidx.proto) { + case IPPROTO_ESP: + if (level == IPSEC_LEVEL_REQUIRE) { + need_conf++; + + if (isr->sav != NULL + && isr->sav->flags == SADB_X_EXT_NONE + && isr->sav->alg_auth != SADB_AALG_NONE) + need_icv++; + } + break; + case IPPROTO_AH: + if (level == IPSEC_LEVEL_REQUIRE) { + need_auth++; + need_icv++; + } + break; + case IPPROTO_IPCOMP: + /* + * we don't really care, as IPcomp document says that + * we shouldn't compress small packets, IPComp policy + * should always be treated as being in "use" level. + */ + break; + } + } + + KEYDEBUG(KEYDEBUG_IPSEC_DUMP, + printf("ipsec_in_reject: auth:%d conf:%d icv:%d m_flags:%x\n", + need_auth, need_conf, need_icv, m->m_flags)); + + if ((need_conf && !(m->m_flags & M_DECRYPTED)) + || (!need_auth && need_icv && !(m->m_flags & M_AUTHIPDGM)) + || (need_auth && !(m->m_flags & M_AUTHIPHDR))) + return 1; + + return 0; +} + +/* + * Check AH/ESP integrity. + * This function is called from tcp_input(), udp_input(), + * and {ah,esp}4_input for tunnel mode + */ +int +ipsec4_in_reject(m, inp) + struct mbuf *m; + struct inpcb *inp; +{ + struct secpolicy *sp = NULL; + int error; + int result; + + /* sanity check */ + if (m == NULL) + return 0; /* XXX should be panic ? */ + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec4_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (inp == NULL) + sp = ipsec4_getpolicybyaddr(m, IPSEC_DIR_INBOUND, IP_FORWARDING, &error); + else + sp = ipsec4_getpolicybypcb(m, IPSEC_DIR_INBOUND, inp, &error); + + if (sp == NULL) + return 0; /* XXX should be panic ? + * -> No, there may be error. */ + + result = ipsec_in_reject(sp, m); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec4_in_reject_so call free SP:%p\n", sp)); + key_freesp(sp); + + return result; +} + +int +ipsec4_in_reject_so(m, so) + struct mbuf *m; + struct socket *so; +{ + if (so == NULL) + return ipsec4_in_reject(m, NULL); + return ipsec4_in_reject(m, sotoinpcb(so)); +} + + +#ifdef INET6 +/* + * Check AH/ESP integrity. + * This function is called from tcp6_input(), udp6_input(), + * and {ah,esp}6_input for tunnel mode + */ +int +ipsec6_in_reject(m, in6p) + struct mbuf *m; + struct in6pcb *in6p; +{ + struct secpolicy *sp = NULL; + int error; + int result; + + /* sanity check */ + if (m == NULL) + return 0; /* XXX should be panic ? */ + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec6_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (in6p == NULL) + sp = ipsec6_getpolicybyaddr(m, IPSEC_DIR_INBOUND, IP_FORWARDING, &error); + else + sp = ipsec6_getpolicybypcb(m, IPSEC_DIR_INBOUND, in6p, &error); + + if (sp == NULL) + return 0; /* XXX should be panic ? */ + + result = ipsec_in_reject(sp, m); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec6_in_reject call free SP:%p\n", sp)); + key_freesp(sp); + + return result; +} + +int +ipsec6_in_reject_so(m, so) + struct mbuf *m; + struct socket *so; +{ + if (so == NULL) + return ipsec6_in_reject(m, NULL); + return ipsec6_in_reject(m, sotoin6pcb(so)); +} +#endif + +/* + * compute the byte size to be occupied by IPsec header. + * in case it is tunneled, it includes the size of outer IP header. + * NOTE: SP passed is free in this function. + */ +static size_t +ipsec_hdrsiz(sp) + struct secpolicy *sp; +{ + struct ipsecrequest *isr; + size_t siz, clen; + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec_hdrsiz: using SP\n"); + kdebug_secpolicy(sp)); + + /* check policy */ + switch (sp->policy) { + case IPSEC_POLICY_DISCARD: + case IPSEC_POLICY_BYPASS: + case IPSEC_POLICY_NONE: + return 0; + + case IPSEC_POLICY_IPSEC: + break; + + case IPSEC_POLICY_ENTRUST: + default: + panic("ipsec_hdrsiz: Invalid policy found. %d", sp->policy); + } + + siz = 0; + + for (isr = sp->req; isr != NULL; isr = isr->next) { + + clen = 0; + + switch (isr->saidx.proto) { + case IPPROTO_ESP: +#ifdef IPSEC_ESP + clen = esp_hdrsiz(isr); +#else + clen = 0; /* XXX */ +#endif + break; + case IPPROTO_AH: + clen = ah_hdrsiz(isr); + break; + case IPPROTO_IPCOMP: + clen = sizeof(struct ipcomp); + break; + } + + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + switch (((struct sockaddr *)&isr->saidx.dst)->sa_family) { + case AF_INET: + clen += sizeof(struct ip); + break; +#ifdef INET6 + case AF_INET6: + clen += sizeof(struct ip6_hdr); + break; +#endif + default: + ipseclog((LOG_ERR, "ipsec_hdrsiz: " + "unknown AF %d in IPsec tunnel SA\n", + ((struct sockaddr *)&isr->saidx.dst)->sa_family)); + break; + } + } + siz += clen; + } + + return siz; +} + +/* This function is called from ip_forward() and ipsec4_hdrsize_tcp(). */ +size_t +ipsec4_hdrsiz(m, dir, inp) + struct mbuf *m; + u_int dir; + struct inpcb *inp; +{ + struct secpolicy *sp = NULL; + int error; + size_t size; + + /* sanity check */ + if (m == NULL) + return 0; /* XXX should be panic ? */ +#if 0 + /* this is possible in TIME_WAIT state */ + if (inp != NULL && inp->inp_socket == NULL) + panic("ipsec4_hdrsize: why is socket NULL but there is PCB."); +#endif + + /* get SP for this packet. + * When we are called from ip_forward(), we call + * ipsec4_getpolicybyaddr() with IP_FORWARDING flag. + */ + if (inp == NULL) + sp = ipsec4_getpolicybyaddr(m, dir, IP_FORWARDING, &error); + else + sp = ipsec4_getpolicybypcb(m, dir, inp, &error); + + if (sp == NULL) + return 0; /* XXX should be panic ? */ + + size = ipsec_hdrsiz(sp); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec4_hdrsiz call free SP:%p\n", sp)); + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec4_hdrsiz: size:%lu.\n", (unsigned long)size)); + key_freesp(sp); + + return size; +} + +#ifdef INET6 +/* This function is called from ipsec6_hdrsize_tcp(), + * and maybe from ip6_forward.() + */ +size_t +ipsec6_hdrsiz(m, dir, in6p) + struct mbuf *m; + u_int dir; + struct in6pcb *in6p; +{ + struct secpolicy *sp = NULL; + int error; + size_t size; + + /* sanity check */ + if (m == NULL) + return 0; /* XXX shoud be panic ? */ +#if 0 + /* this is possible in TIME_WAIT state */ + if (in6p != NULL && in6p->in6p_socket == NULL) + panic("ipsec6_hdrsize: why is socket NULL but there is PCB."); +#endif + + /* get SP for this packet */ + /* XXX Is it right to call with IP_FORWARDING. */ + if (in6p == NULL) + sp = ipsec6_getpolicybyaddr(m, dir, IP_FORWARDING, &error); + else + sp = ipsec6_getpolicybypcb(m, dir, in6p, &error); + + if (sp == NULL) + return 0; + size = ipsec_hdrsiz(sp); + KEYDEBUG(KEYDEBUG_IPSEC_STAMP, + printf("DP ipsec6_hdrsiz call free SP:%p\n", sp)); + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_hdrsiz: size:%lu.\n", (unsigned long)size)); + key_freesp(sp); + + return size; +} +#endif /* INET6 */ + +#ifdef INET +/* + * encapsulate for ipsec tunnel. + * ip->ip_src must be fixed later on. + */ +static int +ipsec4_encapsulate(m, sav) + struct mbuf *m; + struct secasvar *sav; +{ + struct ip *oip; + struct ip *ip; + size_t hlen; + size_t plen; + + /* can't tunnel between different AFs */ + if (((struct sockaddr *)&sav->sah->saidx.src)->sa_family + != ((struct sockaddr *)&sav->sah->saidx.dst)->sa_family + || ((struct sockaddr *)&sav->sah->saidx.src)->sa_family != AF_INET) { + m_freem(m); + return EINVAL; + } +#if 0 + /* XXX if the dst is myself, perform nothing. */ + if (key_ismyaddr((struct sockaddr *)&sav->sah->saidx.dst)) { + m_freem(m); + return EINVAL; + } +#endif + + if (m->m_len < sizeof(*ip)) + panic("ipsec4_encapsulate: assumption failed (first mbuf length)"); + + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = _IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + + if (m->m_len != hlen) + panic("ipsec4_encapsulate: assumption failed (first mbuf length)"); + + /* generate header checksum */ + ip->ip_sum = 0; +#ifdef _IP_VHL + if (ip->ip_vhl == IP_VHL_BORING) + ip->ip_sum = in_cksum_hdr(ip); + else + ip->ip_sum = in_cksum(m, hlen); +#else + ip->ip_sum = in_cksum(m, hlen); +#endif + + plen = m->m_pkthdr.len; + + /* + * grow the mbuf to accomodate the new IPv4 header. + * NOTE: IPv4 options will never be copied. + */ + if (M_LEADINGSPACE(m->m_next) < hlen) { + struct mbuf *n; + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + m_freem(m); + return ENOBUFS; + } + n->m_len = hlen; + n->m_next = m->m_next; + m->m_next = n; + m->m_pkthdr.len += hlen; + oip = mtod(n, struct ip *); + } else { + m->m_next->m_len += hlen; + m->m_next->m_data -= hlen; + m->m_pkthdr.len += hlen; + oip = mtod(m->m_next, struct ip *); + } + ip = mtod(m, struct ip *); + ovbcopy((caddr_t)ip, (caddr_t)oip, hlen); + m->m_len = sizeof(struct ip); + m->m_pkthdr.len -= (hlen - sizeof(struct ip)); + + /* construct new IPv4 header. see RFC 2401 5.1.2.1 */ + /* ECN consideration. */ + ip_ecn_ingress(ip4_ipsec_ecn, &ip->ip_tos, &oip->ip_tos); +#ifdef _IP_VHL + ip->ip_vhl = IP_MAKE_VHL(IPVERSION, sizeof(struct ip) >> 2); +#else + ip->ip_hl = sizeof(struct ip) >> 2; +#endif + ip->ip_off &= htons(~IP_OFFMASK); + ip->ip_off &= htons(~IP_MF); + switch (ip4_ipsec_dfbit) { + case 0: /* clear DF bit */ + ip->ip_off &= htons(~IP_DF); + break; + case 1: /* set DF bit */ + ip->ip_off |= htons(IP_DF); + break; + default: /* copy DF bit */ + break; + } + ip->ip_p = IPPROTO_IPIP; + if (plen + sizeof(struct ip) < IP_MAXPACKET) + ip->ip_len = htons(plen + sizeof(struct ip)); + else { + ipseclog((LOG_ERR, "IPv4 ipsec: size exceeds limit: " + "leave ip_len as is (invalid packet)\n")); + } +#ifdef RANDOM_IP_ID + ip->ip_id = ip_randomid(); +#else + ip->ip_id = htons(ip_id++); +#endif + bcopy(&((struct sockaddr_in *)&sav->sah->saidx.src)->sin_addr, + &ip->ip_src, sizeof(ip->ip_src)); + bcopy(&((struct sockaddr_in *)&sav->sah->saidx.dst)->sin_addr, + &ip->ip_dst, sizeof(ip->ip_dst)); + ip->ip_ttl = IPDEFTTL; + + /* XXX Should ip_src be updated later ? */ + + return 0; +} +#endif /* INET */ + +#ifdef INET6 +static int +ipsec6_encapsulate(m, sav) + struct mbuf *m; + struct secasvar *sav; +{ + struct ip6_hdr *oip6; + struct ip6_hdr *ip6; + size_t plen; + + /* can't tunnel between different AFs */ + if (((struct sockaddr *)&sav->sah->saidx.src)->sa_family + != ((struct sockaddr *)&sav->sah->saidx.dst)->sa_family + || ((struct sockaddr *)&sav->sah->saidx.src)->sa_family != AF_INET6) { + m_freem(m); + return EINVAL; + } +#if 0 + /* XXX if the dst is myself, perform nothing. */ + if (key_ismyaddr((struct sockaddr *)&sav->sah->saidx.dst)) { + m_freem(m); + return EINVAL; + } +#endif + + plen = m->m_pkthdr.len; + + /* + * grow the mbuf to accomodate the new IPv6 header. + */ + if (m->m_len != sizeof(struct ip6_hdr)) + panic("ipsec6_encapsulate: assumption failed (first mbuf length)"); + if (M_LEADINGSPACE(m->m_next) < sizeof(struct ip6_hdr)) { + struct mbuf *n; + MGET(n, M_DONTWAIT, MT_DATA); + if (!n) { + m_freem(m); + return ENOBUFS; + } + n->m_len = sizeof(struct ip6_hdr); + n->m_next = m->m_next; + m->m_next = n; + m->m_pkthdr.len += sizeof(struct ip6_hdr); + oip6 = mtod(n, struct ip6_hdr *); + } else { + m->m_next->m_len += sizeof(struct ip6_hdr); + m->m_next->m_data -= sizeof(struct ip6_hdr); + m->m_pkthdr.len += sizeof(struct ip6_hdr); + oip6 = mtod(m->m_next, struct ip6_hdr *); + } + ip6 = mtod(m, struct ip6_hdr *); + ovbcopy((caddr_t)ip6, (caddr_t)oip6, sizeof(struct ip6_hdr)); + + /* Fake link-local scope-class addresses */ + if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_src)) + oip6->ip6_src.s6_addr16[1] = 0; + if (IN6_IS_SCOPE_LINKLOCAL(&oip6->ip6_dst)) + oip6->ip6_dst.s6_addr16[1] = 0; + + /* construct new IPv6 header. see RFC 2401 5.1.2.2 */ + /* ECN consideration. */ + ip6_ecn_ingress(ip6_ipsec_ecn, &ip6->ip6_flow, &oip6->ip6_flow); + if (plen < IPV6_MAXPACKET - sizeof(struct ip6_hdr)) + ip6->ip6_plen = htons(plen); + else { + /* ip6->ip6_plen will be updated in ip6_output() */ + } + ip6->ip6_nxt = IPPROTO_IPV6; + bcopy(&((struct sockaddr_in6 *)&sav->sah->saidx.src)->sin6_addr, + &ip6->ip6_src, sizeof(ip6->ip6_src)); + bcopy(&((struct sockaddr_in6 *)&sav->sah->saidx.dst)->sin6_addr, + &ip6->ip6_dst, sizeof(ip6->ip6_dst)); + ip6->ip6_hlim = IPV6_DEFHLIM; + + /* XXX Should ip6_src be updated later ? */ + + return 0; +} +#endif /* INET6 */ + +/* + * Check the variable replay window. + * ipsec_chkreplay() performs replay check before ICV verification. + * ipsec_updatereplay() updates replay bitmap. This must be called after + * ICV verification (it also performs replay check, which is usually done + * beforehand). + * 0 (zero) is returned if packet disallowed, 1 if packet permitted. + * + * based on RFC 2401. + */ +int +ipsec_chkreplay(seq, sav) + u_int32_t seq; + struct secasvar *sav; +{ + const struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* constant: bits of window size */ + int frlast; /* constant: last frame */ + + /* sanity check */ + if (sav == NULL) + panic("ipsec_chkreplay: NULL pointer was passed."); + + replay = sav->replay; + + if (replay->wsize == 0) + return 1; /* no need to check replay. */ + + /* constant */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* sequence number of 0 is invalid */ + if (seq == 0) + return 0; + + /* first time is always okay */ + if (replay->count == 0) + return 1; + + if (seq > replay->lastseq) { + /* larger sequences are okay */ + return 1; + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* over range to check, i.e. too old or wrapped */ + if (diff >= wsizeb) + return 0; + + fr = frlast - diff / 8; + + /* this packet already seen ? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return 0; + + /* out of order but good */ + return 1; + } +} + +/* + * check replay counter whether to update or not. + * OUT: 0: OK + * 1: NG + */ +int +ipsec_updatereplay(seq, sav) + u_int32_t seq; + struct secasvar *sav; +{ + struct secreplay *replay; + u_int32_t diff; + int fr; + u_int32_t wsizeb; /* constant: bits of window size */ + int frlast; /* constant: last frame */ + + /* sanity check */ + if (sav == NULL) + panic("ipsec_chkreplay: NULL pointer was passed."); + + replay = sav->replay; + + if (replay->wsize == 0) + goto ok; /* no need to check replay. */ + + /* constant */ + frlast = replay->wsize - 1; + wsizeb = replay->wsize << 3; + + /* sequence number of 0 is invalid */ + if (seq == 0) + return 1; + + /* first time */ + if (replay->count == 0) { + replay->lastseq = seq; + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + goto ok; + } + + if (seq > replay->lastseq) { + /* seq is larger than lastseq. */ + diff = seq - replay->lastseq; + + /* new larger sequence number */ + if (diff < wsizeb) { + /* In window */ + /* set bit for this packet */ + vshiftl(replay->bitmap, diff, replay->wsize); + (replay->bitmap)[frlast] |= 1; + } else { + /* this packet has a "way larger" */ + bzero(replay->bitmap, replay->wsize); + (replay->bitmap)[frlast] = 1; + } + replay->lastseq = seq; + + /* larger is good */ + } else { + /* seq is equal or less than lastseq. */ + diff = replay->lastseq - seq; + + /* over range to check, i.e. too old or wrapped */ + if (diff >= wsizeb) + return 1; + + fr = frlast - diff / 8; + + /* this packet already seen ? */ + if ((replay->bitmap)[fr] & (1 << (diff % 8))) + return 1; + + /* mark as seen */ + (replay->bitmap)[fr] |= (1 << (diff % 8)); + + /* out of order but good */ + } + +ok: + if (replay->count == ~0) { + + /* set overflow flag */ + replay->overflow++; + + /* don't increment, no more packets accepted */ + if ((sav->flags & SADB_X_EXT_CYCSEQ) == 0) + return 1; + + ipseclog((LOG_WARNING, "replay counter made %d cycle. %s\n", + replay->overflow, ipsec_logsastr(sav))); + } + + replay->count++; + + return 0; +} + +/* + * shift variable length buffer to left. + * IN: bitmap: pointer to the buffer + * nbit: the number of to shift. + * wsize: buffer size (bytes). + */ +static void +vshiftl(bitmap, nbit, wsize) + unsigned char *bitmap; + int nbit, wsize; +{ + int s, j, i; + unsigned char over; + + for (j = 0; j < nbit; j += 8) { + s = (nbit - j < 8) ? (nbit - j): 8; + bitmap[0] <<= s; + for (i = 1; i < wsize; i++) { + over = (bitmap[i] >> (8 - s)); + bitmap[i] <<= s; + bitmap[i-1] |= over; + } + } + + return; +} + +const char * +ipsec4_logpacketstr(ip, spi) + struct ip *ip; + u_int32_t spi; +{ + static char buf[256]; + char *p; + u_int8_t *s, *d; + + s = (u_int8_t *)(&ip->ip_src); + d = (u_int8_t *)(&ip->ip_dst); + + p = buf; + snprintf(buf, sizeof(buf), "packet(SPI=%u ", (u_int32_t)ntohl(spi)); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), "src=%u.%u.%u.%u", + s[0], s[1], s[2], s[3]); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), " dst=%u.%u.%u.%u", + d[0], d[1], d[2], d[3]); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), ")"); + + return buf; +} + +#ifdef INET6 +const char * +ipsec6_logpacketstr(ip6, spi) + struct ip6_hdr *ip6; + u_int32_t spi; +{ + static char buf[256]; + char *p; + + p = buf; + snprintf(buf, sizeof(buf), "packet(SPI=%u ", (u_int32_t)ntohl(spi)); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), "src=%s", + ip6_sprintf(&ip6->ip6_src)); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), " dst=%s", + ip6_sprintf(&ip6->ip6_dst)); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), ")"); + + return buf; +} +#endif /* INET6 */ + +const char * +ipsec_logsastr(sav) + struct secasvar *sav; +{ + static char buf[256]; + char *p; + struct secasindex *saidx = &sav->sah->saidx; + + /* validity check */ + if (((struct sockaddr *)&sav->sah->saidx.src)->sa_family + != ((struct sockaddr *)&sav->sah->saidx.dst)->sa_family) + panic("ipsec_logsastr: family mismatched."); + + p = buf; + snprintf(buf, sizeof(buf), "SA(SPI=%u ", (u_int32_t)ntohl(sav->spi)); + while (p && *p) + p++; + if (((struct sockaddr *)&saidx->src)->sa_family == AF_INET) { + u_int8_t *s, *d; + s = (u_int8_t *)&((struct sockaddr_in *)&saidx->src)->sin_addr; + d = (u_int8_t *)&((struct sockaddr_in *)&saidx->dst)->sin_addr; + snprintf(p, sizeof(buf) - (p - buf), + "src=%d.%d.%d.%d dst=%d.%d.%d.%d", + s[0], s[1], s[2], s[3], d[0], d[1], d[2], d[3]); + } +#ifdef INET6 + else if (((struct sockaddr *)&saidx->src)->sa_family == AF_INET6) { + snprintf(p, sizeof(buf) - (p - buf), + "src=%s", + ip6_sprintf(&((struct sockaddr_in6 *)&saidx->src)->sin6_addr)); + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), + " dst=%s", + ip6_sprintf(&((struct sockaddr_in6 *)&saidx->dst)->sin6_addr)); + } +#endif + while (p && *p) + p++; + snprintf(p, sizeof(buf) - (p - buf), ")"); + + return buf; +} + +void +ipsec_dumpmbuf(m) + struct mbuf *m; +{ + int totlen; + int i; + u_char *p; + + totlen = 0; + printf("---\n"); + while (m) { + p = mtod(m, u_char *); + for (i = 0; i < m->m_len; i++) { + printf("%02x ", p[i]); + totlen++; + if (totlen % 16 == 0) + printf("\n"); + } + m = m->m_next; + } + if (totlen % 16 != 0) + printf("\n"); + printf("---\n"); +} + +#ifdef INET +/* + * IPsec output logic for IPv4. + */ +int +ipsec4_output(state, sp, flags) + struct ipsec_output_state *state; + struct secpolicy *sp; + int flags; +{ + struct ip *ip = NULL; + struct ipsecrequest *isr = NULL; + struct secasindex saidx; + int s; + int error; + struct sockaddr_in *dst4; + struct sockaddr_in *sin; + + if (!state) + panic("state == NULL in ipsec4_output"); + if (!state->m) + panic("state->m == NULL in ipsec4_output"); + if (!state->ro) + panic("state->ro == NULL in ipsec4_output"); + if (!state->dst) + panic("state->dst == NULL in ipsec4_output"); + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec4_output: applyed SP\n"); + kdebug_secpolicy(sp)); + + for (isr = sp->req; isr != NULL; isr = isr->next) { + +#if 0 /* give up to check restriction of transport mode */ + /* XXX but should be checked somewhere */ + /* + * some of the IPsec operation must be performed only in + * originating case. + */ + if (isr->saidx.mode == IPSEC_MODE_TRANSPORT + && (flags & IP_FORWARDING)) + continue; +#endif + + /* make SA index for search proper SA */ + ip = mtod(state->m, struct ip *); + bcopy(&isr->saidx, &saidx, sizeof(saidx)); + saidx.mode = isr->saidx.mode; + saidx.reqid = isr->saidx.reqid; + sin = (struct sockaddr_in *)&saidx.src; + if (sin->sin_len == 0) { + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + bcopy(&ip->ip_src, &sin->sin_addr, + sizeof(sin->sin_addr)); + } + sin = (struct sockaddr_in *)&saidx.dst; + if (sin->sin_len == 0) { + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = IPSEC_PORT_ANY; + bcopy(&ip->ip_dst, &sin->sin_addr, + sizeof(sin->sin_addr)); + } + + if ((error = key_checkrequest(isr, &saidx)) != 0) { + /* + * IPsec processing is required, but no SA found. + * I assume that key_acquire() had been called + * to get/establish the SA. Here I discard + * this packet because it is responsibility for + * upper layer to retransmit the packet. + */ + ipsecstat.out_nosa++; + goto bad; + } + + /* validity check */ + if (isr->sav == NULL) { + switch (ipsec_get_reqlevel(isr)) { + case IPSEC_LEVEL_USE: + continue; + case IPSEC_LEVEL_REQUIRE: + /* must be not reached here. */ + panic("ipsec4_output: no SA found, but required."); + } + } + + /* + * If there is no valid SA, we give up to process any + * more. In such a case, the SA's status is changed + * from DYING to DEAD after allocating. If a packet + * send to the receiver by dead SA, the receiver can + * not decode a packet because SA has been dead. + */ + if (isr->sav->state != SADB_SASTATE_MATURE + && isr->sav->state != SADB_SASTATE_DYING) { + ipsecstat.out_nosa++; + error = EINVAL; + goto bad; + } + + /* + * There may be the case that SA status will be changed when + * we are refering to one. So calling splsoftnet(). + */ + s = splnet(); + + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + /* + * build IPsec tunnel. + */ + /* XXX should be processed with other familiy */ + if (((struct sockaddr *)&isr->sav->sah->saidx.src)->sa_family != AF_INET) { + ipseclog((LOG_ERR, "ipsec4_output: " + "family mismatched between inner and outer spi=%u\n", + (u_int32_t)ntohl(isr->sav->spi))); + splx(s); + error = EAFNOSUPPORT; + goto bad; + } + + state->m = ipsec4_splithdr(state->m); + if (!state->m) { + splx(s); + error = ENOMEM; + goto bad; + } + error = ipsec4_encapsulate(state->m, isr->sav); + splx(s); + if (error) { + state->m = NULL; + goto bad; + } + ip = mtod(state->m, struct ip *); + + state->ro = &isr->sav->sah->sa_route; + state->dst = (struct sockaddr *)&state->ro->ro_dst; + dst4 = (struct sockaddr_in *)state->dst; + if (state->ro->ro_rt + && ((state->ro->ro_rt->rt_flags & RTF_UP) == 0 + || dst4->sin_addr.s_addr != ip->ip_dst.s_addr)) { + RTFREE(state->ro->ro_rt); + state->ro->ro_rt = NULL; + } + if (state->ro->ro_rt == 0) { + dst4->sin_family = AF_INET; + dst4->sin_len = sizeof(*dst4); + dst4->sin_addr = ip->ip_dst; + rtalloc(state->ro); + } + if (state->ro->ro_rt == 0) { + ipstat.ips_noroute++; + error = EHOSTUNREACH; + goto bad; + } + + /* adjust state->dst if tunnel endpoint is offlink */ + if (state->ro->ro_rt->rt_flags & RTF_GATEWAY) { + state->dst = (struct sockaddr *)state->ro->ro_rt->rt_gateway; + dst4 = (struct sockaddr_in *)state->dst; + } + } else + splx(s); + + state->m = ipsec4_splithdr(state->m); + if (!state->m) { + error = ENOMEM; + goto bad; + } + switch (isr->saidx.proto) { + case IPPROTO_ESP: +#ifdef IPSEC_ESP + if ((error = esp4_output(state->m, isr)) != 0) { + state->m = NULL; + goto bad; + } + break; +#else + m_freem(state->m); + state->m = NULL; + error = EINVAL; + goto bad; +#endif + case IPPROTO_AH: + if ((error = ah4_output(state->m, isr)) != 0) { + state->m = NULL; + goto bad; + } + break; + case IPPROTO_IPCOMP: + if ((error = ipcomp4_output(state->m, isr)) != 0) { + state->m = NULL; + goto bad; + } + break; + default: + ipseclog((LOG_ERR, + "ipsec4_output: unknown ipsec protocol %d\n", + isr->saidx.proto)); + m_freem(state->m); + state->m = NULL; + error = EINVAL; + goto bad; + } + + if (state->m == 0) { + error = ENOMEM; + goto bad; + } + ip = mtod(state->m, struct ip *); + } + + return 0; + +bad: + m_freem(state->m); + state->m = NULL; + return error; +} +#endif + +#ifdef INET6 +/* + * IPsec output logic for IPv6, transport mode. + */ +int +ipsec6_output_trans(state, nexthdrp, mprev, sp, flags, tun) + struct ipsec_output_state *state; + u_char *nexthdrp; + struct mbuf *mprev; + struct secpolicy *sp; + int flags; + int *tun; +{ + struct ip6_hdr *ip6; + struct ipsecrequest *isr = NULL; + struct secasindex saidx; + int error = 0; + int plen; + struct sockaddr_in6 *sin6; + + if (!state) + panic("state == NULL in ipsec6_output_trans"); + if (!state->m) + panic("state->m == NULL in ipsec6_output_trans"); + if (!nexthdrp) + panic("nexthdrp == NULL in ipsec6_output_trans"); + if (!mprev) + panic("mprev == NULL in ipsec6_output_trans"); + if (!sp) + panic("sp == NULL in ipsec6_output_trans"); + if (!tun) + panic("tun == NULL in ipsec6_output_trans"); + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_output_trans: applyed SP\n"); + kdebug_secpolicy(sp)); + + *tun = 0; + for (isr = sp->req; isr; isr = isr->next) { + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + /* the rest will be handled by ipsec6_output_tunnel() */ + break; + } + + /* make SA index for search proper SA */ + ip6 = mtod(state->m, struct ip6_hdr *); + bcopy(&isr->saidx, &saidx, sizeof(saidx)); + saidx.mode = isr->saidx.mode; + saidx.reqid = isr->saidx.reqid; + sin6 = (struct sockaddr_in6 *)&saidx.src; + if (sin6->sin6_len == 0) { + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + bcopy(&ip6->ip6_src, &sin6->sin6_addr, + sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + } + sin6 = (struct sockaddr_in6 *)&saidx.dst; + if (sin6->sin6_len == 0) { + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + bcopy(&ip6->ip6_dst, &sin6->sin6_addr, + sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } + } + + if (key_checkrequest(isr, &saidx) == ENOENT) { + /* + * IPsec processing is required, but no SA found. + * I assume that key_acquire() had been called + * to get/establish the SA. Here I discard + * this packet because it is responsibility for + * upper layer to retransmit the packet. + */ + ipsec6stat.out_nosa++; + error = ENOENT; + + /* + * Notify the fact that the packet is discarded + * to ourselves. I believe this is better than + * just silently discarding. (jinmei@kame.net) + * XXX: should we restrict the error to TCP packets? + * XXX: should we directly notify sockets via + * pfctlinputs? + */ + icmp6_error(state->m, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADMIN, 0); + state->m = NULL; /* icmp6_error freed the mbuf */ + goto bad; + } + + /* validity check */ + if (isr->sav == NULL) { + switch (ipsec_get_reqlevel(isr)) { + case IPSEC_LEVEL_USE: + continue; + case IPSEC_LEVEL_REQUIRE: + /* must be not reached here. */ + panic("ipsec6_output_trans: no SA found, but required."); + } + } + + /* + * If there is no valid SA, we give up to process. + * see same place at ipsec4_output(). + */ + if (isr->sav->state != SADB_SASTATE_MATURE + && isr->sav->state != SADB_SASTATE_DYING) { + ipsec6stat.out_nosa++; + error = EINVAL; + goto bad; + } + + switch (isr->saidx.proto) { + case IPPROTO_ESP: +#ifdef IPSEC_ESP + error = esp6_output(state->m, nexthdrp, mprev->m_next, isr); +#else + m_freem(state->m); + error = EINVAL; +#endif + break; + case IPPROTO_AH: + error = ah6_output(state->m, nexthdrp, mprev->m_next, isr); + break; + case IPPROTO_IPCOMP: + error = ipcomp6_output(state->m, nexthdrp, mprev->m_next, isr); + break; + default: + ipseclog((LOG_ERR, "ipsec6_output_trans: " + "unknown ipsec protocol %d\n", isr->saidx.proto)); + m_freem(state->m); + ipsec6stat.out_inval++; + error = EINVAL; + break; + } + if (error) { + state->m = NULL; + goto bad; + } + plen = state->m->m_pkthdr.len - sizeof(struct ip6_hdr); + if (plen > IPV6_MAXPACKET) { + ipseclog((LOG_ERR, "ipsec6_output_trans: " + "IPsec with IPv6 jumbogram is not supported\n")); + ipsec6stat.out_inval++; + error = EINVAL; /* XXX */ + goto bad; + } + ip6 = mtod(state->m, struct ip6_hdr *); + ip6->ip6_plen = htons(plen); + } + + /* if we have more to go, we need a tunnel mode processing */ + if (isr != NULL) + *tun = 1; + + return 0; + +bad: + m_freem(state->m); + state->m = NULL; + return error; +} + +/* + * IPsec output logic for IPv6, tunnel mode. + */ +int +ipsec6_output_tunnel(state, sp, flags) + struct ipsec_output_state *state; + struct secpolicy *sp; + int flags; +{ + struct ip6_hdr *ip6; + struct ipsecrequest *isr = NULL; + struct secasindex saidx; + int error = 0; + int plen; + struct sockaddr_in6* dst6; + int s; + + if (!state) + panic("state == NULL in ipsec6_output_tunnel"); + if (!state->m) + panic("state->m == NULL in ipsec6_output_tunnel"); + if (!sp) + panic("sp == NULL in ipsec6_output_tunnel"); + + KEYDEBUG(KEYDEBUG_IPSEC_DATA, + printf("ipsec6_output_tunnel: applyed SP\n"); + kdebug_secpolicy(sp)); + + /* + * transport mode ipsec (before the 1st tunnel mode) is already + * processed by ipsec6_output_trans(). + */ + for (isr = sp->req; isr; isr = isr->next) { + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) + break; + } + + for (/* already initialized */; isr; isr = isr->next) { + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + /* When tunnel mode, SA peers must be specified. */ + bcopy(&isr->saidx, &saidx, sizeof(saidx)); + } else { + /* make SA index to look for a proper SA */ + struct sockaddr_in6 *sin6; + + bzero(&saidx, sizeof(saidx)); + saidx.proto = isr->saidx.proto; + saidx.mode = isr->saidx.mode; + saidx.reqid = isr->saidx.reqid; + + ip6 = mtod(state->m, struct ip6_hdr *); + sin6 = (struct sockaddr_in6 *)&saidx.src; + if (sin6->sin6_len == 0) { + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + bcopy(&ip6->ip6_src, &sin6->sin6_addr, + sizeof(ip6->ip6_src)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_src)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_src.s6_addr16[1]); + } + } + sin6 = (struct sockaddr_in6 *)&saidx.dst; + if (sin6->sin6_len == 0) { + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = IPSEC_PORT_ANY; + bcopy(&ip6->ip6_dst, &sin6->sin6_addr, + sizeof(ip6->ip6_dst)); + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + /* fix scope id for comparing SPD */ + sin6->sin6_addr.s6_addr16[1] = 0; + sin6->sin6_scope_id = ntohs(ip6->ip6_dst.s6_addr16[1]); + } + } + } + + if (key_checkrequest(isr, &saidx) == ENOENT) { + /* + * IPsec processing is required, but no SA found. + * I assume that key_acquire() had been called + * to get/establish the SA. Here I discard + * this packet because it is responsibility for + * upper layer to retransmit the packet. + */ + ipsec6stat.out_nosa++; + error = ENOENT; + goto bad; + } + + /* validity check */ + if (isr->sav == NULL) { + switch (ipsec_get_reqlevel(isr)) { + case IPSEC_LEVEL_USE: + continue; + case IPSEC_LEVEL_REQUIRE: + /* must be not reached here. */ + panic("ipsec6_output_tunnel: no SA found, but required."); + } + } + + /* + * If there is no valid SA, we give up to process. + * see same place at ipsec4_output(). + */ + if (isr->sav->state != SADB_SASTATE_MATURE + && isr->sav->state != SADB_SASTATE_DYING) { + ipsec6stat.out_nosa++; + error = EINVAL; + goto bad; + } + + /* + * There may be the case that SA status will be changed when + * we are refering to one. So calling splsoftnet(). + */ + s = splnet(); + + if (isr->saidx.mode == IPSEC_MODE_TUNNEL) { + /* + * build IPsec tunnel. + */ + /* XXX should be processed with other familiy */ + if (((struct sockaddr *)&isr->sav->sah->saidx.src)->sa_family != AF_INET6) { + ipseclog((LOG_ERR, "ipsec6_output_tunnel: " + "family mismatched between inner and outer, spi=%u\n", + (u_int32_t)ntohl(isr->sav->spi))); + splx(s); + ipsec6stat.out_inval++; + error = EAFNOSUPPORT; + goto bad; + } + + state->m = ipsec6_splithdr(state->m); + if (!state->m) { + splx(s); + ipsec6stat.out_nomem++; + error = ENOMEM; + goto bad; + } + error = ipsec6_encapsulate(state->m, isr->sav); + splx(s); + if (error) { + state->m = 0; + goto bad; + } + ip6 = mtod(state->m, struct ip6_hdr *); + + state->ro = &isr->sav->sah->sa_route; + state->dst = (struct sockaddr *)&state->ro->ro_dst; + dst6 = (struct sockaddr_in6 *)state->dst; + if (state->ro->ro_rt + && ((state->ro->ro_rt->rt_flags & RTF_UP) == 0 + || !IN6_ARE_ADDR_EQUAL(&dst6->sin6_addr, &ip6->ip6_dst))) { + RTFREE(state->ro->ro_rt); + state->ro->ro_rt = NULL; + } + if (state->ro->ro_rt == 0) { + bzero(dst6, sizeof(*dst6)); + dst6->sin6_family = AF_INET6; + dst6->sin6_len = sizeof(*dst6); + dst6->sin6_addr = ip6->ip6_dst; + rtalloc(state->ro); + } + if (state->ro->ro_rt == 0) { + ip6stat.ip6s_noroute++; + ipsec6stat.out_noroute++; + error = EHOSTUNREACH; + goto bad; + } + + /* adjust state->dst if tunnel endpoint is offlink */ + if (state->ro->ro_rt->rt_flags & RTF_GATEWAY) { + state->dst = (struct sockaddr *)state->ro->ro_rt->rt_gateway; + dst6 = (struct sockaddr_in6 *)state->dst; + } + } else + splx(s); + + state->m = ipsec6_splithdr(state->m); + if (!state->m) { + ipsec6stat.out_nomem++; + error = ENOMEM; + goto bad; + } + ip6 = mtod(state->m, struct ip6_hdr *); + switch (isr->saidx.proto) { + case IPPROTO_ESP: +#ifdef IPSEC_ESP + error = esp6_output(state->m, &ip6->ip6_nxt, state->m->m_next, isr); +#else + m_freem(state->m); + error = EINVAL; +#endif + break; + case IPPROTO_AH: + error = ah6_output(state->m, &ip6->ip6_nxt, state->m->m_next, isr); + break; + case IPPROTO_IPCOMP: + /* XXX code should be here */ + /* FALLTHROUGH */ + default: + ipseclog((LOG_ERR, "ipsec6_output_tunnel: " + "unknown ipsec protocol %d\n", isr->saidx.proto)); + m_freem(state->m); + ipsec6stat.out_inval++; + error = EINVAL; + break; + } + if (error) { + state->m = NULL; + goto bad; + } + plen = state->m->m_pkthdr.len - sizeof(struct ip6_hdr); + if (plen > IPV6_MAXPACKET) { + ipseclog((LOG_ERR, "ipsec6_output_tunnel: " + "IPsec with IPv6 jumbogram is not supported\n")); + ipsec6stat.out_inval++; + error = EINVAL; /* XXX */ + goto bad; + } + ip6 = mtod(state->m, struct ip6_hdr *); + ip6->ip6_plen = htons(plen); + } + + return 0; + +bad: + m_freem(state->m); + state->m = NULL; + return error; +} +#endif /* INET6 */ + +#ifdef INET +/* + * Chop IP header and option off from the payload. + */ +static struct mbuf * +ipsec4_splithdr(m) + struct mbuf *m; +{ + struct mbuf *mh; + struct ip *ip; + int hlen; + + if (m->m_len < sizeof(struct ip)) + panic("ipsec4_splithdr: first mbuf too short"); + ip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = _IP_VHL_HL(ip->ip_vhl) << 2; +#else + hlen = ip->ip_hl << 2; +#endif + if (m->m_len > hlen) { + MGETHDR(mh, M_DONTWAIT, MT_HEADER); + if (!mh) { + m_freem(m); + return NULL; + } + M_MOVE_PKTHDR(mh, m); + MH_ALIGN(mh, hlen); + m->m_len -= hlen; + m->m_data += hlen; + mh->m_next = m; + m = mh; + m->m_len = hlen; + bcopy((caddr_t)ip, mtod(m, caddr_t), hlen); + } else if (m->m_len < hlen) { + m = m_pullup(m, hlen); + if (!m) + return NULL; + } + return m; +} +#endif + +#ifdef INET6 +static struct mbuf * +ipsec6_splithdr(m) + struct mbuf *m; +{ + struct mbuf *mh; + struct ip6_hdr *ip6; + int hlen; + + if (m->m_len < sizeof(struct ip6_hdr)) + panic("ipsec6_splithdr: first mbuf too short"); + ip6 = mtod(m, struct ip6_hdr *); + hlen = sizeof(struct ip6_hdr); + if (m->m_len > hlen) { + MGETHDR(mh, M_DONTWAIT, MT_HEADER); + if (!mh) { + m_freem(m); + return NULL; + } + M_MOVE_PKTHDR(mh, m); + MH_ALIGN(mh, hlen); + m->m_len -= hlen; + m->m_data += hlen; + mh->m_next = m; + m = mh; + m->m_len = hlen; + bcopy((caddr_t)ip6, mtod(m, caddr_t), hlen); + } else if (m->m_len < hlen) { + m = m_pullup(m, hlen); + if (!m) + return NULL; + } + return m; +} +#endif + +/* validate inbound IPsec tunnel packet. */ +int +ipsec4_tunnel_validate(m, off, nxt0, sav) + struct mbuf *m; /* no pullup permitted, m->m_len >= ip */ + int off; + u_int nxt0; + struct secasvar *sav; +{ + u_int8_t nxt = nxt0 & 0xff; + struct sockaddr_in *sin; + struct sockaddr_in osrc, odst, isrc, idst; + int hlen; + struct secpolicy *sp; + struct ip *oip; + +#ifdef DIAGNOSTIC + if (m->m_len < sizeof(struct ip)) + panic("too short mbuf on ipsec4_tunnel_validate"); +#endif + if (nxt != IPPROTO_IPV4) + return 0; + if (m->m_pkthdr.len < off + sizeof(struct ip)) + return 0; + /* do not decapsulate if the SA is for transport mode only */ + if (sav->sah->saidx.mode == IPSEC_MODE_TRANSPORT) + return 0; + + oip = mtod(m, struct ip *); +#ifdef _IP_VHL + hlen = _IP_VHL_HL(oip->ip_vhl) << 2; +#else + hlen = oip->ip_hl << 2; +#endif + if (hlen != sizeof(struct ip)) + return 0; + + /* AF_INET6 should be supported, but at this moment we don't. */ + sin = (struct sockaddr_in *)&sav->sah->saidx.dst; + if (sin->sin_family != AF_INET) + return 0; + if (bcmp(&oip->ip_dst, &sin->sin_addr, sizeof(oip->ip_dst)) != 0) + return 0; + + /* XXX slow */ + bzero(&osrc, sizeof(osrc)); + bzero(&odst, sizeof(odst)); + bzero(&isrc, sizeof(isrc)); + bzero(&idst, sizeof(idst)); + osrc.sin_family = odst.sin_family = isrc.sin_family = idst.sin_family = + AF_INET; + osrc.sin_len = odst.sin_len = isrc.sin_len = idst.sin_len = + sizeof(struct sockaddr_in); + osrc.sin_addr = oip->ip_src; + odst.sin_addr = oip->ip_dst; + m_copydata(m, off + offsetof(struct ip, ip_src), sizeof(isrc.sin_addr), + (caddr_t)&isrc.sin_addr); + m_copydata(m, off + offsetof(struct ip, ip_dst), sizeof(idst.sin_addr), + (caddr_t)&idst.sin_addr); + + /* + * RFC2401 5.2.1 (b): (assume that we are using tunnel mode) + * - if the inner destination is multicast address, there can be + * multiple permissible inner source address. implementation + * may want to skip verification of inner source address against + * SPD selector. + * - if the inner protocol is ICMP, the packet may be an error report + * from routers on the other side of the VPN cloud (R in the + * following diagram). in this case, we cannot verify inner source + * address against SPD selector. + * me -- gw === gw -- R -- you + * + * we consider the first bullet to be users responsibility on SPD entry + * configuration (if you need to encrypt multicast traffic, set + * the source range of SPD selector to 0.0.0.0/0, or have explicit + * address ranges for possible senders). + * the second bullet is not taken care of (yet). + * + * therefore, we do not do anything special about inner source. + */ + + sp = key_gettunnel((struct sockaddr *)&osrc, (struct sockaddr *)&odst, + (struct sockaddr *)&isrc, (struct sockaddr *)&idst); + if (!sp) + return 0; + key_freesp(sp); + + return 1; +} + +#ifdef INET6 +/* validate inbound IPsec tunnel packet. */ +int +ipsec6_tunnel_validate(m, off, nxt0, sav) + struct mbuf *m; /* no pullup permitted, m->m_len >= ip */ + int off; + u_int nxt0; + struct secasvar *sav; +{ + u_int8_t nxt = nxt0 & 0xff; + struct sockaddr_in6 *sin6; + struct sockaddr_in6 osrc, odst, isrc, idst; + struct secpolicy *sp; + struct ip6_hdr *oip6; + +#ifdef DIAGNOSTIC + if (m->m_len < sizeof(struct ip6_hdr)) + panic("too short mbuf on ipsec6_tunnel_validate"); +#endif + if (nxt != IPPROTO_IPV6) + return 0; + if (m->m_pkthdr.len < off + sizeof(struct ip6_hdr)) + return 0; + /* do not decapsulate if the SA is for transport mode only */ + if (sav->sah->saidx.mode == IPSEC_MODE_TRANSPORT) + return 0; + + oip6 = mtod(m, struct ip6_hdr *); + /* AF_INET should be supported, but at this moment we don't. */ + sin6 = (struct sockaddr_in6 *)&sav->sah->saidx.dst; + if (sin6->sin6_family != AF_INET6) + return 0; + if (!IN6_ARE_ADDR_EQUAL(&oip6->ip6_dst, &sin6->sin6_addr)) + return 0; + + /* XXX slow */ + bzero(&osrc, sizeof(osrc)); + bzero(&odst, sizeof(odst)); + bzero(&isrc, sizeof(isrc)); + bzero(&idst, sizeof(idst)); + osrc.sin6_family = odst.sin6_family = isrc.sin6_family = + idst.sin6_family = AF_INET6; + osrc.sin6_len = odst.sin6_len = isrc.sin6_len = idst.sin6_len = + sizeof(struct sockaddr_in6); + osrc.sin6_addr = oip6->ip6_src; + odst.sin6_addr = oip6->ip6_dst; + m_copydata(m, off + offsetof(struct ip6_hdr, ip6_src), + sizeof(isrc.sin6_addr), (caddr_t)&isrc.sin6_addr); + m_copydata(m, off + offsetof(struct ip6_hdr, ip6_dst), + sizeof(idst.sin6_addr), (caddr_t)&idst.sin6_addr); + + /* + * regarding to inner source address validation, see a long comment + * in ipsec4_tunnel_validate. + */ + + sp = key_gettunnel((struct sockaddr *)&osrc, (struct sockaddr *)&odst, + (struct sockaddr *)&isrc, (struct sockaddr *)&idst); + /* + * when there is no suitable inbound policy for the packet of the ipsec + * tunnel mode, the kernel never decapsulate the tunneled packet + * as the ipsec tunnel mode even when the system wide policy is "none". + * then the kernel leaves the generic tunnel module to process this + * packet. if there is no rule of the generic tunnel, the packet + * is rejected and the statistics will be counted up. + */ + if (!sp) + return 0; + key_freesp(sp); + + return 1; +} +#endif + +/* + * Make a mbuf chain for encryption. + * If the original mbuf chain contains a mbuf with a cluster, + * allocate a new cluster and copy the data to the new cluster. + * XXX: this hack is inefficient, but is necessary to handle cases + * of TCP retransmission... + */ +struct mbuf * +ipsec_copypkt(m) + struct mbuf *m; +{ + struct mbuf *n, **mpp, *mnew; + + for (n = m, mpp = &m; n; n = n->m_next) { + if (n->m_flags & M_EXT) { + /* + * Make a copy only if there are more than one + * references to the cluster. + * XXX: is this approach effective? + */ + if (n->m_ext.ext_type != EXT_CLUSTER || MEXT_IS_REF(n)) + { + int remain, copied; + struct mbuf *mm; + + if (n->m_flags & M_PKTHDR) { + MGETHDR(mnew, M_DONTWAIT, MT_HEADER); + if (mnew == NULL) + goto fail; + if (!m_dup_pkthdr(mnew, n, M_DONTWAIT)) { + m_free(mnew); + goto fail; + } + } + else { + MGET(mnew, M_DONTWAIT, MT_DATA); + if (mnew == NULL) + goto fail; + } + mnew->m_len = 0; + mm = mnew; + + /* + * Copy data. If we don't have enough space to + * store the whole data, allocate a cluster + * or additional mbufs. + * XXX: we don't use m_copyback(), since the + * function does not use clusters and thus is + * inefficient. + */ + remain = n->m_len; + copied = 0; + while (1) { + int len; + struct mbuf *mn; + + if (remain <= (mm->m_flags & M_PKTHDR ? MHLEN : MLEN)) + len = remain; + else { /* allocate a cluster */ + MCLGET(mm, M_DONTWAIT); + if (!(mm->m_flags & M_EXT)) { + m_free(mm); + goto fail; + } + len = remain < MCLBYTES ? + remain : MCLBYTES; + } + + bcopy(n->m_data + copied, mm->m_data, + len); + + copied += len; + remain -= len; + mm->m_len = len; + + if (remain <= 0) /* completed? */ + break; + + /* need another mbuf */ + MGETHDR(mn, M_DONTWAIT, MT_HEADER); + if (mn == NULL) + goto fail; + mn->m_pkthdr.rcvif = NULL; + mm->m_next = mn; + mm = mn; + } + + /* adjust chain */ + mm->m_next = m_free(n); + n = mm; + *mpp = mnew; + mpp = &n->m_next; + + continue; + } + } + *mpp = n; + mpp = &n->m_next; + } + + return(m); + fail: + m_freem(m); + return(NULL); +} + +void +ipsec_delaux(m) + struct mbuf *m; +{ + struct m_tag *tag; + + while ((tag = m_tag_find(m, PACKET_TAG_IPSEC_HISTORY, NULL)) != NULL) + m_tag_delete(m, tag); +} + +int +ipsec_addhist(m, proto, spi) + struct mbuf *m; + int proto; + u_int32_t spi; +{ + struct m_tag *tag; + struct ipsec_history *p; + + tag = m_tag_get(PACKET_TAG_IPSEC_HISTORY, + sizeof (struct ipsec_history), M_NOWAIT); + if (tag == NULL) + return ENOBUFS; + p = (struct ipsec_history *)(tag+1); + bzero(p, sizeof(*p)); + p->ih_proto = proto; + p->ih_spi = spi; + m_tag_prepend(m, tag); + return 0; +} + +struct ipsec_history * +ipsec_gethist(m, lenp) + struct mbuf *m; + int *lenp; +{ + struct m_tag *tag; + + tag = m_tag_find(m, PACKET_TAG_IPSEC_HISTORY, NULL); + if (tag == NULL) + return NULL; + /* XXX NB: noone uses this so fake it */ + if (lenp) + *lenp = sizeof (struct ipsec_history); + return ((struct ipsec_history *)(tag+1)); +} diff --git a/sys/netinet6/ipsec.h b/sys/netinet6/ipsec.h new file mode 100644 index 0000000..40f5f56 --- /dev/null +++ b/sys/netinet6/ipsec.h @@ -0,0 +1,353 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.h,v 1.53 2001/11/20 08:32:38 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * IPsec controller part. + */ + +#ifndef _NETINET6_IPSEC_H_ +#define _NETINET6_IPSEC_H_ + +#if defined(_KERNEL) && !defined(_LKM) && !defined(KLD_MODULE) +#include "opt_inet.h" +#include "opt_ipsec.h" +#endif + +#include <net/pfkeyv2.h> +#include <netkey/keydb.h> + +#ifdef _KERNEL + +/* + * Security Policy Index + * Ensure that both address families in the "src" and "dst" are same. + * When the value of the ul_proto is ICMPv6, the port field in "src" + * specifies ICMPv6 type, and the port field in "dst" specifies ICMPv6 code. + */ +struct secpolicyindex { + u_int8_t dir; /* direction of packet flow, see blow */ + struct sockaddr_storage src; /* IP src address for SP */ + struct sockaddr_storage dst; /* IP dst address for SP */ + u_int8_t prefs; /* prefix length in bits for src */ + u_int8_t prefd; /* prefix length in bits for dst */ + u_int16_t ul_proto; /* upper layer Protocol */ +#ifdef notyet + uid_t uids; + uid_t uidd; + gid_t gids; + gid_t gidd; +#endif +}; + +/* Security Policy Data Base */ +struct secpolicy { + LIST_ENTRY(secpolicy) chain; + + int refcnt; /* reference count */ + struct secpolicyindex spidx; /* selector */ + u_int32_t id; /* It's unique number on the system. */ + u_int state; /* 0: dead, others: alive */ +#define IPSEC_SPSTATE_DEAD 0 +#define IPSEC_SPSTATE_ALIVE 1 + + u_int policy; /* DISCARD, NONE or IPSEC, see keyv2.h */ + struct ipsecrequest *req; + /* pointer to the ipsec request tree, */ + /* if policy == IPSEC else this value == NULL.*/ + + /* + * lifetime handler. + * the policy can be used without limitiation if both lifetime and + * validtime are zero. + * "lifetime" is passed by sadb_lifetime.sadb_lifetime_addtime. + * "validtime" is passed by sadb_lifetime.sadb_lifetime_usetime. + */ + long created; /* time created the policy */ + long lastused; /* updated every when kernel sends a packet */ + long lifetime; /* duration of the lifetime of this policy */ + long validtime; /* duration this policy is valid without use */ +}; + +/* Request for IPsec */ +struct ipsecrequest { + struct ipsecrequest *next; + /* pointer to next structure */ + /* If NULL, it means the end of chain. */ + struct secasindex saidx;/* hint for search proper SA */ + /* if __ss_len == 0 then no address specified.*/ + u_int level; /* IPsec level defined below. */ + + struct secasvar *sav; /* place holder of SA for use */ + struct secpolicy *sp; /* back pointer to SP */ +}; + +/* security policy in PCB */ +struct inpcbpolicy { + struct secpolicy *sp_in; + struct secpolicy *sp_out; + int priv; /* privileged socket ? */ +}; + +/* SP acquiring list table. */ +struct secspacq { + LIST_ENTRY(secspacq) chain; + + struct secpolicyindex spidx; + + long created; /* for lifetime */ + int count; /* for lifetime */ + /* XXX: here is mbuf place holder to be sent ? */ +}; +#endif /* _KERNEL */ + +/* according to IANA assignment, port 0x0000 and proto 0xff are reserved. */ +#define IPSEC_PORT_ANY 0 +#define IPSEC_ULPROTO_ANY 255 +#define IPSEC_PROTO_ANY 255 + +/* mode of security protocol */ +/* NOTE: DON'T use IPSEC_MODE_ANY at SPD. It's only use in SAD */ +#define IPSEC_MODE_ANY 0 /* i.e. wildcard. */ +#define IPSEC_MODE_TRANSPORT 1 +#define IPSEC_MODE_TUNNEL 2 + +/* + * Direction of security policy. + * NOTE: Since INVALID is used just as flag. + * The other are used for loop counter too. + */ +#define IPSEC_DIR_ANY 0 +#define IPSEC_DIR_INBOUND 1 +#define IPSEC_DIR_OUTBOUND 2 +#define IPSEC_DIR_MAX 3 +#define IPSEC_DIR_INVALID 4 + +/* Policy level */ +/* + * IPSEC, ENTRUST and BYPASS are allowed for setsockopt() in PCB, + * DISCARD, IPSEC and NONE are allowed for setkey() in SPD. + * DISCARD and NONE are allowed for system default. + */ +#define IPSEC_POLICY_DISCARD 0 /* discarding packet */ +#define IPSEC_POLICY_NONE 1 /* through IPsec engine */ +#define IPSEC_POLICY_IPSEC 2 /* do IPsec */ +#define IPSEC_POLICY_ENTRUST 3 /* consulting SPD if present. */ +#define IPSEC_POLICY_BYPASS 4 /* only for privileged socket. */ + +/* Security protocol level */ +#define IPSEC_LEVEL_DEFAULT 0 /* reference to system default */ +#define IPSEC_LEVEL_USE 1 /* use SA if present. */ +#define IPSEC_LEVEL_REQUIRE 2 /* require SA. */ +#define IPSEC_LEVEL_UNIQUE 3 /* unique SA. */ + +#define IPSEC_MANUAL_REQID_MAX 0x3fff + /* + * if security policy level == unique, this id + * indicate to a relative SA for use, else is + * zero. + * 1 - 0x3fff are reserved for manual keying. + * 0 are reserved for above reason. Others is + * for kernel use. + * Note that this id doesn't identify SA + * by only itself. + */ +#define IPSEC_REPLAYWSIZE 32 + +/* statistics for ipsec processing */ +struct ipsecstat { + u_quad_t in_success; /* succeeded inbound process */ + u_quad_t in_polvio; + /* security policy violation for inbound process */ + u_quad_t in_nosa; /* inbound SA is unavailable */ + u_quad_t in_inval; /* inbound processing failed due to EINVAL */ + u_quad_t in_nomem; /* inbound processing failed due to ENOBUFS */ + u_quad_t in_badspi; /* failed getting a SPI */ + u_quad_t in_ahreplay; /* AH replay check failed */ + u_quad_t in_espreplay; /* ESP replay check failed */ + u_quad_t in_ahauthsucc; /* AH authentication success */ + u_quad_t in_ahauthfail; /* AH authentication failure */ + u_quad_t in_espauthsucc; /* ESP authentication success */ + u_quad_t in_espauthfail; /* ESP authentication failure */ + u_quad_t in_esphist[256]; + u_quad_t in_ahhist[256]; + u_quad_t in_comphist[256]; + u_quad_t out_success; /* succeeded outbound process */ + u_quad_t out_polvio; + /* security policy violation for outbound process */ + u_quad_t out_nosa; /* outbound SA is unavailable */ + u_quad_t out_inval; /* outbound process failed due to EINVAL */ + u_quad_t out_nomem; /* inbound processing failed due to ENOBUFS */ + u_quad_t out_noroute; /* there is no route */ + u_quad_t out_esphist[256]; + u_quad_t out_ahhist[256]; + u_quad_t out_comphist[256]; +}; + +/* + * Definitions for IPsec & Key sysctl operations. + */ +/* + * Names for IPsec & Key sysctl objects + */ +#define IPSECCTL_STATS 1 /* stats */ +#define IPSECCTL_DEF_POLICY 2 +#define IPSECCTL_DEF_ESP_TRANSLEV 3 /* int; ESP transport mode */ +#define IPSECCTL_DEF_ESP_NETLEV 4 /* int; ESP tunnel mode */ +#define IPSECCTL_DEF_AH_TRANSLEV 5 /* int; AH transport mode */ +#define IPSECCTL_DEF_AH_NETLEV 6 /* int; AH tunnel mode */ +#if 0 /* obsolete, do not reuse */ +#define IPSECCTL_INBOUND_CALL_IKE 7 +#endif +#define IPSECCTL_AH_CLEARTOS 8 +#define IPSECCTL_AH_OFFSETMASK 9 +#define IPSECCTL_DFBIT 10 +#define IPSECCTL_ECN 11 +#define IPSECCTL_DEBUG 12 +#define IPSECCTL_ESP_RANDPAD 13 +#define IPSECCTL_MAXID 14 + +#define IPSECCTL_NAMES { \ + { 0, 0 }, \ + { 0, 0 }, \ + { "def_policy", CTLTYPE_INT }, \ + { "esp_trans_deflev", CTLTYPE_INT }, \ + { "esp_net_deflev", CTLTYPE_INT }, \ + { "ah_trans_deflev", CTLTYPE_INT }, \ + { "ah_net_deflev", CTLTYPE_INT }, \ + { 0, 0 }, \ + { "ah_cleartos", CTLTYPE_INT }, \ + { "ah_offsetmask", CTLTYPE_INT }, \ + { "dfbit", CTLTYPE_INT }, \ + { "ecn", CTLTYPE_INT }, \ + { "debug", CTLTYPE_INT }, \ + { "esp_randpad", CTLTYPE_INT }, \ +} + +#define IPSEC6CTL_NAMES { \ + { 0, 0 }, \ + { 0, 0 }, \ + { "def_policy", CTLTYPE_INT }, \ + { "esp_trans_deflev", CTLTYPE_INT }, \ + { "esp_net_deflev", CTLTYPE_INT }, \ + { "ah_trans_deflev", CTLTYPE_INT }, \ + { "ah_net_deflev", CTLTYPE_INT }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { 0, 0 }, \ + { "ecn", CTLTYPE_INT }, \ + { "debug", CTLTYPE_INT }, \ + { "esp_randpad", CTLTYPE_INT }, \ +} + +#ifdef _KERNEL +struct ipsec_output_state { + struct mbuf *m; + struct route *ro; + struct sockaddr *dst; +}; + +struct ipsec_history { + int ih_proto; + u_int32_t ih_spi; +}; + +extern int ipsec_debug; + +extern struct ipsecstat ipsecstat; +extern struct secpolicy ip4_def_policy; +extern int ip4_esp_trans_deflev; +extern int ip4_esp_net_deflev; +extern int ip4_ah_trans_deflev; +extern int ip4_ah_net_deflev; +extern int ip4_ah_cleartos; +extern int ip4_ah_offsetmask; +extern int ip4_ipsec_dfbit; +extern int ip4_ipsec_ecn; +extern int ip4_esp_randpad; + +#define ipseclog(x) do { if (ipsec_debug) log x; } while (0) + +struct inpcb; +extern struct secpolicy *ipsec4_getpolicybypcb + __P((struct mbuf *, u_int, struct inpcb *, int *)); +extern struct secpolicy *ipsec4_getpolicybysock + __P((struct mbuf *, u_int, struct socket *, int *)); +extern struct secpolicy *ipsec4_getpolicybyaddr + __P((struct mbuf *, u_int, int, int *)); + +extern int ipsec_init_policy __P((struct socket *so, struct inpcbpolicy **)); +extern int ipsec_copy_policy + __P((struct inpcbpolicy *, struct inpcbpolicy *)); +extern u_int ipsec_get_reqlevel __P((struct ipsecrequest *)); + +extern int ipsec4_set_policy __P((struct inpcb *inp, int optname, + caddr_t request, size_t len, int priv)); +extern int ipsec4_get_policy __P((struct inpcb *inpcb, caddr_t request, + size_t len, struct mbuf **mp)); +extern int ipsec4_delete_pcbpolicy __P((struct inpcb *)); +extern int ipsec4_in_reject_so __P((struct mbuf *, struct socket *)); +extern int ipsec4_in_reject __P((struct mbuf *, struct inpcb *)); + +struct secas; +struct tcpcb; +extern int ipsec_chkreplay __P((u_int32_t, struct secasvar *)); +extern int ipsec_updatereplay __P((u_int32_t, struct secasvar *)); + +extern size_t ipsec4_hdrsiz __P((struct mbuf *, u_int, struct inpcb *)); +extern size_t ipsec_hdrsiz_tcp __P((struct tcpcb *)); + +struct ip; +extern const char *ipsec4_logpacketstr __P((struct ip *, u_int32_t)); +extern const char *ipsec_logsastr __P((struct secasvar *)); + +extern void ipsec_dumpmbuf __P((struct mbuf *)); + +extern int ipsec4_output __P((struct ipsec_output_state *, struct secpolicy *, + int)); +extern int ipsec4_tunnel_validate __P((struct mbuf *, int, u_int, + struct secasvar *)); +extern struct mbuf *ipsec_copypkt __P((struct mbuf *)); +extern void ipsec_delaux __P((struct mbuf *)); +extern int ipsec_addhist __P((struct mbuf *, int, u_int32_t)); +extern struct ipsec_history *ipsec_gethist __P((struct mbuf *, int *)); +#endif /* _KERNEL */ + +#ifndef _KERNEL +extern caddr_t ipsec_set_policy __P((char *, int)); +extern int ipsec_get_policylen __P((caddr_t)); +extern char *ipsec_dump_policy __P((caddr_t, char *)); + +extern const char *ipsec_strerror __P((void)); +#endif /* !_KERNEL */ + +#endif /* _NETINET6_IPSEC_H_ */ diff --git a/sys/netinet6/ipsec6.h b/sys/netinet6/ipsec6.h new file mode 100644 index 0000000..1811088 --- /dev/null +++ b/sys/netinet6/ipsec6.h @@ -0,0 +1,84 @@ +/* $FreeBSD$ */ +/* $KAME: ipsec.h,v 1.44 2001/03/23 08:08:47 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * IPsec controller part. + */ + +#ifndef _NETINET6_IPSEC6_H_ +#define _NETINET6_IPSEC6_H_ + +#include <net/pfkeyv2.h> +#include <netkey/keydb.h> + +#ifdef _KERNEL +extern struct ipsecstat ipsec6stat; +extern struct secpolicy ip6_def_policy; +extern int ip6_esp_trans_deflev; +extern int ip6_esp_net_deflev; +extern int ip6_ah_trans_deflev; +extern int ip6_ah_net_deflev; +extern int ip6_ipsec_ecn; +extern int ip6_esp_randpad; + +struct inpcb; +extern struct secpolicy *ipsec6_getpolicybypcb + __P((struct mbuf *, u_int, struct inpcb *, int *)); +extern struct secpolicy *ipsec6_getpolicybysock + __P((struct mbuf *, u_int, struct socket *, int *)); +extern struct secpolicy *ipsec6_getpolicybyaddr + __P((struct mbuf *, u_int, int, int *)); + +extern int ipsec6_in_reject_so __P((struct mbuf *, struct socket *)); +extern int ipsec6_delete_pcbpolicy __P((struct inpcb *)); +extern int ipsec6_set_policy __P((struct inpcb *inp, int optname, + caddr_t request, size_t len, int priv)); +extern int ipsec6_get_policy + __P((struct inpcb *inp, caddr_t request, size_t len, struct mbuf **mp)); +extern int ipsec6_in_reject __P((struct mbuf *, struct inpcb *)); + +struct tcp6cb; + +extern size_t ipsec6_hdrsiz __P((struct mbuf *, u_int, struct inpcb *)); + +struct ip6_hdr; +extern const char *ipsec6_logpacketstr __P((struct ip6_hdr *, u_int32_t)); + +extern int ipsec6_output_trans __P((struct ipsec_output_state *, u_char *, + struct mbuf *, struct secpolicy *, int, int *)); +extern int ipsec6_output_tunnel __P((struct ipsec_output_state *, + struct secpolicy *, int)); +extern int ipsec6_tunnel_validate __P((struct mbuf *, int, u_int, + struct secasvar *)); +#endif /*_KERNEL*/ + +#endif /*_NETINET6_IPSEC6_H_*/ diff --git a/sys/netinet6/mld6.c b/sys/netinet6/mld6.c new file mode 100644 index 0000000..61c0e0c --- /dev/null +++ b/sys/netinet6/mld6.c @@ -0,0 +1,474 @@ +/* $FreeBSD$ */ +/* $KAME: mld6.c,v 1.27 2001/04/04 05:17:30 itojun Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1988 Stephen Deering. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Stephen Deering of Stanford University. + * + * 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)igmp.c 8.1 (Berkeley) 7/19/93 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/protosw.h> +#include <sys/syslog.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet/icmp6.h> +#include <netinet6/mld6_var.h> + +#include <net/net_osdep.h> + +/* + * Protocol constants + */ + +/* denotes that the MLD max response delay field specifies time in milliseconds */ +#define MLD6_TIMER_SCALE 1000 +/* + * time between repetitions of a node's initial report of interest in a + * multicast address(in seconds) + */ +#define MLD6_UNSOLICITED_REPORT_INTERVAL 10 + +static struct ip6_pktopts ip6_opts; +static int mld6_timers_are_running; +/* XXX: These are necessary for KAME's link-local hack */ +static struct in6_addr mld6_all_nodes_linklocal = IN6ADDR_LINKLOCAL_ALLNODES_INIT; +static struct in6_addr mld6_all_routers_linklocal = IN6ADDR_LINKLOCAL_ALLROUTERS_INIT; + +static void mld6_sendpkt __P((struct in6_multi *, int, const struct in6_addr *)); + +void +mld6_init() +{ + static u_int8_t hbh_buf[8]; + struct ip6_hbh *hbh = (struct ip6_hbh *)hbh_buf; + u_int16_t rtalert_code = htons((u_int16_t)IP6OPT_RTALERT_MLD); + + mld6_timers_are_running = 0; + + /* ip6h_nxt will be fill in later */ + hbh->ip6h_len = 0; /* (8 >> 3) - 1 */ + + /* XXX: grotty hard coding... */ + hbh_buf[2] = IP6OPT_PADN; /* 2 byte padding */ + hbh_buf[3] = 0; + hbh_buf[4] = IP6OPT_RTALERT; + hbh_buf[5] = IP6OPT_RTALERT_LEN - 2; + bcopy((caddr_t)&rtalert_code, &hbh_buf[6], sizeof(u_int16_t)); + + init_ip6pktopts(&ip6_opts); + ip6_opts.ip6po_hbh = hbh; +} + +void +mld6_start_listening(in6m) + struct in6_multi *in6m; +{ + int s = splnet(); + + /* + * RFC2710 page 10: + * The node never sends a Report or Done for the link-scope all-nodes + * address. + * MLD messages are never sent for multicast addresses whose scope is 0 + * (reserved) or 1 (node-local). + */ + mld6_all_nodes_linklocal.s6_addr16[1] = + htons(in6m->in6m_ifp->if_index); /* XXX */ + if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld6_all_nodes_linklocal) || + IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < IPV6_ADDR_SCOPE_LINKLOCAL) { + in6m->in6m_timer = 0; + in6m->in6m_state = MLD6_OTHERLISTENER; + } else { + mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); + in6m->in6m_timer = MLD6_RANDOM_DELAY( + MLD6_UNSOLICITED_REPORT_INTERVAL * PR_FASTHZ); + in6m->in6m_state = MLD6_IREPORTEDLAST; + mld6_timers_are_running = 1; + } + splx(s); +} + +void +mld6_stop_listening(in6m) + struct in6_multi *in6m; +{ + mld6_all_nodes_linklocal.s6_addr16[1] = + htons(in6m->in6m_ifp->if_index); /* XXX */ + mld6_all_routers_linklocal.s6_addr16[1] = + htons(in6m->in6m_ifp->if_index); /* XXX: necessary when mrouting */ + + if (in6m->in6m_state == MLD6_IREPORTEDLAST && + (!IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, &mld6_all_nodes_linklocal)) && + IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) > IPV6_ADDR_SCOPE_NODELOCAL) + mld6_sendpkt(in6m, MLD_LISTENER_DONE, + &mld6_all_routers_linklocal); +} + +void +mld6_input(m, off) + struct mbuf *m; + int off; +{ + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct mld_hdr *mldh; + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct in6_multi *in6m; + struct in6_ifaddr *ia; + struct ifmultiaddr *ifma; + int timer; /* timer value in the MLD query header */ + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(*mldh),); + mldh = (struct mld_hdr *)(mtod(m, caddr_t) + off); +#else + IP6_EXTHDR_GET(mldh, struct mld_hdr *, m, off, sizeof(*mldh)); + if (mldh == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + + /* source address validation */ + ip6 = mtod(m, struct ip6_hdr *);/* in case mpullup */ + if (!IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src)) { + log(LOG_ERR, + "mld6_input: src %s is not link-local (grp=%s)\n", + ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&mldh->mld_addr)); + /* + * spec (RFC2710) does not explicitly + * specify to discard the packet from a non link-local + * source address. But we believe it's expected to do so. + * XXX: do we have to allow :: as source? + */ + m_freem(m); + return; + } + + /* + * In the MLD6 specification, there are 3 states and a flag. + * + * In Non-Listener state, we simply don't have a membership record. + * In Delaying Listener state, our timer is running (in6m->in6m_timer) + * In Idle Listener state, our timer is not running (in6m->in6m_timer==0) + * + * The flag is in6m->in6m_state, it is set to MLD6_OTHERLISTENER if + * we have heard a report from another member, or MLD6_IREPORTEDLAST + * if we sent the last report. + */ + switch(mldh->mld_type) { + case MLD_LISTENER_QUERY: + if (ifp->if_flags & IFF_LOOPBACK) + break; + + if (!IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) && + !IN6_IS_ADDR_MULTICAST(&mldh->mld_addr)) + break; /* print error or log stat? */ + if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr)) + mldh->mld_addr.s6_addr16[1] = + htons(ifp->if_index); /* XXX */ + + /* + * - Start the timers in all of our membership records + * that the query applies to for the interface on + * which the query arrived excl. those that belong + * to the "all-nodes" group (ff02::1). + * - Restart any timer that is already running but has + * A value longer than the requested timeout. + * - Use the value specified in the query message as + * the maximum timeout. + */ + IFP_TO_IA6(ifp, ia); + if (ia == NULL) + break; + + /* + * XXX: System timer resolution is too low to handle Max + * Response Delay, so set 1 to the internal timer even if + * the calculated value equals to zero when Max Response + * Delay is positive. + */ + timer = ntohs(mldh->mld_maxdelay)*PR_FASTHZ/MLD6_TIMER_SCALE; + if (timer == 0 && mldh->mld_maxdelay) + timer = 1; + mld6_all_nodes_linklocal.s6_addr16[1] = + htons(ifp->if_index); /* XXX */ + + TAILQ_FOREACH(ifma, &ifp->if_multiaddrs, ifma_link) + { + if (ifma->ifma_addr->sa_family != AF_INET6) + continue; + in6m = (struct in6_multi *)ifma->ifma_protospec; + if (IN6_ARE_ADDR_EQUAL(&in6m->in6m_addr, + &mld6_all_nodes_linklocal) || + IPV6_ADDR_MC_SCOPE(&in6m->in6m_addr) < + IPV6_ADDR_SCOPE_LINKLOCAL) + continue; + + if (IN6_IS_ADDR_UNSPECIFIED(&mldh->mld_addr) || + IN6_ARE_ADDR_EQUAL(&mldh->mld_addr, + &in6m->in6m_addr)) + { + if (timer == 0) { + /* send a report immediately */ + mld6_sendpkt(in6m, MLD_LISTENER_REPORT, + NULL); + in6m->in6m_timer = 0; /* reset timer */ + in6m->in6m_state = MLD6_IREPORTEDLAST; + } + else if (in6m->in6m_timer == 0 || /*idle state*/ + in6m->in6m_timer > timer) { + in6m->in6m_timer = + MLD6_RANDOM_DELAY(timer); + mld6_timers_are_running = 1; + } + } + } + + if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr)) + mldh->mld_addr.s6_addr16[1] = 0; /* XXX */ + break; + case MLD_LISTENER_REPORT: + /* + * For fast leave to work, we have to know that we are the + * last person to send a report for this group. Reports + * can potentially get looped back if we are a multicast + * router, so discard reports sourced by me. + * Note that it is impossible to check IFF_LOOPBACK flag of + * ifp for this purpose, since ip6_mloopback pass the physical + * interface to looutput. + */ + if (m->m_flags & M_LOOP) /* XXX: grotty flag, but efficient */ + break; + + if (!IN6_IS_ADDR_MULTICAST(&mldh->mld_addr)) + break; + + if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr)) + mldh->mld_addr.s6_addr16[1] = + htons(ifp->if_index); /* XXX */ + /* + * If we belong to the group being reported, stop + * our timer for that group. + */ + IN6_LOOKUP_MULTI(mldh->mld_addr, ifp, in6m); + if (in6m) { + in6m->in6m_timer = 0; /* transit to idle state */ + in6m->in6m_state = MLD6_OTHERLISTENER; /* clear flag */ + } + + if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr)) + mldh->mld_addr.s6_addr16[1] = 0; /* XXX */ + break; + default: /* this is impossible */ + log(LOG_ERR, "mld6_input: illegal type(%d)", mldh->mld_type); + break; + } + + m_freem(m); +} + +void +mld6_fasttimeo() +{ + struct in6_multi *in6m; + struct in6_multistep step; + int s; + + /* + * Quick check to see if any work needs to be done, in order + * to minimize the overhead of fasttimo processing. + */ + if (!mld6_timers_are_running) + return; + + s = splnet(); + mld6_timers_are_running = 0; + IN6_FIRST_MULTI(step, in6m); + while (in6m != NULL) { + if (in6m->in6m_timer == 0) { + /* do nothing */ + } else if (--in6m->in6m_timer == 0) { + mld6_sendpkt(in6m, MLD_LISTENER_REPORT, NULL); + in6m->in6m_state = MLD6_IREPORTEDLAST; + } else { + mld6_timers_are_running = 1; + } + IN6_NEXT_MULTI(step, in6m); + } + splx(s); +} + +static void +mld6_sendpkt(in6m, type, dst) + struct in6_multi *in6m; + int type; + const struct in6_addr *dst; +{ + struct mbuf *mh, *md; + struct mld_hdr *mldh; + struct ip6_hdr *ip6; + struct ip6_moptions im6o; + struct in6_ifaddr *ia; + struct ifnet *ifp = in6m->in6m_ifp; + struct ifnet *outif = NULL; + + /* + * At first, find a link local address on the outgoing interface + * to use as the source address of the MLD packet. + */ + if ((ia = in6ifa_ifpforlinklocal(ifp, IN6_IFF_NOTREADY|IN6_IFF_ANYCAST)) + == NULL) + return; + + /* + * Allocate mbufs to store ip6 header and MLD header. + * We allocate 2 mbufs and make chain in advance because + * it is more convenient when inserting the hop-by-hop option later. + */ + MGETHDR(mh, M_DONTWAIT, MT_HEADER); + if (mh == NULL) + return; + MGET(md, M_DONTWAIT, MT_DATA); + if (md == NULL) { + m_free(mh); + return; + } + mh->m_next = md; + + mh->m_pkthdr.rcvif = NULL; + mh->m_pkthdr.len = sizeof(struct ip6_hdr) + sizeof(struct mld_hdr); + mh->m_len = sizeof(struct ip6_hdr); + MH_ALIGN(mh, sizeof(struct ip6_hdr)); + + /* fill in the ip6 header */ + ip6 = mtod(mh, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + /* ip6_plen will be set later */ + ip6->ip6_nxt = IPPROTO_ICMPV6; + /* ip6_hlim will be set by im6o.im6o_multicast_hlim */ + ip6->ip6_src = ia->ia_addr.sin6_addr; + ip6->ip6_dst = dst ? *dst : in6m->in6m_addr; + + /* fill in the MLD header */ + md->m_len = sizeof(struct mld_hdr); + mldh = mtod(md, struct mld_hdr *); + mldh->mld_type = type; + mldh->mld_code = 0; + mldh->mld_cksum = 0; + /* XXX: we assume the function will not be called for query messages */ + mldh->mld_maxdelay = 0; + mldh->mld_reserved = 0; + mldh->mld_addr = in6m->in6m_addr; + if (IN6_IS_ADDR_MC_LINKLOCAL(&mldh->mld_addr)) + mldh->mld_addr.s6_addr16[1] = 0; /* XXX */ + mldh->mld_cksum = in6_cksum(mh, IPPROTO_ICMPV6, + sizeof(struct ip6_hdr), + sizeof(struct mld_hdr)); + + /* construct multicast option */ + bzero(&im6o, sizeof(im6o)); + im6o.im6o_multicast_ifp = ifp; + im6o.im6o_multicast_hlim = 1; + + /* + * Request loopback of the report if we are acting as a multicast + * router, so that the process-level routing daemon can hear it. + */ + im6o.im6o_multicast_loop = (ip6_mrouter != NULL); + + /* increment output statictics */ + icmp6stat.icp6s_outhist[type]++; + + ip6_output(mh, &ip6_opts, NULL, 0, &im6o, &outif, NULL); + if (outif) { + icmp6_ifstat_inc(outif, ifs6_out_msg); + switch (type) { + case MLD_LISTENER_QUERY: + icmp6_ifstat_inc(outif, ifs6_out_mldquery); + break; + case MLD_LISTENER_REPORT: + icmp6_ifstat_inc(outif, ifs6_out_mldreport); + break; + case MLD_LISTENER_DONE: + icmp6_ifstat_inc(outif, ifs6_out_mlddone); + break; + } + } +} diff --git a/sys/netinet6/mld6_var.h b/sys/netinet6/mld6_var.h new file mode 100644 index 0000000..e58560e --- /dev/null +++ b/sys/netinet6/mld6_var.h @@ -0,0 +1,53 @@ +/* $FreeBSD$ */ +/* $KAME: mld6_var.h,v 1.4 2000/03/25 07:23:54 sumikawa Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_MLD6_VAR_H_ +#define _NETINET6_MLD6_VAR_H_ + +#ifdef _KERNEL + +#define MLD6_RANDOM_DELAY(X) (random() % (X) + 1) + +/* + * States for MLD stop-listening processing + */ +#define MLD6_OTHERLISTENER 0 +#define MLD6_IREPORTEDLAST 1 + +void mld6_init __P((void)); +void mld6_input __P((struct mbuf *, int)); +void mld6_start_listening __P((struct in6_multi *)); +void mld6_stop_listening __P((struct in6_multi *)); +void mld6_fasttimeo __P((void)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_MLD6_VAR_H_ */ diff --git a/sys/netinet6/nd6.c b/sys/netinet6/nd6.c new file mode 100644 index 0000000..f2f09f4 --- /dev/null +++ b/sys/netinet6/nd6.c @@ -0,0 +1,2251 @@ +/* $FreeBSD$ */ +/* $KAME: nd6.c,v 1.144 2001/05/24 07:44:00 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * XXX + * KAME 970409 note: + * BSD/OS version heavily modifies this code, related to llinfo. + * Since we don't have BSD/OS version of net/route.c in our hand, + * I left the code mostly as it was in 970310. -- itojun + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_mac.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/callout.h> +#include <sys/mac.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/protosw.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/queue.h> +#include <sys/sysctl.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_types.h> +#include <net/if_atm.h> +#include <net/fddi.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/in6_prefix.h> +#include <netinet/icmp6.h> + +#include <net/net_osdep.h> + +#define ND6_SLOWTIMER_INTERVAL (60 * 60) /* 1 hour */ +#define ND6_RECALC_REACHTM_INTERVAL (60 * 120) /* 2 hours */ + +#define SIN6(s) ((struct sockaddr_in6 *)s) +#define SDL(s) ((struct sockaddr_dl *)s) + +/* timer values */ +int nd6_prune = 1; /* walk list every 1 seconds */ +int nd6_delay = 5; /* delay first probe time 5 second */ +int nd6_umaxtries = 3; /* maximum unicast query */ +int nd6_mmaxtries = 3; /* maximum multicast query */ +int nd6_useloopback = 1; /* use loopback interface for local traffic */ +int nd6_gctimer = (60 * 60 * 24); /* 1 day: garbage collection timer */ + +/* preventing too many loops in ND option parsing */ +int nd6_maxndopt = 10; /* max # of ND options allowed */ + +int nd6_maxnudhint = 0; /* max # of subsequent upper layer hints */ + +#ifdef ND6_DEBUG +int nd6_debug = 1; +#else +int nd6_debug = 0; +#endif + +/* for debugging? */ +static int nd6_inuse, nd6_allocated; + +struct llinfo_nd6 llinfo_nd6 = {&llinfo_nd6, &llinfo_nd6}; +static size_t nd_ifinfo_indexlim = 8; +struct nd_ifinfo *nd_ifinfo = NULL; +struct nd_drhead nd_defrouter; +struct nd_prhead nd_prefix = { 0 }; + +int nd6_recalc_reachtm_interval = ND6_RECALC_REACHTM_INTERVAL; +static struct sockaddr_in6 all1_sa; + +static void nd6_slowtimo __P((void *)); +static int regen_tmpaddr __P((struct in6_ifaddr *)); + +struct callout nd6_slowtimo_ch; +struct callout nd6_timer_ch; +extern struct callout in6_tmpaddrtimer_ch; + +void +nd6_init() +{ + static int nd6_init_done = 0; + int i; + + if (nd6_init_done) { + log(LOG_NOTICE, "nd6_init called more than once(ignored)\n"); + return; + } + + all1_sa.sin6_family = AF_INET6; + all1_sa.sin6_len = sizeof(struct sockaddr_in6); + for (i = 0; i < sizeof(all1_sa.sin6_addr); i++) + all1_sa.sin6_addr.s6_addr[i] = 0xff; + + /* initialization of the default router list */ + TAILQ_INIT(&nd_defrouter); + + nd6_init_done = 1; + + /* start timer */ + callout_reset(&nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz, + nd6_slowtimo, NULL); +} + +void +nd6_ifattach(ifp) + struct ifnet *ifp; +{ + + /* + * We have some arrays that should be indexed by if_index. + * since if_index will grow dynamically, they should grow too. + */ + if (nd_ifinfo == NULL || if_index >= nd_ifinfo_indexlim) { + size_t n; + caddr_t q; + + while (if_index >= nd_ifinfo_indexlim) + nd_ifinfo_indexlim <<= 1; + + /* grow nd_ifinfo */ + n = nd_ifinfo_indexlim * sizeof(struct nd_ifinfo); + q = (caddr_t)malloc(n, M_IP6NDP, M_WAITOK); + bzero(q, n); + if (nd_ifinfo) { + bcopy((caddr_t)nd_ifinfo, q, n/2); + free((caddr_t)nd_ifinfo, M_IP6NDP); + } + nd_ifinfo = (struct nd_ifinfo *)q; + } + +#define ND nd_ifinfo[ifp->if_index] + + /* + * Don't initialize if called twice. + * XXX: to detect this, we should choose a member that is never set + * before initialization of the ND structure itself. We formaly used + * the linkmtu member, which was not suitable because it could be + * initialized via "ifconfig mtu". + */ + if (ND.basereachable) + return; + + ND.linkmtu = ifnet_byindex(ifp->if_index)->if_mtu; + ND.chlim = IPV6_DEFHLIM; + ND.basereachable = REACHABLE_TIME; + ND.reachable = ND_COMPUTE_RTIME(ND.basereachable); + ND.retrans = RETRANS_TIMER; + ND.receivedra = 0; + ND.flags = ND6_IFF_PERFORMNUD; + nd6_setmtu(ifp); +#undef ND +} + +/* + * Reset ND level link MTU. This function is called when the physical MTU + * changes, which means we might have to adjust the ND level MTU. + */ +void +nd6_setmtu(ifp) + struct ifnet *ifp; +{ + struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index]; + u_long oldmaxmtu = ndi->maxmtu; + u_long oldlinkmtu = ndi->linkmtu; + + switch (ifp->if_type) { + case IFT_ARCNET: /* XXX MTU handling needs more work */ + ndi->maxmtu = MIN(60480, ifp->if_mtu); + break; + case IFT_ETHER: + ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu); + break; + case IFT_FDDI: + ndi->maxmtu = MIN(FDDIIPMTU, ifp->if_mtu); + break; + case IFT_ATM: + ndi->maxmtu = MIN(ATMMTU, ifp->if_mtu); + break; + case IFT_IEEE1394: /* XXX should be IEEE1394MTU(1500) */ + ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu); + break; +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: /* XXX should be IEEE80211MTU(1500) */ + ndi->maxmtu = MIN(ETHERMTU, ifp->if_mtu); + break; +#endif + default: + ndi->maxmtu = ifp->if_mtu; + break; + } + + if (oldmaxmtu != ndi->maxmtu) { + /* + * If the ND level MTU is not set yet, or if the maxmtu + * is reset to a smaller value than the ND level MTU, + * also reset the ND level MTU. + */ + if (ndi->linkmtu == 0 || + ndi->maxmtu < ndi->linkmtu) { + ndi->linkmtu = ndi->maxmtu; + /* also adjust in6_maxmtu if necessary. */ + if (oldlinkmtu == 0) { + /* + * XXX: the case analysis is grotty, but + * it is not efficient to call in6_setmaxmtu() + * here when we are during the initialization + * procedure. + */ + if (in6_maxmtu < ndi->linkmtu) + in6_maxmtu = ndi->linkmtu; + } else + in6_setmaxmtu(); + } + } +#undef MIN +} + +void +nd6_option_init(opt, icmp6len, ndopts) + void *opt; + int icmp6len; + union nd_opts *ndopts; +{ + bzero(ndopts, sizeof(*ndopts)); + ndopts->nd_opts_search = (struct nd_opt_hdr *)opt; + ndopts->nd_opts_last + = (struct nd_opt_hdr *)(((u_char *)opt) + icmp6len); + + if (icmp6len == 0) { + ndopts->nd_opts_done = 1; + ndopts->nd_opts_search = NULL; + } +} + +/* + * Take one ND option. + */ +struct nd_opt_hdr * +nd6_option(ndopts) + union nd_opts *ndopts; +{ + struct nd_opt_hdr *nd_opt; + int olen; + + if (!ndopts) + panic("ndopts == NULL in nd6_option"); + if (!ndopts->nd_opts_last) + panic("uninitialized ndopts in nd6_option"); + if (!ndopts->nd_opts_search) + return NULL; + if (ndopts->nd_opts_done) + return NULL; + + nd_opt = ndopts->nd_opts_search; + + /* make sure nd_opt_len is inside the buffer */ + if ((caddr_t)&nd_opt->nd_opt_len >= (caddr_t)ndopts->nd_opts_last) { + bzero(ndopts, sizeof(*ndopts)); + return NULL; + } + + olen = nd_opt->nd_opt_len << 3; + if (olen == 0) { + /* + * Message validation requires that all included + * options have a length that is greater than zero. + */ + bzero(ndopts, sizeof(*ndopts)); + return NULL; + } + + ndopts->nd_opts_search = (struct nd_opt_hdr *)((caddr_t)nd_opt + olen); + if (ndopts->nd_opts_search > ndopts->nd_opts_last) { + /* option overruns the end of buffer, invalid */ + bzero(ndopts, sizeof(*ndopts)); + return NULL; + } else if (ndopts->nd_opts_search == ndopts->nd_opts_last) { + /* reached the end of options chain */ + ndopts->nd_opts_done = 1; + ndopts->nd_opts_search = NULL; + } + return nd_opt; +} + +/* + * Parse multiple ND options. + * This function is much easier to use, for ND routines that do not need + * multiple options of the same type. + */ +int +nd6_options(ndopts) + union nd_opts *ndopts; +{ + struct nd_opt_hdr *nd_opt; + int i = 0; + + if (!ndopts) + panic("ndopts == NULL in nd6_options"); + if (!ndopts->nd_opts_last) + panic("uninitialized ndopts in nd6_options"); + if (!ndopts->nd_opts_search) + return 0; + + while (1) { + nd_opt = nd6_option(ndopts); + if (!nd_opt && !ndopts->nd_opts_last) { + /* + * Message validation requires that all included + * options have a length that is greater than zero. + */ + icmp6stat.icp6s_nd_badopt++; + bzero(ndopts, sizeof(*ndopts)); + return -1; + } + + if (!nd_opt) + goto skip1; + + switch (nd_opt->nd_opt_type) { + case ND_OPT_SOURCE_LINKADDR: + case ND_OPT_TARGET_LINKADDR: + case ND_OPT_MTU: + case ND_OPT_REDIRECTED_HEADER: + if (ndopts->nd_opt_array[nd_opt->nd_opt_type]) { + nd6log((LOG_INFO, + "duplicated ND6 option found (type=%d)\n", + nd_opt->nd_opt_type)); + /* XXX bark? */ + } else { + ndopts->nd_opt_array[nd_opt->nd_opt_type] + = nd_opt; + } + break; + case ND_OPT_PREFIX_INFORMATION: + if (ndopts->nd_opt_array[nd_opt->nd_opt_type] == 0) { + ndopts->nd_opt_array[nd_opt->nd_opt_type] + = nd_opt; + } + ndopts->nd_opts_pi_end = + (struct nd_opt_prefix_info *)nd_opt; + break; + default: + /* + * Unknown options must be silently ignored, + * to accomodate future extension to the protocol. + */ + nd6log((LOG_DEBUG, + "nd6_options: unsupported option %d - " + "option ignored\n", nd_opt->nd_opt_type)); + } + +skip1: + i++; + if (i > nd6_maxndopt) { + icmp6stat.icp6s_nd_toomanyopt++; + nd6log((LOG_INFO, "too many loop in nd opt\n")); + break; + } + + if (ndopts->nd_opts_done) + break; + } + + return 0; +} + +/* + * ND6 timer routine to expire default route list and prefix list + */ +void +nd6_timer(ignored_arg) + void *ignored_arg; +{ + int s; + struct llinfo_nd6 *ln; + struct nd_defrouter *dr; + struct nd_prefix *pr; + struct ifnet *ifp; + struct in6_ifaddr *ia6, *nia6; + struct in6_addrlifetime *lt6; + + s = splnet(); + callout_reset(&nd6_timer_ch, nd6_prune * hz, + nd6_timer, NULL); + + ln = llinfo_nd6.ln_next; + while (ln && ln != &llinfo_nd6) { + struct rtentry *rt; + struct sockaddr_in6 *dst; + struct llinfo_nd6 *next = ln->ln_next; + /* XXX: used for the DELAY case only: */ + struct nd_ifinfo *ndi = NULL; + + if ((rt = ln->ln_rt) == NULL) { + ln = next; + continue; + } + if ((ifp = rt->rt_ifp) == NULL) { + ln = next; + continue; + } + ndi = &nd_ifinfo[ifp->if_index]; + dst = (struct sockaddr_in6 *)rt_key(rt); + + if (ln->ln_expire > time_second) { + ln = next; + continue; + } + + /* sanity check */ + if (!rt) + panic("rt=0 in nd6_timer(ln=%p)", ln); + if (rt->rt_llinfo && (struct llinfo_nd6 *)rt->rt_llinfo != ln) + panic("rt_llinfo(%p) is not equal to ln(%p)", + rt->rt_llinfo, ln); + if (!dst) + panic("dst=0 in nd6_timer(ln=%p)", ln); + + switch (ln->ln_state) { + case ND6_LLINFO_INCOMPLETE: + if (ln->ln_asked < nd6_mmaxtries) { + ln->ln_asked++; + ln->ln_expire = time_second + + nd_ifinfo[ifp->if_index].retrans / 1000; + nd6_ns_output(ifp, NULL, &dst->sin6_addr, + ln, 0); + } else { + struct mbuf *m = ln->ln_hold; + if (m) { + if (rt->rt_ifp) { + /* + * Fake rcvif to make ICMP error + * more helpful in diagnosing + * for the receiver. + * XXX: should we consider + * older rcvif? + */ + m->m_pkthdr.rcvif = rt->rt_ifp; + } + icmp6_error(m, ICMP6_DST_UNREACH, + ICMP6_DST_UNREACH_ADDR, 0); + ln->ln_hold = NULL; + } + next = nd6_free(rt); + } + break; + case ND6_LLINFO_REACHABLE: + if (ln->ln_expire) { + ln->ln_state = ND6_LLINFO_STALE; + ln->ln_expire = time_second + nd6_gctimer; + } + break; + + case ND6_LLINFO_STALE: + /* Garbage Collection(RFC 2461 5.3) */ + if (ln->ln_expire) + next = nd6_free(rt); + break; + + case ND6_LLINFO_DELAY: + if (ndi && (ndi->flags & ND6_IFF_PERFORMNUD) != 0) { + /* We need NUD */ + ln->ln_asked = 1; + ln->ln_state = ND6_LLINFO_PROBE; + ln->ln_expire = time_second + + ndi->retrans / 1000; + nd6_ns_output(ifp, &dst->sin6_addr, + &dst->sin6_addr, + ln, 0); + } else { + ln->ln_state = ND6_LLINFO_STALE; /* XXX */ + ln->ln_expire = time_second + nd6_gctimer; + } + break; + case ND6_LLINFO_PROBE: + if (ln->ln_asked < nd6_umaxtries) { + ln->ln_asked++; + ln->ln_expire = time_second + + nd_ifinfo[ifp->if_index].retrans / 1000; + nd6_ns_output(ifp, &dst->sin6_addr, + &dst->sin6_addr, ln, 0); + } else { + next = nd6_free(rt); + } + break; + } + ln = next; + } + + /* expire default router list */ + dr = TAILQ_FIRST(&nd_defrouter); + while (dr) { + if (dr->expire && dr->expire < time_second) { + struct nd_defrouter *t; + t = TAILQ_NEXT(dr, dr_entry); + defrtrlist_del(dr); + dr = t; + } else { + dr = TAILQ_NEXT(dr, dr_entry); + } + } + + /* + * expire interface addresses. + * in the past the loop was inside prefix expiry processing. + * However, from a stricter speci-confrmance standpoint, we should + * rather separate address lifetimes and prefix lifetimes. + */ + addrloop: + for (ia6 = in6_ifaddr; ia6; ia6 = nia6) { + nia6 = ia6->ia_next; + /* check address lifetime */ + lt6 = &ia6->ia6_lifetime; + if (IFA6_IS_INVALID(ia6)) { + int regen = 0; + + /* + * If the expiring address is temporary, try + * regenerating a new one. This would be useful when + * we suspended a laptop PC, then turned it on after a + * period that could invalidate all temporary + * addresses. Although we may have to restart the + * loop (see below), it must be after purging the + * address. Otherwise, we'd see an infinite loop of + * regeneration. + */ + if (ip6_use_tempaddr && + (ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0) { + if (regen_tmpaddr(ia6) == 0) + regen = 1; + } + + in6_purgeaddr(&ia6->ia_ifa); + + if (regen) + goto addrloop; /* XXX: see below */ + } + if (IFA6_IS_DEPRECATED(ia6)) { + int oldflags = ia6->ia6_flags; + + ia6->ia6_flags |= IN6_IFF_DEPRECATED; + + /* + * If a temporary address has just become deprecated, + * regenerate a new one if possible. + */ + if (ip6_use_tempaddr && + (ia6->ia6_flags & IN6_IFF_TEMPORARY) != 0 && + (oldflags & IN6_IFF_DEPRECATED) == 0) { + + if (regen_tmpaddr(ia6) == 0) { + /* + * A new temporary address is + * generated. + * XXX: this means the address chain + * has changed while we are still in + * the loop. Although the change + * would not cause disaster (because + * it's not a deletion, but an + * addition,) we'd rather restart the + * loop just for safety. Or does this + * significantly reduce performance?? + */ + goto addrloop; + } + } + } else { + /* + * A new RA might have made a deprecated address + * preferred. + */ + ia6->ia6_flags &= ~IN6_IFF_DEPRECATED; + } + } + + /* expire prefix list */ + pr = nd_prefix.lh_first; + while (pr) { + /* + * check prefix lifetime. + * since pltime is just for autoconf, pltime processing for + * prefix is not necessary. + */ + if (pr->ndpr_expire && pr->ndpr_expire < time_second) { + struct nd_prefix *t; + t = pr->ndpr_next; + + /* + * address expiration and prefix expiration are + * separate. NEVER perform in6_purgeaddr here. + */ + + prelist_remove(pr); + pr = t; + } else + pr = pr->ndpr_next; + } + splx(s); +} + +static int +regen_tmpaddr(ia6) + struct in6_ifaddr *ia6; /* deprecated/invalidated temporary address */ +{ + struct ifaddr *ifa; + struct ifnet *ifp; + struct in6_ifaddr *public_ifa6 = NULL; + + ifp = ia6->ia_ifa.ifa_ifp; + for (ifa = ifp->if_addrlist.tqh_first; ifa; + ifa = ifa->ifa_list.tqe_next) + { + struct in6_ifaddr *it6; + + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + it6 = (struct in6_ifaddr *)ifa; + + /* ignore no autoconf addresses. */ + if ((it6->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + /* ignore autoconf addresses with different prefixes. */ + if (it6->ia6_ndpr == NULL || it6->ia6_ndpr != ia6->ia6_ndpr) + continue; + + /* + * Now we are looking at an autoconf address with the same + * prefix as ours. If the address is temporary and is still + * preferred, do not create another one. It would be rare, but + * could happen, for example, when we resume a laptop PC after + * a long period. + */ + if ((it6->ia6_flags & IN6_IFF_TEMPORARY) != 0 && + !IFA6_IS_DEPRECATED(it6)) { + public_ifa6 = NULL; + break; + } + + /* + * This is a public autoconf address that has the same prefix + * as ours. If it is preferred, keep it. We can't break the + * loop here, because there may be a still-preferred temporary + * address with the prefix. + */ + if (!IFA6_IS_DEPRECATED(it6)) + public_ifa6 = it6; + } + + if (public_ifa6 != NULL) { + int e; + + if ((e = in6_tmpifadd(public_ifa6, 0)) != 0) { + log(LOG_NOTICE, "regen_tmpaddr: failed to create a new" + " tmp addr,errno=%d\n", e); + return(-1); + } + return(0); + } + + return(-1); +} + +/* + * Nuke neighbor cache/prefix/default router management table, right before + * ifp goes away. + */ +void +nd6_purge(ifp) + struct ifnet *ifp; +{ + struct llinfo_nd6 *ln, *nln; + struct nd_defrouter *dr, *ndr, drany; + struct nd_prefix *pr, *npr; + + /* Nuke default router list entries toward ifp */ + if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) { + /* + * The first entry of the list may be stored in + * the routing table, so we'll delete it later. + */ + for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = ndr) { + ndr = TAILQ_NEXT(dr, dr_entry); + if (dr->ifp == ifp) + defrtrlist_del(dr); + } + dr = TAILQ_FIRST(&nd_defrouter); + if (dr->ifp == ifp) + defrtrlist_del(dr); + } + + /* Nuke prefix list entries toward ifp */ + for (pr = nd_prefix.lh_first; pr; pr = npr) { + npr = pr->ndpr_next; + if (pr->ndpr_ifp == ifp) { + /* + * Previously, pr->ndpr_addr is removed as well, + * but I strongly believe we don't have to do it. + * nd6_purge() is only called from in6_ifdetach(), + * which removes all the associated interface addresses + * by itself. + * (jinmei@kame.net 20010129) + */ + prelist_remove(pr); + } + } + + /* cancel default outgoing interface setting */ + if (nd6_defifindex == ifp->if_index) + nd6_setdefaultiface(0); + + if (!ip6_forwarding && ip6_accept_rtadv) { /* XXX: too restrictive? */ + /* refresh default router list */ + bzero(&drany, sizeof(drany)); + defrouter_delreq(&drany, 0); + defrouter_select(); + } + + /* + * Nuke neighbor cache entries for the ifp. + * Note that rt->rt_ifp may not be the same as ifp, + * due to KAME goto ours hack. See RTM_RESOLVE case in + * nd6_rtrequest(), and ip6_input(). + */ + ln = llinfo_nd6.ln_next; + while (ln && ln != &llinfo_nd6) { + struct rtentry *rt; + struct sockaddr_dl *sdl; + + nln = ln->ln_next; + rt = ln->ln_rt; + if (rt && rt->rt_gateway && + rt->rt_gateway->sa_family == AF_LINK) { + sdl = (struct sockaddr_dl *)rt->rt_gateway; + if (sdl->sdl_index == ifp->if_index) + nln = nd6_free(rt); + } + ln = nln; + } +} + +struct rtentry * +nd6_lookup(addr6, create, ifp) + struct in6_addr *addr6; + int create; + struct ifnet *ifp; +{ + struct rtentry *rt; + struct sockaddr_in6 sin6; + + bzero(&sin6, sizeof(sin6)); + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = *addr6; +#ifdef SCOPEDROUTING + sin6.sin6_scope_id = in6_addr2scopeid(ifp, addr6); +#endif + rt = rtalloc1((struct sockaddr *)&sin6, create, 0UL); + if (rt && (rt->rt_flags & RTF_LLINFO) == 0) { + /* + * This is the case for the default route. + * If we want to create a neighbor cache for the address, we + * should free the route for the destination and allocate an + * interface route. + */ + if (create) { + RTFREE(rt); + rt = 0; + } + } + if (!rt) { + if (create && ifp) { + int e; + + /* + * If no route is available and create is set, + * we allocate a host route for the destination + * and treat it like an interface route. + * This hack is necessary for a neighbor which can't + * be covered by our own prefix. + */ + struct ifaddr *ifa = + ifaof_ifpforaddr((struct sockaddr *)&sin6, ifp); + if (ifa == NULL) + return(NULL); + + /* + * Create a new route. RTF_LLINFO is necessary + * to create a Neighbor Cache entry for the + * destination in nd6_rtrequest which will be + * called in rtrequest via ifa->ifa_rtrequest. + */ + if ((e = rtrequest(RTM_ADD, (struct sockaddr *)&sin6, + ifa->ifa_addr, + (struct sockaddr *)&all1_sa, + (ifa->ifa_flags | + RTF_HOST | RTF_LLINFO) & + ~RTF_CLONING, + &rt)) != 0) + log(LOG_ERR, + "nd6_lookup: failed to add route for a " + "neighbor(%s), errno=%d\n", + ip6_sprintf(addr6), e); + if (rt == NULL) + return(NULL); + if (rt->rt_llinfo) { + struct llinfo_nd6 *ln = + (struct llinfo_nd6 *)rt->rt_llinfo; + ln->ln_state = ND6_LLINFO_NOSTATE; + } + } else + return(NULL); + } + rt->rt_refcnt--; + /* + * Validation for the entry. + * Note that the check for rt_llinfo is necessary because a cloned + * route from a parent route that has the L flag (e.g. the default + * route to a p2p interface) may have the flag, too, while the + * destination is not actually a neighbor. + * XXX: we can't use rt->rt_ifp to check for the interface, since + * it might be the loopback interface if the entry is for our + * own address on a non-loopback interface. Instead, we should + * use rt->rt_ifa->ifa_ifp, which would specify the REAL + * interface. + */ + if ((rt->rt_flags & RTF_GATEWAY) || (rt->rt_flags & RTF_LLINFO) == 0 || + rt->rt_gateway->sa_family != AF_LINK || rt->rt_llinfo == NULL || + (ifp && rt->rt_ifa->ifa_ifp != ifp)) { + if (create) { + log(LOG_DEBUG, "nd6_lookup: failed to lookup %s (if = %s)\n", + ip6_sprintf(addr6), ifp ? if_name(ifp) : "unspec"); + /* xxx more logs... kazu */ + } + return(NULL); + } + return(rt); +} + +/* + * Detect if a given IPv6 address identifies a neighbor on a given link. + * XXX: should take care of the destination of a p2p link? + */ +int +nd6_is_addr_neighbor(addr, ifp) + struct sockaddr_in6 *addr; + struct ifnet *ifp; +{ + struct ifaddr *ifa; + int i; + +#define IFADDR6(a) ((((struct in6_ifaddr *)(a))->ia_addr).sin6_addr) +#define IFMASK6(a) ((((struct in6_ifaddr *)(a))->ia_prefixmask).sin6_addr) + + /* + * A link-local address is always a neighbor. + * XXX: we should use the sin6_scope_id field rather than the embedded + * interface index. + */ + if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr) && + ntohs(*(u_int16_t *)&addr->sin6_addr.s6_addr[2]) == ifp->if_index) + return(1); + + /* + * If the address matches one of our addresses, + * it should be a neighbor. + */ + for (ifa = ifp->if_addrlist.tqh_first; + ifa; + ifa = ifa->ifa_list.tqe_next) + { + if (ifa->ifa_addr->sa_family != AF_INET6) + next: continue; + + for (i = 0; i < 4; i++) { + if ((IFADDR6(ifa).s6_addr32[i] ^ + addr->sin6_addr.s6_addr32[i]) & + IFMASK6(ifa).s6_addr32[i]) + goto next; + } + return(1); + } + + /* + * Even if the address matches none of our addresses, it might be + * in the neighbor cache. + */ + if (nd6_lookup(&addr->sin6_addr, 0, ifp) != NULL) + return(1); + + return(0); +#undef IFADDR6 +#undef IFMASK6 +} + +/* + * Free an nd6 llinfo entry. + */ +struct llinfo_nd6 * +nd6_free(rt) + struct rtentry *rt; +{ + struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo, *next; + struct in6_addr in6 = ((struct sockaddr_in6 *)rt_key(rt))->sin6_addr; + struct nd_defrouter *dr; + + /* + * we used to have pfctlinput(PRC_HOSTDEAD) here. + * even though it is not harmful, it was not really necessary. + */ + + if (!ip6_forwarding && ip6_accept_rtadv) { /* XXX: too restrictive? */ + int s; + s = splnet(); + dr = defrouter_lookup(&((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, + rt->rt_ifp); + + if (ln->ln_router || dr) { + /* + * rt6_flush must be called whether or not the neighbor + * is in the Default Router List. + * See a corresponding comment in nd6_na_input(). + */ + rt6_flush(&in6, rt->rt_ifp); + } + + if (dr) { + /* + * Unreachablity of a router might affect the default + * router selection and on-link detection of advertised + * prefixes. + */ + + /* + * Temporarily fake the state to choose a new default + * router and to perform on-link determination of + * prefixes correctly. + * Below the state will be set correctly, + * or the entry itself will be deleted. + */ + ln->ln_state = ND6_LLINFO_INCOMPLETE; + + /* + * Since defrouter_select() does not affect the + * on-link determination and MIP6 needs the check + * before the default router selection, we perform + * the check now. + */ + pfxlist_onlink_check(); + + if (dr == TAILQ_FIRST(&nd_defrouter)) { + /* + * It is used as the current default router, + * so we have to move it to the end of the + * list and choose a new one. + * XXX: it is not very efficient if this is + * the only router. + */ + TAILQ_REMOVE(&nd_defrouter, dr, dr_entry); + TAILQ_INSERT_TAIL(&nd_defrouter, dr, dr_entry); + + defrouter_select(); + } + } + splx(s); + } + + /* + * Before deleting the entry, remember the next entry as the + * return value. We need this because pfxlist_onlink_check() above + * might have freed other entries (particularly the old next entry) as + * a side effect (XXX). + */ + next = ln->ln_next; + + /* + * Detach the route from the routing tree and the list of neighbor + * caches, and disable the route entry not to be used in already + * cached routes. + */ + rtrequest(RTM_DELETE, rt_key(rt), (struct sockaddr *)0, + rt_mask(rt), 0, (struct rtentry **)0); + + return(next); +} + +/* + * Upper-layer reachability hint for Neighbor Unreachability Detection. + * + * XXX cost-effective metods? + */ +void +nd6_nud_hint(rt, dst6, force) + struct rtentry *rt; + struct in6_addr *dst6; + int force; +{ + struct llinfo_nd6 *ln; + + /* + * If the caller specified "rt", use that. Otherwise, resolve the + * routing table by supplied "dst6". + */ + if (!rt) { + if (!dst6) + return; + if (!(rt = nd6_lookup(dst6, 0, NULL))) + return; + } + + if ((rt->rt_flags & RTF_GATEWAY) != 0 || + (rt->rt_flags & RTF_LLINFO) == 0 || + !rt->rt_llinfo || !rt->rt_gateway || + rt->rt_gateway->sa_family != AF_LINK) { + /* This is not a host route. */ + return; + } + + ln = (struct llinfo_nd6 *)rt->rt_llinfo; + if (ln->ln_state < ND6_LLINFO_REACHABLE) + return; + + /* + * if we get upper-layer reachability confirmation many times, + * it is possible we have false information. + */ + if (!force) { + ln->ln_byhint++; + if (ln->ln_byhint > nd6_maxnudhint) + return; + } + + ln->ln_state = ND6_LLINFO_REACHABLE; + if (ln->ln_expire) + ln->ln_expire = time_second + + nd_ifinfo[rt->rt_ifp->if_index].reachable; +} + +void +nd6_rtrequest(req, rt, info) + int req; + struct rtentry *rt; + struct rt_addrinfo *info; /* xxx unused */ +{ + struct sockaddr *gate = rt->rt_gateway; + struct llinfo_nd6 *ln = (struct llinfo_nd6 *)rt->rt_llinfo; + static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK}; + struct ifnet *ifp = rt->rt_ifp; + struct ifaddr *ifa; + + if ((rt->rt_flags & RTF_GATEWAY)) + return; + + if (nd6_need_cache(ifp) == 0 && (rt->rt_flags & RTF_HOST) == 0) { + /* + * This is probably an interface direct route for a link + * which does not need neighbor caches (e.g. fe80::%lo0/64). + * We do not need special treatment below for such a route. + * Moreover, the RTF_LLINFO flag which would be set below + * would annoy the ndp(8) command. + */ + return; + } + + if (req == RTM_RESOLVE && + (nd6_need_cache(ifp) == 0 || /* stf case */ + !nd6_is_addr_neighbor((struct sockaddr_in6 *)rt_key(rt), ifp))) { + /* + * FreeBSD and BSD/OS often make a cloned host route based + * on a less-specific route (e.g. the default route). + * If the less specific route does not have a "gateway" + * (this is the case when the route just goes to a p2p or an + * stf interface), we'll mistakenly make a neighbor cache for + * the host route, and will see strange neighbor solicitation + * for the corresponding destination. In order to avoid the + * confusion, we check if the destination of the route is + * a neighbor in terms of neighbor discovery, and stop the + * process if not. Additionally, we remove the LLINFO flag + * so that ndp(8) will not try to get the neighbor information + * of the destination. + */ + rt->rt_flags &= ~RTF_LLINFO; + return; + } + + switch (req) { + case RTM_ADD: + /* + * There is no backward compatibility :) + * + * if ((rt->rt_flags & RTF_HOST) == 0 && + * SIN(rt_mask(rt))->sin_addr.s_addr != 0xffffffff) + * rt->rt_flags |= RTF_CLONING; + */ + if (rt->rt_flags & (RTF_CLONING | RTF_LLINFO)) { + /* + * Case 1: This route should come from + * a route to interface. RTF_LLINFO flag is set + * for a host route whose destination should be + * treated as on-link. + */ + rt_setgate(rt, rt_key(rt), + (struct sockaddr *)&null_sdl); + gate = rt->rt_gateway; + SDL(gate)->sdl_type = ifp->if_type; + SDL(gate)->sdl_index = ifp->if_index; + if (ln) + ln->ln_expire = time_second; +#if 1 + if (ln && ln->ln_expire == 0) { + /* kludge for desktops */ +#if 0 + printf("nd6_rtequest: time.tv_sec is zero; " + "treat it as 1\n"); +#endif + ln->ln_expire = 1; + } +#endif + if ((rt->rt_flags & RTF_CLONING)) + break; + } + /* + * In IPv4 code, we try to annonuce new RTF_ANNOUNCE entry here. + * We don't do that here since llinfo is not ready yet. + * + * There are also couple of other things to be discussed: + * - unsolicited NA code needs improvement beforehand + * - RFC2461 says we MAY send multicast unsolicited NA + * (7.2.6 paragraph 4), however, it also says that we + * SHOULD provide a mechanism to prevent multicast NA storm. + * we don't have anything like it right now. + * note that the mechanism needs a mutual agreement + * between proxies, which means that we need to implement + * a new protocol, or a new kludge. + * - from RFC2461 6.2.4, host MUST NOT send an unsolicited NA. + * we need to check ip6forwarding before sending it. + * (or should we allow proxy ND configuration only for + * routers? there's no mention about proxy ND from hosts) + */ +#if 0 + /* XXX it does not work */ + if (rt->rt_flags & RTF_ANNOUNCE) + nd6_na_output(ifp, + &SIN6(rt_key(rt))->sin6_addr, + &SIN6(rt_key(rt))->sin6_addr, + ip6_forwarding ? ND_NA_FLAG_ROUTER : 0, + 1, NULL); +#endif + /* FALLTHROUGH */ + case RTM_RESOLVE: + if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) == 0) { + /* + * Address resolution isn't necessary for a point to + * point link, so we can skip this test for a p2p link. + */ + if (gate->sa_family != AF_LINK || + gate->sa_len < sizeof(null_sdl)) { + log(LOG_DEBUG, + "nd6_rtrequest: bad gateway value: %s\n", + if_name(ifp)); + break; + } + SDL(gate)->sdl_type = ifp->if_type; + SDL(gate)->sdl_index = ifp->if_index; + } + if (ln != NULL) + break; /* This happens on a route change */ + /* + * Case 2: This route may come from cloning, or a manual route + * add with a LL address. + */ + R_Malloc(ln, struct llinfo_nd6 *, sizeof(*ln)); + rt->rt_llinfo = (caddr_t)ln; + if (!ln) { + log(LOG_DEBUG, "nd6_rtrequest: malloc failed\n"); + break; + } + nd6_inuse++; + nd6_allocated++; + Bzero(ln, sizeof(*ln)); + ln->ln_rt = rt; + /* this is required for "ndp" command. - shin */ + if (req == RTM_ADD) { + /* + * gate should have some valid AF_LINK entry, + * and ln->ln_expire should have some lifetime + * which is specified by ndp command. + */ + ln->ln_state = ND6_LLINFO_REACHABLE; + ln->ln_byhint = 0; + } else { + /* + * When req == RTM_RESOLVE, rt is created and + * initialized in rtrequest(), so rt_expire is 0. + */ + ln->ln_state = ND6_LLINFO_NOSTATE; + ln->ln_expire = time_second; + } + rt->rt_flags |= RTF_LLINFO; + ln->ln_next = llinfo_nd6.ln_next; + llinfo_nd6.ln_next = ln; + ln->ln_prev = &llinfo_nd6; + ln->ln_next->ln_prev = ln; + + /* + * check if rt_key(rt) is one of my address assigned + * to the interface. + */ + ifa = (struct ifaddr *)in6ifa_ifpwithaddr(rt->rt_ifp, + &SIN6(rt_key(rt))->sin6_addr); + if (ifa) { + caddr_t macp = nd6_ifptomac(ifp); + ln->ln_expire = 0; + ln->ln_state = ND6_LLINFO_REACHABLE; + ln->ln_byhint = 0; + if (macp) { + Bcopy(macp, LLADDR(SDL(gate)), ifp->if_addrlen); + SDL(gate)->sdl_alen = ifp->if_addrlen; + } + if (nd6_useloopback) { + rt->rt_ifp = &loif[0]; /* XXX */ + /* + * Make sure rt_ifa be equal to the ifaddr + * corresponding to the address. + * We need this because when we refer + * rt_ifa->ia6_flags in ip6_input, we assume + * that the rt_ifa points to the address instead + * of the loopback address. + */ + if (ifa != rt->rt_ifa) { + IFAFREE(rt->rt_ifa); + IFAREF(ifa); + rt->rt_ifa = ifa; + } + } + } else if (rt->rt_flags & RTF_ANNOUNCE) { + ln->ln_expire = 0; + ln->ln_state = ND6_LLINFO_REACHABLE; + ln->ln_byhint = 0; + + /* join solicited node multicast for proxy ND */ + if (ifp->if_flags & IFF_MULTICAST) { + struct in6_addr llsol; + int error; + + llsol = SIN6(rt_key(rt))->sin6_addr; + llsol.s6_addr16[0] = htons(0xff02); + llsol.s6_addr16[1] = htons(ifp->if_index); + llsol.s6_addr32[1] = 0; + llsol.s6_addr32[2] = htonl(1); + llsol.s6_addr8[12] = 0xff; + + if (!in6_addmulti(&llsol, ifp, &error)) { + nd6log((LOG_ERR, "%s: failed to join " + "%s (errno=%d)\n", if_name(ifp), + ip6_sprintf(&llsol), error)); + } + } + } + break; + + case RTM_DELETE: + if (!ln) + break; + /* leave from solicited node multicast for proxy ND */ + if ((rt->rt_flags & RTF_ANNOUNCE) != 0 && + (ifp->if_flags & IFF_MULTICAST) != 0) { + struct in6_addr llsol; + struct in6_multi *in6m; + + llsol = SIN6(rt_key(rt))->sin6_addr; + llsol.s6_addr16[0] = htons(0xff02); + llsol.s6_addr16[1] = htons(ifp->if_index); + llsol.s6_addr32[1] = 0; + llsol.s6_addr32[2] = htonl(1); + llsol.s6_addr8[12] = 0xff; + + IN6_LOOKUP_MULTI(llsol, ifp, in6m); + if (in6m) + in6_delmulti(in6m); + } + nd6_inuse--; + ln->ln_next->ln_prev = ln->ln_prev; + ln->ln_prev->ln_next = ln->ln_next; + ln->ln_prev = NULL; + rt->rt_llinfo = 0; + rt->rt_flags &= ~RTF_LLINFO; + if (ln->ln_hold) + m_freem(ln->ln_hold); + Free((caddr_t)ln); + } +} + +int +nd6_ioctl(cmd, data, ifp) + u_long cmd; + caddr_t data; + struct ifnet *ifp; +{ + struct in6_drlist *drl = (struct in6_drlist *)data; + struct in6_prlist *prl = (struct in6_prlist *)data; + struct in6_ndireq *ndi = (struct in6_ndireq *)data; + struct in6_nbrinfo *nbi = (struct in6_nbrinfo *)data; + struct in6_ndifreq *ndif = (struct in6_ndifreq *)data; + struct nd_defrouter *dr, any; + struct nd_prefix *pr; + struct rtentry *rt; + int i = 0, error = 0; + int s; + + switch (cmd) { + case SIOCGDRLST_IN6: + /* + * obsolete API, use sysctl under net.inet6.icmp6 + */ + bzero(drl, sizeof(*drl)); + s = splnet(); + dr = TAILQ_FIRST(&nd_defrouter); + while (dr && i < DRLSTSIZ) { + drl->defrouter[i].rtaddr = dr->rtaddr; + if (IN6_IS_ADDR_LINKLOCAL(&drl->defrouter[i].rtaddr)) { + /* XXX: need to this hack for KAME stack */ + drl->defrouter[i].rtaddr.s6_addr16[1] = 0; + } else + log(LOG_ERR, + "default router list contains a " + "non-linklocal address(%s)\n", + ip6_sprintf(&drl->defrouter[i].rtaddr)); + + drl->defrouter[i].flags = dr->flags; + drl->defrouter[i].rtlifetime = dr->rtlifetime; + drl->defrouter[i].expire = dr->expire; + drl->defrouter[i].if_index = dr->ifp->if_index; + i++; + dr = TAILQ_NEXT(dr, dr_entry); + } + splx(s); + break; + case SIOCGPRLST_IN6: + /* + * obsolete API, use sysctl under net.inet6.icmp6 + */ + /* + * XXX meaning of fields, especialy "raflags", is very + * differnet between RA prefix list and RR/static prefix list. + * how about separating ioctls into two? + */ + bzero(prl, sizeof(*prl)); + s = splnet(); + pr = nd_prefix.lh_first; + while (pr && i < PRLSTSIZ) { + struct nd_pfxrouter *pfr; + int j; + + (void)in6_embedscope(&prl->prefix[i].prefix, + &pr->ndpr_prefix, NULL, NULL); + prl->prefix[i].raflags = pr->ndpr_raf; + prl->prefix[i].prefixlen = pr->ndpr_plen; + prl->prefix[i].vltime = pr->ndpr_vltime; + prl->prefix[i].pltime = pr->ndpr_pltime; + prl->prefix[i].if_index = pr->ndpr_ifp->if_index; + prl->prefix[i].expire = pr->ndpr_expire; + + pfr = pr->ndpr_advrtrs.lh_first; + j = 0; + while (pfr) { + if (j < DRLSTSIZ) { +#define RTRADDR prl->prefix[i].advrtr[j] + RTRADDR = pfr->router->rtaddr; + if (IN6_IS_ADDR_LINKLOCAL(&RTRADDR)) { + /* XXX: hack for KAME */ + RTRADDR.s6_addr16[1] = 0; + } else + log(LOG_ERR, + "a router(%s) advertises " + "a prefix with " + "non-link local address\n", + ip6_sprintf(&RTRADDR)); +#undef RTRADDR + } + j++; + pfr = pfr->pfr_next; + } + prl->prefix[i].advrtrs = j; + prl->prefix[i].origin = PR_ORIG_RA; + + i++; + pr = pr->ndpr_next; + } + { + struct rr_prefix *rpp; + + for (rpp = LIST_FIRST(&rr_prefix); rpp; + rpp = LIST_NEXT(rpp, rp_entry)) { + if (i >= PRLSTSIZ) + break; + (void)in6_embedscope(&prl->prefix[i].prefix, + &pr->ndpr_prefix, NULL, NULL); + prl->prefix[i].raflags = rpp->rp_raf; + prl->prefix[i].prefixlen = rpp->rp_plen; + prl->prefix[i].vltime = rpp->rp_vltime; + prl->prefix[i].pltime = rpp->rp_pltime; + prl->prefix[i].if_index = rpp->rp_ifp->if_index; + prl->prefix[i].expire = rpp->rp_expire; + prl->prefix[i].advrtrs = 0; + prl->prefix[i].origin = rpp->rp_origin; + i++; + } + } + splx(s); + + break; + case OSIOCGIFINFO_IN6: + if (!nd_ifinfo || i >= nd_ifinfo_indexlim) { + error = EINVAL; + break; + } + ndi->ndi.linkmtu = nd_ifinfo[ifp->if_index].linkmtu; + ndi->ndi.maxmtu = nd_ifinfo[ifp->if_index].maxmtu; + ndi->ndi.basereachable = + nd_ifinfo[ifp->if_index].basereachable; + ndi->ndi.reachable = nd_ifinfo[ifp->if_index].reachable; + ndi->ndi.retrans = nd_ifinfo[ifp->if_index].retrans; + ndi->ndi.flags = nd_ifinfo[ifp->if_index].flags; + ndi->ndi.recalctm = nd_ifinfo[ifp->if_index].recalctm; + ndi->ndi.chlim = nd_ifinfo[ifp->if_index].chlim; + ndi->ndi.receivedra = nd_ifinfo[ifp->if_index].receivedra; + break; + case SIOCGIFINFO_IN6: + if (!nd_ifinfo || i >= nd_ifinfo_indexlim) { + error = EINVAL; + break; + } + ndi->ndi = nd_ifinfo[ifp->if_index]; + break; + case SIOCSIFINFO_FLAGS: + /* XXX: almost all other fields of ndi->ndi is unused */ + if (!nd_ifinfo || i >= nd_ifinfo_indexlim) { + error = EINVAL; + break; + } + nd_ifinfo[ifp->if_index].flags = ndi->ndi.flags; + break; + case SIOCSNDFLUSH_IN6: /* XXX: the ioctl name is confusing... */ + /* flush default router list */ + /* + * xxx sumikawa: should not delete route if default + * route equals to the top of default router list + */ + bzero(&any, sizeof(any)); + defrouter_delreq(&any, 0); + defrouter_select(); + /* xxx sumikawa: flush prefix list */ + break; + case SIOCSPFXFLUSH_IN6: + { + /* flush all the prefix advertised by routers */ + struct nd_prefix *pr, *next; + + s = splnet(); + for (pr = nd_prefix.lh_first; pr; pr = next) { + struct in6_ifaddr *ia, *ia_next; + + next = pr->ndpr_next; + + if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) + continue; /* XXX */ + + /* do we really have to remove addresses as well? */ + for (ia = in6_ifaddr; ia; ia = ia_next) { + /* ia might be removed. keep the next ptr. */ + ia_next = ia->ia_next; + + if ((ia->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + if (ia->ia6_ndpr == pr) + in6_purgeaddr(&ia->ia_ifa); + } + prelist_remove(pr); + } + splx(s); + break; + } + case SIOCSRTRFLUSH_IN6: + { + /* flush all the default routers */ + struct nd_defrouter *dr, *next; + + s = splnet(); + if ((dr = TAILQ_FIRST(&nd_defrouter)) != NULL) { + /* + * The first entry of the list may be stored in + * the routing table, so we'll delete it later. + */ + for (dr = TAILQ_NEXT(dr, dr_entry); dr; dr = next) { + next = TAILQ_NEXT(dr, dr_entry); + defrtrlist_del(dr); + } + defrtrlist_del(TAILQ_FIRST(&nd_defrouter)); + } + splx(s); + break; + } + case SIOCGNBRINFO_IN6: + { + struct llinfo_nd6 *ln; + struct in6_addr nb_addr = nbi->addr; /* make local for safety */ + + /* + * XXX: KAME specific hack for scoped addresses + * XXXX: for other scopes than link-local? + */ + if (IN6_IS_ADDR_LINKLOCAL(&nbi->addr) || + IN6_IS_ADDR_MC_LINKLOCAL(&nbi->addr)) { + u_int16_t *idp = (u_int16_t *)&nb_addr.s6_addr[2]; + + if (*idp == 0) + *idp = htons(ifp->if_index); + } + + s = splnet(); + if ((rt = nd6_lookup(&nb_addr, 0, ifp)) == NULL) { + error = EINVAL; + splx(s); + break; + } + ln = (struct llinfo_nd6 *)rt->rt_llinfo; + nbi->state = ln->ln_state; + nbi->asked = ln->ln_asked; + nbi->isrouter = ln->ln_router; + nbi->expire = ln->ln_expire; + splx(s); + + break; + } + case SIOCGDEFIFACE_IN6: /* XXX: should be implemented as a sysctl? */ + ndif->ifindex = nd6_defifindex; + break; + case SIOCSDEFIFACE_IN6: /* XXX: should be implemented as a sysctl? */ + return(nd6_setdefaultiface(ndif->ifindex)); + break; + } + return(error); +} + +/* + * Create neighbor cache entry and cache link-layer address, + * on reception of inbound ND6 packets. (RS/RA/NS/redirect) + */ +struct rtentry * +nd6_cache_lladdr(ifp, from, lladdr, lladdrlen, type, code) + struct ifnet *ifp; + struct in6_addr *from; + char *lladdr; + int lladdrlen; + int type; /* ICMP6 type */ + int code; /* type dependent information */ +{ + struct rtentry *rt = NULL; + struct llinfo_nd6 *ln = NULL; + int is_newentry; + struct sockaddr_dl *sdl = NULL; + int do_update; + int olladdr; + int llchange; + int newstate = 0; + + if (!ifp) + panic("ifp == NULL in nd6_cache_lladdr"); + if (!from) + panic("from == NULL in nd6_cache_lladdr"); + + /* nothing must be updated for unspecified address */ + if (IN6_IS_ADDR_UNSPECIFIED(from)) + return NULL; + + /* + * Validation about ifp->if_addrlen and lladdrlen must be done in + * the caller. + * + * XXX If the link does not have link-layer adderss, what should + * we do? (ifp->if_addrlen == 0) + * Spec says nothing in sections for RA, RS and NA. There's small + * description on it in NS section (RFC 2461 7.2.3). + */ + + rt = nd6_lookup(from, 0, ifp); + if (!rt) { +#if 0 + /* nothing must be done if there's no lladdr */ + if (!lladdr || !lladdrlen) + return NULL; +#endif + + rt = nd6_lookup(from, 1, ifp); + is_newentry = 1; + } else { + /* do nothing if static ndp is set */ + if (rt->rt_flags & RTF_STATIC) + return NULL; + is_newentry = 0; + } + + if (!rt) + return NULL; + if ((rt->rt_flags & (RTF_GATEWAY | RTF_LLINFO)) != RTF_LLINFO) { +fail: + (void)nd6_free(rt); + return NULL; + } + ln = (struct llinfo_nd6 *)rt->rt_llinfo; + if (!ln) + goto fail; + if (!rt->rt_gateway) + goto fail; + if (rt->rt_gateway->sa_family != AF_LINK) + goto fail; + sdl = SDL(rt->rt_gateway); + + olladdr = (sdl->sdl_alen) ? 1 : 0; + if (olladdr && lladdr) { + if (bcmp(lladdr, LLADDR(sdl), ifp->if_addrlen)) + llchange = 1; + else + llchange = 0; + } else + llchange = 0; + + /* + * newentry olladdr lladdr llchange (*=record) + * 0 n n -- (1) + * 0 y n -- (2) + * 0 n y -- (3) * STALE + * 0 y y n (4) * + * 0 y y y (5) * STALE + * 1 -- n -- (6) NOSTATE(= PASSIVE) + * 1 -- y -- (7) * STALE + */ + + if (lladdr) { /* (3-5) and (7) */ + /* + * Record source link-layer address + * XXX is it dependent to ifp->if_type? + */ + sdl->sdl_alen = ifp->if_addrlen; + bcopy(lladdr, LLADDR(sdl), ifp->if_addrlen); + } + + if (!is_newentry) { + if ((!olladdr && lladdr) /* (3) */ + || (olladdr && lladdr && llchange)) { /* (5) */ + do_update = 1; + newstate = ND6_LLINFO_STALE; + } else /* (1-2,4) */ + do_update = 0; + } else { + do_update = 1; + if (!lladdr) /* (6) */ + newstate = ND6_LLINFO_NOSTATE; + else /* (7) */ + newstate = ND6_LLINFO_STALE; + } + + if (do_update) { + /* + * Update the state of the neighbor cache. + */ + ln->ln_state = newstate; + + if (ln->ln_state == ND6_LLINFO_STALE) { + /* + * XXX: since nd6_output() below will cause + * state tansition to DELAY and reset the timer, + * we must set the timer now, although it is actually + * meaningless. + */ + ln->ln_expire = time_second + nd6_gctimer; + + if (ln->ln_hold) { + /* + * we assume ifp is not a p2p here, so just + * set the 2nd argument as the 1st one. + */ + nd6_output(ifp, ifp, ln->ln_hold, + (struct sockaddr_in6 *)rt_key(rt), + rt); + ln->ln_hold = NULL; + } + } else if (ln->ln_state == ND6_LLINFO_INCOMPLETE) { + /* probe right away */ + ln->ln_expire = time_second; + } + } + + /* + * ICMP6 type dependent behavior. + * + * NS: clear IsRouter if new entry + * RS: clear IsRouter + * RA: set IsRouter if there's lladdr + * redir: clear IsRouter if new entry + * + * RA case, (1): + * The spec says that we must set IsRouter in the following cases: + * - If lladdr exist, set IsRouter. This means (1-5). + * - If it is old entry (!newentry), set IsRouter. This means (7). + * So, based on the spec, in (1-5) and (7) cases we must set IsRouter. + * A quetion arises for (1) case. (1) case has no lladdr in the + * neighbor cache, this is similar to (6). + * This case is rare but we figured that we MUST NOT set IsRouter. + * + * newentry olladdr lladdr llchange NS RS RA redir + * D R + * 0 n n -- (1) c ? s + * 0 y n -- (2) c s s + * 0 n y -- (3) c s s + * 0 y y n (4) c s s + * 0 y y y (5) c s s + * 1 -- n -- (6) c c c s + * 1 -- y -- (7) c c s c s + * + * (c=clear s=set) + */ + switch (type & 0xff) { + case ND_NEIGHBOR_SOLICIT: + /* + * New entry must have is_router flag cleared. + */ + if (is_newentry) /* (6-7) */ + ln->ln_router = 0; + break; + case ND_REDIRECT: + /* + * If the icmp is a redirect to a better router, always set the + * is_router flag. Otherwise, if the entry is newly created, + * clear the flag. [RFC 2461, sec 8.3] + */ + if (code == ND_REDIRECT_ROUTER) + ln->ln_router = 1; + else if (is_newentry) /* (6-7) */ + ln->ln_router = 0; + break; + case ND_ROUTER_SOLICIT: + /* + * is_router flag must always be cleared. + */ + ln->ln_router = 0; + break; + case ND_ROUTER_ADVERT: + /* + * Mark an entry with lladdr as a router. + */ + if ((!is_newentry && (olladdr || lladdr)) /* (2-5) */ + || (is_newentry && lladdr)) { /* (7) */ + ln->ln_router = 1; + } + break; + } + + /* + * When the link-layer address of a router changes, select the + * best router again. In particular, when the neighbor entry is newly + * created, it might affect the selection policy. + * Question: can we restrict the first condition to the "is_newentry" + * case? + * XXX: when we hear an RA from a new router with the link-layer + * address option, defrouter_select() is called twice, since + * defrtrlist_update called the function as well. However, I believe + * we can compromise the overhead, since it only happens the first + * time. + * XXX: although defrouter_select() should not have a bad effect + * for those are not autoconfigured hosts, we explicitly avoid such + * cases for safety. + */ + if (do_update && ln->ln_router && !ip6_forwarding && ip6_accept_rtadv) + defrouter_select(); + + return rt; +} + +static void +nd6_slowtimo(ignored_arg) + void *ignored_arg; +{ + int s = splnet(); + int i; + struct nd_ifinfo *nd6if; + + callout_reset(&nd6_slowtimo_ch, ND6_SLOWTIMER_INTERVAL * hz, + nd6_slowtimo, NULL); + for (i = 1; i < if_index + 1; i++) { + if (!nd_ifinfo || i >= nd_ifinfo_indexlim) + continue; + nd6if = &nd_ifinfo[i]; + if (nd6if->basereachable && /* already initialized */ + (nd6if->recalctm -= ND6_SLOWTIMER_INTERVAL) <= 0) { + /* + * Since reachable time rarely changes by router + * advertisements, we SHOULD insure that a new random + * value gets recomputed at least once every few hours. + * (RFC 2461, 6.3.4) + */ + nd6if->recalctm = nd6_recalc_reachtm_interval; + nd6if->reachable = ND_COMPUTE_RTIME(nd6if->basereachable); + } + } + splx(s); +} + +#define senderr(e) { error = (e); goto bad;} +int +nd6_output(ifp, origifp, m0, dst, rt0) + struct ifnet *ifp; + struct ifnet *origifp; + struct mbuf *m0; + struct sockaddr_in6 *dst; + struct rtentry *rt0; +{ + struct mbuf *m = m0; + struct rtentry *rt = rt0; + struct sockaddr_in6 *gw6 = NULL; + struct llinfo_nd6 *ln = NULL; + int error = 0; + + if (IN6_IS_ADDR_MULTICAST(&dst->sin6_addr)) + goto sendpkt; + + if (nd6_need_cache(ifp) == 0) + goto sendpkt; + + /* + * next hop determination. This routine is derived from ether_outpout. + */ + if (rt) { + if ((rt->rt_flags & RTF_UP) == 0) { + if ((rt0 = rt = rtalloc1((struct sockaddr *)dst, 1, 0UL)) != + NULL) + { + rt->rt_refcnt--; + if (rt->rt_ifp != ifp) { + /* XXX: loop care? */ + return nd6_output(ifp, origifp, m0, + dst, rt); + } + } else + senderr(EHOSTUNREACH); + } + + if (rt->rt_flags & RTF_GATEWAY) { + gw6 = (struct sockaddr_in6 *)rt->rt_gateway; + + /* + * We skip link-layer address resolution and NUD + * if the gateway is not a neighbor from ND point + * of view, regardless of the value of nd_ifinfo.flags. + * The second condition is a bit tricky; we skip + * if the gateway is our own address, which is + * sometimes used to install a route to a p2p link. + */ + if (!nd6_is_addr_neighbor(gw6, ifp) || + in6ifa_ifpwithaddr(ifp, &gw6->sin6_addr)) { + /* + * We allow this kind of tricky route only + * when the outgoing interface is p2p. + * XXX: we may need a more generic rule here. + */ + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + senderr(EHOSTUNREACH); + + goto sendpkt; + } + + if (rt->rt_gwroute == 0) + goto lookup; + if (((rt = rt->rt_gwroute)->rt_flags & RTF_UP) == 0) { + rtfree(rt); rt = rt0; + lookup: rt->rt_gwroute = rtalloc1(rt->rt_gateway, 1, 0UL); + if ((rt = rt->rt_gwroute) == 0) + senderr(EHOSTUNREACH); + } + } + } + + /* + * Address resolution or Neighbor Unreachability Detection + * for the next hop. + * At this point, the destination of the packet must be a unicast + * or an anycast address(i.e. not a multicast). + */ + + /* Look up the neighbor cache for the nexthop */ + if (rt && (rt->rt_flags & RTF_LLINFO) != 0) + ln = (struct llinfo_nd6 *)rt->rt_llinfo; + else { + /* + * Since nd6_is_addr_neighbor() internally calls nd6_lookup(), + * the condition below is not very efficient. But we believe + * it is tolerable, because this should be a rare case. + */ + if (nd6_is_addr_neighbor(dst, ifp) && + (rt = nd6_lookup(&dst->sin6_addr, 1, ifp)) != NULL) + ln = (struct llinfo_nd6 *)rt->rt_llinfo; + } + if (!ln || !rt) { + if ((ifp->if_flags & IFF_POINTOPOINT) == 0 && + !(nd_ifinfo[ifp->if_index].flags & ND6_IFF_PERFORMNUD)) { + log(LOG_DEBUG, + "nd6_output: can't allocate llinfo for %s " + "(ln=%p, rt=%p)\n", + ip6_sprintf(&dst->sin6_addr), ln, rt); + senderr(EIO); /* XXX: good error? */ + } + + goto sendpkt; /* send anyway */ + } + + /* We don't have to do link-layer address resolution on a p2p link. */ + if ((ifp->if_flags & IFF_POINTOPOINT) != 0 && + ln->ln_state < ND6_LLINFO_REACHABLE) { + ln->ln_state = ND6_LLINFO_STALE; + ln->ln_expire = time_second + nd6_gctimer; + } + + /* + * The first time we send a packet to a neighbor whose entry is + * STALE, we have to change the state to DELAY and a sets a timer to + * expire in DELAY_FIRST_PROBE_TIME seconds to ensure do + * neighbor unreachability detection on expiration. + * (RFC 2461 7.3.3) + */ + if (ln->ln_state == ND6_LLINFO_STALE) { + ln->ln_asked = 0; + ln->ln_state = ND6_LLINFO_DELAY; + ln->ln_expire = time_second + nd6_delay; + } + + /* + * If the neighbor cache entry has a state other than INCOMPLETE + * (i.e. its link-layer address is already resolved), just + * send the packet. + */ + if (ln->ln_state > ND6_LLINFO_INCOMPLETE) + goto sendpkt; + + /* + * There is a neighbor cache entry, but no ethernet address + * response yet. Replace the held mbuf (if any) with this + * latest one. + * + * This code conforms to the rate-limiting rule described in Section + * 7.2.2 of RFC 2461, because the timer is set correctly after sending + * an NS below. + */ + if (ln->ln_state == ND6_LLINFO_NOSTATE) + ln->ln_state = ND6_LLINFO_INCOMPLETE; + if (ln->ln_hold) + m_freem(ln->ln_hold); + ln->ln_hold = m; + if (ln->ln_expire) { + if (ln->ln_asked < nd6_mmaxtries && + ln->ln_expire < time_second) { + ln->ln_asked++; + ln->ln_expire = time_second + + nd_ifinfo[ifp->if_index].retrans / 1000; + nd6_ns_output(ifp, NULL, &dst->sin6_addr, ln, 0); + } + } + return(0); + + sendpkt: + +#ifdef MAC + mac_create_mbuf_linklayer(ifp, m); +#endif + if ((ifp->if_flags & IFF_LOOPBACK) != 0) { + return((*ifp->if_output)(origifp, m, (struct sockaddr *)dst, + rt)); + } + return((*ifp->if_output)(ifp, m, (struct sockaddr *)dst, rt)); + + bad: + if (m) + m_freem(m); + return (error); +} +#undef senderr + +int +nd6_need_cache(ifp) + struct ifnet *ifp; +{ + /* + * XXX: we currently do not make neighbor cache on any interface + * other than ARCnet, Ethernet, FDDI and GIF. + * + * RFC2893 says: + * - unidirectional tunnels needs no ND + */ + switch (ifp->if_type) { + case IFT_ARCNET: + case IFT_ETHER: + case IFT_FDDI: + case IFT_IEEE1394: +#ifdef IFT_L2VLAN + case IFT_L2VLAN: +#endif +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: +#endif + case IFT_GIF: /* XXX need more cases? */ + return(1); + default: + return(0); + } +} + +int +nd6_storelladdr(ifp, rt, m, dst, desten) + struct ifnet *ifp; + struct rtentry *rt; + struct mbuf *m; + struct sockaddr *dst; + u_char *desten; +{ + int i; + struct sockaddr_dl *sdl; + + if (m->m_flags & M_MCAST) { + switch (ifp->if_type) { + case IFT_ETHER: + case IFT_FDDI: +#ifdef IFT_L2VLAN + case IFT_L2VLAN: +#endif +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: +#endif + ETHER_MAP_IPV6_MULTICAST(&SIN6(dst)->sin6_addr, + desten); + return(1); + case IFT_IEEE1394: + /* + * netbsd can use if_broadcastaddr, but we don't do so + * to reduce # of ifdef. + */ + for (i = 0; i < ifp->if_addrlen; i++) + desten[i] = ~0; + return(1); + case IFT_ARCNET: + *desten = 0; + return(1); + default: + m_freem(m); + return(0); + } + } + + if (rt == NULL) { + /* this could happen, if we could not allocate memory */ + m_freem(m); + return(0); + } + if (rt->rt_gateway->sa_family != AF_LINK) { + printf("nd6_storelladdr: something odd happens\n"); + m_freem(m); + return(0); + } + sdl = SDL(rt->rt_gateway); + if (sdl->sdl_alen == 0) { + /* this should be impossible, but we bark here for debugging */ + printf("nd6_storelladdr: sdl_alen == 0\n"); + m_freem(m); + return(0); + } + + bcopy(LLADDR(sdl), desten, sdl->sdl_alen); + return(1); +} + +static int nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS); +static int nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS); +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_inet6_icmp6); +#endif +SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_DRLIST, nd6_drlist, + CTLFLAG_RD, nd6_sysctl_drlist, ""); +SYSCTL_NODE(_net_inet6_icmp6, ICMPV6CTL_ND6_PRLIST, nd6_prlist, + CTLFLAG_RD, nd6_sysctl_prlist, ""); + +static int +nd6_sysctl_drlist(SYSCTL_HANDLER_ARGS) +{ + int error; + char buf[1024]; + struct in6_defrouter *d, *de; + struct nd_defrouter *dr; + + if (req->newptr) + return EPERM; + error = 0; + + for (dr = TAILQ_FIRST(&nd_defrouter); + dr; + dr = TAILQ_NEXT(dr, dr_entry)) { + d = (struct in6_defrouter *)buf; + de = (struct in6_defrouter *)(buf + sizeof(buf)); + + if (d + 1 <= de) { + bzero(d, sizeof(*d)); + d->rtaddr.sin6_family = AF_INET6; + d->rtaddr.sin6_len = sizeof(d->rtaddr); + if (in6_recoverscope(&d->rtaddr, &dr->rtaddr, + dr->ifp) != 0) + log(LOG_ERR, + "scope error in " + "default router list (%s)\n", + ip6_sprintf(&dr->rtaddr)); + d->flags = dr->flags; + d->rtlifetime = dr->rtlifetime; + d->expire = dr->expire; + d->if_index = dr->ifp->if_index; + } else + panic("buffer too short"); + + error = SYSCTL_OUT(req, buf, sizeof(*d)); + if (error) + break; + } + return error; +} + +static int +nd6_sysctl_prlist(SYSCTL_HANDLER_ARGS) +{ + int error; + char buf[1024]; + struct in6_prefix *p, *pe; + struct nd_prefix *pr; + + if (req->newptr) + return EPERM; + error = 0; + + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + u_short advrtrs; + size_t advance; + struct sockaddr_in6 *sin6, *s6; + struct nd_pfxrouter *pfr; + + p = (struct in6_prefix *)buf; + pe = (struct in6_prefix *)(buf + sizeof(buf)); + + if (p + 1 <= pe) { + bzero(p, sizeof(*p)); + sin6 = (struct sockaddr_in6 *)(p + 1); + + p->prefix = pr->ndpr_prefix; + if (in6_recoverscope(&p->prefix, + &p->prefix.sin6_addr, pr->ndpr_ifp) != 0) + log(LOG_ERR, + "scope error in prefix list (%s)\n", + ip6_sprintf(&p->prefix.sin6_addr)); + p->raflags = pr->ndpr_raf; + p->prefixlen = pr->ndpr_plen; + p->vltime = pr->ndpr_vltime; + p->pltime = pr->ndpr_pltime; + p->if_index = pr->ndpr_ifp->if_index; + p->expire = pr->ndpr_expire; + p->refcnt = pr->ndpr_refcnt; + p->flags = pr->ndpr_stateflags; + p->origin = PR_ORIG_RA; + advrtrs = 0; + for (pfr = pr->ndpr_advrtrs.lh_first; + pfr; + pfr = pfr->pfr_next) { + if ((void *)&sin6[advrtrs + 1] > + (void *)pe) { + advrtrs++; + continue; + } + s6 = &sin6[advrtrs]; + bzero(s6, sizeof(*s6)); + s6->sin6_family = AF_INET6; + s6->sin6_len = sizeof(*sin6); + if (in6_recoverscope(s6, + &pfr->router->rtaddr, + pfr->router->ifp) != 0) + log(LOG_ERR, + "scope error in " + "prefix list (%s)\n", + ip6_sprintf(&pfr->router->rtaddr)); + advrtrs++; + } + p->advrtrs = advrtrs; + } else + panic("buffer too short"); + + advance = sizeof(*p) + sizeof(*sin6) * advrtrs; + error = SYSCTL_OUT(req, buf, advance); + if (error) + break; + } + return error; +} diff --git a/sys/netinet6/nd6.h b/sys/netinet6/nd6.h new file mode 100644 index 0000000..c8b531a --- /dev/null +++ b/sys/netinet6/nd6.h @@ -0,0 +1,404 @@ +/* $FreeBSD$ */ +/* $KAME: nd6.h,v 1.76 2001/12/18 02:10:31 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_ND6_H_ +#define _NETINET6_ND6_H_ + +/* see net/route.h, or net/if_inarp.h */ +#ifndef RTF_ANNOUNCE +#define RTF_ANNOUNCE RTF_PROTO2 +#endif + +#include <sys/queue.h> +#include <sys/callout.h> + +struct llinfo_nd6 { + struct llinfo_nd6 *ln_next; + struct llinfo_nd6 *ln_prev; + struct rtentry *ln_rt; + struct mbuf *ln_hold; /* last packet until resolved/timeout */ + long ln_asked; /* number of queries already sent for this addr */ + u_long ln_expire; /* lifetime for NDP state transition */ + short ln_state; /* reachability state */ + short ln_router; /* 2^0: ND6 router bit */ + int ln_byhint; /* # of times we made it reachable by UL hint */ +}; + +#define ND6_LLINFO_NOSTATE -2 +/* + * We don't need the WAITDELETE state any more, but we keep the definition + * in a comment line instead of removing it. This is necessary to avoid + * unintentionally reusing the value for another purpose, which might + * affect backward compatibility with old applications. + * (20000711 jinmei@kame.net) + */ +/* #define ND6_LLINFO_WAITDELETE -1 */ +#define ND6_LLINFO_INCOMPLETE 0 +#define ND6_LLINFO_REACHABLE 1 +#define ND6_LLINFO_STALE 2 +#define ND6_LLINFO_DELAY 3 +#define ND6_LLINFO_PROBE 4 + +#define ND6_IS_LLINFO_PROBREACH(n) ((n)->ln_state > ND6_LLINFO_INCOMPLETE) + +struct nd_ifinfo { + u_int32_t linkmtu; /* LinkMTU */ + u_int32_t maxmtu; /* Upper bound of LinkMTU */ + u_int32_t basereachable; /* BaseReachableTime */ + u_int32_t reachable; /* Reachable Time */ + u_int32_t retrans; /* Retrans Timer */ + u_int32_t flags; /* Flags */ + int recalctm; /* BaseReacable re-calculation timer */ + u_int8_t chlim; /* CurHopLimit */ + u_int8_t receivedra; + /* the following 3 members are for privacy extension for addrconf */ + u_int8_t randomseed0[8]; /* upper 64 bits of MD5 digest */ + u_int8_t randomseed1[8]; /* lower 64 bits (usually the EUI64 IFID) */ + u_int8_t randomid[8]; /* current random ID */ +}; + +#define ND6_IFF_PERFORMNUD 0x1 + +struct in6_nbrinfo { + char ifname[IFNAMSIZ]; /* if name, e.g. "en0" */ + struct in6_addr addr; /* IPv6 address of the neighbor */ + long asked; /* number of queries already sent for this addr */ + int isrouter; /* if it acts as a router */ + int state; /* reachability state */ + int expire; /* lifetime for NDP state transition */ +}; + +#define DRLSTSIZ 10 +#define PRLSTSIZ 10 +struct in6_drlist { + char ifname[IFNAMSIZ]; + struct { + struct in6_addr rtaddr; + u_char flags; + u_short rtlifetime; + u_long expire; + u_short if_index; + } defrouter[DRLSTSIZ]; +}; + +struct in6_defrouter { + struct sockaddr_in6 rtaddr; + u_char flags; + u_short rtlifetime; + u_long expire; + u_short if_index; +}; + +struct in6_prlist { + char ifname[IFNAMSIZ]; + struct { + struct in6_addr prefix; + struct prf_ra raflags; + u_char prefixlen; + u_char origin; + u_int32_t vltime; + u_int32_t pltime; + time_t expire; + u_short if_index; + u_short advrtrs; /* number of advertisement routers */ + struct in6_addr advrtr[DRLSTSIZ]; /* XXX: explicit limit */ + } prefix[PRLSTSIZ]; +}; + +struct in6_prefix { + struct sockaddr_in6 prefix; + struct prf_ra raflags; + u_char prefixlen; + u_char origin; + u_long vltime; + u_long pltime; + u_long expire; + u_int32_t flags; + int refcnt; + u_short if_index; + u_short advrtrs; /* number of advertisement routers */ + /* struct sockaddr_in6 advrtr[] */ +}; + +#ifdef _KERNEL +struct in6_ondireq { + char ifname[IFNAMSIZ]; + struct { + u_int32_t linkmtu; /* LinkMTU */ + u_int32_t maxmtu; /* Upper bound of LinkMTU */ + u_int32_t basereachable; /* BaseReachableTime */ + u_int32_t reachable; /* Reachable Time */ + u_int32_t retrans; /* Retrans Timer */ + u_int32_t flags; /* Flags */ + int recalctm; /* BaseReacable re-calculation timer */ + u_int8_t chlim; /* CurHopLimit */ + u_int8_t receivedra; + } ndi; +}; +#endif + +struct in6_ndireq { + char ifname[IFNAMSIZ]; + struct nd_ifinfo ndi; +}; + +struct in6_ndifreq { + char ifname[IFNAMSIZ]; + u_long ifindex; +}; + +/* Prefix status */ +#define NDPRF_ONLINK 0x1 +#define NDPRF_DETACHED 0x2 + +/* protocol constants */ +#define MAX_RTR_SOLICITATION_DELAY 1 /* 1sec */ +#define RTR_SOLICITATION_INTERVAL 4 /* 4sec */ +#define MAX_RTR_SOLICITATIONS 3 + +#define ND6_INFINITE_LIFETIME 0xffffffff + +#ifdef _KERNEL +/* node constants */ +#define MAX_REACHABLE_TIME 3600000 /* msec */ +#define REACHABLE_TIME 30000 /* msec */ +#define RETRANS_TIMER 1000 /* msec */ +#define MIN_RANDOM_FACTOR 512 /* 1024 * 0.5 */ +#define MAX_RANDOM_FACTOR 1536 /* 1024 * 1.5 */ +#define DEF_TEMP_VALID_LIFETIME 604800 /* 1 week */ +#define DEF_TEMP_PREFERRED_LIFETIME 86400 /* 1 day */ +#define TEMPADDR_REGEN_ADVANCE 5 /* sec */ +#define MAX_TEMP_DESYNC_FACTOR 600 /* 10 min */ +#define ND_COMPUTE_RTIME(x) \ + (((MIN_RANDOM_FACTOR * (x >> 10)) + (random() & \ + ((MAX_RANDOM_FACTOR - MIN_RANDOM_FACTOR) * (x >> 10)))) /1000) + +TAILQ_HEAD(nd_drhead, nd_defrouter); +struct nd_defrouter { + TAILQ_ENTRY(nd_defrouter) dr_entry; + struct in6_addr rtaddr; + u_char flags; /* flags on RA message */ + u_short rtlifetime; + u_long expire; + u_long advint; /* Mobile IPv6 addition (milliseconds) */ + u_long advint_expire; /* Mobile IPv6 addition */ + int advints_lost; /* Mobile IPv6 addition */ + struct ifnet *ifp; +}; + +struct nd_prefix { + struct ifnet *ndpr_ifp; + LIST_ENTRY(nd_prefix) ndpr_entry; + struct sockaddr_in6 ndpr_prefix; /* prefix */ + struct in6_addr ndpr_mask; /* netmask derived from the prefix */ + struct in6_addr ndpr_addr; /* address that is derived from the prefix */ + u_int32_t ndpr_vltime; /* advertised valid lifetime */ + u_int32_t ndpr_pltime; /* advertised preferred lifetime */ + time_t ndpr_expire; /* expiration time of the prefix */ + time_t ndpr_preferred; /* preferred time of the prefix */ + struct prf_ra ndpr_flags; + u_int32_t ndpr_stateflags; /* actual state flags */ + /* list of routers that advertise the prefix: */ + LIST_HEAD(pr_rtrhead, nd_pfxrouter) ndpr_advrtrs; + u_char ndpr_plen; + int ndpr_refcnt; /* reference couter from addresses */ +}; + +#define ndpr_next ndpr_entry.le_next + +#define ndpr_raf ndpr_flags +#define ndpr_raf_onlink ndpr_flags.onlink +#define ndpr_raf_auto ndpr_flags.autonomous + +/* + * We keep expired prefix for certain amount of time, for validation purposes. + * 1800s = MaxRtrAdvInterval + */ +#define NDPR_KEEP_EXPIRED (1800 * 2) + +/* + * Message format for use in obtaining information about prefixes + * from inet6 sysctl function + */ +struct inet6_ndpr_msghdr { + u_short inpm_msglen; /* to skip over non-understood messages */ + u_char inpm_version; /* future binary compatibility */ + u_char inpm_type; /* message type */ + struct in6_addr inpm_prefix; + u_long prm_vltim; + u_long prm_pltime; + u_long prm_expire; + u_long prm_preferred; + struct in6_prflags prm_flags; + u_short prm_index; /* index for associated ifp */ + u_char prm_plen; /* length of prefix in bits */ +}; + +#define prm_raf_onlink prm_flags.prf_ra.onlink +#define prm_raf_auto prm_flags.prf_ra.autonomous + +#define prm_statef_onlink prm_flags.prf_state.onlink + +#define prm_rrf_decrvalid prm_flags.prf_rr.decrvalid +#define prm_rrf_decrprefd prm_flags.prf_rr.decrprefd + +#define ifpr2ndpr(ifpr) ((struct nd_prefix *)(ifpr)) +#define ndpr2ifpr(ndpr) ((struct ifprefix *)(ndpr)) + +struct nd_pfxrouter { + LIST_ENTRY(nd_pfxrouter) pfr_entry; +#define pfr_next pfr_entry.le_next + struct nd_defrouter *router; +}; + +LIST_HEAD(nd_prhead, nd_prefix); + +/* nd6.c */ +extern int nd6_prune; +extern int nd6_delay; +extern int nd6_umaxtries; +extern int nd6_mmaxtries; +extern int nd6_useloopback; +extern int nd6_maxnudhint; +extern int nd6_gctimer; +extern struct llinfo_nd6 llinfo_nd6; +extern struct nd_ifinfo *nd_ifinfo; +extern struct nd_drhead nd_defrouter; +extern struct nd_prhead nd_prefix; +extern int nd6_debug; + +#define nd6log(x) do { if (nd6_debug) log x; } while (0) + +extern struct callout nd6_timer_ch; + +/* nd6_rtr.c */ +extern int nd6_defifindex; +extern int ip6_desync_factor; /* seconds */ +extern u_int32_t ip6_temp_preferred_lifetime; /* seconds */ +extern u_int32_t ip6_temp_valid_lifetime; /* seconds */ +extern int ip6_temp_regen_advance; /* seconds */ + +union nd_opts { + struct nd_opt_hdr *nd_opt_array[9]; /* max = home agent info */ + struct { + struct nd_opt_hdr *zero; + struct nd_opt_hdr *src_lladdr; + struct nd_opt_hdr *tgt_lladdr; + struct nd_opt_prefix_info *pi_beg; /* multiple opts, start */ + struct nd_opt_rd_hdr *rh; + struct nd_opt_mtu *mtu; + struct nd_opt_hdr *six; + struct nd_opt_advint *adv; + struct nd_opt_hai *hai; + struct nd_opt_hdr *search; /* multiple opts */ + struct nd_opt_hdr *last; /* multiple opts */ + int done; + struct nd_opt_prefix_info *pi_end;/* multiple opts, end */ + } nd_opt_each; +}; +#define nd_opts_src_lladdr nd_opt_each.src_lladdr +#define nd_opts_tgt_lladdr nd_opt_each.tgt_lladdr +#define nd_opts_pi nd_opt_each.pi_beg +#define nd_opts_pi_end nd_opt_each.pi_end +#define nd_opts_rh nd_opt_each.rh +#define nd_opts_mtu nd_opt_each.mtu +#define nd_opts_adv nd_opt_each.adv +#define nd_opts_hai nd_opt_each.hai +#define nd_opts_search nd_opt_each.search +#define nd_opts_last nd_opt_each.last +#define nd_opts_done nd_opt_each.done + +/* XXX: need nd6_var.h?? */ +/* nd6.c */ +void nd6_init __P((void)); +void nd6_ifattach __P((struct ifnet *)); +int nd6_is_addr_neighbor __P((struct sockaddr_in6 *, struct ifnet *)); +void nd6_option_init __P((void *, int, union nd_opts *)); +struct nd_opt_hdr *nd6_option __P((union nd_opts *)); +int nd6_options __P((union nd_opts *)); +struct rtentry *nd6_lookup __P((struct in6_addr *, int, struct ifnet *)); +void nd6_setmtu __P((struct ifnet *)); +void nd6_timer __P((void *)); +void nd6_purge __P((struct ifnet *)); +struct llinfo_nd6 *nd6_free __P((struct rtentry *)); +void nd6_nud_hint __P((struct rtentry *, struct in6_addr *, int)); +int nd6_resolve __P((struct ifnet *, struct rtentry *, + struct mbuf *, struct sockaddr *, u_char *)); +void nd6_rtrequest __P((int, struct rtentry *, struct rt_addrinfo *)); +int nd6_ioctl __P((u_long, caddr_t, struct ifnet *)); +struct rtentry *nd6_cache_lladdr __P((struct ifnet *, struct in6_addr *, + char *, int, int, int)); +int nd6_output __P((struct ifnet *, struct ifnet *, struct mbuf *, + struct sockaddr_in6 *, struct rtentry *)); +int nd6_storelladdr __P((struct ifnet *, struct rtentry *, struct mbuf *, + struct sockaddr *, u_char *)); +int nd6_need_cache __P((struct ifnet *)); + +/* nd6_nbr.c */ +void nd6_na_input __P((struct mbuf *, int, int)); +void nd6_na_output __P((struct ifnet *, const struct in6_addr *, + const struct in6_addr *, u_long, int, struct sockaddr *)); +void nd6_ns_input __P((struct mbuf *, int, int)); +void nd6_ns_output __P((struct ifnet *, const struct in6_addr *, + const struct in6_addr *, struct llinfo_nd6 *, int)); +caddr_t nd6_ifptomac __P((struct ifnet *)); +void nd6_dad_start __P((struct ifaddr *, int *)); +void nd6_dad_stop __P((struct ifaddr *)); +void nd6_dad_duplicated __P((struct ifaddr *)); + +/* nd6_rtr.c */ +void nd6_rs_input __P((struct mbuf *, int, int)); +void nd6_ra_input __P((struct mbuf *, int, int)); +void prelist_del __P((struct nd_prefix *)); +void defrouter_addreq __P((struct nd_defrouter *)); +void defrouter_delreq __P((struct nd_defrouter *, int)); +void defrouter_select __P((void)); +void defrtrlist_del __P((struct nd_defrouter *)); +void prelist_remove __P((struct nd_prefix *)); +int prelist_update __P((struct nd_prefix *, struct nd_defrouter *, + struct mbuf *)); +int nd6_prelist_add __P((struct nd_prefix *, struct nd_defrouter *, + struct nd_prefix **)); +int nd6_prefix_onlink __P((struct nd_prefix *)); +int nd6_prefix_offlink __P((struct nd_prefix *)); +void pfxlist_onlink_check __P((void)); +struct nd_defrouter *defrouter_lookup __P((struct in6_addr *, + struct ifnet *)); +struct nd_prefix *nd6_prefix_lookup __P((struct nd_prefix *)); +int in6_init_prefix_ltimes __P((struct nd_prefix *ndpr)); +void rt6_flush __P((struct in6_addr *, struct ifnet *)); +int nd6_setdefaultiface __P((int)); +int in6_tmpifadd __P((const struct in6_ifaddr *, int)); + +#endif /* _KERNEL */ + +#endif /* _NETINET6_ND6_H_ */ diff --git a/sys/netinet6/nd6_nbr.c b/sys/netinet6/nd6_nbr.c new file mode 100644 index 0000000..16bfbb4 --- /dev/null +++ b/sys/netinet6/nd6_nbr.c @@ -0,0 +1,1396 @@ +/* $FreeBSD$ */ +/* $KAME: nd6_nbr.c,v 1.86 2002/01/21 02:33:04 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/queue.h> +#include <sys/callout.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet/icmp6.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#endif + +#include <net/net_osdep.h> + +#define SDL(s) ((struct sockaddr_dl *)s) + +struct dadq; +static struct dadq *nd6_dad_find __P((struct ifaddr *)); +static void nd6_dad_starttimer __P((struct dadq *, int)); +static void nd6_dad_stoptimer __P((struct dadq *)); +static void nd6_dad_timer __P((struct ifaddr *)); +static void nd6_dad_ns_output __P((struct dadq *, struct ifaddr *)); +static void nd6_dad_ns_input __P((struct ifaddr *)); +static void nd6_dad_na_input __P((struct ifaddr *)); + +static int dad_ignore_ns = 0; /* ignore NS in DAD - specwise incorrect*/ +static int dad_maxtry = 15; /* max # of *tries* to transmit DAD packet */ + +/* + * Input a Neighbor Solicitation Message. + * + * Based on RFC 2461 + * Based on RFC 2462 (duplicated address detection) + */ +void +nd6_ns_input(m, off, icmp6len) + struct mbuf *m; + int off, icmp6len; +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct nd_neighbor_solicit *nd_ns; + struct in6_addr saddr6 = ip6->ip6_src; + struct in6_addr daddr6 = ip6->ip6_dst; + struct in6_addr taddr6; + struct in6_addr myaddr6; + char *lladdr = NULL; + struct ifaddr *ifa; + int lladdrlen = 0; + int anycast = 0, proxy = 0, tentative = 0; + int tlladdr; + union nd_opts ndopts; + struct sockaddr_dl *proxydl = NULL; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, icmp6len,); + nd_ns = (struct nd_neighbor_solicit *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(nd_ns, struct nd_neighbor_solicit *, m, off, icmp6len); + if (nd_ns == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + ip6 = mtod(m, struct ip6_hdr *); /* adjust pointer for safety */ + taddr6 = nd_ns->nd_ns_target; + + if (ip6->ip6_hlim != 255) { + nd6log((LOG_ERR, + "nd6_ns_input: invalid hlim (%d) from %s to %s on %s\n", + ip6->ip6_hlim, ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), if_name(ifp))); + goto bad; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) { + /* dst has to be solicited node multicast address. */ + if (daddr6.s6_addr16[0] == IPV6_ADDR_INT16_MLL + /* don't check ifindex portion */ + && daddr6.s6_addr32[1] == 0 + && daddr6.s6_addr32[2] == IPV6_ADDR_INT32_ONE + && daddr6.s6_addr8[12] == 0xff) { + ; /* good */ + } else { + nd6log((LOG_INFO, "nd6_ns_input: bad DAD packet " + "(wrong ip6 dst)\n")); + goto bad; + } + } + + if (IN6_IS_ADDR_MULTICAST(&taddr6)) { + nd6log((LOG_INFO, "nd6_ns_input: bad NS target (multicast)\n")); + goto bad; + } + + if (IN6_IS_SCOPE_LINKLOCAL(&taddr6)) + taddr6.s6_addr16[1] = htons(ifp->if_index); + + icmp6len -= sizeof(*nd_ns); + nd6_option_init(nd_ns + 1, icmp6len, &ndopts); + if (nd6_options(&ndopts) < 0) { + nd6log((LOG_INFO, + "nd6_ns_input: invalid ND option, ignored\n")); + /* nd6_options have incremented stats */ + goto freeit; + } + + if (ndopts.nd_opts_src_lladdr) { + lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1); + lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src) && lladdr) { + nd6log((LOG_INFO, "nd6_ns_input: bad DAD packet " + "(link-layer address option)\n")); + goto bad; + } + + /* + * Attaching target link-layer address to the NA? + * (RFC 2461 7.2.4) + * + * NS IP dst is unicast/anycast MUST NOT add + * NS IP dst is solicited-node multicast MUST add + * + * In implementation, we add target link-layer address by default. + * We do not add one in MUST NOT cases. + */ +#if 0 /* too much! */ + ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, &daddr6); + if (ifa && (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_ANYCAST)) + tlladdr = 0; + else +#endif + if (!IN6_IS_ADDR_MULTICAST(&daddr6)) + tlladdr = 0; + else + tlladdr = 1; + + /* + * Target address (taddr6) must be either: + * (1) Valid unicast/anycast address for my receiving interface, + * (2) Unicast address for which I'm offering proxy service, or + * (3) "tentative" address on which DAD is being performed. + */ + /* (1) and (3) check. */ + ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, &taddr6); + + /* (2) check. */ + if (!ifa) { + struct rtentry *rt; + struct sockaddr_in6 tsin6; + + bzero(&tsin6, sizeof tsin6); + tsin6.sin6_len = sizeof(struct sockaddr_in6); + tsin6.sin6_family = AF_INET6; + tsin6.sin6_addr = taddr6; + + rt = rtalloc1((struct sockaddr *)&tsin6, 0, 0); + if (rt && (rt->rt_flags & RTF_ANNOUNCE) != 0 && + rt->rt_gateway->sa_family == AF_LINK) { + /* + * proxy NDP for single entry + */ + ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, + IN6_IFF_NOTREADY|IN6_IFF_ANYCAST); + if (ifa) { + proxy = 1; + proxydl = SDL(rt->rt_gateway); + } + } + if (rt) + rtfree(rt); + } + if (!ifa) { + /* + * We've got an NS packet, and we don't have that adddress + * assigned for us. We MUST silently ignore it. + * See RFC2461 7.2.3. + */ + goto freeit; + } + myaddr6 = *IFA_IN6(ifa); + anycast = ((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_ANYCAST; + tentative = ((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_TENTATIVE; + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_DUPLICATED) + goto freeit; + + if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) { + nd6log((LOG_INFO, + "nd6_ns_input: lladdrlen mismatch for %s " + "(if %d, NS packet %d)\n", + ip6_sprintf(&taddr6), ifp->if_addrlen, lladdrlen - 2)); + goto bad; + } + + if (IN6_ARE_ADDR_EQUAL(&myaddr6, &saddr6)) { + nd6log((LOG_INFO, + "nd6_ns_input: duplicate IP6 address %s\n", + ip6_sprintf(&saddr6))); + goto freeit; + } + + /* + * We have neighbor solicitation packet, with target address equals to + * one of my tentative address. + * + * src addr how to process? + * --- --- + * multicast of course, invalid (rejected in ip6_input) + * unicast somebody is doing address resolution -> ignore + * unspec dup address detection + * + * The processing is defined in RFC 2462. + */ + if (tentative) { + /* + * If source address is unspecified address, it is for + * duplicated address detection. + * + * If not, the packet is for addess resolution; + * silently ignore it. + */ + if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) + nd6_dad_ns_input(ifa); + + goto freeit; + } + + /* + * If the source address is unspecified address, entries must not + * be created or updated. + * It looks that sender is performing DAD. Output NA toward + * all-node multicast address, to tell the sender that I'm using + * the address. + * S bit ("solicited") must be zero. + */ + if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) { + saddr6 = in6addr_linklocal_allnodes; + saddr6.s6_addr16[1] = htons(ifp->if_index); + nd6_na_output(ifp, &saddr6, &taddr6, + ((anycast || proxy || !tlladdr) + ? 0 : ND_NA_FLAG_OVERRIDE) + | (ip6_forwarding ? ND_NA_FLAG_ROUTER : 0), + tlladdr, (struct sockaddr *)proxydl); + goto freeit; + } + + nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_NEIGHBOR_SOLICIT, 0); + + nd6_na_output(ifp, &saddr6, &taddr6, + ((anycast || proxy || !tlladdr) ? 0 : ND_NA_FLAG_OVERRIDE) + | (ip6_forwarding ? ND_NA_FLAG_ROUTER : 0) + | ND_NA_FLAG_SOLICITED, + tlladdr, (struct sockaddr *)proxydl); + freeit: + m_freem(m); + return; + + bad: + nd6log((LOG_ERR, "nd6_ns_input: src=%s\n", ip6_sprintf(&saddr6))); + nd6log((LOG_ERR, "nd6_ns_input: dst=%s\n", ip6_sprintf(&daddr6))); + nd6log((LOG_ERR, "nd6_ns_input: tgt=%s\n", ip6_sprintf(&taddr6))); + icmp6stat.icp6s_badns++; + m_freem(m); +} + +/* + * Output a Neighbor Solicitation Message. Caller specifies: + * - ICMP6 header source IP6 address + * - ND6 header target IP6 address + * - ND6 header source datalink address + * + * Based on RFC 2461 + * Based on RFC 2462 (duplicated address detection) + */ +void +nd6_ns_output(ifp, daddr6, taddr6, ln, dad) + struct ifnet *ifp; + const struct in6_addr *daddr6, *taddr6; + struct llinfo_nd6 *ln; /* for source address determination */ + int dad; /* duplicated address detection */ +{ + struct mbuf *m; + struct ip6_hdr *ip6; + struct nd_neighbor_solicit *nd_ns; + struct in6_ifaddr *ia = NULL; + struct ip6_moptions im6o; + int icmp6len; + int maxlen; + caddr_t mac; + struct ifnet *outif = NULL; + + if (IN6_IS_ADDR_MULTICAST(taddr6)) + return; + + /* estimate the size of message */ + maxlen = sizeof(*ip6) + sizeof(*nd_ns); + maxlen += (sizeof(struct nd_opt_hdr) + ifp->if_addrlen + 7) & ~7; + if (max_linkhdr + maxlen >= MCLBYTES) { +#ifdef DIAGNOSTIC + printf("nd6_ns_output: max_linkhdr + maxlen >= MCLBYTES " + "(%d + %d > %d)\n", max_linkhdr, maxlen, MCLBYTES); +#endif + return; + } + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m && max_linkhdr + maxlen >= MHLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + m = NULL; + } + } + if (m == NULL) + return; + m->m_pkthdr.rcvif = NULL; + + if (daddr6 == NULL || IN6_IS_ADDR_MULTICAST(daddr6)) { + m->m_flags |= M_MCAST; + im6o.im6o_multicast_ifp = ifp; + im6o.im6o_multicast_hlim = 255; + im6o.im6o_multicast_loop = 0; + } + + icmp6len = sizeof(*nd_ns); + m->m_pkthdr.len = m->m_len = sizeof(*ip6) + icmp6len; + m->m_data += max_linkhdr; /* or MH_ALIGN() equivalent? */ + + /* fill neighbor solicitation packet */ + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + /* ip6->ip6_plen will be set later */ + ip6->ip6_nxt = IPPROTO_ICMPV6; + ip6->ip6_hlim = 255; + if (daddr6) + ip6->ip6_dst = *daddr6; + else { + ip6->ip6_dst.s6_addr16[0] = IPV6_ADDR_INT16_MLL; + ip6->ip6_dst.s6_addr16[1] = htons(ifp->if_index); + ip6->ip6_dst.s6_addr32[1] = 0; + ip6->ip6_dst.s6_addr32[2] = IPV6_ADDR_INT32_ONE; + ip6->ip6_dst.s6_addr32[3] = taddr6->s6_addr32[3]; + ip6->ip6_dst.s6_addr8[12] = 0xff; + } + if (!dad) { +#if 0 /* KAME way, exact address scope match */ + /* + * Select a source whose scope is the same as that of the dest. + * Typically, the dest is link-local solicitation multicast + * (i.e. neighbor discovery) or link-local/global unicast + * (i.e. neighbor un-reachability detection). + */ + ia = in6_ifawithifp(ifp, &ip6->ip6_dst); + if (ia == NULL) { + m_freem(m); + return; + } + ip6->ip6_src = ia->ia_addr.sin6_addr; +#else /* spec-wise correct */ + /* + * RFC2461 7.2.2: + * "If the source address of the packet prompting the + * solicitation is the same as one of the addresses assigned + * to the outgoing interface, that address SHOULD be placed + * in the IP Source Address of the outgoing solicitation. + * Otherwise, any one of the addresses assigned to the + * interface should be used." + * + * We use the source address for the prompting packet + * (saddr6), if: + * - saddr6 is given from the caller (by giving "ln"), and + * - saddr6 belongs to the outgoing interface. + * Otherwise, we perform a scope-wise match. + */ + struct ip6_hdr *hip6; /* hold ip6 */ + struct in6_addr *saddr6; + + if (ln && ln->ln_hold) { + hip6 = mtod(ln->ln_hold, struct ip6_hdr *); + /* XXX pullup? */ + if (sizeof(*hip6) < ln->ln_hold->m_len) + saddr6 = &hip6->ip6_src; + else + saddr6 = NULL; + } else + saddr6 = NULL; + if (saddr6 && in6ifa_ifpwithaddr(ifp, saddr6)) + bcopy(saddr6, &ip6->ip6_src, sizeof(*saddr6)); + else { + ia = in6_ifawithifp(ifp, &ip6->ip6_dst); + if (ia == NULL) { + m_freem(m); + return; + } + ip6->ip6_src = ia->ia_addr.sin6_addr; + } +#endif + } else { + /* + * Source address for DAD packet must always be IPv6 + * unspecified address. (0::0) + */ + bzero(&ip6->ip6_src, sizeof(ip6->ip6_src)); + } + nd_ns = (struct nd_neighbor_solicit *)(ip6 + 1); + nd_ns->nd_ns_type = ND_NEIGHBOR_SOLICIT; + nd_ns->nd_ns_code = 0; + nd_ns->nd_ns_reserved = 0; + nd_ns->nd_ns_target = *taddr6; + + if (IN6_IS_SCOPE_LINKLOCAL(&nd_ns->nd_ns_target)) + nd_ns->nd_ns_target.s6_addr16[1] = 0; + + /* + * Add source link-layer address option. + * + * spec implementation + * --- --- + * DAD packet MUST NOT do not add the option + * there's no link layer address: + * impossible do not add the option + * there's link layer address: + * Multicast NS MUST add one add the option + * Unicast NS SHOULD add one add the option + */ + if (!dad && (mac = nd6_ifptomac(ifp))) { + int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen; + struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_ns + 1); + /* 8 byte alignments... */ + optlen = (optlen + 7) & ~7; + + m->m_pkthdr.len += optlen; + m->m_len += optlen; + icmp6len += optlen; + bzero((caddr_t)nd_opt, optlen); + nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR; + nd_opt->nd_opt_len = optlen >> 3; + bcopy(mac, (caddr_t)(nd_opt + 1), ifp->if_addrlen); + } + + ip6->ip6_plen = htons((u_short)icmp6len); + nd_ns->nd_ns_cksum = 0; + nd_ns->nd_ns_cksum + = in6_cksum(m, IPPROTO_ICMPV6, sizeof(*ip6), icmp6len); + + ip6_output(m, NULL, NULL, dad ? IPV6_DADOUTPUT : 0, &im6o, &outif, NULL); + if (outif) { + icmp6_ifstat_inc(outif, ifs6_out_msg); + icmp6_ifstat_inc(outif, ifs6_out_neighborsolicit); + } + icmp6stat.icp6s_outhist[ND_NEIGHBOR_SOLICIT]++; +} + +/* + * Neighbor advertisement input handling. + * + * Based on RFC 2461 + * Based on RFC 2462 (duplicated address detection) + * + * the following items are not implemented yet: + * - proxy advertisement delay rule (RFC2461 7.2.8, last paragraph, SHOULD) + * - anycast advertisement delay rule (RFC2461 7.2.7, SHOULD) + */ +void +nd6_na_input(m, off, icmp6len) + struct mbuf *m; + int off, icmp6len; +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct nd_neighbor_advert *nd_na; +#if 0 + struct in6_addr saddr6 = ip6->ip6_src; +#endif + struct in6_addr daddr6 = ip6->ip6_dst; + struct in6_addr taddr6; + int flags; + int is_router; + int is_solicited; + int is_override; + char *lladdr = NULL; + int lladdrlen = 0; + struct ifaddr *ifa; + struct llinfo_nd6 *ln; + struct rtentry *rt; + struct sockaddr_dl *sdl; + union nd_opts ndopts; + + if (ip6->ip6_hlim != 255) { + nd6log((LOG_ERR, + "nd6_na_input: invalid hlim (%d) from %s to %s on %s\n", + ip6->ip6_hlim, ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), if_name(ifp))); + goto bad; + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, icmp6len,); + nd_na = (struct nd_neighbor_advert *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(nd_na, struct nd_neighbor_advert *, m, off, icmp6len); + if (nd_na == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + taddr6 = nd_na->nd_na_target; + flags = nd_na->nd_na_flags_reserved; + is_router = ((flags & ND_NA_FLAG_ROUTER) != 0); + is_solicited = ((flags & ND_NA_FLAG_SOLICITED) != 0); + is_override = ((flags & ND_NA_FLAG_OVERRIDE) != 0); + + if (IN6_IS_SCOPE_LINKLOCAL(&taddr6)) + taddr6.s6_addr16[1] = htons(ifp->if_index); + + if (IN6_IS_ADDR_MULTICAST(&taddr6)) { + nd6log((LOG_ERR, + "nd6_na_input: invalid target address %s\n", + ip6_sprintf(&taddr6))); + goto bad; + } + if (IN6_IS_ADDR_MULTICAST(&daddr6)) + if (is_solicited) { + nd6log((LOG_ERR, + "nd6_na_input: a solicited adv is multicasted\n")); + goto bad; + } + + icmp6len -= sizeof(*nd_na); + nd6_option_init(nd_na + 1, icmp6len, &ndopts); + if (nd6_options(&ndopts) < 0) { + nd6log((LOG_INFO, + "nd6_na_input: invalid ND option, ignored\n")); + /* nd6_options have incremented stats */ + goto freeit; + } + + if (ndopts.nd_opts_tgt_lladdr) { + lladdr = (char *)(ndopts.nd_opts_tgt_lladdr + 1); + lladdrlen = ndopts.nd_opts_tgt_lladdr->nd_opt_len << 3; + } + + ifa = (struct ifaddr *)in6ifa_ifpwithaddr(ifp, &taddr6); + + /* + * Target address matches one of my interface address. + * + * If my address is tentative, this means that there's somebody + * already using the same address as mine. This indicates DAD failure. + * This is defined in RFC 2462. + * + * Otherwise, process as defined in RFC 2461. + */ + if (ifa + && (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_TENTATIVE)) { + nd6_dad_na_input(ifa); + goto freeit; + } + + /* Just for safety, maybe unnecessary. */ + if (ifa) { + log(LOG_ERR, + "nd6_na_input: duplicate IP6 address %s\n", + ip6_sprintf(&taddr6)); + goto freeit; + } + + if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) { + nd6log((LOG_INFO, + "nd6_na_input: lladdrlen mismatch for %s " + "(if %d, NA packet %d)\n", + ip6_sprintf(&taddr6), ifp->if_addrlen, lladdrlen - 2)); + goto bad; + } + + /* + * If no neighbor cache entry is found, NA SHOULD silently be discarded. + */ + rt = nd6_lookup(&taddr6, 0, ifp); + if ((rt == NULL) || + ((ln = (struct llinfo_nd6 *)rt->rt_llinfo) == NULL) || + ((sdl = SDL(rt->rt_gateway)) == NULL)) + goto freeit; + + if (ln->ln_state == ND6_LLINFO_INCOMPLETE) { + /* + * If the link-layer has address, and no lladdr option came, + * discard the packet. + */ + if (ifp->if_addrlen && !lladdr) + goto freeit; + + /* + * Record link-layer address, and update the state. + */ + sdl->sdl_alen = ifp->if_addrlen; + bcopy(lladdr, LLADDR(sdl), ifp->if_addrlen); + if (is_solicited) { + ln->ln_state = ND6_LLINFO_REACHABLE; + ln->ln_byhint = 0; + if (ln->ln_expire) + ln->ln_expire = time_second + + nd_ifinfo[rt->rt_ifp->if_index].reachable; + } else { + ln->ln_state = ND6_LLINFO_STALE; + ln->ln_expire = time_second + nd6_gctimer; + } + if ((ln->ln_router = is_router) != 0) { + /* + * This means a router's state has changed from + * non-reachable to probably reachable, and might + * affect the status of associated prefixes.. + */ + pfxlist_onlink_check(); + } + } else { + int llchange; + + /* + * Check if the link-layer address has changed or not. + */ + if (!lladdr) + llchange = 0; + else { + if (sdl->sdl_alen) { + if (bcmp(lladdr, LLADDR(sdl), ifp->if_addrlen)) + llchange = 1; + else + llchange = 0; + } else + llchange = 1; + } + + /* + * This is VERY complex. Look at it with care. + * + * override solicit lladdr llchange action + * (L: record lladdr) + * + * 0 0 n -- (2c) + * 0 0 y n (2b) L + * 0 0 y y (1) REACHABLE->STALE + * 0 1 n -- (2c) *->REACHABLE + * 0 1 y n (2b) L *->REACHABLE + * 0 1 y y (1) REACHABLE->STALE + * 1 0 n -- (2a) + * 1 0 y n (2a) L + * 1 0 y y (2a) L *->STALE + * 1 1 n -- (2a) *->REACHABLE + * 1 1 y n (2a) L *->REACHABLE + * 1 1 y y (2a) L *->REACHABLE + */ + if (!is_override && (lladdr && llchange)) { /* (1) */ + /* + * If state is REACHABLE, make it STALE. + * no other updates should be done. + */ + if (ln->ln_state == ND6_LLINFO_REACHABLE) { + ln->ln_state = ND6_LLINFO_STALE; + ln->ln_expire = time_second + nd6_gctimer; + } + goto freeit; + } else if (is_override /* (2a) */ + || (!is_override && (lladdr && !llchange)) /* (2b) */ + || !lladdr) { /* (2c) */ + /* + * Update link-local address, if any. + */ + if (lladdr) { + sdl->sdl_alen = ifp->if_addrlen; + bcopy(lladdr, LLADDR(sdl), ifp->if_addrlen); + } + + /* + * If solicited, make the state REACHABLE. + * If not solicited and the link-layer address was + * changed, make it STALE. + */ + if (is_solicited) { + ln->ln_state = ND6_LLINFO_REACHABLE; + ln->ln_byhint = 0; + if (ln->ln_expire) { + ln->ln_expire = time_second + + nd_ifinfo[ifp->if_index].reachable; + } + } else { + if (lladdr && llchange) { + ln->ln_state = ND6_LLINFO_STALE; + ln->ln_expire = time_second + nd6_gctimer; + } + } + } + + if (ln->ln_router && !is_router) { + /* + * The peer dropped the router flag. + * Remove the sender from the Default Router List and + * update the Destination Cache entries. + */ + struct nd_defrouter *dr; + struct in6_addr *in6; + int s; + + in6 = &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr; + + /* + * Lock to protect the default router list. + * XXX: this might be unnecessary, since this function + * is only called under the network software interrupt + * context. However, we keep it just for safety. + */ + s = splnet(); + dr = defrouter_lookup(in6, rt->rt_ifp); + if (dr) + defrtrlist_del(dr); + else if (!ip6_forwarding && ip6_accept_rtadv) { + /* + * Even if the neighbor is not in the default + * router list, the neighbor may be used + * as a next hop for some destinations + * (e.g. redirect case). So we must + * call rt6_flush explicitly. + */ + rt6_flush(&ip6->ip6_src, rt->rt_ifp); + } + splx(s); + } + ln->ln_router = is_router; + } + rt->rt_flags &= ~RTF_REJECT; + ln->ln_asked = 0; + if (ln->ln_hold) { + /* + * we assume ifp is not a loopback here, so just set the 2nd + * argument as the 1st one. + */ + nd6_output(ifp, ifp, ln->ln_hold, + (struct sockaddr_in6 *)rt_key(rt), rt); + ln->ln_hold = 0; + } + + freeit: + m_freem(m); + return; + + bad: + icmp6stat.icp6s_badna++; + m_freem(m); +} + +/* + * Neighbor advertisement output handling. + * + * Based on RFC 2461 + * + * the following items are not implemented yet: + * - proxy advertisement delay rule (RFC2461 7.2.8, last paragraph, SHOULD) + * - anycast advertisement delay rule (RFC2461 7.2.7, SHOULD) + */ +void +nd6_na_output(ifp, daddr6, taddr6, flags, tlladdr, sdl0) + struct ifnet *ifp; + const struct in6_addr *daddr6, *taddr6; + u_long flags; + int tlladdr; /* 1 if include target link-layer address */ + struct sockaddr *sdl0; /* sockaddr_dl (= proxy NA) or NULL */ +{ + struct mbuf *m; + struct ip6_hdr *ip6; + struct nd_neighbor_advert *nd_na; + struct in6_ifaddr *ia = NULL; + struct ip6_moptions im6o; + int icmp6len; + int maxlen; + caddr_t mac = NULL; + struct ifnet *outif = NULL; + + /* estimate the size of message */ + maxlen = sizeof(*ip6) + sizeof(*nd_na); + maxlen += (sizeof(struct nd_opt_hdr) + ifp->if_addrlen + 7) & ~7; + if (max_linkhdr + maxlen >= MCLBYTES) { +#ifdef DIAGNOSTIC + printf("nd6_na_output: max_linkhdr + maxlen >= MCLBYTES " + "(%d + %d > %d)\n", max_linkhdr, maxlen, MCLBYTES); +#endif + return; + } + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m && max_linkhdr + maxlen >= MHLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + m = NULL; + } + } + if (m == NULL) + return; + m->m_pkthdr.rcvif = NULL; + + if (IN6_IS_ADDR_MULTICAST(daddr6)) { + m->m_flags |= M_MCAST; + im6o.im6o_multicast_ifp = ifp; + im6o.im6o_multicast_hlim = 255; + im6o.im6o_multicast_loop = 0; + } + + icmp6len = sizeof(*nd_na); + m->m_pkthdr.len = m->m_len = sizeof(struct ip6_hdr) + icmp6len; + m->m_data += max_linkhdr; /* or MH_ALIGN() equivalent? */ + + /* fill neighbor advertisement packet */ + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = 0; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; + ip6->ip6_nxt = IPPROTO_ICMPV6; + ip6->ip6_hlim = 255; + if (IN6_IS_ADDR_UNSPECIFIED(daddr6)) { + /* reply to DAD */ + ip6->ip6_dst.s6_addr16[0] = IPV6_ADDR_INT16_MLL; + ip6->ip6_dst.s6_addr16[1] = htons(ifp->if_index); + ip6->ip6_dst.s6_addr32[1] = 0; + ip6->ip6_dst.s6_addr32[2] = 0; + ip6->ip6_dst.s6_addr32[3] = IPV6_ADDR_INT32_ONE; + flags &= ~ND_NA_FLAG_SOLICITED; + } else + ip6->ip6_dst = *daddr6; + + /* + * Select a source whose scope is the same as that of the dest. + */ + ia = in6_ifawithifp(ifp, &ip6->ip6_dst); + if (ia == NULL) { + m_freem(m); + return; + } + ip6->ip6_src = ia->ia_addr.sin6_addr; + nd_na = (struct nd_neighbor_advert *)(ip6 + 1); + nd_na->nd_na_type = ND_NEIGHBOR_ADVERT; + nd_na->nd_na_code = 0; + nd_na->nd_na_target = *taddr6; + if (IN6_IS_SCOPE_LINKLOCAL(&nd_na->nd_na_target)) + nd_na->nd_na_target.s6_addr16[1] = 0; + + /* + * "tlladdr" indicates NS's condition for adding tlladdr or not. + * see nd6_ns_input() for details. + * Basically, if NS packet is sent to unicast/anycast addr, + * target lladdr option SHOULD NOT be included. + */ + if (tlladdr) { + /* + * sdl0 != NULL indicates proxy NA. If we do proxy, use + * lladdr in sdl0. If we are not proxying (sending NA for + * my address) use lladdr configured for the interface. + */ + if (sdl0 == NULL) + mac = nd6_ifptomac(ifp); + else if (sdl0->sa_family == AF_LINK) { + struct sockaddr_dl *sdl; + sdl = (struct sockaddr_dl *)sdl0; + if (sdl->sdl_alen == ifp->if_addrlen) + mac = LLADDR(sdl); + } + } + if (tlladdr && mac) { + int optlen = sizeof(struct nd_opt_hdr) + ifp->if_addrlen; + struct nd_opt_hdr *nd_opt = (struct nd_opt_hdr *)(nd_na + 1); + + /* roundup to 8 bytes alignment! */ + optlen = (optlen + 7) & ~7; + + m->m_pkthdr.len += optlen; + m->m_len += optlen; + icmp6len += optlen; + bzero((caddr_t)nd_opt, optlen); + nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR; + nd_opt->nd_opt_len = optlen >> 3; + bcopy(mac, (caddr_t)(nd_opt + 1), ifp->if_addrlen); + } else + flags &= ~ND_NA_FLAG_OVERRIDE; + + ip6->ip6_plen = htons((u_short)icmp6len); + nd_na->nd_na_flags_reserved = flags; + nd_na->nd_na_cksum = 0; + nd_na->nd_na_cksum = + in6_cksum(m, IPPROTO_ICMPV6, sizeof(struct ip6_hdr), icmp6len); + + ip6_output(m, NULL, NULL, 0, &im6o, &outif, NULL); + if (outif) { + icmp6_ifstat_inc(outif, ifs6_out_msg); + icmp6_ifstat_inc(outif, ifs6_out_neighboradvert); + } + icmp6stat.icp6s_outhist[ND_NEIGHBOR_ADVERT]++; +} + +caddr_t +nd6_ifptomac(ifp) + struct ifnet *ifp; +{ + switch (ifp->if_type) { + case IFT_ARCNET: + case IFT_ETHER: + case IFT_FDDI: + case IFT_IEEE1394: +#ifdef IFT_L2VLAN + case IFT_L2VLAN: +#endif +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: +#endif + return ((caddr_t)(ifp + 1)); + break; + default: + return NULL; + } +} + +TAILQ_HEAD(dadq_head, dadq); +struct dadq { + TAILQ_ENTRY(dadq) dad_list; + struct ifaddr *dad_ifa; + int dad_count; /* max NS to send */ + int dad_ns_tcount; /* # of trials to send NS */ + int dad_ns_ocount; /* NS sent so far */ + int dad_ns_icount; + int dad_na_icount; + struct callout dad_timer_ch; +}; + +static struct dadq_head dadq; +static int dad_init = 0; + +static struct dadq * +nd6_dad_find(ifa) + struct ifaddr *ifa; +{ + struct dadq *dp; + + for (dp = dadq.tqh_first; dp; dp = dp->dad_list.tqe_next) { + if (dp->dad_ifa == ifa) + return dp; + } + return NULL; +} + +static void +nd6_dad_starttimer(dp, ticks) + struct dadq *dp; + int ticks; +{ + + callout_reset(&dp->dad_timer_ch, ticks, + (void (*) __P((void *)))nd6_dad_timer, (void *)dp->dad_ifa); +} + +static void +nd6_dad_stoptimer(dp) + struct dadq *dp; +{ + + callout_stop(&dp->dad_timer_ch); +} + +/* + * Start Duplicated Address Detection (DAD) for specified interface address. + */ +void +nd6_dad_start(ifa, tick) + struct ifaddr *ifa; + int *tick; /* minimum delay ticks for IFF_UP event */ +{ + struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; + struct dadq *dp; + + if (!dad_init) { + TAILQ_INIT(&dadq); + dad_init++; + } + + /* + * If we don't need DAD, don't do it. + * There are several cases: + * - DAD is disabled (ip6_dad_count == 0) + * - the interface address is anycast + */ + if (!(ia->ia6_flags & IN6_IFF_TENTATIVE)) { + log(LOG_DEBUG, + "nd6_dad_start: called with non-tentative address " + "%s(%s)\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), + ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???"); + return; + } + if (ia->ia6_flags & IN6_IFF_ANYCAST) { + ia->ia6_flags &= ~IN6_IFF_TENTATIVE; + return; + } + if (!ip6_dad_count) { + ia->ia6_flags &= ~IN6_IFF_TENTATIVE; + return; + } + if (!ifa->ifa_ifp) + panic("nd6_dad_start: ifa->ifa_ifp == NULL"); + if (!(ifa->ifa_ifp->if_flags & IFF_UP)) + return; + if (nd6_dad_find(ifa) != NULL) { + /* DAD already in progress */ + return; + } + + dp = malloc(sizeof(*dp), M_IP6NDP, M_NOWAIT); + if (dp == NULL) { + log(LOG_ERR, "nd6_dad_start: memory allocation failed for " + "%s(%s)\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), + ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???"); + return; + } + bzero(dp, sizeof(*dp)); + callout_init(&dp->dad_timer_ch, 0); + TAILQ_INSERT_TAIL(&dadq, (struct dadq *)dp, dad_list); + + nd6log((LOG_DEBUG, "%s: starting DAD for %s\n", if_name(ifa->ifa_ifp), + ip6_sprintf(&ia->ia_addr.sin6_addr))); + + /* + * Send NS packet for DAD, ip6_dad_count times. + * Note that we must delay the first transmission, if this is the + * first packet to be sent from the interface after interface + * (re)initialization. + */ + dp->dad_ifa = ifa; + IFAREF(ifa); /* just for safety */ + dp->dad_count = ip6_dad_count; + dp->dad_ns_icount = dp->dad_na_icount = 0; + dp->dad_ns_ocount = dp->dad_ns_tcount = 0; + if (tick == NULL) { + nd6_dad_ns_output(dp, ifa); + nd6_dad_starttimer(dp, + nd_ifinfo[ifa->ifa_ifp->if_index].retrans * hz / 1000); + } else { + int ntick; + + if (*tick == 0) + ntick = random() % (MAX_RTR_SOLICITATION_DELAY * hz); + else + ntick = *tick + random() % (hz / 2); + *tick = ntick; + nd6_dad_starttimer(dp, ntick); + } +} + +/* + * terminate DAD unconditionally. used for address removals. + */ +void +nd6_dad_stop(ifa) + struct ifaddr *ifa; +{ + struct dadq *dp; + + if (!dad_init) + return; + dp = nd6_dad_find(ifa); + if (!dp) { + /* DAD wasn't started yet */ + return; + } + + nd6_dad_stoptimer(dp); + + TAILQ_REMOVE(&dadq, (struct dadq *)dp, dad_list); + free(dp, M_IP6NDP); + dp = NULL; + IFAFREE(ifa); +} + +static void +nd6_dad_timer(ifa) + struct ifaddr *ifa; +{ + int s; + struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; + struct dadq *dp; + + s = splnet(); /* XXX */ + + /* Sanity check */ + if (ia == NULL) { + log(LOG_ERR, "nd6_dad_timer: called with null parameter\n"); + goto done; + } + dp = nd6_dad_find(ifa); + if (dp == NULL) { + log(LOG_ERR, "nd6_dad_timer: DAD structure not found\n"); + goto done; + } + if (ia->ia6_flags & IN6_IFF_DUPLICATED) { + log(LOG_ERR, "nd6_dad_timer: called with duplicated address " + "%s(%s)\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), + ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???"); + goto done; + } + if ((ia->ia6_flags & IN6_IFF_TENTATIVE) == 0) { + log(LOG_ERR, "nd6_dad_timer: called with non-tentative address " + "%s(%s)\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), + ifa->ifa_ifp ? if_name(ifa->ifa_ifp) : "???"); + goto done; + } + + /* timeouted with IFF_{RUNNING,UP} check */ + if (dp->dad_ns_tcount > dad_maxtry) { + nd6log((LOG_INFO, "%s: could not run DAD, driver problem?\n", + if_name(ifa->ifa_ifp))); + + TAILQ_REMOVE(&dadq, (struct dadq *)dp, dad_list); + free(dp, M_IP6NDP); + dp = NULL; + IFAFREE(ifa); + goto done; + } + + /* Need more checks? */ + if (dp->dad_ns_ocount < dp->dad_count) { + /* + * We have more NS to go. Send NS packet for DAD. + */ + nd6_dad_ns_output(dp, ifa); + nd6_dad_starttimer(dp, + nd_ifinfo[ifa->ifa_ifp->if_index].retrans * hz / 1000); + } else { + /* + * We have transmitted sufficient number of DAD packets. + * See what we've got. + */ + int duplicate; + + duplicate = 0; + + if (dp->dad_na_icount) { + /* + * the check is in nd6_dad_na_input(), + * but just in case + */ + duplicate++; + } + + if (dp->dad_ns_icount) { +#if 0 /* heuristics */ + /* + * if + * - we have sent many(?) DAD NS, and + * - the number of NS we sent equals to the + * number of NS we've got, and + * - we've got no NA + * we may have a faulty network card/driver which + * loops back multicasts to myself. + */ + if (3 < dp->dad_count + && dp->dad_ns_icount == dp->dad_count + && dp->dad_na_icount == 0) { + log(LOG_INFO, "DAD questionable for %s(%s): " + "network card loops back multicast?\n", + ip6_sprintf(&ia->ia_addr.sin6_addr), + if_name(ifa->ifa_ifp)); + /* XXX consider it a duplicate or not? */ + /* duplicate++; */ + } else { + /* We've seen NS, means DAD has failed. */ + duplicate++; + } +#else + /* We've seen NS, means DAD has failed. */ + duplicate++; +#endif + } + + if (duplicate) { + /* (*dp) will be freed in nd6_dad_duplicated() */ + dp = NULL; + nd6_dad_duplicated(ifa); + } else { + /* + * We are done with DAD. No NA came, no NS came. + * duplicated address found. + */ + ia->ia6_flags &= ~IN6_IFF_TENTATIVE; + + nd6log((LOG_DEBUG, + "%s: DAD complete for %s - no duplicates found\n", + if_name(ifa->ifa_ifp), + ip6_sprintf(&ia->ia_addr.sin6_addr))); + + TAILQ_REMOVE(&dadq, (struct dadq *)dp, dad_list); + free(dp, M_IP6NDP); + dp = NULL; + IFAFREE(ifa); + } + } + +done: + splx(s); +} + +void +nd6_dad_duplicated(ifa) + struct ifaddr *ifa; +{ + struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; + struct dadq *dp; + + dp = nd6_dad_find(ifa); + if (dp == NULL) { + log(LOG_ERR, "nd6_dad_duplicated: DAD structure not found\n"); + return; + } + + log(LOG_ERR, "%s: DAD detected duplicate IPv6 address %s: " + "NS in/out=%d/%d, NA in=%d\n", + if_name(ifa->ifa_ifp), ip6_sprintf(&ia->ia_addr.sin6_addr), + dp->dad_ns_icount, dp->dad_ns_ocount, dp->dad_na_icount); + + ia->ia6_flags &= ~IN6_IFF_TENTATIVE; + ia->ia6_flags |= IN6_IFF_DUPLICATED; + + /* We are done with DAD, with duplicated address found. (failure) */ + nd6_dad_stoptimer(dp); + + log(LOG_ERR, "%s: DAD complete for %s - duplicate found\n", + if_name(ifa->ifa_ifp), ip6_sprintf(&ia->ia_addr.sin6_addr)); + log(LOG_ERR, "%s: manual intervention required\n", + if_name(ifa->ifa_ifp)); + + TAILQ_REMOVE(&dadq, (struct dadq *)dp, dad_list); + free(dp, M_IP6NDP); + dp = NULL; + IFAFREE(ifa); +} + +static void +nd6_dad_ns_output(dp, ifa) + struct dadq *dp; + struct ifaddr *ifa; +{ + struct in6_ifaddr *ia = (struct in6_ifaddr *)ifa; + struct ifnet *ifp = ifa->ifa_ifp; + + dp->dad_ns_tcount++; + if ((ifp->if_flags & IFF_UP) == 0) { +#if 0 + printf("%s: interface down?\n", if_name(ifp)); +#endif + return; + } + if ((ifp->if_flags & IFF_RUNNING) == 0) { +#if 0 + printf("%s: interface not running?\n", if_name(ifp)); +#endif + return; + } + + dp->dad_ns_ocount++; + nd6_ns_output(ifp, NULL, &ia->ia_addr.sin6_addr, NULL, 1); +} + +static void +nd6_dad_ns_input(ifa) + struct ifaddr *ifa; +{ + struct in6_ifaddr *ia; + struct ifnet *ifp; + const struct in6_addr *taddr6; + struct dadq *dp; + int duplicate; + + if (!ifa) + panic("ifa == NULL in nd6_dad_ns_input"); + + ia = (struct in6_ifaddr *)ifa; + ifp = ifa->ifa_ifp; + taddr6 = &ia->ia_addr.sin6_addr; + duplicate = 0; + dp = nd6_dad_find(ifa); + + /* Quickhack - completely ignore DAD NS packets */ + if (dad_ignore_ns) { + nd6log((LOG_INFO, + "nd6_dad_ns_input: ignoring DAD NS packet for " + "address %s(%s)\n", ip6_sprintf(taddr6), + if_name(ifa->ifa_ifp))); + return; + } + + /* + * if I'm yet to start DAD, someone else started using this address + * first. I have a duplicate and you win. + */ + if (!dp || dp->dad_ns_ocount == 0) + duplicate++; + + /* XXX more checks for loopback situation - see nd6_dad_timer too */ + + if (duplicate) { + dp = NULL; /* will be freed in nd6_dad_duplicated() */ + nd6_dad_duplicated(ifa); + } else { + /* + * not sure if I got a duplicate. + * increment ns count and see what happens. + */ + if (dp) + dp->dad_ns_icount++; + } +} + +static void +nd6_dad_na_input(ifa) + struct ifaddr *ifa; +{ + struct dadq *dp; + + if (!ifa) + panic("ifa == NULL in nd6_dad_na_input"); + + dp = nd6_dad_find(ifa); + if (dp) + dp->dad_na_icount++; + + /* remove the address. */ + nd6_dad_duplicated(ifa); +} diff --git a/sys/netinet6/nd6_rtr.c b/sys/netinet6/nd6_rtr.c new file mode 100644 index 0000000..651a14d --- /dev/null +++ b/sys/netinet6/nd6_rtr.c @@ -0,0 +1,1982 @@ +/* $FreeBSD$ */ +/* $KAME: nd6_rtr.c,v 1.111 2001/04/27 01:37:15 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/queue.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> +#include <net/radix.h> + +#include <netinet/in.h> +#include <netinet6/in6_var.h> +#include <netinet6/in6_ifattach.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet/icmp6.h> +#include <netinet6/scope6_var.h> + +#include <net/net_osdep.h> + +#define SDL(s) ((struct sockaddr_dl *)s) + +static struct nd_defrouter *defrtrlist_update __P((struct nd_defrouter *)); +static struct in6_ifaddr *in6_ifadd __P((struct nd_prefix *, + struct in6_addr *)); +static struct nd_pfxrouter *pfxrtr_lookup __P((struct nd_prefix *, + struct nd_defrouter *)); +static void pfxrtr_add __P((struct nd_prefix *, struct nd_defrouter *)); +static void pfxrtr_del __P((struct nd_pfxrouter *)); +static struct nd_pfxrouter *find_pfxlist_reachable_router + __P((struct nd_prefix *)); +static void defrouter_addifreq __P((struct ifnet *)); +static void nd6_rtmsg __P((int, struct rtentry *)); + +static void in6_init_address_ltimes __P((struct nd_prefix *ndpr, + struct in6_addrlifetime *lt6)); + +static int rt6_deleteroute __P((struct radix_node *, void *)); + +extern int nd6_recalc_reachtm_interval; + +static struct ifnet *nd6_defifp; +int nd6_defifindex; + +int ip6_use_tempaddr = 0; + +int ip6_desync_factor; +u_int32_t ip6_temp_preferred_lifetime = DEF_TEMP_PREFERRED_LIFETIME; +u_int32_t ip6_temp_valid_lifetime = DEF_TEMP_VALID_LIFETIME; +/* + * shorter lifetimes for debugging purposes. +int ip6_temp_preferred_lifetime = 800; +static int ip6_temp_valid_lifetime = 1800; +*/ +int ip6_temp_regen_advance = TEMPADDR_REGEN_ADVANCE; + +/* + * Receive Router Solicitation Message - just for routers. + * Router solicitation/advertisement is mostly managed by userland program + * (rtadvd) so here we have no function like nd6_ra_output(). + * + * Based on RFC 2461 + */ +void +nd6_rs_input(m, off, icmp6len) + struct mbuf *m; + int off, icmp6len; +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct nd_router_solicit *nd_rs; + struct in6_addr saddr6 = ip6->ip6_src; +#if 0 + struct in6_addr daddr6 = ip6->ip6_dst; +#endif + char *lladdr = NULL; + int lladdrlen = 0; +#if 0 + struct sockaddr_dl *sdl = (struct sockaddr_dl *)NULL; + struct llinfo_nd6 *ln = (struct llinfo_nd6 *)NULL; + struct rtentry *rt = NULL; + int is_newentry; +#endif + union nd_opts ndopts; + + /* If I'm not a router, ignore it. */ + if (ip6_accept_rtadv != 0 || ip6_forwarding != 1) + goto freeit; + + /* Sanity checks */ + if (ip6->ip6_hlim != 255) { + nd6log((LOG_ERR, + "nd6_rs_input: invalid hlim (%d) from %s to %s on %s\n", + ip6->ip6_hlim, ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), if_name(ifp))); + goto bad; + } + + /* + * Don't update the neighbor cache, if src = ::. + * This indicates that the src has no IP address assigned yet. + */ + if (IN6_IS_ADDR_UNSPECIFIED(&saddr6)) + goto freeit; + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, icmp6len,); + nd_rs = (struct nd_router_solicit *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(nd_rs, struct nd_router_solicit *, m, off, icmp6len); + if (nd_rs == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + + icmp6len -= sizeof(*nd_rs); + nd6_option_init(nd_rs + 1, icmp6len, &ndopts); + if (nd6_options(&ndopts) < 0) { + nd6log((LOG_INFO, + "nd6_rs_input: invalid ND option, ignored\n")); + /* nd6_options have incremented stats */ + goto freeit; + } + + if (ndopts.nd_opts_src_lladdr) { + lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1); + lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3; + } + + if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) { + nd6log((LOG_INFO, + "nd6_rs_input: lladdrlen mismatch for %s " + "(if %d, RS packet %d)\n", + ip6_sprintf(&saddr6), ifp->if_addrlen, lladdrlen - 2)); + goto bad; + } + + nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_ROUTER_SOLICIT, 0); + + freeit: + m_freem(m); + return; + + bad: + icmp6stat.icp6s_badrs++; + m_freem(m); +} + +/* + * Receive Router Advertisement Message. + * + * Based on RFC 2461 + * TODO: on-link bit on prefix information + * TODO: ND_RA_FLAG_{OTHER,MANAGED} processing + */ +void +nd6_ra_input(m, off, icmp6len) + struct mbuf *m; + int off, icmp6len; +{ + struct ifnet *ifp = m->m_pkthdr.rcvif; + struct nd_ifinfo *ndi = &nd_ifinfo[ifp->if_index]; + struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + struct nd_router_advert *nd_ra; + struct in6_addr saddr6 = ip6->ip6_src; +#if 0 + struct in6_addr daddr6 = ip6->ip6_dst; + int flags; /* = nd_ra->nd_ra_flags_reserved; */ + int is_managed = ((flags & ND_RA_FLAG_MANAGED) != 0); + int is_other = ((flags & ND_RA_FLAG_OTHER) != 0); +#endif + union nd_opts ndopts; + struct nd_defrouter *dr; + + if (ip6_accept_rtadv == 0) + goto freeit; + + if (ip6->ip6_hlim != 255) { + nd6log((LOG_ERR, + "nd6_ra_input: invalid hlim (%d) from %s to %s on %s\n", + ip6->ip6_hlim, ip6_sprintf(&ip6->ip6_src), + ip6_sprintf(&ip6->ip6_dst), if_name(ifp))); + goto bad; + } + + if (!IN6_IS_ADDR_LINKLOCAL(&saddr6)) { + nd6log((LOG_ERR, + "nd6_ra_input: src %s is not link-local\n", + ip6_sprintf(&saddr6))); + goto bad; + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, icmp6len,); + nd_ra = (struct nd_router_advert *)((caddr_t)ip6 + off); +#else + IP6_EXTHDR_GET(nd_ra, struct nd_router_advert *, m, off, icmp6len); + if (nd_ra == NULL) { + icmp6stat.icp6s_tooshort++; + return; + } +#endif + + icmp6len -= sizeof(*nd_ra); + nd6_option_init(nd_ra + 1, icmp6len, &ndopts); + if (nd6_options(&ndopts) < 0) { + nd6log((LOG_INFO, + "nd6_ra_input: invalid ND option, ignored\n")); + /* nd6_options have incremented stats */ + goto freeit; + } + + { + struct nd_defrouter dr0; + u_int32_t advreachable = nd_ra->nd_ra_reachable; + + dr0.rtaddr = saddr6; + dr0.flags = nd_ra->nd_ra_flags_reserved; + dr0.rtlifetime = ntohs(nd_ra->nd_ra_router_lifetime); + dr0.expire = time_second + dr0.rtlifetime; + dr0.ifp = ifp; + dr0.advint = 0; /* Mobile IPv6 */ + dr0.advint_expire = 0; /* Mobile IPv6 */ + dr0.advints_lost = 0; /* Mobile IPv6 */ + /* unspecified or not? (RFC 2461 6.3.4) */ + if (advreachable) { + advreachable = ntohl(advreachable); + if (advreachable <= MAX_REACHABLE_TIME && + ndi->basereachable != advreachable) { + ndi->basereachable = advreachable; + ndi->reachable = ND_COMPUTE_RTIME(ndi->basereachable); + ndi->recalctm = nd6_recalc_reachtm_interval; /* reset */ + } + } + if (nd_ra->nd_ra_retransmit) + ndi->retrans = ntohl(nd_ra->nd_ra_retransmit); + if (nd_ra->nd_ra_curhoplimit) + ndi->chlim = nd_ra->nd_ra_curhoplimit; + dr = defrtrlist_update(&dr0); + } + + /* + * prefix + */ + if (ndopts.nd_opts_pi) { + struct nd_opt_hdr *pt; + struct nd_opt_prefix_info *pi = NULL; + struct nd_prefix pr; + + for (pt = (struct nd_opt_hdr *)ndopts.nd_opts_pi; + pt <= (struct nd_opt_hdr *)ndopts.nd_opts_pi_end; + pt = (struct nd_opt_hdr *)((caddr_t)pt + + (pt->nd_opt_len << 3))) { + if (pt->nd_opt_type != ND_OPT_PREFIX_INFORMATION) + continue; + pi = (struct nd_opt_prefix_info *)pt; + + if (pi->nd_opt_pi_len != 4) { + nd6log((LOG_INFO, + "nd6_ra_input: invalid option " + "len %d for prefix information option, " + "ignored\n", pi->nd_opt_pi_len)); + continue; + } + + if (128 < pi->nd_opt_pi_prefix_len) { + nd6log((LOG_INFO, + "nd6_ra_input: invalid prefix " + "len %d for prefix information option, " + "ignored\n", pi->nd_opt_pi_prefix_len)); + continue; + } + + if (IN6_IS_ADDR_MULTICAST(&pi->nd_opt_pi_prefix) + || IN6_IS_ADDR_LINKLOCAL(&pi->nd_opt_pi_prefix)) { + nd6log((LOG_INFO, + "nd6_ra_input: invalid prefix " + "%s, ignored\n", + ip6_sprintf(&pi->nd_opt_pi_prefix))); + continue; + } + + /* aggregatable unicast address, rfc2374 */ + if ((pi->nd_opt_pi_prefix.s6_addr8[0] & 0xe0) == 0x20 + && pi->nd_opt_pi_prefix_len != 64) { + nd6log((LOG_INFO, + "nd6_ra_input: invalid prefixlen " + "%d for rfc2374 prefix %s, ignored\n", + pi->nd_opt_pi_prefix_len, + ip6_sprintf(&pi->nd_opt_pi_prefix))); + continue; + } + + bzero(&pr, sizeof(pr)); + pr.ndpr_prefix.sin6_family = AF_INET6; + pr.ndpr_prefix.sin6_len = sizeof(pr.ndpr_prefix); + pr.ndpr_prefix.sin6_addr = pi->nd_opt_pi_prefix; + pr.ndpr_ifp = (struct ifnet *)m->m_pkthdr.rcvif; + + pr.ndpr_raf_onlink = (pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_ONLINK) ? 1 : 0; + pr.ndpr_raf_auto = (pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_AUTO) ? 1 : 0; + pr.ndpr_plen = pi->nd_opt_pi_prefix_len; + pr.ndpr_vltime = ntohl(pi->nd_opt_pi_valid_time); + pr.ndpr_pltime = + ntohl(pi->nd_opt_pi_preferred_time); + + if (in6_init_prefix_ltimes(&pr)) + continue; /* prefix lifetime init failed */ + + (void)prelist_update(&pr, dr, m); + } + } + + /* + * MTU + */ + if (ndopts.nd_opts_mtu && ndopts.nd_opts_mtu->nd_opt_mtu_len == 1) { + u_int32_t mtu = ntohl(ndopts.nd_opts_mtu->nd_opt_mtu_mtu); + + /* lower bound */ + if (mtu < IPV6_MMTU) { + nd6log((LOG_INFO, "nd6_ra_input: bogus mtu option " + "mtu=%d sent from %s, ignoring\n", + mtu, ip6_sprintf(&ip6->ip6_src))); + goto skip; + } + + /* upper bound */ + if (ndi->maxmtu) { + if (mtu <= ndi->maxmtu) { + int change = (ndi->linkmtu != mtu); + + ndi->linkmtu = mtu; + if (change) /* in6_maxmtu may change */ + in6_setmaxmtu(); + } else { + nd6log((LOG_INFO, "nd6_ra_input: bogus mtu " + "mtu=%d sent from %s; " + "exceeds maxmtu %d, ignoring\n", + mtu, ip6_sprintf(&ip6->ip6_src), + ndi->maxmtu)); + } + } else { + nd6log((LOG_INFO, "nd6_ra_input: mtu option " + "mtu=%d sent from %s; maxmtu unknown, " + "ignoring\n", + mtu, ip6_sprintf(&ip6->ip6_src))); + } + } + + skip: + + /* + * Source link layer address + */ + { + char *lladdr = NULL; + int lladdrlen = 0; + + if (ndopts.nd_opts_src_lladdr) { + lladdr = (char *)(ndopts.nd_opts_src_lladdr + 1); + lladdrlen = ndopts.nd_opts_src_lladdr->nd_opt_len << 3; + } + + if (lladdr && ((ifp->if_addrlen + 2 + 7) & ~7) != lladdrlen) { + nd6log((LOG_INFO, + "nd6_ra_input: lladdrlen mismatch for %s " + "(if %d, RA packet %d)\n", + ip6_sprintf(&saddr6), ifp->if_addrlen, lladdrlen - 2)); + goto bad; + } + + nd6_cache_lladdr(ifp, &saddr6, lladdr, lladdrlen, ND_ROUTER_ADVERT, 0); + + /* + * Installing a link-layer address might change the state of the + * router's neighbor cache, which might also affect our on-link + * detection of adveritsed prefixes. + */ + pfxlist_onlink_check(); + } + + freeit: + m_freem(m); + return; + + bad: + icmp6stat.icp6s_badra++; + m_freem(m); +} + +/* + * default router list proccessing sub routines + */ + +/* tell the change to user processes watching the routing socket. */ +static void +nd6_rtmsg(cmd, rt) + int cmd; + struct rtentry *rt; +{ + struct rt_addrinfo info; + + bzero((caddr_t)&info, sizeof(info)); + info.rti_info[RTAX_DST] = rt_key(rt); + info.rti_info[RTAX_GATEWAY] = rt->rt_gateway; + info.rti_info[RTAX_NETMASK] = rt_mask(rt); + info.rti_info[RTAX_IFP] = + (struct sockaddr *)TAILQ_FIRST(&rt->rt_ifp->if_addrlist); + info.rti_info[RTAX_IFA] = rt->rt_ifa->ifa_addr; + + rt_missmsg(cmd, &info, rt->rt_flags, 0); +} + +void +defrouter_addreq(new) + struct nd_defrouter *new; +{ + struct sockaddr_in6 def, mask, gate; + struct rtentry *newrt = NULL; + int s; + + Bzero(&def, sizeof(def)); + Bzero(&mask, sizeof(mask)); + Bzero(&gate, sizeof(gate)); + + def.sin6_len = mask.sin6_len = gate.sin6_len + = sizeof(struct sockaddr_in6); + def.sin6_family = mask.sin6_family = gate.sin6_family = AF_INET6; + gate.sin6_addr = new->rtaddr; + + s = splnet(); + (void)rtrequest(RTM_ADD, (struct sockaddr *)&def, + (struct sockaddr *)&gate, (struct sockaddr *)&mask, + RTF_GATEWAY, &newrt); + if (newrt) { + nd6_rtmsg(RTM_ADD, newrt); /* tell user process */ + newrt->rt_refcnt--; + } + splx(s); + return; +} + +/* Add a route to a given interface as default */ +void +defrouter_addifreq(ifp) + struct ifnet *ifp; +{ + struct sockaddr_in6 def, mask; + struct ifaddr *ifa; + struct rtentry *newrt = NULL; + int error, flags; + + bzero(&def, sizeof(def)); + bzero(&mask, sizeof(mask)); + + def.sin6_len = mask.sin6_len = sizeof(struct sockaddr_in6); + def.sin6_family = mask.sin6_family = AF_INET6; + + /* + * Search for an ifaddr beloging to the specified interface. + * XXX: An IPv6 address are required to be assigned on the interface. + */ + if ((ifa = ifaof_ifpforaddr((struct sockaddr *)&def, ifp)) == NULL) { + nd6log((LOG_ERR, /* better error? */ + "defrouter_addifreq: failed to find an ifaddr " + "to install a route to interface %s\n", + if_name(ifp))); + return; + } + + flags = ifa->ifa_flags; + error = rtrequest(RTM_ADD, (struct sockaddr *)&def, ifa->ifa_addr, + (struct sockaddr *)&mask, flags, &newrt); + if (error != 0) { + nd6log((LOG_ERR, + "defrouter_addifreq: failed to install a route to " + "interface %s (errno = %d)\n", + if_name(ifp), error)); + + if (newrt) /* maybe unnecessary, but do it for safety */ + newrt->rt_refcnt--; + } else { + if (newrt) { + nd6_rtmsg(RTM_ADD, newrt); + newrt->rt_refcnt--; + } + } +} + +struct nd_defrouter * +defrouter_lookup(addr, ifp) + struct in6_addr *addr; + struct ifnet *ifp; +{ + struct nd_defrouter *dr; + + for (dr = TAILQ_FIRST(&nd_defrouter); dr; + dr = TAILQ_NEXT(dr, dr_entry)) { + if (dr->ifp == ifp && IN6_ARE_ADDR_EQUAL(addr, &dr->rtaddr)) + return(dr); + } + + return(NULL); /* search failed */ +} + +void +defrouter_delreq(dr, dofree) + struct nd_defrouter *dr; + int dofree; +{ + struct sockaddr_in6 def, mask, gate; + struct rtentry *oldrt = NULL; + + Bzero(&def, sizeof(def)); + Bzero(&mask, sizeof(mask)); + Bzero(&gate, sizeof(gate)); + + def.sin6_len = mask.sin6_len = gate.sin6_len + = sizeof(struct sockaddr_in6); + def.sin6_family = mask.sin6_family = gate.sin6_family = AF_INET6; + gate.sin6_addr = dr->rtaddr; + + rtrequest(RTM_DELETE, (struct sockaddr *)&def, + (struct sockaddr *)&gate, + (struct sockaddr *)&mask, + RTF_GATEWAY, &oldrt); + if (oldrt) { + nd6_rtmsg(RTM_DELETE, oldrt); + RTFREE(oldrt); + } + + if (dofree) /* XXX: necessary? */ + free(dr, M_IP6NDP); +} + +void +defrtrlist_del(dr) + struct nd_defrouter *dr; +{ + struct nd_defrouter *deldr = NULL; + struct nd_prefix *pr; + + /* + * Flush all the routing table entries that use the router + * as a next hop. + */ + if (!ip6_forwarding && ip6_accept_rtadv) { + /* above is a good condition? */ + rt6_flush(&dr->rtaddr, dr->ifp); + } + + if (dr == TAILQ_FIRST(&nd_defrouter)) + deldr = dr; /* The router is primary. */ + + TAILQ_REMOVE(&nd_defrouter, dr, dr_entry); + + /* + * Also delete all the pointers to the router in each prefix lists. + */ + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + struct nd_pfxrouter *pfxrtr; + if ((pfxrtr = pfxrtr_lookup(pr, dr)) != NULL) + pfxrtr_del(pfxrtr); + } + pfxlist_onlink_check(); + + /* + * If the router is the primary one, choose a new one. + * Note that defrouter_select() will remove the current gateway + * from the routing table. + */ + if (deldr) + defrouter_select(); + + free(dr, M_IP6NDP); +} + +/* + * Default Router Selection according to Section 6.3.6 of RFC 2461: + * 1) Routers that are reachable or probably reachable should be + * preferred. + * 2) When no routers on the list are known to be reachable or + * probably reachable, routers SHOULD be selected in a round-robin + * fashion. + * 3) If the Default Router List is empty, assume that all + * destinations are on-link. + */ +void +defrouter_select() +{ + int s = splnet(); + struct nd_defrouter *dr, anydr; + struct rtentry *rt = NULL; + struct llinfo_nd6 *ln = NULL; + + /* + * Search for a (probably) reachable router from the list. + */ + for (dr = TAILQ_FIRST(&nd_defrouter); dr; + dr = TAILQ_NEXT(dr, dr_entry)) { + if ((rt = nd6_lookup(&dr->rtaddr, 0, dr->ifp)) && + (ln = (struct llinfo_nd6 *)rt->rt_llinfo) && + ND6_IS_LLINFO_PROBREACH(ln)) { + /* Got it, and move it to the head */ + TAILQ_REMOVE(&nd_defrouter, dr, dr_entry); + TAILQ_INSERT_HEAD(&nd_defrouter, dr, dr_entry); + break; + } + } + + if ((dr = TAILQ_FIRST(&nd_defrouter))) { + /* + * De-install the previous default gateway and install + * a new one. + * Note that if there is no reachable router in the list, + * the head entry will be used anyway. + * XXX: do we have to check the current routing table entry? + */ + bzero(&anydr, sizeof(anydr)); + defrouter_delreq(&anydr, 0); + defrouter_addreq(dr); + } + else { + /* + * The Default Router List is empty, so install the default + * route to an inteface. + * XXX: The specification does not say this mechanism should + * be restricted to hosts, but this would be not useful + * (even harmful) for routers. + */ + if (!ip6_forwarding) { + /* + * De-install the current default route + * in advance. + */ + bzero(&anydr, sizeof(anydr)); + defrouter_delreq(&anydr, 0); + if (nd6_defifp) { + /* + * Install a route to the default interface + * as default route. + * XXX: we enable this for host only, because + * this may override a default route installed + * a user process (e.g. routing daemon) in a + * router case. + */ + defrouter_addifreq(nd6_defifp); + } else { + nd6log((LOG_INFO, "defrouter_select: " + "there's no default router and no default" + " interface\n")); + } + } + } + + splx(s); + return; +} + +static struct nd_defrouter * +defrtrlist_update(new) + struct nd_defrouter *new; +{ + struct nd_defrouter *dr, *n; + int s = splnet(); + + if ((dr = defrouter_lookup(&new->rtaddr, new->ifp)) != NULL) { + /* entry exists */ + if (new->rtlifetime == 0) { + defrtrlist_del(dr); + dr = NULL; + } else { + /* override */ + dr->flags = new->flags; /* xxx flag check */ + dr->rtlifetime = new->rtlifetime; + dr->expire = new->expire; + } + splx(s); + return(dr); + } + + /* entry does not exist */ + if (new->rtlifetime == 0) { + splx(s); + return(NULL); + } + + n = (struct nd_defrouter *)malloc(sizeof(*n), M_IP6NDP, M_NOWAIT); + if (n == NULL) { + splx(s); + return(NULL); + } + bzero(n, sizeof(*n)); + *n = *new; + + /* + * Insert the new router at the end of the Default Router List. + * If there is no other router, install it anyway. Otherwise, + * just continue to use the current default router. + */ + TAILQ_INSERT_TAIL(&nd_defrouter, n, dr_entry); + if (TAILQ_FIRST(&nd_defrouter) == n) + defrouter_select(); + splx(s); + + return(n); +} + +static struct nd_pfxrouter * +pfxrtr_lookup(pr, dr) + struct nd_prefix *pr; + struct nd_defrouter *dr; +{ + struct nd_pfxrouter *search; + + for (search = pr->ndpr_advrtrs.lh_first; search; search = search->pfr_next) { + if (search->router == dr) + break; + } + + return(search); +} + +static void +pfxrtr_add(pr, dr) + struct nd_prefix *pr; + struct nd_defrouter *dr; +{ + struct nd_pfxrouter *new; + + new = (struct nd_pfxrouter *)malloc(sizeof(*new), M_IP6NDP, M_NOWAIT); + if (new == NULL) + return; + bzero(new, sizeof(*new)); + new->router = dr; + + LIST_INSERT_HEAD(&pr->ndpr_advrtrs, new, pfr_entry); + + pfxlist_onlink_check(); +} + +static void +pfxrtr_del(pfr) + struct nd_pfxrouter *pfr; +{ + LIST_REMOVE(pfr, pfr_entry); + free(pfr, M_IP6NDP); +} + +struct nd_prefix * +nd6_prefix_lookup(pr) + struct nd_prefix *pr; +{ + struct nd_prefix *search; + + for (search = nd_prefix.lh_first; search; search = search->ndpr_next) { + if (pr->ndpr_ifp == search->ndpr_ifp && + pr->ndpr_plen == search->ndpr_plen && + in6_are_prefix_equal(&pr->ndpr_prefix.sin6_addr, + &search->ndpr_prefix.sin6_addr, + pr->ndpr_plen) + ) { + break; + } + } + + return(search); +} + +int +nd6_prelist_add(pr, dr, newp) + struct nd_prefix *pr, **newp; + struct nd_defrouter *dr; +{ + struct nd_prefix *new = NULL; + int i, s; + + new = (struct nd_prefix *)malloc(sizeof(*new), M_IP6NDP, M_NOWAIT); + if (new == NULL) + return ENOMEM; + bzero(new, sizeof(*new)); + *new = *pr; + if (newp != NULL) + *newp = new; + + /* initilization */ + LIST_INIT(&new->ndpr_advrtrs); + in6_prefixlen2mask(&new->ndpr_mask, new->ndpr_plen); + /* make prefix in the canonical form */ + for (i = 0; i < 4; i++) + new->ndpr_prefix.sin6_addr.s6_addr32[i] &= + new->ndpr_mask.s6_addr32[i]; + + s = splnet(); + /* link ndpr_entry to nd_prefix list */ + LIST_INSERT_HEAD(&nd_prefix, new, ndpr_entry); + splx(s); + + /* ND_OPT_PI_FLAG_ONLINK processing */ + if (new->ndpr_raf_onlink) { + int e; + + if ((e = nd6_prefix_onlink(new)) != 0) { + nd6log((LOG_ERR, "nd6_prelist_add: failed to make " + "the prefix %s/%d on-link on %s (errno=%d)\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, if_name(pr->ndpr_ifp), e)); + /* proceed anyway. XXX: is it correct? */ + } + } + + if (dr) { + pfxrtr_add(new, dr); + } + + return 0; +} + +void +prelist_remove(pr) + struct nd_prefix *pr; +{ + struct nd_pfxrouter *pfr, *next; + int e, s; + + /* make sure to invalidate the prefix until it is really freed. */ + pr->ndpr_vltime = 0; + pr->ndpr_pltime = 0; +#if 0 + /* + * Though these flags are now meaningless, we'd rather keep the value + * not to confuse users when executing "ndp -p". + */ + pr->ndpr_raf_onlink = 0; + pr->ndpr_raf_auto = 0; +#endif + if ((pr->ndpr_stateflags & NDPRF_ONLINK) != 0 && + (e = nd6_prefix_offlink(pr)) != 0) { + nd6log((LOG_ERR, "prelist_remove: failed to make %s/%d offlink " + "on %s, errno=%d\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, if_name(pr->ndpr_ifp), e)); + /* what should we do? */ + } + + if (pr->ndpr_refcnt > 0) + return; /* notice here? */ + + s = splnet(); + + /* unlink ndpr_entry from nd_prefix list */ + LIST_REMOVE(pr, ndpr_entry); + + /* free list of routers that adversed the prefix */ + for (pfr = pr->ndpr_advrtrs.lh_first; pfr; pfr = next) { + next = pfr->pfr_next; + + free(pfr, M_IP6NDP); + } + splx(s); + + free(pr, M_IP6NDP); + + pfxlist_onlink_check(); +} + +int +prelist_update(new, dr, m) + struct nd_prefix *new; + struct nd_defrouter *dr; /* may be NULL */ + struct mbuf *m; +{ + struct in6_ifaddr *ia6 = NULL, *ia6_match = NULL; + struct ifaddr *ifa; + struct ifnet *ifp = new->ndpr_ifp; + struct nd_prefix *pr; + int s = splnet(); + int error = 0; + int newprefix = 0; + int auth; + struct in6_addrlifetime lt6_tmp; + + auth = 0; + if (m) { + /* + * Authenticity for NA consists authentication for + * both IP header and IP datagrams, doesn't it ? + */ +#if defined(M_AUTHIPHDR) && defined(M_AUTHIPDGM) + auth = (m->m_flags & M_AUTHIPHDR + && m->m_flags & M_AUTHIPDGM) ? 1 : 0; +#endif + } + + + if ((pr = nd6_prefix_lookup(new)) != NULL) { + /* + * nd6_prefix_lookup() ensures that pr and new have the same + * prefix on a same interface. + */ + + /* + * Update prefix information. Note that the on-link (L) bit + * and the autonomous (A) bit should NOT be changed from 1 + * to 0. + */ + if (new->ndpr_raf_onlink == 1) + pr->ndpr_raf_onlink = 1; + if (new->ndpr_raf_auto == 1) + pr->ndpr_raf_auto = 1; + if (new->ndpr_raf_onlink) { + pr->ndpr_vltime = new->ndpr_vltime; + pr->ndpr_pltime = new->ndpr_pltime; + pr->ndpr_preferred = new->ndpr_preferred; + pr->ndpr_expire = new->ndpr_expire; + } + + if (new->ndpr_raf_onlink && + (pr->ndpr_stateflags & NDPRF_ONLINK) == 0) { + int e; + + if ((e = nd6_prefix_onlink(pr)) != 0) { + nd6log((LOG_ERR, + "prelist_update: failed to make " + "the prefix %s/%d on-link on %s " + "(errno=%d)\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, if_name(pr->ndpr_ifp), e)); + /* proceed anyway. XXX: is it correct? */ + } + } + + if (dr && pfxrtr_lookup(pr, dr) == NULL) + pfxrtr_add(pr, dr); + } else { + struct nd_prefix *newpr = NULL; + + newprefix = 1; + + if (new->ndpr_vltime == 0) + goto end; + if (new->ndpr_raf_onlink == 0 && new->ndpr_raf_auto == 0) + goto end; + + bzero(&new->ndpr_addr, sizeof(struct in6_addr)); + + error = nd6_prelist_add(new, dr, &newpr); + if (error != 0 || newpr == NULL) { + nd6log((LOG_NOTICE, "prelist_update: " + "nd6_prelist_add failed for %s/%d on %s " + "errno=%d, returnpr=%p\n", + ip6_sprintf(&new->ndpr_prefix.sin6_addr), + new->ndpr_plen, if_name(new->ndpr_ifp), + error, newpr)); + goto end; /* we should just give up in this case. */ + } + + /* + * XXX: from the ND point of view, we can ignore a prefix + * with the on-link bit being zero. However, we need a + * prefix structure for references from autoconfigured + * addresses. Thus, we explicitly make suret that the prefix + * itself expires now. + */ + if (newpr->ndpr_raf_onlink == 0) { + newpr->ndpr_vltime = 0; + newpr->ndpr_pltime = 0; + in6_init_prefix_ltimes(newpr); + } + + pr = newpr; + } + + /* + * Address autoconfiguration based on Section 5.5.3 of RFC 2462. + * Note that pr must be non NULL at this point. + */ + + /* 5.5.3 (a). Ignore the prefix without the A bit set. */ + if (!new->ndpr_raf_auto) + goto afteraddrconf; + + /* + * 5.5.3 (b). the link-local prefix should have been ignored in + * nd6_ra_input. + */ + + /* + * 5.5.3 (c). Consistency check on lifetimes: pltime <= vltime. + * This should have been done in nd6_ra_input. + */ + + /* + * 5.5.3 (d). If the prefix advertised does not match the prefix of an + * address already in the list, and the Valid Lifetime is not 0, + * form an address. Note that even a manually configured address + * should reject autoconfiguration of a new address. + */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + struct in6_ifaddr *ifa6; + int ifa_plen; + u_int32_t storedlifetime; + + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + + ifa6 = (struct in6_ifaddr *)ifa; + + /* + * Spec is not clear here, but I believe we should concentrate + * on unicast (i.e. not anycast) addresses. + * XXX: other ia6_flags? detached or duplicated? + */ + if ((ifa6->ia6_flags & IN6_IFF_ANYCAST) != 0) + continue; + + ifa_plen = in6_mask2len(&ifa6->ia_prefixmask.sin6_addr, NULL); + if (ifa_plen != new->ndpr_plen || + !in6_are_prefix_equal(&ifa6->ia_addr.sin6_addr, + &new->ndpr_prefix.sin6_addr, + ifa_plen)) + continue; + + if (ia6_match == NULL) /* remember the first one */ + ia6_match = ifa6; + + if ((ifa6->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + /* + * An already autoconfigured address matched. Now that we + * are sure there is at least one matched address, we can + * proceed to 5.5.3. (e): update the lifetimes according to the + * "two hours" rule and the privacy extension. + */ +#define TWOHOUR (120*60) + lt6_tmp = ifa6->ia6_lifetime; + + if (lt6_tmp.ia6t_vltime == ND6_INFINITE_LIFETIME) + storedlifetime = ND6_INFINITE_LIFETIME; + else if (IFA6_IS_INVALID(ifa6)) + storedlifetime = 0; + else + storedlifetime = lt6_tmp.ia6t_expire - time_second; + + /* when not updating, keep the current stored lifetime. */ + lt6_tmp.ia6t_vltime = storedlifetime; + + if (TWOHOUR < new->ndpr_vltime || + storedlifetime < new->ndpr_vltime) { + lt6_tmp.ia6t_vltime = new->ndpr_vltime; + } else if (storedlifetime <= TWOHOUR +#if 0 + /* + * This condition is logically redundant, so we just + * omit it. + * See IPng 6712, 6717, and 6721. + */ + && new->ndpr_vltime <= storedlifetime +#endif + ) { + if (auth) { + lt6_tmp.ia6t_vltime = new->ndpr_vltime; + } + } else { + /* + * new->ndpr_vltime <= TWOHOUR && + * TWOHOUR < storedlifetime + */ + lt6_tmp.ia6t_vltime = TWOHOUR; + } + + /* The 2 hour rule is not imposed for preferred lifetime. */ + lt6_tmp.ia6t_pltime = new->ndpr_pltime; + + in6_init_address_ltimes(pr, <6_tmp); + + /* + * When adjusting the lifetimes of an existing temporary + * address, only lower the lifetimes. + * RFC 3041 3.3. (1). + * XXX: how should we modify ia6t_[pv]ltime? + */ + if ((ifa6->ia6_flags & IN6_IFF_TEMPORARY) != 0) { + if (lt6_tmp.ia6t_expire == 0 || /* no expire */ + lt6_tmp.ia6t_expire > + ifa6->ia6_lifetime.ia6t_expire) { + lt6_tmp.ia6t_expire = + ifa6->ia6_lifetime.ia6t_expire; + } + if (lt6_tmp.ia6t_preferred == 0 || /* no expire */ + lt6_tmp.ia6t_preferred > + ifa6->ia6_lifetime.ia6t_preferred) { + lt6_tmp.ia6t_preferred = + ifa6->ia6_lifetime.ia6t_preferred; + } + } + + ifa6->ia6_lifetime = lt6_tmp; + } + if (ia6_match == NULL && new->ndpr_vltime) { + /* + * No address matched and the valid lifetime is non-zero. + * Create a new address. + */ + if ((ia6 = in6_ifadd(new, NULL)) != NULL) { + /* + * note that we should use pr (not new) for reference. + */ + pr->ndpr_refcnt++; + ia6->ia6_ndpr = pr; + +#if 0 + /* XXXYYY Don't do this, according to Jinmei. */ + pr->ndpr_addr = new->ndpr_addr; +#endif + + /* + * RFC 3041 3.3 (2). + * When a new public address is created as described + * in RFC2462, also create a new temporary address. + * + * RFC 3041 3.5. + * When an interface connects to a new link, a new + * randomized interface identifier should be generated + * immediately together with a new set of temporary + * addresses. Thus, we specifiy 1 as the 2nd arg of + * in6_tmpifadd(). + */ + if (ip6_use_tempaddr) { + int e; + if ((e = in6_tmpifadd(ia6, 1)) != 0) { + nd6log((LOG_NOTICE, "prelist_update: " + "failed to create a temporary " + "address, errno=%d\n", + e)); + } + } + + /* + * A newly added address might affect the status + * of other addresses, so we check and update it. + * XXX: what if address duplication happens? + */ + pfxlist_onlink_check(); + } else { + /* just set an error. do not bark here. */ + error = EADDRNOTAVAIL; /* XXX: might be unused. */ + } + } + + afteraddrconf: + + end: + splx(s); + return error; +} + +/* + * A supplement function used in the on-link detection below; + * detect if a given prefix has a (probably) reachable advertising router. + * XXX: lengthy function name... + */ +static struct nd_pfxrouter * +find_pfxlist_reachable_router(pr) + struct nd_prefix *pr; +{ + struct nd_pfxrouter *pfxrtr; + struct rtentry *rt; + struct llinfo_nd6 *ln; + + for (pfxrtr = LIST_FIRST(&pr->ndpr_advrtrs); pfxrtr; + pfxrtr = LIST_NEXT(pfxrtr, pfr_entry)) { + if ((rt = nd6_lookup(&pfxrtr->router->rtaddr, 0, + pfxrtr->router->ifp)) && + (ln = (struct llinfo_nd6 *)rt->rt_llinfo) && + ND6_IS_LLINFO_PROBREACH(ln)) + break; /* found */ + } + + return(pfxrtr); + +} + +/* + * Check if each prefix in the prefix list has at least one available router + * that advertised the prefix (a router is "available" if its neighbor cache + * entry is reachable or probably reachable). + * If the check fails, the prefix may be off-link, because, for example, + * we have moved from the network but the lifetime of the prefix has not + * expired yet. So we should not use the prefix if there is another prefix + * that has an available router. + * But, if there is no prefix that has an available router, we still regards + * all the prefixes as on-link. This is because we can't tell if all the + * routers are simply dead or if we really moved from the network and there + * is no router around us. + */ +void +pfxlist_onlink_check() +{ + struct nd_prefix *pr; + struct in6_ifaddr *ifa; + + /* + * Check if there is a prefix that has a reachable advertising + * router. + */ + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + if (pr->ndpr_raf_onlink && find_pfxlist_reachable_router(pr)) + break; + } + + if (pr) { + /* + * There is at least one prefix that has a reachable router. + * Detach prefixes which have no reachable advertising + * router, and attach other prefixes. + */ + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + /* XXX: a link-local prefix should never be detached */ + if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) + continue; + + /* + * we aren't interested in prefixes without the L bit + * set. + */ + if (pr->ndpr_raf_onlink == 0) + continue; + + if ((pr->ndpr_stateflags & NDPRF_DETACHED) == 0 && + find_pfxlist_reachable_router(pr) == NULL) + pr->ndpr_stateflags |= NDPRF_DETACHED; + if ((pr->ndpr_stateflags & NDPRF_DETACHED) != 0 && + find_pfxlist_reachable_router(pr) != 0) + pr->ndpr_stateflags &= ~NDPRF_DETACHED; + } + } else { + /* there is no prefix that has a reachable router */ + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) + continue; + + if (pr->ndpr_raf_onlink == 0) + continue; + + if ((pr->ndpr_stateflags & NDPRF_DETACHED) != 0) + pr->ndpr_stateflags &= ~NDPRF_DETACHED; + } + } + + /* + * Remove each interface route associated with a (just) detached + * prefix, and reinstall the interface route for a (just) attached + * prefix. Note that all attempt of reinstallation does not + * necessarily success, when a same prefix is shared among multiple + * interfaces. Such cases will be handled in nd6_prefix_onlink, + * so we don't have to care about them. + */ + for (pr = nd_prefix.lh_first; pr; pr = pr->ndpr_next) { + int e; + + if (IN6_IS_ADDR_LINKLOCAL(&pr->ndpr_prefix.sin6_addr)) + continue; + + if (pr->ndpr_raf_onlink == 0) + continue; + + if ((pr->ndpr_stateflags & NDPRF_DETACHED) != 0 && + (pr->ndpr_stateflags & NDPRF_ONLINK) != 0) { + if ((e = nd6_prefix_offlink(pr)) != 0) { + nd6log((LOG_ERR, + "pfxlist_onlink_check: failed to " + "make %s/%d offlink, errno=%d\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, e)); + } + } + if ((pr->ndpr_stateflags & NDPRF_DETACHED) == 0 && + (pr->ndpr_stateflags & NDPRF_ONLINK) == 0 && + pr->ndpr_raf_onlink) { + if ((e = nd6_prefix_onlink(pr)) != 0) { + nd6log((LOG_ERR, + "pfxlist_onlink_check: failed to " + "make %s/%d offlink, errno=%d\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, e)); + } + } + } + + /* + * Changes on the prefix status might affect address status as well. + * Make sure that all addresses derived from an attached prefix are + * attached, and that all addresses derived from a detached prefix are + * detached. Note, however, that a manually configured address should + * always be attached. + * The precise detection logic is same as the one for prefixes. + */ + for (ifa = in6_ifaddr; ifa; ifa = ifa->ia_next) { + if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + if (ifa->ia6_ndpr == NULL) { + /* + * This can happen when we first configure the address + * (i.e. the address exists, but the prefix does not). + * XXX: complicated relationships... + */ + continue; + } + + if (find_pfxlist_reachable_router(ifa->ia6_ndpr)) + break; + } + if (ifa) { + for (ifa = in6_ifaddr; ifa; ifa = ifa->ia_next) { + if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + if (ifa->ia6_ndpr == NULL) /* XXX: see above. */ + continue; + + if (find_pfxlist_reachable_router(ifa->ia6_ndpr)) + ifa->ia6_flags &= ~IN6_IFF_DETACHED; + else + ifa->ia6_flags |= IN6_IFF_DETACHED; + } + } + else { + for (ifa = in6_ifaddr; ifa; ifa = ifa->ia_next) { + if ((ifa->ia6_flags & IN6_IFF_AUTOCONF) == 0) + continue; + + ifa->ia6_flags &= ~IN6_IFF_DETACHED; + } + } +} + +int +nd6_prefix_onlink(pr) + struct nd_prefix *pr; +{ + struct ifaddr *ifa; + struct ifnet *ifp = pr->ndpr_ifp; + struct sockaddr_in6 mask6; + struct nd_prefix *opr; + u_long rtflags; + int error = 0; + struct rtentry *rt = NULL; + + /* sanity check */ + if ((pr->ndpr_stateflags & NDPRF_ONLINK) != 0) { + nd6log((LOG_ERR, + "nd6_prefix_onlink: %s/%d is already on-link\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), pr->ndpr_plen); + return(EEXIST)); + } + + /* + * Add the interface route associated with the prefix. Before + * installing the route, check if there's the same prefix on another + * interface, and the prefix has already installed the interface route. + * Although such a configuration is expected to be rare, we explicitly + * allow it. + */ + for (opr = nd_prefix.lh_first; opr; opr = opr->ndpr_next) { + if (opr == pr) + continue; + + if ((opr->ndpr_stateflags & NDPRF_ONLINK) == 0) + continue; + + if (opr->ndpr_plen == pr->ndpr_plen && + in6_are_prefix_equal(&pr->ndpr_prefix.sin6_addr, + &opr->ndpr_prefix.sin6_addr, + pr->ndpr_plen)) + return(0); + } + + /* + * We prefer link-local addresses as the associated interface address. + */ + /* search for a link-local addr */ + ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, + IN6_IFF_NOTREADY| + IN6_IFF_ANYCAST); + if (ifa == NULL) { + /* XXX: freebsd does not have ifa_ifwithaf */ + TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + { + if (ifa->ifa_addr->sa_family == AF_INET6) + break; + } + /* should we care about ia6_flags? */ + } + if (ifa == NULL) { + /* + * This can still happen, when, for example, we receive an RA + * containing a prefix with the L bit set and the A bit clear, + * after removing all IPv6 addresses on the receiving + * interface. This should, of course, be rare though. + */ + nd6log((LOG_NOTICE, + "nd6_prefix_onlink: failed to find any ifaddr" + " to add route for a prefix(%s/%d) on %s\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, if_name(ifp))); + return(0); + } + + /* + * in6_ifinit() sets nd6_rtrequest to ifa_rtrequest for all ifaddrs. + * ifa->ifa_rtrequest = nd6_rtrequest; + */ + bzero(&mask6, sizeof(mask6)); + mask6.sin6_len = sizeof(mask6); + mask6.sin6_addr = pr->ndpr_mask; + rtflags = ifa->ifa_flags | RTF_CLONING | RTF_UP; + if (nd6_need_cache(ifp)) { + /* explicitly set in case ifa_flags does not set the flag. */ + rtflags |= RTF_CLONING; + } else { + /* + * explicitly clear the cloning bit in case ifa_flags sets it. + */ + rtflags &= ~RTF_CLONING; + } + error = rtrequest(RTM_ADD, (struct sockaddr *)&pr->ndpr_prefix, + ifa->ifa_addr, (struct sockaddr *)&mask6, + rtflags, &rt); + if (error == 0) { + if (rt != NULL) /* this should be non NULL, though */ + nd6_rtmsg(RTM_ADD, rt); + pr->ndpr_stateflags |= NDPRF_ONLINK; + } + else { + nd6log((LOG_ERR, "nd6_prefix_onlink: failed to add route for a" + " prefix (%s/%d) on %s, gw=%s, mask=%s, flags=%lx " + "errno = %d\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), + pr->ndpr_plen, if_name(ifp), + ip6_sprintf(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr), + ip6_sprintf(&mask6.sin6_addr), rtflags, error)); + } + + if (rt != NULL) + rt->rt_refcnt--; + + return(error); +} + +int +nd6_prefix_offlink(pr) + struct nd_prefix *pr; +{ + int error = 0; + struct ifnet *ifp = pr->ndpr_ifp; + struct nd_prefix *opr; + struct sockaddr_in6 sa6, mask6; + struct rtentry *rt = NULL; + + /* sanity check */ + if ((pr->ndpr_stateflags & NDPRF_ONLINK) == 0) { + nd6log((LOG_ERR, + "nd6_prefix_offlink: %s/%d is already off-link\n", + ip6_sprintf(&pr->ndpr_prefix.sin6_addr), pr->ndpr_plen)); + return(EEXIST); + } + + bzero(&sa6, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + sa6.sin6_len = sizeof(sa6); + bcopy(&pr->ndpr_prefix.sin6_addr, &sa6.sin6_addr, + sizeof(struct in6_addr)); + bzero(&mask6, sizeof(mask6)); + mask6.sin6_family = AF_INET6; + mask6.sin6_len = sizeof(sa6); + bcopy(&pr->ndpr_mask, &mask6.sin6_addr, sizeof(struct in6_addr)); + error = rtrequest(RTM_DELETE, (struct sockaddr *)&sa6, NULL, + (struct sockaddr *)&mask6, 0, &rt); + if (error == 0) { + pr->ndpr_stateflags &= ~NDPRF_ONLINK; + + /* report the route deletion to the routing socket. */ + if (rt != NULL) + nd6_rtmsg(RTM_DELETE, rt); + + /* + * There might be the same prefix on another interface, + * the prefix which could not be on-link just because we have + * the interface route (see comments in nd6_prefix_onlink). + * If there's one, try to make the prefix on-link on the + * interface. + */ + for (opr = nd_prefix.lh_first; opr; opr = opr->ndpr_next) { + if (opr == pr) + continue; + + if ((opr->ndpr_stateflags & NDPRF_ONLINK) != 0) + continue; + + /* + * KAME specific: detached prefixes should not be + * on-link. + */ + if ((opr->ndpr_stateflags & NDPRF_DETACHED) != 0) + continue; + + if (opr->ndpr_plen == pr->ndpr_plen && + in6_are_prefix_equal(&pr->ndpr_prefix.sin6_addr, + &opr->ndpr_prefix.sin6_addr, + pr->ndpr_plen)) { + int e; + + if ((e = nd6_prefix_onlink(opr)) != 0) { + nd6log((LOG_ERR, + "nd6_prefix_offlink: failed to " + "recover a prefix %s/%d from %s " + "to %s (errno = %d)\n", + ip6_sprintf(&opr->ndpr_prefix.sin6_addr), + opr->ndpr_plen, if_name(ifp), + if_name(opr->ndpr_ifp), e)); + } + } + } + } + else { + /* XXX: can we still set the NDPRF_ONLINK flag? */ + nd6log((LOG_ERR, + "nd6_prefix_offlink: failed to delete route: " + "%s/%d on %s (errno = %d)\n", + ip6_sprintf(&sa6.sin6_addr), pr->ndpr_plen, if_name(ifp), + error)); + } + + if (rt != NULL) + RTFREE(rt); + + return(error); +} + +static struct in6_ifaddr * +in6_ifadd(pr, ifid) + struct nd_prefix *pr; + struct in6_addr *ifid; /* Mobile IPv6 addition */ +{ + struct ifnet *ifp = pr->ndpr_ifp; + struct ifaddr *ifa; + struct in6_aliasreq ifra; + struct in6_ifaddr *ia, *ib; + int error, plen0; + struct in6_addr mask; + int prefixlen = pr->ndpr_plen; + + in6_len2mask(&mask, prefixlen); + + /* + * find a link-local address (will be interface ID). + * Is it really mandatory? Theoretically, a global or a site-local + * address can be configured without a link-local address, if we + * have a unique interface identifier... + * + * it is not mandatory to have a link-local address, we can generate + * interface identifier on the fly. we do this because: + * (1) it should be the easiest way to find interface identifier. + * (2) RFC2462 5.4 suggesting the use of the same interface identifier + * for multiple addresses on a single interface, and possible shortcut + * of DAD. we omitted DAD for this reason in the past. + * (3) a user can prevent autoconfiguration of global address + * by removing link-local address by hand (this is partly because we + * don't have other way to control the use of IPv6 on an interface. + * this has been our design choice - cf. NRL's "ifconfig auto"). + * (4) it is easier to manage when an interface has addresses + * with the same interface identifier, than to have multiple addresses + * with different interface identifiers. + * + * Mobile IPv6 addition: allow for caller to specify a wished interface + * ID. This is to not break connections when moving addresses between + * interfaces. + */ + ifa = (struct ifaddr *)in6ifa_ifpforlinklocal(ifp, 0);/* 0 is OK? */ + if (ifa) + ib = (struct in6_ifaddr *)ifa; + else + return NULL; + +#if 0 /* don't care link local addr state, and always do DAD */ + /* if link-local address is not eligible, do not autoconfigure. */ + if (((struct in6_ifaddr *)ifa)->ia6_flags & IN6_IFF_NOTREADY) { + printf("in6_ifadd: link-local address not ready\n"); + return NULL; + } +#endif + + /* prefixlen + ifidlen must be equal to 128 */ + plen0 = in6_mask2len(&ib->ia_prefixmask.sin6_addr, NULL); + if (prefixlen != plen0) { + nd6log((LOG_INFO, "in6_ifadd: wrong prefixlen for %s " + "(prefix=%d ifid=%d)\n", + if_name(ifp), prefixlen, 128 - plen0)); + return NULL; + } + + /* make ifaddr */ + + bzero(&ifra, sizeof(ifra)); + /* + * in6_update_ifa() does not use ifra_name, but we accurately set it + * for safety. + */ + strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name)); + ifra.ifra_addr.sin6_family = AF_INET6; + ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6); + /* prefix */ + bcopy(&pr->ndpr_prefix.sin6_addr, &ifra.ifra_addr.sin6_addr, + sizeof(ifra.ifra_addr.sin6_addr)); + ifra.ifra_addr.sin6_addr.s6_addr32[0] &= mask.s6_addr32[0]; + ifra.ifra_addr.sin6_addr.s6_addr32[1] &= mask.s6_addr32[1]; + ifra.ifra_addr.sin6_addr.s6_addr32[2] &= mask.s6_addr32[2]; + ifra.ifra_addr.sin6_addr.s6_addr32[3] &= mask.s6_addr32[3]; + + /* interface ID */ + if (ifid == NULL || IN6_IS_ADDR_UNSPECIFIED(ifid)) + ifid = &ib->ia_addr.sin6_addr; + ifra.ifra_addr.sin6_addr.s6_addr32[0] + |= (ifid->s6_addr32[0] & ~mask.s6_addr32[0]); + ifra.ifra_addr.sin6_addr.s6_addr32[1] + |= (ifid->s6_addr32[1] & ~mask.s6_addr32[1]); + ifra.ifra_addr.sin6_addr.s6_addr32[2] + |= (ifid->s6_addr32[2] & ~mask.s6_addr32[2]); + ifra.ifra_addr.sin6_addr.s6_addr32[3] + |= (ifid->s6_addr32[3] & ~mask.s6_addr32[3]); + + /* new prefix mask. */ + ifra.ifra_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + ifra.ifra_prefixmask.sin6_family = AF_INET6; + bcopy(&mask, &ifra.ifra_prefixmask.sin6_addr, + sizeof(ifra.ifra_prefixmask.sin6_addr)); + + /* + * lifetime. + * XXX: in6_init_address_ltimes would override these values later. + * We should reconsider this logic. + */ + ifra.ifra_lifetime.ia6t_vltime = pr->ndpr_vltime; + ifra.ifra_lifetime.ia6t_pltime = pr->ndpr_pltime; + + /* XXX: scope zone ID? */ + + ifra.ifra_flags |= IN6_IFF_AUTOCONF; /* obey autoconf */ + /* + * temporarily set the nopfx flag to avoid conflict. + * XXX: we should reconsider the entire mechanism about prefix + * manipulation. + */ + ifra.ifra_flags |= IN6_IFF_NOPFX; + + /* + * keep the new address, regardless of the result of in6_update_ifa. + * XXX: this address is now meaningless. + * We should reconsider its role. + */ + pr->ndpr_addr = ifra.ifra_addr.sin6_addr; + + /* allocate ifaddr structure, link into chain, etc. */ + if ((error = in6_update_ifa(ifp, &ifra, NULL)) != 0) { + nd6log((LOG_ERR, + "in6_ifadd: failed to make ifaddr %s on %s (errno=%d)\n", + ip6_sprintf(&ifra.ifra_addr.sin6_addr), if_name(ifp), + error)); + return(NULL); /* ifaddr must not have been allocated. */ + } + + ia = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr); + + return(ia); /* this must NOT be NULL. */ +} + +int +in6_tmpifadd(ia0, forcegen) + const struct in6_ifaddr *ia0; /* corresponding public address */ + int forcegen; +{ + struct ifnet *ifp = ia0->ia_ifa.ifa_ifp; + struct in6_ifaddr *newia; + struct in6_aliasreq ifra; + int i, error; + int trylimit = 3; /* XXX: adhoc value */ + u_int32_t randid[2]; + time_t vltime0, pltime0; + + bzero(&ifra, sizeof(ifra)); + strncpy(ifra.ifra_name, if_name(ifp), sizeof(ifra.ifra_name)); + ifra.ifra_addr = ia0->ia_addr; + /* copy prefix mask */ + ifra.ifra_prefixmask = ia0->ia_prefixmask; + /* clear the old IFID */ + for (i = 0; i < 4; i++) { + ifra.ifra_addr.sin6_addr.s6_addr32[i] + &= ifra.ifra_prefixmask.sin6_addr.s6_addr32[i]; + } + + again: + in6_get_tmpifid(ifp, (u_int8_t *)randid, + (const u_int8_t *)&ia0->ia_addr.sin6_addr.s6_addr[8], + forcegen); + ifra.ifra_addr.sin6_addr.s6_addr32[2] + |= (randid[0] & ~(ifra.ifra_prefixmask.sin6_addr.s6_addr32[2])); + ifra.ifra_addr.sin6_addr.s6_addr32[3] + |= (randid[1] & ~(ifra.ifra_prefixmask.sin6_addr.s6_addr32[3])); + + /* + * If by chance the new temporary address is the same as an address + * already assigned to the interface, generate a new randomized + * interface identifier and repeat this step. + * RFC 3041 3.3 (4). + */ + if (in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr) != NULL) { + if (trylimit-- == 0) { + nd6log((LOG_NOTICE, "in6_tmpifadd: failed to find " + "a unique random IFID\n")); + return(EEXIST); + } + forcegen = 1; + goto again; + } + + /* + * The Valid Lifetime is the lower of the Valid Lifetime of the + * public address or TEMP_VALID_LIFETIME. + * The Preferred Lifetime is the lower of the Preferred Lifetime + * of the public address or TEMP_PREFERRED_LIFETIME - + * DESYNC_FACTOR. + */ + if (ia0->ia6_lifetime.ia6t_expire != 0) { + vltime0 = IFA6_IS_INVALID(ia0) ? 0 : + (ia0->ia6_lifetime.ia6t_expire - time_second); + if (vltime0 > ip6_temp_valid_lifetime) + vltime0 = ip6_temp_valid_lifetime; + } else + vltime0 = ip6_temp_valid_lifetime; + if (ia0->ia6_lifetime.ia6t_preferred != 0) { + pltime0 = IFA6_IS_DEPRECATED(ia0) ? 0 : + (ia0->ia6_lifetime.ia6t_preferred - time_second); + if (pltime0 > ip6_temp_preferred_lifetime - ip6_desync_factor){ + pltime0 = ip6_temp_preferred_lifetime - + ip6_desync_factor; + } + } else + pltime0 = ip6_temp_preferred_lifetime - ip6_desync_factor; + ifra.ifra_lifetime.ia6t_vltime = vltime0; + ifra.ifra_lifetime.ia6t_pltime = pltime0; + + /* + * A temporary address is created only if this calculated Preferred + * Lifetime is greater than REGEN_ADVANCE time units. + */ + if (ifra.ifra_lifetime.ia6t_pltime <= ip6_temp_regen_advance) + return(0); + + /* XXX: scope zone ID? */ + + ifra.ifra_flags |= (IN6_IFF_AUTOCONF|IN6_IFF_TEMPORARY); + + /* allocate ifaddr structure, link into chain, etc. */ + if ((error = in6_update_ifa(ifp, &ifra, NULL)) != 0) + return(error); + + newia = in6ifa_ifpwithaddr(ifp, &ifra.ifra_addr.sin6_addr); + if (newia == NULL) { /* XXX: can it happen? */ + nd6log((LOG_ERR, + "in6_tmpifadd: ifa update succeeded, but we got " + "no ifaddr\n")); + return(EINVAL); /* XXX */ + } + newia->ia6_ndpr = ia0->ia6_ndpr; + newia->ia6_ndpr->ndpr_refcnt++; + + /* + * A newly added address might affect the status of other addresses. + * XXX: when the temporary address is generated with a new public + * address, the onlink check is redundant. However, it would be safe + * to do the check explicitly everywhere a new address is generated, + * and, in fact, we surely need the check when we create a new + * temporary address due to deprecation of an old temporary address. + */ + pfxlist_onlink_check(); + + return(0); +} + +int +in6_init_prefix_ltimes(struct nd_prefix *ndpr) +{ + /* check if preferred lifetime > valid lifetime. RFC2462 5.5.3 (c) */ + if (ndpr->ndpr_pltime > ndpr->ndpr_vltime) { + nd6log((LOG_INFO, "in6_init_prefix_ltimes: preferred lifetime" + "(%d) is greater than valid lifetime(%d)\n", + (u_int)ndpr->ndpr_pltime, (u_int)ndpr->ndpr_vltime)); + return (EINVAL); + } + if (ndpr->ndpr_pltime == ND6_INFINITE_LIFETIME) + ndpr->ndpr_preferred = 0; + else + ndpr->ndpr_preferred = time_second + ndpr->ndpr_pltime; + if (ndpr->ndpr_vltime == ND6_INFINITE_LIFETIME) + ndpr->ndpr_expire = 0; + else + ndpr->ndpr_expire = time_second + ndpr->ndpr_vltime; + + return 0; +} + +static void +in6_init_address_ltimes(struct nd_prefix *new, struct in6_addrlifetime *lt6) +{ + /* init ia6t_expire */ + if (lt6->ia6t_vltime == ND6_INFINITE_LIFETIME) + lt6->ia6t_expire = 0; + else { + lt6->ia6t_expire = time_second; + lt6->ia6t_expire += lt6->ia6t_vltime; + } + + /* init ia6t_preferred */ + if (lt6->ia6t_pltime == ND6_INFINITE_LIFETIME) + lt6->ia6t_preferred = 0; + else { + lt6->ia6t_preferred = time_second; + lt6->ia6t_preferred += lt6->ia6t_pltime; + } +} + +/* + * Delete all the routing table entries that use the specified gateway. + * XXX: this function causes search through all entries of routing table, so + * it shouldn't be called when acting as a router. + */ +void +rt6_flush(gateway, ifp) + struct in6_addr *gateway; + struct ifnet *ifp; +{ + struct radix_node_head *rnh = rt_tables[AF_INET6]; + int s = splnet(); + + /* We'll care only link-local addresses */ + if (!IN6_IS_ADDR_LINKLOCAL(gateway)) { + splx(s); + return; + } + /* XXX: hack for KAME's link-local address kludge */ + gateway->s6_addr16[1] = htons(ifp->if_index); + + RADIX_NODE_HEAD_LOCK(rnh); + rnh->rnh_walktree(rnh, rt6_deleteroute, (void *)gateway); + RADIX_NODE_HEAD_UNLOCK(rnh); + splx(s); +} + +static int +rt6_deleteroute(rn, arg) + struct radix_node *rn; + void *arg; +{ +#define SIN6(s) ((struct sockaddr_in6 *)s) + struct rtentry *rt = (struct rtentry *)rn; + struct in6_addr *gate = (struct in6_addr *)arg; + + if (rt->rt_gateway == NULL || rt->rt_gateway->sa_family != AF_INET6) + return(0); + + if (!IN6_ARE_ADDR_EQUAL(gate, &SIN6(rt->rt_gateway)->sin6_addr)) + return(0); + + /* + * Do not delete a static route. + * XXX: this seems to be a bit ad-hoc. Should we consider the + * 'cloned' bit instead? + */ + if ((rt->rt_flags & RTF_STATIC) != 0) + return(0); + + /* + * We delete only host route. This means, in particular, we don't + * delete default route. + */ + if ((rt->rt_flags & RTF_HOST) == 0) + return(0); + + return(rtrequest(RTM_DELETE, rt_key(rt), + rt->rt_gateway, rt_mask(rt), rt->rt_flags, 0)); +#undef SIN6 +} + +int +nd6_setdefaultiface(ifindex) + int ifindex; +{ + int error = 0; + + if (ifindex < 0 || if_index < ifindex) + return(EINVAL); + + if (nd6_defifindex != ifindex) { + nd6_defifindex = ifindex; + if (nd6_defifindex > 0) + nd6_defifp = ifnet_byindex(nd6_defifindex); + else + nd6_defifp = NULL; + + /* + * If the Default Router List is empty, install a route + * to the specified interface as default or remove the default + * route when the default interface becomes canceled. + * The check for the queue is actually redundant, but + * we do this here to avoid re-install the default route + * if the list is NOT empty. + */ + if (TAILQ_FIRST(&nd_defrouter) == NULL) + defrouter_select(); + + /* + * Our current implementation assumes one-to-one maping between + * interfaces and links, so it would be natural to use the + * default interface as the default link. + */ + scope6_setdefault(nd6_defifp); + } + + return(error); +} diff --git a/sys/netinet6/pim6.h b/sys/netinet6/pim6.h new file mode 100644 index 0000000..d420db9 --- /dev/null +++ b/sys/netinet6/pim6.h @@ -0,0 +1,69 @@ +/* $FreeBSD$ */ +/* $KAME: pim6.h,v 1.3 2000/03/25 07:23:58 sumikawa Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ +/* + * Protocol Independent Multicast (PIM) definitions + * + * Written by Ahmed Helmy, SGI, July 1996 + * + * MULTICAST + */ + +/* + * PIM packet header + */ +#define PIM_VERSION 2 +struct pim { +#if defined(BYTE_ORDER) && (BYTE_ORDER == LITTLE_ENDIAN) + u_char pim_type:4, /* the PIM message type, currently they are: + * Hello, Register, Register-Stop, Join/Prune, + * Bootstrap, Assert, Graft (PIM-DM only), + * Graft-Ack (PIM-DM only), C-RP-Adv + */ + pim_ver:4; /* PIM version number; 2 for PIMv2 */ +#else + u_char pim_ver:4, /* PIM version */ + pim_type:4; /* PIM type */ +#endif + u_char pim_rsv; /* Reserved */ + u_short pim_cksum; /* IP style check sum */ +}; + +#define PIM_MINLEN 8 /* The header min. length is 8 */ +#define PIM6_REG_MINLEN (PIM_MINLEN+40) /* Register message + inner IP6 header */ + +/* + * Message types + */ +#define PIM_REGISTER 1 /* PIM Register type is 1 */ + +/* second bit in reg_head is the null bit */ +#define PIM_NULL_REGISTER 0x40000000 diff --git a/sys/netinet6/pim6_var.h b/sys/netinet6/pim6_var.h new file mode 100644 index 0000000..358a195d --- /dev/null +++ b/sys/netinet6/pim6_var.h @@ -0,0 +1,70 @@ +/* $FreeBSD$ */ +/* $KAME: pim6_var.h,v 1.8 2000/06/06 08:07:43 jinmei Exp $ */ + +/* + * Copyright (C) 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_PIM6_VAR_H_ +#define _NETINET6_PIM6_VAR_H_ + +/* + * Protocol Independent Multicast (PIM), + * implementation-specific definitions. + * + * Written by George Edmond Eddy (Rusty), ISI, February 1998 + * Modified by Pavlin Ivanov Radoslavov, USC/ISI, May 1998 + */ + +struct pim6stat { + u_quad_t pim6s_rcv_total; /* total PIM messages received */ + u_quad_t pim6s_rcv_tooshort; /* received with too few bytes */ + u_quad_t pim6s_rcv_badsum; /* received with bad checksum */ + u_quad_t pim6s_rcv_badversion; /* received bad PIM version */ + u_quad_t pim6s_rcv_registers; /* received registers */ + u_quad_t pim6s_rcv_badregisters; /* received invalid registers */ + u_quad_t pim6s_snd_registers; /* sent registers */ +}; + +#if (defined(KERNEL)) || (defined(_KERNEL)) +extern struct pim6stat pim6stat; + +int pim6_input __P((struct mbuf **, int*, int)); +#endif /* KERNEL */ + +/* + * Names for PIM sysctl objects + */ +#define PIM6CTL_STATS 1 /* statistics (read-only) */ +#define PIM6CTL_MAXID 2 + +#define PIM6CTL_NAMES { \ + { 0, 0 }, \ + { 0, 0 }, \ +} +#endif /* _NETINET6_PIM6_VAR_H_ */ diff --git a/sys/netinet6/raw_ip6.c b/sys/netinet6/raw_ip6.c new file mode 100644 index 0000000..728da31 --- /dev/null +++ b/sys/netinet6/raw_ip6.c @@ -0,0 +1,743 @@ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)raw_ip.c 8.2 (Berkeley) 1/4/94 + */ + +#include "opt_ipsec.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/protosw.h> +#include <sys/signalvar.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sx.h> +#include <sys/systm.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/icmp6.h> +#include <netinet/in_pcb.h> +#include <netinet/ip6.h> +#include <netinet6/ip6protosw.h> +#include <netinet6/ip6_mroute.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/ip6_var.h> +#include <netinet6/nd6.h> +#include <netinet6/raw_ip6.h> +#ifdef ENABLE_DEFAULT_SCOPE +#include <netinet6/scope6_var.h> +#endif + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#include <netinet6/ipsec6.h> +#endif /*IPSEC*/ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#endif /* FAST_IPSEC */ + +#include <machine/stdarg.h> + +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) +#define ifatoia6(ifa) ((struct in6_ifaddr *)(ifa)) + +/* + * Raw interface to IP6 protocol. + */ + +extern struct inpcbhead ripcb; +extern struct inpcbinfo ripcbinfo; +extern u_long rip_sendspace; +extern u_long rip_recvspace; + +struct rip6stat rip6stat; + +/* + * Setup generic address and protocol structures + * for raw_input routine, then pass them along with + * mbuf chain. + */ +int +rip6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + register struct ip6_hdr *ip6 = mtod(m, struct ip6_hdr *); + register struct inpcb *in6p; + struct inpcb *last = 0; + struct mbuf *opts = NULL; + struct sockaddr_in6 rip6src; + + rip6stat.rip6s_ipackets++; + + if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { + /* XXX send icmp6 host/port unreach? */ + m_freem(m); + return IPPROTO_DONE; + } + + init_sin6(&rip6src, m); /* general init */ + + LIST_FOREACH(in6p, &ripcb, inp_list) { + if ((in6p->in6p_vflag & INP_IPV6) == 0) + continue; + if (in6p->in6p_ip6_nxt && + in6p->in6p_ip6_nxt != proto) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) && + !IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, &ip6->ip6_dst)) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) && + !IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src)) + continue; + if (in6p->in6p_cksum != -1) { + rip6stat.rip6s_isum++; + if (in6_cksum(m, ip6->ip6_nxt, *offp, + m->m_pkthdr.len - *offp)) { + rip6stat.rip6s_badsum++; + continue; + } + } + if (last) { + struct mbuf *n = m_copy(m, 0, (int)M_COPYALL); + +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + if (n && ipsec6_in_reject(n, last)) { + m_freem(n); + ipsec6stat.in_polvio++; + /* do not inject data into pcb */ + } else +#endif /*IPSEC*/ +#ifdef FAST_IPSEC + /* + * Check AH/ESP integrity. + */ + if (n && ipsec6_in_reject(n, last)) { + m_freem(n); + /* do not inject data into pcb */ + } else +#endif /*FAST_IPSEC*/ + if (n) { + if (last->in6p_flags & IN6P_CONTROLOPTS || + last->in6p_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(last, &opts, ip6, n); + /* strip intermediate headers */ + m_adj(n, *offp); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&rip6src, + n, opts) == 0) { + m_freem(n); + if (opts) + m_freem(opts); + rip6stat.rip6s_fullsock++; + } else + sorwakeup(last->in6p_socket); + opts = NULL; + } + } + last = in6p; + } +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + if (last && ipsec6_in_reject(m, last)) { + m_freem(m); + ipsec6stat.in_polvio++; + ip6stat.ip6s_delivered--; + /* do not inject data into pcb */ + } else +#endif /*IPSEC*/ +#ifdef FAST_IPSEC + /* + * Check AH/ESP integrity. + */ + if (last && ipsec6_in_reject(m, last)) { + m_freem(m); + ip6stat.ip6s_delivered--; + /* do not inject data into pcb */ + } else +#endif /*FAST_IPSEC*/ + if (last) { + if (last->in6p_flags & IN6P_CONTROLOPTS || + last->in6p_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(last, &opts, ip6, m); + /* strip intermediate headers */ + m_adj(m, *offp); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&rip6src, m, opts) == 0) { + m_freem(m); + if (opts) + m_freem(opts); + rip6stat.rip6s_fullsock++; + } else + sorwakeup(last->in6p_socket); + } else { + rip6stat.rip6s_nosock++; + if (m->m_flags & M_MCAST) + rip6stat.rip6s_nosockmcast++; + if (proto == IPPROTO_NONE) + m_freem(m); + else { + char *prvnxtp = ip6_get_prevhdr(m, *offp); /* XXX */ + icmp6_error(m, ICMP6_PARAM_PROB, + ICMP6_PARAMPROB_NEXTHEADER, + prvnxtp - mtod(m, char *)); + } + ip6stat.ip6s_delivered--; + } + return IPPROTO_DONE; +} + +void +rip6_ctlinput(cmd, sa, d) + int cmd; + struct sockaddr *sa; + void *d; +{ + struct ip6_hdr *ip6; + struct mbuf *m; + int off = 0; + struct ip6ctlparam *ip6cp = NULL; + const struct sockaddr_in6 *sa6_src = NULL; + struct inpcb *(*notify) __P((struct inpcb *, int)) = in6_rtchange; + + if (sa->sa_family != AF_INET6 || + sa->sa_len != sizeof(struct sockaddr_in6)) + return; + + if ((unsigned)cmd >= PRC_NCMDS) + return; + if (PRC_IS_REDIRECT(cmd)) + notify = in6_rtchange, d = NULL; + else if (cmd == PRC_HOSTDEAD) + d = NULL; + else if (inet6ctlerrmap[cmd] == 0) + return; + + /* if the parameter is from icmp6, decode it. */ + if (d != NULL) { + ip6cp = (struct ip6ctlparam *)d; + m = ip6cp->ip6c_m; + ip6 = ip6cp->ip6c_ip6; + off = ip6cp->ip6c_off; + sa6_src = ip6cp->ip6c_src; + } else { + m = NULL; + ip6 = NULL; + sa6_src = &sa6_any; + } + + (void) in6_pcbnotify(&ripcb, sa, 0, (const struct sockaddr *)sa6_src, + 0, cmd, notify); +} + +/* + * Generate IPv6 header and pass packet to ip6_output. + * Tack on options user may have setup with control call. + */ +int +#if __STDC__ +rip6_output(struct mbuf *m, ...) +#else +rip6_output(m, va_alist) + struct mbuf *m; + va_dcl +#endif +{ + struct socket *so; + struct sockaddr_in6 *dstsock; + struct mbuf *control; + struct in6_addr *dst; + struct ip6_hdr *ip6; + struct inpcb *in6p; + u_int plen = m->m_pkthdr.len; + int error = 0; + struct ip6_pktopts opt, *optp = 0; + struct ifnet *oifp = NULL; + int type = 0, code = 0; /* for ICMPv6 output statistics only */ + int priv = 0; + va_list ap; + + va_start(ap, m); + so = va_arg(ap, struct socket *); + dstsock = va_arg(ap, struct sockaddr_in6 *); + control = va_arg(ap, struct mbuf *); + va_end(ap); + + in6p = sotoin6pcb(so); + + priv = 0; + if (so->so_cred->cr_uid == 0) + priv = 1; + dst = &dstsock->sin6_addr; + if (control) { + if ((error = ip6_setpktoptions(control, &opt, priv, 0)) != 0) + goto bad; + optp = &opt; + } else + optp = in6p->in6p_outputopts; + + /* + * For an ICMPv6 packet, we should know its type and code + * to update statistics. + */ + if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) { + struct icmp6_hdr *icmp6; + if (m->m_len < sizeof(struct icmp6_hdr) && + (m = m_pullup(m, sizeof(struct icmp6_hdr))) == NULL) { + error = ENOBUFS; + goto bad; + } + icmp6 = mtod(m, struct icmp6_hdr *); + type = icmp6->icmp6_type; + code = icmp6->icmp6_code; + } + + M_PREPEND(m, sizeof(*ip6), M_TRYWAIT); + ip6 = mtod(m, struct ip6_hdr *); + + /* + * Next header might not be ICMP6 but use its pseudo header anyway. + */ + ip6->ip6_dst = *dst; + + /* + * If the scope of the destination is link-local, embed the interface + * index in the address. + * + * XXX advanced-api value overrides sin6_scope_id + */ + if (IN6_IS_SCOPE_LINKLOCAL(&ip6->ip6_dst)) { + struct in6_pktinfo *pi; + + /* + * XXX Boundary check is assumed to be already done in + * ip6_setpktoptions(). + */ + if (optp && (pi = optp->ip6po_pktinfo) && pi->ipi6_ifindex) { + ip6->ip6_dst.s6_addr16[1] = htons(pi->ipi6_ifindex); + oifp = ifnet_byindex(pi->ipi6_ifindex); + } else if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) && + in6p->in6p_moptions && + in6p->in6p_moptions->im6o_multicast_ifp) { + oifp = in6p->in6p_moptions->im6o_multicast_ifp; + ip6->ip6_dst.s6_addr16[1] = htons(oifp->if_index); + } else if (dstsock->sin6_scope_id) { + /* boundary check */ + if (dstsock->sin6_scope_id < 0 + || if_index < dstsock->sin6_scope_id) { + error = ENXIO; /* XXX EINVAL? */ + goto bad; + } + ip6->ip6_dst.s6_addr16[1] + = htons(dstsock->sin6_scope_id & 0xffff);/*XXX*/ + } + } + + /* + * Source address selection. + */ + { + struct in6_addr *in6a; + + if ((in6a = in6_selectsrc(dstsock, optp, + in6p->in6p_moptions, + &in6p->in6p_route, + &in6p->in6p_laddr, + &error)) == 0) { + if (error == 0) + error = EADDRNOTAVAIL; + goto bad; + } + ip6->ip6_src = *in6a; + if (in6p->in6p_route.ro_rt) + oifp = ifnet_byindex(in6p->in6p_route.ro_rt->rt_ifp->if_index); + } + ip6->ip6_flow = (ip6->ip6_flow & ~IPV6_FLOWINFO_MASK) | + (in6p->in6p_flowinfo & IPV6_FLOWINFO_MASK); + ip6->ip6_vfc = (ip6->ip6_vfc & ~IPV6_VERSION_MASK) | + (IPV6_VERSION & IPV6_VERSION_MASK); + /* ip6_plen will be filled in ip6_output, so not fill it here. */ + ip6->ip6_nxt = in6p->in6p_ip6_nxt; + ip6->ip6_hlim = in6_selecthlim(in6p, oifp); + + if (so->so_proto->pr_protocol == IPPROTO_ICMPV6 || + in6p->in6p_cksum != -1) { + struct mbuf *n; + int off; + u_int16_t *p; + + /* compute checksum */ + if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) + off = offsetof(struct icmp6_hdr, icmp6_cksum); + else + off = in6p->in6p_cksum; + if (plen < off + 1) { + error = EINVAL; + goto bad; + } + off += sizeof(struct ip6_hdr); + + n = m; + while (n && n->m_len <= off) { + off -= n->m_len; + n = n->m_next; + } + if (!n) + goto bad; + p = (u_int16_t *)(mtod(n, caddr_t) + off); + *p = 0; + *p = in6_cksum(m, ip6->ip6_nxt, sizeof(*ip6), plen); + } + + error = ip6_output(m, optp, &in6p->in6p_route, 0, + in6p->in6p_moptions, &oifp, in6p); + if (so->so_proto->pr_protocol == IPPROTO_ICMPV6) { + if (oifp) + icmp6_ifoutstat_inc(oifp, type, code); + icmp6stat.icp6s_outhist[type]++; + } else + rip6stat.rip6s_opackets++; + + goto freectl; + + bad: + if (m) + m_freem(m); + + freectl: + if (optp == &opt && optp->ip6po_rthdr && optp->ip6po_route.ro_rt) + RTFREE(optp->ip6po_route.ro_rt); + if (control) { + if (optp == &opt) + ip6_clearpktopts(optp, 0, -1); + m_freem(control); + } + return(error); +} + +/* + * Raw IPv6 socket option processing. + */ +int +rip6_ctloutput(so, sopt) + struct socket *so; + struct sockopt *sopt; +{ + int error; + + if (sopt->sopt_level == IPPROTO_ICMPV6) + /* + * XXX: is it better to call icmp6_ctloutput() directly + * from protosw? + */ + return(icmp6_ctloutput(so, sopt)); + else if (sopt->sopt_level != IPPROTO_IPV6) + return (EINVAL); + + error = 0; + + switch (sopt->sopt_dir) { + case SOPT_GET: + switch (sopt->sopt_name) { + case MRT6_INIT: + case MRT6_DONE: + case MRT6_ADD_MIF: + case MRT6_DEL_MIF: + case MRT6_ADD_MFC: + case MRT6_DEL_MFC: + case MRT6_PIM: + error = ip6_mrouter_get(so, sopt); + break; + default: + error = ip6_ctloutput(so, sopt); + break; + } + break; + + case SOPT_SET: + switch (sopt->sopt_name) { + case MRT6_INIT: + case MRT6_DONE: + case MRT6_ADD_MIF: + case MRT6_DEL_MIF: + case MRT6_ADD_MFC: + case MRT6_DEL_MFC: + case MRT6_PIM: + error = ip6_mrouter_set(so, sopt); + break; + default: + error = ip6_ctloutput(so, sopt); + break; + } + break; + } + + return (error); +} + +static int +rip6_attach(struct socket *so, int proto, struct thread *td) +{ + struct inpcb *inp; + int error, s; + + inp = sotoinpcb(so); + if (inp) + panic("rip6_attach"); + if (td && (error = suser(td)) != 0) + return error; + + error = soreserve(so, rip_sendspace, rip_recvspace); + if (error) + return error; + s = splnet(); + error = in_pcballoc(so, &ripcbinfo, td); + splx(s); + if (error) + return error; + inp = (struct inpcb *)so->so_pcb; + inp->inp_vflag |= INP_IPV6; + inp->in6p_ip6_nxt = (long)proto; + inp->in6p_hops = -1; /* use kernel default */ + inp->in6p_cksum = -1; + MALLOC(inp->in6p_icmp6filt, struct icmp6_filter *, + sizeof(struct icmp6_filter), M_PCB, M_NOWAIT); + ICMP6_FILTER_SETPASSALL(inp->in6p_icmp6filt); + return 0; +} + +static int +rip6_detach(struct socket *so) +{ + struct inpcb *inp; + + inp = sotoinpcb(so); + if (inp == 0) + panic("rip6_detach"); + /* xxx: RSVP */ + if (so == ip6_mrouter) + ip6_mrouter_done(); + if (inp->in6p_icmp6filt) { + FREE(inp->in6p_icmp6filt, M_PCB); + inp->in6p_icmp6filt = NULL; + } + in6_pcbdetach(inp); + return 0; +} + +static int +rip6_abort(struct socket *so) +{ + soisdisconnected(so); + return rip6_detach(so); +} + +static int +rip6_disconnect(struct socket *so) +{ + struct inpcb *inp = sotoinpcb(so); + + if ((so->so_state & SS_ISCONNECTED) == 0) + return ENOTCONN; + inp->in6p_faddr = in6addr_any; + return rip6_abort(so); +} + +static int +rip6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp = sotoinpcb(so); + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam; + struct ifaddr *ia = NULL; + + if (nam->sa_len != sizeof(*addr)) + return EINVAL; + + if (TAILQ_EMPTY(&ifnet) || addr->sin6_family != AF_INET6) + return EADDRNOTAVAIL; +#ifdef ENABLE_DEFAULT_SCOPE + if (addr->sin6_scope_id == 0) { /* not change if specified */ + addr->sin6_scope_id = scope6_addr2default(&addr->sin6_addr); + } +#endif + if (!IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr) && + (ia = ifa_ifwithaddr((struct sockaddr *)addr)) == 0) + return EADDRNOTAVAIL; + if (ia && + ((struct in6_ifaddr *)ia)->ia6_flags & + (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY| + IN6_IFF_DETACHED|IN6_IFF_DEPRECATED)) { + return(EADDRNOTAVAIL); + } + inp->in6p_laddr = addr->sin6_addr; + return 0; +} + +static int +rip6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp = sotoinpcb(so); + struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam; + struct in6_addr *in6a = NULL; + int error = 0; +#ifdef ENABLE_DEFAULT_SCOPE + struct sockaddr_in6 tmp; +#endif + + if (nam->sa_len != sizeof(*addr)) + return EINVAL; + if (TAILQ_EMPTY(&ifnet)) + return EADDRNOTAVAIL; + if (addr->sin6_family != AF_INET6) + return EAFNOSUPPORT; +#ifdef ENABLE_DEFAULT_SCOPE + if (addr->sin6_scope_id == 0) { /* not change if specified */ + /* avoid overwrites */ + tmp = *addr; + addr = &tmp; + addr->sin6_scope_id = scope6_addr2default(&addr->sin6_addr); + } +#endif + /* Source address selection. XXX: need pcblookup? */ + in6a = in6_selectsrc(addr, inp->in6p_outputopts, + inp->in6p_moptions, &inp->in6p_route, + &inp->in6p_laddr, &error); + if (in6a == NULL) + return (error ? error : EADDRNOTAVAIL); + inp->in6p_laddr = *in6a; + inp->in6p_faddr = addr->sin6_addr; + soisconnected(so); + return 0; +} + +static int +rip6_shutdown(struct socket *so) +{ + socantsendmore(so); + return 0; +} + +static int +rip6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *nam, + struct mbuf *control, struct thread *td) +{ + struct inpcb *inp = sotoinpcb(so); + struct sockaddr_in6 tmp; + struct sockaddr_in6 *dst; + + /* always copy sockaddr to avoid overwrites */ + if (so->so_state & SS_ISCONNECTED) { + if (nam) { + m_freem(m); + return EISCONN; + } + /* XXX */ + bzero(&tmp, sizeof(tmp)); + tmp.sin6_family = AF_INET6; + tmp.sin6_len = sizeof(struct sockaddr_in6); + bcopy(&inp->in6p_faddr, &tmp.sin6_addr, + sizeof(struct in6_addr)); + dst = &tmp; + } else { + if (nam == NULL) { + m_freem(m); + return ENOTCONN; + } + tmp = *(struct sockaddr_in6 *)nam; + dst = &tmp; + } +#ifdef ENABLE_DEFAULT_SCOPE + if (dst->sin6_scope_id == 0) { /* not change if specified */ + dst->sin6_scope_id = scope6_addr2default(&dst->sin6_addr); + } +#endif + return rip6_output(m, so, dst, control); +} + +struct pr_usrreqs rip6_usrreqs = { + rip6_abort, pru_accept_notsupp, rip6_attach, rip6_bind, rip6_connect, + pru_connect2_notsupp, in6_control, rip6_detach, rip6_disconnect, + pru_listen_notsupp, in6_setpeeraddr, pru_rcvd_notsupp, + pru_rcvoob_notsupp, rip6_send, pru_sense_null, rip6_shutdown, + in6_setsockaddr, sosend, soreceive, sopoll +}; diff --git a/sys/netinet6/raw_ip6.h b/sys/netinet6/raw_ip6.h new file mode 100644 index 0000000..cfb390b --- /dev/null +++ b/sys/netinet6/raw_ip6.h @@ -0,0 +1,54 @@ +/* $FreeBSD$ */ +/* $KAME: raw_ip6.h,v 1.2 2001/05/27 13:28:35 itojun Exp $ */ + +/* + * Copyright (C) 2001 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_RAW_IP6_H_ +#define _NETINET6_RAW_IP6_H_ + +/* + * ICMPv6 stat is counted separately. see netinet/icmp6.h + */ +struct rip6stat { + u_quad_t rip6s_ipackets; /* total input packets */ + u_quad_t rip6s_isum; /* input checksum computations */ + u_quad_t rip6s_badsum; /* of above, checksum error */ + u_quad_t rip6s_nosock; /* no matching socket */ + u_quad_t rip6s_nosockmcast; /* of above, arrived as multicast */ + u_quad_t rip6s_fullsock; /* not delivered, input socket full */ + + u_quad_t rip6s_opackets; /* total output packets */ +}; + +#ifdef _KERNEL +extern struct rip6stat rip6stat; +#endif + +#endif diff --git a/sys/netinet6/route6.c b/sys/netinet6/route6.c new file mode 100644 index 0000000..f2575bc --- /dev/null +++ b/sys/netinet6/route6.c @@ -0,0 +1,221 @@ +/* $FreeBSD$ */ +/* $KAME: route6.c,v 1.24 2001/03/14 03:07:05 itojun Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/queue.h> + +#include <net/if.h> + +#include <netinet/in.h> +#include <netinet6/in6_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> + +#include <netinet/icmp6.h> + +static int ip6_rthdr0 __P((struct mbuf *, struct ip6_hdr *, + struct ip6_rthdr0 *)); + +int +route6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; /* proto is unused */ +{ + struct ip6_hdr *ip6; + struct mbuf *m = *mp; + struct ip6_rthdr *rh; + int off = *offp, rhlen; + struct ip6aux *ip6a; + + ip6a = ip6_findaux(m); + if (ip6a) { + /* XXX reject home-address option before rthdr */ + if (ip6a->ip6a_flags & IP6A_SWAP) { + ip6stat.ip6s_badoptions++; + m_freem(m); + return IPPROTO_DONE; + } + } + +#ifndef PULLDOWN_TEST + IP6_EXTHDR_CHECK(m, off, sizeof(*rh), IPPROTO_DONE); + ip6 = mtod(m, struct ip6_hdr *); + rh = (struct ip6_rthdr *)((caddr_t)ip6 + off); +#else + ip6 = mtod(m, struct ip6_hdr *); + IP6_EXTHDR_GET(rh, struct ip6_rthdr *, m, off, sizeof(*rh)); + if (rh == NULL) { + ip6stat.ip6s_tooshort++; + return IPPROTO_DONE; + } +#endif + + switch (rh->ip6r_type) { + case IPV6_RTHDR_TYPE_0: + rhlen = (rh->ip6r_len + 1) << 3; +#ifndef PULLDOWN_TEST + /* + * note on option length: + * due to IP6_EXTHDR_CHECK assumption, we cannot handle + * very big routing header (max rhlen == 2048). + */ + IP6_EXTHDR_CHECK(m, off, rhlen, IPPROTO_DONE); +#else + /* + * note on option length: + * maximum rhlen: 2048 + * max mbuf m_pulldown can handle: MCLBYTES == usually 2048 + * so, here we are assuming that m_pulldown can handle + * rhlen == 2048 case. this may not be a good thing to + * assume - we may want to avoid pulling it up altogether. + */ + IP6_EXTHDR_GET(rh, struct ip6_rthdr *, m, off, rhlen); + if (rh == NULL) { + ip6stat.ip6s_tooshort++; + return IPPROTO_DONE; + } +#endif + if (ip6_rthdr0(m, ip6, (struct ip6_rthdr0 *)rh)) + return(IPPROTO_DONE); + break; + default: + /* unknown routing type */ + if (rh->ip6r_segleft == 0) { + rhlen = (rh->ip6r_len + 1) << 3; + break; /* Final dst. Just ignore the header. */ + } + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, + (caddr_t)&rh->ip6r_type - (caddr_t)ip6); + return(IPPROTO_DONE); + } + + *offp += rhlen; + return(rh->ip6r_nxt); +} + +/* + * Type0 routing header processing + * + * RFC2292 backward compatibility warning: no support for strict/loose bitmap, + * as it was dropped between RFC1883 and RFC2460. + */ +static int +ip6_rthdr0(m, ip6, rh0) + struct mbuf *m; + struct ip6_hdr *ip6; + struct ip6_rthdr0 *rh0; +{ + int addrs, index; + struct in6_addr *nextaddr, tmpaddr; + + if (rh0->ip6r0_segleft == 0) + return(0); + + if (rh0->ip6r0_len % 2 +#ifdef COMPAT_RFC1883 + || rh0->ip6r0_len > 46 +#endif + ) { + /* + * Type 0 routing header can't contain more than 23 addresses. + * RFC 2462: this limitation was removed since strict/loose + * bitmap field was deleted. + */ + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, + (caddr_t)&rh0->ip6r0_len - (caddr_t)ip6); + return(-1); + } + + if ((addrs = rh0->ip6r0_len / 2) < rh0->ip6r0_segleft) { + ip6stat.ip6s_badoptions++; + icmp6_error(m, ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER, + (caddr_t)&rh0->ip6r0_segleft - (caddr_t)ip6); + return(-1); + } + + index = addrs - rh0->ip6r0_segleft; + rh0->ip6r0_segleft--; + /* note that ip6r0_addr does not exist in RFC2292bis */ + nextaddr = rh0->ip6r0_addr + index; + + /* + * reject invalid addresses. be proactive about malicious use of + * IPv4 mapped/compat address. + * XXX need more checks? + */ + if (IN6_IS_ADDR_MULTICAST(nextaddr) || + IN6_IS_ADDR_UNSPECIFIED(nextaddr) || + IN6_IS_ADDR_V4MAPPED(nextaddr) || + IN6_IS_ADDR_V4COMPAT(nextaddr)) { + ip6stat.ip6s_badoptions++; + m_freem(m); + return(-1); + } + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) || + IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst) || + IN6_IS_ADDR_V4MAPPED(&ip6->ip6_dst) || + IN6_IS_ADDR_V4COMPAT(&ip6->ip6_dst)) { + ip6stat.ip6s_badoptions++; + m_freem(m); + return(-1); + } + + /* + * Swap the IPv6 destination address and nextaddr. Forward the packet. + */ + tmpaddr = *nextaddr; + *nextaddr = ip6->ip6_dst; + if (IN6_IS_ADDR_LINKLOCAL(nextaddr)) + nextaddr->s6_addr16[1] = 0; + ip6->ip6_dst = tmpaddr; + if (IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst)) + ip6->ip6_dst.s6_addr16[1] = htons(m->m_pkthdr.rcvif->if_index); + +#ifdef COMPAT_RFC1883 + if (rh0->ip6r0_slmap[index / 8] & (1 << (7 - (index % 8)))) + ip6_forward(m, IPV6_SRCRT_NEIGHBOR); + else + ip6_forward(m, IPV6_SRCRT_NOTNEIGHBOR); +#else + ip6_forward(m, 1); +#endif + + return(-1); /* m would be freed in ip6_forward() */ +} diff --git a/sys/netinet6/scope6.c b/sys/netinet6/scope6.c new file mode 100644 index 0000000..0c32da8 --- /dev/null +++ b/sys/netinet6/scope6.c @@ -0,0 +1,303 @@ +/* $FreeBSD$ */ +/* $KAME: scope6.c,v 1.10 2000/07/24 13:29:31 itojun Exp $ */ + +/* + * Copyright (C) 2000 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#include <sys/param.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/systm.h> +#include <sys/queue.h> + +#include <net/route.h> +#include <net/if.h> + +#include <netinet/in.h> + +#include <netinet6/in6_var.h> +#include <netinet6/scope6_var.h> + +struct scope6_id { + /* + * 16 is correspondent to 4bit multicast scope field. + * i.e. from node-local to global with some reserved/unassigned types. + */ + u_int32_t s6id_list[16]; +}; +static size_t if_indexlim = 8; +struct scope6_id *scope6_ids = NULL; + +void +scope6_ifattach(ifp) + struct ifnet *ifp; +{ + int s = splnet(); + + /* + * We have some arrays that should be indexed by if_index. + * since if_index will grow dynamically, they should grow too. + */ + if (scope6_ids == NULL || if_index >= if_indexlim) { + size_t n; + caddr_t q; + + while (if_index >= if_indexlim) + if_indexlim <<= 1; + + /* grow scope index array */ + n = if_indexlim * sizeof(struct scope6_id); + /* XXX: need new malloc type? */ + q = (caddr_t)malloc(n, M_IFADDR, M_WAITOK); + bzero(q, n); + if (scope6_ids) { + bcopy((caddr_t)scope6_ids, q, n/2); + free((caddr_t)scope6_ids, M_IFADDR); + } + scope6_ids = (struct scope6_id *)q; + } + +#define SID scope6_ids[ifp->if_index] + + /* don't initialize if called twice */ + if (SID.s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL]) { + splx(s); + return; + } + + /* + * XXX: IPV6_ADDR_SCOPE_xxx macros are not standard. + * Should we rather hardcode here? + */ + SID.s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = ifp->if_index; +#ifdef MULTI_SCOPE + /* by default, we don't care about scope boundary for these scopes. */ + SID.s6id_list[IPV6_ADDR_SCOPE_SITELOCAL] = 1; + SID.s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL] = 1; +#endif +#undef SID + + splx(s); +} + +int +scope6_set(ifp, idlist) + struct ifnet *ifp; + u_int32_t *idlist; +{ + int i, s; + int error = 0; + + if (scope6_ids == NULL) /* paranoid? */ + return(EINVAL); + + /* + * XXX: We need more consistency checks of the relationship among + * scopes (e.g. an organization should be larger than a site). + */ + + /* + * TODO(XXX): after setting, we should reflect the changes to + * interface addresses, routing table entries, PCB entries... + */ + + s = splnet(); + + for (i = 0; i < 16; i++) { + if (idlist[i] && + idlist[i] != scope6_ids[ifp->if_index].s6id_list[i]) { + if (i == IPV6_ADDR_SCOPE_LINKLOCAL && + idlist[i] > if_index) { + /* + * XXX: theoretically, there should be no + * relationship between link IDs and interface + * IDs, but we check the consistency for + * safety in later use. + */ + splx(s); + return(EINVAL); + } + + /* + * XXX: we must need lots of work in this case, + * but we simply set the new value in this initial + * implementation. + */ + scope6_ids[ifp->if_index].s6id_list[i] = idlist[i]; + } + } + splx(s); + + return(error); +} + +int +scope6_get(ifp, idlist) + struct ifnet *ifp; + u_int32_t *idlist; +{ + if (scope6_ids == NULL) /* paranoid? */ + return(EINVAL); + + bcopy(scope6_ids[ifp->if_index].s6id_list, idlist, + sizeof(scope6_ids[ifp->if_index].s6id_list)); + + return(0); +} + + +/* + * Get a scope of the address. Node-local, link-local, site-local or global. + */ +int +in6_addrscope(addr) +struct in6_addr *addr; +{ + int scope; + + if (addr->s6_addr8[0] == 0xfe) { + scope = addr->s6_addr8[1] & 0xc0; + + switch (scope) { + case 0x80: + return IPV6_ADDR_SCOPE_LINKLOCAL; + break; + case 0xc0: + return IPV6_ADDR_SCOPE_SITELOCAL; + break; + default: + return IPV6_ADDR_SCOPE_GLOBAL; /* just in case */ + break; + } + } + + + if (addr->s6_addr8[0] == 0xff) { + scope = addr->s6_addr8[1] & 0x0f; + + /* + * due to other scope such as reserved, + * return scope doesn't work. + */ + switch (scope) { + case IPV6_ADDR_SCOPE_NODELOCAL: + return IPV6_ADDR_SCOPE_NODELOCAL; + break; + case IPV6_ADDR_SCOPE_LINKLOCAL: + return IPV6_ADDR_SCOPE_LINKLOCAL; + break; + case IPV6_ADDR_SCOPE_SITELOCAL: + return IPV6_ADDR_SCOPE_SITELOCAL; + break; + default: + return IPV6_ADDR_SCOPE_GLOBAL; + break; + } + } + + if (bcmp(&in6addr_loopback, addr, sizeof(*addr) - 1) == 0) { + if (addr->s6_addr8[15] == 1) /* loopback */ + return IPV6_ADDR_SCOPE_NODELOCAL; + if (addr->s6_addr8[15] == 0) /* unspecified */ + return IPV6_ADDR_SCOPE_LINKLOCAL; + } + + return IPV6_ADDR_SCOPE_GLOBAL; +} + +int +in6_addr2scopeid(ifp, addr) + struct ifnet *ifp; /* must not be NULL */ + struct in6_addr *addr; /* must not be NULL */ +{ + int scope = in6_addrscope(addr); + + if (scope6_ids == NULL) /* paranoid? */ + return(0); /* XXX */ + if (ifp->if_index >= if_indexlim) + return(0); /* XXX */ + +#define SID scope6_ids[ifp->if_index] + switch(scope) { + case IPV6_ADDR_SCOPE_NODELOCAL: + return(-1); /* XXX: is this an appropriate value? */ + + case IPV6_ADDR_SCOPE_LINKLOCAL: + return(SID.s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL]); + + case IPV6_ADDR_SCOPE_SITELOCAL: + return(SID.s6id_list[IPV6_ADDR_SCOPE_SITELOCAL]); + + case IPV6_ADDR_SCOPE_ORGLOCAL: + return(SID.s6id_list[IPV6_ADDR_SCOPE_ORGLOCAL]); + + default: + return(0); /* XXX: treat as global. */ + } +#undef SID +} + +void +scope6_setdefault(ifp) + struct ifnet *ifp; /* note that this might be NULL */ +{ + /* + * Currently, this function just set the default "link" according to + * the given interface. + * We might eventually have to separate the notion of "link" from + * "interface" and provide a user interface to set the default. + */ + if (ifp) { + scope6_ids[0].s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = + ifp->if_index; + } + else + scope6_ids[0].s6id_list[IPV6_ADDR_SCOPE_LINKLOCAL] = 0; +} + +int +scope6_get_default(idlist) + u_int32_t *idlist; +{ + if (scope6_ids == NULL) /* paranoid? */ + return(EINVAL); + + bcopy(scope6_ids[0].s6id_list, idlist, + sizeof(scope6_ids[0].s6id_list)); + + return(0); +} + +u_int32_t +scope6_addr2default(addr) + struct in6_addr *addr; +{ + return(scope6_ids[0].s6id_list[in6_addrscope(addr)]); +} diff --git a/sys/netinet6/scope6_var.h b/sys/netinet6/scope6_var.h new file mode 100644 index 0000000..6e107d7 --- /dev/null +++ b/sys/netinet6/scope6_var.h @@ -0,0 +1,46 @@ +/* $FreeBSD$ */ +/* $KAME: scope6_var.h,v 1.4 2000/05/18 15:03:27 jinmei Exp $ */ + +/* + * Copyright (C) 2000 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +#ifndef _NETINET6_SCOPE6_VAR_H_ +#define _NETINET6_SCOPE6_VAR_H_ + +#ifdef _KERNEL +void scope6_ifattach __P((struct ifnet *)); +int scope6_set __P((struct ifnet *, u_int32_t *)); +int scope6_get __P((struct ifnet *, u_int32_t *)); +void scope6_setdefault __P((struct ifnet *)); +int scope6_get_default __P((u_int32_t *)); +u_int32_t scope6_in6_addrscope __P((struct in6_addr *)); +u_int32_t scope6_addr2default __P((struct in6_addr *)); +#endif /* _KERNEL */ + +#endif /* _NETINET6_SCOPE6_VAR_H_ */ diff --git a/sys/netinet6/tcp6_var.h b/sys/netinet6/tcp6_var.h new file mode 100644 index 0000000..d911167 --- /dev/null +++ b/sys/netinet6/tcp6_var.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +/* + * Copyright (c) 1982, 1986, 1993, 1994, 1995 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)tcp_var.h 8.4 (Berkeley) 5/24/95 + * $FreeBSD$ + */ + +#ifndef _NETINET_TCP6_VAR_H_ +#define _NETINET_TCP6_VAR_H_ + +#ifdef _KERNEL +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_inet6_tcp6); +#endif + +extern int tcp_v6mssdflt; /* XXX */ + +struct ip6_hdr; +void tcp6_ctlinput __P((int, struct sockaddr *, void *)); +void tcp6_init __P((void)); +int tcp6_input __P((struct mbuf **, int *, int)); +struct rtentry *tcp_rtlookup6(struct in_conninfo *); + +extern struct pr_usrreqs tcp6_usrreqs; + +#endif /* _KERNEL */ + +#endif /* _NETINET_TCP6_VAR_H_ */ diff --git a/sys/netinet6/udp6_output.c b/sys/netinet6/udp6_output.c new file mode 100644 index 0000000..1de45f0 --- /dev/null +++ b/sys/netinet6/udp6_output.c @@ -0,0 +1,312 @@ +/* $FreeBSD$ */ +/* $KAME: udp6_output.c,v 1.31 2001/05/21 16:39:15 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 + */ + +#include "opt_ipsec.h" +#include "opt_inet.h" +#include "opt_inet6.h" + +#include <sys/param.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/sysctl.h> +#include <sys/errno.h> +#include <sys/stat.h> +#include <sys/systm.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/if_types.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_pcb.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet/ip6.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/udp6_var.h> +#include <netinet/icmp6.h> +#include <netinet6/ip6protosw.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#ifdef INET6 +#include <netinet6/ipsec6.h> +#endif +#endif /* IPSEC */ + +#include <net/net_osdep.h> + +/* + * UDP protocol inplementation. + * Per RFC 768, August, 1980. + */ + +#define in6pcb inpcb +#define udp6stat udpstat +#define udp6s_opackets udps_opackets + +int +udp6_output(in6p, m, addr6, control, td) + struct in6pcb *in6p; + struct mbuf *m; + struct mbuf *control; + struct sockaddr *addr6; + struct thread *td; +{ + u_int32_t ulen = m->m_pkthdr.len; + u_int32_t plen = sizeof(struct udphdr) + ulen; + struct ip6_hdr *ip6; + struct udphdr *udp6; + struct in6_addr *laddr, *faddr; + u_short fport; + int error = 0; + struct ip6_pktopts opt, *stickyopt = in6p->in6p_outputopts; + int priv; + int af = AF_INET6, hlen = sizeof(struct ip6_hdr); + int flags; + struct sockaddr_in6 tmp; + + priv = 0; + if (td && !suser(td)) + priv = 1; + if (control) { + if ((error = ip6_setpktoptions(control, &opt, priv, 0)) != 0) + goto release; + in6p->in6p_outputopts = &opt; + } + + if (addr6) { + /* + * IPv4 version of udp_output calls in_pcbconnect in this case, + * which needs splnet and affects performance. + * Since we saw no essential reason for calling in_pcbconnect, + * we get rid of such kind of logic, and call in6_selectsrc + * and in6_pcbsetport in order to fill in the local address + * and the local port. + */ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr6; + if (sin6->sin6_port == 0) { + error = EADDRNOTAVAIL; + goto release; + } + + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr)) { + /* how about ::ffff:0.0.0.0 case? */ + error = EISCONN; + goto release; + } + + /* protect *sin6 from overwrites */ + tmp = *sin6; + sin6 = &tmp; + + faddr = &sin6->sin6_addr; + fport = sin6->sin6_port; /* allow 0 port */ + + if (IN6_IS_ADDR_V4MAPPED(faddr)) { + if ((in6p->in6p_flags & IN6P_IPV6_V6ONLY)) { + /* + * I believe we should explicitly discard the + * packet when mapped addresses are disabled, + * rather than send the packet as an IPv6 one. + * If we chose the latter approach, the packet + * might be sent out on the wire based on the + * default route, the situation which we'd + * probably want to avoid. + * (20010421 jinmei@kame.net) + */ + error = EINVAL; + goto release; + } else + af = AF_INET; + } + + /* KAME hack: embed scopeid */ + if (in6_embedscope(&sin6->sin6_addr, sin6, in6p, NULL) != 0) { + error = EINVAL; + goto release; + } + + if (!IN6_IS_ADDR_V4MAPPED(faddr)) { + laddr = in6_selectsrc(sin6, in6p->in6p_outputopts, + in6p->in6p_moptions, + &in6p->in6p_route, + &in6p->in6p_laddr, &error); + } else + laddr = &in6p->in6p_laddr; /* XXX */ + if (laddr == NULL) { + if (error == 0) + error = EADDRNOTAVAIL; + goto release; + } + if (in6p->in6p_lport == 0 && + (error = in6_pcbsetport(laddr, in6p, td)) != 0) + goto release; + } else { + if (IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr)) { + error = ENOTCONN; + goto release; + } + if (IN6_IS_ADDR_V4MAPPED(&in6p->in6p_faddr)) { + if ((in6p->in6p_flags & IN6P_IPV6_V6ONLY)) { + /* + * XXX: this case would happen when the + * application sets the V6ONLY flag after + * connecting the foreign address. + * Such applications should be fixed, + * so we bark here. + */ + log(LOG_INFO, "udp6_output: IPV6_V6ONLY " + "option was set for a connected socket\n"); + error = EINVAL; + goto release; + } else + af = AF_INET; + } + laddr = &in6p->in6p_laddr; + faddr = &in6p->in6p_faddr; + fport = in6p->in6p_fport; + } + + if (af == AF_INET) + hlen = sizeof(struct ip); + + /* + * Calculate data length and get a mbuf + * for UDP and IP6 headers. + */ + M_PREPEND(m, hlen + sizeof(struct udphdr), M_DONTWAIT); + if (m == 0) { + error = ENOBUFS; + goto release; + } + + /* + * Stuff checksum and output datagram. + */ + udp6 = (struct udphdr *)(mtod(m, caddr_t) + hlen); + udp6->uh_sport = in6p->in6p_lport; /* lport is always set in the PCB */ + udp6->uh_dport = fport; + if (plen <= 0xffff) + udp6->uh_ulen = htons((u_short)plen); + else + udp6->uh_ulen = 0; + udp6->uh_sum = 0; + + switch (af) { + case AF_INET6: + ip6 = mtod(m, struct ip6_hdr *); + ip6->ip6_flow = in6p->in6p_flowinfo & IPV6_FLOWINFO_MASK; + ip6->ip6_vfc &= ~IPV6_VERSION_MASK; + ip6->ip6_vfc |= IPV6_VERSION; +#if 0 /* ip6_plen will be filled in ip6_output. */ + ip6->ip6_plen = htons((u_short)plen); +#endif + ip6->ip6_nxt = IPPROTO_UDP; + ip6->ip6_hlim = in6_selecthlim(in6p, + in6p->in6p_route.ro_rt ? + in6p->in6p_route.ro_rt->rt_ifp : NULL); + ip6->ip6_src = *laddr; + ip6->ip6_dst = *faddr; + + if ((udp6->uh_sum = in6_cksum(m, IPPROTO_UDP, + sizeof(struct ip6_hdr), plen)) == 0) { + udp6->uh_sum = 0xffff; + } + + flags = 0; + + udp6stat.udp6s_opackets++; + error = ip6_output(m, in6p->in6p_outputopts, &in6p->in6p_route, + flags, in6p->in6p_moptions, NULL, in6p); + break; + case AF_INET: + error = EAFNOSUPPORT; + goto release; + } + goto releaseopt; + +release: + m_freem(m); + +releaseopt: + if (control) { + ip6_clearpktopts(in6p->in6p_outputopts, 0, -1); + in6p->in6p_outputopts = stickyopt; + m_freem(control); + } + return(error); +} diff --git a/sys/netinet6/udp6_usrreq.c b/sys/netinet6/udp6_usrreq.c new file mode 100644 index 0000000..fd642cb --- /dev/null +++ b/sys/netinet6/udp6_usrreq.c @@ -0,0 +1,772 @@ +/* $FreeBSD$ */ +/* $KAME: udp6_usrreq.c,v 1.27 2001/05/21 05:45:10 jinmei Exp $ */ + +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + */ + +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 + */ + +#include "opt_inet.h" +#include "opt_inet6.h" +#include "opt_ipsec.h" + +#include <sys/param.h> +#include <sys/errno.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/mbuf.h> +#include <sys/proc.h> +#include <sys/protosw.h> +#include <sys/signalvar.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/stat.h> +#include <sys/sx.h> +#include <sys/sysctl.h> +#include <sys/syslog.h> +#include <sys/systm.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_pcb.h> +#include <netinet/in_systm.h> +#include <netinet/in_var.h> +#include <netinet/ip.h> +#include <netinet/ip6.h> +#include <netinet/icmp6.h> +#include <netinet/ip_var.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> +#include <netinet6/ip6protosw.h> +#include <netinet6/ip6_var.h> +#include <netinet6/in6_pcb.h> +#include <netinet6/udp6_var.h> + +#ifdef IPSEC +#include <netinet6/ipsec.h> +#include <netinet6/ipsec6.h> +#endif /* IPSEC */ + +#ifdef FAST_IPSEC +#include <netipsec/ipsec.h> +#include <netipsec/ipsec6.h> +#endif /* FAST_IPSEC */ + +/* + * UDP protocol inplementation. + * Per RFC 768, August, 1980. + */ + +extern struct protosw inetsw[]; +static int in6_mcmatch __P((struct inpcb *, struct in6_addr *, struct ifnet *)); +static int udp6_detach __P((struct socket *so)); + +static int +in6_mcmatch(in6p, ia6, ifp) + struct inpcb *in6p; + register struct in6_addr *ia6; + struct ifnet *ifp; +{ + struct ip6_moptions *im6o = in6p->in6p_moptions; + struct in6_multi_mship *imm; + + if (im6o == NULL) + return 0; + + for (imm = im6o->im6o_memberships.lh_first; imm != NULL; + imm = imm->i6mm_chain.le_next) { + if ((ifp == NULL || + imm->i6mm_maddr->in6m_ifp == ifp) && + IN6_ARE_ADDR_EQUAL(&imm->i6mm_maddr->in6m_addr, + ia6)) + return 1; + } + return 0; +} + +int +udp6_input(mp, offp, proto) + struct mbuf **mp; + int *offp, proto; +{ + struct mbuf *m = *mp; + register struct ip6_hdr *ip6; + register struct udphdr *uh; + register struct inpcb *in6p; + struct mbuf *opts = NULL; + int off = *offp; + int plen, ulen; + struct sockaddr_in6 udp_in6; + + IP6_EXTHDR_CHECK(m, off, sizeof(struct udphdr), IPPROTO_DONE); + + ip6 = mtod(m, struct ip6_hdr *); + + if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { + /* XXX send icmp6 host/port unreach? */ + m_freem(m); + return IPPROTO_DONE; + } + + udpstat.udps_ipackets++; + + plen = ntohs(ip6->ip6_plen) - off + sizeof(*ip6); + uh = (struct udphdr *)((caddr_t)ip6 + off); + ulen = ntohs((u_short)uh->uh_ulen); + + if (plen != ulen) { + udpstat.udps_badlen++; + goto bad; + } + + /* + * Checksum extended UDP header and data. + */ + if (uh->uh_sum == 0) + udpstat.udps_nosum++; + else if (in6_cksum(m, IPPROTO_UDP, off, ulen) != 0) { + udpstat.udps_badsum++; + goto bad; + } + + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + struct inpcb *last; + + /* + * Deliver a multicast datagram to all sockets + * for which the local and remote addresses and ports match + * those of the incoming datagram. This allows more than + * one process to receive multicasts on the same port. + * (This really ought to be done for unicast datagrams as + * well, but that would cause problems with existing + * applications that open both address-specific sockets and + * a wildcard socket listening to the same port -- they would + * end up receiving duplicates of every unicast datagram. + * Those applications open the multiple sockets to overcome an + * inadequacy of the UDP socket interface, but for backwards + * compatibility we avoid the problem here rather than + * fixing the interface. Maybe 4.5BSD will remedy this?) + */ + + /* + * In a case that laddr should be set to the link-local + * address (this happens in RIPng), the multicast address + * specified in the received packet does not match with + * laddr. To cure this situation, the matching is relaxed + * if the receiving interface is the same as one specified + * in the socket and if the destination multicast address + * matches one of the multicast groups specified in the socket. + */ + + /* + * Construct sockaddr format source address. + */ + init_sin6(&udp_in6, m); /* general init */ + udp_in6.sin6_port = uh->uh_sport; + /* + * KAME note: traditionally we dropped udpiphdr from mbuf here. + * We need udphdr for IPsec processing so we do that later. + */ + + /* + * Locate pcb(s) for datagram. + * (Algorithm copied from raw_intr().) + */ + last = NULL; + LIST_FOREACH(in6p, &udb, inp_list) { + if ((in6p->inp_vflag & INP_IPV6) == 0) + continue; + if (in6p->in6p_lport != uh->uh_dport) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr)) { + if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, + &ip6->ip6_dst) && + !in6_mcmatch(in6p, &ip6->ip6_dst, + m->m_pkthdr.rcvif)) + continue; + } + if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr)) { + if (!IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, + &ip6->ip6_src) || + in6p->in6p_fport != uh->uh_sport) + continue; + } + + if (last != NULL) { + struct mbuf *n; + +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, last)) + ipsec6stat.in_polvio++; + /* do not inject data into pcb */ + else +#endif /* IPSEC */ +#ifdef FAST_IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, last)) + ; + else +#endif /* FAST_IPSEC */ + if ((n = m_copy(m, 0, M_COPYALL)) != NULL) { + /* + * KAME NOTE: do not + * m_copy(m, offset, ...) above. + * sbappendaddr() expects M_PKTHDR, + * and m_copy() will copy M_PKTHDR + * only if offset is 0. + */ + if (last->in6p_flags & IN6P_CONTROLOPTS + || last->in6p_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(last, &opts, + ip6, n); + + m_adj(n, off + sizeof(struct udphdr)); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&udp_in6, + n, opts) == 0) { + m_freem(n); + if (opts) + m_freem(opts); + udpstat.udps_fullsock++; + } else + sorwakeup(last->in6p_socket); + opts = NULL; + } + } + last = in6p; + /* + * Don't look for additional matches if this one does + * not have either the SO_REUSEPORT or SO_REUSEADDR + * socket options set. This heuristic avoids searching + * through all pcbs in the common case of a non-shared + * port. It assumes that an application will never + * clear these options after setting them. + */ + if ((last->in6p_socket->so_options & + (SO_REUSEPORT|SO_REUSEADDR)) == 0) + break; + } + + if (last == NULL) { + /* + * No matching pcb found; discard datagram. + * (No need to send an ICMP Port Unreachable + * for a broadcast or multicast datgram.) + */ + udpstat.udps_noport++; + udpstat.udps_noportmcast++; + goto bad; + } +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, last)) { + ipsec6stat.in_polvio++; + goto bad; + } +#endif /* IPSEC */ +#ifdef FAST_IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, last)) { + goto bad; + } +#endif /* FAST_IPSEC */ + if (last->in6p_flags & IN6P_CONTROLOPTS + || last->in6p_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(last, &opts, ip6, m); + + m_adj(m, off + sizeof(struct udphdr)); + if (sbappendaddr(&last->in6p_socket->so_rcv, + (struct sockaddr *)&udp_in6, + m, opts) == 0) { + udpstat.udps_fullsock++; + goto bad; + } + sorwakeup(last->in6p_socket); + return IPPROTO_DONE; + } + /* + * Locate pcb for datagram. + */ + in6p = in6_pcblookup_hash(&udbinfo, &ip6->ip6_src, uh->uh_sport, + &ip6->ip6_dst, uh->uh_dport, 1, + m->m_pkthdr.rcvif); + if (in6p == 0) { + if (log_in_vain) { + char buf[INET6_ADDRSTRLEN]; + + strcpy(buf, ip6_sprintf(&ip6->ip6_dst)); + log(LOG_INFO, + "Connection attempt to UDP [%s]:%d from [%s]:%d\n", + buf, ntohs(uh->uh_dport), + ip6_sprintf(&ip6->ip6_src), ntohs(uh->uh_sport)); + } + udpstat.udps_noport++; + if (m->m_flags & M_MCAST) { + printf("UDP6: M_MCAST is set in a unicast packet.\n"); + udpstat.udps_noportmcast++; + goto bad; + } + icmp6_error(m, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT, 0); + return IPPROTO_DONE; + } +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, in6p)) { + ipsec6stat.in_polvio++; + goto bad; + } +#endif /* IPSEC */ +#ifdef FAST_IPSEC + /* + * Check AH/ESP integrity. + */ + if (ipsec6_in_reject(m, in6p)) { + goto bad; + } +#endif /* FAST_IPSEC */ + + /* + * Construct sockaddr format source address. + * Stuff source address and datagram in user buffer. + */ + init_sin6(&udp_in6, m); /* general init */ + udp_in6.sin6_port = uh->uh_sport; + if (in6p->in6p_flags & IN6P_CONTROLOPTS + || in6p->in6p_socket->so_options & SO_TIMESTAMP) + ip6_savecontrol(in6p, &opts, ip6, m); + m_adj(m, off + sizeof(struct udphdr)); + if (sbappendaddr(&in6p->in6p_socket->so_rcv, + (struct sockaddr *)&udp_in6, + m, opts) == 0) { + udpstat.udps_fullsock++; + goto bad; + } + sorwakeup(in6p->in6p_socket); + return IPPROTO_DONE; +bad: + if (m) + m_freem(m); + if (opts) + m_freem(opts); + return IPPROTO_DONE; +} + +void +udp6_ctlinput(cmd, sa, d) + int cmd; + struct sockaddr *sa; + void *d; +{ + struct udphdr uh; + struct ip6_hdr *ip6; + struct mbuf *m; + int off = 0; + struct ip6ctlparam *ip6cp = NULL; + const struct sockaddr_in6 *sa6_src = NULL; + struct inpcb *(*notify) __P((struct inpcb *, int)) = udp_notify; + struct udp_portonly { + u_int16_t uh_sport; + u_int16_t uh_dport; + } *uhp; + + if (sa->sa_family != AF_INET6 || + sa->sa_len != sizeof(struct sockaddr_in6)) + return; + + if ((unsigned)cmd >= PRC_NCMDS) + return; + if (PRC_IS_REDIRECT(cmd)) + notify = in6_rtchange, d = NULL; + else if (cmd == PRC_HOSTDEAD) + d = NULL; + else if (inet6ctlerrmap[cmd] == 0) + return; + + /* if the parameter is from icmp6, decode it. */ + if (d != NULL) { + ip6cp = (struct ip6ctlparam *)d; + m = ip6cp->ip6c_m; + ip6 = ip6cp->ip6c_ip6; + off = ip6cp->ip6c_off; + sa6_src = ip6cp->ip6c_src; + } else { + m = NULL; + ip6 = NULL; + sa6_src = &sa6_any; + } + + if (ip6) { + /* + * XXX: We assume that when IPV6 is non NULL, + * M and OFF are valid. + */ + + /* check if we can safely examine src and dst ports */ + if (m->m_pkthdr.len < off + sizeof(*uhp)) + return; + + bzero(&uh, sizeof(uh)); + m_copydata(m, off, sizeof(*uhp), (caddr_t)&uh); + + (void) in6_pcbnotify(&udb, sa, uh.uh_dport, + (struct sockaddr *)ip6cp->ip6c_src, + uh.uh_sport, cmd, notify); + } else + (void) in6_pcbnotify(&udb, sa, 0, + (const struct sockaddr *)sa6_src, + 0, cmd, notify); +} + +static int +udp6_getcred(SYSCTL_HANDLER_ARGS) +{ + struct xucred xuc; + struct sockaddr_in6 addrs[2]; + struct inpcb *inp; + int error, s; + + error = suser(req->td); + if (error) + return (error); + + if (req->newlen != sizeof(addrs)) + return (EINVAL); + if (req->oldlen != sizeof(struct xucred)) + return (EINVAL); + error = SYSCTL_IN(req, addrs, sizeof(addrs)); + if (error) + return (error); + s = splnet(); + inp = in6_pcblookup_hash(&udbinfo, &addrs[1].sin6_addr, + addrs[1].sin6_port, + &addrs[0].sin6_addr, addrs[0].sin6_port, + 1, NULL); + if (!inp || !inp->inp_socket) { + error = ENOENT; + goto out; + } + cru2x(inp->inp_socket->so_cred, &xuc); + error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred)); +out: + splx(s); + return (error); +} + +SYSCTL_PROC(_net_inet6_udp6, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, + 0, 0, + udp6_getcred, "S,xucred", "Get the xucred of a UDP6 connection"); + +static int +udp6_abort(struct socket *so) +{ + struct inpcb *inp; + int s; + + inp = sotoinpcb(so); + if (inp == 0) + return EINVAL; /* ??? possible? panic instead? */ + soisdisconnected(so); + s = splnet(); + in6_pcbdetach(inp); + splx(s); + return 0; +} + +static int +udp6_attach(struct socket *so, int proto, struct thread *td) +{ + struct inpcb *inp; + int s, error; + + inp = sotoinpcb(so); + if (inp != 0) + return EINVAL; + + if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { + error = soreserve(so, udp_sendspace, udp_recvspace); + if (error) + return error; + } + s = splnet(); + error = in_pcballoc(so, &udbinfo, td); + splx(s); + if (error) + return error; + inp = (struct inpcb *)so->so_pcb; + inp->inp_vflag |= INP_IPV6; + if (!ip6_v6only) + inp->inp_vflag |= INP_IPV4; + inp->in6p_hops = -1; /* use kernel default */ + inp->in6p_cksum = -1; /* just to be sure */ + /* + * XXX: ugly!! + * IPv4 TTL initialization is necessary for an IPv6 socket as well, + * because the socket may be bound to an IPv6 wildcard address, + * which may match an IPv4-mapped IPv6 address. + */ + inp->inp_ip_ttl = ip_defttl; + return 0; +} + +static int +udp6_bind(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp; + int s, error; + + inp = sotoinpcb(so); + if (inp == 0) + return EINVAL; + + inp->inp_vflag &= ~INP_IPV4; + inp->inp_vflag |= INP_IPV6; + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { + struct sockaddr_in6 *sin6_p; + + sin6_p = (struct sockaddr_in6 *)nam; + + if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) + inp->inp_vflag |= INP_IPV4; + else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { + struct sockaddr_in sin; + + in6_sin6_2_sin(&sin, sin6_p); + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; + s = splnet(); + error = in_pcbbind(inp, (struct sockaddr *)&sin, td); + splx(s); + return error; + } + } + + s = splnet(); + error = in6_pcbbind(inp, nam, td); + splx(s); + return error; +} + +static int +udp6_connect(struct socket *so, struct sockaddr *nam, struct thread *td) +{ + struct inpcb *inp; + int s, error; + + inp = sotoinpcb(so); + if (inp == 0) + return EINVAL; + + if ((inp->inp_flags & IN6P_IPV6_V6ONLY) == 0) { + struct sockaddr_in6 *sin6_p; + + sin6_p = (struct sockaddr_in6 *)nam; + if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { + struct sockaddr_in sin; + + if (inp->inp_faddr.s_addr != INADDR_ANY) + return EISCONN; + in6_sin6_2_sin(&sin, sin6_p); + s = splnet(); + error = in_pcbconnect(inp, (struct sockaddr *)&sin, td); + splx(s); + if (error == 0) { + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; + soisconnected(so); + } + return error; + } + } + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + return EISCONN; + s = splnet(); + error = in6_pcbconnect(inp, nam, td); + splx(s); + if (error == 0) { + if (!ip6_v6only) { /* should be non mapped addr */ + inp->inp_vflag &= ~INP_IPV4; + inp->inp_vflag |= INP_IPV6; + } + soisconnected(so); + } + return error; +} + +static int +udp6_detach(struct socket *so) +{ + struct inpcb *inp; + int s; + + inp = sotoinpcb(so); + if (inp == 0) + return EINVAL; + s = splnet(); + in6_pcbdetach(inp); + splx(s); + return 0; +} + +static int +udp6_disconnect(struct socket *so) +{ + struct inpcb *inp; + int s; + + inp = sotoinpcb(so); + if (inp == 0) + return EINVAL; + + if (inp->inp_vflag & INP_IPV4) { + struct pr_usrreqs *pru; + + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + return ((*pru->pru_disconnect)(so)); + } + + if (IN6_IS_ADDR_UNSPECIFIED(&inp->in6p_faddr)) + return ENOTCONN; + + s = splnet(); + in6_pcbdisconnect(inp); + inp->in6p_laddr = in6addr_any; + splx(s); + so->so_state &= ~SS_ISCONNECTED; /* XXX */ + return 0; +} + +static int +udp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *td) +{ + struct inpcb *inp; + int error = 0; + + inp = sotoinpcb(so); + if (inp == 0) { + error = EINVAL; + goto bad; + } + + if (addr) { + if (addr->sa_len != sizeof(struct sockaddr_in6)) { + error = EINVAL; + goto bad; + } + if (addr->sa_family != AF_INET6) { + error = EAFNOSUPPORT; + goto bad; + } + } + + if (!ip6_v6only) { + int hasv4addr; + struct sockaddr_in6 *sin6 = 0; + + if (addr == 0) + hasv4addr = (inp->inp_vflag & INP_IPV4); + else { + sin6 = (struct sockaddr_in6 *)addr; + hasv4addr = IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) + ? 1 : 0; + } + if (hasv4addr) { + struct pr_usrreqs *pru; + + if (sin6) + in6_sin6_2_sin_in_sock(addr); + pru = inetsw[ip_protox[IPPROTO_UDP]].pr_usrreqs; + error = ((*pru->pru_send)(so, flags, m, addr, control, + td)); + /* addr will just be freed in sendit(). */ + return error; + } + } + + return udp6_output(inp, m, addr, control, td); + + bad: + m_freem(m); + return(error); +} + +struct pr_usrreqs udp6_usrreqs = { + udp6_abort, pru_accept_notsupp, udp6_attach, udp6_bind, udp6_connect, + pru_connect2_notsupp, in6_control, udp6_detach, udp6_disconnect, + pru_listen_notsupp, in6_mapped_peeraddr, pru_rcvd_notsupp, + pru_rcvoob_notsupp, udp6_send, pru_sense_null, udp_shutdown, + in6_mapped_sockaddr, sosend, soreceive, sopoll +}; diff --git a/sys/netinet6/udp6_var.h b/sys/netinet6/udp6_var.h new file mode 100644 index 0000000..2da5057 --- /dev/null +++ b/sys/netinet6/udp6_var.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * 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. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``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 PROJECT OR CONTRIBUTORS 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. + * + * $FreeBSD$ + */ + +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. 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. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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. + * + * @(#)udp_var.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _NETINET6_UDP6_VAR_H_ +#define _NETINET6_UDP6_VAR_H_ + +#ifdef _KERNEL +SYSCTL_DECL(_net_inet6_udp6); + +extern struct pr_usrreqs udp6_usrreqs; + +void udp6_ctlinput __P((int, struct sockaddr *, void *)); +int udp6_input __P((struct mbuf **, int *, int)); +int udp6_output __P((struct inpcb *inp, struct mbuf *m, + struct sockaddr *addr, struct mbuf *control, + struct thread *td)); +#endif + +#endif /*_NETINET6_UDP6_VAR_H_*/ |