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_icmp.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_icmp.c')
-rw-r--r-- | sys/netinet6/ipv6_icmp.c | 1077 |
1 files changed, 1077 insertions, 0 deletions
diff --git a/sys/netinet6/ipv6_icmp.c b/sys/netinet6/ipv6_icmp.c new file mode 100644 index 00000000000..843fc2ddcd7 --- /dev/null +++ b/sys/netinet6/ipv6_icmp.c @@ -0,0 +1,1077 @@ +/* +%%% 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/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/time.h> +#include <sys/kernel.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> + +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> +#include <netinet6/ipv6_icmp.h> +#include <netinet6/icmpv6_var.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> + +extern struct netproc_security fixedencrypt; +extern struct netproc_auth fixedauth; +#endif /* IPSEC */ + +#include <sys/debug.h> + +#if __FreeBSD__ +#include <sys/sysctl.h> +#endif /* __FreeBSD__ */ + +/* + * Globals. + */ + +static struct sockaddr_in6 icmpsrc = { sizeof (struct sockaddr_in6), + AF_INET6 }; +static struct sockaddr_in6 icmpdst = { sizeof (struct sockaddr_in6), + AF_INET6 }; +struct icmpv6stat icmpv6stat; + +/* + * External globals. + */ + +extern struct in6_ifaddr *in6_ifaddr; +extern u_char ipv6_protox[]; +extern struct protosw inet6sw[]; + +/* + * Functions and macros (and one global that needs to be near the function). + */ + +uint8_t ipv6_saved_routing[384]; /* If a routing header has been processed, + then it will have been bcopy()d into + this buffer. If not, the first byte + will be set to zero (which would be + a nexthdr of hop-by-hop, and is not + valid). */ + +struct mbuf *ipv6_srcrt __P((void)); +void ipv6_icmp_reflect __P((struct mbuf *, int)); +void update_pathmtu __P((struct in6_addr *, uint32_t)); + +/* This is broken as of getting rid of hdrindex and the mbuf structure it + creates. While this needs to be fixed, it's a Real Shame that source + route reflection couldn't be reimplemented for this release... - cmetz */ +#if 0 +/*---------------------------------------------------------------------- + * Reverse a saved IPv6 source route, for possible use on replies. + ----------------------------------------------------------------------*/ + +struct mbuf *ipv6_srcrt() +{ + struct ipv6_srcroute0 *sr, *osr = (struct ipv6_srcroute0 *)ipv6_saved_routing; + struct in6_addr *sra, *osra; + struct mbuf *srm; + int i, j; + + if (!osr->i6sr_nexthdr) + return NULL; + if (osr->i6sr_type) + return NULL; + if (!(srm = m_get(M_DONTWAIT, MT_DATA))) + return NULL; + + sr = mtod(srm, struct ipv6_srcroute0 *); + bzero(sr, sizeof(struct ipv6_srcroute0)); + sr->i6sr_nexthdr = IPPROTO_ICMPV6; + sr->i6sr_len = osr->i6sr_len; + j = sr->i6sr_left = sr->i6sr_len/2; +/* We probably should reverse the bit mask, but it's painful, and defaulting + to loose source routing might be preferable anyway. */ + sra = (struct in6_addr *)((caddr_t)sr + sizeof(struct ipv6_srcroute0)); + osra = (struct in6_addr *)((caddr_t)osr + sizeof(struct ipv6_srcroute0)); + srm->m_len = sizeof(struct ipv6_srcroute0) + sizeof(struct in6_addr) * j; + for (i = 0; i < sr->i6sr_len/2; sra[i++] = osra[j--]); + return srm; +} +#endif /* 0 */ + +/*---------------------------------------------------------------------- + * Reflect an IPv6 ICMP packet back to the source. + ----------------------------------------------------------------------*/ + +void +ipv6_icmp_reflect(m, extra) + struct mbuf *m; + int extra; +{ + struct in6_addr tmp; + struct ipv6 *ipv6; + struct in6_ifaddr *i6a; + struct ipv6_icmp *icmp; +#if 0 + struct mbuf *routing = NULL; +#endif /* 0 */ +#ifdef IPSEC + struct socket *socket, fake; +#endif /* IPSEC */ + + /* + * Hmmm, we potentially have authentication, routing, and hop-by-hop + * headers behind this. OUCH. For now, however, assume only IPv6 + * header, followed by ICMP. + */ + + DP(FINISHED, extra, d); + + ipv6 = mtod(m, struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(m, caddr_t) + extra); + + tmp = ipv6->ipv6_dst; + ipv6->ipv6_dst = ipv6->ipv6_src; + + /* + * If the incoming packet was addressed directly to us, + * use dst as the src for the reply. Otherwise (multicast + * or anonymous), use the address which corresponds + * to the incoming interface. + */ + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + { + /* Find first (non-local if possible) address for + source usage. If multiple locals, use last one found. */ + + if (IN6_ARE_ADDR_EQUAL(&tmp, &I6A_SIN(i6a)->sin6_addr)) + break; + } + icmpdst.sin6_addr = tmp; + + if (i6a == NULL && m->m_pkthdr.rcvif != NULL) + i6a = (struct in6_ifaddr *)ifaof_ifpforaddr((struct sockaddr *)&icmpdst, + m->m_pkthdr.rcvif); + + if (i6a == NULL) + { + /* Want globally-routable if I can help it. */ + i6a = in6_ifaddr; + } + + ipv6->ipv6_src = I6A_SIN(i6a)->sin6_addr; + + ipv6->ipv6_nexthdr = IPPROTO_ICMPV6; + + if (extra > sizeof(struct ipv6)) { + DP(FINISHED, extra, 08x); + DP(FINISHED, m->m_pkthdr.len, 08x); + ipv6_stripoptions(m, extra); + DP(FINISHED, m->m_pkthdr.len, 08x); + extra = sizeof(struct ipv6); +#if 0 + if ((routing = ipv6_srcrt())) { + ipv6->ipv6_nexthdr = IPPROTO_ROUTING; + mtod(routing, struct ipv6_srcroute0 *)->i6sr_nexthdr = IPPROTO_ICMPV6; + routing->m_next = m->m_next; + m->m_next = routing; + extra += routing->m_len; + } else + DPRINTF(IDL_ERROR, ("icmp_reflect() got options but can't strip them\n")); +#endif /* 0 */ + } + + m->m_flags &= ~(M_BCAST|M_MCAST); + + DP(FINISHED, m->m_pkthdr.len, d); + + /* For errors, anything over the 576 byte mark we discard. */ + if (!(ICMPV6_INFOTYPE(icmp->icmp_type))) + if (m->m_pkthdr.len > ICMPV6_MAXLEN) + m_adj(m, -(m->m_pkthdr.len - ICMPV6_MAXLEN)); + + DP(FINISHED, m->m_pkthdr.len - extra, d); + + icmp->icmp_cksum = 0; + DPRINTF(IDL_EVENT,("ipv6_icmp_reflect() calling in6_cksum().\n")); + icmp->icmp_cksum = in6_cksum(m,IPPROTO_ICMPV6, m->m_pkthdr.len - extra, + extra); + DP(FINISHED, icmp->icmp_cksum, 04x); + + ipv6->ipv6_hoplimit = 255; + /* Version 6, priority 12 (info) or 15 (error), flow zero */ + ipv6->ipv6_versfl = htonl(((6) << 28) | + ((ICMPV6_INFOTYPE(icmp->icmp_type) ? 12 : 15) << 24)); + +#ifdef IPSEC + /* + * Packet sent should be authenticated/encrypted if responding to + * received packet that was authenticated/encrypted. + */ + if (m->m_flags & (M_AUTHENTIC | M_DECRYPTED)) { + struct netproc_requestandstate *r; + + bzero(&fake, sizeof(struct socket)); + + if (netproc_alloc(&fake)) { + DPRINTF(ERROR, ("icmp_send: netproc_alloc failed\n")); + m_freem(m); + return; + } + + r = &((struct netproc_socketdata *)fake.so_netproc)->requests[0]; + r->requestlen = ((m->m_flags & M_AUTHENTIC) ? + sizeof(struct netproc_auth) : 0) + + ((m->m_flags & M_DECRYPTED) ? + sizeof(struct netproc_security) : 0); + if (!(r->request = OSDEP_MALLOC(r->requestlen))) { + DPRINTF(ERROR, ("icmp_send: malloc(%d) failed\n", + r->requestlen)); + netproc_free(&fake); + m_freem(m); + return; + } + + { + void *p = r->request; + + if (m->m_flags & M_AUTHENTIC) { + memcpy(p, &fixedauth, sizeof(struct netproc_auth)); + p += sizeof(fixedauth); + } + if (m->m_flags & M_DECRYPTED) + memcpy(p, &fixedencrypt, sizeof(struct netproc_security)); + } + + socket = &fake; + } else + socket = NULL; +#endif /* IPSEC */ + + icmpv6stat.icps_outhist[icmp->icmp_type]++; + +#ifdef IPSEC + ipv6_output(m, NULL, IPV6_RAWOUTPUT, NULL, NULL, socket); + + if (socket) + netproc_free(socket); +#else /* IPSEC */ + ipv6_output(m, NULL, IPV6_RAWOUTPUT, NULL, NULL, NULL); +#endif /* IPSEC */ +} + +/*---------------------------------------------------------------------- + * Given a bad packet (badpack), generate an ICMP error packet in response. + * We assume that ipv6_preparse() has been run over badpack. + * + * Add rate-limiting code to this function, on a timer basis. + * (i.e. if t(current) - t(lastsent) < limit, then don't send a message. + ----------------------------------------------------------------------*/ + +void +ipv6_icmp_error(badpack, type, code, paramptr) + struct mbuf *badpack; + int type,code; + uint32_t paramptr; +{ + struct ipv6 *oipv6; + int divpoint = sizeof(struct ipv6); + struct ipv6_icmp *icmp; + struct mbuf *outgoing; + + if ((badpack->m_flags & M_MCAST) /*&& type != ICMPV6_TOOBIG */) { + m_freem(badpack); + return; + } + + /*if (type != ICMPV6_REDIRECT)*/ + icmpv6stat.icps_error++; + + /* + * Since MTU and max ICMP packet size is less than a cluster (so far...) + * pull the offending packet into a single cluster. + * + * If option-stripping becomes required, here might be the place to do it. + * (The current design decision is to not strip options. Besides, one of + * the callers of this function is ipv6_hop(), which does hop-by-hop + * option processing.) + */ + + oipv6 = mtod(badpack,struct ipv6 *); + + DDO(GROSSEVENT,printf("oipv6 (0x%lx) is:\n",(unsigned long)oipv6);dump_ipv6(oipv6)); + + DP(FINISHED, badpack->m_pkthdr.len, d); + + /* + * Get a new cluster mbuf for ICMP error message. Since IPv6 ICMP messages + * have a length limit that should be less than MCLBYTES, one cluster should + * work nicely. + */ + + if (!(outgoing = m_gethdr(M_DONTWAIT,MT_HEADER))) { + m_freem(badpack); + return; + }; + + MCLGET(outgoing, M_DONTWAIT); + if (!(outgoing->m_flags & M_EXT)) { + m_freem(badpack); + m_free(outgoing); + return; + }; + + outgoing->m_len = sizeof(struct ipv6) + ICMPV6_MINLEN; + bcopy(oipv6, mtod(outgoing, caddr_t), sizeof(struct ipv6)); + icmp = (struct ipv6_icmp *)(mtod(outgoing, caddr_t) + sizeof(struct ipv6)); + + { + int i = badpack->m_pkthdr.len; + + if (i > ICMPV6_MAXLEN - sizeof(struct ipv6) - sizeof(struct icmpv6hdr)) + i = ICMPV6_MAXLEN - sizeof(struct ipv6) - sizeof(struct icmpv6hdr); + + outgoing->m_len += i; + /* Copies are expensive, but linear buffers are nice. Luckily, the data + is bounded, short, and ICMP errors aren't that performance critical. */ + m_copydata(badpack, 0, i, mtod(outgoing, caddr_t) + + sizeof(struct ipv6) + sizeof(struct icmpv6hdr)); + } + + /* Need rcvif to do source address selection later */ + outgoing->m_pkthdr.rcvif = badpack->m_pkthdr.rcvif; + + outgoing->m_pkthdr.len = outgoing->m_len; + +#if defined(IPSEC) || defined(NRL_IPSEC) + /* + * Copy over the DECRYPTED and AUTHENTIC flag. + * NB: If the inbound packet was sent to us in an authenticated + * or encrypted tunnel, these flags are cleared when we get here. + * We don't have a way to preserve tunnel state at the moment. + * This is a hole we need to fix soon. + */ + outgoing->m_flags |= badpack->m_flags & (M_DECRYPTED | M_AUTHENTIC); + DDO(IDL_ERROR,if (outgoing->m_flags & M_AUTHENTIC) printf("icmpv6_error processing authentic pkt\n")); + DDO(IDL_ERROR,if (outgoing->m_flags & M_DECRYPTED) printf("icmpv6_error processing encrypted pkt\n")); +#endif /* defined(IPSEC) || defined(NRL_IPSEC) */ + + m_freem(badpack); + + icmp->icmp_type = type; + icmp->icmp_code = code; + icmp->icmp_unused = 0; + if (type == ICMPV6_PARAMPROB || type == ICMPV6_TOOBIG) + icmp->icmp_paramptr = htonl(paramptr); + + ipv6_icmp_reflect(outgoing, divpoint); +} + +/*---------------------------------------------------------------------- + * Update path MTU for an IPv6 destination. This function may have to go + * scan for TCP control blocks and give them hints to scale down. + * There is a small denial-of-service attack if MTU messages are + * unauthenticated. I can lower MTU to 576. + ----------------------------------------------------------------------*/ + +void +update_pathmtu(dst, newmtu) + struct in6_addr *dst; + uint32_t newmtu; /* ntohl()'ed by caller. */ +{ + int s = splnet(); + struct rtentry *rt; + struct sockaddr_in6 sin6; + + DDO(IDL_EVENT,printf("Entering update_pathmtu with:\n");\ + dump_in6_addr(dst);printf("And newmtu of %d.\n",(unsigned int)newmtu)); + + if (IN6_IS_ADDR_MULTICAST(dst)) + { + /* Multicast MTU Discovery not yet implemented */ + DPRINTF(IDL_ERROR, ("Multicast MTU too big message.\n")); + splx(s); + return; + } + + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr = *dst; + + /* + * Since I'm doing a rtalloc, no need to zero-out the port and flowlabel. + */ +#ifdef __FreeBSD__ + if ((rt = rtalloc1((struct sockaddr *)&sin6,0,0UL)) != NULL) +#else /* __FreeBSD__ */ + if ((rt = rtalloc1((struct sockaddr *)&sin6,0)) != NULL) +#endif /* __FreeBSD__ */ + { + if (rt->rt_flags & RTF_HOST) + { + if (rt->rt_rmx.rmx_mtu < newmtu) + DPRINTF(IDL_ERROR, + ("DANGER: New MTU message is LARGER than current MTU.\n")); + + rt->rt_rmx.rmx_mtu = newmtu; /* This should be enough for HLP's to + know MTU has changed, IMHO. */ + rt->rt_refcnt--; + } + else + { + DPRINTF(IDL_ERROR, + ("Got path MTU message for non-cloned destination route.\n")); + } + /* + * Find all active tcp connections, and indicate they need path MTU + * updating as well. + * + * Also, find RTF_TUNNEL routes that point to this updated route, + * because they need their path MTU lowered. Perhaps decapsulating + * the message, and sending TOOBIG messages back. + */ + } + + splx(s); + return; +} + +/*---------------------------------------------------------------------- + * ICMPv6 input routine. Handles inbound ICMPv6 packets, including + * direct handling of some packets. + ----------------------------------------------------------------------*/ + +void +ipv6_icmp_input(incoming, extra) + register struct mbuf *incoming; + int extra; +{ + struct ipv6_icmp *icmp; + struct ipv6 *ipv6; + int icmplen,code; + void (*ctlfunc) __P((int, struct sockaddr *, struct ipv6 *, struct mbuf *)); + + /* + * Q: Any address validity checks beyond those in ipv6_input()? + */ + + DPRINTF(FINISHED, ("ipv6_icmp_input -- pkthdr.len = %d, extra = %d\n", + incoming->m_pkthdr.len, extra)); + + DDO(FINISHED, dump_mbuf_tcpdump(incoming)); + + icmplen = incoming->m_pkthdr.len - extra; + if (icmplen < ICMPV6_MINLEN) + { + /* Not enough for full ICMP packet. */ + icmpv6stat.icps_tooshort++; + m_freem(incoming); + return; + } + + if (incoming->m_len < extra + ICMPV6_MINLEN) + if (!(incoming = m_pullup2(incoming, extra + ICMPV6_MINLEN))) + return; + + DDO(FINISHED, dump_mbuf_tcpdump(incoming)); + + ipv6 = mtod(incoming, struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(incoming, caddr_t) + extra); + + /* + * Verify checksum with IPv6 header at the top of this chain. + */ + + DPRINTF(IDL_EVENT,("ipv6_icmp_input() calling in6_cksum().\n")); + DPRINTF(IDL_EVENT,("icmplen = %d\n", icmplen)); + { + unsigned int cksum; + + if (cksum = in6_cksum(incoming, IPPROTO_ICMPV6, icmplen, extra)) + { + DPRINTF(IDL_ERROR,("ipv6_icmp_input() -- checksum returned %08x.\n", cksum)); + m_freem(incoming); + icmpv6stat.icps_checksum++; + return; + } + } + +#ifdef IPSEC + /* Perform input-side policy check. Drop packet if policy says to drop it. */ + { + 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 = ipv6->ipv6_src; + + bzero(&dstsa, sizeof(struct sockaddr_in6)); + dstsa.sin6_family = AF_INET6; + dstsa.sin6_len = sizeof(struct sockaddr_in6); + dstsa.sin6_addr = 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_ICMPV6, + incoming, NULL, NULL)) { + m_freem(incoming); + return; + } + } +#endif /* IPSEC */ + + code = icmp->icmp_code; + DPRINTF(IDL_EVENT, ("icmp->icmp_type = %d\n", icmp->icmp_type)); + + if (icmp->icmp_type < ICMPV6_MAXTYPE + 1) + { + icmpv6stat.icps_inhist[icmp->icmp_type]++; + + /* + * Deal with the appropriate ICMPv6 message type/code. + */ + switch(icmp->icmp_type) + { + case ICMPV6_ECHO: + icmp->icmp_type = ICMPV6_ECHOREPLY; + icmpv6stat.icps_reflect++; + ipv6_icmp_reflect(incoming, extra); + return; + case ICMPV6_UNREACH: + /* + * The pair of <type,code> should map into a PRC_* + * value such that I don't have to rewrite in_pcb.c. + */ + switch (code) + { + case ICMPV6_UNREACH_NOROUTE: + code = PRC_UNREACH_NET; + break; + case ICMPV6_UNREACH_ADMIN: + /* Subject to change */ + code = PRC_UNREACH_HOST; + break; + case ICMPV6_UNREACH_NOTNEIGHBOR: + /* Subject to change */ + code = PRC_UNREACH_HOST; + break; + case ICMPV6_UNREACH_ADDRESS: + code = PRC_HOSTDEAD; + break; + case ICMPV6_UNREACH_PORT: + code = PRC_UNREACH_PORT; + break; + default: + goto badcode; + } + goto deliver; + break; + case ICMPV6_TIMXCEED: + if (code >1) + goto badcode; + code += PRC_TIMXCEED_INTRANS; + goto deliver; + + case ICMPV6_PARAMPROB: + if (code >2) + goto badcode; + code = PRC_PARAMPROB; + + case ICMPV6_TOOBIG: + deliver: + /* + * Problem with datagram, advice HLP's. + */ + DPRINTF(IDL_EVENT, ("delivering\n")); + if (icmplen < ICMPV6_HLPMINLEN) + { + icmpv6stat.icps_badlen++; + m_freem(incoming); + return; + } + + /* May want to pullup more than this */ + if (!(incoming = m_pullup2(incoming, extra + ICMPV6_HLPMINLEN))) + return; + + /* + * If cannot determine HLP, discard packet. + * + * For now, I assume that ICMP messages will be generated such that + * the enclosed header contains only IPv6+<HLP header>. This is not + * a good assumption to make in light of all sorts of options between + * IPv6 and the relevant place to deliver this message. + */ + { + struct ipv6 *ipv6 = (struct ipv6 *)(mtod(incoming, caddr_t) + extra + ICMPV6_MINLEN); + icmpsrc.sin6_addr = ipv6->ipv6_dst; + + if (icmp->icmp_type == ICMPV6_TOOBIG) + { + update_pathmtu(&ipv6->ipv6_dst,htonl(icmp->icmp_nexthopmtu)); + /* If I want to deliver to HLP, remove the break, and + set code accordingly. */ + break; + } + DPRINTF(IDL_EVENT, ("Finding control function for %d\n", + ipv6->ipv6_nexthdr)); + if ((ctlfunc = (void *)inet6sw[ipv6_protox[ipv6->ipv6_nexthdr]].pr_ctlinput)) { + DPRINTF(IDL_EVENT, ("Calling control function for %d\n", + ipv6->ipv6_nexthdr)); + (*ctlfunc)(code, (struct sockaddr *)&icmpsrc, ipv6,incoming); + } + } + break; + + badcode: + DPRINTF(IDL_EVENT, ("Bad code!\n")); + icmpv6stat.icps_badcode++; + break; + + /* + * IPv6 multicast group messages. + */ + case ICMPV6_GRPQUERY: + case ICMPV6_GRPREPORT: + case ICMPV6_GRPTERM: + break; + + /* + * IPv6 discovery messages. + */ + case ICMPV6_ROUTERSOL: + ipv6_routersol_input(incoming, extra); + break; + case ICMPV6_ROUTERADV: + ipv6_routeradv_input(incoming, extra); + break; + case ICMPV6_NEIGHBORSOL: + ipv6_neighborsol_input(incoming, extra); + break; + case ICMPV6_NEIGHBORADV: + ipv6_neighboradv_input(incoming, extra); + break; + case ICMPV6_REDIRECT: + ipv6_redirect_input(incoming, extra); + break; + default: + /* Allow delivery to raw socket. */ + break; + } + } + DPRINTF(IDL_EVENT, ("Delivering ICMPv6 to raw socket\n")); + DP(FINISHED, incoming->m_pkthdr.len, d); + + ripv6_input(incoming, extra); /* Deliver to raw socket. */ +} + + +/* + * The following functions attempt to address ICMP deficiencies in IPv6. + * Mostly these are part of a hack to keep the user-level program from + * computing checksums. :-P + */ + +/*---------------------------------------------------------------------- + * This function should never be called. + ----------------------------------------------------------------------*/ + +int +ipv6_icmp_output(m, so, dst) + struct mbuf *m; + struct socket *so; + struct in6_addr *dst; +{ + DPRINTF(IDL_ERROR, + ("ipv6_icmp_output() was called and shouldn't have been.\n")); + + return ripv6_output(m,so,dst,NULL); +} + +#if !__FreeBSD__ +/*---------------------------------------------------------------------- + * Prepend IPv6 header, and compute IPv6 checksum for PRU_SEND, otherwise, + * redirect call to ripv6_usrreq(). + ----------------------------------------------------------------------*/ +int +#if __NetBSD__ +ipv6_icmp_usrreq(so, req, m, nam, control, p) +#else /* __NetBSD__ */ +ipv6_icmp_usrreq(so, req, m, nam, control) +#endif /* __NetBSD__ */ + struct socket *so; + int req; + struct mbuf *m, *nam, *control; +#if __NetBSD__ + struct proc *p; +#endif /* __NetBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + + DPRINTF(IDL_EVENT,("Entering ipv6_icmp_usrreq(), req == %d\n",req)); + + /* + * If not sending, or sending with the header included (which IMHO means + * the user filled in the src & dest on his/her own), do normal raw + * IPv6 user request. + */ + + DPRINTF(IDL_EVENT,("Before check for ripv6_usrreq().\n")); + if (req != PRU_SEND || inp->inp_flags & INP_HDRINCL) +#if __NetBSD__ + return ripv6_usrreq(so,req,m,nam,control,p); +#else /* __NetBSD__ */ + return ripv6_usrreq(so,req,m,nam,control); +#endif /* __NetBSD__ */ + + { + struct sockaddr *sa; + + if (nam) + sa = mtod(nam, struct sockaddr *); + else + sa = NULL; + +#if __NetBSD__ + return ipv6_icmp_send(so, req, m, sa, control, p); +#else /* __NetBSD__ */ + return ipv6_icmp_send(so, req, m, sa, control, NULL); +#endif /* __NetBSD__ */ + }; +} +#endif /* !__FreeBSD__ */ + +int ipv6_icmp_send(struct socket *so, int req, struct mbuf *m, + struct sockaddr *addr, struct mbuf *control, + struct proc *p) +{ + register struct inpcb *inp = sotoinpcb(so); + register struct ipv6 *ipv6; + register struct ipv6_icmp *icp; + struct in6_addr *dst; + int tflags = 0, len, rc; + struct in6_ifaddr *i6a; + + /* + * redundant check, but necessary since we don't know if we are coming from + * icmp_usrreq or not. + */ + if (inp->inp_flags & INP_HDRINCL) +#if __NetBSD__ || __FreeBSD__ + return ripv6_usrreq_send(so, req, m, addr, control, p); +#else /* __NetBSD__ || __FreeBSD__ */ + return ripv6_usrreq_send(so, req, m, addr, control); +#endif /* __NetBSD__ || __FreeBSD__ */ + + if (in6_ifaddr == NULL) + { + m_freem(m); + return EADDRNOTAVAIL; + } + len = m->m_pkthdr.len; + + /* + * If we get here, req == PRU_SEND and flags do not have INP_HDRINCL set. + * What that means in English is that a user process is sending an ICMPv6 + * datagram without constructing an IPv6 header. + * We will construct an IPv6 header, fill it in completely, THEN COMPUTE + * THE ICMPv6 CHECKSUM and tell ipv6_output() that we are raw. + */ + + if (so->so_state & SS_ISCONNECTED) + { + if (addr) + { + m_freem(m); + return EISCONN; + } + dst = &(inp->inp_faddr6); + i6a = (struct in6_ifaddr *)inp->inp_route6.ro_rt->rt_ifa; + } + else /* Not connected */ + { + if (addr == NULL) + { + m_freem(m); + return ENOTCONN; + } + DPRINTF(GROSSEVENT,("Sockaddr in nam is:\n")); + DDO(GROSSEVENT,dump_smart_sockaddr(addr)); + dst = &(((struct sockaddr_in6 *) addr)->sin6_addr); + inp->inp_route6.ro_dst = *((struct sockaddr_in6 *)addr); + DPRINTF(EVENT,("In icmpv6_usrreq, Route is:\n")); + DDO(EVENT, dump_rtentry(inp->inp_route6.ro_rt)); + if (so->so_options & SO_DONTROUTE) + { +#define ifatoi6a(x) ((struct in6_ifaddr *)x) + if ((i6a = + ifatoi6a(ifa_ifwithdstaddr((struct sockaddr *)addr))) == 0 + && + (i6a = + ifatoi6a(ifa_ifwithnet((struct sockaddr *)addr))) == 0) + { + m_freem(m); + return ENETUNREACH; + } + inp->inp_route.ro_rt = NULL; + } + else + { + struct route *ro = &inp->inp_route; + + /* + * If there is no route, consider sending out a neighbor advert + * across all the nodes. This is like the ipv6_onlink_query() + * call in ipv6_output.c. + */ + if (ro->ro_rt == NULL) + rtalloc(ro); + if (ro->ro_rt == NULL) + { + /* + * No route of any kind, so spray neighbor solicits out all + * interfaces, unless it's a multicast address. + */ + DPRINTF(IDL_FINISHED,("Icmpv6 spraying neigbor solicits.\n")); + if (IN6_IS_ADDR_MULTICAST(dst)) + { + /* + * Select source address for multicast, for now + * return an error. + */ + m_freem(m); + return ENETUNREACH; + } + ipv6_onlink_query((struct sockaddr_in6 *)&ro->ro_dst); + rtalloc(ro); + } + if (ro->ro_rt == NULL) + { + m_freem(m); + return ENETUNREACH; + } + DPRINTF(IDL_EVENT,("Route after ipv6_onlink_query:\n")); + DDO(IDL_EVENT,dump_route(ro)); + DDO(IDL_EVENT,if (ro) dump_rtentry(ro->ro_rt)); + 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. + */ + int error; + + DPRINTF(IDL_EVENT,("Okay...rt_ifa==NULL!\n")); + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + switch (error = ipv6_verify_onlink((struct sockaddr_in6 *)&ro->ro_dst)) + { + case 0: + break; + case -1: + error = ENETUNREACH; + default: + m_freem(m); + return error; + } + rtalloc((struct route *)ro); + if (ro->ro_rt == NULL || ro->ro_rt->rt_ifa == NULL) + panic("Oops3, I'm forgetting something after verify_onlink()."); + DPRINTF(IDL_EVENT,("Route after ipv6_verify_onlink:\n")); + DDO(IDL_EVENT,dump_route(ro)); + DDO(IDL_EVENT,if (ro) dump_rtentry(ro->ro_rt)); + } + + i6a = (struct in6_ifaddr *)ro->ro_rt->rt_ifa; + DPRINTF(IDL_EVENT,("Okay we got an interface for ipv6_icmp:\n")); + DDO(IDL_EVENT,dump_ifa((struct ifaddr *)i6a)); + } + + if (IN6_IS_ADDR_MULTICAST(dst) && inp->inp_moptions6 && + inp->inp_moptions6->i6mo_multicast_ifp && + inp->inp_moptions6->i6mo_multicast_ifp != i6a->i6a_ifp) + { + struct ifaddr *ifa; + +#if __FreeBSD__ + for (ifa = inp->inp_moptions6->i6mo_multicast_ifp->if_addrhead.tqh_first; + ifa != NULL; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = inp->inp_moptions6->i6mo_multicast_ifp->if_addrlist.tqh_first; + ifa != NULL; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = inp->inp_moptions6->i6mo_multicast_ifp->if_addrlist; + ifa != NULL; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (ifa->ifa_addr->sa_family == AF_INET6) + { + i6a = (struct in6_ifaddr *)ifa; + break; + } + if (ifa == NULL) + { + DPRINTF(IDL_ERROR,("mcast inconsitency in icmp PRU_SEND.\n")); + } + } + } + + /* + * If PCB has options hanging off of it, insert them here. + */ + + DPRINTF(GROSSEVENT,("ipv6_icmp_usrreq(): dst is ")); + DDO(GROSSEVENT,dump_in6_addr(dst)); + + M_PREPEND(m,sizeof(struct ipv6),M_WAIT); + if (m == NULL) + panic("M_PREPEND died in ipv6_icmp_usrreq().\n"); + + DPRINTF(EVENT,("Before m_pullup() for %d bytes.\n",\ + sizeof(struct ipv6) + ICMPV6_MINLEN)); + if ((m = m_pullup(m,sizeof(struct ipv6) + ICMPV6_MINLEN)) == NULL) + { + DPRINTF(IDL_ERROR,("m_pullup in ipv6_icmp_usrreq() failed.\n")); + return ENOBUFS; /* Any better ideas? */ + } + + ipv6 = mtod(m,struct ipv6 *); + ipv6->ipv6_length = len; + ipv6->ipv6_nexthdr = IPPROTO_ICMPV6; + ipv6->ipv6_hoplimit = 255; + ipv6->ipv6_dst = *dst; + ipv6->ipv6_versfl = htonl(0x60000000); /* Plus flow label stuff. */ + /* + * i6a pointer should be checked here. + */ + ipv6->ipv6_src = i6a->i6a_addr.sin6_addr; + + icp = (struct ipv6_icmp *)(m->m_data + sizeof(struct ipv6)); + if (!(sotoinpcb(so)->inp_csumoffset)) + sotoinpcb(so)->inp_csumoffset = 2; + + DPRINTF(GROSSEVENT,("ipv6_icmp_usrreq(): Headers are\n")); + DDO(GROSSEVENT,dump_ipv6(ipv6)); + DDO(GROSSEVENT,dump_ipv6_icmp(icp)); + + /* + * After this comment block you'd probably insert options, + * and adjust lengths accordingly. + */ + + /* + * Temporarily tweak INP_HDRINCL to fool ripv6_output(). I still don't + * know how a user who sets INP_HDRINCL for real will prepare ICMP packets. + * Also, set up data structures for callback routine in ipv6_output(). + */ + + if (!(sotoinpcb(so)->inp_flags & INP_HDRINCL)) + { + sotoinpcb(so)->inp_flags |= INP_HDRINCL; + tflags = 1; + } + rc = ripv6_output(m,so,dst,control); + if (!(so->so_state & SS_ISCONNECTED) && !(so->so_options & SO_DONTROUTE)) + { + RTFREE(inp->inp_route.ro_rt); + inp->inp_route.ro_rt = NULL; + } + if (tflags) + sotoinpcb(so)->inp_flags &= ~INP_HDRINCL; + + return rc; +} + +#if __FreeBSD__ +#if __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ +#define MAYBEINLINE __inline__ +#else /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ */ +#define MAYBEINLINE +#endif /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ */ + +#if 0 +MAYBEINLINE int ripv6_usrreq_attach(struct socket *, int , struct proc *); +MAYBEINLINE int ripv6_usrreq_detach(struct socket *); +MAYBEINLINE int ripv6_usrreq_abort(struct socket *); +MAYBEINLINE int ripv6_usrreq_bind(struct socket *, struct sockaddr *, struct proc *); +MAYBEINLINE int ripv6_usrreq_connect(struct socket *, struct sockaddr *, struct proc *); +MAYBEINLINE int ripv6_usrreq_shutdown(struct socket *so); +MAYBEINLINE int ripv6_usrreq_control(struct socket *, u_long, caddr_t, + struct ifnet *, struct proc *); +MAYBEINLINE int ripv6_usrreq_sense(struct socket *, struct stat *); +MAYBEINLINE int ripv6_usrreq_sockaddr(struct socket *, struct sockaddr **); +MAYBEINLINE int ripv6_usrreq_peeraddr(struct socket *, struct sockaddr **); +#endif /* 0 */ + +struct pr_usrreqs ipv6_icmp_usrreqs = { + ripv6_usrreq_abort, pru_accept_notsupp, ripv6_usrreq_attach, + ripv6_usrreq_bind, ripv6_usrreq_connect, pru_connect2_notsupp, + ripv6_usrreq_control, ripv6_usrreq_detach, ripv6_usrreq_detach, + pru_listen_notsupp, ripv6_usrreq_peeraddr, pru_rcvd_notsupp, + pru_rcvoob_notsupp, ipv6_icmp_send, ripv6_usrreq_sense, + ripv6_usrreq_shutdown, ripv6_usrreq_sockaddr, sosend, soreceive, sopoll +}; +#endif /* __FreeBSD__ */ + +int *icmpv6_sysvars[] = ICMPV6CTL_VARS; + +#if __FreeBSD__ +SYSCTL_STRUCT(_net_inet_icmpv6, ICMPV6CTL_STATS, icmpv6stat, CTLFLAG_RD, &icmpv6stat, icmpv6stat, ""); +#else /* __FreeBSD__ */ +int +ipv6_icmp_sysctl(name, namelen, oldp, oldlenp, newp, newlen) + int *name; + u_int namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +{ + if (name[0] >= ICMPV6CTL_MAXID) + return (EOPNOTSUPP); + switch (name[0]) { +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + case ICMPV6CTL_STATS: + return sysctl_rdtrunc(oldp, oldlenp, newp, &icmpv6stat, sizeof(icmpv6stat)); + default: + return (sysctl_int_arr(icmpv6_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__ */ |