From: Roy Marples Date: Wed, 15 May 2013 10:27:36 +0000 (+0000) Subject: Use the kernel DAD for IPv6 addresses and finish the action once each X-Git-Tag: v5.99.6~1 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=a8df1b28fbc5dd08895418476d58fe11b8fc4def;p=thirdparty%2Fdhcpcd.git Use the kernel DAD for IPv6 addresses and finish the action once each address DAD completes. BSD kernels will require a patch as noted within the README. The linux netlink part still needs to be written. --- diff --git a/README b/README index c85432a2..17eb5108 100644 --- a/README +++ b/README @@ -38,6 +38,16 @@ routes. You can find discussion here: BSD systems where this has been fixed are: NetBSD-5.0 +Some BSD systems announce IPv6 addresses to userland when the address has +been added, not when it's actually ready to use. This is important because +no kernel allows to send from the unspecified address which means userland +cannot be RFC conformation when creating DAD messages so we need to rely on +the kernel implementation. +You can find the discussion here: + http://mail-index.netbsd.org/tech-net/2013/03/15/msg004019.html +BSD systems where this will be fixed are: + NetBSD-7.0 + We try and detect how dhcpcd should interact with system services during the configure stage. If we cannot auto-detect how do to this, or it is wrong then you can change this by passing shell commands to --service-exists, @@ -67,11 +77,6 @@ Compatibility dhcpcd-5.0 is only fully command line compatible with dhcpcd-4.0 For compatibility with older versions, use dhcpcd-4.0 -dhcpcd no longer sends a default ClientID for ethernet interfaces. -This is so we can re-use the address the kernel DHCP client found. -To retain the old behaviour of sending a default ClientID based on the -hardware address for interface, simply add the keyword clientid to dhcpcd.conf. - ChangeLog --------- diff --git a/defs.h b/defs.h index 47f20533..e606056c 100644 --- a/defs.h +++ b/defs.h @@ -28,7 +28,7 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "5.99.5" +#define VERSION "5.99.6" #ifndef CONFIG # define CONFIG SYSCONFDIR "/" PACKAGE ".conf" diff --git a/dhcp6.c b/dhcp6.c index dc1d4770..6d8d3bef 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -1559,6 +1559,7 @@ dhcp6_find_delegates(struct interface *ifp) if (k) { syslog(LOG_INFO, "%s: adding delegated prefixes", ifp->name); state = D6_STATE(ifp); + state->state = DH6S_DELEGATED; ipv6ns_probeaddrs(&state->addrs); ipv6_buildroutes(); } @@ -1577,7 +1578,6 @@ dhcp6_handledata(__unused void *arg) struct dhcp6_message *r; struct dhcp6_state *state; const struct dhcp6_option *o; - const char *reason; const struct dhcp_opt *opt; const struct if_options *ifo; const struct ipv6_addr *ap; @@ -1638,7 +1638,8 @@ dhcp6_handledata(__unused void *arg) r->xid[2] != state->send->xid[2]) { syslog(LOG_ERR, - "%s: wrong xid 0x%02x%02x%02x (expecting 0x%02x%02x%02x) from %s", + "%s: wrong xid 0x%02x%02x%02x" + " (expecting 0x%02x%02x%02x) from %s", ifp->name, r->xid[0], r->xid[1], r->xid[2], state->send->xid[0], state->send->xid[1], @@ -1746,7 +1747,7 @@ replyok: recv: syslog(LOG_INFO, "%s: %s received from %s", ifp->name, op, sfrom); - reason = NULL; + state->reason = NULL; eloop_timeout_delete(NULL, ifp); switch(state->state) { case DH6S_INFORM: @@ -1754,22 +1755,22 @@ recv: state->rebind = 0; state->expire = ~0U; state->lowpl = ~0U; - reason = "INFORM6"; + state->reason = "INFORM6"; break; case DH6S_REQUEST: - if (reason == NULL) - reason = "BOUND6"; + if (state->reason == NULL) + state->reason = "BOUND6"; /* FALLTHROUGH */ case DH6S_RENEW: - if (reason == NULL) - reason = "RENEW6"; + if (state->reason == NULL) + state->reason = "RENEW6"; /* FALLTHROUGH */ case DH6S_REBIND: - if (reason == NULL) - reason = "REBIND6"; + if (state->reason == NULL) + state->reason = "REBIND6"; case DH6S_CONFIRM: - if (reason == NULL) - reason = "REBOOT6"; + if (state->reason == NULL) + state->reason = "REBOOT6"; if (state->renew == 0) { if (state->expire == ~0U) state->renew = ~0U; @@ -1784,7 +1785,7 @@ recv: } break; default: - reason = "UNKNOWN6"; + state->reason = "UNKNOWN6"; break; } @@ -1798,7 +1799,11 @@ recv: state->recv_len = 0; } - if (!(options & DHCPCD_TEST)) { + if (options & DHCPCD_TEST) + script_runreason(ifp, "TEST"); + else { + if (state->state == DH6S_INFORM) + script_runreason(ifp, state->reason); state->state = DH6S_BOUND; if (state->renew) eloop_timeout_add_sec(state->renew, @@ -1818,9 +1823,23 @@ recv: ifp->name, state->renew, state->rebind); ipv6_buildroutes(); dhcp6_writelease(ifp); + + len = 1; + /* If all addresses have completed DAD run the script */ + TAILQ_FOREACH(ap, &state->addrs, next) { + if (ap->dadcompleted == 0) { + len = 0; + break; + } + } + if (len) { + script_runreason(ifp, state->reason); + daemonise(); + } else + syslog(LOG_DEBUG, "%s: waiting for RA DAD to complete", + ifp->name); } - script_runreason(ifp, options & DHCPCD_TEST ? "TEST" : reason); if (options & DHCPCD_TEST || (ifp->options->options & DHCPCD_INFORM && !(options & DHCPCD_MASTER))) @@ -1830,7 +1849,6 @@ recv: #endif exit(EXIT_SUCCESS); } - daemonise(); } static int @@ -1920,10 +1938,8 @@ dhcp6_start(struct interface *ifp, int manage) return -1; TAILQ_INIT(&state->addrs); - if (dhcp6_find_delegates(ifp)) { - state->state = DH6S_DELEGATED; + if (dhcp6_find_delegates(ifp)) return 0; - } syslog(LOG_INFO, "%s: %s", ifp->name, manage ? "soliciting DHCPv6 address" : @@ -1997,6 +2013,27 @@ dhcp6_free(struct interface *ifp) dhcp6_freedrop(ifp, 0, NULL); } +void +dhcp6_handleifa(int cmd, const char *ifname, const struct in6_addr *addr) +{ + struct interface *ifp; + struct dhcp6_state *state; + int found; + + TAILQ_FOREACH(ifp, ifaces, next) { + state = D6_STATE(ifp); + if (state == NULL || strcmp(ifp->name, ifname)) + continue; + found = ipv6_handleifa_addrs(cmd, &state->addrs, addr); + if (found && state->state == DH6S_BOUND) { + syslog(LOG_DEBUG, "%s: DHCPv6 DAD completed", + ifp->name); + script_runreason(ifp, state->reason); + daemonise(); + } + } +} + ssize_t dhcp6_env(char **env, const char *prefix, const struct interface *ifp, const struct dhcp6_message *m, ssize_t mlen) diff --git a/dhcp6.h b/dhcp6.h index 282b8375..c7df8c25 100644 --- a/dhcp6.h +++ b/dhcp6.h @@ -1,4 +1,4 @@ -/* +/* * dhcpcd - DHCP client daemon * Copyright (c) 2006-2013 Roy Marples * All rights reserved @@ -180,6 +180,7 @@ struct dhcp6_state { struct ipv6_addrhead addrs; uint32_t lowpl; char leasefile[PATH_MAX]; + const char *reason; }; #define D6_STATE(ifp) \ @@ -212,6 +213,7 @@ int dhcp6_start(struct interface *, int); ssize_t dhcp6_env(char **, const char *, const struct interface *, const struct dhcp6_message *, ssize_t); void dhcp6_free(struct interface *); +void dhcp6_handleifa(int, const char *, const struct in6_addr *addr); void dhcp6_drop(struct interface *, const char *); #else #define dhcp6_printoptions() diff --git a/dhcpcd.c b/dhcpcd.c index 2aac91da..5fd2b432 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -457,7 +457,10 @@ init_state(struct interface *ifp, int argc, char **argv) configure_interface(ifp, argc, argv); ifo = ifp->options; - if (ifo->options & DHCPCD_LINK && linkfd == -1) { + /* RTM_NEWADDR goes through the link socket as well which we + * need for IPv6 DAD, so we check for DHCPCD_LINK in handle_carrier + * instead */ + if (linkfd == -1) { linkfd = open_link_socket(); if (linkfd == -1) { syslog(LOG_ERR, "open_link_socket: %m"); diff --git a/if-bsd.c b/if-bsd.c index 78425d0f..b647e077 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -71,11 +71,14 @@ #define RT_ADVANCE(x, n) (x += RT_ROUNDUP((n)->sa_len)) #endif -/* FIXME: Why do we need to check for sa_family 255 */ #define COPYOUT(sin, sa) \ sin.s_addr = ((sa) != NULL) ? \ (((struct sockaddr_in *)(void *)sa)->sin_addr).s_addr : 0 +#define COPYOUT6(sin, sa) \ + sin.s6_addr = ((sa) != NULL) ? \ + (((struct sockaddr_in6 *)(void *)sa)->sin6_addr).s6_addr : 0 + static int r_fd = -1; static char *link_buf; static ssize_t link_buflen; @@ -478,6 +481,10 @@ manage_link(int fd) struct sockaddr_dl sdl; unsigned char *hwaddr; #endif +#ifdef INET6 + struct in6_addr ia6; + struct sockaddr_in6 *sin6; +#endif for (;;) { if (ioctl(fd, FIONREAD, &len) == -1) @@ -598,6 +605,17 @@ manage_link(int fd) ipv4_handleifa(rtm->rtm_type, ifname, &rt.dest, &rt.net, &rt.gate); break; +#endif +#ifdef INET6 + case AF_INET6: + sin6 = (struct sockaddr_in6*) + rti_info[RTAX_IFA]; + memcpy(ia6.s6_addr, + sin6->sin6_addr.s6_addr, + sizeof(ia6.s6_addr)); + ipv6_handleifa(rtm->rtm_type, ifname, + &ia6); + break; #endif } break; diff --git a/ipv6.c b/ipv6.c index e8752815..5e201b19 100644 --- a/ipv6.c +++ b/ipv6.c @@ -28,8 +28,14 @@ #include #include +#include #include +#ifdef __linux__ +# include /* for systems with broken headers */ +# include +#endif + #include #include #include @@ -285,6 +291,49 @@ ipv6_addaddrs(struct ipv6_addrhead *addrs) return i; } +void +ipv6_handleifa(int cmd, const char *ifname, const struct in6_addr *addr) +{ + + ipv6rs_handleifa(cmd, ifname, addr); + dhcp6_handleifa(cmd, ifname, addr); +} + +int +ipv6_handleifa_addrs(int cmd, + struct ipv6_addrhead *addrs, const struct in6_addr *addr) +{ + struct ipv6_addr *ap, *apn; + uint8_t found, alldadcompleted; + + alldadcompleted = 1; + found = 0; + TAILQ_FOREACH_SAFE(ap, addrs, next, apn) { + if (memcmp(addr->s6_addr, ap->addr.s6_addr, + sizeof(addr->s6_addr))) + { + if (ap->dadcompleted == 0) + alldadcompleted = 0; + continue; + } + switch (cmd) { + case RTM_DELADDR: + syslog(LOG_INFO, "%s: deleted address %s", + ap->iface->name, ap->saddr); + TAILQ_REMOVE(addrs, ap, next); + free(ap); + break; + case RTM_NEWADDR: + if (!ap->dadcompleted) { + found++; + ap->dadcompleted = 1; + } + } + } + + return alldadcompleted ? found : 0; +} + static struct rt6 * find_route6(struct rt6head *rts, const struct rt6 *r) { diff --git a/ipv6.h b/ipv6.h index ad4b91a8..6b037e38 100644 --- a/ipv6.h +++ b/ipv6.h @@ -84,6 +84,8 @@ int ipv6_mask(struct in6_addr *, int); int ipv6_prefixlen(const struct in6_addr *); int ipv6_addaddr(struct ipv6_addr *); ssize_t ipv6_addaddrs(struct ipv6_addrhead *); +void ipv6_handleifa(int, const char *, const struct in6_addr *); +int ipv6_handleifa_addrs(int, struct ipv6_addrhead *, const struct in6_addr *); int ipv6_removesubnet(const struct interface *, struct ipv6_addr *); void ipv6_buildroutes(void); void ipv6_drop(struct interface *); diff --git a/ipv6ns.c b/ipv6ns.c index 09a37e55..bb24162b 100644 --- a/ipv6ns.c +++ b/ipv6ns.c @@ -55,8 +55,8 @@ #include "ipv6ns.h" #include "script.h" -#define MIN_RANDOM_FACTOR 500 /* milliseconds */ -#define MAX_RANDOM_FACTOR 1500 /* milliseconds */ +#define MIN_RANDOM_FACTOR 500 /* millisecs */ +#define MAX_RANDOM_FACTOR 1500 /* millisecs */ #define MIN_RANDOM_FACTOR_U MIN_RANDOM_FACTOR * 1000 /* usecs */ #define MAX_RANDOM_FACTOR_U MAX_RANDOM_FACTOR * 1000 /* usecs */ diff --git a/ipv6rs.c b/ipv6rs.c index 79c646e5..705364d9 100644 --- a/ipv6rs.c +++ b/ipv6rs.c @@ -410,6 +410,50 @@ ipv6rs_dadcallback(void *arg) #endif } +static void +ipv6rs_scriptrun(const struct ra *rap) +{ + int alldadcomplete, hasdns; + const struct ipv6_addr *ap; + const struct ra_opt *rao; + + /* If all addresses have completed DAD run the script */ + alldadcomplete = 1; + TAILQ_FOREACH(ap, &rap->addrs, next) { + if (ap->dadcompleted == 0) + return; + } + + /* If we don't require RDNSS then set hasdns = 1 so we fork */ + if (!(rap->iface->options->options & DHCPCD_IPV6RA_REQRDNSS)) + hasdns = 1; + else { + hasdns = 0; + TAILQ_FOREACH(rao, &rap->options, next) { + if (rao->type == ND_OPT_RDNSS && + rao->option && + timerisset(&rao->expire)) + { + hasdns = 1; + break; + } + } + } + + script_runreason(rap->iface, "ROUTERADVERT"); + if (hasdns) + daemonise(); +#if 0 + else if (options & DHCPCD_DAEMONISE && + !(options & DHCPCD_DAEMONISED) && new_data) + syslog(LOG_WARNING, + "%s: did not fork due to an absent" + " RDNSS option in the RA", + ifp->name); +} +#endif +} + /* ARGSUSED */ static void ipv6rs_handledata(__unused void *arg) @@ -437,7 +481,7 @@ ipv6rs_handledata(__unused void *arg) struct ipv6_addr *ap; char *opt, *tmp; struct timeval expire; - uint8_t has_dns, new_rap, new_data; + uint8_t new_rap, new_data; len = recvmsg(sock, &rcvhdr, 0); if (len == -1) { @@ -578,7 +622,6 @@ ipv6rs_handledata(__unused void *arg) p = ((uint8_t *)icp) + sizeof(struct nd_router_advert); olen = 0; lifetime = ~0U; - has_dns = 0; for (olen = 0; len > 0; p += olen, len -= olen) { if ((size_t)len < sizeof(struct nd_opt_hdr)) { syslog(LOG_ERR, "%s: Short option", ifp->name); @@ -746,8 +789,6 @@ ipv6rs_handledata(__unused void *arg) l -= (m + 1); tmp += m; *tmp++ = ' '; - if (lifetime > 0) - has_dns = 1; } } if (tmp != opt) @@ -836,23 +877,13 @@ ipv6rs_handledata(__unused void *arg) if (options & DHCPCD_IPV6RA_OWN) ipv6ns_probeaddrs(&rap->addrs); ipv6_buildroutes(); + /* We will get run by the expire function */ if (rap->lifetime) - script_runreason(ifp, "ROUTERADVERT"); - - /* If we don't require RDNSS then set has_dns = 1 so we fork */ - if (!(ifp->options->options & DHCPCD_IPV6RA_REQRDNSS)) - has_dns = 1; + ipv6rs_scriptrun(rap); eloop_timeout_delete(NULL, ifp); eloop_timeout_delete(NULL, rap); /* reachable timer */ - if (has_dns) - daemonise(); - else if (options & DHCPCD_DAEMONISE && - !(options & DHCPCD_DAEMONISED) && new_data) - syslog(LOG_WARNING, - "%s: did not fork due to an absent RDNSS option in the RA", - ifp->name); /* If we're owning the RA then we need to try and ensure the * router is actually reachable */ @@ -1050,6 +1081,25 @@ ipv6rs_findsameaddr(const struct ipv6_addr *ap) return NULL; } +void +ipv6rs_handleifa(int cmd, const char *ifname, const struct in6_addr *addr) +{ + struct ra *rap; + int found; + + TAILQ_FOREACH(rap, &ipv6_routers, next) { + if (strcmp(rap->iface->name, ifname)) + continue; + found = ipv6_handleifa_addrs(cmd, &rap->addrs, addr); + if (found && rap->lifetime) { + syslog(LOG_DEBUG, + "%s: IPv6 Router Advertisement DAD completed", + rap->iface->name); + ipv6rs_scriptrun(rap); + } + } +} + void ipv6rs_expire(void *arg) { diff --git a/ipv6rs.h b/ipv6rs.h index 2b53d0cf..bb08a60e 100644 --- a/ipv6rs.h +++ b/ipv6rs.h @@ -86,6 +86,7 @@ void ipv6rs_freedrop_ra(struct ra *, int); ssize_t ipv6rs_free(struct interface *); void ipv6rs_expire(void *arg); int ipv6rs_has_ra(const struct interface *); +void ipv6rs_handleifa(int, const char *, const struct in6_addr *); void ipv6rs_drop(struct interface *); #else #define ipv6rs_start(a) {}