diff options
author | Theo de Raadt <deraadt@cvs.openbsd.org> | 1999-01-06 23:26:59 +0000 |
---|---|---|
committer | Theo de Raadt <deraadt@cvs.openbsd.org> | 1999-01-06 23:26:59 +0000 |
commit | 003294c571f5404cf3bb2ab3cf33eba8643b932b (patch) | |
tree | 0efc8463ff2d3c2555c4a6a830fe508568cc5e07 /sys/netinet6/ipv6_input.c | |
parent | 21e642825078d715499c3110ace9c6e61f6c6db4 (diff) |
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_input.c')
-rw-r--r-- | sys/netinet6/ipv6_input.c | 1414 |
1 files changed, 1414 insertions, 0 deletions
diff --git a/sys/netinet6/ipv6_input.c b/sys/netinet6/ipv6_input.c new file mode 100644 index 00000000000..c1fa3f20371 --- /dev/null +++ b/sys/netinet6/ipv6_input.c @@ -0,0 +1,1414 @@ +/* +%%% 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/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> +#ifdef __FreeBSD__ +#include <net/netisr.h> +#endif /* __FreeBSD__ */ + +#include <netinet/in.h> + +#include <netinet6/in6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> +#include <netinet6/ipv6_icmp.h> +#include <netinet6/ipv6_addrconf.h> + +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> + +#if __OpenBSD__ && defined(NRL_IPSEC) +#define IPSEC 1 +#endif /* __OpenBSD__ && defined(NRL_IPSEC) */ + +#ifdef IPSEC +#include <sys/osdep.h> +#include <net/netproc.h> +#include <net/netproc_var.h> + +#include <netsec/ipsec.h> +#endif /* IPSEC */ + +#if __FreeBSD__ +#include <sys/sysctl.h> +#endif /* __FreeBSD__ */ + +#include <sys/debug.h> + +/* + * Globals + */ + +u_char ipv6_protox[IPPROTO_MAX]; /* For easy demuxing to HLP's. */ +struct ipv6stat ipv6stat; /* Stats. */ +struct in6_ifaddr *in6_ifaddr = NULL; /* List of IPv6 addresses. */ +struct in6_ifnet *in6_ifnet = NULL; /* List of IPv6 interfaces. */ +struct ipv6_fragment *ipv6_fragmentq = NULL; /* Fragment reassembly queue */ +struct ifqueue ipv6intrq; +struct route6 ipv6forward_rt; + +/* + * External globals + */ + +extern int ipv6forwarding; /* See in6_proto.c */ +extern int ipv6rsolicit; /* See in6_proto.c */ +extern int ipv6_defhoplmt; /* See in6_proto.c */ +extern int ipv6qmaxlen; /* See in6_proto.c */ +extern struct protosw inet6sw[]; /* See in6_proto.c */ +extern struct domain inet6domain; /* See in6_proto.c */ +extern struct discq dqhead; /* See ipv6_discovery.c */ + +/* + * Funct. prototypes. + */ + +void ipv6_forward __P((struct mbuf *)); +void ipv6_discovery_init __P((void)); +extern int ipv6_clean_nexthop __P((struct radix_node *,void *)); +#if !__FreeBSD__ +int sysctl_int __P((void *, size_t *, void *, size_t, int *)); +/* For reasons somewhat unknown, <sys/mbuf.h> doesn't prototype this */ +struct mbuf *m_split __P((register struct mbuf *, int, int)); +#endif /* !__FreeBSD__ */ + +void ipv6intr __P((void)); +int ipv6_enabled __P((struct ifnet *)); + +static struct mbuf *ipv6_saveopt(caddr_t p, int size, int type, int level); +#if 0 +static struct mbuf *ipv6_savebag(struct mbuf *m, int level); +#endif /* 0 */ +struct mbuf *ipv6_headertocontrol(struct mbuf *m, int extra, int inp_flags); + +/*---------------------------------------------------------------------- + * IPv6 initialization function. + ----------------------------------------------------------------------*/ + +void +ipv6_init() +{ + register struct protosw *pr; + register int i; + + DPRINTF(GROSSEVENT,("IPv6 initializing...")); + + bzero(&ipv6stat,sizeof(struct ipv6stat)); + + pr = pffindproto(PF_INET6, IPPROTO_RAW, SOCK_RAW); + if (pr == 0) + panic("ipv6_init"); + + /* + * To call the right IPv6 next header function off next header type, have + * next header numbers index the protocol switch, like protocols in IP + * (default the switch for this). Otherwise, just switch off into normal + * (TCP,UDP) stuff. + * + * Initialize ipv6_protox[]. + */ + + for (i = 0; i < IPPROTO_MAX; i++) + ipv6_protox[i] = pr - inet6sw; + for (pr = inet6domain.dom_protosw; + pr < inet6domain.dom_protoswNPROTOSW; pr++) + if (pr->pr_domain->dom_family == PF_INET6 + && pr->pr_protocol != IPPROTO_RAW ) + ipv6_protox[pr->pr_protocol] = pr - inet6sw; + + /* + * Initialize discovery stuff. + */ + + ipv6_discovery_init(); + + /* + * Initialize addrconf stuff. + */ + + addrconf_init(); + + /* + * Initialize IPv6 i/f queue. + */ + + ipv6intrq.ifq_maxlen = ipv6qmaxlen; + +#ifdef IPSEC + /* + * Initialise IPsec + */ + ipsec_init(); +#endif /* IPSEC */ + + DPRINTF(GROSSEVENT,("...done\n")); +} + +/*---------------------------------------------------------------------- + * IPv6 input queue interrupt handler. + ----------------------------------------------------------------------*/ + +void +ipv6intr() +{ + struct mbuf *m; + int s; + + while (1) /* Keep yanking off packets until I hit... */ + { + s = splimp(); + IF_DEQUEUE(&ipv6intrq, m); + splx(s); + + if (m == NULL) + { + return; /* ...HERE. THIS is how I exit this endless loop. */ + } + +#ifdef IPSEC + m->m_flags &= ~(M_AUTHENTIC | M_DECRYPTED); +#endif /* IPSEC */ + + ipv6_input(m, 0); + } +} + +#if __FreeBSD__ +NETISR_SET(NETISR_IPV6, ipv6intr); +#endif /* __FreeBSD__ */ + +/*---------------------------------------------------------------------- + * Actual inbound (up the protocol graph from the device to the user) + * IPv6 processing. + ----------------------------------------------------------------------*/ + +void +ipv6_input(incoming, extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *header; + struct in6_ifaddr *i6a = NULL; + struct in6_multi *in6m = NULL; + int jumbogram = 0; + uint8_t nexthdr; + int payload_len; + + DPRINTF(GROSSEVENT,("ipv6_input(struct mbuf *incoming=%08lx, int extra=%x)\n", (unsigned long)incoming, extra)); + DPRINTF(IDL_FINISHED,("incoming->m_data = %08lx, & 3 = %lx\n", (unsigned long)incoming->m_data, (unsigned long)incoming->m_data & 3)); + + /* + * Can't do anything until at least an interface is marked as + * ready for IPv6. + */ + + if (in6_ifaddr == NULL) + { + m_freem(incoming); + return; + } + + ipv6stat.ips_total++; + + /* + * IPv6 inside something else. Discard encapsulating header(s) and + * (maybe try to) make it look like virgin IPv6 packet. + * + * As with other stupid mbuf tricks, there probably is a better way. + */ + + if (extra) { + struct mbuf *newpacket; + + DP(FINISHED, extra, d); + +#if IPSEC + /* Perform input-side policy check. Drop packet if policy says to drop + it. + + Note that the gist of this check is that every decapsulation + requires a trip to input policy. For packets that end up locally, + this is probably bad. For packets that go off-host, this is probably + good. + + Right now, we err on the side of security and always check. -cmetz*/ + { + struct sockaddr_in6 srcsa, dstsa; + + bzero(&srcsa, sizeof(struct sockaddr_in6)); + srcsa.sin6_family = AF_INET6; + srcsa.sin6_len = sizeof(struct sockaddr_in6); + srcsa.sin6_addr = mtod(incoming, struct ipv6 *)->ipv6_src; + + bzero(&dstsa, sizeof(struct sockaddr_in6)); + dstsa.sin6_family = AF_INET6; + dstsa.sin6_len = sizeof(struct sockaddr_in6); + dstsa.sin6_addr = mtod(incoming, struct ipv6 *)->ipv6_dst; + + /* XXX - state arg should NOT be NULL, it should be the netproc state + carried up the stack - cmetz */ + if (netproc_inputpolicy(NULL, (struct sockaddr *)&srcsa, + (struct sockaddr *)&dstsa, IPPROTO_IPV6, + incoming, NULL, NULL)) { + m_freem(incoming); + return; + } + } +#endif /* IPSEC */ + + /* + * Split packet into what I need, and what was encapsulating what I + * need. Discard the encapsulating portion. + */ + + if (!(newpacket = m_split(incoming, extra, M_DONTWAIT))) { + printf("ipv6_input() couldn't trim extra off.\n"); + m_freem(incoming); + return; + } + newpacket->m_flags |= incoming->m_flags; + m_freem(incoming); + incoming = newpacket; + extra = 0; + } + + /* + * Even before preparsing (see vitriolic comments later), + * I need to have the whole IPv6 header at my disposal. + */ + + + if (incoming->m_len < sizeof (struct ipv6) && + (incoming = m_pullup(incoming, sizeof(struct ipv6))) == NULL) + { + ipv6stat.ips_toosmall++; + return; + } + + /* + * Check version bits immediately. + */ + + if ((incoming->m_data[0] >> 4) != IPV6VERSION) + { + ipv6stat.ips_badvers++; + m_freem(incoming); + return; + } + + header = mtod(incoming, struct ipv6 *); + + DDO(IDL_FINISHED,dump_ipv6(header)); + + /* + * Save off payload_len because of munging later... + */ + payload_len = ntohs(header->ipv6_length); + + /* + * Check that the amount of data in the buffers + * is as at least much as the IPv6 header would have us expect. + * Trim mbufs if longer than we expect. + * Drop packet if shorter than we expect. + */ + + if (incoming->m_pkthdr.len < payload_len + sizeof(struct ipv6)) + { + ipv6stat.ips_tooshort++; + m_freem(incoming); + return; + } + + if (incoming->m_pkthdr.len > payload_len + sizeof(struct ipv6)) + if (!payload_len) + jumbogram = 1; /* We might have a jumbogram here! */ + else if (incoming->m_len == incoming->m_pkthdr.len) + { + incoming->m_len = payload_len + sizeof(struct ipv6); + incoming->m_pkthdr.len = payload_len + sizeof(struct ipv6); + } + else m_adj(incoming, + (payload_len + sizeof(struct ipv6)) - incoming->m_pkthdr.len ); + + /* + * See if it's for me by checking list of i6a's. I may want to convert + * this into a routing lookup and see if rt->rt_ifp is loopback. + * (Might be quicker. :) + */ + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&I6A_SIN(i6a)->sin6_addr, &header->ipv6_dst)) + break; + /* + * Check for inbound multicast datagram is for us. + */ + + if (i6a == NULL && IN6_IS_ADDR_MULTICAST(&header->ipv6_dst)) + { +#ifdef MROUTING + /* + * Do IPv6 mcast routing stuff here. This means even if I'm in the + * m-cast group, I may have to forward the packet too. Hence, I put + * the m-cast routing stuff HERE. + */ +#endif + DPRINTF(IDL_EVENT,("In multicast case. Looking up...")); + IN6_LOOKUP_MULTI(&header->ipv6_dst,incoming->m_pkthdr.rcvif,in6m); + if (!in6m) { + DPRINTF(IDL_EVENT,("mcast lookup failed.\n")); + DDO(IDL_EVENT, dump_ipv6(header)); + ipv6stat.ips_cantforward++; + m_freem(incoming); + return; + } + DPRINTF(IDL_EVENT,("succeeded.\n")); + } + + /* If this datagram is for me, I'll either have one of my addresses in + i6a, or one of my multicast groups in in6m. From the code given + above, the condition (in6m && i6a) is always false. */ + + if (i6a || in6m) + { + if (i6a) { +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + /* Don't increment stats for encapsulated pkt */ + if (!extra) { + i6a->i6a_ifa.ifa_ipackets++; + i6a->i6a_ifa.ifa_ibytes += incoming->m_pkthdr.len; + }; +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + + if (i6a->i6a_addrflags & I6AF_NOTSURE) { + /* + * Don't want to unicast receive packets for me unless I know this + * is a verified unique address. + * + * Multicast packets will still come in and be handled. + */ + m_freem(incoming); + return; + } + }; + + nexthdr = (mtod(incoming, struct ipv6 *))->ipv6_nexthdr; + + DPRINTF(IDL_EVENT, ("nexthdr = %d.\n", nexthdr)); + + DPRINTF(IDL_EVENT, ("Calling protoswitch for %d\n", nexthdr)); + + ipv6stat.ips_delivered++; + (*inet6sw[ipv6_protox[nexthdr]].pr_input) + (incoming, extra + sizeof(struct ipv6)); + + DPRINTF(IDL_EVENT, ("Back from protoswitch for %d\n", nexthdr)); + return; + } + + DDO(IDL_EVENT,printf("Might forward for dest: ");\ + dump_in6_addr(&header->ipv6_dst);); + + if (ipv6forwarding == 0) + { + DPRINTF(IDL_ERROR,("ipv6_input: Would call ipv6_forward if on.\n")); + ipv6stat.ips_cantforward++; + m_freem(incoming); + return; + } + else + { + /* + * Perform hop-by-hop options, if present. Dest opt. bags, and source + * routes SHOULD be handled by previous protosw call. + */ + DPRINTF(IDL_FINISHED,("Calling ipv6_forward.\n")); + ipv6_forward(incoming); + } +} + +/*---------------------------------------------------------------------- + * IPv6 reassembly code. Returns an mbuf chain if the fragment completes + * a message, otherwise it returns NULL. + * + * Assumptions: + * * The IPv6 header is at the beginning of the incoming structure + * and has already been pulled up. I don't expect this to be + * a problem. + * * Fragments don't overlap (or, if they do, we can discard them). + * They can be duplicates (which we drop), but they cannot overlap. + * If we have IPv4->IPv6 header translation, this assumption could + * be incorrect because IPv4 has intermediate fragmentation (which + * you have to have in order for fragments to overlap). This is yet + * another reason why header translation is a Bad Thing. + * * The slowtimeo()-based routine that frees fragments will not + * get called during the middle of this routine. This assumption + * seems to be made in the IPv4 reassembly code. How this is so + * actually so I have yet to discover. Requiring splnet() or some + * sort of resource arbitration to handle this assumption being + * incorrect could be a major hassle. + * + * Other comments: + * + * * m_split(), a valuable call that gets no press, does much magic here. + * * Security against denial-of-service attacks on reassembly cannot be + * provided. + * * frag_id is more or less used as a 32-bit cookie. We don't need to + * htonl() it, because it's either equal or it's not, no matter what + * byte order you use. + ----------------------------------------------------------------------*/ + +void +ipv6_reasm(incoming, extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6_fraghdr *fraghdr; + struct ipv6_fragment *fragment, **pfragment; + + if (incoming->m_len < extra + sizeof(struct ipv6_fraghdr)) + if (!(incoming = m_pullup(incoming, extra + sizeof(struct ipv6_fraghdr)))) + return; + + DP(FINISHED, OSDEP_PCAST(incoming), 08x); + + fraghdr = (struct ipv6_fraghdr *)(mtod(incoming, caddr_t) + extra); + fraghdr->frag_bitsoffset = htons(fraghdr->frag_bitsoffset); + + /* + * Locate reassembly queue for incoming fragment. + */ + { + struct ipv6 *ipv6i, *ipv6f; + + ipv6i = mtod(incoming, struct ipv6 *); + + for (pfragment = &ipv6_fragmentq; *pfragment; + pfragment = &((*pfragment)->next)) { + + ipv6f = mtod((*pfragment)->prefix, struct ipv6 *); + + if (IN6_ARE_ADDR_EQUAL(&ipv6i->ipv6_src, &ipv6f->ipv6_src)) { + if (IN6_ARE_ADDR_EQUAL(&ipv6i->ipv6_dst, &ipv6f->ipv6_dst)) { + register struct ipv6_fraghdr *fhf = mtod((*pfragment)->data, + struct ipv6_fraghdr *); + + if ((fraghdr->frag_id == fhf->frag_id) && + (fraghdr->frag_nexthdr == fhf->frag_nexthdr)) { + break; + } + } + } + } + } + + fragment = *pfragment; + + if (!fragment) { + /* + * Create a new fragment queue entry - this one looks new. + * + * Notice that the order of insertion is such that the newest queues + * are at the head of the one-way list. The entry aging code takes + * advantage of this. + */ + MALLOC(fragment, struct ipv6_fragment *, sizeof(*fragment), M_FRAGQ, + M_NOWAIT); + if (!fragment) { + DPRINTF(IDL_ERROR, ("MALLOC fragment queue entry failed.\n")); + goto reasm_cleanup; + }; + fragment->ttl = 60; /* 30 seconds */ + if (!(fragment->data = m_split(incoming, extra, M_DONTWAIT))) { + free(fragment, M_FRAGQ); + goto reasm_cleanup; + } + + fragment->prefix = incoming; + + DP(FINISHED, OSDEP_PCAST(fragment->prefix), 08x); + DP(FINISHED, OSDEP_PCAST(fragment->data), 08x); + + fragment->flags = (~fraghdr->frag_bitsoffset) & 1; + + fragment->next = ipv6_fragmentq; + ipv6_fragmentq = fragment; + return; + } + + /* + * If two packets have claimed to be the beginning or the end, we don't + * know which is right. The easiest solution is to drop this packet. + */ + + if (!(fraghdr->frag_bitsoffset & 1)) + if (fragment->flags & 1) /* i.e. we already have the end... */ + { + /* + * Duplicate (ending) fragment. + */ + DPRINTF(IDL_FINISHED, ("Got a dupe/overlap fragment")); + goto reasm_cleanup; + } + else + fragment->flags |= 1; + + if (!(fraghdr->frag_bitsoffset & 0xFFF8)) { + /* + * We want to end up with the part before the frag header for the packet + * at offset zero. (RFC1123?) + */ + if (!(mtod(fragment->data, struct ipv6_fraghdr *)->frag_bitsoffset & 0xFFF8)) { + /* + * Duplicate (initial) packet. + */ + DPRINTF(IDL_FINISHED, ("Got a dupe/overlap fragment")); + goto reasm_cleanup; + } else { + struct mbuf *mbuf; + + DP(FINISHED, OSDEP_PCAST(fragment->prefix), 08x); + DP(FINISHED, OSDEP_PCAST(fragment->data), 08x); + + m_freem(fragment->prefix); + + /* Save everything before the frag header */ + if (!(mbuf = m_split(incoming, extra, M_DONTWAIT))) { + /* should probably toss whole fragment queue */ + m_freem(incoming); + return; + } + + fragment->prefix = incoming; + incoming = mbuf; + + DP(FINISHED, OSDEP_PCAST(fragment->prefix), 08x); + DP(FINISHED, OSDEP_PCAST(fragment->data), 08x); + } + } else + m_adj(incoming, extra); /* Discard everything before the frag header */ + + { + struct mbuf *hm[3]; + int i; + + { + struct mbuf *m, *pm; + + /* + * Find where this fragment fits in. + */ + for (pm = NULL, m = fragment->data; m; pm = m, m = m->m_nextpkt) { + if (((mtod(m, struct ipv6_fraghdr *))->frag_bitsoffset & 0xFFF8) > + (fraghdr->frag_bitsoffset & 0xFFF8)) { + break; + } + + if ((mtod(m, struct ipv6_fraghdr *))->frag_bitsoffset == + fraghdr->frag_bitsoffset) { + /* + * Duplicate fragment. + */ + DPRINTF(IDL_FINISHED, ("Got a dupe/overlap fragment")); + goto reasm_cleanup; + } + } + + /* + * Right here, pm will contain the preceeding fragment to the incoming + * one, and m will contain the succeeding fragment to the incoming one. + * + * This is somewhat non-obvious. hm[] is a vector of pointers to the + * mbufs containing ipv6_fraghdrs and dm[] is a vector of pointers to + * the mbufs at the head of each of their associated data lists. [0] + * is the mbuf in the main chain to the immediate left of where our + * new data should go, [1] is our new data, and [2] is the mbuf in + * the main chain to the immediate right of where our new data should + * go. One of [0] or [2] may be NULL. + * + * Each dm[n]->m_nextpkt will have that fragment's length stored. + * + * The reason why we do this is so we can bubble together the [0] and + * [1] elements if possible, make the [1] = [0] if we do, then we can + * bubble the [1] and the [2] together and it'll do the right thing. + * We could theoretically do this for the rest of the list except that + * it is made deliberately unecessary (we bubble on insertion until we + * have a known-done big bubble so we don't have to do a O(N/2) + * rescan of the list every time just to figure out whether or not + * we're done. + * + * This seems really ugly, but it does the job and it may even be + * somewhat efficient. + */ + + hm[0] = pm; + hm[1] = incoming; + hm[2] = m; + + if (!pm) { + incoming->m_nextpkt = fragment->data; + fragment->data = incoming; + } else { + pm->m_nextpkt = incoming; + incoming->m_nextpkt = m; + } + } + + for (i = 0; i < 2; i++) { + if (!hm[i] || !hm[i+1]) { + DP(FINISHED, i, d); + continue; + } + + if ((((mtod(hm[i], struct ipv6_fraghdr *))->frag_bitsoffset + & 0xFFF8) + hm[i]->m_pkthdr.len - sizeof(struct ipv6_fraghdr)) > + ((mtod(hm[i+1], struct ipv6_fraghdr *))->frag_bitsoffset + & 0xFFF8)) { + /* + * Overlapping fragment. + */ + DPRINTF(IDL_FINISHED, ("Got a dupe/overlap fragment")); + goto reasm_cleanup; + } + + if ((((mtod(hm[i], struct ipv6_fraghdr *))->frag_bitsoffset + & 0xFFF8) + hm[i]->m_pkthdr.len - sizeof(struct ipv6_fraghdr)) == + ((mtod(hm[i+1], struct ipv6_fraghdr *))->frag_bitsoffset + & 0xFFF8)) { + /* + * If the fragments are contiguous, bubble them together: + * Combine the data chains and increase the appropriate + * chain data length. The second fragment header is now + * unnecessary. + */ + if (!((mtod(hm[i+1], struct ipv6_fraghdr *))->frag_bitsoffset & 1)){ + (mtod(hm[i], struct ipv6_fraghdr *))->frag_bitsoffset &= (~1); + } + + DDO(FINISHED, dump_mchain(hm[i])); + DDO(FINISHED, dump_mchain(hm[i+1])); + + /* Trim the second fragment header */ + m_adj(hm[i+1], sizeof(struct ipv6_fraghdr)); + /* Append the second fragment's data */ + m_cat(hm[i], hm[i+1]); + /* And update the first fragment's length */ + hm[i]->m_pkthdr.len += hm[i+1]->m_pkthdr.len; + + hm[i]->m_nextpkt = hm[i+1]->m_nextpkt; + + DDO(FINISHED, dump_mchain(hm[i])); + + /* Hack to make the bubble happen with the m found above if we + just bubbled with the pm found above */ + if (!i) + hm[i+1] = hm[i]; + } + } + } + + /* + * Now, the moment of truth. Do we have a complete packet? + * To be done, we have to have only one packet left in the queue now that + * we've bubbled together (i.e., one complete packet), have it at offset + * zero (i.e., there's nothing before it), and not have its more bit set + * (i.e., there's nothing after it). If we meet these conditions, we are + * DONE! + * + * Remember when we htons()ed frag_bitsoffset? If we're done, it contains + * a zero. I don't know of any architecture in which a zero in network + * byte order isn't a zero in host byte order, do you? + */ + + if (!fragment->data->m_nextpkt && + !(mtod(fragment->data, struct ipv6_fraghdr *))->frag_bitsoffset) { + uint8_t nexthdr = mtod(fragment->data, struct ipv6_fraghdr *)->frag_nexthdr; + + if (*pfragment) + *pfragment = fragment->next; + + /* + * Pain time. + * + * The fragmentation header must be removed. This requires me to rescan + * prefix, going through each header until I figure out where the last + * header before the fragmentation header is. Then I set that header's + * next header to the fragmentation header's next header. + * + * N.B. that this means people adding new random header processing code + * to this IPv6 implementation need to make the appropriate mods below. + * Failure to do so will really hose you if your header appears before + * a fragment header. It is a Good Thing to mod the code below even if + * you don't *think* it will ever appear before a fragment header, just + * because it *could*. + * + * This is really annoying and somewhat expensive. On the other hand, + * it might prove itself to be yet another reason for higher level + * protocols to work at avoiding fragmentation where possible. + */ + + /* + Since we start out by doing a m_pullup() of extra + sizeof(struct + ipv6_fraghdr, we can treat this as a straight linear buffer. + + This could easily be implemented as a fast and slow path, but + reassembly is an inherently slow path anyway. -cmetz + */ + + DDO(FINISHED, dump_mchain(fragment->prefix)); + DDO(FINISHED, dump_mchain(fragment->data)); + + { + caddr_t data; + uint8_t *type; + + DDO(FINISHED, dump_ipv6(mtod(fragment->prefix, struct ipv6 *))); + + data = mtod(fragment->prefix, caddr_t) + sizeof(struct ipv6); + type = &(mtod(fragment->prefix, struct ipv6 *)->ipv6_nexthdr); + + while(*type != IPPROTO_FRAGMENT) { + switch(*type) { + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + { + struct ipv6_opthdr *ipv6_opthdr = (struct ipv6_opthdr *)data; + + type = &(ipv6_opthdr->oh_nexthdr); + data += sizeof(struct ipv6_opthdr) + + ipv6_opthdr->oh_extlen * sizeof(uint64_t); + }; + break; + case IPPROTO_ROUTING: + { + struct ipv6_srcroute0 *ipv6_srcroute0 = + (struct ipv6_srcroute0 *)data; + + type = &(ipv6_srcroute0->i6sr_nexthdr); + data += sizeof(struct ipv6_srcroute0) + + ipv6_srcroute0->i6sr_len * sizeof(uint64_t); + }; + break; + default: + DPRINTF(ERROR, ("ipv6_reasm: Received a header (%d) that isn't allowed before a fragment header", *type)); + + DP(FINISHED, OSDEP_PCAST(fragment->data), 08x); + DP(FINISHED, OSDEP_PCAST(fragment->prefix), 08x); + + m_freem(fragment->data); + m_freem(fragment->prefix); + FREE(fragment, M_FRAGQ); + + return; + } + } + *type = nexthdr; + } + + extra = fragment->prefix->m_pkthdr.len; + + incoming = fragment->prefix; + m_adj(fragment->data, sizeof(struct ipv6_fraghdr)); + m_cat(incoming, fragment->data); + incoming->m_pkthdr.len += fragment->data->m_pkthdr.len; + + FREE(fragment, M_FRAGQ); + + /* Can't reassemble into a jumbogram */ + if (incoming->m_pkthdr.len > 0xffff) { + m_freem(incoming); + return; /* no other cleanup needed */ + } + + /* Dummy up length */ + (mtod(incoming, struct ipv6 *))->ipv6_length = + htons(incoming->m_pkthdr.len - sizeof(struct ipv6)); + + DDO(FINISHED, dump_mchain(incoming)); + + (*inet6sw[ipv6_protox[nexthdr]].pr_input)(incoming, extra); + } + return; + +reasm_cleanup: + if (incoming) + m_freem(incoming); + + return; +}; + +/*---------------------------------------------------------------------- + * IPv6 forwarding engine. + * + * Assumes IPv6 header is already pulled up and ready-to-read. + ----------------------------------------------------------------------*/ + +void +ipv6_forward(outbound) + struct mbuf *outbound; +{ + struct ipv6 *ipv6 = mtod(outbound, struct ipv6 *); + struct sockaddr_in6 *sin6; + struct rtentry *rt; + struct mbuf *ocopy; + int type = 0, code = 0, pptr = 0, error; + + /* + * Check hop limit. + */ + + if (ipv6->ipv6_hoplimit <= 1) + { + ipv6_icmp_error(outbound,ICMPV6_TIMXCEED,ICMPV6_TIMXCEED_INTRANS,0); + return; + } + + /* + * Check link-local nature of source and dest. (Thanks to rja!) + */ + if (IN6_IS_ADDR_LINKLOCAL(&ipv6->ipv6_src) || IN6_IS_ADDR_LINKLOCAL(&ipv6->ipv6_dst)) + { + printf("Can't forward packet with link-locals in it!\n"); + m_freem(outbound); + return; + } + ipv6->ipv6_hoplimit--; + sin6 = &ipv6forward_rt.ro_dst; + if ((rt = ipv6forward_rt.ro_rt) == NULL || + !IN6_ARE_ADDR_EQUAL(&ipv6->ipv6_dst, &sin6->sin6_addr)) + { + if (ipv6forward_rt.ro_rt != NULL) + { + RTFREE(ipv6forward_rt.ro_rt); + ipv6forward_rt.ro_rt = NULL; + } + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_addr = ipv6->ipv6_dst; + + rtalloc_noclone((struct route *)&ipv6forward_rt,ONNET_CLONING); + if (ipv6forward_rt.ro_rt == NULL) + { + ipv6_icmp_error(outbound,ICMPV6_UNREACH, ICMPV6_UNREACH_NOROUTE, 0); + return; + } + rt = ipv6forward_rt.ro_rt; + } + + /* + * Save 576 bytes of the packet in case we need to generate and ICMPv6 + * message to the sender. + */ + + ocopy = m_copy(outbound,0, min(ntohs(ipv6->ipv6_length),IPV6_MINMTU)); + + /* + * ip_forward() keeps some statistics here if GATEWAY is defined. We + * skip that for now. + */ + + if (rt->rt_ifp == outbound->m_pkthdr.rcvif && rt_mask(rt) + && rt_mask(rt)->sa_len + && (rt->rt_flags & (RTF_TUNNEL|RTF_DYNAMIC|RTF_MODIFIED)) == 0 ) + { + DPRINTF(IDL_EVENT, ("WARNING: May be cause for a redirect in ipv6_forward().\n")); + } + + /* + * Perform hop-by-hop options since we're ready to go! + */ + + if (!ipv6->ipv6_nexthdr) + { + /* + * If bad hop-by-hop, return. + */ + printf("Hop-by-hop options present in packet to be forwarded!\n"); + } + + error = ipv6_output(outbound, &ipv6forward_rt, IPV6_FORWARDING, NULL, NULL, NULL); + + if (error) + ipv6stat.ips_cantforward++; + else + { + /* + * Check for redirect flags that should've been set in the redirect + * code, otherwise... + */ + m_freem(ocopy); + return; + } + + switch (error) + { + case 0: /* type and code should've been set by redirect code. */ + break; + + case ENETUNREACH: /* These two should've been checked above. */ + case EHOSTUNREACH: + type = ICMPV6_UNREACH; + code = ICMPV6_UNREACH_NOROUTE; + break; + + case EHOSTDOWN: + type = ICMPV6_UNREACH; + code = ICMPV6_UNREACH_ADDRESS; + break; + + case EMSGSIZE: + type = ICMPV6_TOOBIG; + code = 0; + ipv6stat.ips_cantfrag++; + pptr = rt->rt_rmx.rmx_mtu; + break; + } + + ipv6_icmp_error(ocopy, type, code, pptr); +} + +/*---------------------------------------------------------------------- + * IPv6 hop-by-hop option handler. + ----------------------------------------------------------------------*/ + +void +ipv6_hop(incoming, extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *header; + struct ipv6_opthdr *hopopt; + struct ipv6_option *option; + uint8_t *tmp; + + if (incoming->m_len < extra + sizeof(struct ipv6_opthdr)) + if (!(incoming = m_pullup2(incoming, extra + sizeof(struct ipv6_opthdr)))) + return; + + hopopt = (struct ipv6_opthdr *)(mtod(incoming, caddr_t) + extra); + + if (incoming->m_len < extra + sizeof(struct ipv6_opthdr) + + (hopopt->oh_extlen * sizeof(uint64_t))) { + if (!(incoming = m_pullup2(incoming, extra + sizeof(struct ipv6_opthdr) + + (hopopt->oh_extlen * sizeof(uint64_t))))) + return; + + hopopt = (struct ipv6_opthdr *)(mtod(incoming, caddr_t) + extra); + } + + header = mtod(incoming, struct ipv6 *); + + tmp = hopopt->oh_data; + /* + * Slide the char pointer tmp along, parsing each option in the "bag" of + * hop-by-hop options. + */ + while (tmp < hopopt->oh_data + (hopopt->oh_extlen << 2)) + { + option = (struct ipv6_option *)tmp; + switch (option->opt_type) + { + case OPT_PAD1: + tmp++; + break; + case OPT_PADN: + tmp += option->opt_datalen + 2; + break; + case OPT_JUMBO: + tmp += 2; + /* + * Confirm that the packet header field matches the jumbogram size. + */ + if (incoming->m_pkthdr.len != ntohl(*(uint32_t *)tmp) + sizeof(*header)) + { + /* + * For now, bail. Add instrumenting code here, too. + */ + m_freem(incoming); + return; + } + break; + default: + /* + * Handle unknown option by taking appropriate action based on + * high bit values. With this code, this first non-skipping + * unknown option will cause the packet to be dropped. + */ + switch (option->opt_type & 0xC0) + { + case 0: /* Skip over */ + tmp += option->opt_datalen + 2; + break; + case 0xC0: /* Only if not multicast... */ + if (IN6_IS_ADDR_MULTICAST(&header->ipv6_dst)) + /* FALLTHROUGH */ + case 0x80: /* Send ICMP Unrecognized type. */ + { + /* Issue ICMP parameter problem. */ + ipv6_icmp_error(incoming,ICMPV6_PARAMPROB, + ICMPV6_PARAMPROB_BADOPT, +#ifdef __alpha__ + (u_long)option - (u_long)hopopt->oh_data + +#else + (uint32_t)option - (uint32_t)hopopt->oh_data + +#endif + sizeof(struct ipv6)); + } + return; /* incoming has already been freed. */ + + case 0x40: /* Discard packet */ + m_freem(incoming); + return; + } + } + } + + DPRINTF(GROSSEVENT, ("ipv6_hop calling protoswitch for %d\n", \ + hopopt->oh_nexthdr)); + + (*inet6sw[ipv6_protox[hopopt->oh_nexthdr]].pr_input) + (incoming, extra + sizeof(struct ipv6_opthdr) + + (hopopt->oh_extlen * sizeof(uint64_t))); + + DPRINTF(GROSSEVENT, ("Leaving ipv6_hop\n")); +} + + +/*---------------------------------------------------------------------- + * If timers expires on reassembly queues, discard them. + * Also update any discovery queues. + ----------------------------------------------------------------------*/ + +void +ipv6_slowtimo() +{ + struct ipv6_fragment *fragment, *fragmentprev; + int s = splnet(); + + /* + * Age reasssembly fragments. + * + * (Since fragments are inserted at the beginning of the queue, once we've + * found the first timed-out fragment, we know that everything beyond is + * also timed-out since it must be older.) + * + */ + for (fragmentprev = NULL, fragment = ipv6_fragmentq; fragment; + fragmentprev = fragment, fragment = fragment->next) + if (fragment->ttl <= 1) + break; + else + fragment->ttl--; + + if (fragment) { + struct mbuf *m, *m2; + + if (fragmentprev) + fragmentprev->next = NULL; + else + ipv6_fragmentq = NULL; + + /* + * This loop does most of the work and doesn't require splnet()...? + */ + splx(s); + + while(fragment) { + /* + * We "should" (says the spec) send an ICMP time exceeded here. + * However, among other headaches, we may not actually have a copy + * of the real packet sent to us (if we bubbled, we now have a frag + * header that never really came from the sender). The solution taken + * for now is to continue the BSD tradition of not bothering to send + * these messages. + */ + m = fragment->data; + ipv6stat.ips_fragtimeout++; + while(m) { + m2 = m; + m = m->m_nextpkt; + m_freem(m2); + } + m_freem(fragment->prefix); + fragmentprev = fragment; + fragment = fragment->next; + FREE(fragmentprev, M_FRAGQ); + } + } else + splx(s); +} + +/*---------------------------------------------------------------------- + * Drain all fragments & possibly discovery structures. + ----------------------------------------------------------------------*/ + +void +ipv6_drain() +{ + struct ipv6_fragment *totrash; + struct mbuf *m,*m2; + struct radix_node_head *rnh = rt_tables[AF_INET6]; + + while (ipv6_fragmentq != NULL) + { + ipv6stat.ips_fragdropped++; + totrash = ipv6_fragmentq; + ipv6_fragmentq = totrash->next; + m = totrash->data; + while (m) + { + m2 = m; + m = m->m_nextpkt; + m_freem(m2); + } + if (totrash->prefix) + m_freem(totrash->prefix); + FREE(totrash, M_FRAGQ); + } + + /* + * Might want to delete all off-net host routes, + * and maybe even on-net host routes. + * + * For now, do only the off-net host routes. + */ + (void) rnh->rnh_walktree(rnh, ipv6_clean_nexthop, (void *)1); +} + +/*---------------------------------------------------------------------- + * sysctl(2) handler for IPv6. Not yet implemented. + ----------------------------------------------------------------------*/ +int *ipv6_sysvars[] = IPV6CTL_VARS; + +#if __FreeBSD__ +SYSCTL_STRUCT(_net_inet_ipv6, IPV6CTL_STATS, ipv6stats, CTLFLAG_RD, &ipv6stat, ipv6stat, ""); +#else /* __FreeBSD__ */ +int +ipv6_sysctl(name, namelen, oldp, oldlenp, newp, newlen) + int *name; + uint namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +{ + if (namelen != 1) + return ENOTDIR; + + switch (name[0]) + { +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + case IPV6CTL_STATS: + return sysctl_rdtrunc(oldp, oldlenp, newp, &ipv6stat, sizeof(ipv6stat)); + default: + return (sysctl_int_arr(ipv6_sysvars, name, namelen, oldp, oldlenp, newp, newlen)); +#else /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + default: + return EOPNOTSUPP; +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + } +} +#endif __FreeBSD__ + +/*---------------------------------------------------------------------- + * Should be a macro, this function determines if IPv6 is running on a + * given interface. + ----------------------------------------------------------------------*/ + +int +ipv6_enabled(ifp) + struct ifnet *ifp; +{ + struct in6_ifaddr *i6a; + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp) + return 1; + + return 0; +} + +/*---------------------------------------------------------------------- + * Strips IPv6 options for TCP or UDP. + * + * This function assumes that the input chain (incoming) has been + * munged by ipv6_preparse() and starts with an IPv6 header. + * The header index is invalid after this call. + * The IPv6 header is not updated EXCEPT for the length, which must be + * in HOST order. + * Note that the API for this function is NOT THE SAME as its IPv4 + * counterpart. + * + * Often called like: + * + * ipv6_stripoptions(incoming, extra, nexthdr); + * + * Where ihi and ihioff are the header index arrays, passed up after + * ipv6_preparse(). If preparse is eliminated or altered, this code will + * be too. + ----------------------------------------------------------------------*/ + +void ipv6_stripoptions(incoming, extra) +register struct mbuf *incoming; +int extra; +{ + struct mbuf *optm, *m; + int optlen; + + if (extra == sizeof(struct ipv6)) { + /* i.e. If there are no options... */ + optm = NULL; + optlen = 0; + return; + } + + if (!(optm = m_split(incoming, sizeof(struct ipv6), M_DONTWAIT))) + return; + + if (!(m = m_split(optm, extra - sizeof(struct ipv6), M_DONTWAIT))) { + m_cat(incoming, optm); + return; + } + + m_cat(incoming, m); + incoming->m_pkthdr.len -= (extra - sizeof(struct ipv6)); + + (mtod(incoming, struct ipv6 *))->ipv6_length = + (incoming->m_pkthdr.len - sizeof(struct ipv6) > 0xffff) ? 0 : + htons(incoming->m_pkthdr.len - sizeof(struct ipv6)); + + /* + * XXX - We should be saving these stripped options somewhere... + */ + m_freem(optm); +} + +static struct mbuf *ipv6_saveopt(caddr_t p, int size, int type, int level) +{ + register struct cmsghdr *cp; + struct mbuf *m; + + if ((m = m_get(M_DONTWAIT, MT_CONTROL)) == NULL) + return ((struct mbuf *) NULL); + cp = (struct cmsghdr *) mtod(m, struct cmsghdr *); + bcopy(p, CMSG_DATA(cp), size); + size += sizeof(*cp); + m->m_len = size; + cp->cmsg_len = size; + cp->cmsg_level = level; + cp->cmsg_type = type; + return m; +} + +#if 0 +static struct mbuf *ipv6_savebag(struct mbuf *m, int level) +{ + uint8_t *p = mtod(m, uint8_t *) + 1; + int len = (*(p++) << 3) + 6; + struct mbuf *opts = NULL, **mp = &opts; + + while(len > 0) { + if (!*p) { + p++; len--; + continue; + } + + if (len <= 1) + return opts; + + if (*p != 1) + if ((*mp = ipv6_saveopt((caddr_t)(p + 2), *(p + 1), *p, level))) + mp = &(*mp)->m_next; + + len -= *(p + 1) + 2; + p += *(p + 1) + 2; + }; + + return opts; +}; +#endif /* 0 */ + +struct mbuf *ipv6_headertocontrol(struct mbuf *m, int extra, int inp_flags) +{ + struct mbuf *opts = NULL, **mp = &opts; + + if (inp_flags & INP_RXINFO) { + struct in6_pktinfo pktinfo; + + if (m->m_pkthdr.rcvif) { + pktinfo.ipi6_ifindex = m->m_pkthdr.rcvif->if_index; + } else { + DPRINTF(IDL_ERROR, ("ipv6_controldata: m->m_pkthdr.rcvif = NULL!\n")); + pktinfo.ipi6_ifindex = 0; + } + + bcopy(&(mtod(m, struct ipv6 *)->ipv6_dst), &pktinfo.ipi6_addr, sizeof(struct in6_addr)); + if ((*mp = ipv6_saveopt((caddr_t)&pktinfo, sizeof(struct in6_pktinfo), IPV6_PKTINFO, IPPROTO_IPV6))) + mp = &(*mp)->m_next; + }; + + if (inp_flags & INP_HOPLIMIT) { + int hoplimit = mtod(m, struct ipv6 *)->ipv6_hoplimit; + if ((*mp = ipv6_saveopt((caddr_t)&hoplimit, sizeof(int), IPV6_HOPLIMIT, IPPROTO_IPV6))) + mp = &(*mp)->m_next; + }; + +#if 0 + /* Since there's not any immediate need for these options anyway, we'll + worry about reimplementing them later. - cmetz */ + + if (inp_flags & INP_RXHOPOPTS) { + int i; + for (i = 1; i < ihioff; i++) + if (ihi[i].ihi_nexthdr == IPPROTO_HOPOPTS) { + if ((*mp = ipv6_savebag(ihi[i].ihi_mbuf, IPPROTO_HOPOPTS))) + mp = &(*mp)->m_next; + break; + }; + }; + + if (inp_flags & INP_RXDSTOPTS) { + int i; + for (i = 1; i < ihioff; i++) + if (ihi[i].ihi_nexthdr == IPPROTO_DSTOPTS) { + if ((*mp = ipv6_savebag(ihi[i].ihi_mbuf, IPPROTO_DSTOPTS))) + mp = &(*mp)->m_next; + break; + }; + }; + + if (inp_flags & INP_RXSRCRT) { + int i; + for (i = 1; i < ihioff; i++) + if (ihi[i].ihi_nexthdr == IPPROTO_ROUTING) { + if ((*mp = ipv6_saveopt((caddr_t)(mtod(ihi[i].ihi_mbuf, uint8_t *) + 3), (*mtod(ihi[i].ihi_mbuf, uint8_t *) << 3) + 5, *(mtod(ihi[i].ihi_mbuf, uint8_t *) + 2), IPPROTO_ROUTING))) + mp = &(*mp)->m_next; + break; + }; + }; +#endif /* 0 */ + + return opts; +}; |