diff options
author | suz <suz@FreeBSD.org> | 2005-10-21 16:23:01 +0000 |
---|---|---|
committer | suz <suz@FreeBSD.org> | 2005-10-21 16:23:01 +0000 |
commit | c2b19f24a4ba01108e047a35a4a060cbfdf28a17 (patch) | |
tree | b24292a814b8011ab44cabf6cac263a8a214a60a /sys/netinet6/in6.c | |
parent | 6ee4447c50d54cfee8b2556c2cdcc45dc05bca37 (diff) | |
download | FreeBSD-src-c2b19f24a4ba01108e047a35a4a060cbfdf28a17.zip FreeBSD-src-c2b19f24a4ba01108e047a35a4a060cbfdf28a17.tar.gz |
sync with KAME regarding NDP
- introduced fine-grain-timer to manage ND-caches and IPv6 Multicast-Listeners
- supports Router-Preference <draft-ietf-ipv6-router-selection-07.txt>
- better prefix lifetime management
- more spec-comformant DAD advertisement
- updated RFC/internet-draft revisions
Obtained from: KAME
Reviewed by: ume, gnn
MFC after: 2 month
Diffstat (limited to 'sys/netinet6/in6.c')
-rw-r--r-- | sys/netinet6/in6.c | 402 |
1 files changed, 322 insertions, 80 deletions
diff --git a/sys/netinet6/in6.c b/sys/netinet6/in6.c index 53d2c2d..d5a8e15 100644 --- a/sys/netinet6/in6.c +++ b/sys/netinet6/in6.c @@ -328,6 +328,7 @@ in6_control(so, cmd, data, ifp, td) struct in6_ifaddr *ia = NULL; struct in6_aliasreq *ifra = (struct in6_aliasreq *)data; int error, privileged; + struct sockaddr_in6 *sa6; privileged = 0; if (td == NULL || !suser(td)) @@ -408,19 +409,56 @@ in6_control(so, cmd, data, ifp, td) /* * Find address for this interface, if it exists. + * + * In netinet code, we have checked ifra_addr in SIOCSIF*ADDR operation + * only, and used the first interface address as the target of other + * operations (without checking ifra_addr). This was because netinet + * code/API assumed at most 1 interface address per interface. + * Since IPv6 allows a node to assign multiple addresses + * on a single interface, we almost always look and check the + * presence of ifra_addr, and reject invalid ones here. + * It also decreases duplicated code among SIOC*_IN6 operations. */ - if (ifra->ifra_addr.sin6_family == AF_INET6) { /* XXX */ + switch (cmd) { + case SIOCAIFADDR_IN6: + case SIOCSIFPHYADDR_IN6: + sa6 = &ifra->ifra_addr; + break; + case SIOCSIFADDR_IN6: + case SIOCGIFADDR_IN6: + case SIOCSIFDSTADDR_IN6: + case SIOCSIFNETMASK_IN6: + case SIOCGIFDSTADDR_IN6: + case SIOCGIFNETMASK_IN6: + case SIOCDIFADDR_IN6: + case SIOCGIFPSRCADDR_IN6: + case SIOCGIFPDSTADDR_IN6: + case SIOCGIFAFLAG_IN6: + case SIOCSNDFLUSH_IN6: + case SIOCSPFXFLUSH_IN6: + case SIOCSRTRFLUSH_IN6: + case SIOCGIFALIFETIME_IN6: + case SIOCSIFALIFETIME_IN6: + case SIOCGIFSTAT_IN6: + case SIOCGIFSTAT_ICMP6: + sa6 = &ifr->ifr_addr; + break; + default: + sa6 = NULL; + break; + } + if (sa6 && sa6->sin6_family == AF_INET6) { int error = 0; - if (ifra->ifra_addr.sin6_scope_id != 0) - error = sa6_embedscope(&ifra->ifra_addr, 0); + if (sa6->sin6_scope_id != 0) + error = sa6_embedscope(sa6, 0); else - error = in6_setscope(&ifra->ifra_addr.sin6_addr, - ifp, NULL); + error = in6_setscope(&sa6->sin6_addr, ifp, NULL); if (error != 0) return (error); - ia = in6ifa_ifpwithaddr(ifp, &ifra->ifra_addr.sin6_addr); - } + ia = in6ifa_ifpwithaddr(ifp, &sa6->sin6_addr); + } else + ia = NULL; switch (cmd) { case SIOCSIFADDR_IN6: @@ -538,6 +576,42 @@ in6_control(so, cmd, data, ifp, td) case SIOCGIFALIFETIME_IN6: ifr->ifr_ifru.ifru_lifetime = ia->ia6_lifetime; + if (ia->ia6_lifetime.ia6t_vltime != ND6_INFINITE_LIFETIME) { + time_t maxexpire; + struct in6_addrlifetime *retlt = + &ifr->ifr_ifru.ifru_lifetime; + + /* + * XXX: adjust expiration time assuming time_t is + * signed. + */ + maxexpire = (-1) & + ~(1 << ((sizeof(maxexpire) * 8) - 1)); + if (ia->ia6_lifetime.ia6t_vltime < + maxexpire - ia->ia6_updatetime) { + retlt->ia6t_expire = ia->ia6_updatetime + + ia->ia6_lifetime.ia6t_vltime; + } else + retlt->ia6t_expire = maxexpire; + } + if (ia->ia6_lifetime.ia6t_pltime != ND6_INFINITE_LIFETIME) { + time_t maxexpire; + struct in6_addrlifetime *retlt = + &ifr->ifr_ifru.ifru_lifetime; + + /* + * XXX: adjust expiration time assuming time_t is + * signed. + */ + maxexpire = (-1) & + ~(1 << ((sizeof(maxexpire) * 8) - 1)); + if (ia->ia6_lifetime.ia6t_pltime < + maxexpire - ia->ia6_updatetime) { + retlt->ia6t_preferred = ia->ia6_updatetime + + ia->ia6_lifetime.ia6t_pltime; + } else + retlt->ia6t_preferred = maxexpire; + } break; case SIOCSIFALIFETIME_IN6: @@ -558,13 +632,14 @@ in6_control(so, cmd, data, ifp, td) case SIOCAIFADDR_IN6: { int i, error = 0; - struct nd_prefix pr0, *pr; + struct nd_prefixctl pr0; + struct nd_prefix *pr; /* * first, make or update the interface address structure, * and link it to the list. */ - if ((error = in6_update_ifa(ifp, ifra, ia)) != 0) + if ((error = in6_update_ifa(ifp, ifra, ia, 0)) != 0) return (error); /* @@ -586,7 +661,6 @@ in6_control(so, cmd, data, ifp, td) break; /* we don't need to install a host route. */ } pr0.ndpr_prefix = ifra->ifra_addr; - pr0.ndpr_mask = ifra->ifra_prefixmask.sin6_addr; /* apply the mask for safety. */ for (i = 0; i < 4; i++) { pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= @@ -638,7 +712,7 @@ in6_control(so, cmd, data, ifp, td) if (ip6_use_tempaddr && pr->ndpr_refcnt == 1) { int e; - if ((e = in6_tmpifadd(ia, 1)) != 0) { + if ((e = in6_tmpifadd(ia, 1, 0)) != 0) { log(LOG_NOTICE, "in6_control: " "failed to create a " "temporary address, " @@ -662,7 +736,8 @@ in6_control(so, cmd, data, ifp, td) case SIOCDIFADDR_IN6: { int i = 0; - struct nd_prefix pr0, *pr; + struct nd_prefixctl pr0; + struct nd_prefix *pr; /* * If the address being deleted is the only one that owns @@ -680,10 +755,10 @@ in6_control(so, cmd, data, ifp, td) if (pr0.ndpr_plen == 128) goto purgeaddr; pr0.ndpr_prefix = ia->ia_addr; - pr0.ndpr_mask = ia->ia_prefixmask.sin6_addr; + /* apply the mask for safety. */ for (i = 0; i < 4; i++) { pr0.ndpr_prefix.sin6_addr.s6_addr32[i] &= - ia->ia_prefixmask.sin6_addr.s6_addr32[i]; + ifra->ifra_prefixmask.sin6_addr.s6_addr32[i]; } /* * The logic of the following condition is a bit complicated. @@ -723,16 +798,20 @@ in6_control(so, cmd, data, ifp, td) * XXX: should this be performed under splnet()? */ int -in6_update_ifa(ifp, ifra, ia) +in6_update_ifa(ifp, ifra, ia, flags) struct ifnet *ifp; struct in6_aliasreq *ifra; struct in6_ifaddr *ia; + int flags; { int error = 0, hostIsNew = 0, plen = -1; struct in6_ifaddr *oia; struct sockaddr_in6 dst6; struct in6_addrlifetime *lt; + struct in6_multi_mship *imm; + struct in6_multi *in6m_sol; struct rtentry *rt; + int delay; /* Validate parameters */ if (ifp == NULL || ifra == NULL) /* this maybe redundant */ @@ -818,10 +897,8 @@ in6_update_ifa(ifp, ifra, ia) } /* lifetime consistency check */ lt = &ifra->ifra_lifetime; - if (lt->ia6t_vltime != ND6_INFINITE_LIFETIME - && lt->ia6t_vltime + time_second < time_second) { - return EINVAL; - } + if (lt->ia6t_pltime > lt->ia6t_vltime) + return (EINVAL); if (lt->ia6t_vltime == 0) { /* * the following log might be noisy, but this is a typical @@ -830,10 +907,9 @@ in6_update_ifa(ifp, ifra, ia) nd6log((LOG_INFO, "in6_update_ifa: valid lifetime is 0 for %s\n", ip6_sprintf(&ifra->ifra_addr.sin6_addr))); - } - if (lt->ia6t_pltime != ND6_INFINITE_LIFETIME - && lt->ia6t_pltime + time_second < time_second) { - return EINVAL; + + if (ia == NULL) + return (0); /* there's nothing to do */ } /* @@ -852,11 +928,12 @@ in6_update_ifa(ifp, ifra, ia) if (ia == NULL) return (ENOBUFS); bzero((caddr_t)ia, sizeof(*ia)); - /* Initialize the address and masks */ + /* Initialize the address and masks, and put time stamp */ IFA_LOCK_INIT(&ia->ia_ifa); ia->ia_ifa.ifa_addr = (struct sockaddr *)&ia->ia_addr; ia->ia_addr.sin6_family = AF_INET6; ia->ia_addr.sin6_len = sizeof(ia->ia_addr); + ia->ia6_createtime = time_second; if ((ifp->if_flags & (IFF_POINTOPOINT | IFF_LOOPBACK)) != 0) { /* * XXX: some functions expect that ifa_dstaddr is not @@ -881,6 +958,9 @@ in6_update_ifa(ifp, ifra, ia) TAILQ_INSERT_TAIL(&ifp->if_addrlist, &ia->ia_ifa, ifa_list); } + /* update timestamp */ + ia->ia6_updatetime = time_second; + /* set prefix mask */ if (ifra->ifra_prefixmask.sin6_len) { /* @@ -945,8 +1025,6 @@ in6_update_ifa(ifp, ifra, ia) * configure address flags. */ ia->ia6_flags = ifra->ifra_flags; - ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /* safety */ - ia->ia6_flags &= ~IN6_IFF_NODAD; /* Mobile IPv6 */ /* * backward compatibility - if IN6_IFF_DEPRECATED is set from the * userland, make it deprecated. @@ -955,17 +1033,14 @@ in6_update_ifa(ifp, ifra, ia) ia->ia6_lifetime.ia6t_pltime = 0; ia->ia6_lifetime.ia6t_preferred = time_second; } - /* - * Perform DAD, if needed. - * XXX It may be of use, if we can administratively - * disable DAD. + * Make the address tentative before joining multicast addresses, + * so that corresponding MLD responses would not have a tentative + * source address. */ - if (in6if_do_dad(ifp) && hostIsNew && - (ifra->ifra_flags & IN6_IFF_NODAD) == 0) { + ia->ia6_flags &= ~IN6_IFF_DUPLICATED; /* safety */ + if (hostIsNew && in6if_do_dad(ifp)) ia->ia6_flags |= IN6_IFF_TENTATIVE; - nd6_dad_start((struct ifaddr *)ia, NULL); - } /* * We are done if we have simply modified an existing address. @@ -979,9 +1054,9 @@ in6_update_ifa(ifp, ifra, ia) */ /* Join necessary multicast groups */ + in6m_sol = NULL; if ((ifp->if_flags & IFF_MULTICAST) != 0) { struct sockaddr_in6 mltaddr, mltmask; - struct in6_multi *in6m; struct in6_addr llsol; /* join solicited multicast addr for new host id */ @@ -997,15 +1072,29 @@ in6_update_ifa(ifp, ifra, ia) "in6_setscope failed\n"); goto cleanup; } - (void)in6_addmulti(&llsol, ifp, &error); + delay = 0; + if ((flags & IN6_IFAUPDATE_DADDELAY)) { + /* + * We need a random delay for DAD on the address + * being configured. It also means delaying + * transmission of the corresponding MLD report to + * avoid report collision. + * [draft-ietf-ipv6-rfc2462bis-02.txt] + */ + delay = arc4random() % + (MAX_RTR_SOLICITATION_DELAY * hz); + } + imm = in6_joingroup(ifp, &llsol, &error, delay); if (error != 0) { nd6log((LOG_WARNING, "in6_update_ifa: addmulti failed for " "%s on %s (errno=%d)\n", ip6_sprintf(&llsol), if_name(ifp), error)); - goto cleanup; + in6_purgeaddr((struct ifaddr *)ia); + return (error); } + in6m_sol = imm->i6mm_maddr; bzero(&mltmask, sizeof(mltmask)); mltmask.sin6_len = sizeof(struct sockaddr_in6); @@ -1050,37 +1139,67 @@ in6_update_ifa(ifp, ifra, ia) } else RTFREE_LOCKED(rt); - IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); - if (in6m == NULL) { - (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); - if (error != 0) { - nd6log((LOG_WARNING, - "in6_update_ifa: addmulti failed for " - "%s on %s (errno=%d)\n", - ip6_sprintf(&mltaddr.sin6_addr), - if_name(ifp), error)); - goto cleanup; + /* + * XXX: do we really need this automatic routes? + * We should probably reconsider this stuff. Most applications + * actually do not need the routes, since they usually specify + * the outgoing interface. + */ + rt = rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL); + if (rt) { + /* XXX: only works in !SCOPEDROUTING case. */ + if (memcmp(&mltaddr.sin6_addr, + &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, + MLTMASK_LEN)) { + RTFREE_LOCKED(rt); + rt = NULL; } } + if (!rt) { + error = rtrequest(RTM_ADD, (struct sockaddr *)&mltaddr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&mltmask, RTF_UP | RTF_CLONING, + (struct rtentry **)0); + if (error) + goto cleanup; + } else { + RTFREE_LOCKED(rt); + } + + imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, 0); + if (!imm) { + nd6log((LOG_WARNING, + "in6_update_ifa: addmulti failed for " + "%s on %s (errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error)); + goto cleanup; + } /* * join node information group address */ #define hostnamelen strlen(hostname) + delay = 0; + if ((flags & IN6_IFAUPDATE_DADDELAY)) { + /* + * The spec doesn't say anything about delay for this + * group, but the same logic should apply. + */ + delay = arc4random() % + (MAX_RTR_SOLICITATION_DELAY * hz); + } if (in6_nigroup(ifp, hostname, hostnamelen, &mltaddr.sin6_addr) == 0) { - IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); - if (in6m == NULL) { - (void)in6_addmulti(&mltaddr.sin6_addr, - ifp, &error); - if (error != 0) { - nd6log((LOG_WARNING, "in6_update_ifa: " - "addmulti failed for " - "%s on %s (errno=%d)\n", - ip6_sprintf(&mltaddr.sin6_addr), - if_name(ifp), error)); - goto cleanup; - } + imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, + delay); /* XXX jinmei */ + if (!imm) { + nd6log((LOG_WARNING, "in6_update_ifa: " + "addmulti failed for %s on %s " + "(errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error)); + /* XXX not very fatal, go on... */ } } #undef hostnamelen @@ -1113,21 +1232,77 @@ in6_update_ifa(ifp, ifra, ia) } else RTFREE_LOCKED(rt); - IN6_LOOKUP_MULTI(mltaddr.sin6_addr, ifp, in6m); - if (in6m == NULL) { - (void)in6_addmulti(&mltaddr.sin6_addr, ifp, &error); - if (error != 0) { - nd6log((LOG_WARNING, "in6_update_ifa: " - "addmulti failed for %s on %s " - "(errno=%d)\n", - ip6_sprintf(&mltaddr.sin6_addr), - if_name(ifp), error)); - goto cleanup; + /* XXX: again, do we really need the route? */ + rt = rtalloc1((struct sockaddr *)&mltaddr, 0, 0UL); + if (rt) { + if (memcmp(&mltaddr.sin6_addr, + &((struct sockaddr_in6 *)rt_key(rt))->sin6_addr, + MLTMASK_LEN)) { + RTFREE_LOCKED(rt); + rt = NULL; } } + if (!rt) { + error = rtrequest(RTM_ADD, (struct sockaddr *)&mltaddr, + (struct sockaddr *)&ia->ia_addr, + (struct sockaddr *)&mltmask, RTF_UP | RTF_CLONING, + (struct rtentry **)0); + if (error) + goto cleanup; + } else { + RTFREE_LOCKED(rt); + } + + imm = in6_joingroup(ifp, &mltaddr.sin6_addr, &error, 0); + if (!imm) { + nd6log((LOG_WARNING, "in6_update_ifa: " + "addmulti failed for %s on %s " + "(errno=%d)\n", + ip6_sprintf(&mltaddr.sin6_addr), + if_name(ifp), error)); + goto cleanup; + } #undef MLTMASK_LEN } + /* + * Perform DAD, if needed. + * XXX It may be of use, if we can administratively + * disable DAD. + */ + if (hostIsNew && in6if_do_dad(ifp) && + ((ifra->ifra_flags & IN6_IFF_NODAD) == 0) && + (ia->ia6_flags & IN6_IFF_TENTATIVE)) + { + int mindelay, maxdelay; + + delay = 0; + if ((flags & IN6_IFAUPDATE_DADDELAY)) { + /* + * We need to impose a delay before sending an NS + * for DAD. Check if we also needed a delay for the + * corresponding MLD message. If we did, the delay + * should be larger than the MLD delay (this could be + * relaxed a bit, but this simple logic is at least + * safe). + */ + mindelay = 0; + if (in6m_sol != NULL && + in6m_sol->in6m_state == MLD_REPORTPENDING) { + mindelay = in6m_sol->in6m_timer; + } + maxdelay = MAX_RTR_SOLICITATION_DELAY * hz; + if (maxdelay - mindelay == 0) + delay = 0; + else { + delay = + (arc4random() % (maxdelay - mindelay)) + + mindelay; + } + } + nd6_dad_start((struct ifaddr *)ia, delay); + } + return (error); unlink: @@ -1603,10 +1778,11 @@ in6_ifinit(ifp, ia, sin6, newhost) } struct in6_multi_mship * -in6_joingroup(ifp, addr, errorp) +in6_joingroup(ifp, addr, errorp, delay) struct ifnet *ifp; struct in6_addr *addr; int *errorp; + int delay; { struct in6_multi_mship *imm; @@ -1615,7 +1791,7 @@ in6_joingroup(ifp, addr, errorp) *errorp = ENOBUFS; return NULL; } - imm->i6mm_maddr = in6_addmulti(addr, ifp, errorp); + imm->i6mm_maddr = in6_addmulti(addr, ifp, errorp, delay); if (!imm->i6mm_maddr) { /* *errorp is alrady set */ free(imm, M_IP6MADDR); @@ -1943,21 +2119,27 @@ in6_if_up(ifp) { struct ifaddr *ifa; struct in6_ifaddr *ia; - int dad_delay; /* delay ticks before DAD output */ - - /* - * special cases, like 6to4, are handled in in6_ifattach - */ - in6_ifattach(ifp, NULL); - dad_delay = 0; TAILQ_FOREACH(ifa, &ifp->if_addrlist, ifa_list) { if (ifa->ifa_addr->sa_family != AF_INET6) continue; ia = (struct in6_ifaddr *)ifa; - if (ia->ia6_flags & IN6_IFF_TENTATIVE) - nd6_dad_start(ifa, &dad_delay); + if (ia->ia6_flags & IN6_IFF_TENTATIVE) { + /* + * The TENTATIVE flag was likely set by hand + * beforehand, implicitly indicating the need for DAD. + * We may be able to skip the random delay in this + * case, but we impose delays just in case. + */ + nd6_dad_start(ifa, + arc4random() % (MAX_RTR_SOLICITATION_DELAY * hz)); + } } + + /* + * special cases, like 6to4, are handled in in6_ifattach + */ + in6_ifattach(ifp, NULL); } int @@ -2021,6 +2203,66 @@ in6_setmaxmtu() in6_maxmtu = maxmtu; } +/* + * Provide the length of interface identifiers to be used for the link attached + * to the given interface. The length should be defined in "IPv6 over + * xxx-link" document. Note that address architecture might also define + * the length for a particular set of address prefixes, regardless of the + * link type. As clarified in rfc2462bis, those two definitions should be + * consistent, and those really are as of August 2004. + */ +int +in6_if2idlen(ifp) + struct ifnet *ifp; +{ + switch (ifp->if_type) { + case IFT_ETHER: /* RFC2464 */ +#ifdef IFT_PROPVIRTUAL + case IFT_PROPVIRTUAL: /* XXX: no RFC. treat it as ether */ +#endif +#ifdef IFT_L2VLAN + case IFT_L2VLAN: /* ditto */ +#endif +#ifdef IFT_IEEE80211 + case IFT_IEEE80211: /* ditto */ +#endif +#ifdef IFT_MIP + case IFT_MIP: /* ditto */ +#endif + return (64); + case IFT_FDDI: /* RFC2467 */ + return (64); + case IFT_ISO88025: /* RFC2470 (IPv6 over Token Ring) */ + return (64); + case IFT_PPP: /* RFC2472 */ + return (64); + case IFT_ARCNET: /* RFC2497 */ + return (64); + case IFT_FRELAY: /* RFC2590 */ + return (64); + case IFT_IEEE1394: /* RFC3146 */ + return (64); + case IFT_GIF: + return (64); /* draft-ietf-v6ops-mech-v2-07 */ + case IFT_LOOP: + return (64); /* XXX: is this really correct? */ + default: + /* + * Unknown link type: + * It might be controversial to use the today's common constant + * of 64 for these cases unconditionally. For full compliance, + * we should return an error in this case. On the other hand, + * if we simply miss the standard for the link type or a new + * standard is defined for a new link type, the IFID length + * is very likely to be the common constant. As a compromise, + * we always use the constant, but make an explicit notice + * indicating the "unknown" case. + */ + printf("in6_if2idlen: unknown link type (%d)\n", ifp->if_type); + return (64); + } +} + void * in6_domifattach(ifp) struct ifnet *ifp; |