Improve handling of DNS search domains
authorSteven Barth <steven@midlink.org>
Mon, 13 Apr 2015 12:48:52 +0000 (14:48 +0200)
committerSteven Barth <steven@midlink.org>
Mon, 13 Apr 2015 12:48:52 +0000 (14:48 +0200)
Signed-off-by: Steven Barth <steven@midlink.org>
README
src/dhcpv6.c
src/odhcp6c.c
src/odhcp6c.h
src/ra.c
src/script.c

diff --git a/README b/README
index 9b56b20..c362966 100644 (file)
--- a/README
+++ b/README
@@ -77,6 +77,7 @@ Environment:
 * RA_ROUTES            A space-separated list of routes from the RA
                                Format: <address>/<length>,gateway,valid,metric
 * RA_DNS               A space-separated list of recursive DNS servers from the RA
+* RA_DOMAINS           A space-separated list of DNS search domains from the RA
 * RA_HOPLIMIT  Highest hop-limit received in RAs
 * RA_MTU               MTU-value received in RA
 * RA_REACHABLE ND Reachability time
index 000d999..eeb669b 100644 (file)
@@ -995,7 +995,6 @@ static int dhcpv6_handle_reply(enum dhcpv6_msg orig, _unused const int rc,
                                odhcp6c_add_state(STATE_DNS, odata, olen);
                } else if (otype == DHCPV6_OPT_DNS_DOMAIN) {
                        odhcp6c_add_state(STATE_SEARCH, odata, olen);
-                       passthru = false;
                } else if (otype == DHCPV6_OPT_SNTP_SERVERS) {
                        if (olen % 16 == 0)
                                odhcp6c_add_state(STATE_SNTP_IP, odata, olen);
@@ -1142,7 +1141,7 @@ static int dhcpv6_parse_ia(void *opt, void *end)
 
        // Update address IA
        dhcpv6_for_each_option(&ia_hdr[1], end, otype, olen, odata) {
-               struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0,
+               struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, 0,
                                IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
 
                entry.iaid = ia_hdr->iaid;
index 4c534c1..b62b222 100644 (file)
@@ -556,11 +556,12 @@ void* odhcp6c_get_state(enum odhcp6c_state state, size_t *len)
 static struct odhcp6c_entry* odhcp6c_find_entry(enum odhcp6c_state state, const struct odhcp6c_entry *new)
 {
        size_t len, cmplen = offsetof(struct odhcp6c_entry, target) + ((new->length + 7) / 8);
-       struct odhcp6c_entry *start = odhcp6c_get_state(state, &len);
-       struct odhcp6c_entry *x = NULL;
+       uint8_t *start = odhcp6c_get_state(state, &len);
 
-       for (struct odhcp6c_entry *c = start; !x && c < &start[len/sizeof(*c)]; ++c)
-               if (!memcmp(c, new, cmplen))
+       for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
+                       (uint8_t*)c < &start[len] && &c->auxtarget[c->auxlen] <= &start[len];
+                       c = (struct odhcp6c_entry*)(&c->auxtarget[c->auxlen]))
+               if (!memcmp(c, new, cmplen) && !memcmp(c->auxtarget, new->auxtarget, new->auxlen))
                        return c;
 
        return NULL;
@@ -572,7 +573,7 @@ bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new,
 {
        size_t len;
        struct odhcp6c_entry *x = odhcp6c_find_entry(state, new);
-       struct odhcp6c_entry *start = odhcp6c_get_state(state, &len);
+       uint8_t *start = odhcp6c_get_state(state, &len);
 
        if (x && x->valid > new->valid && new->valid < safe)
                new->valid = safe;
@@ -592,10 +593,10 @@ bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new,
                        x->t2 = new->t2;
                        x->iaid = new->iaid;
                } else {
-                       odhcp6c_add_state(state, new, sizeof(*new));
+                       odhcp6c_add_state(state, new, sizeof(*new) + new->auxlen);
                }
        } else if (x) {
-               odhcp6c_remove_state(state, (x - start) * sizeof(*x), sizeof(*x));
+               odhcp6c_remove_state(state, ((uint8_t*)x) - start, sizeof(*x) + x->auxlen);
        }
        return true;
 }
@@ -604,8 +605,10 @@ bool odhcp6c_update_entry(enum odhcp6c_state state, struct odhcp6c_entry *new,
 static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed)
 {
        size_t len;
-       struct odhcp6c_entry *start = odhcp6c_get_state(state, &len);
-       for (struct odhcp6c_entry *c = start; c < &start[len / sizeof(*c)]; ++c) {
+       uint8_t *start = odhcp6c_get_state(state, &len);
+       for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
+                       (uint8_t*)c < &start[len] && &c->auxtarget[c->auxlen] <= &start[len];
+                       c = (struct odhcp6c_entry*)(&c->auxtarget[c->auxlen])) {
                if (c->t1 < elapsed)
                        c->t1 = 0;
                else if (c->t1 != UINT32_MAX)
@@ -627,7 +630,7 @@ static void odhcp6c_expire_list(enum odhcp6c_state state, uint32_t elapsed)
                        c->valid -= elapsed;
 
                if (!c->valid)
-                       odhcp6c_remove_state(state, (c - start) * sizeof(*c), sizeof(*c));
+                       odhcp6c_remove_state(state, ((uint8_t*)c) - start, sizeof(*c) + c->auxlen);
        }
 }
 
@@ -641,6 +644,7 @@ void odhcp6c_expire(void)
        odhcp6c_expire_list(STATE_RA_PREFIX, elapsed);
        odhcp6c_expire_list(STATE_RA_ROUTE, elapsed);
        odhcp6c_expire_list(STATE_RA_DNS, elapsed);
+       odhcp6c_expire_list(STATE_RA_SEARCH, elapsed);
        odhcp6c_expire_list(STATE_IA_NA, elapsed);
        odhcp6c_expire_list(STATE_IA_PD, elapsed);
 }
index 1042136..fff9360 100644 (file)
@@ -255,6 +255,7 @@ enum odhcp6c_state {
        STATE_RA_ROUTE,
        STATE_RA_PREFIX,
        STATE_RA_DNS,
+       STATE_RA_SEARCH,
        STATE_AFTR_NAME,
        STATE_VENDORCLASS,
        STATE_USERCLASS,
@@ -289,7 +290,8 @@ enum odhcp6c_ia_mode {
 
 struct odhcp6c_entry {
        struct in6_addr router;
-       uint16_t length;
+       uint8_t auxlen;
+       uint8_t length;
        int16_t priority;
        struct in6_addr target;
        uint32_t valid;
@@ -297,6 +299,7 @@ struct odhcp6c_entry {
        uint32_t t1;
        uint32_t t2;
        uint32_t iaid;
+       uint8_t auxtarget[];
 };
 
 struct odhcp6c_request_prefix {
index 5809fcd..dcb3e0f 100644 (file)
--- a/src/ra.c
+++ b/src/ra.c
@@ -20,6 +20,8 @@
 #include <stdbool.h>
 #include <syslog.h>
 #include <unistd.h>
+#include <resolv.h>
+#include <alloca.h>
 
 #include <net/if.h>
 #include <arpa/inet.h>
@@ -274,9 +276,11 @@ bool ra_process(void)
        bool changed = false;
        uint8_t buf[1500], cmsg_buf[128];
        struct nd_router_advert *adv = (struct nd_router_advert*)buf;
-       struct odhcp6c_entry entry = {IN6ADDR_ANY_INIT, 0, 0, IN6ADDR_ANY_INIT, 0, 0, 0, 0, 0};
+       struct odhcp6c_entry *entry = alloca(sizeof(*entry) + 256);
        const struct in6_addr any = IN6ADDR_ANY_INIT;
 
+       memset(entry, 0, sizeof(*entry));
+
        if (IN6_IS_ADDR_UNSPECIFIED(&lladdr)) {
                struct sockaddr_in6 addr = {AF_INET6, 0, 0, ALL_IPV6_ROUTERS, if_index};
                socklen_t alen = sizeof(addr);
@@ -332,15 +336,15 @@ bool ra_process(void)
                uint32_t router_valid = ntohs(adv->nd_ra_router_lifetime);
 
                // Parse default route
-               entry.target = any;
-               entry.length = 0;
-               entry.router = from.sin6_addr;
-               entry.priority = pref_to_priority(adv->nd_ra_flags_reserved);
-               if (entry.priority < 0)
-                       entry.priority = pref_to_priority(0);
-               entry.valid = router_valid;
-               entry.preferred = entry.valid;
-               changed |= odhcp6c_update_entry(STATE_RA_ROUTE, &entry, 0, true);
+               entry->target = any;
+               entry->length = 0;
+               entry->router = from.sin6_addr;
+               entry->priority = pref_to_priority(adv->nd_ra_flags_reserved);
+               if (entry->priority < 0)
+                       entry->priority = pref_to_priority(0);
+               entry->valid = router_valid;
+               entry->preferred = entry->valid;
+               changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 0, true);
 
                // Parse hoplimit
                ra_conf_hoplimit(adv->nd_ra_curhoplimit);
@@ -356,69 +360,93 @@ bool ra_process(void)
                                uint32_t *mtu = (uint32_t*)&opt->data[2];
                                ra_conf_mtu(ntohl(*mtu));
                        } else if (opt->type == ND_OPT_ROUTE_INFORMATION && opt->len <= 3) {
-                               entry.router = from.sin6_addr;
-                               entry.target = any;
-                               entry.priority = pref_to_priority(opt->data[1]);
-                               entry.length = opt->data[0];
+                               entry->router = from.sin6_addr;
+                               entry->target = any;
+                               entry->priority = pref_to_priority(opt->data[1]);
+                               entry->length = opt->data[0];
                                uint32_t *valid = (uint32_t*)&opt->data[2];
-                               entry.valid = ntohl(*valid);
-                               memcpy(&entry.target, &opt->data[6], (opt->len - 1) * 8);
+                               entry->valid = ntohl(*valid);
+                               memcpy(&entry->target, &opt->data[6], (opt->len - 1) * 8);
 
-                               if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target)
-                                               || IN6_IS_ADDR_LOOPBACK(&entry.target)
-                                               || IN6_IS_ADDR_MULTICAST(&entry.target))
+                               if (entry->length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry->target)
+                                               || IN6_IS_ADDR_LOOPBACK(&entry->target)
+                                               || IN6_IS_ADDR_MULTICAST(&entry->target))
                                        continue;
 
-                               if (entry.priority > 0)
-                                       changed |= odhcp6c_update_entry(STATE_RA_ROUTE, &entry, 0, true);
+                               if (entry->priority > 0)
+                                       changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 0, true);
                        } else if (opt->type == ND_OPT_PREFIX_INFORMATION && opt->len == 4) {
                                struct nd_opt_prefix_info *pinfo = (struct nd_opt_prefix_info*)opt;
-                               entry.router = any;
-                               entry.target = pinfo->nd_opt_pi_prefix;
-                               entry.priority = 256;
-                               entry.length = pinfo->nd_opt_pi_prefix_len;
-                               entry.valid = ntohl(pinfo->nd_opt_pi_valid_time);
-                               entry.preferred = ntohl(pinfo->nd_opt_pi_preferred_time);
-
-                               if (entry.length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry.target)
-                                               || IN6_IS_ADDR_LOOPBACK(&entry.target)
-                                               || IN6_IS_ADDR_MULTICAST(&entry.target)
-                                               || entry.valid < entry.preferred)
+                               entry->router = any;
+                               entry->target = pinfo->nd_opt_pi_prefix;
+                               entry->priority = 256;
+                               entry->length = pinfo->nd_opt_pi_prefix_len;
+                               entry->valid = ntohl(pinfo->nd_opt_pi_valid_time);
+                               entry->preferred = ntohl(pinfo->nd_opt_pi_preferred_time);
+
+                               if (entry->length > 128 || IN6_IS_ADDR_LINKLOCAL(&entry->target)
+                                               || IN6_IS_ADDR_LOOPBACK(&entry->target)
+                                               || IN6_IS_ADDR_MULTICAST(&entry->target)
+                                               || entry->valid < entry->preferred)
                                        continue;
 
                                if (pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)
-                                       changed |= odhcp6c_update_entry(STATE_RA_ROUTE, &entry, 7200, true);
+                                       changed |= odhcp6c_update_entry(STATE_RA_ROUTE, entry, 7200, true);
 
                                if (!(pinfo->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) ||
                                                pinfo->nd_opt_pi_prefix_len != 64)
                                        continue;
 
-                               entry.target.s6_addr32[2] = lladdr.s6_addr32[2];
-                               entry.target.s6_addr32[3] = lladdr.s6_addr32[3];
+                               entry->target.s6_addr32[2] = lladdr.s6_addr32[2];
+                               entry->target.s6_addr32[3] = lladdr.s6_addr32[3];
 
-                               changed |= odhcp6c_update_entry(STATE_RA_PREFIX, &entry, 7200, true);
+                               changed |= odhcp6c_update_entry(STATE_RA_PREFIX, entry, 7200, true);
                        } else if (opt->type == ND_OPT_RECURSIVE_DNS && opt->len > 2) {
-                               entry.router = from.sin6_addr;
-                               entry.priority = 0;
-                               entry.length = 128;
+                               entry->router = from.sin6_addr;
+                               entry->priority = 0;
+                               entry->length = 128;
                                uint32_t *valid = (uint32_t*)&opt->data[2];
-                               entry.valid = ntohl(*valid);
-                               entry.preferred = 0;
+                               entry->valid = ntohl(*valid);
+                               entry->preferred = 0;
 
                                for (ssize_t i = 0; i < (opt->len - 1) / 2; ++i) {
-                                       memcpy(&entry.target, &opt->data[6 + i * sizeof(entry.target)],
-                                                       sizeof(entry.target));
-                                       changed |= odhcp6c_update_entry(STATE_RA_DNS, &entry, 0, true);
+                                       memcpy(&entry->target, &opt->data[6 + i * sizeof(entry->target)],
+                                                       sizeof(entry->target));
+                                       changed |= odhcp6c_update_entry(STATE_RA_DNS, entry, 0, true);
+                               }
+                       } else if (opt->type == ND_OPT_DNSSL && opt->len > 1) {
+                               uint32_t *valid = (uint32_t*)&opt->data[2];
+                               uint8_t *buf = &opt->data[6];
+                               uint8_t *end = &buf[(opt->len - 1) * 8];
+
+                               entry->router = from.sin6_addr;
+                               entry->valid = ntohl(*valid);
+
+                               while (buf < end) {
+                                       int len = dn_expand(buf, end, buf, (char*)entry->auxtarget, 256);
+                                       if (len > 0) {
+                                               buf = &buf[len];
+                                               entry->auxlen = strlen((char*)entry->auxtarget);
+                                               changed |= odhcp6c_update_entry(STATE_RA_SEARCH, entry, 0, true);
+                                               entry->auxlen = 0;
+                                       } else {
+                                               break;
+                                       }
                                }
                        }
                }
 
-               size_t ra_dns_len;
-               struct odhcp6c_entry *entry = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len);
-               for (size_t i = 0; i < ra_dns_len / sizeof(*entry); ++i)
-                       if (IN6_ARE_ADDR_EQUAL(&entry[i].router, &from.sin6_addr) &&
-                                       entry[i].valid > router_valid)
-                               entry[i].valid = router_valid;
+               int states[2] = {STATE_RA_DNS, STATE_RA_SEARCH};
+               for (size_t i = 0; i < 2; ++i) {
+                       size_t ra_dns_len;
+                       uint8_t *start = odhcp6c_get_state(states[i], &ra_dns_len);
+                       for (struct odhcp6c_entry *c = (struct odhcp6c_entry*)start;
+                                               (uint8_t*)c < &start[ra_dns_len] && &c->auxtarget[c->auxlen] <= &start[ra_dns_len];
+                                               c = (struct odhcp6c_entry*)(&c->auxtarget[c->auxlen]))
+                               if (IN6_ARE_ADDR_EQUAL(&c->router, &from.sin6_addr) &&
+                                               c->valid > router_valid)
+                                       c->valid = router_valid;
+               }
        }
 
        if (found)
index f8d440f..30441b9 100644 (file)
@@ -182,6 +182,25 @@ static void entry_to_env(const char *name, const void *data, size_t len, enum en
 }
 
 
+static void search_to_env(const char *name, const uint8_t *start, size_t len)
+{
+       size_t buf_len = strlen(name);
+       char *buf = realloc(NULL, buf_len + 2 + len);
+       char *c = mempcpy(buf, name, buf_len);
+       *c++ = '=';
+
+       for (struct odhcp6c_entry *e = (struct odhcp6c_entry*)start;
+                               (uint8_t*)e < &start[len] && &e->auxtarget[e->auxlen] <= &start[len];
+                               e = (struct odhcp6c_entry*)(&e->auxtarget[e->auxlen])) {
+               c = mempcpy(c, e->auxtarget, e->auxlen);
+               *c++ = ' ';
+       }
+
+       c[-1] = '\0';
+       putenv(buf);
+}
+
+
 static void int_to_env(const char *name, int value)
 {
        size_t len = 12 + strlen(name);
@@ -340,12 +359,14 @@ void script_call(const char *status)
        uint8_t *s46_lw = odhcp6c_get_state(STATE_S46_LW, &s46_lw_len);
        uint8_t *passthru = odhcp6c_get_state(STATE_PASSTHRU, &passthru_len);
 
-       size_t prefix_len, address_len, ra_pref_len, ra_route_len, ra_dns_len;
+       size_t prefix_len, address_len, ra_pref_len,
+               ra_route_len, ra_dns_len, ra_search_len;
        uint8_t *prefix = odhcp6c_get_state(STATE_IA_PD, &prefix_len);
        uint8_t *address = odhcp6c_get_state(STATE_IA_NA, &address_len);
        uint8_t *ra_pref = odhcp6c_get_state(STATE_RA_PREFIX, &ra_pref_len);
        uint8_t *ra_route = odhcp6c_get_state(STATE_RA_ROUTE, &ra_route_len);
        uint8_t *ra_dns = odhcp6c_get_state(STATE_RA_DNS, &ra_dns_len);
+       uint8_t *ra_search = odhcp6c_get_state(STATE_RA_SEARCH, &ra_search_len);
 
        // Don't set environment before forking, because env is leaky.
        if (fork() == 0) {
@@ -372,6 +393,7 @@ void script_call(const char *status)
                entry_to_env("RA_ADDRESSES", ra_pref, ra_pref_len, ENTRY_ADDRESS);
                entry_to_env("RA_ROUTES", ra_route, ra_route_len, ENTRY_ROUTE);
                entry_to_env("RA_DNS", ra_dns, ra_dns_len, ENTRY_HOST);
+               search_to_env("RA_DOMAINS", ra_search, ra_search_len);
 
                int_to_env("RA_HOPLIMIT", ra_conf_hoplimit(0));
                int_to_env("RA_MTU", ra_conf_mtu(0));