[Midnightbsd-cvs] src: sys/netinet6:

laffer1 at midnightbsd.org laffer1 at midnightbsd.org
Fri Sep 12 20:55:45 EDT 2008


Log Message:
-----------


Added Files:
-----------
    src/sys/netinet6:
        icmp6.c.orig (r1.1)
        ip6_ipsec.c (r1.1)
        ip6_ipsec.h (r1.1)
        sctp6_usrreq.c (r1.1)
        sctp6_var.h (r1.1)

-------------- next part --------------
--- /dev/null
+++ sys/netinet6/icmp6.c.orig
@@ -0,0 +1,2784 @@
+/*	$FreeBSD: src/sys/netinet6/icmp6.c,v 1.80 2007/07/05 16:29:39 delphij Exp $	*/
+/*	$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.
+ * 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 <netinet/tcp_var.h>
+#include <netinet6/in6_ifattach.h>
+#include <netinet6/in6_pcb.h>
+#include <netinet6/ip6protosw.h>
+#include <netinet6/ip6_var.h>
+#include <netinet6/scope6_var.h>
+#include <netinet6/mld6_var.h>
+#include <netinet6/nd6.h>
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#include <netipsec/key.h>
+#endif
+
+extern struct domain inet6domain;
+
+struct icmp6stat icmp6stat;
+
+extern struct inpcbinfo ripcbinfo;
+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 *));
+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 **, struct in6_addr *));
+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));
+
+
+void
+icmp6_init(void)
+{
+
+	mld6_init();
+}
+
+static void
+icmp6_errcount(struct icmp6errstat *stat, int type, int 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++;
+}
+
+/*
+ * A wrapper function for icmp6_error() necessary when the erroneous packet
+ * may not contain enough scope zone information.
+ */
+void
+icmp6_error2(struct mbuf *m, int type, int code, int param,
+    struct ifnet *ifp)
+{
+	struct ip6_hdr *ip6;
+
+	if (ifp == NULL)
+		return;
+
+#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
+
+	ip6 = mtod(m, struct ip6_hdr *);
+
+	if (in6_setscope(&ip6->ip6_src, ifp, NULL) != 0)
+		return;
+	if (in6_setscope(&ip6->ip6_dst, ifp, NULL) != 0)
+		return;
+
+	icmp6_error(m, type, code, param);
+}
+
+/*
+ * Generate an error packet of type error in response to bad IP6 packet.
+ */
+void
+icmp6_error(struct mbuf *m, int type, int code, int 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 *);
+
+	/*
+	 * If the destination address of the erroneous packet is a multicast
+	 * address, or the packet was sent using link-layer multicast,
+	 * we should basically suppress sending an error (RFC 2463, Section
+	 * 2.4).
+	 * We have two exceptions (the item e.2 in that section):
+	 * - the Pakcet Too Big message can be sent for path MTU discovery.
+	 * - the Parameter Problem Message that can be allowed an icmp6 error
+	 *   in the option type field.  This check has been done in
+	 *   ip6_unknown_opt(), so we can just check the type and code.
+	 */
+	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;
+
+	/*
+	 * RFC 2463, 2.4 (e.5): 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;
+
+	in6_clearscope(&oip6->ip6_src);
+	in6_clearscope(&oip6->ip6_dst);
+
+	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 output 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 whether or not we can generate ICMP6, free it.
+	 */
+	m_freem(m);
+}
+
+/*
+ * Process a received ICMP6 message.
+ */
+int
+icmp6_input(struct mbuf **mp, int *offp, int 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;
+	char ip6bufs[INET6_ADDRSTRLEN], ip6bufd[INET6_ADDRSTRLEN];
+
+#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(ip6bufs, &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 deliver 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;
+		case ICMP6_DST_UNREACH_BEYONDSCOPE:
+			/* I mean "source address was incorrect." */
+			code = PRC_PARAMPROB;
+			break;
+		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);
+
+		/* validation is made in icmp6_mtudisc_update */
+
+		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:
+			code = PRC_TIMXCEED_INTRANS;
+			break;
+		case ICMP6_TIME_EXCEED_REASSEMBLY:
+			code = PRC_TIMXCEED_REASS;
+			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;
+
+			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 *);
+			IP6_EXTHDR_GET(nicmp6, struct icmp6_hdr *, n, off,
+			    sizeof(*nicmp6));
+			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 defined. */
+		/* 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;
+
+			/*
+			 * XXX: this combination of flags is pointless,
+			 * but should we keep this for compatibility?
+			 */
+			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 (n && !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(ip6bufs, &ip6->ip6_src),
+		    ip6_sprintf(ip6bufd, &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(struct mbuf **mp, int off, int icmp6len, int code)
+{
+	struct mbuf *m = *mp;
+	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
+
+		/*
+		 * retrieve parameters from the inner IPv6 header, and convert
+		 * them into sockaddr structures.
+		 * XXX: there is no guarantee that the source or destination
+		 * addresses of the inner packet are in the same scope as
+		 * the addresses of the icmp packet.  But there is no other
+		 * way to determine the zone.
+		 */
+		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;
+		if (in6_setscope(&icmp6dst.sin6_addr, m->m_pkthdr.rcvif, NULL))
+			goto freeit;
+		bzero(&icmp6src, sizeof(icmp6src));
+		icmp6src.sin6_len = sizeof(struct sockaddr_in6);
+		icmp6src.sin6_family = AF_INET6;
+		icmp6src.sin6_addr = eip6->ip6_src;
+		if (in6_setscope(&icmp6src.sin6_addr, m->m_pkthdr.rcvif, NULL))
+			goto freeit;
+		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);
+		}
+	}
+	*mp = m;
+	return (0);
+
+  freeit:
+	m_freem(m);
+	return (-1);
+}
+
+void
+icmp6_mtudisc_update(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 in_conninfo inc;
+
+#if 0
+	/*
+	 * RFC2460 section 5, last paragraph.
+	 * even though minimum link MTU for IPv6 is IPV6_MMTU,
+	 * we may see ICMPv6 too big with mtu < IPV6_MMTU
+	 * due to packet translator in the middle.
+	 * see ip6_output() and ip6_getpmtu() "alwaysfrag" case for
+	 * special handling.
+	 */
+	if (mtu < IPV6_MMTU)
+		return;
+#endif
+
+	/*
+	 * we reject ICMPv6 too big with abnormally small value.
+	 * XXX what is the good definition of "abnormally small"?
+	 */
+	if (mtu < sizeof(struct ip6_hdr) + sizeof(struct ip6_frag) + 8)
+		return;
+
+	if (!validated)
+		return;
+
+	bzero(&inc, sizeof(inc));
+	inc.inc_flags = 1; /* IPv6 */
+	inc.inc6_faddr = *dst;
+	if (in6_setscope(&inc.inc6_faddr, m->m_pkthdr.rcvif, NULL))
+		return;
+
+	if (mtu < tcp_maxmtu6(&inc, NULL)) {
+		tcp_hc_updatemtu(&inc, mtu);
+		icmp6stat.icp6s_pmtuchg++;
+	}
+}
+
+/*
+ * 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(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 in6_addr in6_subj; /* subject address */
+	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 source address.
+	 * The default configuration MUST be to refuse answering queries from
+	 * global-scope addresses according to RFC4602.
+	 * Notes:
+	 *  - it's not very clear what "refuse" means; this implementation
+	 *    simply drops it.
+	 *  - it's not very easy to identify global-scope (unicast) addresses
+	 *    since there are many prefixes for them.  It should be safer
+	 *    and in practice sufficient to check "all" but loopback and
+	 *    link-local (note that site-local unicast was deprecated and
+	 *    ULA is defined as global scope-wise)
+	 */
+	if ((icmp6_nodeinfo & ICMP6_NODEINFO_GLOBALOK) == 0 &&
+	    !IN6_IS_ADDR_LOOPBACK(&ip6->ip6_src) &&
+	    !IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src))
+		goto bad;
+
+	/*
+	 * 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.
+	 * [RFC4602, Section 5.]
+	 */
+	if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+		if (!IN6_IS_ADDR_MC_LINKLOCAL(&ip6->ip6_dst))
+			goto bad;
+		/* else it's a link-local multicast, fine */
+	} else {		/* unicast or anycast */
+		if ((ia6 = ip6_getdstifaddr(m)) == NULL)
+			goto bad; /* XXX impossible */
+
+		if ((ia6->ia6_flags & IN6_IFF_TEMPORARY) &&
+		    !(icmp6_nodeinfo & ICMP6_NODEINFO_TMPADDROK)) {
+			nd6log((LOG_DEBUG, "ni6_input: ignore node info to "
+				"a temporary address in %s:%d",
+			       __FILE__, __LINE__));
+			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:
+	case NI_QTYPE_IPV4ADDR:
+		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(struct in6_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)&in6_subj);
+			if (in6_setscope(&in6_subj, m->m_pkthdr.rcvif, NULL))
+				goto bad;
+
+			subj = (char *)&in6_subj;
+			if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_dst, &in6_subj))
+				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 & ICMP6_NODEINFO_FQDNOK) == 0)
+			goto bad;
+		break;
+	case NI_QTYPE_NODEADDR:
+	case NI_QTYPE_IPV4ADDR:
+		if ((icmp6_nodeinfo & ICMP6_NODEINFO_NODEADDROK) == 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, (struct in6_addr *)subj);
+		if ((replylen += addrs * (sizeof(struct in6_addr) +
+		    sizeof(u_int32_t))) > MCLBYTES)
+			replylen = MCLBYTES; /* XXX: will truncate pkt later */
+		break;
+	case NI_QTYPE_IPV4ADDR:
+		/* unsupported - should respond with unknown Qtype? */
+		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.
+ *
+ * old - return pascal string if non-zero
+ */
+static struct mbuf *
+ni6_nametodns(const char *name, int namelen, int old)
+{
+	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(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(struct icmp6_nodeinfo *ni6, struct mbuf *m, struct ifnet **ifpp,
+    struct in6_addr *subj)
+{
+	struct ifnet *ifp;
+	struct in6_ifaddr *ifa6;
+	struct ifaddr *ifa;
+	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);
+			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, &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 & ICMP6_NODEINFO_TMPADDROK) == 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(struct icmp6_nodeinfo *ni6, struct icmp6_nodeinfo *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 & ICMP6_NODEINFO_TMPADDROK) == 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(&ltime, 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));
+			in6_clearscope((struct in6_addr *)cp); /* XXX */
+			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(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 fromsa;
+	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
+
+	/*
+	 * XXX: the address may have embedded scope zone ID, which should be
+	 * hidden from applications.
+	 */
+	bzero(&fromsa, sizeof(fromsa));
+	fromsa.sin6_family = AF_INET6;
+	fromsa.sin6_len = sizeof(struct sockaddr_in6);
+	fromsa.sin6_addr = ip6->ip6_src;
+	if (sa6_recoverscope(&fromsa)) {
+		m_freem(m);
+		return (IPPROTO_DONE);
+	}
+
+	INP_INFO_RLOCK(&ripcbinfo);
+	LIST_FOREACH(in6p, &ripcb, inp_list) {
+		INP_LOCK(in6p);
+		if ((in6p->inp_vflag & INP_IPV6) == 0) {
+	docontinue:
+			INP_UNLOCK(in6p);
+			continue;
+		}
+		if (in6p->in6p_ip6_nxt != IPPROTO_ICMPV6)
+			goto docontinue;
+		if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_laddr) &&
+		   !IN6_ARE_ADDR_EQUAL(&in6p->in6p_laddr, &ip6->ip6_dst))
+			goto docontinue;
+		if (!IN6_IS_ADDR_UNSPECIFIED(&in6p->in6p_faddr) &&
+		   !IN6_ARE_ADDR_EQUAL(&in6p->in6p_faddr, &ip6->ip6_src))
+			goto docontinue;
+		if (in6p->in6p_icmp6filt
+		    && ICMP6_FILTER_WILLBLOCK(icmp6->icmp6_type,
+				 in6p->in6p_icmp6filt))
+			goto docontinue;
+		if (last) {
+			struct	mbuf *n = NULL;
+
+			/*
+			 * Recent network drivers tend to allocate a single
+			 * mbuf cluster, rather than to make a couple of
+			 * mbufs without clusters.  Also, since the IPv6 code
+			 * path tries to avoid m_pullup(), it is highly
+			 * probable that we still have an mbuf cluster here
+			 * even though the necessary length can be stored in an
+			 * mbuf's internal buffer.
+			 * Meanwhile, the default size of the receive socket
+			 * buffer for raw sockets is not so large.  This means
+			 * the possibility of packet loss is relatively higher
+			 * than before.  To avoid this scenario, we copy the
+			 * received data to a separate mbuf that does not use
+			 * a cluster, if possible.
+			 * XXX: it is better to copy the data after stripping
+			 * intermediate headers.
+			 */
+			if ((m->m_flags & M_EXT) && m->m_next == NULL &&
+			    m->m_len <= MHLEN) {
+				MGET(n, M_DONTWAIT, m->m_type);
+				if (n != NULL) {
+					if (m_dup_pkthdr(n, m, M_NOWAIT)) {
+						bcopy(m->m_data, n->m_data,
+						      m->m_len);
+						n->m_len = m->m_len;
+					} else {
+						m_free(n);
+						n = NULL;
+					}
+				}
+			}
+			if (n != NULL ||
+			    (n = m_copy(m, 0, (int)M_COPYALL)) != NULL) {
+				if (last->in6p_flags & IN6P_CONTROLOPTS)
+					ip6_savecontrol(last, n, &opts);
+				/* strip intermediate headers */
+				m_adj(n, off);
+				SOCKBUF_LOCK(&last->in6p_socket->so_rcv);
+				if (sbappendaddr_locked(
+				    &last->in6p_socket->so_rcv,
+				    (struct sockaddr *)&fromsa, n, opts)
+				    == 0) {
+					/* should notify about lost packet */
+					m_freem(n);
+					if (opts) {
+						m_freem(opts);
+					}
+					SOCKBUF_UNLOCK(
+					    &last->in6p_socket->so_rcv);
+				} else
+					sorwakeup_locked(last->in6p_socket);
+				opts = NULL;
+			}
+			INP_UNLOCK(last);
+		}
+		last = in6p;
+	}
+	if (last) {
+		if (last->in6p_flags & IN6P_CONTROLOPTS)
+			ip6_savecontrol(last, m, &opts);
+		/* strip intermediate headers */
+		m_adj(m, off);
+
+		/* avoid using mbuf clusters if possible (see above) */
+		if ((m->m_flags & M_EXT) && m->m_next == NULL &&
+		    m->m_len <= MHLEN) {
+			struct mbuf *n;
+
+			MGET(n, M_DONTWAIT, m->m_type);
+			if (n != NULL) {
+				if (m_dup_pkthdr(n, m, M_NOWAIT)) {
+					bcopy(m->m_data, n->m_data, m->m_len);
+					n->m_len = m->m_len;
+
+					m_freem(m);
+					m = n;
+				} else {
+					m_freem(n);
+					n = NULL;
+				}
+			}
+		}
+		SOCKBUF_LOCK(&last->in6p_socket->so_rcv);
+		if (sbappendaddr_locked(&last->in6p_socket->so_rcv,
+		    (struct sockaddr *)&fromsa, m, opts) == 0) {
+			m_freem(m);
+			if (opts)
+				m_freem(opts);
+			SOCKBUF_UNLOCK(&last->in6p_socket->so_rcv);
+		} else
+			sorwakeup_locked(last->in6p_socket);
+		INP_UNLOCK(last);
+	} else {
+		m_freem(m);
+		ip6stat.ip6s_delivered--;
+	}
+	INP_INFO_RUNLOCK(&ripcbinfo);
+	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(struct mbuf *m, size_t off)
+{
+	struct ip6_hdr *ip6;
+	struct icmp6_hdr *icmp6;
+	struct in6_ifaddr *ia;
+	int plen;
+	int type, code;
+	struct ifnet *outif = NULL;
+	struct in6_addr origdst, *src = NULL;
+
+	/* 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. */
+
+	origdst = 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;
+
+	/*
+	 * 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 should be VERY rare, but is possible
+	 * (for example) when we encounter an error while forwarding procedure
+	 * destined to a duplicated address of ours.
+	 * Note that ip6_getdstifaddr() may fail if we are in an error handling
+	 * procedure of an outgoing packet of our own, in which case we need
+	 * to search in the ifaddr list.
+	 */
+	if (!IN6_IS_ADDR_MULTICAST(&origdst)) {
+		if ((ia = ip6_getdstifaddr(m))) {
+			if (!(ia->ia6_flags &
+			    (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY)))
+				src = &ia->ia_addr.sin6_addr;
+		} else {
+			struct sockaddr_in6 d;
+
+			bzero(&d, sizeof(d));
+			d.sin6_family = AF_INET6;
+			d.sin6_len = sizeof(d);
+			d.sin6_addr = origdst;
+			ia = (struct in6_ifaddr *)
+			    ifa_ifwithaddr((struct sockaddr *)&d);
+			if (ia &&
+			    !(ia->ia6_flags &
+			    (IN6_IFF_ANYCAST|IN6_IFF_NOTREADY))) {
+				src = &ia->ia_addr.sin6_addr;
+			}
+		}
+	}
+
+	if (src == NULL) {
+		int e;
+		struct sockaddr_in6 sin6;
+		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(&sin6, sizeof(sin6));
+		sin6.sin6_family = AF_INET6;
+		sin6.sin6_len = sizeof(sin6);
+		sin6.sin6_addr = ip6->ip6_dst; /* zone ID should be embedded */
+
+		bzero(&ro, sizeof(ro));
+		src = in6_selectsrc(&sin6, NULL, NULL, &ro, NULL, &outif, &e);
+		if (ro.ro_rt)
+			RTFREE(ro.ro_rt); /* XXX: we could use this */
+		if (src == NULL) {
+			char ip6buf[INET6_ADDRSTRLEN];
+			nd6log((LOG_DEBUG,
+			    "icmp6_reflect: source can't be determined: "
+			    "dst=%s, error=%d\n",
+			    ip6_sprintf(ip6buf, &sin6.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 (outif)
+		ip6->ip6_hlim = ND_IFINFO(outif)->chlim;
+	else if (m->m_pkthdr.rcvif) {
+		/* XXX: This may not be the outgoing interface */
+		ip6->ip6_hlim = ND_IFINFO(m->m_pkthdr.rcvif)->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);
+
+	ip6_output(m, NULL, NULL, 0, NULL, &outif, NULL);
+	if (outif)
+		icmp6_ifoutstat_inc(outif, type, code);
+
+	return;
+
+ bad:
+	m_freem(m);
+	return;
+}
+
+void
+icmp6_fasttimo(void)
+{
+
+	return;
+}
+
+static const char *
+icmp6_redirect_diag(struct in6_addr *src6, struct in6_addr *dst6,
+    struct in6_addr *tgt6)
+{
+	static char buf[1024];
+	char ip6bufs[INET6_ADDRSTRLEN];
+	char ip6bufd[INET6_ADDRSTRLEN];
+	char ip6buft[INET6_ADDRSTRLEN];
+	snprintf(buf, sizeof(buf), "(src=%s dst=%s tgt=%s)",
+	    ip6_sprintf(ip6bufs, src6), ip6_sprintf(ip6bufd, dst6),
+	    ip6_sprintf(ip6buft, tgt6));
+	return buf;
+}
+
+void
+icmp6_redirect_input(struct mbuf *m, int off)
+{
+	struct ifnet *ifp;
+	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;
+	char ip6buf[INET6_ADDRSTRLEN];
+
+	if (!m)
+		return;
+
+	ifp = m->m_pkthdr.rcvif;
+
+	if (!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_setscope(&redtgt6, m->m_pkthdr.rcvif, NULL) ||
+	    in6_setscope(&reddst6, m->m_pkthdr.rcvif, NULL)) {
+		goto freeit;
+	}
+
+	/* validation */
+	if (!IN6_IS_ADDR_LINKLOCAL(&src6)) {
+		nd6log((LOG_ERR,
+		    "ICMP6 redirect sent from %s rejected; "
+		    "must be from linklocal\n",
+		    ip6_sprintf(ip6buf, &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(ip6buf, &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_LOCKED(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(ip6buf, gw6),
+			    icmp6_redirect_diag(&src6, &reddst6, &redtgt6)));
+			RTFREE_LOCKED(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_LOCKED(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(ip6buf, &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);
+	}
+	/* 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 /* IPSEC */
+    }
+
+ freeit:
+	m_freem(m);
+	return;
+
+ bad:
+	icmp6stat.icp6s_badredirect++;
+	m_freem(m);
+}
+
+void
+icmp6_redirect_output(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)
+		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;
+	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;
+	}
+
+	/* XXX: clear embedded link IDs in the inner header */
+	in6_clearscope(&sip6->ip6_src);
+	in6_clearscope(&sip6->ip6_dst);
+	in6_clearscope(&nd_rd->nd_rd_target);
+	in6_clearscope(&nd_rd->nd_rd_dst);
+
+	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);
+}
+
+/*
+ * ICMPv6 socket option processing.
+ */
+int
+icmp6_ctloutput(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);
+}
+
+/*
+ * 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?
+ *
+ * dst - not used at this moment
+ * type - not used at this moment
+ * code - not used at this moment
+ */
+static int
+icmp6_ratelimit(const struct in6_addr *dst, const int type,
+    const int code)
+{
+	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;
+}
--- /dev/null
+++ sys/netinet6/sctp6_var.h
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * b) 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.
+ *
+ * c) Neither the name of Cisco Systems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
+ */
+/*	$KAME: sctp6_var.h,v 1.7 2004/08/17 04:06:22 itojun Exp $	*/
+#ifndef _NETINET6_SCTP6_VAR_H_
+#define _NETINET6_SCTP6_VAR_H_
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/sys/netinet6/sctp6_var.h,v 1.8 2007/09/13 10:36:43 rrs Exp $");
+
+#if defined(_KERNEL)
+
+SYSCTL_DECL(_net_inet6_sctp6);
+extern struct pr_usrreqs sctp6_usrreqs;
+
+
+int sctp6_input __P((struct mbuf **, int *, int));
+int sctp6_output
+__P((struct sctp_inpcb *, struct mbuf *, struct sockaddr *,
+    struct mbuf *, struct proc *));
+	void sctp6_ctlinput __P((int, struct sockaddr *, void *));
+
+
+	extern void sctp6_notify(struct sctp_inpcb *inp,
+         struct icmp6_hdr *icmph,
+         struct sctphdr *sh,
+         struct sockaddr *to,
+         struct sctp_tcb *stcb,
+         struct sctp_nets *net);
+
+
+#endif				/* _KERNEL */
+#endif
--- /dev/null
+++ sys/netinet6/ip6_ipsec.c
@@ -0,0 +1,368 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * $FreeBSD: src/sys/netinet6/ip6_ipsec.c,v 1.6 2007/08/05 16:16:15 bz Exp $
+ */
+
+#include "opt_ipsec.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/mac.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 <net/if.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_var.h>
+#include <netinet/ip.h>
+#include <netinet/in_pcb.h>
+#include <netinet/ip_var.h>
+#include <netinet/ip_options.h>
+
+#include <machine/in_cksum.h>
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#include <netipsec/ipsec6.h>
+#include <netipsec/xform.h>
+#include <netipsec/key.h>
+#ifdef IPSEC_DEBUG
+#include <netipsec/key_debug.h>
+#else
+#define	KEYDEBUG(lev,arg)
+#endif
+#endif /*IPSEC*/
+
+#include <netinet6/ip6_ipsec.h>
+
+extern	struct protosw inet6sw[];
+
+/*
+ * Check if we have to jump over firewall processing for this packet.
+ * Called from ip_input().
+ * 1 = jump over firewall, 0 = packet goes through firewall.
+ */
+int
+ip6_ipsec_filtertunnel(struct mbuf *m)
+{
+#if defined(IPSEC) && !defined(IPSEC_FILTERTUNNEL)
+	/*
+	 * Bypass packet filtering for packets from a tunnel.
+	 */
+	if (m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL) != NULL)
+		return 1;
+#endif
+	return 0;
+}
+
+/*
+ * Check if this packet has an active SA and needs to be dropped instead
+ * of forwarded.
+ * Called from ip_input().
+ * 1 = drop packet, 0 = forward packet.
+ */
+int
+ip6_ipsec_fwd(struct mbuf *m)
+{
+#ifdef IPSEC
+	struct m_tag *mtag;
+	struct tdb_ident *tdbi;
+	struct secpolicy *sp;
+	int s, error;
+	mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL);
+	s = splnet();
+	if (mtag != NULL) {
+		tdbi = (struct tdb_ident *)(mtag + 1);
+		sp = ipsec_getpolicy(tdbi, IPSEC_DIR_INBOUND);
+	} else {
+		sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_INBOUND,
+					   IP_FORWARDING, &error);
+	}
+	if (sp == NULL) {	/* NB: can happen if error */
+		splx(s);
+		/*XXX error stat???*/
+		DPRINTF(("ip_input: no SP for forwarding\n"));	/*XXX*/
+		return 1;
+	}
+
+	/*
+	 * Check security policy against packet attributes.
+	 */
+	error = ipsec_in_reject(sp, m);
+	KEY_FREESP(&sp);
+	splx(s);
+	if (error) {
+		ipstat.ips_cantforward++;
+		return 1;
+	}
+#endif /* IPSEC */
+	return 0;
+}
+
+/*
+ * Check if protocol type doesn't have a further header and do IPSEC
+ * decryption or reject right now.  Protocols with further headers get
+ * their IPSEC treatment within the protocol specific processing.
+ * Called from ip_input().
+ * 1 = drop packet, 0 = continue processing packet.
+ */
+int
+ip6_ipsec_input(struct mbuf *m, int nxt)
+{
+#ifdef IPSEC
+	struct m_tag *mtag;
+	struct tdb_ident *tdbi;
+	struct secpolicy *sp;
+	int s, error;
+	/*
+	 * 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)) {
+
+		/*
+		 * Check if the packet has already had IPsec processing
+		 * done.  If so, then just pass it along.  This tag gets
+		 * set during AH, ESP, etc. input handling, before the
+		 * packet is returned to the ip input queue for delivery.
+		 */
+		mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL);
+		s = splnet();
+		if (mtag != NULL) {
+			tdbi = (struct tdb_ident *)(mtag + 1);
+			sp = ipsec_getpolicy(tdbi, IPSEC_DIR_INBOUND);
+		} else {
+			sp = ipsec_getpolicybyaddr(m, IPSEC_DIR_INBOUND,
+						   IP_FORWARDING, &error);
+		}
+		if (sp != NULL) {
+			/*
+			 * Check security policy against packet attributes.
+			 */
+			error = ipsec_in_reject(sp, m);
+			KEY_FREESP(&sp);
+		} else {
+			/* XXX error stat??? */
+			error = EINVAL;
+			DPRINTF(("ip_input: no SP, packet discarded\n"));/*XXX*/
+			return 1;
+		}
+		splx(s);
+		if (error)
+			return 1;
+	}
+#endif /* IPSEC */
+	return 0;
+}
+
+/*
+ * Called from ip6_output().
+ * 1 = drop packet, 0 = continue processing packet,
+ * -1 = packet was reinjected and stop processing packet
+ */
+
+int
+ip6_ipsec_output(struct mbuf **m, struct inpcb *inp, int *flags, int *error,
+    struct ifnet **ifp, struct secpolicy **sp)
+{
+#ifdef IPSEC
+	struct tdb_ident *tdbi;
+	struct m_tag *mtag;
+	/* XXX int s; */
+	if (sp == NULL)
+		return 1;
+	mtag = m_tag_find(*m, PACKET_TAG_IPSEC_PENDING_TDB, NULL);
+	if (mtag != NULL) {
+		tdbi = (struct tdb_ident *)(mtag + 1);
+		*sp = ipsec_getpolicy(tdbi, IPSEC_DIR_OUTBOUND);
+		if (*sp == NULL)
+			*error = -EINVAL;	/* force silent drop */
+		m_tag_delete(*m, mtag);
+	} else {
+		*sp = ipsec4_checkpolicy(*m, IPSEC_DIR_OUTBOUND, *flags,
+					error, inp);
+	}
+
+	/*
+	 * There are four return cases:
+	 *    sp != NULL		    apply IPsec policy
+	 *    sp == NULL, error == 0	    no IPsec handling needed
+	 *    sp == NULL, error == -EINVAL  discard packet w/o error
+	 *    sp == NULL, error != 0	    discard packet, report error
+	 */
+	if (*sp != NULL) {
+		/* Loop detection, check if ipsec processing already done */
+		KASSERT((*sp)->req != NULL, ("ip_output: no ipsec request"));
+		for (mtag = m_tag_first(*m); mtag != NULL;
+		     mtag = m_tag_next(*m, mtag)) {
+			if (mtag->m_tag_cookie != MTAG_ABI_COMPAT)
+				continue;
+			if (mtag->m_tag_id != PACKET_TAG_IPSEC_OUT_DONE &&
+			    mtag->m_tag_id != PACKET_TAG_IPSEC_OUT_CRYPTO_NEEDED)
+				continue;
+			/*
+			 * Check if policy has an SA associated with it.
+			 * This can happen when an SP has yet to acquire
+			 * an SA; e.g. on first reference.  If it occurs,
+			 * then we let ipsec4_process_packet do its thing.
+			 */
+			if ((*sp)->req->sav == NULL)
+				break;
+			tdbi = (struct tdb_ident *)(mtag + 1);
+			if (tdbi->spi == (*sp)->req->sav->spi &&
+			    tdbi->proto == (*sp)->req->sav->sah->saidx.proto &&
+			    bcmp(&tdbi->dst, &(*sp)->req->sav->sah->saidx.dst,
+				 sizeof (union sockaddr_union)) == 0) {
+				/*
+				 * No IPsec processing is needed, free
+				 * reference to SP.
+				 *
+				 * NB: null pointer to avoid free at
+				 *     done: below.
+				 */
+				KEY_FREESP(sp), sp = NULL;
+				/* XXX splx(s); */
+				goto done;
+			}
+		}
+
+		/*
+		 * Do delayed checksums now because we send before
+		 * this is done in the normal processing path.
+		 */
+		if ((*m)->m_pkthdr.csum_flags & CSUM_DELAY_DATA) {
+			in_delayed_cksum(*m);
+			(*m)->m_pkthdr.csum_flags &= ~CSUM_DELAY_DATA;
+		}
+
+		/*
+		 * Preserve KAME behaviour: ENOENT can be returned
+		 * when an SA acquire is in progress.  Don't propagate
+		 * this to user-level; it confuses applications.
+		 *
+		 * XXX this will go away when the SADB is redone.
+		 */
+		if (*error == ENOENT)
+			*error = 0;
+		goto do_ipsec;
+	} else {	/* sp == NULL */
+		if (*error != 0) {
+			/*
+			 * Hack: -EINVAL is used to signal that a packet
+			 * should be silently discarded.  This is typically
+			 * because we asked key management for an SA and
+			 * it was delayed (e.g. kicked up to IKE).
+			 */
+			if (*error == -EINVAL)
+				*error = 0;
+			goto bad;
+		} else {
+			/* No IPsec processing for this packet. */
+		}
+	}
+done:
+	if (sp != NULL)
+		if (*sp != NULL)
+			KEY_FREESP(sp);
+	return 0;
+do_ipsec:
+	return -1;
+bad:
+	if (sp != NULL)
+		if (*sp != NULL)
+			KEY_FREESP(sp);
+	return 1;
+#endif /* IPSEC */
+	return 0;
+}
+
+/*
+ * Compute the MTU for a forwarded packet that gets IPSEC encapsulated.
+ * Called from ip_forward().
+ * Returns MTU suggestion for ICMP needfrag reply.
+ */
+int
+ip6_ipsec_mtu(struct mbuf *m)
+{
+	int mtu = 0;
+	/*
+	 * If the packet is routed over IPsec tunnel, tell the
+	 * originator the tunnel MTU.
+	 *	tunnel MTU = if MTU - sizeof(IP) - ESP/AH hdrsiz
+	 * XXX quickhack!!!
+	 */
+	struct secpolicy *sp = NULL;
+	int ipsecerror;
+	int ipsechdr;
+	struct route *ro;
+#ifdef IPSEC
+	sp = ipsec_getpolicybyaddr(m,
+				   IPSEC_DIR_OUTBOUND,
+				   IP_FORWARDING,
+				   &ipsecerror);
+#endif /* IPSEC */
+	if (sp != NULL) {
+		/* count IPsec header size */
+		ipsechdr = ipsec4_hdrsiz(m,
+					 IPSEC_DIR_OUTBOUND,
+					 NULL);
+
+		/*
+		 * find the correct route for outer IPv4
+		 * header, compute tunnel MTU.
+		 */
+		if (sp->req != NULL &&
+		    sp->req->sav != NULL &&
+		    sp->req->sav->sah != NULL) {
+			ro = &sp->req->sav->sah->sa_route;
+			if (ro->ro_rt && ro->ro_rt->rt_ifp) {
+				mtu =
+				    ro->ro_rt->rt_rmx.rmx_mtu ?
+				    ro->ro_rt->rt_rmx.rmx_mtu :
+				    ro->ro_rt->rt_ifp->if_mtu;
+				mtu -= ipsechdr;
+			}
+		}
+#ifdef IPSEC
+		KEY_FREESP(&sp);
+#endif /* IPSEC */
+	}
+	return mtu;
+}
+
--- /dev/null
+++ sys/netinet6/ip6_ipsec.h
@@ -0,0 +1,41 @@
+/*-
+ * 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.
+ * 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.
+ *
+ * $FreeBSD: src/sys/netinet6/ip6_ipsec.h,v 1.2 2007/08/05 16:16:15 bz Exp $
+ */
+
+#ifndef _NETINET_IP6_IPSEC_H_
+#define _NETINET_IP6_IPSEC_H_
+
+int	ip6_ipsec_filtertunnel(struct mbuf *);
+int	ip6_ipsec_fwd(struct mbuf *);
+int	ip6_ipsec_input(struct mbuf *, int);
+int	ip6_ipsec_mtu(struct mbuf *);
+int	ip6_ipsec_output(struct mbuf **, struct inpcb *, int *, int *,
+			 struct ifnet **, struct secpolicy **sp);
+#endif
--- /dev/null
+++ sys/netinet6/sctp6_usrreq.c
@@ -0,0 +1,1271 @@
+/*-
+ * Copyright (c) 2001-2007, by Cisco Systems, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * a) Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ *
+ * b) 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.
+ *
+ * c) Neither the name of Cisco Systems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
+ */
+/*	$KAME: sctp6_usrreq.c,v 1.38 2005/08/24 08:08:56 suz Exp $	*/
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD: src/sys/netinet6/sctp6_usrreq.c,v 1.41 2007/09/13 10:36:43 rrs Exp $");
+
+
+#include <netinet/sctp_os.h>
+#include <sys/proc.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctp_var.h>
+#if defined(INET6)
+#include <netinet6/sctp6_var.h>
+#endif
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_uio.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_indata.h>
+#include <netinet/sctp_timer.h>
+#include <netinet/sctp_auth.h>
+#include <netinet/sctp_input.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_bsd_addr.h>
+
+#ifdef IPSEC
+#include <netipsec/ipsec.h>
+#if defined(INET6)
+#include <netipsec/ipsec6.h>
+#endif				/* INET6 */
+#endif				/* IPSEC */
+
+extern struct protosw inetsw[];
+
+int
+sctp6_input(struct mbuf **i_pak, int *offp, int proto)
+{
+	struct mbuf *m;
+	struct ip6_hdr *ip6;
+	struct sctphdr *sh;
+	struct sctp_inpcb *in6p = NULL;
+	struct sctp_nets *net;
+	int refcount_up = 0;
+	uint32_t check, calc_check;
+	uint32_t vrf_id = 0;
+	struct inpcb *in6p_ip;
+	struct sctp_chunkhdr *ch;
+	int length, mlen, offset, iphlen;
+	uint8_t ecn_bits;
+	struct sctp_tcb *stcb = NULL;
+	int pkt_len = 0;
+	int off = *offp;
+
+	/* get the VRF and table id's */
+	if (SCTP_GET_PKT_VRFID(*i_pak, vrf_id)) {
+		SCTP_RELEASE_PKT(*i_pak);
+		return (-1);
+	}
+	m = SCTP_HEADER_TO_CHAIN(*i_pak);
+	pkt_len = SCTP_HEADER_LEN((*i_pak));
+
+#ifdef  SCTP_PACKET_LOGGING
+	sctp_packet_log(m, pkt_len);
+#endif
+	ip6 = mtod(m, struct ip6_hdr *);
+	/* Ensure that (sctphdr + sctp_chunkhdr) in a row. */
+	IP6_EXTHDR_GET(sh, struct sctphdr *, m, off,
+	    (int)(sizeof(*sh) + sizeof(*ch)));
+	if (sh == NULL) {
+		SCTP_STAT_INCR(sctps_hdrops);
+		return IPPROTO_DONE;
+	}
+	ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr));
+	iphlen = off;
+	offset = iphlen + sizeof(*sh) + sizeof(*ch);
+	SCTPDBG(SCTP_DEBUG_INPUT1,
+	    "sctp6_input() length:%d iphlen:%d\n", pkt_len, iphlen);
+
+
+#if defined(NFAITH) && NFAITH > 0
+
+	if (faithprefix_p != NULL && (*faithprefix_p) (&ip6->ip6_dst)) {
+		/* XXX send icmp6 host/port unreach? */
+		goto bad;
+	}
+#endif				/* NFAITH defined and > 0 */
+	SCTP_STAT_INCR(sctps_recvpackets);
+	SCTP_STAT_INCR_COUNTER64(sctps_inpackets);
+	SCTPDBG(SCTP_DEBUG_INPUT1, "V6 input gets a packet iphlen:%d pktlen:%d\n",
+	    iphlen, pkt_len);
+	if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) {
+		/* No multi-cast support in SCTP */
+		goto bad;
+	}
+	/* destination port of 0 is illegal, based on RFC2960. */
+	if (sh->dest_port == 0)
+		goto bad;
+	check = sh->checksum;	/* save incoming checksum */
+	if ((check == 0) && (sctp_no_csum_on_loopback) &&
+	    (IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &ip6->ip6_dst))) {
+		goto sctp_skip_csum;
+	}
+	sh->checksum = 0;	/* prepare for calc */
+	calc_check = sctp_calculate_sum(m, &mlen, iphlen);
+	if (calc_check != check) {
+		SCTPDBG(SCTP_DEBUG_INPUT1, "Bad CSUM on SCTP packet calc_check:%x check:%x  m:%p mlen:%d iphlen:%d\n",
+		    calc_check, check, m, mlen, iphlen);
+		stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch),
+		    sh, ch, &in6p, &net, vrf_id);
+		/* in6p's ref-count increased && stcb locked */
+		if ((in6p) && (stcb)) {
+			sctp_send_packet_dropped(stcb, net, m, iphlen, 1);
+			sctp_chunk_output((struct sctp_inpcb *)in6p, stcb, SCTP_OUTPUT_FROM_INPUT_ERROR, SCTP_SO_NOT_LOCKED);
+		} else if ((in6p != NULL) && (stcb == NULL)) {
+			refcount_up = 1;
+		}
+		SCTP_STAT_INCR(sctps_badsum);
+		SCTP_STAT_INCR_COUNTER32(sctps_checksumerrors);
+		goto bad;
+	}
+	sh->checksum = calc_check;
+
+sctp_skip_csum:
+	net = NULL;
+	/*
+	 * Locate pcb and tcb for datagram sctp_findassociation_addr() wants
+	 * IP/SCTP/first chunk header...
+	 */
+	stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch),
+	    sh, ch, &in6p, &net, vrf_id);
+	/* in6p's ref-count increased */
+	if (in6p == NULL) {
+		struct sctp_init_chunk *init_chk, chunk_buf;
+
+		SCTP_STAT_INCR(sctps_noport);
+		if (ch->chunk_type == SCTP_INITIATION) {
+			/*
+			 * we do a trick here to get the INIT tag, dig in
+			 * and get the tag from the INIT and put it in the
+			 * common header.
+			 */
+			init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m,
+			    iphlen + sizeof(*sh), sizeof(*init_chk),
+			    (uint8_t *) & chunk_buf);
+			if (init_chk)
+				sh->v_tag = init_chk->init.initiate_tag;
+			else
+				sh->v_tag = 0;
+		}
+		if (ch->chunk_type == SCTP_SHUTDOWN_ACK) {
+			sctp_send_shutdown_complete2(m, iphlen, sh, vrf_id);
+			goto bad;
+		}
+		if (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) {
+			goto bad;
+		}
+		if (ch->chunk_type != SCTP_ABORT_ASSOCIATION)
+			sctp_send_abort(m, iphlen, sh, 0, NULL, vrf_id);
+		goto bad;
+	} else if (stcb == NULL) {
+		refcount_up = 1;
+	}
+	in6p_ip = (struct inpcb *)in6p;
+#ifdef IPSEC
+	/*
+	 * Check AH/ESP integrity.
+	 */
+	if (in6p_ip && (ipsec6_in_reject(m, in6p_ip))) {
+/* XXX */
+		ipsec6stat.in_polvio++;
+		goto bad;
+	}
+#endif				/* IPSEC */
+
+	/*
+	 * CONTROL chunk processing
+	 */
+	offset -= sizeof(*ch);
+	ecn_bits = ((ntohl(ip6->ip6_flow) >> 20) & 0x000000ff);
+
+	/* Length now holds the total packet length payload + iphlen */
+	length = ntohs(ip6->ip6_plen) + iphlen;
+
+	/* sa_ignore NO_NULL_CHK */
+	sctp_common_input_processing(&m, iphlen, offset, length, sh, ch,
+	    in6p, stcb, net, ecn_bits, vrf_id);
+	/* inp's ref-count reduced && stcb unlocked */
+	/* XXX this stuff below gets moved to appropriate parts later... */
+	if (m)
+		sctp_m_freem(m);
+	if ((in6p) && refcount_up) {
+		/* reduce ref-count */
+		SCTP_INP_WLOCK(in6p);
+		SCTP_INP_DECR_REF(in6p);
+		SCTP_INP_WUNLOCK(in6p);
+	}
+	return IPPROTO_DONE;
+
+bad:
+	if (stcb) {
+		SCTP_TCB_UNLOCK(stcb);
+	}
+	if ((in6p) && refcount_up) {
+		/* reduce ref-count */
+		SCTP_INP_WLOCK(in6p);
+		SCTP_INP_DECR_REF(in6p);
+		SCTP_INP_WUNLOCK(in6p);
+	}
+	if (m)
+		sctp_m_freem(m);
+	return IPPROTO_DONE;
+}
+
+
+static void
+sctp6_notify_mbuf(struct sctp_inpcb *inp, struct icmp6_hdr *icmp6,
+    struct sctphdr *sh, struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+	uint32_t nxtsz;
+
+	if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+	    (icmp6 == NULL) || (sh == NULL)) {
+		goto out;
+	}
+	/* First do we even look at it? */
+	if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag))
+		goto out;
+
+	if (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG) {
+		/* not PACKET TO BIG */
+		goto out;
+	}
+	/*
+	 * ok we need to look closely. We could even get smarter and look at
+	 * anyone that we sent to in case we get a different ICMP that tells
+	 * us there is no way to reach a host, but for this impl, all we
+	 * care about is MTU discovery.
+	 */
+	nxtsz = ntohl(icmp6->icmp6_mtu);
+	/* Stop any PMTU timer */
+	sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL, SCTP_FROM_SCTP6_USRREQ + SCTP_LOC_1);
+
+	/* Adjust destination size limit */
+	if (net->mtu > nxtsz) {
+		net->mtu = nxtsz;
+	}
+	/* now what about the ep? */
+	if (stcb->asoc.smallest_mtu > nxtsz) {
+		struct sctp_tmit_chunk *chk;
+
+		/* Adjust that too */
+		stcb->asoc.smallest_mtu = nxtsz;
+		/* now off to subtract IP_DF flag if needed */
+
+		TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) {
+			if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > nxtsz) {
+				chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+			}
+		}
+		TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+			if ((uint32_t) (chk->send_size + IP_HDR_SIZE) > nxtsz) {
+				/*
+				 * For this guy we also mark for immediate
+				 * resend since we sent to big of chunk
+				 */
+				chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
+				if (chk->sent != SCTP_DATAGRAM_RESEND)
+					stcb->asoc.sent_queue_retran_cnt++;
+				chk->sent = SCTP_DATAGRAM_RESEND;
+				chk->rec.data.doing_fast_retransmit = 0;
+
+				chk->sent = SCTP_DATAGRAM_RESEND;
+				/* Clear any time so NO RTT is being done */
+				chk->sent_rcv_time.tv_sec = 0;
+				chk->sent_rcv_time.tv_usec = 0;
+				stcb->asoc.total_flight -= chk->send_size;
+				net->flight_size -= chk->send_size;
+			}
+		}
+	}
+	sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL);
+out:
+	if (stcb) {
+		SCTP_TCB_UNLOCK(stcb);
+	}
+}
+
+
+void
+sctp6_notify(struct sctp_inpcb *inp,
+    struct icmp6_hdr *icmph,
+    struct sctphdr *sh,
+    struct sockaddr *to,
+    struct sctp_tcb *stcb,
+    struct sctp_nets *net)
+{
+#if defined (__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+	struct socket *so;
+
+#endif
+	/* protection */
+	int reason;
+
+
+	if ((inp == NULL) || (stcb == NULL) || (net == NULL) ||
+	    (sh == NULL) || (to == NULL)) {
+		if (stcb)
+			SCTP_TCB_UNLOCK(stcb);
+		return;
+	}
+	/* First job is to verify the vtag matches what I would send */
+	if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) {
+		SCTP_TCB_UNLOCK(stcb);
+		return;
+	}
+	if (icmph->icmp6_type != ICMP_UNREACH) {
+		/* We only care about unreachable */
+		SCTP_TCB_UNLOCK(stcb);
+		return;
+	}
+	if ((icmph->icmp6_code == ICMP_UNREACH_NET) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_HOST) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_NET_UNKNOWN) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_HOST_UNKNOWN) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_ISOLATED) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_NET_PROHIB) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_HOST_PROHIB) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_FILTER_PROHIB)) {
+
+		/*
+		 * Hmm reachablity problems we must examine closely. If its
+		 * not reachable, we may have lost a network. Or if there is
+		 * NO protocol at the other end named SCTP. well we consider
+		 * it a OOTB abort.
+		 */
+		if (net->dest_state & SCTP_ADDR_REACHABLE) {
+			/* Ok that destination is NOT reachable */
+			SCTP_PRINTF("ICMP (thresh %d/%d) takes interface %p down\n",
+			    net->error_count,
+			    net->failure_threshold,
+			    net);
+
+			net->dest_state &= ~SCTP_ADDR_REACHABLE;
+			net->dest_state |= SCTP_ADDR_NOT_REACHABLE;
+			/*
+			 * JRS 5/14/07 - If a destination is unreachable,
+			 * the PF bit is turned off.  This allows an
+			 * unambiguous use of the PF bit for destinations
+			 * that are reachable but potentially failed. If the
+			 * destination is set to the unreachable state, also
+			 * set the destination to the PF state.
+			 */
+			/*
+			 * Add debug message here if destination is not in
+			 * PF state.
+			 */
+			/* Stop any running T3 timers here? */
+			if (sctp_cmt_on_off && sctp_cmt_pf) {
+				net->dest_state &= ~SCTP_ADDR_PF;
+				SCTPDBG(SCTP_DEBUG_TIMER4, "Destination %p moved from PF to unreachable.\n",
+				    net);
+			}
+			net->error_count = net->failure_threshold + 1;
+			sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN,
+			    stcb, SCTP_FAILED_THRESHOLD,
+			    (void *)net, SCTP_SO_NOT_LOCKED);
+		}
+		SCTP_TCB_UNLOCK(stcb);
+	} else if ((icmph->icmp6_code == ICMP_UNREACH_PROTOCOL) ||
+	    (icmph->icmp6_code == ICMP_UNREACH_PORT)) {
+		/*
+		 * Here the peer is either playing tricks on us, including
+		 * an address that belongs to someone who does not support
+		 * SCTP OR was a userland implementation that shutdown and
+		 * now is dead. In either case treat it like a OOTB abort
+		 * with no TCB
+		 */
+		reason = SCTP_PEER_FAULTY;
+		sctp_abort_notification(stcb, reason, SCTP_SO_NOT_LOCKED);
+#if defined (__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+		so = SCTP_INP_SO(inp);
+		atomic_add_int(&stcb->asoc.refcnt, 1);
+		SCTP_TCB_UNLOCK(stcb);
+		SCTP_SOCKET_LOCK(so, 1);
+		SCTP_TCB_LOCK(stcb);
+		atomic_subtract_int(&stcb->asoc.refcnt, 1);
+#endif
+		(void)sctp_free_assoc(inp, stcb, SCTP_NORMAL_PROC, SCTP_FROM_SCTP_USRREQ + SCTP_LOC_2);
+#if defined (__APPLE__) || defined(SCTP_SO_LOCK_TESTING)
+		SCTP_SOCKET_UNLOCK(so, 1);
+		/* SCTP_TCB_UNLOCK(stcb); MT: I think this is not needed. */
+#endif
+		/* no need to unlock here, since the TCB is gone */
+	} else {
+		SCTP_TCB_UNLOCK(stcb);
+	}
+}
+
+
+
+void
+sctp6_ctlinput(int cmd, struct sockaddr *pktdst, void *d)
+{
+	struct sctphdr sh;
+	struct ip6ctlparam *ip6cp = NULL;
+	uint32_t vrf_id;
+
+	vrf_id = SCTP_DEFAULT_VRFID;
+
+	if (pktdst->sa_family != AF_INET6 ||
+	    pktdst->sa_len != sizeof(struct sockaddr_in6))
+		return;
+
+	if ((unsigned)cmd >= PRC_NCMDS)
+		return;
+	if (PRC_IS_REDIRECT(cmd)) {
+		d = NULL;
+	} else if (inet6ctlerrmap[cmd] == 0) {
+		return;
+	}
+	/* if the parameter is from icmp6, decode it. */
+	if (d != NULL) {
+		ip6cp = (struct ip6ctlparam *)d;
+	} else {
+		ip6cp = (struct ip6ctlparam *)NULL;
+	}
+
+	if (ip6cp) {
+		/*
+		 * XXX: We assume that when IPV6 is non NULL, M and OFF are
+		 * valid.
+		 */
+		/* check if we can safely examine src and dst ports */
+		struct sctp_inpcb *inp = NULL;
+		struct sctp_tcb *stcb = NULL;
+		struct sctp_nets *net = NULL;
+		struct sockaddr_in6 final;
+
+		if (ip6cp->ip6c_m == NULL)
+			return;
+
+		bzero(&sh, sizeof(sh));
+		bzero(&final, sizeof(final));
+		inp = NULL;
+		net = NULL;
+		m_copydata(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(sh),
+		    (caddr_t)&sh);
+		ip6cp->ip6c_src->sin6_port = sh.src_port;
+		final.sin6_len = sizeof(final);
+		final.sin6_family = AF_INET6;
+		final.sin6_addr = ((struct sockaddr_in6 *)pktdst)->sin6_addr;
+		final.sin6_port = sh.dest_port;
+		stcb = sctp_findassociation_addr_sa((struct sockaddr *)ip6cp->ip6c_src,
+		    (struct sockaddr *)&final,
+		    &inp, &net, 1, vrf_id);
+		/* inp's ref-count increased && stcb locked */
+		if (stcb != NULL && inp && (inp->sctp_socket != NULL)) {
+			if (cmd == PRC_MSGSIZE) {
+				sctp6_notify_mbuf(inp,
+				    ip6cp->ip6c_icmp6,
+				    &sh,
+				    stcb,
+				    net);
+				/* inp's ref-count reduced && stcb unlocked */
+			} else {
+				sctp6_notify(inp, ip6cp->ip6c_icmp6, &sh,
+				    (struct sockaddr *)&final,
+				    stcb, net);
+				/* inp's ref-count reduced && stcb unlocked */
+			}
+		} else {
+			if (PRC_IS_REDIRECT(cmd) && inp) {
+				in6_rtchange((struct in6pcb *)inp,
+				    inet6ctlerrmap[cmd]);
+			}
+			if (inp) {
+				/* reduce inp's ref-count */
+				SCTP_INP_WLOCK(inp);
+				SCTP_INP_DECR_REF(inp);
+				SCTP_INP_WUNLOCK(inp);
+			}
+			if (stcb)
+				SCTP_TCB_UNLOCK(stcb);
+		}
+	}
+}
+
+/*
+ * this routine can probably be collasped into the one in sctp_userreq.c
+ * since they do the same thing and now we lookup with a sockaddr
+ */
+static int
+sctp6_getcred(SYSCTL_HANDLER_ARGS)
+{
+	struct xucred xuc;
+	struct sockaddr_in6 addrs[2];
+	struct sctp_inpcb *inp;
+	struct sctp_nets *net;
+	struct sctp_tcb *stcb;
+	int error;
+	uint32_t vrf_id;
+
+	vrf_id = SCTP_DEFAULT_VRFID;
+
+	error = priv_check(req->td, PRIV_NETINET_GETCRED);
+	if (error)
+		return (error);
+
+	if (req->newlen != sizeof(addrs)) {
+		SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return (EINVAL);
+	}
+	if (req->oldlen != sizeof(struct ucred)) {
+		SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return (EINVAL);
+	}
+	error = SYSCTL_IN(req, addrs, sizeof(addrs));
+	if (error)
+		return (error);
+
+	stcb = sctp_findassociation_addr_sa(sin6tosa(&addrs[0]),
+	    sin6tosa(&addrs[1]),
+	    &inp, &net, 1, vrf_id);
+	if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) {
+		if ((inp != NULL) && (stcb == NULL)) {
+			/* reduce ref-count */
+			SCTP_INP_WLOCK(inp);
+			SCTP_INP_DECR_REF(inp);
+			goto cred_can_cont;
+		}
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+		error = ENOENT;
+		goto out;
+	}
+	SCTP_TCB_UNLOCK(stcb);
+	/*
+	 * We use the write lock here, only since in the error leg we need
+	 * it. If we used RLOCK, then we would have to
+	 * wlock/decr/unlock/rlock. Which in theory could create a hole.
+	 * Better to use higher wlock.
+	 */
+	SCTP_INP_WLOCK(inp);
+cred_can_cont:
+	error = cr_canseesocket(req->td->td_ucred, inp->sctp_socket);
+	if (error) {
+		SCTP_INP_WUNLOCK(inp);
+		goto out;
+	}
+	cru2x(inp->sctp_socket->so_cred, &xuc);
+	SCTP_INP_WUNLOCK(inp);
+	error = SYSCTL_OUT(req, &xuc, sizeof(struct xucred));
+out:
+	return (error);
+}
+
+SYSCTL_PROC(_net_inet6_sctp6, OID_AUTO, getcred, CTLTYPE_OPAQUE | CTLFLAG_RW,
+    0, 0,
+    sctp6_getcred, "S,ucred", "Get the ucred of a SCTP6 connection");
+
+
+/* This is the same as the sctp_abort() could be made common */
+static void
+sctp6_abort(struct socket *so)
+{
+	struct sctp_inpcb *inp;
+	uint32_t flags;
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == 0) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return;
+	}
+sctp_must_try_again:
+	flags = inp->sctp_flags;
+#ifdef SCTP_LOG_CLOSING
+	sctp_log_closing(inp, NULL, 17);
+#endif
+	if (((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) &&
+	    (atomic_cmpset_int(&inp->sctp_flags, flags, (flags | SCTP_PCB_FLAGS_SOCKET_GONE | SCTP_PCB_FLAGS_CLOSE_IP)))) {
+#ifdef SCTP_LOG_CLOSING
+		sctp_log_closing(inp, NULL, 16);
+#endif
+		sctp_inpcb_free(inp, SCTP_FREE_SHOULD_USE_ABORT,
+		    SCTP_CALLED_AFTER_CMPSET_OFCLOSE);
+		SOCK_LOCK(so);
+		SCTP_SB_CLEAR(so->so_snd);
+		/*
+		 * same for the rcv ones, they are only here for the
+		 * accounting/select.
+		 */
+		SCTP_SB_CLEAR(so->so_rcv);
+		/* Now null out the reference, we are completely detached. */
+		so->so_pcb = NULL;
+		SOCK_UNLOCK(so);
+	} else {
+		flags = inp->sctp_flags;
+		if ((flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) {
+			goto sctp_must_try_again;
+		}
+	}
+	return;
+}
+
+static int
+sctp6_attach(struct socket *so, int proto, struct thread *p)
+{
+	struct in6pcb *inp6;
+	int error;
+	struct sctp_inpcb *inp;
+	uint32_t vrf_id = SCTP_DEFAULT_VRFID;
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp != NULL) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return EINVAL;
+	}
+	if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) {
+		error = SCTP_SORESERVE(so, sctp_sendspace, sctp_recvspace);
+		if (error)
+			return error;
+	}
+	error = sctp_inpcb_alloc(so, vrf_id);
+	if (error)
+		return error;
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	SCTP_INP_WLOCK(inp);
+	inp->sctp_flags |= SCTP_PCB_FLAGS_BOUND_V6;	/* I'm v6! */
+	inp6 = (struct in6pcb *)inp;
+
+	inp6->inp_vflag |= INP_IPV6;
+	inp6->in6p_hops = -1;	/* use kernel default */
+	inp6->in6p_cksum = -1;	/* just to be sure */
+#ifdef INET
+	/*
+	 * 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.
+	 */
+	inp6->inp_ip_ttl = ip_defttl;
+#endif
+	/*
+	 * Hmm what about the IPSEC stuff that is missing here but in
+	 * sctp_attach()?
+	 */
+	SCTP_INP_WUNLOCK(inp);
+	return 0;
+}
+
+static int
+sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+	struct sctp_inpcb *inp;
+	struct in6pcb *inp6;
+	int error;
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == 0) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return EINVAL;
+	}
+	if (addr) {
+		if ((addr->sa_family == AF_INET6) &&
+		    (addr->sa_len != sizeof(struct sockaddr_in6))) {
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+		if ((addr->sa_family == AF_INET) &&
+		    (addr->sa_len != sizeof(struct sockaddr_in))) {
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+	}
+	inp6 = (struct in6pcb *)inp;
+	inp6->inp_vflag &= ~INP_IPV4;
+	inp6->inp_vflag |= INP_IPV6;
+	if ((addr != NULL) && (SCTP_IPV6_V6ONLY(inp6) == 0)) {
+		if (addr->sa_family == AF_INET) {
+			/* binding v4 addr to v6 socket, so reset flags */
+			inp6->inp_vflag |= INP_IPV4;
+			inp6->inp_vflag &= ~INP_IPV6;
+		} else {
+			struct sockaddr_in6 *sin6_p;
+
+			sin6_p = (struct sockaddr_in6 *)addr;
+
+			if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) {
+				inp6->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);
+				inp6->inp_vflag |= INP_IPV4;
+				inp6->inp_vflag &= ~INP_IPV6;
+				error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, NULL, p);
+				return error;
+			}
+		}
+	} else if (addr != NULL) {
+		/* IPV6_V6ONLY socket */
+		if (addr->sa_family == AF_INET) {
+			/* can't bind v4 addr to v6 only socket! */
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		} else {
+			struct sockaddr_in6 *sin6_p;
+
+			sin6_p = (struct sockaddr_in6 *)addr;
+
+			if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) {
+				/* can't bind v4-mapped addrs either! */
+				/* NOTE: we don't support SIIT */
+				SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+				return EINVAL;
+			}
+		}
+	}
+	error = sctp_inpcb_bind(so, addr, NULL, p);
+	return error;
+}
+
+
+static void
+sctp6_close(struct socket *so)
+{
+	sctp_close(so);
+}
+
+/* This could be made common with sctp_detach() since they are identical */
+
+static
+int
+sctp6_disconnect(struct socket *so)
+{
+	return (sctp_disconnect(so));
+}
+
+
+int
+sctp_sendm(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+    struct mbuf *control, struct thread *p);
+
+
+static int
+sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr,
+    struct mbuf *control, struct thread *p)
+{
+	struct sctp_inpcb *inp;
+	struct inpcb *in_inp;
+	struct in6pcb *inp6;
+
+#ifdef INET
+	struct sockaddr_in6 *sin6;
+
+#endif				/* INET */
+	/* No SPL needed since sctp_output does this */
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == NULL) {
+		if (control) {
+			SCTP_RELEASE_PKT(control);
+			control = NULL;
+		}
+		SCTP_RELEASE_PKT(m);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return EINVAL;
+	}
+	in_inp = (struct inpcb *)inp;
+	inp6 = (struct in6pcb *)inp;
+	/*
+	 * For the TCP model we may get a NULL addr, if we are a connected
+	 * socket thats ok.
+	 */
+	if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) &&
+	    (addr == NULL)) {
+		goto connected_type;
+	}
+	if (addr == NULL) {
+		SCTP_RELEASE_PKT(m);
+		if (control) {
+			SCTP_RELEASE_PKT(control);
+			control = NULL;
+		}
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EDESTADDRREQ);
+		return (EDESTADDRREQ);
+	}
+#ifdef INET
+	sin6 = (struct sockaddr_in6 *)addr;
+	if (SCTP_IPV6_V6ONLY(inp6)) {
+		/*
+		 * if IPV6_V6ONLY flag, we discard datagrams destined to a
+		 * v4 addr or v4-mapped addr
+		 */
+		if (addr->sa_family == AF_INET) {
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+		if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+	}
+	if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+		if (!ip6_v6only) {
+			struct sockaddr_in sin;
+
+			/* convert v4-mapped into v4 addr and send */
+			in6_sin6_2_sin(&sin, sin6);
+			return sctp_sendm(so, flags, m, (struct sockaddr *)&sin,
+			    control, p);
+		} else {
+			/* mapped addresses aren't enabled */
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+	}
+#endif				/* INET */
+connected_type:
+	/* now what about control */
+	if (control) {
+		if (inp->control) {
+			SCTP_PRINTF("huh? control set?\n");
+			SCTP_RELEASE_PKT(inp->control);
+			inp->control = NULL;
+		}
+		inp->control = control;
+	}
+	/* Place the data */
+	if (inp->pkt) {
+		SCTP_BUF_NEXT(inp->pkt_last) = m;
+		inp->pkt_last = m;
+	} else {
+		inp->pkt_last = inp->pkt = m;
+	}
+	if (
+	/* FreeBSD and MacOSX uses a flag passed */
+	    ((flags & PRUS_MORETOCOME) == 0)
+	    ) {
+		/*
+		 * note with the current version this code will only be used
+		 * by OpenBSD, NetBSD and FreeBSD have methods for
+		 * re-defining sosend() to use sctp_sosend().  One can
+		 * optionaly switch back to this code (by changing back the
+		 * defininitions but this is not advisable.
+		 */
+		int ret;
+
+		ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags);
+		inp->pkt = NULL;
+		inp->control = NULL;
+		return (ret);
+	} else {
+		return (0);
+	}
+}
+
+static int
+sctp6_connect(struct socket *so, struct sockaddr *addr, struct thread *p)
+{
+	uint32_t vrf_id;
+	int error = 0;
+	struct sctp_inpcb *inp;
+	struct in6pcb *inp6;
+	struct sctp_tcb *stcb;
+
+#ifdef INET
+	struct sockaddr_in6 *sin6;
+	struct sockaddr_storage ss;
+
+#endif				/* INET */
+
+	inp6 = (struct in6pcb *)so->so_pcb;
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == 0) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+		return (ECONNRESET);	/* I made the same as TCP since we are
+					 * not setup? */
+	}
+	if (addr == NULL) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return (EINVAL);
+	}
+	if ((addr->sa_family == AF_INET6) && (addr->sa_len != sizeof(struct sockaddr_in6))) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return (EINVAL);
+	}
+	if ((addr->sa_family == AF_INET) && (addr->sa_len != sizeof(struct sockaddr_in))) {
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return (EINVAL);
+	}
+	vrf_id = inp->def_vrf_id;
+	SCTP_ASOC_CREATE_LOCK(inp);
+	SCTP_INP_RLOCK(inp);
+	if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) ==
+	    SCTP_PCB_FLAGS_UNBOUND) {
+		/* Bind a ephemeral port */
+		SCTP_INP_RUNLOCK(inp);
+		error = sctp6_bind(so, NULL, p);
+		if (error) {
+			SCTP_ASOC_CREATE_UNLOCK(inp);
+
+			return (error);
+		}
+		SCTP_INP_RLOCK(inp);
+	}
+	if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
+	    (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) {
+		/* We are already connected AND the TCP model */
+		SCTP_INP_RUNLOCK(inp);
+		SCTP_ASOC_CREATE_UNLOCK(inp);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EADDRINUSE);
+		return (EADDRINUSE);
+	}
+#ifdef INET
+	sin6 = (struct sockaddr_in6 *)addr;
+	if (SCTP_IPV6_V6ONLY(inp6)) {
+		/*
+		 * if IPV6_V6ONLY flag, ignore connections destined to a v4
+		 * addr or v4-mapped addr
+		 */
+		if (addr->sa_family == AF_INET) {
+			SCTP_INP_RUNLOCK(inp);
+			SCTP_ASOC_CREATE_UNLOCK(inp);
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+		if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+			SCTP_INP_RUNLOCK(inp);
+			SCTP_ASOC_CREATE_UNLOCK(inp);
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+	}
+	if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+		if (!ip6_v6only) {
+			/* convert v4-mapped into v4 addr */
+			in6_sin6_2_sin((struct sockaddr_in *)&ss, sin6);
+			addr = (struct sockaddr *)&ss;
+		} else {
+			/* mapped addresses aren't enabled */
+			SCTP_INP_RUNLOCK(inp);
+			SCTP_ASOC_CREATE_UNLOCK(inp);
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+			return EINVAL;
+		}
+	} else
+#endif				/* INET */
+		addr = addr;	/* for true v6 address case */
+
+	/* Now do we connect? */
+	if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+		stcb = LIST_FIRST(&inp->sctp_asoc_list);
+		if (stcb) {
+			SCTP_TCB_UNLOCK(stcb);
+		}
+		SCTP_INP_RUNLOCK(inp);
+	} else {
+		SCTP_INP_RUNLOCK(inp);
+		SCTP_INP_WLOCK(inp);
+		SCTP_INP_INCR_REF(inp);
+		SCTP_INP_WUNLOCK(inp);
+		stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL);
+		if (stcb == NULL) {
+			SCTP_INP_WLOCK(inp);
+			SCTP_INP_DECR_REF(inp);
+			SCTP_INP_WUNLOCK(inp);
+		}
+	}
+
+	if (stcb != NULL) {
+		/* Already have or am bring up an association */
+		SCTP_ASOC_CREATE_UNLOCK(inp);
+		SCTP_TCB_UNLOCK(stcb);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EALREADY);
+		return (EALREADY);
+	}
+	/* We are GOOD to go */
+	stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0, vrf_id, p);
+	SCTP_ASOC_CREATE_UNLOCK(inp);
+	if (stcb == NULL) {
+		/* Gak! no memory */
+		return (error);
+	}
+	if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
+		stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
+		/* Set the connected flag so we can queue data */
+		soisconnecting(so);
+	}
+	stcb->asoc.state = SCTP_STATE_COOKIE_WAIT;
+	(void)SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered);
+
+	/* initialize authentication parameters for the assoc */
+	sctp_initialize_auth_params(inp, stcb);
+
+	sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
+	SCTP_TCB_UNLOCK(stcb);
+	return error;
+}
+
+static int
+sctp6_getaddr(struct socket *so, struct sockaddr **addr)
+{
+	struct sockaddr_in6 *sin6;
+	struct sctp_inpcb *inp;
+	uint32_t vrf_id;
+	struct sctp_ifa *sctp_ifa;
+
+	int error;
+
+	/*
+	 * Do the malloc first in case it blocks.
+	 */
+	SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_len = sizeof(*sin6);
+
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == NULL) {
+		SCTP_FREE_SONAME(sin6);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+		return ECONNRESET;
+	}
+	SCTP_INP_RLOCK(inp);
+	sin6->sin6_port = inp->sctp_lport;
+	if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+		/* For the bound all case you get back 0 */
+		if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
+			struct sctp_tcb *stcb;
+			struct sockaddr_in6 *sin_a6;
+			struct sctp_nets *net;
+			int fnd;
+
+			stcb = LIST_FIRST(&inp->sctp_asoc_list);
+			if (stcb == NULL) {
+				goto notConn6;
+			}
+			fnd = 0;
+			sin_a6 = NULL;
+			TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+				sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+				if (sin_a6 == NULL)
+					/* this will make coverity happy */
+					continue;
+
+				if (sin_a6->sin6_family == AF_INET6) {
+					fnd = 1;
+					break;
+				}
+			}
+			if ((!fnd) || (sin_a6 == NULL)) {
+				/* punt */
+				goto notConn6;
+			}
+			vrf_id = inp->def_vrf_id;
+			sctp_ifa = sctp_source_address_selection(inp, stcb, (sctp_route_t *) & net->ro, net, 0, vrf_id);
+			if (sctp_ifa) {
+				sin6->sin6_addr = sctp_ifa->address.sin6.sin6_addr;
+			}
+		} else {
+			/* For the bound all case you get back 0 */
+	notConn6:
+			memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr));
+		}
+	} else {
+		/* Take the first IPv6 address in the list */
+		struct sctp_laddr *laddr;
+		int fnd = 0;
+
+		LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) {
+			if (laddr->ifa->address.sa.sa_family == AF_INET6) {
+				struct sockaddr_in6 *sin_a;
+
+				sin_a = (struct sockaddr_in6 *)&laddr->ifa->address.sin6;
+				sin6->sin6_addr = sin_a->sin6_addr;
+				fnd = 1;
+				break;
+			}
+		}
+		if (!fnd) {
+			SCTP_FREE_SONAME(sin6);
+			SCTP_INP_RUNLOCK(inp);
+			SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+			return ENOENT;
+		}
+	}
+	SCTP_INP_RUNLOCK(inp);
+	/* Scoping things for v6 */
+	if ((error = sa6_recoverscope(sin6)) != 0) {
+		SCTP_FREE_SONAME(sin6);
+		return (error);
+	}
+	(*addr) = (struct sockaddr *)sin6;
+	return (0);
+}
+
+static int
+sctp6_peeraddr(struct socket *so, struct sockaddr **addr)
+{
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)*addr;
+	int fnd;
+	struct sockaddr_in6 *sin_a6;
+	struct sctp_inpcb *inp;
+	struct sctp_tcb *stcb;
+	struct sctp_nets *net;
+
+	int error;
+
+	/*
+	 * Do the malloc first in case it blocks.
+	 */
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) {
+		/* UDP type and listeners will drop out here */
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOTCONN);
+		return (ENOTCONN);
+	}
+	SCTP_MALLOC_SONAME(sin6, struct sockaddr_in6 *, sizeof *sin6);
+	sin6->sin6_family = AF_INET6;
+	sin6->sin6_len = sizeof(*sin6);
+
+	/* We must recapture incase we blocked */
+	inp = (struct sctp_inpcb *)so->so_pcb;
+	if (inp == NULL) {
+		SCTP_FREE_SONAME(sin6);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+		return ECONNRESET;
+	}
+	SCTP_INP_RLOCK(inp);
+	stcb = LIST_FIRST(&inp->sctp_asoc_list);
+	if (stcb) {
+		SCTP_TCB_LOCK(stcb);
+	}
+	SCTP_INP_RUNLOCK(inp);
+	if (stcb == NULL) {
+		SCTP_FREE_SONAME(sin6);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ECONNRESET);
+		return ECONNRESET;
+	}
+	fnd = 0;
+	TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+		sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+		if (sin_a6->sin6_family == AF_INET6) {
+			fnd = 1;
+			sin6->sin6_port = stcb->rport;
+			sin6->sin6_addr = sin_a6->sin6_addr;
+			break;
+		}
+	}
+	SCTP_TCB_UNLOCK(stcb);
+	if (!fnd) {
+		/* No IPv4 address */
+		SCTP_FREE_SONAME(sin6);
+		SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, ENOENT);
+		return ENOENT;
+	}
+	if ((error = sa6_recoverscope(sin6)) != 0)
+		return (error);
+	*addr = (struct sockaddr *)sin6;
+	return (0);
+}
+
+static int
+sctp6_in6getaddr(struct socket *so, struct sockaddr **nam)
+{
+	struct sockaddr *addr;
+	struct in6pcb *inp6 = sotoin6pcb(so);
+	int error;
+
+	if (inp6 == NULL) {
+		SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return EINVAL;
+	}
+	/* allow v6 addresses precedence */
+	error = sctp6_getaddr(so, nam);
+	if (error) {
+		/* try v4 next if v6 failed */
+		error = sctp_ingetaddr(so, nam);
+		if (error) {
+			return (error);
+		}
+		addr = *nam;
+		/* if I'm V6ONLY, convert it to v4-mapped */
+		if (SCTP_IPV6_V6ONLY(inp6)) {
+			struct sockaddr_in6 sin6;
+
+			in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+			memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+
+		}
+	}
+	return (error);
+}
+
+
+static int
+sctp6_getpeeraddr(struct socket *so, struct sockaddr **nam)
+{
+	struct sockaddr *addr = *nam;
+	struct in6pcb *inp6 = sotoin6pcb(so);
+	int error;
+
+	if (inp6 == NULL) {
+		SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP6_USRREQ, EINVAL);
+		return EINVAL;
+	}
+	/* allow v6 addresses precedence */
+	error = sctp6_peeraddr(so, nam);
+	if (error) {
+		/* try v4 next if v6 failed */
+		error = sctp_peeraddr(so, nam);
+		if (error) {
+			return (error);
+		}
+		/* if I'm V6ONLY, convert it to v4-mapped */
+		if (SCTP_IPV6_V6ONLY(inp6)) {
+			struct sockaddr_in6 sin6;
+
+			in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6);
+			memcpy(addr, &sin6, sizeof(struct sockaddr_in6));
+		}
+	}
+	return error;
+}
+
+struct pr_usrreqs sctp6_usrreqs = {
+	.pru_abort = sctp6_abort,
+	.pru_accept = sctp_accept,
+	.pru_attach = sctp6_attach,
+	.pru_bind = sctp6_bind,
+	.pru_connect = sctp6_connect,
+	.pru_control = in6_control,
+	.pru_close = sctp6_close,
+	.pru_detach = sctp6_close,
+	.pru_sopoll = sopoll_generic,
+	.pru_disconnect = sctp6_disconnect,
+	.pru_listen = sctp_listen,
+	.pru_peeraddr = sctp6_getpeeraddr,
+	.pru_send = sctp6_send,
+	.pru_shutdown = sctp_shutdown,
+	.pru_sockaddr = sctp6_in6getaddr,
+	.pru_sosend = sctp_sosend,
+	.pru_soreceive = sctp_soreceive
+};


More information about the Midnightbsd-cvs mailing list