From: Roy Marples Date: Sat, 17 Jan 2015 02:29:54 +0000 (+0000) Subject: Implement RFC4941, Privacy Extensions for Stateless Address Autoconfiguration X-Git-Tag: v6.7.0~14 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a1f7b32ceffa4e707a051e58573e60b6603a942b;p=thirdparty%2Fdhcpcd.git Implement RFC4941, Privacy Extensions for Stateless Address Autoconfiguration in IPv6 when dhcpcd is overriding the in-kernel RA support. For Linux kernels (3.18+) which support IFA_F_MANAGETEMPADDR the bulk of this changeset is compiled out and the kernel will manage the temporary addresses entirely. For BSD, this is a fully compliant implementation with the caveat that when dhcpcd is restarted the last non deprecated temp address on the interface will be treated as being created when it was last updated rather when it was actually added. Thus this may voilate section 3.3. As dhcpcd won't restart in normal operation, this isn't an issue. For Linux (3.18+) which supports IFA_F_MANAGETEMPADDR, the bulk of this changeset is compiled out as the kernel will manage the temporary addresses. For older Linux this is a fully compliant implementation with the caveat that when restarted new temporary addresses will be generated. Fixes [2ddfcb190f] --- diff --git a/dhcp6.c b/dhcp6.c index 3e00b299..20c3d712 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -1736,6 +1736,7 @@ dhcp6_findna(struct interface *ifp, uint16_t ot, const uint8_t *iaid, a->ia_type = ot; memcpy(a->iaid, iaid, sizeof(a->iaid)); a->addr = iap->addr; + a->created = *acquired; /* * RFC 5942 Section 5 @@ -1818,6 +1819,7 @@ dhcp6_findpd(struct interface *ifp, const uint8_t *iaid, } a->iface = ifp; a->flags = IPV6_AF_NEW | IPV6_AF_DELEGATEDPFX; + a->created = *acquired; a->dadcallback = dhcp6_dadcallback; a->ia_type = D6_OPTION_IA_PD; memcpy(a->iaid, iaid, sizeof(a->iaid)); @@ -2284,7 +2286,7 @@ dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix, a->dadcallback = dhcp6_dadcallback; a->delegating_iface = ifs; memcpy(&a->iaid, &prefix->iaid, sizeof(a->iaid)); - a->acquired = prefix->acquired; + a->created = a->acquired = prefix->acquired; a->prefix_pltime = prefix->prefix_pltime; a->prefix_vltime = prefix->prefix_vltime; a->prefix = addr; @@ -2303,6 +2305,7 @@ dhcp6_ifdelegateaddr(struct interface *ifp, struct ipv6_addr *prefix, /* Keep our flags */ a->flags |= ap->flags; a->flags &= ~IPV6_AF_NEW; + a->created = ap->created; free(ap); } } diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 98a198b3..9ca613b2 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd November 26, 2014 +.Dd January 15, 2015 .Dt DHCPCD 8 .Os .Sh NAME @@ -122,6 +122,14 @@ and .Li RFC 6106 . .Pp .Nm +is also an implementation of the IPv6 Privacy Extensions to AutoConf as +specified in +.Li RFC 4941 . +This feature needs to be enabled in the kernel and +.Nm +will start using it. +.Pp +.Nm is also an implemenation of the DHCPv6 client as specified in .Li RFC 3315 . By default, @@ -701,8 +709,8 @@ RFC\ 951, RFC\ 1534, RFC\ 2104, RFC\ 2131, RFC\ 2132, RFC\ 2563, RFC\ 2855, RFC\ 3004, RFC\ 3118, RFC\ 3203, RFC\ 3315, RFC\ 3361, RFC\ 3633, RFC\ 3396, RFC\ 3397, RFC\ 3442, RFC\ 3495, RFC\ 3925, RFC\ 3927, RFC\ 4039, RFC\ 4075, RFC\ 4242, RFC\ 4361, RFC\ 4390, RFC\ 4702, RFC\ 4074, RFC\ 4861, RFC\ 4833, -RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6603, RFC\ 6704, -RFC\ 7217. +RFC\ 4941, RFC\ 5227, RFC\ 5942, RFC\ 5969, RFC\ 6106, RFC\ 6334, RFC\ 6603, +RFC\ 6704, RFC\ 7217. .Sh AUTHORS .An Roy Marples Aq Mt roy@marples.name .Sh BUGS diff --git a/dhcpcd.c b/dhcpcd.c index 26d8e894..0c1a8867 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -601,12 +601,8 @@ dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, ifp->carrier = LINK_DOWN; script_runreason(ifp, "NOCARRIER"); dhcp6_drop(ifp, "EXPIRE6"); + ipv6_drop(ifp); ipv6nd_drop(ifp); - /* Don't blindly delete our knowledge of LL addresses. - * We need to listen to what the kernel does with - * them as some OS's will remove, mark tentative or - * do nothing. */ - ipv6_free_ll_callbacks(ifp); dhcp_drop(ifp, "EXPIRE"); arp_close(ifp); } @@ -622,8 +618,11 @@ dhcpcd_handlecarrier(struct dhcpcd_ctx *ctx, int carrier, unsigned int flags, #endif if (ifp->wireless) if_getssid(ifp); - configure_interface(ifp, ctx->argc, ctx->argv); + dhcpcd_initstate(ifp); script_runreason(ifp, "CARRIER"); + /* RFC4941 Section 3.5 */ + if (ifp->options->options & DHCPCD_IPV6RA_OWN) + ipv6_gentempifid(ifp); dhcpcd_startinterface(ifp); } } diff --git a/if-bsd.c b/if-bsd.c index baa4bd80..35548fd6 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -618,6 +618,8 @@ if_address6(const struct ipv6_addr *a, int action) if (a->autoconf) ifa.ifra_flags |= IN6_IFF_AUTOCONF; #endif + if (a->flags & IPV6_AF_TEMPORARY) + ifa.ifra_flags |= IN6_IFF_TEMPORARY; #define ADDADDR(v, addr) { \ (v)->sin6_family = AF_INET6; \ @@ -787,6 +789,51 @@ if_addrflags6(const struct in6_addr *addr, const struct interface *ifp) } return flags; } + +int +if_getlifetime6(struct ipv6_addr *ia) +{ + int s, r; + struct in6_ifreq ifr6; + + s = socket(PF_INET6, SOCK_DGRAM, 0); + r = -1; + if (s != -1) { + memset(&ifr6, 0, sizeof(ifr6)); + strncpy(ifr6.ifr_name, ia->iface->name, sizeof(ifr6.ifr_name)); + ifr6.ifr_addr.sin6_family = AF_INET6; + ifr6.ifr_addr.sin6_addr = ia->addr; + ifa_scope(&ifr6.ifr_addr, ia->iface->index); + if (ioctl(s, SIOCGIFALIFETIME_IN6, &ifr6) != -1) { + time_t t; + struct in6_addrlifetime *lifetime; + + t = time(NULL); + lifetime = &ifr6.ifr_ifru.ifru_lifetime; + + if (lifetime->ia6t_preferred) + ia->prefix_pltime = + (uint32_t)(lifetime->ia6t_preferred - + MIN(t, lifetime->ia6t_preferred)); + else + ia->prefix_pltime = ND6_INFINITE_LIFETIME; + if (lifetime->ia6t_expire) { + ia->prefix_vltime = + (uint32_t)(lifetime->ia6t_expire - + MIN(t, lifetime->ia6t_expire)); + /* Calculate the created time */ + get_monotonic(&ia->created); + ia->created.tv_sec -= + lifetime->ia6t_vltime - ia->prefix_vltime; + } else + ia->prefix_vltime = ND6_INFINITE_LIFETIME; + + r = 0; + } + close(s); + } + return r; +} #endif int @@ -808,7 +855,7 @@ if_managelink(struct dhcpcd_ctx *ctx) #endif #ifdef INET6 struct rt6 rt6; - struct in6_addr ia6; + struct in6_addr ia6, net6; struct sockaddr_in6 *sin6; int ifa_flags; #endif @@ -974,6 +1021,10 @@ if_managelink(struct dhcpcd_ctx *ctx) rti_info[RTAX_IFA]; ia6 = sin6->sin6_addr; DESCOPE(&ia6); + sin6 = (struct sockaddr_in6*)(void *) + rti_info[RTAX_NETMASK]; + net6 = sin6->sin6_addr; + DESCOPE(&net6); if (rtm->rtm_type == RTM_NEWADDR) { ifa_flags = if_addrflags6(&ia6, ifp); if (ifa_flags == -1) @@ -981,7 +1032,8 @@ if_managelink(struct dhcpcd_ctx *ctx) } else ifa_flags = 0; ipv6_handleifa(ctx, rtm->rtm_type, NULL, - ifp->name, &ia6, ifa_flags); + ifp->name, &ia6, ipv6_prefixlen(&net6), + ifa_flags); break; #endif } @@ -1035,8 +1087,65 @@ inet6_sysctl(int code, int val, int action) return -1; return val; } + +#define get_inet6_sysctlbyname(code) inet6_sysctlbyname(code, 0, 0) +#define set_inet6_sysctlbyname(code, val) inet6_sysctlbyname(code, val, 1) +static int +inet6_sysctlbyname(const char *name, int val, int action) +{ + size_t size; + + size = sizeof(val); + if (action) { + if (sysctlbyname(name, NULL, 0, &val, size) == -1) + return -1; + return 0; + } + if (sysctlbyname(name, &val, &size, NULL, 0) == -1) + return -1; + return val; +} #endif +int +ip6_use_tempaddr(__unused const char *ifname) +{ + int val; + +#ifdef IPV6CTL_USETEMPADDR + val = get_inet6_sysctl(IPV6CTL_USETEMPADDR); +#else + val = get_inet6_sysctlbyname("net.inet6.ip6.use_tempaddr"); +#endif + return val == -1 ? TEMP_PREFERRED_LIFETIME : val; +} + +int +ip6_temp_preferred_lifetime(__unused const char *ifname) +{ + int val; + +#ifdef IPV6CTL_TEMPPLTIME + val = get_inet6_sysctl(IPV6CTL_TEMPPLTIME); +#else + val = get_inet6_sysctlbyname("net.inet6.ip6.temppltime"); +#endif + return val < 0 ? TEMP_PREFERRED_LIFETIME : val; +} + +int +ip6_temp_valid_lifetime(__unused const char *ifname) +{ + int val; + +#ifdef IPV6CTL_TEMPVLTIME + val = get_inet6_sysctl(IPV6CTL_TEMPVLTIME); +#else + val = get_inet6_sysctlbyname("net.inet6.ip6.tempvltime"); +#endif + return val < 0 ? TEMP_VALID_LIFETIME : val; +} + #define del_if_nd6_flag(ifname, flag) if_nd6_flag(ifname, flag, -1) #define get_if_nd6_flag(ifname, flag) if_nd6_flag(ifname, flag, 0) #define set_if_nd6_flag(ifname, flag) if_nd6_flag(ifname, flag, 1) diff --git a/if-linux.c b/if-linux.c index 69f5432e..a2d87b62 100644 --- a/if-linux.c +++ b/if-linux.c @@ -581,7 +581,7 @@ link_addr(struct dhcpcd_ctx *ctx, struct interface *ifp, struct nlmsghdr *nlm) rta = RTA_NEXT(rta, len); } ipv6_handleifa(ctx, nlm->nlmsg_type, NULL, ifp->name, - &addr6, ifa->ifa_flags); + &addr6, ifa->ifa_prefixlen, ifa->ifa_flags); break; #endif } @@ -1325,8 +1325,10 @@ if_address6(const struct ipv6_addr *ap, int action) struct nlma nlm; struct ifa_cacheinfo cinfo; int retval = 0; +/* IFA_FLAGS is not a define, but is was added at the same time + * IFA_F_NOPREFIXROUTE was do use that. */ #ifdef IFA_F_NOPREFIXROUTE - uint32_t flags; + uint32_t flags = 0; #endif memset(&nlm, 0, sizeof(nlm)); @@ -1339,6 +1341,20 @@ if_address6(const struct ipv6_addr *ap, int action) nlm.hdr.nlmsg_type = RTM_DELADDR; nlm.ifa.ifa_index = ap->iface->index; nlm.ifa.ifa_family = AF_INET6; + if (ap->addr_flags & IFA_F_TEMPORARY) { +#ifdef IFA_F_NOPREFIXROUTE + flags |= IFA_F_TEMPORARY; +#else + nlm.ifa.ifa_flags |= IFA_F_TEMPORARY; +#endif + } +#ifdef IFA_F_MANAGETEMPADDR + else if (ap->flags & IPV6_AF_AUTOCONF && + ip6_use_tempaddr(ap->iface->name)) + flags |= IFA_F_MANAGETEMPADDR; +#endif + + /* Add as /128 if no IFA_F_NOPREFIXROUTE ? */ nlm.ifa.ifa_prefixlen = ap->prefix_len; /* This creates the aliased interface */ add_attr_l(&nlm.hdr, sizeof(nlm), IFA_LABEL, @@ -1355,10 +1371,12 @@ if_address6(const struct ipv6_addr *ap, int action) } #ifdef IFA_F_NOPREFIXROUTE - if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr)) { - flags = IFA_F_NOPREFIXROUTE; + if (!IN6_IS_ADDR_LINKLOCAL(&ap->addr)) + flags |= IFA_F_NOPREFIXROUTE; +#endif +#ifdef IFA_F_NOPREFIXROUTE + if (flags) add_attr_32(&nlm.hdr, sizeof(nlm), IFA_FLAGS, flags); - } #endif if (send_netlink(ap->iface->ctx, NULL, @@ -1492,6 +1510,15 @@ if_addrflags6(const struct in6_addr *addr, const struct interface *ifp) return -1; } +int +if_getlifetime6(__unused struct ipv6_addr *ia) +{ + + /* God knows how to work out address lifetimes on Linux */ + errno = ENOTSUP; + return -1; +} + struct nlml { struct nlmsghdr hdr; @@ -1597,4 +1624,44 @@ if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *ifp, int own) return ra; } + +int +ip6_use_tempaddr(const char *ifname) +{ + char path[256]; + int val; + + if (ifname == NULL) + ifname = "all"; + snprintf(path, sizeof(path), "%s/%s/use_tempaddr", prefix, ifname); + val = check_proc_int(path); + return val == -1 ? 0 : val; +} + +int +ip6_temp_preferred_lifetime(const char *ifname) +{ + char path[256]; + int val; + + if (ifname == NULL) + ifname = "all"; + snprintf(path, sizeof(path), "%s/%s/temp_prefered_lft", prefix, + ifname); + val = check_proc_int(path); + return val < 0 ? TEMP_PREFERRED_LIFETIME : val; +} + +int +ip6_temp_valid_lifetime(const char *ifname) +{ + char path[256]; + int val; + + if (ifname == NULL) + ifname = "all"; + snprintf(path, sizeof(path), "%s/%s/temp_valid_lft", prefix, ifname); + val = check_proc_int(path); + return val < 0 ? TEMP_VALID_LIFETIME : val; +} #endif diff --git a/if.c b/if.c index 595c4dbc..b41a2eb2 100644 --- a/if.c +++ b/if.c @@ -195,7 +195,7 @@ if_discover(struct dhcpcd_ctx *ctx, int argc, char * const *argv) const struct sockaddr_in *dst; #endif #ifdef INET6 - struct sockaddr_in6 *sin6; + struct sockaddr_in6 *sin6, *net6; int ifa_flags; #endif #ifdef AF_LINK @@ -505,6 +505,7 @@ if_discover(struct dhcpcd_ctx *ctx, int argc, char * const *argv) if (ifp == NULL) break; /* Should be impossible */ sin6 = (struct sockaddr_in6 *)(void *)ifa->ifa_addr; + net6 = (struct sockaddr_in6 *)(void *)ifa->ifa_netmask; #ifdef __KAME__ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) /* Remove the scope from the address */ @@ -515,7 +516,9 @@ if_discover(struct dhcpcd_ctx *ctx, int argc, char * const *argv) if (ifa_flags != -1) ipv6_handleifa(ctx, RTM_NEWADDR, ifs, ifa->ifa_name, - &sin6->sin6_addr, ifa_flags); + &sin6->sin6_addr, + ipv6_prefixlen(&net6->sin6_addr), + ifa_flags); break; #endif } diff --git a/if.h b/if.h index 98a07586..c44aed6f 100644 --- a/if.h +++ b/if.h @@ -125,12 +125,16 @@ int if_route(const struct rt *rt, int); #ifdef INET6 int if_checkipv6(struct dhcpcd_ctx *ctx, const struct interface *, int); +int ip6_use_tempaddr(const char *ifname); +int ip6_temp_preferred_lifetime(const char *ifname); +int ip6_temp_valid_lifetime(const char *ifname); int if_address6(const struct ipv6_addr *, int); #define if_addaddress6(a) if_address6(a, 1) #define if_deladdress6(a) if_address6(a, -1) int if_addrflags6(const struct in6_addr *, const struct interface *); +int if_getlifetime6(struct ipv6_addr *); int if_route6(const struct rt6 *rt, int); #define if_addroute6(rt) if_route6(rt, 1) diff --git a/ipv6.c b/ipv6.c index 2142c173..6d0fe68b 100644 --- a/ipv6.c +++ b/ipv6.c @@ -35,20 +35,7 @@ #include #include -#ifdef __linux__ - /* Match Linux defines to BSD */ -# ifdef IFA_F_OPTIMISTIC -# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC) -# else -# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | 0x04) -# endif -# ifdef IF_F_DADFAILED -# define IN6_IFF_DUPLICATED IFA_F_DADFAILED -# else -# define IN6_IFF_DUPLICATED 0x08 -# endif -# define IN6_IFF_DETACHED 0 -#else /* !__linux__ */ +#ifndef __linux__ # ifndef __QNX__ # include # endif @@ -69,6 +56,7 @@ #include #include +#define ELOOP_QUEUE 7 #include "common.h" #include "dhcpcd.h" #include "dhcp6.h" @@ -77,6 +65,14 @@ #include "ipv6.h" #include "ipv6nd.h" +#ifdef HAVE_MD5_H +# ifndef DEPGEN +# include +# endif +#else +# include "md5.h" +#endif + #ifdef SHA2_H # include SHA2_H #else @@ -92,6 +88,22 @@ # warning polling tentative address flags periodically instead #endif +#ifdef __linux__ + /* Match Linux defines to BSD */ +# define IN6_IFF_TEMPORARY IFA_F_TEMPORARY +# ifdef IFA_F_OPTIMISTIC +# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | IFA_F_OPTIMISTIC) +# else +# define IN6_IFF_TENTATIVE (IFA_F_TENTATIVE | 0x04) +# endif +# ifdef IF_F_DADFAILED +# define IN6_IFF_DUPLICATED IFA_F_DADFAILED +# else +# define IN6_IFF_DUPLICATED 0x08 +# endif +# define IN6_IFF_DETACHED 0 +#endif + #define IN6_IFF_NOTUSEABLE \ (IN6_IFF_TENTATIVE | IN6_IFF_DUPLICATED | IN6_IFF_DETACHED) @@ -104,6 +116,14 @@ # endif #endif + +#ifdef KERNEL_MANAGETEMPADDR +#define ipv6_regentempifid(a) {} +#else +static void ipv6_regentempifid(void *); +static void ipv6_regentempaddr(void *); +#endif + struct ipv6_ctx * ipv6_init(struct dhcpcd_ctx *dhcpcd_ctx) { @@ -150,6 +170,7 @@ ipv6_init(struct dhcpcd_ctx *dhcpcd_ctx) ctx->dhcp_fd = -1; dhcpcd_ctx->ipv6 = ctx; + return ctx; } @@ -569,7 +590,7 @@ ipv6_checkaddrflags(void *arg) else if (!(ifa_flags & IN6_IFF_TENTATIVE)) { ipv6_handleifa(ap->iface->ctx, RTM_NEWADDR, ap->iface->ctx->ifaces, ap->iface->name, - &ap->addr, ifa_flags); + &ap->addr, ap->prefix_len, ifa_flags); } else { struct timeval tv; @@ -609,7 +630,6 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timeval *now) struct interface *ifp; struct ipv6_state *state; struct ipv6_addr *nap; - struct timeval n; uint32_t pltime, vltime; /* Ensure no other interface has this address */ @@ -627,6 +647,30 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timeval *now) } } + if (!(ap->flags & IPV6_AF_DADCOMPLETED) && + ipv6_iffindaddr(ap->iface, &ap->addr)) + ap->flags |= IPV6_AF_DADCOMPLETED; + + syslog(ap->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG, + "%s: adding address %s", ap->iface->name, ap->saddr); + if (ap->prefix_pltime == ND6_INFINITE_LIFETIME && + ap->prefix_vltime == ND6_INFINITE_LIFETIME) + syslog(LOG_DEBUG, + "%s: pltime infinity, vltime infinity", + ap->iface->name); + else if (ap->prefix_pltime == ND6_INFINITE_LIFETIME) + syslog(LOG_DEBUG, + "%s: pltime infinity, vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_vltime); + else if (ap->prefix_vltime == ND6_INFINITE_LIFETIME) + syslog(LOG_DEBUG, + "%s: pltime %"PRIu32"seconds, vltime infinity", + ap->iface->name, ap->prefix_pltime); + else + syslog(LOG_DEBUG, + "%s: pltime %"PRIu32" seconds, vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_pltime, ap->prefix_vltime); + /* Adjust plftime and vltime based on acquired time */ pltime = ap->prefix_pltime; vltime = ap->prefix_vltime; @@ -634,6 +678,8 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timeval *now) (ap->prefix_pltime != ND6_INFINITE_LIFETIME || ap->prefix_vltime != ND6_INFINITE_LIFETIME)) { + struct timeval n; + if (now == NULL) { get_monotonic(&n); now = &n; @@ -645,22 +691,36 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timeval *now) ap->prefix_vltime -= (uint32_t)n.tv_sec; } - syslog(ap->flags & IPV6_AF_NEW ? LOG_INFO : LOG_DEBUG, - "%s: adding address %s", ap->iface->name, ap->saddr); - if (!(ap->flags & IPV6_AF_DADCOMPLETED) && - ipv6_iffindaddr(ap->iface, &ap->addr)) - ap->flags |= IPV6_AF_DADCOMPLETED; if (if_addaddress6(ap) == -1) { syslog(LOG_ERR, "if_addaddress6: %m"); +#if 0 + syslog(LOG_DEBUG, + "%s: adj pltime %"PRIu32" seconds, " + "vltime %"PRIu32" seconds", + ap->iface->name, ap->prefix_pltime, ap->prefix_vltime); +#endif /* Restore real pltime and vltime */ ap->prefix_pltime = pltime; ap->prefix_vltime = vltime; return -1; } +#ifndef KERNEL_MANAGETEMPADDR + /* RFC4941 Section 3.4 */ + if (ap->flags & IPV6_AF_TEMPORARY && + ap->prefix_pltime && + ap->prefix_vltime && + ap->iface->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ifp->name)) + eloop_timeout_add_sec(ap->iface->ctx->eloop, + (time_t)ap->prefix_pltime - REGEN_ADVANCE, + ipv6_regentempaddr, ap); +#endif + /* Restore real pltime and vltime */ ap->prefix_pltime = pltime; ap->prefix_vltime = vltime; + ap->flags &= ~IPV6_AF_NEW; ap->flags |= IPV6_AF_ADDED; if (ap->delegating_iface) @@ -668,23 +728,6 @@ ipv6_addaddr(struct ipv6_addr *ap, const struct timeval *now) if (ap->iface->options->options & DHCPCD_IPV6RA_OWN && ipv6_removesubnet(ap->iface, ap) == -1) syslog(LOG_ERR,"ipv6_removesubnet: %m"); - if (ap->prefix_pltime == ND6_INFINITE_LIFETIME && - ap->prefix_vltime == ND6_INFINITE_LIFETIME) - syslog(LOG_DEBUG, - "%s: vltime infinity, pltime infinity", - ap->iface->name); - else if (ap->prefix_pltime == ND6_INFINITE_LIFETIME) - syslog(LOG_DEBUG, - "%s: vltime %"PRIu32" seconds, pltime infinity", - ap->iface->name, ap->prefix_vltime); - else if (ap->prefix_vltime == ND6_INFINITE_LIFETIME) - syslog(LOG_DEBUG, - "%s: vltime infinity, pltime %"PRIu32"seconds", - ap->iface->name, ap->prefix_pltime); - else - syslog(LOG_DEBUG, - "%s: vltime %"PRIu32" seconds, pltime %"PRIu32" seconds", - ap->iface->name, ap->prefix_vltime, ap->prefix_pltime); #ifdef IPV6_POLLADDRFLAG eloop_timeout_delete(ap->iface->ctx->eloop, @@ -792,13 +835,16 @@ ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop, TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { if (ifd && ap->delegating_iface != ifd) continue; - TAILQ_REMOVE(addrs, ap, next); + if (drop != 2) + TAILQ_REMOVE(addrs, ap, next); eloop_q_timeout_delete(ap->iface->ctx->eloop, 0, NULL, ap); if (drop && ap->flags & IPV6_AF_ADDED && (ap->iface->options->options & (DHCPCD_EXITING | DHCPCD_PERSISTENT)) != (DHCPCD_EXITING | DHCPCD_PERSISTENT)) { + if (drop == 2) + TAILQ_REMOVE(addrs, ap, next); /* Find the same address somewhere else */ apf = ipv6_findaddr(ap->iface->ctx, &ap->addr, 0); if (apf == NULL || @@ -812,8 +858,11 @@ ipv6_freedrop_addrs(struct ipv6_addrhead *addrs, int drop, get_monotonic(&now); ipv6_addaddr(apf, &now); } + if (drop == 2) + free(ap); } - free(ap); + if (drop != 2) + free(ap); } } @@ -824,7 +873,7 @@ ipv6_getstate(struct interface *ifp) state = IPV6_STATE(ifp); if (state == NULL) { - ifp->if_data[IF_DATA_IPV6] = malloc(sizeof(*state)); + ifp->if_data[IF_DATA_IPV6] = calloc(1, sizeof(*state)); state = IPV6_STATE(ifp); if (state == NULL) { syslog(LOG_ERR, "%s: %m", __func__); @@ -832,6 +881,12 @@ ipv6_getstate(struct interface *ifp) } TAILQ_INIT(&state->addrs); TAILQ_INIT(&state->ll_callbacks); + + /* Regenerate new ids */ + if (ifp->options && + ifp->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ifp->name)) + ipv6_regentempifid(ifp); } return state; } @@ -839,7 +894,7 @@ ipv6_getstate(struct interface *ifp) void ipv6_handleifa(struct dhcpcd_ctx *ctx, int cmd, struct if_head *ifs, const char *ifname, - const struct in6_addr *addr, int flags) + const struct in6_addr *addr, uint8_t prefix_len, int flags) { struct interface *ifp; struct ipv6_state *state; @@ -887,16 +942,50 @@ ipv6_handleifa(struct dhcpcd_ctx *ctx, break; case RTM_NEWADDR: if (ap == NULL) { + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + ap = calloc(1, sizeof(*ap)); ap->iface = ifp; ap->addr = *addr; - inet_ntop(AF_INET6, &addr->s6_addr, - ap->saddr, sizeof(ap->saddr)); + ap->prefix_len = prefix_len; + ipv6_makeprefix(&ap->prefix, &ap->addr, + ap->prefix_len); + cbp = inet_ntop(AF_INET6, &addr->s6_addr, + buf, sizeof(buf)); + if (cbp) + snprintf(ap->saddr, sizeof(ap->saddr), + "%s/%d", cbp, prefix_len); + if (if_getlifetime6(ap) == -1) { + /* No support or address vanished. + * Either way, just set a deprecated + * infinite time lifetime and continue. + * This is fine because we only want + * to know this when trying to extend + * temporary addresses. + * As we can't extend infinite, we'll + * create a new temporary address. */ + ap->prefix_pltime = 0; + ap->prefix_vltime = + ND6_INFINITE_LIFETIME; + } + /* This is a minor regression against RFC 4941 + * because the kernel only knows when the + * lifetimes were last updated, not when the + * address was initially created. + * Provided dhcpcd is not restarted, this + * won't be a problem. + * If we don't like it, we can always + * pretend lifetimes are infinite and always + * generate a new temporary address on + * restart. */ + ap->acquired = ap->created; TAILQ_INSERT_TAIL(&state->addrs, ap, next); } ap->addr_flags = flags; - + if (ap->addr_flags & IN6_IFF_TEMPORARY) + ap->flags |= IPV6_AF_TEMPORARY; if (IN6_IS_ADDR_LINKLOCAL(&ap->addr)) { #ifdef IPV6_POLLADDRFLAG if (ap->addr_flags & IN6_IFF_TENTATIVE) { @@ -952,7 +1041,8 @@ ipv6_iffindaddr(const struct interface *ifp, const struct in6_addr *addr) return NULL; } -int ipv6_addlinklocalcallback(struct interface *ifp, +int +ipv6_addlinklocalcallback(struct interface *ifp, void (*callback)(void *), void *arg) { struct ipv6_state *state; @@ -977,7 +1067,7 @@ int ipv6_addlinklocalcallback(struct interface *ifp, } void -ipv6_free_ll_callbacks(struct interface *ifp) +ipv6_drop(struct interface *ifp) { struct ipv6_state *state; struct ll_callback *cb; @@ -988,6 +1078,7 @@ ipv6_free_ll_callbacks(struct interface *ifp) TAILQ_REMOVE(&state->ll_callbacks, cb, next); free(cb); } + ipv6_freedrop_addrs(&state->addrs, 2, NULL); } } @@ -1140,6 +1231,10 @@ ipv6_start(struct interface *ifp) !(ap->addr_flags & IN6_IFF_DUPLICATED)) break; } + /* Regenerate new ids */ + if (ifp->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ifp->name)) + ipv6_regentempifid(ifp); } else ap = NULL; @@ -1155,7 +1250,7 @@ ipv6_free(struct interface *ifp) struct ipv6_addr *ap; if (ifp) { - ipv6_free_ll_callbacks(ifp); + ipv6_drop(ifp); state = IPV6_STATE(ifp); if (state) { while ((ap = TAILQ_FIRST(&state->addrs))) { @@ -1227,6 +1322,364 @@ ipv6_handleifa_addrs(int cmd, return alldadcompleted ? found : 0; } +#ifndef KERNEL_MANAGETEMPADDR +static const struct ipv6_addr * +ipv6_findaddrid(struct dhcpcd_ctx *ctx, uint8_t *addr) +{ + const struct interface *ifp; + const struct ipv6_state *state; + const struct ipv6_addr *ia; + + TAILQ_FOREACH(ifp, ctx->ifaces, next) { + if ((state = IPV6_CSTATE(ifp))) { + TAILQ_FOREACH(ia, &state->addrs, next) { + if (memcmp(&ia->addr.s6_addr[8], addr, 8) == 0) + return ia; + } + } + } + return NULL; +} + +static const uint8_t nullid[8]; +static const uint8_t anycastid[8] = { + 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80 }; +static const uint8_t isatapid[4] = { 0x00, 0x00, 0x5e, 0xfe }; + +static void +ipv6_regen_desync(struct interface *ifp, int force) +{ + struct ipv6_state *state; + time_t max; + + state = IPV6_STATE(ifp); + + /* RFC4941 Section 5 states that DESYNC_FACTOR must never be + * greater than TEMP_VALID_LIFETIME - REGEN_ADVANCE. + * I believe this is an error and it should be never be greateter than + * TEMP_PREFERRED_LIFETIME - REGEN_ADVANCE. */ + max = ip6_temp_preferred_lifetime(ifp->name) - REGEN_ADVANCE; + if (state->desync_factor && !force && state->desync_factor < max) + return; + if (state->desync_factor == 0) + state->desync_factor = + (time_t)arc4random_uniform(MIN(MAX_DESYNC_FACTOR, + (uint32_t)max)); + max = ip6_temp_preferred_lifetime(ifp->name) - + state->desync_factor - REGEN_ADVANCE; + eloop_timeout_add_sec(ifp->ctx->eloop, max, ipv6_regentempifid, ifp); +} + +void +ipv6_gentempifid(struct interface *ifp) +{ + struct ipv6_state *state; + MD5_CTX md5; + uint8_t seed[16], digest[16]; + int retry; + + state = IPV6_STATE(ifp); + if (state == NULL) + return; + + retry = 0; + if (memcmp(nullid, state->randomseed0, sizeof(nullid)) == 0) { + uint32_t r; + + r = arc4random(); + memcpy(seed, &r, sizeof(r)); + r = arc4random(); + memcpy(seed + sizeof(r), &r, sizeof(r)); + } else + memcpy(seed, state->randomseed0, sizeof(state->randomseed0)); + + memcpy(seed + sizeof(state->randomseed0), + state->randomseed1, sizeof(state->randomseed1)); + +again: + /* RFC4941 Section 3.2.1.1 + * Take the left-most 64bits and set bit 6 to zero */ + MD5Init(&md5); + MD5Update(&md5, seed, sizeof(seed)); + MD5Final(digest, &md5); + + /* RFC4941 Section 3.2.1.1 + * Take the left-most 64bits and set bit 6 to zero */ + memcpy(state->randomid, digest, sizeof(state->randomid)); + state->randomid[0] &= ~EUI64_UBIT; + + /* RFC4941 Section 3.2.1.4 + * Reject reserved or existing id's */ + if (memcmp(nullid, state->randomid, sizeof(nullid)) == 0 || + (memcmp(anycastid, state->randomid, 7) == 0 && + (anycastid[7] & state->randomid[7]) == anycastid[7]) || + memcmp(isatapid, state->randomid, sizeof(isatapid)) == 0 || + ipv6_findaddrid(ifp->ctx, state->randomid)) + { + if (++retry < GEN_TEMPID_RETRY_MAX) { + memcpy(seed, digest + 8, 8); + goto again; + } + memset(state->randomid, 0, sizeof(state->randomid)); + } + + /* RFC4941 Section 3.2.1.6 + * Save the right-most 64bits of the digest */ + memcpy(state->randomseed0, digest + 8, + sizeof(state->randomseed0)); +} + +/* RFC4941 Section 3.3.7 */ +static void +ipv6_tempdadcallback(void *arg) +{ + struct ipv6_addr *ia = arg; + + if (ia->flags & IPV6_AF_DUPLICATED) { + struct ipv6_addr *ia1; + struct timeval tv; + + if (++ia->dadcounter == TEMP_IDGEN_RETRIES) { + syslog(LOG_ERR, + "%s: too many duplicate temporary addresses", + ia->iface->name); + return; + } + get_monotonic(&tv); + if ((ia1 = ipv6_createtempaddr(ia, &tv)) == NULL) + syslog(LOG_ERR, "ipv6_createtempaddr: %m"); + else + ia1->dadcounter = ia->dadcounter; + ipv6_deleteaddr(ia); + if (ia1) + ipv6_addaddr(ia1, &ia1->acquired); + } +} + +struct ipv6_addr * +ipv6_createtempaddr(struct ipv6_addr *ia0, const struct timeval *now) +{ + struct ipv6_state *state; + const struct ipv6_state *cstate; + int genid; + struct in6_addr addr, mask; + uint32_t randid[2]; + const struct interface *ifp; + const struct ipv6_addr *ap; + struct ipv6_addr *ia; + uint32_t i, trylimit; + char buf[INET6_ADDRSTRLEN]; + const char *cbp; + + trylimit = TEMP_IDGEN_RETRIES; + state = IPV6_STATE(ia0->iface); + genid = 0; + + addr = ia0->addr; + ipv6_mask(&mask, ia0->prefix_len); + /* clear the old ifid */ + for (i = 0; i < 4; i++) + addr.s6_addr32[i] &= mask.s6_addr32[i]; + +again: + if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) + genid = 1; + if (genid) { + memcpy(state->randomseed1, &ia0->addr.s6_addr[8], + sizeof(state->randomseed1)); + ipv6_gentempifid(ia0->iface); + if (memcmp(state->randomid, nullid, sizeof(nullid)) == 0) { + errno = EFAULT; + return NULL; + } + } + memcpy(&randid[0], state->randomid, sizeof(randid[0])); + memcpy(&randid[1], state->randomid + sizeof(randid[1]), + sizeof(randid[2])); + addr.s6_addr32[2] |= randid[0] & ~mask.s6_addr32[2]; + addr.s6_addr32[3] |= randid[1] & ~mask.s6_addr32[3]; + + /* Ensure we don't already have it */ + TAILQ_FOREACH(ifp, ia0->iface->ctx->ifaces, next) { + cstate = IPV6_CSTATE(ifp); + if (cstate) { + TAILQ_FOREACH(ap, &cstate->addrs, next) { + if (IN6_ARE_ADDR_EQUAL(&ap->addr, &addr)) { + if (--trylimit == 0) { + errno = EEXIST; + return NULL; + } + genid = 1; + goto again; + } + } + } + } + + if ((ia = calloc(1, sizeof(*ia))) == NULL) + return NULL; + + ia->iface = ia0->iface; + ia->addr = addr; + /* Must be made tentative, for our DaD to work */ + ia->addr_flags = IN6_IFF_TENTATIVE; + ia->dadcallback = ipv6_tempdadcallback; + ia->flags = IPV6_AF_NEW | IPV6_AF_AUTOCONF | IPV6_AF_TEMPORARY; + ia->prefix = ia0->prefix; + ia->prefix_len = ia0->prefix_len; + ia->created = ia->acquired = now ? *now : ia0->acquired; + + /* Ensure desync is still valid */ + ipv6_regen_desync(ia->iface, 0); + + /* RFC4941 Section 3.3.4 */ + i = (uint32_t)(ip6_temp_preferred_lifetime(ia0->iface->name) - + state->desync_factor); + ia->prefix_pltime = MIN(ia0->prefix_pltime, i); + i = (uint32_t)ip6_temp_valid_lifetime(ia0->iface->name); + ia->prefix_vltime = MIN(ia0->prefix_vltime, i); + if (ia->prefix_pltime <= REGEN_ADVANCE || + ia->prefix_pltime > ia0->prefix_vltime) + { + errno = EINVAL; + free(ia); + return NULL; + } + + cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); + if (cbp) + snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", + cbp, ia->prefix_len); + else + ia->saddr[0] = '\0'; + + TAILQ_INSERT_TAIL(&state->addrs, ia, next); + return ia; +} + +void +ipv6_settempstale(struct interface *ifp) +{ + struct ipv6_state *state; + struct ipv6_addr *ia; + + state = IPV6_STATE(ifp); + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_TEMPORARY) + ia->flags |= IPV6_AF_STALE; + } +} + +struct ipv6_addr * +ipv6_settemptime(struct ipv6_addr *ia, int flags) +{ + struct ipv6_state *state; + struct ipv6_addr *ap; + + state = IPV6_STATE(ia->iface); + TAILQ_FOREACH_REVERSE(ap, &state->addrs, ipv6_addrhead, next) { + if (ap->flags & IPV6_AF_TEMPORARY && + IN6_ARE_ADDR_EQUAL(&ia->prefix, &ap->prefix)) + { + time_t max, ext; + + if (flags == 0) { + /* Don't try and extend an address which is + * deprecated */ + if (ap->prefix_pltime == 0 || + ap->prefix_pltime == ND6_INFINITE_LIFETIME) + continue; + + if (ap->prefix_pltime - + (uint32_t)(ia->acquired.tv_sec - + ap->acquired.tv_sec) + < REGEN_ADVANCE) + continue; + + return ap; + } + + if (!(ap->flags & IPV6_AF_ADDED)) + ap->flags |= IPV6_AF_NEW | IPV6_AF_AUTOCONF; + ap->flags &= ~IPV6_AF_STALE; + + /* Ensure desync is still valid */ + ipv6_regen_desync(ap->iface, 0); + + /* RFC4941 Section 3.3.2 + * Extend temporary times, but ensure that they + * never last beyond the system limit. */ + ext = ia->acquired.tv_sec + (time_t)ia->prefix_pltime; + max = ap->created.tv_sec + + ip6_temp_preferred_lifetime(ap->iface->name) - + state->desync_factor; + if (ext < max) + ap->prefix_pltime = ia->prefix_pltime; + else + ap->prefix_pltime = + (uint32_t)(max - ia->acquired.tv_sec); + + ext = ia->acquired.tv_sec + (time_t)ia->prefix_vltime; + max = ap->created.tv_sec + + ip6_temp_valid_lifetime(ap->iface->name); + if (ext < max) + ap->prefix_vltime = ia->prefix_vltime; + else + ap->prefix_vltime = + (uint32_t)(max - ia->acquired.tv_sec); + + /* Just extend the latest matching prefix */ + ap->acquired = ia->acquired; + return ap; + } + } + return NULL; +} + +void +ipv6_addtempaddrs(struct interface *ifp, const struct timeval *now) +{ + struct ipv6_state *state; + struct ipv6_addr *ia; + + state = IPV6_STATE(ifp); + TAILQ_FOREACH(ia, &state->addrs, next) { + if (ia->flags & IPV6_AF_TEMPORARY && + !(ia->flags & IPV6_AF_STALE)) + ipv6_addaddr(ia, now); + } +} + +static void +ipv6_regentempaddr(void *arg) +{ + struct ipv6_addr *ia = arg, *ia1; + struct timeval tv; + + syslog(LOG_DEBUG, "%s: regen temp addr %s", + ia->iface->name, ia->saddr); + get_monotonic(&tv); + ia1 = ipv6_createtempaddr(ia, &tv); + if (ia1) + ipv6_addaddr(ia1, &tv); + else + syslog(LOG_ERR, "ipv6_createtempaddr: %m"); +} + +static void +ipv6_regentempifid(void *arg) +{ + struct interface *ifp = arg; + struct ipv6_state *state; + + state = IPV6_STATE(ifp); + if (memcmp(state->randomid, nullid, sizeof(state->randomid))) + ipv6_gentempifid(ifp); + + ipv6_regen_desync(ifp, 1); +} +#endif /* !KERNEL_MANAGETEMPADDR */ + static struct rt6 * find_route6(struct rt6_head *rts, const struct rt6 *r) { diff --git a/ipv6.h b/ipv6.h index 9264c825..7ecc9fcb 100644 --- a/ipv6.h +++ b/ipv6.h @@ -54,6 +54,15 @@ # define ND6_INFINITE_LIFETIME ((uint32_t)~0) #endif +/* RFC4941 constants */ +#define TEMP_VALID_LIFETIME 604800 /* 1 week */ +#define TEMP_PREFERRED_LIFETIME 86400 /* 1 day */ +#define REGEN_ADVANCE 5 /* seconds */ +#define MAX_DESYNC_FACTOR 600 /* 10 minutes */ + +#define TEMP_IDGEN_RETRIES 3 +#define GEN_TEMPID_RETRY_MAX 5 + /* RFC7217 constants */ #define IDGEN_RETRIES 3 #define IDGEN_DELAY 1 /* second */ @@ -76,6 +85,13 @@ # undef IPV6_POLLADDRFLAG #endif +/* Linux-3.18 can manage temporary addresses even with RA + * processing disabled. */ +//#undef IFA_F_MANAGETEMPADDR +#ifdef IFA_F_MANAGETEMPADDR +#define KERNEL_MANAGETEMPADDR +#endif + struct ipv6_addr { TAILQ_ENTRY(ipv6_addr) next; struct interface *iface; @@ -83,6 +99,7 @@ struct ipv6_addr { uint8_t prefix_len; uint32_t prefix_vltime; uint32_t prefix_pltime; + struct timeval created; struct timeval acquired; struct in6_addr addr; int addr_flags; @@ -113,6 +130,7 @@ TAILQ_HEAD(ipv6_addrhead, ipv6_addr); #define IPV6_AF_DELEGATEDPFX 0x0100 #define IPV6_AF_DELEGATEDZERO 0x0200 #define IPV6_AF_REQUEST 0x0400 +#define IPV6_AF_TEMPORARY 0X0800 struct rt6 { TAILQ_ENTRY(rt6) next; @@ -136,6 +154,11 @@ TAILQ_HEAD(ll_callback_head, ll_callback); struct ipv6_state { struct ipv6_addrhead addrs; struct ll_callback_head ll_callbacks; + + time_t desync_factor; + uint8_t randomseed0[8]; /* upper 64 bits of MD5 digest */ + uint8_t randomseed1[8]; /* lower 64 bits */ + uint8_t randomid[8]; }; #define IPV6_STATE(ifp) \ @@ -201,7 +224,7 @@ ssize_t ipv6_addaddrs(struct ipv6_addrhead *addrs); void ipv6_freedrop_addrs(struct ipv6_addrhead *, int, const struct interface *); void ipv6_handleifa(struct dhcpcd_ctx *ctx, int, struct if_head *, - const char *, const struct in6_addr *, int); + const char *, const struct in6_addr *, uint8_t, int); int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct in6_addr *, int); const struct ipv6_addr *ipv6_iffindaddr(const struct interface *, @@ -210,7 +233,23 @@ struct ipv6_addr *ipv6_findaddr(struct dhcpcd_ctx *, const struct in6_addr *, short); #define ipv6_linklocal(ifp) (ipv6_iffindaddr((ifp), NULL)) int ipv6_addlinklocalcallback(struct interface *, void (*)(void *), void *); -void ipv6_free_ll_callbacks(struct interface *); +void ipv6_drop(struct interface *); + +#ifdef KERNEL_MANAGETEMPADDR +#define ipv6_gentempifid(a) {} +#define ipv6_settempstale(a) {} +#define ipv6_createtempaddr(a, b) (NULL) +#define ipv6_settemptime(a, b) (NULL) +#define ipv6_addtempaddrs(a, b) {} +#else +void ipv6_gentempifid(struct interface *); +void ipv6_settempstale(struct interface *); +struct ipv6_addr *ipv6_createtempaddr(struct ipv6_addr *, + const struct timeval *); +struct ipv6_addr *ipv6_settemptime(struct ipv6_addr *, int); +void ipv6_addtempaddrs(struct interface *, const struct timeval *); +#endif + int ipv6_start(struct interface *); void ipv6_free(struct interface *); void ipv6_ctxfree(struct dhcpcd_ctx *); diff --git a/ipv6nd.c b/ipv6nd.c index 51952f3d..ed9bb2cc 100644 --- a/ipv6nd.c +++ b/ipv6nd.c @@ -691,7 +691,7 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp, struct ipv6_addr *ap; char *opt, *opt2, *tmp; struct timeval expire; - uint8_t new_rap, new_data; + uint8_t new_rap, new_data, new_ap; if (len < sizeof(struct nd_router_advert)) { syslog(LOG_ERR, "IPv6 RA packet too short from %s", ctx->sfrom); @@ -793,6 +793,7 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp, if (rap->lifetime) rap->expired = 0; + ipv6_settempstale(ifp); TAILQ_FOREACH(ap, &rap->addrs, next) { ap->flags |= IPV6_AF_STALE; } @@ -894,13 +895,27 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp, ap->saddr[0] = '\0'; } ap->dadcallback = ipv6nd_dadcallback; + ap->created = ap->acquired = rap->received; TAILQ_INSERT_TAIL(&rap->addrs, ap, next); - } else + + /* New address to dhcpcd RA handling. + * If the address already exists and a valid + * temporary address also exists then + * extend the existing one rather than + * create a new one */ + if (ipv6_iffindaddr(ifp, &ap->addr) && + ipv6_settemptime(ap, 0)) + new_ap = 0; + else + new_ap = 1; + } else { + new_ap = 0; ap->flags &= ~IPV6_AF_STALE; + ap->acquired = rap->received; + } if (pi->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ap->flags |= IPV6_AF_ONLINK; - ap->acquired = rap->received; ap->prefix_vltime = ntohl(pi->nd_opt_pi_valid_time); ap->prefix_pltime = @@ -916,6 +931,24 @@ ipv6nd_handlera(struct ipv6_ctx *ctx, struct interface *ifp, opt2 = strdup(ap->saddr); } } + + /* RFC4941 Section 3.3.3 */ + if (ap->flags & IPV6_AF_AUTOCONF && + ap->iface->options->options & DHCPCD_IPV6RA_OWN && + ip6_use_tempaddr(ap->iface->name)) + { + if (!new_ap) { + if (ipv6_settemptime(ap, 1) == NULL) + new_ap = 1; + } + if (new_ap && ap->prefix_pltime) { + if (ipv6_createtempaddr(ap, + &ap->acquired) == NULL) + syslog(LOG_ERR, + "ipv6_createtempaddr: %m"); + } + } + lifetime = ap->prefix_vltime; break; @@ -1067,6 +1100,7 @@ extra_opt: goto handle_flag; } ipv6_addaddrs(&rap->addrs); + ipv6_addtempaddrs(ifp, &rap->received); ipv6_buildroutes(ifp->ctx); if (ipv6nd_scriptrun(rap)) return;