#define ELOOP_QUEUE 1
#include "common.h"
#include "dhcpcd.h"
+#include "dhcp6.h"
#include "eloop.h"
#include "ipv6.h"
#include "ipv6ns.h"
#include "script.h"
-#define MIN_RANDOM_FACTOR (500 * 1000) /* milliseconds in usecs */
-#define MAX_RANDOM_FACTOR (1500 * 1000) /* milliseconds in usecs */
+#define MIN_RANDOM_FACTOR 500 /* milliseconds */
+#define MAX_RANDOM_FACTOR 1500 /* milliseconds */
+#define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */
+#define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */
+
+#if BYTE_ORDER == BIG_ENDIAN
+#define IPV6_ADDR_INT32_ONE 1
+#define IPV6_ADDR_INT16_MLL 0xff02
+#elif BYTE_ORDER == LITTLE_ENDIAN
+#define IPV6_ADDR_INT32_ONE 0x01000000
+#define IPV6_ADDR_INT16_MLL 0x02ff
+#endif
/* Debugging Neighbor Solicitations is a lot of spam, so disable it */
//#define DEBUG_NS
+//
+
+/* Currently, no known kernel allows us to send from the unspecified address
+ * which is required for DAD to work. This isn't that much of a problem as
+ * the kernel will do DAD for us correctly, however we don't know the exact
+ * randomness the kernel applies to the timeouts. So we just follow the same
+ * logic and have a little faith.
+ * This define is purely for completeness */
+// #define IPV6_SEND_DAD
static int sock = -1;
+#ifdef IPV6_SEND_DAD
+static int unspec_sock = -1;
+#endif
static struct sockaddr_in6 from;
static struct msghdr sndhdr;
static struct iovec sndiov[2];
int on;
int len;
struct icmp6_filter filt;
+#ifdef IPV6_SEND_DAD
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in6 sin;
+ } su;
+#endif
+
+ if (sock != -1)
+ return sock;
sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
if (sock == -1)
goto eexit;
ICMP6_FILTER_SETBLOCKALL(&filt);
+
+#ifdef IPV6_SEND_DAD
+ /* We send DAD requests from the unspecified address. */
+ unspec_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ if (unspec_sock == -1)
+ goto eexit;
+ if (setsockopt(unspec_sock, IPPROTO_ICMPV6, ICMP6_FILTER,
+ &filt, sizeof(filt)) == -1)
+ goto eexit;
+ memset(&su, 0, sizeof(su));
+ su.sin.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ su.sin.sin6_len = sizeof(su.sin);
+#endif
+ if (bind(unspec_sock, &su.sa, sizeof(su.sin)) == -1)
+ goto eexit;
+#endif
+
ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filt);
if (setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER,
&filt, sizeof(filt)) == -1)
rcvhdr.msg_controllen = len;
rcviov[0].iov_base = ansbuf;
rcviov[0].iov_len = sizeof(ansbuf);
+
+ eloop_event_add(sock, ipv6ns_handledata, NULL);
return sock;
eexit:
+ syslog(LOG_ERR, "%s: %m", __func__);
close(sock);
sock = -1;
free(sndbuf);
return -1;
}
-static int
-ipv6ns_makeprobe(struct ra *rap)
-{
- struct nd_neighbor_solicit *ns;
- struct nd_opt_hdr *nd;
-
- free(rap->ns);
- rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2);
- rap->ns = calloc(1, rap->nslen);
- if (rap->ns == NULL)
- return -1;
- ns = (struct nd_neighbor_solicit *)(void *)rap->ns;
- ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
- ns->nd_ns_cksum = 0;
- ns->nd_ns_code = 0;
- ns->nd_ns_reserved = 0;
- ns->nd_ns_target = rap->from;
- nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns));
- nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
- nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3;
- memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen);
- return 0;
-}
-
static void
ipv6ns_unreachable(void *arg)
{
}
void
-ipv6ns_sendprobe(void *arg)
+ipv6ns_cancelprobeaddr(struct ipv6_addr *ap)
{
- struct ra *rap = arg;
+
+ eloop_timeout_delete(ipv6ns_probeaddr, ap);
+ if (ap->dadcallback)
+ eloop_timeout_delete(ap->dadcallback, ap);
+}
+
+void
+ipv6ns_probeaddr(void *arg)
+{
+ struct ipv6_addr *ap = arg;
+#ifdef IPV6_SEND_DAD
+ struct nd_neighbor_solicit *ns;
+ struct nd_opt_hdr *nd;
struct sockaddr_in6 dst;
struct cmsghdr *cm;
struct in6_pktinfo pi;
int hoplimit = HOPLIMIT;
+#else
+ struct timeval mtv;
+ int i;
+#endif
struct timeval tv, rtv;
- if (sock == -1) {
- if (ipv6ns_open() == -1) {
- syslog(LOG_ERR, "%s: ipv6ns_open: %m", __func__);
+ if (ap->dadcallback &&
+ (ap->new == 0 || ap->nsprobes >= ap->iface->options->dadtransmits))
+ {
+#ifdef IPV6_SEND_DAD
+ ap->dadcallback(ap);
+#else
+ ipv6_addaddr(ap);
+#endif
+ return;
+ }
+
+ if (ipv6ns_open() == -1)
+ return;
+
+ ap->dadcompleted = 0;
+
+#ifdef IPV6_SEND_DAD
+ if (!ap->ns) {
+ ap->nslen = sizeof(*ns) + ROUNDUP8(ap->iface->hwlen + 2);
+ ap->ns = calloc(1, ap->nslen);
+ if (ap->ns == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
return;
}
- eloop_event_add(sock, ipv6ns_handledata, NULL);
+ ns = (struct nd_neighbor_solicit *)(void *)ap->ns;
+ ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
+ //ns->nd_ns_cksum = 0;
+ //ns->nd_ns_code = 0;
+ //ns->nd_ns_reserved = 0;
+ ns->nd_ns_target = ap->addr;
+ nd = (struct nd_opt_hdr *)(ap->ns + sizeof(*ns));
+ nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ nd->nd_opt_len = (ROUNDUP8(ap->iface->hwlen + 2)) >> 3;
+ memcpy(nd + 1, ap->iface->hwaddr, ap->iface->hwlen);
}
- if (!rap->ns && ipv6ns_makeprobe(rap) == -1) {
- syslog(LOG_ERR, "%s: ipv6ns_makeprobe: %m", __func__);
+ memset(&dst, 0, sizeof(dst));
+ dst.sin6_family = AF_INET6;
+#ifdef SIN6_LEN
+ dst.sin6_len = sizeof(dst);
+#endif
+ dst.sin6_addr.s6_addr16[0] = IPV6_ADDR_INT16_MLL;
+ dst.sin6_addr.s6_addr16[1] = 0;
+ dst.sin6_addr.s6_addr32[1] = 0;
+ dst.sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_ONE;
+ dst.sin6_addr.s6_addr32[3] = ap->addr.s6_addr32[3];
+ dst.sin6_addr.s6_addr[12] = 0xff;
+
+ //memcpy(&dst.sin6_addr, &ap->addr, sizeof(dst.sin6_addr));
+ //dst.sin6_scope_id = ap->iface->index;
+
+ sndhdr.msg_name = (caddr_t)&dst;
+ sndhdr.msg_iov[0].iov_base = ap->ns;
+ sndhdr.msg_iov[0].iov_len = ap->nslen;
+
+ /* Set the outbound interface */
+ cm = CMSG_FIRSTHDR(&sndhdr);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_PKTINFO;
+ cm->cmsg_len = CMSG_LEN(sizeof(pi));
+ memset(&pi, 0, sizeof(pi));
+ pi.ipi6_ifindex = ap->iface->index;
+ memcpy(CMSG_DATA(cm), &pi, sizeof(pi));
+
+ /* Hop limit */
+ cm = CMSG_NXTHDR(&sndhdr, cm);
+ cm->cmsg_level = IPPROTO_IPV6;
+ cm->cmsg_type = IPV6_HOPLIMIT;
+ cm->cmsg_len = CMSG_LEN(sizeof(hoplimit));
+ memcpy(CMSG_DATA(cm), &hoplimit, sizeof(hoplimit));
+
+#ifdef DEBUG_NS
+ syslog(LOG_INFO, "%s: sending IPv6 NS for %s",
+ ap->iface->name, ap->saddr);
+ if (ap->dadcallback == NULL)
+ syslog(LOG_WARNING, "%s: no callback!", ap->iface->name);
+#endif
+ if (sendmsg(unspec_sock, &sndhdr, 0) == -1)
+ syslog(LOG_ERR, "%s: %s: sendmsg: %m",
+ __func__, ap->iface->name);
+
+ if (ap->dadcallback) {
+ ms_to_tv(&tv, RETRANS_TIMER);
+ ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
+ timeradd(&tv, &rtv, &tv);
+ rtv.tv_sec = 0;
+ rtv.tv_usec = arc4random() %
+ (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U);
+ timeradd(&tv, &rtv, &tv);
+
+ eloop_timeout_add_tv(&tv,
+ ++(ap->nsprobes) < ap->iface->options->dadtransmits ?
+ ipv6ns_probeaddr : ap->dadcallback,
+ ap);
+ }
+#else /* IPV6_SEND_DAD */
+ /* Let the kernel handle DAD.
+ * We don't know the timings, so just wait for the max */
+ ipv6_addaddr(ap);
+ if (ap->dadcallback) {
+ mtv.tv_sec = 0;
+ mtv.tv_usec = 0;
+ for (i = 0; i < ap->iface->options->dadtransmits; i++) {
+ ms_to_tv(&tv, RETRANS_TIMER);
+ ms_to_tv(&rtv, MAX_RANDOM_FACTOR);
+ timeradd(&tv, &rtv, &tv);
+ timeradd(&mtv, &tv, &mtv);
+ }
+ eloop_timeout_add_tv(&mtv, ap->dadcallback, ap);
+ }
+#endif /* IPV6_SEND_DAD */
+}
+
+ssize_t
+ipv6ns_probeaddrs(struct ipv6_addrhead *addrs)
+{
+ struct ipv6_addr *ap;
+ ssize_t i;
+
+ i = 0;
+ TAILQ_FOREACH(ap, addrs, next) {
+ if (ap->prefix_vltime == 0 ||
+ IN6_IS_ADDR_UNSPECIFIED(&ap->addr))
+ continue;
+ ipv6ns_probeaddr(ap);
+ i++;
+ }
+
+ return i;
+}
+
+void
+ipv6ns_proberouter(void *arg)
+{
+ struct ra *rap = arg;
+ struct nd_neighbor_solicit *ns;
+ struct nd_opt_hdr *nd;
+ struct sockaddr_in6 dst;
+ struct cmsghdr *cm;
+ struct in6_pktinfo pi;
+ int hoplimit = HOPLIMIT;
+ struct timeval tv, rtv;
+
+ if (ipv6ns_open() == -1)
return;
+
+ if (!rap->ns) {
+ rap->nslen = sizeof(*ns) + ROUNDUP8(rap->iface->hwlen + 2);
+ rap->ns = calloc(1, rap->nslen);
+ if (rap->ns == NULL) {
+ syslog(LOG_ERR, "%s: %m", __func__);
+ return;
+ }
+ ns = (struct nd_neighbor_solicit *)(void *)rap->ns;
+ ns->nd_ns_type = ND_NEIGHBOR_SOLICIT;
+ //ns->nd_ns_cksum = 0;
+ //ns->nd_ns_code = 0;
+ //ns->nd_ns_reserved = 0;
+ ns->nd_ns_target = rap->from;
+ nd = (struct nd_opt_hdr *)(rap->ns + sizeof(*ns));
+ nd->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
+ nd->nd_opt_len = (ROUNDUP8(rap->iface->hwlen + 2)) >> 3;
+ memcpy(nd + 1, rap->iface->hwaddr, rap->iface->hwlen);
}
memset(&dst, 0, sizeof(dst));
ms_to_tv(&rtv, MIN_RANDOM_FACTOR);
timeradd(&tv, &rtv, &tv);
rtv.tv_sec = 0;
- rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR - MIN_RANDOM_FACTOR);
+ rtv.tv_usec = arc4random() % (MAX_RANDOM_FACTOR_U - MIN_RANDOM_FACTOR_U);
timeradd(&tv, &rtv, &tv);
- eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap);
+ eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap);
if (rap->nsprobes++ == 0)
eloop_timeout_add_sec(DELAY_FIRST_PROBE_TIME,
struct nd_neighbor_advert *nd_na;
struct ra *rap;
int is_router, is_solicited;
+#ifdef DEBUG_NS
+ int found;
+#endif
struct timeval tv;
+ struct dhcp6_state *d6state;
+ struct ipv6_addr *ap;
len = recvmsg(sock, &rcvhdr, 0);
if (len == -1) {
return;
}
+#ifdef DEBUG_NS
+ found = 0;
+#endif
TAILQ_FOREACH(rap, &ipv6_routers, next) {
- if (rap->iface == ifp &&
- memcmp(rap->from.s6_addr, from.sin6_addr.s6_addr,
+ if (rap->iface != ifp)
+ continue;
+ if (memcmp(rap->from.s6_addr, nd_na->nd_na_target.s6_addr,
sizeof(rap->from.s6_addr)) == 0)
break;
+ TAILQ_FOREACH(ap, &rap->addrs, next) {
+ if (memcmp(ap->addr.s6_addr,
+ nd_na->nd_na_target.s6_addr,
+ sizeof(ap->addr.s6_addr)) == 0)
+ {
+ ap->dad = 1;
+ if (ap->dadcallback)
+ ap->dadcallback(ap);
+#ifdef DEBUG_NS
+ found++;
+#endif
+ }
+ }
}
if (rap == NULL) {
+ d6state = D6_STATE(ifp);
+ if (d6state) {
+ TAILQ_FOREACH(ap, &d6state->addrs, next) {
+ if (memcmp(ap->addr.s6_addr,
+ nd_na->nd_na_target.s6_addr,
+ sizeof(ap->addr.s6_addr)) == 0)
+ {
+ ap->dad = 1;
+ if (ap->dadcallback)
+ ap->dadcallback(ap);
#ifdef DEBUG_NS
- syslog(LOG_DEBUG, "%s: unexpected NA from %s",
- ifp->name, sfrom);
+ found++;
+#endif
+ }
+ }
+ }
+
+#ifdef DEBUG_NS
+ if (found == 0)
+ syslog(LOG_DEBUG, "%s: unexpected NA from %s",
+ ifp->name, sfrom);
#endif
return;
}
tv.tv_sec = REACHABLE_TIME;
tv.tv_usec = 0;
}
- eloop_timeout_add_tv(&tv, ipv6ns_sendprobe, rap);
+ eloop_timeout_add_tv(&tv, ipv6ns_proberouter, rap);
eloop_timeout_delete(ipv6ns_unreachable, rap);
}
}
-
-int
-ipv6ns_init(void)
-{
- int fd;
-
- fd = ipv6ns_open();
- if (fd == -1) {
- syslog(LOG_ERR, "ipv6ns_open: %m");
- return -1;
- }
- eloop_event_add(fd, ipv6ns_handledata, NULL);
- return 0;
-}
/* Only drop the address if no other RAs have assigned it.
* This is safe because the RA is removed from the list
* before we are called. */
- if (drop && (options & DHCPCD_IPV6RA_OWN) &&
+ if (drop && ap->added &&
!IN6_IS_ADDR_UNSPECIFIED(&ap->addr) &&
!ipv6rs_addrexists(ap) && !dhcp6_addrexists(ap))
{
TAILQ_INSERT_HEAD(&ipv6_routers, router, next);
}
+static void
+ipv6rs_dadcallback(void *arg)
+{
+ struct ipv6_addr *ap = arg;
+
+ ipv6ns_cancelprobeaddr(ap);
+ ap->dadcompleted = 1;
+ if (ap->dad)
+ /* No idea what how to try and make another address :( */
+ syslog(LOG_WARNING, "%s: DAD detected %s",
+ ap->iface->name, ap->saddr);
+#ifdef IPV6_SEND_DAD
+ else
+ ipv6_addaddr(ap);
+#endif
+}
+
/* ARGSUSED */
static void
ipv6rs_handledata(__unused void *arg)
!(pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK))
break;
- ap = malloc(sizeof(*ap));
+ ap = calloc(1, sizeof(*ap));
if (ap == NULL) {
syslog(LOG_ERR, "%s: %m", __func__);
break;
}
+ ap->iface = rap->iface;
ap->new = 1;
ap->onlink = 0;
+ ap->autoconf = 1;
ap->prefix_len = pi->nd_opt_pi_prefix_len;
memcpy(ap->prefix.s6_addr,
pi->nd_opt_pi_prefix.s6_addr,
memset(&ap->addr, 0, sizeof(ap->addr));
ap->saddr[0] = '\0';
}
+ ap->dadcallback = ipv6rs_dadcallback;
TAILQ_INSERT_TAIL(&rap->addrs, ap, next);
} else if (ap->prefix_vltime !=
ntohl(pi->nd_opt_pi_valid_time) ||
ap->prefix_pltime !=
- ntohl(pi->nd_opt_pi_preferred_time))
+ ntohl(pi->nd_opt_pi_preferred_time) ||
+ ap->dad)
+ {
ap->new = 1;
- else
- ap->new = 0;
+ }
if (pi->nd_opt_pi_flags_reserved &
ND_OPT_PI_FLAG_ONLINK)
ap->onlink = 1;
ntohl(pi->nd_opt_pi_valid_time);
ap->prefix_pltime =
ntohl(pi->nd_opt_pi_preferred_time);
+ ap->nsprobes = 0;
+ ap->dad = 0;
if (opt) {
l = strlen(opt);
tmp = realloc(opt,
if (new_rap)
add_router(rap);
- if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST))
- ipv6_addaddrs(ifp, &rap->addrs);
if (options & DHCPCD_TEST) {
script_runreason(ifp, "TEST");
goto handle_flag;
}
+ if (options & DHCPCD_IPV6RA_OWN)
+ ipv6ns_probeaddrs(&rap->addrs);
ipv6_buildroutes();
/* We will get run by the expire function */
if (rap->lifetime)
ifp->options->options & DHCPCD_IPV6RA_OWN_DEFAULT)
{
rap->nsprobes = 0;
- ipv6ns_sendprobe(rap);
+ ipv6ns_proberouter(rap);
}
handle_flag:
syslog(LOG_INFO,
"%s: %s: expired address",
ifp->name, ap->saddr);
+ eloop_timeout_delete(NULL, ap);
TAILQ_REMOVE(&rap->addrs, ap, next);
free(ap);
/* No need to delete it as the kernel
script_runreason(ifp, "ROUTERADVERT");
}
}
-
-int
-ipv6rs_init(void)
-{
- int fd;
-
- fd = ipv6rs_open();
- if (fd == -1) {
- syslog(LOG_ERR, "ipv6rs: %m");
- options &= ~(DHCPCD_IPV6RS |
- DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT);
- return -1;
- }
-
- eloop_event_add(fd, ipv6rs_handledata, NULL);
- // atexit(restore_rtadv);
-
- if (options & DHCPCD_IPV6RA_OWN ||
- options & DHCPCD_IPV6RA_OWN_DEFAULT)
- return ipv6ns_init();
-
- return 0;
-}