diff options
Diffstat (limited to 'sys/netinet6/in6.c')
-rw-r--r-- | sys/netinet6/in6.c | 910 |
1 files changed, 910 insertions, 0 deletions
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); +} |