summaryrefslogtreecommitdiffstats
path: root/sys/netinet6/ipv6_output.c
diff options
context:
space:
mode:
authorderaadt <deraadt@openbsd.org>1999-01-06 23:26:55 +0000
committerderaadt <deraadt@openbsd.org>1999-01-06 23:26:55 +0000
commitd3b325d04f91aca047d457317e946ad0efb2adaa (patch)
tree67856b54ad4966cc198d6fad776906a93118012e /sys/netinet6/ipv6_output.c
parent_THREAD_SAFE (diff)
downloadwireguard-openbsd-d3b325d04f91aca047d457317e946ad0efb2adaa.tar.xz
wireguard-openbsd-d3b325d04f91aca047d457317e946ad0efb2adaa.zip
first few files of NRL ipv6. This NRL release was officially exported
to me by US DOD officials, with the crypto already removed.
Diffstat (limited to 'sys/netinet6/ipv6_output.c')
-rw-r--r--sys/netinet6/ipv6_output.c2078
1 files changed, 2078 insertions, 0 deletions
diff --git a/sys/netinet6/ipv6_output.c b/sys/netinet6/ipv6_output.c
new file mode 100644
index 00000000000..cd845d99324
--- /dev/null
+++ b/sys/netinet6/ipv6_output.c
@@ -0,0 +1,2078 @@
+/*
+%%% copyright-nrl-95
+This software is Copyright 1995-1998 by Randall Atkinson, Ronald Lee,
+Daniel McDonald, Bao Phan, and Chris Winters. All Rights Reserved. All
+rights under this copyright have been assigned to the US Naval Research
+Laboratory (NRL). The NRL Copyright Notice and License Agreement Version
+1.1 (January 17, 1995) applies to this software.
+You should have received a copy of the license with this software. If you
+didn't get a copy, you may request one from <license@ipv6.nrl.navy.mil>.
+
+*/
+
+#include <sys/param.h>
+#include <sys/protosw.h>
+#include <sys/domain.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+
+#include <net/if.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/ipv6.h>
+#include <netinet6/ipv6_var.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/ip.h>
+#include <netinet/in_pcb.h>
+#include <netinet/tcp.h>
+#include <netinet/tcp_timer.h>
+#include <netinet/ip_var.h>
+#include <netinet/tcpip.h>
+#include <netinet/tcp_var.h>
+
+#if __OpenBSD__ && NRL_IPSEC
+#define IPSEC 1
+#endif /* __OpenBSD__ && NRL_IPSEC */
+
+#if IPSEC
+#include <sys/osdep.h>
+#include <net/netproc.h>
+#include <net/netproc_var.h>
+#include <sys/nbuf.h>
+#endif /* IPSEC */
+
+#include <sys/debug.h>
+
+/*
+ * Globals and function definitions.
+ */
+
+uint32_t outfragid = 0; /* Outbound fragment groups have unique id's. */
+struct mbuf *ipv6_fragment __P((struct mbuf *,int));
+
+/*
+ * External globals.
+ */
+
+extern struct ipv6stat ipv6stat;
+extern struct in6_ifaddr *in6_ifaddr;
+extern struct in6_ifnet *in6_ifnet;
+extern struct ifnet *mcastdefault;
+extern int ipv6_defhoplmt;
+
+#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802)
+extern struct ifnet *loifp;
+#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */
+
+void ipv6_nsolicit __P((struct ifnet *, struct mbuf *, struct rtentry *));
+int ipv6_tunnel_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *));
+
+int ipv6_setmoptions __P((int, struct inpcb *, void *, size_t));
+int ipv6_getmoptions __P((int, struct ipv6_moptions *, int *));
+void ipv6_mloopback __P((struct ifnet *, struct mbuf *, struct sockaddr_in6 *));
+int ipv6_finddivpoint __P((struct mbuf *, uint8_t *));
+
+int ipv6_controltoheader(struct mbuf **m, struct mbuf *control, struct ifnet **forceifp, int *);
+
+/*----------------------------------------------------------------------
+ * IPv6 output routine. The mbuf chain contains a near-complete IPv6 header,
+ * and an already-inserted list of options. (I figure it's something for
+ * the code with PCB access to handle.) The options should have their
+ * fields in network order. The header should have its fields in host order.
+ * (Save the addresses, which IMHO are always in network order. Weird.)
+ ----------------------------------------------------------------------*/
+
+int
+ipv6_output(outgoing,ro,flags,i6mo, forceifp, socket)
+ struct mbuf *outgoing;
+ struct route6 *ro;
+ int flags;
+ struct ipv6_moptions *i6mo;
+ struct ifnet *forceifp;
+ struct socket *socket;
+{
+ struct ipv6 *header;
+ struct route6 ipv6route;
+ struct sockaddr_in6 *dst;
+ struct in6_ifaddr *i6a = NULL;
+ struct ifnet *ifp = NULL;
+ int error=0;
+ uint32_t outmtu = 0;
+
+#ifdef DIAGNOSTIC
+ if ((outgoing->m_flags & M_PKTHDR) == 0)
+ panic("ipv6_output() no HDR");
+#endif
+
+ /*
+ * Assume the IPv6 header is already contiguous.
+ */
+ header = mtod(outgoing, struct ipv6 *);
+
+ DDO(IDL_FINISHED,printf("ipv6_output:\n");dump_ipv6(header));
+ DPRINTF(IDL_FINISHED,("\n"));
+
+ /*
+ * Fill in v6 header. Assume flow id/version field is in network order,
+ * and that the high 4 bits are 0's.
+ */
+
+ if ((flags & (IPV6_FORWARDING|IPV6_RAWOUTPUT)) == 0)
+ {
+ header->ipv6_versfl = htonl(0x60000000) |
+ (header->ipv6_versfl & htonl(0x0fffffff));
+ ipv6stat.ips_localout++;
+ }
+
+ /*
+ * Determine interface and physical destination to send datagram out
+ * towards. Do this by looking up a route, or using the route we were
+ * passed.
+ */
+
+ DPRINTF(IDL_FINISHED,("route passed to ipv6_output is:\n"));
+ DDO(IDL_FINISHED,if (ro) dump_rtentry(ro->ro_rt));
+ if (ro == 0)
+ {
+ ro = &ipv6route;
+ bzero((caddr_t)ro,sizeof(struct route6));
+ }
+ dst = &ro->ro_dst;
+
+ if (ro->ro_rt && ((ro->ro_rt->rt_flags & RTF_UP) == 0 ||
+ !IN6_ARE_ADDR_EQUAL(&dst->sin6_addr, &header->ipv6_dst)))
+ {
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ }
+
+ if (ro->ro_rt == NULL)
+ {
+ dst->sin6_family = AF_INET6;
+ dst->sin6_len = sizeof(struct sockaddr_in6);
+ dst->sin6_addr = header->ipv6_dst;
+ dst->sin6_port = 0;
+ dst->sin6_flowinfo = 0;
+ }
+
+#define ifatoi6a(ifa) ((struct in6_ifaddr *)(ifa))
+#define sin6tosa(sin6) ((struct sockaddr *)(sin6))
+
+ if (flags & IPV6_ROUTETOIF)
+ {
+ /*
+ * Check for route to interface only. (i.e. the user doesn't want
+ * to rely on the routing tables, so send it out an interface).
+ */
+ if ((i6a = ifatoi6a(ifa_ifwithdstaddr(sin6tosa(dst)))) == 0 &&
+ (i6a = ifatoi6a(ifa_ifwithnet(sin6tosa(dst)))) == 0 )
+ {
+ /*
+ * Q: Do we want to assume that if a user specifies this option,
+ * the user doesn't want ANYTHING to do with the routing tables?
+ */
+
+ ipv6stat.ips_noroute++;
+ error = ENETUNREACH;
+ goto bad;
+ }
+ ifp = i6a->i6a_ifp;
+ header->ipv6_hoplimit = 1;
+ outmtu = ifp->if_mtu;
+ }
+ else
+ {
+ /*
+ * Do normal next-hop determination with the help of the routing tree.
+ */
+ if (ro->ro_rt == 0)
+ rtalloc((struct route *)ro); /* Initial route lookup. */
+
+ if (ro->ro_rt == 0)
+ {
+ /*
+ * No route of any kind, so spray neighbor solicits out all
+ * interfaces, unless it's a multicast address.
+ */
+ if (IN6_IS_ADDR_MULTICAST(&header->ipv6_dst))
+ goto mcast;
+ DPRINTF(IDL_FINISHED, ("v6_output doesn't have a route...calling onlink_query!\n"));
+ ipv6_onlink_query(dst);
+ rtalloc((struct route *)ro);
+ }
+ if (ro->ro_rt == NULL)
+ {
+ /*
+ * ipv6_onlink_query() should've added a route. Probably
+ * failed.
+ */
+ DPRINTF(IDL_GROSS_EVENT, ("v6_output: onlink_query didn't add route!\n"));
+ ipv6stat.ips_noroute++;
+ error = ENETUNREACH;
+ goto bad;
+ }
+
+ if (ro->ro_rt->rt_ifa == NULL)
+ {
+ /*
+ * We have a route where we don't quite know which interface
+ * the neighbor belongs to yet. If I get here, I know that this
+ * route is not pre-allocated (such as done by in6_pcbconnect()),
+ * because those pre-allocators will do the same ipv6_onlink_query()
+ * and ipv6_verify_onlink() in advance.
+ *
+ * I can therefore free the route, and get it again.
+ * Multicast dgrams should NEVER be in this code path.
+ */
+
+ RTFREE(ro->ro_rt);
+ ro->ro_rt = NULL;
+ DPRINTF(IDL_FINISHED,("v6_output calling ipv6_verify_onlink\n"));
+ if ((error = ipv6_verify_onlink(dst)) != 0)
+ {
+ if (error == -1)
+ {
+ DPRINTF(IDL_ERROR,("verify_onlink() failed in v6_out.\n"));
+ error = ENETUNREACH;
+ }
+ ipv6stat.ips_noroute++; /* Better stat needed, because
+ error might not be
+ E{NET,HOST}UNREACH. */
+
+ goto bad;
+ }
+ rtalloc((struct route *)ro);
+ if (ro->ro_rt == NULL || ro->ro_rt->rt_ifa == NULL)
+ panic("Oops, I'm forgetting something after verify_onlink().");
+ }
+
+ /*
+ * Exploit properties of route.
+ */
+
+ ifp = ro->ro_rt->rt_ifp; /* Q: Is this REALLY the ifp
+ for the route?
+
+ A: Maybe. If multi-homed,
+ and we attempt to
+ intelligently figure out
+ where link-locals are
+ destined, then we're
+ in trouble. */
+ /*
+ * On-net route exists, but no destination as of yet. This can
+ * be snipped if an ifp is just selected. (Depends on multihomed
+ * experience.)
+ *
+ * Currently, this code never executes, because we guarantee rt_ifp is
+ * set. This may, however, change in later versions of this code as
+ * we gain multihomed experience.
+ */
+ if (ifp == NULL && ro->ro_rt->rt_gateway->sa_family == AF_LINK)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("ipv6_output() calling ipv6_nsolicit(2)\n"));
+ ipv6_nsolicit(NULL, outgoing, ro->ro_rt);
+ DPRINTF(IDL_EVENT,\
+ ("ipv6_output() attempted to send neighbor solicit(2), returning.\n"));
+ goto done;
+ }
+
+ /*
+ * Q: What if address has expired? Perhaps I should use ifp to
+ * obtain optimal i6a value. There's also the question of using
+ * link-local source addresses for off-link communication. (or for
+ * that matter, on-link, but not link-local communication.
+ *
+ * Q2: Perhaps use this time to reset the route's ifa?
+ * Q3: Perhaps it is better to use the ipv6_rtrequest()?
+ *
+ * Regardless, i6a's only use in this function is to determine the
+ * source address of the packet.
+ *
+ * Currently, ipv6_rtrequest() attempts to store a decent in6_ifaddr
+ * in rt_ifa. This also may change with experience.
+ */
+
+ i6a = ifatoi6a(ro->ro_rt->rt_ifa);
+ if (i6a->i6a_addrflags & I6AF_NOTSURE)
+ if (!(outgoing->m_flags & M_DAD))
+ {
+ /*
+ * 1. Think of a better error.
+ *
+ * 2. Keep some sort of statistic.
+ */
+ DPRINTF(IDL_ERROR,("Using NOTSURE source address.\n"));
+ error = EADDRNOTAVAIL;
+ goto bad;
+ }
+ else i6a = NULL;
+
+ /*
+ * More source address selection goes here.
+ */
+
+ ro->ro_rt->rt_use++;
+ /*
+ * Path MTU comes from the route entry.
+ */
+ outmtu = ro->ro_rt->rt_rmx.rmx_mtu;
+
+ if (ro->ro_rt->rt_flags & RTF_GATEWAY) /* Gateway/router/whatever. */
+ dst = (struct sockaddr_in6 *)ro->ro_rt->rt_gateway;
+ }
+
+ if (forceifp) {
+ DPRINTF(IDL_EVENT, ("ipv6_output: in forceifp case\n"));
+ ifp = forceifp;
+ if (outmtu > ifp->if_mtu)
+ outmtu = ifp->if_mtu;
+ };
+
+ /*
+ * Handle case of a multicast destination.
+ */
+ mcast:
+ if (IN6_IS_ADDR_MULTICAST(&header->ipv6_dst))
+ {
+ struct in6_multi *in6m;
+
+ outgoing->m_flags |= M_MCAST;
+
+ dst = &ro->ro_dst;
+
+ if (i6mo != NULL)
+ {
+ /*
+ * As we gain more multicast experience, use i6mo fields to alter
+ * properties of outgoing packet. (Including, quite possibly,
+ * the source address.)
+ */
+ if (i6mo->i6mo_multicast_ifp != NULL)
+ ifp = i6mo->i6mo_multicast_ifp;
+ header->ipv6_hoplimit = i6mo->i6mo_multicast_ttl;
+ }
+ else
+ {
+ /*
+ * Use default values, since there are no multicast options to
+ * use.
+ */
+ if (ifp == NULL)
+ ifp = mcastdefault;
+ header->ipv6_hoplimit = IPV6_DEFAULT_MCAST_HOPS;
+ }
+
+ if (outmtu == 0) /* But what about mcast Path MTU? */
+ outmtu = ifp->if_mtu;
+
+ if ((ifp->if_flags & IFF_MULTICAST) == 0)
+ {
+ ipv6stat.ips_noroute++;
+ error = ENETUNREACH;
+ goto bad;
+ }
+
+ if ((IN6_IS_ADDR_UNSPECIFIED(&header->ipv6_src) && !(outgoing->m_flags & M_DAD)) ||
+ (IN6_IS_ADDR_LINKLOCAL(&header->ipv6_src) &&
+ GET_IN6_MCASTSCOPE(header->ipv6_dst) > IN6_INTRA_LINK))
+ {
+ register struct in6_ifaddr *i6a;
+
+ /*
+ * Source address selection for multicast datagrams.
+ * If link-local source, get in here too, because you don't want
+ * link-local addresses going on non-local multicast.
+ *
+ * Eventually should fix this to perform best source address
+ * selection. Probably should separate this out into a function.
+ */
+ for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next)
+ if (i6a->i6a_ifp == ifp)
+ {
+ header->ipv6_src = I6A_SIN(i6a)->sin6_addr;
+ break;
+ }
+ }
+
+ IN6_LOOKUP_MULTI(&header->ipv6_dst, ifp, in6m);
+ DPRINTF(IDL_GROSS_EVENT,("in6m == 0x%lx, i6mo == 0x%lx\n", (unsigned long)in6m, (unsigned long)i6mo));
+ if (in6m != NULL &&
+ (i6mo == NULL || i6mo->i6mo_multicast_loop))
+ {
+
+ /*
+ * See ipv6_mloopback for details, but that function will tag
+ * the packet with the actual interface the multicast is
+ * supposed to go out. This makes duplicate address detection
+ * harder to implement, because the inbound mbuf SHOULD be tagged
+ * as coming from me for the case of solicits. (Perhaps burning
+ * another flag...)
+ */
+ DPRINTF(IDL_GROSS_EVENT,("Calling ipv6_mloopback().\n"));
+ ipv6_mloopback(ifp, outgoing, dst);
+ }
+
+#ifdef MROUTING
+ /*
+ * Do m-cast routing even if I can't find it in my m-cast list.
+ */
+#endif
+
+ /*
+ * If intra-node scope. I've already hit it with ipv6_mloopback above.
+ */
+
+ if (GET_IN6_MCASTSCOPE(header->ipv6_dst) == IN6_INTRA_NODE || (ifp->if_flags & IFF_LOOPBACK))
+ goto bad; /* Not really bad, y'know, just getting out of here. */
+ }
+
+ if (ro->ro_rt == NULL && outmtu == 0)
+ panic("ipv6_output: How did I get here without a route or MTU?");
+
+ /*
+ * Specify source address. Use i6a, for now.
+ */
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&header->ipv6_src) && i6a != NULL &&
+ !(outgoing->m_flags & M_DAD))
+ header->ipv6_src = I6A_SIN(i6a)->sin6_addr;
+
+ DPRINTF(IDL_FINISHED,("header & chain before security check are:\n"));
+ DDO(IDL_FINISHED,dump_ipv6(header));
+ DDO(IDL_FINISHED,dump_mchain(outgoing));
+
+#ifdef IPSEC
+ if (!(flags & IPV6_FORWARDING)) {
+ size_t preoverhead, postoverhead;
+ void *state;
+
+ /* NB: If there exists a configured secure tunnel, then
+ the packet being tunneled will have been encapsulated
+ inside an IP packet with (src=me, dest=tunnel-end-point)
+ PRIOR to ip_output() being called, so the above
+ check doesn't preclude secure tunnelling. rja */
+ /*
+ * I would like to just pass in &ia->ia_addr, but there is a small
+ * chance that the source address doesn't match ia->ia_addr.
+ *
+ * Also, if you need a dest. port, fill in ro->ro_dst with it.
+ */
+ {
+ struct sockaddr_in6 srcsa, dstsa;
+
+ bzero(&srcsa, sizeof(struct sockaddr_in6));
+ srcsa.sin6_family = AF_INET6;
+ srcsa.sin6_len = sizeof(struct sockaddr_in6);
+ /* XXX - port */
+ srcsa.sin6_addr = header->ipv6_src;
+
+ bzero(&dstsa, sizeof(struct sockaddr_in6));
+ dstsa.sin6_family = AF_INET6;
+ dstsa.sin6_len = sizeof(struct sockaddr_in6);
+ /* XXX - port */
+ dstsa.sin6_addr = header->ipv6_dst;
+
+ /* XXX - get the ULP protocol number */
+ if (error = netproc_outputpolicy(socket, (struct sockaddr *)&srcsa,
+ (struct sockaddr *)&dstsa, header->ipv6_nexthdr, &preoverhead,
+ &postoverhead, &state)) {
+ if (error == EACCES) /* XXX - means fail silently */
+ error = 0;
+ goto bad;
+ }
+ }
+
+ if (state) {
+ struct nbuf *nbuf;
+
+ DP(FINISHED, preoverhead, d);
+ DP(FINISHED, postoverhead, d);
+ {
+ struct netproc_policycache *policycache =
+ (struct netproc_policycache *)state;
+
+ DP(FINISHED, policycache->doah, d);
+ DP(FINISHED, policycache->doesp, d);
+ DP(FINISHED, policycache->docombinedesp, d);
+ }
+
+ if (!(nbuf = mton(outgoing, preoverhead, postoverhead))) {
+ netproc_outputfree(state);
+ error = ENOMEM;
+ goto bad;
+ }
+
+ outgoing = NULL;
+
+ if (error = netproc_output(state, nbuf)) {
+ if (error == EACCES)
+ error = 0;
+ }
+
+ /* If successful, netproc_output actually does the output.
+ Either way, it frees the nbuf. */
+ goto done;
+ }
+ }
+#endif /* defined(IPSEC) || defined(NRL_IPSEC) */
+
+ /*
+ * Assume above three return a contiguous and UPDATED IPv6 header.
+ */
+ header = mtod(outgoing,struct ipv6 *);
+
+ /*
+ * Determine the outbound i6a to record statistics.
+ */
+ if (flags & IPV6_FORWARDING)
+ i6a = NULL;
+ else if (i6a == NULL ||
+ !IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &header->ipv6_src))
+ {
+ for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next)
+ if (i6a->i6a_ifp == ifp &&
+ IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &header->ipv6_src))
+ break;
+ }
+
+ /*
+ * If small enough for path MTU, send, otherwise, fragment.
+ */
+
+ DPRINTF(IDL_FINISHED,("Before output, path mtu = %d, header is:\n",\
+ (int)outmtu));
+ DDO(IDL_FINISHED,dump_ipv6(header));
+ DDO(IDL_FINISHED,printf("Chain is:\n");dump_mchain(outgoing));
+
+#if 0
+/* DEBUG tunnel */
+ DPRINTF(IDL_EVENT,("ROUTE passed to ipv6_output is:\n"));
+ DDO(IDL_EVENT,if (ro) dump_rtentry(ro->ro_rt));
+ if (ro->ro_rt && (ro->ro_rt->rt_flags & RTF_TUNNEL))
+ DPRINTF(IDL_FINISHED,("HEY !! I see the tunnel!!!\n"));
+ else {
+ DPRINTF(IDL_FINISHED,("HEY !! I can't see the tunnel!!!\n"));
+ if (ro->ro_rt == NULL)
+ DPRINTF(IDL_FINISHED,("ro->ro_rt is null!!\n"));
+ else
+ {
+ DPRINTF(IDL_FINISHED,("ro->ro_rt is not null!!\n"));
+ if (ro->ro_rt->rt_flags & RTF_TUNNEL)
+ DPRINTF(IDL_FINISHED,("HEY, I can see RTFTUNNEL!\n"));
+ else
+ DPRINTF(IDL_FINISHED,("HEY, I can't see RTFTUNNEL!\n"));
+ }
+ }
+/* END OF DEBUG tunnel */
+#endif /* 0 */
+
+ if (outgoing->m_pkthdr.len <= outmtu)
+ {
+DPRINTF(IDL_EVENT,("IPV6_OUTPUT(): Not entering fragmenting engine.\n"));
+ header->ipv6_length = htons(outgoing->m_pkthdr.len -
+ sizeof(struct ipv6));
+
+ /*
+ * If there is a route, and its TUNNEL bit is turned on, do not send
+ * out the interface, but send through a tunneling routine, which will,
+ * given information from the route, encapsulate the packet accordingly.
+ *
+ * Keith Sklower suggested a "rt_output() method" which would save
+ * the checking here.
+ */
+ if (ro->ro_rt && (ro->ro_rt->rt_flags & RTF_TUNNEL)) {
+ DPRINTF(IDL_EVENT,("ipv6_output():-Sending out IPV6 in IPV4/6 tunnel.\n"));
+ error = ipv6_tunnel_output(outgoing, dst, ro->ro_rt);
+ } else {
+#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802)
+ if (i6a) {
+ i6a->i6a_ifa.ifa_opackets++;
+ i6a->i6a_ifa.ifa_obytes += outgoing->m_pkthdr.len;
+ }
+#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */
+ error = (*ifp->if_output)(ifp, outgoing, (struct sockaddr *)dst, ro->ro_rt);
+ }
+ DPRINTF(IDL_FINISHED,("Lone IPv6 went out if (error = %d).\n",error));
+ goto done;
+ }
+
+
+ /*
+ * If I make it here, then the packet is too big for the path MTU, and must
+ * be fragmented.
+ */
+
+ DPRINTF(IDL_EVENT,("IPV6_OUTPUT(): Entering fragmenting engine.\n"));
+
+ if (flags & IPV6_FORWARDING)
+ {
+ error = EMSGSIZE;
+ goto bad;
+ }
+
+ if (outgoing->m_pkthdr.len > 0xffff) {
+ DPRINTF(IDL_ERROR,("Jumbogram needs fragmentation, something that can't be done\n"));
+ ipv6stat.ips_odropped++; /* ?!? */
+ error = EINVAL;
+ goto bad;
+ }
+
+ /*
+ * The following check should never really take place.
+ */
+#ifdef DIAGNOSTIC
+ if (outmtu < IPV6_MINMTU)
+ {
+ DPRINTF(IDL_ERROR,("Outbound MTU is less than IPV6_MINMTU (%d).\n",\
+ IPV6_MINMTU));
+ error = ENETUNREACH; /* Can you think of a better idea? */
+ goto bad;
+ }
+#endif
+
+ /*
+ * ipv6_fragment returns a chain of outgoing packets. It returns NULL
+ * if something went wrong.
+ */
+ outgoing = ipv6_fragment(outgoing,outmtu);
+ if (outgoing == NULL)
+ error = ENOBUFS; /* Can you think of a better idea? */
+
+ DPRINTF(IDL_FINISHED,\
+ ("ipv6_fragment() returned, attempting to send fragments out.\n"));
+
+ /*
+ * Walk through chain of fragments, and send them out.
+ */
+ while (outgoing != NULL)
+ {
+ struct mbuf *current = outgoing;
+
+ DPRINTF(IDL_FINISHED,("In fragment-sending loop, error == %d.\n",\
+ error));
+ outgoing = current->m_nextpkt;
+ current->m_nextpkt = NULL;
+
+ DDO(IDL_FINISHED,printf("Current (0x%lx) 1st mbuf is:\n", (unsigned long)current);\
+ dump_mbuf(current));
+
+ if (error != 0)
+ m_freem(current);
+ else
+ if (ro->ro_rt && (ro->ro_rt->rt_flags & RTF_TUNNEL)) {
+ DPRINTF(IDL_EVENT,("Sending fragments out tunnel.\n"));
+ error = ipv6_tunnel_output(current, dst, ro->ro_rt);
+ } else {
+ DPRINTF(IDL_EVENT,("After if_output(), error == %d.\n",error));
+#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802)
+ if (i6a) {
+ i6a->i6a_ifa.ifa_opackets++;
+ i6a->i6a_ifa.ifa_obytes += current->m_pkthdr.len;
+ }
+#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */
+ error = (*ifp->if_output)(ifp, current,(struct sockaddr *)dst, ro->ro_rt);
+ }
+ }
+
+ if (error == 0)
+ ipv6stat.ips_fragmented++;
+
+done:
+ if (ro == &ipv6route && (flags & IPV6_ROUTETOIF) == 0 && ro->ro_rt)
+ RTFREE(ro->ro_rt);
+ return (error);
+
+bad:
+ if (outgoing != NULL)
+ m_freem(outgoing);
+ goto done;
+}
+
+#define INDEX_TO_IFP(index, ifp)\
+{\
+ struct in6_ifnet *i6ifp; \
+ for (i6ifp = in6_ifnet; i6ifp; i6ifp = i6ifp->i6ifp_next) \
+ if (i6ifp->i6ifp_ifp->if_index == index) { \
+ (ifp) = i6ifp->i6ifp_ifp; \
+ break; \
+ }; \
+} \
+
+/*----------------------------------------------------------------------
+ * Set IPv6 multicast options.
+ ----------------------------------------------------------------------*/
+int ipv6_setmoptions(int optname, struct inpcb *inp, void *p, size_t len)
+{
+ register int error = 0;
+ register int i;
+ register struct ipv6_mreq *mreq;
+ register struct ifnet *ifp = NULL;
+ register struct ipv6_moptions *imo = inp->inp_moptions6;
+ struct route6 ro;
+
+ if (imo == NULL)
+ {
+ imo = (struct ipv6_moptions *)malloc(sizeof(*imo), M_IPMOPTS,M_WAITOK);
+ if (imo == NULL)
+ return ENOBUFS;
+ inp->inp_moptions6 = imo;
+ inp->inp_flags |= INP_IPV6_MCAST;
+ imo->i6mo_multicast_ifp = NULL;
+ imo->i6mo_multicast_ttl = IPV6_DEFAULT_MCAST_HOPS;
+ imo->i6mo_multicast_loop = IPV6_DEFAULT_MCAST_LOOP;
+ imo->i6mo_num_memberships = 0;
+ }
+ else /* Only if points to v6 moptions can I set them! */
+ if (!(inp->inp_flags & INP_IPV6_MCAST))
+ return EEXIST;
+
+ switch (optname)
+ {
+ case IPV6_MULTICAST_IF:
+ {
+ unsigned int index;
+ if (!p || (len != sizeof(unsigned int))) {
+ error = EINVAL;
+ break;
+ }
+
+ index = *((int *)p);
+
+ if (!index) {
+ imo->i6mo_multicast_ifp = NULL;
+ break;
+ }
+
+ INDEX_TO_IFP(index, ifp);
+ if (!ifp || !(ifp->if_flags & IFF_MULTICAST))
+ error = EADDRNOTAVAIL;
+ else
+ imo->i6mo_multicast_ifp = ifp;
+ };
+ break;
+
+ case IPV6_MULTICAST_HOPS:
+ /*
+ * Set the IPv6 hop limit for outgoing multicast packets.
+ */
+ if (!p || (len != sizeof(int))) {
+ error = EINVAL;
+ break;
+ }
+ if (*((int *)p) == -1)
+ imo->i6mo_multicast_ttl = IPV6_DEFAULT_MCAST_HOPS;
+ else
+ if ((*((int *)p) > -1) && (*((int *)p) < 256))
+ imo->i6mo_multicast_ttl = *((int *)p);
+ else
+ error = EINVAL;
+ break;
+
+ case IPV6_MULTICAST_LOOP:
+ /*
+ * Set the loopback flag for outgoing multicast packets.
+ * Must be zero or one.
+ */
+ if (!p || (len != sizeof(int))) {
+ error = EINVAL;
+ break;
+ }
+ switch(*((int *)p)) {
+ case 0:
+ case 1:
+ imo->i6mo_multicast_loop = *((int *)p);
+ break;
+ case -1:
+ imo->i6mo_multicast_loop = IPV6_DEFAULT_MCAST_LOOP;
+ break;
+ default:
+ error = EINVAL;
+ break;
+ };
+ break;
+ case IPV6_ADD_MEMBERSHIP:
+ /*
+ * Add a multicast group membership.
+ * Group must be a valid IP multicast address.
+ */
+ if (!p || (len != sizeof(struct ipv6_mreq))) {
+ error = EINVAL;
+ break;
+ }
+ mreq = (struct ipv6_mreq *)p;
+ if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
+ error = EINVAL;
+ break;
+ }
+ /*
+ * If no interface address was provided, use the interface of
+ * the route to the given multicast address.
+ */
+ if (!mreq->ipv6mr_interface) {
+ ro.ro_rt = NULL;
+ ro.ro_dst.sin6_family = AF_INET6;
+ ro.ro_dst.sin6_len = sizeof(struct sockaddr_in6);
+ ro.ro_dst.sin6_addr = mreq->ipv6mr_multiaddr;
+ rtalloc((struct route *)&ro);
+ if (ro.ro_rt == NULL)
+ {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ ifp = ro.ro_rt->rt_ifp;
+ rtfree(ro.ro_rt);
+ }
+ else {
+ INDEX_TO_IFP(mreq->ipv6mr_interface, ifp);
+ }
+ /*
+ * See if we found an interface, and confirm that it
+ * supports multicast.
+ */
+ if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ /*
+ * See if the membership already exists or if all the
+ * membership slots are full.
+ */
+ for (i = 0; i < imo->i6mo_num_memberships; ++i) {
+ if (imo->i6mo_membership[i]->in6m_ifp == ifp &&
+ IN6_ARE_ADDR_EQUAL(&mreq->ipv6mr_multiaddr,
+ &imo->i6mo_membership[i]->in6m_addr))
+ break;
+ }
+ if (i < imo->i6mo_num_memberships) {
+ error = EADDRINUSE;
+ break;
+ }
+ if (i == IN6_MAX_MEMBERSHIPS) {
+ error = ETOOMANYREFS;
+ break;
+ }
+ /*
+ * Everything looks good; add a new record to the multicast
+ * address list for the given interface.
+ */
+ if ((imo->i6mo_membership[i] = in6_addmulti(&mreq->ipv6mr_multiaddr, ifp))
+ == NULL)
+ {
+ error = ENOBUFS;
+ break;
+ }
+ ++imo->i6mo_num_memberships;
+ break;
+
+ case IPV6_DROP_MEMBERSHIP:
+ /*
+ * Drop a multicast group membership.
+ * Group must be a valid IP multicast address.
+ */
+ if (!p || (len != sizeof(struct ipv6_mreq))) {
+ error = EINVAL;
+ break;
+ }
+ mreq = (struct ipv6_mreq *)p;
+ if (!IN6_IS_ADDR_MULTICAST(&mreq->ipv6mr_multiaddr)) {
+ error = EINVAL;
+ break;
+ }
+
+ /*
+ * If an interface index was specified, get a pointer to its ifnet
+ * structure.
+ */
+ if (!mreq->ipv6mr_interface)
+ ifp = NULL;
+ else {
+ INDEX_TO_IFP(mreq->ipv6mr_interface, ifp);
+ if (ifp == NULL) {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ }
+
+ /*
+ * Find the membership in the membership array.
+ */
+ for (i = 0; i < imo->i6mo_num_memberships; ++i) {
+ if ((ifp == NULL ||
+ imo->i6mo_membership[i]->in6m_ifp == ifp) &&
+ IN6_ARE_ADDR_EQUAL(&imo->i6mo_membership[i]->in6m_addr,
+ &mreq->ipv6mr_multiaddr))
+ break;
+ }
+
+ if (i == imo->i6mo_num_memberships) {
+ error = EADDRNOTAVAIL;
+ break;
+ }
+ /*
+ * Give up the multicast address record to which the
+ * membership points.
+ */
+ in6_delmulti(imo->i6mo_membership[i]);
+ /*
+ * Remove the gap in the membership array.
+ */
+ for (++i; i < imo->i6mo_num_memberships; ++i)
+ imo->i6mo_membership[i-1] = imo->i6mo_membership[i];
+
+ --imo->i6mo_num_memberships;
+
+ break;
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+
+ if (imo->i6mo_multicast_ifp == NULL &&
+ imo->i6mo_multicast_ttl == IPV6_DEFAULT_MCAST_HOPS &&
+ imo->i6mo_multicast_loop == IPV6_DEFAULT_MCAST_LOOP &&
+ imo->i6mo_num_memberships == 0) {
+ free(inp->inp_moptions6, M_IPMOPTS);
+ inp->inp_moptions6 = NULL;
+ inp->inp_flags &= ~INP_IPV6_MCAST;
+ }
+
+ return (error);
+}
+
+#define IFP_TO_INDEX(ifp, index) \
+{\
+ (index) = ifp->if_index; \
+}
+
+/*----------------------------------------------------------------------
+ * Get IPv6 multicast options.
+ ----------------------------------------------------------------------*/
+/* ... now assumes all returned values are ints... */
+int ipv6_getmoptions(int optname, struct ipv6_moptions *i6mo, int *mp)
+{
+ switch (optname)
+ {
+ case IPV6_MULTICAST_IF:
+ if (!i6mo == NULL || !i6mo->i6mo_multicast_ifp)
+ *mp = 0;
+ else {
+ IFP_TO_INDEX(i6mo->i6mo_multicast_ifp, *(unsigned int *)mp);
+ }
+ return (0);
+
+ case IPV6_MULTICAST_HOPS:
+ *mp = i6mo ? IPV6_DEFAULT_MCAST_HOPS : i6mo->i6mo_multicast_ttl;
+ return (0);
+
+ case IPV6_MULTICAST_LOOP:
+ *mp = i6mo ? IPV6_DEFAULT_MCAST_LOOP : i6mo->i6mo_multicast_loop;
+ return (0);
+
+ default:
+ return (EOPNOTSUPP);
+ }
+}
+
+/*----------------------------------------------------------------------
+ * Free IPv6 multicast options.
+ ----------------------------------------------------------------------*/
+void
+ipv6_freemoptions(i6mo)
+ register struct ipv6_moptions *i6mo;
+{
+ register int i;
+
+ if (i6mo != NULL)
+ {
+ for (i = 0 ; i < i6mo->i6mo_num_memberships ; i++)
+ in6_delmulti(i6mo->i6mo_membership[i]);
+ free(i6mo, M_IPMOPTS);
+ }
+}
+
+/*----------------------------------------------------------------------
+ * Handler for IPV6 [gs]etsockopt() calls. One problem arises when an
+ * AF_INET6 socket actually wants to set IPv4 options.
+ *
+ * The decision to call this or to call ip_ctloutput() is best left in
+ * the hands of TCP/UDP/etc., which have information about which IP is
+ * in use.
+ *
+ ----------------------------------------------------------------------*/
+
+#if __FreeBSD__
+int ipv6_ctloutput(struct socket *so, struct sockopt *sopt)
+{
+ register struct inpcb *inp;
+ int op;
+ int level;
+ int optname;
+ int optval;
+ int error = 0;
+
+ DPRINTF(FINISHED, ("ipv6_ctloutput(so=%08x, sopt=%08x)\n", OSDEP_PCAST(so),
+ OSDEP_PCAST(sopt)));
+
+ inp = sotoinpcb(so);
+
+ switch(sopt->sopt_dir) {
+ case SOPT_GET:
+ op = PRCO_GETOPT;
+ break;
+ case SOPT_SET:
+ op = PRCO_SETOPT;
+ break;
+ default:
+ return EINVAL;
+ };
+
+ level = sopt->sopt_level;
+ optname = sopt->sopt_name;
+
+ DS();
+#else /* __FreeBSD__ */
+int
+ipv6_ctloutput (op, so, level, optname, mp)
+ int op;
+ struct socket *so;
+ int level;
+ int optname;
+ struct mbuf **mp;
+{
+ register struct inpcb *inp = sotoinpcb(so);
+ struct mbuf *m = *mp;
+ int error = 0;
+
+ DPRINTF(IDL_EVENT, ("ipv6_ctloutput(op=%x,so=%08lx,level=%x,optname=%x,mp)\n", op, (unsigned long)so, level, optname));
+#endif /* __FreeBSD__ */
+
+ if ((level != IPPROTO_IP) && (level != IPPROTO_IPV6) && (level != IPPROTO_ROUTING) && (level != IPPROTO_ICMPV6)) {
+#if !__FreeBSD__
+ if (op == PRCO_SETOPT && *mp)
+ m_free(*mp);
+#endif /* !__FreeBSD__ */
+ return EINVAL;
+ }
+
+ DS();
+ switch (op) {
+ case PRCO_SETOPT:
+ switch(optname) {
+ case IPV6_UNICAST_HOPS:
+ DPRINTF(IDL_GROSS_EVENT, ("ipv6_ctloutput: Reached IPV6_UNICAST_HOPS\n"));
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int))
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int))
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ else {
+ struct tcpcb *tp;
+#if __FreeBSD__
+ int i;
+
+ if (error = sooptcopyin(sopt, &i, sizeof(int), sizeof(int)))
+ break;
+#else /* __FreeBSD__ */
+ int i = *mtod(m, int *);
+#endif /* __FreeBSD__ */
+
+ if (i == -1)
+ i = ipv6_defhoplmt;
+
+ if ((i < 0) || (i > 255)) {
+ error = EINVAL;
+ break;
+ };
+
+ inp->inp_ipv6.ipv6_hoplimit = i;
+
+ /*
+ * Minor optimization for TCP. We change the hoplimit
+ * in the template here so we don't have to do the extra
+ * load before the ipv6_output() call in tcp_output() for
+ * every single packet (as is the case for IPv4).
+ */
+ DS();
+#if !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)
+ if ((so->so_type == SOCK_STREAM) && (tp = intotcpcb(inp)) &&
+ tp->t_template)
+#if __FreeBSD__
+ (mtod(tp->t_template, struct ipv6 *))->ipv6_hoplimit = i;
+#else /* __FreeBSD__ */
+ ((struct ipv6 *)(tp->t_template))->ipv6_hoplimit = i;
+#endif /* __FreeBSD__ */
+#else /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */
+ if ((so->so_type == SOCK_STREAM) && (tp = intotcpcb(inp)))
+ ((struct ipv6 *)(tp->t_tcpiphdr))->ipv6_hoplimit = i;
+#endif /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */
+ }
+ break;
+ case IPV6_MULTICAST_IF:
+ case IPV6_MULTICAST_HOPS:
+ case IPV6_MULTICAST_LOOP:
+ case IPV6_DROP_MEMBERSHIP:
+ case IPV6_ADD_MEMBERSHIP:
+#if __FreeBSD__
+ {
+ void *val;
+
+ if (!(val = OSDEP_MALLOC(sopt->sopt_valsize))) {
+ error = ENOMEM;
+ break;
+ };
+
+ if (error = sooptcopyin(sopt, val, sopt->sopt_valsize,
+ sopt->sopt_valsize)) {
+ OSDEP_FREE(val);
+ break;
+ };
+
+ error = ipv6_setmoptions(optname, inp, val, sopt->sopt_valsize);
+ OSDEP_FREE(val);
+ };
+#else /* __FreeBSD__ */
+ error = ipv6_setmoptions(optname, inp, mtod(m, void *), m->m_len);
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_ADDRFORM:
+ {
+ int newpf;
+ int oldpf = sotopf(inp->inp_socket);
+ union inpaddru new_faddru;
+ union inpaddru new_laddru;
+ int new_flags;
+ struct protosw *new_proto;
+ int s;
+
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+ DPRINTF(IDL_ERROR, ("addrform: valsize = %d\n",
+ sopt->sopt_valsize));
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+ DPRINTF(IDL_ERROR, ("addrform: m->m_len = %d\n", m->m_len));
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &newpf, sizeof(int), sizeof(int)))
+ break;
+#else /* __FreeBSD__ */
+ newpf = *(mtod(m, int *));
+#endif /* __FreeBSD__ */
+
+ DPRINTF(IDL_ERROR, ("newpf = %d, oldpf = %d", newpf, oldpf));
+
+ if (((newpf != AF_INET) && (newpf != AF_INET6)) ||
+ ((oldpf != AF_INET) && (oldpf != AF_INET6)))
+ return EINVAL;
+
+ DP(ERROR, __LINE__, d);
+
+ if (newpf == oldpf)
+ return 0;
+
+ DP(ERROR, inp->inp_flags, 08x);
+
+ if (newpf == AF_INET6)
+ if (!(inp->inp_flags & INP_IPV6_UNDEC) &&
+ !(inp->inp_flags & INP_IPV6_MAPPED))
+ return EINVAL;
+
+ DP(ERROR, __LINE__, d);
+
+ if (!(new_proto = pffindproto(newpf,
+ so->so_proto->pr_protocol,
+ so->so_proto->pr_type)))
+ return EINVAL;
+
+ DP(ERROR, new_proto->pr_domain->dom_family, d);
+
+ new_flags = inp->inp_flags;
+ new_faddru = inp->inp_faddru;
+ new_laddru = inp->inp_laddru;
+
+ if (newpf == AF_INET) {
+ if (new_flags & INP_IPV6_UNDEC) {
+ new_flags &= ~(INP_IPV6 | INP_IPV6_MAPPED | INP_IPV6_UNDEC);
+ new_laddru.iau_a4u.inaddr.s_addr = INADDR_ANY;
+ new_faddru.iau_a4u.inaddr.s_addr = INADDR_ANY;
+ } else {
+ new_flags &= ~(INP_IPV6 | INP_IPV6_MAPPED);
+ }
+ } else {
+ new_faddru.iau_addr6.in6a_words[0] = 0;
+ new_faddru.iau_addr6.in6a_words[1] = 0;
+ new_laddru.iau_addr6.in6a_words[0] = 0;
+ new_laddru.iau_addr6.in6a_words[1] = 0;
+
+ if (new_laddru.iau_a4u.inaddr.s_addr == INADDR_ANY) {
+ new_flags |= (INP_IPV6 | INP_IPV6_MAPPED | INP_IPV6_UNDEC);
+ new_faddru.iau_addr6.in6a_words[2] = 0;
+ new_laddru.iau_addr6.in6a_words[2] = 0;
+ } else {
+ new_flags |= (INP_IPV6 | INP_IPV6_MAPPED);
+ new_faddru.iau_addr6.in6a_words[2] = htonl(0xffff);
+ new_faddru.iau_addr6.in6a_words[2] = htonl(0xffff);
+ }
+ }
+
+ s = splnet();
+
+ inp->inp_flags = new_flags;
+ inp->inp_faddru = new_faddru;
+ inp->inp_laddru = new_laddru;
+ so->so_proto = new_proto;
+
+ splx(s);
+ }
+ break;
+ case IPV6_PKTINFO:
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)))
+ break;
+
+ if (optval)
+#else /* __FreeBSD__ */
+ if (*(mtod(m, int *)))
+#endif /* __FreeBSD__ */
+ inp->inp_flags |= INP_RXINFO;
+ else
+ inp->inp_flags &= ~INP_RXINFO;
+ break;
+ case IPV6_HOPOPTS:
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)))
+ break;
+
+ if (optval)
+#else /* __FreeBSD__ */
+ if (*(mtod(m, int *)))
+#endif /* __FreeBSD__ */
+ inp->inp_flags |= INP_RXHOPOPTS;
+ else
+ inp->inp_flags &= ~INP_RXHOPOPTS;
+ break;
+ case IPV6_DSTOPTS:
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)))
+ break;
+
+ if (optval)
+#else /* __FreeBSD__ */
+ if (*(mtod(m, int *)))
+#endif /* __FreeBSD__ */
+ inp->inp_flags |= INP_RXDSTOPTS;
+ else
+ inp->inp_flags &= ~INP_RXDSTOPTS;
+ break;
+ case IPV6_RTHDR:
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)))
+ break;
+
+ if (optval)
+#else /* __FreeBSD__ */
+ if (*(mtod(m, int *)))
+#endif /* __FreeBSD__ */
+ inp->inp_flags |= INP_RXSRCRT;
+ else
+ inp->inp_flags &= ~INP_RXSRCRT;
+ break;
+ case IPV6_HOPLIMIT:
+#if __FreeBSD__
+ if (sopt->sopt_valsize != sizeof(int)) {
+#else /* __FreeBSD__ */
+ if (m->m_len != sizeof(int)) {
+#endif /* __FreeBSD__ */
+ error = EINVAL;
+ break;
+ };
+
+#if __FreeBSD__
+ if (error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)))
+ break;
+
+ if (optval)
+#else /* __FreeBSD__ */
+ if (*(mtod(m, int *)))
+#endif /* __FreeBSD__ */
+ inp->inp_flags |= INP_HOPLIMIT;
+ else
+ inp->inp_flags &= ~INP_HOPLIMIT;
+ break;
+ default:
+ error = ENOPROTOOPT;
+ break;
+ }
+#if !__FreeBSD__
+ if (m)
+ m_free(m);
+#endif /* !__FreeBSD__ */
+ break;
+ case PRCO_GETOPT:
+ switch(optname)
+ {
+ case IPV6_ADDRFORM:
+ {
+ int pf = sotopf(inp->inp_socket);
+ DP(ERROR, pf, d);
+ if ((pf != PF_INET) && (pf != PF_INET6))
+ return EINVAL;
+ DP(ERROR, __LINE__, d);
+#if __FreeBSD__
+ error = sooptcopyout(sopt, &pf, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ *mp = m;
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = pf;
+#endif /* __FreeBSD__ */
+ }
+ break;
+ case IPV6_UNICAST_HOPS:
+ DPRINTF(IDL_GROSS_EVENT,("ipv6_ctloutput(): Reached IP_UNICAST_HOPS\n"));
+#if __FreeBSD__
+ error = sooptcopyout(sopt, &inp->inp_ipv6.ipv6_hoplimit,
+ sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ *mp = m;
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = inp->inp_ipv6.ipv6_hoplimit;
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_MULTICAST_IF:
+ case IPV6_MULTICAST_HOPS:
+ case IPV6_MULTICAST_LOOP:
+ case IPV6_DROP_MEMBERSHIP:
+ case IPV6_ADD_MEMBERSHIP:
+#if __FreeBSD__
+ error = ipv6_getmoptions(optname, inp->inp_moptions6, &optval);
+ if (error)
+ break;
+
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(*mp = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+
+ error = ipv6_getmoptions(optname, inp->inp_moptions6,
+ mtod(*mp, int *));
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_PKTINFO:
+#if __FreeBSD__
+ optval = (inp->inp_flags & INP_RXINFO) ? 1 : 0;
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ *mp = m;
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = (inp->inp_flags & INP_RXINFO) ? 1 : 0;
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_HOPOPTS:
+#if __FreeBSD__
+ optval = (inp->inp_flags & INP_RXHOPOPTS) ? 1 : 0;
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(*mp = m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = (inp->inp_flags & INP_RXHOPOPTS) ? 1 : 0;
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_DSTOPTS:
+#if __FreeBSD__
+ optval = (inp->inp_flags & INP_RXDSTOPTS) ? 1 : 0;
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(*mp = m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = (inp->inp_flags & INP_RXDSTOPTS) ? 1 : 0;
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_RTHDR:
+#if __FreeBSD__
+ optval = (inp->inp_flags & INP_RXSRCRT) ? 1 : 0;
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(*mp = m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = (inp->inp_flags & INP_RXSRCRT) ? 1 : 0;
+#endif /* __FreeBSD__ */
+ break;
+ case IPV6_HOPLIMIT:
+#if __FreeBSD__
+ optval = (inp->inp_flags & INP_HOPLIMIT) ? 1 : 0;
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+#else /* __FreeBSD__ */
+ if (!(*mp = m = m_get(M_NOWAIT, MT_SOOPTS))) {
+ error = ENOBUFS;
+ break;
+ };
+ m->m_len = sizeof(int);
+ *mtod(m, int *) = (inp->inp_flags & INP_HOPLIMIT) ? 1 : 0;
+#endif /* __FreeBSD__ */
+ break;
+ default:
+ error = ENOPROTOOPT;
+ break;
+ }
+ break;
+ default:
+ error = ENOPROTOOPT;
+ break;
+ }
+
+ return error;
+}
+
+/*----------------------------------------------------------------------
+ * Loops back multicast packets to groups of which I'm a member.
+ ----------------------------------------------------------------------*/
+
+void
+ipv6_mloopback(ifp, m, dst)
+ struct ifnet *ifp;
+ register struct mbuf *m;
+ register struct sockaddr_in6 *dst;
+{
+ struct mbuf *copym;
+ register struct ipv6 *header;
+
+#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802
+ if (!loifp)
+ return;
+#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */
+
+ /*
+ * Copy mbuf chain in m, and send to loopback interface.
+ */
+
+ copym=m_copym(m,0,M_COPYALL,M_DONTWAIT);
+ if (copym != NULL)
+ {
+ header=mtod(copym,struct ipv6 *);
+ /* Jumbogram? */
+ header->ipv6_length = htons(header->ipv6_length);
+ /*
+ * Also, there's an issue about address collision. You may want to
+ * check the ipv6 destination (or the dst address) and set the ifp
+ * argument to looutput to be the loopback interface itself iff
+ * it is to a solicited nodes multicast.
+ *
+ * Then again, it may be easier for the soliciting code to burn another
+ * m_flags bit, and look for it on loopback.
+ */
+#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802
+ (*loifp->if_output)(ifp, copym, (struct sockaddr *)dst, NULL);
+#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */
+#if __FreeBSD__
+ loif->if_output(ifp,copym,(struct sockaddr *)dst,NULL);
+#else /* __FreeBSD__ */
+ looutput(ifp,copym,(struct sockaddr *)dst,NULL);
+#endif /* __FreeBSD__ */
+#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */
+ }
+ else
+ DPRINTF(IDL_GROSS_EVENT,("m_copym() failed in ipv6_mloopback.\n"));
+}
+
+/*----------------------------------------------------------------------
+ * Fragment IPv6 datagram.
+ *
+ * Given a large mbuf chain m, with only its ipv6->ipv6_length field in
+ * host order, fragment into mtu sized chunks, and return a meta-chain
+ * with m->m_nextpkt being the subsequent fragments. If there's a problem,
+ * m_freem all fragments, and return NULL. Also, all ipv6->ipv6_length
+ * fields are in network order, i.e. ready-to-transmit.
+ *
+ * Note that there is an unusually large amount of calls to m_pullup,
+ * m_copym2etc. here. This will cause performance hits.
+ *
+ * A redesign of this is in order, but that will have to wait.
+ ----------------------------------------------------------------------*/
+
+struct mbuf *
+ipv6_fragment(m,mtu)
+ struct mbuf *m;
+ int mtu;
+{
+ uint8_t nextopt = IPPROTO_FRAGMENT;
+ uint divpoint = sizeof(struct ipv6), chunksize, sofar = 0, goal;
+ struct mbuf *retval = NULL, *newfrag = NULL;
+ struct ipv6_fraghdr *frag = NULL;
+
+ outfragid++;
+
+ DPRINTF(IDL_FINISHED,\
+ ("Entering ipv6_fragment, m_pkthdr.len = %d, mtu = %d\n",\
+ m->m_pkthdr.len, mtu));
+
+ /*
+ * Find the dividing point between pre-fragment and post-fragment options.
+ */
+ divpoint = ipv6_finddivpoint(m, &nextopt);
+
+ /*
+ * Options being larger than MTU can happen, especially given large routing
+ * headers and large options bags.
+ */
+ if (divpoint + sizeof(struct ipv6_fraghdr) >= mtu)
+ {
+ DPRINTF(IDL_ERROR,
+ ("ipv6_fragment(): Options are larger than passed-in MTU.\n"));
+ m_freem(m);
+ return NULL;
+ }
+
+#ifdef DIAGNOSTIC
+ if (divpoint & 0x7)
+ panic("divpoint not a multiple of 8!");
+#endif
+
+ /*
+ * sofar keeps track of how much I've fragmented, chunksize is how
+ * much per fragment, goal is how much data to actually fragment.
+ */
+ chunksize = mtu - divpoint - sizeof(struct ipv6_fraghdr);
+ chunksize &= ~7;
+ goal = m->m_pkthdr.len - divpoint;
+
+ DPRINTF(IDL_FINISHED, \
+ ("Found divpoint (%d), nextopt (%d), chunksize(%d) goal(%d)\n",\
+ divpoint, nextopt, chunksize,goal));
+
+ while (sofar < goal)
+ if ((newfrag = m_copym2(m, 0, divpoint, M_DONTWAIT)) != NULL)
+ {
+ struct mbuf *fraghdrmbuf = NULL;
+ struct ipv6 *ipv6 = NULL;
+ uint tocopy = (chunksize <= (goal - sofar))?chunksize:(goal - sofar);
+
+ DPRINTF(IDL_FINISHED,("tocopy == %d\n",tocopy));
+
+ /*
+ * Create a new IPv6 fragment, using the header that was slightly
+ * munged by ipv6_finddivpoint().
+ *
+ * The above m_copym2() creates a copy of the first
+ */
+
+ newfrag->m_nextpkt = retval;
+ retval = newfrag;
+
+ /*
+ * Append IPv6 fragment header to pre-fragment
+ */
+ for(fraghdrmbuf = retval;fraghdrmbuf->m_next != NULL;)
+ fraghdrmbuf = fraghdrmbuf->m_next;
+ MGET(fraghdrmbuf->m_next,M_DONTWAIT,MT_DATA);
+ if (fraghdrmbuf->m_next == NULL)
+ {
+ DPRINTF(IDL_ERROR,("couldn't get new mbuf for frag hdr\n"));
+ ipv6stat.ips_odropped++;
+ goto bail;
+ }
+
+ fraghdrmbuf = fraghdrmbuf->m_next;
+ fraghdrmbuf->m_len = sizeof(struct ipv6_fraghdr);
+ retval->m_pkthdr.len += sizeof(struct ipv6_fraghdr);
+ frag = mtod(fraghdrmbuf,struct ipv6_fraghdr *);
+ frag->frag_nexthdr = nextopt;
+ frag->frag_reserved = 0;
+ frag->frag_bitsoffset = htons(sofar | ((sofar + tocopy) < goal));
+ frag->frag_id = outfragid;
+
+ /*
+ * Copy off (rather than just m_split()) the portion of data that
+ * goes with this fragment.
+ */
+ if ((fraghdrmbuf->m_next = m_copym2(m,divpoint + sofar,tocopy,
+ M_DONTWAIT)) == NULL)
+ {
+ DPRINTF(IDL_ERROR,("couldn't copy segment.\n"));
+ goto bail;
+ }
+ retval->m_pkthdr.len += tocopy;
+
+ /*
+ * Update fragment's IPv6 header appropriately.
+ */
+ ipv6 = mtod(retval,struct ipv6 *);
+ ipv6->ipv6_length = htons(retval->m_pkthdr.len - sizeof(struct ipv6));
+ ipv6->ipv6_nexthdr = IPPROTO_FRAGMENT;
+ ipv6stat.ips_ofragments++;
+ sofar += tocopy;
+ }
+ else
+ {
+ /*
+ * Creation of new fragment failed.
+ */
+ DPRINTF(IDL_ERROR,("m_copym2() failed in fragmentation loop.\n"));
+ ipv6stat.ips_odropped++;
+ bail:
+ DPRINTF(IDL_ERROR,("Bailing out of ipv6_fragment()\n"));
+ while (retval != NULL)
+ {
+ newfrag = retval;
+ retval = retval->m_nextpkt;
+ m_freem(newfrag);
+ }
+ m_freem(m);
+ return NULL;
+ }
+
+ m_freem(m);
+
+ /* Dump mbuf chain list constructed for debugging purposes. */
+ DDO(IDL_FINISHED,\
+ for (newfrag = retval; newfrag != NULL; newfrag = newfrag->m_nextpkt)\
+ dump_mbuf(newfrag) );
+
+ return retval;
+}
+
+/*----------------------------------------------------------------------
+ * Find the dividing point between pre-fragment and post-fragment options.
+ * The argument nexthdr is read/write, on input, it is the next header
+ * value that should be written into the previous header's "next hdr" field,
+ * and what is written back is what used to be in the previous field's
+ * "next hdr" field. For example:
+ *
+ * IP (next hdr = routing) becomes --> IP (next hdr = routing)
+ * Routing (next hdr = TCP) Routing (next hdr = fragment)
+ * TCP TCP
+ * argument nexthdr = fragment argument nexthdr = TCP
+ *
+ * This function returns the length of the pre-fragment options, ideal for
+ * calls to m_split.
+ *
+ * As in ipv6_fragment, too many calls to m_pullup/m_pullup2 are performed
+ * here. Another redesign is called for, but not now.
+ ----------------------------------------------------------------------*/
+
+int
+ipv6_finddivpoint(m, nexthdr)
+ struct mbuf *m;
+ uint8_t *nexthdr;
+{
+ uint8_t iprevopt, *prevopt = &(iprevopt), new = *nexthdr;
+ uint8_t *nextopt;
+ uint divpoint,maybe = 0;
+
+ /*
+ * IPv4 authentication code calls this function too. It is likely that
+ * v4 will just return almost immedately, after determining options
+ * length. (i.e. never go through the while loop.)
+ */
+ if (mtod(m, struct ip *)->ip_v == 4)
+ {
+ iprevopt = IPPROTO_IPV4;
+ nextopt = &(mtod(m, struct ip *)->ip_p);
+ divpoint = sizeof(struct ip);
+ }
+ else
+ {
+ iprevopt = IPPROTO_IPV6;
+ nextopt = &(mtod(m, struct ipv6 *)->ipv6_nexthdr);
+ divpoint = sizeof(struct ipv6);
+ }
+
+ /*
+ * Scan through options finding dividing point. Dividing point
+ * for authentication and fragmentation is the same place.
+ *
+ * Some weirdness here is that there MIGHT be a "Destination options bag"
+ * which is actually a "per source-route-hop" bag. There is a strong
+ * argument for giving this particular options bag a separate type, but
+ * for now, kludge around it.
+ *
+ * The "maybe" variable takes into account the length of this options bag.
+ */
+ while (IS_PREFRAG(*nextopt) && *prevopt != IPPROTO_ROUTING)
+ {
+ struct ipv6_srcroute0 *i6sr;
+ struct ipv6_opthdr *oh;
+
+ /*
+ * ASSUMES: both nextopt and length will be in the first
+ * 8 bytes of ANY pre-fragment header.
+ */
+
+ if ((divpoint + maybe + 8) > MHLEN)
+ {
+ /*
+ * This becomes complicated. Try and collect invariant part into
+ * first (now cluster) mbuf on chain. m_pullup() doesn't work with
+ * clusters, so either write m_pullup2() or inline it here.
+ *
+ * m_pullup2(), unlike m_pullup() will only collect exactly
+ * how many bytes the user requested. This is to avoid problems
+ * with m_copym() and altering data that is merely referenced
+ * multiple times, rather than actually copied. (We may eliminate
+ * the Net/2 hack of adding m_copym2().)
+ */
+ if ((m = m_pullup2(m,divpoint + maybe + 8)) == NULL)
+ {
+ DPRINTF(IDL_ERROR,\
+ ("m_pullup2(%d) failed in ipv6_fragment().\n",\
+ divpoint + maybe + 8));
+ return 0;
+ }
+ }
+ else
+ {
+ if ((m = m_pullup(m,divpoint + maybe + 8)) == NULL)
+ {
+ DPRINTF(IDL_ERROR,\
+ ("m_pullup() failed in ipv6_fragment().\n"));
+ return 0;
+ }
+ }
+
+ /*
+ * Find nextopt, and advance accordingly.
+ */
+ switch (*nextopt)
+ {
+ case IPPROTO_HOPOPTS:
+ /*
+ * Hop-by-hops should be right after IPv6 hdr. If extra is nonzero,
+ * then there was a destination options bag. If divpoint is not
+ * only the size of the IPv6 header, then something came before
+ * hop-by-hop options. This is not good.
+ */
+ if (maybe || divpoint != sizeof(struct ipv6))
+ {
+ DPRINTF(IDL_ERROR,
+ ("ipv6_input(): Weird ordering in headers.\n"));
+ m_freem(m);
+ return 0;
+ }
+ oh = (struct ipv6_opthdr *)(m->m_data + divpoint);
+ prevopt = nextopt;
+ nextopt = &(oh->oh_nexthdr);
+ divpoint += 8 + (oh->oh_extlen << 3);
+ if (oh->oh_extlen)
+ if (divpoint > MHLEN)
+ {
+ if ((m = m_pullup2(m,divpoint)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup2(%d) failed in IPPROTO_HOPOPTS nextopt.\n",\
+ divpoint));
+ return 0;
+ }
+ }
+ else
+ {
+ if ((m = m_pullup(m,divpoint)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup() failed in IPPROTO_HOPOPTS nextopt.\n"));
+ return 0;
+ }
+ }
+ break;
+ case IPPROTO_DSTOPTS:
+ oh = (struct ipv6_opthdr *)(m->m_data + divpoint);
+ prevopt = nextopt;
+ nextopt = &(oh->oh_nexthdr);
+ maybe = 8 + (oh->oh_extlen << 3);
+ if (oh->oh_extlen)
+ if ( divpoint + maybe > MHLEN)
+ {
+ if ((m = m_pullup2(m,divpoint + maybe)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup2(%d) failed in IPPROTO_DSTOPTS nextopt.\n",\
+ divpoint+maybe));
+ return 0;
+ }
+ }
+ else
+ {
+ if ((m = m_pullup(m,divpoint + maybe)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup() failed in IPPROTO_DSTOPTS nextopt.\n"));
+ return 0;
+ }
+ }
+ break;
+ case IPPROTO_ROUTING:
+ if (maybe) /* i.e. if we had a destination options bag before
+ this routing header, we should advance the dividing
+ point. */
+ divpoint += maybe;
+ maybe = 0;
+ i6sr = (struct ipv6_srcroute0 *)(m->m_data + divpoint);
+ prevopt = nextopt;
+ nextopt = &(i6sr->i6sr_nexthdr);
+ switch (i6sr->i6sr_type)
+ {
+ case 0:
+ divpoint += 8 + (i6sr->i6sr_len * 8);
+ break;
+ default:
+ DPRINTF(IDL_ERROR,
+ ("ipv6_input(): Unknown outbound routing header.\n"));
+ break;
+ }
+ if (divpoint > MHLEN)
+ {
+ if ((m = m_pullup2(m,divpoint)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup2(%d) failed in IPPROTO_ROUTING nextopt.\n",\
+ divpoint));
+ return 0;
+ }
+ }
+ else
+ {
+ if ((m = m_pullup(m,divpoint)) == NULL)
+ {
+ DPRINTF(IDL_EVENT,\
+ ("m_pullup() failed in IPPROTO_ROUTING nextopt.\n"));
+ return 0;
+ }
+ }
+ break;
+ } /* End of switch statement. */
+ }; /* End of while loop. */
+ *nexthdr = *nextopt;
+ *nextopt = new;
+ return divpoint;
+}
+
+int ipv6_controltoheader(struct mbuf **m, struct mbuf *control, struct ifnet **forceifp, int *payload)
+{
+ struct cmsghdr *cmsghdr;
+ int error = EINVAL;
+ struct mbuf *srcrtm = NULL;
+
+ DPRINTF(IDL_EVENT, ("ipv6_controltoheader(m=%08lx, control=%08lx, forceif=%08lx, payload=%08lx)\n", (unsigned long)m, (unsigned long)control, (unsigned long)forceifp, (unsigned long)payload));
+ DDO(IDL_EVENT, dump_mchain(control));
+
+ while((control = m_pullup2(control, sizeof(struct cmsghdr))) &&
+ (cmsghdr = mtod(control, struct cmsghdr *)) &&
+ (control = m_pullup2(control, cmsghdr->cmsg_len))) {
+ cmsghdr = mtod(control, struct cmsghdr *);
+ switch(cmsghdr->cmsg_level) {
+ case IPPROTO_IPV6:
+ switch(cmsghdr->cmsg_type) {
+ case IPV6_PKTINFO:
+ {
+ struct in6_pktinfo in6_pktinfo;
+ struct in6_ifnet *i6ifp;
+ struct ifaddr *ifa;
+
+ if (cmsghdr->cmsg_len != sizeof(struct cmsghdr) + sizeof(struct in6_pktinfo))
+ goto ret;
+
+ bcopy((caddr_t)cmsghdr + sizeof(struct cmsghdr), &in6_pktinfo, sizeof(struct in6_pktinfo));
+
+ if (!in6_pktinfo.ipi6_ifindex) {
+ if (IN6_IS_ADDR_UNSPECIFIED(&in6_pktinfo.ipi6_addr)) {
+ DPRINTF(IDL_EVENT, ("ipv6_controltoheader: in degenerate IPV6_PKTINFO case\n"));
+ break;
+ } else {
+ struct in6_ifaddr *i6a;
+ DPRINTF(IDL_EVENT, ("ipv6_controltoheader: in index = unspec, addr = spec case\n"));
+ for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next)
+ if (IN6_ARE_ADDR_EQUAL(&I6A_SIN(i6a)->sin6_addr, &in6_pktinfo.ipi6_addr))
+ goto l2;
+ goto ret;
+ };
+ };
+
+ DPRINTF(FINISHED, ("ipv6_controltoheader: in index = spec case\n"));
+
+ for (i6ifp = in6_ifnet; i6ifp && (i6ifp->i6ifp_ifp->if_index != in6_pktinfo.ipi6_ifindex); i6ifp = i6ifp->i6ifp_next);
+
+ if (!i6ifp)
+ goto ret;
+
+ if (IN6_IS_ADDR_UNSPECIFIED(&in6_pktinfo.ipi6_addr)) {
+ DPRINTF(FINISHED, ("ipv6_controltoheader: in index = spec, addr = unspec case\n"));
+ goto l1;
+ };
+
+ DPRINTF(FINISHED, ("ipv6_controltoheader: in index = spec, addr = spec case\n"));
+
+#ifdef __FreeBSD__
+ for (ifa = i6ifp->i6ifp_ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) {
+#else /* __FreeBSD__ */
+#if __NetBSD__ || __OpenBSD__
+ for (ifa = i6ifp->i6ifp_ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) {
+#else /* __NetBSD__ || __OpenBSD__ */
+ for (ifa = i6ifp->i6ifp_ifp->if_addrlist; ifa; ifa = ifa->ifa_next) {
+#endif /* __NetBSD__ || __OpenBSD__ */
+#endif /* __FreeBSD__ */
+ if ((ifa->ifa_addr->sa_family == AF_INET6) && !bcmp(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr, &in6_pktinfo.ipi6_addr, sizeof(struct in6_addr)))
+ goto l1;
+ };
+ goto ret;
+
+l1: *forceifp = i6ifp->i6ifp_ifp;
+l2: bcopy(&in6_pktinfo.ipi6_addr, &(mtod(*m, struct ipv6 *)->ipv6_src), sizeof(struct in6_addr));
+ };
+ break;
+ case IPV6_HOPLIMIT:
+ {
+ int i;
+
+ if (cmsghdr->cmsg_len != sizeof(struct cmsghdr) + sizeof(int))
+ goto ret;
+
+ i = *((int *)((caddr_t)cmsghdr + sizeof(struct cmsghdr)));
+
+ if (i == -1)
+ if (IN6_IS_ADDR_MULTICAST(&mtod(*m, struct ipv6 *)->ipv6_dst))
+ i = IPV6_DEFAULT_MCAST_HOPS;
+ else
+ i = ipv6_defhoplmt;
+
+ if ((i < 0) || (i > 255))
+ goto ret;
+
+ mtod(*m, struct ipv6 *)->ipv6_hoplimit = i;
+ };
+ break;
+ default:
+ goto ret;
+ };
+ break;
+ case IPPROTO_ROUTING:
+ MGET(srcrtm, M_DONTWAIT, MT_DATA);
+ if (!srcrtm) {
+ error = ENOBUFS;
+ goto ret;
+ };
+
+ srcrtm->m_len = cmsghdr->cmsg_len - sizeof(struct cmsghdr) + 3;
+ if (srcrtm->m_len > MLEN) {
+ DPRINTF(IDL_ERROR, ("ipv6_controltoheader: requested source route that we can't fit in an mbuf (length %d)\n", srcrtm->m_len));
+ goto ret;
+ };
+
+ if (srcrtm->m_len & 7) {
+ DPRINTF(IDL_ERROR, ("ipv6_controltoheader: requested source route has an invalid length; %d needs to be a multiple of eight bytes\n", srcrtm->m_len));
+ goto ret;
+ };
+
+ *(mtod(srcrtm, uint8_t *) + 1) = (srcrtm->m_len >> 3) - 1;
+ *(mtod(srcrtm, uint8_t *) + 2) = cmsghdr->cmsg_type;
+ bcopy((caddr_t *)cmsghdr + sizeof(struct cmsghdr), mtod(srcrtm, uint8_t *) + 3, cmsghdr->cmsg_len - sizeof(struct cmsghdr));
+ break;
+ case IPPROTO_HOPOPTS:
+ case IPPROTO_DSTOPTS:
+ /* XXX */
+ goto ret;
+ default:
+ goto ret;
+ };
+ m_adj(control, cmsghdr->cmsg_len);
+ if (!control->m_len)
+ goto finish;
+ };
+
+ DPRINTF(IDL_ERROR, ("ipv6_controltoheader: pullups failed\n"));
+ goto ret;
+
+finish:
+ if (srcrtm) {
+ struct mbuf *m2;
+ DPRINTF(IDL_EVENT, ("ipv6_controltoheader: in srcrtm case\n"));
+ if (!(m2 = m_split(*m, sizeof(struct ipv6), M_DONTWAIT)))
+ goto ret;
+ (*m)->m_next = srcrtm;
+ srcrtm->m_next = m2;
+ *mtod(srcrtm, uint8_t *) = mtod(*m, struct ipv6 *)->ipv6_nexthdr;
+ mtod(*m, struct ipv6 *)->ipv6_nexthdr = IPPROTO_ROUTING;
+
+ *payload += srcrtm->m_len;
+ (*m)->m_pkthdr.len += srcrtm->m_len;
+ };
+ m_freem(control);
+ DPRINTF(IDL_FINISHED, ("ipv6_controltoheader: returning\n"));
+ return 0;
+
+ret:
+ DPRINTF(IDL_ERROR, ("ipv6_controltoheader: returning error %d\n", error));
+ if (srcrtm)
+ m_free(srcrtm);
+ if (control)
+ m_freem(control);
+ return error;
+};