diff options
Diffstat (limited to 'sys/netinet6/in6_ifattach.c')
-rw-r--r-- | sys/netinet6/in6_ifattach.c | 1126 |
1 files changed, 727 insertions, 399 deletions
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c index 7d332ed..39d26f7 100644 --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -1,3 +1,6 @@ +/* $FreeBSD$ */ +/* $KAME: in6_ifattach.c,v 1.61 2000/06/13 08:15:27 itojun Exp $ */ + /* * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. * All rights reserved. @@ -25,8 +28,6 @@ * 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. - * - * $FreeBSD$ */ #include <sys/param.h> @@ -46,267 +47,657 @@ #include <netinet/in_var.h> #include <netinet/if_ether.h> -#include <netinet6/in6.h> -#include <netinet6/ip6.h> +#include <netinet/ip6.h> #include <netinet6/ip6_var.h> #include <netinet6/in6_ifattach.h> -#include <netinet6/ip6.h> #include <netinet6/ip6_var.h> #include <netinet6/nd6.h> +#include <netinet6/scope6_var.h> #include <net/net_osdep.h> -static struct in6_addr llsol; - -struct in6_ifstat **in6_ifstat = NULL; -struct icmp6_ifstat **icmp6_ifstat = NULL; -size_t in6_ifstatmax = 0; -size_t icmp6_ifstatmax = 0; -unsigned long in6_maxmtu = 0; - -int found_first_ifid = 0; -#define IFID_LEN 8 -static char first_ifid[IFID_LEN]; - -static int laddr_to_eui64 __P((u_int8_t *, u_int8_t *, size_t)); -static int gen_rand_eui64 __P((u_int8_t *)); - -static int -laddr_to_eui64(dst, src, len) - u_int8_t *dst; - u_int8_t *src; - size_t len; -{ - static u_int8_t zero[8]; - - bzero(zero, sizeof(zero)); - - switch (len) { - case 6: - if (bcmp(zero, src, 6) == 0) - return EINVAL; - dst[0] = src[0]; - dst[1] = src[1]; - dst[2] = src[2]; - dst[3] = 0xff; - dst[4] = 0xfe; - dst[5] = src[3]; - dst[6] = src[4]; - dst[7] = src[5]; - break; - case 8: - if (bcmp(zero, src, 8) == 0) - return EINVAL; - bcopy(src, dst, len); - break; - default: - return EINVAL; - } - - return 0; -} +struct in6_ifstat **in6_ifstat = NULL; +struct icmp6_ifstat **icmp6_ifstat = NULL; +size_t in6_ifstatmax = 0; +size_t icmp6_ifstatmax = 0; +unsigned long in6_maxmtu = 0; + +static int get_rand_ifid __P((struct ifnet *, struct in6_addr *)); +static int get_hw_ifid __P((struct ifnet *, struct in6_addr *)); +static int get_ifid __P((struct ifnet *, struct ifnet *, struct in6_addr *)); +static int in6_ifattach_addaddr __P((struct ifnet *, struct in6_ifaddr *)); +static int in6_ifattach_linklocal __P((struct ifnet *, struct ifnet *)); +static int in6_ifattach_loopback __P((struct ifnet *)); +static int nigroup __P((struct ifnet *, const char *, int, struct in6_addr *)); + +#define EUI64_GBIT 0x01 +#define EUI64_UBIT 0x02 +#define EUI64_TO_IFID(in6) do {(in6)->s6_addr[8] ^= EUI64_UBIT; } while (0) +#define EUI64_GROUP(in6) ((in6)->s6_addr[8] & EUI64_GBIT) +#define EUI64_INDIVIDUAL(in6) (!EUI64_GROUP(in6)) +#define EUI64_LOCAL(in6) ((in6)->s6_addr[8] & EUI64_UBIT) +#define EUI64_UNIVERSAL(in6) (!EUI64_LOCAL(in6)) + +#define IFID_LOCAL(in6) (!EUI64_LOCAL(in6)) +#define IFID_UNIVERSAL(in6) (!EUI64_UNIVERSAL(in6)) /* * Generate a last-resort interface identifier, when the machine has no * IEEE802/EUI64 address sources. - * The address should be random, and should not change across reboot. + * The goal here is to get an interface identifier that is + * (1) random enough and (2) does not change across reboot. + * We currently use MD5(hostname) for it. */ static int -gen_rand_eui64(dst) - u_int8_t *dst; +get_rand_ifid(ifp, in6) + struct ifnet *ifp; + struct in6_addr *in6; /*upper 64bits are preserved */ { MD5_CTX ctxt; u_int8_t digest[16]; int hostnamelen = strlen(hostname); - /* generate 8bytes of pseudo-random value. */ +#if 0 + /* we need at least several letters as seed for ifid */ + if (hostnamelen < 3) + return -1; +#endif + + /* generate 8 bytes of pseudo-random value. */ bzero(&ctxt, sizeof(ctxt)); MD5Init(&ctxt); MD5Update(&ctxt, hostname, hostnamelen); MD5Final(digest, &ctxt); - /* assumes sizeof(digest) > sizeof(first_ifid) */ - bcopy(digest, dst, 8); + /* assumes sizeof(digest) > sizeof(ifid) */ + bcopy(digest, &in6->s6_addr[8], 8); /* make sure to set "u" bit to local, and "g" bit to individual. */ - dst[0] &= 0xfe; - dst[0] |= 0x02; /* EUI64 "local" */ + in6->s6_addr[8] &= ~EUI64_GBIT; /* g bit to "individual" */ + in6->s6_addr[8] |= EUI64_UBIT; /* u bit to "local" */ + + /* convert EUI64 into IPv6 interface identifier */ + EUI64_TO_IFID(in6); return 0; } /* - * Find first ifid on list of interfaces. - * This is assumed that ifp0's interface token (for example, IEEE802 MAC) - * is globally unique. We may need to have a flag parameter in the future. + * Get interface identifier for the specified interface. + * XXX assumes single sockaddr_dl (AF_LINK address) per an interface */ -int -in6_ifattach_getifid(ifp0) - struct ifnet *ifp0; -{ +static int +get_hw_ifid(ifp, in6) struct ifnet *ifp; + struct in6_addr *in6; /*upper 64bits are preserved */ +{ struct ifaddr *ifa; - u_int8_t *addr = NULL; - int addrlen = 0; struct sockaddr_dl *sdl; - - if (found_first_ifid) - return 0; - - TAILQ_FOREACH(ifp, &ifnet, if_list) + u_int8_t *addr; + size_t addrlen; + static u_int8_t allzero[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + static u_int8_t allone[8] = + { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + for (ifa = ifp->if_addrlist.tqh_first; + ifa; + ifa = ifa->ifa_list.tqe_next) { - if (ifp0 != NULL && ifp0 != ifp) + if (ifa->ifa_addr->sa_family != AF_LINK) continue; - TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) - { - if (ifa->ifa_addr->sa_family != AF_LINK) - continue; - sdl = (struct sockaddr_dl *)ifa->ifa_addr; - if (sdl == NULL) - continue; - if (sdl->sdl_alen == 0) - continue; - switch (ifp->if_type) { - case IFT_ETHER: - case IFT_FDDI: - case IFT_ATM: - /* IEEE802/EUI64 cases - what others? */ - addr = LLADDR(sdl); - addrlen = sdl->sdl_alen; - /* - * to copy ifid from IEEE802/EUI64 interface, - * u bit of the source needs to be 0. - */ - if ((addr[0] & 0x02) != 0) - break; - goto found; - case IFT_ARCNET: - /* - * ARCnet interface token cannot be used as - * globally unique identifier due to its - * small bitwidth. - */ - break; - default: - break; - } - } + sdl = (struct sockaddr_dl *)ifa->ifa_addr; + if (sdl == NULL) + continue; + if (sdl->sdl_alen == 0) + continue; + + goto found; } -#ifdef DEBUG - printf("in6_ifattach_getifid: failed to get EUI64"); -#endif - return EADDRNOTAVAIL; + + return -1; found: - if (laddr_to_eui64(first_ifid, addr, addrlen) == 0) - found_first_ifid = 1; - - if (found_first_ifid) { - printf("%s: supplying EUI64: " - "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - if_name(ifp), - first_ifid[0] & 0xff, first_ifid[1] & 0xff, - first_ifid[2] & 0xff, first_ifid[3] & 0xff, - first_ifid[4] & 0xff, first_ifid[5] & 0xff, - first_ifid[6] & 0xff, first_ifid[7] & 0xff); - - /* invert u bit to convert EUI64 to RFC2373 interface ID. */ - first_ifid[0] ^= 0x02; - - return 0; - } else { -#ifdef DEBUG - printf("in6_ifattach_getifid: failed to get EUI64"); + addr = LLADDR(sdl); + addrlen = sdl->sdl_alen; + + /* get EUI64 */ + switch (ifp->if_type) { + case IFT_ETHER: + case IFT_FDDI: + case IFT_ATM: + /* IEEE802/EUI64 cases - what others? */ + + /* look at IEEE802/EUI64 only */ + if (addrlen != 8 && addrlen != 6) + return -1; + + /* + * check for invalid MAC address - on bsdi, we see it a lot + * since wildboar configures all-zero MAC on pccard before + * card insertion. + */ + if (bcmp(addr, allzero, addrlen) == 0) + return -1; + if (bcmp(addr, allone, addrlen) == 0) + return -1; + + /* make EUI64 address */ + if (addrlen == 8) + bcopy(addr, &in6->s6_addr[8], 8); + else if (addrlen == 6) { + in6->s6_addr[8] = addr[0]; + in6->s6_addr[9] = addr[1]; + in6->s6_addr[10] = addr[2]; + in6->s6_addr[11] = 0xff; + in6->s6_addr[12] = 0xfe; + in6->s6_addr[13] = addr[3]; + in6->s6_addr[14] = addr[4]; + in6->s6_addr[15] = addr[5]; + } + break; + + case IFT_ARCNET: + if (addrlen != 1) + return -1; + if (!addr[0]) + return -1; + + bzero(&in6->s6_addr[8], 8); + in6->s6_addr[15] = addr[0]; + + /* + * due to insufficient bitwidth, we mark it local. + */ + in6->s6_addr[8] &= ~EUI64_GBIT; /* g bit to "individual" */ + in6->s6_addr[8] |= EUI64_UBIT; /* u bit to "local" */ + break; + + case IFT_GIF: +#ifdef IFT_STF + case IFT_STF: #endif - return EADDRNOTAVAIL; + /* + * mech-06 says: "SHOULD use IPv4 address as ifid source". + * however, IPv4 address is not very suitable as unique + * identifier source (can be renumbered). + * we don't do this. + */ + return -1; + + default: + return -1; } + + /* sanity check: g bit must not indicate "group" */ + if (EUI64_GROUP(in6)) + return -1; + + /* convert EUI64 into IPv6 interface identifier */ + EUI64_TO_IFID(in6); + + /* + * sanity check: ifid must not be all zero, avoid conflict with + * subnet router anycast + */ + if ((in6->s6_addr[8] & ~(EUI64_GBIT | EUI64_UBIT)) == 0x00 && + bcmp(&in6->s6_addr[9], allzero, 7) == 0) { + return -1; + } + + return 0; } /* - * add link-local address to *pseudo* p2p interfaces. - * get called when the first MAC address is made available in in6_ifattach(). - * - * XXX I start considering this loop as a bad idea. (itojun) + * Get interface identifier for the specified interface. If it is not + * available on ifp0, borrow interface identifier from other information + * sources. */ -void -in6_ifattach_p2p() +static int +get_ifid(ifp0, altifp, in6) + struct ifnet *ifp0; + struct ifnet *altifp; /*secondary EUI64 source*/ + struct in6_addr *in6; { struct ifnet *ifp; - /* prevent infinite loop. just in case. */ - if (found_first_ifid == 0) - return; + /* first, try to get it from the interface itself */ + if (get_hw_ifid(ifp0, in6) == 0) { +#ifdef ND6_DEBUG + printf("%s: got interface identifier from itself\n", + if_name(ifp0)); +#endif + goto success; + } - TAILQ_FOREACH(ifp, &ifnet, if_list) + /* try secondary EUI64 source. this basically is for ATM PVC */ + if (altifp && get_hw_ifid(altifp, in6) == 0) { +#ifdef ND6_DEBUG + printf("%s: got interface identifier from %s\n", + if_name(ifp0), if_name(altifp)); +#endif + goto success; + } + + /* next, try to get it from some other hardware interface */ + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) { - switch (ifp->if_type) { - case IFT_GIF: - /* pseudo interfaces - safe to initialize here */ - in6_ifattach(ifp, IN6_IFT_P2P, 0, 0); - break; -#ifdef IFT_DUMMY - case IFT_DUMMY: + if (ifp == ifp0) + continue; + if (get_hw_ifid(ifp, in6) != 0) + continue; + + /* + * to borrow ifid from other interface, ifid needs to be + * globally unique + */ + if (IFID_UNIVERSAL(in6)) { + +#ifdef ND6_DEBUG + printf("%s: borrow interface identifier from %s\n", + if_name(ifp0), if_name(ifp)); #endif - case IFT_FAITH: - /* this mistakingly becomes IFF_UP */ + goto success; + } + } + + /* last resort: get from random number source */ + if (get_rand_ifid(ifp, in6) == 0) { +#ifdef ND6_DEBUG + printf("%s: interface identifier generated by random number\n", + if_name(ifp0)); +#endif + goto success; + } + + printf("%s: failed to get interface identifier", if_name(ifp0)); + return -1; + +success: +#ifdef ND6_DEBUG + printf("%s: ifid: " + "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + if_name(ifp0), + in6->s6_addr[8], in6->s6_addr[9], + in6->s6_addr[10], in6->s6_addr[11], + in6->s6_addr[12], in6->s6_addr[13], + in6->s6_addr[14], in6->s6_addr[15]); +#endif + return 0; +} + +/* + * configure IPv6 interface address. XXX code duplicated with in.c + */ +static int +in6_ifattach_addaddr(ifp, ia) + struct ifnet *ifp; + struct in6_ifaddr *ia; +{ + struct in6_ifaddr *oia; + struct ifaddr *ifa; + int error; + int rtflag; + struct in6_addr llsol; + + /* + * initialize if_addrlist, if we are the very first one + */ + ifa = TAILQ_FIRST(&ifp->if_addrlist); + if (ifa == NULL) { + TAILQ_INIT(&ifp->if_addrlist); + } + + /* + * link the interface address to global list + */ + TAILQ_INSERT_TAIL(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); + /* gain a refcnt for the link from if_addrlist */ + ia->ia_ifa.ifa_refcnt++; + + /* + * Also link into the IPv6 address chain beginning with in6_ifaddr. + * kazu opposed it, but itojun & jinmei wanted. + */ + if ((oia = in6_ifaddr) != NULL) { + for (; oia->ia_next; oia = oia->ia_next) + continue; + oia->ia_next = ia; + } else + in6_ifaddr = ia; + /* gain another refcnt for the link from in6_ifaddr */ + ia->ia_ifa.ifa_refcnt++; + + /* + * give the interface a chance to initialize, in case this + * is the first address to be added. + */ + if (ifp->if_ioctl != NULL) { + int s; + s = splimp(); + error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia); + splx(s); + } else + error = 0; + if (error) { + switch (error) { + case EAFNOSUPPORT: + printf("%s: IPv6 not supported\n", if_name(ifp)); break; - case IFT_SLIP: - /* IPv6 is not supported */ + default: + printf("%s: SIOCSIFADDR error %d\n", if_name(ifp), + error); break; - case IFT_PPP: - /* this is not a pseudo interface, skip it */ + } + + /* undo changes */ + TAILQ_REMOVE(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); + IFAFREE(&ia->ia_ifa); + if (oia) + oia->ia_next = ia->ia_next; + else + in6_ifaddr = ia->ia_next; + IFAFREE(&ia->ia_ifa); + return -1; + } + + /* configure link-layer address resolution */ + rtflag = 0; + if (IN6_ARE_ADDR_EQUAL(&ia->ia_prefixmask.sin6_addr, &in6mask128)) + rtflag = RTF_HOST; + else { + switch (ifp->if_type) { + case IFT_LOOP: +#ifdef IFT_STF + case IFT_STF: +#endif + rtflag = 0; break; default: + ia->ia_ifa.ifa_rtrequest = nd6_rtrequest; + ia->ia_ifa.ifa_flags |= RTF_CLONING; + rtflag = RTF_CLONING; break; } } + + /* add route to the interface. */ + { + int e; + + e = rtrequest(RTM_ADD, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&ia->ia_prefixmask, + RTF_UP | rtflag, + (struct rtentry **)0); + if (e) { + printf("in6_ifattach_addaddr: rtrequest failed. errno = %d\n", e); + } + } + ia->ia_flags |= IFA_ROUTE; + + if ((rtflag & RTF_CLONING) != 0 && + (ifp->if_flags & IFF_MULTICAST) != 0) { + /* + * join solicited multicast address + */ + bzero(&llsol, sizeof(llsol)); + llsol.s6_addr16[0] = htons(0xff02); + llsol.s6_addr16[1] = htons(ifp->if_index); + llsol.s6_addr32[1] = 0; + llsol.s6_addr32[2] = htonl(1); + llsol.s6_addr32[3] = ia->ia_addr.sin6_addr.s6_addr32[3]; + llsol.s6_addr8[12] = 0xff; + (void)in6_addmulti(&llsol, ifp, &error); + + /* XXX should we run DAD on other interface types? */ + switch (ifp->if_type) { +#if 1 + case IFT_ARCNET: + case IFT_ETHER: + case IFT_FDDI: +#else + default: +#endif + /* mark the address TENTATIVE, if needed. */ + ia->ia6_flags |= IN6_IFF_TENTATIVE; + /* nd6_dad_start() will be called in in6_if_up */ + } + } + + return 0; } -void -in6_ifattach(ifp, type, laddr, noloop) +static int +in6_ifattach_linklocal(ifp, altifp) struct ifnet *ifp; - u_int type; - caddr_t laddr; - /* size_t laddrlen; */ - int noloop; + struct ifnet *altifp; /*secondary EUI64 source*/ { - static size_t if_indexlim = 8; - struct sockaddr_in6 mltaddr; - struct sockaddr_in6 mltmask; - struct sockaddr_in6 gate; - struct sockaddr_in6 mask; + struct in6_ifaddr *ia; - struct in6_ifaddr *ia, *ib, *oia; - struct ifaddr *ifa; - int rtflag = 0; - - if (type == IN6_IFT_P2P && found_first_ifid == 0) { - printf("%s: no ifid available for IPv6 link-local address\n", - if_name(ifp)); - /* last resort */ - if (gen_rand_eui64(first_ifid) == 0) { - printf("%s: using random value as EUI64: " - "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - if_name(ifp), - first_ifid[0] & 0xff, first_ifid[1] & 0xff, - first_ifid[2] & 0xff, first_ifid[3] & 0xff, - first_ifid[4] & 0xff, first_ifid[5] & 0xff, - first_ifid[6] & 0xff, first_ifid[7] & 0xff); - /* - * invert u bit to convert EUI64 to RFC2373 interface - * ID. - */ - first_ifid[0] ^= 0x02; + /* + * configure link-local address + */ + ia = (struct in6_ifaddr *)malloc(sizeof(*ia), M_IFADDR, M_WAITOK); + bzero((caddr_t)ia, sizeof(*ia)); + ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; + if (ifp->if_flags & IFF_POINTOPOINT) + ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; + else + ia->ia_ifa.ifa_dstaddr = NULL; + ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; + ia->ia_ifp = ifp; + + bzero(&ia->ia_prefixmask, sizeof(ia->ia_prefixmask)); + ia->ia_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_prefixmask.sin6_family = AF_INET6; +#ifdef SCOPEDROUTING + /* take into accound the sin6_scope_id field for routing */ + ia->ia_prefixmask.sin6_scope_id = 0xffffffff; +#endif + ia->ia_prefixmask.sin6_addr = in6mask64; - found_first_ifid = 1; + /* just in case */ + bzero(&ia->ia_dstaddr, sizeof(ia->ia_dstaddr)); + ia->ia_dstaddr.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_dstaddr.sin6_family = AF_INET6; + + bzero(&ia->ia_addr, sizeof(ia->ia_addr)); + ia->ia_addr.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_addr.sin6_family = AF_INET6; + ia->ia_addr.sin6_addr.s6_addr16[0] = htons(0xfe80); + ia->ia_addr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + ia->ia_addr.sin6_addr.s6_addr32[1] = 0; + if (ifp->if_flags & IFF_LOOPBACK) { + ia->ia_addr.sin6_addr.s6_addr32[2] = 0; + ia->ia_addr.sin6_addr.s6_addr32[3] = htonl(1); + } else { + if (get_ifid(ifp, altifp, &ia->ia_addr.sin6_addr) != 0) { +#ifdef ND6_DEBUG + printf("%s: no ifid available\n", if_name(ifp)); +#endif + free(ia, M_IFADDR); + return -1; } } +#ifdef SCOPEDROUTING + ia->ia_addr.sin6_scope_id = in6_addr2scopeid(ifp, + &ia->ia_addr.sin6_addr); +#endif - if ((ifp->if_flags & IFF_MULTICAST) == 0) { - printf("%s: not multicast capable, IPv6 not enabled\n", - if_name(ifp)); + ia->ia_ifa.ifa_metric = ifp->if_metric; + + if (in6_ifattach_addaddr(ifp, ia) != 0) { + /* ia will be freed on failure */ + return -1; + } + + return 0; +} + +static int +in6_ifattach_loopback(ifp) + struct ifnet *ifp; /* must be IFT_LOOP */ +{ + struct in6_ifaddr *ia; + + /* + * configure link-local address + */ + ia = (struct in6_ifaddr *)malloc(sizeof(*ia), M_IFADDR, M_WAITOK); + bzero((caddr_t)ia, sizeof(*ia)); + ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; + ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; + ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; + ia->ia_ifp = ifp; + + bzero(&ia->ia_prefixmask, sizeof(ia->ia_prefixmask)); + ia->ia_prefixmask.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_prefixmask.sin6_family = AF_INET6; + ia->ia_prefixmask.sin6_addr = in6mask128; + + /* + * Always initialize ia_dstaddr (= broadcast address) to loopback + * address, to make getifaddr happier. + * + * For BSDI, it is mandatory. The BSDI version of + * ifa_ifwithroute() rejects to add a route to the loopback + * interface. Even for other systems, loopback looks somewhat + * special. + */ + bzero(&ia->ia_dstaddr, sizeof(ia->ia_dstaddr)); + ia->ia_dstaddr.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_dstaddr.sin6_family = AF_INET6; + ia->ia_dstaddr.sin6_addr = in6addr_loopback; + + bzero(&ia->ia_addr, sizeof(ia->ia_addr)); + ia->ia_addr.sin6_len = sizeof(struct sockaddr_in6); + ia->ia_addr.sin6_family = AF_INET6; + ia->ia_addr.sin6_addr = in6addr_loopback; + + ia->ia_ifa.ifa_metric = ifp->if_metric; + + if (in6_ifattach_addaddr(ifp, ia) != 0) { + /* ia will be freed on failure */ + return -1; + } + + return 0; +} + +/* + * compute NI group address, based on the current hostname setting. + * see draft-ietf-ipngwg-icmp-name-lookup-* (04 and later). + * + * when ifp == NULL, the caller is responsible for filling scopeid. + */ +static int +nigroup(ifp, name, namelen, in6) + struct ifnet *ifp; + const char *name; + int namelen; + struct in6_addr *in6; +{ + const char *p; + MD5_CTX ctxt; + u_int8_t digest[16]; + char l; + + if (!namelen || !name) + return -1; + + p = name; + while (p && *p && *p != '.' && p - name < namelen) + p++; + if (p - name > 63) + return -1; /*label too long*/ + l = p - name; + + /* generate 8 bytes of pseudo-random value. */ + bzero(&ctxt, sizeof(ctxt)); + MD5Init(&ctxt); + MD5Update(&ctxt, &l, sizeof(l)); + /* LINTED const cast */ + MD5Update(&ctxt, (void *)name, p - name); + MD5Final(digest, &ctxt); + + bzero(in6, sizeof(*in6)); + in6->s6_addr16[0] = htons(0xff02); + if (ifp) + in6->s6_addr16[1] = htons(ifp->if_index); + in6->s6_addr8[11] = 2; + bcopy(digest, &in6->s6_addr32[3], sizeof(in6->s6_addr32[3])); + + return 0; +} + +void +in6_nigroup_attach(name, namelen) + const char *name; + int namelen; +{ + struct ifnet *ifp; + struct sockaddr_in6 mltaddr; + struct in6_multi *in6m; + int error; + + bzero(&mltaddr, sizeof(mltaddr)); + mltaddr.sin6_family = AF_INET6; + mltaddr.sin6_len = sizeof(struct sockaddr_in6); + if (nigroup(NULL, name, namelen, &mltaddr.sin6_addr) != 0) + return; + + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) + { + mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (!in6m) + (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); + } +} + +void +in6_nigroup_detach(name, namelen) + const char *name; + int namelen; +{ + struct ifnet *ifp; + struct sockaddr_in6 mltaddr; + struct in6_multi *in6m; + + bzero(&mltaddr, sizeof(mltaddr)); + mltaddr.sin6_family = AF_INET6; + mltaddr.sin6_len = sizeof(struct sockaddr_in6); + if (nigroup(NULL, name, namelen, &mltaddr.sin6_addr) != 0) return; + + for (ifp = ifnet.tqh_first; ifp; ifp = ifp->if_list.tqe_next) + { + mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m) + in6_delmulti(in6m); } +} + +/* + * XXX multiple loopback interface needs more care. for instance, + * nodelocal address needs to be configured onto only one of them. + * XXX multiple link-local address case + */ +void +in6_ifattach(ifp, altifp) + struct ifnet *ifp; + struct ifnet *altifp; /* secondary EUI64 source */ +{ + static size_t if_indexlim = 8; + struct sockaddr_in6 mltaddr; + struct sockaddr_in6 mltmask; + struct sockaddr_in6 gate; + struct sockaddr_in6 mask; + struct in6_ifaddr *ia; + struct in6_addr in6; + int hostnamelen = strlen(hostname); /* * We have some arrays that should be indexed by if_index. @@ -314,8 +705,8 @@ in6_ifattach(ifp, type, laddr, noloop) * struct in6_ifstat **in6_ifstat * struct icmp6_ifstat **icmp6_ifstat */ - if (in6_ifstat == NULL || icmp6_ifstat == NULL - || if_index >= if_indexlim) { + if (in6_ifstat == NULL || icmp6_ifstat == NULL || + if_index >= if_indexlim) { size_t n; caddr_t q; size_t olim; @@ -349,144 +740,53 @@ in6_ifattach(ifp, type, laddr, noloop) icmp6_ifstatmax = if_indexlim; } + /* initialize scope identifiers */ + scope6_ifattach(ifp); + /* - * To prevent to assign link-local address to PnP network - * cards multiple times. - * This is lengthy for P2P and LOOP but works. + * quirks based on interface type */ - ifa = TAILQ_FIRST(&ifp->if_addrlist); - if (ifa != NULL) { - for ( ; ifa; ifa = TAILQ_NEXT(ifa, ifa_list)) { - if (ifa->ifa_addr->sa_family != AF_INET6) - continue; - if (IN6_IS_ADDR_LINKLOCAL(&satosin6(ifa->ifa_addr)->sin6_addr)) - return; - } - } else { - TAILQ_INIT(&ifp->if_addrlist); + switch (ifp->if_type) { +#ifdef IFT_STF + case IFT_STF: + /* + * 6to4 interface is a very speical kind of beast. + * no multicast, no linklocal (based on 03 draft). + */ + goto statinit; +#endif + default: + break; } /* - * link-local address + * usually, we require multicast capability to the interface */ - ia = (struct in6_ifaddr *)malloc(sizeof(*ia), M_IFADDR, M_WAITOK); - bzero((caddr_t)ia, sizeof(*ia)); - ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; - ia->ia_ifa.ifa_dstaddr = (struct sockaddr *)&ia->ia_dstaddr; - ia->ia_ifa.ifa_netmask = (struct sockaddr *)&ia->ia_prefixmask; - ia->ia_ifp = ifp; - TAILQ_INSERT_TAIL(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); - /* - * Also link into the IPv6 address chain beginning with in6_ifaddr. - * kazu opposed it, but itojun & jinmei wanted. - */ - if ((oia = in6_ifaddr) != NULL) { - for (; oia->ia_next; oia = oia->ia_next) - continue; - oia->ia_next = ia; - } else - in6_ifaddr = ia; - - ia->ia_prefixmask.sin6_len = sizeof(struct sockaddr_in6); - ia->ia_prefixmask.sin6_family = AF_INET6; - ia->ia_prefixmask.sin6_addr = in6mask64; - - bzero(&ia->ia_addr, sizeof(struct sockaddr_in6)); - ia->ia_addr.sin6_len = sizeof(struct sockaddr_in6); - ia->ia_addr.sin6_family = AF_INET6; - ia->ia_addr.sin6_addr.s6_addr16[0] = htons(0xfe80); - ia->ia_addr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); - ia->ia_addr.sin6_addr.s6_addr32[1] = 0; - - switch (type) { - case IN6_IFT_LOOP: - ia->ia_addr.sin6_addr.s6_addr32[2] = 0; - ia->ia_addr.sin6_addr.s6_addr32[3] = htonl(1); - break; - case IN6_IFT_802: - ia->ia_ifa.ifa_rtrequest = nd6_rtrequest; - ia->ia_ifa.ifa_flags |= RTF_CLONING; - rtflag = RTF_CLONING; - /* fall through */ - case IN6_IFT_P2P802: - if (laddr == NULL) - break; - /* XXX use laddrlen */ - if (laddr_to_eui64(&ia->ia_addr.sin6_addr.s6_addr8[8], - laddr, 6) != 0) { - break; - } - /* invert u bit to convert EUI64 to RFC2373 interface ID. */ - ia->ia_addr.sin6_addr.s6_addr8[8] ^= 0x02; - if (found_first_ifid == 0) { - if (in6_ifattach_getifid(ifp) == 0) - in6_ifattach_p2p(); - } - break; - case IN6_IFT_P2P: - bcopy((caddr_t)first_ifid, - (caddr_t)&ia->ia_addr.sin6_addr.s6_addr8[8], - IFID_LEN); - break; - case IN6_IFT_ARCNET: - ia->ia_ifa.ifa_rtrequest = nd6_rtrequest; - ia->ia_ifa.ifa_flags |= RTF_CLONING; - rtflag = RTF_CLONING; - if (laddr == NULL) - break; - - /* make non-global IF id out of link-level address */ - bzero(&ia->ia_addr.sin6_addr.s6_addr8[8], 7); - ia->ia_addr.sin6_addr.s6_addr8[15] = *laddr; + if ((ifp->if_flags & IFF_MULTICAST) == 0) { + printf("%s: not multicast capable, IPv6 not enabled\n", + if_name(ifp)); + return; } - ia->ia_ifa.ifa_metric = ifp->if_metric; - - if (ifp->if_ioctl != NULL) { - int s; - int error; - - /* - * give the interface a chance to initialize, in case this - * is the first address to be added. - */ - s = splimp(); - error = (*ifp->if_ioctl)(ifp, SIOCSIFADDR, (caddr_t)ia); - splx(s); + /* + * assign link-local address, if there's none + */ + ia = in6ifa_ifpforlinklocal(ifp, 0); + if (ia == NULL) { + if (in6_ifattach_linklocal(ifp, altifp) != 0) + return; + ia = in6ifa_ifpforlinklocal(ifp, 0); - if (error) { - switch (error) { - case EAFNOSUPPORT: - printf("%s: IPv6 not supported\n", - if_name(ifp)); - break; - default: - printf("%s: SIOCSIFADDR error %d\n", - if_name(ifp), error); - break; - } + if (ia == NULL) { + printf("%s: failed to add link-local address", + if_name(ifp)); - /* undo changes */ - TAILQ_REMOVE(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); - if (oia) - oia->ia_next = ia->ia_next; - else - in6_ifaddr = ia->ia_next; - free(ia, M_IFADDR); - return; + /* we can't initialize multicasts without link-local */ + goto statinit; } } - /* add route to the interface. */ - rtrequest(RTM_ADD, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&ia->ia_prefixmask, - RTF_UP|rtflag, - (struct rtentry **)0); - ia->ia_flags |= IFA_ROUTE; - - if (type == IN6_IFT_P2P || type == IN6_IFT_P2P802) { + if (ifp->if_flags & IFF_POINTOPOINT) { /* * route local address to loopback */ @@ -507,45 +807,30 @@ in6_ifattach(ifp, type, laddr, noloop) } /* - * loopback address + * assign loopback address for loopback interface + * XXX multiple loopback interface case */ - ib = (struct in6_ifaddr *)NULL; - if (type == IN6_IFT_LOOP) { - ib = (struct in6_ifaddr *) - malloc(sizeof(*ib), M_IFADDR, M_WAITOK); - bzero((caddr_t)ib, sizeof(*ib)); - ib->ia_ifa.ifa_addr = (struct sockaddr *)&ib->ia_addr; - ib->ia_ifa.ifa_dstaddr = (struct sockaddr *)&ib->ia_dstaddr; - ib->ia_ifa.ifa_netmask = (struct sockaddr *)&ib->ia_prefixmask; - ib->ia_ifp = ifp; - - ia->ia_next = ib; - TAILQ_INSERT_TAIL(&ifp->if_addrlist, (struct ifaddr *)ib, - ifa_list); - - ib->ia_prefixmask.sin6_len = sizeof(struct sockaddr_in6); - ib->ia_prefixmask.sin6_family = AF_INET6; - ib->ia_prefixmask.sin6_addr = in6mask128; - ib->ia_addr.sin6_len = sizeof(struct sockaddr_in6); - ib->ia_addr.sin6_family = AF_INET6; - ib->ia_addr.sin6_addr = in6addr_loopback; - ib->ia_ifa.ifa_metric = ifp->if_metric; - - rtrequest(RTM_ADD, - (struct sockaddr *)&ib->ia_addr, - (struct sockaddr *)&ib->ia_addr, - (struct sockaddr *)&ib->ia_prefixmask, - RTF_UP|RTF_HOST, - (struct rtentry **)0); + in6 = in6addr_loopback; + if (ifp->if_flags & IFF_LOOPBACK) { + if (in6ifa_ifpwithaddr(ifp, &in6) == NULL) { + if (in6_ifattach_loopback(ifp) != 0) + return; + } + } - ib->ia_flags |= IFA_ROUTE; +#ifdef DIAGNOSTIC + if (!ia) { + panic("ia == NULL in in6_ifattach"); + /*NOTREACHED*/ } +#endif /* * join multicast */ if (ifp->if_flags & IFF_MULTICAST) { int error; /* not used */ + struct in6_multi *in6m; bzero(&mltmask, sizeof(mltmask)); mltmask.sin6_len = sizeof(struct sockaddr_in6); @@ -560,41 +845,53 @@ in6_ifattach(ifp, type, laddr, noloop) mltaddr.sin6_family = AF_INET6; mltaddr.sin6_addr = in6addr_linklocal_allnodes; mltaddr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); - rtrequest(RTM_ADD, - (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ia->ia_addr, - (struct sockaddr *)&mltmask, - RTF_UP|RTF_CLONING, /* xxx */ - (struct rtentry **)0); - (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); - if (type == IN6_IFT_LOOP) { - /* - * join node-local all-nodes address - */ - mltaddr.sin6_addr = in6addr_nodelocal_allnodes; + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL) { rtrequest(RTM_ADD, (struct sockaddr *)&mltaddr, - (struct sockaddr *)&ib->ia_addr, + (struct sockaddr *)&ia->ia_addr, (struct sockaddr *)&mltmask, - RTF_UP, + RTF_UP|RTF_CLONING, /* xxx */ (struct rtentry **)0); (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); - } else { + } + + /* + * join node information group address + */ + if (nigroup(ifp, hostname, hostnamelen, &mltaddr.sin6_addr) + == 0) { + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL && ia != NULL) { + (void)in6_addmulti(&mltaddr.sin6_addr, + ifp, &error); + } + } + + if (ifp->if_flags & IFF_LOOPBACK) { + in6 = in6addr_loopback; + ia = in6ifa_ifpwithaddr(ifp, &in6); /* - * join solicited multicast address + * join node-local all-nodes address, on loopback */ - bzero(&llsol, sizeof(llsol)); - llsol.s6_addr16[0] = htons(0xff02); - llsol.s6_addr16[1] = htons(ifp->if_index); - llsol.s6_addr32[1] = 0; - llsol.s6_addr32[2] = htonl(1); - llsol.s6_addr32[3] = ia->ia_addr.sin6_addr.s6_addr32[3]; - llsol.s6_addr8[12] = 0xff; - (void)in6_addmulti(&llsol, ifp, &error); + mltaddr.sin6_addr = in6addr_nodelocal_allnodes; + + IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); + if (in6m == NULL && ia != NULL) { + rtrequest(RTM_ADD, + (struct sockaddr *)&mltaddr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&mltmask, + RTF_UP, + (struct rtentry **)0); + (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); + } } } +statinit:; + /* update dynamically. */ if (in6_maxmtu < ifp->if_mtu) in6_maxmtu = ifp->if_mtu; @@ -612,39 +909,44 @@ in6_ifattach(ifp, type, laddr, noloop) /* initialize NDP variables */ nd6_ifattach(ifp); - - /* mark the address TENTATIVE, if needed. */ - switch (ifp->if_type) { - case IFT_ARCNET: - case IFT_ETHER: - case IFT_FDDI: - ia->ia6_flags |= IN6_IFF_TENTATIVE; - /* nd6_dad_start() will be called in in6_if_up */ - break; -#ifdef IFT_DUMMY - case IFT_DUMMY: -#endif - case IFT_GIF: /*XXX*/ - case IFT_LOOP: - case IFT_FAITH: - default: - break; - } - - return; } +/* + * NOTE: in6_ifdetach() does not support loopback if at this moment. + */ void in6_ifdetach(ifp) struct ifnet *ifp; { struct in6_ifaddr *ia, *oia; - struct ifaddr *ifa; + struct ifaddr *ifa, *next; struct rtentry *rt; short rtflags; + struct sockaddr_in6 sin6; + struct in6_multi *in6m; + struct in6_multi *in6m_next; - TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) + /* nuke prefix list. this may try to remove some of ifaddrs as well */ + in6_purgeprefix(ifp); + + /* remove neighbor management table */ + nd6_purge(ifp); + + /* nuke any of IPv6 addresses we have */ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = next) { + next = ifa->ifa_list.tqe_next; + if (ifa->ifa_addr->sa_family != AF_INET6) + continue; + in6_purgeaddr(ifa, ifp); + } + + /* undo everything done by in6_ifattach(), just in case */ + for (ifa = ifp->if_addrlist.tqh_first; ifa; ifa = next) + { + next = ifa->ifa_list.tqe_next; + + if (ifa->ifa_addr->sa_family != AF_INET6 || !IN6_IS_ADDR_LINKLOCAL(&satosin6(&ifa->ifa_addr)->sin6_addr)) { continue; @@ -666,6 +968,7 @@ in6_ifdetach(ifp) /* remove from the linked list */ TAILQ_REMOVE(&ifp->if_addrlist, (struct ifaddr *)ia, ifa_list); + IFAFREE(&ia->ia_ifa); /* also remove from the IPv6 address chain(itojun&jinmei) */ oia = ia; @@ -676,13 +979,38 @@ in6_ifdetach(ifp) ia = ia->ia_next; if (ia->ia_next) ia->ia_next = oia->ia_next; -#ifdef DEBUG +#ifdef ND6_DEBUG else printf("%s: didn't unlink in6ifaddr from " "list\n", if_name(ifp)); #endif } - free(ia, M_IFADDR); + IFAFREE(&oia->ia_ifa); + } + + /* leave from all multicast groups joined */ + for (in6m = LIST_FIRST(&in6_multihead); in6m; in6m = in6m_next) { + in6m_next = LIST_NEXT(in6m, in6m_entry); + if (in6m->in6m_ifp != ifp) + continue; + in6_delmulti(in6m); + in6m = NULL; + } + + /* remove neighbor management table */ + nd6_purge(ifp); + + /* remove route to link-local allnodes multicast (ff02::1) */ + bzero(&sin6, sizeof(sin6)); + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_linklocal_allnodes; + sin6.sin6_addr.s6_addr16[1] = htons(ifp->if_index); + if ((rt = rtalloc1((struct sockaddr *)&sin6, 0, 0UL)) != NULL) + { + rtrequest(RTM_DELETE, (struct sockaddr *)rt_key(rt), + rt->rt_gateway, rt_mask(rt), rt->rt_flags, 0); + rtfree(rt); } } |