From: Roy Marples Date: Tue, 6 Nov 2012 23:40:15 +0000 (+0000) Subject: Implement the core DHCPv6 client for SOLICIT, REQUEST, RENEW, CONFIRM. X-Git-Tag: v5.99.3~35 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e54dee19baa8add8723bdff6b52f7330e40c6c8b;p=thirdparty%2Fdhcpcd.git Implement the core DHCPv6 client for SOLICIT, REQUEST, RENEW, CONFIRM. --- diff --git a/bind.c b/bind.c index 30e639ac..3a00465a 100644 --- a/bind.c +++ b/bind.c @@ -64,6 +64,7 @@ daemonise(void) char buf = '\0'; int sidpipe[2], fd; + delete_timeout(handle_exit_timeout, NULL); if (options & DHCPCD_DAEMONISED || !(options & DHCPCD_DAEMONISE)) return 0; sigfillset(&full); @@ -130,7 +131,6 @@ bind_interface(void *arg) /* We're binding an address now - ensure that sockets are closed */ close_sockets(iface); state->reason = NULL; - delete_timeout(handle_exit_timeout, NULL); if (clock_monotonic) get_monotonic(&lease->boundtime); state->xid = 0; diff --git a/common.h b/common.h index e11cc511..f2a894ef 100644 --- a/common.h +++ b/common.h @@ -37,10 +37,15 @@ #define UNCONST(a) ((void *)(unsigned long)(const void *)(a)) #define timeval_to_double(tv) ((tv)->tv_sec * 1.0 + (tv)->tv_usec * 1.0e-6) +#define tv_to_ms(ms, tv) \ + do { \ + ms = (tv)->tv_sec * 1000; \ + ms += (tv)->tv_usec / 1000; \ + } while (0 /* CONSTCOND */); #define ms_to_tv(tv, ms) \ do { \ - (tv)->tv_sec = (ms / 1000); \ - (tv)->tv_usec = ((ms % 1000) * 1000); \ + (tv)->tv_sec = ms / 1000; \ + (tv)->tv_usec = (ms - (tv)->tv_sec * 1000) * 1000; \ } while (0 /* CONSTCOND */); #define timernorm(tvp) \ do { \ diff --git a/defs.h b/defs.h index dc92c651..db315377 100644 --- a/defs.h +++ b/defs.h @@ -28,7 +28,7 @@ #define CONFIG_H #define PACKAGE "dhcpcd" -#define VERSION "5.99.1" +#define VERSION "5.99.2" #ifndef CONFIG # define CONFIG SYSCONFDIR "/" PACKAGE ".conf" @@ -42,6 +42,9 @@ #ifndef LEASEFILE # define LEASEFILE DBDIR "/" PACKAGE "-%s.lease" #endif +#ifndef LEASEFILE6 +# define LEASEFILE6 DBDIR "/" PACKAGE "-%s.lease6" +#endif #ifndef PIDFILE # define PIDFILE RUNDIR "/" PACKAGE "%s%s.pid" #endif diff --git a/dhcp6.c b/dhcp6.c index 96e48143..bf2f7736 100644 --- a/dhcp6.c +++ b/dhcp6.c @@ -25,6 +25,7 @@ * SUCH DAMAGE. */ +#include #include #include @@ -43,6 +44,7 @@ #define ELOOP_QUEUE 2 +#include "bind.h" #include "config.h" #include "common.h" #include "configure.h" @@ -50,6 +52,7 @@ #include "dhcp6.h" #include "duid.h" #include "eloop.h" +#include "ipv6rs.h" #include "platform.h" #ifndef __UNCONST @@ -74,6 +77,7 @@ static unsigned char ansbuf[1500]; static unsigned char *duid; static uint16_t duid_len; static char ntopbuf[INET6_ADDRSTRLEN]; +static char *status; struct dhcp6_op { uint16_t type; @@ -82,8 +86,11 @@ struct dhcp6_op { static const struct dhcp6_op dhcp6_ops[] = { { DHCP6_SOLICIT, "SOLICIT6" }, + { DHCP6_ADVERTISE, "ADVERTISE6" }, { DHCP6_REQUEST, "REQUEST6" }, { DHCP6_REPLY, "REPLY6" }, + { DHCP6_RENEW, "RENEW6" }, + { DHCP6_CONFIRM, "CONFIRM6" }, { DHCP6_INFORMATION_REQ, "INFORM6" }, { 0, NULL } }; @@ -119,6 +126,7 @@ dhcp6_cleanup(void) free(sndbuf); free(rcvbuf); free(duid); + free(status); } #endif @@ -263,13 +271,12 @@ dhcp6_makevendor(struct dhcp6_option *o) #endif static const struct dhcp6_option * -dhcp6_getoption(int code, const struct dhcp6_message *m, ssize_t len) +dhcp6_findoption(int code, const uint8_t *d, ssize_t len) { const struct dhcp6_option *o; code = htons(code); - len -= sizeof(*m); - for (o = D6_CFIRST_OPTION(m); + for (o = (const struct dhcp6_option *)d; len > (ssize_t)sizeof(*o); o = D6_CNEXT_OPTION(o)) { @@ -288,6 +295,14 @@ dhcp6_getoption(int code, const struct dhcp6_message *m, ssize_t len) return NULL; } +static const struct dhcp6_option * +dhcp6_getoption(int code, const struct dhcp6_message *m, ssize_t len) +{ + + len -= sizeof(*m); + return dhcp6_findoption(code, + (const uint8_t *)D6_CFIRST_OPTION(m), len); +} static int dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, ssize_t len) @@ -312,16 +327,31 @@ dhcp6_updateelapsed(struct interface *ifp, struct dhcp6_message *m, ssize_t len) return 0; } +static void +dhcp6_newxid(struct dhcp6_message *m) +{ + uint32_t xid; + + xid = arc4random(); + m->xid[0] = (xid >> 16) & 0xff; + m->xid[1] = (xid >> 8) & 0xff; + m->xid[2] = xid & 0xff; +} + static int dhcp6_makemessage(struct interface *ifp) { struct dhcp6_state *state; - struct dhcp6_option *o; - int xid; - ssize_t len; + struct dhcp6_message *m; + struct dhcp6_option *o, *so; + const struct dhcp6_option *si; + ssize_t len, ml; uint16_t *u16; const struct if_options *ifo; const struct dhcp_opt *opt; + uint8_t IA_NA, *p; + uint32_t u32; + const struct ipv6_addr *ap; state = D6_STATE(ifp); if (state->send) { @@ -332,6 +362,7 @@ dhcp6_makemessage(struct interface *ifp) /* Work out option size first */ ifo = ifp->state->options; len = 0; + si = NULL; for (opt = dhcp6_opts; opt->option; opt++) { if (!(opt->type & REQUEST || has_option_mask(ifo->requestmask6, opt->option))) @@ -342,42 +373,135 @@ dhcp6_makemessage(struct interface *ifp) len = sizeof(*u16) * 2; len += sizeof(*o); - len += sizeof(state->send); + len += sizeof(*state->send); len += sizeof(*o) + 14; /* clientid */ len += sizeof(*o) + sizeof(uint16_t); /* elapsed */ #ifdef DHCPCD_IANA_PEN len += sizeof(*o) + dhcp6_makevendor(NULL); #endif + /* IA_NA */ + m = NULL; + ml = 0; + switch(state->state) { + case DH6S_REQUEST: + m = state->recv; + ml = state->recv_len; + /* FALLTHROUGH */ + case DH6S_RENEW: + if (m == NULL) { + m = state->new; + ml = state->new_len; + } + si = dhcp6_getoption(D6_OPTION_SERVERID, m, ml); + len += sizeof(*si) + ntohs(si->len); + /* FALLTHROUGH */ + case DH6S_REBOOT: + if (m == NULL) { + m = state->new; + ml = state->new_len; + } + TAILQ_FOREACH(ap, &state->addrs, next) { + len += sizeof(*o) + sizeof(ap->addr.s6_addr) + + sizeof(u32) + sizeof(u32); + } + /* FALLTHROUGH */ + case DH6S_INIT: /* FALLTHROUGH */ + case DH6S_DISCOVER: + len += sizeof(*o) + sizeof(u32) + sizeof(u32) + sizeof(u32); + IA_NA = 1; + break; + default: + IA_NA = 0; + } + + if (m == NULL) { + m = state->new; + ml = state->new_len; + } - state->send = calloc(1, len); + state->send = malloc(len); if (state->send == NULL) return -1; state->send_len = len; - if (state->state == DH6S_INFORM) - state->send->type = DHCP6_INFORMATION_REQ; - else + switch(state->state) { + break; + case DH6S_INIT: /* FALLTHROUGH */ + case DH6S_DISCOVER: state->send->type = DHCP6_SOLICIT; - xid = arc4random(); - state->send->xid[0] = (xid >> 16) & 0xff; - state->send->xid[1] = (xid >> 8) & 0xff; - state->send->xid[2] = xid & 0xff; + break; + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_REBIND: + state->send->type = DHCP6_REQUEST; + break; + case DH6S_RENEW: + state->send->type = DHCP6_RENEW; + break; + case DH6S_REBOOT: + state->send->type = DHCP6_CONFIRM; + break; + case DH6S_INFORM: + state->send->type = DHCP6_INFORMATION_REQ; + break; + default: + printf ("state %d\n", state->state); + errno = EINVAL; + free(state->send); + state->send = NULL; + return -1; + } + + dhcp6_newxid(state->send); o = D6_FIRST_OPTION(state->send); o->code = htons(D6_OPTION_CLIENTID); o->len = htons(duid_len); memcpy(D6_OPTION_DATA(o), duid, duid_len); + if (si) { + o = D6_NEXT_OPTION(o); + memcpy(o, si, sizeof(*si) + ntohs(si->len)); + } + o = D6_NEXT_OPTION(o); o->code = htons(D6_OPTION_ELAPSED); o->len = htons(sizeof(uint16_t)); - dhcp6_updateelapsed(ifp, state->send, state->send_len); + p = D6_OPTION_DATA(o); + memset(p, 0, sizeof(u16)); #ifdef DHCPCD_IANA_PEN o = D6_NEXT_OPTION(o); dhcp6_makevendor(o); #endif + if (IA_NA) { + o = D6_NEXT_OPTION(o); + o->code = htons(D6_OPTION_IA_NA); + o->len = htons(sizeof(u32) + sizeof(u32) + sizeof(u32)); + p = D6_OPTION_DATA(o); + memcpy(p, state->iaid, sizeof(u32)); + p += sizeof(u32); + memset(p, 0, sizeof(u32) + sizeof(u32)); + TAILQ_FOREACH(ap, &state->addrs, next) { + so = D6_NEXT_OPTION(o); + so->code = htons(D6_OPTION_IA_ADDR); + so->len = htons(sizeof(ap->addr.s6_addr) + + sizeof(u32) + sizeof(u32)); + p = D6_OPTION_DATA(so); + memcpy(p, &ap->addr.s6_addr, sizeof(ap->addr.s6_addr)); + p += sizeof(ap->addr.s6_addr); + u32 = htonl(ap->prefix_pltime); + memcpy(p, &u32, sizeof(u32)); + p += sizeof(u32); + u32 = htonl(ap->prefix_vltime); + memcpy(p, &u32, sizeof(u32)); + /* Avoid a shadowed declaration warning by + * moving our addition outside of the htons macro */ + u32 = ntohs(o->len) + sizeof(*so) + ntohs(so->len); + o->len = htons(u32); + } + } + o = D6_NEXT_OPTION(o); o->code = htons(D6_OPTION_ORO); o->len = 0; @@ -410,14 +534,45 @@ dhcp6_get_op(uint16_t type) return NULL; } + +static void +dhcp6_freedrop_addrs(struct interface *ifp, int drop) +{ + struct dhcp6_state *state; + struct ipv6_addr *ap; + + state = D6_STATE(ifp); + while ((ap = TAILQ_FIRST(&state->addrs))) { + TAILQ_REMOVE(&state->addrs, ap, next); + /* 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 && + !dhcp6_addrexists(ap) && + !ipv6rs_addrexists(ap)) + { + syslog(LOG_INFO, "%s: deleting address %s", + ifp->name, ap->saddr); + if (del_address6(ifp, ap) == -1) + syslog(LOG_ERR, "del_address6 %m"); + } + free(ap); + } + if (drop) + ipv6_buildroutes(); +} + static void dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) { struct dhcp6_state *state; - struct timeval tv; struct sockaddr_in6 to; struct cmsghdr *cm; struct in6_pktinfo pi; + struct timeval RTprev; + double rnd; + suseconds_t ms; + uint8_t neg; state = D6_STATE(ifp); if (!callback) @@ -428,24 +583,56 @@ dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) state->send->xid[1], state->send->xid[2]); else { - if (state->interval == 0) - state->interval = 4; - else { - state->interval *= 2; - if (state->interval > 64) - state->interval = 64; + if (state->RTC == 0) { + RTprev.tv_sec = state->IRT; + RTprev.tv_usec = 0; + state->RT.tv_sec = state->IRT; + state->RT.tv_usec = 0; + } else { + RTprev = state->RT; + timeradd(&state->RT, &state->RT, &state->RT); } - tv.tv_sec = state->interval + DHCP_RAND_MIN; - tv.tv_usec = arc4random() % (DHCP_RAND_MAX_U - DHCP_RAND_MIN_U); + + rnd = DHCP6_RAND_MIN; + rnd += arc4random() % (DHCP6_RAND_MAX - DHCP6_RAND_MIN); + rnd /= 1000; + neg = (rnd < 0.0); + if (neg) + rnd = -rnd; + tv_to_ms(ms, &RTprev); + ms *= rnd; + ms_to_tv(&RTprev, ms); + if (neg) + timersub(&state->RT, &RTprev, &state->RT); + else + timeradd(&state->RT, &RTprev, &state->RT); + + if (state->RT.tv_sec > state->MRT) { + RTprev.tv_sec = state->MRT; + RTprev.tv_usec = 0; + state->RT.tv_sec = state->MRT; + state->RT.tv_usec = 0; + tv_to_ms(ms, &RTprev); + ms *= rnd; + ms_to_tv(&RTprev, ms); + if (neg) + timersub(&state->RT, &RTprev, &state->RT); + else + timeradd(&state->RT, &RTprev, &state->RT); + } + syslog(LOG_DEBUG, "%s: sending %s (xid 0x%02x%02x%02x), next in %0.2f seconds", ifp->name, dhcp6_get_op(state->send->type), state->send->xid[0], state->send->xid[1], state->send->xid[2], - timeval_to_double(&tv)); + timeval_to_double(&state->RT)); } + /* Update the elapsed time */ + dhcp6_updateelapsed(ifp, state->send, state->send_len); + to = allrouters; sndhdr.msg_name = (caddr_t)&to; sndhdr.msg_iov[0].iov_base = (caddr_t)state->send; @@ -463,8 +650,16 @@ dhcp6_sendmessage(struct interface *ifp, void (*callback)(void *)) if (sendmsg(sock, &sndhdr, 0) == -1) syslog(LOG_ERR, "%s: sendmsg: %m", ifp->name); - if (callback) - add_timeout_tv(&tv, callback, ifp); + state->RTC++; + if (callback) { + if (state->MRC == 0 || state->RTC < state->MRC) + add_timeout_tv(&state->RT, callback, ifp); + else if (state->MRC != 0 && state->MRCcallback) + add_timeout_tv(&state->RT, state->MRCcallback, ifp); + else + syslog(LOG_WARNING, "%s: sent %d times with no reply", + ifp->name, state->RTC); + } } static void @@ -474,6 +669,469 @@ dhcp6_sendinform(void *arg) dhcp6_sendmessage(arg, dhcp6_sendinform); } +static void +dhcp6_senddiscover(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_senddiscover); +} + +static void +dhcp6_sendrequest(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrequest); +} + +static void +dhcp6_sendrebind(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrebind); +} + +static void +dhcp6_sendrenew(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendrenew); +} + +static void +dhcp6_sendconfirm(void *arg) +{ + + dhcp6_sendmessage(arg, dhcp6_sendconfirm); +} + +static void +dhcp6_startrenew(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = arg; + state = D6_STATE(ifp); + state->state = DH6S_RENEW; + state->start_uptime = uptime(); + state->RTC = 0; + state->IRT = REN_TIMEOUT; + state->MRT = REN_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendrenew(ifp); +} + +static void +dhcp6_startrebind(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = arg; + delete_timeout(dhcp6_sendrenew, ifp); + state = D6_STATE(ifp); + state->state = DH6S_REBIND; + state->RTC = 0; + state->IRT = REB_TIMEOUT; + state->MRT = REB_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendrebind(ifp); +} + +static void +dhcp6_startdiscover(void *arg) +{ + struct interface *ifp; + struct dhcp6_state *state; + + ifp = arg; + state = D6_STATE(ifp); + state->state = DH6S_DISCOVER; + state->start_uptime = uptime(); + state->RTC = 0; + state->IRT = SOL_TIMEOUT; + state->MRT = SOL_MAX_RT; + state->MRC = 0; + + delete_timeout(NULL, ifp); + free(state->new); + state->new = NULL; + state->new_len = 0; + + if (dhcp6_makemessage(ifp) == -1) + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_senddiscover(ifp); +} + +static void +dhcp6_failconfirm(void *arg) +{ + struct interface *ifp; + + ifp = arg; + syslog(LOG_ERR, "%s: failed to confirm prior address", ifp->name); + /* Section 18.1.2 says that we SHOULD use the last known + * IP address(s) and lifetimes if we didn't get a reply. + * I disagree with this. */ + dhcp6_startdiscover(ifp); +} + +static void +dhcp6_failrequest(void *arg) +{ + struct interface *ifp; + + ifp = arg; + syslog(LOG_ERR, "%s: failed to request address", ifp->name); + /* Section 18.1.1 says that client local policy dictates + * what happens if a REQUEST fails. + * Of the possible scenarios listed, moving back to the + * DISCOVER phase makes more sense for us. */ + dhcp6_startdiscover(ifp); +} + +static void +dhcp6_startrequest(struct interface *ifp) +{ + struct dhcp6_state *state; + + delete_timeout(dhcp6_senddiscover, ifp); + state = D6_STATE(ifp); + state->state = DH6S_REQUEST; + state->RTC = 0; + state->IRT = REQ_TIMEOUT; + state->MRT = REQ_MAX_RT; + state->MRC = REQ_MAX_RC; + state->MRCcallback = dhcp6_failrequest; + + if (dhcp6_makemessage(ifp) == -1) { + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + return; + } + dhcp6_sendrequest(ifp); +} + +static void +dhcp6_startconfirm(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + state->state = DH6S_REBOOT; + state->start_uptime = uptime(); + state->RTC = 0; + state->IRT = CNF_TIMEOUT; + state->MRT = CNF_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) { + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + return; + } + dhcp6_sendconfirm(ifp); + add_timeout_sec(CNF_MAX_RD, dhcp6_failconfirm, ifp); +} + +static void +dhcp6_startinform(struct interface *ifp) +{ + struct dhcp6_state *state; + + state = D6_STATE(ifp); + state->state = DH6S_INFORM; + state->start_uptime = uptime(); + state->RTC = 0; + state->IRT = INF_TIMEOUT; + state->MRT = INF_MAX_RT; + state->MRC = 0; + + if (dhcp6_makemessage(ifp) == -1) + syslog(LOG_ERR, "%s: dhcp6_makemessage: %m", ifp->name); + else + dhcp6_sendinform(ifp); +} + +static void +dhcp6_startexpire(void *arg) +{ + struct interface *ifp; + const struct dhcp6_state *state; + + ifp = arg; + delete_timeout(dhcp6_sendrebind, ifp); + + syslog(LOG_ERR, "%s: DHCPv6 lease expired", ifp->name); + dhcp6_freedrop_addrs(ifp, 1); + run_script_reason(ifp, "EXPIRE6"); + state = D6_CSTATE(ifp); + unlink(state->leasefile); + dhcp6_startdiscover(ifp); +} + +static int dhcp6_getstatus(const struct dhcp6_option *o) +{ + const struct dhcp6_status *s; + size_t len; + + len = ntohs(o->len); + if (len < sizeof(uint16_t)) { + syslog(LOG_ERR, "status truncated"); + return -1; + } + if (ntohs(o->code) != D6_OPTION_STATUS_CODE) { + /* unlikely */ + syslog(LOG_ERR, "not a status"); + return -1; + } + s = (const struct dhcp6_status *)o; + len = ntohs(s->len) - sizeof(s->len); + if (status == NULL || len > strlen(status)) { + free(status); + status = malloc(len + 1); + } + memcpy(status, (const char *)s + sizeof(*s), len); + status[len] = '\0'; + return ntohs(s->status); +} + +int +dhcp6_addrexists(const struct ipv6_addr *a) +{ + const struct interface *ifp; + const struct dhcp6_state *state; + const struct ipv6_addr *ap; + + for (ifp = ifaces; ifp; ifp = ifp->next) { + state = D6_CSTATE(ifp); + if (state == NULL) + continue; + TAILQ_FOREACH(ap, &state->addrs, next) { + if (memcmp(&ap->addr, &a->addr, sizeof(a->addr)) == 0) + return 1; + } + } + return 0; +} + +static int +dhcp6_findia(struct interface *ifp, const uint8_t *d, size_t l) +{ + struct dhcp6_state *state; + const struct dhcp6_option *o; + const uint8_t *p; + struct ipv6_addr *a; + const struct ipv6_addr *pa; + char iabuf[INET6_ADDRSTRLEN]; + const char *ia; + int i; + uint32_t u32; + + i = 0; + dhcp6_freedrop_addrs(ifp, 0); + state = D6_STATE(ifp); + while ((o = dhcp6_findoption(D6_OPTION_IA_ADDR, d, l))) { + d += ntohs(o->len); + l -= ntohs(o->len); + a = malloc(sizeof(*a)); + if (a) { + a->new = 1; + p = D6_COPTION_DATA(o); + memcpy(&a->addr.s6_addr, p, + sizeof(a->addr.s6_addr)); + p += sizeof(a->addr.s6_addr); + pa = ipv6rs_findprefix(a); + if (pa) { + memcpy(&a->prefix, &pa->prefix, + sizeof(a->prefix)); + a->prefix_len = pa->prefix_len; + } else { + a->prefix_len = 64; + ipv6_makeprefix(&a->prefix, &a->addr, 64); + } + memcpy(&u32, p, sizeof(u32)); + a->prefix_pltime = ntohl(u32); + p += sizeof(u32); + memcpy(&u32, p, sizeof(u32)); + a->prefix_vltime = ntohl(u32); + if (a->prefix_pltime < state->lowpl) + state->lowpl = a->prefix_pltime; + if (a->prefix_vltime > state->expire) + state->expire = a->prefix_vltime; + ia = inet_ntop(AF_INET6, &a->addr.s6_addr, + iabuf, sizeof(iabuf)); + snprintf(a->saddr, sizeof(a->saddr), + "%s/%d", ia, a->prefix_len); + TAILQ_INSERT_TAIL(&state->addrs, a, next); + i++; + } + } + return i; +} + + +static int +dhcp6_validatelease(struct interface *ifp, + const struct dhcp6_message *m, size_t len, + const char *sfrom) +{ + struct dhcp6_state *state; + const struct dhcp6_option *o; + size_t l, ol; + const uint8_t *p; + uint32_t u32; + + state = D6_STATE(ifp); + o = dhcp6_getoption(D6_OPTION_IA_NA, m, len); + if (o == NULL) { + if (sfrom) + syslog(LOG_ERR, "%s: no IA_NA in REPLY from %s", + ifp->name, sfrom); + return -1; + } + ol = ntohs(o->len); + l = sizeof(state->iaid) + sizeof(uint32_t) + sizeof(uint32_t); + if (ol < l + sizeof(struct dhcp6_status)) { + if (sfrom) + syslog(LOG_ERR, "%s: truncated IA NA from %s", + ifp->name, sfrom); + return -1; + } + p = D6_COPTION_DATA(o); + if (memcmp(p, state->iaid, sizeof(state->iaid)) != 0) { + syslog(LOG_ERR, "%s: IAID mismatch from %s", + ifp->name, sfrom ? sfrom : "lease"); + return -1; + } + p += sizeof(state->iaid); + memcpy(&u32, p, sizeof(u32)); + state->renew = ntohl(u32); + p += sizeof(u32); + memcpy(&u32, p, sizeof(u32)); + state->rebind = ntohl(u32); + if (state->renew > state->rebind && state->rebind > 0) { + if (sfrom) + syslog(LOG_WARNING, "%s: T1 (%d) > T2 (%d) from %s", + ifp->name, state->renew, state->rebind, sfrom); + state->renew = 0; + state->rebind = 0; + } + p += sizeof(u32); + state->expire = 0; + state->lowpl = ~0U; + ol -= l; + o = dhcp6_findoption(D6_OPTION_STATUS_CODE, p, ol); + if (o && dhcp6_getstatus(o) != D6_STATUS_OK) { + syslog(LOG_ERR, "%s: DHCPv6 REPLY: %s", ifp->name, status); + return -1; + } + if (dhcp6_findia(ifp, p, ol) == 0) { + syslog(LOG_ERR, "%s: %s: DHCPv6 REPLY missing IA ADDR", + ifp->name, sfrom); + return -1; + } + return 0; +} + +static ssize_t +dhcp6_writelease(const struct interface *ifp) +{ + const struct dhcp6_state *state; + int fd; + ssize_t bytes; + + state = D6_CSTATE(ifp); + syslog(LOG_DEBUG, "%s: writing lease `%s'", + ifp->name, state->leasefile); + + fd = open(state->leasefile, O_WRONLY | O_CREAT | O_TRUNC, 0444); + if (fd == -1) { + syslog(LOG_ERR, "%s: dhcp6_writelease: %m", ifp->name); + return -1; + } + bytes = write(fd, state->new, state->new_len); + close(fd); + return bytes; +} + +static ssize_t +dhcp6_readlease(struct interface *ifp) +{ + struct dhcp6_state *state; + struct stat st; + int fd; + ssize_t bytes; + struct timeval now; + + state = D6_STATE(ifp); + if (stat(state->leasefile, &st) == -1) { + if (errno == ENOENT) + return 0; + return -1; + } + syslog(LOG_DEBUG, "%s: reading lease `%s'", + ifp->name, state->leasefile); + state->new = malloc(st.st_size); + if (state->new == NULL) + return -1; + state->new_len = st.st_size; + fd = open(state->leasefile, O_RDONLY); + if (fd == -1) + return -1; + bytes = read(fd, state->new, state->new_len); + close(fd); + + /* Check to see if the lease is still valid */ + if (dhcp6_validatelease(ifp, state->new, state->new_len, NULL) == -1) + goto ex; + + gettimeofday(&now, NULL); + if ((time_t)state->expire < now.tv_sec - st.st_mtime) { + syslog(LOG_DEBUG, "%s: discarding expired lease", ifp->name); + goto ex; + } + + return bytes; + +ex: + free(state->new); + state->new = NULL; + state->new_len = 0; + unlink(state->leasefile); + return 0; +} + +static void +dhcp6_startinit(struct interface *ifp) +{ + struct dhcp6_state *state; + int r; + + state = D6_STATE(ifp); + state->state = DH6S_INIT; + state->expire = ~0U; + state->lowpl = ~0U; + if (!(options & DHCPCD_TEST)) { + r = dhcp6_readlease(ifp); + if (r == -1) + syslog(LOG_ERR, "%s: dhcp6_readlease: %s: %m", + ifp->name, state->leasefile); + else if (r != 0) { + dhcp6_startconfirm(ifp); + return; + } + } + dhcp6_startdiscover(ifp); +} + /* ARGSUSED */ static void dhcp6_handledata(_unused void *arg) @@ -483,12 +1141,13 @@ dhcp6_handledata(_unused void *arg) struct in6_pktinfo pkt; struct interface *ifp; const char *sfrom, *op; - struct dhcp6_message *m, *r; + 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; len = recvmsg(sock, &rcvhdr, 0); if (len == -1) { @@ -524,7 +1183,6 @@ dhcp6_handledata(_unused void *arg) return; } - for (ifp = ifaces; ifp; ifp = ifp->next) if (ifp->index == (unsigned int)pkt.ipi6_ifindex) break; @@ -540,17 +1198,17 @@ dhcp6_handledata(_unused void *arg) return; } - m = state->send; r = (struct dhcp6_message *)rcvhdr.msg_iov[0].iov_base; - if (r->xid[0] != m->xid[0] || - r->xid[1] != m->xid[1] || - r->xid[2] != m->xid[2]) + if (r->xid[0] != state->send->xid[0] || + r->xid[1] != state->send->xid[1] || + r->xid[2] != state->send->xid[2]) { syslog(LOG_ERR, "%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], - r->xid[0], r->xid[1], r->xid[2], + state->send->xid[0], state->send->xid[1], + state->send->xid[2], sfrom); return; } @@ -562,7 +1220,7 @@ dhcp6_handledata(_unused void *arg) } o = dhcp6_getoption(D6_OPTION_CLIENTID, r, len); - if (o && ntohs(o->len) != duid_len && + if (o == NULL || ntohs(o->len) != duid_len || memcmp(D6_COPTION_DATA(o), duid, duid_len) != 0) { syslog(LOG_ERR, "%s: incorrect client ID from %s", @@ -582,35 +1240,147 @@ dhcp6_handledata(_unused void *arg) } } - m = malloc(len); - if (m == NULL) { - syslog(LOG_ERR, "%s: malloc DHCPv6 reply: %m", ifp->name); - return; - } - - free(state->old); - state->old = state->new; - state->old_len = state->new_len; - state->new = m; - memcpy(m, r, len); - state->new_len = len; - op = dhcp6_get_op(r->type); - if (r->type != DHCP6_REPLY) { + switch(r->type) { + case DHCP6_REPLY: + if (state->state == DH6S_INFORM) + break; + switch(state->state) { + case DH6S_REBOOT: + o = dhcp6_getoption(D6_OPTION_STATUS_CODE, r, len); + if (o == NULL) { + syslog(LOG_ERR, + "%s: no status code in reply from %s", + ifp->name, sfrom); + return; + } + if (dhcp6_getstatus(o) != D6_STATUS_OK) { + syslog(LOG_ERR, "%s: DHCPv6 REPLY: %s", + ifp->name, status); + dhcp6_startdiscover(ifp); + return; + } + goto recv; + case DH6S_REQUEST: /* FALLTHROUGH */ + case DH6S_RENEW: /* FALLTHROUGH */ + case DH6S_REBIND: + goto replyok; + default: + op = NULL; + } + break; + case DHCP6_ADVERTISE: + if (state->state != DH6S_DISCOVER) { + op = NULL; + break; + } +replyok: + if (dhcp6_validatelease(ifp, r, len, sfrom) == -1) + return; + break; + default: syslog(LOG_ERR, "%s: invalid DHCP6 type %s (%d)", ifp->name, op, r->type); return; } + if (op == NULL) { + syslog(LOG_WARNING, "%s: invalid state for DHCP6 type %s (%d)", + ifp->name, op, r->type); + return; + } + + if (state->recv_len < (size_t)len) { + free(state->recv); + state->recv = malloc(len); + if (state->recv == NULL) { + syslog(LOG_ERR, "%s: malloc recv: %m", ifp->name); + return; + } + } + memcpy(state->recv, r, len); + state->recv_len = len; + + switch(r->type) { + case DHCP6_ADVERTISE: + ap = TAILQ_FIRST(&state->addrs); + syslog(LOG_INFO, "%s: ADV %s from %s", + ifp->name, ap->saddr, sfrom); + dhcp6_startrequest(ifp); + return; + } +recv: syslog(LOG_INFO, "%s: %s received from %s", ifp->name, op, sfrom); + + reason = NULL; + delete_timeout(NULL, ifp); switch(state->state) { case DH6S_INFORM: + state->renew = 0; + state->rebind = 0; + state->expire = ~0U; + state->lowpl = ~0U; reason = "INFORM6"; break; + case DH6S_REQUEST: + if (reason == NULL) + reason = "BOUND6"; + /* FALLTHROUGH */ + case DH6S_RENEW: + if (reason == NULL) + reason = "RENEW6"; + /* FALLTHROUGH */ + case DH6S_REBIND: + if (reason == NULL) + reason = "REBIND6"; + case DH6S_REBOOT: + if (reason == NULL) + reason = "REBOOT6"; + if (state->renew == 0) { + if (state->expire == ~0U) + state->renew = ~0U; + else + state->renew = state->lowpl * 0.5; + } + if (state->rebind == 0) { + if (state->expire == ~0U) + state->rebind = ~0U; + else + state->rebind = state->lowpl * 0.8; + } + break; default: reason = "UNKNOWN6"; break; } + + if (state->state != DH6S_REBOOT) { + free(state->old); + state->old = state->new; + state->old_len = state->new_len; + state->new = state->recv; + state->new_len = state->recv_len; + state->recv = NULL; + state->recv_len = 0; + } + + if (!(options & DHCPCD_TEST)) { + state->state = DH6S_BOUND; + if (state->renew) + add_timeout_sec(state->renew, dhcp6_startrenew, ifp); + if (state->rebind) + add_timeout_sec(state->rebind, dhcp6_startrebind, ifp); + if (state->expire != ~0U) + add_timeout_sec(state->expire, dhcp6_startexpire, ifp); + ipv6_addaddrs(ifp, &state->addrs); + if (state->renew || state->rebind) + syslog(LOG_INFO, + "%s: renew in %u seconds, rebind in %u seconds", + ifp->name, state->renew, state->rebind); + ipv6_buildroutes(); + dhcp6_writelease(ifp); + } + run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : reason); if (options & DHCPCD_TEST || (ifp->state->options->options & DHCPCD_INFORM && @@ -621,10 +1391,9 @@ dhcp6_handledata(_unused void *arg) #endif exit(EXIT_SUCCESS); } - delete_timeout(NULL, ifp); + daemonise(); } - static int dhcp6_open(void) { @@ -686,6 +1455,7 @@ int dhcp6_start(struct interface *ifp, int manage) { struct dhcp6_state *state; + uint32_t u32; state = D6_STATE(ifp); if (state) { @@ -714,13 +1484,24 @@ dhcp6_start(struct interface *ifp, int manage) return -1; state->state = manage ? DH6S_INIT : DH6S_INFORM; - state->start_uptime = uptime(); - - if (dhcp6_makemessage(ifp) == -1) - return -1; + TAILQ_INIT(&state->addrs); + snprintf(state->leasefile, sizeof(state->leasefile), + LEASEFILE6, ifp->name); + + u32 = strlen(ifp->name); + if (u32 < 5) { + memcpy(state->iaid, ifp->name, u32); + if (u32 < 4) + memset(state->iaid + u32, 0, 4 - u32); + } else { + u32 = htonl(ifp->index); + memcpy(state->iaid, &u32, 4); + } if (state->state == DH6S_INFORM) - dhcp6_sendinform(ifp); + dhcp6_startinform(ifp); + else + dhcp6_startinit(ifp); return 1; } @@ -733,9 +1514,11 @@ dhcp6_freedrop(struct interface *ifp, int drop) delete_timeout(NULL, ifp); state = D6_STATE(ifp); if (state) { + dhcp6_freedrop_addrs(ifp, drop); if (drop && state->new) run_script_reason(ifp, "STOP6"); free(state->send); + free(state->recv); free(state->new); free(state->old); free(state); @@ -772,6 +1555,7 @@ ssize_t dhcp6_env(char **env, const char *prefix, const struct interface *ifp, const struct dhcp6_message *m, ssize_t mlen) { + const struct dhcp6_state *state; const struct if_options *ifo; const struct dhcp_opt *opt; const struct dhcp6_option *o; @@ -779,7 +1563,9 @@ dhcp6_env(char **env, const char *prefix, const struct interface *ifp, uint16_t ol; const uint8_t *od; char **ep, *v, *val; + const struct ipv6_addr *ap; + state = D6_CSTATE(ifp); e = 0; ep = env; ifo = ifp->state->options; @@ -808,6 +1594,25 @@ dhcp6_env(char **env, const char *prefix, const struct interface *ifp, } + if (TAILQ_FIRST(&state->addrs)) { + if (env == NULL) + e++; + else { + e = strlen(prefix) + strlen("_dhcp6_ip_address="); + TAILQ_FOREACH(ap, &state->addrs, next) { + e += strlen(ap->saddr) + 1; + } + v = val = *ep++ = xmalloc(e); + v += snprintf(val, e, "%s_dhcp6_ip_address=", prefix); + TAILQ_FOREACH(ap, &state->addrs, next) { + strcpy(v, ap->saddr); + v += strlen(ap->saddr); + *v++ = ' '; + } + *--v = '\0'; + } + } + if (env == NULL) return e; return ep - env; diff --git a/dhcp6.h b/dhcp6.h index f6af057c..3a1b023b 100644 --- a/dhcp6.h +++ b/dhcp6.h @@ -51,6 +51,7 @@ #define D6_OPTION_CLIENTID 1 #define D6_OPTION_SERVERID 2 +#define D6_OPTION_IA_NA 3 #define D6_OPTION_ORO 6 #define D6_OPTION_IA_ADDR 5 #define D6_OPTION_PREFERENCE 7 @@ -73,6 +74,7 @@ #define D6_OPTION_BCMS_SERVER_A 34 #include "dhcp.h" +#include "ipv6.h" extern const struct dhcp_opt const dhcp6_opts[]; struct dhcp6_message { @@ -87,6 +89,48 @@ struct dhcp6_option { /* followed by data */ } _packed; +struct dhcp6_status { + uint16_t code; + uint16_t len; + uint16_t status; + /* followed by message */ +} _packed; + +#define D6_STATUS_OK 0 +#define D6_STATUS_FAIL 1 +#define D6_STATUS_NOADDR 2 +#define D6_STATUS_NOBINDING 3 +#define D6_STATUS_NOTONLINK 4 +#define D6_STATUS_USEMULTICAST 5 + +#define SOL_MAX_DELAY 1 +#define SOL_TIMEOUT 1 +#define SOL_MAX_RT 120 +#define REQ_TIMEOUT 1 +#define REQ_MAX_RT 30 +#define REQ_MAX_RC 10 +#define CNF_MAX_DELAY 1 +#define CNF_TIMEOUT 1 +#define CNF_MAX_RT 4 +#define CNF_MAX_RD 10 +#define REN_TIMEOUT 10 +#define REN_MAX_RT 600 +#define REB_TIMEOUT 10 +#define REB_MAX_RT 600 +#define INF_MAX_DELAY 1 +#define INF_TIMEOUT 1 +#define INF_MAX_RT 120 +#define REL_TIMEOUT 1 +#define REL_MAX_RC 5 +#define DEC_TIMEOUT 1 +#define DEC_MAX_RC 5 +#define REC_TIMEOUT 2 +#define REC_MAX_RC 8 +#define HOP_COUNT_LIMIT 32 + +#define DHCP6_RAND_MIN -100 +#define DHCP6_RAND_MAX 100 + enum DH6S { DH6S_INIT, DH6S_DISCOVER, @@ -102,17 +146,38 @@ enum DH6S { struct dhcp6_state { enum DH6S state; + uint8_t iaid[4]; time_t start_uptime; - int interval; + + /* Message retransmission timings */ + struct timeval RT; + int RTC; + int IRT; + int MRC; + int MRT; + void (*MRCcallback)(void *); + struct dhcp6_message *send; size_t send_len; + struct dhcp6_message *recv; + size_t recv_len; struct dhcp6_message *new; size_t new_len; struct dhcp6_message *old; size_t old_len; + + uint32_t renew; + uint32_t rebind; + uint32_t expire; + struct ipv6_addrhead addrs; + uint32_t lowpl; + char leasefile[PATH_MAX]; }; -#define D6_STATE(ifp) ((struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) +#define D6_STATE(ifp) \ + ((struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) +#define D6_CSTATE(ifp) \ + ((const struct dhcp6_state *)(ifp)->if_data[IF_DATA_DHCP6]) #define D6_STATE_RUNNING(ifp) (D6_STATE((ifp)) && D6_STATE((ifp))->new) #define D6_FIRST_OPTION(m) \ ((struct dhcp6_option *) \ @@ -132,6 +197,7 @@ struct dhcp6_state { ((const uint8_t *)(o) + sizeof(struct dhcp6_option)) void dhcp6_printoptions(void); +int dhcp6_addrexists(const struct ipv6_addr *); int dhcp6_start(struct interface *, int); ssize_t dhcp6_env(char **, const char *, const struct interface *, const struct dhcp6_message *, ssize_t); diff --git a/dhcpcd-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf index 719202de..4610079d 100644 --- a/dhcpcd-hooks/20-resolv.conf +++ b/dhcpcd-hooks/20-resolv.conf @@ -140,7 +140,7 @@ remove_resolv_conf() # For ease of use, map DHCP6 names onto our DHCP4 names case "$reason" in -INFORM6) +BOUND6|RENEW6|REBIND6|REBOOT6|INFORM6) new_domain_name_servers="$new_dhcp6_name_servers" new_domain_search="$new_dhcp6_domain_search" ;; diff --git a/dhcpcd-run-hooks.8.in b/dhcpcd-run-hooks.8.in index 789171aa..49a2962a 100644 --- a/dhcpcd-run-hooks.8.in +++ b/dhcpcd-run-hooks.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 11, 2012 +.Dd November 6, 2012 .Dt DHCPCD-RUN-HOOKS 8 .Os .Sh NAME @@ -78,13 +78,13 @@ This is generally just a notification and no action need be taken. .It Dv INFORM | Dv INFORM6 dhcpcd informed a DHCP server about it's address and obtained other configuration details. -.It Dv BOUND +.It Dv BOUND | Dv BOUND6 dhcpcd obtained a new lease from a DHCP server. -.It Dv RENEW +.It Dv RENEW | Dv RENEW6 dhcpcd renewed it's lease. -.It Dv REBIND +.It Dv REBIND | Dv REBIND6 dhcpcd has rebound to a new DHCP server. -.It Dv REBOOT +.It Dv REBOOT | Dv REBOOT6 dhcpcd successfully requested a lease from a DHCP server. .It Dv IPV4LL dhcpcd failed to contact any DHCP servers but did obtain an IPV4LL address. diff --git a/dhcpcd-run-hooks.in b/dhcpcd-run-hooks.in index 7f46cd62..6210eed8 100644 --- a/dhcpcd-run-hooks.in +++ b/dhcpcd-run-hooks.in @@ -5,7 +5,7 @@ case "$reason" in ROUTERADVERT) ifsuffix=":ra";; - INFORM6|BOUND6|RENEW6|REBIND6|EXPIRE6|RELEASE6|STOP6) + INFORM6|BOUND6|RENEW6|REBIND6|REBOOT6|EXPIRE6|RELEASE6|STOP6) ifsuffix=":dhcp6";; *) ifsuffix=;; diff --git a/dhcpcd.8.in b/dhcpcd.8.in index 22197c2c..121a5640 100644 --- a/dhcpcd.8.in +++ b/dhcpcd.8.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd October 11, 2012 +.Dd November 6, 2012 .Dt DHCPCD 8 .Os .Sh NAME @@ -101,7 +101,7 @@ is also an implementation of the BOOTP client specified in .Li RFC 951 . .Pp .Nm -is also an implementation of an IPv6 Router Solicitor as specified in +is also an implementation of the IPv6 Router Solicitor as specified in .Li RFC 4861 and .Li RFC 6106 . @@ -116,10 +116,11 @@ sends Neighbor Solicitions to each advertising router periodically and will expire the ones that do not respond. .Pp .Nm -is also an implemenation of a DHCPv6 client as specified in -.Li RFC 3315 -but only for sending Information Request messages when instructed to do so -by an IPv6 Router Advertisment or manually. +is also an implemenation of the DHCPv6 client as specified in +.Li RFC 3315 . +By default, +.Nm +only starts DHCPv6 when instructed to do so by an IPV6 Router Advertisement. .Ss Local Link configuration If .Nm @@ -564,7 +565,8 @@ unicasts INFORM to the destination, otherwise it defaults to STATIC. .Sh NOTES .Nm requires a Berkley Packet Filter, or BPF device on BSD based systems and a -Linux Socket Filter, or LPF device on Linux based systems. +Linux Socket Filter, or LPF device on Linux based systems for all IPv4 +configuration. .Sh FILES .Bl -ohang .It Pa @SYSCONFDIR@/dhcpcd.conf diff --git a/dhcpcd.c b/dhcpcd.c index 0c62a433..a1071b04 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -217,7 +217,7 @@ handle_exit_timeout(_unused void *arg) int timeout; syslog(LOG_ERR, "timed out"); - if (!(options & DHCPCD_TIMEOUT_IPV4LL)) { + if (!(options & DHCPCD_IPV4) || !(options & DHCPCD_TIMEOUT_IPV4LL)) { if (options & DHCPCD_MASTER) { daemonise(); return; @@ -1216,8 +1216,12 @@ start_interface(void *arg) return; } - if (ifo->options & DHCPCD_INFORM && ifo->options & DHCPCD_IPV6) - dhcp6_start(iface, 0); + if (ifo->options & DHCPCD_IPV6) { + if (ifo->options & DHCPCD_INFORM) + dhcp6_start(iface, 0); + else if (!(ifo->options & DHCPCD_IPV6RS)) + dhcp6_start(iface, 1); + } if (!(ifo->options & DHCPCD_IPV4)) return; @@ -2081,12 +2085,12 @@ main(int argc, char **argv) } #endif + if (options & DHCPCD_IPV6 && ipv6_init() == -1) { + options &= ~DHCPCD_IPV6; + syslog(LOG_ERR, "ipv6_init: %m"); + } if (options & DHCPCD_IPV6RS && !check_ipv6(NULL)) options &= ~DHCPCD_IPV6RS; - if (options & DHCPCD_IPV6RS && ipv6_open() == -1) { - options &= ~DHCPCD_IPV6RS; - syslog(LOG_ERR, "ipv6_open: %m"); - } if (options & DHCPCD_IPV6RS) { ipv6rsfd = ipv6rs_open(); if (ipv6rsfd == -1) { diff --git a/if-bsd.c b/if-bsd.c index 1a35ab76..10cf1c3b 100644 --- a/if-bsd.c +++ b/if-bsd.c @@ -276,9 +276,13 @@ if_route(const struct rt *rt, int action) int if_address6(const struct interface *ifp, const struct ipv6_addr *a, int action) { + int s, r; struct in6_aliasreq ifa; struct in6_addr mask; + s = socket(AF_INET6, SOCK_DGRAM, 0); + if (s == -1) + return -1; memset(&ifa, 0, sizeof(ifa)); strlcpy(ifa.ifra_name, ifp->name, sizeof(ifa.ifra_name)); @@ -295,8 +299,9 @@ if_address6(const struct interface *ifp, const struct ipv6_addr *a, int action) ifa.ifra_lifetime.ia6t_pltime = a->prefix_pltime; #undef ADDADDR - return ioctl(socket_afnet6, - action < 0 ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa); + r = ioctl(s, action < 0 ? SIOCDIFADDR_IN6 : SIOCAIFADDR_IN6, &ifa); + close(s); + return r; } int diff --git a/ipv6.c b/ipv6.c index e50a7484..7a4a166d 100644 --- a/ipv6.c +++ b/ipv6.c @@ -39,6 +39,7 @@ #include "common.h" #include "configure.h" #include "dhcpcd.h" +#include "dhcp6.h" #include "ipv6.h" #include "ipv6rs.h" @@ -47,7 +48,6 @@ # define s6_addr32 __u6_addr.__u6_addr32 #endif -int socket_afnet6; static struct rt6head *routes; #ifdef DEBUG_MEMORY @@ -64,19 +64,18 @@ ipv6_cleanup() } #endif -int -ipv6_open(void) +int ipv6_init(void) { - socket_afnet6 = socket(AF_INET6, SOCK_DGRAM, 0); - if (socket_afnet6 == -1) + + routes = malloc(sizeof(*routes)); + if (routes == NULL) return -1; - set_cloexec(socket_afnet6); - routes = xmalloc(sizeof(*routes)); + TAILQ_INIT(routes); #ifdef DEBUG_MEMORY atexit(ipv6_cleanup); #endif - return socket_afnet6; + return 0; } struct in6_addr * @@ -116,7 +115,7 @@ ipv6_makeaddr(struct in6_addr *addr, const char *ifname, { struct in6_addr *lla; - if (prefix_len > 64) { + if (prefix_len < 0 || prefix_len > 64) { errno = EINVAL; return -1; } @@ -134,6 +133,26 @@ ipv6_makeaddr(struct in6_addr *addr, const char *ifname, return 0; } +int +ipv6_makeprefix(struct in6_addr *prefix, const struct in6_addr *addr, int len) +{ + int bytelen, bitlen; + + if (len < 0 || len > 128) { + errno = EINVAL; + return -1; + } + + bytelen = len / NBBY; + bitlen = len % NBBY; + memcpy(&prefix->s6_addr, &addr->s6_addr, bytelen); + if (bitlen != 0) + prefix->s6_addr[bytelen] >>= NBBY - bitlen; + memset((char *)prefix->s6_addr + bytelen, 0, + sizeof(prefix->s6_addr) - bytelen); + return 0; +} + int ipv6_mask(struct in6_addr *mask, int len) { @@ -190,6 +209,36 @@ ipv6_prefixlen(const struct in6_addr *mask) return x * NBBY + y; } +ssize_t +ipv6_addaddrs(const struct interface *ifp, 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; + syslog(ap->new ? LOG_INFO : LOG_DEBUG, + "%s: adding address %s", + ifp->name, ap->saddr); + if (add_address6(ifp, ap) == -1) + syslog(LOG_ERR, "add_address6 %m"); + else { + i++; + if (ipv6_removesubnet(ifp, ap) == -1) + syslog(LOG_ERR,"ipv6_removesubnet %m"); + syslog(LOG_DEBUG, + "%s: pltime %d seconds, vltime %d seconds", + ifp->name, ap->prefix_pltime, + ap->prefix_vltime); + } + } + + return i; +} + static struct rt6 * find_route6(struct rt6head *rts, const struct rt6 *r) { @@ -284,39 +333,43 @@ d_route(struct rt6 *rt) } static struct rt6 * -make_route(struct ra *rap) +make_route(const struct interface *ifp, struct ra *rap) { struct rt6 *r; r = xzalloc(sizeof(*r)); r->ra = rap; - r->iface = rap->iface; - r->metric = rap->iface->metric; - r->mtu = rap->mtu; + r->iface = ifp; + r->metric = ifp->metric; + if (rap) + r->mtu = rap->mtu; + else + r->mtu = 0; return r; } static struct rt6 * -make_prefix(struct ra *rap, struct ipv6_addr *addr) +make_prefix(const struct interface * ifp,struct ra *rap, struct ipv6_addr *addr) { struct rt6 *r; if (addr == NULL || addr->prefix_len > 128) return NULL; - r = make_route(rap); + r = make_route(ifp, rap); r->dest = addr->prefix; ipv6_mask(&r->net, addr->prefix_len); r->gate = in6addr_any; return r; } + static struct rt6 * make_router(struct ra *rap) { struct rt6 *r; - r = make_route(rap); + r = make_route(rap->iface, rap); r->dest = in6addr_any; r->net = in6addr_any; r->gate = rap->from; @@ -324,7 +377,7 @@ make_router(struct ra *rap) } int -ipv6_remove_subnet(struct ra *rap, struct ipv6_addr *addr) +ipv6_removesubnet(const struct interface *ifp, struct ipv6_addr *addr) { struct rt6 *rt; #if HAVE_ROUTE_METRIC @@ -335,9 +388,9 @@ ipv6_remove_subnet(struct ra *rap, struct ipv6_addr *addr) /* We need to delete the subnet route to have our metric or * prefer the interface. */ r = 0; - rt = make_prefix(rap, addr); + rt = make_prefix(ifp, NULL, addr); if (rt) { - rt->iface = rap->iface; + rt->iface = ifp; #ifdef __linux__ rt->metric = 256; #else @@ -370,22 +423,34 @@ ipv6_remove_subnet(struct ra *rap, struct ipv6_addr *addr) IN6_ARE_ADDR_EQUAL(&((rtp)->net), &in6addr_any)) void -ipv6_build_routes(void) +ipv6_buildroutes(void) { struct rt6head dnr, *nrs; struct rt6 *rt, *rtn, *or; struct ra *rap; struct ipv6_addr *addr; + const struct interface *ifp; + const struct dhcp6_state *d6_state; int have_default; if (!(options & (DHCPCD_IPV6RA_OWN | DHCPCD_IPV6RA_OWN_DEFAULT))) return; TAILQ_INIT(&dnr); + for (ifp = ifaces; ifp; ifp = ifp->next) { + d6_state = D6_CSTATE(ifp); + if (d6_state && d6_state->state == DH6S_BOUND) { + TAILQ_FOREACH(addr, &d6_state->addrs, next) { + rt = make_prefix(ifp, NULL, addr); + if (rt) + TAILQ_INSERT_TAIL(&dnr, rt, next); + } + } + } TAILQ_FOREACH(rap, &ipv6_routers, next) { if (options & DHCPCD_IPV6RA_OWN) { TAILQ_FOREACH(addr, &rap->addrs, next) { - rt = make_prefix(rap, addr); + rt = make_prefix(rap->iface, rap, addr); if (rt) TAILQ_INSERT_TAIL(&dnr, rt, next); } diff --git a/ipv6.h b/ipv6.h index 8f6ef27f..55be2168 100644 --- a/ipv6.h +++ b/ipv6.h @@ -32,8 +32,6 @@ #include -#include "ipv6rs.h" - #define ALLROUTERS "ff02::2" #define HOPLIMIT 255 @@ -49,6 +47,7 @@ struct ipv6_addr { int new; char saddr[INET6_ADDRSTRLEN]; }; +TAILQ_HEAD(ipv6_addrhead, ipv6_addr); struct rt6 { TAILQ_ENTRY(rt6) next; @@ -62,15 +61,15 @@ struct rt6 { }; TAILQ_HEAD(rt6head, rt6); -extern int socket_afnet6; - -int ipv6_open(void); +int ipv6_init(void); struct in6_addr *ipv6_linklocal(const char *); int ipv6_makeaddr(struct in6_addr *, const char *, const struct in6_addr *, int); +int ipv6_makeprefix(struct in6_addr *, const struct in6_addr *, int); int ipv6_mask(struct in6_addr *, int); int ipv6_prefixlen(const struct in6_addr *); -int ipv6_remove_subnet(struct ra *, struct ipv6_addr *); -void ipv6_build_routes(void); +ssize_t ipv6_addaddrs(const struct interface *, struct ipv6_addrhead *); +int ipv6_removesubnet(const struct interface *, struct ipv6_addr *); +void ipv6_buildroutes(void); void ipv6_drop(struct interface *); #endif diff --git a/ipv6ns.c b/ipv6ns.c index c66770c2..9a2ac249 100644 --- a/ipv6ns.c +++ b/ipv6ns.c @@ -168,7 +168,7 @@ ipv6ns_unreachable(void *arg) syslog(LOG_WARNING, "%s: %s is unreachable, expiring it", rap->iface->name, rap->sfrom); rap->expired = 1; - ipv6_build_routes(); + ipv6_buildroutes(); run_script_reason(rap->iface, "ROUTERADVERT"); /* XXX not RA */ } @@ -341,7 +341,7 @@ ipv6ns_handledata(_unused void *arg) syslog(LOG_INFO, "%s: %s is no longer a router", ifp->name, sfrom); rap->expired = 1; - ipv6_build_routes(); + ipv6_buildroutes(); run_script_reason(ifp, "ROUTERADVERT"); return; } diff --git a/ipv6rs.c b/ipv6rs.c index deb2a968..62bc34a0 100644 --- a/ipv6rs.c +++ b/ipv6rs.c @@ -269,8 +269,8 @@ ipv6rs_free_opts(struct ra *rap) } } -static int -ipv6rs_addrexists(struct ipv6_addr *a) +int +ipv6rs_addrexists(const struct ipv6_addr *a) { struct ra *rap; struct ipv6_addr *ap; @@ -295,7 +295,7 @@ ipv6rs_freedrop_addrs(struct ra *rap, int drop) * This is safe because the RA is removed from the list * before we are called. */ if (drop && (options & DHCPCD_IPV6RA_OWN) && - !ipv6rs_addrexists(ap)) + !ipv6rs_addrexists(ap) && !dhcp6_addrexists(ap)) { syslog(LOG_INFO, "%s: deleting address %s", rap->iface->name, ap->saddr); @@ -566,6 +566,13 @@ ipv6rs_handledata(_unused void *arg) "%s: invalid prefix in RA", ifp->name); break; } + if (ntohl(pi->nd_opt_pi_preferred_time) > + ntohl(pi->nd_opt_pi_valid_time)) + { + syslog(LOG_ERR, + "%s: pltime > vltime", ifp->name); + break; + } TAILQ_FOREACH(ap, &rap->addrs, next) if (ap->prefix_len ==pi->nd_opt_pi_prefix_len && memcmp(ap->prefix.s6_addr, @@ -579,15 +586,26 @@ ipv6rs_handledata(_unused void *arg) memcpy(ap->prefix.s6_addr, pi->nd_opt_pi_prefix.s6_addr, sizeof(ap->prefix.s6_addr)); - ipv6_makeaddr(&ap->addr, ifp->name, - &ap->prefix, pi->nd_opt_pi_prefix_len); - cbp = inet_ntop(AF_INET6, ap->addr.s6_addr, - ntopbuf, INET6_ADDRSTRLEN); - if (cbp) - snprintf(ap->saddr, sizeof(ap->saddr), - "%s/%d", cbp, ap->prefix_len); - else + if (pi->nd_opt_pi_flags_reserved & + ND_OPT_PI_FLAG_AUTO) + { + ipv6_makeaddr(&ap->addr, ifp->name, + &ap->prefix, + pi->nd_opt_pi_prefix_len); + cbp = inet_ntop(AF_INET6, + ap->addr.s6_addr, + ntopbuf, INET6_ADDRSTRLEN); + if (cbp) + snprintf(ap->saddr, + sizeof(ap->saddr), + "%s/%d", + cbp, ap->prefix_len); + else + ap->saddr[0] = '\0'; + } else { + memset(&ap->addr, 0, sizeof(ap->addr)); ap->saddr[0] = '\0'; + } TAILQ_INSERT_TAIL(&rap->addrs, ap, next); } else if (ap->prefix_vltime != ntohl(pi->nd_opt_pi_valid_time) || @@ -711,27 +729,10 @@ ipv6rs_handledata(_unused void *arg) if (new_rap) add_router(rap); - if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST)) { - TAILQ_FOREACH(ap, &rap->addrs, next) { - if (ap->prefix_vltime == 0) - continue; - syslog(ap->new ? LOG_INFO : LOG_DEBUG, - "%s: adding address %s", - ifp->name, ap->saddr); - if (add_address6(ifp, ap) == -1) - syslog(LOG_ERR, "add_address6 %m"); - else { - if (ipv6_remove_subnet(rap, ap) == -1) - syslog(LOG_ERR,"ipv6_remove_subnet %m"); - syslog(LOG_DEBUG, - "%s: vltime %d seconds, pltime %d seconds", - ifp->name, ap->prefix_vltime, - ap->prefix_pltime); - } - } - } + if (options & DHCPCD_IPV6RA_OWN && !(options & DHCPCD_TEST)) + ipv6_addaddrs(ifp, &rap->addrs); if (!(options & DHCPCD_TEST)) - ipv6_build_routes(); + ipv6_buildroutes(); run_script_reason(ifp, options & DHCPCD_TEST ? "TEST" : "ROUTERADVERT"); if (options & DHCPCD_TEST) goto handle_flag; @@ -740,8 +741,6 @@ ipv6rs_handledata(_unused void *arg) if (!(ifp->state->options->options & DHCPCD_IPV6RA_REQRDNSS)) has_dns = 1; - if (has_dns) - delete_q_timeout(0, handle_exit_timeout, NULL); delete_timeout(NULL, ifp); delete_timeout(NULL, rap); /* reachable timer */ if (has_dns) @@ -763,13 +762,8 @@ ipv6rs_handledata(_unused void *arg) handle_flag: if (rap->flags & ND_RA_FLAG_MANAGED) { - if (new_data) - syslog(LOG_WARNING, "%s: no support for DHCPv6 management", - ifp->name); - if (options & DHCPCD_TEST) - exit(EXIT_SUCCESS); -// if (dhcp6_start(ifp, 1) == -1) -// syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name); + if (dhcp6_start(ifp, 1) == -1) + syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name); } else if (rap->flags & ND_RA_FLAG_OTHER) { if (dhcp6_start(ifp, 0) == -1) syslog(LOG_ERR, "dhcp6_start: %s: %m", ifp->name); @@ -861,7 +855,8 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp) continue; } else new++; - len = (new - **var) + strlen(rao->option) + 1; + len = (new - **var) + + strlen(rao->option) + 1; if (len > strlen(**var)) new = realloc(**var, len); else @@ -870,9 +865,11 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp) **var = new; new = strchr(**var, '='); if (new) - strcpy(new + 1, rao->option); + strcpy(new + 1, + rao->option); else - syslog(LOG_ERR, "new is null"); + syslog(LOG_ERR, + "new is null"); } continue; } @@ -901,6 +898,34 @@ ipv6rs_env(char **env, const char *prefix, const struct interface *ifp) return l; } +const struct ipv6_addr * +ipv6rs_findprefix(const struct ipv6_addr *a) +{ + const struct ra *rap; + const struct ipv6_addr *ap; + const struct in6_addr *p1, *p2; + int bytelen, bitlen; + + p1 = &a->addr; + TAILQ_FOREACH(rap, &ipv6_routers, next) { + TAILQ_FOREACH(ap, &rap->addrs, next) { + p2 = & ap->prefix; + bytelen = ap->prefix_len / NBBY; + bitlen = ap->prefix_len % NBBY; + if (memcmp(&p1->s6_addr, &p2->s6_addr, bytelen)) + continue; + if (bitlen != 0) { + bitlen = NBBY - bitlen; + if (p1->s6_addr[bytelen] >> bitlen != + p2->s6_addr[bytelen] >> bitlen) + continue; + } + return ap; + } + } + return NULL; +} + static const struct ipv6_addr * ipv6rs_findsameaddr(const struct ipv6_addr *ap) { @@ -1017,7 +1042,7 @@ ipv6rs_expire(void *arg) if (timerisset(&next)) add_timeout_tv(&next, ipv6rs_expire, ifp); if (expired) { - ipv6_build_routes(); + ipv6_buildroutes(); run_script_reason(ifp, "ROUTERADVERT"); } } @@ -1065,7 +1090,7 @@ ipv6rs_drop(struct interface *ifp) } } if (expired) { - ipv6_build_routes(); + ipv6_buildroutes(); TAILQ_FOREACH_SAFE(rap, &ipv6_routers, next, ran) { if (rap->iface == ifp) { TAILQ_CONCAT(&rap->addrs, &addrs, next); diff --git a/ipv6rs.h b/ipv6rs.h index 7fa73fde..eeff3ce7 100644 --- a/ipv6rs.h +++ b/ipv6rs.h @@ -55,7 +55,7 @@ struct ra { uint32_t reachable; uint32_t retrans; uint32_t mtu; - TAILQ_HEAD(, ipv6_addr) addrs; + struct ipv6_addrhead addrs; TAILQ_HEAD(, ra_opt) options; unsigned char *ns; @@ -79,6 +79,8 @@ int ipv6rs_open(void); void ipv6rs_handledata(void *); int ipv6rs_start(struct interface *); ssize_t ipv6rs_env(char **, const char *, const struct interface *); +const struct ipv6_addr * ipv6rs_findprefix(const struct ipv6_addr *); +int ipv6rs_addrexists(const struct ipv6_addr *); void ipv6rs_freedrop_ra(struct ra *, int); #define ipv6rs_free_ra(ra) ipv6rs_freedrop_ra((ra), 0) #define ipv6rs_drop_ra(ra) ipv6rs_freedrop_ra((ra), 1)