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);


Reply via email to