From: Roy Marples Date: Thu, 14 May 2015 19:46:21 +0000 (+0000) Subject: Handle ND options in the same way we handle DHCP and DHCPv6 options. X-Git-Tag: v6.9.0~19 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2be15e8895120d6d772e0da2463cbf6ab702858b;p=thirdparty%2Fdhcpcd.git Handle ND options in the same way we handle DHCP and DHCPv6 options. --- diff --git a/Makefile b/Makefile index 36ff297a..8b17c39b 100644 --- a/Makefile +++ b/Makefile @@ -85,7 +85,7 @@ CLEANFILES+= dhcpcd-embedded.h dhcpcd-embedded.c dhcpcd-embedded.h: genembedh dhcpcd-definitions.conf dhcpcd-embedded.h.in ${HOST_SH} ${.ALLSRC} $^ > $@ -dhcpcd-embedded.c: genembedc dhcpcd-definitions.conf +dhcpcd-embedded.c: genembedc dhcpcd-definitions.conf dhcpcd-embedded.h ${HOST_SH} ${.ALLSRC} $^ > $@ if-options.c: dhcpcd-embedded.h diff --git a/common.c b/common.c index 71668cb9..5317dcc8 100644 --- a/common.c +++ b/common.c @@ -203,33 +203,44 @@ logger(struct dhcpcd_ctx *ctx, int pri, const char *fmt, ...) ssize_t setvar(struct dhcpcd_ctx *ctx, - char ***e, const char *prefix, const char *var, const char *value) + char **e, const char *prefix, const char *var, const char *value) { size_t len = strlen(var) + strlen(value) + 3; if (prefix) len += strlen(prefix) + 1; - **e = malloc(len); - if (**e == NULL) { + *e = malloc(len); + if (*e == NULL) { logger(ctx, LOG_ERR, "%s: %m", __func__); return -1; } if (prefix) - snprintf(**e, len, "%s_%s=%s", prefix, var, value); + snprintf(*e, len, "%s_%s=%s", prefix, var, value); else - snprintf(**e, len, "%s=%s", var, value); - (*e)++; + snprintf(*e, len, "%s=%s", var, value); + return (ssize_t)len; +} + +ssize_t +addvar(struct dhcpcd_ctx *ctx, + char ***e, const char *prefix, const char *var, const char *value) +{ + ssize_t len; + + len = setvar(ctx, *e, prefix, var, value); + if (len != -1) + (*e)++; return (ssize_t)len; } ssize_t -setvard(struct dhcpcd_ctx *ctx, +addvard(struct dhcpcd_ctx *ctx, char ***e, const char *prefix, const char *var, size_t value) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%zu", value); - return setvar(ctx, e, prefix, var, buffer); + return addvar(ctx, e, prefix, var, buffer); } diff --git a/common.h b/common.h index 9a0c0c15..619277a4 100644 --- a/common.h +++ b/common.h @@ -184,8 +184,10 @@ void logger_close(struct dhcpcd_ctx *); #endif ssize_t setvar(struct dhcpcd_ctx *, + char **, const char *, const char *, const char *); +ssize_t addvar(struct dhcpcd_ctx *, char ***, const char *, const char *, const char *); -ssize_t setvard(struct dhcpcd_ctx *, +ssize_t addvard(struct dhcpcd_ctx *, char ***, const char *, const char *, size_t); time_t uptime(void); diff --git a/dhcp.c b/dhcp.c index 4588d090..3fb9ddd3 100644 --- a/dhcp.c +++ b/dhcp.c @@ -1274,35 +1274,35 @@ dhcp_env(char **env, const char *prefix, const struct dhcp_message *dhcp, /* Set some useful variables that we derive from the DHCP * message but are not necessarily in the options */ addr.s_addr = dhcp->yiaddr ? dhcp->yiaddr : dhcp->ciaddr; - setvar(ifp->ctx, &ep, prefix, "ip_address", inet_ntoa(addr)); + addvar(ifp->ctx, &ep, prefix, "ip_address", inet_ntoa(addr)); if (get_option_addr(ifp->ctx, &net, dhcp, DHO_SUBNETMASK) == -1) { net.s_addr = ipv4_getnetmask(addr.s_addr); - setvar(ifp->ctx, &ep, prefix, + addvar(ifp->ctx, &ep, prefix, "subnet_mask", inet_ntoa(net)); } snprintf(cidr, sizeof(cidr), "%d", inet_ntocidr(net)); - setvar(ifp->ctx, &ep, prefix, "subnet_cidr", cidr); + addvar(ifp->ctx, &ep, prefix, "subnet_cidr", cidr); if (get_option_addr(ifp->ctx, &brd, dhcp, DHO_BROADCAST) == -1) { brd.s_addr = addr.s_addr | ~net.s_addr; - setvar(ifp->ctx, &ep, prefix, + addvar(ifp->ctx, &ep, prefix, "broadcast_address", inet_ntoa(brd)); } addr.s_addr = dhcp->yiaddr & net.s_addr; - setvar(ifp->ctx, &ep, prefix, + addvar(ifp->ctx, &ep, prefix, "network_number", inet_ntoa(addr)); } if (*dhcp->bootfile && !(overl & 1)) { print_string(safe, sizeof(safe), STRING, dhcp->bootfile, sizeof(dhcp->bootfile)); - setvar(ifp->ctx, &ep, prefix, "filename", safe); + addvar(ifp->ctx, &ep, prefix, "filename", safe); } if (*dhcp->servername && !(overl & 2)) { print_string(safe, sizeof(safe), STRING | DOMAIN, dhcp->servername, sizeof(dhcp->servername)); - setvar(ifp->ctx, &ep, prefix, "server_name", safe); + addvar(ifp->ctx, &ep, prefix, "server_name", safe); } /* Zero our indexes */ diff --git a/dhcpcd-definitions.conf b/dhcpcd-definitions.conf index 58eba076..d2991c51 100644 --- a/dhcpcd-definitions.conf +++ b/dhcpcd-definitions.conf @@ -286,6 +286,37 @@ encap 255 flag global # Options 224-254 are reserved for Private Use # Option 255 End +############################################################################## +# ND6 options, RFC4861 +definend 1 binhex source_address +definend 2 binhex target_address + +# FIXME: L and A flag need to be extracted from reserved1 +definend 3 index embed prefix_information +embed byte length +embed byte reserved1 +embed uint32 vltime +embed uint32 pltime +embed uint32 reserved2 +embed array ip6address prefix + +# option 4 is only for Redirect messages + +definend 5 embed mtu +embed uint16 reserved +embed uint32 mtu + +# ND6 options, RFC6101 +definend 25 index embed rdnss +embed uint16 reserved +embed uint32 lifetime +embed array ip6address servers + +definend 31 index embed dnssl +embed uint16 reserved +embed uint32 lifetime +embed domain search + ############################################################################## # DHCPv6 options, RFC3315 define6 1 binhex client_id diff --git a/dhcpcd-embedded.h.in b/dhcpcd-embedded.h.in index 3d153163..5e5096e5 100644 --- a/dhcpcd-embedded.h.in +++ b/dhcpcd-embedded.h.in @@ -26,6 +26,7 @@ */ #define INITDEFINES @INITDEFINES@ +#define INITDEFINENDS @INITDEFINENDS@ #define INITDEFINE6S @INITDEFINE6S@ extern const char * const dhcpcd_embedded_conf[]; diff --git a/dhcpcd-hooks/20-resolv.conf b/dhcpcd-hooks/20-resolv.conf index c9f7bedb..e010ab7c 100644 --- a/dhcpcd-hooks/20-resolv.conf +++ b/dhcpcd-hooks/20-resolv.conf @@ -70,15 +70,32 @@ build_resolv_conf() add_resolv_conf() { - local x= conf="$signature$NL" i=${ra_count:-0} ra= warn=true - - while [ $i -ne 0 ]; do - eval ra=\$ra${i}_rdnss - new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$ra" - eval ra=\$ra${i}_dnssl - new_domain_search="$new_domain_search${new_domain_search:+ }$ra" - i=$(($i - 1)) + local x= conf="$signature$NL" warn=true + local i j rdnss dnssl new_rdnss new_dnssl + + # Extract any ND DNS options from the RA + i=1 + j=1 + while true; do + while true; do + eval rdnss=\$nd${i}_rdnss${j}_servers + eval dnssl=\$nd${i}_dnssl${j}_search + [ -z "$rdnss" -a -z "$dnssl" ] && break + new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" + new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" + j=$(($j + 1)) + done + i=$(($i + 1)) + j=1 + eval rdnss=\$nd${i}_rdnss${j}_servers + eval dnssl=\$nd${i}_dnssl${j}_search + [ -z "$rdnss" -a -z "$dnssl" ] && break + new_rdnss="$new_rdnss${new_rdnss:+ }$rdnss" + new_dnssl="$new_dnssl${new_dnssl:+ }$dnssl" + j=$(($j + 1)) done + new_domain_name_servers="$new_domain_name_servers${new_domain_name_servers:+ }$new_rdnss" + new_domain_search="$new_domain_search${new_domain_search:+ }$new_dnssl" # If we don't have any configuration, remove it if [ -z "$new_domain_name_servers" -a \ diff --git a/dhcpcd.c b/dhcpcd.c index 27c5ac91..84036264 100644 --- a/dhcpcd.c +++ b/dhcpcd.c @@ -164,6 +164,14 @@ free_globals(struct dhcpcd_ctx *ctx) } #endif #ifdef INET6 + if (ctx->nd_opts) { + for (opt = ctx->nd_opts; + ctx->nd_opts_len > 0; + opt++, ctx->nd_opts_len--) + free_dhcp_opt_embenc(opt); + free(ctx->nd_opts); + ctx->nd_opts = NULL; + } if (ctx->dhcp6_opts) { for (opt = ctx->dhcp6_opts; ctx->dhcp6_opts_len > 0; @@ -1454,6 +1462,9 @@ main(int argc, char **argv) #endif #ifdef INET6 if (family == 0 || family == AF_INET6) { + printf("\nND options:\n"); + ipv6nd_printoptions(&ctx, + ifo->nd_override, ifo->nd_override_len); printf("\nDHCPv6 options:\n"); dhcp6_printoptions(&ctx, ifo->dhcp6_override, ifo->dhcp6_override_len); diff --git a/dhcpcd.conf.5.in b/dhcpcd.conf.5.in index 905a924f..ab072da7 100644 --- a/dhcpcd.conf.5.in +++ b/dhcpcd.conf.5.in @@ -22,7 +22,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd April 6, 2015 +.Dd May 14, 2015 .Dt DHCPCD.CONF 5 .Os .Sh NAME @@ -462,14 +462,39 @@ You can specify more separated by commas, spaces or more .Ic option lines. +.Ar option Prepend dhcp6_ to .Ar option to request a DHCPv6 option. DHCPv4 options are mapped to DHCPv6 where applicable. +.Pp +Prepend nd_ to +.Ar option +to handle ND options, but this only works for the +.Ic nooption , +.Ic reject +and +.Ic require +options. .It Ic nooption Ar option -Remove the option from the DHCP message. -This should only be used when a DHCP server sends a non requested option -that should not be processed. +Remove the option from the message before it's processed. +.It Ic require Ar option +Requires the +.Ar option +to be present in all messages, otherwise the message is ignored. +To enforce that +.Nm dhcpcd +only responds to DHCP servers and not BOOTP servers, you can +.Ic require +.Ar dhcp_message_type . +This isn't an exact science though because a BOOTP server can send DHCP like +options. +.It Ic reject Ar option +Reject a message that contains the +.Ar option . +This is useful when you cannot use +.Ic require +to select / de-select BOOTP messages. .It Ic destination Ar option If .Nm @@ -499,27 +524,6 @@ will timeout before moving back to the DISCOVER phase. .It Ic release .Nm dhcpcd will release the lease prior to stopping the interface. -.It Ic require Ar option -Requires the -.Ar option -to be present in all DHCP messages, otherwise the message is ignored. -It can be a variable to be used in -.Xr dhcpcd-run-hooks 8 -or the numerical value. -You can specify more options separated by commas, spaces or more require lines. -To enforce that -.Nm dhcpcd -only responds to DHCP servers and not BOOTP servers, you can -.Ic require -.Ar dhcp_message_type . -This isn't an exact science though because a BOOTP server can send DHCP like -options. -.It Ic reject Ar option -Reject a DHCP message that contains the -.Ar option . -This is useful when you cannot use -.Ic require -to select / de-select BOOTP messages. .It Ic script Ar script Use .Ar script @@ -630,9 +634,10 @@ Use the last four bytes of the hardware address as the DHCP xid instead of a randomly generated number. .El .Ss Defining new options -DHCP allows for the use of custom options. +DHCP, ND and DHCPv6 allow for the use of custom options. Each option needs to be started with the -.Ic define +.Ic define , +.If definend or .Ic define6 directive. @@ -655,6 +660,17 @@ with a name of .Ar variable exported to .Xr dhcpcd-run-hooks 8 . +.It Ic definend Ar code Ar type Ar variable +Defines the ND option +.Ar code +of +.Ar type +with a name of +.Ar variable +exported to +.Xr dhcpcd-run-hooks 8 , +with a prefix of +.Va _nd . .It Ic define6 Ar code Ar type Ar variable Defines the DHCPv6 option .Ar code diff --git a/dhcpcd.h b/dhcpcd.h index d89bbffc..6d7223ed 100644 --- a/dhcpcd.h +++ b/dhcpcd.h @@ -135,6 +135,8 @@ struct dhcpcd_ctx { unsigned char secret[SECRET_LEN]; size_t secret_len; + struct dhcp_opt *nd_opts; + size_t nd_opts_len; struct dhcp_opt *dhcp6_opts; size_t dhcp6_opts_len; struct ipv6_ctx *ipv6; diff --git a/genembedh b/genembedh index a155cfae..cd97cbb4 100755 --- a/genembedh +++ b/genembedh @@ -8,8 +8,10 @@ CONF=${1:-dhcpcd-definitions.conf} H=${2:-dhcpcd-embedded.h.in} INITDEFINES=$($TOOL_GREP "^define " $CONF | $TOOL_WC -l) +INITDEFINENDS=$($TOOL_GREP "^definend " $CONF | $TOOL_WC -l) INITDEFINE6S=$($TOOL_GREP "^define6 " $CONF | $TOOL_WC -l) $TOOL_SED \ -e "s/@INITDEFINES@/$INITDEFINES/" \ + -e "s/@INITDEFINENDS@/$INITDEFINENDS/" \ -e "s/@INITDEFINE6S@/$INITDEFINE6S/" \ $H diff --git a/if-options.c b/if-options.c index 85b6e8e5..8bb1185b 100644 --- a/if-options.c +++ b/if-options.c @@ -100,6 +100,7 @@ #define O_REJECT O_BASE + 40 #define O_IPV6RA_ACCEPT_NOPUBLIC O_BASE + 41 #define O_BOOTP O_BASE + 42 +#define O_DEFINEND O_BASE + 43 const struct option cf_options[] = { {"background", no_argument, NULL, 'b'}, @@ -175,6 +176,7 @@ const struct option cf_options[] = { {"dev", required_argument, NULL, O_DEV}, {"nodev", no_argument, NULL, O_NODEV}, {"define", required_argument, NULL, O_DEFINE}, + {"definend", required_argument, NULL, O_DEFINEND}, {"define6", required_argument, NULL, O_DEFINE6}, {"embed", required_argument, NULL, O_EMBED}, {"encap", required_argument, NULL, O_ENCAP}, @@ -519,11 +521,22 @@ set_option_space(struct dhcpcd_ctx *ctx, { #if !defined(INET) && !defined(INET6) - /* Satisfy use */ - ctx = NULL; + UNUSED(ctx); #endif #ifdef INET6 + if (strncmp(arg, "nd_", strlen("nd_")) == 0) { + *d = ctx->nd_opts; + *dl = ctx->nd_opts_len; + *od = ifo->nd_override; + *odl = ifo->nd_override_len; + *request = ifo->requestmasknd; + *require = ifo->requiremasknd; + *no = ifo->nomasknd; + *reject = ifo->rejectmasknd; + return arg + strlen("nd_"); + } + if (strncmp(arg, "dhcp6_", strlen("dhcp6_")) == 0) { *d = ctx->dhcp6_opts; *dl = ctx->dhcp6_opts_len; @@ -1466,6 +1479,12 @@ err_sla: dop = &ifo->dhcp_override; dop_len = &ifo->dhcp_override_len; /* FALLTHROUGH */ + case O_DEFINEND: + if (dop == NULL) { + dop = &ifo->nd_override; + dop_len = &ifo->nd_override_len; + } + /* FALLTHROUGH */ case O_DEFINE6: if (dop == NULL) { dop = &ifo->dhcp6_override; @@ -1689,10 +1708,17 @@ err_sla: ndop->len = (size_t)l; ndop->var = np; /* Save the define for embed and encap options */ - if (opt == O_DEFINE || opt == O_DEFINE6 || opt == O_VENDOPT) + switch (opt) { + case O_DEFINE: + case O_DEFINEND: + case O_DEFINE6: + case O_VENDOPT: *ldop = ndop; - else if (opt == O_ENCAP) + break; + case O_ENCAP: *edop = ndop; + break; + } break; case O_VENDCLASS: fp = strwhite(arg); @@ -2100,6 +2126,14 @@ read_config(struct dhcpcd_ctx *ctx, ifo->dhcp_override_len = INITDEFINES; #endif +#if defined(INET6) && defined(INITDEFINENDS) + ifo->nd_override = + calloc(INITDEFINENDS, sizeof(*ifo->nd_override)); + if (ifo->nd_override == NULL) + logger(ctx, LOG_ERR, "%s: %m", __func__); + else + ifo->nd_override_len = INITDEFINENDS; +#endif #if defined(INET6) && defined(INITDEFINE6S) ifo->dhcp6_override = calloc(INITDEFINE6S, sizeof(*ifo->dhcp6_override)); @@ -2172,15 +2206,24 @@ read_config(struct dhcpcd_ctx *ctx, ifo->dhcp_override_len = 0; #ifdef INET6 + ctx->nd_opts = ifo->nd_override; + ctx->nd_opts_len = ifo->nd_override_len; ctx->dhcp6_opts = ifo->dhcp6_override; ctx->dhcp6_opts_len = ifo->dhcp6_override_len; #else + for (i = 0, opt = ifo->nd_override; + i < ifo->nd_override_len; + i++, opt++) + free_dhcp_opt_embenc(opt); + free(ifo->nd_override); for (i = 0, opt = ifo->dhcp6_override; i < ifo->dhcp6_override_len; i++, opt++) free_dhcp_opt_embenc(opt); free(ifo->dhcp6_override); #endif + ifo->nd_override = NULL; + ifo->nd_override_len = 0; ifo->dhcp6_override = NULL; ifo->dhcp6_override_len = 0; @@ -2334,6 +2377,11 @@ free_options(struct if_options *ifo) opt++, ifo->dhcp_override_len--) free_dhcp_opt_embenc(opt); free(ifo->dhcp_override); + for (opt = ifo->nd_override; + ifo->nd_override_len > 0; + opt++, ifo->nd_override_len--) + free_dhcp_opt_embenc(opt); + free(ifo->nd_override); for (opt = ifo->dhcp6_override; ifo->dhcp6_override_len > 0; opt++, ifo->dhcp6_override_len--) diff --git a/if-options.h b/if-options.h index 4813528f..a84de5f6 100644 --- a/if-options.h +++ b/if-options.h @@ -153,11 +153,15 @@ struct if_options { uint8_t requiremask[256 / NBBY]; uint8_t nomask[256 / NBBY]; uint8_t rejectmask[256 / NBBY]; + uint8_t dstmask[256 / NBBY]; + uint8_t requestmasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t requiremasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t nomasknd[(UINT16_MAX + 1) / NBBY]; + uint8_t rejectmasknd[(UINT16_MAX + 1) / NBBY]; uint8_t requestmask6[(UINT16_MAX + 1) / NBBY]; uint8_t requiremask6[(UINT16_MAX + 1) / NBBY]; uint8_t nomask6[(UINT16_MAX + 1) / NBBY]; uint8_t rejectmask6[(UINT16_MAX + 1) / NBBY]; - uint8_t dstmask[256 / NBBY]; uint32_t leasetime; time_t timeout; time_t reboot; @@ -191,6 +195,8 @@ struct if_options { struct dhcp_opt *dhcp_override; size_t dhcp_override_len; + struct dhcp_opt *nd_override; + size_t nd_override_len; struct dhcp_opt *dhcp6_override; size_t dhcp6_override_len; uint32_t vivco_en; diff --git a/ipv6.c b/ipv6.c index b3a3a5f2..1e6778f1 100644 --- a/ipv6.c +++ b/ipv6.c @@ -1580,9 +1580,7 @@ again: cbp = inet_ntop(AF_INET6, &ia->addr, buf, sizeof(buf)); if (cbp) snprintf(ia->saddr, sizeof(ia->saddr), "%s/%d", - cbp, ia->prefix_len); - else - ia->saddr[0] = '\0'; + cbp, ia->prefix_len); else ia->saddr[0] = '\0'; TAILQ_INSERT_TAIL(&state->addrs, ia, next); return ia; 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, <); - 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. */ diff --git a/ipv6nd.h b/ipv6nd.h index 2ab76df3..cc4b67d7 100644 --- a/ipv6nd.h +++ b/ipv6nd.h @@ -34,13 +34,6 @@ #include "dhcpcd.h" #include "ipv6.h" -struct ra_opt { - TAILQ_ENTRY(ra_opt) next; - uint16_t type; - struct timespec expire; - char *option; -}; - struct ra { TAILQ_ENTRY(ra) next; struct interface *iface; @@ -55,7 +48,7 @@ struct ra { uint32_t retrans; uint32_t mtu; struct ipv6_addrhead addrs; - TAILQ_HEAD(, ra_opt) options; + uint8_t hasdns; uint8_t expired; uint8_t no_public_warned; }; @@ -71,6 +64,15 @@ struct rs_state { #define RS_STATE(a) ((struct rs_state *)(ifp)->if_data[IF_DATA_IPV6ND]) #define RS_STATE_RUNNING(a) (ipv6nd_hasra((a)) && ipv6nd_dadcompleted((a))) +#define ND_CFIRST_OPTION(m) \ + ((const struct nd_opt_hdr *) \ + ((const uint8_t *)(m)->data + sizeof(struct nd_router_advert))) +#define ND_OPTION_LEN(o) (((o)->nd_opt_len * 8) - sizeof(struct nd_opt_hdr)) +#define ND_CNEXT_OPTION(o) \ + ((const struct nd_opt_hdr *)((const uint8_t *)(o) + ((o)->nd_opt_len * 8))) +#define ND_COPTION_DATA(o) \ + ((const uint8_t *)(o) + sizeof(struct nd_opt_hdr)) + #define MAX_RTR_SOLICITATION_DELAY 1 /* seconds */ #define MAX_UNICAST_SOLICIT 3 /* 3 transmissions */ #define RTR_SOLICITATION_INTERVAL 4 /* seconds */ @@ -91,6 +93,8 @@ struct rs_state { #define IPV6ND_ROUTER (1 << 1) #ifdef INET6 +void ipv6nd_printoptions(const struct dhcpcd_ctx *, + const struct dhcp_opt *, size_t); void ipv6nd_startrs(struct interface *); ssize_t ipv6nd_env(char **, const char *, const struct interface *); struct ipv6_addr *ipv6nd_findaddr(struct dhcpcd_ctx *,