]> git.ipfire.org Git - thirdparty/dhcpcd.git/commitdiff
Implement RFC4941, Privacy Extensions for Stateless Address Autoconfiguration
authorRoy Marples <roy@marples.name>
Sat, 17 Jan 2015 02:29:54 +0000 (02:29 +0000)
committerRoy Marples <roy@marples.name>
Sat, 17 Jan 2015 02:29:54 +0000 (02:29 +0000)
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]

dhcp6.c
dhcpcd.8.in
dhcpcd.c
if-bsd.c
if-linux.c
if.c
if.h
ipv6.c
ipv6.h
ipv6nd.c

diff --git a/dhcp6.c b/dhcp6.c
index 3e00b2996501f51118d1676b20a50498e3bd0779..20c3d7120e76e056dac5665167416ff6a96ecd76 100644 (file)
--- 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);
                }
        }
index 98a198b3b88af6044c64e0755f4d9745ecbf0e50..9ca613b22ddda850c22bdecece4d523a8b37a58a 100644 (file)
@@ -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
index 26d8e894d824281bd1f57af0d07c4f705662b58c..0c1a8867df6139f90140b3e40125196c3ee68992 100644 (file)
--- 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);
                }
        }
index baa4bd8089f02a7ca517b146484864c1dcbdbaa5..35548fd68541ff49650f4593664b2eabc4aecc1e 100644 (file)
--- 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)
index 69f5432e7ec68346c688ac1278e3450a81c3f8c1..a2d87b62ba38a9381955d89ff1ef8c14bcf91655 100644 (file)
@@ -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 595c4dbc3c0778ab17cf4bb14459722082bce91c..b41a2eb21e164afb5e2ab12429566aeb3a41b24e 100644 (file)
--- 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 98a07586dfa1b7007b4447f126f4879653bf2ca8..c44aed6fd30257a8415602530337df630c7219b8 100644 (file)
--- 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 2142c1736e2dd1cf34ca2f3871bd4cc24e898acf..6d0fe68ba36c369461b4db531f88b6ed42a3d65e 100644 (file)
--- a/ipv6.c
+++ b/ipv6.c
 #include <netinet/in.h>
 #include <netinet/if_ether.h>
 
-#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 <sys/endian.h>
 #  endif
@@ -69,6 +56,7 @@
 #include <syslog.h>
 #include <unistd.h>
 
+#define ELOOP_QUEUE 7
 #include "common.h"
 #include "dhcpcd.h"
 #include "dhcp6.h"
 #include "ipv6.h"
 #include "ipv6nd.h"
 
+#ifdef HAVE_MD5_H
+#  ifndef DEPGEN
+#    include <md5.h>
+#  endif
+#else
+#  include "md5.h"
+#endif
+
 #ifdef SHA2_H
 #  include SHA2_H
 #else
 #  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)
 
 #  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 9264c8257ad645ffe850f1c60dd18d3469178b15..7ecc9fcbfb2e4c49cd6079c2539db51291c95abc 100644 (file)
--- a/ipv6.h
+++ b/ipv6.h
 #  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 */
 #  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 *);
index 51952f3d269a35df92f6584d9aec37fadda35dd8..ed9bb2cc639debeebeae26333f8eadf1905cb9dd 100644 (file)
--- 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;