diff --git a/testdata/val_unsecds_negcache.rpl b/testdata/val_unsecds_negcache.rpl new file mode 100644 index 000000000..3f474cdb2 --- /dev/null +++ b/testdata/val_unsecds_negcache.rpl @@ -0,0 +1,191 @@ +; config options +; The island of trust is at example.com +server: + trust-anchor: "example.com. 3600 IN DS 2854 3 1 46e4ffc6e9a4793b488954bd3f0cc6af0dfb201b" + val-override-date: "20070916134226" + +stub-zone: + name: "." + stub-addr: 193.0.14.129 # K.ROOT-SERVERS.NET. +CONFIG_END + +SCENARIO_BEGIN Test validator with insecure delegation and DS negative cache + +; K.ROOT-SERVERS.NET. +RANGE_BEGIN 0 100 + ADDRESS 193.0.14.129 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +. IN NS +SECTION ANSWER +. IN NS K.ROOT-SERVERS.NET. +SECTION ADDITIONAL +K.ROOT-SERVERS.NET. IN A 193.0.14.129 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION AUTHORITY +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END +RANGE_END + +; a.gtld-servers.net. +RANGE_BEGIN 0 100 + ADDRESS 192.5.6.30 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +com. IN NS +SECTION ANSWER +com. IN NS a.gtld-servers.net. +SECTION ADDITIONAL +a.gtld-servers.net. IN A 192.5.6.30 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION AUTHORITY +example.com. IN NS ns.example.com. +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ENTRY_END +RANGE_END + +; ns.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.4 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN NS +SECTION ANSWER +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} +ENTRY_END + +; response to DNSKEY priming query +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +example.com. IN DNSKEY +SECTION ANSWER +example.com. 3600 IN DNSKEY 256 3 3 ALXLUsWqUrY3JYER3T4TBJII s70j+sDS/UT2QRp61SE7S3E EXopNXoFE73JLRmvpi/UrOO/Vz4Se 6wXv/CYCKjGw06U4WRgR YXcpEhJROyNapmdIKSx hOzfLVE1gqA0PweZR8d tY3aNQSRn3sPpwJr6Mi /PqQKAMMrZ9ckJpf1+b QMOOvxgzz2U1GS18b3y ZKcgTMEaJzd/GZYzi/B N2DzQ0MsrSwYXfsNLFO Bbs8PJMW4LYIxeeOe6rUgkWOF 7CC9Dh/dduQ1QrsJhmZAEFfd6ByYV+ ;{id = 2854 (zsk), size = 1688b} +example.com. 3600 IN RRSIG DNSKEY DSA 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFBQRtlR4BEv9ohi+PGFjp+AHsJuHAhRCvz0shggvnvI88DFnBDCczHUcVA== ;{id = 2854} +SECTION AUTHORITY +example.com. IN NS ns.example.com. +example.com. 3600 IN RRSIG NS 3 2 3600 20070926134150 20070829134150 2854 example.com. MC0CFQCN+qHdJxoI/2tNKwsb08pra/G7aAIUAWA5sDdJTbrXA1/3OaesGBAO3sI= ;{id = 2854} +SECTION ADDITIONAL +ns.example.com. IN A 1.2.3.4 +ns.example.com. 3600 IN RRSIG A 3 3 3600 20070926135752 20070829135752 2854 example.com. MC0CFQCMSWxVehgOQLoYclB9PIAbNP229AIUeH0vNNGJhjnZiqgIOKvs1EhzqAo= ;{id = 2854} +ENTRY_END + +; response for delegation to sub.example.com. +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION ANSWER +SECTION AUTHORITY +sub.example.com. IN NS ns.sub.example.com. +sub.example.com. IN NSEC www.example.com. NS RRSIG NSEC +sub.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. MCwCFDCaiDM6G+glwNW276HWdH+McmjgAhRSwF5OfimNQCqkWgnYotLOwUghKQ== ;{id = 2854} +SECTION ADDITIONAL +ns.sub.example.com. IN A 1.2.3.6 +ENTRY_END + +; query for missing DS record. +; get it from the negative cache instead! +;ENTRY_BEGIN +;MATCH opcode qtype qname +;ADJUST copy_id +;REPLY QR NOERROR +;SECTION QUESTION +;sub.example.com. IN DS +;SECTION ANSWER +;SECTION AUTHORITY +;example.com. IN SOA ns.example.com. h.example.com. 2007090504 1800 1800 2419200 7200 +;example.com. 3600 IN RRSIG SOA 3 2 3600 20070926134150 20070829134150 2854 example.com. MCwCFC5uwIHSehZtetK2CMNXttSFUB0XAhROFDAgy/FaxR8zFXJzyPdpQG93Sw== ;{id = 2854} +;sub.example.com. IN NSEC www.example.com. NS RRSIG NSEC +;sub.example.com. 3600 IN RRSIG NSEC 3 3 3600 20070926134150 20070829134150 2854 example.com. MCwCFDCaiDM6G+glwNW276HWdH+McmjgAhRSwF5OfimNQCqkWgnYotLOwUghKQ== ;{id = 2854} +;SECTION ADDITIONAL +;ns.sub.example.com. IN A 1.2.3.6 +;ENTRY_END + + +RANGE_END + +; ns.sub.example.com. +RANGE_BEGIN 0 100 + ADDRESS 1.2.3.6 +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +sub.example.com. IN NS +SECTION ANSWER +sub.example.com. IN NS ns.sub.example.com. +SECTION ADDITIONAL +ns.sub.example.com. IN A 1.2.3.6 +ENTRY_END + +; response to query of interest +ENTRY_BEGIN +MATCH opcode qtype qname +ADJUST copy_id +REPLY QR NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION ANSWER +www.sub.example.com. IN A 11.11.11.11 +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END +RANGE_END + +STEP 1 QUERY +ENTRY_BEGIN +REPLY RD DO +SECTION QUESTION +www.sub.example.com. IN A +ENTRY_END + +; recursion happens here. +STEP 10 CHECK_ANSWER +ENTRY_BEGIN +MATCH all +REPLY QR RD RA NOERROR +SECTION QUESTION +www.sub.example.com. IN A +SECTION ANSWER +www.sub.example.com. 3600 IN A 11.11.11.11 +SECTION AUTHORITY +SECTION ADDITIONAL +ENTRY_END + +SCENARIO_END diff --git a/util/module.h b/util/module.h index 831212b1f..68308835b 100644 --- a/util/module.h +++ b/util/module.h @@ -56,6 +56,8 @@ struct module_qstate; struct ub_randstate; struct mesh_area; struct mesh_state; +struct val_anchors; +struct val_neg_cache; /** Maximum number of modules in operation */ #define MAX_MODULE 5 @@ -203,6 +205,9 @@ struct module_env { * and are not primed and ready for validation, but on the bright * side, they are read only memory, thus no locks and fast. */ struct val_anchors* anchors; + /** negative cache, configured by the validator. if not NULL, + * contains NSEC record lookup trees. */ + struct val_neg_cache* neg_cache; /** module specific data. indexed by module id. */ void* modinfo[MAX_MODULE]; }; diff --git a/validator/val_neg.c b/validator/val_neg.c index 3184ef5a5..06adfa7fe 100644 --- a/validator/val_neg.c +++ b/validator/val_neg.c @@ -44,6 +44,7 @@ #include "config.h" #include "validator/val_neg.h" #include "validator/val_nsec.h" +#include "validator/val_utils.h" #include "util/data/dname.h" #include "util/data/msgreply.h" #include "util/log.h" @@ -272,19 +273,21 @@ static void neg_make_space(struct val_neg_cache* neg, size_t need) /** * Find the given zone, from the SOA owner name and class * @param neg: negative cache - * @param soa: what to look for. + * @param nm: what to look for. + * @param len: length of nm + * @param dclass: class to look for. * @return zone or NULL if not found. */ static struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, - struct ub_packed_rrset_key* soa) + uint8_t* nm, size_t len, uint16_t dclass) { struct val_neg_zone lookfor; struct val_neg_zone* result; lookfor.node.key = &lookfor; - lookfor.name = soa->rk.dname; - lookfor.len = soa->rk.dname_len; + lookfor.name = nm; + lookfor.len = len; lookfor.labs = dname_count_labels(lookfor.name); - lookfor.dclass = ntohs(soa->rk.rrset_class); + lookfor.dclass = dclass; result = (struct val_neg_zone*) rbtree_search(&neg->tree, lookfor.node.key); @@ -318,13 +321,12 @@ static size_t calc_data_need(struct reply_info* rep) /** * Calculate space needed for zone and all its parents - * @param soa: with name. + * @param d: name of zone + * @param len: length of name * @return size. */ -static size_t calc_zone_need(struct ub_packed_rrset_key* soa) +static size_t calc_zone_need(uint8_t* d, size_t len) { - uint8_t* d = soa->rk.dname; - size_t len = soa->rk.dname_len; size_t res = sizeof(struct val_neg_zone) + len; while(!dname_is_root(d)) { log_assert(len > 1); /* not root label */ @@ -419,7 +421,7 @@ static struct val_neg_data* neg_closest_data_parent( * @param nm: name for zone (copied) * @param nm_len: length of name * @param labs: labels in name. - * @param dclass: class of zone. + * @param dclass: class of zone, host order. * @return new zone or NULL on failure */ static struct val_neg_zone* neg_setup_zone_node( @@ -494,19 +496,18 @@ static struct val_neg_zone* neg_zone_chain( /** * Create a new zone. * @param neg: negative cache - * @param soa: what to look for. + * @param nm: what to look for. + * @param nm_len: length of name. + * @param dclass: class of zone, host order. * @return zone or NULL if out of memory. */ static struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, - struct ub_packed_rrset_key* soa) + uint8_t* nm, size_t nm_len, uint16_t dclass) { struct val_neg_zone* zone; struct val_neg_zone* parent; struct val_neg_zone* p, *np; - uint8_t* nm = soa->rk.dname; - size_t nm_len = soa->rk.dname_len; int labs = dname_count_labels(nm); - uint16_t dclass = ntohs(soa->rk.rrset_class); /* find closest enclosing parent zone that (still) exists */ parent = neg_closest_zone_parent(neg, nm, nm_len, labs, dclass); @@ -809,14 +810,17 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep) soa->rk.dname, LDNS_RR_TYPE_SOA, ntohs(soa->rk.rrset_class)); /* ask for enough space to store all of it */ - need = calc_data_need(rep) + calc_zone_need(soa); + need = calc_data_need(rep) + + calc_zone_need(soa->rk.dname, soa->rk.dname_len); lock_basic_lock(&neg->lock); neg_make_space(neg, need); /* find or create the zone entry */ - zone = neg_find_zone(neg, soa); + zone = neg_find_zone(neg, soa->rk.dname, soa->rk.dname_len, + ntohs(soa->rk.rrset_class)); if(!zone) { - if(!(zone = neg_create_zone(neg, soa))) { + if(!(zone = neg_create_zone(neg, soa->rk.dname, + soa->rk.dname_len, ntohs(soa->rk.rrset_class)))) { lock_basic_unlock(&neg->lock); log_err("out of memory adding negative zone"); return; @@ -958,3 +962,92 @@ int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, verbose(VERB_ALGO, "negcache DLV denial proven"); return 1; } + +/** see if the reply has signed NSEC records and return the signer */ +static uint8_t* reply_nsec_signer(struct reply_info* rep, size_t* signer_len, + uint16_t* dclass) +{ + size_t i; + struct packed_rrset_data* d; + uint8_t* s; + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) { + d = (struct packed_rrset_data*)rep->rrsets[i]-> + entry.data; + /* return first signer name of first NSEC */ + if(d->rrsig_count != 0) { + val_find_rrset_signer(rep->rrsets[i], + &s, signer_len); + if(s && *signer_len) { + *dclass = ntohs(rep->rrsets[i]-> + rk.rrset_class); + return s; + } + } + } + } + return 0; +} + +void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, + uint8_t* zone_name) +{ + size_t i, need; + uint8_t* signer; + size_t signer_len; + uint16_t dclass; + struct val_neg_zone* zone; + /* no SOA in this message, find RRSIG over NSEC's signer name. + * note the NSEC records are maybe not validated yet */ + signer = reply_nsec_signer(rep, &signer_len, &dclass); + if(!signer) + return; + if(!dname_subdomain_c(signer, zone_name)) { + /* the signer is not in the bailiwick, throw it out */ + return; + } + + log_nametypeclass(VERB_ALGO, "negcache insert referral ", + signer, LDNS_RR_TYPE_NS, dclass); + + /* ask for enough space to store all of it */ + need = calc_data_need(rep) + calc_zone_need(signer, signer_len); + lock_basic_lock(&neg->lock); + neg_make_space(neg, need); + + /* find or create the zone entry */ + zone = neg_find_zone(neg, signer, signer_len, dclass); + if(!zone) { + if(!(zone = neg_create_zone(neg, signer, signer_len, + dclass))) { + lock_basic_unlock(&neg->lock); + log_err("out of memory adding negative zone"); + return; + } + } + + /* insert the NSECs */ + for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ + if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC) + continue; + if(!dname_subdomain_c(rep->rrsets[i]->rk.dname, + zone->name)) continue; + /* insert NSEC into this zone's tree */ + neg_insert_data(neg, zone, rep->rrsets[i]); + } + lock_basic_unlock(&neg->lock); +} + +struct dns_msg* +val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, + struct regional* region, struct rrset_cache* rrset_cache) +{ + struct dns_msg* msg; + /* only for DS queries */ + if(qinfo->qtype != LDNS_RR_TYPE_DS) + return NULL; + + /* see if info from neg cache is available */ + + return msg; +} diff --git a/validator/val_neg.h b/validator/val_neg.h index 2bbf1118a..ff79c31cf 100644 --- a/validator/val_neg.h +++ b/validator/val_neg.h @@ -50,6 +50,9 @@ struct val_neg_data; struct config_file; struct reply_info; struct rrset_cache; +struct regional; +struct query_info; +struct dns_msg; /** * The negative cache. It is shared between the threads, so locked. @@ -187,6 +190,16 @@ int val_neg_zone_compare(const void* a, const void* b); */ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep); +/** + * Insert NSECs from this referral into the negative cache for reference. + * @param neg: negative cache + * @param rep: referral reply with NS, NSECs. + * @param zone: bailiwick for the referral. + * Errors are ignored, means that storage is omitted. + */ +void val_neg_addreferral(struct val_neg_cache* neg, struct reply_info* rep, + uint8_t* zone); + /** * Perform a DLV style lookup * During the lookup, we could find out that data has expired. In that @@ -207,4 +220,19 @@ void val_neg_addreply(struct val_neg_cache* neg, struct reply_info* rep); int val_neg_dlvlookup(struct val_neg_cache* neg, uint8_t* qname, size_t len, uint16_t qclass, struct rrset_cache* rrset_cache, uint32_t now); +/** + * For the given query, try to get a reply out of the negative cache. + * The reply still needs to be validated. + * @param neg: negative cache. + * @param qinfo: query + * @param region: where to allocate reply. + * @param rrset_cache: rrset cache. + * @return a reply message if something was found. + * This reply may still need validation. + * NULL if nothing found (or out of memory). + */ +struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, + struct query_info* qinfo, struct regional* region, + struct rrset_cache* rrset_cache); + #endif /* VALIDATOR_VAL_NEG_H */ diff --git a/validator/validator.c b/validator/validator.c index 1873b8936..bcc8a5b7e 100644 --- a/validator/validator.c +++ b/validator/validator.c @@ -127,6 +127,7 @@ val_apply_cfg(struct module_env* env, struct val_env* val_env, log_err("out of memory"); return 0; } + env->neg_cache = val_env->neg_cache; val_env->date_override = cfg->val_date_override; c = cfg_count_numbers(cfg->val_nsec3_key_iterations); if(c < 1 || (c&1)) {