diff --git a/contrib/unbound_munin_ b/contrib/unbound_munin_ index df63ead4b..341f5071b 100755 --- a/contrib/unbound_munin_ +++ b/contrib/unbound_munin_ @@ -9,8 +9,9 @@ # statistics-cumulative: no # statistics-interval: 0 # remote-control: control-enable: yes +# Run the command unbound-control-setup to generate the key files. # -# Environment variables +# Environment variables for this script # statefile - where to put temporary statefile. # unbound_conf - where the unbound.conf file is located. # unbound_control - where to find unbound-control executable. diff --git a/daemon/remote.c b/daemon/remote.c index d6e4f0ef6..870fec0a0 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -54,8 +54,10 @@ #include "services/listen_dnsport.h" #include "services/cache/rrset.h" #include "services/mesh.h" +#include "services/localzone.h" #include "util/storage/slabhash.h" #include "util/fptr_wlist.h" +#include "util/data/dname.h" #ifdef HAVE_SYS_TYPES_H # include @@ -839,6 +841,139 @@ do_stats(SSL* ssl, struct daemon_remote* rc) } } +/** parse commandline argument domain name */ +static int +parse_arg_name(SSL* ssl, char* str, uint8_t** res, size_t* len, int* labs) +{ + ldns_rdf* rdf; + *res = NULL; + *len = 0; + *labs = 0; + rdf = ldns_dname_new_frm_str(str); + if(!rdf) { + ssl_printf(ssl, "error cannot parse name %s\n", str); + return 0; + } + *res = memdup(ldns_rdf_data(rdf), ldns_rdf_size(rdf)); + ldns_rdf_deep_free(rdf); + if(!*res) { + ssl_printf(ssl, "error out of memory\n"); + return 0; + } + *labs = dname_count_size_labels(*res, len); + return 1; +} + +/** find second argument, modifies string */ +static int +find_arg2(SSL* ssl, char* arg, char** arg2) +{ + char* as = strchr(arg, ' '); + char* at = strchr(arg, '\t'); + if(as && at) { + if(at < as) + as = at; + as[0]=0; + *arg2 = skipwhite(as+1); + } else if(as) { + as[0]=0; + *arg2 = skipwhite(as+1); + } else if(at) { + at[0]=0; + *arg2 = skipwhite(at+1); + } else { + ssl_printf(ssl, "error could not find next argument " + "after %s\n", arg); + return 0; + } + return 1; +} + +/** Add a new zone */ +static void +do_zone_add(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + char* arg2; + enum localzone_type t; + struct local_zone* z; + if(!find_arg2(ssl, arg, &arg2)) + return; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + if(!local_zone_str2type(arg2, &t)) { + ssl_printf(ssl, "error not a zone type. %s\n", arg2); + return; + } + lock_quick_lock(&worker->daemon->local_zones->lock); + if((z=local_zones_find(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN))) { + /* already present in tree */ + lock_rw_wrlock(&z->lock); + z->type = t; /* update type anyway */ + lock_rw_unlock(&z->lock); + lock_quick_unlock(&worker->daemon->local_zones->lock); + send_ok(ssl); + return; + } + if(!local_zones_add_zone(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN, t)) { + lock_quick_unlock(&worker->daemon->local_zones->lock); + ssl_printf(ssl, "error out of memory\n"); + return; + } + lock_quick_unlock(&worker->daemon->local_zones->lock); + send_ok(ssl); +} + +/** Remove a zone */ +static void +do_zone_remove(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + struct local_zone* z; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + lock_quick_lock(&worker->daemon->local_zones->lock); + if((z=local_zones_find(worker->daemon->local_zones, nm, nmlen, + nmlabs, LDNS_RR_CLASS_IN))) { + /* present in tree */ + local_zones_del_zone(worker->daemon->local_zones, z); + } + lock_quick_unlock(&worker->daemon->local_zones->lock); + send_ok(ssl); +} + +/** Add new RR data */ +static void +do_data_add(SSL* ssl, struct worker* worker, char* arg) +{ + if(!local_zones_add_RR(worker->daemon->local_zones, arg, + worker->env.scratch_buffer)) { + ssl_printf(ssl,"error in syntax or out of memory, %s\n", arg); + return; + } + send_ok(ssl); +} + +/** Remove RR data */ +static void +do_data_remove(SSL* ssl, struct worker* worker, char* arg) +{ + uint8_t* nm; + int nmlabs; + size_t nmlen; + if(!parse_arg_name(ssl, arg, &nm, &nmlen, &nmlabs)) + return; + local_zones_del_data(worker->daemon->local_zones, nm, + nmlen, nmlabs, LDNS_RR_CLASS_IN); + send_ok(ssl); +} + /** execute a remote control command */ static void execute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd) @@ -853,6 +988,14 @@ execute_cmd(struct daemon_remote* rc, SSL* ssl, char* cmd) do_verbosity(ssl, skipwhite(p+9)); } else if(strncmp(p, "stats", 5) == 0) { do_stats(ssl, rc); + } else if(strncmp(p, "local_zone_remove", 17) == 0) { + do_zone_remove(ssl, rc->worker, skipwhite(p+17)); + } else if(strncmp(p, "local_zone", 10) == 0) { + do_zone_add(ssl, rc->worker, skipwhite(p+10)); + } else if(strncmp(p, "local_data_remove", 17) == 0) { + do_data_remove(ssl, rc->worker, skipwhite(p+17)); + } else if(strncmp(p, "local_data", 10) == 0) { + do_data_add(ssl, rc->worker, skipwhite(p+10)); } else { (void)ssl_printf(ssl, "error unknown command '%s'\n", p); } diff --git a/doc/Changelog b/doc/Changelog index 8f64b7dc7..768a8d9ec 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,7 @@ +19 September 2008: Wouter + - locking on the localdata structure. + - add and remove local zone and data with unbound-control. + 18 September 2008: Wouter - fixup error in time calculation. - munin plugin improvements. diff --git a/doc/plan b/doc/plan index bf9e34ded..305a0860e 100644 --- a/doc/plan +++ b/doc/plan @@ -43,19 +43,19 @@ like dnswall does. Allow certain subdomains to do it, config options. note in config/man that we may consider turning on by default. *** Remote control feature -* remote control using a TCP unbound-control commandline app. -* secure remote control w. TSIG. Or TLS. -* Nicer statistics (over that unbound-control app for ease) ++ remote control using a TCP unbound-control commandline app. ++ secure remote control w. TSIG. Or TLS. ++ Nicer statistics (over that unbound-control app for ease) stats display added over threads, displayed in rddtool easy format. -* option for extended statistics. If enabled (not by default) collect print ++ option for extended statistics. If enabled (not by default) collect print rcode, uptime, spoofnearmisses, cache size, qtype, bits(RD, CD, DO, EDNS-present, AD)query, (Secure, Bogus)reply. - perhaps also see which slow auth servers cause >1sec values. stats-file possible with key: value or key=value lines in it. - stats on SIGUSR1. addup stats over threads. + addup stats over threads. +not stats on SIGUSR1. perhaps also see which slow auth servers cause >1sec values. * remote control to add/remove localinfo, redirects. * remote control to load/store cache contents -* remote control to start, stop, reload. ++ remote control to start, stop, reload. * remote control to flush names or domains (all under a name) from the cache. Include NSes. And the A, AAAA for its NSes. * remote control to see delegation; what servers would be used to get diff --git a/doc/unbound-control.8.in b/doc/unbound-control.8.in index 593dcdd13..0cecc5c81 100644 --- a/doc/unbound-control.8.in +++ b/doc/unbound-control.8.in @@ -60,8 +60,30 @@ a reload (taken from config file again), or the next verbosity control command. Print statistics. Resets the internal counters to zero, this can be controlled using the \fBstatistics\-cumulative\fR config statement. Statistics are printed with one [name]: [value] per line. +.TP +.B local_zone \fIname\fR \fItype +Add new local zone with name and type. Like \fBlocal\-zone\fR config statement. +If the zone already exists, the type is changed to the given argument. +.TP +.B local_zone_remove \fIname +Remove the local zone with the given name. Removes all local data inside +it. If the zone does not exist, the command succeeds. +.TP +.B local_data \fIRR data... +Add new local data, the given resource record. Like \fBlocal\-data\fR +config statement, except for when no covering zone exists. In that case +this remote control command creates a transparent zone with the same +name as this record. This command is not good at returning detailed syntax +errors. +.TP +.B local_data_remove \fIname +Remove all RR data from local name. If the name already has no items, +nothing happens. Often results in NXDOMAIN for the name (in a static zone), +but if the name has become an empty nonterminal (there is still data in +domain names below the removed name), NOERROR nodata answers are the +result for that name. .SH "EXIT CODE" -The unbound-control program exits with status code 1 on error. +The unbound-control program exits with status code 1 on error, 0 on success. .SH "SET UP" The setup requires a self\-signed certificate and private keys for both the server and client. The script \fIunbound\-control\-setup\fR generates diff --git a/services/localzone.c b/services/localzone.c index 2712bab7a..289d3435e 100644 --- a/services/localzone.c +++ b/services/localzone.c @@ -57,6 +57,9 @@ local_zones_create() if(!zones) return NULL; rbtree_init(&zones->ztree, &local_zone_cmp); + lock_quick_init(&zones->lock); + lock_protect(&zones->lock, &zones->ztree, sizeof(zones->ztree)); + /* also lock protects the rbnode's in struct local_zone */ return zones; } @@ -73,6 +76,7 @@ local_zones_delete(struct local_zones* zones) { if(!zones) return; + lock_quick_destroy(&zones->lock); /* walk through zones and delete them all */ traverse_postorder(&zones->ztree, lzdel, NULL); free(zones); @@ -83,6 +87,7 @@ local_zone_delete(struct local_zone* z) { if(!z) return; + lock_rw_destroy(&z->lock); regional_destroy(z->region); free(z->name); free(z); @@ -137,14 +142,13 @@ parse_dname(const char* str, uint8_t** res, size_t* len, int* labs) return 1; } -/** enter a new zone with allocated dname */ +/** create a new localzone */ static struct local_zone* -lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, +local_zone_create(struct local_zones* zones, uint8_t* nm, size_t len, int labs, enum localzone_type t, uint16_t dclass) { struct local_zone* z = (struct local_zone*)calloc(1, sizeof(*z)); if(!z) { - log_err("out of memory"); return NULL; } z->node.key = z; @@ -153,19 +157,46 @@ lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, z->name = nm; z->namelen = len; z->namelabs = labs; + lock_rw_init(&z->lock); z->region = regional_create(); if(!z->region) { - log_err("out of memory"); free(z); return NULL; } rbtree_init(&z->data, &local_data_cmp); - /* add to rbtree */ - if(!rbtree_insert(&zones->ztree, &z->node)) { - log_warn("duplicate local-zone"); - local_zone_delete(z); + lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_t)); + lock_protect(&zones->lock, &z->node, sizeof(z->node)); + lock_protect(&zones->lock, &z->parent, sizeof(z->parent)); + lock_protect(&zones->lock, &z->name, sizeof(z->name)); + lock_protect(&zones->lock, &z->namelen, sizeof(z->namelen)); + lock_protect(&zones->lock, &z->namelabs, sizeof(z->namelabs)); + lock_protect(&zones->lock, &z->dclass, sizeof(z->dclass)); + (void)zones; /* avoid argument unused warning if no lock checks */ + return z; +} + +/** enter a new zone with allocated dname returns with WRlock */ +static struct local_zone* +lz_enter_zone_dname(struct local_zones* zones, uint8_t* nm, size_t len, + int labs, enum localzone_type t, uint16_t c) +{ + struct local_zone* z = local_zone_create(zones, nm, len, labs, t, c); + if(!z) { + log_err("out of memory"); return NULL; } + + /* add to rbtree */ + lock_quick_lock(&zones->lock); + lock_rw_wrlock(&z->lock); + if(!rbtree_insert(&zones->ztree, &z->node)) { + log_warn("duplicate local-zone"); + lock_rw_unlock(&z->lock); + local_zone_delete(z); + lock_quick_unlock(&zones->lock); + return NULL; + } + lock_quick_unlock(&zones->lock); return z; } @@ -183,17 +214,7 @@ lz_enter_zone(struct local_zones* zones, const char* name, const char* type, log_err("bad zone name %s %s", name, type); return NULL; } - if(strcmp(type, "deny") == 0) - t = local_zone_deny; - else if(strcmp(type, "refuse") == 0) - t = local_zone_refuse; - else if(strcmp(type, "static") == 0) - t = local_zone_static; - else if(strcmp(type, "transparent") == 0) - t = local_zone_transparent; - else if(strcmp(type, "redirect") == 0) - t = local_zone_redirect; - else { + if(!local_zone_str2type(type, &t)) { log_err("bad lz_enter_zone type %s %s", name, type); free(nm); return NULL; @@ -373,18 +394,24 @@ insert_rr(struct regional* region, struct packed_rrset_data* pd, return 1; } +/** find a data node by exact name */ +static struct local_data* +lz_find_node(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs) +{ + struct local_data key; + key.node.key = &key; + key.name = nm; + key.namelen = nmlen; + key.namelabs = nmlabs; + return (struct local_data*)rbtree_search(&z->data, &key.node); +} + /** 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); + struct local_data* ld = lz_find_node(z, nm, nmlen, nmlabs); if(!ld) { /* create a domain name to store rr. */ ld = (struct local_data*)regional_alloc_zero(z->region, @@ -480,16 +507,24 @@ lz_enter_rr_str(struct local_zones* zones, const char* rr, ldns_buffer* buf) size_t len; int labs; struct local_zone* z; + int r; if(!get_rr_nameclass(rr, &rr_name, &rr_class)) { log_err("bad rr %s", rr); return 0; } labs = dname_count_size_labels(rr_name, &len); + lock_quick_lock(&zones->lock); z = local_zones_lookup(zones, rr_name, len, labs, rr_class); - if(!z) + if(!z) { + lock_quick_unlock(&zones->lock); fatal_exit("internal error: no zone for rr %s", rr); + } + lock_rw_wrlock(&z->lock); + lock_quick_unlock(&zones->lock); free(rr_name); - return lz_enter_rr_into_zone(z, buf, rr); + r = lz_enter_rr_into_zone(z, buf, rr); + lock_rw_unlock(&z->lock); + return r; } /** parse local-zone: statements */ @@ -497,9 +532,12 @@ static int lz_enter_zones(struct local_zones* zones, struct config_file* cfg) { struct config_str2list* p; + struct local_zone* z; for(p = cfg->local_zones; p; p = p->next) { - if(!lz_enter_zone(zones, p->str, p->str2, LDNS_RR_CLASS_IN)) + if(!(z=lz_enter_zone(zones, p->str, p->str2, + LDNS_RR_CLASS_IN))) return 0; + lock_rw_unlock(&z->lock); } return 1; } @@ -515,10 +553,13 @@ lz_exists(struct local_zones* zones, const char* name) log_err("bad name %s", name); return 0; } + lock_quick_lock(&zones->lock); if(rbtree_search(&zones->ztree, &z.node)) { + lock_quick_unlock(&zones->lock); free(z.name); return 1; } + lock_quick_unlock(&zones->lock); free(z.name); return 0; } @@ -555,11 +596,16 @@ add_as112_default(struct local_zones* zones, struct config_file* cfg, return 0; snprintf(str, sizeof(str), "%s 10800 IN SOA localhost. " "nobody.invalid. 1 3600 1200 604800 10800", name); - if(!lz_enter_rr_into_zone(z, buf, str)) + if(!lz_enter_rr_into_zone(z, buf, str)) { + lock_rw_unlock(&z->lock); return 0; + } snprintf(str, sizeof(str), "%s 10800 IN NS localhost. ", name); - if(!lz_enter_rr_into_zone(z, buf, str)) + if(!lz_enter_rr_into_zone(z, buf, str)) { + lock_rw_unlock(&z->lock); return 0; + } + lock_rw_unlock(&z->lock); return 1; } @@ -585,8 +631,10 @@ lz_enter_defaults(struct local_zones* zones, struct config_file* cfg, !lz_enter_rr_into_zone(z, buf, "localhost. 10800 IN AAAA ::1")) { log_err("out of memory adding default zone"); + if(z) { lock_rw_unlock(&z->lock); } return 0; } + lock_rw_unlock(&z->lock); } /* reverse ip4 zone */ if(!lz_exists(zones, "127.in-addr.arpa.") && @@ -601,8 +649,10 @@ lz_enter_defaults(struct local_zones* zones, struct config_file* cfg, !lz_enter_rr_into_zone(z, buf, "1.0.0.127.in-addr.arpa. 10800 IN PTR localhost.")) { log_err("out of memory adding default zone"); + if(z) { lock_rw_unlock(&z->lock); } return 0; } + lock_rw_unlock(&z->lock); } /* reverse ip6 zone */ if(!lz_exists(zones, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.") && @@ -617,8 +667,10 @@ lz_enter_defaults(struct local_zones* zones, struct config_file* cfg, !lz_enter_rr_into_zone(z, buf, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa. 10800 IN PTR localhost.")) { log_err("out of memory adding default zone"); + if(z) { lock_rw_unlock(&z->lock); } return 0; } + lock_rw_unlock(&z->lock); } if ( !add_as112_default(zones, cfg, buf, "10.in-addr.arpa.") || !add_as112_default(zones, cfg, buf, "16.172.in-addr.arpa.") || @@ -661,10 +713,13 @@ init_parents(struct local_zones* zones) { struct local_zone* node, *prev = NULL, *p; int m; + lock_quick_lock(&zones->lock); RBTREE_FOR(node, struct local_zone*, &zones->ztree) { + lock_rw_wrlock(&node->lock); node->parent = NULL; if(!prev || prev->dclass != node->dclass) { prev = node; + lock_rw_unlock(&node->lock); continue; } (void)dname_lab_cmp(prev->name, prev->namelabs, node->name, @@ -681,7 +736,9 @@ init_parents(struct local_zones* zones) break; } prev = node; + lock_rw_unlock(&node->lock); } + lock_quick_unlock(&zones->lock); } /** enter implicit transparent zone for local-data: without local-zone: */ @@ -711,6 +768,7 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) return 0; } labs = dname_count_size_labels(rr_name, &len); + lock_quick_lock(&zones->lock); if(!local_zones_lookup(zones, rr_name, len, labs, rr_class)) { if(!have_name) { dclass = rr_class; @@ -725,6 +783,7 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) /* process other classes later */ free(rr_name); have_other_classes = 1; + lock_quick_unlock(&zones->lock); continue; } /* find smallest shared topdomain */ @@ -735,9 +794,11 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) match = m; } } else free(rr_name); + lock_quick_unlock(&zones->lock); } if(have_name) { uint8_t* n2; + struct local_zone* z; /* allocate zone of smallest shared topdomain to contain em */ n2 = nm; dname_remove_labels(&n2, &nmlen, nmlabs - match); @@ -749,10 +810,11 @@ lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) } log_nametypeclass(VERB_ALGO, "implicit transparent local-zone", n2, 0, dclass); - if(!lz_enter_zone_dname(zones, n2, nmlen, match, - local_zone_transparent, dclass)) { + if(!(z=lz_enter_zone_dname(zones, n2, nmlen, match, + local_zone_transparent, dclass))) { return 0; } + lock_rw_unlock(&z->lock); } if(have_other_classes) { /* restart to setup other class */ @@ -854,6 +916,20 @@ local_zones_lookup(struct local_zones* zones, } } +struct local_zone* +local_zones_find(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass) +{ + struct local_zone key; + key.node.key = &key; + key.dclass = dclass; + key.name = name; + key.namelen = len; + key.namelabs = labs; + /* exact */ + return (struct local_zone*)rbtree_search(&zones->ztree, &key); +} + /** print all RRsets in local zone */ static void local_zone_out(struct local_zone* z) @@ -872,8 +948,10 @@ local_zone_out(struct local_zone* z) void local_zones_print(struct local_zones* zones) { struct local_zone* z; + lock_quick_lock(&zones->lock); log_info("number of auth zones %u", (unsigned)zones->ztree.count); RBTREE_FOR(z, struct local_zone*, &zones->ztree) { + lock_rw_rdlock(&z->lock); switch(z->type) { case local_zone_deny: log_nametypeclass(0, "deny zone", @@ -901,7 +979,9 @@ void local_zones_print(struct local_zones* zones) break; } local_zone_out(z); + lock_rw_unlock(&z->lock); } + lock_quick_unlock(&zones->lock); } /** encode answer consisting of 1 rrset */ @@ -1032,10 +1112,203 @@ local_zones_answer(struct local_zones* zones, struct query_info* qinfo, * - 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, + struct local_zone* z; + int r; + lock_quick_lock(&zones->lock); + 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)) + if(!z) { + lock_quick_unlock(&zones->lock); + return 0; + } + lock_rw_rdlock(&z->lock); + lock_quick_unlock(&zones->lock); + + if(local_data_answer(z, qinfo, edns, buf, temp, labs, &ld)) { + lock_rw_unlock(&z->lock); return 1; - return lz_zone_answer(z, qinfo, edns, buf, temp, ld); + } + r = lz_zone_answer(z, qinfo, edns, buf, temp, ld); + lock_rw_unlock(&z->lock); + return r; +} + +int local_zone_str2type(const char* type, enum localzone_type* t) +{ + if(strcmp(type, "deny") == 0) + *t = local_zone_deny; + else if(strcmp(type, "refuse") == 0) + *t = local_zone_refuse; + else if(strcmp(type, "static") == 0) + *t = local_zone_static; + else if(strcmp(type, "transparent") == 0) + *t = local_zone_transparent; + else if(strcmp(type, "redirect") == 0) + *t = local_zone_redirect; + else return 0; + return 1; +} + +/** iterate over the kiddies of the given name and set their parent ptr */ +static void +set_kiddo_parents(struct local_zone* z, struct local_zone* match, + struct local_zone* newp) +{ + /* both zones and z are locked already */ + /* in the sorted rbtree, the kiddies of z are located after z */ + /* z must be present in the tree */ + struct local_zone* p = z; + p = (struct local_zone*)rbtree_next(&p->node); + while(p!=(struct local_zone*)RBTREE_NULL && + p->dclass == z->dclass && dname_strict_subdomain(p->name, + p->namelabs, z->name, z->namelabs)) { + /* update parent ptr */ + /* only when matches with existing parent pointer, so that + * deeper child structures are not touched, i.e. + * update of x, and a.x, b.x, f.b.x, g.b.x, c.x, y + * gets to update a.x, b.x and c.x */ + lock_rw_wrlock(&p->lock); + if(p->parent == match) + p->parent = newp; + lock_rw_unlock(&p->lock); + p = (struct local_zone*)rbtree_next(&p->node); + } +} + +struct local_zone* local_zones_add_zone(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + enum localzone_type tp) +{ + /* create */ + struct local_zone* z = local_zone_create(zones, name, len, labs, tp, + dclass); + if(!z) return NULL; + lock_rw_wrlock(&z->lock); + + /* find the closest parent */ + z->parent = local_zones_find(zones, name, len, labs, dclass); + + /* insert into the tree */ + if(!rbtree_insert(&zones->ztree, &z->node)) { + /* duplicate entry! */ + lock_rw_unlock(&z->lock); + local_zone_delete(z); + log_err("internal: duplicate entry in local_zones_add_zone"); + return NULL; + } + + /* set parent pointers right */ + set_kiddo_parents(z, z->parent, z); + + lock_rw_unlock(&z->lock); + return z; +} + +void local_zones_del_zone(struct local_zones* zones, struct local_zone* z) +{ + /* fix up parents in tree */ + lock_rw_wrlock(&z->lock); + set_kiddo_parents(z, z, z->parent); + + /* remove from tree */ + (void)rbtree_delete(&zones->ztree, z); + + /* delete the zone */ + lock_rw_unlock(&z->lock); + local_zone_delete(z); +} + +int +local_zones_add_RR(struct local_zones* zones, const char* rr, ldns_buffer* buf) +{ + uint8_t* rr_name; + uint16_t rr_class; + size_t len; + int labs; + struct local_zone* z; + int r; + if(!get_rr_nameclass(rr, &rr_name, &rr_class)) { + return 0; + } + labs = dname_count_size_labels(rr_name, &len); + lock_quick_lock(&zones->lock); + z = local_zones_lookup(zones, rr_name, len, labs, rr_class); + if(!z) { + z = local_zones_add_zone(zones, rr_name, len, labs, rr_class, + local_zone_transparent); + if(!z) { + lock_quick_unlock(&zones->lock); + return 0; + } + } + lock_rw_wrlock(&z->lock); + lock_quick_unlock(&zones->lock); + free(rr_name); + r = lz_enter_rr_into_zone(z, buf, rr); + lock_rw_unlock(&z->lock); + return r; +} + +/** returns true if the node is terminal so no deeper domain names exist */ +static int +is_terminal(struct local_data* d) +{ + /* for empty nonterminals, the deeper domain names are sorted + * right after them, so simply check the next name in the tree + */ + struct local_data* n = (struct local_data*)rbtree_next(&d->node); + if(n == (struct local_data*)RBTREE_NULL) + return 1; /* last in tree, no deeper node */ + if(dname_strict_subdomain(n->name, n->namelabs, d->name, d->namelabs)) + return 0; /* there is a deeper node */ + return 1; +} + +/** delete empty terminals from tree when final data is deleted */ +static void +del_empty_term(struct local_zone* z, struct local_data* d, + uint8_t* name, size_t len, int labs) +{ + while(d && d->rrsets == NULL && is_terminal(d)) { + /* is this empty nonterminal? delete */ + /* note, no memory recycling in zone region */ + (void)rbtree_delete(&z->data, d); + + /* go up and to the next label */ + if(dname_is_root(name)) + return; + dname_remove_label(&name, &len); + labs--; + d = lz_find_node(z, name, len, labs); + } +} + +void local_zones_del_data(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass) +{ + /* find zone */ + struct local_zone* z; + struct local_data* d; + lock_quick_lock(&zones->lock); + z = local_zones_lookup(zones, name, len, labs, dclass); + if(!z) { + /* no such zone, we're done */ + lock_quick_unlock(&zones->lock); + return; + } + lock_rw_wrlock(&z->lock); + lock_quick_unlock(&zones->lock); + + /* find the domain */ + d = lz_find_node(z, name, len, labs); + /* no memory recycling for zone deletions ... */ + d->rrsets = NULL; + /* did we delete the soa record ? */ + if(query_dname_compare(d->name, z->name) == 0) + z->soa = NULL; + + /* cleanup the empty nonterminals for this name */ + del_empty_term(z, d, name, len, labs); + + lock_rw_unlock(&z->lock); } diff --git a/services/localzone.h b/services/localzone.h index 4e6a54f98..742405c74 100644 --- a/services/localzone.h +++ b/services/localzone.h @@ -42,6 +42,7 @@ #ifndef SERVICES_LOCALZONE_H #define SERVICES_LOCALZONE_H #include "util/rbtree.h" +#include "util/locks.h" struct ub_packed_rrset_key; struct regional; struct config_file; @@ -74,6 +75,8 @@ enum localzone_type { * This tree is fixed at startup, so, readonly, no locks or mutexes necessary. */ struct local_zones { + /** lock on the localzone tree */ + lock_quick_t lock; /** rbtree of struct local_zone */ rbtree_t ztree; }; @@ -97,6 +100,12 @@ struct local_zone { * uses 'dclass' to not conflict with c++ keyword class. */ uint16_t dclass; + /** lock on the data in the structure + * For the node, parent, name, namelen, namelabs, dclass, you + * need to also hold the zones_tree lock to change them (or to + * delete this zone) */ + lock_rw_t lock; + /** how to process zone */ enum localzone_type type; @@ -151,6 +160,7 @@ void local_zones_delete(struct local_zones* zones); /** * Apply config settings; setup the local authoritative data. + * Takes care of locking. * @param zones: is set up. * @param cfg: config data. * @return false on error. @@ -182,6 +192,7 @@ void local_zone_delete(struct local_zone* z); /** * Lookup zone that contains the given name, class. + * User must lock the tree or result zone. * @param zones: the zones tree * @param name: dname to lookup * @param len: length of name. @@ -194,12 +205,14 @@ struct local_zone* local_zones_lookup(struct local_zones* zones, /** * Debug helper. Print all zones + * Takes care of locking. * @param zones: the zones tree */ void local_zones_print(struct local_zones* zones); /** * Answer authoritatively for local zones. + * Takes care of locking. * @param zones: the stored zones (shared, read only). * @param qinfo: query info (parsed). * @param edns: edns info (parsed). @@ -212,4 +225,74 @@ void local_zones_print(struct local_zones* zones); int local_zones_answer(struct local_zones* zones, struct query_info* qinfo, struct edns_data* edns, ldns_buffer* buf, struct regional* temp); +/** + * Parse the string into localzone type. + * + * @param str: string to parse + * @param t: local zone type returned here. + * @return 0 on parse error. + */ +int local_zone_str2type(const char* str, enum localzone_type* t); + +/** + * Find zone that with exactly given name, class. + * User must lock the tree or result zone. + * @param zones: the zones tree + * @param name: dname to lookup + * @param len: length of name. + * @param labs: labelcount of name. + * @param dclass: class to lookup. + * @return the exact local_zone or NULL. + */ +struct local_zone* local_zones_find(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass); + +/** + * Add a new zone. Caller must hold the zones lock. + * Adjusts the other zones as well (parent pointers) after insertion. + * The zone must NOT exist (returns NULL and logs error). + * @param zones: the zones tree + * @param name: dname to add + * @param len: length of name. + * @param labs: labelcount of name. + * @param dclass: class to add. + * @param tp: type. + * @return local_zone or NULL on error, caller must printout memory error. + */ +struct local_zone* local_zones_add_zone(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass, + enum localzone_type tp); + +/** + * Delete a zone. Caller must hold the zones lock. + * Adjusts the other zones as well (parent pointers) after insertion. + * @param zones: the zones tree + * @param zone: the zone to delete from tree. Also deletes zone from memory. + */ +void local_zones_del_zone(struct local_zones* zones, struct local_zone* zone); + +/** + * Add RR data into the localzone data. + * Looks up the zone, if no covering zone, a transparent zone with the + * name of the RR is created. + * @param zones: the zones tree. Not locked by caller. + * @param rr: string with on RR. + * @param buf: buffer for scratch. + * @return false on failure. + */ +int local_zones_add_RR(struct local_zones* zones, const char* rr, + ldns_buffer* buf); + +/** + * Remove data from domain name in the tree. + * All types are removed. No effect if zone or name does not exist. + * @param zones: zones tree. + * @param name: dname to remove + * @param len: length of name. + * @param labs: labelcount of name. + * @param dclass: class to remove. + */ +void local_zones_del_data(struct local_zones* zones, + uint8_t* name, size_t len, int labs, uint16_t dclass); + #endif /* SERVICES_LOCALZONE_H */ diff --git a/smallapp/unbound-control.c b/smallapp/unbound-control.c index b50410f73..53964ff52 100644 --- a/smallapp/unbound-control.c +++ b/smallapp/unbound-control.c @@ -62,7 +62,12 @@ usage() printf(" stop stops the server\n"); printf(" reload reloads the server\n"); printf(" stats print statistics\n"); - printf(" verbosity [number] change logging detail\n"); + printf(" verbosity [number] change logging detail\n"); + printf(" local_zone [name] [type] add new local zone\n"); + printf(" local_zone_remove [name] remove local zone and its contents\n"); + printf(" local_data [RR data...] add local data, for example\n"); + printf(" local_data www.example.com A 192.0.2.1\n"); + printf(" local_data_remove [name] remove local RR data from name\n"); printf("Version %s\n", PACKAGE_VERSION); printf("BSD licensed, see LICENSE in source package for details.\n"); printf("Report bugs to %s\n", PACKAGE_BUGREPORT); diff --git a/testcode/ldns-testpkts.c b/testcode/ldns-testpkts.c index 4f5023269..64873defc 100644 --- a/testcode/ldns-testpkts.c +++ b/testcode/ldns-testpkts.c @@ -487,9 +487,12 @@ read_entry(FILE* in, const char* name, int *lineno, uint32_t* default_ttl, /* it must be a RR, parse and add to packet. */ ldns_rr* n = NULL; ldns_status status; - status = ldns_rr_new_frm_str(&n, parse, *default_ttl, - *origin, prev_rr); - if (status != LDNS_STATUS_OK) + if(add_section == LDNS_SECTION_QUESTION) + status = ldns_rr_new_question_frm_str( + &n, parse, *origin, prev_rr); + else status = ldns_rr_new_frm_str(&n, parse, + *default_ttl, *origin, prev_rr); + if(status != LDNS_STATUS_OK) error("%s line %d:\n\t%s: %s", name, *lineno, ldns_get_errorstr_by_id(status), parse); ldns_pkt_push_rr(cur_reply->reply, add_section, n); @@ -637,7 +640,7 @@ match_all(ldns_pkt* q, ldns_pkt* p, bool mttl) { verbose(3, "allmatch: nscount different"); return 0;} if(ldns_pkt_arcount(q) != ldns_pkt_arcount(p)) { verbose(3, "allmatch: arcount different"); return 0;} - if(!match_list(ldns_pkt_question(q), ldns_pkt_question(p), mttl)) + if(!match_list(ldns_pkt_question(q), ldns_pkt_question(p), 0)) { verbose(3, "allmatch: qd section different"); return 0;} if(!match_list(ldns_pkt_answer(q), ldns_pkt_answer(p), mttl)) { verbose(3, "allmatch: an section different"); return 0;} diff --git a/validator/validator.c b/validator/validator.c index 1f7d9c07e..1873b8936 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -156,7 +156,7 @@ val_init(struct module_env* env, int id) val_env->permissive_mode = 0; lock_basic_init(&val_env->bogus_lock); lock_protect(&val_env->bogus_lock, &val_env->num_rrset_bogus, - sizeof(val->env->num_rrset_bogus)); + sizeof(val_env->num_rrset_bogus)); if(!val_apply_cfg(env, val_env, env->cfg)) { log_err("validator: could not apply configuration settings."); return 0;