diff options
| author | Roy Marples <roy@marples.name> | 2015-05-14 19:46:21 +0000 |
|---|---|---|
| committer | Roy Marples <roy@marples.name> | 2015-05-14 19:46:21 +0000 |
| commit | 2be15e8895120d6d772e0da2463cbf6ab702858b (patch) | |
| tree | e3d5610e32ea05dda8825fba7b22d02a5d23fb4a /ipv6nd.c | |
| parent | 8c82f2e4007bdf896ebab0ddc39ddb45b26f41f3 (diff) | |
| download | dhcpcd-2be15e8895120d6d772e0da2463cbf6ab702858b.tar.xz | |
Handle ND options in the same way we handle DHCP and DHCPv6 options.
Diffstat (limited to 'ipv6nd.c')
| -rw-r--r-- | ipv6nd.c | 515 |
1 files changed, 213 insertions, 302 deletions
@@ -154,6 +154,31 @@ static void ipv6nd_handledata(void *); #define IPV6_RECVPKTINFO IPV6_PKTINFO #endif +void +ipv6nd_printoptions(const struct dhcpcd_ctx *ctx, + const struct dhcp_opt *opts, size_t opts_len) +{ + size_t i, j; + const struct dhcp_opt *opt, *opt2; + int cols; + + for (i = 0, opt = ctx->nd_opts; + i < ctx->nd_opts_len; i++, opt++) + { + for (j = 0, opt2 = opts; j < opts_len; j++, opt2++) + if (opt2->option == opt->option) + break; + if (j == opts_len) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } + } + for (i = 0, opt = opts; i < opts_len; i++, opt++) { + cols = printf("%03d %s", opt->option, opt->var); + dhcp_print_option_encoding(opt, cols); + } +} + static int ipv6nd_open(struct dhcpcd_ctx *dctx) { @@ -332,7 +357,6 @@ ipv6nd_expire(struct interface *ifp, uint32_t seconds) rap->received = now; rap->expired = seconds ? 0 : 1; if (seconds) { - struct ra_opt *rao; struct ipv6_addr *ap; rap->lifetime = seconds; @@ -343,9 +367,6 @@ ipv6nd_expire(struct interface *ifp, uint32_t seconds) } } ipv6_addaddrs(&rap->addrs); - TAILQ_FOREACH(rao, &rap->options, next) { - timespecclear(&rao->expire); - } } } } @@ -397,18 +418,6 @@ ipv6nd_neighbour(struct dhcpcd_ctx *ctx, struct in6_addr *addr, int flags) } } -static void -ipv6nd_free_opts(struct ra *rap) -{ - struct ra_opt *rao; - - while ((rao = TAILQ_FIRST(&rap->options))) { - TAILQ_REMOVE(&rap->options, rao, next); - free(rao->option); - free(rao); - } -} - struct ipv6_addr * ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, short flags) @@ -435,19 +444,26 @@ ipv6nd_findaddr(struct dhcpcd_ctx *ctx, const struct in6_addr *addr, return NULL; } -void ipv6nd_freedrop_ra(struct ra *rap, int drop) +static void +ipv6nd_removefreedrop_ra(struct ra *rap, int remove, int drop) { eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap->iface); eloop_timeout_delete(rap->iface->ctx->eloop, NULL, rap); - if (!drop) + if (remove && !drop) TAILQ_REMOVE(rap->iface->ctx->ipv6->ra_routers, rap, next); ipv6_freedrop_addrs(&rap->addrs, drop, NULL); - ipv6nd_free_opts(rap); free(rap->data); free(rap); } +void +ipv6nd_freedrop_ra(struct ra *rap, int drop) +{ + + ipv6nd_removefreedrop_ra(rap, 1, drop); +} + ssize_t ipv6nd_free(struct interface *ifp) { @@ -531,7 +547,6 @@ ipv6nd_scriptrun(struct ra *rap) { int hasdns, hasaddress, pid; struct ipv6_addr *ap; - const struct ra_opt *rao; hasaddress = 0; /* If all addresses have completed DAD run the script */ @@ -557,16 +572,7 @@ ipv6nd_scriptrun(struct ra *rap) 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 && - timespecisset(&rao->expire)) - { - hasdns = 1; - break; - } - } + hasdns = rap->hasdns; } script_runreason(rap->iface, "ROUTERADVERT"); @@ -729,24 +735,20 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, struct icmp6_hdr *icp, size_t len) { struct ipv6_ctx *ctx = dctx->ipv6; - size_t olen, l, n; - ssize_t r; + size_t i, olen; struct nd_router_advert *nd_ra; struct nd_opt_prefix_info *pi; struct nd_opt_mtu *mtu; struct nd_opt_rdnss *rdnss; - struct nd_opt_dnssl *dnssl; uint32_t lifetime, mtuv; - uint8_t *p, *op; - struct in6_addr addr; + uint8_t *p; char buf[INET6_ADDRSTRLEN]; const char *cbp; struct ra *rap; struct nd_opt_hdr *ndo; - struct ra_opt *rao; struct ipv6_addr *ap; - char *opt, *opt2, *tmp; - struct timespec expire; + char *opt, *opt2; + struct dhcp_opt *dho; uint8_t new_rap, new_data; #ifdef IPV6_MANAGETEMPADDR uint8_t new_ap; @@ -830,7 +832,6 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, rap->from = ctx->from.sin6_addr; strlcpy(rap->sfrom, ctx->sfrom, sizeof(rap->sfrom)); TAILQ_INIT(&rap->addrs); - TAILQ_INIT(&rap->options); new_rap = 1; } else new_rap = 0; @@ -861,6 +862,7 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, rap->retrans = ntohl(nd_ra->nd_ra_retransmit); if (rap->lifetime) rap->expired = 0; + rap->hasdns = 0; ipv6_settempstale(ifp); TAILQ_FOREACH(ap, &rap->addrs, next) { @@ -889,6 +891,34 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, break; } + if (has_option_mask(ifp->options->rejectmasknd, + ndo->nd_opt_type)) + { + for (i = 0, dho = dctx->nd_opts; + i < dctx->nd_opts_len; + i++, dho++) + { + if (dho->option == ndo->nd_opt_type) + break; + } + if (dho != NULL) + logger(ifp->ctx, LOG_WARNING, + "%s: reject RA (option %s) from %s", + ifp->name, dho->var, ctx->sfrom); + else + logger(ifp->ctx, LOG_WARNING, + "%s: reject RA (option %d) from %s", + ifp->name, ndo->nd_opt_type, ctx->sfrom); + if (new_rap) + ipv6nd_removefreedrop_ra(rap, 0, 0); + else + ipv6nd_free_ra(rap); + return; + } + + if (has_option_mask(ifp->options->nomasknd, ndo->nd_opt_type)) + continue; + opt = opt2 = NULL; switch (ndo->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: @@ -996,16 +1026,6 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, ap->prefix_pltime = ntohl(pi->nd_opt_pi_preferred_time); ap->nsprobes = 0; - cbp = inet_ntop(AF_INET6, &ap->prefix, buf, sizeof(buf)); - if (cbp) { - l = strlen(cbp); - opt = malloc(l + 5); - if (opt) { - snprintf(opt, l + 5, "%s/%d", cbp, - ap->prefix_len); - opt2 = strdup(ap->saddr); - } - } #ifdef IPV6_MANAGETEMPADDR /* RFC4941 Section 3.3.3 */ @@ -1038,140 +1058,40 @@ ipv6nd_handlera(struct dhcpcd_ctx *dctx, struct interface *ifp, break; } rap->mtu = mtuv; - snprintf(buf, sizeof(buf), "%d", mtuv); - opt = strdup(buf); break; case ND_OPT_RDNSS: - rdnss = (struct nd_opt_rdnss *)p; - lifetime = ntohl(rdnss->nd_opt_rdnss_lifetime); - op = (uint8_t *)ndo; - op += offsetof(struct nd_opt_rdnss, - nd_opt_rdnss_lifetime); - op += sizeof(rdnss->nd_opt_rdnss_lifetime); - l = 0; - for (n = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, - op += sizeof(addr)) - { - r = ipv6_printaddr(NULL, 0, op, ifp->name); - if (r != -1) - l += (size_t)r + 1; - } - op = (uint8_t *)ndo; - op += offsetof(struct nd_opt_rdnss, - nd_opt_rdnss_lifetime); - op += sizeof(rdnss->nd_opt_rdnss_lifetime); - tmp = opt = malloc(l); - if (opt == NULL) - continue; - for (n = (size_t)ndo->nd_opt_len - 1; n > 1; n -= 2, - op += sizeof(addr)) - { - r = ipv6_printaddr(tmp, l, op, - ifp->name); - if (r != -1) { - l -= ((size_t)r + 1); - tmp += (size_t)r; - *tmp++ = ' '; - } - } - if (tmp != opt) - (*--tmp) = '\0'; - else - *opt = '\0'; - break; - - case ND_OPT_DNSSL: - dnssl = (struct nd_opt_dnssl *)p; - lifetime = ntohl(dnssl->nd_opt_dnssl_lifetime); - op = p + offsetof(struct nd_opt_dnssl, - nd_opt_dnssl_lifetime); - op += sizeof(dnssl->nd_opt_dnssl_lifetime); - n = (size_t)(dnssl->nd_opt_dnssl_len - 1) * 8; - r = decode_rfc3397(NULL, 0, op, n); - if (r < 1) { - logger(ifp->ctx, new_data ? LOG_ERR : LOG_DEBUG, - "%s: invalid DNSSL option", - ifp->name); - continue; - } else { - l = (size_t)r + 1; - tmp = malloc(l); - if (tmp) { - decode_rfc3397(tmp, l, op, n); - l -= 1; - n = (size_t)print_string(NULL, 0, - STRING | ARRAY | DOMAIN, - (const uint8_t *)tmp, l); - n++; - opt = malloc(n); - if (opt) { - print_string(opt, n, - STRING | ARRAY | DOMAIN, - (const uint8_t *)tmp, l); - } else - logger(ifp->ctx, LOG_ERR, - "%s: %m", __func__); - free(tmp); - } - } - break; + rdnss = (struct nd_opt_rdnss *)(void *)p; + if (rdnss->nd_opt_rdnss_lifetime && + rdnss->nd_opt_rdnss_len > 1) + rap->hasdns = 1; default: continue; } + } - if (opt == NULL) { - logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); - continue; - } - - n = ndo->nd_opt_type; -extra_opt: - TAILQ_FOREACH(rao, &rap->options, next) { - if (rao->type == n && - strcmp(rao->option, opt) == 0) - break; - } - if (lifetime == 0 || *opt == '\0') { - if (rao) { - TAILQ_REMOVE(&rap->options, rao, next); - free(rao->option); - free(rao); - } - free(opt); - free(opt2); - continue; - } - - if (rao == NULL) { - rao = malloc(sizeof(*rao)); - if (rao == NULL) { - logger(ifp->ctx, LOG_ERR, "%s: %m", __func__); - continue; - } - rao->type = (uint16_t)n; - rao->option = opt; - TAILQ_INSERT_TAIL(&rap->options, rao, next); - } else - free(opt); - if (lifetime == ~0U) - timespecclear(&rao->expire); - else { - expire.tv_sec = (time_t)lifetime; - expire.tv_nsec = 0; - timespecadd(&rap->received, &expire, &rao->expire); - } - if (rao && rao->type == ND_OPT_PREFIX_INFORMATION && opt2) { - n = _ND_OPT_PREFIX_ADDR; - opt = opt2; - opt2 = NULL; - goto extra_opt; + for (i = 0, dho = dctx->nd_opts; + i < dctx->nd_opts_len; + i++, dho++) + { + if (has_option_mask(ifp->options->requiremasknd, + dho->option)) + { + logger(ifp->ctx, LOG_WARNING, + "%s: reject RA (no option %s) from %s", + ifp->name, dho->var, ctx->sfrom); + if (new_rap) + ipv6nd_removefreedrop_ra(rap, 0, 0); + else + ipv6nd_free_ra(rap); + return; } } if (new_rap) add_router(ifp->ctx->ipv6, rap); + if (!ipv6nd_ra_has_public_addr(rap) && !(rap->iface->options->options & DHCPCD_IPV6RA_ACCEPT_NOPUBLIC) && (!(rap->flags & ND_RA_FLAG_MANAGED) || @@ -1289,119 +1209,144 @@ ipv6nd_hasradhcp(const struct interface *ifp) return 0; } +static const uint8_t * +ipv6nd_getoption(struct dhcpcd_ctx *ctx, + size_t *os, unsigned int *code, size_t *len, + const uint8_t *od, size_t ol, struct dhcp_opt **oopt) +{ + const struct nd_opt_hdr *o; + size_t i; + struct dhcp_opt *opt; + + if (od) { + *os = sizeof(*o); + if (ol < *os) { + errno = EINVAL; + return NULL; + } + o = (const struct nd_opt_hdr *)od; + if (o->nd_opt_len > ol) { + errno = EINVAL; + return NULL; + } + *len = (o->nd_opt_len * 8) - sizeof(*o); + *code = o->nd_opt_type; + } else + o = NULL; + + for (i = 0, opt = ctx->nd_opts; + i < ctx->nd_opts_len; i++, opt++) + { + if (opt->option == *code) { + *oopt = opt; + break; + } + } + + if (o) + return ND_COPTION_DATA(o); + return NULL; +} + ssize_t ipv6nd_env(char **env, const char *prefix, const struct interface *ifp) { - size_t i, l, len; - const struct ra *rap; - const struct ra_opt *rao; - char buffer[32]; - const char *optn; - char **pref, **addr, **mtu, **rdnss, **dnssl, ***var, *new; + size_t i, j, n, len; + struct ra *rap; + char ndprefix[32], abuf[24]; + struct dhcp_opt *opt; + const struct nd_opt_hdr *o; + struct ipv6_addr *ia; - i = l = 0; + i = n = 0; TAILQ_FOREACH(rap, ifp->ctx->ipv6->ra_routers, next) { if (rap->iface != ifp) continue; i++; + if (prefix != NULL) + snprintf(ndprefix, sizeof(ndprefix), + "%s_nd%zu", prefix, i); + else + snprintf(ndprefix, sizeof(ndprefix), + "nd%zu", i); + if (env) + setvar(rap->iface->ctx, &env[n], ndprefix, + "from", rap->sfrom); + n++; + + /* Zero our indexes */ if (env) { - snprintf(buffer, sizeof(buffer), - "ra%zu_from", i); - setvar(ifp->ctx, &env, prefix, buffer, rap->sfrom); + for (j = 0, opt = rap->iface->ctx->nd_opts; + j < rap->iface->ctx->nd_opts_len; + j++, opt++) + dhcp_zero_index(opt); + for (j = 0, opt = rap->iface->options->nd_override; + j < rap->iface->options->nd_override_len; + j++, opt++) + dhcp_zero_index(opt); } - l++; - pref = addr = mtu = rdnss = dnssl = NULL; - TAILQ_FOREACH(rao, &rap->options, next) { - if (rao->option == NULL) - continue; - var = NULL; - switch(rao->type) { - case ND_OPT_PREFIX_INFORMATION: - optn = "prefix"; - var = &pref; - break; - case _ND_OPT_PREFIX_ADDR: - optn = "addr"; - var = &addr; - break; - case ND_OPT_MTU: - optn = "mtu"; - var = &mtu; - break; - case ND_OPT_RDNSS: - optn = "rdnss"; - var = &rdnss; - break; - case ND_OPT_DNSSL: - optn = "dnssl"; - var = &dnssl; + /* Unlike DHCP, ND6 options *may* occur more than once. + * There is also no provision for option concatenation + * unlike DHCP. */ + len = rap->data_len; + if (ND_CFIRST_OPTION(rap)) + len -= (size_t)((const uint8_t *)ND_CFIRST_OPTION(rap) + - rap->data); + + for (o = ND_CFIRST_OPTION(rap); + len >= (ssize_t)sizeof(*o); + o = ND_CNEXT_OPTION(o)) + { + if (o->nd_opt_len * 8 > len) { + errno = EINVAL; break; - default: - continue; } - if (*var == NULL) { - *var = env ? env : &new; - l++; - } else if (env) { - /* With single only options, last one takes - * precedence */ - if (rao->type == ND_OPT_MTU) { - new = strchr(**var, '='); - if (new == NULL) { - logger(ifp->ctx, LOG_ERR, - "new is null"); - continue; - } else - new++; - len = (size_t)(new - **var) + - strlen(rao->option) + 1; - if (len > strlen(**var)) - new = realloc(**var, len); - else - new = **var; - if (new) { - **var = new; - new = strchr(**var, '='); - if (new) { - len -= - (size_t) - (new - **var); - strlcpy(new + 1, - rao->option, - len - 1); - } else - logger(ifp->ctx, - LOG_ERR, - "new is null"); - } - continue; - } - len = strlen(rao->option) + 1; - new = realloc(**var, strlen(**var) + 1 + len); - if (new) { - **var = new; - new += strlen(new); - *new++ = ' '; - strlcpy(new, rao->option, len); - } else - logger(ifp->ctx, LOG_ERR, - "%s: %m", __func__); + len -= o->nd_opt_len * 8; + if (has_option_mask(rap->iface->options->nomasknd, + o->nd_opt_type)) continue; + for (j = 0, opt = rap->iface->options->nd_override; + j < rap->iface->options->nd_override_len; + j++, opt++) + if (opt->option == o->nd_opt_type) + break; + if (j == rap->iface->options->nd_override_len) { + for (j = 0, opt = rap->iface->ctx->nd_opts; + j < rap->iface->ctx->nd_opts_len; + j++, opt++) + if (opt->option == o->nd_opt_type) + break; + if (j == rap->iface->ctx->nd_opts_len) + opt = NULL; + } + if (opt) { + n += dhcp_envoption(rap->iface->ctx, + env == NULL ? NULL : &env[n], + ndprefix, rap->iface->name, + opt, ipv6nd_getoption, + ND_COPTION_DATA(o), ND_OPTION_LEN(o)); } + } + + /* We need to output the addresses we actually made + * from the prefix information options as well. */ + j = 0; + TAILQ_FOREACH(ia, &rap->addrs, next) { + if (!(ia->flags & IPV6_AF_AUTOCONF) || + ia->flags & IPV6_AF_TEMPORARY) + continue; + j++; if (env) { - snprintf(buffer, sizeof(buffer), - "ra%zu_%s", i, optn); - setvar(ifp->ctx, &env, - prefix, buffer, rao->option); + snprintf(abuf, sizeof(abuf), "addr%zu", j); + setvar(rap->iface->ctx, &env[n], ndprefix, + abuf, ia->saddr); } + n++; } + } - - if (env) - setvard(ifp->ctx, &env, prefix, "ra_count", i); - l++; - return (ssize_t)l; + return (ssize_t)n; } void @@ -1424,7 +1369,6 @@ ipv6nd_expirera(void *arg) { struct interface *ifp; struct ra *rap, *ran; - struct ra_opt *rao, *raon; struct timespec now, lt, expire, next; uint8_t expired, valid, validone; @@ -1460,43 +1404,10 @@ ipv6nd_expirera(void *arg) } } - /* Addresses are expired in ipv6_addaddrs - * so that DHCPv6 addresses can be removed also. */ - TAILQ_FOREACH_SAFE(rao, &rap->options, next, raon) { - if (rap->expired) { - switch(rao->type) { - case ND_OPT_RDNSS: /* FALLTHROUGH */ - case ND_OPT_DNSSL: - /* RFC6018 end of section 5.2 states - * that if tha RA has a lifetime of 0 - * then we should expire these - * options */ - TAILQ_REMOVE(&rap->options, rao, next); - expired = 1; - free(rao->option); - free(rao); - continue; - } - } - if (!timespecisset(&rao->expire)) - continue; - if (timespeccmp(&now, &rao->expire, >)) { - /* Expired prefixes are logged above */ - if (rao->type != ND_OPT_PREFIX_INFORMATION) - logger(ifp->ctx, LOG_WARNING, - "%s: %s: expired option %d", - ifp->name, rap->sfrom, rao->type); - TAILQ_REMOVE(&rap->options, rao, next); - expired = 1; - free(rao->option); - free(rao); - continue; - } - valid = 1; - timespecsub(&rao->expire, &now, <); - if (!timespecisset(&next) || timespeccmp(&next, <, >)) - next = lt; - } + /* XXX FixMe! + * We need to extract the lifetime from each option and check + * if that has expired or not. + * If it has, zero the option out in the returned data. */ /* No valid lifetimes are left on the RA, so we might * as well punt it. */ |
