]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Patch that resolves CNAMEs entered in local-data conf statements that
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 18 Oct 2016 13:18:20 +0000 (13:18 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Tue, 18 Oct 2016 13:18:20 +0000 (13:18 +0000)
  point to data on the internet, from Jinmei Tatuya (Infoblox).

git-svn-id: file:///svn/unbound/trunk@3885 be551aaa-1e26-0410-a405-d3ace91eadb9

16 files changed:
daemon/acl_list.c
daemon/worker.c
doc/Changelog
iterator/iterator.c
libunbound/libworker.c
services/localzone.c
services/localzone.h
services/mesh.c
services/mesh.h
testcode/perf.c
testcode/streamtcp.c
util/data/msgencode.c
util/data/msgreply.c
util/data/msgreply.h
validator/autotrust.c
validator/validator.c

index fde61ebb8866074bff187c3425036587914caf6d..a37c8b0e621ac3d321818b90e89780793929e6a1 100644 (file)
@@ -227,15 +227,48 @@ acl_list_tag_action_cfg(struct acl_list* acl, struct config_file* cfg,
 
 /** check wire data parse */
 static int
-check_data(const char* data)
+check_data(const char* data, const struct config_strlist* head)
 {
        char buf[65536];
        uint8_t rr[LDNS_RR_BUF_SIZE];
        size_t len = sizeof(rr);
        int res;
-       snprintf(buf, sizeof(buf), "%s %s", "example.com.", data);
+       /* '.' is sufficient for validation, and it makes the call to
+        * sldns_wirerr_get_type() simpler below.
+        * (once adopted, this comment can be removed) */
+       snprintf(buf, sizeof(buf), "%s %s", ".", data);
        res = sldns_str2wire_rr_buf(buf, rr, &len, NULL, 3600, NULL, 0,
                NULL, 0);
+
+       /* Reject it if we would end up having CNAME and other data (including
+        * another CNAME) for the same tag. */
+       if(res == 0 && head) {
+               const char* err_data = NULL;
+
+               if(sldns_wirerr_get_type(rr, len, 1) == LDNS_RR_TYPE_CNAME) {
+                       /* adding CNAME while other data already exists. */
+                       err_data = data;
+               } else {
+                       snprintf(buf, sizeof(buf), "%s %s", ".", head->str);
+                       len = sizeof(rr);
+                       res = sldns_str2wire_rr_buf(buf, rr, &len, NULL, 3600,
+                               NULL, 0, NULL, 0);
+                       if(res != 0) {
+                               /* This should be impossible here as head->str
+                                * has been validated, but we check it just in
+                                * case. */
+                               return 0;
+                       }
+                       if(sldns_wirerr_get_type(rr, len, 1) ==
+                               LDNS_RR_TYPE_CNAME) /* already have CNAME */
+                               err_data = head->str;
+               }
+               if(err_data) {
+                       log_err("redirect tag data '%s' must not coexist with "
+                               "other data.", err_data);
+                       return 0;
+               }
+       }
        if(res == 0)
                return 1;
        log_err("rr data [char %d] parse error %s",
@@ -275,7 +308,7 @@ acl_list_tag_data_cfg(struct acl_list* acl, struct config_file* cfg,
        }
 
        /* check data? */
-       if(!check_data(data)) {
+       if(!check_data(data, node->tag_datas[tagid])) {
                log_err("cannot parse access-control-tag data: %s %s '%s'",
                        str, tag, data);
                return 0;
index 1d4c17f5d812a2022fcc4b71a29bfa0e1df8448e..2adb1d49d5b98719ff1c7e475a116abc04fc7af7 100644 (file)
@@ -485,6 +485,10 @@ answer_norec_from_cache(struct worker* worker, struct query_info* qinfo,
        if(!dp) { /* no delegation, need to reprime */
                return 0;
        }
+       /* In case we have a local alias, copy it into the delegation message.
+        * Shallow copy should be fine, as we'll be done with msg in this
+        * function. */
+       msg->qinfo.local_alias = qinfo->local_alias;
        if(must_validate) {
                switch(check_delegation_secure(msg->rep)) {
                case sec_status_unchecked:
@@ -986,6 +990,26 @@ worker_handle_request(struct comm_point* c, void* arg, int error,
                        &repinfo->addr, repinfo->addrlen);
                goto send_reply;
        }
+
+       /* If we've found a local alias, replace the qname with the alias
+        * target before resolving it. */
+       if(qinfo.local_alias) {
+               struct ub_packed_rrset_key* rrset = qinfo.local_alias->rrset;
+               struct packed_rrset_data* d = rrset->entry.data;
+
+               /* Sanity check: our current implementation only supports
+                * a single CNAME RRset as a local alias. */
+               if(qinfo.local_alias->next ||
+                       rrset->rk.type != htons(LDNS_RR_TYPE_CNAME) ||
+                       d->count != 1) {
+                       log_err("assumption failure: unexpected local alias");
+                       regional_free_all(worker->scratchpad);
+                       return 0; /* drop it */
+               }
+               qinfo.qname = d->rr_data[0] + 2;
+               qinfo.qname_len = d->rr_len[0] - 2;
+       }
+
        h = query_info_hash(&qinfo, sldns_buffer_read_u16_at(c->buffer, 2));
        if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) {
                /* answer from cache - we have acquired a readlock on it */
index 39a63d78e4af0ae57a2c840281dccd22d9f6d479..17895981784e1a5761b9566dd2e535a26b1b8afb 100644 (file)
@@ -1,3 +1,7 @@
+18 October 2016: Wouter
+       - Patch that resolves CNAMEs entered in local-data conf statements that
+         point to data on the internet, from Jinmei Tatuya (Infoblox).
+
 17 October 2016: Wouter
        - Re-fix #839 from view commit overwrite.
        - Fixup const void cast warning.
index 2afb244968e218bb17ad5925999d555e355509da..a2ba77822d6eca22270f5097a2a65095624b3510 100644 (file)
@@ -551,6 +551,7 @@ generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype,
        qinf.qname_len = qnamelen;
        qinf.qtype = qtype;
        qinf.qclass = qclass;
+       qinf.local_alias = NULL;
 
        /* RD should be set only when sending the query back through the INIT
         * state. */
index 3e22c998fb2f796c4f84fd9e5f0026fc175540fa..08599090e035987983c570be224c0c6d5c2557ca 100644 (file)
@@ -577,6 +577,7 @@ setup_qinfo_edns(struct libworker* w, struct ctx_query* q,
        if(!qinfo->qname) {
                return 0;
        }
+       qinfo->local_alias = NULL;
        edns->edns_present = 1;
        edns->ext_rcode = 0;
        edns->edns_version = 0;
index 41461192571588f49777bf8aecaf9ff951f82edc..898ad7dc6dc31c6426a88cadbb1fe3a02f9b3c46 100644 (file)
@@ -280,16 +280,20 @@ get_rr_nameclass(const char* str, uint8_t** nm, uint16_t* dclass)
  * Find an rrset in local data structure.
  * @param data: local data domain name structure.
  * @param type: type to look for (host order).
+ * @param alias_ok: 1 if matching a non-exact, alias type such as CNAME is
+ * allowed.  otherwise 0.
  * @return rrset pointer or NULL if not found.
  */
 static struct local_rrset*
-local_data_find_type(struct local_data* data, uint16_t type)
+local_data_find_type(struct local_data* data, uint16_t type, int alias_ok)
 {
        struct local_rrset* p;
        type = htons(type);
        for(p = data->rrsets; p; p = p->next) {
                if(p->rrset->rk.type == type)
                        return p;
+               if(alias_ok && p->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME))
+                       return p;
        }
        return NULL;
 }
@@ -469,7 +473,23 @@ lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr)
        log_assert(node);
        free(nm);
 
-       rrset = local_data_find_type(node, rrtype);
+       /* Reject it if we would end up having CNAME and other data (including
+        * another CNAME) for a redirect zone. */
+       if(z->type == local_zone_redirect && node->rrsets) {
+               const char* othertype = NULL;
+               if (rrtype == LDNS_RR_TYPE_CNAME)
+                       othertype = "other";
+               else if (node->rrsets->rrset->rk.type ==
+                        htons(LDNS_RR_TYPE_CNAME)) {
+                       othertype = "CNAME";
+               }
+               if(othertype) {
+                       log_err("local-data '%s' in redirect zone must not "
+                               "coexist with %s local-data", rrstr, othertype);
+                       return 0;
+               }
+       }
+       rrset = local_data_find_type(node, rrtype, 0);
        if(!rrset) {
                rrset = new_local_rrset(z->region, node, rrtype, rrclass);
                if(!rrset)
@@ -1203,6 +1223,8 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list,
        int res;
        struct packed_rrset_data* d;
        for(p=list; p; p=p->next) {
+               uint16_t rdr_type;
+
                len = sizeof(rr);
                /* does this element match the type? */
                snprintf(buf, sizeof(buf), ". %s", p->str);
@@ -1214,7 +1236,8 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list,
                        continue;
                if(len < 1 /* . */ + 8 /* typeclassttl*/ + 2 /*rdatalen*/)
                        continue;
-               if(sldns_wirerr_get_type(rr, len, 1) != qinfo->qtype)
+               rdr_type = sldns_wirerr_get_type(rr, len, 1);
+               if(rdr_type != qinfo->qtype && rdr_type != LDNS_RR_TYPE_CNAME)
                        continue;
                
                /* do we have entries already? if not setup key */
@@ -1222,7 +1245,7 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list,
                        r->entry.key = r;
                        r->rk.dname = qinfo->qname;
                        r->rk.dname_len = qinfo->qname_len;
-                       r->rk.type = htons(qinfo->qtype);
+                       r->rk.type = htons(rdr_type);
                        r->rk.rrset_class = htons(qinfo->qclass);
                        r->rk.flags = 0;
                        d = (struct packed_rrset_data*)regional_alloc_zero(
@@ -1271,6 +1294,20 @@ find_tag_datas(struct query_info* qinfo, struct config_strlist* list,
                        if(!d) return 0; /* out of memory */
                d->count++;
        }
+       /* If we've found a non-exact alias type of local data, make a shallow
+        * copy of the RRset and remember it in qinfo to complete the alias
+        * chain later. */
+       if(r->rk.dname && qinfo->qtype != LDNS_RR_TYPE_CNAME &&
+               r->rk.type == htons(LDNS_RR_TYPE_CNAME)) {
+               qinfo->local_alias =
+                       regional_alloc_zero(temp, sizeof(struct local_rrset));
+               if(!qinfo->local_alias)
+                       return 0; /* out of memory */
+               qinfo->local_alias->rrset =
+                       regional_alloc_init(temp, r, sizeof(*r));
+               if(!qinfo->local_alias->rrset)
+                       return 0; /* out of memory */
+       }
        if(r->rk.dname)
                return 1;
        return 0;
@@ -1302,6 +1339,13 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo,
                                z->name, z->namelen)) {
                                verbose(VERB_ALGO, "redirect with tag data [%d] %s",
                                        tag, (tag<num_tags?tagname[tag]:"null"));
+
+                               /* If we found a matching alias, we should
+                                * use it as part of the answer, but we can't
+                                * encode it until we complete the alias
+                                * chain. */
+                               if(qinfo->local_alias)
+                                       return 1;
                                return local_encode(qinfo, edns, buf, temp,
                                        &r, 1, LDNS_RCODE_NOERROR);
                        }
@@ -1312,9 +1356,26 @@ local_data_answer(struct local_zone* z, struct query_info* qinfo,
        if(!ld) {
                return 0;
        }
-       lr = local_data_find_type(ld, qinfo->qtype);
+       lr = local_data_find_type(ld, qinfo->qtype, 1);
        if(!lr)
                return 0;
+
+       /* Special case for alias matching.  See local_data_answer(). */
+       if(lz_type == local_zone_redirect &&
+               qinfo->qtype != LDNS_RR_TYPE_CNAME &&
+               lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) {
+               qinfo->local_alias =
+                       regional_alloc_zero(temp, sizeof(struct local_rrset));
+               if(!qinfo->local_alias)
+                       return 0; /* out of memory */
+               qinfo->local_alias->rrset =
+                       regional_alloc_init(temp, lr->rrset, sizeof(*lr->rrset));
+               if(!qinfo->local_alias->rrset)
+                       return 0; /* out of memory */
+               qinfo->local_alias->rrset->rk.dname = qinfo->qname;
+               qinfo->local_alias->rrset->rk.dname_len = qinfo->qname_len;
+               return 1;
+       }
        if(lz_type == local_zone_redirect) {
                /* convert rrset name to query name; like a wildcard */
                struct ub_packed_rrset_key r = *lr->rrset;
@@ -1518,11 +1579,13 @@ local_zones_answer(struct local_zones* zones, struct query_info* qinfo,
                && local_data_answer(z, qinfo, edns, buf, temp, labs, &ld, lzt,
                        tag, tag_datas, tag_datas_size, tagname, num_tags)) {
                lock_rw_unlock(&z->lock);
-               return 1;
+               /* We should tell the caller that encode is deferred if we found
+                * a local alias. */
+               return !qinfo->local_alias;
        }
        r = lz_zone_answer(z, qinfo, edns, buf, temp, ld, lzt);
        lock_rw_unlock(&z->lock);
-       return r;
+       return r && !qinfo->local_alias; /* see above */
 }
 
 const char* local_zone_type2str(enum localzone_type t)
index 9408a5f5ff72aa5f0e973c7dd141ac274feef5f9..46f5cdbec872ccfe4197b6a79a33cd3e845d96e7 100644 (file)
@@ -282,6 +282,14 @@ void local_zones_print(struct local_zones* zones);
  * @return true if answer is in buffer. false if query is not answered 
  * by authority data. If the reply should be dropped altogether, the return 
  * value is true, but the buffer is cleared (empty).
+ * It can also return true if a non-exact alias answer is found.  In this
+ * case qinfo->local_alias points to the corresponding alias RRset but the
+ * answer is NOT encoded in buffer.  It's the caller's responsibility to
+ * complete the alias chain (if needed) and encode the final set of answer.
+ * Data pointed to by qinfo->local_alias is allocated in 'temp' or refers to
+ * configuration data.  So the caller will need to make a deep copy of it
+ * if it needs to keep it beyond the lifetime of 'temp' or a dynamic update
+ * to local zone data.
  */
 int local_zones_answer(struct local_zones* zones, struct query_info* qinfo,
        struct edns_data* edns, struct sldns_buffer* buf, struct regional* temp,
index 04912383c621cb8bcb3496a0e01519b502366ddb..24d16ae92239617957c62b00bfd4dfc50d7152f0 100644 (file)
@@ -56,6 +56,8 @@
 #include "util/alloc.h"
 #include "util/config_file.h"
 #include "sldns/sbuffer.h"
+#include "services/localzone.h"
+#include "util/data/dname.h"
 
 /** subtract timers and the values do not overflow or become negative */
 static void
@@ -338,7 +340,7 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
        if(!s->reply_list && !s->cb_list)
                was_noreply = 1;
        /* add reply to s */
-       if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo->qname)) {
+       if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) {
                        log_err("mesh_new_client: out of memory; SERVFAIL");
                        if(!edns_opt_inplace_reply(edns, mesh->env->scratch))
                                edns->opt_list = NULL;
@@ -847,6 +849,10 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
        struct timeval end_time;
        struct timeval duration;
        int secure;
+       /* Copy the client's EDNS for later restore to fix a bug of the
+        * original code.  See unbound bug #1125.  Once NLNet Labs fixes it
+        * we should replace this local fix with the upstream one. */
+       struct edns_data edns_bak = r->edns;
        /* examine security status */
        if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) ||
                m->s.env->cfg->ignore_cd) && rep && 
@@ -861,7 +867,13 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
        if(!rep && rcode == LDNS_RCODE_NOERROR)
                rcode = LDNS_RCODE_SERVFAIL;
        /* send the reply */
+       /* We don't reuse the encoded answer if either the previous or current
+        * response has a local alias.  We could compare the alias records
+        * and still reuse the previous answer if they are the same, but that
+        * would be complicated and error prone for the relatively minor case.
+        * So we err on the side of safety. */
        if(prev && prev->qflags == r->qflags && 
+               !prev->local_alias && !r->local_alias &&
                prev->edns.edns_present == r->edns.edns_present && 
                prev->edns.bits == r->edns.bits && 
                prev->edns.udp_size == r->edns.udp_size &&
@@ -880,6 +892,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
                m->s.qinfo.qname = r->qname;
                if(!edns_opt_inplace_reply(&r->edns, m->s.region))
                        r->edns.opt_list = NULL;
+               m->s.qinfo.local_alias = r->local_alias;
                error_encode(r->query_reply.c->buffer, rcode, &m->s.qinfo,
                        r->qid, r->qflags, &r->edns);
                comm_point_send_reply(&r->query_reply);
@@ -890,6 +903,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
                r->edns.ext_rcode = 0;
                r->edns.bits &= EDNS_DO;
                m->s.qinfo.qname = r->qname;
+               m->s.qinfo.local_alias = r->local_alias;
                if(!edns_opt_inplace_reply(&r->edns, m->s.region) ||
                   !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, 
                        r->qflags, r->query_reply.c->buffer, 0, 1, 
@@ -900,6 +914,7 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
                                LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, 
                                r->qflags, &r->edns);
                }
+               r->edns = edns_bak; /* Fix unbound bug #1125.  See above. */
                comm_point_send_reply(&r->query_reply);
        }
        /* account */
@@ -998,7 +1013,8 @@ int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns,
 }
 
 int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
-        struct comm_reply* rep, uint16_t qid, uint16_t qflags, uint8_t* qname)
+        struct comm_reply* rep, uint16_t qid, uint16_t qflags,
+        const struct query_info* qinfo)
 {
        struct mesh_reply* r = regional_alloc(s->s.region, 
                sizeof(struct mesh_reply));
@@ -1016,10 +1032,62 @@ int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
        r->qflags = qflags;
        r->start_time = *s->s.env->now_tv;
        r->next = s->reply_list;
-       r->qname = regional_alloc_init(s->s.region, qname, 
+       r->qname = regional_alloc_init(s->s.region, qinfo->qname,
                s->s.qinfo.qname_len);
        if(!r->qname)
                return 0;
+
+       /* Data related to local alias stored in 'qinfo' (if any) is ephemeral
+        * and can be different for different original queries (even if the
+        * replaced query name is the same).  So we need to make a deep copy
+        * and store the copy for each reply info. */
+       if(qinfo->local_alias) {
+               struct packed_rrset_data* d;
+               struct packed_rrset_data* dsrc;
+               r->local_alias = regional_alloc_zero(s->s.region,
+                       sizeof(*qinfo->local_alias));
+               if(!r->local_alias)
+                       return 0;
+               r->local_alias->rrset = regional_alloc_init(s->s.region,
+                       qinfo->local_alias->rrset,
+                       sizeof(*qinfo->local_alias->rrset));
+               if(!r->local_alias->rrset)
+                       return 0;
+               dsrc = qinfo->local_alias->rrset->entry.data;
+
+               /* In the current implementation, a local alias must be
+                * a single CNAME RR (see worker_handle_request()). */
+               log_assert(!qinfo->local_alias->next && dsrc->count == 1 &&
+                       qinfo->local_alias->rrset->rk.type ==
+                       htons(LDNS_RR_TYPE_CNAME));
+               /* Technically, we should make a local copy for the owner
+                * name of the RRset, but in the case of the first (and
+                * currently only) local alias RRset, the owner name should
+                * point to the qname of the corresponding query, which should
+                * be valid throughout the lifetime of this mesh_reply.  So
+                * we can skip copying. */
+               log_assert(qinfo->local_alias->rrset->rk.dname ==
+                       sldns_buffer_at(rep->c->buffer, LDNS_HEADER_SIZE));
+
+               d = regional_alloc_init(s->s.region, dsrc,
+                       sizeof(struct packed_rrset_data)
+                       + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t));
+               if(!d)
+                       return 0;
+               r->local_alias->rrset->entry.data = d;
+               d->rr_len = (size_t*)((uint8_t*)d +
+                       sizeof(struct packed_rrset_data));
+               d->rr_data = (uint8_t**)&(d->rr_len[1]);
+               d->rr_ttl = (time_t*)&(d->rr_data[1]);
+               d->rr_len[0] = dsrc->rr_len[0];
+               d->rr_ttl[0] = dsrc->rr_ttl[0];
+               d->rr_data[0] = regional_alloc_init(s->s.region,
+                       dsrc->rr_data[0], d->rr_len[0]);
+               if(!d->rr_data[0])
+                       return 0;
+       } else
+               r->local_alias = NULL;
+
        s->reply_list = r;
        return 1;
 }
index 086e39094e8f9caed021fa4b8c470268cc5d8854..ca09d8958048c54fbf6488afc48762aeca0bb846 100644 (file)
@@ -214,6 +214,8 @@ struct mesh_reply {
        uint16_t qflags;
        /** qname from this query. len same as mesh qinfo. */
        uint8_t* qname;
+       /** same as that in query_info. */
+       struct local_rrset* local_alias;
 };
 
 /** 
@@ -460,10 +462,12 @@ int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub);
  * @param qid: ID of reply.
  * @param qflags: original query flags.
  * @param qname: original query name.
+ * @param qinfo: original query info.
  * @return: 0 on alloc error.
  */
-int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns, 
-       struct comm_reply* rep, uint16_t qid, uint16_t qflags, uint8_t* qname);
+int mesh_state_add_reply(struct mesh_state* s, struct edns_data* edns,
+       struct comm_reply* rep, uint16_t qid, uint16_t qflags,
+       const struct query_info* qinfo);
 
 /**
  * Create new callback structure and attach it to a mesh state.
index 320cbc933e9e12bd266dd031bf733238b144d3c4..d11357c4acaa771dc6a12f4ab4e0980fd1bd1b57 100644 (file)
@@ -487,6 +487,7 @@ qlist_parse_line(sldns_buffer* buf, char* p)
        qinfo.qname = sldns_str2wire_dname(nm, &qinfo.qname_len);
        if(!qinfo.qname)
                return 0;
+       qinfo.local_alias = NULL;
        qinfo_query_encode(buf, &qinfo);
        sldns_buffer_write_u16_at(buf, 0, 0); /* zero ID */
        if(rec) LDNS_RD_SET(sldns_buffer_begin(buf));
index 05dcf98a85ec385cc5bd9330410d774c2cdce859..34b5c0281369228cc4015ea3f4db526ab591f664 100644 (file)
@@ -128,6 +128,9 @@ write_q(int fd, int udp, SSL* ssl, sldns_buffer* buf, uint16_t id,
        qinfo.qtype = sldns_get_rr_type_by_name(strtype);
        qinfo.qclass = sldns_get_rr_class_by_name(strclass);
 
+       /* clear local alias */
+       qinfo.local_alias = NULL;
+
        /* make query */
        qinfo_query_encode(buf, &qinfo);
        sldns_buffer_write_u16_at(buf, 0, id);
index 034bb24bd6e4fcdeb616a0fe13f077d480b4eb31..cecc8d8e161b1db64bb102c0313af4338ded8b69 100644 (file)
@@ -48,6 +48,7 @@
 #include "util/regional.h"
 #include "util/net_help.h"
 #include "sldns/sbuffer.h"
+#include "services/localzone.h"
 
 /** return code that means the function ran out of memory. negative so it does
  * not conflict with DNS rcodes. */
@@ -534,7 +535,14 @@ insert_section(struct reply_info* rep, size_t num_rrsets, uint16_t* num_rrs,
 {
        int r;
        size_t i, setstart;
-       *num_rrs = 0;
+       /* we now allow this function to be called multiple times for the
+        * same section, incrementally updating num_rrs.  The caller is
+        * responsible for initializing it (which is the case in the current
+        * implementation).
+        * Note: once approved, this comment and the following line should be
+        * removed. */
+       /**num_rrs = 0;*/
+
        if(s != LDNS_SECTION_ADDITIONAL) {
                if(s == LDNS_SECTION_ANSWER && qtype == LDNS_RR_TYPE_ANY)
                        dnssec = 1; /* include all types in ANY answer */
@@ -581,17 +589,20 @@ static int
 insert_query(struct query_info* qinfo, struct compress_tree_node** tree, 
        sldns_buffer* buffer, struct regional* region)
 {
+       uint8_t* qname = qinfo->local_alias ?
+               qinfo->local_alias->rrset->rk.dname : qinfo->qname;
+       size_t qname_len = qinfo->local_alias ?
+               qinfo->local_alias->rrset->rk.dname_len : qinfo->qname_len;
        if(sldns_buffer_remaining(buffer) < 
                qinfo->qname_len+sizeof(uint16_t)*2)
                return RETVAL_TRUNC; /* buffer too small */
        /* the query is the first name inserted into the tree */
-       if(!compress_tree_store(qinfo->qname, 
-               dname_count_labels(qinfo->qname), 
+       if(!compress_tree_store(qname, dname_count_labels(qname),
                sldns_buffer_position(buffer), region, NULL, tree))
                return RETVAL_OUTMEM;
-       if(sldns_buffer_current(buffer) == qinfo->qname)
-               sldns_buffer_skip(buffer, (ssize_t)qinfo->qname_len);
-       else    sldns_buffer_write(buffer, qinfo->qname, qinfo->qname_len);
+       if(sldns_buffer_current(buffer) == qname)
+               sldns_buffer_skip(buffer, (ssize_t)qname_len);
+       else    sldns_buffer_write(buffer, qname, qname_len);
        sldns_buffer_write_u16(buffer, qinfo->qtype);
        sldns_buffer_write_u16(buffer, qinfo->qclass);
        return RETVAL_OK;
@@ -662,6 +673,33 @@ reply_info_encode(struct query_info* qinfo, struct reply_info* rep,
         * for different roundrobins for sequential id client senders. */
        rr_offset = RRSET_ROUNDROBIN?ntohs(id):0;
 
+       /* "prepend" any local alias records in the answer section if this
+        * response is supposed to be authoritative.  Currently it should
+        * be a single CNAME record (sanity-checked in worker_handle_request())
+        * but it can be extended if and when we support more variations of
+        * aliases. */
+       if(qinfo->local_alias && (flags & BIT_AA)) {
+               struct reply_info arep;
+               time_t timezero = 0; /* to use the 'authoritative' TTL */
+               memset(&arep, 0, sizeof(arep));
+               arep.flags = rep->flags;
+               arep.an_numrrsets = 1;
+               arep.rrset_count = 1;
+               arep.rrsets = &qinfo->local_alias->rrset;
+               if((r=insert_section(&arep, 1, &ancount, buffer, 0,
+                       timezero, region, &tree, LDNS_SECTION_ANSWER,
+                       qinfo->qtype, dnssec, rr_offset)) != RETVAL_OK) {
+                       if(r == RETVAL_TRUNC) {
+                               /* create truncated message */
+                               sldns_buffer_write_u16_at(buffer, 6, ancount);
+                               LDNS_TC_SET(sldns_buffer_begin(buffer));
+                               sldns_buffer_flip(buffer);
+                               return 1;
+                       }
+                       return 0;
+               }
+       }
+
        /* insert answer section */
        if((r=insert_section(rep, rep->an_numrrsets, &ancount, buffer, 
                0, timenow, region, &tree, LDNS_SECTION_ANSWER, qinfo->qtype, 
@@ -782,6 +820,15 @@ reply_info_answer_encode(struct query_info* qinf, struct reply_info* rep,
        }
        if(secure && (dnssec || (qflags&BIT_AD)))
                flags |= BIT_AD;
+       /* restore AA bit if we have a local alias and the response can be
+        * authoritative.  Also clear AD bit if set as the local data is the
+        * primary answer. */
+       if(qinf->local_alias &&
+               (FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR ||
+               FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN)) {
+               flags |= BIT_AA;
+               flags &= ~BIT_AD;
+       }
        log_assert(flags & BIT_QR); /* QR bit must be on in our replies */
        if(udpsize < LDNS_HEADER_SIZE)
                return 0;
@@ -807,13 +854,17 @@ void
 qinfo_query_encode(sldns_buffer* pkt, struct query_info* qinfo)
 {
        uint16_t flags = 0; /* QUERY, NOERROR */
+       const uint8_t* qname = qinfo->local_alias ?
+               qinfo->local_alias->rrset->rk.dname : qinfo->qname;
+       size_t qname_len = qinfo->local_alias ?
+               qinfo->local_alias->rrset->rk.dname_len : qinfo->qname_len;
        sldns_buffer_clear(pkt);
        log_assert(sldns_buffer_remaining(pkt) >= 12+255+4/*max query*/);
        sldns_buffer_skip(pkt, 2); /* id done later */
        sldns_buffer_write_u16(pkt, flags);
        sldns_buffer_write_u16(pkt, 1); /* query count */
        sldns_buffer_write(pkt, "\000\000\000\000\000\000", 6); /* counts */
-       sldns_buffer_write(pkt, qinfo->qname, qinfo->qname_len);
+       sldns_buffer_write(pkt, qname, qname_len);
        sldns_buffer_write_u16(pkt, qinfo->qtype);
        sldns_buffer_write_u16(pkt, qinfo->qclass);
        sldns_buffer_flip(pkt);
@@ -838,9 +889,14 @@ error_encode(sldns_buffer* buf, int r, struct query_info* qinfo,
        sldns_buffer_write(buf, &flags, sizeof(uint16_t));
        sldns_buffer_write(buf, &flags, sizeof(uint16_t));
        if(qinfo) {
-               if(sldns_buffer_current(buf) == qinfo->qname)
-                       sldns_buffer_skip(buf, (ssize_t)qinfo->qname_len);
-               else    sldns_buffer_write(buf, qinfo->qname, qinfo->qname_len);
+               const uint8_t* qname = qinfo->local_alias ?
+                       qinfo->local_alias->rrset->rk.dname : qinfo->qname;
+               size_t qname_len = qinfo->local_alias ?
+                       qinfo->local_alias->rrset->rk.dname_len :
+                       qinfo->qname_len;
+               if(sldns_buffer_current(buf) == qname)
+                       sldns_buffer_skip(buf, (ssize_t)qname_len);
+               else    sldns_buffer_write(buf, qname, qname_len);
                sldns_buffer_write_u16(buf, qinfo->qtype);
                sldns_buffer_write_u16(buf, qinfo->qclass);
        }
index f8a24918dcad552056943a68a14d70b5f02cc35b..1f02eac331d87cb3c084b6a97a9e748df8b4cc3a 100644 (file)
@@ -76,6 +76,7 @@ parse_create_qinfo(sldns_buffer* pkt, struct msg_parse* msg,
        qinf->qname_len = msg->qname_len;
        qinf->qtype = msg->qtype;
        qinf->qclass = msg->qclass;
+       qinf->local_alias = NULL;
        return 1;
 }
 
@@ -451,6 +452,7 @@ int reply_info_parse(sldns_buffer* pkt, struct alloc_cache* alloc,
        int ret;
        
        qinf->qname = NULL;
+       qinf->local_alias = NULL;
        *rep = NULL;
        if(!(msg = regional_alloc(region, sizeof(*msg)))) {
                return LDNS_RCODE_SERVFAIL;
@@ -542,6 +544,7 @@ query_info_parse(struct query_info* m, sldns_buffer* query)
                return 0; /* need qtype, qclass */
        m->qtype = sldns_buffer_read_u16(query);
        m->qclass = sldns_buffer_read_u16(query);
+       m->local_alias = NULL;
        return 1;
 }
 
index b542b75e697086a5982a9fe6188103ce13e9db11..729e35573753fa863ece9baf1bf1725b98f2ee4e 100644 (file)
@@ -51,6 +51,7 @@ struct regional;
 struct edns_data;
 struct msg_parse;
 struct rrset_parse;
+struct local_rrset;
 
 /** calculate the prefetch TTL as 90% of original. Calculation
  * without numerical overflow (uin32_t) */
@@ -73,6 +74,23 @@ struct query_info {
        uint16_t qtype;
        /** qclass, host byte order */
        uint16_t qclass;
+       /**
+        * Alias local answer(s) for the qname.  If 'qname' is an alias defined
+        * in a local zone, this field will be set to the corresponding local
+        * RRset when the alias is determined.
+        * In the initial implementation this can only be a single CNAME RR
+        * (or NULL), but it could possibly be extended to be a DNAME or a
+        * chain of aliases.
+        * Users of this structure are responsible to initialize this field
+        * to be NULL; otherwise other part of query handling code may be
+        * confused.
+        * Users also have to be careful about the lifetime of data.  On return
+        * from local zone lookup, it may point to data derived from
+        * configuration that may be dynamically invalidated or data allocated
+        * in an ephemeral regional allocator.  A deep copy of the data may
+        * have to be generated if it has to be kept during iterative
+        * resolution. */
+       struct local_rrset* local_alias;
 };
 
 /**
index da8829cebf250a9a574f06920e9ccca11f44751a..a2fcc871e7b1559ce1983121baa8d90f16fd5133 100644 (file)
@@ -2328,6 +2328,7 @@ probe_anchor(struct module_env* env, struct trust_anchor* tp)
        qinfo.qname_len = tp->namelen;
        qinfo.qtype = LDNS_RR_TYPE_DNSKEY;
        qinfo.qclass = tp->dclass;
+       qinfo.local_alias = NULL;
        log_query_info(VERB_ALGO, "autotrust probe", &qinfo);
        verbose(VERB_ALGO, "retry probe set in %d seconds", 
                (int)tp->autr->next_probe_time - (int)*env->now);
index f9b6a986ec9e1b0f937eba5c340b883417cef162..362cd2a39adc42959b367d6d7f16f23054fe0e3c 100644 (file)
@@ -377,6 +377,7 @@ generate_request(struct module_qstate* qstate, int id, uint8_t* name,
        ask.qname_len = namelen;
        ask.qtype = qtype;
        ask.qclass = qclass;
+       ask.local_alias = NULL;
        log_query_info(VERB_ALGO, "generate request", &ask);
        fptr_ok(fptr_whitelist_modenv_attach_sub(qstate->env->attach_sub));
        /* enable valrec flag to avoid recursion to the same validation