summaryrefslogtreecommitdiffstats
path: root/ipv6nd.c
diff options
context:
space:
mode:
authorRoy Marples <roy@marples.name>2015-05-14 19:46:21 +0000
committerRoy Marples <roy@marples.name>2015-05-14 19:46:21 +0000
commit2be15e8895120d6d772e0da2463cbf6ab702858b (patch)
treee3d5610e32ea05dda8825fba7b22d02a5d23fb4a /ipv6nd.c
parent8c82f2e4007bdf896ebab0ddc39ddb45b26f41f3 (diff)
downloaddhcpcd-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.c515
1 files changed, 213 insertions, 302 deletions
diff --git a/ipv6nd.c b/ipv6nd.c
index e6ebd7bd..2e2af130 100644
--- a/ipv6nd.c
+++ b/ipv6nd.c
@@ -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, &lt);
- if (!timespecisset(&next) || timespeccmp(&next, &lt, >))
- 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. */