The following patch adds processing for RDNSS and DNSSL RA options to rtsol(8) and rtsold(8), as a step toward conformance with RFC 6106. The code is ported from FreeBSD 10's implementation. These options are processed in a manner similar to the currently-supported "other config" option: when detected, the program invokes a script, passing the option data on standard input. rtsol/rtsold now have two new flags, -R to specify an alternate script (a default is baked into the program), and -u, which causes more information about the RA source to be passed to the script.
The patch is at the bottom, but here are some discussion points: - This patch modifies the rtsold/rtsol program, but does not add the script. I've been testing with very simple one, but that won't suffice for a release. FreeBSD incorporates the "openresolv" script (http://roy.marples.name/projects/openresolv/index). If the script-invocation approach seems like the way forward, I'll have to learn how to incorporate this script (or something else) into the build. Figuring out what script to provide is probably the biggest sticking point with this change. My test script (saved as /sbin/resolvconf) is this: #!/bin/sh echo "# Generated by rtsol: $*" > /etc/resolv.conf cat >> /etc/resolv.conf echo 'lookup file bind' >> /etc/resolv.conf - The build compiles rtsol with a SMALL preprocessor define, which removes the "Other Configuration" flag handling and dump file processing from the rtsol executable (it is enabled in rtsold). The handling of this flag and the new resolver option processing share some functions, so I though it would be simpler to remove SMALL around the RA option processing, thus enabling "Other Configuration" and the new resolver config in both programs. The current man page indicates (incorrectly) that these options are supported for rtsol. I took the view that the man page's intent was correct, but the code was wrong. Chuck Patch (against -current, as of 2014/09/20) is below. Index: dump.c =================================================================== RCS file: /cvs/src/usr.sbin/rtsold/dump.c,v retrieving revision 1.15 diff -u -p -r1.15 dump.c --- dump.c 21 Apr 2013 19:46:31 -0000 1.15 +++ dump.c 20 Sep 2014 20:58:04 -0000 @@ -33,6 +33,7 @@ #include <sys/types.h> #include <sys/time.h> #include <sys/socket.h> +#include <sys/queue.h> #include <net/if.h> #include <netinet/in.h> @@ -51,7 +52,6 @@ static FILE *fp; extern struct ifinfo *iflist; static void dump_interface_status(void); -static char *sec2str(time_t); char *ifstatstr[] = {"IDLE", "DELAY", "PROBE", "DOWN", "TENTATIVE"}; static void @@ -108,7 +108,7 @@ rtsold_dump_file(char *dumpfile) fclose(fp); } -static char * +const char * sec2str(time_t total) { static char result[256]; Index: rtsol.c =================================================================== RCS file: /cvs/src/usr.sbin/rtsold/rtsol.c,v retrieving revision 1.19 diff -u -p -r1.19 rtsol.c --- rtsol.c 21 Oct 2013 09:58:14 -0000 1.19 +++ rtsol.c 20 Sep 2014 20:58:04 -0000 @@ -50,6 +50,7 @@ #include <arpa/inet.h> +#include <netdb.h> #include <time.h> #include <unistd.h> #include <stdio.h> @@ -71,13 +72,39 @@ static struct iovec sndiov[2]; static struct sockaddr_in6 from; int rssock; +static char rsid[IFNAMSIZ + 1 + sizeof(DNSINFO_ORIGIN_LABEL) + 1 + NI_MAXHOST]; static struct sockaddr_in6 sin6_allrouters = {sizeof(sin6_allrouters), AF_INET6}; -#ifndef SMALL -void call_script(char *, char *); +static void call_script(const int, const char *const *, + struct script_msg_head_t *); int safefile(const char *); -#endif +static size_t dname_labeldec(char *, size_t, const char *); +static struct ra_opt *find_raopt(struct rainfo *, int, void *, size_t); +static int ra_opt_rdnss_dispatch(struct ifinfo *, struct rainfo *, + struct script_msg_head_t *, struct script_msg_head_t *); +static char *make_rsid(const char *, const char *, struct rainfo *); + +#define _ARGS_OTHER otherconf_script, ifi->ifname +#define _ARGS_RESADD resolvconf_script, "-a", rsid +#define _ARGS_RESDEL resolvconf_script, "-d", rsid + +#define CALL_SCRIPT(name, sm_head) \ + do { \ + const char *const sarg[] = { _ARGS_##name, NULL }; \ + call_script(sizeof(sarg), sarg, sm_head); \ + } while(0) + +#define ELM_MALLOC(p,error_action) \ + do { \ + p = malloc(sizeof(*p)); \ + if (p == NULL) { \ + warnmsg(LOG_ERR, __func__, "malloc failed: %s", \ + strerror(errno)); \ + error_action; \ + } \ + memset(p, 0, sizeof(*p)); \ + } while(0) int sockopen(u_int rdomain) @@ -226,18 +253,31 @@ void rtsol_input(int s) { u_char ntopbuf[INET6_ADDRSTRLEN], ifnamebuf[IFNAMSIZ]; - int ifindex = 0, *hlimp = NULL; + int l, ifindex = 0, *hlimp = NULL; + ssize_t msglen; struct in6_pktinfo *pi = NULL; struct ifinfo *ifi = NULL; + struct ra_opt *rao = NULL; struct icmp6_hdr *icp; struct cmsghdr *cm; - ssize_t i; -#ifndef SMALL struct nd_router_advert *nd_ra; -#endif + struct rainfo *rai; + char *raoptp; + char *p; + struct in6_addr *addr; + struct nd_opt_hdr *ndo; + struct nd_opt_rdnss *rdnss; + struct nd_opt_dnssl *dnssl; + size_t len; + char nsbuf[INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1]; + char dname[NI_MAXHOST]; + struct timeval now; + struct timeval lifetime; + int newent_rai; + int newent_rao; /* get message */ - if ((i = recvmsg(s, &rcvmhdr, 0)) < 0) { + if ((msglen = recvmsg(s, &rcvmhdr, 0)) < 0) { warnmsg(LOG_ERR, __func__, "recvmsg: %s", strerror(errno)); return; } @@ -268,9 +308,9 @@ rtsol_input(int s) return; } - if (i < sizeof(struct nd_router_advert)) { + if (msglen < sizeof(struct nd_router_advert)) { warnmsg(LOG_ERR, __func__, - "packet size(%zd) is too short", i); + "packet size(%zd) is too short", msglen); return; } @@ -329,7 +369,6 @@ rtsol_input(int s) inet_ntop(AF_INET6, &from.sin6_addr, ntopbuf, INET6_ADDRSTRLEN), ifi->ifname, ifi->state); -#ifndef SMALL nd_ra = (struct nd_router_advert *)icp; /* @@ -344,9 +383,183 @@ rtsol_input(int s) warnmsg(LOG_DEBUG, __func__, "OtherConfigFlag on %s is turned on", ifi->ifname); ifi->otherconfig = 1; - call_script(otherconf_script, ifi->ifname); + CALL_SCRIPT(OTHER, NULL); + } + gettimeofday(&now, NULL); + newent_rai = 0; + rai = find_rainfo(ifi, &from); + if (rai == NULL) { + ELM_MALLOC(rai, exit(1)); + rai->rai_ifinfo = ifi; + TAILQ_INIT(&rai->rai_ra_opt); + rai->rai_saddr.sin6_family = AF_INET6; + rai->rai_saddr.sin6_len = sizeof(rai->rai_saddr); + memcpy(&rai->rai_saddr.sin6_addr, &from.sin6_addr, + sizeof(rai->rai_saddr.sin6_addr)); + newent_rai = 1; + } + +#define RA_OPT_NEXT_HDR(x) (struct nd_opt_hdr *)((char *)x + \ + (((struct nd_opt_hdr *)x)->nd_opt_len * 8)) + /* Process RA options. */ + warnmsg(LOG_DEBUG, __func__, "Processing RA"); + raoptp = (char *)icp + sizeof(struct nd_router_advert); + while (raoptp < (char *)icp + msglen) { + ndo = (struct nd_opt_hdr *)raoptp; + warnmsg(LOG_DEBUG, __func__, "ndo = %p", raoptp); + warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_type = %d", + ndo->nd_opt_type); + warnmsg(LOG_DEBUG, __func__, "ndo->nd_opt_len = %d", + ndo->nd_opt_len); + + switch (ndo->nd_opt_type) { + case ND_OPT_RDNSS: + warnmsg(LOG_DEBUG, __func__, "RDNSS option"); + rdnss = (struct nd_opt_rdnss *)raoptp; + + /* Optlen sanity check (Section 5.3.1 in RFC 6106) */ + if (rdnss->nd_opt_rdnss_len < 3) { + warnmsg(LOG_INFO, __func__, + "too short RDNSS option" + "in RA from %s was ignored.", + inet_ntop(AF_INET6, &from.sin6_addr, + ntopbuf, sizeof(ntopbuf))); + break; + } + + addr = (struct in6_addr *)(void *)(raoptp + sizeof(*rdnss)); + while ((char *)addr < (char *)RA_OPT_NEXT_HDR(raoptp)) { + if (inet_ntop(AF_INET6, addr, ntopbuf, + sizeof(ntopbuf)) == NULL) { + warnmsg(LOG_INFO, __func__, + "an invalid address in RDNSS option" + " in RA from %s was ignored.", + inet_ntop(AF_INET6, &from.sin6_addr, + ntopbuf, sizeof(ntopbuf))); + addr++; + continue; + } + if (IN6_IS_ADDR_LINKLOCAL(addr)) + /* XXX: % has to be escaped here */ + l = snprintf(nsbuf, sizeof(nsbuf), + "%s%c%s", ntopbuf, + SCOPE_DELIMITER, + ifi->ifname); + else + l = snprintf(nsbuf, sizeof(nsbuf), + "%s", ntopbuf); + if (l < 0 || (size_t)l >= sizeof(nsbuf)) { + warnmsg(LOG_ERR, __func__, + "address copying error in " + "RDNSS option: %d.", l); + addr++; + continue; + } + warnmsg(LOG_DEBUG, __func__, "nsbuf = %s", + nsbuf); + + newent_rao = 0; + rao = find_raopt(rai, ndo->nd_opt_type, nsbuf, + strlen(nsbuf)); + if (rao == NULL) { + ELM_MALLOC(rao, break); + rao->rao_type = ndo->nd_opt_type; + rao->rao_len = strlen(nsbuf); + rao->rao_msg = strdup(nsbuf); + if (rao->rao_msg == NULL) { + warnmsg(LOG_ERR, __func__, + "strdup failed: %s", + strerror(errno)); + free(rao); + addr++; + continue; + } + newent_rao = 1; + } + /* Set expiration timer */ + memset(&rao->rao_expire, 0, + sizeof(rao->rao_expire)); + memset(&lifetime, 0, sizeof(lifetime)); + lifetime.tv_sec = + ntohl(rdnss->nd_opt_rdnss_lifetime); + timeradd(&now, &lifetime, &rao->rao_expire); + + if (newent_rao) + TAILQ_INSERT_TAIL(&rai->rai_ra_opt, + rao, rao_next); + addr++; + } + break; + case ND_OPT_DNSSL: + warnmsg(LOG_DEBUG, __func__, "DNSSL option"); + dnssl = (struct nd_opt_dnssl *)raoptp; + + /* Optlen sanity check (Section 5.3.1 in RFC 6106) */ + if (dnssl->nd_opt_dnssl_len < 2) { + warnmsg(LOG_INFO, __func__, + "too short DNSSL option" + "in RA from %s was ignored.", + inet_ntop(AF_INET6, &from.sin6_addr, + ntopbuf, sizeof(ntopbuf))); + break; + } + + /* + * Ensure NUL-termination in DNSSL in case of + * malformed field. + */ + p = (char *)RA_OPT_NEXT_HDR(raoptp); + *(p - 1) = '\0'; + + p = raoptp + sizeof(*dnssl); + while (1 < (len = dname_labeldec(dname, sizeof(dname), + p))) { + /* length == 1 means empty string */ + warnmsg(LOG_DEBUG, __func__, "dname = %s", + dname); + + newent_rao = 0; + rao = find_raopt(rai, ndo->nd_opt_type, dname, + strlen(dname)); + if (rao == NULL) { + ELM_MALLOC(rao, break); + rao->rao_type = ndo->nd_opt_type; + rao->rao_len = strlen(dname); + rao->rao_msg = strdup(dname); + if (rao->rao_msg == NULL) { + warnmsg(LOG_ERR, __func__, + "strdup failed: %s", + strerror(errno)); + free(rao); + addr++; + continue; + } + newent_rao = 1; + } + /* Set expiration timer */ + memset(&rao->rao_expire, 0, + sizeof(rao->rao_expire)); + memset(&lifetime, 0, sizeof(lifetime)); + lifetime.tv_sec = + ntohl(dnssl->nd_opt_dnssl_lifetime); + timeradd(&now, &lifetime, &rao->rao_expire); + + if (newent_rao) + TAILQ_INSERT_TAIL(&rai->rai_ra_opt, + rao, rao_next); + p += len; + } + break; + default: + /* nothing to do for other options */ + break; + } + raoptp = (char *)RA_OPT_NEXT_HDR(raoptp); } -#endif + if (newent_rai) + TAILQ_INSERT_TAIL(&ifi->ifi_rainfo, rai, rai_next); + + ra_opt_handler(ifi); ifi->racnt++; @@ -362,15 +575,242 @@ rtsol_input(int s) } } -#ifndef SMALL -void -call_script(char *scriptpath, char *ifname) +static char resstr_ns_prefix[] = "nameserver "; +static char resstr_sh_prefix[] = "search "; +static char resstr_nl[] = "\n"; +static char resstr_sp[] = " "; + +int +ra_opt_handler(struct ifinfo *ifi) +{ + struct ra_opt *rao; + struct rainfo *rai; + struct script_msg *smp1, *smp2, *smp3; + struct timeval now; + struct script_msg_head_t sm_rdnss_head = + TAILQ_HEAD_INITIALIZER(sm_rdnss_head); + struct script_msg_head_t sm_dnssl_head = + TAILQ_HEAD_INITIALIZER(sm_dnssl_head); + + int dcount, dlen; + + dcount = 0; + dlen = strlen(resstr_sh_prefix) + strlen(resstr_nl); + gettimeofday(&now, NULL); + + /* + * All options from multiple RAs with the same or different + * source addresses on a single interface will be gathered and + * handled, not overridden. [RFC 4861 6.3.4] + */ + TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next) { + TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) { + switch (rao->rao_type) { + case ND_OPT_RDNSS: + if (timercmp(&now, &rao->rao_expire, >)) { + warnmsg(LOG_INFO, __func__, + "expired rdnss entry: %s", + (char *)rao->rao_msg); + break; + } + ELM_MALLOC(smp1, continue); + ELM_MALLOC(smp2, goto free1); + ELM_MALLOC(smp3, goto free2); + smp1->sm_msg = resstr_ns_prefix; + TAILQ_INSERT_TAIL(&sm_rdnss_head, smp1, + sm_next); + smp2->sm_msg = rao->rao_msg; + TAILQ_INSERT_TAIL(&sm_rdnss_head, smp2, + sm_next); + smp3->sm_msg = resstr_nl; + TAILQ_INSERT_TAIL(&sm_rdnss_head, smp3, + sm_next); + ifi->ifi_rdnss = IFI_DNSOPT_STATE_RECEIVED; + + break; + case ND_OPT_DNSSL: + if (timercmp(&now, &rao->rao_expire, >)) { + warnmsg(LOG_INFO, __func__, + "expired dnssl entry: %s", + (char *)rao->rao_msg); + break; + } + dcount++; + /* Check resolv.conf(5) restrictions. */ + if (dcount > 6) { + warnmsg(LOG_INFO, __func__, + "dnssl entry exceeding maximum count (%d>6)" + ": %s", dcount, (char *)rao->rao_msg); + break; + } + if (256 < dlen + strlen(rao->rao_msg) + + strlen(resstr_sp)) { + warnmsg(LOG_INFO, __func__, + "dnssl entry exceeding maximum length " + "(>256): %s", (char *)rao->rao_msg); + break; + } + ELM_MALLOC(smp1, continue); + ELM_MALLOC(smp2, goto free1); + if (TAILQ_EMPTY(&sm_dnssl_head)) { + ELM_MALLOC(smp3, goto free2); + smp3->sm_msg = resstr_sh_prefix; + TAILQ_INSERT_TAIL(&sm_dnssl_head, smp3, + sm_next); + } + smp1->sm_msg = rao->rao_msg; + TAILQ_INSERT_TAIL(&sm_dnssl_head, smp1, + sm_next); + smp2->sm_msg = resstr_sp; + TAILQ_INSERT_TAIL(&sm_dnssl_head, smp2, + sm_next); + dlen += strlen(rao->rao_msg) + + strlen(resstr_sp); + break; + + ifi->ifi_dnssl = IFI_DNSOPT_STATE_RECEIVED; + default: + break; + } + continue; +free2: + free(smp2); +free1: + free(smp1); + } + /* Call the script for each information source. */ + if (uflag) + ra_opt_rdnss_dispatch(ifi, rai, &sm_rdnss_head, + &sm_dnssl_head); + } + /* Call the script for each interface. */ + if (!uflag) + ra_opt_rdnss_dispatch(ifi, NULL, &sm_rdnss_head, + &sm_dnssl_head); + return (0); +} + +char * +make_rsid(const char *ifname, const char *origin, struct rainfo *rai) +{ + char hbuf[NI_MAXHOST]; + + if (rai == NULL) + snprintf(rsid, sizeof(rsid), "%s:%s", ifname, origin); + else { + if (!IN6_IS_ADDR_LINKLOCAL(&rai->rai_saddr.sin6_addr)) + return (NULL); + if (getnameinfo((struct sockaddr *)&rai->rai_saddr, + rai->rai_saddr.sin6_len, hbuf, sizeof(hbuf), NULL, 0, + NI_NUMERICHOST) != 0) + return (NULL); + snprintf(rsid, sizeof(rsid), "%s:%s:[%s]", ifname, origin, hbuf); + } + warnmsg(LOG_DEBUG, __func__, "rsid = [%s]", rsid); + return (rsid); +} + +int +ra_opt_rdnss_dispatch(struct ifinfo *ifi, + struct rainfo *rai, + struct script_msg_head_t *sm_rdnss_head, + struct script_msg_head_t *sm_dnssl_head) +{ + const char *r; + struct script_msg *smp1, *dnssl, *dnssl_tmp; + int error; + + error = 0; + + /* Add \n for DNSSL list. */ + if (!TAILQ_EMPTY(sm_dnssl_head)) { + ELM_MALLOC(smp1, goto ra_opt_rdnss_freeit); + smp1->sm_msg = resstr_nl; + TAILQ_INSERT_TAIL(sm_dnssl_head, smp1, sm_next); + } + /* Concatenate the search list elements onto the server list + elements. + */ + + TAILQ_FOREACH_SAFE(dnssl, sm_dnssl_head, sm_next, dnssl_tmp) { + TAILQ_REMOVE(sm_dnssl_head, dnssl, sm_next); + TAILQ_INSERT_TAIL(sm_rdnss_head, dnssl, sm_next); + } + + if (rai != NULL && uflag) + r = make_rsid(ifi->ifname, DNSINFO_ORIGIN_LABEL, rai); + else + r = make_rsid(ifi->ifname, DNSINFO_ORIGIN_LABEL, NULL); + if (r == NULL) { + warnmsg(LOG_ERR, __func__, "make_rsid() failed. " + "Script was not invoked."); + error = 1; + goto ra_opt_rdnss_freeit; + } + if (!TAILQ_EMPTY(sm_rdnss_head)) + CALL_SCRIPT(RESADD, sm_rdnss_head); + else if (ifi->ifi_rdnss == IFI_DNSOPT_STATE_RECEIVED || + ifi->ifi_dnssl == IFI_DNSOPT_STATE_RECEIVED) { + CALL_SCRIPT(RESDEL, NULL); + ifi->ifi_rdnss = IFI_DNSOPT_STATE_NOINFO; + ifi->ifi_dnssl = IFI_DNSOPT_STATE_NOINFO; + } + +ra_opt_rdnss_freeit: + /* Clear script message queue. */ + if (!TAILQ_EMPTY(sm_rdnss_head)) { + while ((smp1 = TAILQ_FIRST(sm_rdnss_head)) != NULL) { + TAILQ_REMOVE(sm_rdnss_head, smp1, sm_next); + free(smp1); + } + } + if (!TAILQ_EMPTY(sm_dnssl_head)) { + while ((smp1 = TAILQ_FIRST(sm_dnssl_head)) != NULL) { + TAILQ_REMOVE(sm_dnssl_head, smp1, sm_next); + free(smp1); + } + } + + return (error); +} + +static struct ra_opt * +find_raopt(struct rainfo *rai, int type, void *msg, size_t len) { + struct ra_opt *rao; + + TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) { + if (rao->rao_type == type && + rao->rao_len == strlen(msg) && + memcmp(rao->rao_msg, msg, len) == 0) + break; + } + + return (rao); +} + +static void +call_script(const int argc, const char *const argv[], + struct script_msg_head_t *sm_head) +{ + const char *scriptpath; + int fd[2]; + int error; pid_t pid, wpid; - if (scriptpath == NULL) + if ((scriptpath = argv[0]) == NULL) return; + fd[0] = fd[1] = -1; + if (sm_head != NULL && !TAILQ_EMPTY(sm_head)) { + error = pipe(fd); + if (error) { + warnmsg(LOG_ERR, __func__, + "failed to create a pipe: %s", strerror(errno)); + return; + } + } + /* launch the script */ pid = fork(); if (pid < 0) { @@ -380,6 +820,25 @@ call_script(char *scriptpath, char *ifna } else if (pid) { int wstatus; + if (fd[0] != -1) { /* Send message to the child if any. */ + ssize_t len; + struct script_msg *smp; + + close(fd[0]); + TAILQ_FOREACH(smp, sm_head, sm_next) { + len = strlen(smp->sm_msg); + warnmsg(LOG_DEBUG, __func__, + "write to child = %s(%zd)", + smp->sm_msg, len); + if (write(fd[1], smp->sm_msg, len) != len) { + warnmsg(LOG_ERR, __func__, + "write to child failed: %s", + strerror(errno)); + break; + } + } + close(fd[1]); + } do { wpid = wait(&wstatus); } while (wpid != pid && wpid > 0); @@ -392,12 +851,8 @@ call_script(char *scriptpath, char *ifna "script \"%s\" terminated", scriptpath); } } else { - char *argv[3]; - int fd; - - argv[0] = scriptpath; - argv[1] = ifname; - argv[2] = NULL; + int nullfd; + char **_argv; if (safefile(scriptpath) != 0) { warnmsg(LOG_ERR, __func__, @@ -405,17 +860,39 @@ call_script(char *scriptpath, char *ifna scriptpath); exit(1); } - - if ((fd = open("/dev/null", O_RDWR)) != -1) { - dup2(fd, STDIN_FILENO); - dup2(fd, STDOUT_FILENO); - dup2(fd, STDERR_FILENO); - if (fd > STDERR_FILENO) - close(fd); + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) { + warnmsg(LOG_ERR, __func__, + "open /dev/null: %s", strerror(errno)); + exit(1); } + if (fd[0] != -1) { /* Receive message from STDIN if any. */ + close(fd[1]); + if (fd[0] != STDIN_FILENO) { + /* Connect a pipe read-end to child's STDIN. */ + if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) { + warnmsg(LOG_ERR, __func__, + "dup2 STDIN: %s", strerror(errno)); + exit(1); + } + close(fd[0]); + } + } else + dup2(nullfd, STDIN_FILENO); + + dup2(nullfd, STDOUT_FILENO); + dup2(nullfd, STDERR_FILENO); + if (nullfd > STDERR_FILENO) + close(nullfd); - execv(scriptpath, argv); - + _argv = malloc(sizeof(*_argv) * argc); + if (_argv == NULL) { + warnmsg(LOG_ERR, __func__, + "malloc: %s", strerror(errno)); + exit(1); + } + memcpy(_argv, argv, (size_t)argc); + execv(scriptpath, (char *const *)_argv); warnmsg(LOG_ERR, __func__, "child: exec failed: %s", strerror(errno)); exit(1); @@ -463,4 +940,37 @@ safefile(const char *path) return (0); } -#endif + +/* Decode domain name label encoding in RFC 1035 Section 3.1 */ +static size_t +dname_labeldec(char *dst, size_t dlen, const char *src) +{ + size_t len; + const char *src_origin; + const char *src_last; + const char *dst_origin; + + src_origin = src; + src_last = strchr(src, '\0'); + dst_origin = dst; + memset(dst, '\0', dlen); + while (src && (len = (uint8_t)(*src++) & 0x3f) && + (src + len) <= src_last) { + if (dst != dst_origin) + *dst++ = '.'; + warnmsg(LOG_DEBUG, __func__, "labellen = %zd", len); + memcpy(dst, src, len); + src += len; + dst += len; + } + *dst = '\0'; + + /* + * XXX validate that domain name only contains valid characters + * for two reasons: 1) correctness, 2) we do not want to pass + * possible malicious, unescaped characters like `` to a script + * or program that could be exploited that way. + */ + + return (src - src_origin); +} Index: rtsold.8 =================================================================== RCS file: /cvs/src/usr.sbin/rtsold/rtsold.8,v retrieving revision 1.33 diff -u -p -r1.33 rtsold.8 --- rtsold.8 27 Aug 2014 14:04:16 -0000 1.33 +++ rtsold.8 20 Sep 2014 20:58:05 -0000 @@ -38,19 +38,25 @@ .\" .Sh SYNOPSIS .Nm rtsold -.Op Fl 1DdFfm +.Op Fl 1DdFfmu .Op Fl O Ar script-name +.Op Fl R Ar script-name .Ar interface ... .Nm rtsold .Op Fl 1DdFfm +.Op Fl O Ar script-name +.Op Fl R Ar script-name .Fl a .Pp .Nm rtsol -.Op Fl DdF +.Op Fl DdFu .Op Fl O Ar script-name +.Op Fl R Ar script-name .Ar interface ... .Nm rtsol .Op Fl DdF +.Op Fl O Ar script-name +.Op Fl R Ar script-name .Fl a .\" .Sh DESCRIPTION @@ -137,6 +143,15 @@ When sending a Router Solicitation on an includes a Source Link-layer address option if the interface has a link-layer address. .Pp +If +.Nm +receives a Router Advertisement with RDNSS (Recursive DNS Server) or +DNSSL (DNS Search List) options, it invokes a script, passing the options +on standard input. By default, the resolvconf(8) script is invoked. This can +be overridden with the +.Fl R +option. +.Pp .Nm is able to do some additional configuration for interfaces where more than setting the host's address is needed. @@ -216,6 +231,30 @@ should be specified as the absolute path and the file itself should be a regular file and owned by the same user running .Nm . +.It Fl R Ar script-name +Specifies a script to run when router advertisement options RDNSS +(Recursive DNS Server) or DNSSL (DNS Search List) are encountered. The +information of DNS servers and DNS search domains will be sent to standard +input of this script. +.Ar script-name +should be specified as the absolute path from root to the script file, +and the file itself should be a regular file and owned by the same user +running +.Nm . +The +resolvconf(8) +script is used by default. +.It Fl u +Adds the source address of Router Advertisement messages to the interface +name in an argument of the RDNSS and DNSSL script. +.Pp +If +.Fl u +is specified, the interface name in the script argument will be +.Ql ifname:slaac:[RA-source-address] . +.Pp +If not, it will be +.Ql ifname:slaac . .El .\" .Sh FILES Index: rtsold.c =================================================================== RCS file: /cvs/src/usr.sbin/rtsold/rtsold.c,v retrieving revision 1.53 diff -u -p -r1.53 rtsold.c --- rtsold.c 27 Aug 2014 14:04:16 -0000 1.53 +++ rtsold.c 20 Sep 2014 20:58:05 -0000 @@ -33,6 +33,7 @@ #include <sys/types.h> #include <sys/time.h> #include <sys/socket.h> +#include <sys/queue.h> #include <sys/param.h> #include <net/if.h> @@ -60,11 +61,13 @@ struct ifinfo *iflist; static int log_upto = 999; static int fflag = 0; static int Fflag = 0; /* force setting sysctl parameters */ +int uflag = 0; int aflag = 0; int dflag = 0; char *otherconf_script; +const char *resolvconf_script = "/sbin/resolvconf"; /* protocol constants */ #define MAX_RTR_SOLICITATION_DELAY 1 /* second */ @@ -116,9 +119,9 @@ main(int argc, char *argv[]) if (argv0 && argv0[0] != '\0' && argv0[strlen(argv0) - 1] != 'd') { fflag = 1; once = 1; - opts = "adDFO:"; + opts = "adDFuO:R:"; } else - opts = "adDfFm1O:"; + opts = "adDfFmu1O:R:"; while ((ch = getopt(argc, argv, opts)) != -1) { switch (ch) { @@ -143,11 +146,15 @@ main(int argc, char *argv[]) case '1': once = 1; break; -#ifndef SMALL case 'O': otherconf_script = optarg; break; -#endif + case 'R': + resolvconf_script = optarg; + break; + case 'u': + uflag = 1; + break; default: usage(argv0); /*NOTREACHED*/ @@ -177,12 +184,10 @@ main(int argc, char *argv[]) setlogmask(LOG_UPTO(log_upto)); } -#ifndef SMALL if (otherconf_script && *otherconf_script != '/') { errx(1, "configuration script (%s) must be an absolute path", otherconf_script); } -#endif if (Fflag) setinet6sysctl(IPPROTO_IPV6, IPV6CTL_FORWARDING, 0); @@ -322,8 +327,10 @@ ifconfig(char *ifname) return(-1); } ifinfo->sdl = sdl; - - strncpy(ifinfo->ifname, ifname, sizeof(ifinfo->ifname)); + ifinfo->ifi_rdnss = IFI_DNSOPT_STATE_NOINFO; + ifinfo->ifi_dnssl = IFI_DNSOPT_STATE_NOINFO; + TAILQ_INIT(&ifinfo->ifi_rainfo); + strlcpy(ifinfo->ifname, ifname, sizeof(ifinfo->ifname)); /* construct a router solicitation message */ if (make_packet(ifinfo)) @@ -402,6 +409,19 @@ ifreconfig(char *ifname) } #endif +struct rainfo * +find_rainfo(struct ifinfo *ifi, struct sockaddr_in6 *sin6) +{ + struct rainfo *rai; + + TAILQ_FOREACH(rai, &ifi->ifi_rainfo, rai_next) + if (memcmp(&rai->rai_saddr.sin6_addr, &sin6->sin6_addr, + sizeof(rai->rai_saddr.sin6_addr)) == 0) + return (rai); + + return (NULL); +} + struct ifinfo * find_ifinfo(int ifindex) { @@ -457,6 +477,8 @@ rtsol_check_timer(void) static struct timeval returnval; struct timeval now, rtsol_timer; struct ifinfo *ifinfo; + struct rainfo *rai; + struct ra_opt *rao; int flags, timers; gettimeofday(&now, NULL); @@ -471,6 +493,20 @@ rtsol_check_timer(void) "state = %d", ifinfo->ifname, ifinfo->state); + while((rai = TAILQ_FIRST(&ifinfo->ifi_rainfo)) != NULL) { + /* Remove all RA options. */ + TAILQ_REMOVE(&ifinfo->ifi_rainfo, rai, rai_next); + while ((rao = TAILQ_FIRST(&rai->rai_ra_opt)) != + NULL) { + TAILQ_REMOVE(&rai->rai_ra_opt, rao, + rao_next); + if (rao->rao_msg != NULL) + free(rao->rao_msg); + free(rao); + } + free(rai); + } + switch (ifinfo->state) { case IFS_DOWN: case IFS_TENTATIVE: @@ -508,14 +544,12 @@ rtsol_check_timer(void) ifinfo->state = IFS_PROBE; } -#ifndef SMALL /* * If we need a probe, clear the previous * status wrt the "other" configuration. */ if (probe) ifinfo->otherconfig = 0; -#endif if (probe && mobile_node) defrouter_probe(ifinfo); @@ -538,6 +572,33 @@ rtsol_check_timer(void) break; } rtsol_timer_update(ifinfo); + } else { + /* Expiration check for RA options. */ + int expire = 0; + + TAILQ_FOREACH(rai, &ifinfo->ifi_rainfo, rai_next) { + TAILQ_FOREACH(rao, &rai->rai_ra_opt, rao_next) { + warnmsg(LOG_DEBUG, __func__, + "RA expiration timer: " + "type=%d, msg=%s, expire=%s", + rao->rao_type, (char *)rao->rao_msg, + sec2str(rao->rao_expire.tv_sec)); + if (timercmp(&now, &rao->rao_expire, + >=)) { + warnmsg(LOG_DEBUG, __func__, + "RA expiration timer: " + "expired."); + TAILQ_REMOVE(&rai->rai_ra_opt, + rao, rao_next); + if (rao->rao_msg != NULL) + free(rao->rao_msg); + free(rao); + expire = 1; + } + } + } + if (expire) + ra_opt_handler(ifinfo); } if (!ifinfo->stoptimer) { if (timers == 0) @@ -652,12 +713,14 @@ usage(char *progname) { if (progname && progname[0] != '\0' && progname[strlen(progname) - 1] != 'd') { fprintf(stderr, - "usage: rtsol [-DdF] [-O script-name] interface ...\n" - " rtsol [-DdF] -a\n"); + "usage: rtsol [-DdFu] [-O script-name] [-R script-name] " + "interface ...\n" + " rtsol [-DdFu] [-O script-name] [-R script-name] -a\n"); } else { fprintf(stderr, - "usage: rtsold [-1DdFfm] [-O script-name] interface ...\n" - " rtsold [-1DdFfm] -a\n"); + "usage: rtsold [-1DdFfmu] [-O script-name] [-R script-name] " + "interface ...\n" + " rtsold [-1DdFfmu] [-O script-name] [-R script-name] -a\n"); } exit(1); } Index: rtsold.h =================================================================== RCS file: /cvs/src/usr.sbin/rtsold/rtsold.h,v retrieving revision 1.17 diff -u -p -r1.17 rtsold.h --- rtsold.h 12 Nov 2013 22:27:13 -0000 1.17 +++ rtsold.h 20 Sep 2014 20:58:05 -0000 @@ -30,6 +30,33 @@ * SUCH DAMAGE. */ +struct script_msg { + TAILQ_ENTRY(script_msg) sm_next; + + char *sm_msg; +}; + +TAILQ_HEAD(script_msg_head_t, script_msg); + +struct ra_opt { + TAILQ_ENTRY(ra_opt) rao_next; + + u_int8_t rao_type; + struct timeval rao_expire; + size_t rao_len; + void *rao_msg; +}; + +TAILQ_HEAD(rainfo_head, ra_opt); + +struct rainfo { + TAILQ_ENTRY(rainfo) rai_next; + + struct ifinfo *rai_ifinfo; + struct sockaddr_in6 rai_saddr; + TAILQ_HEAD(, ra_opt) rai_ra_opt; +}; + struct ifinfo { struct ifinfo *next; /* pointer to the next interface */ @@ -49,8 +76,13 @@ struct ifinfo { struct timeval expire; int stoptimer; int errors; /* # of errors we've got - detect wedge */ +#define IFI_DNSOPT_STATE_NOINFO 0 +#define IFI_DNSOPT_STATE_RECEIVED 1 + int ifi_rdnss; /* RDNSS option state */ + int ifi_dnssl; /* DNSSL option state */ int racnt; /* total # of valid RAs it have got */ + TAILQ_HEAD(, rainfo) ifi_rainfo; size_t rs_datalen; u_char *rs_data; @@ -63,14 +95,20 @@ struct ifinfo { #define IFS_DOWN 3 #define IFS_TENTATIVE 4 +#define DNSINFO_ORIGIN_LABEL "slaac" + /* rtsold.c */ extern int dflag; +extern int uflag; extern char *otherconf_script; +extern const char *resolvconf_script; struct ifinfo *find_ifinfo(int ifindex); +struct rainfo *find_rainfo(struct ifinfo *, struct sockaddr_in6 *); void rtsol_timer_update(struct ifinfo *); extern void warnmsg(int, const char *, const char *, ...) __attribute__((__format__(__printf__, 3, 4))); extern char **autoifprobe(u_int); +extern int ra_opt_handler(struct ifinfo *); /* if.c */ extern int ifinit(void); @@ -94,3 +132,4 @@ extern void defrouter_probe(struct ifinf /* dump.c */ extern void rtsold_dump_file(char *); +extern const char *sec2str(time_t);