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 | |
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')
-rw-r--r-- | sys/netinet6/icmpv6.h | 81 | ||||
-rw-r--r-- | sys/netinet6/icmpv6_var.h | 52 | ||||
-rw-r--r-- | sys/netinet6/in6.c | 910 | ||||
-rw-r--r-- | sys/netinet6/in6_cksum.c | 232 | ||||
-rw-r--r-- | sys/netinet6/in6_pcb.c | 1426 | ||||
-rw-r--r-- | sys/netinet6/in6_proto.c | 387 | ||||
-rw-r--r-- | sys/netinet6/in6_var.h | 189 | ||||
-rw-r--r-- | sys/netinet6/ipv6.h | 123 | ||||
-rw-r--r-- | sys/netinet6/ipv6_addrconf.c | 241 | ||||
-rw-r--r-- | sys/netinet6/ipv6_addrconf.h | 22 | ||||
-rw-r--r-- | sys/netinet6/ipv6_discovery.c | 3285 | ||||
-rw-r--r-- | sys/netinet6/ipv6_icmp.c | 1077 | ||||
-rw-r--r-- | sys/netinet6/ipv6_icmp.h | 257 | ||||
-rw-r--r-- | sys/netinet6/ipv6_input.c | 1414 | ||||
-rw-r--r-- | sys/netinet6/ipv6_output.c | 2078 | ||||
-rw-r--r-- | sys/netinet6/ipv6_rtrequest.c | 1005 | ||||
-rw-r--r-- | sys/netinet6/ipv6_trans.c | 669 | ||||
-rw-r--r-- | sys/netinet6/ipv6_trans.h | 26 | ||||
-rw-r--r-- | sys/netinet6/ipv6_var.h | 346 | ||||
-rw-r--r-- | sys/netinet6/nd6_protocol.h | 100 | ||||
-rw-r--r-- | sys/netinet6/raw_ipv6.c | 1059 | ||||
-rw-r--r-- | sys/netinet6/tcpipv6.h | 36 |
22 files changed, 15015 insertions, 0 deletions
diff --git a/sys/netinet6/icmpv6.h b/sys/netinet6/icmpv6.h new file mode 100644 index 00000000000..31ac045a677 --- /dev/null +++ b/sys/netinet6/icmpv6.h @@ -0,0 +1,81 @@ +/* +%%% portions-copyright-nrl-97 +Portions of this software are Copyright 1997-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 these portions of the +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>. + +*/ + +#ifndef _NETINET6_ICMPV6_H +#define _NETINET6_ICMPV6_H 1 + +#include <netinet6/ipv6.h> + +struct icmpv6hdr { + uint8_t icmpv6_type; /* type field */ + uint8_t icmpv6_code; /* code field */ + uint16_t icmpv6_cksum; /* checksum field */ + union { + uint32_t un_data32[1]; /* type-specific field */ + uint16_t un_data16[2]; /* type-specific field */ + uint8_t un_data8[4]; /* type-specific field */ + } icmpv6_dataun; +}; + +#define icmpv6_data32 icmpv6_dataun.un_data32 +#define icmpv6_data16 icmpv6_dataun.un_data16 +#define icmpv6_data8 icmpv6_dataun.un_data8 +#define icmpv6_pptr icmpv6_data32[0] /* parameter prob */ +#define icmpv6_mtu icmpv6_data32[0] /* packet too big */ +#define icmpv6_id icmpv6_data16[0] /* echo request/reply */ +#define icmpv6_seq icmpv6_data16[1] /* echo request/reply */ +#define icmpv6_maxdelay icmpv6_data16[0] /* mcast group membership */ + +#define ICMPV6_DST_UNREACH 1 +#define ICMPV6_PACKET_TOOBIG 2 +#define ICMPV6_TIME_EXCEEDED 3 +#define ICMPV6_PARAMETER_PROBLEM 4 + +#define ICMPV6_INFOMSG_MASK 128 /* all informational messages */ +#define ICMPV6_ECHO_REQUEST 128 +#define ICMPV6_ECHO_REPLY 129 +#define ICMPV6_MEMBERSHIP_QUERY 130 +#define ICMPV6_MEMBERSHIP_REPORT 131 +#define ICMPV6_MEMBERSHIP_REDUCTION 132 + +#define ICMPV6_UNREACH_NOROUTE 0 +#define ICMPV6_UNREACH_ADMIN 1 /* administratively prohibited */ +#define ICMPV6_UNREACH_NOTNEIGHBOR 2 /* not a neighbor (and must be) */ +#define ICMPV6_UNREACH_ADDRESS 3 +#define ICMPV6_UNREACH_PORT 4 + +#define ICMPV6_EXCEEDED_HOPS 0 /* Hop Limit == 0 in transit */ +#define ICMPV6_EXCEEDED_REASSEMBLY 1 /* Reassembly time out */ + +#define ICMPV6_PARAMPROB_HDR 0 /* erroneous header field */ +#define ICMPV6_PARAMPROB_NEXTHDR 1 /* unrecognized Next Header */ +#define ICMPV6_PARAMPROB_OPTION 2 /* unrecognized option */ + +struct icmpv6_filter { + uint32_t data[8]; /* 8*32 = 256 bits */ +}; + +#define ICMPV6_FILTER_WILLPASS(type, filterp) \ + ((((filterp)->data[(type) >> 5]) & (1 << ((type) & 31))) == 0) +#define ICMPV6_FILTER_WILLBLOCK(type, filterp) \ + ((((filterp)->data[(type) >> 5]) & (1 << ((type) & 31))) != 0) +#define ICMPV6_FILTER_SETPASS(type, filterp) \ + ((((filterp)->data[(type) >> 5]) &= ~(1 << ((type) & 31)))) +#define ICMPV6_FILTER_SETBLOCK(type, filterp) \ + ((((filterp)->data[(type) >> 5]) |= (1 << ((type) & 31)))) +#define ICMPV6_FILTER_SETPASSALL(filterp) \ + memset((filterp), 0, sizeof(struct icmpv6_filter)) +#define ICMPV6_FILTER_SETBLOCKALL(filterp) \ + memset((filterp), 0xff, sizeof(struct icmpv6_filter)) + +#endif /* _NETINET6_ICMPV6_H */ diff --git a/sys/netinet6/icmpv6_var.h b/sys/netinet6/icmpv6_var.h new file mode 100644 index 00000000000..36f86fbda1b --- /dev/null +++ b/sys/netinet6/icmpv6_var.h @@ -0,0 +1,52 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_ICMPV6_VAR_H +#define _NETINET6_ICMPV6_VAR_H 1 + +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 +#define _ICMPV6STAT_TYPE u_quad_t +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ +#define _ICMPV6STAT_TYPE u_long +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + +struct icmpv6stat +{ + _ICMPV6STAT_TYPE icps_error; + _ICMPV6STAT_TYPE icps_tooshort; + _ICMPV6STAT_TYPE icps_checksum; + _ICMPV6STAT_TYPE icps_outhist[ICMPV6_MAXTYPE+1]; + _ICMPV6STAT_TYPE icps_badlen; + _ICMPV6STAT_TYPE icps_badcode; + _ICMPV6STAT_TYPE icps_reflect; /* Number of in-kernel responses */ + _ICMPV6STAT_TYPE icps_inhist[ICMPV6_MAXTYPE+1]; +}; + +/* + * Names for ICMPV6 sysctl objects + */ +#define ICMPV6CTL_STATS 1 /* statistics */ +#define ICMPV6CTL_MAXID 2 + +#define ICMPV6CTL_NAMES { \ + { 0, 0 }, \ + { "stats", CTLTYPE_STRUCT }, \ +} + +#define ICMPV6CTL_VARS { \ + 0, \ + 0, \ +} + +#ifdef KERNEL +struct icmpv6stat icmpv6stat; +#endif +#endif /* _NETINET6_ICMPV6_VAR_H */ diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c new file mode 100644 index 00000000000..db08496f193 --- /dev/null +++ b/sys/netinet6/in6.c @@ -0,0 +1,910 @@ +/* +%%% 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/ioctl.h> +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/systm.h> +#if __NetBSD__ || __FreeBSD__ +#include <sys/proc.h> +#endif /* __NetBSD__ || __FreeBSD__ */ + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> + +#include <netinet/in.h> + +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> +#include <netinet6/ipv6_icmp.h> + +#include <sys/debug.h> + +/* + * Globals + */ + +struct ifnet *mcastdefault = NULL; /* Should be changeable by sysctl(). */ + +const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; +const struct in6_addr in6addr_loopback = IN6ADDR_LOOPBACK_INIT; + +/* + * External globals + */ + +extern struct sockaddr_in6 in6_allones; +extern struct in6_ifaddr *in6_ifaddr; +extern struct in6_ifnet *in6_ifnet; +extern int ipv6forwarding; + +static void setmcastdef __P((register struct ifnet *)); +void del_in6_ifnet __P((struct ifnet *)); +struct in6_ifnet *add_in6_ifnet __P((struct ifnet *, int *)); +int in6_ifscrub __P((struct ifnet *, struct in6_ifaddr *)); +int in6_ifinit __P((register struct ifnet *, register struct in6_ifaddr *, struct sockaddr_in6 *, int, int)); +void addrconf_dad __P((struct in6_ifaddr *)); + +/*---------------------------------------------------------------------- + * Set the default multicast interface. In single-homed case, this will + * always be the non-loopback interface. In multi-homed cases, the function + * should be able to set one accordingly. The multicast route entry + * (ff00::/8) will have its rt_ifp point to this interface, and its rt_ifa + * point to whatever rtrequest() does. The rt_ifa should be more intelligently + * set eventually. + ----------------------------------------------------------------------*/ + +static void +setmcastdef(ifp) + register struct ifnet *ifp; +{ +#ifdef __FreeBSD__ +struct ifaddr *ifa = ifp->if_addrhead.tqh_first; +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + struct ifaddr *ifa = ifp->if_addrlist.tqh_first; +#else /* __NetBSD__ || __OpenBSD__ */ + struct ifaddr *ifa = ifp->if_addrlist; +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + struct sockaddr_dl lsdl; + struct sockaddr_in6 lsin6; + struct rtentry *newrt=NULL; + int s; + + if (ifp == mcastdefault) + return; + + /* + * If NULL, nuke any mcast entry. + */ + + /* + * Find link addr for ifp. + */ + + while (ifa != NULL && ifa->ifa_addr->sa_family != AF_LINK) +#ifdef __FreeBSD__ + ifa = ifa->ifa_link.tqe_next; +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + ifa = ifa->ifa_list.tqe_next; +#else /* __NetBSD__ || __OpenBSD__ */ + ifa = ifa->ifa_next; +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + + if (ifa == NULL) + panic("Can't find AF_LINK for new multicast default interface."); + + bcopy(ifa->ifa_addr,&lsdl,ifa->ifa_addr->sa_len); + DDO(IDL_EVENT,dump_smart_sockaddr((struct sockaddr *)&lsdl)); + lsdl.sdl_alen = 0; + lsdl.sdl_slen = 0; + lsdl.sdl_nlen = 0; + + /* + * Delete old route, and add new one. + */ + + bzero(&lsin6,sizeof(lsin6)); + lsin6.sin6_family = AF_INET6; + lsin6.sin6_len = sizeof(lsin6); + lsin6.sin6_addr.s6_addr[0]=0xff; + + /* Neat property, mask and value are identical! */ + + s = splnet(); + rtrequest(RTM_DELETE,(struct sockaddr *)&lsin6,NULL, + (struct sockaddr *)&lsin6,0,NULL); + /* + * + * NB: If we clone, we have mcast dests being on a route. + * Consider multihomed system with processes talking to the + * same mcast group, but out different interfaces. + * + * Also, the RTM_ADD will do its best to find a "source address" to stick + * in the rt_ifa field. (See ipv6_rtrequest.c for this code.) + */ + rtrequest(RTM_ADD,(struct sockaddr *)&lsin6,(struct sockaddr *)&lsdl, + (struct sockaddr *)&lsin6,0,&newrt); + if (newrt == NULL) + panic("Assigning default multicast if."); + newrt->rt_rmx.rmx_mtu = ifp->if_mtu; + newrt->rt_refcnt--; + mcastdefault = ifp; + splx(s); +} + +/*---------------------------------------------------------------------- + * Delete an "IPv6 interface". Only called inside splnet(). + ----------------------------------------------------------------------*/ + +void +del_in6_ifnet(ifp) + struct ifnet *ifp; +{ + struct in6_ifnet *i6ifp,*prev = NULL; + + for (i6ifp = in6_ifnet; i6ifp != NULL; i6ifp = i6ifp->i6ifp_next) + { + if (i6ifp->i6ifp_ifp == ifp) + break; + prev = i6ifp; + } + + if (i6ifp == NULL) + panic("Ooooh boy, consistency mismatch in del_in6_ifnet!"); + + if (--(i6ifp->i6ifp_numaddrs) == 0) + { + while (i6ifp->i6ifp_multiaddrs != NULL) + { + i6ifp->i6ifp_multiaddrs->in6m_refcount = 1; + in6_delmulti(i6ifp->i6ifp_multiaddrs); + } + if (prev == NULL) + in6_ifnet = i6ifp->i6ifp_next; + else prev->i6ifp_next = i6ifp->i6ifp_next; + free(i6ifp,M_I6IFP); + } +} + +/*---------------------------------------------------------------------- + * Add a new "IPv6 interface". Only called inside splnet(). + * Perhaps send router adverts when this gets called. For now, they + * are issued when duplicate address detection succeeds on link-locals. + * See ipv6_addrconf.c for details. + ----------------------------------------------------------------------*/ + +struct in6_ifnet * +add_in6_ifnet(ifp, new) + struct ifnet *ifp; /* Assume an in6_ifaddr with this ifp is already + allocated and linked into the master list. */ + int *new; /* XXX */ +{ + struct in6_ifnet *i6ifp; + + *new = 0; + for (i6ifp = in6_ifnet; i6ifp != NULL; i6ifp = i6ifp->i6ifp_next) + if (i6ifp->i6ifp_ifp == ifp) + break; + + if (i6ifp == NULL) + { + i6ifp = malloc(sizeof(*i6ifp),M_I6IFP,M_NOWAIT); + if (i6ifp == NULL) + { + printf("DANGER! Malloc for i6ifp failed.\n"); + return NULL; + } + i6ifp->i6ifp_ifp = ifp; + i6ifp->i6ifp_multiaddrs = NULL; + i6ifp->i6ifp_numaddrs = 1; + /* Other inits... */ + i6ifp->i6ifp_next = in6_ifnet; + in6_ifnet = i6ifp; + *new = 1; + } + + return i6ifp; +} + +/*---------------------------------------------------------------------- + * This function is called by the PRU_CONTROL handlers in both TCP and UDP. + * (Actually raw_ipv6 might need a PRU_CONTROL handler, but raw_ip doesn't + * have one.) + ----------------------------------------------------------------------*/ + +int +#if __NetBSD__ || __FreeBSD__ +in6_control(so, cmd, data, ifp, internal, p) +#else /* __NetBSD__ || __FreeBSD__ */ +in6_control(so, cmd, data, ifp, internal) +#endif /* __NetBSD__ || __FreeBSD__ */ + struct socket *so; +#if __NetBSD__ + u_long cmd; +#else /* __NetBSD__ */ + int cmd; +#endif /* __NetBSD__ */ + caddr_t data; + register struct ifnet *ifp; + int internal; +#if __NetBSD__ || __FreeBSD__ + struct proc *p; +#endif /* __NetBSD__ || __FreeBSD__ */ +{ + register struct inet6_ifreq *ifr = (struct inet6_ifreq *)data; + register struct in6_ifaddr *i6a = 0; + struct in6_ifaddr *oi6a; + struct inet6_aliasreq *ifra = (struct inet6_aliasreq *)data; + struct sockaddr_in6 oldaddr; + int error, hostIsNew, maskIsNew, ifnetIsNew = 0; +#if !__NetBSD__ && !__OpenBSD__ && !__FreeBSD__ + struct ifaddr *ifa; +#endif /* !__NetBSD__ && !__OpenBSD__ && !__FreeBSD__ */ + + /* + * If given an interface, find first IPv6 address on that interface. + * I may want to change how this is searched. I also may want to + * discriminate between link-local, site-local, v4-compatible, etc. + * + * This is used by the SIOCGIFADDR_INET6, and other such things. + * Those ioctls() currently assume only one IPv6 address on an interface. + * This is not a good assumption, and this code will have to be modified + * to correct that assumption. + */ + if (ifp) + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp) + break; + + switch (cmd) + { + case SIOCAIFADDR_INET6: + case SIOCDIFADDR_INET6: + case SIOCVIFADDR_INET6: + /* + * For adding and deleting an address, find an exact match for + * that address. Note that ifr_addr and ifra_addr are in the same + * place, so even though VIFADDR uses a different struct than AIFADDR, + * the match will still occur. + */ + if (ifra->ifra_addr.sin6_family == AF_INET6 && + (cmd != SIOCDIFADDR_INET6 || + !IN6_IS_ADDR_UNSPECIFIED(&ifra->ifra_addr.sin6_addr))) + for (oi6a = i6a; i6a; i6a = i6a->i6a_next) + { + if (i6a->i6a_ifp == ifp && + IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &ifra->ifra_addr.sin6_addr)) + break; /* Out of for loop. */ + } + + /* + * You can't delete what you don't have... + */ + if (cmd == SIOCDIFADDR_INET6 && i6a == 0) + return EADDRNOTAVAIL; + + /* + * User program requests verification of address. No harm done in + * letting ANY program use this ioctl(), so we put code in for it + * here. + * + * If I found the i6a, check if I'm not sure. Return EWOULDBLOCK if + * not sure, return 0 if sure. Return EADDRNOTAVAIL if not available + * (i.e. DAD failed.). + */ + if (cmd == SIOCVIFADDR_INET6) + if (i6a == NULL) + return EADDRNOTAVAIL; + else if (i6a->i6a_addrflags & I6AF_NOTSURE) + return EWOULDBLOCK; + else return 0; + + /* FALLTHROUGH TO... */ + + case SIOCSIFDSTADDR_INET6: +#if __NetBSD__ || __FreeBSD__ + if (p == 0 || (error = suser(p->p_ucred, &p->p_acflag)) ) +#else /* __NetBSD__ || __FreeBSD__ */ + if ((so->so_state & SS_PRIV) == 0) +#endif /* __NetBSD__ || __FreeBSD__ */ + return EPERM; + + if (ifp==0) + panic("in6_control, ifp==0"); + if (i6a == NULL) + { + struct in6_ifaddr *tmp; + + /* + * Create new in6_ifaddr (IPv6 interface address) for additions + * and destination settings. + */ + if (!(tmp = (struct in6_ifaddr *)malloc(sizeof(struct in6_ifaddr), + M_IFADDR,M_NOWAIT))) + { + return ENOBUFS; + } + + bzero(tmp,sizeof(struct in6_ifaddr)); + /* + * Set NOTSURE addrflag before putting in list. + */ + tmp->i6a_addrflags = I6AF_NOTSURE; + if ((i6a = in6_ifaddr)) + { + for (; i6a->i6a_next; i6a=i6a->i6a_next) + ; + i6a->i6a_next = tmp; + } + else in6_ifaddr = tmp; + i6a = tmp; +#ifdef __FreeBSD__ + TAILQ_INSERT_TAIL(&ifp->if_addrhead, (struct ifaddr *)i6a, + ifa_link); +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + TAILQ_INSERT_TAIL(&ifp->if_addrlist, (struct ifaddr *)i6a, + ifa_list); +#else /* __NetBSD__ || __OpenBSD__ */ + if (ifa = ifp->if_addrlist) + { + for (; ifa->ifa_next; ifa=ifa->ifa_next) + ; + ifa->ifa_next = (struct ifaddr *)i6a; + } + else ifp->if_addrlist = (struct ifaddr *)i6a; +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + i6a->i6a_ifa.ifa_addr = (struct sockaddr *)&i6a->i6a_addr; + i6a->i6a_ifa.ifa_dstaddr = (struct sockaddr *)&i6a->i6a_dstaddr; + i6a->i6a_ifa.ifa_netmask + = (struct sockaddr *)&i6a->i6a_sockmask; + i6a->i6a_sockmask.sin6_len = sizeof(struct sockaddr_in6); + i6a->i6a_ifp = ifp; + + /* + * Add address to IPv6 interface lists. + */ + i6a->i6a_i6ifp = add_in6_ifnet(ifp, &ifnetIsNew); + } + break; + case SIOCGIFADDR_INET6: + case SIOCGIFNETMASK_INET6: + case SIOCGIFDSTADDR_INET6: + /* + * Can't get information on what is not there... + */ + if (i6a == NULL) + return EADDRNOTAVAIL; + break; + + default: + return EOPNOTSUPP; + } + + switch (cmd) + { + /* + * The following three cases assume that there is only one address per + * interface; this is not good in IPv6-land. Unfortunately, the + * ioctl() interface, is such that I'll have to rewrite the way things + * work here, either that, or curious user programs will have to troll + * /dev/kmem (like netstat(8) does). + */ + case SIOCGIFADDR_INET6: + bcopy(&(i6a->i6a_addr),&(ifr->ifr_addr),sizeof(struct sockaddr_in6)); + break; + + case SIOCGIFDSTADDR_INET6: + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + return EINVAL; + bcopy(&(i6a->i6a_dstaddr),&(ifr->ifr_dstaddr), + sizeof(struct sockaddr_in6)); + break; + + case SIOCGIFNETMASK_INET6: + bcopy(&(i6a->i6a_sockmask),&(ifr->ifr_addr),sizeof(struct sockaddr_in6)); + break; + + case SIOCSIFDSTADDR_INET6: + i6a->i6a_addrflags &= ~I6AF_NOTSURE; + if ((ifp->if_flags & IFF_POINTOPOINT) == 0) + return EINVAL; + oldaddr = i6a->i6a_dstaddr; + i6a->i6a_dstaddr = *(struct sockaddr_in6 *)&ifr->ifr_dstaddr; + if (ifp->if_ioctl && (error = (*ifp->if_ioctl)(ifp, SIOCSIFDSTADDR, + (caddr_t)i6a))) + { + i6a->i6a_dstaddr = oldaddr; + return error; + } + if (i6a->i6a_flags & IFA_ROUTE) + { + i6a->i6a_ifa.ifa_dstaddr = (struct sockaddr *)&oldaddr; + rtinit(&(i6a->i6a_ifa), RTM_DELETE, RTF_HOST); + i6a->i6a_ifa.ifa_dstaddr = (struct sockaddr *)&i6a->i6a_dstaddr; + rtinit(&(i6a->i6a_ifa), RTM_ADD, RTF_HOST|RTF_UP); + } + break; + + /* + * For adding new IPv6 addresses to an interface, I stuck to the way + * that IPv4 uses, pretty much. + */ + case SIOCAIFADDR_INET6: + maskIsNew = 0; + hostIsNew = 1; + error = 0; + if (i6a->i6a_addr.sin6_family == AF_INET6) + if (ifra->ifra_addr.sin6_len == 0) + { + bcopy(&(i6a->i6a_addr),&(ifra->ifra_addr), + sizeof(struct sockaddr_in6)); + hostIsNew = 0; + } + else if (IN6_ARE_ADDR_EQUAL(&ifra->ifra_addr.sin6_addr, &i6a->i6a_addr.sin6_addr)) + hostIsNew = 0; + + if (ifra->ifra_mask.sin6_len) + { + in6_ifscrub(ifp,i6a); + bcopy(&(ifra->ifra_mask),&(i6a->i6a_sockmask), + sizeof(struct sockaddr_in6)); + maskIsNew = 1; + } + + if ((ifp->if_flags & IFF_POINTOPOINT) && + (ifra->ifra_dstaddr.sin6_family == AF_INET6)) + { + in6_ifscrub(ifp,i6a); + bcopy(&(ifra->ifra_dstaddr),&(i6a->i6a_dstaddr), + sizeof(struct sockaddr_in6)); + maskIsNew = 1; /* We lie, simply so that in6_ifinit() will be + called to initialize the peer's address. */ + } + if (ifra->ifra_addr.sin6_family == AF_INET6 && (hostIsNew || maskIsNew)) + error = in6_ifinit(ifp,i6a,&ifra->ifra_addr,0,!internal); + /* else i6a->i6a_addrflags &= ~I6AF_NOTSURE; */ + + if (error == EEXIST) /* XXX, if route exists, we should be ok */ + error = 0; + + if (hostIsNew && !ifnetIsNew /* && (!error || error == EEXIST) */) + { + if (i6a->i6a_i6ifp) + i6a->i6a_i6ifp->i6ifp_numaddrs++; + else + panic("in6_control: missing i6ifp"); + } + return error; + + case SIOCDIFADDR_INET6: + in6_ifscrub(ifp, i6a); + /* + * If last address on this interface, delete IPv6 interface record. + */ + del_in6_ifnet(ifp); + +#ifdef __FreeBSD__ + TAILQ_REMOVE(&ifp->if_addrhead, (struct ifaddr *)i6a, ifa_link); +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + TAILQ_REMOVE(&ifp->if_addrlist, (struct ifaddr *)i6a, ifa_list); +#else /* __NetBSD__ || __OpenBSD__ */ + if ((ifa = ifp->if_addrlist) == (struct ifaddr *)i6a) + ifp->if_addrlist = ifa->ifa_next; + else + { + while (ifa->ifa_next && + (ifa->ifa_next != (struct ifaddr *)i6a)) + ifa=ifa->ifa_next; + if (ifa->ifa_next) + ifa->ifa_next = i6a->i6a_ifa.ifa_next; + else + DPRINTF(IDL_ERROR, ("Couldn't unlink in6_ifaddr from ifp!\n")); + } +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + oi6a = i6a; + if (oi6a == (i6a = in6_ifaddr)) + in6_ifaddr = i6a->i6a_next; + else + { + while (i6a->i6a_next && (i6a->i6a_next != oi6a)) + i6a = i6a->i6a_next; + if (i6a->i6a_next) + i6a->i6a_next = oi6a->i6a_next; + else + DPRINTF(IDL_ERROR, ("Didn't unlink in6_ifaddr from list.\n")); + } + IFAFREE((&oi6a->i6a_ifa)); /* For the benefit of routes pointing + to this ifa. */ + break; + + default: + DPRINTF(IDL_ERROR, + ("in6_control(): Default case not implemented.\n")); + return EOPNOTSUPP; + } + + return 0; +} + +/*---------------------------------------------------------------------- + * in6_ifscrub: + * Delete any existing route for an IPv6 interface. + ----------------------------------------------------------------------*/ + +int +in6_ifscrub(ifp,i6a) + register struct ifnet *ifp; + register struct in6_ifaddr *i6a; +{ + if (!(i6a->i6a_flags & IFA_ROUTE)) + return 1; + + if (ifp->if_flags & (IFF_LOOPBACK|IFF_POINTOPOINT)) + rtinit(&(i6a->i6a_ifa), (int)RTM_DELETE, RTF_HOST); + else + rtinit(&(i6a->i6a_ifa), (int)RTM_DELETE, 0); + i6a->i6a_flags &= ~IFA_ROUTE; + + return 0; +} + +/*---------------------------------------------------------------------- + * Initialize an IPv6 address for an interface. + * + * When I get around to doing duplicate address detection, this is probably + * the place to do it. + ----------------------------------------------------------------------*/ + +int +in6_ifinit(ifp, i6a, sin6, scrub, useDAD) + register struct ifnet *ifp; + register struct in6_ifaddr *i6a; + struct sockaddr_in6 *sin6; + int scrub; + int useDAD; +{ + int s, error, flags = RTF_UP; + struct sockaddr_in6 oldaddr; + + DPRINTF(IDL_EVENT,("Before splimp in in6_ifinit()\n")); + s = splimp(); + + bcopy(&(i6a->i6a_addr),&oldaddr,sizeof(struct sockaddr_in6)); + bcopy(sin6,&(i6a->i6a_addr),sizeof(struct sockaddr_in6)); + + /* + * Give the interface a chance to initialize + * if this is its first address, + * and to validate the address if necessary. + */ + + if (ifp->if_ioctl && (error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, + (caddr_t)i6a))) + { + bcopy(&oldaddr,&(i6a->i6a_addr),sizeof(struct sockaddr_in6)); + splx(s); + return error; + } + + /* + * IPv4 in 4.4BSD sets the RTF_CLONING flag here if it's an Ethernet. + * I delay this until later. + */ + + splx(s); + DPRINTF(IDL_EVENT,("After splx() in in6_ifinit().\n")); + + sin6->sin6_port = 0; + + if (scrub) + { + i6a->i6a_ifa.ifa_addr = (struct sockaddr *)&oldaddr; + in6_ifscrub(ifp, i6a); + i6a->i6a_ifa.ifa_addr = (struct sockaddr *)&i6a->i6a_addr; + } + + /* + * Adjust the sin6_len such that it only counts mask bytes with + * 1's in them. + */ + + { + register char *cpbase = (char *)&(i6a->i6a_sockmask.sin6_addr); + register char *cp = cpbase + sizeof(struct in6_addr); + + i6a->i6a_sockmask.sin6_len = 0; + while (--cp >=cpbase) + if (*cp) + { + i6a->i6a_sockmask.sin6_len = 1 + cp - (char *)&(i6a->i6a_sockmask); + break; + } + } + + /* + * Add route. Also, set some properties of the interface address here. + * (Properties include permanance, lifetime, etc.) + */ + + i6a->i6a_ifa.ifa_metric = ifp->if_metric; + i6a->i6a_ifa.ifa_rtrequest = ipv6_rtrequest; /* Want this to be true + for ALL IPv6 ifaddrs. */ + if (ifp->if_flags & IFF_LOOPBACK) + { + useDAD = 0; + i6a->i6a_ifa.ifa_dstaddr = i6a->i6a_ifa.ifa_addr; + flags |= RTF_HOST; + + /* Loopback is definitely a permanent address. */ + if (IN6_IS_ADDR_LOOPBACK(&i6a->i6a_addr.sin6_addr)) + i6a->i6a_addrflags |= I6AF_PERMANENT; + } + else if (ifp->if_flags & IFF_POINTOPOINT) + { + useDAD = 0; /* ??!!?? */ + if (i6a->i6a_dstaddr.sin6_family != AF_INET6) + return 0; + + flags |= RTF_HOST; + } + else + { + /* + * No b-cast in IPv6, therefore the ifa_broadaddr (concidentally the + * dest address filled in above...) should be set to NULL! + */ + i6a->i6a_ifa.ifa_broadaddr = NULL; + + if (IN6_IS_ADDR_LINKLOCAL(&i6a->i6a_addr.sin6_addr)) + { + flags |= RTF_HOST; + i6a->i6a_ifa.ifa_dstaddr = i6a->i6a_ifa.ifa_addr; + + /* + * Possibly do other stuff specific to link-local addresses, hence + * keeping this separate from IFF_LOOPBACK case above. I may move + * the link-local check to || with IFF_LOOPBACK. + * + * Other stuff includes setting i6a_preflen so when addrconf + * needs to know what part of the link-local is used for uniqueness, + * it doesn't have to gyrate. + */ + switch(i6a->i6a_ifp->if_type) + { + case IFT_ETHER: + i6a->i6a_preflen = 64; + break; + default: + DPRINTF(IDL_ERROR,("Can't set i6a_preflen for type %d.\n",\ + i6a->i6a_ifp->if_type)); + break; + } + + i6a->i6a_addrflags |= (I6AF_LINKLOC | I6AF_PERMANENT); + } + else + { + if (!(i6a->i6a_sockmask.sin6_len == sizeof(struct sockaddr_in6) && + IN6_ARE_ADDR_EQUAL(&i6a->i6a_sockmask.sin6_addr, &in6_allones.sin6_addr))) + flags |= RTF_CLONING; /* IMHO, ALL network routes + have the cloning bit set for next-hop + resolution if they aren't loopback or + pt. to pt. */ + i6a->i6a_addrflags |= I6AF_PREFIX; /* I'm a 'prefix list entry'. */ + } + } + + if ((error = rtinit(&(i6a->i6a_ifa), RTM_ADD,flags)) == 0) + { + i6a->i6a_flags |= IFA_ROUTE; + } + + /* + * If the interface supports multicast, join the appropriate + * multicast groups (all {nodes, routers}) on that interface. + * + * Also join the solicited nodes discovery multicast group for that + * destination. + */ + if (ifp->if_flags & IFF_MULTICAST) + { + struct in6_addr addr; + struct in6_multi *rc; + + /* NOTE2: Set default multicast interface here. + Set up cloning route for ff00::0/8 */ + if (ifp->if_type != IFT_LOOP && mcastdefault == NULL) + setmcastdef(ifp); + + /* All-nodes. */ + SET_IN6_ALLNODES(addr); + SET_IN6_MCASTSCOPE(addr,IN6_INTRA_LINK); + rc = in6_addmulti(&addr, ifp); + + /* All-routers, if forwarding */ + if (ipv6forwarding) { + SET_IN6_ALLROUTERS(addr); + SET_IN6_MCASTSCOPE(addr, IN6_INTRA_LINK); + rc = in6_addmulti(&addr, ifp); + }; + + /* Solicited-nodes. */ + addr.in6a_words[0] = htonl(0xff020000); + addr.in6a_words[1] = 0; + addr.in6a_words[2] = htonl(1); + addr.in6a_words[3] = i6a->i6a_addr.sin6_addr.in6a_words[3] | htonl(0xff000000); + + DDO(IDL_EVENT, dump_in6_addr(&addr)); + + rc=in6_addmulti(&addr, ifp); + } + + if (useDAD /*&& error != 0*/) + addrconf_dad(i6a); + else + i6a->i6a_addrflags &= ~I6AF_NOTSURE; + + return error; +} + +/*---------------------------------------------------------------------- + * Add IPv6 multicast address. IPv6 multicast addresses are handled + * pretty much like IP multicast addresses for now. + * + * Multicast addresses hang off in6_ifaddr's. Eventually, they should hang + * off the link-local multicast address, this way, there are no ambiguities. + ----------------------------------------------------------------------*/ + +struct in6_multi *in6_addmulti(addr,ifp) + register struct in6_addr *addr; + struct ifnet *ifp; + +{ + register struct in6_multi *in6m; + struct inet6_ifreq ifr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&ifr.ifr_addr; + struct in6_ifnet *i6ifp; + int s = splnet(); + + /* + * See if address is already in list.. + */ + + IN6_LOOKUP_MULTI(addr,ifp,in6m); + + if (in6m != NULL) + { + /* Increment the reference count. */ + in6m->in6m_refcount++; + } + else + { +#if __FreeBSD__ + struct ifmultiaddr *ifma; +#endif /* __FreeBSD__ */ + /* + * Otherwise, allocate a new m-cast record and link it to + * the interface's multicast list. + */ + + if ((in6m=malloc(sizeof(struct in6_multi),M_IPMADDR,M_NOWAIT)) == NULL) + { + splx(s); + return NULL; + } + bzero(in6m,sizeof(struct in6_multi)); + in6m->in6m_addr = *addr; + in6m->in6m_refcount = 1; + in6m->in6m_ifp = ifp; + + for(i6ifp = in6_ifnet; i6ifp != NULL && i6ifp->i6ifp_ifp != ifp; + i6ifp = i6ifp->i6ifp_next) + ; + if (i6ifp == NULL) + { + free(in6m,M_IPMADDR); + splx(s); + return NULL; + } + in6m->in6m_i6ifp = i6ifp; + in6m->in6m_next = i6ifp->i6ifp_multiaddrs; + i6ifp->i6ifp_multiaddrs = in6m; + + /* + * Ask the network driver to update its multicast reception + * filter appropriately for the new address. + */ + sin6->sin6_family=AF_INET6; + sin6->sin6_len=sizeof(struct sockaddr_in6); + sin6->sin6_addr = *addr; + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + + +#if __FreeBSD__ + if (if_addmulti(ifp, (struct sockaddr *) sin6, &ifma)) +#else /* __FreeBSD */ + if (ifp->if_ioctl == NULL || + (*ifp->if_ioctl)(ifp, SIOCADDMULTI,(caddr_t)&ifr) != 0) +#endif /* __FreeBSD__ */ + { + i6ifp->i6ifp_multiaddrs = in6m->in6m_next; + free(in6m,M_IPMADDR); + splx(s); + return NULL; + } +#ifdef __FreeBSD__ + ifma->ifma_protospec = in6m; +#endif /* __FreeBSD__ */ + + /* Tell IGMP that we've joined a new group. */ + /*ipv6_igmp_joingroup(in6m);*/ + } + splx(s); + return in6m; +} + +/*---------------------------------------------------------------------- + * Delete IPv6 multicast address. + ----------------------------------------------------------------------*/ + +void +in6_delmulti(in6m) + register struct in6_multi *in6m; +{ + register struct in6_multi **p; + struct inet6_ifreq ifr; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&(ifr.ifr_addr); + int s = splnet(); + + if (--in6m->in6m_refcount == 0) + { + /* Tell IGMP that I'm bailing this group. */ + /* ipv6_igmp_leavegroup(in6m);*/ + + /* Unlink from list. */ + for (p = &(in6m->in6m_i6ifp->i6ifp_multiaddrs); + *p != in6m; + p = &(*p)->in6m_next) + ; + *p = (*p)->in6m_next; + + /* + * Notify the network driver to update its multicast reception + * filter. + */ + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = 0; + sin6->sin6_flowinfo = 0; + sin6->sin6_addr = in6m->in6m_addr; + (*(in6m->in6m_ifp->if_ioctl))(in6m->in6m_ifp, SIOCDELMULTI, + (caddr_t)&ifr); + + free(in6m,M_IPMADDR); + } + splx(s); +} diff --git a/sys/netinet6/in6_cksum.c b/sys/netinet6/in6_cksum.c new file mode 100644 index 00000000000..0288b74daaf --- /dev/null +++ b/sys/netinet6/in6_cksum.c @@ -0,0 +1,232 @@ +/* +%%% 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>. + +*/ +/* + * Copyright (c) 1988 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * (Originally from) @(#)in_cksum.c 7.3 (Berkeley) 6/28/90 + */ + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/mbuf.h> +#include <sys/systm.h> + +#include <netinet/in.h> +#include <netinet6/in6.h> +#include <netinet6/ipv6.h> + +#define ADDCARRY(x) (x > 65535 ? x -= 65535 : x) +#define REDUCE \ +{l_util.l = sum; sum = l_util.s[0] + l_util.s[1]; ADDCARRY(sum);} + +/*---------------------------------------------------------------------- + * in6_cksum() takes an mbuf chain (with a IPv6 header at the beginning), and + * computes the checksum. This also assumes that the IPv6 header is pulled + * up into the first mbuf in the chain, and that said header has valid + * source and destination fields. I do the pseudo-header first, then + * I do the rest. Unlike v4, I treat the pseudo-header separately, so + * transports don't have to do funky gymnastics with zeroing out headers. + ----------------------------------------------------------------------*/ + +int +in6_cksum(m, proto, len, start) + struct mbuf *m; /* Chain, complete with IPv6 header. */ + int proto; /* Protocol number of HLP that needs sum. */ + uint len; /* Length of stuff to checksum. Note that len + and proto are what get computed in the pseudo- + header. Len is a uint because of potential + jumbograms. */ + uint start; /* How far (in bytes) into chain's data to + start remainder of computation. (e.g. Where + the TCP segment begins. */ +{ + u_short *w; + int sum = 0; + int mlen = 0; + int byte_swapped = 0; + struct ipv6 *header; + short done = 0; + union + { + uint8_t c[2]; + uint16_t s; + } s_util; + union + { + uint16_t s[2]; + uint32_t l; + } l_util; + + /* + * Get pseudo-header summed up. Assume ipv6 is first. + * I do pseudo-header here because it'll save bletch in both TCP and + * UDP. + */ + + header = mtod(m,struct ipv6 *); + + w = (u_short *)&(header->ipv6_src); + /* Source address */ + sum+=w[0]; sum+=w[1]; sum+=w[2]; sum+=w[3]; + sum+=w[4]; sum+=w[5]; sum+=w[6]; sum+=w[7]; + + /* Destination address */ + sum+=w[8]; sum+=w[9]; sum+=w[10]; sum+=w[11]; + sum+=w[12]; sum+=w[13]; sum+=w[14]; sum+=w[15]; + + /* Next header value for transport layer. */ + sum += htons(proto); + + /* Length of transport header and transport data. */ + l_util.l = htonl(len); + sum+=l_util.s[0]; + sum+=l_util.s[1]; + + /* Find starting point for rest of data.. */ + + while (!done) + if (m->m_len >start) + { + done = 1; + mlen = m->m_len - start; + } + else + { + start -= m->m_len; + m = m->m_next; + } + + for (;m && len; m = m->m_next) { + if (m->m_len == 0) + continue; + + w = (u_short *)(m->m_data + (done ? start : 0)); + + if (mlen == -1) + { + /* + * The first byte of this mbuf is the continuation + * of a word spanning between this mbuf and the + * last mbuf. + * + * s_util.c[0] is already saved when scanning previous + * mbuf. + */ + s_util.c[1] = *(char *)w; + sum += s_util.s; + w = (u_short *)((char *)w + 1); + mlen = m->m_len - 1; + len--; + } + else + if (!done) + mlen = m->m_len; + else done=0; + + if (len < mlen) + mlen = len; + len -= mlen; + + /* + * Force to even boundary. + */ +#ifdef __alpha__ + if ((1 & (long) w) && (mlen > 0)) { +#else + if ((1 & (int) w) && (mlen > 0)) { +#endif + REDUCE; + sum <<= 8; + s_util.c[0] = *(u_char *)w; + w = (u_short *)((char *)w + 1); + mlen--; + byte_swapped = 1; + } + /* + * Unroll the loop to make overhead from + * branches small. + */ + while ((mlen -= 32) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + sum += w[4]; sum += w[5]; sum += w[6]; sum += w[7]; + sum += w[8]; sum += w[9]; sum += w[10]; sum += w[11]; + sum += w[12]; sum += w[13]; sum += w[14]; sum += w[15]; + w += 16; + } + mlen += 32; + while ((mlen -= 8) >= 0) { + sum += w[0]; sum += w[1]; sum += w[2]; sum += w[3]; + w += 4; + } + mlen += 8; + if (mlen == 0 && byte_swapped == 0) + continue; + REDUCE; + while ((mlen -= 2) >= 0) { + sum += *w++; + } + if (byte_swapped) { + REDUCE; + sum <<= 8; + byte_swapped = 0; + if (mlen == -1) { + s_util.c[1] = *(char *)w; + sum += s_util.s; + mlen = 0; + } else + mlen = -1; + } else if (mlen == -1) + s_util.c[0] = *(char *)w; + } + if (len) + printf("in6_cksum: out of data\n"); + if (mlen == -1) { + /* + * The last mbuf has odd # of bytes. Follow the + * standard (the odd byte may be shifted left by 8 bits + * or not as determined by endian-ness of the machine) + */ + s_util.c[1] = 0; + sum += s_util.s; + } + REDUCE; + return(~sum & 0xffff); +} diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c new file mode 100644 index 00000000000..ec2a27b665e --- /dev/null +++ b/sys/netinet6/in6_pcb.c @@ -0,0 +1,1426 @@ +/* +%%% 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>. + +*/ +/* + * Copyright (c) 1982, 1986, 1990, 1993, 1995 + * Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#if __FreeBSD__ +#include <sys/queue.h> +#endif /* __FreeBSD__ */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +/*#include <sys/ioctl.h>*/ +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/proc.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> + +#include <netinet6/in6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_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> +#endif /* IPSEC */ + +#include <sys/debug.h> + +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) +#include <machine/pcpu.h> +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + +/* + * External globals + */ + +#if __OpenBSD__ +#include <dev/rndvar.h> +#endif /* __OpenBSD__ */ + +#if __FreeBSD__ +/* + * These configure the range of local port addresses assigned to + * "unspecified" outgoing connections/packets/whatever. + */ +static int ipport_lowfirstauto = IPPORT_RESERVED - 1; /* 1023 */ +static int ipport_lowlastauto = IPPORT_RESERVEDSTART; /* 600 */ +static int ipport_firstauto = IPPORT_RESERVED; /* 1024 */ +static int ipport_lastauto = IPPORT_USERRESERVED; /* 5000 */ +static int ipport_hifirstauto = IPPORT_HIFIRSTAUTO; /* 49152 */ +static int ipport_hilastauto = IPPORT_HILASTAUTO; /* 65535 */ +extern void in_rtchange(struct inpcb *, int); +#endif /* __FreeBSD__ */ + +extern struct in6_ifaddr *in6_ifaddr; +#if __FreeBSD__ +int in6_pcbladdr(register struct inpcb *, struct sockaddr *, struct sockaddr **); +extern TAILQ_HEAD(in_ifaddrhead, in_ifaddr) in_ifaddrhead; +#else /* __FreeBSD__ */ +extern struct in_ifaddr *in_ifaddr; +#endif /* __FreeBSD__ */ +/* + * Globals + */ + +struct in6_addr zeroin6_addr; + +#if __OpenBSD__ +extern int ipport_firstauto; +extern int ipport_lastauto; +extern int ipport_hifirstauto; +extern int ipport_hilastauto; +#endif /* __OpenBSD__ */ + +#if __OpenBSD__ +int in_baddynamic __P((u_int16_t, u_int16_t)); +#endif /* __OpenBSD__ */ + +/* + * Keep separate inet6ctlerrmap, because I may remap some of these. + * I also put it here, because, quite frankly, it belongs here, not in + * ip{v6,}_input(). + */ +u_char inet6ctlerrmap[PRC_NCMDS] = { + 0, 0, 0, 0, + 0, EMSGSIZE, EHOSTDOWN, EHOSTUNREACH, + EHOSTUNREACH, EHOSTUNREACH, ECONNREFUSED, ECONNREFUSED, + EMSGSIZE, EHOSTUNREACH, 0, 0, + 0, 0, 0, 0, + ENOPROTOOPT +}; + +/*---------------------------------------------------------------------- + * Bind an address (or at least a port) to an PF_INET6 socket. + ----------------------------------------------------------------------*/ +int +in6_pcbbind(inp, nam) + register struct inpcb *inp; +#if __FreeBSD__ + struct sockaddr *nam; +#else /* __FreeBSD__ */ + struct mbuf *nam; +#endif /* __FreeBSD__ */ +{ + register struct socket *so = inp->inp_socket; + +#if __NetBSD__ || __OpenBSD__ + register struct inpcbtable *head = inp->inp_table; +#else /* __OpenBSD__ */ +#ifndef __FreeBSD__ + register struct inpcb *head = inp->inp_head; +#else /* __FreeBSD__ */ + struct inpcbinfo *pcbinfo = inp->inp_pcbinfo; +#endif /* __FreeBSD__ */ +#endif /* __NetBSD__ || __OpenBSD__ */ + register struct sockaddr_in6 *sin6; +#if !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) + struct proc *p = curproc; /* XXX */ +#else /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + struct proc *p = PCPU(curproc); /* XXX */ +#endif /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + u_short lport = 0; + int wild = INPLOOKUP_IPV6, reuseport = (so->so_options & SO_REUSEPORT); + int error; + + /* + * REMINDER: Once up to speed, flow label processing should go here, + * too. (Same with in6_pcbconnect.) + */ + +#if __FreeBSD__ + if (in6_ifaddr == 0 || in_ifaddrhead.tqh_first == 0) + return EADDRNOTAVAIL; +#else /* __FreeBSD__ */ + if (in6_ifaddr == 0 || in_ifaddr == 0) + return EADDRNOTAVAIL; +#endif/* __FreeBSD__ */ + if (inp->inp_lport != 0 || !IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) + return EINVAL; /* If already bound, EINVAL! */ + + if ((so->so_options & (SO_REUSEADDR | SO_REUSEPORT)) == 0 && + ((so->so_proto->pr_flags & PR_CONNREQUIRED) == 0 || + (so->so_options & SO_ACCEPTCONN) == 0)) + wild |= INPLOOKUP_WILDCARD; + + /* + * If I did get a sockaddr passed in... + */ + if (nam) + { +#if __FreeBSD__ + sin6 = (struct sockaddr_in6 *)nam; + if (nam->sa_len != sizeof (*sin6)) + return EINVAL; +#else /* __FreeBSD__ */ + sin6 = mtod(nam, struct sockaddr_in6 *); + if (nam->m_len != sizeof (*sin6)) + return EINVAL; +#endif /* __FreeBSD__ */ + + /* + * Unlike v4, I have no qualms about EAFNOSUPPORT if the + * wretched family is not filled in! + */ + if (sin6->sin6_family != AF_INET6) + return EAFNOSUPPORT; + lport = sin6->sin6_port; + + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + { + /* + * Treat SO_REUSEADDR as SO_REUSEPORT for multicast; + * allow complete duplication of binding if + * SO_REUSEPORT is set, or if SO_REUSEADDR is set + * and a multicast address is bound on both + * new and duplicated sockets. + */ + if (so->so_options & SO_REUSEADDR) + reuseport = SO_REUSEADDR | SO_REUSEPORT; + } + else if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + { + struct sockaddr_in sin; + + sin.sin_port = 0; + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = sin6->sin6_addr.in6a_words[3]; + bzero(&sin.sin_zero,8); + + sin6->sin6_port = 0; /* Yechhhh, because of upcoming call to + ifa_ifwithaddr(), which does bcmp's + over the PORTS as well. (What about flow?) + */ + sin6->sin6_flowinfo = 0; + if (ifa_ifwithaddr((struct sockaddr *)sin6) == 0) + if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr) || + ifa_ifwithaddr((struct sockaddr *)&sin) == 0) + return EADDRNOTAVAIL; + } + if (lport) + { + struct inpcb *t; + struct in_addr fa,la; + + /* Question: Do we wish to continue the Berkeley tradition of + ports < IPPORT_RESERVED be only for root? + + Answer: For now yes, but IMHO, it should be REMOVED! + + OUCH: One other thing, is there no better way of finding + a process for a socket instead of using curproc? (Marked + with BSD's {in,}famous XXX ? */ + if (ntohs(lport) < IPPORT_RESERVED && + (error = suser(p->p_ucred, &p->p_acflag))) + return error; + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + { + fa.s_addr = 0; + la.s_addr = sin6->sin6_addr.in6a_words[3]; + wild &= ~INPLOOKUP_IPV6; + +#if __FreeBSD__ + t = in_pcblookup_local(inp->inp_pcbinfo, + (struct in_addr *)&la, lport, wild); +#else /* __FreeBSD__ */ +#if __NetBSD__ + t = in_pcblookup_port(inp->inp_table, (struct in_addr *)&la, + lport, wild); +#else /* __NetBSD__ */ + t = in_pcblookup(head, (struct in_addr *)&fa, 0, + (struct in_addr *)&la, lport, wild); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + + } + else + { +#if __FreeBSD__ + t = in_pcblookup_local(inp->inp_pcbinfo, + (struct in_addr *)&sin6->sin6_addr, lport, wild); +#else /* __FreeBSD__ */ +#if __NetBSD__ + t = in_pcblookup_port(inp->inp_table, + (struct in_addr *)&in6addr_any, lport, wild); +#else /* __NetBSD__ */ + t = in_pcblookup(head, (struct in_addr *)&zeroin6_addr, 0, + (struct in_addr *)&sin6->sin6_addr, lport, wild); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + } + + if (t && (reuseport & t->inp_socket->so_options) == 0) + return EADDRINUSE; + } + inp->inp_laddr6 = sin6->sin6_addr; + + if (!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + inp->inp_ipv6.ipv6_versfl = htonl(0x60000000) | + (sin6->sin6_flowinfo & htonl(0x0fffffff)); + + /* + * Unroll first 2 compares of {UNSPEC,V4MAPPED}. + * Mark PF_INET6 socket as undecided (bound to port-only) or + * mapped (INET6 socket talking IPv4) here. I may need to move + * this code out of this if (nam) clause, and put it just before + * function return. + * + * Then again, the only time this function is called with NULL nam + * might be during a *_pcbconnect(), which then sets the local address + * ANYWAY. + */ + if (inp->inp_laddr6.in6a_words[0] == 0 && + inp->inp_laddr6.in6a_words[1] == 0) + { + if (inp->inp_laddr6.in6a_words[2] == ntohl(0xffff)) + inp->inp_flags |= INP_IPV6_MAPPED; + if (inp->inp_laddr6.in6a_words[2] == 0 && + inp->inp_laddr6.in6a_words[3] == 0) + inp->inp_flags |= INP_IPV6_UNDEC; + } + } + + if (lport == 0) { + /* This code block was derived from OpenBSD */ + uint16_t first, last, old = 0; + int count; + int loopcount = 0; + struct in_addr fa, la; + u_int16_t *lastport; +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + u_int16_t last_port = head->inp_lport; + + lastport = &last_port; +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ +#if __NetBSD__ || __OpenBSD__ + lastport = &inp->inp_table->inpt_lastport; +#endif /* __NetBSD__ || __OpenBSD__ */ + + if (inp->inp_flags & INP_IPV6_MAPPED) { + la.s_addr = inp->inp_laddr6.in6a_words[3]; + fa.s_addr = 0; + wild &= ~INPLOOKUP_IPV6; + }; + +#if __OpenBSD__ || __FreeBSD__ +#if __FreeBSD__ + inp->inp_flags |= INP_ANONPORT; +#endif /* __FreeBSD__ */ + + if (inp->inp_flags & INP_HIGHPORT) { + first = ipport_hifirstauto; /* sysctl */ + last = ipport_hilastauto; +#if __FreeBSD__ + lastport = &pcbinfo->lasthi; +#endif /* __FreeBSD__ */ + } else if (inp->inp_flags & INP_LOWPORT) { + if ((error = suser(p->p_ucred, &p->p_acflag))) + return (EACCES); +#if __FreeBSD__ + first = ipport_lowfirstauto; + last = ipport_lowlastauto; + lastport = &pcbinfo->lastlow; +#else /* __FreeBSD__ */ + first = IPPORT_RESERVED-1; /* 1023 */ + last = 600; /* not IPPORT_RESERVED/2 */ +#endif /* __FreeBSD__ */ + } else { + first = ipport_firstauto; /* sysctl */ + last = ipport_lastauto; +#if __FreeBSD__ + lastport = &pcbinfo->lastport; +#endif /* __FreeBSD__ */ + } +#else /* __OpenBSD__ */ +#if __bsdi__ + first = IPPORT_DYNAMIC; + last = IPPORT_DYNAMIC_LAST; +#else /* __bsdi__ */ + first = IPPORT_RESERVED; + last = IPPORT_USERRESERVED; +#endif /* __bsdi__ */ +#endif /* __OpenBSD__ */ + + /* + * Simple check to ensure all ports are not used up causing + * a deadlock here. + * + * We split the two cases (up and down) so that the direction + * is not being tested on each round of the loop. + */ + +portloop: + if (first > last) { + /* + * counting down + */ + if (loopcount == 0) { /* only do this once. */ + old = first; +#if __OpenBSD__ + first -= (arc4random() % (first - last)); +#else /* __OpenBSD__ */ + first -= (random() % (first - last)); +#endif /* __OpenBSD__ */ + } + count = first - last; + *lastport = first; /* restart each time */ + + do { + if (count-- <= 0) { /* completely used? */ + if (loopcount == 0) { + last = old; + loopcount++; + goto portloop; + } + return (EADDRNOTAVAIL); + } + --*lastport; + if (*lastport > first || *lastport < last) + *lastport = first; + lport = htons(*lastport); +#if __OpenBSD__ + } while (in_baddynamic(*lastport, so->so_proto->pr_protocol) || +#else /* __OpenBSD__ */ + } while ( +#endif /* __OpenBSD__ */ + ((wild & INPLOOKUP_IPV6) ? +#ifdef __FreeBSD__ + in_pcblookup_local(pcbinfo, + (struct in_addr *)&inp->inp_laddr6, lport, wild) : + in_pcblookup_local(pcbinfo, + (struct in_addr *)&la, lport, wild))); +#else /* __FreeBSD__ */ +#if __NetBSD__ + in_pcblookup_port(head, (struct in_addr *)&inp->inp_laddr6, + lport, wild) : + in_pcblookup_port(head, (struct in_addr *)&la, lport, wild))); +#else /* __NetBSD__ */ + in_pcblookup(head, (struct in_addr *)&zeroin6_addr, 0, + (struct in_addr *)&inp->inp_laddr6, lport, wild) : + in_pcblookup(head, (struct in_addr *)&fa, 0, + (struct in_addr *)&la, lport, wild))); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + } else { + /* + * counting up + */ + if (loopcount == 0) { /* only do this once. */ + old = first; +#if __OpenBSD__ + first += (arc4random() % (last - first)); +#else /* __OpenBSD__ */ + first += (random() % (last - first)); +#endif /* __OpenBSD__ */ + } + count = last - first; + *lastport = first; /* restart each time */ + + do { + if (count-- <= 0) { /* completely used? */ + if (loopcount == 0) { + first = old; + loopcount++; + goto portloop; + } + return (EADDRNOTAVAIL); + } + ++*lastport; + if (*lastport < first || *lastport > last) + *lastport = first; + lport = htons(*lastport); +#if __OpenBSD__ + } while (in_baddynamic(*lastport, so->so_proto->pr_protocol) || +#else /* __OpenBSD__ */ + } while ( +#endif /* __OpenBSD__ */ + ((wild & INPLOOKUP_IPV6) ? +#ifdef __FreeBSD__ + in_pcblookup_local(pcbinfo, + (struct in_addr *)&inp->inp_laddr6, lport, wild) : + in_pcblookup_local(pcbinfo, + (struct in_addr *)&la, lport, wild))); +#else /* __FreeBSD__ */ +#if __NetBSD__ + in_pcblookup_port(head, (struct in_addr *)&inp->inp_laddr6, + lport, wild) : + in_pcblookup_port(head, (struct in_addr *)&la, lport, wild))); +#else /* __NetBSD__ */ + in_pcblookup(head, (struct in_addr *)&zeroin6_addr, 0, + (struct in_addr *)&inp->inp_laddr6, lport, wild) : + in_pcblookup(head, (struct in_addr *)&fa, 0, + (struct in_addr *)&la, lport, wild))); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + } + } + + inp->inp_lport = lport; + +#if __FreeBSD__ +#if 0 + inp->inp_laddr.s_addr = inp->inp_laddr6.in6a_words[3] + ^ inp->inp_laddr6.in6a_words[1]; + /* + * If this is FreeBSD then it requires the inpcb structure to be + * inserted into various hash-tables. Right now this is copies + * directly from FreeBSD's source, but this will have to change for + * v6 addresses soon I think. We're still not sure if word2 XOR word4 + * is a good subject for the hash function. + */ +#endif /* 0 */ + if (in_pcbinshash(inp) != 0) { + bzero(&inp->inp_laddru, sizeof(inp->inp_laddru)); + inp->inp_lport = 0; + return (EAGAIN); + } +#endif /* __FreeBSD__ */ +#if __NetBSD__ + in_pcbstate(inp, INP_BOUND); +#endif /* __NetBSD__ */ + + /* XXX hash */ + return 0; +} + +/*---------------------------------------------------------------------- + * This is maximum suckage point value. This function is the first + * half or so of in6_pcbconnect. It exists because FreeBSD's code + * supports this function for T/TCP. If we want TCP to work with our + * code it must be supported. + ----------------------------------------------------------------------*/ +#if __FreeBSD__ +int +in6_pcbladdr(inp, nam, plocal_sin) + register struct inpcb *inp; + struct sockaddr *nam; + struct sockaddr **plocal_sin; +{ + + struct in6_ifaddr *i6a; + struct sockaddr_in *ifaddr = NULL; + + register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; + if (nam->sa_len != sizeof(struct sockaddr_in6)) + return (EINVAL); + + if (sin6->sin6_family != AF_INET6) + return (EAFNOSUPPORT); + if (sin6->sin6_port == 0) + return (EADDRNOTAVAIL); + + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + { + struct domain hackdomain, *save; + struct sockaddr_in *sin; + int rc; + + /* + * FreeBSD doesn't want an mbuf to pcbconnect. + */ + struct sockaddr_in hacksin; + + /* + * Hmmm, if this is a v4-mapped v6 address, re-call in_pcbconnect + * with a fake domain for now. Then re-adjust the socket, and + * return out of here. Since this is called at splnet(), I don't + * think temporarily altering the socket will matter. XXX + */ + + /* + * Can't have v6 talking to v4, now can we! + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6) && + !IN6_IS_ADDR_V4MAPPED(&inp->inp_laddr6)) + return EINVAL; + + bzero(&hackdomain,sizeof(hackdomain)); + save = inp->inp_socket->so_proto->pr_domain; + inp->inp_socket->so_proto->pr_domain = &hackdomain; + hackdomain.dom_family = PF_INET; + + sin = &hacksin; + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = sin6->sin6_port; + sin->sin_addr.s_addr = sin6->sin6_addr.in6a_words[3]; + + /* CHANGE */ + rc = in_pcbladdr(inp, (struct sockaddr *) sin, &ifaddr); + + inp->inp_socket->so_proto->pr_domain = save; + + if (rc == 0) + { + inp->inp_laddr6.in6a_words[2] = htonl(0xffff); + inp->inp_faddr6.in6a_words[2] = htonl(0xffff); + inp->inp_flags |= INP_IPV6_MAPPED; + inp->inp_flags &= ~INP_IPV6_UNDEC; + } + *plocal_sin = (struct sockaddr *)ifaddr; + + return rc; + } + + if (in6_ifaddr && IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + { + /* + * If the destination address is all 0's, + * use the first non-loopback (and if possibly, non-link-local, else + * use the LAST link-local, non-loopback) address as the destination. + */ +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) +#define sin6tosa(sin6) ((struct sockaddr *)(sin6)) +#define ifatoi6a(ifa) ((struct in6_ifaddr *)(ifa)) + struct in6_ifaddr *ti6a = NULL; + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + { + /* Find first (non-link-local if possible) address for + source usage. If multiple link-locals, use last one found. */ + if (IN6_IS_ADDR_LINKLOCAL(&I6A_SIN(i6a)->sin6_addr)) + ti6a=i6a; + else if (!IN6_IS_ADDR_LOOPBACK(&I6A_SIN(i6a)->sin6_addr)) + break; + } + if (i6a == NULL && ti6a != NULL) + i6a = ti6a; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) + { + register struct route6 *ro; + + i6a = NULL; + /* + * If route is known or can be allocated now, + * our src addr is taken from the rt_ifa, else punt. + */ + ro = &inp->inp_route6; + if (ro->ro_rt && + (ro->ro_dst.sin6_family != sin6->sin6_family || + rt_key(ro->ro_rt)->sa_family != sin6->sin6_family || + !IN6_ARE_ADDR_EQUAL(&ro->ro_dst.sin6_addr, &sin6->sin6_addr) || + inp->inp_socket->so_options & SO_DONTROUTE)) + { + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + } + if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/ + (ro->ro_rt == NULL || ro->ro_rt->rt_ifp == NULL)) + { + /* No route yet, try and acquire one. */ + ro->ro_dst.sin6_family = AF_INET6; /* Is ro blanked out? */ + ro->ro_dst.sin6_len = sizeof(struct sockaddr_in6); + ro->ro_dst.sin6_addr = sin6->sin6_addr; + /* + * Need to wipe out the flowlabel for ifa_ifwith* + * but don't need to for rtalloc. + */ + rtalloc((struct route *)ro); + } + + if (ro->ro_rt == NULL) + { + /* + * No route of any kind, so spray neighbor solicits out all + * interfaces, unless it's a multicast address. + */ + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + return ENETUNREACH; + ipv6_onlink_query((struct sockaddr_in6 *)&ro->ro_dst); + rtalloc((struct route *)ro); + } + if (ro->ro_rt == NULL) + { + /* + * ipv6_onlink_query() should've added a route. It probably + * failed. + */ + DPRINTF(IDL_GROSS_EVENT, ("v6_output: onlink_query didn't add route!\n")); + return ENETUNREACH; + } + + 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; + + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + switch (error = ipv6_verify_onlink(&ro->ro_dst)) + { + case -1: + return ENETUNREACH; + case 0: + break; + default: + return error; + } + rtalloc((struct route *)ro); + if (ro->ro_rt == NULL || ro->ro_rt->rt_ifa == NULL) + panic("Oops2, I'm forgetting something after verify_onlink()."); + } + + + /* + * If we found a route, use the address + * corresponding to the outgoing interface + * unless it is the loopback (in case a route + * to our address on another net goes to loopback). + * + * This code may be simplified if the above gyrations work. + */ + if (ro->ro_rt && ro->ro_rt->rt_ifp && + !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK)) + i6a = ifatoi6a(ro->ro_rt->rt_ifa); + if (i6a == NULL) + { + u_short fport = sin6->sin6_port; + + /* + * Source address selection when there is no route. + * + * If ND out all if's is to be used, this is the point to do it. + * (Similar to when the route lookup fails in ipv6_output.c, and + * the 'on-link' assumption kicks in.) + * + * For multicast, use i6a of mcastdef. + */ + + sin6->sin6_port = 0; + i6a = ifatoi6a(ifa_ifwithdstaddr(sin6tosa(sin6))); + if (i6a == NULL) + i6a = ifatoi6a(ifa_ifwithnet(sin6tosa(sin6))); + sin6->sin6_port = fport; + if (i6a == NULL) + { + struct in6_ifaddr *ti6a = NULL; + + 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_IS_ADDR_LINKLOCAL(&I6A_SIN(i6a)->sin6_addr)) + ti6a=i6a; + else if (!IN6_IS_ADDR_LOOPBACK(&I6A_SIN(i6a)->sin6_addr)) + break; + } + if (i6a == NULL && ti6a != NULL) + i6a = ti6a; + } + if (i6a == NULL) + return EADDRNOTAVAIL; + } + + /* + * If I'm "connecting" to a multicast address, gyrate properly to + * get a source address based upon the user-requested mcast interface. + */ + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) && inp->inp_moptions6 != NULL && + (inp->inp_flags & INP_IPV6_MCAST)) + { + struct ipv6_moptions *i6mo; + struct ifnet *ifp; + + i6mo = inp->inp_moptions6; + if (i6mo->i6mo_multicast_ifp != NULL) + { + ifp = i6mo->i6mo_multicast_ifp; + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp) /* Linkloc vs. global? */ + break; + if (i6a == NULL) + return EADDRNOTAVAIL; + } + } + + *plocal_sin = (struct sockaddr *)&i6a->i6a_addr; + } + + return (0); +} +#endif /* __FreeBSD__ */ + +/*---------------------------------------------------------------------- + * Connect from a socket to a specified address. + * Both address and port must be specified in argument sin6. + * Eventually, flow labels will have to be dealt with here, as well. + * + * If don't have a local address for this socket yet, + * then pick one. + * + * I believe this has to be called at splnet(). + ----------------------------------------------------------------------*/ + +int +in6_pcbconnect(inp, nam) + register struct inpcb *inp; +#if __FreeBSD__ + struct sockaddr *nam; +#else /* __FreeBSD__ */ + struct mbuf *nam; +#endif /* __FreeBSD__ */ +{ + struct in6_ifaddr *i6a; + struct sockaddr_in6 *ifaddr = NULL; + +#if __FreeBSD__ + register struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)nam; +#else /* __FreeBSD__ */ + register struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *); + if (nam->m_len != sizeof(struct sockaddr_in6)) + return (EINVAL); +#endif /* __FreeBSD__ */ + + if (sin6->sin6_len != sizeof(struct sockaddr_in6)) + return (EINVAL); + if (sin6->sin6_family != AF_INET6) + return (EAFNOSUPPORT); + if (sin6->sin6_port == 0) + return (EADDRNOTAVAIL); + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) + { + struct domain hackdomain, *save; + struct mbuf hackmbuf; + struct sockaddr_in *sin; + int rc; + +#if __FreeBSD__ + /* + * FreeBSD doesn't want an mbuf to pcbconnect. + */ + struct sockaddr_in hacksin; +#endif /* __FreeBSD__ */ + + /* + * Hmmm, if this is a v4-mapped v6 address, re-call in_pcbconnect + * with a fake domain for now. Then re-adjust the socket, and + * return out of here. Since this is called at splnet(), I don't + * think temporarily altering the socket will matter. XXX + */ + + /* + * Can't have v6 talking to v4, now can we! + */ + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6) && + !IN6_IS_ADDR_V4MAPPED(&inp->inp_laddr6)) + return EINVAL; + + bzero(&hackdomain,sizeof(hackdomain)); + bzero(&hackmbuf,sizeof(hackmbuf)); + save = inp->inp_socket->so_proto->pr_domain; + inp->inp_socket->so_proto->pr_domain = &hackdomain; + hackdomain.dom_family = PF_INET; +#ifndef __FreeBSD__ + hackmbuf.m_hdr = nam->m_hdr; +#endif /* __FreeBSD__ */ + hackmbuf.m_len = sizeof(*sin); + hackmbuf.m_data = hackmbuf.m_dat; + +#if __FreeBSD__ + sin = &hacksin; +#else /* __FreeBSD__ */ + sin = mtod(&hackmbuf,struct sockaddr_in *); +#endif /* __FreeBSD__ */ + sin->sin_len = sizeof(*sin); + sin->sin_family = AF_INET; + sin->sin_port = sin6->sin6_port; + sin->sin_addr.s_addr = sin6->sin6_addr.in6a_words[3]; + +#if __FreeBSD__ + { +#if !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) + struct proc *p = curproc; /* XXX */ +#else /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + struct proc *p = PCPU(curproc); /* XXX */ +#endif /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + rc = in_pcbconnect(inp, (struct sockaddr *) sin, p); + } +#else /* __FreeBSD__ */ + rc = in_pcbconnect(inp,&hackmbuf); +#endif /* __FreeBSD__ */ + + inp->inp_socket->so_proto->pr_domain = save; + + if (rc == 0) + { + inp->inp_laddr6.in6a_words[2] = htonl(0xffff); + inp->inp_faddr6.in6a_words[2] = htonl(0xffff); + inp->inp_flags |= INP_IPV6_MAPPED; + inp->inp_flags &= ~INP_IPV6_UNDEC; + } + + return rc; + } + + if (in6_ifaddr && IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) + { + /* + * If the destination address is all 0's, + * use the first non-loopback (and if possibly, non-link-local, else + * use the LAST link-local, non-loopback) address as the destination. + */ +#define satosin6(sa) ((struct sockaddr_in6 *)(sa)) +#define sin6tosa(sin6) ((struct sockaddr *)(sin6)) +#define ifatoi6a(ifa) ((struct in6_ifaddr *)(ifa)) + struct in6_ifaddr *ti6a = NULL; + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + { + /* Find first (non-link-local if possible) address for + source usage. If multiple link-locals, use last one found. */ + if (IN6_IS_ADDR_LINKLOCAL(&I6A_SIN(i6a)->sin6_addr)) + ti6a=i6a; + else if (!IN6_IS_ADDR_LOOPBACK(&I6A_SIN(i6a)->sin6_addr)) + break; + } + if (i6a == NULL && ti6a != NULL) + i6a = ti6a; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) + { + register struct route6 *ro; + + + i6a = NULL; + /* + * If route is known or can be allocated now, + * our src addr is taken from the rt_ifa, else punt. + */ + ro = &inp->inp_route6; + if (ro->ro_rt && + (ro->ro_dst.sin6_family != sin6->sin6_family || + rt_key(ro->ro_rt)->sa_family != sin6->sin6_family || + !IN6_ARE_ADDR_EQUAL(&ro->ro_dst.sin6_addr, &sin6->sin6_addr) || + inp->inp_socket->so_options & SO_DONTROUTE)) + { + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + } + if ((inp->inp_socket->so_options & SO_DONTROUTE) == 0 && /*XXX*/ + (ro->ro_rt == NULL || ro->ro_rt->rt_ifp == NULL)) + { + /* No route yet, try and acquire one. */ + ro->ro_dst.sin6_family = AF_INET6; /* Is ro blanked out? */ + ro->ro_dst.sin6_len = sizeof(struct sockaddr_in6); + ro->ro_dst.sin6_addr = sin6->sin6_addr; + /* + * Need to wipe out the flowlabel for ifa_ifwith* + * but don't need to for rtalloc. + */ + rtalloc((struct route *)ro); + } + + if (ro->ro_rt == NULL) + { + /* + * No route of any kind, so spray neighbor solicits out all + * interfaces, unless it's a multicast address. + */ + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) + return ENETUNREACH; + ipv6_onlink_query((struct sockaddr_in6 *)&ro->ro_dst); + rtalloc((struct route *)ro); + } + if (ro->ro_rt == NULL) + { + /* + * ipv6_onlink_query() should've added a route. It probably + * failed. + */ + DPRINTF(GROSSEVENT, ("v6_output: onlink_query didn't add route!\n")); + return ENETUNREACH; + } + + 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; + + RTFREE(ro->ro_rt); + ro->ro_rt = NULL; + switch (error = ipv6_verify_onlink(&ro->ro_dst)) + { + case -1: + return ENETUNREACH; + case 0: + break; + default: + return error; + } + rtalloc((struct route *)ro); + if (ro->ro_rt == NULL || ro->ro_rt->rt_ifa == NULL) + panic("Oops2, I'm forgetting something after verify_onlink()."); + } + + + /* + * If we found a route, use the address + * corresponding to the outgoing interface + * unless it is the loopback (in case a route + * to our address on another net goes to loopback). + * + * This code may be simplified if the above gyrations work. + */ + if (ro->ro_rt && ro->ro_rt->rt_ifp && + !(ro->ro_rt->rt_ifp->if_flags & IFF_LOOPBACK)) + i6a = ifatoi6a(ro->ro_rt->rt_ifa); + if (i6a == NULL) + { + u_short fport = sin6->sin6_port; + + /* + * Source address selection when there is no route. + * + * If ND out all if's is to be used, this is the point to do it. + * (Similar to when the route lookup fails in ipv6_output.c, and + * the 'on-link' assumption kicks in.) + * + * For multicast, use i6a of mcastdef. + */ + + sin6->sin6_port = 0; + i6a = ifatoi6a(ifa_ifwithdstaddr(sin6tosa(sin6))); + if (i6a == NULL) + i6a = ifatoi6a(ifa_ifwithnet(sin6tosa(sin6))); + sin6->sin6_port = fport; + if (i6a == NULL) + { + struct in6_ifaddr *ti6a = NULL; + + 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_IS_ADDR_LINKLOCAL(&I6A_SIN(i6a)->sin6_addr)) + ti6a=i6a; + else if (!IN6_IS_ADDR_LOOPBACK(&I6A_SIN(i6a)->sin6_addr)) + break; + } + if (i6a == NULL && ti6a != NULL) + i6a = ti6a; + } + if (i6a == NULL) + return EADDRNOTAVAIL; + } + + /* + * If I'm "connecting" to a multicast address, gyrate properly to + * get a source address based upon the user-requested mcast interface. + */ + if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr) && inp->inp_moptions6 != NULL && + (inp->inp_flags & INP_IPV6_MCAST)) + { + struct ipv6_moptions *i6mo; + struct ifnet *ifp; + + i6mo = inp->inp_moptions6; + if (i6mo->i6mo_multicast_ifp != NULL) + { + ifp = i6mo->i6mo_multicast_ifp; + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp) /* Linkloc vs. global? */ + break; + if (i6a == NULL) + return EADDRNOTAVAIL; + } + } + + ifaddr = (struct sockaddr_in6 *)&i6a->i6a_addr; + } + +#if __FreeBSD__ + if (in_pcblookup_hash(inp->inp_pcbinfo, + ((IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) ? + (struct in_addr *)&ifaddr->sin6_addr : + (struct in_addr *)&inp->inp_laddr6), + sin6->sin6_port, (struct in_addr *)&sin6->sin6_addr, + inp->inp_lport, INPLOOKUP_IPV6)) return EADDRINUSE; +#else /* __FreeBSD__ */ +#if __NetBSD__ + if (in6_pcblookup_connect(inp->inp_table, + ((IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) ? &ifaddr->sin6_addr : + &inp->inp_laddr6), sin6->sin6_port, &sin6->sin6_addr, inp->inp_lport)) + return EADDRINUSE; +#else /* __NetBSD__ */ +#ifdef __OpenBSD__ + if (in_pcblookup(inp->inp_table, ((IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) ? +#else /* __OpenBSD__ */ + if (in_pcblookup(inp->inp_head, ((IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) ? +#endif /* __OpenBSD__ */ + (struct in_addr *)&ifaddr->sin6_addr : + (struct in_addr *)&inp->inp_laddr6), + sin6->sin6_port, (struct in_addr *)&sin6->sin6_addr, + inp->inp_lport, INPLOOKUP_IPV6)) return EADDRINUSE; +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + + if (IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6)) + { + if (inp->inp_lport == 0) + (void)in6_pcbbind(inp, NULL); /* To find free port & bind to it. */ + inp->inp_laddr6 = ifaddr->sin6_addr; + } + inp->inp_faddr6 = sin6->sin6_addr; + inp->inp_fport = sin6->sin6_port; + + /* + * Assumes user specify flowinfo in network order. + */ + inp->inp_ipv6.ipv6_versfl = htonl(0x60000000) | + (sin6->sin6_flowinfo & htonl(0x0fffffff)); + +#if __NetBSD__ + in_pcbstate(inp, INP_CONNECTED); +#endif /* __NetBSD__ */ + +#if __FreeBSD__ + /* + * See pcbbind near the end for reasoning on setting this for a value to + * hash on. + */ +#if 0 + inp->inp_laddr.s_addr = inp->inp_laddr6.in6a_words[3] + ^ inp->inp_laddr6.in6a_words[1]; +#endif /* 0 */ + in_pcbrehash(inp); +#endif /* __FreeBSD__ */ + return 0; +} + +/*---------------------------------------------------------------------- + * Pass some notification to all connections of a protocol + * associated with address dst. The local address and/or port numbers + * may be specified to limit the search. The "usual action" will be + * taken, depending on the ctlinput cmd. The caller must filter any + * cmds that are uninteresting (e.g., no error in the map). + * Call the protocol specific routine (if any) to report + * any errors for each matching socket. + * + * Also perform input-side security policy check + * once PCB to be notified has been located. + * + * Must be called at splnet. + ----------------------------------------------------------------------*/ + +int +#ifdef IPSEC +in6_pcbnotify(head, dst, fport_arg, la, lport_arg, cmd, notify, m, nexthdr) +#else /* IPSEC */ +in6_pcbnotify(head, dst, fport_arg, la, lport_arg, cmd, notify) +#endif /* IPSEC */ +#if __NetBSD__ || __OpenBSD__ + struct inpcbtable *head; +#else /* __NetBSD__ || __OpenBSD__ */ +#if __FreeBSD__ + struct inpcbhead *head; +#else /* __FreeBSD__ */ + struct inpcb *head; +#endif /* __FreeBSD__ */ +#endif /* __NetBSD__ || __OpenBSD__ */ + struct sockaddr *dst; + uint fport_arg; + struct in6_addr *la; + uint lport_arg; + int cmd; + void (*notify) __P((struct inpcb *, int)); +#ifdef IPSEC + struct mbuf *m; + int nexthdr; +#endif /* IPSEC */ +{ + register struct inpcb *inp, *oinp; + struct in6_addr *faddr,laddr = *la; + u_short fport = fport_arg, lport = lport_arg; + int errno; +#ifdef IPSEC + struct sockaddr_in6 srcsa, dstsa; +#endif /* IPSEC */ + + DPRINTF(IDL_EVENT,("Entering in6_pcbnotify. head = 0x%lx, dst is\n", (unsigned long)head)); + DDO(IDL_EVENT,dump_smart_sockaddr(dst)); + DPRINTF(IDL_EVENT,("fport_arg = %d, lport_arg = %d, cmd = %d\n",\ + fport_arg, lport_arg, cmd)); + DDO(IDL_EVENT,printf("la is ");dump_in6_addr(la)); + + if ((unsigned)cmd > PRC_NCMDS || dst->sa_family != AF_INET6) + return 1; + faddr = &(((struct sockaddr_in6 *)dst)->sin6_addr); + if (IN6_IS_ADDR_UNSPECIFIED(faddr)) + return 1; + if (IN6_IS_ADDR_V4MAPPED(faddr)) + { + printf("Huh? Thought in6_pcbnotify() never got called with mapped!\n"); + } + + /* + * Redirects go to all references to the destination, + * and use in_rtchange to invalidate the route cache. + * Dead host indications: notify all references to the destination. + * Otherwise, if we have knowledge of the local port and address, + * deliver only to that socket. + */ + + if (PRC_IS_REDIRECT(cmd) || cmd == PRC_HOSTDEAD) + { + fport = 0; + lport = 0; + laddr = in6addr_any; + if (cmd != PRC_HOSTDEAD) + notify = in_rtchange; + } + errno = inet6ctlerrmap[cmd]; + +#ifdef IPSEC + /* Build these again because args aren't necessarily correctly formed + sockaddrs, and the policy code will eventually need that. -cmetz */ + + memset(&srcsa, 0, sizeof(struct sockaddr_in6)); + srcsa.sin6_len = sizeof(struct sockaddr_in6); + srcsa.sin6_family = AF_INET6; + /* defer port */ + srcsa.sin6_addr = *faddr; + + memset(&dstsa, 0, sizeof(struct sockaddr_in6)); + dstsa.sin6_len = sizeof(struct sockaddr_in6); + dstsa.sin6_family = AF_INET6; + /* defer port */ + dstsa.sin6_addr = laddr; +#endif /* IPSEC */ + +#if __NetBSD__ || __OpenBSD__ + for (inp = head->inpt_queue.cqh_first; + inp != (struct inpcb *)&head->inpt_queue;) +#else /* __NetBSD__ || __OpenBSD__ */ +#if __FreeBSD__ + for (inp = head->lh_first; inp != NULL;) +#else /* __FreeBSD__ */ + for (inp = head->inp_next; inp != head;) +#endif /* __FreeBSD__ */ +#endif /* __NetBSD__ || __OpenBSD__ */ + { + if (!IN6_ARE_ADDR_EQUAL(&inp->inp_faddr6, faddr) || + !inp->inp_socket || + (lport && inp->inp_lport != lport) || + (!IN6_IS_ADDR_UNSPECIFIED(&laddr) && !IN6_ARE_ADDR_EQUAL(&inp->inp_laddr6, &laddr)) || + (fport && inp->inp_fport != fport)) + { +#if __NetBSD__ || __OpenBSD__ + inp = inp->inp_queue.cqe_next; +#else /* __NetBSD__ || __OpenBSD__ */ +#if __FreeBSD__ + inp = inp->inp_list.le_next; +#else /* __FreeBSD__ */ + inp = inp->inp_next; +#endif /* __FreeBSD__ */ +#endif /* __NetBSD__ || __OpenBSD__ */ + continue; + } + oinp = inp; + +#if __NetBSD__ || __OpenBSD__ + inp = inp->inp_queue.cqe_next; +#else /* __NetBSD__ || __OpenBSD__ */ +#if __FreeBSD__ + inp = inp->inp_list.le_next; +#else /* __FreeBSD__ */ + inp = inp->inp_next; +#endif /* __FreeBSD__ */ +#endif /* __NetBSD__ || __OpenBSD__ */ + + if (notify) +#ifdef IPSEC + { + /* Pretend the packet came in for this source/destination port pair, + since that's what we care about for policy. If the passed in + fport and/or lport were nonzero, the comparison will make sure + they match that of the PCB and the right thing will happen. + -cmetz */ + srcsa.sin6_port = oinp->inp_fport; + dstsa.sin6_port = oinp->inp_lport; + + /* XXX - state arg should not be NULL */ + if (!netproc_inputpolicy(oinp->inp_socket, (struct sockaddr *)&srcsa, + (struct sockaddr *)&dstsa, nexthdr, m, NULL, + NULL)) +#endif /* IPSEC */ + (*notify)(oinp, errno); +#ifdef IPSEC + } +#endif /* IPSEC */ + } + return 0; +} + +/*---------------------------------------------------------------------- + * Get the local address/port, and put it in a sockaddr_in6. + * This services the getsockname(2) call. + ----------------------------------------------------------------------*/ + +int +in6_setsockaddr(inp, nam) + register struct inpcb *inp; +#if __FreeBSD__ + struct sockaddr **nam; +#else /* __FreeBSD__ */ + struct mbuf *nam; +#endif /* __FreeBSD__ */ +{ + register struct sockaddr_in6 *sin6; + + +#if __FreeBSD__ + /* + * In FreeBSD we have to allocate the sockaddr_in6 structure since we aren't + * given an mbuf, but a sockaddr ** as the second argument. (i.e. a location + * to reference the space this routine allocates. + */ + if (!(sin6 = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6),M_SONAME, M_DONTWAIT))) + return ENOBUFS; /* XXX */ + +#else /* __FreeBSD__ */ + + nam->m_len = sizeof(struct sockaddr_in6); + sin6 = mtod(nam,struct sockaddr_in6 *); + +#endif /* __FreeBSD__ */ + + bzero ((caddr_t)sin6,sizeof(struct sockaddr_in6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = inp->inp_lport; + sin6->sin6_addr = inp->inp_laddr6; + +#if __FreeBSD__ + *nam = (struct sockaddr *) sin6; +#endif /* __FreeBSD__ */ + + return 0; +} + +/*---------------------------------------------------------------------- + * Get the foreign address/port, and put it in a sockaddr_in6. + * This services the getpeername(2) call. + ----------------------------------------------------------------------*/ + +int +in6_setpeeraddr(inp, nam) + register struct inpcb *inp; +#if __FreeBSD__ + struct sockaddr **nam; +#else /* __FreeBSD__ */ + struct mbuf *nam; +#endif /* __FreeBSD__ */ +{ + register struct sockaddr_in6 *sin6; + +#if __FreeBSD__ + /* + * In FreeBSD we have to allocate the sockaddr_in6 structure since we aren't + * given an mbuf, but a sockaddr ** as the second argument. (i.e. a location + * to reference the space this routine allocates. + */ + if (!(sin6 = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6),M_SONAME, M_DONTWAIT))) + return ENOBUFS; /* XXX */ + +#else /* __FreeBSD__ */ + + nam->m_len = sizeof(struct sockaddr_in6); + sin6 = mtod(nam,struct sockaddr_in6 *); + +#endif /* __FreeBSD__ */ + + bzero ((caddr_t)sin6,sizeof(struct sockaddr_in6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = inp->inp_fport; + sin6->sin6_addr = inp->inp_faddr6; + sin6->sin6_flowinfo = inp->inp_fflowinfo; + +#if __FreeBSD__ + *nam = (struct sockaddr *) sin6; +#endif /* __FreeBSD__ */ + + return 0; +} diff --git a/sys/netinet6/in6_proto.c b/sys/netinet6/in6_proto.c new file mode 100644 index 00000000000..a4222730caa --- /dev/null +++ b/sys/netinet6/in6_proto.c @@ -0,0 +1,387 @@ +/* +%%% 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/socket.h> +#include <sys/protosw.h> +#include <sys/kernel.h> +#include <sys/domain.h> +#include <sys/mbuf.h> + +#include <net/if.h> +#include <net/route.h> +#include <net/radix.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/in_pcb.h> +#include <netinet/udp.h> +#include <netinet/udp_var.h> + +#include <netinet/tcp.h> +#include <netinet/tcp_fsm.h> +#include <netinet/tcp_seq.h> +#include <netinet/tcp_timer.h> +#include <netinet/tcpip.h> +#if __FreeBSD__ && defined(_NETINET_IN_PCB_H_) +#undef _NETINET_IN_PCB_H_ +#include <netinet/tcp_var.h> +#define _NETINET_IN_PCB_H_ +#else /* __FreeBSD__ */ +#include <netinet/tcp_var.h> +#endif /* __FreeBSD__ */ +#include <netinet/tcpip.h> +#include <netinet/tcp_debug.h> + +#include <netinet6/in6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> +#include <netinet6/ipv6_icmp.h> + +#if defined(IPSEC) || defined(NRL_IPSEC) +#include <netsec/ipsec.h> +#endif /* defined(IPSEC) || defined(NRL_IPSEC) */ + +#if __FreeBSD__ +#include <sys/sysctl.h> +#endif /* __FreeBSD__ */ + +extern struct domain inet6domain; + +#define CAST (void *) + +#if !__FreeBSD__ +struct protosw inet6sw[] = { +/* normal protocol switch */ + { + 0, &inet6domain, 0, 0, /* NOTE: This 0 is the same as IPPROTO_HOPOPTS, + but we specially demux IPPROTO_HOPOPTS + in ipv6_input(). */ + CAST ipv6_hop, CAST ipv6_output, 0, 0, /* Watch for hop-by-hop input! */ + 0, + ipv6_init, 0, ipv6_slowtimo, ipv6_drain, ipv6_sysctl + }, + + /* ICMPv6 entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_ICMPV6, PR_ATOMIC|PR_ADDR, + CAST ipv6_icmp_input, CAST ipv6_icmp_output, 0, ripv6_ctloutput, + ipv6_icmp_usrreq, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, ipv6_icmp_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + + /* IPv6-in-IPv6 tunnel entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_IPV6, PR_ATOMIC|PR_ADDR, + CAST ipv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + ripv6_usrreq, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, ipv6_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + + /* IPv4-in-IPv6 tunnel entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_IPV4, PR_ATOMIC|PR_ADDR, + CAST ipv4_input, 0, 0, 0, + 0, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, ip_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + + /* Fragment entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_FRAGMENT, PR_ATOMIC|PR_ADDR, + CAST ipv6_reasm, 0, 0, 0, + 0, + 0, 0, 0, 0, 0 + }, + + + /* UDP entry */ + + /* + * Eventually, that ipv6_ctloutput() will have to be replaced with a + * udp_ctloutput(), which knows whether or not to redirect things down to + * IP or IPv6 appropriately. + */ + + { + SOCK_DGRAM, &inet6domain, IPPROTO_UDP, PR_ATOMIC|PR_ADDR, + CAST udp_input, 0, CAST udp_ctlinput, ipv6_ctloutput, + udp_usrreq, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, udp_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + + /* TCP entry */ + + { + SOCK_STREAM, &inet6domain, IPPROTO_TCP, PR_CONNREQUIRED|PR_WANTRCVD, + CAST tcp_input, 0, CAST tcp_ctlinput, tcp_ctloutput, + tcp_usrreq, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, tcp_sysctl /* init, fasttimo, etc. in v4 protosw already! */ +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 /* init, fasttimo, etc. in v4 protosw already! */ +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + +#if defined(IPSEC) || defined(NRL_IPSEC) + /* IPv6 & IPv4 Authentication Header */ + { + SOCK_RAW, &inet6domain, IPPROTO_AH, PR_ATOMIC|PR_ADDR, + CAST ipsec_ah_input, 0, 0, 0, + 0, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, ipsec_ah_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, + +#ifdef IPSEC_ESP + /* IPv6 & IPv4 Encapsulating Security Payload Header */ + { + SOCK_RAW, &inet6domain, IPPROTO_ESP, PR_ATOMIC|PR_ADDR, + CAST ipsec_esp_input, 0, 0, 0, + 0, +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 + 0, 0, 0, 0, ipsec_esp_sysctl +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + 0, 0, 0, 0, 0 +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + }, +#endif /* IPSEC_ESP */ +#endif /* defined(IPSEC) || defined(NRL_IPSEC) */ + + /* Unknown header. */ + + { + SOCK_RAW, &inet6domain, IPPROTO_RAW, PR_ATOMIC|PR_ADDR, + CAST ripv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + ripv6_usrreq, + 0,0,0,0,0 + }, + + /* Raw wildcard */ + { + SOCK_RAW, &inet6domain, 0, PR_ATOMIC|PR_ADDR, + CAST ripv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + ripv6_usrreq, + ripv6_init,0,0,0,0 + }, +}; +#else /* !__FreeBSD__ */ +extern struct pr_usrreqs nousrreqs; +struct protosw inet6sw[] = { + { + 0, &inet6domain, 0, 0, /* NOTE: This 0 is the same as IPPROTO_HOPOPTS, + but we specially demux IPPROTO_HOPOPTS + in ipv6_input(). */ + CAST ipv6_hop, CAST ipv6_output, 0, 0, /* Watch for hop-by-hop input! */ + 0, + ipv6_init, 0, ipv6_slowtimo, ipv6_drain, + &nousrreqs + }, + + /* ICMPv6 entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_ICMPV6, PR_ATOMIC|PR_ADDR, + CAST ipv6_icmp_input, CAST ipv6_icmp_output, 0, CAST ripv6_ctloutput, + 0, + 0, 0, 0, 0, + &ipv6_icmp_usrreqs, + }, + + /* IPv6-in-IPv6 tunnel entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_IPV6, PR_ATOMIC|PR_ADDR, + CAST ipv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + 0, + 0, 0, 0, 0, + &ripv6_usrreqs + }, + + /* IPv4-in-IPv6 tunnel entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_IPV4, PR_ATOMIC|PR_ADDR, + CAST ipv4_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs + }, + + /* Fragment entry */ + + { + SOCK_RAW, &inet6domain, IPPROTO_FRAGMENT, PR_ATOMIC|PR_ADDR, + CAST ipv6_reasm, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs + }, + + + /* UDP entry */ + + /* + * Eventually, that ipv6_ctloutput() will have to be replaced with a + * udp_ctloutput(), which knows whether or not to redirect things down to + * IP or IPv6 appropriately. + */ + + { + SOCK_DGRAM, &inet6domain, IPPROTO_UDP, PR_ATOMIC|PR_ADDR, + CAST udp_input, 0, CAST udp_ctlinput, ipv6_ctloutput, + 0, + udp_init, 0, 0, 0, + &udp_usrreqs + }, + + /* TCP entry */ + + { + SOCK_STREAM, &inet6domain, IPPROTO_TCP, PR_CONNREQUIRED|PR_WANTRCVD, + CAST tcp_input, 0, CAST tcp_ctlinput, tcp_ctloutput, + 0, + tcp_init, tcp_fasttimo, tcp_slowtimo, tcp_drain, + &tcp_usrreqs, + }, + +#if defined(IPSEC) || defined(NRL_IPSEC) + /* IPv6 & IPv4 Authentication Header */ + { + SOCK_RAW, &inet6domain, IPPROTO_AH, PR_ATOMIC|PR_ADDR, + CAST ipsec_ah_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs + }, + +#ifdef IPSEC_ESP + /* IPv6 & IPv4 Encapsulating Security Payload Header */ + { + SOCK_RAW, &inet6domain, IPPROTO_ESP, PR_ATOMIC|PR_ADDR, + CAST ipsec_esp_input, 0, 0, 0, + 0, + 0, 0, 0, 0, + &nousrreqs + }, +#endif /* IPSEC_ESP */ +#endif /* defined(IPSEC) || defined(NRL_IPSEC) */ + + /* Unknown header. */ + + { + SOCK_RAW, &inet6domain, IPPROTO_RAW, PR_ATOMIC|PR_ADDR, + CAST ripv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + 0, + 0,0,0,0, + &ripv6_usrreqs + }, + + /* Raw wildcard */ + { + SOCK_RAW, &inet6domain, 0, PR_ATOMIC|PR_ADDR, + CAST ripv6_input, CAST ripv6_output, 0, ripv6_ctloutput, + 0, + ripv6_init,0,0,0, + &ripv6_usrreqs, + }, +}; + +#endif /* !__FreeBSD__ */ + +#if !__FreeBSD__ +struct domain inet6domain = +{ + PF_INET6, "IPv6", 0, 0, 0, + inet6sw, &inet6sw[sizeof(inet6sw)/sizeof(inet6sw[0])], 0, + /* + * FreeBSD's IPv4 replaces rn_inithead() with an IPv4-specific function. + * Our IPv6 uses the ifa->ifa_rtrequest() function pointer to intercept + * rtrequest()s. The consequence of this is that we use the generic + * rn_inithead(). + */ + rn_inithead, 64, sizeof(struct sockaddr_in6) +}; +#else /* !__FreeBSD__ */ +struct domain inet6domain = +{ + PF_INET6, "IPv6", 0, 0, 0, + inet6sw, &inet6sw[sizeof(inet6sw)/sizeof(inet6sw[0])], 0, + /* + * FreeBSD's IPv4 replaces rn_inithead() with an IPv4-specific function. + * Our IPv6 uses the ifa->ifa_rtrequest() function pointer to intercept + * rtrequest()s. The consequence of this is that we use the generic + * rn_inithead(). + */ + rn_inithead, 64, sizeof(struct sockaddr_in6) +}; + +DOMAIN_SET(inet6); +#endif /* !__FreeBSD__ */ + +/* Eventually, make these go away -- if you want to be a router, twiddle the + sysctls before bringing up your interfaces */ + +#ifndef IPV6FORWARDING +#ifdef IPV6GATEWAY +#define IPV6FORWARDING 1 +#else +#define IPV6FORWARDING 0 +#endif /* IPV6GATEWAY */ +#endif /* IPV6FORWARDING */ + +#ifndef IPV6RSOLICIT +#if IPV6FORWARDING +#define IPV6RSOLICIT 0 +#else /* IPV6FORWARDING */ +#define IPV6RSOLICIT 1 +#endif /* IPV6FORWARDING */ +#endif /* IPV6RSOLICIT */ + +#ifndef IFQMAXLEN +#define IFQMAXLEN IFQ_MAXLEN +#endif + +int ipv6forwarding = IPV6FORWARDING; +int ipv6rsolicit = IPV6RSOLICIT; +int ipv6_defhoplmt = MAXHOPLIMIT; +int ipv6qmaxlen = IFQMAXLEN; + +#if __FreeBSD__ +SYSCTL_NODE(_net_inet, IPPROTO_IPV6, ipv6, CTLFLAG_RW, 0, "IPV6"); +SYSCTL_NODE(_net_inet, IPPROTO_ICMPV6, icmpv6, CTLFLAG_RW, 0, "ICMPV6"); +#endif /* __FreeBSD__ */ diff --git a/sys/netinet6/in6_var.h b/sys/netinet6/in6_var.h new file mode 100644 index 00000000000..3052498d387 --- /dev/null +++ b/sys/netinet6/in6_var.h @@ -0,0 +1,189 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IN6_VAR_H +#define _NETINET6_IN6_VAR_H 1 + +/* + * IPv6 interface request and alias request. Use sockaddr_in6 because + * it is larger than sockaddr. + */ + +struct inet6_ifreq +{ +#ifndef IFNAMSIZ +#define IFNAMSIZ 16 +#endif /* IFNAMSIZ */ + char ifr_name[IFNAMSIZ]; /* if name, e.g. "en0" */ + union { + struct sockaddr_in6 ifru_addr; + struct sockaddr_in6 ifru_dstaddr; + struct sockaddr_in6 ifru_broadaddr; + short ifru_flags; + int ifru_metric; + caddr_t ifru_data; + } ifr_ifru; +#define ifr_addr ifr_ifru.ifru_addr /* address */ +#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-to-p link */ +#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */ +#define ifr_flags ifr_ifru.ifru_flags /* flags */ +#define ifr_metric ifr_ifru.ifru_metric /* metric */ +#define ifr_data ifr_ifru.ifru_data /* for use by interface */ + }; + +/* + * IPv6 interface "alias" request. Used to add interface addresses. This + * may be needed to be expanded to pass down/up permanancy information, and + * possibly deprecation lifetime values. (That is, if the kernel doesn't + * compute that stuff itself.) + */ + +struct inet6_aliasreq +{ + char ifra_name[IFNAMSIZ]; + struct sockaddr_in6 ifra_addr; + struct sockaddr_in6 ifra_dstaddr; +#define ifra_broadaddr ifra_dstaddr + struct sockaddr_in6 ifra_mask; +}; + +/* ioctl()'s for stuff with inet6_{aliasreq,ifreq} (gag!) */ + +#define SIOCDIFADDR_INET6 _IOW('i',25, struct inet6_ifreq) /* delete IF addr */ +#define SIOCAIFADDR_INET6 _IOW('i',26, struct inet6_aliasreq)/* add/chg IFalias */ +#define SIOCGIFADDR_INET6 _IOWR('i',33, struct inet6_ifreq) /* get ifnet address */ +#define SIOCGIFDSTADDR_INET6 _IOWR('i',34, struct inet6_ifreq) /* get dst address */ +#define SIOCSIFDSTADDR_INET6 _IOW('i', 14, struct inet6_ifreq) /* set dst address */ +#define SIOCGIFNETMASK_INET6 _IOWR('i',37, struct inet6_ifreq) /* get netmask */ + +#define SIOCVIFADDR_INET6 _IOW('i',69,struct inet6_ifreq) /* Verify IPv6 addr */ + +/* + * INET6 interface address. This might also serve as the prefix list, + * with the help of the I6AF_PREFIX flag. + */ + +struct in6_ifaddr +{ + struct ifaddr i6a_ifa; /* protocol-independent info (32 bytes) */ +#define i6a_ifp i6a_ifa.ifa_ifp +#ifdef KERNEL +#define i6a_flags i6a_ifa.ifa_flags +#endif + + /* All sorts of INET6-specific junk, some of it, very similar to IP's + in_ifaddr. */ + + /* Put any subnetting, etc here. */ + + struct in6_ifaddr *i6a_next; + struct in6_ifnet *i6a_i6ifp; /* Pointer to IPv6 interface info */ + struct sockaddr_in6 i6a_addr; /* Address. */ + struct sockaddr_in6 i6a_dstaddr; /* Dest. if PPP link. */ + struct sockaddr_in6 i6a_sockmask; /* Netmask. This is IPv6, so + there is no "subnet/net" + distinction. */ + + /* + * IPv6 addresses have lifetimes. Put in expiration information in + * here. A DEPRECATED address is still valid for inbound, but not for + * outbound. An EXPIRED address is invalid for both inbound and outbound, + * and should be put out of its misery (and our in6_ifaddr list) ASAP. + */ + u_long i6a_preferred; /* Preferred lifetime. */ + u_long i6a_expire; /* Expiration time. */ + u_short i6a_preflen; /* Prefix length for link-locals + (in bits). */ + u_short i6a_addrflags; /* Additional flags because + ifa_flags isn't big enough. */ +}; + +#define I6A_SIN(i6a) (&(((struct in6_ifaddr *)(i6a))->i6a_addr)) +#define IS_EXPIRED(i6a) ((i6a->i6a_expire != 0 && \ + i6a->i6a_expire < time.tv_sec)) +#define IS_DEPRECATED(i6a) ((i6a)->i6a_addrflags & I6AF_DEPRECATED) + +#define I6AF_LINKLOC 0x1 /* Link-local address. Saves the IS_IN6_LINKLOC + check. */ +#define I6AF_PERMANENT 0x2 /* Permanent address */ +#define I6AF_PREFIX 0x4 /* I am a, "prefix list entry," meaning that + the portion of the address inside the mask + is directly attached to the link. */ +#define I6AF_NOTSURE 0x8 /* I'm not sure if I'm allowed to be used yet. + This is designed for use with addresses + that haven't been verified as unique on a + link yet. */ +#define I6AF_DEPRECATED 0x10 /* The use of this address should be discouraged. + The address should not be used as a source + address for new communications. The address + is still valid for receiving packets. */ + +/* + * IPv6 multicast structures and macros. + */ + +struct in6_multi +{ + struct in6_multi *in6m_next; /* Ptr. to next one. */ + struct in6_addr in6m_addr; /* Multicast address. */ + struct ifnet *in6m_ifp; /* Pointer to interface. */ + struct in6_ifnet *in6m_i6ifp; /* Back ptr. to IPv6 if info. */ + uint in6m_refcount; /* Number of membership claims by + sockets. */ + uint in6m_timer; /* IGMP membership report timer. */ +}; + +#ifdef KERNEL +/* General case IN6 multicast lookup. Can be optimized out in certain + places (like netinet6/ipv6_input.c ?). */ + +#define IN6_LOOKUP_MULTI(addr,ifp,in6m) \ +{\ + register struct in6_ifnet *i6ifp;\ +\ + for (i6ifp=in6_ifnet; i6ifp != NULL && i6ifp->i6ifp_ifp != ifp;\ + i6ifp=i6ifp->i6ifp_next)\ + ;\ + if (i6ifp == NULL)\ + in6m=NULL;\ + else\ + for ((in6m) = i6ifp->i6ifp_multiaddrs;\ + (in6m) != NULL && !IN6_ARE_ADDR_EQUAL(&(in6m)->in6m_addr,(addr));\ + (in6m) = (in6m)->in6m_next) ;\ +} + + +#define IN6_MCASTOPTS 0x2 + +#define ETHER_MAP_IN6_MULTICAST(in6addr,enaddr) { \ + (enaddr)[0] = 0x33; \ + (enaddr)[1] = 0x33; \ + (enaddr)[2] = in6addr.s6_addr[12]; \ + (enaddr)[3] = in6addr.s6_addr[13]; \ + (enaddr)[4] = in6addr.s6_addr[14]; \ + (enaddr)[5] = in6addr.s6_addr[15]; \ + } + +struct in6_ifnet +{ + struct in6_ifnet *i6ifp_next; /* Next in list. */ + struct ifnet *i6ifp_ifp; /* Back pointer to actual interface. */ + struct in6_multi *i6ifp_multiaddrs; /* Multicast addresses for this + interface. */ + uint i6ifp_numaddrs; /* Number of IPv6 addresses on this + interface. */ + + /* Addrconf and ND variables will go here. */ +}; + +#endif /* KERNEL */ + +#endif /* _NETINET6_IN6_VAR_H */ diff --git a/sys/netinet6/ipv6.h b/sys/netinet6/ipv6.h new file mode 100644 index 00000000000..311231a0e7d --- /dev/null +++ b/sys/netinet6/ipv6.h @@ -0,0 +1,123 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IPV6_H +#define _NETINET6_IPV6_H 1 + +#define IPV6VERSION 6 + +/* + * Header structures. + */ + +struct ipv6 +{ + uint32_t ipv6_versfl; /* Version and flow label word. */ + + uint16_t ipv6_length; /* Datagram length (not including the length + of this header). */ + uint8_t ipv6_nexthdr; /* Next header type. */ + uint8_t ipv6_hoplimit; /* Hop limit. */ + + struct in6_addr ipv6_src; /* Source address. */ + struct in6_addr ipv6_dst; /* Destination address. */ +}; + +#if __linux__ +#include <endian.h> +#else /* __linux__ */ +#include <machine/endian.h> +#endif /* __linux__ */ + +struct ipv6hdr { +#if BYTE_ORDER == LITTLE_ENDIAN + uint8_t ipv6_priority:4; /* going away? */ + uint8_t ipv6_version:4; + uint32_t ipv6_flowid:24; +#elif BYTE_ORDER == BIG_ENDIAN + uint32_t ipv6_flowid:24; + uint8_t ipv6_priority:4; /* going away? */ + uint8_t ipv6_version:4; +#else +#error "Don't know what endian to use." +#endif + uint16_t ipv6_len; + uint8_t ipv6_nextheader; + uint8_t ipv6_hoplimit; + struct in6_addr ipv6_src; /* source address */ + struct in6_addr ipv6_dst; /* destination address */ +}; + +/* + * Macros and defines for header fields, and values thereof. + * Assume things are in host order for these three macros. + */ + +#define IPV6_VERSION(h) ((h)->ipv6_versfl >> 28) +#define IPV6_PRIORITY(h) (((h)->ipv6_versfl & 0x0f000000) >> 24) +#define IPV6_FLOWID(h) ((h)->ipv6_versfl & 0x00ffffff) + +#define MAXHOPLIMIT 64 +#define IPV6_MINMTU 576 + +/* + * Other IPv6 header definitions. + */ + +/* Fragmentation header & macros for it. NOTE: Host order assumption. */ + +struct ipv6_fraghdr +{ + uint8_t frag_nexthdr; /* Next header type. */ + uint8_t frag_reserved; + uint16_t frag_bitsoffset; /* More bit and fragment offset. */ + uint32_t frag_id; /* Fragment identifier. */ +}; + +#define FRAG_MOREMASK 0x1 +#define FRAG_OFFMASK 0xFFF8 +#define FRAG_MORE_BIT(fh) ((fh)->frag_bitsoffset & FRAG_MOREMASK) +#define FRAG_OFFSET(fh) ((fh)->frag_bitsoffset & FRAG_OFFMASK) + +/* Source routing header. Host order assumption for macros. */ + +struct ipv6_srcroute0 +{ + uint8_t i6sr_nexthdr; /* Next header type. */ + uint8_t i6sr_len; /* RH len in 8-byte addrs, !incl this structure */ + uint8_t i6sr_type; /* Routing type, should be 0 */ + uint8_t i6sr_left; /* Segments left */ + uint32_t i6sr_reserved; /* 8 bits of reserved padding. */ +}; + +#define I6SR_BITMASK(i6sr) ((i6sr)->i6sr_reserved & 0xffffff) + +/* Options header. For "ignoreable" options. */ + +struct ipv6_opthdr +{ + uint8_t oh_nexthdr; /* Next header type. */ + uint8_t oh_extlen; /* Header extension length. */ + uint8_t oh_data[6]; /* Option data, may be reserved for + alignment purposes. */ +}; + +#define OPT_PAD1 0 +#define OPT_PADN 1 +#define OPT_JUMBO 194 + +struct ipv6_option +{ + uint8_t opt_type; /* Option type. */ + uint8_t opt_datalen; /* Option data length. */ + uint8_t opt_data[1]; /* Option data. */ +}; +#endif /* _NETINET6_IPV6_H */ diff --git a/sys/netinet6/ipv6_addrconf.c b/sys/netinet6/ipv6_addrconf.c new file mode 100644 index 00000000000..b818854c7a8 --- /dev/null +++ b/sys/netinet6/ipv6_addrconf.c @@ -0,0 +1,241 @@ +/* +%%% 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/ioctl.h> */ +#include <sys/errno.h> +#include <sys/malloc.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/mbuf.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> + +#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 <sys/debug.h> + +/* + * External Globals + */ + +extern struct in6_ifaddr *in6_ifaddr; +extern u_long v6d_retranstime; +extern int ipv6rsolicit; + +void send_nsolicit __P((struct rtentry *, struct ifnet *, struct in6_addr *, int)); +struct mbuf *get_discov_cluster __P((void)); + +static void send_rsolicit __P((struct ifnet *)); + +/*---------------------------------------------------------------------- + * Initialize addrconf. + ----------------------------------------------------------------------*/ + +void +addrconf_init() +{ + timeout(addrconf_timer,NULL,v6d_retranstime * hz); +} + +/*---------------------------------------------------------------------- + * Send a router solicitation out a certain interface. + ----------------------------------------------------------------------*/ + +static void +send_rsolicit(ifp) + struct ifnet *ifp; +{ + struct mbuf *solicit = NULL; + struct ipv6 *header; + struct ipv6_icmp *icmp; + struct ipv6_moptions i6mo,*i6mop = NULL; + struct in6_ifaddr *i6a; + + if ((solicit = get_discov_cluster()) == NULL) + { + DPRINTF(IDL_ERROR, ("Can't allocate mbuf in send_gsolicit().\n")); + return; + } + header = mtod(solicit,struct ipv6 *); + icmp = (struct ipv6_icmp *)(header + 1);/* I want the bytes after the hdr. */ + + bzero(&i6mo,sizeof(struct ipv6_moptions)); + i6mo.i6mo_multicast_ifp = ifp; + i6mo.i6mo_multicast_ttl = 255; + i6mop = &i6mo; + /* Find source link-local or use unspec. */ + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp && (i6a->i6a_addrflags & I6AF_LINKLOC) && + !(i6a->i6a_addrflags & I6AF_NOTSURE)) + break; + + if (i6a == NULL) + header->ipv6_src = in6addr_any; + else + header->ipv6_src = i6a->i6a_addr.sin6_addr; + + header->ipv6_dst.in6a_words[0] = htonl(0xff020000); + header->ipv6_dst.in6a_words[1] = 0; + header->ipv6_dst.in6a_words[2] = 0; + header->ipv6_dst.in6a_words[3] = htonl(2); + + header->ipv6_versfl = htonl(0x6f000000); + header->ipv6_hoplimit = 255; /* Guaranteed to be intra-link if arrives with + 255. */ + header->ipv6_nexthdr = IPPROTO_ICMPV6; + header->ipv6_length = ICMPV6_RSOLMINLEN; /* For now. */ + icmp->icmp_type = ICMPV6_ROUTERSOL; + icmp->icmp_code = 0; + icmp->icmp_unused = 0; + icmp->icmp_cksum = 0; + + /* + * For now, just let ND run its normal course and don't include the link + * extension. + */ + + solicit->m_len = solicit->m_pkthdr.len = + header->ipv6_length + sizeof(*header); + icmp->icmp_cksum = in6_cksum(solicit,IPPROTO_ICMPV6,header->ipv6_length, + sizeof(struct ipv6)); + /* + * NOTE: The solicit mbuf chain will have a NULL instead of a valid + * socket ptr. When ipv6_output() calls ipsec_output_policy(), + * this socket ptr will STILL be NULL. Sooo, the security + * policy on outbound packets from here will == system security + * level (set in ipsec_init() of netinet6/ipsec.c). If your + * system security level is paranoid, then you won't move packets + * unless you have _preloaded_ keys for at least the ND addresses. + * - danmcd rja + */ + ipv6_output(solicit, NULL, IPV6_RAWOUTPUT, i6mop, NULL, NULL); +} + +/*---------------------------------------------------------------------- + * Scan list if in6_ifaddrs and see if any are expired (or can go to + * being unique). + ----------------------------------------------------------------------*/ +/* XXX - This function doesn't appear to ever actually remove addresses... */ +void +addrconf_timer(whocares) + void *whocares; +{ + struct in6_ifaddr *i6a = in6_ifaddr; + int s = splnet(); + + while (i6a != NULL) + { + /* + * Scan address list for all sorts of neat stuff. Also, in6_ifaddr + * may be a "prefix list" as well. This will be difficult when + * an router advert. advertises an on-link prefix, but I don't have + * (for whatever reason) an address on that link. + */ +#ifdef __FreeBSD__ + if (i6a->i6a_preferred && i6a->i6a_preferred <= time_second) +#else /* __FreeBSD__ */ + if (i6a->i6a_preferred && i6a->i6a_preferred <= time.tv_sec) +#endif /* __FreeBSD__ */ + { + i6a->i6a_addrflags |= I6AF_DEPRECATED; + DPRINTF(IDL_EVENT,("Address has been deprecated.\n")); + } +#ifdef __FreeBSD__ + if (i6a->i6a_expire && i6a->i6a_expire <= time_second) +#else /* __FreeBSD__ */ + if (i6a->i6a_expire && i6a->i6a_expire <= time.tv_sec) +#endif /* __FreeBSD__ */ + if (i6a->i6a_addrflags & I6AF_NOTSURE) + { + DPRINTF(IDL_FINISHED, ("Address appears to be unique.\n")); + i6a->i6a_addrflags &= ~I6AF_NOTSURE; + /* + * From what I can tell, addrs that survive DAD are + * permanent. I won't mark as permanent, but I will zero + * expiration for now. + * + * If this is a link-local address, it may be a good idea to + * send a router solicit. (But only if I'm a host.) + */ + if (ipv6rsolicit && IN6_IS_ADDR_LINKLOCAL(&i6a->i6a_addr.sin6_addr)) + send_rsolicit(i6a->i6a_ifp); + + i6a->i6a_expire = 0; + i6a->i6a_preferred = 0; + } + else + { + /* + * Do address deletion, and nuke any routes, pcb's, etc. + * that use this address. + * + * As an implementation note, it's probably more likely than + * not that addresses that get deprecated (see above) will be + * moved off the master list, as that keeps them away from + * some things. This is something we couldn't implement in time, + * however. + */ + } + i6a = i6a->i6a_next; + } + timeout(addrconf_timer,NULL,v6d_retranstime * hz); + splx(s); +} + +/*---------------------------------------------------------------------- + * Send multicast solicit for this address from all 0's. Set timer such + * that if address is still in in6_ifaddr list, it's good. + ----------------------------------------------------------------------*/ + +void +addrconf_dad(i6a) + struct in6_ifaddr *i6a; +{ + int s; + struct rtentry dummy; + + DPRINTF(IDL_GROSS_EVENT,("Sending DAD solicit!\n")); + s = splnet(); + rt_key(&dummy) = i6a->i6a_ifa.ifa_addr; + dummy.rt_gateway = NULL; + /* + * Set i6a flags and expirations such that it is NOT SURE about uniqueness. + * + * What about random delay? + */ + i6a->i6a_addrflags |= I6AF_NOTSURE; /* Might be done already. */ + i6a->i6a_preferred = 0; +#ifdef __FreeBSD__ + i6a->i6a_expire = time_second + v6d_retranstime; +#else /* __FreeBSD__ */ + i6a->i6a_expire = time.tv_sec + v6d_retranstime; +#endif /* __FreeBSD__ */ + splx(s); + /* + * It would be nice if I delayed a random amount of time here. + */ + send_nsolicit(&dummy, i6a->i6a_ifp, (struct in6_addr *)&in6addr_any, 1); +} diff --git a/sys/netinet6/ipv6_addrconf.h b/sys/netinet6/ipv6_addrconf.h new file mode 100644 index 00000000000..26f6bf76034 --- /dev/null +++ b/sys/netinet6/ipv6_addrconf.h @@ -0,0 +1,22 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IPV6_ADDRCONF_H +#define _NETINET6_IPV6_ADDRCONF_H 1 + +/* + * Function definitions. + */ +void addrconf_init __P((void)); +void addrconf_timer __P((void *)); +void addrconf_dad __P((struct in6_ifaddr *)); + +#endif /* _NETINET6_IPV6_ADDRCONF_H */ diff --git a/sys/netinet6/ipv6_discovery.c b/sys/netinet6/ipv6_discovery.c new file mode 100644 index 00000000000..6614a18e2aa --- /dev/null +++ b/sys/netinet6/ipv6_discovery.c @@ -0,0 +1,3285 @@ +/* +%%% 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/proc.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/ioctl.h> +#include <sys/errno.h> +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/radix.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.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 <sys/debug.h> + +#if __OpenBSD__ && defined(NRL_IPSEC) +#define IPSEC 1 +#endif /* __OpenBSD__ && defined(NRL_IPSEC) */ + +#ifdef IPSEC +#include <netsec/ipsec.h> +#endif /* IPSEC */ + +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) +#include <machine/pcpu.h> +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + +#ifdef IPSEC +#include <net/netproc.h> +#include <net/netproc_var.h> + +extern struct netproc_security fixedencrypt; +extern struct netproc_auth fixedauth; +#endif /* IPSEC */ + +/* + * Globals (and forward function declarations). + */ + +struct discq dqhead; +struct v6router defrtr,nondefrtr; +struct sockaddr_in6 in6_allones; + +void ipv6_nsolicit __P((struct ifnet *, struct mbuf *, struct rtentry *)); +void ipv6_uni_nsolicit __P((struct rtentry *)); + +/* + * These should be sysctl-tweakable. See ipv6_var.h for units. + * They share the common prefix v6d_. + */ + +u_long v6d_maxinitrai = MAX_INITIAL_RTR_ADVERT_INTERVAL; +u_long v6d_maxinitra = MAX_INITIAL_RTR_ADVERTISEMENTS; +u_long v6d_maxrtrdel = MAX_RTR_RESPONSE_DELAY; + +u_long v6d_maxrtsoldel = MAX_RTR_SOLICITATION_DELAY; +u_long v6d_rtsolint = RTR_SOLICITATION_INTERVAL; +u_long v6d_maxrtsol = MAX_RTR_SOLICITATIONS; + +u_long v6d_maxmcastsol = MAX_MULTICAST_SOLICIT; +u_long v6d_maxucastsol = MAX_UNICAST_SOLICIT; +u_long v6d_maxacastdel = MAX_ANYCAST_DELAY_TIME; +u_long v6d_maxnadv = MAX_NEIGHBOR_ADVERTISEMENTS; +u_long v6d_minnadvint = MIN_NEIGHBOR_ADVERT_INTERVAL; +u_long v6d_reachtime = REACHABLE_TIME; +u_long v6d_retranstime = RETRANS_TIMER; +u_long v6d_delfirstprobe= DELAY_FIRST_PROBE_TIME; +/* Need to somehow define random factors. */ + +u_long v6d_nexthopclean = NEXTHOP_CLEAN_INTERVAL; +u_long v6d_toolong = NEXTHOP_CLEAN_INTERVAL >> 1; /* Half the cleaning + interval. */ + +u_long v6d_down = REJECT_TIMER; /* Dead node routes are marked REJECT + for this amt. */ + +/* + * External globals. + */ + +extern struct in6_ifaddr *in6_ifaddr; +extern struct in6_ifnet *in6_ifnet; + +extern int ipv6forwarding; +extern int ipv6rsolicit; +extern int ipv6_defhoplmt; + +#if __NetBSD__ || __OpenBSD__ +#define if_name if_xname +#endif /* __NetBSD__ || __OpenBSD__ */ + +/* + * General notes: + * + * Currently this module does not support encryption/authentication + * of ND messages. That support is probably needed in some environments. + * NRL intends to add it in a later release. + * + * Please keep this in mind. + * danmcd rja + */ +int ipv6_enabled __P((struct ifnet *)); + +void ipv6_neighbor_timer __P((void *)); +int ipv6_clean_nexthop __P((struct radix_node *, void *)); +void ipv6_nexthop_timer __P((void *)); +void ipv6_discovery_init __P((void)); +struct mbuf *get_discov_cluster __P((void)); +void send_nsolicit __P((struct rtentry *, struct ifnet *, struct in6_addr *, int)); +struct mbuf *ipv6_discov_pullup __P((struct mbuf *, int)); +struct v6router *ipv6_add_defrouter_advert __P((struct in6_addr *, int lifetime, struct ifnet *)); +struct v6router *ipv6_add_defrouter_rtrequest __P((struct rtentry *)); +void ipv6_nadvert __P((struct in6_ifaddr *, struct ifnet *, struct rtentry *, uint32_t)); +static int update_defrouter __P((struct v6router *, struct ipv6_icmp *)); +int ipv6_delete_defrouter __P((struct v6router *)); +static void prefix_concat __P((struct in6_addr *, struct in6_addr *, struct in6_addr *)); +struct rtentry *ipv6_new_neighbor __P((struct sockaddr_in6 *, struct ifnet *)); +int ipv6_discov_resolve __P((struct ifnet *, struct rtentry *, struct mbuf *, struct sockaddr *, u_char *)); +void tunnel_parent __P((struct rtentry *)); +void tunnel_parent_clean __P((struct rtentry *)); +void tunnel_child __P((struct rtentry *)); +void tunnel_child_clean __P((struct rtentry *)); + +/* + * Functions and macros. + */ + +/*---------------------------------------------------------------------- + void padto(u_char *ptr, u_long mask, int alignto) + + Examples: padto(foo, 0x3, 0) will get 4-byte alignment + padto(foo, 0x7, 6) will get to 2 bytes before an 8-byte + boundary. + + Padding generated will be most efficient. + + This is now deprecated for discovery, but it still has + uses in generating IPv6 options (the option bags). + +----------------------------------------------------------------------*/ +#define padto(ptr,mask,alignto)\ +{\ + int difference = 0;\ + \ + if (((u_long)(ptr) & (mask)) != (alignto))\ + {\ + difference = (((u_long)(ptr) & ~(mask)) + (alignto)) - (long)(ptr);\ + if (difference < 0)\ + difference += ((mask) +1);\ + if (difference == 1)\ + (ptr)[0] = EXT_PAD1;\ + else\ + {\ + (ptr)[0] = EXT_PADN;\ + (ptr)[1] = (difference) - 2; /* difference >= 2 always here. */\ + bzero((ptr)+2,(ptr)[1]);\ + }\ + (ptr) += difference;\ + }\ +} + + +/*---------------------------------------------------------------------- + * ipv6_neighbor_timer(): + * Scan neighbor list and resend (1) mcast ND solicits for all + * neighbors in INCOMPLETE state and (2) unicast ND solicits + * for all neighbors in PROBE state + ----------------------------------------------------------------------*/ +void +ipv6_neighbor_timer(whocares) + void *whocares; +{ + struct discq *dq; + int s = splnet(); + struct rtentry *rt; + + /* + * 1. Scan all neighbors (go down dq list) and retransmit those which + * are still in INCOMPLETE. + * 2. Also, those in PROBE state should have unicast solicits sent. + * 3. ... + */ + + dq = dqhead.dq_next; + while (dq != &dqhead) + { + struct discq *current = dq; + + dq = dq->dq_next; /* Before any rtrequest */ + + rt = current->dq_rt; +#ifdef __FreeBSD__ + if (rt->rt_rmx.rmx_expire <= time_second) +#else /* __FreeBSD__ */ + if (rt->rt_rmx.rmx_expire <= time.tv_sec) +#endif /* __FreeBSD__ */ + { + struct sockaddr_dl *sdl = (struct sockaddr_dl *)rt->rt_gateway; + + /* + * Gotta do SOMETHING... + */ + if (sdl->sdl_alen == 0) { + /* If RTF_REJECT, then delete... */ + if (rt->rt_flags & RTF_REJECT) + { + DPRINTF(GROSSEVENT, + ("neighbor_timer() deleting rejected route\n")); + rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL); + continue; + } + + /* Am in INCOMPLETE mode... */ + + if (current->dq_unanswered >= 0) /* If not newly created... */ + if (current->dq_unanswered >= v6d_maxmcastsol) + { + /* Dead node. For now, just delete. + May want to do an RTF_REJECT entry. + May also want to send ICMP errors on enqueued packet(s). + May also want to do routing cleanup if a defult router. + XXX */ + DPRINTF(GROSSEVENT,("Deleting dead node... ")); + rt->rt_flags |= RTF_REJECT; + /* + * If there's no ifa, add an aribitrary one. + * (May be unnecessary if rtfree() ifa != NULL check + * is in place.) + * + * It turns out that adding a dummy ifa causes + * problems for subsequent packets that successfully + * rtalloc-ed this route (since rtalloc1 doesn't + * check if a route is marked RTF_REJECT... the + * check is done at a further down the stack, e.g., + * ether_output(). This causes ipv6_output to + * call ipv6_nsolicit (with an incomplete route that + * has an ifa but no ifp). ipv6_nsolicit then uses + * the address of the dummy ifa as the source address + * to be used in send_nsolicit which may panic + * if the address turns out to not have a link-local + * address (e.g. loopback). + * + * The solution, for now, is to check for RTF_REJECT + * in ipv6_nsolicit. This may change in the future. + */ + if (rt->rt_ifa == NULL) + { + rt->rt_ifa = (struct ifaddr *)in6_ifaddr; + rt->rt_ifa->ifa_refcnt++; + } +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second + v6d_down; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec + v6d_down; +#endif /* __FreeBSD__ */ + + /* ICMPv6 error part. */ + while (current->dq_queue != NULL) + { + struct mbuf *badpack = current->dq_queue; + + current->dq_queue = badpack->m_nextpkt; + /* Set rcvif for icmp_error, is this good? */ + badpack->m_pkthdr.rcvif = current->dq_rt->rt_ifp; + ipv6_icmp_error(badpack,ICMPV6_UNREACH, + ICMPV6_UNREACH_ADDRESS,0); + } + } + else + { + /* Send another solicit. The ipv6_nsolicit call will + ensure the rate-limitation. Send the queued packet + to ipv6_nsolicit for source address determination. */ + ipv6_nsolicit(rt->rt_ifp,NULL,rt); + } + } + else + /* + * Am in either PROBE mode or REACHABLE mode. + * + * Either way, that will (in the case of REACHABLE->PROBE) + * or might (in the case of PROBE->INCOMPLETE) change. + */ + if (current->dq_unanswered >= 0) /* PROBE */ + if (current->dq_unanswered >= v6d_maxucastsol) + /* PROBE -> INCOMPLETE */ + if (rt->rt_refcnt > 0) + { + /* The code below is an optimization for the case + * when someone is using this route : + * - defer actual deletion until mcast solicits fail. + * + * Q: Do we still want to do this? + */ + sdl->sdl_alen = 0; + current->dq_unanswered = 0; +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second + v6d_retranstime; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec + v6d_retranstime; +#endif /* __FreeBSD__ */ + } + else + { + /* + * Unicast probes failed and no one is using this route. + * Delete and let address resolution take its course. + * + * At this point, I have an ifa, so I don't need to add + * one. + */ + rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL); + } + else + { + /* Retry PROBE */ + ipv6_uni_nsolicit(rt); + } + else + { + /* + * Do nothing if REACHABLE expires. Only on output, + * BUT... some of these might hang around for too long. + * See ipv6_clean_nexthop for details on a solution. + * + * I am now in the STALE state. + */ + } + } + } + + timeout(ipv6_neighbor_timer,NULL,v6d_retranstime * hz); + splx(s); +} + +/*---------------------------------------------------------------------- + * ipv6_clean_nexthop: + * Delete requested route, if either the route is both GATEWAY and HOST + * and not held (or the caller REALLY requests it). + ----------------------------------------------------------------------*/ +int +ipv6_clean_nexthop(rn, arg) + struct radix_node *rn; + void *arg; +{ + struct rtentry *rt = (struct rtentry *)rn; +#ifdef __alpha__ + long hardcore = (long)arg; +#else + int hardcore = (int)arg; +#endif + /* If hardcore is not zero, then the caller REALLY insists that we + delete the route. A drain function would call this with hardcore != 0. */ + + DPRINTF(GROSSEVENT,("Entering ipv6_clean_nexthop... ")); + if (!(rt->rt_flags & RTF_HOST) || + !(rt->rt_flags & (RTF_LLINFO|RTF_TUNNEL|RTF_GATEWAY)) || + /* Keep the static host routes; unless told to delete? */ + ( (rt->rt_flags & (RTF_STATIC)) && !hardcore) || + (!hardcore && rt->rt_refcnt > 0)) + { + /* + * Unless asked (i.e. a hardcore clean :), don't delete held routes. + * Only delete host routes (that aren't my own addresses) this way. + */ + + DPRINTF(GROSSEVENT,("not deleting.\n")); + return 0; + } + + if ((rt->rt_flags & RTF_LLINFO) && rt->rt_refcnt == 0) + { + struct discq *dq = (struct discq *)rt->rt_llinfo; + /* + * This is a neighbor cache entry. Delete only if not held, and + * if in STALE state (i.e. pre-PROBE) for too long. + */ +#ifdef __FreeBSD__ + if (rt->rt_rmx.rmx_expire + v6d_toolong >= time_second && +#else /* __FreeBSD__ */ + if (rt->rt_rmx.rmx_expire + v6d_toolong >= time.tv_sec && +#endif /* __FreeBSD__ */ + dq->dq_unanswered <= 0) + return 0; + /* + * In case clean_nexthop catches one of these non-determinate + * neighbor entries... + * (May be unnecessary if rtfree() ifa != NULL check is in place.) + */ + if (rt->rt_ifa == NULL) + { + rt->rt_ifa = (struct ifaddr *)in6_ifaddr; + rt->rt_ifa->ifa_refcnt++; + } + } + + /* + * At this point, the route is RTF_HOST, and is either a + * force or a legitimate node to be cleaned. Call RTM_DELETE... + */ + DPRINTF(GROSSEVENT,("deleting.\n")); + + return rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL); +} + +/*---------------------------------------------------------------------- + * ipv6_nexthop_timer(): + * Keeps routing table from getting too filled up with off-net host + * routes. + * + * NOTES: + * Later might want to put some intelligence in here like __FreeBSD__ does, + * but for now just do an rnh->rnh_walktree() every so often. + * + * A smarter function might: + * + * - Keep a count of how many off-net host routes we have. + * - Maintain upper bounds on said count. + * - Avoid deletion if said count is low. + * - Etc. + ----------------------------------------------------------------------*/ + +void +ipv6_nexthop_timer(whocares) + void *whocares; +{ + struct radix_node_head *rnh = rt_tables[AF_INET6]; + int s, rc; + + DPRINTF(IDL_EVENT,("Entering ipv6_nexthop_timer().\n")); + s = splnet(); + rc = rnh->rnh_walktree(rnh, ipv6_clean_nexthop, (void *)0); + splx(s); + + DDO(IDL_ERROR,if (rc != 0)\ + printf("walktree rc is %d in nexthop_timer.\n", rc)); + + timeout(ipv6_nexthop_timer,NULL,v6d_nexthopclean * hz); +} + +/*---------------------------------------------------------------------- + * ipv6_discovery_init(): + * Initializes ND data structures. + ----------------------------------------------------------------------*/ +void +ipv6_discovery_init() +{ + dqhead.dq_next = dqhead.dq_prev = &dqhead; + defrtr.v6r_next = defrtr.v6r_prev = &defrtr; + nondefrtr.v6r_next = nondefrtr.v6r_prev = &nondefrtr; + + in6_allones.sin6_family = AF_INET6; + in6_allones.sin6_len = sizeof(in6_allones); + + /* Other fields are bzeroed. */ + in6_allones.sin6_addr.in6a_words[0] = 0xffffffff; + in6_allones.sin6_addr.in6a_words[1] = 0xffffffff; + in6_allones.sin6_addr.in6a_words[2] = 0xffffffff; + in6_allones.sin6_addr.in6a_words[3] = 0xffffffff; + + timeout(ipv6_nexthop_timer,NULL,hz); + timeout(ipv6_neighbor_timer,NULL,hz); +} + +/*---------------------------------------------------------------------- + * get_discov_cluster(): + * Allocates a single-cluster mbuf and sets it up for use by ND. + * + ----------------------------------------------------------------------*/ + +struct mbuf * +get_discov_cluster() +{ + struct mbuf *rc; + + MGET(rc, M_DONTWAIT, MT_HEADER); + if (rc != NULL) + { + MCLGET(rc,M_DONTWAIT); + if ((rc->m_flags & M_EXT) == 0) + { + m_free(rc); + rc = NULL; + } + } + + /* Make it a pkthdr appropriately. */ + if (rc) + { + rc->m_flags |= M_PKTHDR; + rc->m_pkthdr.rcvif = NULL; + rc->m_pkthdr.len = 0; + rc->m_len = 0; + } + return rc; +} + +/*---------------------------------------------------------------------- + * send_nsolicit(): + * Send a neighbor solicit for destination in route entry rt, + * across interface pointed by ifp. Use source address in src (see below), + * and either unicast or multicast depending on the mcast flag. + * + * NOTES: The entry pointed to by rt MUST exist. + * If the caller has to, it may cruft up a dummy rtentry with at least + * a valid rt_key() for the neighbor's IPv6 address, and maybe + * a dummy rt_gateway. + * + * If src points to 0::0 address, set M_DAD flag so ipv6_output() doesn't + * try and fill in a source address. + ----------------------------------------------------------------------*/ +void +send_nsolicit(rt,ifp,src,mcast) + struct rtentry *rt; + struct ifnet *ifp; + struct in6_addr *src; /* Source address of invoking packet... */ + int mcast; +{ + struct mbuf *solicit = NULL; + struct ipv6 *header; + struct ipv6_icmp *icmp; + struct ipv6_moptions i6mo,*i6mop = NULL; + struct in6_ifaddr *i6a; + struct sockaddr_in6 *neighbor = (struct sockaddr_in6 *)rt_key(rt); + struct sockaddr_dl *sdl = (struct sockaddr_dl *)rt->rt_gateway; + + DPRINTF(IDL_EVENT,("Entering send_nsolicit with\n")); + DPRINTF(IDL_EVENT,("rt=0x%lx\n",(unsigned long)rt)); + DPRINTF(IDL_EVENT,("ifp:\n")); + DDO(IDL_EVENT,dump_ifp(ifp)); + DPRINTF(IDL_EVENT,("src:\n")); + DDO(IDL_EVENT,dump_in6_addr(src)); + DPRINTF(IDL_EVENT,("mcast=%d\n",mcast)); + + if ((solicit = get_discov_cluster()) == NULL) + { + DPRINTF(IDL_ERROR, ("Can't allocate mbuf in send_gsolicit().\n")); + return; + } + + header = mtod(solicit,struct ipv6 *); + icmp = (struct ipv6_icmp *)(header + 1);/* I want the bytes after the hdr. */ + + if (mcast) + { + bzero(&i6mo,sizeof(struct ipv6_moptions)); + i6mo.i6mo_multicast_ifp = ifp; + i6mo.i6mo_multicast_ttl = 255; /* Must set, hoplimit is otherwise + overridden! */ + i6mop = &i6mo; + + header->ipv6_dst.in6a_words[0] = htonl(0xff020000); + header->ipv6_dst.in6a_words[1] = 0; + header->ipv6_dst.in6a_words[2] = htonl(1); + header->ipv6_dst.in6a_words[3] = neighbor->sin6_addr.in6a_words[3] | htonl(0xff000000); + } + else + { + if (sdl == NULL || sdl->sdl_alen == 0) + { + DPRINTF(IDL_ERROR, ("Can't unicast if I don't know address.\n")); + m_freem(solicit); + return; + } + header->ipv6_dst = neighbor->sin6_addr; + /* If rmx_expire is not already set, set it to avoid chicken-egg loop. */ + } + + header->ipv6_versfl = htonl(0x6f000000); + header->ipv6_hoplimit = 255; /* Guaranteed to be intra-link if arrives with + 255. */ + header->ipv6_nexthdr = IPPROTO_ICMPV6; + header->ipv6_length = ICMPV6_NSOLMINLEN; /* For now. */ + + /* + * Now find source address for solicit packet. + * + * Rules on src address selection: + * if NULL find link-local for i/f. + * If no link-local, then use unspec source. + * (If unspec source and !mcast, fail) + * if UNSPEC src + * if not mcast, fail. + * if a real address, sanity check to see if it's indeed my address. + * Additionally check to see if it matches the + * outbound ifp requested. + */ + + if (src == NULL) + { + /* Find source link-local or use unspec. */ + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == ifp && (i6a->i6a_addrflags & I6AF_LINKLOC) && + !(i6a->i6a_addrflags & I6AF_NOTSURE)) + break; + if (i6a == NULL) { + if (mcast) + header->ipv6_src = in6addr_any; + else { + DPRINTF(IDL_ERROR, ("send_nsolicit: Unicast solicit w/o any known source address.\n")); + m_freem(solicit); + return; + } + } else + header->ipv6_src = i6a->i6a_addr.sin6_addr; + } + else if (IN6_IS_ADDR_UNSPECIFIED(src)) + if (!mcast) { + DPRINTF(IDL_ERROR, ("send_nsolicit: Unicast DAD solicit.\n")); + m_freem(solicit); + return; + } else { + DPRINTF(GROSSEVENT, ("Sending DAD solicit.\n")); + solicit->m_flags |= M_DAD; + header->ipv6_src = in6addr_any; + } + else + { + struct in6_ifaddr *llsave = NULL; + + for (i6a = in6_ifaddr; i6a; i6a = i6a->i6a_next) + { + /* + * Might want to put llsave code where it's actually needed. + * (i.e. take it out of this loop and put it in i6a == NULL case.) + */ + if (i6a->i6a_ifp == ifp && IN6_IS_ADDR_LINKLOCAL(&i6a->i6a_addr.sin6_addr)) + llsave = i6a; + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, src)) + break; + } + if (i6a == NULL) + { + /* + * This path is entered by a router forwarding packets to a yet + * undiscovered neighbor. + */ +#ifdef DIAGNOSTIC + if (llsave == NULL) + panic("No link-local for this address."); +#endif /* DIAGNOSTIC */ + header->ipv6_src = llsave->i6a_addr.sin6_addr; + } + else if (i6a->i6a_ifp != ifp) + { + /* + * Q: Is this a reason to be panicking? + * A: For now, no. + */ + DDO(IDL_ERROR,\ + printf("WARNING: Src addr fubar Addr, ifp, i6a: ");\ + dump_in6_addr(src);dump_ifp(ifp);\ + dump_ifa((struct ifaddr *)i6a)); +#ifdef DIAGNOSTIC + if (llsave == NULL) + panic("No link-local for this address."); +#endif /* DIAGNOSTIC */ + header->ipv6_src = llsave->i6a_addr.sin6_addr; + } + else header->ipv6_src = *src; + } + + /* Have source address, now create outbound packet */ + icmp->icmp_type = ICMPV6_NEIGHBORSOL; + icmp->icmp_code = 0; + icmp->icmp_unused = 0; + icmp->icmp_cksum = 0; + icmp->icmp_nsoltarg = neighbor->sin6_addr; + + /* + * Put ND extensions here, if any. + * This next code fragment could be its own function if there + * were enough callers of the fragment to make that sensible. + * + * This might also want to be its own function because of variations in + * link (ifp) type. + */ + if (ifp->if_addrlen != 0 && !IN6_IS_ADDR_UNSPECIFIED(&header->ipv6_src)) + { + struct icmp_exthdr *ext = (struct icmp_exthdr *)&(icmp->icmp_nsolext); + struct ifaddr *ifa; + struct sockaddr_dl *lsdl; + + ext->ext_id = EXT_SOURCELINK; + + switch (ifp->if_type) + { + case IFT_ETHER: + ext->ext_length = 1 + ((ifp->if_addrlen + 1) >> 3); +#ifdef __FreeBSD__ + for (ifa = ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (ifa->ifa_addr->sa_family == AF_LINK) + break; + if (ifa == NULL) + { + DPRINTF(IDL_ERROR, ("Can't find link addr. in nsolicit().\n")); + m_freem(solicit); + return; + } + lsdl = (struct sockaddr_dl *)ifa->ifa_addr; + bzero(ext->ext_data,ext->ext_length * 8 - 2); + bcopy(LLADDR(lsdl),ext->ext_data,ifp->if_addrlen); + break; + default: + DPRINTF(IDL_ERROR,("DANGER: unk. link type for n. sol.\n")); + break; + } + + header->ipv6_length += ext->ext_length <<3; + } + + solicit->m_len = solicit->m_pkthdr.len = + header->ipv6_length + sizeof(*header); + icmp->icmp_cksum = in6_cksum(solicit,IPPROTO_ICMPV6,header->ipv6_length, + sizeof(struct ipv6)); + + /* + * NOTE: We pass in a NULL instead of a valid + * socket ptr. When ipv6_output() calls ipsec_output_policy(), + * this socket ptr will STILL be NULL. Sooo, the security + * policy on outbound packets from here will == system security + * level (set in ipsec_init()). If your + * system security level is paranoid, then you won't move packets + * unless you have _preloaded_ keys for at least the ND addresses. + * - danmcd rja + */ + + ipv6_output(solicit, NULL, IPV6_RAWOUTPUT, i6mop, NULL, NULL); +} + +/*---------------------------------------------------------------------- + * ipv6_nsolicit: + * Send an IPv6 Neighbor Solicit message + * + * NOTES: + * State checking is needed so that a neighbor can be declared unreachable. + * + * newrt == NULL iff this is a virgin packet with no known i/f, + * otherwise valid newrt MUST be passed in. + * + * If ifp == NULL, ipv6_nsolicit() executes potentially multihomed code. + * (For now, I guess I should pick an arbitrary interface. For single-homed + * nodes, this is the optimal behavior.) + * This is called when a neighbor is in INCOMPLETE state. + * + * The NUD INCOMPLETE and new-neighbor detection state belongs here. + * + * Not yet clear how will implement duplicate address detection. + * (See send_nsolicit.) + * Should I splnet() inside here? + ----------------------------------------------------------------------*/ +void +ipv6_nsolicit(outifp,outgoing,newrt) + struct ifnet *outifp; + struct mbuf *outgoing; + struct rtentry *newrt; +{ + struct discq *dq; + struct ipv6 *ipv6 = (outgoing == NULL) ? NULL : + mtod(outgoing,struct ipv6 *); + + /* + * ASSERT: The header is pulled up, and has either the + * unspecified address or one of MY valid ipv6 addresses. + */ +#if __NetBSD__ || __FreeBSD__ || __OpenBSD__ + struct ifnet *ifp = (outifp == NULL)?ifnet.tqh_first:outifp; +#else /* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */ + struct ifnet *ifp = (outifp == NULL)?ifnet:outifp; +#endif /* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */ + + DPRINTF(IDL_EVENT,("Entering IPV6_NSOLICIT.\n")); + DPRINTF(IDL_EVENT,("outifp:\n")); + DDO(IDL_EVENT,dump_ifp(outifp)); + DPRINTF(IDL_EVENT,("outgoing=0x%lx\n",(unsigned long)outgoing)); + DPRINTF(IDL_EVENT,("newrt:\n")); + DDO(IDL_EVENT,dump_rtentry(newrt)); + DPRINTF(IDL_EVENT,("ifp:\n")); + DDO(IDL_EVENT,dump_ifp(ifp)); + + if (newrt == NULL) + { + DPRINTF(IDL_ERROR, + ("ipv6_nsolicit() called with newrt == NULL.\n")); + m_freem(outgoing); + return; + } + + /* + * Route with RTF_REJECT has dummy ifa assigned to it in + * ipv6_neighbor_timer(). We shouldn't need to send out + * another solicit if the route is already marked REJECT. + */ + if (newrt->rt_flags & RTF_REJECT) + { + DPRINTF(ERROR, ("ipv6_nsolicit passed a RTF_REJECT route\n")); + m_freem(outgoing); + return; + } + + dq = (struct discq *)newrt->rt_llinfo; + DPRINTF(GROSSEVENT,("dq:\n")); + DDO(GROSSEVENT,dump_discq(dq)); + DDO(IDL_ERROR,if (dq == NULL) \ + panic("dq == NULL in nsolicit().\n"); \ + if (dq->dq_rt != newrt)\ + panic("dq <-> rt mismatch in nsolicit.\n");\ + {\ + struct sockaddr_dl *sdl = (struct sockaddr_dl *)\ + newrt->rt_gateway;\ + \ + if (sdl->sdl_alen != 0)\ + panic("sdl_alen not 0 in nsolicit!"); + }); + DDO(GROSSEVENT,if (outifp == NULL) + printf("nsolicit passed in outifp of NULL.\n")); + + /* + * If new entry, set up certain variables. + */ + if (newrt->rt_rmx.rmx_expire == 0) + dq->dq_unanswered = 0; + + /* + * Currently queue the last packet sent. May want to + * queue up last n packets. + */ + + if (outgoing != NULL) + { + if (dq->dq_queue != NULL) + m_freem(dq->dq_queue); + dq->dq_queue = outgoing; + } + + /* + * We want to rate-limit these, so only send out if the time has + * expired. + */ +#ifdef __FreeBSD__ + if (newrt->rt_rmx.rmx_expire < time_second) +#else /* __FreeBSD__ */ + if (newrt->rt_rmx.rmx_expire < time.tv_sec) +#endif /* __FreeBSD__ */ + { + DPRINTF(IDL_EVENT, + ("ipv6_nsolict: solicit time expired -- send another one!\n")); +#ifdef __FreeBSD__ + newrt->rt_rmx.rmx_expire = time_second + v6d_retranstime; +#else /* __FreeBSD__ */ + newrt->rt_rmx.rmx_expire = time.tv_sec + v6d_retranstime; +#endif /* __FreeBSD__ */ + dq->dq_unanswered++; /* Overload for "number of m-cast probes sent". */ + do + { + if (ifp->if_type != IFT_LOOP && ipv6_enabled(ifp)) + { + if (ipv6 == NULL && dq->dq_queue != NULL) { + ipv6 = mtod(dq->dq_queue,struct ipv6 *); + DPRINTF(IDL_EVENT,("v6_solicit: grabbing src from dq!\n")); + } + DPRINTF(IDL_EVENT,("dq b/f calling send_nsolicit:\n")); + DDO(IDL_EVENT,dump_discq(dq)); + DPRINTF(IDL_EVENT,("ipv6 hdr b/f calling send_nsolicit:\n")); + DDO(IDL_EVENT,dump_ipv6(ipv6)); + send_nsolicit(newrt,ifp,(ipv6 == NULL) ? NULL : &ipv6->ipv6_src, + 1); + } + + if (outifp == NULL) +#ifdef __FreeBSD__ + ifp = ifp->if_link.tqe_next; +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + ifp = ifp->if_list.tqe_next; +#else /* __NetBSD__ || __OpenBSD__ */ + ifp = ifp->if_next; +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + else ifp = NULL; + } + while (ifp != NULL); + } +} + +/*---------------------------------------------------------------------- + * ipv6_onlink_query(): + * If I have no route, and now assume that a destination is on-link, + * then I should create a route entry (I'll have to do it using raw + * radix code, because I don't know which interface the destination is + * for yet.) + * + * I should probably optimize this for single-homed hosts, such that + * the route lookup after doing this will return with an rt_ifa. + ----------------------------------------------------------------------*/ + +void +ipv6_onlink_query(dst) + struct sockaddr_in6 *dst; /* Dest. sockaddr_in6 */ +{ + struct rtentry *rt; + struct sockaddr_dl *sdl; + struct in6_ifnet *i6ifp = in6_ifnet; + struct ifnet *ifp; + int s = splnet(); + + /* + * Pick an interface, trick ipv6_new_neighbor() into adding a route, + * then blank out the interface-specific stuff. + */ + + while (i6ifp != NULL && (i6ifp->i6ifp_ifp->if_flags & IFF_LOOPBACK)) + i6ifp = i6ifp->i6ifp_next; + + if (i6ifp == NULL) + { + DPRINTF(IDL_ERROR,("Oooh boy. No non-loopback i6ifp.\n")); + splx(s); + return; + } + ifp = i6ifp->i6ifp_ifp; + + rt = ipv6_new_neighbor(dst,ifp); + if (rt == NULL) + { + DPRINTF(IDL_ERROR,("ipv6_new_neighbor failed in onlink_query.\n")); + splx(s); + return; + } + + if (rt->rt_gateway->sa_family != AF_LINK) { + DPRINTF(IDL_ERROR,("onlink_query returns route with non AF_LINK gateway.\n")); + splx(s); + return; + } + + sdl = (struct sockaddr_dl *)rt->rt_gateway; + sdl->sdl_index = 0; + sdl->sdl_nlen = 0; + sdl->sdl_alen = 0; + sdl->sdl_slen = 0; + + rt->rt_ifp = NULL; + rt->rt_ifa->ifa_refcnt--; + rt->rt_ifa = NULL; + rt->rt_rmx.rmx_mtu = 0; + + /* + * I think I'm cool, now. So send a multicast nsolicit. + */ + ipv6_nsolicit(NULL,NULL,rt); + splx(s); +} + +/*---------------------------------------------------------------------- + * ipv6_verify_onlink(): + * Verify sockaddr_in6 dst has been determined to be a neighbor + * or not. Will only work on an output/route lookup caused by + * a process that's trapped in the kernel. I do a tsleep of the + * current process while neighbor discovery runs its course. + ----------------------------------------------------------------------*/ + +int +ipv6_verify_onlink(dst) + struct sockaddr_in6 *dst; +{ + struct rtentry *rt = NULL; +#if !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) + struct proc *p = curproc; /* XXX */ +#else /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + struct proc *p = PCPU(curproc); /* XXX */ +#endif /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + + if (!p) /* i.e. if I'm an interrupt-caused output... */ + return 1; + + do + { + if (rt != NULL) + rt->rt_refcnt--; + /* + * Sleep this process, then lookup neighbor route to see if it's + * been updated. + */ + switch (tsleep((caddr_t)&rt, PCATCH , "mhomed", hz)) + { + case 0: + DPRINTF(IDL_ERROR,("How did I awaken?\n")); + /* Fallthrough to... */ + case EWOULDBLOCK: + break; + case EINTR: + case ERESTART: + return EINTR; + } +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)dst,0, 0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)dst,0); +#endif /* __FreeBSD__ */ + } + while (rt && rt->rt_ifa == NULL); + + DPRINTF(FINISHED, ("verify_onlink came up with rt:\n")); + DDO(FINISHED, dump_rtentry(rt)); + if (rt == NULL) + return -1; + rt->rt_refcnt--; + return 0; +} + +/*---------------------------------------------------------------------- + * ipv6_uni_nsolicit(): + * Send a unicast neighbor solicit to neighbor specified in rt. + * Also update the next-probe time. + * + * NOTES: + * This is so small that it might be better inlined. + ----------------------------------------------------------------------*/ + +void +ipv6_uni_nsolicit(rt) + struct rtentry *rt; +{ + struct discq *dq = (struct discq *)rt->rt_llinfo; + +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second + v6d_retranstime; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec + v6d_retranstime; +#endif /* __FreeBSD__ */ + /* If ifa->ifa_addr doesn't work, revert to NULL. */ + send_nsolicit(rt,rt->rt_ifp,&(I6A_SIN(rt->rt_ifa)->sin6_addr),0); + dq->dq_unanswered ++; +} + + +/*---------------------------------------------------------------------- + *ipv6_nadvert(): + * Construct an IPv6 neighbor advertisement, + * and send it out via either unicast or multicast. + * + * NOTES: + * Might later add a proxy advertisement bit or anycast bit to the flags + * parameter. + ----------------------------------------------------------------------*/ +void +ipv6_nadvert(i6a,ifp,dstrt,flags) + struct in6_ifaddr *i6a; + struct ifnet *ifp; + struct rtentry *dstrt; /* If null, send m-cast neighbor advert. */ + uint32_t flags; +{ + struct ipv6_moptions i6mo,*i6mop = NULL; + struct mbuf *advert; + struct ipv6 *ipv6; + struct ipv6_icmp *icmp; + + advert = get_discov_cluster(); + if (advert == NULL) + return; + + ipv6 = mtod(advert,struct ipv6 *); + icmp = (struct ipv6_icmp *)&(advert->m_data[sizeof(struct ipv6)]); + + ipv6->ipv6_src = i6a->i6a_addr.sin6_addr; /* May be different for + proxy. */ + ipv6->ipv6_versfl = htonl(0x6f000000); + ipv6->ipv6_hoplimit = 255; /* Guaranteed to be intra-link if arrives with + 255. */ + ipv6->ipv6_nexthdr = IPPROTO_ICMPV6; + ipv6->ipv6_length = ICMPV6_NADVMINLEN; /* For now */ + icmp->icmp_type = ICMPV6_NEIGHBORADV; + icmp->icmp_code = 0; + icmp->icmp_cksum = 0; + icmp->icmp_nadvbits = flags; + + /* Flags in ipv6_icmp.h are in endian-specific #ifdefs, so + there is no need to HTONL(icmp->icmp_nadvbits); */ + + /* If proxy advert, set proxy bits. */ + icmp->icmp_nadvaddr = i6a->i6a_addr.sin6_addr; + + if (dstrt == NULL) + { + i6mo.i6mo_multicast_ifp = ifp; + i6mo.i6mo_multicast_ttl = 255; /* Must set. */ + i6mop = &i6mo; + SET_IN6_ALLNODES(ipv6->ipv6_dst); + SET_IN6_MCASTSCOPE(ipv6->ipv6_dst, IN6_INTRA_LINK); + } + else ipv6->ipv6_dst = ((struct sockaddr_in6 *)rt_key(dstrt))->sin6_addr; + + /* + * Set up extensions (if any) + */ + + /* + * Perhaps create a separate function to look through interface's address + * list to find my data link address, but if this would really be called + * enough other places... + */ + if (i6a->i6a_ifp->if_addrlen != 0) + { + struct icmp_exthdr *ext = (struct icmp_exthdr *)&(icmp->icmp_nadvext); + struct ifaddr *ifa; + struct sockaddr_dl *lsdl; /* Target's Local Link-layer Sockaddr */ + + ext->ext_id = EXT_TARGETLINK; + switch (i6a->i6a_ifp->if_type) + { + case IFT_ETHER: + ext->ext_length = 1 + ((ifp->if_addrlen +1) >> 3); +#ifdef __FreeBSD__ + for (ifa = ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (ifa->ifa_addr->sa_family == AF_LINK) + break; + if (ifa == NULL) + { + DPRINTF(IDL_ERROR, ("Can't find link addr. in nadvert().\n")); + m_freem(advert); + return; + } + lsdl = (struct sockaddr_dl *)ifa->ifa_addr; + bzero(ext->ext_data,ext->ext_length*8 - 2); + bcopy(LLADDR(lsdl),ext->ext_data,ifp->if_addrlen); + break; + default: + DPRINTF(IDL_ERROR,("DANGER: sending n. adv on unk. link type.\n")); + break; + } + ipv6->ipv6_length += ext->ext_length <<3; + } + + advert->m_len = advert->m_pkthdr.len = ipv6->ipv6_length + sizeof(*ipv6); + icmp->icmp_cksum = in6_cksum(advert,IPPROTO_ICMPV6,ipv6->ipv6_length, + sizeof(struct ipv6)); + ipv6_output(advert,NULL,IPV6_RAWOUTPUT,i6mop, NULL, NULL); +} + +/*---------------------------------------------------------------------- + * ipv6_routersol_input(): + * Handle reception of Router Solicit messages. + * + ----------------------------------------------------------------------*/ +void +ipv6_routersol_input(incoming, extra) + struct mbuf *incoming; + int extra; +{ + /* + * Return and let user-level process deal with it. + */ +} + +/* Add a default router in response to a route request (RTM_ADD/RTM_RESOLVE) */ + +struct v6router *ipv6_add_defrouter_rtrequest(struct rtentry *inrt) +{ +#if 0 + struct rtentry *rt, *defrt=NULL; + struct v6router *v6r; + struct sockaddr_in6 sin6; +#endif /* 0 */ + + DPRINTF(GROSSEVENT, ("ipv6_add_defrouter_rtrequest(inrt=%08x)\n", OSDEP_PCAST(inrt))); + DDO(GROSSEVENT, dump_rtentry(inrt)); + +#if 0 + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + + if (!(rt = inrt->rt_gwroute)) +#ifdef __FreeBSD__ + if (!(rt = inrt->rt_gwroute = rtalloc1(inrt->rt_gateway, 1, 0UL))) +#else /* __FreeBSD__ */ + if (!(rt = inrt->rt_gwroute = rtalloc1(inrt->rt_gateway, 1))) +#endif /* __FreeBSD__ */ + return NULL; + + if (!(v6r = malloc(sizeof(*v6r),M_DISCQ,M_NOWAIT))) { + DPRINTF(IDL_ERROR, ("ipv6_add_defrouter_rtrequest: Can't allocate router list entry.\n")); + /* Q: Do I want to delete the neighbor entry? */ + /*rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL);*/ + return NULL; + }; + + /* + * Assertion: + * Tunnel default routes are added only by manual + * (i.e. user-level) adds and deletes. + * If that's true, then the route in the v6r structure is "held" by + * the default route. Oooh, I'd better make sure that default router + * entries hold their... wait a minute, when I delete neighbors, the + * default router neigbors are searched in the list (and nuked...). + */ + + bzero(v6r,sizeof(*v6r)); + v6r->v6r_rt = rt; + v6r->v6r_children.v6c_next = v6r->v6r_children.v6c_prev = &v6r->v6r_children; + + if (!inrt->rt_gwroute) { + /* + * First default router added, and not a manual add. Add + * default route. + */ + struct sockaddr_in6 mask; + + bzero(&mask,sizeof(mask)); + mask.sin6_family = AF_INET6; + mask.sin6_len = sizeof(struct sockaddr_in6); + + sin6.sin6_addr = in6addr_any; + + DDO(IDL_EVENT,printf("------(Before rtrequest)----------\n");\ + dump_smart_sockaddr((struct sockaddr *)&sin6);\ + printf("----------------\n")); + + insque(v6r,&defrtr); /* To prevent double-adds. */ + if ((rc = rtrequest(RTM_ADD,(struct sockaddr *)&sin6,rt_key(rt), + (struct sockaddr *)&mask,RTF_DEFAULT|RTF_GATEWAY, + &defrt))) { + remque(v6r); + DPRINTF(IDL_ERROR, ("ipv6_add_defrouter_rtrequest: Default route add failed (%d).\n",rc)); + free(v6r, M_DISCQ); + /* Q: Do I want do delete the neighbor entry? */ + /* rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL);*/ + return NULL; + } + defrt->rt_refcnt--; + /*defrt->rt_flags |= RTF_CLONING;*/ + } else + insque(v6r,&defrtr); /* If double-add prevention not needed. */ + + return v6r; +#else /* 0 */ + + if (!inrt->rt_gwroute) +#if __FreeBSD__ + if (!(inrt->rt_gwroute = rtalloc1(inrt->rt_gateway, 1, 0))) +#else /* __FreeBSD__ */ + if (!(inrt->rt_gwroute = rtalloc1(inrt->rt_gateway, 1))) +#endif /* __FreeBSD__ */ + return NULL; + + return &defrtr; +#endif /* 0 */ +}; + +/* Add a default router in response to a router advertisement. + ipv6_add_defrouter_rtrequest ends up getting called underneath because of + the rtalloc1(). */ + +struct v6router *ipv6_add_defrouter_advert(struct in6_addr *addr, int lifetime, struct ifnet *ifp) +{ + struct rtentry *rt, *defrt=NULL; + struct v6router *v6r; + struct sockaddr_in6 sin6; + int rc; + + DPRINTF(IDL_EVENT, ("ipv6_add_defrouter_advert(addr=%08x, lifetime=%d, ifp=%08x)\n", OSDEP_PCAST(addr), lifetime, OSDEP_PCAST(ifp))); + + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + sin6.sin6_addr = *addr; + + /* + * Find it if it already exists. Router adverts come from + * link-local addresses, so doing rtalloc1() will be safe. + * If this function is called because of a manual add, inrt should + * be non-NULL, so this codepath won't be executed. + */ +#if __FreeBSD__ + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)&sin6, 0); +#endif /* __FreeBSD__ */ + if (rt == NULL || !(rt->rt_flags & RTF_LLINFO)) { + if (rt != NULL) + RTFREE(rt); + rt = ipv6_new_neighbor(&sin6,ifp); + } else + rt->rt_refcnt--; + + DDO(GROSSEVENT,printf("After new_neighbor:\n"); dump_smart_sockaddr((struct sockaddr *)&sin6)); + if (rt == NULL) { + DPRINTF(IDL_ERROR, ("ipv6_add_defrouter's new neighbor failed.\n")); + return NULL; + }; + rt->rt_flags |= RTF_DEFAULT|RTF_ISAROUTER; + + if (!(v6r = malloc(sizeof(struct v6router), M_DISCQ, M_NOWAIT))) { + DPRINTF(IDL_ERROR,("ipv6_add_defrouter_advert: Can't allocate router list entry.\n")); + /* Q: Do I want to delete the neighbor entry? */ + /*rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL);*/ + return NULL; + }; + + /* + * Assertion: + * Tunnel default routes are added only by manual + * (i.e. user-level) adds and deletes. + * If that's true, then the route in the v6r structure is "held" by + * the default route. Oooh, I'd better make sure that default router + * entries hold their... wait a minute, when I delete neighbors, the + * default router neigbors are searched in the list (and nuked...). + */ + + bzero(v6r,sizeof(*v6r)); + v6r->v6r_rt = rt; + v6r->v6r_children.v6c_next = v6r->v6r_children.v6c_prev = &v6r->v6r_children; +#ifdef __FreeBSD__ + v6r->v6r_expire = time_second + lifetime; +#else /* __FreeBSD__ */ + v6r->v6r_expire = time.tv_sec + lifetime; +#endif /* __FreeBSD__ */ + + if (defrtr.v6r_next == &defrtr) { + /* + * First default router added, and not a manual add. Add + * default route. + */ + struct sockaddr_in6 mask; + + bzero(&mask,sizeof(mask)); + mask.sin6_family = AF_INET6; + mask.sin6_len = sizeof(struct sockaddr_in6); + + sin6.sin6_addr = in6addr_any; + + DDO(GROSSEVENT,printf("------(Before rtrequest)----------\n");\ + dump_smart_sockaddr((struct sockaddr *)&sin6);\ + printf("----------------\n")); + + insque(v6r, &defrtr); /* To prevent double-adds. */ + if ((rc = rtrequest(RTM_ADD, (struct sockaddr *)&sin6, rt_key(rt), + (struct sockaddr *)&mask, RTF_DEFAULT|RTF_GATEWAY, + &defrt))) { + remque(v6r); + DPRINTF(IDL_ERROR, ("ipv6_add_defrouter_advert: Default route add failed (%d).\n", rc)); + free(v6r, M_DISCQ); + /* Q: Do I want do delete the neighbor entry? */ + /* rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL);*/ + return NULL; + }; + defrt->rt_refcnt--; + /*defrt->rt_flags |= RTF_CLONING;*/ + } else + insque(v6r, &defrtr); /* If double-add prevention not needed. */ + + return v6r; +} + +/*---------------------------------------------------------------------- + * Update a default router entry. + ----------------------------------------------------------------------*/ + +static int +update_defrouter(v6r,icmp) + struct v6router *v6r; + struct ipv6_icmp *icmp; +{ + /* Is this it? What if someone deleted the default route? */ +#ifdef __FreeBSD__ + v6r->v6r_expire = time_second + ntohs(icmp->icmp_radvlifetime); +#else /* __FreeBSD__ */ + v6r->v6r_expire = time.tv_sec + ntohs(icmp->icmp_radvlifetime); +#endif /* __FreeBSD__ */ + return 0; +} + +/*---------------------------------------------------------------------- + * Delete a default router entry. This function may even delete the + * default route itself. + ----------------------------------------------------------------------*/ + +int +ipv6_delete_defrouter(v6r) + struct v6router *v6r; +{ + struct sockaddr_in6 sin6,mask; + struct rtentry *rt = v6r->v6r_rt; + u_long flags = rt->rt_flags; + + DPRINTF(FINISHED, ("In ipv6_delete_defrouter, v6r = 0x%lx\n", (unsigned long)v6r)); + + remque(v6r); + rt->rt_flags |= RTF_UP; /* To prevent double-freeing of the route. */ + while (v6r->v6r_children.v6c_next != &v6r->v6r_children) + { + DDO(IDL_ERROR,printf("Deleting route (0x%lx):\n",\ + (unsigned long)v6r->v6r_children.v6c_next->v6c_route);\ + dump_rtentry(v6r->v6r_children.v6c_next->v6c_route)); + rtrequest(RTM_DELETE,rt_key(v6r->v6r_children.v6c_next->v6c_route),NULL, + NULL,0,NULL); + } + rt->rt_flags = flags; + /* + * Do I RTFREE() or do I rtrequest(RTM_DELETE) or do neither? + * For now, neither, because this is called from either a r. advert. + * advertising 0, or from an RTM_DELETE request for the neighbor. + */ + /*RTFREE(v6r->v6r_rt);*/ + free(v6r,M_DISCQ); + + /* + * What if I'm the last router in the default router list? If so, then + * delete the default route. + */ + + if (defrtr.v6r_next == &defrtr) + { + DPRINTF(IDL_ERROR,\ + ("Last router on list deleted. Deleting default route")); + bzero(&mask,sizeof(mask)); + bzero(&sin6,sizeof(sin6)); + mask.sin6_family = AF_INET6; + mask.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + rt->rt_flags |= RTF_UP; /* To prevent double-freeing. */ + rtrequest(RTM_DELETE,(struct sockaddr *)&sin6,NULL, + (struct sockaddr *)&mask,0,NULL); + rt->rt_flags = flags; + } + + return 1; +} + +/*---------------------------------------------------------------------- + * Given an advertised prefix, a mask, and a link-local, create the + * new address. Write result into "prefix" argument space. + * (Should inline this.) + ----------------------------------------------------------------------*/ + +static void +prefix_concat(prefix,linkloc,mask) + struct in6_addr *prefix,*linkloc,*mask; +{ + prefix->in6a_words[0] = (prefix->in6a_words[0] & mask->in6a_words[0]) | (linkloc->in6a_words[0] & ~mask->in6a_words[0]); + prefix->in6a_words[1] = (prefix->in6a_words[1] & mask->in6a_words[1]) | (linkloc->in6a_words[1] & ~mask->in6a_words[1]); + prefix->in6a_words[2] = (prefix->in6a_words[2] & mask->in6a_words[2]) | (linkloc->in6a_words[2] & ~mask->in6a_words[2]); + prefix->in6a_words[3] = (prefix->in6a_words[3] & mask->in6a_words[3]) | (linkloc->in6a_words[3] & ~mask->in6a_words[3]); +} + +/*---------------------------------------------------------------------- + * ipv6_routeradv_input(): + * Handle reception of Router Advertisement messages. + * + ----------------------------------------------------------------------*/ +void +ipv6_routeradv_input(incoming, extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *ipv6; + struct ipv6_icmp *icmp; + struct v6router *v6r; + struct icmp_exthdr *ext; + int howbig; + + /* + * Hmmmm, do want to handle some things down here in the kernel, but + * what about other things, like addrconf? + */ + + DPRINTF(IDL_EVENT, ("OK, got a router advert.\n")); + if (!ipv6rsolicit) /* If I'm not soliciting routers, ignore this */ + return; + + /* Verify that length looks OK */ + howbig = incoming->m_pkthdr.len - extra; + if (howbig < ICMPV6_RADVMINLEN) + return; + + /* XXX - Assumes that the entire packet fits within MCLBYTES. */ + if (incoming->m_len < incoming->m_pkthdr.len) + if (incoming = m_pullup2(incoming, incoming->m_pkthdr.len)) + return; + + ipv6 = mtod(incoming,struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(incoming, caddr_t) + extra); + + /* Check to see hop count is 255. */ + if (ipv6->ipv6_hoplimit != 255) + { + /* + * Keep stats on this! + */ + DPRINTF(IDL_ERROR, ("Received Router Advertisement with hoplimit != 255.\n")); + return; + } + + /* Verify source address is link-local */ + if (!IN6_IS_ADDR_LINKLOCAL(&ipv6->ipv6_src)) + { + DPRINTF(IDL_ERROR, ("Received Router Advertisement with non link-local source address.\n")); + return; + } + + /* + * I now have a router advertisement. + */ + + /* + * First, find the entry, if available. + */ + for (v6r = defrtr.v6r_next; v6r != &defrtr; v6r = v6r->v6r_next) + if (IN6_ARE_ADDR_EQUAL(&ipv6->ipv6_src, &V6R_SIN6(v6r)->sin6_addr)) + break; + + if (v6r == &defrtr) { + /* Not found. */ + if (icmp->icmp_radvlifetime) { + if (!(v6r = ipv6_add_defrouter_advert(&ipv6->ipv6_src, ntohs(icmp->icmp_radvlifetime), incoming->m_pkthdr.rcvif))) { + printf("Problems adding to default router list.\n"); + v6r = &defrtr; + } + } + /* else fallthrough if no lifetime, and not found. */ + } else { + /* Found. */ + if (icmp->icmp_radvlifetime) { + /* + * Perhaps do some reality checking here. Was the radv snarfed + * off the same interface? Is it the same link address? Is it + * marked M_AUTHENTIC or M_DECRYPTED? + */ + + if (incoming->m_pkthdr.rcvif != v6r->v6r_rt->rt_ifp) { + printf("WARNING: radv for router off different interface.\n"); + v6r = &defrtr; + } else + if (update_defrouter(v6r,icmp)) { + printf("update_defrouter failed on radv_input.\n"); + v6r = &defrtr; + } + } else { + if (ipv6_delete_defrouter(v6r)) /* XXX */ + printf("ipv6_delete_defrouter failed on radv_input.\n"); + v6r = &defrtr; + } + }; + + if (icmp->icmp_radvhop) + ipv6_defhoplmt = icmp->icmp_radvhop; + + if (icmp->icmp_radvbits) + { + /* + * Kick DHCP in the pants to do the right thing(s). + */ + } + + if (icmp->icmp_radvreach) + v6d_reachtime = max(1,ntohl(icmp->icmp_radvreach)/1000); + + if (icmp->icmp_radvretrans) + v6d_retranstime = max(1,ntohl(icmp->icmp_radvretrans)/1000); + + /* + * Handle extensions/options. + */ + ext = (struct icmp_exthdr *)icmp->icmp_radvext; + + DPRINTF(IDL_EVENT,\ + ("Parsing exensions. ext = 0x%lx, icmp = 0x%lx, howbig = %d.\n",\ + (unsigned long)ext, (unsigned long)icmp,howbig)); + + while ((u_long)ext - (u_long)icmp < howbig) + { + struct ext_prefinfo *pre = (struct ext_prefinfo *)ext; + + DPRINTF(GROSSEVENT,("In loop, ext_id = %d.\n",ext->ext_id)); + + switch (ext->ext_id) + { + case EXT_SOURCELINK: + /* + * We already have a v6r that may point to the neighbor. If so, + * fill in the sockaddr_dl part of it, and set the neighbor state + * STALE. + */ + if (v6r != &defrtr) + { + /* + * i.e. if I have a neighbor cache entry... + */ + struct rtentry *rt = v6r->v6r_rt; + struct sockaddr_dl *sdl = (struct sockaddr_dl *)rt->rt_gateway; + struct discq *dq = (struct discq *)rt->rt_llinfo; + + switch (rt->rt_ifp->if_type) + { + case IFT_ETHER: + if (sdl->sdl_alen == 0) + { + sdl->sdl_alen = 6; + bcopy(ext->ext_data,LLADDR(sdl),6); + } + else if (bcmp(LLADDR(sdl),ext->ext_data,6)) + { + DPRINTF(IDL_ERROR,\ + ("WARNING: R. adv is saying lladdr is new.\n")); + } +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec; +#endif /* __FreeBSD__ */ + dq->dq_unanswered = -1; /* in STALE state. + (STALE => expired, + but unans = -1. */ + break; + default: + DPRINTF(IDL_ERROR,\ + ("I haven't a clue what if_type this is.\n")); + break; + } + } + break; + case EXT_PREFIX: + { + /* + * NOTE: We really should handle this section of code off in + * ipv6_addrconf.c, but I didn't have time to move it + * over. + * + * 1. If L bit is set, and A bit is not, then simply add an on-link + * route for this prefix, and be done with it. (If the on-link + * isn't already there, of course!) + * + * But what if I have an i6a that matches this? + * + * 2. Okay, if A bit is set, then do the following: + * a. If prefix is link-local, bail. + * b. Sanity check that preferred <= valid. If not, bail. + * c. Check in6_ifaddr list, see if prefix is present. If so, + * check if autoconf. (i.e. lifetime != 0). If so, update + * lifetimes. (then bail.) + * d. Okay, so it's not present this means we have to: + * i. If L bit is set: + * - If route present (exact prefix match), delete it. + * - Call in6_control, just like ifconfig(8) does. + * - Look up i6a and set expiration times. + * ELSE (no L-bit) + * - Set up i6a manually (gag me). + * - See if there exists an exact prefix match, if so, + * then change its ifa to the new i6a. + */ + struct inet6_aliasreq ifra; + struct sockaddr_in6 *dst = &ifra.ifra_addr,*mask = &ifra.ifra_mask; + +#if 0 + u_char *cp,*cp2,*cp3; +#else /* 0 */ + u_char *cp; +#endif /* 0 */ + int i; + + /* Sanity check prefix length and lifetimes. */ + if (pre->pre_prefixsize == 0 || pre->pre_prefixsize >= 128) + { + DPRINTF(IDL_ERROR,("Prefix size failed sanity check.\n")); + break; + } + if (ntohl(pre->pre_valid) < ntohl(pre->pre_preferred)) + { + DPRINTF(IDL_ERROR,("Lifetimes failed sanity check.\n")); + break; + } + + /* + * Set up dst and mask. + */ + bzero(&ifra,sizeof(ifra)); + dst->sin6_family = AF_INET6; + dst->sin6_len = sizeof(*dst); + dst->sin6_addr = pre->pre_prefix; + mask->sin6_len = sizeof(*mask) - sizeof(struct in6_addr) + + ((pre->pre_prefixsize - 1) / 8) + 1; + cp = (u_char *)&mask->sin6_addr; + for (i = 0; i <= ((pre->pre_prefixsize - 1) / 8) ; i ++) + cp[i] = 0xff; + cp[--i] <<= (8 - (pre->pre_prefixsize % 8)) % 8; + + DDO(GROSSEVENT,printf("mask and prefix are:\n");\ + dump_sockaddr_in6(dst);dump_sockaddr_in6(mask)); + + DP(GROSSEVENT, pre->pre_bits, d); + + if (pre->pre_bits & ICMPV6_PREFIX_AUTO) + { + struct in6_ifaddr *i6a; + + if (IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr)) { + DPRINTF(IDL_ERROR, ("ipv6_routeradv_input: Received router advertisement for link-local prefix\n")); + break; /* Bail. */ + }; + + DPRINTF(GROSSEVENT, ("ipv6_routeradv_input: Scanning interface addresses for the received prefix\n")); + +#if 0 + for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next) + if (bcmp(&i6a->i6a_sockmask.sin6_addr,&mask->sin6_addr, + sizeof(struct in6_addr)) == 0) + { + cp = (u_char *)&i6a->i6a_addr.sin6_addr; + cp2 = (u_char *)&i6a->i6a_sockmask.sin6_addr; + cp3 = (u_char *)&dst->sin6_addr; + for (i = 0; (i >=0 && i < sizeof(struct in6_addr)); i++) + if ((cp[i] ^ cp3[i]) & cp2[i]) + i = -2; + + if (i >= 0 && i6a->i6a_expire != 0 && + ((i6a->i6a_addrflags & I6AF_NOTSURE) == 0)) +#else /* 0 */ + for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next) { + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_sockmask.sin6_addr, &mask->sin6_addr)) + continue; + if ( +(i6a->i6a_addr.sin6_addr.in6a_words[0] ^ dst->sin6_addr.in6a_words[0]) & i6a->i6a_sockmask.sin6_addr.in6a_words[0] || +(i6a->i6a_addr.sin6_addr.in6a_words[1] ^ dst->sin6_addr.in6a_words[1]) & i6a->i6a_sockmask.sin6_addr.in6a_words[1] || +(i6a->i6a_addr.sin6_addr.in6a_words[2] ^ dst->sin6_addr.in6a_words[2]) & i6a->i6a_sockmask.sin6_addr.in6a_words[2] || +(i6a->i6a_addr.sin6_addr.in6a_words[3] ^ dst->sin6_addr.in6a_words[3]) & i6a->i6a_sockmask.sin6_addr.in6a_words[3]) + continue; + + if (i6a->i6a_expire && !(i6a->i6a_addrflags & I6AF_NOTSURE)) +#endif /* 0 */ + { + /* + * Found an autoconfigured i6a. + * + * WARNING: For now, this path will not attempt to + * do route table repair/updating. + * This means if the link bit was off + * before, but on now, you are in trouble. + */ + DDO(FINISHED, printf("Found i6a of:\n");\ + dump_ifa((struct ifaddr *)i6a)); + + if (ntohl(pre->pre_preferred) == 0xffffffff) + i6a->i6a_preferred = ~0; +#ifdef __FreeBSD__ + else i6a->i6a_preferred = time_second + +#else /* __FreeBSD__ */ + else i6a->i6a_preferred = time.tv_sec + +#endif /* __FreeBSD__ */ + ntohl(pre->pre_preferred); + + if (ntohl(pre->pre_valid) == 0xffffffff) + i6a->i6a_expire = ~0; +#ifdef __FreeBSD__ + else i6a->i6a_expire = time_second + +#else /* __FreeBSD__ */ + else i6a->i6a_expire = time.tv_sec + +#endif /* __FreeBSD__ */ + ntohl(pre->pre_valid); + break; + } + } + + DP(GROSSEVENT, OSDEP_PCAST(i6a), 08x); + + if (i6a == NULL) + { + struct rtentry *rt; + struct socket so; +#if __NetBSD__ || __FreeBSD__ + struct proc proc; + struct pcred pcred; + struct ucred ucred; +#endif /* __NetBSD__ || __FreeBSD__ */ + /* + * Need to create new one. + */ + DPRINTF(GROSSEVENT,("Creating new i6a.\n")); + +#if __NetBSD__ || __FreeBSD__ + ucred.cr_uid = 0; + proc.p_cred = &pcred; + proc.p_ucred = &ucred; +#else /* __NetBSD__ || __FreeBSD__ */ + /* + * Do in6_control, like ifconfig does. If L bit is not + * set, delete on-link route. + */ + so.so_state = SS_PRIV; +#endif /* __NetBSD__ || __FreeBSD__ */ + + /* + * Construct address. + */ + + for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == incoming->m_pkthdr.rcvif && + IN6_IS_ADDR_LINKLOCAL(&i6a->i6a_addr.sin6_addr)) + break; + if (i6a == NULL) + { + DPRINTF(IDL_ERROR, + ("Can't find link-local for this if!\n")); + break; + } + + if (i6a->i6a_preflen != pre->pre_prefixsize + || i6a->i6a_preflen == 0) + { + DPRINTF(IDL_ERROR,\ + ("Prefix size problems, i6a_preflen = %d, adv. size = %d.\n",\ + i6a->i6a_preflen,pre->pre_prefixsize)); + break; + } + + prefix_concat(&dst->sin6_addr,&i6a->i6a_addr.sin6_addr, + &mask->sin6_addr); + + if (pre->pre_bits & ICMPV6_PREFIX_ONLINK) + { + /* + * If route already exists, delete it. + */ +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)dst,0,0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)dst,0); +#endif /* __FreeBSD__ */ + if (rt != NULL && + !(rt->rt_flags & (RTF_GATEWAY|RTF_TUNNEL))) + if ((rt_mask(rt) && bcmp(&mask->sin6_addr, + &((struct sockaddr_in6 *)rt_mask(rt))->sin6_addr, + sizeof(struct in6_addr)) == 0) || + (!rt_mask(rt) && (bcmp(&mask->sin6_addr, &in6_allones, + sizeof(mask->sin6_addr)) == 0)) ) + { + /* + * Making sure the route is THIS ONE is as + * simple as checking the masks' equality. + */ + DPRINTF(IDL_EVENT, ("Deleting existing on-link route before autoconfiguring new interface.\n")); + rt->rt_refcnt--; + rtrequest(RTM_DELETE,rt_key(rt),NULL,NULL,0,NULL); + } + if (rt != NULL) + rt->rt_refcnt--; + } + +#if __NetBSD__ || __FreeBSD__ + if (in6_control(&so,SIOCAIFADDR_INET6,(caddr_t)&ifra, incoming->m_pkthdr.rcvif,1, &proc)) +#else /* __NetBSD__ || __FreeBSD__ */ + if (in6_control(&so,SIOCAIFADDR_INET6,(caddr_t)&ifra, incoming->m_pkthdr.rcvif,1)) +#endif /* __NetBSD__ || __FreeBSD__ */ + { + DPRINTF(IDL_ERROR, + ("DANGER: in6_control failed.\n")); + } + else if (!(pre->pre_bits & ICMPV6_PREFIX_ONLINK)) + { + /* + * Router advert didn't specify the prefix as + * being all on-link, so nuke the route. + */ + rtrequest(RTM_DELETE,(struct sockaddr *)dst,NULL, + (struct sockaddr *)mask,0,NULL); + } + for(i6a = in6_ifaddr;i6a != NULL; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &dst->sin6_addr)) + break; + if (i6a != NULL) + { + if (ntohl(pre->pre_preferred) == 0xffffffff) + i6a->i6a_preferred = 0xffffffff; + else i6a->i6a_preferred = +#ifdef __FreeBSD__ + time_second + ntohl(pre->pre_preferred); +#else /* __FreeBSD__ */ + time.tv_sec + ntohl(pre->pre_preferred); +#endif /* __FreeBSD__ */ + if (ntohl(pre->pre_valid) == 0xffffffff) + i6a->i6a_expire = 0xffffffff; + else i6a->i6a_expire = +#ifdef __FreeBSD__ + time_second + ntohl(pre->pre_valid); +#else /* __FreeBSD__ */ + time.tv_sec + ntohl(pre->pre_valid); +#endif /* __FreeBSD__ */ + } + } + } + else if (pre->pre_bits & ICMPV6_PREFIX_ONLINK) + { + /* + * Construct on-link prefix route and add. + * + * WARNING: According to the discovery document, prefixes + * should be kept in a list, though I only do that + * if I have an address on that list. + * The falllout from this is that prefixes don't have + * their lifetimes enforced. + * + * Also, what if I have an i6a for this already? + */ + struct sockaddr_dl sdl; + + bzero(&sdl,sizeof(sdl)); + sdl.sdl_family = AF_LINK; + sdl.sdl_len = sizeof(sdl); + sdl.sdl_index = incoming->m_pkthdr.rcvif->if_index; + rtrequest(RTM_ADD,(struct sockaddr *)dst, + (struct sockaddr *)&sdl, (struct sockaddr *)mask, + 0,NULL); + } + } + break; + case EXT_MTU: + /* + * I'm going to ignore for now. + * + * Processing would include: + * + * 1. Possibly change the ifp->if_mtu + * 2. Traversing all IPv6 routes with this MTU and updating. + * (This could cause TCP pcb's to be updated too.) + */ + break; + default: + /* + * And I quote: + * + * Future version of this protocol may define new option types. + * Receivers MUST silently ignore any options they do not recognize + * and continue processing the message. + */ + break; + } + (u_long)ext += (ext->ext_length << 3); + } +} + +/*---------------------------------------------------------------------- + * ipv6_new_neighbor(): + * Return a new neighbor-cache entry for address sin6 on interface ifp. + * A front-end for rtrequest(RTM_ADD, ...). This returns NULL + * if there is a problem. + * + * NOTES: + * May want to handle case of ifp == NULL. + * ipv6_discov_rtrequest() will handle ancillary structure setup. + ----------------------------------------------------------------------*/ + +struct rtentry * +ipv6_new_neighbor(sin6, ifp) + struct sockaddr_in6 *sin6; /* Neighbor's IPv6 address. */ + struct ifnet *ifp; /* Interface neighbor lies off this. */ +{ + struct sockaddr_dl lsdl; /* Target's Link-local Address sockaddr */ + struct sockaddr *dst = (struct sockaddr *)sin6; + struct sockaddr *gateway = (struct sockaddr *)&lsdl; + struct sockaddr *netmask = (struct sockaddr *)&in6_allones; + struct rtentry *newrt = NULL; + struct ifaddr *ifa; + int flags = RTF_HOST; + +#ifdef __FreeBSD__ + for (ifa = ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (ifa->ifa_addr->sa_family == AF_LINK) + break; + if (ifa == NULL) + { + DPRINTF(IDL_ERROR, + ("ipv6_new_neighbor() can't find AF_LINK for ifp.\n")); + return NULL; + } + + bcopy(ifa->ifa_addr,&lsdl,min(sizeof(lsdl),ifa->ifa_addr->sa_len)); + lsdl.sdl_alen = 0; + lsdl.sdl_nlen = 0; + lsdl.sdl_slen = 0; + +#ifdef DIAGNOSTIC + if (lsdl.sdl_index == 0) + panic("sdl_index is 0 in ipv6_new_neighbor()."); +#endif + + /* ASSUMES: there is enough room for the link address shoved in here */ + if (rtrequest(RTM_ADD,dst,gateway,netmask,flags,&newrt) == EEXIST) { + DPRINTF(FINISHED,("Can't add neighbor that already exists?\n")); + DDO(FINISHED, dump_smart_sockaddr(dst)); + DDO(FINISHED, dump_smart_sockaddr(gateway)); + DDO(FINISHED, dump_smart_sockaddr(netmask)); + DP(FINISHED, flags, d); + } + + if (newrt != NULL) + { + /* Fill in any other goodies, especially MTU. */ + DDO(IDL_EVENT, printf("New route okay, before MTU setup...\n");\ + dump_rtentry(newrt)); + newrt->rt_rmx.rmx_mtu = newrt->rt_ifp->if_mtu; + newrt->rt_refcnt = 0; /* XXX - should decrement instead? */ + DDO(IDL_EVENT,printf("New route okay, after MTU setup...\n");\ + dump_rtentry(newrt)); + } + return newrt; +} + +/*---------------------------------------------------------------------- + * ipv6_neighborsol_input(): + * Handle input processing for Neighbor Solicit messages. + * + ----------------------------------------------------------------------*/ +void +ipv6_neighborsol_input(incoming,extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *ipv6; + struct ipv6_icmp *icmp; + struct rtentry *rt; + struct discq *dq; + struct sockaddr_in6 sin6; + struct sockaddr_dl *sdl; + struct in6_ifaddr *i6a; + struct icmp_exthdr *ext = NULL; + + /* Thanks to ipv6_icmp.h, ICMP_NEIGHBORADV_* are already in network order */ + uint32_t flags = (ipv6forwarding) ? ICMPV6_NEIGHBORADV_RTR : 0; + + if (incoming->m_flags & M_DAD) /* Incoming DAD solicit from me. Ignore. */ + return; + + /* Verify that length looks OK */ + if (incoming->m_pkthdr.len - extra < ICMPV6_NSOLMINLEN) + return; + + /* XXX - Assumes that the entire packet fits within MCLBYTES. */ + if (incoming->m_len < incoming->m_pkthdr.len) + if (incoming = m_pullup2(incoming, incoming->m_pkthdr.len)) + return; + + ipv6 = mtod(incoming,struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(incoming, caddr_t) + extra); + + /* Check to see hop count is 255. */ + if (ipv6->ipv6_hoplimit != 255) + { + /* + * Keep stats on this! + */ + DPRINTF(IDL_ERROR, + ("Received Neighbor Solicit with hoplimit != 255.\n")); + return; + } + + if (IN6_IS_ADDR_MULTICAST(&icmp->icmp_nsoltarg)) + { + DPRINTF(IDL_EVENT, ("Received multicast address in solicit!\n")); + return; + } + /* + * Have a Neighbor Solicit message. + */ + + /* Verify this is for me. */ + /* Eventually proxy & anycast checking will go here. */ + for (i6a = in6_ifaddr ; i6a ; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &icmp->icmp_nsoltarg)) + break; + if (i6a == NULL) + { + /* Not for me. */ + return; + } + + if (i6a->i6a_ifp != incoming->m_pkthdr.rcvif) + { + DPRINTF(IDL_ERROR,\ + ("Received off-link Neighbor Solicit for self\n")); + return; + } + + /* + * Can't process solicits for addresses in DAD phase. + * Furthermore, if solicit comes from all 0's, (and if I made it this far, + * it's not from me), then there's a duplicate. + */ + if (i6a->i6a_addrflags & I6AF_NOTSURE) + { + if (IN6_IS_ADDR_UNSPECIFIED(&ipv6->ipv6_src)) + { + struct socket so; + struct inet6_aliasreq ifra; +#if __NetBSD__ || __FreeBSD__ + struct proc proc; + struct pcred pcred; + struct ucred ucred; +#endif /* __NetBSD__ || __FreeBSD__ */ + printf("Duplicate address detected.\n"); /* NEED to print this. */ + /* + * Delete in6_ifaddr. + */ +#if __NetBSD__ || __FreeBSD__ + ucred.cr_uid = 0; + proc.p_cred = &pcred; + proc.p_ucred = &ucred; +#else /* __NetBSD__ || __FreeBSD__ */ + so.so_state = SS_PRIV; +#endif /* __NetBSD__ || __FreeBSD__ */ + strncpy(ifra.ifra_name,i6a->i6a_ifp->if_name,IFNAMSIZ); + ifra.ifra_addr = i6a->i6a_addr; + ifra.ifra_dstaddr = i6a->i6a_dstaddr; + ifra.ifra_mask = i6a->i6a_sockmask; +#if __NetBSD__ || __FreeBSD__ + in6_control(&so,SIOCDIFADDR_INET6,(caddr_t)&ifra,i6a->i6a_ifp,1, &proc); +#else /* __NetBSD__ || __FreeBSD__ */ + in6_control(&so,SIOCDIFADDR_INET6,(caddr_t)&ifra,i6a->i6a_ifp,1); +#endif /* __NetBSD__ || __FreeBSD__ */ + } + return; + } + + /* + * Create neighbor cache entry for neighbor to send back advertisement. + */ + + if (!IN6_IS_ADDR_UNSPECIFIED(&ipv6->ipv6_src)) + { + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + sin6.sin6_addr = ipv6->ipv6_src; + +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); +#else /* __FreeBSD */ + rt = rtalloc1((struct sockaddr *)&sin6, 0); +#endif /* __FreeBSD */ + + /* + * I just did a passive route lookup. I'll either get: + * + * 1. No route, meaning I have to create one. + * 2. A host neighbor (RTF_LLINFO) route, meaning I might update it. + * 3. A host off-link (RTF_GATEWAY) route, meaning I possibly missed a + * redirect. + * 4. An on-link prefix (RTF_CLONING, no RTF_GATEWAY) route, meaning + * I have to create one, like no route. + * 5. The default route (RTF_DEFAULT), meaning the same as no route. + * 6. A network route, meaning either a subset of that prefix is on-link, + * or my routing table is bogus. I'll create one. + * + * In any case I actually get one, I should decrement the rt_refcnt. + * + * Future support for RTF_TUNNEL needed here. + */ + + DPRINTF(GROSSEVENT,("After rtalloc1().\n")); + if (rt == NULL || !(rt->rt_flags & RTF_HOST) ) + { + /* + * No available host route, create a new entry. + */ + if (rt != NULL) + rt->rt_refcnt--; + + DPRINTF(GROSSEVENT,("Creating new neighbor.\n")); + rt = ipv6_new_neighbor(&sin6,incoming->m_pkthdr.rcvif); + if (rt == NULL) + { + DPRINTF(IDL_ERROR, + ("Can't allocate soliciting neighbor route.\n")); + return; + } + } + else if (rt->rt_flags & RTF_LLINFO) + { + rt->rt_refcnt--; + if (rt->rt_gateway->sa_family != AF_LINK) { + DPRINTF(IDL_ERROR, ("LLINFO but gateway != AF_LINK.")); + return; + }; + } + else + { + /* + * Received Neighbor Solicit from an address which I have + * an off-net host route for. For now, bail. + */ + DPRINTF(FINISHED, + ("Received Neighbor Solicit from unknown target.\n")); + return; + } + + /* + * If new, or inactive, set to probe. + */ + + /* + * All of this data will fit in one mbuf as long as the upper limit + * for ICMP message size <= 576 <= MCLBYTES. + */ + if (incoming->m_pkthdr.len > extra + ICMPV6_NSOLMINLEN) + { + u_char *data = (u_char *)&icmp->icmp_nsolext; + u_char *bounds = data + incoming->m_pkthdr.len - extra - + ICMPV6_NSOLMINLEN; + + /* Only possible extension (so far) in a neighbor advert + is a source link-layer address, but be careful anyway. */ + + ext = (struct icmp_exthdr *)data; + while (ext->ext_id != EXT_SOURCELINK && (data<bounds)) + { + DPRINTF(FINISHED,("Got extension other than source link.\n")); + data += ext->ext_length<<3; + ext = (struct icmp_exthdr *)data; + } + if (data >= bounds) { + DPRINTF(IDL_ERROR, ("couldn't find SOURCELINK")); + return; + }; + } + + sdl = (struct sockaddr_dl *)rt->rt_gateway; + rt->rt_flags &= ~RTF_REJECT; + if (sdl->sdl_alen == 0) /* New or inactive */ + { + if (rt->rt_ifa == NULL) + { + /* + * If multihomed and not sure of interface, take ifp and ifa + * for "My destination," where it does not have to be link- + * local. + */ + rt->rt_ifa = (struct ifaddr *)i6a; + rt->rt_ifp = i6a->i6a_ifp; + rt->rt_ifa->ifa_refcnt++; + sdl->sdl_index = rt->rt_ifp->if_index; + sdl->sdl_type = rt->rt_ifp->if_type; + sdl->sdl_nlen = strlen(rt->rt_ifp->if_name); + bcopy(rt->rt_ifp->if_name,sdl->sdl_data,sdl->sdl_nlen); + rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu; + } + +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec; +#endif /* __FreeBSD__ */ + dq = (struct discq *)rt->rt_llinfo; + dq->dq_unanswered = -1; /* In STALE state */ + /* + * Checks for non-broadcast multiple-access (NBMA) links + * such as PPP, Frame Relay, and ATM are probably needed here. + */ + if (ext != NULL) + switch (rt->rt_ifp->if_type) + { + case IFT_ETHER: + sdl->sdl_alen = 6; + bcopy(ext->ext_data,LLADDR(sdl),sdl->sdl_alen); + break; + default: + DPRINTF(IDL_ERROR,("DANGER: Non-ethernet n. adv.\n")); + break; + } + } + else + { + /* + * For now, ignore if I already have somewhat valid entry. + * Only adverts can affect changes on PROBE or STALE entries. + * (And then, only maybe.) + */ + } + + /* + * Neighbor Cache is now updated. + * Now, send out my unicast advertisement. + * + * NB: ICMPV6_* symbol was already htonl()'d in the header file. + */ + flags |= ICMPV6_NEIGHBORADV_SOL|ICMPV6_NEIGHBORADV_OVERRIDE; + ipv6_nadvert(i6a,i6a->i6a_ifp,rt,flags); + } + else + { + /* + * Send multicast advertisement rather than unicast advertisement + * because the solicit contained the unspecified address. + * + * DAD code may need to be executed in here as well. + */ + ipv6_nadvert(i6a,incoming->m_pkthdr.rcvif,NULL,flags); + } +} + + +/*---------------------------------------------------------------------- + * ipv6_neighboradv_input(): + * Handle reception of a Neighbor Advertisement message. + * + ----------------------------------------------------------------------*/ +void +ipv6_neighboradv_input(incoming,extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *ipv6; + struct ipv6_icmp *icmp; + struct rtentry *rt; + struct sockaddr_in6 sin6; + struct in6_ifaddr *i6a; + int s; + + /* Verify that incoming length looks plausible */ + if (incoming->m_pkthdr.len - extra < ICMPV6_NADVMINLEN) + return; + + /* XXX - Assumes that the entire packet fits within MCLBYTES. */ + if (incoming->m_len < incoming->m_pkthdr.len) + if (incoming = m_pullup2(incoming, incoming->m_pkthdr.len)) + return; + + ipv6 = mtod(incoming,struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(incoming, caddr_t) + extra); + + /* Verify hoplimit == 255 */ + if (ipv6->ipv6_hoplimit != 255) + { + /* + * Keep stats on this! + */ + DPRINTF(IDL_ERROR, + ("Received Neighbor Solicit with hoplimit != 255.\n")); + return; + } + +#if 0 + /* Verify quickly src/node matching. Causes way too many false alarms. */ + if (!IN6_ARE_ADDR_EQUAL(&ipv6->ipv6_src, &icmp->icmp_nadvaddr)) + { + DPRINTF(IDL_ERROR, ("WARNING: Possible proxy, addrs are unequal.\n")); + DPRINTF(IDL_ERROR, ("src=%08x %08x %08x %08x\n", ntohl(ipv6->ipv6_src.in6a_u.words[0]), ntohl(ipv6->ipv6_src.in6a_u.words[1]), ntohl(ipv6->ipv6_src.in6a_u.words[2]), ntohl(ipv6->ipv6_src.in6a_u.words[3]))); + DPRINTF(IDL_ERROR, ("nadvaddr=%08x %08x %08x %08x\n", ntohl(icmp->icmp_nadvaddr.in6a_u.words[0]), ntohl(icmp->icmp_nadvaddr.in6a_u.words[1]), ntohl(icmp->icmp_nadvaddr.in6a_u.words[2]), ntohl(icmp->icmp_nadvaddr.in6a_u.words[3]))); + } +#endif /* 0 */ + + if (IN6_IS_ADDR_MULTICAST(&icmp->icmp_nadvaddr)) + { + DPRINTF(ERROR, ("Received Neighbor Advert with multicast address.\n")); + return; /* For now... */ + } + + /* + * Have a Neighbor Advertisement. + */ + + s = splnet(); + + /* Look to see if it's for one of my addresses. */ + for (i6a = in6_ifaddr ; i6a ; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &icmp->icmp_nadvaddr)) + break; + if (i6a != NULL) + { + if (i6a->i6a_addrflags & I6AF_NOTSURE) + { + struct socket so; + struct inet6_aliasreq ifra; +#if __NetBSD__ || __FreeBSD__ + struct proc proc; + struct pcred pcred; + struct ucred ucred; +#endif /* __NetBSD__ || __FreeBSD__ */ + printf("Duplicate address detected.\n"); /* NEED to print this. */ + /* + * Delete in6_ifaddr. + */ +#if __NetBSD__ || __FreeBSD__ + ucred.cr_uid = 0; + proc.p_cred = &pcred; + proc.p_ucred = &ucred; +#else /* __NetBSD__ || __FreeBSD__ */ + so.so_state = SS_PRIV; +#endif /* __NetBSD__ || __FreeBSD__ */ + strncpy(ifra.ifra_name,i6a->i6a_ifp->if_name,IFNAMSIZ); + ifra.ifra_addr = i6a->i6a_addr; + ifra.ifra_dstaddr = i6a->i6a_dstaddr; + ifra.ifra_mask = i6a->i6a_sockmask; +#if __NetBSD__ || __FreeBSD__ + in6_control(&so,SIOCDIFADDR_INET6,(caddr_t)&ifra,i6a->i6a_ifp,1, &proc); +#else /* __NetBSD__ || __FreeBSD__ */ + in6_control(&so,SIOCDIFADDR_INET6,(caddr_t)&ifra,i6a->i6a_ifp,1); +#endif /* __NetBSD__ || __FreeBSD__ */ + } + else + { + /* For now, ignore advert which is for me. */ + } + + splx(s); + return; + } + + /* Lookup and see if I have something waiting for it... */ + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = icmp->icmp_nadvaddr; + + /* Next 2 lines might not be strictly needed since this is an rtalloc, + but they're included to be safe. */ + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)&sin6,0,0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)&sin6,0); +#endif /* __FreeBSD__ */ + + if (rt != NULL) + rt->rt_refcnt--; + + /* + * After passive route lookup, I have either: + * + * 1. No route, drop the advert. + * 2. Route with no RTF_HOST, drop the advert. + * 3. Route with no RTF_LLINFO, for now, drop the advert, this could be + * a redirect weirdness? + * 4. Route with RTF_LLINFO, try and update. + */ + + if (rt == NULL || !(rt->rt_flags & RTF_HOST)) + { + /* Cases 1 and 2. */ + splx(s); + return; + } + + if (rt->rt_flags & RTF_LLINFO) + { + /* Case 4. */ + struct sockaddr_dl *sdl; + struct icmp_exthdr *liext = NULL; + + /* + * Possibly update the link-layer address, and maybe change to + * REACHABLE state. + */ + rt->rt_flags &= ~RTF_REJECT; /* That neighbor talked to me! */ + + if (incoming->m_pkthdr.len - extra > ICMPV6_NADVMINLEN) + { + u_char *data = (u_char *)&icmp->icmp_nadvext; + u_char *bounds = data + incoming->m_pkthdr.len - extra - + ICMPV6_NADVMINLEN; + struct icmp_exthdr *ext = (struct icmp_exthdr *)data; + + /* Only possible extension (so far) in a neighbor advert is a + source link-layer address, but be careful anyway. */ + + while (ext->ext_id != EXT_TARGETLINK && (data < bounds)) + { + DPRINTF(IDL_EVENT,("Got extension other than source link.\n")); + data += ext->ext_length<<3; + ext = (struct icmp_exthdr *)data; + } + if (data >= bounds) { + DPRINTF(IDL_ERROR,("Received neighbor advertisement with no source link layer address.\n")); + splx(s); + return; + } + liext = ext; + } + + sdl = (struct sockaddr_dl *)rt->rt_gateway; + + if (liext != NULL) + { + if (rt->rt_ifa == NULL) + { + struct in6_ifaddr *i6a; + + /* + * ifa and ifp for this address are null, because it's an + * on-link brute force discovery. Use link-local as source. + */ + rt->rt_ifp = incoming->m_pkthdr.rcvif; + for (i6a = in6_ifaddr ; i6a != NULL; i6a = i6a->i6a_next) + if (i6a->i6a_ifp == rt->rt_ifp && + (i6a->i6a_flags & I6AF_LINKLOC)) + break; + + if (i6a == NULL) + { + DPRINTF(IDL_ERROR, + ("Got advert from interface with no link-local.\n")); + splx(s); + return; + } + rt->rt_ifa = (struct ifaddr *)i6a; + rt->rt_ifa->ifa_refcnt++; + sdl->sdl_index = rt->rt_ifp->if_index; + sdl->sdl_type = rt->rt_ifp->if_type; + sdl->sdl_nlen = strlen(rt->rt_ifp->if_name); + bcopy(rt->rt_ifp->if_name,sdl->sdl_data,sdl->sdl_nlen); + rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu; + } + + /* + * Update neighbor cache link address. + * Should make this code more general than just for Ethernet, + * so we switch off the interface type assigned to the route. + */ + switch (rt->rt_ifp->if_type) + { + case IFT_ETHER: + if (sdl->sdl_alen == 0 || + (icmp->icmp_nadvbits & ICMPV6_NEIGHBORADV_OVERRIDE)) + { + sdl->sdl_alen = 6; + bcopy(&liext->ext_data,LLADDR(sdl),sdl->sdl_alen); + } + else if (bcmp(LLADDR(sdl),&liext->ext_data,6)) + { + /* + * Override bit not set, and have link address already. + * Discard. + */ + DPRINTF(IDL_ERROR,("Danger, got non-override with different link-local address.\n")); + splx(s); + return; + } + break; + } + /* The ICMP_NEIGHBORADV_* bits are already machine-specific. + So no HTONLs/NTOHLs need to be done here. */ + + /* Now in REACHABLE or STALE state, depending on + ICMP_NEIGHBORADV_SOL bit. */ + { + struct discq *dq = (struct discq *)rt->rt_llinfo; + +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second + +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec + +#endif /* __FreeBSD__ */ + ((icmp->icmp_nadvbits & ICMPV6_NEIGHBORADV_SOL)?v6d_reachtime:0); + dq->dq_unanswered = -1; + if (dq->dq_queue != NULL) + { + rt->rt_ifp->if_output(rt->rt_ifp,dq->dq_queue, rt_key(rt), + rt); + dq->dq_queue = NULL; + } + } + } + + /* + * Check for routers becoming hosts, and vice-versa. + */ + if (icmp->icmp_nadvbits & ICMPV6_NEIGHBORADV_RTR) + rt->rt_flags |= RTF_ISAROUTER; + else if (rt->rt_flags & RTF_ISAROUTER) + { + /* + * Deal with router becoming host. + */ + } + } + else + { + /* Case 3. */ + /* Should consider adding a counter for these */ + DPRINTF(ERROR, + ("Received Neighbor Advert for off-link host.\n")); + } + splx(s); +} + +/*---------------------------------------------------------------------- + * ipv6_redirect_input(): + * Handle reception of a Redirect message. + * + ----------------------------------------------------------------------*/ +void +ipv6_redirect_input(incoming,extra) + struct mbuf *incoming; + int extra; +{ + struct ipv6 *ipv6; + struct ipv6_icmp *icmp; + struct rtentry *rt; + struct sockaddr_in6 dst6, gate6, src6; +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + extern int icmp_redirtimeout; +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + + /* XXX - Assumes that the entire packet fits within MCLBYTES. */ + if (incoming->m_len < incoming->m_pkthdr.len) + if (incoming = m_pullup2(incoming, incoming->m_pkthdr.len)) + return; + + ipv6 = mtod(incoming,struct ipv6 *); + icmp = (struct ipv6_icmp *)(mtod(incoming, caddr_t) + extra); + + /* Verify source address is link-local */ + if (!IN6_IS_ADDR_LINKLOCAL(&ipv6->ipv6_src)) { + DPRINTF(IDL_ERROR, ("Received Redirect with non link-local source address.\n")); + return; + }; + + /* Verify hoplimit == 255 */ + if (ipv6->ipv6_hoplimit != 255) { + /* + * Keep stats on this! + */ + DPRINTF(IDL_ERROR, ("Received IPv6 Redirect with hoplimit != 255.\n")); + return; + }; + + /* Verify that incoming length looks plausible */ + if (incoming->m_pkthdr.len - extra < ICMPV6_REDIRMINLEN) { + DPRINTF(IDL_ERROR, ("Received IPv6 Redirect without enough data.\n")); + return; + }; + + /* Verify current next hop == IPv6 src addr */ + bzero(&dst6, sizeof(struct sockaddr_in6)); + dst6.sin6_len = sizeof(struct sockaddr_in6); + dst6.sin6_family = AF_INET6; + dst6.sin6_addr = icmp->icmp_redirdest; + +#ifdef __FreeBSD__ + if (!(rt = rtalloc1((struct sockaddr *)&dst6, 0, 0UL))) { +#else /* __FreeBSD__ */ + if (!(rt = rtalloc1((struct sockaddr *)&dst6, 0))) { +#endif /* __FreeBSD__ */ + DPRINTF(IDL_ERROR, ("Received IPv6 Redirect for unreachable host.\n")); + return; + }; + + if ((rt->rt_gateway->sa_family != AF_INET6) || !IN6_ARE_ADDR_EQUAL(&ipv6->ipv6_src, &((struct sockaddr_in6 *)rt->rt_gateway)->sin6_addr)) { + DPRINTF(IDL_ERROR, ("Received IPv6 Redirect from wrong source.\n")); + return; + }; + + /* No redirects for multicast packets! */ + if (IN6_IS_ADDR_MULTICAST(&icmp->icmp_redirdest)) { + DPRINTF(IDL_ERROR, ("Received Redirect with multicast address.\n")); + return; /* For now... (?) */ + }; + + /* Target must be link local, or same as destination */ + if (!IN6_IS_ADDR_LINKLOCAL(&icmp->icmp_redirtarg) && !IN6_ARE_ADDR_EQUAL(&icmp->icmp_redirtarg, &icmp->icmp_redirdest)) { + DPRINTF(IDL_ERROR, ("Received Redirect with non link-local target addr != dest addr.\n")); + return; + }; + + /* + * We have a valid Redirect. + */ + + bzero(&gate6, sizeof(struct sockaddr_in6)); + gate6.sin6_len = sizeof(struct sockaddr_in6); + gate6.sin6_family = AF_INET6; + gate6.sin6_addr = icmp->icmp_redirtarg; + + bzero(&src6, sizeof(struct sockaddr_in6)); + src6.sin6_len = sizeof(struct sockaddr_in6); + src6.sin6_family = AF_INET6; + src6.sin6_addr = ipv6->ipv6_src; + + rtredirect((struct sockaddr *)&dst6, (struct sockaddr *)&gate6, + NULL, RTF_DONE|RTF_GATEWAY|RTF_HOST, (struct sockaddr *)&src6, +#if defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) + icmp_redirtimeout); +#else /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + (struct rtentry **)0); +#endif /* defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802) */ + return; +}; + +/*---------------------------------------------------------------------- + * ipv6_discov_resolve(): + * Called by LAN output routine. Will either queue outgoing packet + * on the discovery structure that hangs off the route and return 0, + * OR copies LAN MAC address into edst and returns 1. + * + * NOTES: + * Neighbor Unreachable Detection (NUD) should be done here. + * LAN output routine should handle IPv6 multicast -> link multicast + * mapping, unlike arp_resolve, this function doesn't handle multicast. + ----------------------------------------------------------------------*/ + +int ipv6_discov_resolve(ifp, rt, outgoing, dst, edst) + struct ifnet *ifp; + struct rtentry *rt; + struct mbuf *outgoing; + struct sockaddr *dst; + u_char *edst; +{ + struct discq *dq = NULL; + struct sockaddr_dl *sdl; + struct ipv6 *ipv6; + + DPRINTF(IDL_EVENT,("Entering ipv6_discov_resolve().\n")); + if (rt) + dq = (struct discq *)rt->rt_llinfo; + else + { +#ifdef __FreeBSD__ + rt = rtalloc1(dst, 0, 0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1(dst, 0); +#endif /* __FreeBSD__ */ + + /* + * I just did a passive route lookup. I'll either get: + * + * 1. No route, meaning I have to create one. + * 2. A host neighbor (RTF_LLINFO) route, meaning I'm good to go. + * 3. A host off-link route, meaning I possibly missed a redirect. + * 4. An on-link prefix (RTF_CLONING, no RTF_GATEWAY) route, meaning + * I have to create one, like no route. + * 5. The default route (RTF_DEFAULT), meaning the same as no route. + * 6. A network route, meaning either a subset of that prefix is on-link, + * or my routing table is invalid. I'll create a new route entry. + */ + + if (rt == NULL || !(rt->rt_flags & RTF_HOST)) + { + /* + * No available host route, create a new entry. + * (Cases 1, 4, 5, 6.) + */ + if (rt != NULL) + rt->rt_refcnt--; + + rt = ipv6_new_neighbor((struct sockaddr_in6 *)dst,ifp); + if (rt == NULL) + { + DPRINTF(IDL_ERROR, + ("Can't allocate soliciting neighbor route.\n")); + m_freem(outgoing); + return 0; + } + } + else if (rt->rt_flags & RTF_LLINFO) + { + /* (Case 2) */ + rt->rt_refcnt--; + if (rt->rt_gateway->sa_family != AF_LINK) { + DPRINTF(IDL_ERROR, ("LLINFO but gateway != AF_LINK.")); + m_freem(outgoing); + return 0; + } + } + else + { + /* + * I just got a neighbor solicit from an address which I have + * an off-net host route for. For now, bail. (Case 3.) + */ + DPRINTF(ERROR, + ("Received Neighbor Solicit from unknown target.\n")); + return 0; + } + + dq = (struct discq *)rt->rt_llinfo; + } + + if (dq == NULL) + { + DPRINTF(IDL_ERROR, ("No discq structure hanging off route.\n")); + m_freem(outgoing); + return 0; + } + if (dq->dq_rt != rt) { + DPRINTF(IDL_ERROR, ("discov_resolve route passed in (rt) != dq->dq_rt\n")); + m_freem(outgoing); + return 0; + }; + + sdl = (struct sockaddr_dl *)rt->rt_gateway; + if (sdl->sdl_family != AF_LINK) { + DPRINTF(IDL_ERROR, ("ipv6_discov_resolve called with rt->rt_gateway->sa_family == %d.\n", sdl->sdl_family)); + m_freem(outgoing); + return 0; + }; + + if (sdl->sdl_alen == 0) + { + /* + * I'm in INCOMPLETE mode or a new entry. + * + * Also, if this is a LINK-LOCAL address, (or there is some other + * reason that it isn't clear which interface the address is on) + * I might want to send the solicit out all interfaces. + */ + + rt->rt_flags &= ~RTF_REJECT; /* Clear RTF_REJECT in case LAN output + routine caught expiration before + the timer did. */ + ipv6_nsolicit(ifp,outgoing,rt); + return 0; + } + +#ifdef __FreeBSD__ + if (dq->dq_unanswered < 0 && time_second >= rt->rt_rmx.rmx_expire) +#else /* __FreeBSD__ */ + if (dq->dq_unanswered < 0 && time.tv_sec >= rt->rt_rmx.rmx_expire) +#endif /* __FreeBSD__ */ + { + /* + * Timeout on REACHABLE entry. Process accordingly. + * Change the timed out REACHABLE entry into PROBE state. + * ( REACHABLE -> PROBE ) + * PROBE state handling is the job of ipv6_discovery_timer(). + */ +#ifdef __FreeBSD__ + rt->rt_rmx.rmx_expire = time_second + v6d_delfirstprobe; +#else /* __FreeBSD__ */ + rt->rt_rmx.rmx_expire = time.tv_sec + v6d_delfirstprobe; +#endif /* __FreeBSD__ */ + dq->dq_unanswered = 0; + ipv6 = mtod(outgoing,struct ipv6 *); + } + + DPRINTF(GROSSEVENT,("ipv6_discov_resolve() returning 1.\n")); + /* + * Right now, just trust sdl is set up right. May need to change this + * later. + */ + bcopy(LLADDR(sdl),edst, sdl->sdl_alen); + return 1; +} + + +/*---------------------------------------------------------------------- + * tunnel_parent(): + * Set up tunnel state for a network (cloning?) tunnel route. + * Right now, there is no tunnel state unless an IPv6 secure tunnel + * (rt->rt_gateway->sa_family == AF_INET6 && (RTF_CRYPT || RTF_AUTH)) + ----------------------------------------------------------------------*/ + +void tunnel_parent(rt) + register struct rtentry *rt; +{ + struct rtentry *chaser = rt; + + DPRINTF(GROSSEVENT,("ipv6_tunnel_parent():0000-Starting.\n")); + DDO(GROSSEVENT,printf(" rt_flags = 0x%x\n",(unsigned int)rt->rt_flags)); + + /* + * For now, set up master tunnel MTU. Chase rt_gwroute until no more, and + * see if there is either rmx_mtu or ifp->mtu to transfer. This should + * work on both cloning and non-cloning tunnel routes. + * + * Q: Do I want to chase it all the way? Or just to the next one? + * A: For now go to the next one. Change the following "if" to a + * "while" if you want to switch. + * + * Q2: For non-gateway tunnels (i.e. node-to-host tunnels), I may + * need to undo some braindamage. How? + */ + + /* Change "if" to "while" for all-the-way chasing. */ + while (chaser->rt_gwroute != NULL) + chaser = chaser->rt_gwroute; + + DDO(GROSSEVENT,printf("Last route in gwroute chain is:\n");\ + dump_rtentry(rt)); + + if (chaser == rt) + { + /* + * If non-gateway tunnel, find a route for the gateway address. + */ +#ifdef __FreeBSD__ + chaser = rtalloc1(rt->rt_gateway,0,0UL); +#else /* __FreeBSD__ */ + chaser = rtalloc1(rt->rt_gateway,0); +#endif /* __FreeBSD__ */ + if (chaser == NULL) + /* + * Oooh boy, you're on your own, kid! + */ + chaser = rt; + else + { + chaser->rt_refcnt--; + /* else do I want to do that while loop again? */ + } + } + + if (chaser->rt_rmx.rmx_mtu != 0) + { + DPRINTF(GROSSEVENT,("Chaser's route MTU (%d) is set.\n",\ + (int)chaser->rt_rmx.rmx_mtu)); + rt->rt_rmx.rmx_mtu = chaser->rt_rmx.rmx_mtu; + } + else + { + DPRINTF(GROSSEVENT,("Chaser's route MTU is not set. ")); + DPRINTF(GROSSEVENT,("Attempting ifp check.\n")); + if (chaser->rt_ifp == NULL) + { + DPRINTF(IDL_ERROR,\ + ("Can't find ifp. Using IPV6_MINMTU (%d).\n",IPV6_MINMTU)); + rt->rt_rmx.rmx_mtu = IPV6_MINMTU; + } + else + { + DPRINTF(FINISHED,("Found ifp with mtu of (%d).\n",\ + (int)chaser->rt_ifp->if_mtu)); + rt->rt_rmx.rmx_mtu = chaser->rt_ifp->if_mtu; + } + } + + if (chaser->rt_ifp != rt->rt_ifp) + { + /* + * Somewhere along the way, things got messed up. + * (IPv4 tends to confuse ifa_ifwithroute(), and loopback happens.) + * + * For tunnels, set the rt_ifp to the interface the chaser finds. + * Hopefully ipv6_setrtifa() will do the right thing with the ifa. + */ + rt->rt_ifp = chaser->rt_ifp; + } + + /* + * Adjust based on any known encapsulations: + */ + + /* Use the rt->rt_gateway->sa_family to determine kind of tunnel */ + if (rt->rt_gateway->sa_family == AF_INET) /* v6 in v4 tunnel */ + { + /* WARNING: v6-in-v4 tunnel may also have secure bits set. */ + rt->rt_rmx.rmx_mtu -= sizeof(struct ip); + } + else if (rt->rt_gateway->sa_family == AF_INET6) + { + /* Perhaps need to ensure that the rt_gateway is a neighbor. */ + rt->rt_rmx.rmx_mtu -= sizeof(struct ipv6); + } + +#ifdef IPSEC + if (rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) + { + struct socket *so; + + DPRINTF(GROSSEVENT,("tunnel_parent: Setting up secure tunnel state.\n")); + + if (!(rt->rt_netproc = malloc(sizeof(*so), M_SOCKET, M_NOWAIT))) { + DPRINTF(IDL_ERROR, ("tunnel_parent: can't allocate fake socket\n")); + return; + } + + so = (struct socket *)rt->rt_netproc; + bzero(so, sizeof(*so)); + so->so_oobmark = 1; /* really the refcount */ + + if (netproc_alloc(so)) { + DPRINTF(IDL_ERROR, ("tunnel_parent: can't allocate netproc state\n")); + return; + } + + { + struct netproc_requestandstate *r = + &((struct netproc_socketdata *)so->so_netproc)->requests[0]; + void *p; + + r->requestlen = ((rt->rt_flags & RTF_AUTH) ? sizeof(fixedauth) : 0) + + ((rt->rt_flags & RTF_CRYPT) ? sizeof(fixedencrypt) : 0); + + if (!(r->request = malloc(r->requestlen, M_SOOPTS, M_NOWAIT))) { + DPRINTF(IDL_ERROR,("tunnel_child: can't allocate netproc request\n")); + r->requestlen = 0; + return; + } + + p = r->request; + + /* XXX - should be determined at runtime */ + if (rt->rt_flags & RTF_AUTH) { + memcpy(p, &fixedauth, sizeof(fixedauth)); + p += sizeof(fixedauth); + rt->rt_rmx.rmx_mtu -= IPSEC_AH_WORSTPREOVERHEAD + + IPSEC_AH_WORSTPOSTOVERHEAD; + } + + if (rt->rt_flags & RTF_CRYPT) { + memcpy(p, &fixedencrypt, sizeof(fixedencrypt)); + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPREOVERHEAD; + if (rt->rt_flags & RTF_AUTH) + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPOSTOVERHEAD_PLAIN; + else + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPOSTOVERHEAD_COMBINED; + } + } + } +#endif /* IPSEC */ + DPRINTF(GROSSEVENT,("ipv6_tunnel_parent():1000-Done.\n")); +} + +/*---------------------------------------------------------------------- + * tunnel_parent_clean(): + * Frees dummy socket of secure tunnel. + * Any other tunnel state should also be cleaned up here for the parent. + * NOTE: Children won't be cleaned up. + ----------------------------------------------------------------------*/ +void tunnel_parent_clean(rt) + register struct rtentry *rt; +{ + DPRINTF(GROSSEVENT,("tunnel_parent_clean():0000-Starting.\n")); +#ifdef IPSEC + if (rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) + { + struct socket *so = (struct socket *)rt->rt_netproc; + + DPRINTF(GROSSEVENT,("tunnel_parent_clean():0500-It's RTF_CRYPT|RTF_AUTH; now removing.\n")); + if (so == NULL) + { + DPRINTF(IDL_ERROR,("WARNING: Secure tunnel w/o dummy socket.\n")); + return; + } + + if (so->so_netproc) + if (--so->so_oobmark) /* Refcnt != 0 */ + return; + else + netproc_free(so); + + free(so, M_SOCKET); + rt->rt_netproc = NULL; + } +#endif /* IPSEC */ + DPRINTF(GROSSEVENT,("tunnel_parent_clean():1000-Finished.\n")); +} + +/*---------------------------------------------------------------------- + * tunnel_child(): + * Set up tunnel state for a host or cloned tunnel route. + * Right now, there is no tunnel state except for secure tunnels + * ((RTF_CRYPT || RTF_AUTH)) + ----------------------------------------------------------------------*/ + +void tunnel_child(rt) + register struct rtentry *rt; +{ + struct sockaddr_in6 *dst = (struct sockaddr_in6 *)rt_key(rt); + struct sockaddr_in *sin; + + DPRINTF(GROSSEVENT,("tunnel_child():0000-Starting.\n")); + + /* Turn off STATIC flag only if cloned. */ + if (rt->rt_parent != NULL) + rt->rt_flags &= ~RTF_STATIC; + + /* + * If additional tunnel state were needed, it could be hung off rt_llinfo. + * August 14, 1996: Actually, we're now using rt_tunsec to hold the + * tunnel security state information. If we need to + * carry more state information, we'll have to rework + * this. + */ + + if (!(rt->rt_flags & RTF_GATEWAY) && rt->rt_parent != NULL) + { + /* + * If not a gateway route, and cloned, I'll have to do some transforms. + */ + switch (rt->rt_gateway->sa_family) + { + case AF_INET: + if (IN6_IS_ADDR_V4COMPAT(&dst->sin6_addr)) + { + /* Create new self-tunneling IPv6 over IPv4 route. */ + /* DANGER: If original setgate doesn't work properly, this + could be trouble. */ + sin = (struct sockaddr_in *)rt->rt_gateway; + sin->sin_addr.s_addr = dst->sin6_addr.in6a_words[3]; + if (rt_setgate(rt,rt_key(rt),rt->rt_gateway)) + { + DPRINTF(GROSSEVENT, + ("rt_setgate failed in tunnel_child().\n")); + rt->rt_rmx.rmx_mtu = 0; + /* More should probably be done here. */ + } + } + /* else we're in BIG trouble. */ + break; + } + } + +#ifdef IPSEC + if (rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) + { + u_long flagsformtu; + + DPRINTF(GROSSEVENT,("tunnel_child():0300-Setting up secure host tunnel route.\n")); + if (rt->rt_parent == NULL) + { + struct socket *so; + + DPRINTF(GROSSEVENT,("tunnel_child():400-Need to create a fresh socket.\n")); + + if (!(rt->rt_netproc = malloc(sizeof(*so), M_SOCKET, M_NOWAIT))) { + DPRINTF(IDL_ERROR, ("tunnel_child: can't allocate fake socket\n")); + return; + } + + so = (struct socket *)rt->rt_netproc; + bzero(so, sizeof(*so)); + so->so_oobmark = 1; /* really the refcount */ + + if (netproc_alloc(so)) { + DPRINTF(IDL_ERROR, ("tunnel_child: can't allocate netproc state\n")); + return; + } + + { + struct netproc_requestandstate *r = + &((struct netproc_socketdata *)so->so_netproc)->requests[0]; + void *p; + + r->requestlen = ((rt->rt_flags & RTF_AUTH) ? sizeof(fixedauth) : 0) + + ((rt->rt_flags & RTF_CRYPT) ? sizeof(fixedencrypt) : 0); + + if (!(r->request = malloc(r->requestlen, M_SOOPTS, M_NOWAIT))) { + DPRINTF(IDL_ERROR,("tunnel_child: can't allocate netproc request\n")); + r->requestlen = 0; + return; + } + + p = r->request; + + if (rt->rt_flags & RTF_AUTH) { + memcpy(p, &fixedauth, sizeof(fixedauth)); + p += sizeof(fixedauth); + } + + if (rt->rt_flags & RTF_CRYPT) + memcpy(p, &fixedencrypt, sizeof(fixedencrypt)); + } + + flagsformtu = rt->rt_flags; + } else { + struct socket *so = (struct socket *)rt->rt_parent->rt_netproc; + + DPRINTF(GROSSEVENT,("tunnel_child: Using parent's socket.\n")); + if (!so) { + DPRINTF(ERROR, ("tunnel_child: No socket in parent!\n")); + return; + } + + rt->rt_netproc = so; + + /* XXX - Bump refcount. This will break if you have more than about + four million children, but you'll have bigger problems way before + that ever happens - cmetz */ + so->so_oobmark++; + + flagsformtu = rt->rt_parent->rt_flags; + } + + /* XXX - This block of code was a reasonable hack at the time, but isn't + a good idea anymore. - cmetz */ + /* Security Level should be more configurable, possibly using + a sysctl or by calling the IPsec security policy engine */ + if (flagsformtu & RTF_AUTH) + rt->rt_rmx.rmx_mtu -= IPSEC_AH_WORSTPREOVERHEAD + + IPSEC_AH_WORSTPOSTOVERHEAD; + if (flagsformtu & RTF_CRYPT) { + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPREOVERHEAD; + if (rt->rt_flags & RTF_AUTH) + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPOSTOVERHEAD_PLAIN; + else + rt->rt_rmx.rmx_mtu -= IPSEC_ESP_WORSTPOSTOVERHEAD_COMBINED; + } + } +#endif /* IPSEC */ + + if (rt->rt_gwroute != NULL) + { + if (rt->rt_gwroute->rt_ifp != rt->rt_ifp) + rt->rt_ifp = rt->rt_gwroute->rt_ifp; + /* Should also handle Path MTU increases. */ + } + /* else assume ifp is good to go. */ + DPRINTF(GROSSEVENT,("tunnel_child():1000-Finished.\n")); +} + +/*---------------------------------------------------------------------- + * tunnel_child_clean(): + * probably does something like tunnel_parent_clean... + ----------------------------------------------------------------------*/ +void tunnel_child_clean(rt) + register struct rtentry *rt; +{ + DPRINTF(GROSSEVENT,("tunnel_child_clean():0000-Starting.\n")); + +#ifdef IPSEC + if (rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) + { + struct socket *so = (struct socket *)rt->rt_netproc; + + DPRINTF(GROSSEVENT,("tunnel_child_clean():-Removing security state.\n")); + if (so == NULL) + { + DPRINTF(IDL_ERROR,("WARNING: tunnel_child_clean() Secure tunnel w/o dummy socket.\n")); + return; + } + + if (so->so_netproc) + if (--so->so_oobmark) /* Refcnt != 0 */ + return; + else + netproc_free(so); + + free(so, M_SOCKET); + rt->rt_netproc = NULL; + } +#endif /* IPSEC */ + DPRINTF(GROSSEVENT,("tunnel_child_clean():1000-Finished.\n")); +} 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__ */ diff --git a/sys/netinet6/ipv6_icmp.h b/sys/netinet6/ipv6_icmp.h new file mode 100644 index 00000000000..ed8ddd950f1 --- /dev/null +++ b/sys/netinet6/ipv6_icmp.h @@ -0,0 +1,257 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IPV6_ICMP_H +#define _NETINET6_IPV6_ICMP_H 1 + +/* + * ICMPv6 header. + */ + +struct ipv6_icmp +{ + uint8_t icmp_type; + uint8_t icmp_code; + uint16_t icmp_cksum; + union + { + uint32_t ih_reserved; + struct + { + uint16_t ihs_id; + uint16_t ihs_seq; + } ih_idseq; + struct + { + uint8_t ihr_hoplimit; + uint8_t ihr_bits; + uint16_t ihr_lifetime; + } ih_radv; + } icmp_hun; +#define icmp_unused icmp_hun.ih_reserved +#define icmp_nexthopmtu icmp_hun.ih_reserved +#define icmp_paramptr icmp_hun.ih_reserved +#define icmp_echoid icmp_hun.ih_idseq.ihs_id +#define icmp_echoseq icmp_hun.ih_idseq.ihs_seq +#define icmp_grpdelay icmp_hun.ih_idseq.ihs_id +#define icmp_grpunused icmp_hun.ih_idseq.ihs_seq +#define icmp_nadvbits icmp_hun.ih_reserved +#define icmp_radvhop icmp_hun.ih_radv.ihr_hoplimit +#define icmp_radvbits icmp_hun.ih_radv.ihr_bits +#define icmp_radvlifetime icmp_hun.ih_radv.ihr_lifetime + union + { + struct + { + struct ipv6 ido_ipv6; + uint8_t ido_remaining[1]; + } id_offending; + uint8_t id_data[1]; + struct + { + struct in6_addr idn_addr; + uint8_t idn_ext[1]; + } id_neighbor; + struct + { + struct in6_addr idr_addr1; + struct in6_addr idr_addr2; + uint8_t idr_ext[1]; + } id_redirect; + struct + { + uint32_t ida_reachable; + uint32_t ida_retrans; + uint8_t ida_opt[1]; + } id_radv; + } icmp_dun; +#define icmp_offending icmp_dun.id_offending +#define icmp_ipv6 icmp_dun.id_offending.ido_ipv6 + +#define icmp_echodata icmp_dun.id_data + +#define icmp_grpaddr icmp_dun.id_neighbor.idn_addr + +#define icmp_radvreach icmp_dun.id_radv.ida_reachable +#define icmp_radvretrans icmp_dun.id_radv.ida_retrans +#define icmp_radvext icmp_dun.id_radv.ida_opt + +#define icmp_nsoltarg icmp_dun.id_neighbor.idn_addr +#define icmp_nsolext icmp_dun.id_neighbor.idn_ext +#define icmp_nadvaddr icmp_dun.id_neighbor.idn_addr +#define icmp_nadvext icmp_dun.id_neighbor.idn_ext + +#define icmp_redirtarg icmp_dun.id_redirect.idr_addr1 +#define icmp_redirdest icmp_dun.id_redirect.idr_addr2 +#define icmp_redirext icmp_dun.id_redirect.idr_ext +}; + +/* + * ICMPv6 extension constants. + */ + +#define EXT_SOURCELINK 1 +#define EXT_TARGETLINK 2 +#define EXT_PREFIX 3 +#define EXT_REDIR 4 +#define EXT_MTU 5 + +/* + * Extension structures for IPv6 discovery messages. + */ + +struct icmp_exthdr /* Generic extension */ +{ + uint8_t ext_id; + uint8_t ext_length; /* Length is 8 * this field, 0 is invalid. */ + uint8_t ext_data[6]; /* Padded to 8 bytes. */ +}; + +struct ext_prefinfo /* Prefix information */ +{ + uint8_t pre_extid; + uint8_t pre_length; + + uint8_t pre_prefixsize; + uint8_t pre_bits; + + uint32_t pre_valid; + uint32_t pre_preferred; + uint32_t pre_reserved; + + struct in6_addr pre_prefix; +}; + +/* + * Values for pre_bits + */ +#define ICMPV6_PREFIX_ONLINK 0x80 +#define ICMPV6_PREFIX_AUTO 0x40 + +struct ext_redir /* Redirected header */ +{ + uint8_t rd_extid; + uint8_t rd_length; + uint8_t rd_reserved[6]; + struct ipv6 rd_header; +}; + +struct ext_mtu /* Recommended link MTU. */ +{ + uint8_t mtu_extid; + uint8_t mtu_length; + uint16_t mtu_reserved; + uint32_t mtu_mtu; +}; + +/* + * Constants + */ + +/* + * Lower bounds on packet lengths for various types. + * For the error advice packets must first insure that the + * packet is large enought to contain the returned ip header. + * Only then can we do the check to see if enough bits of packet + * data have been returned, since we need to check the returned + * ipv6 header length. + */ +#define ICMPV6_MINLEN 8 /* abs minimum */ +#define ICMPV6_TSLEN (8 + 3 * sizeof (n_time)) /* timestamp */ +#define ICMPV6_NADVMINLEN 24 /* min neighbor advertisement */ +#define ICMPV6_NSOLMINLEN 24 /* min neighbor solicit */ +#define ICMPV6_RADVMINLEN 16 /* min router advertisement */ +#define ICMPV6_RSOLMINLEN 8 /* min router solicit */ +#define ICMPV6_REDIRMINLEN 40 /* min redirect */ +#define ICMPV6_HLPMINLEN (8 + sizeof(struct ipv6) + 8) /* HLP demux len. */ +#define ICMPV6_MAXLEN 576 /* This should be whatever IPV6_MINMTU + will be. I take this to be the WHOLE + packet, including IPv6 header, and any + IPv6 options before the ICMP message. */ + +/* + * Definition of type and code field values. + * ICMPv6 fixes things so that info messages are >= 128. + */ + +/* Error messages and codes. */ + +#define ICMPV6_UNREACH 1 /* dest unreachable, codes: */ +#define ICMPV6_UNREACH_NOROUTE 0 /* No route to dest. */ +#define ICMPV6_UNREACH_ADMIN 1 /* Admin. prohibited */ +#define ICMPV6_UNREACH_NOTNEIGHBOR 2 /* For strict source + routing. */ +#define ICMPV6_UNREACH_ADDRESS 3 /* Address unreach. */ +#define ICMPV6_UNREACH_PORT 4 /* Port unreachable */ +#define ICMPV6_TOOBIG 2 /* Packet too big. */ +#define ICMPV6_TIMXCEED 3 /* time exceeded, code: */ +#define ICMPV6_TIMXCEED_INTRANS 0 /* ttl==0 in transit */ +#define ICMPV6_TIMXCEED_REASS 1 /* Reassembly t.o. */ +#define ICMPV6_PARAMPROB 4 /* ip header bad */ +#define ICMPV6_PARAMPROB_PROB 0 /* Actual incorrect + parameter. */ +#define ICMPV6_PARAMPROB_NEXTHDR 1 /* Bad next hdr. */ +#define ICMPV6_PARAMPROB_BADOPT 2 /* Unrec. option */ + +/* Info messages. */ + +#define ICMPV6_ECHO 128 /* echo service */ +#define ICMPV6_ECHOREPLY 129 /* echo reply */ +#define ICMPV6_GRPQUERY 130 /* Query group membership. */ +#define ICMPV6_GRPREPORT 131 /* Join mcast group. */ +#define ICMPV6_GRPTERM 132 /* Leave mcast group. */ + +#define ICMPV6_ROUTERSOL 133 /* Router solicit. */ +#define ICMPV6_ROUTERADV 134 /* Router advertisement. */ +#define ICMPV6_NEIGHBORSOL 135 /* Neighbor solicit. */ +#define ICMPV6_NEIGHBORADV 136 /* Neighbor advertisement. */ + +#define ICMPV6_REDIRECT 137 /* ICMPv6 redirect. */ + +/* Defined this way to save some HTONL cycles on little-endian boxes. */ +#if BYTE_ORDER == BIG_ENDIAN +#define ICMPV6_NEIGHBORADV_RTR 0x80000000 /* Router flag. */ +#define ICMPV6_NEIGHBORADV_SOL 0x40000000 /* Solicited flag. */ +#define ICMPV6_NEIGHBORADV_OVERRIDE 0x20000000 /* Override flag. */ +#else /* BYTE_ORDER == LITTLE_ENDIAN */ +#define ICMPV6_NEIGHBORADV_RTR 0x80 /* Router flag. */ +#define ICMPV6_NEIGHBORADV_SOL 0x40 /* Solicited flag. */ +#define ICMPV6_NEIGHBORADV_OVERRIDE 0x20 /* Override flag. */ +#endif + +#define ICMPV6_MAXTYPE 137 + +#define ICMPV6_INFOTYPE(type) ((type) >= 128) + +#ifdef KERNEL +#include <netinet6/ipv6_var.h> + +/* Function prototypes */ +void ipv6_icmp_error(struct mbuf *, int, int, uint32_t); +void ipv6_icmp_input(struct mbuf *, int); +void ipv6_gsolicit(struct ifnet *, struct mbuf *, struct rtentry *); +void ipv6_rtrequest(int, struct rtentry *, struct sockaddr *); +int ipv6_icmp_output(struct mbuf *,struct socket *, struct in6_addr *); +int ipv6_icmp_sysctl(int *, u_int, void *, size_t *, void *, size_t); +#if __NetBSD__ || __FreeBSD__ +int ipv6_icmp_usrreq(struct socket *,int, struct mbuf *,struct mbuf *, struct mbuf *, struct proc *); +#else /* __NetBSD__ || __FreeBSD__ */ +int ipv6_icmp_usrreq(struct socket *,int, struct mbuf *,struct mbuf *, struct mbuf *); +#endif /* __NetBSD__ || __FreeBSD__ */ + +void ipv6_routersol_input(struct mbuf *, int); +void ipv6_routeradv_input(struct mbuf *, int); +void ipv6_neighborsol_input(struct mbuf *, int); +void ipv6_neighboradv_input(struct mbuf *, int); +void ipv6_redirect_input(struct mbuf *, int); +#endif + +#endif /* _NETINET6_IPV6_ICMP_H */ 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; +}; 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; +}; diff --git a/sys/netinet6/ipv6_rtrequest.c b/sys/netinet6/ipv6_rtrequest.c new file mode 100644 index 00000000000..7e0bb20e27a --- /dev/null +++ b/sys/netinet6/ipv6_rtrequest.c @@ -0,0 +1,1005 @@ +/* +%%% 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/socket.h> +#include <sys/socketvar.h> +#include <sys/time.h> +#include <sys/kernel.h> +#include <sys/errno.h> +/*#include <sys/ioctl.h>*/ +#include <sys/syslog.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/if_dl.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.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 <sys/debug.h> + +/* + * Globals (and forward function declarations). + */ + +/* + * External globals. + */ +extern struct sockaddr_in6 in6_allones; +extern struct discq dqhead; +extern struct v6router defrtr; +extern struct v6router nondefrtr; + +extern struct in6_ifaddr *in6_ifaddr; + +extern int ipv6forwarding; + +/* + * External function prototypes. + */ + +/* + * General notes: + * + * These functions are mainly called from ipv6_discovery.c but are + * fairly generic and generally useful and so live in their own file. + * danmcd rja + */ +void tunnel_parent __P((struct rtentry *)); +struct v6router *ipv6_add_defrouter_rtrequest(struct rtentry *); +void tunnel_child __P((struct rtentry *)); +void ipv6_nsolicit __P((struct ifnet *, struct mbuf *, struct rtentry *)); +void tunnel_parent_clean __P((struct rtentry *)); +int ipv6_delete_defrouter __P((struct v6router *)); +void tunnel_child_clean __P((struct rtentry *)); + +static int add_defchild __P((struct rtentry *)); +static void add_netchild __P((struct rtentry *)); +void ipv6_setrtifa __P((struct rtentry *)); +static void add_non_default __P((struct rtentry *)); + +/* + * Functions and macros. + */ + + +/*---------------------------------------------------------------------- + * add_defchild(): + * Find the best default router out of our list and use it + * for this destination route. + ----------------------------------------------------------------------*/ +static int +add_defchild(rt) + struct rtentry *rt; +{ + struct v6router *v6r = defrtr.v6r_next; + struct sockaddr_in6 *dst; + + /* + * What if this child turns out to be a tunneling route? OUCH! + * + * One other thing: If rt_key(rt) is link-local, but it hit from + * the default router, then perhaps it should + * go with the ipv6_onlink_query() route. + */ + + dst = (struct sockaddr_in6 *)rt_key(rt); + + if (!IN6_IS_ADDR_LINKLOCAL(&dst->sin6_addr)) + /* + * Since there are no priorities, just pick one. For now, I guess I'll + * Just pick v6r_next. + */ + if (v6r != &defrtr) + { + struct v6child *v6c; + int rc = 0; + + DPRINTF(IDL_EVENT,("About to do rt_setgate. rt before is:\n")); + DDO(IDL_EVENT,dump_rtentry(rt)); + DPRINTF(IDL_EVENT,("Args are (dst, parent->rt_gateway, v6r_rt):\n")); + DDO(IDL_EVENT,dump_smart_sockaddr((struct sockaddr *)dst);\ + dump_smart_sockaddr(rt->rt_parent->rt_gateway);\ + dump_rtentry(v6r->v6r_rt)); + + if (rt_key(v6r->v6r_rt)->sa_family != AF_INET6) + { + /* + * Hmmm, we must be a tunneling default route. Current + * conventional wisdom is that we can only see one in our + * "default router list," and that it was added manually. Using + * that assumption, get things right, including chasing the + * parent and using its rt_gateway sockaddr for rt_setgate. + */ + rt_setgate(rt,(struct sockaddr *)dst,rt->rt_parent->rt_gateway); + rc = 1; + } + else + { + rt_setgate(rt,(struct sockaddr *)dst,rt_key(v6r->v6r_rt)); + rt->rt_flags &= ~RTF_TUNNEL; /* In case cloned off an initially + tunnelling default route. */ + } + + DDO(IDL_GROSS_EVENT,\ + printf("After rt_setgate, rt is:\n");dump_rtentry(rt)); + + rt->rt_llinfo = malloc(sizeof(*v6c),M_DISCQ,M_NOWAIT); + if (rt->rt_llinfo == NULL) + { + DPRINTF(IDL_ERROR, ("add_defchild(): malloc failed.\n")); + /* Perhaps route should be freed here */ + } + else + { + v6c = (struct v6child *)rt->rt_llinfo; + insque(v6c,&v6r->v6r_children); + v6c->v6c_route = rt; + } + return rc; + } + else + { + /* + * Default route hit, but no default router children. + * + * Perhaps delete it? + */ + DPRINTF(IDL_ERROR,\ + ("Default route hit, but no default routers in list.\n")); + } + + /* Either on-link or in trouble. Needs to be coded. */ + DPRINTF(IDL_ERROR, ("On-link or in trouble in ipv6_rtrequest\n")); + + /* + * Convert off-link entry to indeterminate on-link entry. + * Subsequent code after a call to this function will do the right thing. + * (ipv6_rtrequest code to handle new on-link neighbors follows this.) + */ + rt->rt_flags = RTF_UP|RTF_LLINFO|RTF_HOST; + rt->rt_llinfo = NULL; + rt->rt_ifa->ifa_refcnt--; + rt->rt_ifa = NULL; + rt->rt_ifp = NULL; + bzero(rt->rt_gateway,rt->rt_gateway->sa_len); + rt->rt_gateway->sa_family = AF_LINK; + rt->rt_gateway->sa_len = sizeof(struct sockaddr_dl); + rt->rt_rmx.rmx_mtu = 0; + + /* + * QUESTION: If I do this, should I call ipv6_nsolicit() to complete + * this conversion from default route child to non-deterministic + * on-link neighbor? + * + * ANSWER: Yes, but not here. Just return 1, and let later code take + * care of the nsolicit. + */ + return 1; +} + +/*---------------------------------------------------------------------- + * add_netchild(): + * Given a network route child (i.e. non-default), + * put this child in the parent's descendant list. + ----------------------------------------------------------------------*/ + +static void +add_netchild(rt) + struct rtentry *rt; +{ + struct v6router *v6r; + struct v6child *v6c; + + if (rt->rt_parent == NULL) + { + DPRINTF(IDL_ERROR, + ("add_netchild: No parent route. (Must be manually added.)\n")); + return; + } + v6r = (struct v6router *)rt->rt_parent->rt_llinfo; + + v6c = malloc(sizeof(*v6c),M_DISCQ,M_NOWAIT); + if (v6c == NULL) + { + DPRINTF(IDL_ERROR, ("add_netchild(): malloc failed.\n")); + /* Perhaps should free route here */ + return; + } + insque(v6c,v6r->v6r_children.v6c_next); + v6c->v6c_route = rt; + v6c->v6c_parent = v6r; + rt->rt_llinfo = (caddr_t)v6c; +} + +/*---------------------------------------------------------------------- + * add_non_default(): + * add a non-default routing entry. + * + ----------------------------------------------------------------------*/ +static void +add_non_default(rt) + struct rtentry *rt; +{ + struct v6router *newbie; + + if (rt_key(rt)->sa_family != AF_INET6) + { + DPRINTF(IDL_ERROR, + ("IPv6 off-net non-tunnel route w/o IPv6 gwaddr.\n")); + return; + } + + newbie = malloc(sizeof(*newbie),M_DISCQ,M_NOWAIT); + if (newbie == NULL) + { + DPRINTF(IDL_ERROR, ("add_non_default(): malloc failed.\n")); + /* Should probably free route */ + return; + } + bzero(newbie,sizeof(*newbie)); + newbie->v6r_children.v6c_next = newbie->v6r_children.v6c_prev = + &newbie->v6r_children; + insque(newbie,&nondefrtr); + + /* + * On creation of rt, rt_setgate() was called, therefore we take on blind + * faith that an appropriate neighbor cache entry was created. If not, + * we're in deep trouble. + */ + newbie->v6r_rt = rt->rt_gwroute; + rt->rt_llinfo = (caddr_t)newbie; +} + + +/*---------------------------------------------------------------------- + * ipv6_setrtifa(): + * Set route's interface address. Source address selection for + * a route. + * + ----------------------------------------------------------------------*/ +void +ipv6_setrtifa(rt) + struct rtentry *rt; +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)rt_key(rt); + struct sockaddr_in6 *src = (struct sockaddr_in6 *)rt->rt_ifa->ifa_addr; + struct in6_ifaddr *src_in6ifa = (struct in6_ifaddr *)rt->rt_ifa; + struct sockaddr_in6 *mask = (struct sockaddr_in6 *)rt_mask(rt); + struct ifaddr *ifa = NULL; + + DPRINTF(IDL_EVENT,("Entering ipv6_setrtifa.\n")); + + /* + * If I can't find a "better" source address, stick with the one I got. + * + * ASSUMES: rt_ifp is set PROPERLY. This NEEDS to be true. + */ + + /* + * If link-local, use link-local source. + */ + + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + { + if (IN6_IS_ADDR_LINKLOCAL(&src->sin6_addr)) + return; +#if __FreeBSD__ + for (ifa = rt->rt_ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = rt->rt_ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = rt->rt_ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + { + struct sockaddr_in6 *current = (struct sockaddr_in6 *)ifa->ifa_addr; + + if ((current->sin6_family != AF_INET6) || + IS_DEPRECATED((struct in6_ifaddr *)ifa)) + continue; /* For loop. */ + if (IN6_IS_ADDR_LINKLOCAL(¤t->sin6_addr)) + break; /* For loop. */ + } + if (ifa == NULL) + return; /* We're in real trouble. */ + } + + /* + * If v4-compatible, use v4-compatible source address. + */ + if (ifa == NULL && (sin6->sin6_addr.in6a_words[0] == 0 && + sin6->sin6_addr.in6a_words[1] == 0 && + sin6->sin6_addr.in6a_words[2] == 0 && + sin6->sin6_addr.in6a_words[3] != htonl(1) && + (mask == NULL || mask->sin6_len >= + sizeof(*mask) - sizeof(struct in_addr)))) + { + if (IN6_IS_ADDR_V4COMPAT(&src->sin6_addr) && !IS_DEPRECATED(src_in6ifa)) + return; +#if __FreeBSD__ + for (ifa = rt->rt_ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = rt->rt_ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = rt->rt_ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + { + struct sockaddr_in6 *current = (struct sockaddr_in6 *)ifa->ifa_addr; + + if ((current->sin6_family != AF_INET6) + || IS_DEPRECATED((struct in6_ifaddr *)ifa)) + continue; /* For loop. */ + if (IN6_IS_ADDR_V4COMPAT(¤t->sin6_addr)) + break; /* For loop. */ + } + /*if (ifa == NULL) + return;*/ /* if ifa == NULL here, pretend it's global, because + it is global! */ + } + + /* + * If site-local, use a site-local source address. + */ + + if (ifa == NULL && IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) + { + if (IN6_IS_ADDR_SITELOCAL(&src->sin6_addr) && !IS_DEPRECATED(src_in6ifa)) + return; +#ifdef __FreeBSD__ + for (ifa = rt->rt_ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = rt->rt_ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = rt->rt_ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + { + struct sockaddr_in6 *current = (struct sockaddr_in6 *)ifa->ifa_addr; + + if ((current->sin6_family != AF_INET6) + || IS_DEPRECATED((struct in6_ifaddr *)ifa)) + continue; /* For loop. */ + if (IN6_IS_ADDR_SITELOCAL(¤t->sin6_addr)) + break; /* For loop. */ + } + if (ifa == NULL) + return; /* We don't want to potentially pollute the global + internet with site-local traffic. If you feel + differently, comment out this ifa == NULL check + and fallthrough. */ + } + + if (ifa == NULL) + { + /* + * At this point, the address _could_ be anything, but is most likely + * a globally routable address that didn't fit into the previous + * categories. + * + * Do gyrations iff rt->rt_ifa's address is link-local and the dest + * isn't or the src address previously picked is deprecated. + */ + DPRINTF(IDL_GROSS_EVENT,("In default case of ipv6_setrtifa().\n")); + if ((!IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && + IN6_IS_ADDR_LINKLOCAL(&src->sin6_addr)) || IS_DEPRECATED(src_in6ifa)) + { + struct ifaddr *ifa_compat = 0, *ifa_site = 0; + + /* + * For now, pick a non-link-local address using the following + * order of preference: global, compatible, site-local. + * + */ +#ifdef __FreeBSD__ + for (ifa = rt->rt_ifp->if_addrhead.tqh_first; ifa; ifa = ifa->ifa_link.tqe_next) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (ifa = rt->rt_ifp->if_addrlist.tqh_first; ifa; ifa = ifa->ifa_list.tqe_next) +#else /* __NetBSD__ || __OpenBSD__ */ + for (ifa = rt->rt_ifp->if_addrlist; ifa; ifa = ifa->ifa_next) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (ifa->ifa_addr->sa_family == AF_INET6 && + !IS_DEPRECATED((struct in6_ifaddr *)ifa) && + !IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) + if (IN6_IS_ADDR_V4COMPAT(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) + ifa_compat = ifa; + else if (IN6_IS_ADDR_SITELOCAL(&((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr)) + ifa_site = ifa; + else /* globally routable address */ + break; + if (!ifa) + ifa = ifa_compat ? ifa_compat : ifa_site; + } + } + + if (ifa != NULL) + { + /* + * Q: Do I call ipv6_rtrequest (through ifa_rtrequest) if I + * change ifa's on a route? + */ + /*if (rt->rt_ifa && rt->rt_ifa->ifa_rtrequest) + rt->rt_ifa->ifa_rtrequest(RTM_DELETE,rt,rt->rt_gateway);*/ + IFAFREE(rt->rt_ifa); + ifa->ifa_refcnt++; + rt->rt_ifa = ifa; + rt->rt_ifp = ifa->ifa_ifp; /* Is this desirable? */ + /*if (ifa->ifa_rtrequest) + ifa->ifa_rtrequest(RTM_ADD, rt, rt->rt_gateway);*/ + } +} + + +/*---------------------------------------------------------------------- + * ipv6_rtrequest(): + * IPv6-specific route ADD, RESOLVE, and DELETE function. + * Typically called from route.c's rtrequest() function to handle + * the IPv6 unique things. + ----------------------------------------------------------------------*/ + +void +ipv6_rtrequest(req, rt, sa) + int req; + register struct rtentry *rt; + struct sockaddr *sa; /* Ignored parameter, I believe. */ +{ + static struct sockaddr_dl null_sdl = {sizeof(null_sdl), AF_LINK}, *sdlp; + int spray = 0; + + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0000-Starting.\n")); + DPRINTF(IDL_EVENT,("req = %d, rt = 0x%lx, sa = 0x%lx\n",req,\ + (unsigned long)rt, (unsigned long)sa)); + DDO(IDL_EVENT,dump_rtentry(rt)); + + switch (req) + { + case RTM_ADD: + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0100-case RTM_ADD:.\n")); + + /* + * Set route Path MTU if not already set on brand new route. + */ + if (rt->rt_rmx.rmx_mtu == 0) + rt->rt_rmx.rmx_mtu = rt->rt_ifp->if_mtu; + + /* + * An explicitly added "all-ones" mask or no mask at all should be + * a host route. + */ + + if (rt_mask(rt) == NULL || + (rt_mask(rt)->sa_len == sizeof(struct sockaddr_in6) && + IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)rt_mask(rt))->sin6_addr, + &in6_allones.sin6_addr)) + ) + rt->rt_flags |= RTF_HOST; + + /* + * Explicitly add the cloning bit to the route entry. + * (Do we still want to do this?) + * + * One fallout from this is that if someone wants to disable cloning, + * the disabling will have to be explicitly done after adding the route. + */ + if ((rt->rt_flags & RTF_HOST) == 0) + { + /* + * All non-host routes that aren't m-cast are cloning! + */ + if (!IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)rt_key(rt))->sin6_addr)) + rt->rt_flags |= RTF_CLONING; + + if (rt->rt_flags & RTF_TUNNEL) + { + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0150-Calling tunnel_parent().\n")); + tunnel_parent(rt); + } + + if (rt->rt_flags & RTF_GATEWAY) + if (rt_mask(rt)->sa_len == 0) + { + if (rt->rt_flags & RTF_TUNNEL) + { + /* + * Add tunnel to default router list. + */ + ipv6_add_defrouter_rtrequest(rt); + } + else + { + struct v6router *v6r; + /* + * The "default" route has just been added. + * Do default router stuff here. + * + * Search router list to see if I'm already there. + * this avoids double-adding. + */ + + for (v6r = defrtr.v6r_next; v6r != &defrtr; + v6r = v6r->v6r_next) + if (v6r->v6r_rt == rt->rt_gwroute) + break; + + if (v6r == &defrtr) + { + DPRINTF(IDL_ERROR, + ("Calling ipv6_add_defrouter from RTM_ADD.\n")); + ipv6_add_defrouter_rtrequest(rt); + } + + rt->rt_gwroute->rt_flags |= RTF_ISAROUTER; + } + rt->rt_flags |= RTF_DEFAULT; + } + else + if (rt->rt_flags & RTF_TUNNEL) + { + /* + * Perhaps if the tunnel bit is already set here, don't + * do anything. + */ + } + else + { + /* + * A non-default network route has just been added. + * Do non-default router stuff here. + */ + add_non_default(rt); + } + else if (!(rt->rt_flags & RTF_TUNNEL)) + { + /* + * Interface route (i.e. on-link non-host prefix). + */ + DPRINTF(IDL_EVENT,("Setting up i/f route.\n")); + rt_setgate(rt, rt_key(rt), (struct sockaddr *)&null_sdl); + sdlp = (struct sockaddr_dl *)rt->rt_gateway; + sdlp->sdl_type = rt->rt_ifp->if_type; + sdlp->sdl_index = rt->rt_ifp->if_index; + } + } + + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0199-Falling out of RTM_ADD:.\n")); + /* FALLTHROUGH... */ + case RTM_RESOLVE: + { +#ifdef __FreeBSD__ + extern struct ifnet loif[]; + struct ifnet *loifp = loif; +#else /* __FreeBSD__ */ +#if (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) + extern struct ifnet loif; +#else /* (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) */ + extern struct ifnet *loifp; +#endif /* (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) */ +#endif /* __FreeBSD__ */ + + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0200-Now in case RTM_RESOLVE:.\n")); + + /* + * First, check if ifa addr is same as route addr, + * If yes, then wire the loopback interface as the destination. + */ + if ((rt->rt_flags & RTF_HOST) && rt->rt_ifa && + IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, + &I6A_SIN(rt->rt_ifa)->sin6_addr) +#if (defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802)) + && loifp +#endif /* (defined(_BSDI_VERSION) && (_BSDI_VERSION >= 199802)) */ + ) + { + /* Change ifp to loopback and gateway to itself. */ +#ifdef __FreeBSD__ + rt->rt_ifp = loifp; + rt->rt_gateway = rt_key(rt); + rt->rt_rmx.rmx_mtu = loifp->if_mtu; +#else /* __FreeBSD__ */ +#if (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) + rt->rt_ifp = &loif; + rt->rt_gateway = rt_key(rt); + rt->rt_rmx.rmx_mtu = loif.if_mtu; +#else /* (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) */ + rt->rt_ifp = loifp; + rt->rt_gateway = rt_key(rt); + rt->rt_rmx.rmx_mtu = loifp->if_mtu; +#endif /* (!defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802)) */ +#endif /* __FreeBSD__ */ + + if (rt->rt_parent) + rt->rt_flags &= ~(RTF_STATIC | RTF_TUNNEL); + + break; + }; + } + + /* + * Second, check the accuracy of the route's interface address (ifa). + * If possible, update the route's ifa such that it is the "best" + * source address for packets bound for this destination. + * + * This is done to any routes and is subtle enough + * to merit its own separate function: + */ + + ipv6_setrtifa(rt); + + if (!(rt->rt_flags & RTF_HOST)) + { + /* + * This should be the "none of the above" case. For now, + * do nothing and just return. + */ + DPRINTF(IDL_GROSS_EVENT,("In RESOLVE/lateADD, but not RTF_HOST.\n")); + break; + } + + /* + * At this point, I know (1) I'm a host route which has either been + * added manually or (2) I have cloned off to become either: + * + * a tunnelling route child, + * RTF_TUNNEL is set on this entry. + * I should be okay, although tunnel state may become necessary. + * I'm a next-hop cache entry. + * + * a default router child, + * a non-default network route child, + * RTF_GATEWAY is set on this entry. + * I have to insert this child into the router's children list. + * My child entry hangs off rt_llinfo. I'm a next-hop cache + * entry. + * + * an interface (i.e. neighbor) route child, + * a link-local (i.e. off link-local mask) route child + * rt->rt_gateway is AF_LINK + * I should set RTF_LLINFO, and set up a discq entry because I'm + * a neighbor cache entry. (Neighbor caches double as next-hop + * cache entries.) + */ + + if (rt->rt_gateway == NULL) + panic("ipv6_rtrequest(): No rt_gateway at the RTM_RESOLVE code.\n"); + + /* + * Order of bit checking is very important. I check TUNNEL first, + * because it's REALLY special. Then I check GATEWAY or not. + */ + + if (rt->rt_parent != NULL && rt_mask(rt->rt_parent)->sa_len == 0) + { + DPRINTF(IDL_EVENT,("Cloning off default route!\n")); + /* + * Find a default router out of the list, and assign it to this + * child. Clear or add tunnel bits if necessary. + */ + + rt->rt_flags &= ~RTF_STATIC; + /* + * If add_defchild returns 0, then either it's a tunneling + * default route, or a link-local converted to a neighbor entry. + * + * Set 'spray' to the result of add_defchild, because if it is + * a neighbor entry (see AF_LINK case below), then spray will + * indicate the need to do some pre-emptive neighbor adverts. + */ + if (!(spray = add_defchild(rt))) /* If an actual off-net default + route... */ + break; +DPRINTF(IDL_ERROR,("add_defchild returned 1, either on-link or tunnel.\n")); + /* Otherwise, either tunnel or converted into LINK address. */ + } + else rt->rt_llinfo = NULL; /* Is this already done? */ + + if (rt->rt_flags & RTF_TUNNEL) + { + DPRINTF(IDL_EVENT,("ipv6_rtrequest():0250-Calling tunnel_child().\n")); + tunnel_child(rt); /* Be careful if manually added RTF_TUNNEL. */ + break; /* We're done with this Tunnel host route. */ + } + + if (rt->rt_flags & RTF_GATEWAY) + { + add_netchild(rt); + if (req == RTM_RESOLVE) + rt->rt_flags &= ~RTF_STATIC; + break; + } + + if (rt->rt_gateway->sa_family == AF_LINK) + { + /* + * I may enter here even after RTF_GATEWAY check because + * get_defrouter() may convert the route entry into an on-link one! + */ + struct discq *dq; + + rt->rt_llinfo = malloc(sizeof(*dq),M_DISCQ,M_NOWAIT); + dq = (struct discq *)rt->rt_llinfo; + if (dq == NULL) + { + DPRINTF(IDL_ERROR, + ("ipv6_rtrequest(): malloc for rt_llinfo failed.\n")); + /* Probably should free route */ + break; + } + bzero(dq,sizeof(*dq)); + dq->dq_rt = rt; + dq->dq_unanswered = -1; + rt->rt_flags |= RTF_LLINFO; + insque(dq,&dqhead); + /* + * State is already INCOMPLETE, because of link stuff. + * + * If this neigbor entry is caused by add_defchild (i.e. link-local) + * spray nsolicit out all interfaces. + */ + + if (spray) + ipv6_nsolicit(NULL,NULL,rt); + + break; + } + else +#if 0 + /* + * support for PPP goes here. + * Frame Relay, and ATM will probably probably be handled by the + * AF_LINK case. Other AF_* will be covered later. + */ + panic("ipv6_rtrequest: Not tunnel, not off-net, and not neighbor."); +#else /* 0 */ + { + DPRINTF(IDL_ERROR, ("ipv6_rtrequest: Not tunnel, not off-net, and not neighbor. rt is:\n")); + DDO(IDL_ERROR, dump_rtentry(rt)); + } +#endif /* 0 */ + + break; + case RTM_DELETE: + DPRINTF(IDL_GROSS_EVENT,("ipv6_rtrequest():0300-Now in case RTM_DELETE.\n")); + /* + * The FLUSH call ('route flush...') checks the ifp, perhaps I should + * fill that just in case. + */ + + if (rt->rt_ifp == NULL) + rt->rt_ifp = rt->rt_ifa->ifa_ifp; + + if ((rt->rt_flags & RTF_HOST) == 0) + { + /* + * Clean up after network routes. + */ + if (rt->rt_flags & RTF_TUNNEL) + { + DPRINTF(IDL_EVENT,("Cleaning up tunnel route.\n")); + tunnel_parent_clean(rt); + if (rt_mask(rt)->sa_len != 0) + return; /* If default route is RTF_TUNNEL, then continue. */ + } + if (rt->rt_flags & RTF_GATEWAY) + if (rt_mask(rt)->sa_len == 0) + { + struct v6router *v6r; + + DPRINTF(IDL_GROSS_EVENT,\ + ("Cleaning up THE default route.\n")); + /* + * Find manually added default route thing, and clean up that + * entry. + * + * If user deletes default route added by receiving router + * adverts, then the router is still on the default router + * list, and needs to be added back. + */ + for (v6r = defrtr.v6r_next; v6r != &defrtr; + v6r = v6r->v6r_next) + { + /* + * PROBLEM: rt->rt_gwroute, which is what I REALLY + * want to check against, is gone at this + * point. + * + * POSSIBLE SOLUTION: Do a check with rt_key(v6r->v6r_rt) + * and rt->rt_gateway, but there + * is the question of tunneling + * default routes, etc., which may + * mean doing a masked match. + */ +#define equal(a1, a2) (bcmp((caddr_t)(a1), (caddr_t)(a2), (a1)->sa_len) == 0) + /* + * PRESENTED SOLUTION: Try checking the expiration + * (easy giveaway for manually + * added default routes), followed + * by blatant sockaddr compares + * (will nail all other normal + * cases), followed by sa_family + * check (may be unneccesary). + */ + if (v6r->v6r_expire == 0 || + equal(rt_key(v6r->v6r_rt),rt->rt_gateway) || + (rt->rt_gateway->sa_family != AF_INET6 && + rt->rt_gateway->sa_family == + rt_key(v6r->v6r_rt)->sa_family)) + break; + } + if (v6r != &defrtr) + (void)ipv6_delete_defrouter(v6r); + if (defrtr.v6r_next != &defrtr) + { + /* + * Somehow re-add the default route. + * + * The default route has been deleted from the radix + * tree already, so re-adding should be relatively + * straightforward. + */ + DPRINTF(IDL_ERROR, + ("Auto-added default routers still there!\n")); + } + } + else + { + struct v6router *v6r = (struct v6router *)rt->rt_llinfo; + struct v6child *v6c; + + /* Non-default router. */ + DPRINTF(IDL_GROSS_EVENT,\ + ("Cleaning up a non-{default,host} route.\n")); + + if (v6r == NULL) + panic("Non-default w/o v6router entry."); + + v6c = v6r->v6r_children.v6c_next; + while (v6c != v6c->v6c_next) + { + DPRINTF(IDL_ERROR,("Calling RTM_DELETE of child.\n")); + /* rtrequest() should remove child from the linked list */ + rtrequest(RTM_DELETE,rt_key(v6c->v6c_route),NULL,NULL,0, + NULL); + v6c = v6r->v6r_children.v6c_next; + } + remque(v6r); + free(v6r,M_DISCQ); + } + /* Anything else that isn't a HOST needs no work, so return. */ + return; + } + + DPRINTF(IDL_EVENT,("v6_discov_rtrequest() deleting a host\n")); + DPRINTF(IDL_EVENT,("I'm at a host-route point.\n")); + + if (rt->rt_flags & RTF_TUNNEL) + { + DPRINTF(IDL_EVENT,("Tunneling child.\n")); + /* + * PROBLEM: Following check will die if parent went bye-bye. + * See if you can fix in another way. + */ + /*if (rt_mask(rt->rt_parent)->sa_len == 0)*/ + if (rt->rt_flags & RTF_DEFAULT) + { + /* + * Tunneling default route child. Clean off meta-state. + */ + struct v6child *v6c = (struct v6child *)rt->rt_llinfo; + + DPRINTF(IDL_ERROR,("Cleaning tunnel-default child.\n")); + remque(v6c); + rt->rt_llinfo = NULL; + free(v6c,M_DISCQ); + } + tunnel_child_clean(rt); + } + else if (rt->rt_flags & RTF_GATEWAY) + { + struct v6child *v6c = (struct v6child *) rt->rt_llinfo; + + if (v6c == NULL) + { + if (rt->rt_flags & RTF_HOST) + DPRINTF(IDL_ERROR, + ("no v6c in RTM_DELETE of RTF_GATEWAY.\n")); + } + else + { + remque(v6c); + rt->rt_llinfo = NULL; + free(v6c,M_DISCQ); + } + } + else if (rt->rt_flags & RTF_LLINFO) + { + struct discq *dq; + + /* Neighbor cache entry. */ + if (rt->rt_flags & RTF_ISAROUTER) + { + struct v6router *v6r,*head; + + /* Clean up all children of this router. */ + + DPRINTF(IDL_GROSS_EVENT,("Cleaning up router neighbor.\n")); + if (rt->rt_flags & RTF_DEFAULT) + head = &defrtr; + else + { + head = &nondefrtr; + /* Q: Do I want to delete the actual network route too? */ + /* Q2: Do I want to delete the children? */ + } + + for (v6r = head->v6r_next; v6r != head; v6r = v6r->v6r_next) + if (v6r->v6r_rt == rt) + break; + if (v6r == head) + { + /* + * At many addresses per interface, this isn't a huge + * problem, because a router's on-link address might not + * be tied to a router entry. + */ + DPRINTF(IDL_EVENT, + ("Router neighbor inconsistency.\n")); + } + else + if (head == &defrtr) + (void) ipv6_delete_defrouter(v6r); /* This gets rid of + children, too! */ + } + + dq = (struct discq *)rt->rt_llinfo; + rt->rt_flags &= ~RTF_LLINFO; + if (dq == NULL) + panic("No discq or other rt_llinfo in RTM_DELETE"); + remque(dq); + rt->rt_llinfo = NULL; + if (dq->dq_queue) + { + /* Send ICMP unreachable error. */ + ipv6_icmp_error(dq->dq_queue, ICMPV6_UNREACH, + ICMPV6_UNREACH_ADDRESS, 0); + /* m_freem(dq->dq_queue);*/ + } + free(dq,M_DISCQ); + } + else + { + DPRINTF(IDL_GROSS_EVENT,\ + ("Freeing self-wired address. Doing nothing.\n")); + } + break; + } + DPRINTF(IDL_GROSS_EVENT,("ipv6_rtrequest():1000-Finished.\n")); +} + +/* EOF */ diff --git a/sys/netinet6/ipv6_trans.c b/sys/netinet6/ipv6_trans.c new file mode 100644 index 00000000000..adfd61710b9 --- /dev/null +++ b/sys/netinet6/ipv6_trans.c @@ -0,0 +1,669 @@ +/* +%%% 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/socket.h> +#include <sys/protosw.h> +#include <sys/domain.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/socketvar.h> + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_var.h> +#include <netinet/ip_icmp.h> + +#include <netinet6/in6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> + +#include <sys/debug.h> + +#if __OpenBSD__ && defined(NRL_IPSEC) +#define IPSEC 1 +#endif /* __OpenBSD__ && defined(NRL_IPSEC) */ + +/* + * External globals. + */ + +extern struct in6_ifaddr *in6_ifaddr; +extern int ipv6_defhoplmt; + +int ipv6_trans_mtu __P((struct mbuf **, int, int)); +int ipv6_trans_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *)); +int ipv6_encaps_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *)); +int ipv6_tunnel_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *)); +int ipv4_trans_output __P((struct mbuf *, struct sockaddr_in6 *, struct rtentry *)); +int ipv4_encaps_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *)); +int ipv4_tunnel_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *)); + +/*---------------------------------------------------------------------- + * Called from ip_icmp.c, this function will reduce the tunnel path MTU + * precisely. I know I have enough to reconstruct the IPv6 header, which + * is all I care about for this case. Return 1 if m0 is intact, and 0 if + * m0 is corrupted somehow. Don't forget to update m0. + ----------------------------------------------------------------------*/ + +int +ipv6_trans_mtu(m0, newmtu, len) + struct mbuf **m0; + int newmtu,len; +{ + struct ip *ip,*iip; + struct ipv6 *ipv6; + struct icmp *icp; + struct rtentry *rt; + struct sockaddr_in6 sin6; + struct in6_ifaddr *i6a; + + /* + * Make packet contiguous into one block of memory. If the IPv6 header is + * beyond MCLBYTES into the packet, then I'm in big trouble. + */ + *m0 = m_pullup2(*m0,min(len,MCLBYTES)); + if (*m0 == NULL) + return 0; + + ip = mtod(*m0,struct ip *); + icp = (struct icmp *) ((caddr_t)ip + (ip->ip_hl << 2)); + iip = &icp->icmp_ip; + ipv6 = (struct ipv6 *) ((caddr_t)iip + (iip->ip_hl << 2)); + + /* + * Verify source is one of mine? + */ + for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &ipv6->ipv6_src)) + break; + if (i6a == NULL) + { + /* Packet didn't originate with me. Drop it. */ + return 1; + } + + /* + * Find route for this destination and update it. + */ + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + sin6.sin6_addr = ipv6->ipv6_dst; + +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)&sin6, 0); +#endif /* __FreeBSD__ */ + + if (rt == NULL) + return 1; + rt->rt_refcnt--; + /* + * Update path MTU. + */ + if (!(rt->rt_flags & RTF_HOST)) + return 1; /* Can't update path MTU on non-host-route. */ + if (rt->rt_rmx.rmx_mtu < newmtu - sizeof(struct ip)) + panic("MTU WEIRDNESS !!!"); + rt->rt_rmx.rmx_mtu = newmtu - sizeof(struct ip); + return 1; +} + +/*---------------------------------------------------------------------- + * Handle ICMP errors for IPv6-in-IPv4 tunnels. + * + * Security processing should be put in here, as it was with the other + * ctlinput() functions, but with current ICMP implementations returning + * only sizeof(struct ip) + 64 bits of offending packet. + ----------------------------------------------------------------------*/ +#if __OpenBSD__ +void *ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, void *vp, struct mbuf *incoming) +#else /* __OpenBSD__ */ +void ipv6_trans_ctlinput(int cmd, struct sockaddr *sa, register struct ip *ip, struct mbuf *incoming) +#endif /* __OpenBSD__ */ +{ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + struct sockaddr_in6 sin6; + struct ipv6 *ipv6; + struct rtentry *rt; + struct in6_ifaddr *i6a; +#if __OpenBSD__ + struct ip *ip = (struct ip *)vp; +#endif /* __OpenBSD__ */ + + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = 0; + sin6.sin6_flowinfo = 0; + DPRINTF(IDL_EVENT,("Entered ipv6_trans_ctlinput().\n")); + + /* + * Do standard checks to see that all parameters are here. + */ + if ((unsigned)cmd > PRC_NCMDS || sa->sa_family != AF_INET || + sin->sin_addr.s_addr == INADDR_ANY || ip == NULL) + { + DPRINTF(IDL_EVENT,("Failed one of the four checks. Returning.\n")); +#ifdef __OpenBSD__ + return NULL; +#else /* __OpenBSD__ */ + return; +#endif /* __OpenBSD__ */ + } + + /* + * Okay, at this point I have a contiguous IPv6 in IPv4 datagram. + * I achieved this effect by convincing ip_icmp.[ch] to pull up + * more than the first 64 bits. + */ + + ipv6 = (struct ipv6 *) ((caddr_t)ip + (ip->ip_hl << 2)); + /* + * Verify source address is one of mine. + */ + for (i6a = in6_ifaddr; i6a != NULL; i6a = i6a->i6a_next) + if (IN6_ARE_ADDR_EQUAL(&i6a->i6a_addr.sin6_addr, &ipv6->ipv6_src)) + break; + if (i6a == NULL) + { + /* + * Packet didn't originate with me. Drop it. + */ +#ifdef __OpenBSD__ + return NULL; +#else /* __OpenBSD__ */ + return; +#endif /* __OpenBSD__ */ + } + + sin6.sin6_addr = ipv6->ipv6_dst; +#ifdef __FreeBSD__ + rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL); +#else /* __FreeBSD__ */ + rt = rtalloc1((struct sockaddr *)&sin6, 0); +#endif /* __FreeBSD__ */ + if (rt == NULL) +#ifdef __OpenBSD__ + return NULL; +#else /* __OpenBSD__ */ + return; +#endif /* __OpenBSD__ */ + rt->rt_refcnt--; + + switch (cmd) + { + case PRC_MSGSIZE: + /* + * This function was called because the actual MTU wasn't grokked + * from the ICMP packet. + * + * If I get this, drop to IPV6_MINMTU. If the actual MTU was in the + * ICMP packet and was read correctly, it went up a different codepath. + * + * RFC 1191 talks about a plateau table. Here's the place to do it, + * either that, or on increase. + */ + if (rt->rt_flags & RTF_HOST) + { + /* + * Only attempt path MTU update if I'm a host. + */ + if (rt->rt_rmx.rmx_mtu == IPV6_MINMTU) + panic("Too big on v6 MTU of 576!!!"); + rt->rt_rmx.rmx_mtu = IPV6_MINMTU; + } + break; + case PRC_UNREACH_NET: + rt->rt_flags &= ~RTF_HOST; /* Is this wise? I'm doing this to return + the right error on future requests. */ + /* FALLTHROUGH */ + case PRC_UNREACH_HOST: + case PRC_UNREACH_PROTOCOL: + /* + * Other end isn't a v6/v4 node. + */ + rt->rt_flags |= RTF_REJECT; /* Don't want to send any packets. */ + break; + default: + break; + } +#ifdef __OpenBSD__ + return NULL; +#endif /* __OpenBSD__ */ +} + +/*---------------------------------------------------------------------- + *"IPv6 in IPv4 tunnelling." + * + * Output routine for IPv6 in IPv4. Uses M_PREPEND to prepend an IPv4 + * header, and call ip_output(). + * + * Called in: (only) ipv6_tunnel_output() from this file. + ----------------------------------------------------------------------*/ + +int +ipv6_trans_output(outgoing, v4dst, v6rt) + struct mbuf *outgoing; + struct sockaddr_in *v4dst; + struct rtentry *v6rt; +{ + struct route v4route; + struct ip *ip; + struct ipv6 *ipv6 = mtod(outgoing,struct ipv6 *); + int rc; +#ifdef IPSEC + struct socket *socket; +#endif /* IPSEC */ + + /* + * Like all below-IP(v6) output routines, check RTF_REJECT flag. + */ + if (v6rt->rt_flags & RTF_REJECT) + { + m_freem(outgoing); + return (v6rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH; + } + + if (v6rt->rt_gwroute) + v6rt->rt_gwroute->rt_refcnt++; + v4route.ro_rt = v6rt->rt_gwroute; + bcopy(v4dst,&v4route.ro_dst,v4dst->sin_len); + + /* + * Prepend IPv4 header. + */ + M_PREPEND(outgoing,sizeof(struct ip), M_DONTWAIT); + if (outgoing == NULL) + return ENOBUFS; + + ip = mtod(outgoing,struct ip *); + bzero(ip,sizeof(*ip)); + + /* + * Following four lines are done here rather than ip_output() because of + * the *&*&%^^& don't fragment bit. + */ + ip->ip_v = IPVERSION; + ip->ip_id = htons(ip_id++); + ip->ip_hl = sizeof(*ip)>>2; + if (v6rt->rt_rmx.rmx_mtu > IPV6_MINMTU) + ip->ip_off |= IP_DF; + ipstat.ips_localout++; + + if (v6rt->rt_flags & (RTF_HOST|RTF_GATEWAY)) + ip->ip_dst = v4dst->sin_addr; + else + { + /* + * If I'm in here, this means I'm not a host route, but when I was + * supposed to clone, I was supposed to change the v4dst addr. + * + * This will only happen if I'm a v6-in-v4 router-to-host route, + * in which case I have to do the translation on the fly, based on + * the data in the IPv6 header. + */ + if (!IN6_IS_ADDR_V4COMPAT(&ipv6->ipv6_dst)) + { + printf("Oooh boy, v6-in-v4 tunnel ( trans_output() ) trouble!!!\n"); + } + ip->ip_dst.s_addr = ipv6->ipv6_dst.in6a_words[3]; + } + + ip->ip_src.s_addr = INADDR_ANY; + ip->ip_p = IPPROTO_IPV6; + ip->ip_ttl = ip_defttl; + ip->ip_len = outgoing->m_pkthdr.len; + +#ifdef IPSEC + if (v6rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) { + /* + * A secure route has hanging off its rt_netproc field something which + * can be tagged onto an outgoing mbuf such that ipv4_output can + * secure the IPv6-in-IPv4 packet. + */ + DPRINTF(IDL_EVENT,("Secure route, sending cheesy socket.\n")); + socket = v6rt->rt_netproc; + } else + socket = NULL; + + rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL, socket); +#else /* IPSEC */ +#if __bsdi__ + rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL, NULL); +#else /* __bsdi__ */ + rc = ip_output(outgoing, NULL, &v4route, IP_RAWOUTPUT, NULL); +#endif /* __bsdi__ */ +#endif /* IPSEC */ + + if (rc == EMSGSIZE) + { + DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n")); + } + if (v4route.ro_rt != NULL) + RTFREE(v4route.ro_rt); + return rc; +} + +/*---------------------------------------------------------------------- + * "IPv6 in IPv6 tunnelling." + * + * Encapsulate IPv6 packet in another IPv6 packet. This, in combination + * with passing on a fake socket with a security request, can enable a + * configured secure tunnel. + * + * Called in: (only) ipv6_tunnel_output() from this file. + ----------------------------------------------------------------------*/ + +int +ipv6_encaps_output(outgoing, tundst, tunrt) + struct mbuf *outgoing; + struct sockaddr_in6 *tundst; + struct rtentry *tunrt; +{ + struct route6 actroute; + struct ipv6 *ipv6; + int rc; +#ifdef IPSEC + struct socket *socket; +#endif /* IPSEC */ + +DPRINTF(IDL_GROSS_EVENT,("\n\nipv6_encaps_output():0000-Hey! I'm in IPV6_in_IPV6 tunnelling code!!\n")); + if (tunrt->rt_flags & RTF_REJECT) + { + m_freem(outgoing); + return (tunrt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH; + } + + if (tunrt->rt_gwroute) + tunrt->rt_gwroute->rt_refcnt++; + actroute.ro_rt = tunrt->rt_gwroute; + bcopy(tundst,&actroute.ro_dst,tundst->sin6_len); + + M_PREPEND(outgoing,sizeof(struct ipv6), M_DONTWAIT); + if (outgoing == NULL) + return ENOBUFS; + + ipv6 = mtod(outgoing,struct ipv6 *); + bzero(ipv6,sizeof(*ipv6)); + + ipv6->ipv6_versfl = htonl(0x60000000); + ipv6->ipv6_length = outgoing->m_pkthdr.len - sizeof(struct ipv6); + ipv6->ipv6_nexthdr = IPPROTO_IPV6; + ipv6->ipv6_hoplimit = ipv6_defhoplmt; + ipv6->ipv6_dst = tundst->sin6_addr; + +#ifdef IPSEC + if (tunrt->rt_flags & (RTF_CRYPT|RTF_AUTH)) { + /* + * A secure route has hanging off its rt_netproc field something which + * can be tagged onto an outgoing mbuf such that ipv6_output can + * secure the IPv6-in-IPv6 packet. + */ + DPRINTF(IDL_EVENT,("ipv6_encaps_output():0500-Secure route, sending cheesy socket.\n")); + socket = tunrt->rt_netproc; + } else + socket = NULL; + + rc = ipv6_output(outgoing, &actroute, IPV6_RAWOUTPUT, NULL, NULL, socket); +#else /* IPSEC */ + rc = ipv6_output(outgoing, &actroute, IPV6_RAWOUTPUT, NULL, NULL, NULL); +#endif /* IPSEC */ + + if (rc == EMSGSIZE) + { + DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n")); + } + if (actroute.ro_rt != NULL) + RTFREE(actroute.ro_rt); + return rc; + +} +/*---------------------------------------------------------------------- + * "IPv4 in IPv4 tunnelling" + * + * Output routine for IPv4 in IPv4. Uses M_PREPEND to prepend an IPv4 + * header (i.e. the tunnel header), and then call ip_output() to + * send the tunnel packet. The v4 in v4 tunnel seems redundant, but is + * useful for setting up secure tunnels. + * + * Called in: (only) ipv4_tunnel_output() from this file. + ----------------------------------------------------------------------*/ + +int +ipv4_encaps_output(outgoing, v4tundst, v4rt) + struct mbuf *outgoing; + struct sockaddr_in *v4tundst; /* Goto to destination tunnel endpoint. */ + struct rtentry *v4rt; /* The encapsulated (i.e. passenger) IPv4 + * packet's route. */ +{ + struct route v4tunroute; /* The tunnel- (i.e. actual) route to the tunnel's + * destination endpoint (i.e. to the other + * side of the tunnel). */ + struct ip *ip; /* For setting up the IPv4 data needed for the + * tunnel IPv4 packet. */ + int rc; /* Return Code */ +#ifdef IPSEC + struct socket *socket; +#endif /* IPSEC */ + + /* + * Like all below-IP(v4) output routines, check RTF_REJECT flag. + * Why? We need to make sure the logical (i.e. encapsulated packet's) + * route to the final destination is reachable. + */ + if (v4rt->rt_flags & RTF_REJECT) + { + m_freem(outgoing); + return (v4rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH; + } + + if (v4rt->rt_gwroute) + v4rt->rt_gwroute->rt_refcnt++; + v4tunroute.ro_rt = v4rt->rt_gwroute; + bcopy(v4tundst,&v4tunroute.ro_dst,v4tundst->sin_len); + + /* + * Prepend IPv4 header. + */ + M_PREPEND(outgoing,sizeof(struct ip), M_DONTWAIT); + if (outgoing == NULL) + return ENOBUFS; + + ip = mtod(outgoing,struct ip *); + bzero(ip,sizeof(*ip)); + + /* + * Initialization of IP header: We'll let ip_output() fill in + * IP's version, ip, offset, and header length as well as update + * ipstat.ips_localout. + * + * Of course, we need to do the rest ... + */ + + ip->ip_src.s_addr = INADDR_ANY; + ip->ip_p = IPPROTO_IPV4; + ip->ip_ttl = ip_defttl; + ip->ip_len = outgoing->m_pkthdr.len; + + ip->ip_dst = v4tundst->sin_addr; + +#ifdef IPSEC + if (v4rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) { + /* + * A secure route has hanging off its rt_netproc field something which + * can be tagged onto an outgoing mbuf such that ipv4_output can + * secure the IPv4-in-IPv4 packet. + */ + DPRINTF(IDL_EVENT,("ipv4_encaps_output():0500-Secure route, sending cheesy socket.\n")); + socket = v4rt->rt_netproc; + } else + socket = NULL; + + rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL, socket); +#else /* IPSEC */ +#if __bsdi__ + rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL, NULL); +#else /* __bsdi__ */ + rc = ip_output(outgoing, NULL, &v4tunroute, 0, NULL); +#endif /* __bsdi__ */ +#endif /* IPSEC */ + + if (v4tunroute.ro_rt != NULL) + RTFREE(v4tunroute.ro_rt); + return rc; +} + +/*---------------------------------------------------------------------- + *"IPv4 in IPv6" + * + * Encapsulate IPv4 packet in a IPv6 packet. + * + * Called in: (only) ipv4_tunnel_output() from this file. + *---------------------------------------------------------------------*/ + +int +ipv4_trans_output(outgoing, tunv6dst, v4rt) + struct mbuf *outgoing; + struct sockaddr_in6 *tunv6dst; + struct rtentry *v4rt; +{ + struct route6 tunv6route; + struct ipv6 *ipv6; + int rc; +#ifdef IPSEC + struct socket *socket; +#endif /* IPSEC */ + + if (v4rt->rt_flags & RTF_REJECT) + { + m_freem(outgoing); + return (v4rt->rt_flags & RTF_HOST) ? EHOSTUNREACH : ENETUNREACH; + } + + if (v4rt->rt_gwroute) + v4rt->rt_gwroute->rt_refcnt++; + tunv6route.ro_rt = v4rt->rt_gwroute; + bcopy(tunv6dst,&tunv6route.ro_dst,tunv6dst->sin6_len); + + M_PREPEND(outgoing,sizeof(struct ipv6), M_DONTWAIT); + if (outgoing == NULL) + return ENOBUFS; + + ipv6 = mtod(outgoing,struct ipv6 *); + bzero(ipv6,sizeof(*ipv6)); + + ipv6->ipv6_versfl = htonl(0x60000000); + ipv6->ipv6_length = outgoing->m_pkthdr.len - sizeof(struct ipv6); + ipv6->ipv6_nexthdr = IPPROTO_IPV4; + ipv6->ipv6_hoplimit = ipv6_defhoplmt; + ipv6->ipv6_dst = tunv6dst->sin6_addr; + +#ifdef IPSEC + if (v4rt->rt_flags & (RTF_CRYPT|RTF_AUTH)) { + /* + * A secure route has hanging off its rt_netproc field something which + * can be tagged onto an outgoing mbuf such that ipv6_output can + * secure the IPv4-in-IPv6 packet. + */ + DPRINTF(IDL_EVENT,("ipv4_trans_output():0500-Secure route, sending cheesy socket.\n")); + socket = v4rt->rt_netproc; + } else + socket = NULL; + + rc = ipv6_output(outgoing, &tunv6route, IPV6_RAWOUTPUT, NULL, NULL, socket); +#else /* IPSEC */ + rc = ipv6_output(outgoing, &tunv6route, IPV6_RAWOUTPUT, NULL, NULL, NULL); +#endif /* IPSEC */ + + if (rc == EMSGSIZE) + { + DPRINTF(IDL_ERROR,("Path MTU adjustment needed in trans_output().\n")); + } + if (tunv6route.ro_rt != NULL) + RTFREE(tunv6route.ro_rt); + return rc; +} + + +/*---------------------------------------------------------------------- + * Called by ipv6_output if the RTF_TUNNEL bit is set on a route, + * this function examines the route, and sees what sort of encapsulation is + * needed. Often, the rt->rt_gateway sockaddr is used to figure this out. + ----------------------------------------------------------------------*/ + +int +ipv6_tunnel_output(outgoing, dst, rt) + struct mbuf *outgoing; + struct sockaddr_in6 *dst; + struct rtentry *rt; +{ + DPRINTF(IDL_EVENT,("\n\nipv6_tunnel_output():0000-Just entered.\n")); + + /* + * Determine what type of tunnel it is with rt. Perform correct kind + * of encapsulation (in IPv4, ESP, etc.) and call output routine of + * what you want encapsulated. + */ + + /* IPv6 in IPv4. */ + if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET) + return ipv6_trans_output(outgoing,(struct sockaddr_in *)rt->rt_gateway,rt); + + /* IPv6 in IPv6. */ + if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET6) + return ipv6_encaps_output(outgoing,(struct sockaddr_in6 *)rt->rt_gateway,rt); + + m_freem(outgoing); + return EHOSTUNREACH; +} + + +/*---------------------------------------------------------------------- + * Called by ip_output if the RTF_TUNNEL bit is set on a route, + * this function examines the route, and sees what sort of encapsulation is + * needed. Often, the rt->rt_gateway sockaddr is used to figure this out. + ----------------------------------------------------------------------*/ + +int +ipv4_tunnel_output(outgoing, dst, rt) + struct mbuf *outgoing; + struct sockaddr_in *dst; + struct rtentry *rt; +{ + DPRINTF(IDL_EVENT,("\n\nipv4_tunnel_output():0000-Just entered.\n")); + + /* + * Determine what type of tunnel it is with rt. Perform correct kind + * of encapsulation (in IPv4, ESP, etc.) and call output routine of + * what you want encapsulated. + */ + + /* IPv4 in IPv6. */ + if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET6) + return ipv4_trans_output(outgoing,(struct sockaddr_in6 *)rt->rt_gateway,rt); + + /* IPv4 in IPv4. */ + if (rt->rt_gateway != NULL && rt->rt_gateway->sa_family == AF_INET) + return ipv4_encaps_output(outgoing,(struct sockaddr_in *)rt->rt_gateway,rt); + + m_freem(outgoing); + return EHOSTUNREACH; +} diff --git a/sys/netinet6/ipv6_trans.h b/sys/netinet6/ipv6_trans.h new file mode 100644 index 00000000000..5ceb2456e3a --- /dev/null +++ b/sys/netinet6/ipv6_trans.h @@ -0,0 +1,26 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IPV6_TRANS_H +#define _NETINET6_IPV6_TRANS_H 1 + +/* I don't include any #includes, as I'm using this for our (NRL) + * modified netinet/ip_output() function; thus, this #include should be + * used/stuck-in after all the other necessary includes. + * + * And yes, I only put one declaration here. There's no real need + * to stick the other prototypes in here and have ip_output() fluffed + * during preprocessing time. + */ + +int ipv4_tunnel_output __P((struct mbuf *, struct sockaddr_in *, struct rtentry *)); + +#endif /* _NETINET6_IPV6_TRANS_H */ diff --git a/sys/netinet6/ipv6_var.h b/sys/netinet6/ipv6_var.h new file mode 100644 index 00000000000..f0e88034bcd --- /dev/null +++ b/sys/netinet6/ipv6_var.h @@ -0,0 +1,346 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_IPV6_VAR_H +#define _NETINET6_IPV6_VAR_H 1 + +#include <netinet6/in6.h> + +/* + * IPv6 multicast "options". Session state for multicast, including + * weird per-session multicast things. + */ + +struct ipv6_moptions +{ + struct ifnet *i6mo_multicast_ifp; /* ifp for outgoing multicasts */ + u_char i6mo_multicast_ttl; /* TTL for outgoing multicasts. + Does this matter in IPv6? */ + u_char i6mo_multicast_loop; /* 1 => hear sends if a member */ + u_short i6mo_num_memberships; /* no. memberships this socket */ + struct in6_multi *i6mo_membership[IN6_MAX_MEMBERSHIPS]; +}; + +/* + * IPv6 stats. + */ + +#if defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 +#define _IPV6STAT_TYPE u_quad_t +#else /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ +#define _IPV6STAT_TYPE u_long +#endif /* defined(_BSDI_VERSION) && _BSDI_VERSION >= 199802 */ + +struct ipv6stat { + _IPV6STAT_TYPE ips_total; /* total packets received */ + _IPV6STAT_TYPE ips_tooshort; /* packet too short */ + _IPV6STAT_TYPE ips_toosmall; /* not enough data */ + _IPV6STAT_TYPE ips_fragments; /* fragments received */ + _IPV6STAT_TYPE ips_fragdropped; /* frags dropped (dups, out of space) */ + _IPV6STAT_TYPE ips_fragtimeout; /* fragments timed out */ + _IPV6STAT_TYPE ips_forward; /* packets forwarded */ + _IPV6STAT_TYPE ips_cantforward; /* packets rcvd for unreachable dest */ + _IPV6STAT_TYPE ips_redirectsent; /* packets forwarded on same net */ + _IPV6STAT_TYPE ips_noproto; /* unknown or unsupported protocol */ + _IPV6STAT_TYPE ips_delivered; /* datagrams delivered to upper level*/ + _IPV6STAT_TYPE ips_localout; /* total ip packets generated here */ + _IPV6STAT_TYPE ips_odropped; /* lost packets due to nobufs, etc. */ + _IPV6STAT_TYPE ips_reassembled; /* total packets reassembled ok */ + _IPV6STAT_TYPE ips_fragmented; /* datagrams sucessfully fragmented */ + _IPV6STAT_TYPE ips_ofragments; /* output fragments created */ + _IPV6STAT_TYPE ips_cantfrag; /* don't fragment flag was set, etc. */ + _IPV6STAT_TYPE ips_badoptions; /* error in option processing */ + _IPV6STAT_TYPE ips_noroute; /* packets discarded due to no route */ + _IPV6STAT_TYPE ips_badvers; /* IPv6 version != 6 */ + _IPV6STAT_TYPE ips_rawout; /* total raw ip packets generated */ +}; + +#ifdef KERNEL + +/* + * The IPv6 fragment queue entry structure. + * Notes: + * Nodes are stored in ttl order. + * prefix comes from whichever packet gets here first. + * data contains a chain of chains of mbufs (m_next down a chain, m_nextpkt + * chaining chains together) where the chains are ordered by assembly + * position. When two chains are contiguous for reassembly, they are + * combined and the frag header disappears. + * The structure is deliberately sized so MALLOC will round up on the order + * of much less than the total size instead of doubling the size. + */ + +struct ipv6_fragment +{ + struct ipv6_fragment *next; /* Next fragment chain */ + struct mbuf *prefix; /* Headers before frag header(s) */ + struct mbuf *data; /* Frag headers + whatever data */ + u_char ttl; /* Fragment chain TTL. */ + u_char flags; /* Bit 0 indicates got end of chain */ +}; + +/* + * Structures and definitions for discovery mechanisms in IPv6. + */ + +/* + * Neighbor cache: + * + * Number of unanswered probes is in discq. + * "Time of next event" will be in rt->rt_rmx.rmx_expire + * (rmx_expire will actually be quite overloaded, actually.) + * Status REACHABLE will be dq_unanswered < 0 + * Status PROBE will be dq_unanswered >= 0 + * Status INCOMPLETE will be link addr length of 0 if held, + * or deleted if not held. + * + * If held, but INCOMPLETE fails set RTF_REJECT and make sure + * IPv6 and HLP's know how to deal with RTF_REJECT being set. + */ + +struct discq /* Similar to v4's llinfo_arp, discovery's "neighbor entry". */ +{ + struct discq *dq_next,*dq_prev; /* For {ins,rem}que(). */ + struct rtentry *dq_rt; /* Back pointer to routing entry for + an address that may be dead. */ + struct mbuf *dq_queue; /* Queue of outgoing messages. */ + int dq_unanswered; /* Number of unanswered probes. */ +}; + +#if !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) +/* Routing flag redefinitions */ +#define RTF_ISAROUTER RTF_PROTO2 /* Neighbor is a router. */ +#define RTF_DEFAULT RTF_PROTO1 /* Default route. */ +#endif /* !defined(_BSDI_VERSION) || (_BSDI_VERSION < 199802) */ + +/* + * These should be configurable parameters, see ipv6_discovery.c. + * All units are in comments besides constants. + */ + +#define MAX_INITIAL_RTR_ADVERT_INTERVAL 16 /* seconds */ +#define MAX_INITIAL_RTR_ADVERTISEMENTS 3 /* transmissions */ +#define MAX_RTR_RESPONSE_DELAY 2 /* seconds */ + +#define MAX_RTR_SOLICITATION_DELAY 1 /* second */ +#define RTR_SOLICITATION_INTERVAL 3 /* seconds */ +#define MAX_RTR_SOLICITATIONS 3 /* transmissions */ + +#define MAX_MULTICAST_SOLICIT 3 /* transmissions */ +#define MAX_UNICAST_SOLICIT 3 /* transmissions */ +#define MAX_ANYCAST_DELAY_TIME 1 /* seconds */ +#define MAX_NEIGHBOR_ADVERTISEMENTS 3 /* transmissions */ +#define MIN_NEIGHBOR_ADVERT_INTERVAL 16 /* seconds */ +#define REACHABLE_TIME 30 /* seconds */ +#define RETRANS_TIMER 3 /* seconds */ +#define DELAY_FIRST_PROBE_TIME 3 /* seconds */ +/* Need to somehow define random factors. */ + +#define NEXTHOP_CLEAN_INTERVAL 600 /* seconds */ +#define REJECT_TIMER 20 /* seconds */ + +/* + * Child of a router or tunnel. Is a "meta-entry" for garbage collection. + */ + +struct v6child +{ + struct v6child *v6c_next,*v6c_prev; /* For {ins,rem}que() */ + struct v6router *v6c_parent; /* Parent router. I'm null if + I'm the router, or a tunnel + child. */ + struct rtentry *v6c_route; /* Next-hop cache entry. I won't + be holding it, but I'm attached + to it, like discq is to neighbor + cache entries. */ +}; + +/* + * Default router list entry. Should be inserted + * in priority order. Will also have entries for non- + * default routers, because I may be a router myself. + */ + +struct v6router +{ + struct v6router *v6r_next,*v6r_prev; /* For {ins,rem}que() */ + struct rtentry *v6r_rt; /* Route for this. Could be neighbor, + could be tunnel. */ + struct v6child v6r_children; /* Children of this router. */ + + /* Metric information? */ + uint32_t v6r_expire; /* Expiration time. */ +}; +#define V6R_SIN6(v6r) ((struct sockaddr_in6 *)rt_key((v6r)->v6r_rt)) + +/* + * Flags for "flags" argument in ipv6_output(). + */ + +#define IPV6_FORWARDING 0x1 /* Most of IPv6 header exists? */ +#define IPV6_RAWOUTPUT 0x2 /* Raw IPv6 packet! */ +#define IPV6_ROUTETOIF SO_DONTROUTE /* Include sys/socket.h... */ + +void ipv6_init __P((void)); +void ipv6_drain __P((void)); +void ipv6_slowtimo __P((void)); +int ipv6_sysctl __P((int *, uint, void *, size_t *, void *, size_t)); +struct route6; + +#if __FreeBSD__ +int ipv6_ctloutput __P((struct socket *, struct sockopt *)); +int ripv6_ctloutput __P((struct socket *, struct sockopt *)); +#else /* __FreeBSD__ */ +int ipv6_ctloutput __P((int, struct socket *,int,int, struct mbuf **)); +int ripv6_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); +#endif /* __FreeBSD__ */ +void ripv6_init __P((void)); +#if __OpenBSD__ +void ripv6_input __P((struct mbuf *, ...)); +int ripv6_output __P((struct mbuf *, ...)); +#else /* __OpenBSD__ */ +void ripv6_input __P((struct mbuf *, int)); +int ripv6_output __P((struct mbuf *, struct socket *, struct in6_addr *, struct mbuf *)); +#endif /* __OpenBSD__ */ + +#if __NetBSD__ || __FreeBSD__ +int ripv6_usrreq_send(struct socket *, int, struct mbuf *, struct sockaddr *, + struct mbuf *, struct proc *); +#else /* __NetBSD__ || __FreeBSD__ */ +int ripv6_usrreq_send(struct socket *, int, struct mbuf *, struct sockaddr *, + struct mbuf *); +#endif /* __NetBSD__ || __FreeBSD__ */ + +#if __FreeBSD__ +int ripv6_usrreq_abort(struct socket *); +int ripv6_usrreq_attach(struct socket *, int , struct proc *); +int ripv6_usrreq_bind(struct socket *, struct sockaddr *, struct proc *); +int ripv6_usrreq_connect(struct socket *, struct sockaddr *, struct proc *); +int ripv6_usrreq_control(struct socket *, u_long, caddr_t, struct ifnet *, + struct proc *); +int ripv6_usrreq_detach(struct socket *); +int ripv6_usrreq_peeraddr(struct socket *, struct sockaddr **); +int ripv6_usrreq_sense(struct socket *, struct stat *); +int ripv6_usrreq_shutdown(struct socket *); +int ripv6_usrreq_sockaddr(struct socket *, struct sockaddr **); +#else /* __FreeBSD__ */ +#if __NetBSD__ +int ripv6_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *, struct proc *)); +#else /* __NetBSD__ */ +int ripv6_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *)); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ + +#if 0 /* __OpenBSD__ */ +void ipv6_input __P((struct mbuf *, ...)); +int ipv6_output __P((struct mbuf *, ...)); +#else /* __OpenBSD__ */ +void ipv6_input __P((struct mbuf *, int)); +int ipv6_output __P((struct mbuf *, struct route6 *, int, struct ipv6_moptions *, struct ifnet *, struct socket *)); +#endif /* __OpenBSD__ */ +void ipv6_reasm __P((struct mbuf *, int)); +void ipv6_hop __P((struct mbuf *, int)); + +#if __FreeBSD__ +int in6_control __P((struct socket *,int, caddr_t, struct ifnet *,int, struct proc *)); +#else /* __FreeBSD__ */ +#if __NetBSD__ +int in6_control __P((struct socket *,u_long, caddr_t, struct ifnet *,int, struct proc *)); +#else /* __NetBSD__ */ +int in6_control __P((struct socket *,int, caddr_t, struct ifnet *,int)); +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ +void ipv6_stripoptions __P((struct mbuf *, int)); +struct in6_multi *in6_addmulti __P((struct in6_addr *,struct ifnet *)); +void in6_delmulti __P((struct in6_multi *)); + +#if __FreeBSD__ +/* ripv6_usrreq and ipv6_icmp_usrreq functions */ +extern struct pr_usrreqs ripv6_usrreqs; +extern struct pr_usrreqs ipv6_icmp_usrreqs; + +extern int ripv6_usr_attach(struct socket *, int , struct proc *); +extern int ripv6_usr_disconnect(struct socket *); +extern int ripv6_usr_abort(struct socket *); +extern int ripv6_usr_detach(struct socket *); +extern int ripv6_usr_bind(struct socket *, struct sockaddr *, struct proc *); +extern int ripv6_usr_connect(struct socket *, struct sockaddr *, struct proc *); +extern int ripv6_usr_shutdown(struct socket *); +extern int ripv6_usr_send(struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, struct proc *); +extern int ripv6_usr_control(struct socket *, int, caddr_t, + struct ifnet *, struct proc *); +extern int ripv6_usr_sense(struct socket *, struct stat *); +extern int ripv6_usr_sockaddr(struct socket *, struct sockaddr **); +extern int ripv6_usr_peeraddr(struct socket *, struct sockaddr **); +#endif /* __FreeBSD__ */ + +extern int ipv6_icmp_send(struct socket *, int, struct mbuf *, + struct sockaddr *, struct mbuf *, struct proc *); + +#if __OpenBSD__ +void *ipv6_trans_ctlinput __P((int, struct sockaddr *, void *, struct mbuf *)); +#else /* __OpenBSD__ */ +struct ip; +void ipv6_trans_ctlinput __P((int, struct sockaddr *, struct ip *, struct mbuf *)); +#endif /* __OpenBSD__ */ + +/* These might belong in in_pcb.h */ +struct inpcb; +#if __FreeBSD__ +/* + * FreeBSD, having done away with the *_usrreq() functions no longer needs to + * pass mbufs to these functions. Thus they pass in sockaddrs instead. + */ +int in6_pcbbind(struct inpcb *, struct sockaddr *); +int in6_pcbconnect(struct inpcb *, struct sockaddr *); +int in6_setsockaddr(struct inpcb *, struct sockaddr **); +int in6_setpeeraddr(struct inpcb *, struct sockaddr **); +#else /* __FreeBSD__ */ +int in6_pcbbind(struct inpcb *, struct mbuf *); +int in6_pcbconnect(struct inpcb *, struct mbuf *); +int in6_setsockaddr(struct inpcb *, struct mbuf *); +int in6_setpeeraddr(struct inpcb *, struct mbuf *); +#endif /* __FreeBSD__ */ +void ipv6_onlink_query(struct sockaddr_in6 *); +int ipv6_verify_onlink(struct sockaddr_in6 *); + +#if __FreeBSD__ +struct inpcbhead; /* XXX? Forward declaration needed. */ +#define __IN6_PCBNOTIFY_FIRSTARG struct inpcbhead * +#endif /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ +struct inpcbtable; +#define __IN6_PCBNOTIFY_FIRSTARG struct inpcbtable * +#endif /* __NetBSD__ || __OpenBSD__ */ +#if __bsdi__ +struct inpcb; +#define __IN6_PCBNOTIFY_FIRSTARG struct inpcb * +#endif /* __bsdi__ */ + +#if (!__OpenBSD__ && defined(IPSEC)) || (__OpenBSD__ && defined(NRL_IPSEC)) +int in6_pcbnotify __P((__IN6_PCBNOTIFY_FIRSTARG, struct sockaddr *, uint, + struct in6_addr *, uint, int, void (*)(struct inpcb *, + int), struct mbuf *, int)); +#else /* (!__OpenBSD__ && defined(IPSEC)) || (__OpenBSD__ && defined(NRL_IPSEC)) */ +int in6_pcbnotify __P((__IN6_PCBNOTIFY_FIRSTARG, struct sockaddr *, uint, + struct in6_addr *, uint, int, void (*)(struct inpcb *, + int))); +#endif /* (!__OpenBSD__ && defined(IPSEC)) || (__OpenBSD__ && defined(NRL_IPSEC)) */ + +#undef __IN6_PCBNOTIFY_FIRSTARG + +void ipv6_freemoptions __P((struct ipv6_moptions *)); +#endif /* KERNEL */ + +#endif /* _NETINET6_IPV6_VAR_H */ diff --git a/sys/netinet6/nd6_protocol.h b/sys/netinet6/nd6_protocol.h new file mode 100644 index 00000000000..df20abcd66c --- /dev/null +++ b/sys/netinet6/nd6_protocol.h @@ -0,0 +1,100 @@ +/* +%%% portions-copyright-nrl-97 +Portions of this software are Copyright 1997-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 these portions of the +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>. + +*/ + +#ifndef _NETINET6_ND6_PROTOCOL_H +#define _NETINET6_ND6_PROTOCOL_H 1 + +#include <netinet6/icmpv6.h> + +#define ND6_ROUTER_SOLICITATION 133 +#define ND6_ROUTER_ADVERTISEMENT 134 +#define ND6_NEIGHBOR_SOLICITATION 135 +#define ND6_NEIGHBOR_ADVERTISEMENT 136 +#define ND6_REDIRECT 137 + +enum nd6_option { + ND6_OPT_SOURCE_LINKADDR=1, + ND6_OPT_TARGET_LINKADDR=2, + ND6_OPT_PREFIX_INFORMATION=3, + ND6_OPT_REDIRECTED_HEADER=4, + ND6_OPT_MTU=5, + ND6_OPT_ENDOFLIST=256 +}; + +struct nd_router_solicit { /* router solicitation */ + struct icmpv6hdr rsol_hdr; +}; + +#define rsol_type rsol_hdr.icmpv6_type +#define rsol_code rsol_hdr.icmpv6_code +#define rsol_cksum rsol_hdr.icmpv6_cksum +#define rsol_reserved rsol_hdr.icmpv6_data32[0] + +struct nd_router_advert { /* router advertisement */ + struct icmpv6hdr radv_hdr; + uint32_t radv_reachable; /* reachable time */ + uint32_t radv_retransmit; /* reachable retransmit time */ +}; + +#define radv_type radv_hdr.icmpv6_type +#define radv_code radv_hdr.icmpv6_code +#define radv_cksum radv_hdr.icmpv6_cksum +#define radv_maxhoplimit radv_hdr.icmpv6_data8[0] +#define radv_m_o_res radv_hdr.icmpv6_data8[1] +#define ND6_RADV_M_BIT 0x80 +#define ND6_RADV_O_BIT 0x40 +#define radv_router_lifetime radv_hdr.icmpv6_data16[1] + +struct nd6_nsolicitation { /* neighbor solicitation */ + struct icmpv6hdr nsol6_hdr; + struct in6_addr nsol6_target; +}; + +struct nd6_nadvertisement { /* neighbor advertisement */ + struct icmpv6hdr nadv6_hdr; + struct in6_addr nadv6_target; +}; + +#define nadv6_flags nadv6_hdr.icmpv6_data32[0] +#define ND6_NADVERFLAG_ISROUTER 0x80 +#define ND6_NADVERFLAG_SOLICITED 0x40 +#define ND6_NADVERFLAG_OVERRIDE 0x20 + +struct nd6_redirect { /* redirect */ + struct icmpv6hdr redirect_hdr; + struct in6_addr redirect_target; + struct in6_addr redirect_destination; +}; + +struct nd6_opt_prefix_info { /* prefix information */ + uint8_t opt_type; + uint8_t opt_length; + uint8_t opt_prefix_length; + uint8_t opt_l_a_res; + uint32_t opt_valid_life; + uint32_t opt_preferred_life; + uint32_t opt_reserved2; + struct in6_addr opt_prefix; +}; + +#define ND6_OPT_PI_L_BIT 0x80 +#define ND6_OPT_PI_A_BIT 0x40 + +struct nd6_opt_mtu { /* MTU option */ + uint8_t opt_type; + uint8_t opt_length; + uint16_t opt_reserved; + uint32_t opt_mtu; +}; + +#endif /* _NETINET6_ND6_PROTOCOL_H */ diff --git a/sys/netinet6/raw_ipv6.c b/sys/netinet6/raw_ipv6.c new file mode 100644 index 00000000000..fb95a4858b2 --- /dev/null +++ b/sys/netinet6/raw_ipv6.c @@ -0,0 +1,1059 @@ +/* +%%% 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>. + +*/ +/* + * Copyright (c) 1982, 1986, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)raw_ip.c 8.7 (Berkeley) 5/15/95 + * $Id: raw_ipv6.c,v 1.1 1999/01/06 23:26:58 deraadt Exp $ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/domain.h> +#include <sys/protosw.h> +#include <sys/errno.h> +#include <sys/time.h> +#include <sys/kernel.h> +#if __NetBSD__ || __FreeBSD__ +#include <sys/proc.h> +#endif /* __NetBSD__ || __FreeBSD__ */ +#if __FreeBSD__ +#include <vm/vm_zone.h> +#endif /* __FreeBSD__ */ + +#include <net/if.h> +#include <net/route.h> + +#include <netinet/in.h> +#include <netinet/ip_mroute.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/in_pcb.h> + +#include <netinet6/in6.h> +#include <netinet6/in6_var.h> +#include <netinet6/ipv6.h> +#include <netinet6/ipv6_var.h> +#include <netinet6/icmpv6.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> +#endif /* IPSEC */ + +#include <sys/debug.h> + +/* + * Globals + */ + +#if __NetBSD__ || __OpenBSD__ +struct inpcbtable rawin6pcbtable; +#else /* __NetBSD__ || __OpenBSD__ */ +struct inpcb rawin6pcb; +#endif /* __NetBSD__ || __OpenBSD__ */ +struct sockaddr_in6 ripv6src = { sizeof(struct sockaddr_in6), AF_INET6 }; + +/* + * Nominal space allocated to a raw ip socket. + */ + +#define RIPV6SNDQ 8192 +#define RIPV6RCVQ 8192 +u_long ripv6_sendspace = RIPV6SNDQ; +u_long ripv6_recvspace = RIPV6RCVQ; + +/* + * External globals + */ +#if __FreeBSD__ +static struct inpcbhead ri6pcb; +static struct inpcbinfo ri6pcbinfo; +#endif /* __FreeBSD__ */ + +extern struct ipv6stat ipv6stat; + +struct mbuf *ipv6_headertocontrol(struct mbuf *m, size_t extra, int inp_flags); +int ipv6_controltoheader(struct mbuf **m, struct mbuf *control, struct ifnet **forceifp, int *); + +#define RETURN_ERROR(x) { \ + DPRINTF(EVENT, ("%s: returning %s\n", DEBUG_STATUS, #x)); \ + return x; \ +} +#define RETURN_VALUE(x) { \ + DPRINTF(EVENT, ("%s: returning %d\n", DEBUG_STATUS, x)); \ + return x; \ +} + +/*---------------------------------------------------------------------- + * Raw IPv6 PCB initialization. + ----------------------------------------------------------------------*/ + +void +ripv6_init() +{ +#if __FreeBSD__ + LIST_INIT(&ri6pcb); + ri6pcbinfo.listhead = &ri6pcb; + /* + * XXX We don't use the hash list for raw IP, but it's easier + * to allocate a one entry hash list than it is to check all + * over the place for hashbase == NULL. + */ + ri6pcbinfo.hashbase = hashinit(1, M_PCB, &ri6pcbinfo.hashmask); + ri6pcbinfo.porthashbase = hashinit(1, M_PCB, &ri6pcbinfo.porthashmask); + ri6pcbinfo.ipi_zone = zinit("ri6pcb", sizeof(struct inpcb), + nmbclusters / 4, ZONE_INTERRUPT, 0); +#else /* __FreeBSD__ */ +#if __NetBSD__ + in_pcbinit(&rawin6pcbtable, 1, 1); +#else /* __NetBSD__ */ +#if __OpenBSD__ + in_pcbinit(&rawin6pcbtable, 1); +#else /* __OpenBSD__ */ + rawin6pcb.inp_next = rawin6pcb.inp_prev = &rawin6pcb; +#endif /* __OpenBSD__ */ +#endif /* __NetBSD__ */ +#endif /* __FreeBSD__ */ +} + +/* At the point where this function gets called, we don't know the nexthdr of + the current header to be processed, only its offset. So we have to go find + it the hard way. In the case where there's no chained headers, this is not + really painful. + + The good news is that all fields have been sanity checked. + + Assumes m has already been pulled up by extra. -cmetz +*/ +#if __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ +static __inline__ int ipv6_findnexthdr(struct mbuf *m, size_t extra) +#else /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ */ +static int ipv6_findnexthdr(struct mbuf *m, size_t extra) +#endif /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ */ +{ + caddr_t p = mtod(m, caddr_t); + int nexthdr = IPPROTO_IPV6; + unsigned int hl; + + do { + switch(nexthdr) { + case IPPROTO_IPV6: + hl = sizeof(struct ipv6); + + if ((extra -= hl) < 0) + return -1; + + nexthdr = ((struct ipv6 *)p)->ipv6_nexthdr; + break; + case IPPROTO_HOPOPTS: + case IPPROTO_DSTOPTS: + if (extra < sizeof(struct ipv6_opthdr)) + return -1; + + hl = sizeof(struct ipv6_opthdr) + + ((struct ipv6_opthdr *)p)->oh_extlen << 3; + + if ((extra -= hl) < 0) + return -1; + + nexthdr = ((struct ipv6_opthdr *)p)->oh_nexthdr; + break; + case IPPROTO_ROUTING: + if (extra < sizeof(struct ipv6_srcroute0)) + return -1; + + hl = sizeof(struct ipv6_srcroute0) + + ((struct ipv6_srcroute0 *)p)->i6sr_len << 3; + + if ((extra -= hl) < 0) + return -1; + + nexthdr = ((struct ipv6_srcroute0 *)p)->i6sr_nexthdr; + break; +#ifdef IPSEC + case IPPROTO_AH: + if (extra < sizeof(struct ipv6_srcroute0)) + return -1; + + hl = sizeof(struct ipv6_srcroute0) + + ((struct ipv6_srcroute0 *)p)->i6sr_len << 3; + + if ((extra -= hl) < 0) + return -1; + + nexthdr = ((struct ipv6_srcroute0 *)p)->i6sr_nexthdr; + break; +#endif /* IPSEC */ + default: + return -1; + } + p += hl; + } while(extra > 0); + + return nexthdr; +} + +/*---------------------------------------------------------------------- + * If no HLP's are found for an IPv6 datagram, this routine is called. + ----------------------------------------------------------------------*/ +void +#if __OpenBSD__ +ripv6_input(struct mbuf *m, ...) +#else /* __OpenBSD__ */ +ripv6_input(m,extra) + struct mbuf *m; + int extra; +#endif /* __OpenBSD__ */ +{ + register struct ipv6 *ipv6 = mtod(m, struct ipv6 *); /* Will have been + pulled up by + ipv6_input(). */ + register struct inpcb *inp; + int nexthdr, icmpv6type; + int foundone = 0; + struct mbuf *m2 = NULL, *opts = NULL; + struct sockaddr_in6 srcsa; +#ifdef IPSEC + struct sockaddr_in6 dstsa; +#endif /* IPSEC */ +#if __OpenBSD__ + int extra; + va_list ap; + + va_start(ap, m); + extra = va_arg(ap, int); + va_end(ap); +#endif /* __OpenBSD__ */ + + DPRINTF(FINISHED, ("ripv6_input(m=%08x, extra=%d)\n", OSDEP_PCAST(m), extra)); + DP(FINISHED, m->m_pkthdr.len, d); + + DDO(FINISHED,printf("In ripv6_input(), header is:\n");dump_mchain(m)); + DPRINTF(EVENT, ("In ripv6_input()\n")); + DPRINTF(EVENT, ("Header is: ")); + DDO(GROSSEVENT, dump_ipv6(ipv6)); + + bzero(&srcsa, sizeof(struct sockaddr_in6)); + srcsa.sin6_family = AF_INET6; + srcsa.sin6_len = sizeof(struct sockaddr_in6); + srcsa.sin6_addr = ipv6->ipv6_src; + +#if IPSEC + bzero(&dstsa, sizeof(struct sockaddr_in6)); + dstsa.sin6_family = AF_INET6; + dstsa.sin6_len = sizeof(struct sockaddr_in6); + dstsa.sin6_addr = ipv6->ipv6_dst; +#endif /* IPSEC */ + +#if 0 + /* Will be done already by the previous input functions */ + if (m->m_len < extra)) { + if (!(m = m_pullup2(m, extra))) + return; + ipv6 = mtod(m, struct ipv6 *); + } +#endif /* 0 */ + + if ((nexthdr = ipv6_findnexthdr(m, extra)) < 0) { + DPRINTF(ERROR, ("ripv6_input: ipv6_findnexthdr failed\n")); + goto ret; + } + + DP(FINISHED, nexthdr, d); + + if (nexthdr == IPPROTO_ICMPV6) { + if (m->m_len < extra + sizeof(struct icmpv6hdr)) { + if (!(m = m_pullup2(m, extra + sizeof(struct icmpv6hdr)))) { + DPRINTF(ERROR, ("ripv6_input: m_pullup2 failed\n")); + goto ret; + } + ipv6 = mtod(m, struct ipv6 *); + } + icmpv6type = ((struct icmpv6hdr *)(mtod(m, caddr_t) + extra))->icmpv6_type; + } else + icmpv6type = -1; + + /* + * Locate raw PCB for incoming datagram. + */ +#if __FreeBSD__ + for (inp = ri6pcb.lh_first; inp != NULL; inp = inp->inp_list.le_next) { +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + for (inp = rawin6pcbtable.inpt_queue.cqh_first; + inp != (struct inpcb *)&rawin6pcbtable.inpt_queue; + inp = inp->inp_queue.cqe_next) { +#else /* __NetBSD__ || __OpenBSD__ */ + for (inp = rawin6pcb.inp_next; inp != &rawin6pcb; inp = inp->inp_next) { +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + if (inp->inp_ipv6.ipv6_nexthdr && inp->inp_ipv6.ipv6_nexthdr != nexthdr) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->inp_laddr6) && + !IN6_ARE_ADDR_EQUAL(&inp->inp_laddr6, &ipv6->ipv6_dst)) + continue; + if (!IN6_IS_ADDR_UNSPECIFIED(&inp->inp_faddr6) && + !IN6_ARE_ADDR_EQUAL(&inp->inp_faddr6, &ipv6->ipv6_src)) + continue; + if ((icmpv6type >= 0) && + ICMPV6_FILTER_WILLBLOCK(icmpv6type, &inp->inp_filter)) + continue; + + DPRINTF(IDL_EVENT, ("Found a raw pcb (>1)\n")); + foundone = 1; + +#ifdef IPSEC + /* Perform input-side policy check. Drop packet if policy says to drop it. + + Note: For ICMPv6 packets, we also checked policy in ipv6_icmp_input(). + + 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, nexthdr, m, NULL, NULL)) +#endif /* IPSEC */ + + DP(FINISHED, m->m_pkthdr.len, d); + /* Note the inefficiency here; this is a consequence of the interfaces of + the functions being used. The raw code is not performance critical + enough to require an immediate fix. - cmetz */ + if ((m2 = m_copym(m, 0, (int)M_COPYALL, M_DONTWAIT))) { + m_adj(m2, extra); + DP(FINISHED, m2->m_pkthdr.len, d); + if (inp->inp_flags & INP_CONTROLOPTS) + opts = ipv6_headertocontrol(m, extra, inp->inp_flags); + else + opts = NULL; + if (sbappendaddr(&inp->inp_socket->so_rcv, (struct sockaddr *)&srcsa, m2, + opts)) { + sorwakeup(inp->inp_socket); + } else { + m_freem(m2); + }; + }; + }; + + if (!foundone) { + /* + * We should send an ICMPv6 protocol unreachable here, + * though original UCB 4.4-lite BSD's IPv4 does not do so. + */ + ipv6stat.ips_noproto++; + ipv6stat.ips_delivered--; + } + +ret: + if (m) + m_freem(m); + + DPRINTF(FINISHED, ("ripv6_input\n")); +} + +/*---------------------------------------------------------------------- + * Output function for raw IPv6. Called from ripv6_usrreq(), and + * ipv6_icmp_usrreq(). + ----------------------------------------------------------------------*/ + +#if __OpenBSD__ +int ripv6_output(struct mbuf *m, ...) +#else /* __OpenBSD__ */ +int +ripv6_output(m, so, dst, control) + struct mbuf *m; + struct socket *so; + struct in6_addr *dst; + struct mbuf *control; +#endif /* __OpenBSD__ */ +{ + register struct ipv6 *ipv6; + register struct inpcb *inp; + int flags; + struct ifnet *forceif = NULL; +#if __OpenBSD__ + va_list ap; + struct socket *so; + struct in6_addr *dst; + struct mbuf *control; + + va_start(ap, m); + so = va_arg(ap, struct socket *); + dst = va_arg(ap, struct in6_addr *); + control = va_arg(ap, struct mbuf *); + va_end(ap); +#endif /* __OpenBSD__ */ + + inp = sotoinpcb(so); + flags = (so->so_options & SO_DONTROUTE); + + if (inp->inp_flags & INP_HDRINCL) + { + flags |= IPV6_RAWOUTPUT; + ipv6stat.ips_rawout++; + /* Maybe m_pullup() ipv6 header here for ipv6_output(), which + expects it to be contiguous. */ + } + else + { + M_PREPEND(m, sizeof(struct ipv6), M_WAIT); + ipv6 = mtod(m, struct ipv6 *); + ipv6->ipv6_nexthdr = inp->inp_ipv6.ipv6_nexthdr; + ipv6->ipv6_hoplimit = MAXHOPLIMIT; + ipv6->ipv6_src = inp->inp_laddr6; + ipv6->ipv6_dst = *dst; + ipv6->ipv6_versfl = 0; /* Or possibly user flow label, in host order. */ + /* + * Question: How do I handle options? + * + * Answer: I put them in here, but how? + */ + } + + { + int payload = sizeof(struct ipv6); + int nexthdr = mtod(m, struct ipv6 *)->ipv6_nexthdr; + int error; + + if (control) + if ((error = ipv6_controltoheader(&m, control, &forceif, &payload))) { + m_freem(m); + return error; + } + + if (inp->inp_csumoffset >= 0) { + uint16_t *csum; + + if (!(m = m_pullup2(m, payload + inp->inp_csumoffset))) { + DPRINTF(IDL_ERROR, ("ripv6_output: m_pullup2(m, %d) failed\n", payload + inp->inp_csumoffset)); + m_freem(m); + return ENOBUFS; + }; + + csum = (uint16_t *)(mtod(m, uint8_t *) + payload + inp->inp_csumoffset); + + *csum = 0; + *csum = in6_cksum(m, nexthdr, m->m_pkthdr.len - payload, payload); + }; + }; + + return ipv6_output(m,&inp->inp_route6,flags,inp->inp_moptions6, forceif, so); +} + +/*---------------------------------------------------------------------- + * Handles [gs]etsockopt() calls. + ----------------------------------------------------------------------*/ + +#if __FreeBSD__ +int ripv6_ctloutput(struct socket *so, struct sockopt *sopt) +{ + register struct inpcb *inp = sotoinpcb(so); + int op; + int level; + int optname; + int optval; + + DPRINTF(FINISHED, ("ripv6_ctloutput(so=%08x, sopt=%08x)\n", + OSDEP_PCAST(so), OSDEP_PCAST(sopt))); + + switch(sopt->sopt_dir) { + case SOPT_GET: + op = PRCO_GETOPT; + break; + case SOPT_SET: + op = PRCO_SETOPT; + break; + default: + RETURN_ERROR(EINVAL); + }; + + level = sopt->sopt_level; + optname = sopt->sopt_name; +#else /* __FreeBSD__ */ +int +ripv6_ctloutput (op, so, level, optname, m) + int op; + struct socket *so; + int level, optname; + struct mbuf **m; +{ + register struct inpcb *inp = sotoinpcb(so); + + DPRINTF(FINISHED, ("ripv6_ctloutput(op=%x,so,level=%x,optname=%x,m)\n", op, level, optname)); +#endif /* __FreeBSD__ */ + + if ((level != IPPROTO_IP) && (level != IPPROTO_IPV6) && (level != IPPROTO_ICMPV6)) { +#if !__FreeBSD__ + if (op == PRCO_SETOPT && *m) + (void)m_free(*m); +#endif /* !__FreeBSD__ */ + RETURN_ERROR(EINVAL); + } + + switch (optname) { + case IPV6_CHECKSUM: + if (op == PRCO_SETOPT || op == PRCO_GETOPT) { +#if __FreeBSD__ + if (!sopt->sopt_val || (sopt->sopt_valsize != sizeof(int))) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) { + int error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)); + if (error) + RETURN_VALUE(error); + inp->inp_csumoffset = optval; + + return 0; + } else + return sooptcopyout(sopt, &inp->inp_csumoffset, sizeof(int)); +#else /* __FreeBSD__ */ + if (!m || !*m || (*m)->m_len != sizeof(int)) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) { + inp->inp_csumoffset = *(mtod(*m, int *)); + m_freem(*m); + } else { + (*m)->m_len = sizeof(int); + *(mtod(*m, int *)) = inp->inp_csumoffset; + }; +#endif /* __FreeBSD__ */ + return 0; + }; + break; + case ICMPV6_FILTER: + if (op == PRCO_SETOPT || op == PRCO_GETOPT) { +#if __FreeBSD__ + if (!sopt->sopt_val || (sopt->sopt_valsize != + sizeof(struct icmpv6_filter))) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) { + struct icmpv6_filter icmpv6_filter; + int error = sooptcopyin(sopt, &icmpv6_filter, + sizeof(struct icmpv6_filter), sizeof(struct icmpv6_filter)); + if (error) + return error; + + inp->inp_filter = icmpv6_filter; + + return 0; + } else + return sooptcopyout(sopt, &inp->inp_filter, + sizeof(struct icmpv6_filter)); +#else /* __FreeBSD__ */ + if (!m || !*m || (*m)->m_len != sizeof(struct icmpv6_filter)) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) { + inp->inp_filter = *mtod(*m, struct icmpv6_filter *); + m_freem(*m); + } else { + (*m)->m_len = sizeof(struct icmpv6_filter); + *mtod(*m, struct icmpv6_filter *) = inp->inp_filter; + }; + return 0; +#endif /* __FreeBSD__ */ + }; + break; + +/* Should this be obsoleted? */ + case IP_HDRINCL: + if (op == PRCO_SETOPT || op == PRCO_GETOPT) + { +#if __FreeBSD__ + if (!sopt->sopt_val || (sopt->sopt_valsize != sizeof(int))) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) { + int error = sooptcopyin(sopt, &optval, sizeof(int), sizeof(int)); + if (error) + return error; + + if (optval) + inp->inp_flags |= INP_HDRINCL; + else + inp->inp_flags &= ~INP_HDRINCL; + + return 0; + } else { + optval = (inp->inp_flags & INP_HDRINCL) ? 1 : 0; + return sooptcopyout(sopt, &optval, sizeof(int)); + }; +#else /* __FreeBSD__ */ + if (m == 0 || *m == 0 || (*m)->m_len != sizeof(int)) + RETURN_ERROR(EINVAL); + if (op == PRCO_SETOPT) + { + if (*mtod(*m, int *)) + inp->inp_flags |= INP_HDRINCL; + else inp->inp_flags &= ~INP_HDRINCL; + m_free(*m); + } + else + { + (*m)->m_len = sizeof(int); + *(mtod(*m, int *)) = (inp->inp_flags & INP_HDRINCL) ? 1 : 0; + } + return 0; +#endif /* __FreeBSD__ */ + } + break; + +#ifdef MRT_INIT + default: + if (optname >= MRT_INIT) { +#else /* MRT_INIT */ + case DVMRP_INIT: + case DVMRP_DONE: + case DVMRP_ADD_VIF: + case DVMRP_DEL_VIF: + case DVMRP_ADD_LGRP: + case DVMRP_DEL_LGRP: + case DVMRP_ADD_MRT: + case DVMRP_DEL_MRT: + { +#endif /* MRT_INIT */ +#ifdef MROUTING +/* Be careful here! */ +/* if (op == PRCO_SETOPT) + { + error = ipv6_mrouter_cmd(optname, so, *m); + if (*m) + (void)m_free(*m); + } + else error = EINVAL; + return (error);*/ + RETURN_ERROR(EOPNOTSUPP); +#else /* MROUTING */ +#if !__FreeBSD__ + if (op == PRCO_SETOPT && *m) + (void)m_free(*m); +#endif /* !__FreeBSD__ */ + RETURN_ERROR(EOPNOTSUPP); +#endif /* MROUTING */ + }; + } +#if __FreeBSD__ + return ipv6_ctloutput(so, sopt); +#else /* __FreeBSD__ */ + return ipv6_ctloutput(op, so, level, optname, m); +#endif /* __FreeBSD__ */ +} + +#if __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ +#define MAYBESTATIC static +#define MAYBEINLINE __inline__ +#else /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ */ +#define MAYBESTATIC +#define MAYBEINLINE +#endif /* __GNUC__ && __GNUC__ >= 2 && __OPTIMIZE__ && !__FreeBSD__ */ + +#if __NetBSD__ || __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_attach(struct socket *so, int proto, + struct proc *p) +#else /* __NetBSD__ || __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_attach(struct socket *so, int proto) +#endif /* __NetBSD__ || __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + register int error = 0; + + if (inp) + panic("ripv6_attach - Already got PCB"); + +#if __NetBSD__ || __FreeBSD__ + if (p == 0 || (error = suser(p->p_ucred, &p->p_acflag))) +#else /* __NetBSD__ || __FreeBSD__ */ + if ((so->so_state & SS_PRIV) == 0) +#endif /* __NetBSD__ || __FreeBSD__ */ + { + error = EACCES; + return error; + } + if ((error = soreserve(so, ripv6_sendspace, ripv6_recvspace)) || + +#if __FreeBSD__ + (error = in_pcballoc(so, &ri6pcbinfo, p))) +#else /* __FreeBSD__ */ +#if __NetBSD__ || __OpenBSD__ + (error = in_pcballoc(so, &rawin6pcbtable))) +#else /* __NetBSD__ || __OpenBSD__ */ + (error = in_pcballoc(so, &rawin6pcb))) +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + + return error; + + inp = sotoinpcb(so); +#ifdef __alpha__ + inp->inp_ipv6.ipv6_nexthdr = (u_long)proto; /*nam; Nam contains protocol + type, apparently. */ +#else + inp->inp_ipv6.ipv6_nexthdr = (int)proto; /*nam; Nam contains protocol + type, apparently. */ +#endif + if (inp->inp_ipv6.ipv6_nexthdr == IPPROTO_ICMPV6) + inp->inp_csumoffset = 2; + return error; +} + +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_detach(struct socket *so) +{ + register struct inpcb *inp = sotoinpcb(so); + + if (inp == 0) + panic("ripv6_detach"); +#ifdef MROUTING + /* More MROUTING stuff. */ +#endif + in_pcbdetach(inp); + return 0; +} + +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_abort(struct socket *so) +{ + soisdisconnected(so); + return ripv6_usrreq_detach(so); +} + +static MAYBEINLINE int ripv6_usrreq_disconnect(struct socket *so) +{ + if ((so->so_state & SS_ISCONNECTED) == 0) + return ENOTCONN; + return ripv6_usrreq_abort(so); +} + +#if __NetBSD__ || __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_bind(struct socket *so, + struct sockaddr *nam, struct proc *p) +#else /* __NetBSD__ || __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_bind(struct socket *so, + struct sockaddr *nam) +#endif /* __NetBSD__ || __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + register struct sockaddr_in6 *addr = (struct sockaddr_in6 *)nam; + + /* 'ifnet' is declared in one of the net/ header files. */ +#if __NetBSD__ || __OpenBSD__ || __FreeBSD__ + if ((ifnet.tqh_first == 0) || +#else /* __NetBSD__ || __OpenBSD__ || __FreeBSD__ */ + if ((ifnet == 0) || +#endif /* __NetBSD__ || __OpenBSD__ || __FreeBSD__ */ + (addr->sin6_family != AF_INET6) || /* I only allow AF_INET6 */ + (!IN6_IS_ADDR_UNSPECIFIED(&addr->sin6_addr) && + ifa_ifwithaddr((struct sockaddr *)addr) == 0 ) ) + + return EADDRNOTAVAIL; + + inp->inp_laddr6 = addr->sin6_addr; + return 0; +} + +#if __NetBSD__ || __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_connect(struct socket *so, + struct sockaddr *nam, struct proc *p) +#else /* __NetBSD__ || __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_connect(struct socket *so, + struct sockaddr *nam) +#endif /* __NetBSD__ || __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + register struct sockaddr_in6 *addr = (struct sockaddr_in6 *) nam; + + if (addr->sin6_family != AF_INET6) + return EAFNOSUPPORT; + +#if __NetBSD__ || __FreeBSD__ || __OpenBSD__ + if (ifnet.tqh_first == 0) { +#else /* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */ + if (ifnet == 0) { +#endif /* __NetBSD__ || __FreeBSD__ || __OpenBSD__ */ + return EADDRNOTAVAIL; /* This is a weird way to say there + are no interfaces, no? */ + } + + inp->inp_faddr6 = addr->sin6_addr; /* Will structure assignment + work with this compiler? */ + soisconnected(so); + return 0; +} + +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_shutdown(struct socket *so) +{ + socantsendmore(so); + return 0; +} + +#if __NetBSD__ || __FreeBSD__ +/* + * Note that flags and p are not used, but required by protosw in + * FreeBSD. + */ +int ripv6_usrreq_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *addr, struct mbuf *control, + struct proc *p) +#else /* __NetBSD__ || __FreeBSD__ */ +int ripv6_usrreq_send(struct socket *so, int flags, struct mbuf *m, + struct sockaddr *addr, struct mbuf *control) +#endif /* __NetBSD__ || __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + register int error = 0; + struct in6_addr *dst; + + if (inp == 0) { + m_freem(m); + return EINVAL; + } + + /* + * Check "connected" status, and if there is a supplied destination + * address. + */ + if (so->so_state & SS_ISCONNECTED) + { + if (addr) + return EISCONN; + + dst = &(inp->inp_faddr6); + } + else + { + if (addr == NULL) + return ENOTCONN; + + dst = &((struct sockaddr_in6 *)addr)->sin6_addr; + } + + error = ripv6_output(m,so,dst,control); + /* m = NULL; */ + return error; +} + +#if __NetBSD__ || __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_control(struct socket *so, u_long cmd, + caddr_t data, struct ifnet *ifp, struct proc *p) +#else /* __NetBSD__ || __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_control(struct socket *so, int cmd, + caddr_t data, struct ifnet *ifp) +#endif /* __NetBSD__ || __FreeBSD__ */ +{ +/* Notice that IPv4 raw sockets don't pass PRU_CONTROL. I wonder + if they panic as well? */ +#if __NetBSD__ || __FreeBSD__ + return in6_control(so, cmd, data, ifp, 0, p); +#else /* __NetBSD__ || __FreeBSD__ */ + return in6_control(so, cmd, data, ifp, 0); +#endif /* __NetBSD__ || __FreeBSD__ */ +} + +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_sense(struct socket *so, + struct stat *sb) +{ + /* services stat(2) call. */ + return 0; +} + +#if __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_sockaddr(struct socket *so, + struct sockaddr **nam) +#else /* __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_sockaddr(struct socket *so, + struct mbuf *nam) +#endif /* __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + return in6_setsockaddr(inp, nam); +} + +#if __FreeBSD__ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_peeraddr(struct socket *so, + struct sockaddr **nam) +#else /* __FreeBSD__ */ +MAYBESTATIC MAYBEINLINE int ripv6_usrreq_peeraddr(struct socket *so, + struct mbuf *nam) +#endif /* __FreeBSD__ */ +{ + register struct inpcb *inp = sotoinpcb(so); + return in6_setpeeraddr(inp, nam); +} + +#if __FreeBSD__ +struct pr_usrreqs ripv6_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, ripv6_usrreq_send, ripv6_usrreq_sense, + ripv6_usrreq_shutdown, ripv6_usrreq_sockaddr, sosend, soreceive, sopoll +}; +#endif /* __FreeBSD__ */ + +/*---------------------------------------------------------------------- + * Handles PRU_* for raw IPv6 sockets. + ----------------------------------------------------------------------*/ +#if !__FreeBSD__ +int +#if __NetBSD__ +ripv6_usrreq(so, req, m, nam, control, p) +#else /* __NetBSD__ */ +ripv6_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 int error = 0; + + DPRINTF(IDL_EVENT, ("ripv6_usrreq(so, req, m, nam, control)\n")); + +#ifdef MROUTING + /* + * Ummm, like, multicast routing stuff goes here, huh huh huh. + * + * Seriously, this would be for user-level multicast routing daemons. With + * multicast being a requirement for IPv6, code like what might go here + * may go away. + */ +#endif + + switch (req) + { + case PRU_ATTACH: +#if __NetBSD__ + error = ripv6_usrreq_attach(so, 0, p); /* XXX */ +#else /* __NetBSD__ */ + error = ripv6_usrreq_attach(so, 0); /* XXX */ +#endif /* __NetBSD__ */ + break; + case PRU_DISCONNECT: + error = ripv6_usrreq_disconnect(so); + break; + /* NOT */ + /* FALLTHROUGH */ + case PRU_ABORT: + error = ripv6_usrreq_abort(so); + break; + /* NOT */ + /* FALLTHROUGH */ + case PRU_DETACH: + error = ripv6_usrreq_detach(so); + break; + case PRU_BIND: + if (nam->m_len != sizeof(struct sockaddr_in6)) + return EINVAL; + /* + * Be strict regarding sockaddr_in6 fields. + */ +#if __NetBSD__ + error = ripv6_usrreq_bind(so, mtod(nam, struct sockaddr *), p); +#else /* __NetBSD__ */ + error = ripv6_usrreq_bind(so, mtod(nam, struct sockaddr *)); +#endif /* __NetBSD__ */ + break; + case PRU_CONNECT: + /* + * Be strict regarding sockaddr_in6 fields. + */ + if (nam->m_len != sizeof(struct sockaddr_in6)) + return EINVAL; +#if __NetBSD__ + error = ripv6_usrreq_connect(so, mtod(nam, struct sockaddr *), p); +#else /* __NetBSD__ */ + error = ripv6_usrreq_connect(so, mtod(nam, struct sockaddr *)); +#endif /* __NetBSD__ */ + break; + case PRU_SHUTDOWN: + error = ripv6_usrreq_shutdown(so); + break; + case PRU_SEND: + /* + * Be strict regarding sockaddr_in6 fields. + */ + if (nam->m_len != sizeof(struct sockaddr_in6)) + return EINVAL; +#if __NetBSD__ + error = ripv6_usrreq_send(so, 0, m, mtod(nam, struct sockaddr *), control, p); +#else /* __NetBSD__ */ + error = ripv6_usrreq_send(so, 0, m, mtod(nam, struct sockaddr *), control); +#endif /* __NetBSD__ */ + m = NULL; + break; + case PRU_CONTROL: +#if __NetBSD__ + return ripv6_usrreq_control(so, (u_long)m, (caddr_t) nam, + (struct ifnet *) control, p); +#else /* __NetBSD__ */ + return ripv6_usrreq_control(so, (int)m, (caddr_t) nam, + (struct ifnet *) control); +#endif /* __NetBSD__ */ + case PRU_SENSE: + return ripv6_usrreq_sense(so, NULL); /* XXX */ + case PRU_CONNECT2: + case PRU_RCVOOB: + case PRU_LISTEN: + case PRU_SENDOOB: + case PRU_RCVD: + case PRU_ACCEPT: + error = EOPNOTSUPP; + break; + case PRU_SOCKADDR: + error = ripv6_usrreq_sockaddr(so, nam); + break; + case PRU_PEERADDR: + error = ripv6_usrreq_peeraddr(so, nam); + break; + default: + panic ("ripv6_usrreq - unknown req\n"); + } + if (m != NULL) + m_freem(m); + return error; +} +#endif /* !__FreeBSD__ */ diff --git a/sys/netinet6/tcpipv6.h b/sys/netinet6/tcpipv6.h new file mode 100644 index 00000000000..57ca9505d21 --- /dev/null +++ b/sys/netinet6/tcpipv6.h @@ -0,0 +1,36 @@ +/* +%%% 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>. + +*/ +#ifndef _NETINET6_TCPIPV6_H +#define _NETINET6_TCPIPV6_H 1 + +#include <netinet6/ipv6.h> +#include <netinet/tcp.h> + +struct tcpipv6hdr { + struct ipv6 ti6_i; + struct tcphdr ti6_t; +}; + +#define ti6_src ti6_i.ipv6_src +#define ti6_dst ti6_i.ipv6_dst +#define ti6_sport ti6_t.th_sport +#define ti6_dport ti6_t.th_dport +#define ti6_seq ti6_t.th_seq +#define ti6_ack ti6_t.th_ack +#define ti6_x2 ti6_t.th_x2 +#define ti6_off ti6_t.th_off +#define ti6_flags ti6_t.th_flags +#define ti6_win ti6_t.th_win +#define ti6_sum ti6_t.th_sum +#define ti6_urp ti6_t.th_urp + +#endif /* _NETINET6_TCPIPV6_H */ |