From 4c40b7e8ed0a313f15b06f9a298049cf3842ca3b Mon Sep 17 00:00:00 2001 From: florian Date: Sun, 15 Jul 2018 09:28:21 +0000 Subject: [PATCH] Implement RFC 8106: IPv6 Router Advertisement Options for DNS Configuration. --- usr.sbin/rad/engine.c | 30 +++++++++++-- usr.sbin/rad/frontend.c | 86 ++++++++++++++++++++++++++++++++++- usr.sbin/rad/parse.y | 96 +++++++++++++++++++++++++++++++++++++++- usr.sbin/rad/printconf.c | 28 +++++++++++- usr.sbin/rad/rad.c | 20 ++++++++- usr.sbin/rad/rad.conf.5 | 22 ++++++++- usr.sbin/rad/rad.h | 35 ++++++++++++--- 7 files changed, 300 insertions(+), 17 deletions(-) diff --git a/usr.sbin/rad/engine.c b/usr.sbin/rad/engine.c index 9ecb93da44b..5d186439dd8 100644 --- a/usr.sbin/rad/engine.c +++ b/usr.sbin/rad/engine.c @@ -1,4 +1,4 @@ -/* $OpenBSD: engine.c,v 1.6 2018/07/11 19:05:25 florian Exp $ */ +/* $OpenBSD: engine.c,v 1.7 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -43,9 +43,6 @@ #include "rad.h" #include "engine.h" -#define MAX_RTR_ADV_INTERVAL 600 -#define MIN_RTR_ADV_INTERVAL 200 - struct engine_iface { TAILQ_ENTRY(engine_iface) entry; struct event timer; @@ -267,6 +264,8 @@ engine_dispatch_main(int fd, short event, void *bula) struct imsgev *iev = bula; struct imsgbuf *ibuf; struct ra_prefix_conf *ra_prefix_conf; + struct ra_rdnss_conf *ra_rdnss_conf; + struct ra_dnssl_conf *ra_dnssl_conf; ssize_t n; int shut = 0; @@ -335,6 +334,8 @@ engine_dispatch_main(int fd, short event, void *bula) sizeof(struct ra_iface_conf)); ra_iface_conf->autoprefix = NULL; SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list); + SIMPLEQ_INIT(&ra_iface_conf->ra_rdnss_list); + SIMPLEQ_INIT(&ra_iface_conf->ra_dnssl_list); SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list, ra_iface_conf, entry); break; @@ -354,6 +355,27 @@ engine_dispatch_main(int fd, short event, void *bula) SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, ra_prefix_conf, entry); break; + case IMSG_RECONF_RA_RDNS_LIFETIME: + ra_iface_conf->rdns_lifetime = *((uint32_t *)imsg.data); + break; + case IMSG_RECONF_RA_RDNSS: + if ((ra_rdnss_conf = malloc(sizeof(struct + ra_rdnss_conf))) == NULL) + fatal(NULL); + memcpy(ra_rdnss_conf, imsg.data, sizeof(struct + ra_rdnss_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_rdnss_list, + ra_rdnss_conf, entry); + break; + case IMSG_RECONF_RA_DNSSL: + if ((ra_dnssl_conf = malloc(sizeof(struct + ra_dnssl_conf))) == NULL) + fatal(NULL); + memcpy(ra_dnssl_conf, imsg.data, sizeof(struct + ra_dnssl_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_dnssl_list, + ra_dnssl_conf, entry); + break; case IMSG_RECONF_END: merge_config(engine_conf, nconf); nconf = NULL; diff --git a/usr.sbin/rad/frontend.c b/usr.sbin/rad/frontend.c index 2fa5d7c34dd..c9d2c8e3ddb 100644 --- a/usr.sbin/rad/frontend.c +++ b/usr.sbin/rad/frontend.c @@ -1,4 +1,4 @@ -/* $OpenBSD: frontend.c,v 1.9 2018/07/15 09:26:26 florian Exp $ */ +/* $OpenBSD: frontend.c,v 1.10 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -308,6 +308,8 @@ frontend_dispatch_main(int fd, short event, void *bula) struct imsgev *iev = bula; struct imsgbuf *ibuf = &iev->ibuf; struct ra_prefix_conf *ra_prefix_conf; + struct ra_rdnss_conf *ra_rdnss_conf; + struct ra_dnssl_conf *ra_dnssl_conf; int n, shut = 0; if (event & EV_READ) { @@ -374,6 +376,8 @@ frontend_dispatch_main(int fd, short event, void *bula) ra_iface_conf)); ra_iface_conf->autoprefix = NULL; SIMPLEQ_INIT(&ra_iface_conf->ra_prefix_list); + SIMPLEQ_INIT(&ra_iface_conf->ra_rdnss_list); + SIMPLEQ_INIT(&ra_iface_conf->ra_dnssl_list); SIMPLEQ_INSERT_TAIL(&nconf->ra_iface_list, ra_iface_conf, entry); break; @@ -393,6 +397,27 @@ frontend_dispatch_main(int fd, short event, void *bula) SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_prefix_list, ra_prefix_conf, entry); break; + case IMSG_RECONF_RA_RDNS_LIFETIME: + ra_iface_conf->rdns_lifetime = *((uint32_t *)imsg.data); + break; + case IMSG_RECONF_RA_RDNSS: + if ((ra_rdnss_conf = malloc(sizeof(struct + ra_rdnss_conf))) == NULL) + fatal(NULL); + memcpy(ra_rdnss_conf, imsg.data, sizeof(struct + ra_rdnss_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_rdnss_list, + ra_rdnss_conf, entry); + break; + case IMSG_RECONF_RA_DNSSL: + if ((ra_dnssl_conf = malloc(sizeof(struct + ra_dnssl_conf))) == NULL) + fatal(NULL); + memcpy(ra_dnssl_conf, imsg.data, sizeof(struct + ra_dnssl_conf)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_dnssl_list, + ra_dnssl_conf, entry); + break; case IMSG_RECONF_END: merge_config(frontend_conf, nconf); merge_ra_interfaces(); @@ -866,8 +891,13 @@ build_packet(struct ra_iface *ra_iface) struct ra_iface_conf *ra_iface_conf; struct ra_options_conf *ra_options_conf; struct ra_prefix_conf *ra_prefix_conf; - size_t len; + struct nd_opt_rdnss *ndopt_rdnss; + struct nd_opt_dnssl *ndopt_dnssl; + struct ra_rdnss_conf *ra_rdnss; + struct ra_dnssl_conf *ra_dnssl; + size_t len, label_len; uint8_t *p, buf[RA_MAX_SIZE]; + char *label_start, *label_end; ra_iface_conf = find_ra_iface_conf(&frontend_conf->ra_iface_list, ra_iface->name); @@ -875,6 +905,14 @@ build_packet(struct ra_iface *ra_iface) len = sizeof(*ra); len += sizeof(*ndopt_pi) * ra_iface->prefix_count; + if (ra_iface_conf->rdnss_count > 0) + len += sizeof(*ndopt_rdnss) + ra_iface_conf->rdnss_count * + sizeof(struct in6_addr); + + if (ra_iface_conf->dnssl_len > 0) + /* round up to 8 byte boundary */ + len += sizeof(*ndopt_dnssl) + ((ra_iface_conf->dnssl_len + 7) + & ~7); if (len > sizeof(ra_iface->data)) fatal("%s: packet too big", __func__); /* XXX send multiple */ @@ -922,6 +960,50 @@ build_packet(struct ra_iface *ra_iface) p += sizeof(*ndopt_pi); } + if (ra_iface_conf->rdnss_count > 0) { + ndopt_rdnss = (struct nd_opt_rdnss *)p; + ndopt_rdnss->nd_opt_rdnss_type = ND_OPT_RDNSS; + ndopt_rdnss->nd_opt_rdnss_len = 1 + + ra_iface_conf->rdnss_count * 2; + ndopt_rdnss->nd_opt_rdnss_reserved = 0; + ndopt_rdnss->nd_opt_rdnss_lifetime = + htonl(ra_iface_conf->rdns_lifetime); + p += sizeof(struct nd_opt_rdnss); + SIMPLEQ_FOREACH(ra_rdnss, &ra_iface_conf->ra_rdnss_list, + entry) { + memcpy(p, &ra_rdnss->rdnss, sizeof(ra_rdnss->rdnss)); + p += sizeof(ra_rdnss->rdnss); + } + } + + if (ra_iface_conf->dnssl_len > 0) { + ndopt_dnssl = (struct nd_opt_dnssl *)p; + ndopt_dnssl->nd_opt_dnssl_type = ND_OPT_DNSSL; + /* round up to 8 byte boundary */ + ndopt_dnssl->nd_opt_dnssl_len = 1 + + ((ra_iface_conf->dnssl_len + 7) & ~7) / 8; + ndopt_dnssl->nd_opt_dnssl_reserved = 0; + ndopt_dnssl->nd_opt_dnssl_lifetime = + htonl(ra_iface_conf->rdns_lifetime); + p += sizeof(struct nd_opt_dnssl); + + SIMPLEQ_FOREACH(ra_dnssl, &ra_iface_conf->ra_dnssl_list, + entry) { + label_start = ra_dnssl->search; + while ((label_end = strchr(label_start, '.')) != NULL) { + label_len = label_end - label_start; + *p++ = label_len; + memcpy(p, label_start, label_len); + p += label_len; + label_start = label_end + 1; + } + *p++ = '\0'; /* last dot */ + } + /* zero pad */ + while (((uintptr_t)p) % 8 != 0) + *p++ = '\0'; + } + if (len != ra_iface->datalen || memcmp(buf, ra_iface->data, len) != 0) { memcpy(ra_iface->data, buf, len); diff --git a/usr.sbin/rad/parse.y b/usr.sbin/rad/parse.y index bb2ce2ba4e9..582d165c7d6 100644 --- a/usr.sbin/rad/parse.y +++ b/usr.sbin/rad/parse.y @@ -1,4 +1,4 @@ -/* $OpenBSD: parse.y,v 1.2 2018/07/11 08:47:03 florian Exp $ */ +/* $OpenBSD: parse.y,v 1.3 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -115,7 +115,7 @@ typedef struct { %token DEFAULT ROUTER HOP LIMIT MANAGED ADDRESS %token CONFIGURATION OTHER LIFETIME REACHABLE TIME RETRANS TIMER %token AUTO PREFIX VALID PREFERRED LIFETIME ONLINK AUTONOMOUS -%token ADDRESS_CONFIGURATION +%token ADDRESS_CONFIGURATION DNS RESOLVER SEARCH %token STRING %token NUMBER @@ -265,6 +265,7 @@ ra_ifaceoptsl : NO AUTO PREFIX { } ra_prefix_block { ra_prefix_conf = NULL; } + | DNS dns_block | ra_opt_block ; @@ -290,7 +291,91 @@ ra_prefixoptsl : VALID LIFETIME NUMBER { ra_prefix_conf->aflag = $3; } ; +dns_block : '{' optnl dnsopts_l '}' + | '{' optnl '}' + | /* empty */ + ; + +dnsopts_l : dnsopts_l dnsoptsl nl + | dnsoptsl optnl + ; +dnsoptsl : LIFETIME NUMBER { + ra_iface_conf->rdns_lifetime = $2; + } + | RESOLVER resolver_block + | SEARCH search_block + ; +resolver_block : '{' optnl resolveropts_l '}' + | '{' optnl '}' + | resolveroptsl + | /* empty */ + ; + +resolveropts_l : resolveropts_l resolveroptsl optnl + | resolveroptsl optnl + ; + +resolveroptsl : STRING { + struct ra_rdnss_conf *ra_rdnss_conf; + struct in6_addr addr; + + memset(&addr, 0, sizeof(addr)); + if (inet_pton(AF_INET6, $1, &addr) + != 1) { + yyerror("error parsing resolver address %s", + $1); + free($1); + YYERROR; + } + if ((ra_rdnss_conf = calloc(1, sizeof(*ra_rdnss_conf))) + == NULL) + err(1, "%s", __func__); + memcpy(&ra_rdnss_conf->rdnss, &addr, sizeof(addr)); + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_rdnss_list, + ra_rdnss_conf, entry); + ra_iface_conf->rdnss_count++; + } + ; +search_block : '{' optnl searchopts_l '}' + | '{' optnl '}' + | searchoptsl + | /* empty */ + ; + +searchopts_l : searchopts_l searchoptsl optnl + | searchoptsl optnl + ; + +searchoptsl : STRING { + struct ra_dnssl_conf *ra_dnssl_conf; + size_t len; + + if ((ra_dnssl_conf = calloc(1, + sizeof(*ra_dnssl_conf))) == NULL) + err(1, "%s", __func__); + + if ((len = strlcpy(ra_dnssl_conf->search, $1, + sizeof(ra_dnssl_conf->search))) > + sizeof(ra_dnssl_conf->search)) { + yyerror("search string too long"); + free($1); + YYERROR; + } + if (ra_dnssl_conf->search[len] != '.') { + if ((len = strlcat(ra_dnssl_conf->search, ".", + sizeof(ra_dnssl_conf->search))) > + sizeof(ra_dnssl_conf->search)) { + yyerror("search string too long"); + free($1); + YYERROR; + } + } + SIMPLEQ_INSERT_TAIL(&ra_iface_conf->ra_dnssl_list, + ra_dnssl_conf, entry); + ra_iface_conf->dnssl_len += len + 1; + } + ; %% struct keywords { @@ -331,6 +416,7 @@ lookup(char *s) {"autonomous", AUTONOMOUS}, {"configuration", CONFIGURATION}, {"default", DEFAULT}, + {"dns", DNS}, {"hop", HOP}, {"include", INCLUDE}, {"interface", RA_IFACE}, @@ -343,8 +429,10 @@ lookup(char *s) {"preferred", PREFERRED}, {"prefix", PREFIX}, {"reachable", REACHABLE}, + {"resolver", RESOLVER}, {"retrans", RETRANS}, {"router", ROUTER}, + {"search", SEARCH}, {"time", TIME}, {"timer", TIMER}, {"valid", VALID}, @@ -870,7 +958,11 @@ conf_get_ra_iface(char *name) /* Inherit attributes set in global section. */ iface->ra_options = conf->ra_options; + iface->rdns_lifetime = DEFAULT_RDNS_LIFETIME; + SIMPLEQ_INIT(&iface->ra_prefix_list); + SIMPLEQ_INIT(&iface->ra_rdnss_list); + SIMPLEQ_INIT(&iface->ra_dnssl_list); SIMPLEQ_INSERT_TAIL(&conf->ra_iface_list, iface, entry); diff --git a/usr.sbin/rad/printconf.c b/usr.sbin/rad/printconf.c index 57ccb2ccaf5..1fc8cd4ca3f 100644 --- a/usr.sbin/rad/printconf.c +++ b/usr.sbin/rad/printconf.c @@ -1,4 +1,4 @@ -/* $OpenBSD: printconf.c,v 1.1 2018/07/10 16:39:54 florian Exp $ */ +/* $OpenBSD: printconf.c,v 1.2 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -72,6 +72,8 @@ print_config(struct rad_conf *conf) { struct ra_iface_conf *iface; struct ra_prefix_conf *prefix; + struct ra_rdnss_conf *ra_rdnss; + struct ra_dnssl_conf *ra_dnssl; char buf[INET6_ADDRSTRLEN], *bufp; print_ra_options("", &conf->ra_options); @@ -97,6 +99,30 @@ print_config(struct rad_conf *conf) printf("\t}\n"); } + if (!SIMPLEQ_EMPTY(&iface->ra_rdnss_list) || + !SIMPLEQ_EMPTY(&iface->ra_dnssl_list)) { + printf("\tdns {\n"); + printf("\t\tlifetime %u\n", iface->rdns_lifetime); + if (!SIMPLEQ_EMPTY(&iface->ra_rdnss_list)) { + printf("\t\tresolver {\n"); + SIMPLEQ_FOREACH(ra_rdnss, + &iface->ra_rdnss_list, entry) { + inet_ntop(AF_INET6, &ra_rdnss->rdnss, + buf, sizeof(buf)); + printf("\t\t\t%s\n", buf); + } + printf("\t\t}\n"); + } + if (!SIMPLEQ_EMPTY(&iface->ra_dnssl_list)) { + printf("\t\tsearch {\n"); + SIMPLEQ_FOREACH(ra_dnssl, + &iface->ra_dnssl_list, entry) + printf("\t\t\t%s\n", ra_dnssl->search); + printf("\t\t}\n"); + } + printf("\t}\n"); + } + printf("}\n"); } } diff --git a/usr.sbin/rad/rad.c b/usr.sbin/rad/rad.c index c184c8c2efe..36b68f2e318 100644 --- a/usr.sbin/rad/rad.c +++ b/usr.sbin/rad/rad.c @@ -1,4 +1,4 @@ -/* $OpenBSD: rad.c,v 1.7 2018/07/13 09:16:15 florian Exp $ */ +/* $OpenBSD: rad.c,v 1.8 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -594,6 +594,8 @@ main_imsg_send_config(struct rad_conf *xconf) { struct ra_iface_conf *ra_iface_conf; struct ra_prefix_conf *ra_prefix_conf; + struct ra_rdnss_conf *ra_rdnss_conf; + struct ra_dnssl_conf *ra_dnssl_conf; /* Send fixed part of config to children. */ if (main_sendboth(IMSG_RECONF_CONF, xconf, sizeof(*xconf)) == -1) @@ -616,6 +618,22 @@ main_imsg_send_config(struct rad_conf *xconf) ra_prefix_conf, sizeof(*ra_prefix_conf)) == -1) return (-1); } + if (main_sendboth(IMSG_RECONF_RA_RDNS_LIFETIME, + &ra_iface_conf->rdns_lifetime, + sizeof(ra_iface_conf->rdns_lifetime)) == -1) + return (-1); + SIMPLEQ_FOREACH(ra_rdnss_conf, &ra_iface_conf->ra_rdnss_list, + entry) { + if (main_sendboth(IMSG_RECONF_RA_RDNSS, ra_rdnss_conf, + sizeof(*ra_rdnss_conf)) == -1) + return (-1); + } + SIMPLEQ_FOREACH(ra_dnssl_conf, &ra_iface_conf->ra_dnssl_list, + entry) { + if (main_sendboth(IMSG_RECONF_RA_DNSSL, ra_dnssl_conf, + sizeof(*ra_dnssl_conf)) == -1) + return (-1); + } } /* Tell children the revised config is now complete. */ diff --git a/usr.sbin/rad/rad.conf.5 b/usr.sbin/rad/rad.conf.5 index b0c10e44cc8..286d174784b 100644 --- a/usr.sbin/rad/rad.conf.5 +++ b/usr.sbin/rad/rad.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: rad.conf.5,v 1.4 2018/07/15 09:27:02 florian Exp $ +.\" $OpenBSD: rad.conf.5,v 1.5 2018/07/15 09:28:21 florian Exp $ .\" .\" Copyright (c) 2018 Florian Obser .\" Copyright (c) 2005 Esben Norby @@ -125,6 +125,26 @@ The valid lifetime (vltime) in seconds for addresses generated from this prefix. The default is 2592000. .El +.Pp +Recursive resolvers are configured inside an interface block: +.Bd -unfilled -offset indent +.Ic dns Brq dns options +.Ed +.Pp +.Ic dns +options are as follows: +.Bl -tag -width Ds +.It Ic lifetime Ar seconds +The number of seconds the dns options are valid after receiving a router +advertisement message. +The default is 900 seconds. +.It Ic resolver Pq Ar IP Ns | Ns { resolver list } +IPv6 address or list of IPv6 addresses of recursive dns resolvers. +.It Ic search Pq Ar domain Ns | Ns { domain list } +Domain or list of domains for the +.Xr resolv.conf 5 +search list. +.El .Sh FILES .Bl -tag -width "/etc/rad.conf" -compact .It Pa /etc/rad.conf diff --git a/usr.sbin/rad/rad.h b/usr.sbin/rad/rad.h index 22f296ae63a..db80d7c772d 100644 --- a/usr.sbin/rad/rad.h +++ b/usr.sbin/rad/rad.h @@ -1,4 +1,4 @@ -/* $OpenBSD: rad.h,v 1.10 2018/07/15 09:25:41 florian Exp $ */ +/* $OpenBSD: rad.h,v 1.11 2018/07/15 09:28:21 florian Exp $ */ /* * Copyright (c) 2018 Florian Obser @@ -27,6 +27,11 @@ #define OPT_NOACTION 0x00000004 +#define MAX_RTR_ADV_INTERVAL 600 +#define MIN_RTR_ADV_INTERVAL 200 +#define MAX_SEARCH 1025 /* same as MAXDNAME in arpa/nameser.h */ +#define DEFAULT_RDNS_LIFETIME 600 * 1.5 + enum { PROC_MAIN, PROC_ENGINE, @@ -54,6 +59,9 @@ enum imsg_type { IMSG_RECONF_RA_IFACE, IMSG_RECONF_RA_AUTOPREFIX, IMSG_RECONF_RA_PREFIX, + IMSG_RECONF_RA_RDNS_LIFETIME, + IMSG_RECONF_RA_RDNSS, + IMSG_RECONF_RA_DNSSL, IMSG_RECONF_END, IMSG_ICMP6SOCK, IMSG_ROUTESOCK, @@ -90,13 +98,28 @@ struct ra_prefix_conf { int aflag; /* autonom. addr flag */ }; +/* RFC 8106 */ +struct ra_rdnss_conf { + SIMPLEQ_ENTRY(ra_rdnss_conf) entry; + struct in6_addr rdnss; +}; +struct ra_dnssl_conf { + SIMPLEQ_ENTRY(ra_dnssl_conf) entry; + char search[MAX_SEARCH]; +}; + struct ra_iface_conf { - SIMPLEQ_ENTRY(ra_iface_conf) entry; - struct ra_options_conf ra_options; - struct ra_prefix_conf *autoprefix; + SIMPLEQ_ENTRY(ra_iface_conf) entry; + struct ra_options_conf ra_options; + struct ra_prefix_conf *autoprefix; SIMPLEQ_HEAD(ra_prefix_conf_head, - ra_prefix_conf) ra_prefix_list; - char name[IF_NAMESIZE]; + ra_prefix_conf) ra_prefix_list; + uint32_t rdns_lifetime; + SIMPLEQ_HEAD(, ra_rdnss_conf) ra_rdnss_list; + int rdnss_count; + SIMPLEQ_HEAD(, ra_dnssl_conf) ra_dnssl_list; + int dnssl_len; + char name[IF_NAMESIZE]; }; struct rad_conf { -- 2.20.1