From b72563dcb7e4c5b70a7598a49d69195ff5aa36b1 Mon Sep 17 00:00:00 2001 From: Wouter Wijngaards Date: Thu, 22 Nov 2007 13:48:58 +0000 Subject: [PATCH] local zone answers. git-svn-id: file:///svn/unbound/trunk@775 be551aaa-1e26-0410-a405-d3ace91eadb9 --- daemon/worker.c | 5 + doc/Changelog | 7 ++ doc/requirements.txt | 5 + services/localzone.c | 225 +++++++++++++++++++++++++++++++++++++------ services/localzone.h | 19 +++- 5 files changed, 233 insertions(+), 28 deletions(-) diff --git a/daemon/worker.c b/daemon/worker.c index 2258f0c4d..433e52220 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -58,6 +58,7 @@ #include "services/cache/infra.h" #include "services/cache/dns.h" #include "services/mesh.h" +#include "services/localzone.h" #include "util/data/msgparse.h" #include "util/data/msgencode.h" #include "util/data/dname.h" @@ -755,6 +756,10 @@ worker_handle_request(struct comm_point* c, void* arg, int error, &edns, c->buffer)) { return 1; } + if(local_zones_answer(worker->daemon->local_zones, &qinfo, &edns, + c->buffer, worker->scratchpad)) { + return (ldns_buffer_limit(c->buffer) != 0); + } h = query_info_hash(&qinfo); if((e=slabhash_lookup(worker->env.msg_cache, h, &qinfo, 0))) { /* answer from cache - we have acquired a readlock on it */ diff --git a/doc/Changelog b/doc/Changelog index 22207fce3..bf00e0def 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -2,6 +2,13 @@ - noted EDNS in-the-middle dropping trouble as a TODO. At this point theoretical, no user trouble has been reported. - added all default AS112 zones. + - answers from local zone content. + * positive answer, the rrset in question + * nodata answer (exist, but not that type). + * nxdomain answer (domain does not exist). + * empty-nonterminal answer. + * But not: wildcard, nsec, referral, rrsig, cname/dname, + or additional section processing, NS put in auth. 21 November 2007: Wouter - local zone internal data setup. diff --git a/doc/requirements.txt b/doc/requirements.txt index 34cb10f85..f124a384a 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -175,3 +175,8 @@ o authority features. You can put authority data on a separate server, and set the server in unbound.conf as stub for those zones, this allows clients to access data from the server without making unbound authoritative for the zones. + +o the access control denies queries before any other processing. + This denies queries that are not authoritative, or version.bind, or any. + And thus prevents cache-snooping (denied hosts cannot make non-recursive + queries and get answers from the cache). diff --git a/services/localzone.c b/services/localzone.c index fa849dfb4..7aa3be331 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -44,7 +44,10 @@ #include "util/config_file.h" #include "util/data/dname.h" #include "util/data/packed_rrset.h" +#include "util/data/msgencode.h" #include "util/net_help.h" +#include "util/data/msgreply.h" +#include "util/data/msgparse.h" struct local_zones* local_zones_create() @@ -225,6 +228,7 @@ get_rr_content(const char* str, uint8_t** nm, uint16_t* type, *type = ldns_rr_get_type(rr); *ttl = (uint32_t)ldns_rr_ttl(rr); ldns_buffer_clear(rdata); + ldns_buffer_skip(rdata, 2); status = ldns_rr_rdata2buffer_wire(rdata, rr); ldns_rr_free(rr); if(status != LDNS_STATUS_OK) { @@ -270,8 +274,9 @@ struct local_rrset* local_data_find_type(struct local_data* data, uint16_t type) { struct local_rrset* p; + type = htons(type); for(p = data->rrsets; p; p = p->next) { - if(ntohs(p->rrset->rk.type) == type) + if(p->rrset->rk.type == type) return p; } return NULL; @@ -365,48 +370,79 @@ insert_rr(struct regional* region, struct packed_rrset_data* pd, return 1; } +/** find a node, create it if not and all its empty nonterminal parents */ +static int +lz_find_create_node(struct local_zone* z, uint8_t* nm, size_t nmlen, + int nmlabs, struct local_data** res) +{ + struct local_data key; + struct local_data* ld; + key.node.key = &key; + key.name = nm; + key.namelen = nmlen; + key.namelabs = nmlabs; + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + if(!ld) { + /* create a domain name to store rr. */ + ld = (struct local_data*)regional_alloc_zero(z->region, + sizeof(*ld)); + if(!ld) { + log_err("out of memory adding local data"); + return 0; + } + ld->node.key = ld; + ld->name = regional_alloc_init(z->region, nm, nmlen); + if(!ld->name) { + log_err("out of memory"); + return 0; + } + ld->namelen = nmlen; + ld->namelabs = nmlabs; + if(!rbtree_insert(&z->data, &ld->node)) { + log_assert(0); /* duplicate name */ + } + /* see if empty nonterminals need to be created */ + if(nmlabs > z->namelabs) { + dname_remove_label(&nm, &nmlen); + if(!lz_find_create_node(z, nm, nmlen, nmlabs-1, res)) + return 0; + } + } + *res = ld; + return 1; +} + /** enter data RR into auth zone */ static int lz_enter_rr_into_zone(struct local_zone* z, ldns_buffer* buf, const char* rrstr) { - struct local_data key; + uint8_t* nm; + size_t nmlen; + int nmlabs; struct local_data* node; struct local_rrset* rrset; struct packed_rrset_data* pd; uint16_t rrtype, rrclass; uint32_t ttl; - if(!get_rr_content(rrstr, &key.name, &rrtype, &rrclass, &ttl, buf)) { + if(!get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, buf)) { log_err("bad local-data: %s", rrstr); return 0; } log_assert(z->dclass == rrclass); - key.node.key = &key; - key.namelabs = dname_count_size_labels(key.name, &key.namelen); - node = (struct local_data*)rbtree_search(&z->data, &key.node); - if(!node) { - /* create a domain name to store rr. */ - node = (struct local_data*)regional_alloc_zero(z->region, - sizeof(*node)); - if(!node) { - log_err("out of memory adding local data"); - return 0; - } - node->node.key = node; - node->name = regional_alloc_init(z->region, key.name, - key.namelen); - if(!node->name) { - log_err("out of memory"); - return 0; - } - node->namelen = key.namelen; - node->namelabs = key.namelabs; - if(!rbtree_insert(&z->data, &node->node)) { - log_assert(0); /* duplicate name */ - } + if(z->type == local_zone_redirect && + query_dname_compare(z->name, nm) != 0) { + log_err("local-data in redirect zone must reside at top of zone" + ", not at %s", rrstr); + return 0; + } + nmlabs = dname_count_size_labels(nm, &nmlen); + if(!lz_find_create_node(z, nm, nmlen, nmlabs, &node)) { + free(nm); + return 0; } - free(key.name); log_assert(node); + free(nm); rrset = local_data_find_type(node, rrtype); if(!rrset) { @@ -853,3 +889,138 @@ void local_zones_print(struct local_zones* zones) local_zone_out(z); } } + +/** encode answer consisting of 1 rrset */ +static int +local_encode(struct query_info* qinfo, struct edns_data* edns, + ldns_buffer* buf, struct regional* temp, + struct ub_packed_rrset_key* rrset, int ansec, int rcode) +{ + struct reply_info rep; + uint16_t udpsize; + /* make answer with time=0 for fixed TTL values */ + memset(&rep, 0, sizeof(rep)); + rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode); + rep.qdcount = 1; + if(ansec) + rep.an_numrrsets = 1; + else rep.ns_numrrsets = 1; + rep.rrset_count = 1; + rep.rrsets = &rrset; + udpsize = edns->udp_size; + edns->edns_version = EDNS_ADVERTISED_VERSION; + edns->udp_size = EDNS_ADVERTISED_SIZE; + edns->ext_rcode = 0; + edns->bits &= EDNS_DO; + if(!reply_info_answer_encode(qinfo, &rep, + *(uint16_t*)ldns_buffer_begin(buf), + ldns_buffer_read_u16_at(buf, 2), + buf, 0, 0, temp, udpsize, edns, + (int)(edns->bits&EDNS_DO), 0)) + error_encode(buf, LDNS_RCODE_SERVFAIL, qinfo, + *(uint16_t*)ldns_buffer_begin(buf), + ldns_buffer_read_u16_at(buf, 2), edns); + return 1; +} + +/** answer local data match */ +static int +local_data_answer(struct local_zone* z, struct query_info* qinfo, + struct edns_data* edns, ldns_buffer* buf, struct regional* temp, + int labs, struct local_data** ldp) +{ + struct local_data key; + struct local_data* ld; + struct local_rrset* lr; + key.node.key = &key; + key.name = qinfo->qname; + key.namelen = qinfo->qname_len; + key.namelabs = labs; + if(z->type == local_zone_redirect) { + key.name = z->name; + key.namelen = z->namelen; + key.namelabs = z->namelabs; + } + ld = (struct local_data*)rbtree_search(&z->data, &key.node); + *ldp = ld; + if(!ld) { + return 0; + } + lr = local_data_find_type(ld, qinfo->qtype); + if(!lr) + return 0; + if(z->type == local_zone_redirect) { + /* convert rrset name to zone name; like a wildcard */ + struct ub_packed_rrset_key r = *lr->rrset; + r.rk.dname = z->name; + r.rk.dname_len = z->namelen; + return local_encode(qinfo, edns, buf, temp, &r, 1, + LDNS_RCODE_NOERROR); + } + return local_encode(qinfo, edns, buf, temp, lr->rrset, 1, + LDNS_RCODE_NOERROR); +} + +/** + * answer in case where no exact match is found + * @param z: zone for query + * @param qinfo: query + * @param edns: edns from query + * @param buf: buffer for answer. + * @param temp: temp region for encoding + * @param ld: local data, if NULL, no such name exists in localdata. + * @return 1 if a reply is to be sent, 0 if not. + */ +static int +lz_zone_answer(struct local_zone* z, struct query_info* qinfo, + struct edns_data* edns, ldns_buffer* buf, struct regional* temp, + struct local_data* ld) +{ + if(z->type == local_zone_deny) { + /** no reply at all, signal caller by clearing buffer. */ + ldns_buffer_clear(buf); + return 1; + } else if(z->type == local_zone_refuse) { + error_encode(buf, LDNS_RCODE_REFUSED, qinfo, + *(uint16_t*)ldns_buffer_begin(buf), + ldns_buffer_read_u16_at(buf, 2), edns); + return 1; + } else if(z->type == local_zone_static || + z->type == local_zone_redirect) { + /* for static, reply nodata or nxdomain + * for redirect, reply nodata */ + /* no additional section processing, + * cname, dname or wildcard processing, + * or using closest match for NSEC. + * or using closest match for returning delegation downwards + */ + int rcode = ld?LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; + if(z->soa) + return local_encode(qinfo, edns, buf, temp, + z->soa, 0, rcode); + error_encode(buf, rcode, qinfo, + *(uint16_t*)ldns_buffer_begin(buf), + ldns_buffer_read_u16_at(buf, 2), edns); + return 1; + } + /* else z->type == local_zone_transparent */ + /* stop here, and resolve further on */ + return 0; +} + +int +local_zones_answer(struct local_zones* zones, struct query_info* qinfo, + struct edns_data* edns, ldns_buffer* buf, struct regional* temp) +{ + /* see if query is covered by a zone, + * if so: - try to match (exact) local data + * - look at zone type for negative response. */ + int labs = dname_count_labels(qinfo->qname); + struct local_data* ld; + struct local_zone* z = local_zones_lookup(zones, qinfo->qname, + qinfo->qname_len, labs, qinfo->qclass); + if(!z) return 0; + if(local_data_answer(z, qinfo, edns, buf, temp, labs, &ld)) + return 1; + return lz_zone_answer(z, qinfo, edns, buf, temp, ld); +} diff --git a/services/localzone.h b/services/localzone.h index 6339bf69b..4e6a54f98 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -45,6 +45,8 @@ struct ub_packed_rrset_key; struct regional; struct config_file; +struct edns_data; +struct query_info; /** * Local zone type @@ -120,7 +122,8 @@ struct local_data { size_t namelen; /** number of labels in name */ int namelabs; - /** the data rrsets, with different types, linked list */ + /** the data rrsets, with different types, linked list. + * If this list is NULL, the node is an empty non-terminal. */ struct local_rrset* rrsets; }; @@ -195,4 +198,18 @@ struct local_zone* local_zones_lookup(struct local_zones* zones, */ void local_zones_print(struct local_zones* zones); +/** + * Answer authoritatively for local zones. + * @param zones: the stored zones (shared, read only). + * @param qinfo: query info (parsed). + * @param edns: edns info (parsed). + * @param buf: buffer with query ID and flags, also for reply. + * @param temp: temporary storage region. + * @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). + */ +int local_zones_answer(struct local_zones* zones, struct query_info* qinfo, + struct edns_data* edns, ldns_buffer* buf, struct regional* temp); + #endif /* SERVICES_LOCALZONE_H */ -- 2.47.2