Merge dhcpcd-8.0.4 from branch 'vendor/DHCPCD'
authorRoy Marples <roy@marples.name>
Wed, 4 Sep 2019 18:29:08 +0000 (19:29 +0100)
committerRoy Marples <roy@marples.name>
Wed, 4 Sep 2019 18:29:08 +0000 (19:29 +0100)
1  2 
contrib/dhcpcd/src/if.c
contrib/dhcpcd/src/ipv6.c

diff --combined contrib/dhcpcd/src/if.c
@@@ -1,6 -1,7 +1,7 @@@
+ /* SPDX-License-Identifier: BSD-2-Clause */
  /*
   * dhcpcd - DHCP client daemon
-  * Copyright (c) 2006-2018 Roy Marples <roy@marples.name>
+  * Copyright (c) 2006-2019 Roy Marples <roy@marples.name>
   * All rights reserved
  
   * Redistribution and use in source and binary forms, with or without
@@@ -59,7 -60,6 +60,6 @@@
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
- #include <fcntl.h>
  
  #include "common.h"
  #include "dev.h"
  #include "ipv6nd.h"
  #include "logerr.h"
  
+ #ifdef __sun
+ /* It has the ioctl, but the member is missing from the struct?
+  * No matter, our getifaddrs foo in if-sun.c will DTRT. */
+ #undef SIOCGIFHWADDR
+ #endif
  void
  if_free(struct interface *ifp)
  {
  
        if (ifp == NULL)
                return;
+ #ifdef IPV4LL
        ipv4ll_free(ifp);
+ #endif
+ #ifdef INET
        dhcp_free(ifp);
        ipv4_free(ifp);
+ #endif
+ #ifdef DHCP6
        dhcp6_free(ifp);
+ #endif
+ #ifdef INET6
        ipv6nd_free(ifp);
        ipv6_free(ifp);
+ #endif
        rt_freeif(ifp);
        free_options(ifp->ctx, ifp->options);
        free(ifp);
@@@ -101,12 -115,6 +115,6 @@@ if_opensockets(struct dhcpcd_ctx *ctx
        if (ctx->pf_inet_fd == -1)
                return -1;
  
- #ifdef IFLR_ACTIVE
-       ctx->pf_link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0);
-       if (ctx->pf_link_fd == -1)
-               return -1;
- #endif
        return 0;
  }
  
@@@ -116,10 -124,6 +124,6 @@@ if_closesockets(struct dhcpcd_ctx *ctx
  
        if (ctx->pf_inet_fd != -1)
                close(ctx->pf_inet_fd);
- #ifdef IFLR_ACTIVE
-       if (ctx->pf_link_fd != -1)
-               close(ctx->pf_link_fd);
- #endif
  
        if (ctx->priv) {
                if_closesockets_os(ctx);
  }
  
  int
- if_carrier(struct interface *ifp)
+ if_getflags(struct interface *ifp)
  {
-       int r;
-       struct ifreq ifr;
- #ifdef SIOCGIFMEDIA
-       struct ifmediareq ifmr;
- #endif
+       struct ifreq ifr = { .ifr_flags = 0 };
  
-       memset(&ifr, 0, sizeof(ifr));
        strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
        if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == -1)
-               return LINK_UNKNOWN;
+               return -1;
        ifp->flags = (unsigned int)ifr.ifr_flags;
- #ifdef SIOCGIFMEDIA
-       memset(&ifmr, 0, sizeof(ifmr));
-       strlcpy(ifmr.ifm_name, ifp->name, sizeof(ifmr.ifm_name));
-       if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFMEDIA, &ifmr) != -1 &&
-           ifmr.ifm_status & IFM_AVALID)
-               r = (ifmr.ifm_status & IFM_ACTIVE) ? LINK_UP : LINK_DOWN;
-       else
-               r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_UNKNOWN;
- #else
-       r = ifr.ifr_flags & IFF_RUNNING ? LINK_UP : LINK_DOWN;
- #endif
-       return r;
+       return 0;
  }
  
  int
  if_setflag(struct interface *ifp, short flag)
  {
-       struct ifreq ifr;
-       int r;
+       struct ifreq ifr = { .ifr_flags = 0 };
+       short f;
+       if (if_getflags(ifp) == -1)
+               return -1;
+       f = (short)ifp->flags;
+       if ((f & flag) == flag)
+               return 0;
  
-       memset(&ifr, 0, sizeof(ifr));
        strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
-       r = -1;
-       if (ioctl(ifp->ctx->pf_inet_fd, SIOCGIFFLAGS, &ifr) == 0) {
-               if (flag == 0 || (ifr.ifr_flags & flag) == flag)
-                       r = 0;
-               else {
-                       ifr.ifr_flags |= flag;
-                       if (ioctl(ifp->ctx->pf_inet_fd, SIOCSIFFLAGS, &ifr) ==0)
-                               r = 0;
-               }
-               ifp->flags = (unsigned int)ifr.ifr_flags;
-       }
-       return r;
+       ifr.ifr_flags = f | flag;
+       if (ioctl(ifp->ctx->pf_inet_fd, SIOCSIFFLAGS, &ifr) == -1)
+               return -1;
+       ifp->flags = (unsigned int)ifr.ifr_flags;
+       return 0;
  }
  
  static int
@@@ -254,6 -241,7 +241,7 @@@ if_learnaddrs(struct dhcpcd_ctx *ctx, s
                case AF_INET6:
                        sin6 = (void *)ifa->ifa_addr;
                        net6 = (void *)ifa->ifa_netmask;
  #ifdef __KAME__
                        if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
                                /* Remove the scope from the address */
@@@ -326,29 -314,36 +314,36 @@@ if_discover(struct dhcpcd_ctx *ctx, str
        struct if_spec spec;
  #ifdef AF_LINK
        const struct sockaddr_dl *sdl;
- #ifdef SIOCGIFPRIORITY
-       struct ifreq ifr;
- #endif
  #ifdef IFLR_ACTIVE
-       struct if_laddrreq iflr;
- #endif
- #ifdef IFLR_ACTIVE
-       memset(&iflr, 0, sizeof(iflr));
+       struct if_laddrreq iflr = { .flags = IFLR_PREFIX };
+       int link_fd;
  #endif
  #elif AF_PACKET
        const struct sockaddr_ll *sll;
  #endif
+ #if defined(SIOCGIFPRIORITY) || defined(SIOCGIFHWADDR)
+       struct ifreq ifr;
+ #endif
  
        if ((ifs = malloc(sizeof(*ifs))) == NULL) {
                logerr(__func__);
                return NULL;
        }
-       TAILQ_INIT(ifs);
        if (getifaddrs(ifaddrs) == -1) {
                logerr(__func__);
-               goto out;
+               free(ifs);
+               return NULL;
        }
+       TAILQ_INIT(ifs);
+ #ifdef IFLR_ACTIVE
+       link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+       if (link_fd == -1) {
+               logerr(__func__);
+               free(ifs);
+               return NULL;
+       }
+ #endif
  
        for (ifa = *ifaddrs; ifa; ifa = ifa->ifa_next) {
                if (ifa->ifa_addr != NULL) {
                            MIN(ifa->ifa_addr->sa_len, sizeof(iflr.addr)));
                        iflr.flags = IFLR_PREFIX;
                        iflr.prefixlen = (unsigned int)sdl->sdl_alen * NBBY;
-                       if (ioctl(ctx->pf_link_fd, SIOCGLIFADDR, &iflr) == -1 ||
+                       if (ioctl(link_fd, SIOCGLIFADDR, &iflr) == -1 ||
                            !(iflr.flags & IFLR_ACTIVE))
                        {
                                if_free(ifp);
                        case IFT_PPP: /* FALLTHROUGH */
  #endif
  #ifdef IFT_PROPVIRTUAL
-                       case IFT_PROPVIRTUAL: /* FALLTHROUGH */
+                       case IFT_PROPVIRTUAL:
  #endif
  #if defined(IFT_BRIDGE) || defined(IFT_PPP) || defined(IFT_PROPVIRTUAL)
                                /* Don't allow unless explicit */
                                            ifp->name);
                                        active = IF_INACTIVE;
                                }
+                               __fallthrough; /* Appease gcc-7 */
                                /* FALLTHROUGH */
  #endif
  #ifdef IFT_L2VLAN
 +                              /* FALLTHROUGH */
                        case IFT_L2VLAN: /* FALLTHROUGH */
  #endif
  #ifdef IFT_L3IPVLAN
                                memcpy(ifp->hwaddr, sll->sll_addr, ifp->hwlen);
  #endif
                }
- #ifdef __linux__
-               /* PPP addresses on Linux don't have hardware addresses */
-               else
-                       ifp->index = if_nametoindex(ifp->name);
+ #ifdef SIOCGIFHWADDR
+               else {
+                       /* This is a huge bug in getifaddrs(3) as there
+                        * is no reason why this can't be returned in
+                        * ifa_addr. */
+                       memset(&ifr, 0, sizeof(ifr));
+                       strlcpy(ifr.ifr_name, ifa->ifa_name,
+                           sizeof(ifr.ifr_name));
+                       if (ioctl(ctx->pf_inet_fd, SIOCGIFHWADDR, &ifr) == -1)
+                               logerr("%s: SIOCGIFHWADDR", ifa->ifa_name);
+                       ifp->family = ifr.ifr_hwaddr.sa_family;
+                       if (ioctl(ctx->pf_inet_fd, SIOCGIFINDEX, &ifr) == -1)
+                               logerr("%s: SIOCGIFINDEX", ifa->ifa_name);
+                       ifp->index = (unsigned int)ifr.ifr_ifindex;
+               }
  #endif
  
                /* Ensure hardware address is valid. */
  #endif
  #ifdef ARPHRD_PPP
                        case ARPHRD_PPP:
+ #endif
+ #ifdef ARPHRD_NONE
+                       case ARPHRD_NONE:
  #endif
                                /* We don't warn for supported families */
                                break;
                 * we can work them out. */
                ifp->metric = 200 + ifp->index;
                if (if_getssid(ifp) != -1) {
-                       ifp->wireless = 1;
+                       ifp->wireless = true;
                        ifp->metric += 100;
                }
  #endif
                TAILQ_INSERT_TAIL(ifs, ifp, next);
        }
  
- out:
+ #ifdef IFLR_ACTIVE
+       close(link_fd);
+ #endif
        return ifs;
  }
  
@@@ -699,6 -710,11 +711,11 @@@ if_domtu(const struct interface *ifp, s
        int r;
        struct ifreq ifr;
  
+ #ifdef __sun
+       if (mtu == 0)
+               return if_mtu_os(ifp);
+ #endif
        memset(&ifr, 0, sizeof(ifr));
        strlcpy(ifr.ifr_name, ifp->name, sizeof(ifr.ifr_name));
        ifr.ifr_mtu = mtu;
        return ifr.ifr_mtu;
  }
  
- /* Interface comparer for working out ordering. */
static int
- if_cmp(const struct interface *si, const struct interface *ti)
+ #ifdef ALIAS_ADDR
+ int
+ if_makealias(char *alias, size_t alias_len, const char *ifname, int lun)
  {
- #ifdef INET
-       int r;
- #endif
-       /* Check active first */
-       if (si->active > ti->active)
-               return -1;
-       if (si->active < ti->active)
-               return 1;
-       /* Check carrier status next */
-       if (si->carrier > ti->carrier)
-               return -1;
-       if (si->carrier < ti->carrier)
-               return 1;
-       if (D_STATE_RUNNING(si) && !D_STATE_RUNNING(ti))
-               return -1;
-       if (!D_STATE_RUNNING(si) && D_STATE_RUNNING(ti))
-               return 1;
-       if (RS_STATE_RUNNING(si) && !RS_STATE_RUNNING(ti))
-               return -1;
-       if (!RS_STATE_RUNNING(si) && RS_STATE_RUNNING(ti))
-               return 1;
-       if (D6_STATE_RUNNING(si) && !D6_STATE_RUNNING(ti))
-               return -1;
-       if (!D6_STATE_RUNNING(si) && D6_STATE_RUNNING(ti))
-               return 1;
  
- #ifdef INET
-       /* Special attention needed here due to states and IPv4LL. */
-       if ((r = ipv4_ifcmp(si, ti)) != 0)
-               return r;
- #endif
-       /* Finally, metric */
-       if (si->metric < ti->metric)
-               return -1;
-       if (si->metric > ti->metric)
-               return 1;
-       return 0;
+       if (lun == 0)
+               return strlcpy(alias, ifname, alias_len);
+       return snprintf(alias, alias_len, "%s:%u", ifname, lun);
  }
+ #endif
  
- /* Sort the interfaces into a preferred order - best first, worst last. */
- void
- if_sortinterfaces(struct dhcpcd_ctx *ctx)
+ struct interface *
+ if_findifpfromcmsg(struct dhcpcd_ctx *ctx, struct msghdr *msg, int *hoplimit)
  {
-       struct if_head sorted;
-       struct interface *ifp, *ift;
-       if (ctx->ifaces == NULL ||
-           (ifp = TAILQ_FIRST(ctx->ifaces)) == NULL ||
-           TAILQ_NEXT(ifp, next) == NULL)
-               return;
+       struct cmsghdr *cm;
+       unsigned int ifindex = 0;
+       struct interface *ifp;
+ #if defined(INET) && defined(IP_PKTINFO)
+       struct in_pktinfo ipi;
+ #endif
+ #ifdef INET6
+       struct in6_pktinfo ipi6;
+ #else
+       UNUSED(hoplimit);
+ #endif
  
-       TAILQ_INIT(&sorted);
-       TAILQ_REMOVE(ctx->ifaces, ifp, next);
-       TAILQ_INSERT_HEAD(&sorted, ifp, next);
-       while ((ifp = TAILQ_FIRST(ctx->ifaces))) {
-               TAILQ_REMOVE(ctx->ifaces, ifp, next);
-               TAILQ_FOREACH(ift, &sorted, next) {
-                       if (if_cmp(ifp, ift) == -1) {
-                               TAILQ_INSERT_BEFORE(ift, ifp, next);
+       for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(msg);
+            cm;
+            cm = (struct cmsghdr *)CMSG_NXTHDR(msg, cm))
+       {
+ #if defined(INET) && defined(IP_PKTINFO)
+               if (cm->cmsg_level == IPPROTO_IP) {
+                       switch(cm->cmsg_type) {
+                       case IP_PKTINFO:
+                               if (cm->cmsg_len != CMSG_LEN(sizeof(ipi)))
+                                       continue;
+                               memcpy(&ipi, CMSG_DATA(cm), sizeof(ipi));
+                               ifindex = (unsigned int)ipi.ipi_ifindex;
                                break;
                        }
                }
-               if (ift == NULL)
-                       TAILQ_INSERT_TAIL(&sorted, ifp, next);
+ #endif
+ #ifdef INET6
+               if (cm->cmsg_level == IPPROTO_IPV6) {
+                       switch(cm->cmsg_type) {
+                       case IPV6_PKTINFO:
+                               if (cm->cmsg_len != CMSG_LEN(sizeof(ipi6)))
+                                       continue;
+                               memcpy(&ipi6, CMSG_DATA(cm), sizeof(ipi6));
+                               ifindex = (unsigned int)ipi6.ipi6_ifindex;
+                               break;
+                       case IPV6_HOPLIMIT:
+                               if (cm->cmsg_len != CMSG_LEN(sizeof(int)))
+                                       continue;
+                               if (hoplimit == NULL)
+                                       break;
+                               memcpy(hoplimit, CMSG_DATA(cm), sizeof(int));
+                               break;
+                       }
+               }
+ #endif
        }
-       TAILQ_CONCAT(ctx->ifaces, &sorted, next);
+       /* Find the receiving interface */
+       TAILQ_FOREACH(ifp, ctx->ifaces, next) {
+               if (ifp->index == ifindex)
+                       break;
+       }
+       if (ifp == NULL)
+               errno = ESRCH;
+       return ifp;
  }
  
  int
@@@ -791,6 -804,9 +805,9 @@@ xsocket(int domain, int type, int proto
  #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
        int xflags, xtype = type;
  #endif
+ #ifdef SO_RERROR
+       int on;
+ #endif
  
  #ifndef HAVE_SOCK_CLOEXEC
        if (xtype & SOCK_CLOEXEC)
                goto out;
  #endif
  
+ #ifdef SO_RERROR
+       /* Tell recvmsg(2) to return ENOBUFS if the receiving socket overflows. */
+       on = 1;
+       if (setsockopt(s, SOL_SOCKET, SO_RERROR, &on, sizeof(on)) == -1)
+               logerr("%s: SO_RERROR", __func__);
+ #endif
        return s;
  
  #if !defined(HAVE_SOCK_CLOEXEC) || !defined(HAVE_SOCK_NONBLOCK)
@@@ -1,6 -1,7 +1,7 @@@
+ /* SPDX-License-Identifier: BSD-2-Clause */
  /*
   * dhcpcd - DHCP client daemon
-  * Copyright (c) 2006-2018 Roy Marples <roy@marples.name>
+  * Copyright (c) 2006-2019 Roy Marples <roy@marples.name>
   * All rights reserved
  
   * Redistribution and use in source and binary forms, with or without
@@@ -30,6 -31,7 +31,7 @@@
  #include <sys/socket.h>
  #include <sys/stat.h>
  
+ #include <arpa/inet.h>
  #include <net/if.h>
  #include <net/route.h>
  #include <netinet/in.h>
  #  define SHA256_DIGEST_LENGTH                32
  #endif
  
 +#if 0  /* XXX: suppress the warning as dfly currently doesn't support it */
  #ifdef IPV6_POLLADDRFLAG
  #  warning kernel does not report IPv6 address flag changes
  #  warning polling tentative address flags periodically
  #endif
 +#endif
  
  /* Hackery at it's finest. */
  #ifndef s6_addr32
@@@ -130,30 -130,17 +132,17 @@@ in
  ipv6_init(struct dhcpcd_ctx *ctx)
  {
  
-       if (ctx->sndhdr.msg_iovlen == 1)
+       if (ctx->ra_routers != NULL)
                return 0;
  
-       if (ctx->ra_routers == NULL) {
-               ctx->ra_routers = malloc(sizeof(*ctx->ra_routers));
-               if (ctx->ra_routers == NULL)
-                       return -1;
-       }
+       ctx->ra_routers = malloc(sizeof(*ctx->ra_routers));
+       if (ctx->ra_routers == NULL)
+               return -1;
        TAILQ_INIT(ctx->ra_routers);
  
-       ctx->sndhdr.msg_namelen = sizeof(struct sockaddr_in6);
-       ctx->sndhdr.msg_iov = ctx->sndiov;
-       ctx->sndhdr.msg_iovlen = 1;
-       ctx->sndhdr.msg_control = ctx->sndbuf;
-       ctx->sndhdr.msg_controllen = sizeof(ctx->sndbuf);
-       ctx->rcvhdr.msg_name = &ctx->from;
-       ctx->rcvhdr.msg_namelen = sizeof(ctx->from);
-       ctx->rcvhdr.msg_iov = ctx->iov;
-       ctx->rcvhdr.msg_iovlen = 1;
-       ctx->rcvhdr.msg_control = ctx->ctlbuf;
-       // controllen is set at recieve
-       //ctx->rcvhdr.msg_controllen = sizeof(ctx->rcvbuf);
+ #ifndef __sun
        ctx->nd_fd = -1;
+ #endif
        ctx->dhcp6_fd = -1;
        return 0;
  }
@@@ -628,31 -615,39 +617,39 @@@ ipv6_deleteaddr(struct ipv6_addr *ia
                        break;
                }
        }
+ #ifdef ND6_ADVERTISE
+       /* Advertise the address if it exists on another interface. */
+       ipv6nd_advertise(ia);
+ #endif
  }
  
  static int
  ipv6_addaddr1(struct ipv6_addr *ia, const struct timespec *now)
  {
        struct interface *ifp;
-       struct ipv6_state *state;
-       struct ipv6_addr *ia2;
        uint32_t pltime, vltime;
        __printflike(1, 2) void (*logfunc)(const char *, ...);
+ #ifdef ND6_ADVERTISE
+       bool vltime_was_zero = ia->prefix_vltime == 0;
+ #endif
+ #ifdef __sun
+       struct ipv6_state *state;
+       struct ipv6_addr *ia2;
  
-       /* Ensure no other interface has this address */
-       TAILQ_FOREACH(ifp, ia->iface->ctx->ifaces, next) {
-               if (ifp == ia->iface)
-                       continue;
-               state = IPV6_STATE(ifp);
-               if (state == NULL)
-                       continue;
-               TAILQ_FOREACH(ia2, &state->addrs, next) {
-                       if (IN6_ARE_ADDR_EQUAL(&ia2->addr, &ia->addr)) {
-                               ipv6_deleteaddr(ia2);
-                               break;
-                       }
-               }
+       /* If we re-add then address on Solaris then the prefix
+        * route will be scrubbed and re-added. Something might
+        * be using it, so let's avoid it. */
+       if (ia->flags & IPV6_AF_DADCOMPLETED) {
+               logdebugx("%s: IP address %s already exists",
+                   ia->iface->name, ia->saddr);
+ #ifdef ND6_ADVERTISE
+               goto advertise;
+ #else
+               return 0;
+ #endif
        }
+ #endif
  
        /* Remember the interface of the address. */
        ifp = ia->iface;
                    " seconds",
                    ifp->name, ia->prefix_pltime, ia->prefix_vltime);
  
        if (if_address6(RTM_NEWADDR, ia) == -1) {
                logerr(__func__);
                /* Restore real pltime and vltime */
        }
  #endif
  
+ #ifdef ND6_ADVERTISE
+ #ifdef __sun
+ advertise:
+ #endif
+       /* Re-advertise the preferred address to be safe. */
+       if (!vltime_was_zero)
+               ipv6nd_advertise(ia);
+ #endif
        return 0;
  }
  
  #ifdef ALIAS_ADDR
- /* Find the next logical aliase address we can use. */
+ /* Find the next logical alias address we can use. */
  static int
  ipv6_aliasaddr(struct ipv6_addr *ia, struct ipv6_addr **repl)
  {
        struct ipv6_state *state;
        struct ipv6_addr *iap;
-       unsigned int unit;
+       unsigned int lun;
        char alias[IF_NAMESIZE];
  
        if (ia->alias[0] != '\0')
                }
        }
  
-       unit = 0;
+       lun = 0;
  find_unit:
-       if (unit == 0)
-               strlcpy(alias, ia->iface->name, sizeof(alias));
-       else
-               snprintf(alias, sizeof(alias), "%s:%u", ia->iface->name, unit);
+       if (if_makealias(alias, IF_NAMESIZE, ia->iface->name, lun) >=
+           IF_NAMESIZE)
+       {
+               errno = ENOMEM;
+               return -1;
+       }
        TAILQ_FOREACH(iap, &state->addrs, next) {
                if (iap->alias[0] == '\0')
                        continue;
        }
  
        if (iap != NULL) {
-               if (unit == UINT_MAX) {
+               if (lun == UINT_MAX) {
                        errno = ERANGE;
                        return -1;
                }
-               unit++;
+               lun++;
                goto find_unit;
        }
  
@@@ -893,10 -898,14 +900,14 @@@ ipv6_findaddrmatch(const struct ipv6_ad
  struct ipv6_addr *
  ipv6_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, unsigned int flags)
  {
-       struct ipv6_addr *dap, *nap;
+       struct ipv6_addr *nap;
+ #ifdef DHCP6
+       struct ipv6_addr *dap;
+ #endif
  
-       dap = dhcp6_findaddr(ctx, addr, flags);
        nap = ipv6nd_findaddr(ctx, addr, flags);
+ #ifdef DHCP6
+       dap = dhcp6_findaddr(ctx, addr, flags);
        if (!dap && !nap)
                return NULL;
        if (dap && !nap)
        if (nap->iface->metric < dap->iface->metric)
                return nap;
        return dap;
+ #else
+       return nap;
+ #endif
  }
  
  ssize_t
  ipv6_addaddrs(struct ipv6_addrhead *addrs)
  {
-       struct ipv6_addr *ap, *apn, *apf;
+       struct ipv6_addr *ap, *apn;
        ssize_t i;
        struct timespec now;
  
                } else if (!(ap->flags & IPV6_AF_STALE) &&
                    !IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
                {
-                       apf = ipv6_findaddr(ap->iface->ctx,
-                           &ap->addr, IPV6_AF_ADDED);
-                       if (apf && apf->iface != ap->iface) {
-                               if (apf->iface->metric <= ap->iface->metric) {
-                                       loginfox("%s: preferring %s on %s",
-                                           ap->iface->name,
-                                           ap->saddr,
-                                           apf->iface->name);
-                                       continue;
-                               }
-                               loginfox("%s: preferring %s on %s",
-                                   apf->iface->name,
-                                   ap->saddr,
-                                   ap->iface->name);
-                               if (if_address6(RTM_DELADDR, apf) == -1 &&
-                                   errno != EADDRNOTAVAIL && errno != ENXIO)
-                                       logerr(__func__);
-                               apf->flags &=
-                                   ~(IPV6_AF_ADDED | IPV6_AF_DADCOMPLETED);
-                       } else if (apf)
-                               apf->flags &= ~IPV6_AF_ADDED;
                        if (ap->flags & IPV6_AF_NEW)
                                i++;
                        if (!timespecisset(&now))
@@@ -991,6 -982,7 +984,7 @@@ ipv6_freeaddr(struct ipv6_addr *ia
        }
  
        eloop_q_timeout_delete(ia->iface->ctx->eloop, 0, NULL, ia);
+       free(ia->na);
        free(ia);
  }
  
@@@ -1072,6 -1064,31 +1066,31 @@@ ipv6_getstate(struct interface *ifp
        return state;
  }
  
+ struct ipv6_addr *
+ ipv6_ifanyglobal(struct interface *ifp)
+ {
+       struct ipv6_state *state;
+       struct ipv6_addr *ia;
+       if (ifp->carrier == LINK_DOWN)
+               return NULL;
+       state = IPV6_STATE(ifp);
+       if (state == NULL)
+               return NULL;
+       TAILQ_FOREACH(ia, &state->addrs, next) {
+               if (IN6_IS_ADDR_LINKLOCAL(&ia->addr))
+                       continue;
+               /* Let's be optimistic.
+                * Any decent OS won't forward or accept traffic
+                * from/to tentative or detached addresses. */
+               if (!(ia->addr_flags & IN6_IFF_DUPLICATED))
+                       break;
+       }
+       return ia;
+ }
  void
  ipv6_handleifa(struct dhcpcd_ctx *ctx,
      int cmd, struct if_head *ifs, const char *ifname,
        struct ipv6_state *state;
        struct ipv6_addr *ia;
        struct ll_callback *cb;
+       bool anyglobal;
+ #ifdef __sun
+       struct sockaddr_in6 subnet;
+       /* Solaris on-link route is an unspecified address! */
+       if (IN6_IS_ADDR_UNSPECIFIED(addr)) {
+               if (if_getsubnet(ctx, ifname, AF_INET6,
+                   &subnet, sizeof(subnet)) == -1)
+               {
+                       logerr(__func__);
+                       return;
+               }
+               addr = &subnet.sin6_addr;
+       }
+ #endif
  
  #if 0
        char dbuf[INET6_ADDRSTRLEN];
  
        dbp = inet_ntop(AF_INET6, &addr->s6_addr,
            dbuf, INET6_ADDRSTRLEN);
-       loginfox("%s: cmd %d addr %s",
-           ifname, cmd, dbp);
+       loginfox("%s: cmd %d addr %s addrflags %d",
+           ifname, cmd, dbp, addrflags);
  #endif
  
        if (ifs == NULL)
                return;
        if ((state = ipv6_getstate(ifp)) == NULL)
                return;
+       anyglobal = ipv6_ifanyglobal(ifp) != NULL;
  
        TAILQ_FOREACH(ia, &state->addrs, next) {
                if (IN6_ARE_ADDR_EQUAL(&ia->addr, addr))
        case RTM_DELADDR:
                if (ia != NULL) {
                        TAILQ_REMOVE(&state->addrs, ia, next);
+ #ifdef ND6_ADVERTISE
+                       /* Advertise the address if it exists on
+                        * another interface. */
+                       ipv6nd_advertise(ia);
+ #endif
                        /* We'll free it at the end of the function. */
                }
                break;
        if (ia == NULL)
                return;
  
+       ctx->options &= ~DHCPCD_RTBUILD;
        ipv6nd_handleifa(cmd, ia, pid);
  #ifdef DHCP6
        dhcp6_handleifa(cmd, ia, pid);
        /* Done with the ia now, so free it. */
        if (cmd == RTM_DELADDR)
                ipv6_freeaddr(ia);
+       else if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+               ia->flags |= IPV6_AF_DADCOMPLETED;
+       /* If we've not already called rt_build via the IPv6ND
+        * or DHCP6 handlers and the existance of any useable
+        * global address on the interface has changed,
+        * call rt_build to add/remove the default route. */
+       if (ifp->active && ifp->options->options & DHCPCD_IPV6 &&
+           !(ctx->options & DHCPCD_RTBUILD) &&
+           (ipv6_ifanyglobal(ifp) != NULL) != anyglobal)
+               rt_build(ctx, AF_INET6);
  }
  
  int
@@@ -1211,8 -1262,10 +1264,10 @@@ ipv6_hasaddr(const struct interface *if
  
        if (ipv6nd_iffindaddr(ifp, NULL, 0) != NULL)
                return 1;
+ #ifdef DHCP6
        if (dhcp6_iffindaddr(ifp, NULL, 0) != NULL)
                return 1;
+ #endif
        return 0;
  }
  
@@@ -1479,8 -1532,10 +1534,10 @@@ ipv6_newaddr(struct interface *ifp, con
                goto err;
  
        ia->iface = ifp;
-       ia->flags = IPV6_AF_NEW | flags;
        ia->addr_flags = addr_flags;
+       ia->flags = IPV6_AF_NEW | flags;
+       if (!(ia->addr_flags & IN6_IFF_NOTUSEABLE))
+               ia->flags |= IPV6_AF_DADCOMPLETED;
        ia->prefix_len = prefix_len;
        ia->dhcp6_fd = -1;
  
                        goto err;
        } else if (ia->flags & IPV6_AF_RAPFX) {
                ia->prefix = *addr;
+ #ifdef __sun
+               ia->addr = *addr;
+               cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf));
+               goto paddr;
+ #else
                return ia;
+ #endif
        } else if (ia->flags & (IPV6_AF_REQUEST | IPV6_AF_DELEGATEDPFX) &&
                   prefix_len != 128)
        {
@@@ -1565,23 -1626,17 +1628,17 @@@ ipv6_staticdadcallback(void *arg
  }
  
  ssize_t
- ipv6_env(char **env, const char *prefix, const struct interface *ifp)
+ ipv6_env(FILE *fp, const char *prefix, const struct interface *ifp)
  {
-       char **ep;
-       ssize_t n;
        struct ipv6_addr *ia;
  
-       ep = env;
-       n = 0;
        ia = ipv6_iffindaddr(UNCONST(ifp), &ifp->options->req_addr6,
            IN6_IFF_NOTUSEABLE);
-       if (ia) {
-               if (env)
-                       addvar(&ep, prefix, "ip6_address", ia->saddr);
-               n++;
-       }
-       return n;
+       if (ia == NULL)
+               return 0;
+       if (efprintf(fp, "%s_ip6_address=%s", prefix, ia->saddr) == -1)
+               return -1;
+       return 1;
  }
  
  int
@@@ -1637,7 -1692,6 +1694,6 @@@ ipv6_startstatic(struct interface *ifp
        ia->prefix_pltime = ND6_INFINITE_LIFETIME;
        ia->dadcallback = ipv6_staticdadcallback;
        ipv6_addaddr(ia, NULL);
-       if_initrt(ifp->ctx, AF_INET6);
        rt_build(ifp->ctx, AF_INET6);
        if (run_script)
                script_runreason(ifp, "STATIC6");
@@@ -1679,8 -1733,6 +1735,6 @@@ ipv6_start(struct interface *ifp
                        ipv6_regentempifid(ifp);
        }
  
-       /* Load existing routes */
-       if_initrt(ifp->ctx, AF_INET6);
        return 0;
  }
  
@@@ -1704,10 -1756,8 +1758,8 @@@ ipv6_freedrop(struct interface *ifp, in
  
        ipv6_freedrop_addrs(&state->addrs, drop ? 2 : 0, NULL);
        if (drop) {
-               if (ifp->ctx->ra_routers != NULL) {
-                       if_initrt(ifp->ctx, AF_INET6);
+               if (ifp->ctx->ra_routers != NULL)
                        rt_build(ifp->ctx, AF_INET6);
-               }
        } else {
                /* Because we need to cache the addresses we don't control,
                 * we only free the state on when NOT dropping addresses. */
@@@ -2179,13 -2229,11 +2231,11 @@@ inet6_makeprefix(struct interface *ifp
        }
  
        /* There is no point in trying to manage a /128 prefix,
-        * ones without a lifetime or ones not on link or delegated */
-       if (addr->prefix_len == 128 ||
-           addr->prefix_vltime == 0 ||
-           !(addr->flags & (IPV6_AF_ONLINK | IPV6_AF_DELEGATEDPFX)))
+        * ones without a lifetime.  */
+       if (addr->prefix_len == 128 || addr->prefix_vltime == 0)
                return NULL;
  
-       /* Don't install a reject route when not creating bigger prefixes */
+       /* Don't install a reject route when not creating bigger prefixes. */
        if (addr->flags & IPV6_AF_NOREJECT)
                return NULL;
  
  #ifndef __linux__
                sa_in6_init(&rt->rt_gateway, &in6addr_loopback);
  #endif
-       } else
+       } else if (!(addr->flags & IPV6_AF_ONLINK))
+               sa_in6_init(&rt->rt_gateway, &rap->from);
+       else
                rt->rt_gateway.sa_family = AF_UNSPEC;
        sa_in6_init(&rt->rt_ifa, &addr->addr);
        return rt;
@@@ -2241,7 -2291,7 +2293,7 @@@ inet6_makerouter(struct ra *rap
            IN6_ARE_ADDR_EQUAL(&((rtp)->mask), &in6addr_any))
  
  static int
- inet6_staticroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx)
+ inet6_staticroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
  {
        struct interface *ifp;
        struct ipv6_state *state;
                        {
                                rt = inet6_makeprefix(ifp, NULL, ia);
                                if (rt)
-                                       TAILQ_INSERT_TAIL(routes, rt, rt_next);
+                                       rt_proto_add(routes, rt);
                        }
                }
        }
  }
  
  static int
- inet6_raroutes(struct rt_head *routes, struct dhcpcd_ctx *ctx, int expired,
-     bool *have_default)
+ inet6_raroutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx)
  {
        struct rt *rt;
        struct ra *rap;
        const struct ipv6_addr *addr;
  
        TAILQ_FOREACH(rap, ctx->ra_routers, next) {
-               if (rap->expired != expired)
+               if (rap->expired)
                        continue;
                TAILQ_FOREACH(addr, &rap->addrs, next) {
                        if (addr->prefix_vltime == 0)
                        rt = inet6_makeprefix(rap->iface, rap, addr);
                        if (rt) {
                                rt->rt_dflags |= RTDF_RA;
-                               TAILQ_INSERT_TAIL(routes, rt, rt_next);
-                       }
-               }
-               if (rap->lifetime) {
-                       rt = inet6_makerouter(rap);
-                       if (rt) {
-                               rt->rt_dflags |= RTDF_RA;
-                               TAILQ_INSERT_TAIL(routes, rt, rt_next);
-                               if (have_default)
-                                       *have_default = true;
+                               rt_proto_add(routes, rt);
                        }
                }
+               if (rap->lifetime == 0)
+                       continue;
+               if (ipv6_ifanyglobal(rap->iface) == NULL)
+                       continue;
+               rt = inet6_makerouter(rap);
+               if (rt == NULL)
+                       continue;
+               rt->rt_dflags |= RTDF_RA;
+               rt_proto_add(routes, rt);
        }
        return 0;
  }
  
+ #ifdef DHCP6
  static int
- inet6_dhcproutes(struct rt_head *routes, struct dhcpcd_ctx *ctx,
+ inet6_dhcproutes(rb_tree_t *routes, struct dhcpcd_ctx *ctx,
      enum DH6S dstate)
  {
        struct interface *ifp;
                if (d6_state && d6_state->state == dstate) {
                        TAILQ_FOREACH(addr, &d6_state->addrs, next) {
                                rt = inet6_makeprefix(ifp, NULL, addr);
-                               if (rt) {
-                                       rt->rt_dflags |= RTDF_DHCP;
-                                       TAILQ_INSERT_TAIL(routes, rt, rt_next);
-                               }
+                               if (rt == NULL)
+                                       continue;
+                               rt->rt_dflags |= RTDF_DHCP;
+                               rt_proto_add(routes, rt);
                        }
                }
        }
        return 0;
  }
+ #endif
  
  bool
- inet6_getroutes(struct dhcpcd_ctx *ctx, struct rt_head *routes)
+ inet6_getroutes(struct dhcpcd_ctx *ctx, rb_tree_t *routes)
  {
-       bool have_default;
  
        /* Should static take priority? */
        if (inet6_staticroutes(routes, ctx) == -1)
                return false;
  
        /* First add reachable routers and their prefixes */
-       have_default = false;
-       if (inet6_raroutes(routes, ctx, 0, &have_default) == -1)
+       if (inet6_raroutes(routes, ctx) == -1)
                return false;
  
+ #ifdef DHCP6
        /* We have no way of knowing if prefixes added by DHCP are reachable
         * or not, so we have to assume they are.
-        * Add bound before delegated so we can prefer interfaces better */
+        * Add bound before delegated so we can prefer interfaces better. */
        if (inet6_dhcproutes(routes, ctx, DH6S_BOUND) == -1)
                return false;
        if (inet6_dhcproutes(routes, ctx, DH6S_DELEGATED) == -1)
                return false;
- #ifdef HAVE_ROUTE_METRIC
-       /* If we have an unreachable router, we really do need to remove the
-        * route to it beause it could be a lower metric than a reachable
-        * router. Of course, we should at least have some routers if all
-        * are unreachable. */
-       if (!have_default) {
- #endif
-       /* Add our non-reachable routers and prefixes
-        * Unsure if this is needed, but it's a close match to kernel
-        * behaviour */
-               if (inet6_raroutes(routes, ctx, 1, NULL) == -1)
-                       return false;
- #ifdef HAVE_ROUTE_METRIC
-       }
  #endif
  
        return true;