unbound-1.25.1/0000755000175000017500000000000015203270272012706 5ustar wouterwouterunbound-1.25.1/validator/0000755000175000017500000000000015203270263014673 5ustar wouterwouterunbound-1.25.1/validator/val_nsec.c0000644000175000017500000004154415203270263016641 0ustar wouterwouter/* * validator/val_nsec.c - validator NSEC denial of existence functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with NSEC checking, the different NSEC proofs * for denial of existence, and proofs for presence of types. */ #include "config.h" #include "validator/val_nsec.h" #include "validator/val_utils.h" #include "util/data/msgreply.h" #include "util/data/dname.h" #include "util/net_help.h" #include "util/module.h" #include "services/cache/rrset.h" /** get ttl of rrset */ static uint32_t rrset_get_ttl(struct ub_packed_rrset_key* k) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; return d->ttl; } int nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type) { /* Check type present in NSEC typemap with bitmap arg */ /* bitmasks for determining type-lowerbits presence */ uint8_t masks[8] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01}; uint8_t type_window = type>>8; uint8_t type_low = type&0xff; uint8_t win, winlen; /* read each of the type bitmap windows and see if the searched * type is amongst it */ while(len > 0) { if(len < 3) /* bad window, at least window# winlen bitmap */ return 0; win = *bitmap++; winlen = *bitmap++; len -= 2; if(len < winlen || winlen < 1 || winlen > 32) return 0; /* bad window length */ if(win == type_window) { /* search window bitmap for the correct byte */ /* mybyte is 0 if we need the first byte */ size_t mybyte = type_low>>3; if(winlen <= mybyte) return 0; /* window too short */ return (int)(bitmap[mybyte] & masks[type_low&0x7]); } else { /* not the window we are looking for */ bitmap += winlen; len -= winlen; } } /* end of bitmap reached, no type found */ return 0; } int nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type) { struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> entry.data; size_t len; if(!d || d->count == 0 || d->rr_len[0] < 2+1) return 0; len = dname_valid(d->rr_data[0]+2, d->rr_len[0]-2); if(!len) return 0; return nsecbitmap_has_type_rdata(d->rr_data[0]+2+len, d->rr_len[0]-2-len, type); } /** * Get next owner name from nsec record * @param nsec: the nsec RRset. * If there are multiple RRs, then this will only return one of them. * @param nm: the next name is returned. * @param ln: length of nm is returned. * @return false on a bad NSEC RR (too short, malformed dname). */ static int nsec_get_next(struct ub_packed_rrset_key* nsec, uint8_t** nm, size_t* ln) { struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> entry.data; if(!d || d->count == 0 || d->rr_len[0] < 2+1) { *nm = 0; *ln = 0; return 0; } *nm = d->rr_data[0]+2; *ln = dname_valid(*nm, d->rr_len[0]-2); if(!*ln) { *nm = 0; *ln = 0; return 0; } return 1; } /** * For an NSEC that matches the DS queried for, check absence of DS type. * * @param nsec: NSEC for proof, must be trusted. * @param qinfo: what is queried for. * @return if secure the nsec proves that no DS is present, or * insecure if it proves it is not a delegation point. * or bogus if something was wrong. */ static enum sec_status val_nsec_proves_no_ds(struct ub_packed_rrset_key* nsec, struct query_info* qinfo) { log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); log_assert(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC); if(nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && qinfo->qname_len != 1) { /* SOA present means that this is the NSEC from the child, * not the parent (so it is the wrong one). */ return sec_status_bogus; } if(nsec_has_type(nsec, LDNS_RR_TYPE_DS)) { /* DS present means that there should have been a positive * response to the DS query, so there is something wrong. */ return sec_status_bogus; } if(!nsec_has_type(nsec, LDNS_RR_TYPE_NS)) { /* If there is no NS at this point at all, then this * doesn't prove anything one way or the other. */ return sec_status_insecure; } /* Otherwise, this proves no DS. */ return sec_status_secure; } /** check security status from cache or verify rrset, returns true if secure */ static int nsec_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* nsec, struct key_entry_key* kkey, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) { struct packed_rrset_data* d = (struct packed_rrset_data*) nsec->entry.data; int verified = 0; if(!d) return 0; if(d->security == sec_status_secure) return 1; rrset_check_sec_status(env->rrset_cache, nsec, *env->now); if(d->security == sec_status_secure) return 1; d->security = val_verify_rrset_entry(env, ve, nsec, kkey, reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, reasonbuf, reasonlen); if(d->security == sec_status_secure) { rrset_update_sec_status(env->rrset_cache, nsec, *env->now); return 1; } return 0; } enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, struct query_info* qinfo, struct reply_info* rep, struct key_entry_key* kkey, time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) { struct ub_packed_rrset_key* nsec = reply_find_rrset_section_ns( rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_NSEC, qinfo->qclass); enum sec_status sec; size_t i; uint8_t* wc = NULL, *ce = NULL; int valid_nsec = 0; struct ub_packed_rrset_key* wc_nsec = NULL; /* If we have a NSEC at the same name, it must prove one * of two things * -- * 1) this is a delegation point and there is no DS * 2) this is not a delegation point */ if(nsec) { if(!nsec_verify_rrset(env, ve, nsec, kkey, reason, reason_bogus, qstate, reasonbuf, reasonlen)) { verbose(VERB_ALGO, "NSEC RRset for the " "referral did not verify."); return sec_status_bogus; } sec = val_nsec_proves_no_ds(nsec, qinfo); if(sec == sec_status_bogus) { /* something was wrong. */ *reason = "NSEC does not prove absence of DS"; *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec; } else if(sec == sec_status_insecure) { /* this wasn't a delegation point. */ return sec; } else if(sec == sec_status_secure) { /* this proved no DS. */ *proof_ttl = ub_packed_rrset_ttl(nsec); return sec; } /* if unchecked, fall through to next proof */ } /* Otherwise, there is no NSEC at qname. This could be an ENT. * (ENT=empty non terminal). If not, this is broken. */ /* verify NSEC rrsets in auth section */ for(i=rep->an_numrrsets; i < rep->an_numrrsets+rep->ns_numrrsets; i++) { if(rep->rrsets[i]->rk.type != htons(LDNS_RR_TYPE_NSEC)) continue; if(!nsec_verify_rrset(env, ve, rep->rrsets[i], kkey, reason, reason_bogus, qstate, reasonbuf, reasonlen)) { verbose(VERB_ALGO, "NSEC for empty non-terminal " "did not verify."); *reason = "NSEC for empty non-terminal " "did not verify."; return sec_status_bogus; } if(nsec_proves_nodata(rep->rrsets[i], qinfo, &wc)) { verbose(VERB_ALGO, "NSEC for empty non-terminal " "proved no DS."); *proof_ttl = rrset_get_ttl(rep->rrsets[i]); if(wc && dname_is_wild(rep->rrsets[i]->rk.dname)) wc_nsec = rep->rrsets[i]; valid_nsec = 1; } if(val_nsec_proves_name_error(rep->rrsets[i], qinfo->qname)) { ce = nsec_closest_encloser(qinfo->qname, rep->rrsets[i]); } } if(wc && !ce) valid_nsec = 0; else if(wc && ce) { /* ce and wc must match */ if(query_dname_compare(wc, ce) != 0) valid_nsec = 0; else if(!wc_nsec) valid_nsec = 0; } if(valid_nsec) { if(wc) { /* check if this is a delegation */ *reason = "NSEC for wildcard does not prove absence of DS"; return val_nsec_proves_no_ds(wc_nsec, qinfo); } /* valid nsec proves empty nonterminal */ return sec_status_insecure; } /* NSEC proof did not conclusively point to DS or no DS */ return sec_status_unchecked; } int nsec_proves_nodata(struct ub_packed_rrset_key* nsec, struct query_info* qinfo, uint8_t** wc) { log_assert(wc); if(query_dname_compare(nsec->rk.dname, qinfo->qname) != 0) { uint8_t* nm; size_t ln; /* empty-non-terminal checking. * Done before wildcard, because this is an exact match, * and would prevent a wildcard from matching. */ /* If the nsec is proving that qname is an ENT, the nsec owner * will be less than qname, and the next name will be a child * domain of the qname. */ if(!nsec_get_next(nsec, &nm, &ln)) return 0; /* bad nsec */ if(dname_strict_subdomain_c(nm, qinfo->qname) && dname_canonical_compare(nsec->rk.dname, qinfo->qname) < 0) { return 1; /* proves ENT */ } /* wildcard checking. */ /* If this is a wildcard NSEC, make sure that a) it was * possible to have generated qname from the wildcard and * b) the type map does not contain qtype. Note that this * does NOT prove that this wildcard was the applicable * wildcard. */ if(dname_is_wild(nsec->rk.dname)) { /* the purported closest encloser. */ uint8_t* ce = nsec->rk.dname; size_t ce_len = nsec->rk.dname_len; dname_remove_label(&ce, &ce_len); /* The qname must be a strict subdomain of the * closest encloser, for the wildcard to apply */ if(dname_strict_subdomain_c(qinfo->qname, ce)) { /* here we have a matching NSEC for the qname, * perform matching NSEC checks */ if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { /* should have gotten the wildcard CNAME */ return 0; } if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { /* wrong parentside (wildcard) NSEC used */ return 0; } if(nsec_has_type(nsec, qinfo->qtype)) { return 0; } *wc = ce; return 1; } } else { /* See if the next owner name covers a wildcard * empty non-terminal. */ while (dname_canonical_compare(nsec->rk.dname, nm) < 0) { /* wildcard does not apply if qname below * the name that exists under the '*' */ if (dname_subdomain_c(qinfo->qname, nm)) break; /* but if it is a wildcard and qname is below * it, then the wildcard applies. The wildcard * is an empty nonterminal. nodata proven. */ if (dname_is_wild(nm)) { size_t ce_len = ln; uint8_t* ce = nm; dname_remove_label(&ce, &ce_len); if(dname_strict_subdomain_c(qinfo->qname, ce)) { *wc = ce; return 1; } } dname_remove_label(&nm, &ln); } } /* Otherwise, this NSEC does not prove ENT and is not a * wildcard, so it does not prove NODATA. */ return 0; } /* If the qtype exists, then we should have gotten it. */ if(nsec_has_type(nsec, qinfo->qtype)) { return 0; } /* if the name is a CNAME node, then we should have gotten the CNAME*/ if(nsec_has_type(nsec, LDNS_RR_TYPE_CNAME)) { return 0; } /* If an NS set exists at this name, and NOT a SOA (so this is a * zone cut, not a zone apex), then we should have gotten a * referral (or we just got the wrong NSEC). * The reverse of this check is used when qtype is DS, since that * must use the NSEC from above the zone cut. */ if(qinfo->qtype != LDNS_RR_TYPE_DS && nsec_has_type(nsec, LDNS_RR_TYPE_NS) && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { return 0; } else if(qinfo->qtype == LDNS_RR_TYPE_DS && nsec_has_type(nsec, LDNS_RR_TYPE_SOA) && !dname_is_root(qinfo->qname)) { return 0; } return 1; } int val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname) { uint8_t* owner = nsec->rk.dname; uint8_t* next; size_t nlen; if(!nsec_get_next(nsec, &next, &nlen)) return 0; /* If NSEC owner == qname, then this NSEC proves that qname exists. */ if(query_dname_compare(qname, owner) == 0) { return 0; } /* If NSEC is a parent of qname, we need to check the type map * If the parent name has a DNAME or is a delegation point, then * this NSEC is being misused. */ if(dname_subdomain_c(qname, owner) && (nsec_has_type(nsec, LDNS_RR_TYPE_DNAME) || (nsec_has_type(nsec, LDNS_RR_TYPE_NS) && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) )) { return 0; } if(query_dname_compare(owner, next) == 0) { /* this nsec is the only nsec */ /* zone.name NSEC zone.name, disproves everything else */ /* but only for subdomains of that zone */ if(dname_strict_subdomain_c(qname, next)) return 1; } else if(dname_canonical_compare(owner, next) > 0) { /* this is the last nsec, ....(bigger) NSEC zonename(smaller) */ /* the names after the last (owner) name do not exist * there are no names before the zone name in the zone * but the qname must be a subdomain of the zone name(next). */ if(dname_canonical_compare(owner, qname) < 0 && dname_strict_subdomain_c(qname, next)) return 1; } else { /* regular NSEC, (smaller) NSEC (larger) */ if(dname_canonical_compare(owner, qname) < 0 && dname_canonical_compare(qname, next) < 0) { return 1; } } return 0; } int val_nsec_proves_insecuredelegation(struct ub_packed_rrset_key* nsec, struct query_info* qinfo) { if(nsec_has_type(nsec, LDNS_RR_TYPE_NS) && !nsec_has_type(nsec, LDNS_RR_TYPE_DS) && !nsec_has_type(nsec, LDNS_RR_TYPE_SOA)) { /* see if nsec signals an insecure delegation */ if(qinfo->qtype == LDNS_RR_TYPE_DS) { /* if type is DS and qname is equal to nsec, then it * is an exact match nsec, result not insecure */ if(dname_strict_subdomain_c(qinfo->qname, nsec->rk.dname)) return 1; } else { if(dname_subdomain_c(qinfo->qname, nsec->rk.dname)) return 1; } } return 0; } uint8_t* nsec_closest_encloser(uint8_t* qname, struct ub_packed_rrset_key* nsec) { uint8_t* next; size_t nlen; uint8_t* common1, *common2; if(!nsec_get_next(nsec, &next, &nlen)) return NULL; /* longest common with owner or next name */ common1 = dname_get_shared_topdomain(nsec->rk.dname, qname); common2 = dname_get_shared_topdomain(next, qname); if(dname_count_labels(common1) > dname_count_labels(common2)) return common1; return common2; } int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, struct query_info* qinf, uint8_t* wc) { uint8_t* ce; /* 1) prove that qname doesn't exist and * 2) that the correct wildcard was used * nsec has been verified already. */ if(!val_nsec_proves_name_error(nsec, qinf->qname)) return 0; /* check wildcard name */ ce = nsec_closest_encloser(qinf->qname, nsec); if(!ce) return 0; if(query_dname_compare(wc, ce) != 0) { return 0; } return 1; } int val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, size_t qnamelen) { /* Determine if a NSEC record proves the non-existence of a * wildcard that could have produced qname. */ int labs; uint8_t* ce = nsec_closest_encloser(qname, nsec); uint8_t* strip; size_t striplen; uint8_t buf[LDNS_MAX_DOMAINLEN+3]; if(!ce) return 0; /* we can subtract the closest encloser count - since that is the * largest shared topdomain with owner and next NSEC name, * because the NSEC is no proof for names shorter than the owner * and next names. */ labs = dname_count_labels(qname) - dname_count_labels(ce); if(labs > 0) { /* i is number of labels to strip off qname, prepend * wild */ strip = qname; striplen = qnamelen; dname_remove_labels(&strip, &striplen, labs); if(striplen > LDNS_MAX_DOMAINLEN-2) return 0; /* too long to prepend wildcard */ buf[0] = 1; buf[1] = (uint8_t)'*'; memmove(buf+2, strip, striplen); if(val_nsec_proves_name_error(nsec, buf)) { return 1; } } return 0; } unbound-1.25.1/validator/val_nsec3.h0000644000175000017500000004046715203270263016734 0ustar wouterwouter/* * validator/val_nsec3.h - validator NSEC3 denial of existence functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with NSEC3 checking, the different NSEC3 proofs * for denial of existence, and proofs for presence of types. * * NSEC3 * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Hash Alg. | Flags | Iterations | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Salt Length | Salt / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Hash Length | Next Hashed Owner Name / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Type Bit Maps / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * NSEC3PARAM * 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Hash Alg. | Flags | Iterations | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Salt Length | Salt / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * */ #ifndef VALIDATOR_VAL_NSEC3_H #define VALIDATOR_VAL_NSEC3_H #include "util/rbtree.h" #include "util/data/packed_rrset.h" #include "sldns/rrdef.h" struct val_env; struct regional; struct module_env; struct module_qstate; struct ub_packed_rrset_key; struct reply_info; struct query_info; struct key_entry_key; struct sldns_buffer; /** * 0 1 2 3 4 5 6 7 * +-+-+-+-+-+-+-+-+ * | |O| * +-+-+-+-+-+-+-+-+ * The OPT-OUT bit in the NSEC3 flags field. * If enabled, there can be zero or more unsigned delegations in the span. * If disabled, there are zero unsigned delegations in the span. */ #define NSEC3_OPTOUT 0x01 /** * The unknown flags in the NSEC3 flags field. * They must be zero, or the NSEC3 is ignored. */ #define NSEC3_UNKNOWN_FLAGS 0xFE /** The SHA1 hash algorithm for NSEC3 */ #define NSEC3_HASH_SHA1 0x01 /** * Max number of NSEC3 calculations at once, suspend query for later. * 8 is low enough and allows for cases where multiple proofs are needed. */ #define MAX_NSEC3_CALCULATIONS 8 /** * Cache table for NSEC3 hashes. * It keeps a *pointer* to the region its items are allocated. */ struct nsec3_cache_table { rbtree_type* ct; struct regional* region; }; /** * Determine if the set of NSEC3 records provided with a response prove NAME * ERROR. This means that the NSEC3s prove a) the closest encloser exists, * b) the direct child of the closest encloser towards qname doesn't exist, * and c) *.closest encloser does not exist. * * @param env: module environment with temporary region and buffer. * @param ve: validator environment, with iteration count settings. * @param list: array of RRsets, some of which are NSEC3s. * @param num: number of RRsets in the array to examine. * @param qinfo: query that is verified for. * @param kkey: key entry that signed the NSEC3s. * @param ct: cached hashes table. * @param calc: current hash calculations. * @return: * sec_status SECURE of the Name Error is proven by the NSEC3 RRs, * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, * UNCHECKED if no more hash calculations are allowed at this point. */ enum sec_status nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, struct nsec3_cache_table* ct, int* calc); /** * Determine if the NSEC3s provided in a response prove the NOERROR/NODATA * status. There are a number of different variants to this: * * 1) Normal NODATA -- qname is matched to an NSEC3 record, type is not * present. * * 2) ENT NODATA -- because there must be NSEC3 record for * empty-non-terminals, this is the same as #1. * * 3) NSEC3 ownername NODATA -- qname matched an existing, lone NSEC3 * ownername, but qtype was not NSEC3. NOTE: as of nsec-05, this case no * longer exists. * * 4) Wildcard NODATA -- A wildcard matched the name, but not the type. * * 5) Opt-In DS NODATA -- the qname is covered by an opt-in span and qtype == * DS. (or maybe some future record with the same parent-side-only property) * * @param env: module environment with temporary region and buffer. * @param ve: validator environment, with iteration count settings. * @param list: array of RRsets, some of which are NSEC3s. * @param num: number of RRsets in the array to examine. * @param qinfo: query that is verified for. * @param kkey: key entry that signed the NSEC3s. * @param ct: cached hashes table. * @param calc: current hash calculations. * @return: * sec_status SECURE of the proposition is proven by the NSEC3 RRs, * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, * UNCHECKED if no more hash calculations are allowed at this point. */ enum sec_status nsec3_prove_nodata(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, struct nsec3_cache_table* ct, int* calc); /** * Prove that a positive wildcard match was appropriate (no direct match * RRset). * * @param env: module environment with temporary region and buffer. * @param ve: validator environment, with iteration count settings. * @param list: array of RRsets, some of which are NSEC3s. * @param num: number of RRsets in the array to examine. * @param qinfo: query that is verified for. * @param kkey: key entry that signed the NSEC3s. * @param wc: The purported wildcard that matched. This is the wildcard name * as *.wildcard.name., with the *. label already removed. * @param ct: cached hashes table. * @param calc: current hash calculations. * @return: * sec_status SECURE of the proposition is proven by the NSEC3 RRs, * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, * UNCHECKED if no more hash calculations are allowed at this point. */ enum sec_status nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc, struct nsec3_cache_table* ct, int* calc); /** * Prove that a DS response either had no DS, or wasn't a delegation point. * * Fundamentally there are two cases here: normal NODATA and Opt-In NODATA. * * @param env: module environment with temporary region and buffer. * @param ve: validator environment, with iteration count settings. * @param list: array of RRsets, some of which are NSEC3s. * @param num: number of RRsets in the array to examine. * @param qinfo: query that is verified for. * @param kkey: key entry that signed the NSEC3s. * @param reason: string for bogus result. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. * @param ct: cached hashes table. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return: * sec_status SECURE of the proposition is proven by the NSEC3 RRs, * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored. * or if there was no DS in an insecure (i.e., opt-in) way, * INDETERMINATE if it was clear that this wasn't a delegation point, * UNCHECKED if no more hash calculations are allowed at this point. */ enum sec_status nsec3_prove_nods(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen); /** * Prove NXDOMAIN or NODATA. * * @param env: module environment with temporary region and buffer. * @param ve: validator environment, with iteration count settings. * @param list: array of RRsets, some of which are NSEC3s. * @param num: number of RRsets in the array to examine. * @param qinfo: query that is verified for. * @param kkey: key entry that signed the NSEC3s. * @param nodata: if return value is secure, this indicates if nodata or * nxdomain was proven. * @param ct: cached hashes table. * @param calc: current hash calculations. * @return: * sec_status SECURE of the proposition is proven by the NSEC3 RRs, * BOGUS if not, INSECURE if all of the NSEC3s could be validly ignored, * UNCHECKED if no more hash calculations are allowed at this point. */ enum sec_status nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, int* nodata, struct nsec3_cache_table* ct, int* calc); /** * The NSEC3 hash result storage. * Consists of an rbtree, with these nodes in it. * The nodes detail how a set of parameters (from nsec3 rr) plus * a dname result in a hash. */ struct nsec3_cached_hash { /** rbtree node, key is this structure */ rbnode_type node; /** where are the parameters for conversion, in this rrset data */ struct ub_packed_rrset_key* nsec3; /** where are the parameters for conversion, this RR number in data */ int rr; /** the name to convert */ uint8_t* dname; /** length of the dname */ size_t dname_len; /** the hash result (not base32 encoded) */ uint8_t* hash; /** length of hash in bytes */ size_t hash_len; /** the hash result in base32 encoding */ uint8_t* b32; /** length of base32 encoding (as a label) */ size_t b32_len; }; /** * Rbtree for hash cache comparison function. * @param c1: key 1. * @param c2: key 2. * @return: comparison code, -1, 0, 1, of the keys. */ int nsec3_hash_cmp(const void* c1, const void* c2); /** * Initialise the NSEC3 cache table. * @param ct: the nsec3 cache table. * @param region: the region where allocations for the table will happen. * @return true on success, false on malloc error. */ int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region); /** * Obtain the hash of an owner name. * Used internally by the nsec3 proof functions in this file. * published to enable unit testing of hash algorithms and cache. * * @param table: the cache table. Must be initialised at start. * @param region: scratch region to use for allocation. * This region holds the tree, if you wipe the region, reinit the tree. * @param buf: temporary buffer. * @param nsec3: the rrset with parameters * @param rr: rr number from d that has the NSEC3 parameters to hash to. * @param dname: name to hash * This pointer is used inside the tree, assumed region-alloced. * @param dname_len: the length of the name. * @param hash: the hash node is returned on success. * @return: * 2 on success, hash from cache is returned. * 1 on success, newly computed hash is returned. * 0 on a malloc failure. * -1 if the NSEC3 rr was badly formatted (i.e. formerr). */ int nsec3_hash_name(rbtree_type* table, struct regional* region, struct sldns_buffer* buf, struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname, size_t dname_len, struct nsec3_cached_hash** hash); /** * Get next owner name, converted to base32 encoding and with the * zone name (taken from the nsec3 owner name) appended. * @param rrset: the NSEC3 rrset. * @param r: the rr num of the nsec3 in the rrset. * @param buf: buffer to store name in * @param max: size of buffer. * @return length of name on success. 0 on failure (buffer too short or * bad format nsec3 record). */ size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r, uint8_t* buf, size_t max); /** * Convert hash into base32 encoding and with the * zone name appended. * @param hash: hashed buffer * @param hashlen: length of hash * @param zone: name of zone * @param zonelen: length of zonename. * @param buf: buffer to store name in * @param max: size of buffer. * @return length of name on success. 0 on failure (buffer too short or * bad format nsec3 record). */ size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone, size_t zonelen, uint8_t* buf, size_t max); /** * Get NSEC3 parameters out of rr. * @param rrset: the NSEC3 rrset. * @param r: the rr num of the nsec3 in the rrset. * @param algo: nsec3 hash algo. * @param iter: iteration count. * @param salt: ptr to salt inside rdata. * @param saltlen: length of salt. * @return 0 if bad formatted, unknown nsec3 hash algo, or unknown flags set. */ int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r, int* algo, size_t* iter, uint8_t** salt, size_t* saltlen); /** * Get NSEC3 hashed in a buffer * @param buf: buffer for temp use. * @param nm: name to hash * @param nmlen: length of nm. * @param algo: algo to use, must be known. * @param iter: iterations * @param salt: salt for nsec3 * @param saltlen: length of salt. * @param res: result of hash stored here. * @param max: maximum space for result. * @return 0 on failure, otherwise bytelength stored. */ size_t nsec3_get_hashed(struct sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max); /** * see if NSEC3 RR contains given type * @param rrset: NSEC3 rrset * @param r: RR in rrset * @param type: in host order to check bit for. * @return true if bit set, false if not or error. */ int nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type); /** * return if nsec3 RR has the optout flag * @param rrset: NSEC3 rrset * @param r: RR in rrset * @return true if optout, false on error or not optout */ int nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r); /** * Return nsec3 RR next hashed owner name * @param rrset: NSEC3 rrset * @param r: RR in rrset * @param next: ptr into rdata to next owner hash * @param nextlen: length of hash. * @return false on malformed */ int nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r, uint8_t** next, size_t* nextlen); /** * nsec3Covers * Given a hash and a candidate NSEC3Record, determine if that NSEC3Record * covers the hash. Covers specifically means that the hash is in between * the owner and next hashes and does not equal either. * * @param zone: the zone name. * @param hash: the hash of the name * @param rrset: the rrset of the NSEC3. * @param rr: which rr in the rrset. * @param buf: temporary buffer. * @return true if covers, false if not. */ int nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* rrset, int rr, struct sldns_buffer* buf); #endif /* VALIDATOR_VAL_NSEC3_H */ unbound-1.25.1/validator/validator.h0000644000175000017500000002170315203270263017034 0ustar wouterwouter/* * validator/validator.h - secure validator DNS query response module * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains a module that performs validation of DNS queries. * According to RFC 4034. */ #ifndef VALIDATOR_VALIDATOR_H #define VALIDATOR_VALIDATOR_H #include "util/module.h" #include "util/data/msgreply.h" #include "validator/val_utils.h" #include "validator/val_nsec3.h" struct val_anchors; struct key_cache; struct key_entry_key; struct val_neg_cache; struct config_strlist; struct comm_timer; struct config_file; /** * This is the TTL to use when a trust anchor fails to prime. A trust anchor * will be primed no more often than this interval. Used when harden- * dnssec-stripped is off and the trust anchor fails. */ #define NULL_KEY_TTL 60 /* seconds */ /** * TTL for bogus key entries. When a DS or DNSKEY fails in the chain of * trust the entire zone for that name is blacked out for this TTL. */ #define BOGUS_KEY_TTL 60 /* seconds */ /** Root key sentinel is ta preamble */ #define SENTINEL_IS "root-key-sentinel-is-ta-" /** Root key sentinel is not ta preamble */ #define SENTINEL_NOT "root-key-sentinel-not-ta-" /** Root key sentinel keytag length */ #define SENTINEL_KEYTAG_LEN 5 /** * Global state for the validator. */ struct val_env { /** key cache; these are validated keys. trusted keys only * end up here after being primed. */ struct key_cache* kcache; /** aggressive negative cache. index into NSECs in rrset cache. */ struct val_neg_cache* neg_cache; /** for debug testing a fixed validation date can be entered. * if 0, current time is used for rrsig validation */ int32_t date_override; /** clock skew min for signatures */ int32_t skew_min; /** clock skew max for signatures */ int32_t skew_max; /** max number of query restarts, number of IPs to probe */ int max_restart; /** TTL for bogus data; used instead of untrusted TTL from data. * Bogus data will not be verified more often than this interval. * seconds. */ uint32_t bogus_ttl; /** * Number of entries in the NSEC3 maximum iteration count table. * Keep this table short, and sorted by size */ int nsec3_keyiter_count; /** * NSEC3 maximum iteration count per signing key size. * This array contains key size values (in increasing order) */ size_t* nsec3_keysize; /** * NSEC3 maximum iteration count per signing key size. * This array contains the maximum iteration count for the keysize * in the keysize array. */ size_t* nsec3_maxiter; /** lock on bogus counter */ lock_basic_type bogus_lock; /** number of times rrsets marked bogus */ size_t num_rrset_bogus; }; /** * State of the validator for a query. */ enum val_state { /** initial state for validation */ VAL_INIT_STATE = 0, /** find the proper keys for validation, follow trust chain */ VAL_FINDKEY_STATE, /** validate the answer, using found key entry */ VAL_VALIDATE_STATE, /** finish up */ VAL_FINISHED_STATE, }; /** * Per query state for the validator module. */ struct val_qstate { /** * State of the validator module. */ enum val_state state; /** * The original message we have been given to validate. */ struct dns_msg* orig_msg; /** * The query restart count */ int restart_count; /** The blacklist saved for chain of trust elements */ struct sock_list* chain_blacklist; /** * The query name we have chased to; qname after following CNAMEs */ struct query_info qchase; /** * The chased reply, extract from original message. Can be: * o CNAME * o DNAME + CNAME * o answer * plus authority, additional (nsecs) that have same signature. */ struct reply_info* chase_reply; /** * The cname skip value; the number of rrsets that have been skipped * due to chasing cnames. This is the offset into the * orig_msg->rep->rrsets array, into the answer section. * starts at 0 - for the full original message. * if it is >0 - qchase followed the cname, chase_reply setup to be * that message and relevant authority rrsets. * * The skip is also used for referral messages, where it will * range from 0, over the answer, authority and additional sections. */ size_t rrset_skip; /** trust anchor name */ uint8_t* trust_anchor_name; /** trust anchor labels */ int trust_anchor_labs; /** trust anchor length */ size_t trust_anchor_len; /** the DS rrset */ struct ub_packed_rrset_key* ds_rrset; /** domain name for empty nonterminal detection */ uint8_t* empty_DS_name; /** length of empty_DS_name */ size_t empty_DS_len; /** the current key entry */ struct key_entry_key* key_entry; /** subtype */ enum val_classification subtype; /** signer name */ uint8_t* signer_name; /** length of signer_name */ size_t signer_len; /** true if this state is waiting to prime a trust anchor */ int wait_prime_ta; /** State to continue with RRSIG validation in a message later */ int msg_signatures_state; /** The rrset index for the msg signatures to continue from */ size_t msg_signatures_index; /** Cache table for NSEC3 hashes */ struct nsec3_cache_table nsec3_cache_table; /** DS message from sub if it got suspended from NSEC3 calculations */ struct dns_msg* sub_ds_msg; /** The timer to resume processing msg signatures */ struct comm_timer* suspend_timer; /** Number of suspends */ int suspend_count; }; /** * Get the validator function block. * @return: function block with function pointers to validator methods. */ struct module_func_block* val_get_funcblock(void); /** * Get validator state as a string * @param state: to convert * @return constant string that is printable. */ const char* val_state_to_string(enum val_state state); /** validator init */ int val_init(struct module_env* env, int id); /** validator deinit */ void val_deinit(struct module_env* env, int id); /** validator operate on a query */ void val_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound); /** * inform validator super. * * @param qstate: query state that finished. * @param id: module id. * @param super: the qstate to inform. */ void val_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super); /** validator cleanup query state */ void val_clear(struct module_qstate* qstate, int id); /** * Debug helper routine that assists worker in determining memory in * use. * @param env: module environment * @param id: module id. * @return memory in use in bytes. */ size_t val_get_mem(struct module_env* env, int id); /** Timer callback for msg signatures continue timer */ void validate_suspend_timer_cb(void* arg); /** * Parse the val_nsec3_key_iterations string. * @param val_nsec3_key_iterations: the string with nsec3 iterations config. * @param keysize: returns malloced key size array on success. * @param maxiter: returns malloced max iterations array on success. * @param keyiter_count: returns size of keysize and maxiter arrays. * @return false if it does not parse correctly. */ int val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize, size_t** maxiter, int* keyiter_count); /** * Apply config to validator env * @param val_env: validator env. * @param cfg: config * @param keysize: nsec3 key size array. * @param maxiter: nsec3 max iterations array. * @param keyiter_count: size of keysize and maxiter arrays. */ void val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg, size_t* keysize, size_t* maxiter, int keyiter_count); #endif /* VALIDATOR_VALIDATOR_H */ unbound-1.25.1/validator/val_anchor.h0000644000175000017500000002071215203270263017162 0ustar wouterwouter/* * validator/val_anchor.h - validator trust anchor storage. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains storage for the trust anchors for the validator. */ #ifndef VALIDATOR_VAL_ANCHOR_H #define VALIDATOR_VAL_ANCHOR_H #include "util/rbtree.h" #include "util/locks.h" struct trust_anchor; struct config_file; struct ub_packed_rrset_key; struct autr_point_data; struct autr_global_data; struct sldns_buffer; /** * Trust anchor store. * The tree must be locked, while no other locks (from trustanchors) are held. * And then an anchor searched for. Which can be locked or deleted. Then * the tree can be unlocked again. This means you have to release the lock * on a trust anchor and look it up again to delete it. */ struct val_anchors { /** lock on trees. It is locked in order after stubs. */ lock_basic_type lock; /** * Anchors are store in this tree. Sort order is chosen, so that * dnames are in nsec-like order. A lookup on class, name will return * an exact match of the closest match, with the ancestor needed. * contents of type trust_anchor. */ rbtree_type* tree; /** Autotrust global data, anchors sorted by next probe time */ struct autr_global_data* autr; }; /** * Trust anchor key */ struct ta_key { /** next in list */ struct ta_key* next; /** rdata, in wireformat of the key RR. starts with rdlength. */ uint8_t* data; /** length of the rdata (including rdlength). */ size_t len; /** DNS type (host format) of the key, DS or DNSKEY */ uint16_t type; }; /** * A trust anchor in the trust anchor store. * Unique by name, class. */ struct trust_anchor { /** rbtree node, key is this structure */ rbnode_type node; /** lock on the entire anchor and its keys; for autotrust changes */ lock_basic_type lock; /** name of this trust anchor */ uint8_t* name; /** length of name */ size_t namelen; /** number of labels in name of rrset */ int namelabs; /** the ancestor in the trustanchor tree */ struct trust_anchor* parent; /** * List of DS or DNSKEY rrs that form the trust anchor. */ struct ta_key* keylist; /** Autotrust anchor point data, or NULL */ struct autr_point_data* autr; /** number of DSs in the keylist */ size_t numDS; /** number of DNSKEYs in the keylist */ size_t numDNSKEY; /** the DS RRset */ struct ub_packed_rrset_key* ds_rrset; /** The DNSKEY RRset */ struct ub_packed_rrset_key* dnskey_rrset; /** class of the trust anchor */ uint16_t dclass; }; /** * Create trust anchor storage * @return new storage or NULL on error. */ struct val_anchors* anchors_create(void); /** * Delete trust anchor storage. * @param anchors: to delete. */ void anchors_delete(struct val_anchors* anchors); /** * Process trust anchor config. * @param anchors: struct anchor storage * @param cfg: config options. * @return 0 on error. */ int anchors_apply_cfg(struct val_anchors* anchors, struct config_file* cfg); /** * Recalculate parent pointers. The caller must hold the lock on the * anchors structure (say after removing an item from the rbtree). * Caller must not hold any locks on trust anchors. * After the call is complete the parent pointers are updated and an item * just removed is no longer referenced in parent pointers. * @param anchors: the structure to update. */ void anchors_init_parents_locked(struct val_anchors* anchors); /** * Given a qname/qclass combination, find the trust anchor closest above it. * Or return NULL if none exists. * * @param anchors: struct anchor storage * @param qname: query name, uncompressed wireformat. * @param qname_len: length of qname. * @param qclass: class to query for. * @return the trust anchor or NULL if none is found. The anchor is locked. */ struct trust_anchor* anchors_lookup(struct val_anchors* anchors, uint8_t* qname, size_t qname_len, uint16_t qclass); /** * Find a trust anchor. Exact matching. * @param anchors: anchor storage. * @param name: name of trust anchor (wireformat) * @param namelabs: labels in name * @param namelen: length of name * @param dclass: class of trust anchor * @return NULL if not found. The anchor is locked. */ struct trust_anchor* anchor_find(struct val_anchors* anchors, uint8_t* name, int namelabs, size_t namelen, uint16_t dclass); /** * Store one string as trust anchor RR. * @param anchors: anchor storage. * @param buffer: parsing buffer, to generate the RR wireformat in. * @param str: string. * @return NULL on error. */ struct trust_anchor* anchor_store_str(struct val_anchors* anchors, struct sldns_buffer* buffer, const char* str); /** * Get memory in use by the trust anchor storage * @param anchors: anchor storage. * @return memory in use in bytes. */ size_t anchors_get_mem(struct val_anchors* anchors); /** compare two trust anchors */ int anchor_cmp(const void* k1, const void* k2); /** * Add insecure point trust anchor. For external use (locks and init_parents) * @param anchors: anchor storage. * @param c: class. * @param nm: name of insecure trust point. * @return false on alloc failure. */ int anchors_add_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm); /** * Delete insecure point trust anchor. Does not remove if no such point. * For external use (locks and init_parents) * @param anchors: anchor storage. * @param c: class. * @param nm: name of insecure trust point. */ void anchors_delete_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm); /** * Get a list of keytags for the trust anchor. Zero tags for insecure points. * @param ta: trust anchor (locked by caller). * @param list: array of uint16_t. * @param num: length of array. * @return number of keytags filled into array. If total number of keytags is * bigger than the array, it is truncated at num. On errors, less keytags * are filled in. The array is sorted. */ size_t anchor_list_keytags(struct trust_anchor* ta, uint16_t* list, size_t num); /** * Check if there is a trust anchor for given zone with this keytag. * * @param anchors: anchor storage * @param name: name of trust anchor (wireformat) * @param namelabs: labels in name * @param namelen: length of name * @param dclass: class of trust anchor * @param keytag: keytag * @return 1 if there is a trust anchor in the trustachor store for this zone * and keytag, else 0. */ int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs, size_t namelen, uint16_t dclass, uint16_t keytag); /** * Find an anchor that is not an insecure point, if any, or there are no * DNSSEC verification anchors if none. * @param anchors: anchor storage * @return trust anchor or NULL. It is locked. */ struct trust_anchor* anchors_find_any_noninsecure(struct val_anchors* anchors); /** * Swap internal tree with preallocated entries. * @param anchors: anchor storage. * @param data: the data structure used to take elements from. This contains * the old elements on return. */ void anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data); #endif /* VALIDATOR_VAL_ANCHOR_H */ unbound-1.25.1/validator/val_utils.c0000644000175000017500000012670315203270263017052 0ustar wouterwouter/* * validator/val_utils.c - validator utility functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. */ #include "config.h" #include "validator/val_utils.h" #include "validator/validator.h" #include "validator/val_kentry.h" #include "validator/val_sigcrypt.h" #include "validator/val_anchor.h" #include "validator/val_nsec.h" #include "validator/val_neg.h" #include "services/cache/rrset.h" #include "services/cache/dns.h" #include "util/data/msgreply.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/net_help.h" #include "util/module.h" #include "util/regional.h" #include "util/config_file.h" #include "sldns/wire2str.h" #include "sldns/parseutil.h" /** Maximum allowed digest match failures per DS, for DNSKEYs with the same * properties */ #define MAX_DS_MATCH_FAILURES 4 enum val_classification val_classify_response(uint16_t query_flags, struct query_info* origqinf, struct query_info* qinf, struct reply_info* rep, size_t skip) { int rcode = (int)FLAGS_GET_RCODE(rep->flags); size_t i; /* Normal Name Error's are easy to detect -- but don't mistake a CNAME * chain ending in NXDOMAIN. */ if(rcode == LDNS_RCODE_NXDOMAIN && rep->an_numrrsets == 0) return VAL_CLASS_NAMEERROR; /* check for referral: nonRD query and it looks like a nodata */ if(!(query_flags&BIT_RD) && rep->an_numrrsets == 0 && rcode == LDNS_RCODE_NOERROR) { /* SOA record in auth indicates it is NODATA instead. * All validation requiring NODATA messages have SOA in * authority section. */ /* uses fact that answer section is empty */ int saw_ns = 0; for(i=0; ins_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_SOA) return VAL_CLASS_NODATA; if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DS) return VAL_CLASS_REFERRAL; if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS) saw_ns = 1; } return saw_ns?VAL_CLASS_REFERRAL:VAL_CLASS_NODATA; } /* root referral where NS set is in the answer section */ if(!(query_flags&BIT_RD) && rep->ns_numrrsets == 0 && rep->an_numrrsets == 1 && rcode == LDNS_RCODE_NOERROR && ntohs(rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_NS && query_dname_compare(rep->rrsets[0]->rk.dname, origqinf->qname) != 0) return VAL_CLASS_REFERRAL; /* dump bad messages */ if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) return VAL_CLASS_UNKNOWN; /* next check if the skip into the answer section shows no answer */ if(skip>0 && rep->an_numrrsets <= skip) return VAL_CLASS_CNAMENOANSWER; /* Next is NODATA */ if(rcode == LDNS_RCODE_NOERROR && rep->an_numrrsets == 0) return VAL_CLASS_NODATA; /* We distinguish between CNAME response and other positive/negative * responses because CNAME answers require extra processing. */ /* We distinguish between ANY and CNAME or POSITIVE because * ANY responses are validated differently. */ if(rcode == LDNS_RCODE_NOERROR && qinf->qtype == LDNS_RR_TYPE_ANY) return VAL_CLASS_ANY; /* For the query type DNAME, the name matters. Equal name is the * answer looked for, but a subdomain redirects the query. */ if(qinf->qtype == LDNS_RR_TYPE_DNAME) { for(i=skip; ian_numrrsets; i++) { if(rcode == LDNS_RCODE_NOERROR && ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DNAME && query_dname_compare(qinf->qname, rep->rrsets[i]->rk.dname) == 0) { /* type is DNAME and name is equal, it is * the answer. For the query name a subdomain * of the rrset.dname it would redirect. */ return VAL_CLASS_POSITIVE; } if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME) return VAL_CLASS_CNAME; } log_dns_msg("validator: error. failed to classify response message: ", qinf, rep); return VAL_CLASS_UNKNOWN; } /* Note that DNAMEs will be ignored here, unless qtype=DNAME. Unless * qtype=CNAME, this will yield a CNAME response. */ for(i=skip; ian_numrrsets; i++) { if(rcode == LDNS_RCODE_NOERROR && ntohs(rep->rrsets[i]->rk.type) == qinf->qtype) return VAL_CLASS_POSITIVE; if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME) return VAL_CLASS_CNAME; } log_dns_msg("validator: error. failed to classify response message: ", qinf, rep); return VAL_CLASS_UNKNOWN; } /** Get signer name from RRSIG */ static void rrsig_get_signer(uint8_t* data, size_t len, uint8_t** sname, size_t* slen) { /* RRSIG rdata is not allowed to be compressed, it is stored * uncompressed in memory as well, so return a ptr to the name */ if(len < 21) { /* too short RRSig: * short, byte, byte, long, long, long, short, "." is * 2 1 1 4 4 4 2 1 = 19 * and a skip of 18 bytes to the name. * +2 for the rdatalen is 21 bytes len for root label */ *sname = NULL; *slen = 0; return; } data += 20; /* skip the fixed size bits */ len -= 20; *slen = dname_valid(data, len); if(!*slen) { /* bad dname in this rrsig. */ *sname = NULL; return; } *sname = data; } void val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname, size_t* slen) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; /* return signer for first signature, or NULL */ if(d->rrsig_count == 0) { *sname = NULL; *slen = 0; return; } /* get rrsig signer name out of the signature */ rrsig_get_signer(d->rr_data[d->count], d->rr_len[d->count], sname, slen); } /** * Find best signer name in this set of rrsigs. * @param rrset: which rrsigs to look through. * @param qinf: the query name that needs validation. * @param signer_name: the best signer_name. Updated if a better one is found. * @param signer_len: length of signer name. * @param matchcount: count of current best name (starts at 0 for no match). * Updated if match is improved. */ static void val_find_best_signer(struct ub_packed_rrset_key* rrset, struct query_info* qinf, uint8_t** signer_name, size_t* signer_len, int* matchcount) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; uint8_t* sign; size_t i; int m; for(i=d->count; icount+d->rrsig_count; i++) { sign = d->rr_data[i]+2+18; /* look at signatures that are valid (long enough), * and have a signer name that is a superdomain of qname, * and then check the number of labels in the shared topdomain * improve the match if possible */ if(d->rr_len[i] > 2+19 && /* rdata, sig + root label*/ dname_subdomain_c(qinf->qname, sign)) { (void)dname_lab_cmp(qinf->qname, dname_count_labels(qinf->qname), sign, dname_count_labels(sign), &m); if(m > *matchcount) { *matchcount = m; *signer_name = sign; (void)dname_count_size_labels(*signer_name, signer_len); } } } } /** Detect if the, unsigned, CNAME is under a previous DNAME RR in the * message, and thus it was generated from that previous DNAME. */ static int cname_under_previous_dname(struct reply_info* rep, size_t cname_idx, size_t* ret) { size_t i; for(i=0; irrsets[i]->rk.type) == LDNS_RR_TYPE_DNAME && dname_strict_subdomain_c(rep->rrsets[cname_idx]-> rk.dname, rep->rrsets[i]->rk.dname)) { *ret = i; return 1; } } *ret = 0; return 0; } void val_find_signer(enum val_classification subtype, struct query_info* qinf, struct reply_info* rep, size_t skip, uint8_t** signer_name, size_t* signer_len) { size_t i; if(subtype == VAL_CLASS_POSITIVE) { /* check for the answer rrset */ for(i=skip; ian_numrrsets; i++) { if(query_dname_compare(qinf->qname, rep->rrsets[i]->rk.dname) == 0) { val_find_rrset_signer(rep->rrsets[i], signer_name, signer_len); /* If there was no signer, and the query * was for type CNAME, and this is a CNAME, * and the previous is a DNAME, then this * is the synthesized CNAME, use the signer * of the DNAME record. */ if(*signer_name == NULL && qinf->qtype == LDNS_RR_TYPE_CNAME && ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && i > skip && ntohs(rep->rrsets[i-1]->rk.type) == LDNS_RR_TYPE_DNAME && dname_strict_subdomain_c(rep->rrsets[i]->rk.dname, rep->rrsets[i-1]->rk.dname)) { val_find_rrset_signer(rep->rrsets[i-1], signer_name, signer_len); } return; } } *signer_name = NULL; *signer_len = 0; } else if(subtype == VAL_CLASS_CNAME) { size_t j; /* check for the first signed cname/dname rrset */ for(i=skip; ian_numrrsets; i++) { val_find_rrset_signer(rep->rrsets[i], signer_name, signer_len); if(*signer_name) return; if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && cname_under_previous_dname(rep, i, &j)) { val_find_rrset_signer(rep->rrsets[j], signer_name, signer_len); return; } if(ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_DNAME) break; /* only check CNAME after a DNAME */ } *signer_name = NULL; *signer_len = 0; } else if(subtype == VAL_CLASS_NAMEERROR || subtype == VAL_CLASS_NODATA) { /*Check to see if the AUTH section NSEC record(s) have rrsigs*/ for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC || ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC3) { val_find_rrset_signer(rep->rrsets[i], signer_name, signer_len); return; } } } else if(subtype == VAL_CLASS_CNAMENOANSWER) { /* find closest superdomain signer name in authority section * NSEC and NSEC3s */ int matchcount = 0; *signer_name = NULL; *signer_len = 0; for(i=rep->an_numrrsets; ian_numrrsets+rep-> ns_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC || ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC3) { val_find_best_signer(rep->rrsets[i], qinf, signer_name, signer_len, &matchcount); } } } else if(subtype == VAL_CLASS_ANY) { /* check for one of the answer rrset that has signatures, * or potentially a DNAME is in use with a different qname */ for(i=skip; ian_numrrsets; i++) { if(query_dname_compare(qinf->qname, rep->rrsets[i]->rk.dname) == 0) { val_find_rrset_signer(rep->rrsets[i], signer_name, signer_len); if(*signer_name) return; } } /* no answer RRSIGs with qname, try a DNAME */ if(skip < rep->an_numrrsets && ntohs(rep->rrsets[skip]->rk.type) == LDNS_RR_TYPE_DNAME) { val_find_rrset_signer(rep->rrsets[skip], signer_name, signer_len); if(*signer_name) return; } *signer_name = NULL; *signer_len = 0; } else if(subtype == VAL_CLASS_REFERRAL) { /* find keys for the item at skip */ if(skip < rep->rrset_count) { val_find_rrset_signer(rep->rrsets[skip], signer_name, signer_len); return; } *signer_name = NULL; *signer_len = 0; } else { verbose(VERB_QUERY, "find_signer: could not find signer name" " for unknown type response"); *signer_name = NULL; *signer_len = 0; } } /** return number of rrs in an rrset */ static size_t rrset_get_count(struct ub_packed_rrset_key* rrset) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; if(!d) return 0; return d->count; } /** return TTL of rrset */ static uint32_t rrset_get_ttl(struct ub_packed_rrset_key* rrset) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; if(!d) return 0; return d->ttl; } static enum sec_status val_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* keys, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int *verified, char* reasonbuf, size_t reasonlen) { enum sec_status sec; struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> entry.data; if(d->security == sec_status_secure) { /* re-verify all other statuses, because keyset may change*/ log_nametypeclass(VERB_ALGO, "verify rrset cached", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); *verified = 0; return d->security; } /* check in the cache if verification has already been done */ rrset_check_sec_status(env->rrset_cache, rrset, *env->now); if(d->security == sec_status_secure) { log_nametypeclass(VERB_ALGO, "verify rrset from cache", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); *verified = 0; return d->security; } log_nametypeclass(VERB_ALGO, "verify rrset", rrset->rk.dname, ntohs(rrset->rk.type), ntohs(rrset->rk.rrset_class)); sec = dnskeyset_verify_rrset(env, ve, rrset, keys, sigalg, reason, reason_bogus, section, qstate, verified, reasonbuf, reasonlen); verbose(VERB_ALGO, "verify result: %s", sec_status_to_string(sec)); regional_free_all(env->scratch); /* update rrset security status * only improves security status * and bogus is set only once, even if we rechecked the status */ if(sec > d->security) { d->security = sec; if(sec == sec_status_secure) d->trust = rrset_trust_validated; else if(sec == sec_status_bogus) { size_t i; /* update ttl for rrset to fixed value. */ d->ttl = ve->bogus_ttl; for(i=0; icount+d->rrsig_count; i++) d->rr_ttl[i] = ve->bogus_ttl; /* leave RR specific TTL: not used for determine * if RRset timed out and clients see proper value. */ lock_basic_lock(&ve->bogus_lock); ve->num_rrset_bogus++; lock_basic_unlock(&ve->bogus_lock); } /* if status updated - store in cache for reuse */ rrset_update_sec_status(env->rrset_cache, rrset, *env->now); } return sec; } enum sec_status val_verify_rrset_entry(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int* verified, char* reasonbuf, size_t reasonlen) { /* temporary dnskey rrset-key */ struct ub_packed_rrset_key dnskey; struct key_entry_data* kd = (struct key_entry_data*)kkey->entry.data; enum sec_status sec; dnskey.rk.type = htons(kd->rrset_type); dnskey.rk.rrset_class = htons(kkey->key_class); dnskey.rk.flags = 0; dnskey.rk.dname = kkey->name; dnskey.rk.dname_len = kkey->namelen; dnskey.entry.key = &dnskey; dnskey.entry.data = kd->rrset_data; sec = val_verify_rrset(env, ve, rrset, &dnskey, kd->algo, reason, reason_bogus, section, qstate, verified, reasonbuf, reasonlen); return sec; } /** verify that a DS RR hashes to a key and that key signs the set */ static enum sec_status verify_dnskeys_with_ds_rr(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, int *nonechecked, char* reasonbuf, size_t reasonlen) { enum sec_status sec = sec_status_bogus; size_t i, num, numchecked = 0, numhashok = 0, numsizesupp = 0; num = rrset_get_count(dnskey_rrset); *nonechecked = 0; for(i=0; i numhashok + MAX_DS_MATCH_FAILURES) { verbose(VERB_ALGO, "DS match attempt reached " "MAX_DS_MATCH_FAILURES (%d); bogus", MAX_DS_MATCH_FAILURES); return sec_status_bogus; } continue; } numhashok++; if(!dnskey_size_is_supported(dnskey_rrset, i)) { verbose(VERB_ALGO, "DS okay but that DNSKEY size is not supported"); numsizesupp++; continue; } verbose(VERB_ALGO, "DS match digest ok, trying signature"); /* Otherwise, we have a match! Make sure that the DNSKEY * verifies *with this key* */ sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, reason, reason_bogus, LDNS_SECTION_ANSWER, qstate); if(sec == sec_status_secure) { return sec; } /* If it didn't validate with the DNSKEY, try the next one! */ } if(numsizesupp != 0 || sec == sec_status_indeterminate) { /* there is a working DS, but that DNSKEY is not supported */ return sec_status_insecure; } if(numchecked == 0) { algo_needs_reason(ds_get_key_algo(ds_rrset, ds_idx), reason, "no keys have a DS", reasonbuf, reasonlen); *nonechecked = 1; } else if(numhashok == 0) { *reason = "DS hash mismatches key"; } else if(!*reason) { *reason = "keyset not secured by DNSKEY that matches DS"; } return sec_status_bogus; } int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset) { size_t i, num = rrset_get_count(ds_rrset); int d, digest_algo = 0; /* DS digest algo 0 is not used. */ /* find favorite algo, for now, highest number supported */ for(i=0; i digest_algo) digest_algo = d; } return digest_algo; } enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) { /* as long as this is false, we can consider this DS rrset to be * equivalent to no DS rrset. */ int has_useful_ds = 0, digest_algo, alg, has_algo_refusal = 0, nonechecked, has_checked_ds = 0; struct algo_needs needs; size_t i, num; enum sec_status sec; if(dnskey_rrset->rk.dname_len != ds_rrset->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ds_rrset->rk.dname) != 0) { verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " "by name"); *reason = "DNSKEY RRset did not match DS RRset by name"; return sec_status_bogus; } if(sigalg) { /* harden against algo downgrade is enabled */ digest_algo = val_favorite_ds_algo(ds_rrset); algo_needs_init_ds(&needs, ds_rrset, digest_algo, sigalg); } else { /* accept any key algo, any digest algo */ digest_algo = -1; } num = rrset_get_count(ds_rrset); for(i=0; irk.dname, ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class), dnskey_rrset, downprot?sigalg:NULL, LDNS_EDE_NONE, NULL, *env->now); } else if(sec == sec_status_insecure) { return key_entry_create_null(region, ds_rrset->rk.dname, ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class), rrset_get_ttl(ds_rrset), *reason_bogus, *reason, *env->now); } return key_entry_create_bad(region, ds_rrset->rk.dname, ds_rrset->rk.dname_len, ntohs(ds_rrset->rk.rrset_class), BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now); } enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ta_ds, struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) { /* as long as this is false, we can consider this anchor to be * equivalent to no anchor. */ int has_useful_ta = 0, digest_algo = 0, alg, has_algo_refusal = 0, nonechecked, has_checked_ds = 0; struct algo_needs needs; size_t i, num; enum sec_status sec; if(ta_ds && (dnskey_rrset->rk.dname_len != ta_ds->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ta_ds->rk.dname) != 0)) { verbose(VERB_QUERY, "DNSKEY RRset did not match DS RRset " "by name"); *reason = "DNSKEY RRset did not match DS RRset by name"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSKEY_MISSING; return sec_status_bogus; } if(ta_dnskey && (dnskey_rrset->rk.dname_len != ta_dnskey->rk.dname_len || query_dname_compare(dnskey_rrset->rk.dname, ta_dnskey->rk.dname) != 0)) { verbose(VERB_QUERY, "DNSKEY RRset did not match anchor RRset " "by name"); *reason = "DNSKEY RRset did not match anchor RRset by name"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSKEY_MISSING; return sec_status_bogus; } if(ta_ds) digest_algo = val_favorite_ds_algo(ta_ds); if(sigalg) { if(ta_ds) algo_needs_init_ds(&needs, ta_ds, digest_algo, sigalg); else memset(&needs, 0, sizeof(needs)); if(ta_dnskey) algo_needs_init_dnskey_add(&needs, ta_dnskey, sigalg); } if(ta_ds) { num = rrset_get_count(ta_ds); for(i=0; irk.dname, dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class), dnskey_rrset, downprot?sigalg:NULL, LDNS_EDE_NONE, NULL, *env->now); } else if(sec == sec_status_insecure) { return key_entry_create_null(region, dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class), rrset_get_ttl(dnskey_rrset), *reason_bogus, *reason, *env->now); } return key_entry_create_bad(region, dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len, ntohs(dnskey_rrset->rk.rrset_class), BOGUS_KEY_TTL, *reason_bogus, *reason, *env->now); } int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset) { size_t i; for(i=0; iname); else snprintf(herr, sizeof(herr), "%d", (int)ds_get_digest_algo(ds_rrset, 0)); lt = sldns_lookup_by_id(sldns_algorithms, (int)ds_get_key_algo(ds_rrset, 0)); if(lt) snprintf(aerr, sizeof(aerr), "%s", lt->name); else snprintf(aerr, sizeof(aerr), "%d", (int)ds_get_key_algo(ds_rrset, 0)); verbose(VERB_ALGO, "DS unsupported, hash %s %s, " "key algorithm %s %s", herr, (ds_digest_algo_is_supported(ds_rrset, 0)? "(supported)":"(unsupported)"), aerr, (ds_key_algo_is_supported(ds_rrset, 0)? "(supported)":"(unsupported)")); } return 0; } /** get label count for a signature */ static uint8_t rrsig_get_labcount(struct packed_rrset_data* d, size_t sig) { if(d->rr_len[sig] < 2+4) return 0; /* bad sig length */ return d->rr_data[sig][2+3]; } int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc, size_t* wc_len) { struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> entry.data; uint8_t labcount; int labdiff; uint8_t* wn; size_t i, wl; if(d->rrsig_count == 0) { return 1; } labcount = rrsig_get_labcount(d, d->count + 0); /* check rest of signatures identical */ for(i=1; irrsig_count; i++) { if(labcount != rrsig_get_labcount(d, d->count + i)) { return 0; } } /* OK the rrsigs check out */ /* if the RRSIG label count is shorter than the number of actual * labels, then this rrset was synthesized from a wildcard. * Note that the RRSIG label count doesn't count the root label. */ wn = rrset->rk.dname; wl = rrset->rk.dname_len; /* skip a leading wildcard label in the dname (RFC4035 2.2) */ if(dname_is_wild(wn)) { wn += 2; wl -= 2; } labdiff = (dname_count_labels(wn) - 1) - (int)labcount; if(labdiff > 0) { *wc = wn; dname_remove_labels(wc, &wl, labdiff); *wc_len = wl; return 1; } return 1; } int val_chase_cname(struct query_info* qchase, struct reply_info* rep, size_t* cname_skip) { size_t i; /* skip any DNAMEs, go to the CNAME for next part */ for(i = *cname_skip; i < rep->an_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(qchase->qname, rep->rrsets[i]-> rk.dname) == 0) { qchase->qname = NULL; get_cname_target(rep->rrsets[i], &qchase->qname, &qchase->qname_len); if(!qchase->qname) return 0; /* bad CNAME rdata */ (*cname_skip) = i+1; return 1; } } return 0; /* CNAME classified but no matching CNAME ?! */ } /** see if rrset has signer name as one of the rrsig signers */ static int rrset_has_signer(struct ub_packed_rrset_key* rrset, uint8_t* name, size_t len) { struct packed_rrset_data* d = (struct packed_rrset_data*)rrset-> entry.data; size_t i; for(i = d->count; i< d->count+d->rrsig_count; i++) { if(d->rr_len[i] > 2+18+len) { /* at least rdatalen + signature + signame (+1 sig)*/ if(!dname_valid(d->rr_data[i]+2+18, d->rr_len[i]-2-18)) continue; if(query_dname_compare(name, d->rr_data[i]+2+18) == 0) { return 1; } } } return 0; } void val_fill_reply(struct reply_info* chase, struct reply_info* orig, size_t skip, uint8_t* name, size_t len, uint8_t* signer) { size_t i, j; int seen_dname = 0; chase->rrset_count = 0; chase->an_numrrsets = 0; chase->ns_numrrsets = 0; chase->ar_numrrsets = 0; /* ANSWER section */ for(i=skip; ian_numrrsets; i++) { if(!signer) { if(query_dname_compare(name, orig->rrsets[i]->rk.dname) == 0) chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; } else if(seen_dname && ntohs(orig->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME) { chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; seen_dname = 0; } else if(rrset_has_signer(orig->rrsets[i], name, len)) { chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; if(ntohs(orig->rrsets[i]->rk.type) == LDNS_RR_TYPE_DNAME) { seen_dname = 1; } } else if(ntohs(orig->rrsets[i]->rk.type) == LDNS_RR_TYPE_CNAME && ((struct packed_rrset_data*)orig->rrsets[i]-> entry.data)->rrsig_count == 0 && cname_under_previous_dname(orig, i, &j) && rrset_has_signer(orig->rrsets[j], name, len)) { chase->rrsets[chase->an_numrrsets++] = orig->rrsets[j]; chase->rrsets[chase->an_numrrsets++] = orig->rrsets[i]; } } /* AUTHORITY section */ for(i = (skip > orig->an_numrrsets)?skip:orig->an_numrrsets; ian_numrrsets+orig->ns_numrrsets; i++) { if(!signer) { if(query_dname_compare(name, orig->rrsets[i]->rk.dname) == 0) chase->rrsets[chase->an_numrrsets+ chase->ns_numrrsets++] = orig->rrsets[i]; } else if(rrset_has_signer(orig->rrsets[i], name, len)) { chase->rrsets[chase->an_numrrsets+ chase->ns_numrrsets++] = orig->rrsets[i]; } } /* ADDITIONAL section */ for(i= (skip>orig->an_numrrsets+orig->ns_numrrsets)? skip:orig->an_numrrsets+orig->ns_numrrsets; irrset_count; i++) { if(!signer) { if(query_dname_compare(name, orig->rrsets[i]->rk.dname) == 0) chase->rrsets[chase->an_numrrsets +chase->ns_numrrsets+chase->ar_numrrsets++] = orig->rrsets[i]; } else if(rrset_has_signer(orig->rrsets[i], name, len)) { chase->rrsets[chase->an_numrrsets+chase->ns_numrrsets+ chase->ar_numrrsets++] = orig->rrsets[i]; } } chase->rrset_count = chase->an_numrrsets + chase->ns_numrrsets + chase->ar_numrrsets; } void val_reply_remove_auth(struct reply_info* rep, size_t index) { log_assert(index < rep->rrset_count); log_assert(index >= rep->an_numrrsets); log_assert(index < rep->an_numrrsets+rep->ns_numrrsets); memmove(rep->rrsets+index, rep->rrsets+index+1, sizeof(struct ub_packed_rrset_key*)* (rep->rrset_count - index - 1)); rep->ns_numrrsets--; rep->rrset_count--; } void val_check_nonsecure(struct module_env* env, struct reply_info* rep) { size_t i; /* authority */ for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) ->security != sec_status_secure) { /* because we want to return the authentic original * message when presented with CD-flagged queries, * we need to preserve AUTHORITY section data. * However, this rrset is not signed or signed * with the wrong keys. Validation has tried to * verify this rrset with the keysets of import. * But this rrset did not verify. * Therefore the message is bogus. */ /* check if authority has an NS record * which is bad, and there is an answer section with * data. In that case, delete NS and additional to * be lenient and make a minimal response */ if(rep->an_numrrsets != 0 && ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS) { verbose(VERB_ALGO, "truncate to minimal"); rep->ar_numrrsets = 0; rep->rrset_count = rep->an_numrrsets + rep->ns_numrrsets; /* remove this unneeded authority rrset */ memmove(rep->rrsets+i, rep->rrsets+i+1, sizeof(struct ub_packed_rrset_key*)* (rep->rrset_count - i - 1)); rep->ns_numrrsets--; rep->rrset_count--; i--; return; } log_nametypeclass(VERB_QUERY, "message is bogus, " "non secure rrset", rep->rrsets[i]->rk.dname, ntohs(rep->rrsets[i]->rk.type), ntohs(rep->rrsets[i]->rk.rrset_class)); rep->security = sec_status_bogus; return; } } /* additional */ if(!env->cfg->val_clean_additional) return; for(i=rep->an_numrrsets+rep->ns_numrrsets; irrset_count; i++) { if(((struct packed_rrset_data*)rep->rrsets[i]->entry.data) ->security != sec_status_secure) { /* This does not cause message invalidation. It was * simply unsigned data in the additional. The * RRSIG must have been truncated off the message. * * However, we do not want to return possible bogus * data to clients that rely on this service for * their authentication. */ /* remove this unneeded additional rrset */ memmove(rep->rrsets+i, rep->rrsets+i+1, sizeof(struct ub_packed_rrset_key*)* (rep->rrset_count - i - 1)); rep->ar_numrrsets--; rep->rrset_count--; i--; } } } /** check no anchor and unlock */ static int check_no_anchor(struct val_anchors* anchors, uint8_t* nm, size_t l, uint16_t c) { struct trust_anchor* ta; if((ta=anchors_lookup(anchors, nm, l, c))) { lock_basic_unlock(&ta->lock); } return !ta; } void val_mark_indeterminate(struct reply_info* rep, struct val_anchors* anchors, struct rrset_cache* r, struct module_env* env) { size_t i; struct packed_rrset_data* d; for(i=0; irrset_count; i++) { d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; if(d->security == sec_status_unchecked && check_no_anchor(anchors, rep->rrsets[i]->rk.dname, rep->rrsets[i]->rk.dname_len, ntohs(rep->rrsets[i]->rk.rrset_class))) { /* mark as indeterminate */ d->security = sec_status_indeterminate; rrset_update_sec_status(r, rep->rrsets[i], *env->now); } } } void val_mark_insecure(struct reply_info* rep, uint8_t* kname, struct rrset_cache* r, struct module_env* env) { size_t i; struct packed_rrset_data* d; for(i=0; irrset_count; i++) { d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; if(d->security == sec_status_unchecked && dname_subdomain_c(rep->rrsets[i]->rk.dname, kname)) { /* mark as insecure */ d->security = sec_status_insecure; rrset_update_sec_status(r, rep->rrsets[i], *env->now); } } } size_t val_next_unchecked(struct reply_info* rep, size_t skip) { size_t i; struct packed_rrset_data* d; for(i=skip+1; irrset_count; i++) { d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; if(d->security == sec_status_unchecked) { return i; } } return rep->rrset_count; } const char* val_classification_to_string(enum val_classification subtype) { switch(subtype) { case VAL_CLASS_UNTYPED: return "untyped"; case VAL_CLASS_UNKNOWN: return "unknown"; case VAL_CLASS_POSITIVE: return "positive"; case VAL_CLASS_CNAME: return "cname"; case VAL_CLASS_NODATA: return "nodata"; case VAL_CLASS_NAMEERROR: return "nameerror"; case VAL_CLASS_CNAMENOANSWER: return "cnamenoanswer"; case VAL_CLASS_REFERRAL: return "referral"; case VAL_CLASS_ANY: return "qtype_any"; default: return "bad_val_classification"; } } /** log a sock_list entry */ static void sock_list_logentry(enum verbosity_value v, const char* s, struct sock_list* p) { if(p->len) log_addr(v, s, &p->addr, p->len); else verbose(v, "%s cache", s); } void val_blacklist(struct sock_list** blacklist, struct regional* region, struct sock_list* origin, int cross) { /* debug printout */ if(verbosity >= VERB_ALGO) { struct sock_list* p; for(p=*blacklist; p; p=p->next) sock_list_logentry(VERB_ALGO, "blacklist", p); if(!origin) verbose(VERB_ALGO, "blacklist add: cache"); for(p=origin; p; p=p->next) sock_list_logentry(VERB_ALGO, "blacklist add", p); } /* blacklist the IPs or the cache */ if(!origin) { /* only add if nothing there. anything else also stops cache*/ if(!*blacklist) sock_list_insert(blacklist, NULL, 0, region); } else if(!cross) sock_list_prepend(blacklist, origin); else sock_list_merge(blacklist, region, origin); } int val_has_signed_nsecs(struct reply_info* rep, char** reason) { size_t i, num_nsec = 0, num_nsec3 = 0; struct packed_rrset_data* d; for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC)) num_nsec++; else if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NSEC3)) num_nsec3++; else continue; d = (struct packed_rrset_data*)rep->rrsets[i]->entry.data; if(d && d->rrsig_count != 0) { return 1; } } if(num_nsec == 0 && num_nsec3 == 0) *reason = "no DNSSEC records"; else if(num_nsec != 0) *reason = "no signatures over NSECs"; else *reason = "no signatures over NSEC3s"; return 0; } struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, struct regional* region, uint8_t* topname) { struct dns_msg* msg; struct query_info qinfo; struct ub_packed_rrset_key *rrset = rrset_cache_lookup( env->rrset_cache, nm, nmlen, LDNS_RR_TYPE_DS, c, 0, *env->now, 0); if(rrset) { /* DS rrset exists. Return it to the validator immediately*/ struct ub_packed_rrset_key* copy = packed_rrset_copy_region( rrset, region, *env->now); struct packed_rrset_data* d = copy->entry.data; lock_rw_unlock(&rrset->entry.lock); if(!copy) return NULL; msg = dns_msg_create(nm, nmlen, LDNS_RR_TYPE_DS, c, region, 1); if(!msg) return NULL; msg->rep->rrsets[0] = copy; msg->rep->rrset_count++; msg->rep->an_numrrsets++; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return msg; } /* lookup in rrset and negative cache for NSEC/NSEC3 */ qinfo.qname = nm; qinfo.qname_len = nmlen; qinfo.qtype = LDNS_RR_TYPE_DS; qinfo.qclass = c; qinfo.local_alias = NULL; /* do not add SOA to reply message, it is going to be used internal */ msg = val_neg_getmsg(env->neg_cache, &qinfo, region, env->rrset_cache, env->scratch_buffer, *env->now, 0, topname, env->cfg); return msg; } int derive_cname_from_dname(struct ub_packed_rrset_key* cname, struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen) { size_t prefix_len; uint8_t* dname_target = NULL; size_t dname_target_len = 0; if(!dname_strict_subdomain_c(cname->rk.dname, dname->rk.dname)) return 0; /* Invalid: CNAME owner must be subdomain */ get_cname_target(dname, &dname_target, &dname_target_len); if(!dname_target || !dname_target_len) return 0; /* DNAME malformed */ if(cname->rk.dname_len < dname->rk.dname_len) return 0; /* Not possible, due to subdomain, but check */ if(cname->rk.dname_len == 0) return 0; /* Not possible, but check */ prefix_len = cname->rk.dname_len - dname->rk.dname_len; if(prefix_len + dname_target_len > outlen) return 0; /* Buffer too small */ memmove(out, cname->rk.dname, prefix_len); memmove(out+prefix_len, dname_target, dname_target_len); return 1; } unbound-1.25.1/validator/validator.c0000644000175000017500000036363715203270263017046 0ustar wouterwouter/* * validator/validator.c - secure validator DNS query response module * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains a module that performs validation of DNS queries. * According to RFC 4034. */ #include "config.h" #include #include "validator/validator.h" #include "validator/val_anchor.h" #include "validator/val_kcache.h" #include "validator/val_kentry.h" #include "validator/val_utils.h" #include "validator/val_nsec.h" #include "validator/val_nsec3.h" #include "validator/val_neg.h" #include "validator/val_sigcrypt.h" #include "validator/autotrust.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "util/data/dname.h" #include "util/module.h" #include "util/log.h" #include "util/net_help.h" #include "util/regional.h" #include "util/config_file.h" #include "util/fptr_wlist.h" #include "sldns/rrdef.h" #include "sldns/wire2str.h" #include "sldns/str2wire.h" /** Max number of RRSIGs to validate at once, suspend query for later. */ #define MAX_VALIDATE_AT_ONCE 8 /** Max number of validation suspends allowed, error out otherwise. */ #define MAX_VALIDATION_SUSPENDS 16 /* forward decl for cache response and normal super inform calls of a DS */ static void process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, struct sock_list* origin, int* suspend, struct module_qstate* sub_qstate); /* Updates the supplied EDE (RFC8914) code selectively so we don't lose * a more specific code */ static void update_reason_bogus(struct reply_info* rep, sldns_ede_code reason_bogus) { if(reason_bogus == LDNS_EDE_NONE) return; if(reason_bogus == LDNS_EDE_DNSSEC_BOGUS && rep->reason_bogus != LDNS_EDE_NONE && rep->reason_bogus != LDNS_EDE_DNSSEC_BOGUS) return; rep->reason_bogus = reason_bogus; } /** fill up nsec3 key iterations config entry */ static int fill_nsec3_iter(size_t** keysize, size_t** maxiter, char* s, int c) { char* e; int i; *keysize = (size_t*)calloc((size_t)c, sizeof(size_t)); *maxiter = (size_t*)calloc((size_t)c, sizeof(size_t)); if(!*keysize || !*maxiter) { free(*keysize); *keysize = NULL; free(*maxiter); *maxiter = NULL; log_err("out of memory"); return 0; } for(i=0; i0 && (*keysize)[i-1] >= (*keysize)[i]) { log_err("nsec3 key iterations not ascending: %d %d", (int)(*keysize)[i-1], (int)(*keysize)[i]); free(*keysize); *keysize = NULL; free(*maxiter); *maxiter = NULL; return 0; } verbose(VERB_ALGO, "validator nsec3cfg keysz %d mxiter %d", (int)(*keysize)[i], (int)(*maxiter)[i]); } return 1; } int val_env_parse_key_iter(char* val_nsec3_key_iterations, size_t** keysize, size_t** maxiter, int* keyiter_count) { int c; c = cfg_count_numbers(val_nsec3_key_iterations); if(c < 1 || (c&1)) { log_err("validator: unparsable or odd nsec3 key " "iterations: %s", val_nsec3_key_iterations); return 0; } *keyiter_count = c/2; if(!fill_nsec3_iter(keysize, maxiter, val_nsec3_key_iterations, c/2)) { log_err("validator: cannot apply nsec3 key iterations"); return 0; } return 1; } void val_env_apply_cfg(struct val_env* val_env, struct config_file* cfg, size_t* keysize, size_t* maxiter, int keyiter_count) { free(val_env->nsec3_keysize); free(val_env->nsec3_maxiter); val_env->nsec3_keysize = keysize; val_env->nsec3_maxiter = maxiter; val_env->nsec3_keyiter_count = keyiter_count; val_env->bogus_ttl = (uint32_t)cfg->bogus_ttl; val_env->date_override = cfg->val_date_override; val_env->skew_min = cfg->val_sig_skew_min; val_env->skew_max = cfg->val_sig_skew_max; val_env->max_restart = cfg->val_max_restart; } /** apply config settings to validator */ static int val_apply_cfg(struct module_env* env, struct val_env* val_env, struct config_file* cfg) { size_t* keysize=NULL, *maxiter=NULL; int keyiter_count = 0; if(!env->anchors) env->anchors = anchors_create(); if(!env->anchors) { log_err("out of memory"); return 0; } if (env->key_cache) val_env->kcache = env->key_cache; if(!val_env->kcache) val_env->kcache = key_cache_create(cfg); if(!val_env->kcache) { log_err("out of memory"); return 0; } env->key_cache = val_env->kcache; if(!anchors_apply_cfg(env->anchors, cfg)) { log_err("validator: error in trustanchors config"); return 0; } if(!val_env_parse_key_iter(cfg->val_nsec3_key_iterations, &keysize, &maxiter, &keyiter_count)) { return 0; } val_env_apply_cfg(val_env, cfg, keysize, maxiter, keyiter_count); if (env->neg_cache) val_env->neg_cache = env->neg_cache; if(!val_env->neg_cache) val_env->neg_cache = val_neg_create(cfg, val_env->nsec3_maxiter[val_env->nsec3_keyiter_count-1]); if(!val_env->neg_cache) { log_err("out of memory"); return 0; } env->neg_cache = val_env->neg_cache; return 1; } #ifdef USE_ECDSA_EVP_WORKAROUND void ecdsa_evp_workaround_init(void); #endif int val_init(struct module_env* env, int id) { struct val_env* val_env = (struct val_env*)calloc(1, sizeof(struct val_env)); if(!val_env) { log_err("malloc failure"); return 0; } env->modinfo[id] = (void*)val_env; env->need_to_validate = 1; lock_basic_init(&val_env->bogus_lock); lock_protect(&val_env->bogus_lock, &val_env->num_rrset_bogus, sizeof(val_env->num_rrset_bogus)); #ifdef USE_ECDSA_EVP_WORKAROUND ecdsa_evp_workaround_init(); #endif if(!val_apply_cfg(env, val_env, env->cfg)) { log_err("validator: could not apply configuration settings."); return 0; } if(env->cfg->disable_edns_do) { struct trust_anchor* anchor = anchors_find_any_noninsecure( env->anchors); if(anchor) { char b[LDNS_MAX_DOMAINLEN]; dname_str(anchor->name, b); log_warn("validator: disable-edns-do is enabled, but there is a trust anchor for '%s'. Since DNSSEC could not work, the disable-edns-do setting is turned off. Continuing without it.", b); lock_basic_unlock(&anchor->lock); env->cfg->disable_edns_do = 0; } } return 1; } void val_deinit(struct module_env* env, int id) { struct val_env* val_env; if(!env || !env->modinfo[id]) return; val_env = (struct val_env*)env->modinfo[id]; lock_basic_destroy(&val_env->bogus_lock); anchors_delete(env->anchors); env->anchors = NULL; key_cache_delete(val_env->kcache); env->key_cache = NULL; neg_cache_delete(val_env->neg_cache); env->neg_cache = NULL; free(val_env->nsec3_keysize); free(val_env->nsec3_maxiter); free(val_env); env->modinfo[id] = NULL; } /** fill in message structure */ static struct val_qstate* val_new_getmsg(struct module_qstate* qstate, struct val_qstate* vq) { if(!qstate->return_msg || qstate->return_rcode != LDNS_RCODE_NOERROR) { /* create a message to verify */ verbose(VERB_ALGO, "constructing reply for validation"); vq->orig_msg = (struct dns_msg*)regional_alloc(qstate->region, sizeof(struct dns_msg)); if(!vq->orig_msg) return NULL; vq->orig_msg->qinfo = qstate->qinfo; vq->orig_msg->rep = (struct reply_info*)regional_alloc( qstate->region, sizeof(struct reply_info)); if(!vq->orig_msg->rep) return NULL; memset(vq->orig_msg->rep, 0, sizeof(struct reply_info)); vq->orig_msg->rep->flags = (uint16_t)(qstate->return_rcode&0xf) |BIT_QR|BIT_RA|(qstate->query_flags|(BIT_CD|BIT_RD)); vq->orig_msg->rep->qdcount = 1; vq->orig_msg->rep->reason_bogus = LDNS_EDE_NONE; } else { vq->orig_msg = qstate->return_msg; } vq->qchase = qstate->qinfo; /* chase reply will be an edited (sub)set of the orig msg rrset ptrs */ vq->chase_reply = regional_alloc_init(qstate->region, vq->orig_msg->rep, sizeof(struct reply_info) - sizeof(struct rrset_ref)); if(!vq->chase_reply) return NULL; if(vq->orig_msg->rep->rrset_count > RR_COUNT_MAX) return NULL; /* protect against integer overflow */ /* Over allocate (+an_numrrsets) in case we need to put extra DNAME * records for unsigned CNAME repetitions */ vq->chase_reply->rrsets = regional_alloc(qstate->region, sizeof(struct ub_packed_rrset_key*) * (vq->orig_msg->rep->rrset_count + vq->orig_msg->rep->an_numrrsets)); if(!vq->chase_reply->rrsets) return NULL; memmove(vq->chase_reply->rrsets, vq->orig_msg->rep->rrsets, sizeof(struct ub_packed_rrset_key*) * vq->orig_msg->rep->rrset_count); vq->rrset_skip = 0; return vq; } /** allocate new validator query state */ static struct val_qstate* val_new(struct module_qstate* qstate, int id) { struct val_qstate* vq = (struct val_qstate*)regional_alloc( qstate->region, sizeof(*vq)); log_assert(!qstate->minfo[id]); if(!vq) return NULL; memset(vq, 0, sizeof(*vq)); qstate->minfo[id] = vq; vq->state = VAL_INIT_STATE; return val_new_getmsg(qstate, vq); } /** reset validator query state for query restart */ static void val_restart(struct val_qstate* vq) { struct comm_timer* temp_timer; int restart_count; if(!vq) return; temp_timer = vq->suspend_timer; restart_count = vq->restart_count+1; memset(vq, 0, sizeof(*vq)); vq->suspend_timer = temp_timer; vq->restart_count = restart_count; vq->state = VAL_INIT_STATE; } /** * Exit validation with an error status * * @param qstate: query state * @param id: validator id. * @return false, for use by caller to return to stop processing. */ static int val_error(struct module_qstate* qstate, int id) { qstate->ext_state[id] = module_error; qstate->return_rcode = LDNS_RCODE_SERVFAIL; return 0; } /** * Check to see if a given response needs to go through the validation * process. Typical reasons for this routine to return false are: CD bit was * on in the original request, or the response is a kind of message that * is unvalidatable (i.e., SERVFAIL, REFUSED, etc.) * * @param qstate: query state. * @param ret_rc: rcode for this message (if noerror - examine ret_msg). * @param ret_msg: return msg, can be NULL; look at rcode instead. * @return true if the response could use validation (although this does not * mean we can actually validate this response). */ static int needs_validation(struct module_qstate* qstate, int ret_rc, struct dns_msg* ret_msg) { int rcode; /* If the CD bit is on in the original request, then you could think * that we don't bother to validate anything. * But this is signalled internally with the valrec flag. * User queries are validated with BIT_CD to make our cache clean * so that bogus messages get retried by the upstream also for * downstream validators that set BIT_CD. * For DNS64 bit_cd signals no dns64 processing, but we want to * provide validation there too */ /* if((qstate->query_flags & BIT_CD)) { verbose(VERB_ALGO, "not validating response due to CD bit"); return 0; } */ if(qstate->is_valrec) { verbose(VERB_ALGO, "not validating response, is valrec" "(validation recursion lookup)"); return 0; } if(ret_rc != LDNS_RCODE_NOERROR || !ret_msg) rcode = ret_rc; else rcode = (int)FLAGS_GET_RCODE(ret_msg->rep->flags); if(rcode != LDNS_RCODE_NOERROR && rcode != LDNS_RCODE_NXDOMAIN) { if(verbosity >= VERB_ALGO) { char rc[16]; rc[0]=0; (void)sldns_wire2str_rcode_buf(rcode, rc, sizeof(rc)); verbose(VERB_ALGO, "cannot validate non-answer, rcode %s", rc); } return 0; } /* cannot validate positive RRSIG response. (negatives can) */ if(qstate->qinfo.qtype == LDNS_RR_TYPE_RRSIG && rcode == LDNS_RCODE_NOERROR && ret_msg && ret_msg->rep->an_numrrsets > 0) { verbose(VERB_ALGO, "cannot validate RRSIG, no sigs on sigs."); return 0; } return 1; } /** * Check to see if the response has already been validated. * @param ret_msg: return msg, can be NULL * @return true if the response has already been validated */ static int already_validated(struct dns_msg* ret_msg) { /* validate unchecked, and re-validate bogus messages */ if (ret_msg && ret_msg->rep->security > sec_status_bogus) { verbose(VERB_ALGO, "response has already been validated: %s", sec_status_to_string(ret_msg->rep->security)); return 1; } return 0; } /** * Generate a request for DNS data. * * @param qstate: query state that is the parent. * @param id: module id. * @param name: what name to query for. * @param namelen: length of name. * @param qtype: query type. * @param qclass: query class. * @param flags: additional flags, such as the CD bit (BIT_CD), or 0. * @param newq: If the subquery is newly created, it is returned, * otherwise NULL is returned * @param detached: true if this qstate should not attach to the subquery * @return false on alloc failure. */ static int generate_request(struct module_qstate* qstate, int id, uint8_t* name, size_t namelen, uint16_t qtype, uint16_t qclass, uint16_t flags, struct module_qstate** newq, int detached) { struct val_qstate* vq = (struct val_qstate*)qstate->minfo[id]; struct query_info ask; int valrec; ask.qname = name; ask.qname_len = namelen; ask.qtype = qtype; ask.qclass = qclass; ask.local_alias = NULL; log_query_info(VERB_ALGO, "generate request", &ask); /* enable valrec flag to avoid recursion to the same validation * routine, this lookup is simply a lookup. */ valrec = 1; fptr_ok(fptr_whitelist_modenv_detect_cycle(qstate->env->detect_cycle)); if((*qstate->env->detect_cycle)(qstate, &ask, (uint16_t)(BIT_RD|flags), 0, valrec)) { verbose(VERB_ALGO, "Could not generate request: cycle detected"); return 0; } if(detached) { struct mesh_state* sub = NULL; fptr_ok(fptr_whitelist_modenv_add_sub( qstate->env->add_sub)); if(!(*qstate->env->add_sub)(qstate, &ask, NULL, (uint16_t)(BIT_RD|flags), 0, valrec, newq, &sub)){ log_err("Could not generate request: out of memory"); return 0; } } else { fptr_ok(fptr_whitelist_modenv_attach_sub( qstate->env->attach_sub)); if(!(*qstate->env->attach_sub)(qstate, &ask, NULL, (uint16_t)(BIT_RD|flags), 0, valrec, newq)){ log_err("Could not generate request: out of memory"); return 0; } } /* newq; validator does not need state created for that * query, and its a 'normal' for iterator as well */ if(*newq) { /* add our blacklist to the query blacklist */ sock_list_merge(&(*newq)->blacklist, (*newq)->region, vq->chain_blacklist); } qstate->ext_state[id] = module_wait_subquery; return 1; } /** * Generate, send and detach key tag signaling query. * * @param qstate: query state. * @param id: module id. * @param ta: trust anchor, locked. * @return false on a processing error. */ static int generate_keytag_query(struct module_qstate* qstate, int id, struct trust_anchor* ta) { /* 3 bytes for "_ta", 5 bytes per tag (4 bytes + "-") */ #define MAX_LABEL_TAGS (LDNS_MAX_LABELLEN-3)/5 size_t i, numtag; uint16_t tags[MAX_LABEL_TAGS]; char tagstr[LDNS_MAX_LABELLEN+1] = "_ta"; /* +1 for NULL byte */ size_t tagstr_left = sizeof(tagstr) - strlen(tagstr); char* tagstr_pos = tagstr + strlen(tagstr); uint8_t dnamebuf[LDNS_MAX_DOMAINLEN+1]; /* +1 for label length byte */ size_t dnamebuf_len = sizeof(dnamebuf); uint8_t* keytagdname; struct module_qstate* newq = NULL; enum module_ext_state ext_state = qstate->ext_state[id]; numtag = anchor_list_keytags(ta, tags, MAX_LABEL_TAGS); if(numtag == 0) return 0; for(i=0; iname, ta->namelen); if(!(keytagdname = (uint8_t*)regional_alloc_init(qstate->region, dnamebuf, dnamebuf_len))) { log_err("could not generate key tag query: out of memory"); return 0; } log_nametypeclass(VERB_OPS, "generate keytag query", keytagdname, LDNS_RR_TYPE_NULL, ta->dclass); if(!generate_request(qstate, id, keytagdname, dnamebuf_len, LDNS_RR_TYPE_NULL, ta->dclass, 0, &newq, 1)) { verbose(VERB_ALGO, "failed to generate key tag signaling request"); return 0; } /* Not interested in subquery response. Restore the ext_state, * that might be changed by generate_request() */ qstate->ext_state[id] = ext_state; return 1; } /** * Get keytag as uint16_t from string * * @param start: start of string containing keytag * @param keytag: pointer where to store the extracted keytag * @return: 1 if keytag was extracted, else 0. */ static int sentinel_get_keytag(char* start, uint16_t* keytag) { char* keytag_str; char* e = NULL; keytag_str = calloc(1, SENTINEL_KEYTAG_LEN + 1 /* null byte */); if(!keytag_str) return 0; memmove(keytag_str, start, SENTINEL_KEYTAG_LEN); keytag_str[SENTINEL_KEYTAG_LEN] = '\0'; *keytag = (uint16_t)strtol(keytag_str, &e, 10); if(!e || *e != '\0') { free(keytag_str); return 0; } free(keytag_str); return 1; } /** * Prime trust anchor for use. * Generate and dispatch a priming query for the given trust anchor. * The trust anchor can be DNSKEY or DS and does not have to be signed. * * @param qstate: query state. * @param vq: validator query state. * @param id: module id. * @param toprime: what to prime. * @return false on a processing error. */ static int prime_trust_anchor(struct module_qstate* qstate, struct val_qstate* vq, int id, struct trust_anchor* toprime) { struct module_qstate* newq = NULL; int ret = generate_request(qstate, id, toprime->name, toprime->namelen, LDNS_RR_TYPE_DNSKEY, toprime->dclass, BIT_CD, &newq, 0); if(newq && qstate->env->cfg->trust_anchor_signaling && !generate_keytag_query(qstate, id, toprime)) { verbose(VERB_ALGO, "keytag signaling query failed"); return 0; } if(!ret) { verbose(VERB_ALGO, "Could not prime trust anchor"); return 0; } /* ignore newq; validator does not need state created for that * query, and its a 'normal' for iterator as well */ vq->wait_prime_ta = 1; /* to elicit PRIME_RESP_STATE processing from the validator inform_super() routine */ /* store trust anchor name for later lookup when prime returns */ vq->trust_anchor_name = regional_alloc_init(qstate->region, toprime->name, toprime->namelen); vq->trust_anchor_len = toprime->namelen; vq->trust_anchor_labs = toprime->namelabs; if(!vq->trust_anchor_name) { log_err("Could not prime trust anchor: out of memory"); return 0; } return 1; } /** * Validate if the ANSWER and AUTHORITY sections contain valid rrsets. * They must be validly signed with the given key. * Tries to validate ADDITIONAL rrsets as well, but only to check them. * Allows unsigned CNAME after a DNAME that expands the DNAME. * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. * * @param qstate: query state. * @param vq: validator query state. * @param env: module env for verify. * @param ve: validator env for verify. * @param chase_reply: answer to validate. * @param key_entry: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. * @return false if any of the rrsets in the an or ns sections of the message * fail to verify. The message is then set to bogus. */ static int validate_msg_signatures(struct module_qstate* qstate, struct val_qstate* vq, struct module_env* env, struct val_env* ve, struct reply_info* chase_reply, struct key_entry_key* key_entry, int* suspend) { uint8_t* sname; size_t i, slen; struct ub_packed_rrset_key* s; enum sec_status sec; int num_verifies = 0, verified, have_state = 0; char reasonbuf[256]; char* reason = NULL; sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; *suspend = 0; if(vq->msg_signatures_state) { /* Pick up the state, and reset it, may not be needed now. */ vq->msg_signatures_state = 0; have_state = 1; } /* validate the ANSWER section */ for(i=0; ian_numrrsets; i++) { if(have_state && i <= vq->msg_signatures_index) continue; s = chase_reply->rrsets[i]; /* Skip the CNAME following a (validated) DNAME. * Because of the normalization routines in the iterator, * there will always be an unsigned CNAME following a DNAME * (unless qtype=DNAME in the answer part). */ if(i>0 && ntohs(chase_reply->rrsets[i-1]->rk.type) == LDNS_RR_TYPE_DNAME && ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME && ((struct packed_rrset_data*)chase_reply->rrsets[i-1]->entry.data)->security == sec_status_secure && dname_strict_subdomain_c(s->rk.dname, chase_reply->rrsets[i-1]->rk.dname) ) { /* Check that the CNAME target matches the DNAME * derivation. Zone changes during the redirection * lookups or looped DNAMEs can have such a CNAME. */ uint8_t expected_target[LDNS_MAX_DOMAINLEN]; uint8_t* cname_target = NULL; size_t cname_target_len = 0; get_cname_target(s, &cname_target, &cname_target_len); if(!cname_target || !derive_cname_from_dname(s, /* CNAME RRset */ chase_reply->rrsets[i-1], /* DNAME RRset */ expected_target, /* Output buffer */ sizeof(expected_target))) { verbose(VERB_ALGO, "DNAME CNAME derivation failed"); errinf_ede(qstate, "DNAME CNAME derivation failed", reason_bogus); errinf_origin(qstate, qstate->reply_origin); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, reason_bogus); return 0; } if(query_dname_compare(cname_target, expected_target) != 0) { verbose(VERB_ALGO, "CNAME target mismatch: not synthesized from DNAME"); errinf_ede(qstate, "CNAME target mismatch: not synthesized from DNAME", reason_bogus); errinf_dname(qstate, ", for", s->rk.dname); errinf_dname(qstate, "CNAME", cname_target); errinf(qstate, ","); errinf_origin(qstate, qstate->reply_origin); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, reason_bogus); return 0; } /* CNAME was synthesized by our own iterator */ /* since the DNAME verified, mark the CNAME as secure */ ((struct packed_rrset_data*)s->entry.data)->security = sec_status_secure; ((struct packed_rrset_data*)s->entry.data)->trust = rrset_trust_validated; continue; } /* Verify the answer rrset */ sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, sizeof(reasonbuf)); /* If the (answer) rrset failed to validate, then this * message is BAD. */ if(sec != sec_status_secure) { log_nametypeclass(VERB_QUERY, "validator: response " "has failed ANSWER rrset:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); errinf_ede(qstate, reason, reason_bogus); if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) errinf(qstate, "for CNAME"); else if(ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME) errinf(qstate, "for DNAME"); errinf_origin(qstate, qstate->reply_origin); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, reason_bogus); return 0; } num_verifies += verified; if(num_verifies > MAX_VALIDATE_AT_ONCE && i+1 < (env->cfg->val_clean_additional? chase_reply->an_numrrsets+chase_reply->ns_numrrsets: chase_reply->rrset_count)) { /* If the number of RRSIGs exceeds the maximum in * one go, suspend. Only suspend if there is a next * rrset to verify, i+1msg_signatures_state = 1; vq->msg_signatures_index = i; verbose(VERB_ALGO, "msg signature validation " "suspended"); return 0; } } /* validate the AUTHORITY section */ for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { if(have_state && i <= vq->msg_signatures_index) continue; s = chase_reply->rrsets[i]; sec = val_verify_rrset_entry(env, ve, s, key_entry, &reason, &reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, reasonbuf, sizeof(reasonbuf)); /* If anything in the authority section fails to be secure, * we have a bad message. */ if(sec != sec_status_secure) { log_nametypeclass(VERB_QUERY, "validator: response " "has failed AUTHORITY rrset:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); errinf_ede(qstate, reason, reason_bogus); errinf_origin(qstate, qstate->reply_origin); errinf_rrset(qstate, s); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, reason_bogus); return 0; } num_verifies += verified; if(num_verifies > MAX_VALIDATE_AT_ONCE && i+1 < (env->cfg->val_clean_additional? chase_reply->an_numrrsets+chase_reply->ns_numrrsets: chase_reply->rrset_count)) { *suspend = 1; vq->msg_signatures_state = 1; vq->msg_signatures_index = i; verbose(VERB_ALGO, "msg signature validation " "suspended"); return 0; } } /* If set, the validator should clean the additional section of * secure messages. */ if(!env->cfg->val_clean_additional) return 1; /* attempt to validate the ADDITIONAL section rrsets */ for(i=chase_reply->an_numrrsets+chase_reply->ns_numrrsets; irrset_count; i++) { if(have_state && i <= vq->msg_signatures_index) continue; s = chase_reply->rrsets[i]; /* only validate rrs that have signatures with the key */ /* leave others unchecked, those get removed later on too */ val_find_rrset_signer(s, &sname, &slen); verified = 0; if(sname && query_dname_compare(sname, key_entry->name)==0) (void)val_verify_rrset_entry(env, ve, s, key_entry, &reason, NULL, LDNS_SECTION_ADDITIONAL, qstate, &verified, reasonbuf, sizeof(reasonbuf)); /* the additional section can fail to be secure, * it is optional, check signature in case we need * to clean the additional section later. */ num_verifies += verified; if(num_verifies > MAX_VALIDATE_AT_ONCE && i+1 < chase_reply->rrset_count) { *suspend = 1; vq->msg_signatures_state = 1; vq->msg_signatures_index = i; verbose(VERB_ALGO, "msg signature validation " "suspended"); return 0; } } return 1; } void validate_suspend_timer_cb(void* arg) { struct module_qstate* qstate = (struct module_qstate*)arg; verbose(VERB_ALGO, "validate_suspend timer, continue"); mesh_run(qstate->env->mesh, qstate->mesh_info, module_event_pass, NULL); } /** Setup timer to continue validation of msg signatures later */ static int validate_suspend_setup_timer(struct module_qstate* qstate, struct val_qstate* vq, int id, enum val_state resume_state) { struct timeval tv; int usec, slack, base; if(vq->suspend_count >= MAX_VALIDATION_SUSPENDS) { verbose(VERB_ALGO, "validate_suspend timer: " "reached MAX_VALIDATION_SUSPENDS (%d); error out", MAX_VALIDATION_SUSPENDS); errinf(qstate, "max validation suspends reached, " "too many RRSIG validations"); return 0; } verbose(VERB_ALGO, "validate_suspend timer, set for suspend"); vq->state = resume_state; qstate->ext_state[id] = module_wait_reply; if(!vq->suspend_timer) { vq->suspend_timer = comm_timer_create( qstate->env->worker_base, validate_suspend_timer_cb, qstate); if(!vq->suspend_timer) { log_err("validate_suspend_setup_timer: " "out of memory for comm_timer_create"); return 0; } } /* The timer is activated later, after other events in the event * loop have been processed. The query state can also be deleted, * when the list is full and query states are dropped. */ /* Extend wait time if there are a lot of queries or if this one * is taking long, to keep around cpu time for ordinary queries. */ usec = 50000; /* 50 msec */ slack = 0; if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states) slack += 3; else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/2) slack += 2; else if(qstate->env->mesh->all.count >= qstate->env->mesh->max_reply_states/4) slack += 1; if(vq->suspend_count > 3) slack += 3; else if(vq->suspend_count > 0) slack += vq->suspend_count; if(slack != 0 && slack <= 12 /* No numeric overflow. */) { usec = usec << slack; } /* Spread such timeouts within 90%-100% of the original timer. */ base = usec * 9/10; usec = base + ub_random_max(qstate->env->rnd, usec-base); tv.tv_usec = (usec % 1000000); tv.tv_sec = (usec / 1000000); vq->suspend_count ++; comm_timer_set(vq->suspend_timer, &tv); return 1; } /** * Detect wrong truncated response (say from BIND 9.6.1 that is forwarding * and saw the NS record without signatures from a referral). * The positive response has a mangled authority section. * Remove that authority section and the additional section. * @param rep: reply * @return true if a wrongly truncated response. */ static int detect_wrongly_truncated(struct reply_info* rep) { size_t i; /* only NS in authority, and it is bogus */ if(rep->ns_numrrsets != 1 || rep->an_numrrsets == 0) return 0; if(ntohs(rep->rrsets[ rep->an_numrrsets ]->rk.type) != LDNS_RR_TYPE_NS) return 0; if(((struct packed_rrset_data*)rep->rrsets[ rep->an_numrrsets ] ->entry.data)->security == sec_status_secure) return 0; /* answer section is present and secure */ for(i=0; ian_numrrsets; i++) { if(((struct packed_rrset_data*)rep->rrsets[ i ] ->entry.data)->security != sec_status_secure) return 0; } verbose(VERB_ALGO, "truncating to minimal response"); return 1; } /** * For messages that are not referrals, if the chase reply contains an * unsigned NS record in the authority section it could have been * inserted by a (BIND) forwarder that thinks the zone is insecure, and * that has an NS record without signatures in cache. Remove the NS * record since the reply does not hinge on that record (in the authority * section), but do not remove it if it removes the last record from the * answer+authority sections. * @param chase_reply: the chased reply, we have a key for this contents, * so we should have signatures for these rrsets and not having * signatures means it will be bogus. * @param orig_reply: original reply, remove NS from there as well because * we cannot mark the NS record as DNSSEC valid because it is not * validated by signatures. */ static void remove_spurious_authority(struct reply_info* chase_reply, struct reply_info* orig_reply) { size_t i, found = 0; int remove = 0; /* if no answer and only 1 auth RRset, do not remove that one */ if(chase_reply->an_numrrsets == 0 && chase_reply->ns_numrrsets == 1) return; /* search authority section for unsigned NS records */ for(i = chase_reply->an_numrrsets; i < chase_reply->an_numrrsets+chase_reply->ns_numrrsets; i++) { struct packed_rrset_data* d = (struct packed_rrset_data*) chase_reply->rrsets[i]->entry.data; if(ntohs(chase_reply->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS && d->rrsig_count == 0) { found = i; remove = 1; break; } } /* see if we found the entry */ if(!remove) return; log_rrset_key(VERB_ALGO, "Removing spurious unsigned NS record " "(likely inserted by forwarder)", chase_reply->rrsets[found]); /* find rrset in orig_reply */ for(i = orig_reply->an_numrrsets; i < orig_reply->an_numrrsets+orig_reply->ns_numrrsets; i++) { if(ntohs(orig_reply->rrsets[i]->rk.type) == LDNS_RR_TYPE_NS && query_dname_compare(orig_reply->rrsets[i]->rk.dname, chase_reply->rrsets[found]->rk.dname) == 0) { /* remove from orig_msg */ val_reply_remove_auth(orig_reply, i); break; } } /* remove rrset from chase_reply */ val_reply_remove_auth(chase_reply, found); } /** * Given a "positive" response -- a response that contains an answer to the * question, and no CNAME chain, validate this response. * * The answer and authority RRsets must already be verified as secure. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_positive_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { uint8_t* wc = NULL; size_t wl; int wc_cached = 0; int wc_NSEC_ok = 0; int nsec3s_seen = 0; size_t i; struct ub_packed_rrset_key* s; *suspend = 0; /* validate the ANSWER section - this will be the answer itself */ for(i=0; ian_numrrsets; i++) { s = chase_reply->rrsets[i]; /* Check to see if the rrset is the result of a wildcard * expansion. If so, an additional check will need to be * made in the authority section. */ if(!val_rrset_wildcard(s, &wc, &wl)) { log_nametypeclass(VERB_QUERY, "Positive response has " "inconsistent wildcard sigs:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } if(wc && !wc_cached && env->cfg->aggressive_nsec) { rrset_cache_update_wildcard(env->rrset_cache, s, wc, wl, env->alloc, *env->now); wc_cached = 1; } } /* validate the AUTHORITY section as well - this will generally be * the NS rrset (which could be missing, no problem) */ for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; /* If this is a positive wildcard response, and we have a * (just verified) NSEC record, try to use it to 1) prove * that qname doesn't exist and 2) that the correct wildcard * was used. */ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { wc_NSEC_ok = 1; } /* if not, continue looking for proof */ } /* Otherwise, if this is a positive wildcard response and * we have NSEC3 records */ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } /* If this was a positive wildcard response that we haven't already * proven, and we have NSEC3 records, try to prove it using the NSEC3 * records. */ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { enum sec_status sec = nsec3_prove_wildcard(env, ve, chase_reply->rrsets+chase_reply->an_numrrsets, chase_reply->ns_numrrsets, qchase, kkey, wc, &vq->nsec3_cache_table, nsec3_calculations); if(sec == sec_status_insecure) { verbose(VERB_ALGO, "Positive wildcard response is " "insecure"); chase_reply->security = sec_status_insecure; return; } else if(sec == sec_status_secure) { wc_NSEC_ok = 1; } else if(sec == sec_status_unchecked) { *suspend = 1; return; } } /* If after all this, we still haven't proven the positive wildcard * response, fail. */ if(wc != NULL && !wc_NSEC_ok) { verbose(VERB_QUERY, "positive response was wildcard " "expansion and did not prove original data " "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } verbose(VERB_ALGO, "Successfully validated positive response"); chase_reply->security = sec_status_secure; } /** * Validate a NOERROR/NODATA signed response -- a response that has a * NOERROR Rcode but no ANSWER section RRsets. This consists of making * certain that the authority section NSEC/NSEC3s proves that the qname * does exist and the qtype doesn't. * * The answer and authority RRsets must already be verified as secure. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_nodata_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { /* Since we are here, there must be nothing in the ANSWER section to * validate. */ /* (Note: CNAME/DNAME responses will not directly get here -- * instead, they are chased down into individual CNAME validations, * and at the end of the cname chain a POSITIVE, or CNAME_NOANSWER * validation.) */ /* validate the AUTHORITY section */ int has_valid_nsec = 0; /* If true, then the NODATA has been proven.*/ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the proven closest encloser. */ uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */ int nsec3s_seen = 0; /* nsec3s seen */ struct ub_packed_rrset_key* s; size_t i; *suspend = 0; for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; /* If we encounter an NSEC record, try to use it to prove * NODATA. * This needs to handle the ENT NODATA case. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(nsec_proves_nodata(s, qchase, &wc)) { has_valid_nsec = 1; /* sets wc-encloser if wildcard applicable */ } if(val_nsec_proves_name_error(s, qchase->qname)) { ce = nsec_closest_encloser(qchase->qname, s); } if(val_nsec_proves_insecuredelegation(s, qchase)) { verbose(VERB_ALGO, "delegation is insecure"); chase_reply->security = sec_status_insecure; return; } } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } /* check to see if we have a wildcard NODATA proof. */ /* The wildcard NODATA is 1 NSEC proving that qname does not exist * (and also proving what the closest encloser is), and 1 NSEC * showing the matching wildcard, which must be *.closest_encloser. */ if(wc && !ce) has_valid_nsec = 0; else if(wc && ce) { if(query_dname_compare(wc, ce) != 0) { has_valid_nsec = 0; } } if(!has_valid_nsec && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { enum sec_status sec = nsec3_prove_nodata(env, ve, chase_reply->rrsets+chase_reply->an_numrrsets, chase_reply->ns_numrrsets, qchase, kkey, &vq->nsec3_cache_table, nsec3_calculations); if(sec == sec_status_insecure) { verbose(VERB_ALGO, "NODATA response is insecure"); chase_reply->security = sec_status_insecure; return; } else if(sec == sec_status_secure) { has_valid_nsec = 1; } else if(sec == sec_status_unchecked) { /* check is incomplete; suspend */ *suspend = 1; return; } } if(!has_valid_nsec) { verbose(VERB_QUERY, "NODATA response failed to prove NODATA " "status with NSEC/NSEC3"); if(verbosity >= VERB_ALGO) log_dns_msg("Failed NODATA", qchase, chase_reply); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } verbose(VERB_ALGO, "successfully validated NODATA response."); chase_reply->security = sec_status_secure; } /** * Validate a NAMEERROR signed response -- a response that has a NXDOMAIN * Rcode. * This consists of making certain that the authority section NSEC proves * that the qname doesn't exist and the covering wildcard also doesn't exist.. * * The answer and authority RRsets must have already been verified as secure. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param rcode: adjusted RCODE, in case of RCODE/proof mismatch leniency. * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_nameerror_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, int* rcode, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { int has_valid_nsec = 0; int has_valid_wnsec = 0; int nsec3s_seen = 0; struct ub_packed_rrset_key* s; size_t i; uint8_t* ce; int ce_labs = 0; int prev_ce_labs = 0; *suspend = 0; for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(val_nsec_proves_name_error(s, qchase->qname)) has_valid_nsec = 1; ce = nsec_closest_encloser(qchase->qname, s); ce_labs = dname_count_labels(ce); /* Use longest closest encloser to prove wildcard. */ if(ce_labs > prev_ce_labs || (ce_labs == prev_ce_labs && has_valid_wnsec == 0)) { if(val_nsec_proves_no_wc(s, qchase->qname, qchase->qname_len)) has_valid_wnsec = 1; else has_valid_wnsec = 0; } prev_ce_labs = ce_labs; if(val_nsec_proves_insecuredelegation(s, qchase)) { verbose(VERB_ALGO, "delegation is insecure"); chase_reply->security = sec_status_insecure; return; } } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) nsec3s_seen = 1; } if((!has_valid_nsec || !has_valid_wnsec) && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { /* use NSEC3 proof, both answer and auth rrsets, in case * NSEC3s end up in the answer (due to qtype=NSEC3 or so) */ chase_reply->security = nsec3_prove_nameerror(env, ve, chase_reply->rrsets, chase_reply->an_numrrsets+ chase_reply->ns_numrrsets, qchase, kkey, &vq->nsec3_cache_table, nsec3_calculations); if(chase_reply->security == sec_status_unchecked) { *suspend = 1; return; } else if(chase_reply->security != sec_status_secure) { verbose(VERB_QUERY, "NameError response failed nsec, " "nsec3 proof was %s", sec_status_to_string( chase_reply->security)); return; } has_valid_nsec = 1; has_valid_wnsec = 1; } /* If the message fails to prove either condition, it is bogus. */ if(!has_valid_nsec) { validate_nodata_response(env, ve, qchase, chase_reply, kkey, qstate, vq, nsec3_calculations, suspend); if(*suspend) return; verbose(VERB_QUERY, "NameError response has failed to prove: " "qname does not exist"); /* Be lenient with RCODE in NSEC NameError responses */ if(chase_reply->security == sec_status_secure) { *rcode = LDNS_RCODE_NOERROR; } else { chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); } return; } if(!has_valid_wnsec) { validate_nodata_response(env, ve, qchase, chase_reply, kkey, qstate, vq, nsec3_calculations, suspend); if(*suspend) return; verbose(VERB_QUERY, "NameError response has failed to prove: " "covering wildcard does not exist"); /* Be lenient with RCODE in NSEC NameError responses */ if (chase_reply->security == sec_status_secure) { *rcode = LDNS_RCODE_NOERROR; } else { chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); } return; } /* Otherwise, we consider the message secure. */ verbose(VERB_ALGO, "successfully validated NAME ERROR response."); chase_reply->security = sec_status_secure; } /** * Given a referral response, validate rrsets and take least trusted rrset * as the current validation status. * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. * * @param chase_reply: answer to validate. */ static void validate_referral_response(struct reply_info* chase_reply) { size_t i; enum sec_status s; /* message security equals lowest rrset security */ chase_reply->security = sec_status_secure; for(i=0; irrset_count; i++) { s = ((struct packed_rrset_data*)chase_reply->rrsets[i] ->entry.data)->security; if(s < chase_reply->security) chase_reply->security = s; } verbose(VERB_ALGO, "validated part of referral response as %s", sec_status_to_string(chase_reply->security)); } /** * Given an "ANY" response -- a response that contains an answer to a * qtype==ANY question, with answers. This does no checking that all * types are present. * * NOTE: it may be possible to get parent-side delegation point records * here, which won't all be signed. Right now, this routine relies on the * upstream iterative resolver to not return these responses -- instead * treating them as referrals. * * NOTE: RFC 4035 is silent on this issue, so this may change upon * clarification. Clarification draft -05 says to not check all types are * present. * * Note that by the time this method is called, the process of finding the * trusted DNSKEY rrset that signs this response must already have been * completed. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_any_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { /* all answer and auth rrsets already verified */ /* but check if a wildcard response is given, then check NSEC/NSEC3 * for qname denial to see if wildcard is applicable */ uint8_t* wc = NULL; size_t wl; int wc_NSEC_ok = 0; int nsec3s_seen = 0; size_t i; struct ub_packed_rrset_key* s; *suspend = 0; if(qchase->qtype != LDNS_RR_TYPE_ANY) { log_err("internal error: ANY validation called for non-ANY"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } /* validate the ANSWER section - this will be the answer itself */ for(i=0; ian_numrrsets; i++) { s = chase_reply->rrsets[i]; /* Check to see if the rrset is the result of a wildcard * expansion. If so, an additional check will need to be * made in the authority section. */ if(!val_rrset_wildcard(s, &wc, &wl)) { log_nametypeclass(VERB_QUERY, "Positive ANY response" " has inconsistent wildcard sigs:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } } /* if it was a wildcard, check for NSEC/NSEC3s in both answer * and authority sections (NSEC may be moved to the ANSWER section) */ if(wc != NULL) for(i=0; ian_numrrsets+chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; /* If this is a positive wildcard response, and we have a * (just verified) NSEC record, try to use it to 1) prove * that qname doesn't exist and 2) that the correct wildcard * was used. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { wc_NSEC_ok = 1; } /* if not, continue looking for proof */ } /* Otherwise, if this is a positive wildcard response and * we have NSEC3 records */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } /* If this was a positive wildcard response that we haven't already * proven, and we have NSEC3 records, try to prove it using the NSEC3 * records. */ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { /* look both in answer and auth section for NSEC3s */ enum sec_status sec = nsec3_prove_wildcard(env, ve, chase_reply->rrsets, chase_reply->an_numrrsets+chase_reply->ns_numrrsets, qchase, kkey, wc, &vq->nsec3_cache_table, nsec3_calculations); if(sec == sec_status_insecure) { verbose(VERB_ALGO, "Positive ANY wildcard response is " "insecure"); chase_reply->security = sec_status_insecure; return; } else if(sec == sec_status_secure) { wc_NSEC_ok = 1; } else if(sec == sec_status_unchecked) { *suspend = 1; return; } } /* If after all this, we still haven't proven the positive wildcard * response, fail. */ if(wc != NULL && !wc_NSEC_ok) { verbose(VERB_QUERY, "positive ANY response was wildcard " "expansion and did not prove original data " "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } verbose(VERB_ALGO, "Successfully validated positive ANY response"); chase_reply->security = sec_status_secure; } /** * Validate CNAME response, or DNAME+CNAME. * This is just like a positive proof, except that this is about a * DNAME+CNAME. Possible wildcard proof. * Difference with positive proof is that this routine refuses * wildcarded DNAMEs. * * The answer and authority rrsets must already be verified as secure. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_cname_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { uint8_t* wc = NULL; size_t wl; int wc_NSEC_ok = 0; int nsec3s_seen = 0; size_t i; struct ub_packed_rrset_key* s; *suspend = 0; /* validate the ANSWER section - this will be the CNAME (+DNAME) */ for(i=0; ian_numrrsets; i++) { s = chase_reply->rrsets[i]; /* Check to see if the rrset is the result of a wildcard * expansion. If so, an additional check will need to be * made in the authority section. */ if(!val_rrset_wildcard(s, &wc, &wl)) { log_nametypeclass(VERB_QUERY, "Cname response has " "inconsistent wildcard sigs:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } /* Refuse wildcarded DNAMEs rfc 4597. * Do not follow a wildcarded DNAME because * its synthesized CNAME expansion is underdefined */ if(qchase->qtype != LDNS_RR_TYPE_DNAME && ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME && wc) { log_nametypeclass(VERB_QUERY, "cannot validate a " "wildcarded DNAME:", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } /* If we have found a CNAME, stop looking for one. * The iterator has placed the CNAME chain in correct * order. */ if (ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) { break; } } /* AUTHORITY section */ for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; /* If this is a positive wildcard response, and we have a * (just verified) NSEC record, try to use it to 1) prove * that qname doesn't exist and 2) that the correct wildcard * was used. */ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(val_nsec_proves_positive_wildcard(s, qchase, wc)) { wc_NSEC_ok = 1; } /* if not, continue looking for proof */ } /* Otherwise, if this is a positive wildcard response and * we have NSEC3 records */ if(wc != NULL && ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } /* If this was a positive wildcard response that we haven't already * proven, and we have NSEC3 records, try to prove it using the NSEC3 * records. */ if(wc != NULL && !wc_NSEC_ok && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { enum sec_status sec = nsec3_prove_wildcard(env, ve, chase_reply->rrsets+chase_reply->an_numrrsets, chase_reply->ns_numrrsets, qchase, kkey, wc, &vq->nsec3_cache_table, nsec3_calculations); if(sec == sec_status_insecure) { verbose(VERB_ALGO, "wildcard CNAME response is " "insecure"); chase_reply->security = sec_status_insecure; return; } else if(sec == sec_status_secure) { wc_NSEC_ok = 1; } else if(sec == sec_status_unchecked) { *suspend = 1; return; } } /* If after all this, we still haven't proven the positive wildcard * response, fail. */ if(wc != NULL && !wc_NSEC_ok) { verbose(VERB_QUERY, "CNAME response was wildcard " "expansion and did not prove original data " "did not exist"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } verbose(VERB_ALGO, "Successfully validated CNAME response"); chase_reply->security = sec_status_secure; } /** * Validate CNAME NOANSWER response, no more data after a CNAME chain. * This can be a NODATA or a NAME ERROR case, but not both at the same time. * We don't know because the rcode has been set to NOERROR by the CNAME. * * The answer and authority rrsets must already be verified as secure. * * @param env: module env for verify. * @param ve: validator env for verify. * @param qchase: query that was made. * @param chase_reply: answer to that query to validate. * @param kkey: the key entry, which is trusted, and which matches * the signer of the answer. The key entry isgood(). * @param qstate: query state for the region. * @param vq: validator state for the nsec3 cache table. * @param nsec3_calculations: current nsec3 hash calculations. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. */ static void validate_cname_noanswer_response(struct module_env* env, struct val_env* ve, struct query_info* qchase, struct reply_info* chase_reply, struct key_entry_key* kkey, struct module_qstate* qstate, struct val_qstate* vq, int* nsec3_calculations, int* suspend) { int nodata_valid_nsec = 0; /* If true, then NODATA has been proven.*/ uint8_t* ce = NULL; /* for wildcard nodata responses. This is the proven closest encloser. */ uint8_t* wc = NULL; /* for wildcard nodata responses. wildcard nsec */ int nxdomain_valid_nsec = 0; /* if true, nameerror has been proven */ int nxdomain_valid_wnsec = 0; int nsec3s_seen = 0; /* nsec3s seen */ struct ub_packed_rrset_key* s; size_t i; uint8_t* nsec_ce; /* Used to find the NSEC with the longest ce */ int ce_labs = 0; int prev_ce_labs = 0; *suspend = 0; /* the AUTHORITY section */ for(i=chase_reply->an_numrrsets; ian_numrrsets+ chase_reply->ns_numrrsets; i++) { s = chase_reply->rrsets[i]; /* If we encounter an NSEC record, try to use it to prove * NODATA. This needs to handle the ENT NODATA case. * Also try to prove NAMEERROR, and absence of a wildcard */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC) { if(nsec_proves_nodata(s, qchase, &wc)) { nodata_valid_nsec = 1; /* set wc encloser if wildcard applicable */ } if(val_nsec_proves_name_error(s, qchase->qname)) { ce = nsec_closest_encloser(qchase->qname, s); nxdomain_valid_nsec = 1; } nsec_ce = nsec_closest_encloser(qchase->qname, s); ce_labs = dname_count_labels(nsec_ce); /* Use longest closest encloser to prove wildcard. */ if(ce_labs > prev_ce_labs || (ce_labs == prev_ce_labs && nxdomain_valid_wnsec == 0)) { if(val_nsec_proves_no_wc(s, qchase->qname, qchase->qname_len)) nxdomain_valid_wnsec = 1; else nxdomain_valid_wnsec = 0; } prev_ce_labs = ce_labs; if(val_nsec_proves_insecuredelegation(s, qchase)) { verbose(VERB_ALGO, "delegation is insecure"); chase_reply->security = sec_status_insecure; return; } } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { nsec3s_seen = 1; } } /* check to see if we have a wildcard NODATA proof. */ /* The wildcard NODATA is 1 NSEC proving that qname does not exists * (and also proving what the closest encloser is), and 1 NSEC * showing the matching wildcard, which must be *.closest_encloser. */ if(wc && !ce) nodata_valid_nsec = 0; else if(wc && ce) { if(query_dname_compare(wc, ce) != 0) { nodata_valid_nsec = 0; } } if(nxdomain_valid_nsec && !nxdomain_valid_wnsec) { /* name error is missing wildcard denial proof */ nxdomain_valid_nsec = 0; } if(nodata_valid_nsec && nxdomain_valid_nsec) { verbose(VERB_QUERY, "CNAMEchain to noanswer proves that name " "exists and not exists, bogus"); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } if(!nodata_valid_nsec && !nxdomain_valid_nsec && nsec3s_seen && nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { int nodata; enum sec_status sec = nsec3_prove_nxornodata(env, ve, chase_reply->rrsets+chase_reply->an_numrrsets, chase_reply->ns_numrrsets, qchase, kkey, &nodata, &vq->nsec3_cache_table, nsec3_calculations); if(sec == sec_status_insecure) { verbose(VERB_ALGO, "CNAMEchain to noanswer response " "is insecure"); chase_reply->security = sec_status_insecure; return; } else if(sec == sec_status_secure) { if(nodata) nodata_valid_nsec = 1; else nxdomain_valid_nsec = 1; } else if(sec == sec_status_unchecked) { *suspend = 1; return; } } if(!nodata_valid_nsec && !nxdomain_valid_nsec) { verbose(VERB_QUERY, "CNAMEchain to noanswer response failed " "to prove status with NSEC/NSEC3"); if(verbosity >= VERB_ALGO) log_dns_msg("Failed CNAMEnoanswer", qchase, chase_reply); chase_reply->security = sec_status_bogus; update_reason_bogus(chase_reply, LDNS_EDE_DNSSEC_BOGUS); return; } if(nodata_valid_nsec) verbose(VERB_ALGO, "successfully validated CNAME chain to a " "NODATA response."); else verbose(VERB_ALGO, "successfully validated CNAME chain to a " "NAMEERROR response."); chase_reply->security = sec_status_secure; } /** * Process init state for validator. * Process the INIT state. First tier responses start in the INIT state. * This is where they are vetted for validation suitability, and the initial * key search is done. * * Currently, events the come through this routine will be either promoted * to FINISHED/CNAME_RESP (no validation needed), FINDKEY (next step to * validation), or will be (temporarily) retired and a new priming request * event will be generated. * * @param qstate: query state. * @param vq: validator query state. * @param ve: validator shared global environment. * @param id: module id. * @return true if the event should be processed further on return, false if * not. */ static int processInit(struct module_qstate* qstate, struct val_qstate* vq, struct val_env* ve, int id) { uint8_t* lookup_name; size_t lookup_len; struct trust_anchor* anchor; enum val_classification subtype = val_classify_response( qstate->query_flags, &qstate->qinfo, &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); if(vq->restart_count > ve->max_restart) { verbose(VERB_ALGO, "restart count exceeded"); return val_error(qstate, id); } /* correctly initialize reason_bogus */ update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_BOGUS); verbose(VERB_ALGO, "validator classification %s", val_classification_to_string(subtype)); if(subtype == VAL_CLASS_REFERRAL && vq->rrset_skip < vq->orig_msg->rep->rrset_count) { /* referral uses the rrset name as qchase, to find keys for * that rrset */ vq->qchase.qname = vq->orig_msg->rep-> rrsets[vq->rrset_skip]->rk.dname; vq->qchase.qname_len = vq->orig_msg->rep-> rrsets[vq->rrset_skip]->rk.dname_len; vq->qchase.qtype = ntohs(vq->orig_msg->rep-> rrsets[vq->rrset_skip]->rk.type); vq->qchase.qclass = ntohs(vq->orig_msg->rep-> rrsets[vq->rrset_skip]->rk.rrset_class); } lookup_name = vq->qchase.qname; lookup_len = vq->qchase.qname_len; /* for type DS look at the parent side for keys/trustanchor */ /* also for NSEC not at apex */ if(vq->qchase.qtype == LDNS_RR_TYPE_DS || (vq->qchase.qtype == LDNS_RR_TYPE_NSEC && vq->orig_msg->rep->rrset_count > vq->rrset_skip && ntohs(vq->orig_msg->rep->rrsets[vq->rrset_skip]->rk.type) == LDNS_RR_TYPE_NSEC && !(vq->orig_msg->rep->rrsets[vq->rrset_skip]-> rk.flags&PACKED_RRSET_NSEC_AT_APEX))) { dname_remove_label(&lookup_name, &lookup_len); } val_mark_indeterminate(vq->chase_reply, qstate->env->anchors, qstate->env->rrset_cache, qstate->env); vq->key_entry = NULL; vq->empty_DS_name = NULL; vq->ds_rrset = 0; anchor = anchors_lookup(qstate->env->anchors, lookup_name, lookup_len, vq->qchase.qclass); /* Determine the signer/lookup name */ val_find_signer(subtype, &vq->qchase, vq->orig_msg->rep, vq->rrset_skip, &vq->signer_name, &vq->signer_len); if(vq->signer_name != NULL && !dname_subdomain_c(lookup_name, vq->signer_name)) { log_nametypeclass(VERB_ALGO, "this signer name is not a parent " "of lookupname, omitted", vq->signer_name, 0, 0); vq->signer_name = NULL; } if(vq->signer_name == NULL) { log_nametypeclass(VERB_ALGO, "no signer, using", lookup_name, 0, 0); } else { lookup_name = vq->signer_name; lookup_len = vq->signer_len; log_nametypeclass(VERB_ALGO, "signer is", lookup_name, 0, 0); } /* for NXDOMAIN it could be signed by a parent of the trust anchor */ if(subtype == VAL_CLASS_NAMEERROR && vq->signer_name && anchor && dname_strict_subdomain_c(anchor->name, lookup_name)){ lock_basic_unlock(&anchor->lock); anchor = anchors_lookup(qstate->env->anchors, lookup_name, lookup_len, vq->qchase.qclass); if(!anchor) { /* unsigned parent denies anchor*/ verbose(VERB_QUERY, "unsigned parent zone denies" " trust anchor, indeterminate"); vq->chase_reply->security = sec_status_indeterminate; update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_INDETERMINATE); vq->state = VAL_FINISHED_STATE; return 1; } verbose(VERB_ALGO, "trust anchor NXDOMAIN by signed parent"); } else if(subtype == VAL_CLASS_POSITIVE && qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY && query_dname_compare(lookup_name, qstate->qinfo.qname) == 0) { /* is a DNSKEY so lookup a bit higher since we want to * get it from a parent or from trustanchor */ dname_remove_label(&lookup_name, &lookup_len); } if(vq->rrset_skip > 0 || subtype == VAL_CLASS_CNAME || subtype == VAL_CLASS_REFERRAL) { /* extract this part of orig_msg into chase_reply for * the eventual VALIDATE stage */ val_fill_reply(vq->chase_reply, vq->orig_msg->rep, vq->rrset_skip, lookup_name, lookup_len, vq->signer_name); if(verbosity >= VERB_ALGO) log_dns_msg("chased extract", &vq->qchase, vq->chase_reply); } vq->key_entry = key_cache_obtain(ve->kcache, lookup_name, lookup_len, vq->qchase.qclass, qstate->region, *qstate->env->now); /* there is no key and no trust anchor */ if(vq->key_entry == NULL && anchor == NULL) { /*response isn't under a trust anchor, so we cannot validate.*/ vq->chase_reply->security = sec_status_indeterminate; update_reason_bogus(vq->chase_reply, LDNS_EDE_DNSSEC_INDETERMINATE); /* go to finished state to cache this result */ vq->state = VAL_FINISHED_STATE; return 1; } /* if not key, or if keyentry is *above* the trustanchor, i.e. * the keyentry is based on another (higher) trustanchor */ else if(vq->key_entry == NULL || (anchor && dname_strict_subdomain_c(anchor->name, vq->key_entry->name))) { /* trust anchor is an 'unsigned' trust anchor */ if(anchor && anchor->numDS == 0 && anchor->numDNSKEY == 0) { vq->chase_reply->security = sec_status_insecure; val_mark_insecure(vq->chase_reply, anchor->name, qstate->env->rrset_cache, qstate->env); lock_basic_unlock(&anchor->lock); /* go to finished state to cache this result */ vq->state = VAL_FINISHED_STATE; return 1; } /* fire off a trust anchor priming query. */ verbose(VERB_DETAIL, "prime trust anchor"); if(!prime_trust_anchor(qstate, vq, id, anchor)) { lock_basic_unlock(&anchor->lock); return val_error(qstate, id); } lock_basic_unlock(&anchor->lock); /* and otherwise, don't continue processing this event. * (it will be reactivated when the priming query returns). */ vq->state = VAL_FINDKEY_STATE; return 0; } if(anchor) { lock_basic_unlock(&anchor->lock); } if(key_entry_isnull(vq->key_entry)) { /* response is under a null key, so we cannot validate * However, we do set the status to INSECURE, since it is * essentially proven insecure. */ vq->chase_reply->security = sec_status_insecure; val_mark_insecure(vq->chase_reply, vq->key_entry->name, qstate->env->rrset_cache, qstate->env); /* go to finished state to cache this result */ vq->state = VAL_FINISHED_STATE; return 1; } else if(key_entry_isbad(vq->key_entry)) { /* Bad keys should have the relevant EDE code and text */ sldns_ede_code ede = key_entry_get_reason_bogus(vq->key_entry); /* key is bad, chain is bad, reply is bogus */ errinf_dname(qstate, "key for validation", vq->key_entry->name); errinf_ede(qstate, "is marked as invalid", ede); errinf(qstate, "because of a previous"); errinf(qstate, key_entry_get_reason(vq->key_entry)); /* no retries, stop bothering the authority until timeout */ vq->restart_count = ve->max_restart; vq->chase_reply->security = sec_status_bogus; update_reason_bogus(vq->chase_reply, ede); vq->state = VAL_FINISHED_STATE; return 1; } /* otherwise, we have our "closest" cached key -- continue * processing in the next state. */ vq->state = VAL_FINDKEY_STATE; return 1; } /** * Process the FINDKEY state. Generally this just calculates the next name * to query and either issues a DS or a DNSKEY query. It will check to see * if the correct key has already been reached, in which case it will * advance the event to the next state. * * @param qstate: query state. * @param vq: validator query state. * @param id: module id. * @return true if the event should be processed further on return, false if * not. */ static int processFindKey(struct module_qstate* qstate, struct val_qstate* vq, int id) { uint8_t* target_key_name, *current_key_name; size_t target_key_len; int strip_lab; struct module_qstate* newq = NULL; log_query_info(VERB_ALGO, "validator: FindKey", &vq->qchase); /* We know that state.key_entry is not 0 or bad key -- if it were, * then previous processing should have directed this event to * a different state. * It could be an isnull key, which signals the DNSKEY failed * with retry and has to be looked up again. */ log_assert(vq->key_entry && !key_entry_isbad(vq->key_entry)); if(key_entry_isnull(vq->key_entry)) { if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, vq->qchase.qclass, BIT_CD, &newq, 0)) { verbose(VERB_ALGO, "error generating DNSKEY request"); return val_error(qstate, id); } return 0; } target_key_name = vq->signer_name; target_key_len = vq->signer_len; if(!target_key_name) { target_key_name = vq->qchase.qname; target_key_len = vq->qchase.qname_len; } current_key_name = vq->key_entry->name; /* If our current key entry matches our target, then we are done. */ if(query_dname_compare(target_key_name, current_key_name) == 0) { vq->state = VAL_VALIDATE_STATE; return 1; } if(vq->empty_DS_name) { /* if the last empty nonterminal/emptyDS name we detected is * below the current key, use that name to make progress * along the chain of trust */ if(query_dname_compare(target_key_name, vq->empty_DS_name) == 0) { /* do not query for empty_DS_name again */ verbose(VERB_ALGO, "Cannot retrieve DS for signature"); errinf_ede(qstate, "no signatures", LDNS_EDE_RRSIGS_MISSING); errinf_origin(qstate, qstate->reply_origin); vq->chase_reply->security = sec_status_bogus; update_reason_bogus(vq->chase_reply, LDNS_EDE_RRSIGS_MISSING); vq->state = VAL_FINISHED_STATE; return 1; } current_key_name = vq->empty_DS_name; } log_nametypeclass(VERB_ALGO, "current keyname", current_key_name, LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); log_nametypeclass(VERB_ALGO, "target keyname", target_key_name, LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); /* assert we are walking down the DNS tree */ if(!dname_subdomain_c(target_key_name, current_key_name)) { verbose(VERB_ALGO, "bad signer name"); vq->chase_reply->security = sec_status_bogus; vq->state = VAL_FINISHED_STATE; return 1; } /* so this value is >= -1 */ strip_lab = dname_count_labels(target_key_name) - dname_count_labels(current_key_name) - 1; log_assert(strip_lab >= -1); verbose(VERB_ALGO, "striplab %d", strip_lab); if(strip_lab > 0) { dname_remove_labels(&target_key_name, &target_key_len, strip_lab); } log_nametypeclass(VERB_ALGO, "next keyname", target_key_name, LDNS_RR_TYPE_DNSKEY, LDNS_RR_CLASS_IN); /* The next step is either to query for the next DS, or to query * for the next DNSKEY. */ if(vq->ds_rrset) log_nametypeclass(VERB_ALGO, "DS RRset", vq->ds_rrset->rk.dname, LDNS_RR_TYPE_DS, LDNS_RR_CLASS_IN); else verbose(VERB_ALGO, "No DS RRset"); if(vq->ds_rrset && query_dname_compare(vq->ds_rrset->rk.dname, vq->key_entry->name) != 0) { if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, vq->qchase.qclass, BIT_CD, &newq, 0)) { verbose(VERB_ALGO, "error generating DNSKEY request"); return val_error(qstate, id); } return 0; } if(!vq->ds_rrset || query_dname_compare(vq->ds_rrset->rk.dname, target_key_name) != 0) { /* check if there is a cache entry : pick up an NSEC if * there is no DS, check if that NSEC has DS-bit unset, and * thus can disprove the secure delegation we seek. * We can then use that NSEC even in the absence of a SOA * record that would be required by the iterator to supply * a completely protocol-correct response. * Uses negative cache for NSEC3 lookup of DS responses. */ /* only if cache not blacklisted, of course */ struct dns_msg* msg; int suspend; if(vq->sub_ds_msg) { /* We have a suspended DS reply from a sub-query; * process it. */ verbose(VERB_ALGO, "Process suspended sub DS response"); msg = vq->sub_ds_msg; process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, msg, &msg->qinfo, NULL, &suspend, NULL); if(suspend) { /* we'll come back here later to continue */ if(!validate_suspend_setup_timer(qstate, vq, id, VAL_FINDKEY_STATE)) return val_error(qstate, id); return 0; } vq->sub_ds_msg = NULL; return 1; /* continue processing ds-response results */ } else if(!qstate->blacklist && !vq->chain_blacklist && (msg=val_find_DS(qstate->env, target_key_name, target_key_len, vq->qchase.qclass, qstate->region, vq->key_entry->name)) ) { verbose(VERB_ALGO, "Process cached DS response"); process_ds_response(qstate, vq, id, LDNS_RCODE_NOERROR, msg, &msg->qinfo, NULL, &suspend, NULL); if(suspend) { /* we'll come back here later to continue */ if(!validate_suspend_setup_timer(qstate, vq, id, VAL_FINDKEY_STATE)) return val_error(qstate, id); return 0; } return 1; /* continue processing ds-response results */ } if(!generate_request(qstate, id, target_key_name, target_key_len, LDNS_RR_TYPE_DS, vq->qchase.qclass, BIT_CD, &newq, 0)) { verbose(VERB_ALGO, "error generating DS request"); return val_error(qstate, id); } return 0; } /* Otherwise, it is time to query for the DNSKEY */ if(!generate_request(qstate, id, vq->ds_rrset->rk.dname, vq->ds_rrset->rk.dname_len, LDNS_RR_TYPE_DNSKEY, vq->qchase.qclass, BIT_CD, &newq, 0)) { verbose(VERB_ALGO, "error generating DNSKEY request"); return val_error(qstate, id); } return 0; } /** * Process the VALIDATE stage, the init and findkey stages are finished, * and the right keys are available to validate the response. * Or, there are no keys available, in order to invalidate the response. * * After validation, the status is recorded in the message and rrsets, * and finished state is started. * * @param qstate: query state. * @param vq: validator query state. * @param ve: validator shared global environment. * @param id: module id. * @return true if the event should be processed further on return, false if * not. */ static int processValidate(struct module_qstate* qstate, struct val_qstate* vq, struct val_env* ve, int id) { enum val_classification subtype; int rcode, suspend, nsec3_calculations = 0; if(!vq->key_entry) { verbose(VERB_ALGO, "validate: no key entry, failed"); return val_error(qstate, id); } /* This is the default next state. */ vq->state = VAL_FINISHED_STATE; /* Unsigned responses must be underneath a "null" key entry.*/ if(key_entry_isnull(vq->key_entry)) { verbose(VERB_DETAIL, "Verified that %sresponse is INSECURE", vq->signer_name?"":"unsigned "); vq->chase_reply->security = sec_status_insecure; val_mark_insecure(vq->chase_reply, vq->key_entry->name, qstate->env->rrset_cache, qstate->env); key_cache_insert(ve->kcache, vq->key_entry, qstate->env->cfg->val_log_level >= 2); return 1; } if(key_entry_isbad(vq->key_entry)) { log_nametypeclass(VERB_DETAIL, "Could not establish a chain " "of trust to keys for", vq->key_entry->name, LDNS_RR_TYPE_DNSKEY, vq->key_entry->key_class); vq->chase_reply->security = sec_status_bogus; update_reason_bogus(vq->chase_reply, key_entry_get_reason_bogus(vq->key_entry)); errinf_ede(qstate, "while building chain of trust", key_entry_get_reason_bogus(vq->key_entry)); if(vq->restart_count >= ve->max_restart) key_cache_insert(ve->kcache, vq->key_entry, qstate->env->cfg->val_log_level >= 2); return 1; } /* signerName being null is the indicator that this response was * unsigned */ if(vq->signer_name == NULL) { log_query_info(VERB_ALGO, "processValidate: state has no " "signer name", &vq->qchase); verbose(VERB_DETAIL, "Could not establish validation of " "INSECURE status of unsigned response."); errinf_ede(qstate, "no signatures", LDNS_EDE_RRSIGS_MISSING); errinf_origin(qstate, qstate->reply_origin); vq->chase_reply->security = sec_status_bogus; update_reason_bogus(vq->chase_reply, LDNS_EDE_RRSIGS_MISSING); return 1; } subtype = val_classify_response(qstate->query_flags, &qstate->qinfo, &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); if(subtype != VAL_CLASS_REFERRAL) remove_spurious_authority(vq->chase_reply, vq->orig_msg->rep); /* check signatures in the message; * answer and authority must be valid, additional is only checked. */ if(!validate_msg_signatures(qstate, vq, qstate->env, ve, vq->chase_reply, vq->key_entry, &suspend)) { if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } /* workaround bad recursor out there that truncates (even * with EDNS4k) to 512 by removing RRSIG from auth section * for positive replies*/ if((subtype == VAL_CLASS_POSITIVE || subtype == VAL_CLASS_ANY || subtype == VAL_CLASS_CNAME) && detect_wrongly_truncated(vq->orig_msg->rep)) { /* truncate the message some more */ vq->orig_msg->rep->ns_numrrsets = 0; vq->orig_msg->rep->ar_numrrsets = 0; vq->orig_msg->rep->rrset_count = vq->orig_msg->rep->an_numrrsets; vq->chase_reply->ns_numrrsets = 0; vq->chase_reply->ar_numrrsets = 0; vq->chase_reply->rrset_count = vq->chase_reply->an_numrrsets; qstate->errinf = NULL; } else { verbose(VERB_DETAIL, "Validate: message contains " "bad rrsets"); return 1; } } switch(subtype) { case VAL_CLASS_POSITIVE: verbose(VERB_ALGO, "Validating a positive response"); validate_positive_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(positive): %s", sec_status_to_string( vq->chase_reply->security)); break; case VAL_CLASS_NODATA: verbose(VERB_ALGO, "Validating a nodata response"); validate_nodata_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(nodata): %s", sec_status_to_string( vq->chase_reply->security)); break; case VAL_CLASS_NAMEERROR: rcode = (int)FLAGS_GET_RCODE(vq->orig_msg->rep->flags); verbose(VERB_ALGO, "Validating a nxdomain response"); validate_nameerror_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, &rcode, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(nxdomain): %s", sec_status_to_string( vq->chase_reply->security)); FLAGS_SET_RCODE(vq->orig_msg->rep->flags, rcode); FLAGS_SET_RCODE(vq->chase_reply->flags, rcode); break; case VAL_CLASS_CNAME: verbose(VERB_ALGO, "Validating a cname response"); validate_cname_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(cname): %s", sec_status_to_string( vq->chase_reply->security)); break; case VAL_CLASS_CNAMENOANSWER: verbose(VERB_ALGO, "Validating a cname noanswer " "response"); validate_cname_noanswer_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(cname_noanswer): %s", sec_status_to_string( vq->chase_reply->security)); break; case VAL_CLASS_REFERRAL: verbose(VERB_ALGO, "Validating a referral response"); validate_referral_response(vq->chase_reply); verbose(VERB_DETAIL, "validate(referral): %s", sec_status_to_string( vq->chase_reply->security)); break; case VAL_CLASS_ANY: verbose(VERB_ALGO, "Validating a positive ANY " "response"); validate_any_response(qstate->env, ve, &vq->qchase, vq->chase_reply, vq->key_entry, qstate, vq, &nsec3_calculations, &suspend); if(suspend) { if(!validate_suspend_setup_timer(qstate, vq, id, VAL_VALIDATE_STATE)) return val_error(qstate, id); return 0; } verbose(VERB_DETAIL, "validate(positive_any): %s", sec_status_to_string( vq->chase_reply->security)); break; default: log_err("validate: unhandled response subtype: %d", subtype); } if(vq->chase_reply->security == sec_status_bogus) { if(subtype == VAL_CLASS_POSITIVE) errinf(qstate, "wildcard"); else errinf(qstate, val_classification_to_string(subtype)); errinf(qstate, "proof failed"); errinf_origin(qstate, qstate->reply_origin); } return 1; } /** * The Finished state. The validation status (good or bad) has been determined. * * @param qstate: query state. * @param vq: validator query state. * @param ve: validator shared global environment. * @param id: module id. * @return true if the event should be processed further on return, false if * not. */ static int processFinished(struct module_qstate* qstate, struct val_qstate* vq, struct val_env* ve, int id) { enum val_classification subtype = val_classify_response( qstate->query_flags, &qstate->qinfo, &vq->qchase, vq->orig_msg->rep, vq->rrset_skip); /* store overall validation result in orig_msg */ if(vq->rrset_skip == 0) { vq->orig_msg->rep->security = vq->chase_reply->security; update_reason_bogus(vq->orig_msg->rep, vq->chase_reply->reason_bogus); } else if(subtype != VAL_CLASS_REFERRAL || vq->rrset_skip < vq->orig_msg->rep->an_numrrsets + vq->orig_msg->rep->ns_numrrsets) { /* ignore sec status of additional section if a referral * type message skips there and * use the lowest security status as end result. */ if(vq->chase_reply->security < vq->orig_msg->rep->security) { vq->orig_msg->rep->security = vq->chase_reply->security; update_reason_bogus(vq->orig_msg->rep, vq->chase_reply->reason_bogus); } } if(subtype == VAL_CLASS_REFERRAL) { /* for a referral, move to next unchecked rrset and check it*/ vq->rrset_skip = val_next_unchecked(vq->orig_msg->rep, vq->rrset_skip); if(vq->rrset_skip < vq->orig_msg->rep->rrset_count) { /* and restart for this rrset */ verbose(VERB_ALGO, "validator: go to next rrset"); vq->chase_reply->security = sec_status_unchecked; vq->state = VAL_INIT_STATE; return 1; } /* referral chase is done */ } if(vq->chase_reply->security != sec_status_bogus && subtype == VAL_CLASS_CNAME) { /* chase the CNAME; process next part of the message */ if(!val_chase_cname(&vq->qchase, vq->orig_msg->rep, &vq->rrset_skip)) { verbose(VERB_ALGO, "validator: failed to chase CNAME"); vq->orig_msg->rep->security = sec_status_bogus; update_reason_bogus(vq->orig_msg->rep, LDNS_EDE_DNSSEC_BOGUS); } else { /* restart process for new qchase at rrset_skip */ log_query_info(VERB_ALGO, "validator: chased to", &vq->qchase); vq->chase_reply->security = sec_status_unchecked; vq->state = VAL_INIT_STATE; return 1; } } if(vq->orig_msg->rep->security == sec_status_secure) { /* If the message is secure, check that all rrsets are * secure (i.e. some inserted RRset for CNAME chain with * a different signer name). And drop additional rrsets * that are not secure (if clean-additional option is set) */ /* this may cause the msg to be marked bogus */ val_check_nonsecure(qstate->env, vq->orig_msg->rep); if(vq->orig_msg->rep->security == sec_status_secure) { log_query_info(VERB_DETAIL, "validation success", &qstate->qinfo); if(!qstate->no_cache_store) { val_neg_addreply(qstate->env->neg_cache, vq->orig_msg->rep); } } } /* if the result is bogus - set message ttl to bogus ttl to avoid * endless bogus revalidation */ if(vq->orig_msg->rep->security == sec_status_bogus) { struct msgreply_entry* e; /* see if we can try again to fetch data */ if(vq->restart_count < ve->max_restart) { verbose(VERB_ALGO, "validation failed, " "blacklist and retry to fetch data"); val_blacklist(&qstate->blacklist, qstate->region, qstate->reply_origin, 0); qstate->reply_origin = NULL; qstate->errinf = NULL; val_restart(vq); verbose(VERB_ALGO, "pass back to next module"); qstate->ext_state[id] = module_restart_next; return 0; } if(qstate->env->cfg->serve_expired && (e=msg_cache_lookup(qstate->env, qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, qstate->qinfo.qclass, qstate->query_flags, 0 /*now; allow expired*/, 1 /*wr; we may update the data*/))) { struct reply_info* rep = (struct reply_info*)e->entry.data; if(rep && rep->security > sec_status_bogus && (!qstate->env->cfg->serve_expired_ttl || qstate->env->cfg->serve_expired_ttl_reset || *qstate->env->now <= rep->serve_expired_ttl)) { verbose(VERB_ALGO, "validation failed but " "previously cached valid response " "exists; set serve-expired-norec-ttl " "for response in cache"); rep->serve_expired_norec_ttl = NORR_TTL + *qstate->env->now; if(qstate->env->cfg->serve_expired_ttl_reset && *qstate->env->now + qstate->env->cfg->serve_expired_ttl > rep->serve_expired_ttl) { verbose(VERB_ALGO, "reset serve-expired-ttl for " "valid response in cache"); rep->serve_expired_ttl = *qstate->env->now + qstate->env->cfg->serve_expired_ttl; } /* Return an error response. * If serve-expired-client-timeout is enabled, * the client-timeout logic will try to find an * (expired) answer in the cache as last * resort. If it is not enabled, expired * answers are already used before the mesh * activation. */ qstate->return_rcode = LDNS_RCODE_SERVFAIL; qstate->return_msg = NULL; qstate->ext_state[id] = module_finished; lock_rw_unlock(&e->entry.lock); return 0; } lock_rw_unlock(&e->entry.lock); } vq->orig_msg->rep->ttl = ve->bogus_ttl; vq->orig_msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(vq->orig_msg->rep->ttl); vq->orig_msg->rep->serve_expired_ttl = vq->orig_msg->rep->ttl + qstate->env->cfg->serve_expired_ttl; if((qstate->env->cfg->val_log_level >= 1 || qstate->env->cfg->log_servfail) && !qstate->env->cfg->val_log_squelch) { if(qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) log_query_info(NO_VERBOSE, "validation failure", &qstate->qinfo); else { char* err_str = errinf_to_str_bogus(qstate, qstate->region); if(err_str) { log_info("%s", err_str); vq->orig_msg->rep->reason_bogus_str = err_str; } } } /* * If set, the validator will not make messages bogus, instead * indeterminate is issued, so that no clients receive SERVFAIL. * This allows an operator to run validation 'shadow' without * hurting responses to clients. */ /* If we are in permissive mode, bogus gets indeterminate */ if(qstate->env->cfg->val_permissive_mode) vq->orig_msg->rep->security = sec_status_indeterminate; } if(vq->orig_msg->rep->security == sec_status_secure && qstate->env->cfg->root_key_sentinel && (qstate->qinfo.qtype == LDNS_RR_TYPE_A || qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA)) { char* keytag_start; uint16_t keytag; if(*qstate->qinfo.qname == strlen(SENTINEL_IS) + SENTINEL_KEYTAG_LEN && dname_lab_startswith(qstate->qinfo.qname, SENTINEL_IS, &keytag_start)) { if(sentinel_get_keytag(keytag_start, &keytag) && !anchor_has_keytag(qstate->env->anchors, (uint8_t*)"", 1, 0, vq->qchase.qclass, keytag)) { vq->orig_msg->rep->security = sec_status_secure_sentinel_fail; } } else if(*qstate->qinfo.qname == strlen(SENTINEL_NOT) + SENTINEL_KEYTAG_LEN && dname_lab_startswith(qstate->qinfo.qname, SENTINEL_NOT, &keytag_start)) { if(sentinel_get_keytag(keytag_start, &keytag) && anchor_has_keytag(qstate->env->anchors, (uint8_t*)"", 1, 0, vq->qchase.qclass, keytag)) { vq->orig_msg->rep->security = sec_status_secure_sentinel_fail; } } } /* Update rep->reason_bogus as it is the one being cached */ update_reason_bogus(vq->orig_msg->rep, errinf_to_reason_bogus(qstate)); if(vq->orig_msg->rep->security != sec_status_bogus && vq->orig_msg->rep->security != sec_status_secure_sentinel_fail && vq->orig_msg->rep->reason_bogus == LDNS_EDE_DNSSEC_BOGUS) { /* Not interested in any DNSSEC EDE here, validator by default * uses LDNS_EDE_DNSSEC_BOGUS; * TODO revisit default value for the module */ vq->orig_msg->rep->reason_bogus = LDNS_EDE_NONE; } /* store results in cache */ if((qstate->query_flags&BIT_RD)) { /* if secure, this will override cache anyway, no need * to check if from parentNS */ if(!qstate->no_cache_store) { if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, vq->orig_msg->rep, 0, qstate->prefetch_leeway, 0, qstate->region, qstate->query_flags, qstate->qstarttime, qstate->is_valrec)) { log_err("out of memory caching validator results"); } } } else { /* for a referral, store the verified RRsets */ /* and this does not get prefetched, so no leeway */ if(!dns_cache_store(qstate->env, &vq->orig_msg->qinfo, vq->orig_msg->rep, 1, 0, 0, qstate->region, qstate->query_flags, qstate->qstarttime, qstate->is_valrec)) { log_err("out of memory caching validator results"); } } qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = vq->orig_msg; qstate->ext_state[id] = module_finished; return 0; } /** * Handle validator state. * If a method returns true, the next state is started. If false, then * processing will stop. * @param qstate: query state. * @param vq: validator query state. * @param ve: validator shared global environment. * @param id: module id. */ static void val_handle(struct module_qstate* qstate, struct val_qstate* vq, struct val_env* ve, int id) { int cont = 1; while(cont) { verbose(VERB_ALGO, "val handle processing q with state %s", val_state_to_string(vq->state)); switch(vq->state) { case VAL_INIT_STATE: cont = processInit(qstate, vq, ve, id); break; case VAL_FINDKEY_STATE: cont = processFindKey(qstate, vq, id); break; case VAL_VALIDATE_STATE: cont = processValidate(qstate, vq, ve, id); break; case VAL_FINISHED_STATE: cont = processFinished(qstate, vq, ve, id); break; default: log_warn("validator: invalid state %d", vq->state); cont = 0; break; } } } void val_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct val_qstate* vq = (struct val_qstate*)qstate->minfo[id]; verbose(VERB_QUERY, "validator[module %d] operate: extstate:%s " "event:%s", id, strextstate(qstate->ext_state[id]), strmodulevent(event)); log_query_info(VERB_QUERY, "validator operate: query", &qstate->qinfo); if(vq && qstate->qinfo.qname != vq->qchase.qname) log_query_info(VERB_QUERY, "validator operate: chased to", &vq->qchase); (void)outbound; if(event == module_event_new || (event == module_event_pass && vq == NULL)) { /* pass request to next module, to get it */ verbose(VERB_ALGO, "validator: pass to next module"); qstate->ext_state[id] = module_wait_module; return; } if(event == module_event_moddone) { /* check if validation is needed */ verbose(VERB_ALGO, "validator: nextmodule returned"); if(!needs_validation(qstate, qstate->return_rcode, qstate->return_msg)) { /* no need to validate this */ /* For valrec responses, leave at sec_status_unchecked, * no security status has been requested for it. */ if(qstate->return_msg && !qstate->is_valrec) qstate->return_msg->rep->security = sec_status_indeterminate; qstate->ext_state[id] = module_finished; return; } if(already_validated(qstate->return_msg)) { qstate->ext_state[id] = module_finished; return; } if(qstate->rpz_applied) { verbose(VERB_ALGO, "rpz applied, mark it as insecure"); if(qstate->return_msg) qstate->return_msg->rep->security = sec_status_insecure; qstate->ext_state[id] = module_finished; return; } /* qclass ANY should have validation result from spawned * queries. If we get here, it is bogus or an internal error */ if(qstate->qinfo.qclass == LDNS_RR_CLASS_ANY) { verbose(VERB_ALGO, "cannot validate classANY: bogus"); if(qstate->return_msg) { qstate->return_msg->rep->security = sec_status_bogus; update_reason_bogus(qstate->return_msg->rep, LDNS_EDE_DNSSEC_BOGUS); } qstate->ext_state[id] = module_finished; return; } /* create state to start validation */ qstate->ext_state[id] = module_error; /* override this */ if(!vq) { vq = val_new(qstate, id); if(!vq) { log_err("validator: malloc failure"); qstate->ext_state[id] = module_error; return; } } else if(!vq->orig_msg) { if(!val_new_getmsg(qstate, vq)) { log_err("validator: malloc failure"); qstate->ext_state[id] = module_error; return; } } val_handle(qstate, vq, ve, id); return; } if(event == module_event_pass) { qstate->ext_state[id] = module_error; /* override this */ /* continue processing, since val_env exists */ val_handle(qstate, vq, ve, id); return; } log_err("validator: bad event %s", strmodulevent(event)); qstate->ext_state[id] = module_error; return; } /** * Evaluate the response to a priming request. * * @param dnskey_rrset: DNSKEY rrset (can be NULL if none) in prime reply. * (this rrset is allocated in the wrong region, not the qstate). * @param ta: trust anchor. * @param qstate: qstate that needs key. * @param id: module id. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. * @return new key entry or NULL on allocation failure. * The key entry will either contain a validated DNSKEY rrset, or * represent a Null key (query failed, but validation did not), or a * Bad key (validation failed). */ static struct key_entry_key* primeResponseToKE(struct ub_packed_rrset_key* dnskey_rrset, struct trust_anchor* ta, struct module_qstate* qstate, int id, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct key_entry_key* kkey = NULL; enum sec_status sec = sec_status_unchecked; char reasonbuf[256]; char* reason = NULL; sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; int downprot = qstate->env->cfg->harden_algo_downgrade; if(!dnskey_rrset) { char* err = errinf_to_str_misc(sub_qstate); char rstr[1024]; log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- " "could not fetch DNSKEY rrset", ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); reason_bogus = LDNS_EDE_DNSKEY_MISSING; if(!err) { snprintf(rstr, sizeof(rstr), "no DNSKEY rrset"); } else { snprintf(rstr, sizeof(rstr), "no DNSKEY rrset " "[%s]", err); } if(qstate->env->cfg->harden_dnssec_stripped) { errinf_ede(qstate, rstr, reason_bogus); kkey = key_entry_create_bad(qstate->region, ta->name, ta->namelen, ta->dclass, BOGUS_KEY_TTL, reason_bogus, rstr, *qstate->env->now); } else kkey = key_entry_create_null(qstate->region, ta->name, ta->namelen, ta->dclass, NULL_KEY_TTL, reason_bogus, rstr, *qstate->env->now); if(!kkey) { log_err("out of memory: allocate fail prime key"); return NULL; } return kkey; } /* attempt to verify with trust anchor DS and DNSKEY */ kkey = val_verify_new_DNSKEYs_with_ta(qstate->region, qstate->env, ve, dnskey_rrset, ta->ds_rrset, ta->dnskey_rrset, downprot, &reason, &reason_bogus, qstate, reasonbuf, sizeof(reasonbuf)); if(!kkey) { log_err("out of memory: verifying prime TA"); return NULL; } if(key_entry_isgood(kkey)) sec = sec_status_secure; else sec = sec_status_bogus; verbose(VERB_DETAIL, "validate keys with anchor(DS): %s", sec_status_to_string(sec)); if(sec != sec_status_secure) { log_nametypeclass(VERB_OPS, "failed to prime trust anchor -- " "DNSKEY rrset is not secure", ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); /* NOTE: in this case, we should probably reject the trust * anchor for longer, perhaps forever. */ if(qstate->env->cfg->harden_dnssec_stripped) { errinf_ede(qstate, reason, reason_bogus); kkey = key_entry_create_bad(qstate->region, ta->name, ta->namelen, ta->dclass, BOGUS_KEY_TTL, reason_bogus, reason, *qstate->env->now); } else kkey = key_entry_create_null(qstate->region, ta->name, ta->namelen, ta->dclass, NULL_KEY_TTL, reason_bogus, reason, *qstate->env->now); if(!kkey) { log_err("out of memory: allocate null prime key"); return NULL; } return kkey; } log_nametypeclass(VERB_DETAIL, "Successfully primed trust anchor", ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); return kkey; } /** * In inform supers, with the resulting message and rcode and the current * keyset in the super state, validate the DS response, returning a KeyEntry. * * @param qstate: query state that is validating and asked for a DS. * @param vq: validator query state * @param id: module id. * @param rcode: rcode result value. * @param msg: result message (if rcode is OK). * @param qinfo: from the sub query state, query info. * @param ke: the key entry to return. It returns * is_bad if the DS response fails to validate, is_null if the * DS response indicated an end to secure space, is_good if the DS * validated. It returns ke=NULL if the DS response indicated that the * request wasn't a delegation point. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. * Can be NULL. * @return * 0 on success, * 1 on servfail error (malloc failure), * 2 on NSEC3 suspend. */ static int ds_response_to_ke(struct module_qstate* qstate, struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, struct key_entry_key** ke, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; char reasonbuf[256]; char* reason = NULL; sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; enum val_classification subtype; int verified; if(rcode != LDNS_RCODE_NOERROR) { char rc[16]; rc[0]=0; (void)sldns_wire2str_rcode_buf(rcode, rc, sizeof(rc)); /* errors here pretty much break validation */ verbose(VERB_DETAIL, "DS response was error, thus bogus"); errinf(qstate, rc); reason = "no DS"; if(sub_qstate) { char* err = errinf_to_str_misc(sub_qstate); if(err) { char buf[1024]; snprintf(buf, sizeof(buf), "[%s]", err); errinf(qstate, buf); } } reason_bogus = LDNS_EDE_NETWORK_ERROR; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } subtype = val_classify_response(BIT_RD, qinfo, qinfo, msg->rep, 0); if(subtype == VAL_CLASS_POSITIVE) { struct ub_packed_rrset_key* ds; enum sec_status sec; ds = reply_find_answer_rrset(qinfo, msg->rep); /* If there was no DS rrset, then we have misclassified * this message. */ if(!ds) { log_warn("internal error: POSITIVE DS response was " "missing DS."); reason = "no DS record"; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } /* Verify only returns BOGUS or SECURE. If the rrset is * bogus, then we are done. */ sec = val_verify_rrset_entry(qstate->env, ve, ds, vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, sizeof(reasonbuf)); if(sec != sec_status_secure) { verbose(VERB_DETAIL, "DS rrset in DS response did " "not verify"); errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } /* If the DS rrset validates, we still have to make sure * that they are usable. */ if(!val_dsset_isusable(ds)) { /* If they aren't usable, then we treat it like * there was no DS. */ *ke = key_entry_create_null(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, ub_packed_rrset_ttl(ds), LDNS_EDE_UNSUPPORTED_DS_DIGEST, NULL, *qstate->env->now); return (*ke) == NULL; } /* Otherwise, we return the positive response. */ log_query_info(VERB_DETAIL, "validated DS", qinfo); *ke = key_entry_create_rrset(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, ds, NULL, LDNS_EDE_NONE, NULL, *qstate->env->now); return (*ke) == NULL; } else if(subtype == VAL_CLASS_NODATA || subtype == VAL_CLASS_NAMEERROR) { /* NODATA means that the qname exists, but that there was * no DS. This is a pretty normal case. */ time_t proof_ttl = 0; enum sec_status sec; /* make sure there are NSECs or NSEC3s with signatures */ if(!val_has_signed_nsecs(msg->rep, &reason)) { verbose(VERB_ALGO, "no NSECs: %s", reason); reason_bogus = LDNS_EDE_NSEC_MISSING; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } /* For subtype Name Error. * attempt ANS 2.8.1.0 compatibility where it sets rcode * to nxdomain, but really this is an Nodata/Noerror response. * Find and prove the empty nonterminal in that case */ /* Try to prove absence of the DS with NSEC */ sec = val_nsec_prove_nodata_dsreply( qstate->env, ve, qinfo, msg->rep, vq->key_entry, &proof_ttl, &reason, &reason_bogus, qstate, reasonbuf, sizeof(reasonbuf)); switch(sec) { case sec_status_secure: verbose(VERB_DETAIL, "NSEC RRset for the " "referral proved no DS."); *ke = key_entry_create_null(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, proof_ttl, LDNS_EDE_NONE, NULL, *qstate->env->now); return (*ke) == NULL; case sec_status_insecure: verbose(VERB_DETAIL, "NSEC RRset for the " "referral proved not a delegation point"); *ke = NULL; return 0; case sec_status_bogus: verbose(VERB_DETAIL, "NSEC RRset for the " "referral did not prove no DS."); errinf(qstate, reason); goto return_bogus; case sec_status_unchecked: default: /* NSEC proof did not work, try next */ break; } if(!nsec3_cache_table_init(&vq->nsec3_cache_table, qstate->region)) { log_err("malloc failure in ds_response_to_ke for " "NSEC3 cache"); reason = "malloc failure"; errinf_ede(qstate, reason, 0); goto return_bogus; } sec = nsec3_prove_nods(qstate->env, ve, msg->rep->rrsets + msg->rep->an_numrrsets, msg->rep->ns_numrrsets, qinfo, vq->key_entry, &reason, &reason_bogus, qstate, &vq->nsec3_cache_table, reasonbuf, sizeof(reasonbuf)); switch(sec) { case sec_status_insecure: /* case insecure also continues to unsigned * space. If nsec3-iter-count too high or * optout, then treat below as unsigned */ case sec_status_secure: verbose(VERB_DETAIL, "NSEC3s for the " "referral proved no DS."); *ke = key_entry_create_null(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, proof_ttl, LDNS_EDE_NONE, NULL, *qstate->env->now); return (*ke) == NULL; case sec_status_indeterminate: verbose(VERB_DETAIL, "NSEC3s for the " "referral proved no delegation"); *ke = NULL; return 0; case sec_status_bogus: verbose(VERB_DETAIL, "NSEC3s for the " "referral did not prove no DS."); errinf_ede(qstate, reason, reason_bogus); goto return_bogus; case sec_status_unchecked: return 2; default: /* NSEC3 proof did not work */ break; } /* Apparently, no available NSEC/NSEC3 proved NODATA, so * this is BOGUS. */ verbose(VERB_DETAIL, "DS %s ran out of options, so return " "bogus", val_classification_to_string(subtype)); reason = "no DS but also no proof of that"; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } else if(subtype == VAL_CLASS_CNAME || subtype == VAL_CLASS_CNAMENOANSWER) { /* if the CNAME matches the exact name we want and is signed * properly, then also, we are sure that no DS exists there, * much like a NODATA proof */ enum sec_status sec; struct ub_packed_rrset_key* cname; cname = reply_find_rrset_section_an(msg->rep, qinfo->qname, qinfo->qname_len, LDNS_RR_TYPE_CNAME, qinfo->qclass); if(!cname) { reason = "validator classified CNAME but no " "CNAME of the queried name for DS"; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } if(((struct packed_rrset_data*)cname->entry.data)->rrsig_count == 0) { if(msg->rep->an_numrrsets != 0 && ntohs(msg->rep-> rrsets[0]->rk.type)==LDNS_RR_TYPE_DNAME) { reason = "DS got DNAME answer"; } else { reason = "DS got unsigned CNAME answer"; } errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } sec = val_verify_rrset_entry(qstate->env, ve, cname, vq->key_entry, &reason, &reason_bogus, LDNS_SECTION_ANSWER, qstate, &verified, reasonbuf, sizeof(reasonbuf)); if(sec == sec_status_secure) { /* Check for wildcard expansion */ uint8_t* wc = NULL; size_t wl = 0; if(!val_rrset_wildcard(cname, &wc, &wl)) { verbose(VERB_ALGO, "CNAME has inconsistent wildcard signatures"); reason = "wildcard CNAME inconsistent signatures"; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } if(wc != NULL) { /* Wildcard expansion detected - require NSEC proof */ /* So this is a wildcard CNAME response to DS. * If the wildcard is bogus then we have bogus. * If the wildcard is true, then there is * not a referral point here or lower, * that can be insecure, * and also no DS records, here or lower. */ /* For a valid chain, to DS, but this * wildcard CNAME happens in a middle label, * then that can not happen, because there is * data under that label, and thus the wildcard * should not expand. * If we are going to the wildcard, that also * does not expand the wildcard, when above it. * So for valids lookup chains to DS, no * wildcard CNAME is expected on middle labels. * For lookups to an insecure point, the * delegation is information under the label, * and thus the wildcard does not expand. * So, no insecure point is possible. * Can not get a valid chain of trust, or * to a delegation point for insecure. * Or the wildcard, its nxdomain for the qname * proof, is invalid, in which case this is * a bogus reply. * If this was a lookup where a wildcard * expansion is genuinely expected, eg, * a dnssec valid wildcard query, then the * lookup should go to the right point, and * not into the wildcard under the zone name. * For insecure, or wildcard missing * signatures, it would have to have found * the DS or insecure point earlier, in the * downwards search. * So for missing signatures, it turns the * missing signatures into a failure to the * wildcard CNAME, as the reported log. */ verbose(VERB_ALGO, "wildcard CNAME in chain of trust means no DS can be found and it is also not a delegation point that can be insecure"); reason = "wildcard CNAME in chain of trust means no DS found and it is also not a delegation point that can be insecure"; errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } verbose(VERB_ALGO, "CNAME validated, " "proof that DS does not exist"); /* and that it is not a referral point */ *ke = NULL; return 0; } errinf(qstate, "CNAME in DS response was not secure."); errinf_ede(qstate, reason, reason_bogus); goto return_bogus; } else { verbose(VERB_QUERY, "Encountered an unhandled type of " "DS response, thus bogus."); errinf(qstate, "no DS and"); reason = "no DS"; if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) { char rc[16]; rc[0]=0; (void)sldns_wire2str_rcode_buf((int)FLAGS_GET_RCODE( msg->rep->flags), rc, sizeof(rc)); errinf(qstate, rc); } else errinf(qstate, val_classification_to_string(subtype)); errinf(qstate, "message fails to prove that"); goto return_bogus; } return_bogus: *ke = key_entry_create_bad(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL, reason_bogus, reason, *qstate->env->now); return (*ke) == NULL; } /** * Process DS response. Called from inform_supers. * Because it is in inform_supers, the mesh itself is busy doing callbacks * for a state that is to be deleted soon; don't touch the mesh; instead * set a state in the super, as the super will be reactivated soon. * Perform processing to determine what state to set in the super. * * @param qstate: query state that is validating and asked for a DS. * @param vq: validator query state * @param id: module id. * @param rcode: rcode result value. * @param msg: result message (if rcode is OK). * @param qinfo: from the sub query state, query info. * @param origin: the origin of msg. * @param suspend: returned true if the task takes too long and needs to * suspend to continue the effort later. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. * Can be NULL. */ static void process_ds_response(struct module_qstate* qstate, struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, struct sock_list* origin, int* suspend, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct key_entry_key* dske = NULL; uint8_t* olds = vq->empty_DS_name; int ret; *suspend = 0; vq->empty_DS_name = NULL; if(sub_qstate && sub_qstate->rpz_applied) { verbose(VERB_ALGO, "rpz was applied to the DS lookup, " "make it insecure"); vq->key_entry = NULL; vq->state = VAL_FINISHED_STATE; vq->chase_reply->security = sec_status_insecure; return; } ret = ds_response_to_ke(qstate, vq, id, rcode, msg, qinfo, &dske, sub_qstate); if(ret != 0) { switch(ret) { case 1: log_err("malloc failure in process_ds_response"); vq->key_entry = NULL; /* make it error */ vq->state = VAL_VALIDATE_STATE; return; case 2: *suspend = 1; return; default: log_err("unhandled error value for ds_response_to_ke"); vq->key_entry = NULL; /* make it error */ vq->state = VAL_VALIDATE_STATE; return; } } if(dske == NULL) { vq->empty_DS_name = regional_alloc_init(qstate->region, qinfo->qname, qinfo->qname_len); if(!vq->empty_DS_name) { log_err("malloc failure in empty_DS_name"); vq->key_entry = NULL; /* make it error */ vq->state = VAL_VALIDATE_STATE; return; } vq->empty_DS_len = qinfo->qname_len; vq->chain_blacklist = NULL; /* ds response indicated that we aren't on a delegation point. * Keep the forState.state on FINDKEY. */ } else if(key_entry_isgood(dske)) { vq->ds_rrset = key_entry_get_rrset(dske, qstate->region); if(!vq->ds_rrset) { log_err("malloc failure in process DS"); vq->key_entry = NULL; /* make it error */ vq->state = VAL_VALIDATE_STATE; return; } vq->chain_blacklist = NULL; /* fresh blacklist for next part*/ /* Keep the forState.state on FINDKEY. */ } else if(key_entry_isbad(dske) && vq->restart_count < ve->max_restart) { vq->empty_DS_name = olds; val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; vq->restart_count++; } else { if(key_entry_isbad(dske)) { errinf_origin(qstate, origin); errinf_dname(qstate, "for DS", qinfo->qname); } /* NOTE: the reason for the DS to be not good (that is, * either bad or null) should have been logged by * dsResponseToKE. */ vq->key_entry = dske; /* The FINDKEY phase has ended, so move on. */ vq->state = VAL_VALIDATE_STATE; } } /** * Process DNSKEY response. Called from inform_supers. * Sets the key entry in the state. * Because it is in inform_supers, the mesh itself is busy doing callbacks * for a state that is to be deleted soon; don't touch the mesh; instead * set a state in the super, as the super will be reactivated soon. * Perform processing to determine what state to set in the super. * * @param qstate: query state that is validating and asked for a DNSKEY. * @param vq: validator query state * @param id: module id. * @param rcode: rcode result value. * @param msg: result message (if rcode is OK). * @param qinfo: from the sub query state, query info. * @param origin: the origin of msg. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. */ static void process_dnskey_response(struct module_qstate* qstate, struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, struct query_info* qinfo, struct sock_list* origin, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct key_entry_key* old = vq->key_entry; struct ub_packed_rrset_key* dnskey = NULL; int downprot; char reasonbuf[256]; char* reason = NULL; sldns_ede_code reason_bogus = LDNS_EDE_DNSSEC_BOGUS; if(sub_qstate && sub_qstate->rpz_applied) { verbose(VERB_ALGO, "rpz was applied to the DNSKEY lookup, " "make it insecure"); vq->key_entry = NULL; vq->state = VAL_FINISHED_STATE; vq->chase_reply->security = sec_status_insecure; return; } if(rcode == LDNS_RCODE_NOERROR) dnskey = reply_find_answer_rrset(qinfo, msg->rep); if(dnskey == NULL) { char* err; char rstr[1024]; /* bad response */ verbose(VERB_DETAIL, "Missing DNSKEY RRset in response to " "DNSKEY query."); if(vq->restart_count < ve->max_restart) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; vq->restart_count++; return; } err = errinf_to_str_misc(sub_qstate); if(!err) { snprintf(rstr, sizeof(rstr), "No DNSKEY record"); } else { snprintf(rstr, sizeof(rstr), "No DNSKEY record " "[%s]", err); } reason_bogus = LDNS_EDE_DNSKEY_MISSING; vq->key_entry = key_entry_create_bad(qstate->region, qinfo->qname, qinfo->qname_len, qinfo->qclass, BOGUS_KEY_TTL, reason_bogus, rstr, *qstate->env->now); if(!vq->key_entry) { log_err("alloc failure in missing dnskey response"); /* key_entry is NULL for failure in Validate */ } errinf_ede(qstate, rstr, reason_bogus); errinf_origin(qstate, origin); errinf_dname(qstate, "for key", qinfo->qname); vq->state = VAL_VALIDATE_STATE; return; } if(!vq->ds_rrset) { log_err("internal error: no DS rrset for new DNSKEY response"); vq->key_entry = NULL; vq->state = VAL_VALIDATE_STATE; return; } downprot = qstate->env->cfg->harden_algo_downgrade; vq->key_entry = val_verify_new_DNSKEYs(qstate->region, qstate->env, ve, dnskey, vq->ds_rrset, downprot, &reason, &reason_bogus, qstate, reasonbuf, sizeof(reasonbuf)); if(!vq->key_entry) { log_err("out of memory in verify new DNSKEYs"); vq->state = VAL_VALIDATE_STATE; return; } /* If the key entry isBad or isNull, then we can move on to the next * state. */ if(!key_entry_isgood(vq->key_entry)) { if(key_entry_isbad(vq->key_entry)) { if(vq->restart_count < ve->max_restart) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; vq->restart_count++; vq->key_entry = old; return; } verbose(VERB_DETAIL, "Did not match a DS to a DNSKEY, " "thus bogus."); errinf_ede(qstate, reason, reason_bogus); errinf_origin(qstate, origin); errinf_dname(qstate, "for key", qinfo->qname); } vq->chain_blacklist = NULL; vq->state = VAL_VALIDATE_STATE; return; } vq->chain_blacklist = NULL; qstate->errinf = NULL; /* The DNSKEY validated, so cache it as a trusted key rrset. */ key_cache_insert(ve->kcache, vq->key_entry, qstate->env->cfg->val_log_level >= 2); /* If good, we stay in the FINDKEY state. */ log_query_info(VERB_DETAIL, "validated DNSKEY", qinfo); } /** * Process prime response * Sets the key entry in the state. * * @param qstate: query state that is validating and primed a trust anchor. * @param vq: validator query state * @param id: module id. * @param rcode: rcode result value. * @param msg: result message (if rcode is OK). * @param origin: the origin of msg. * @param sub_qstate: the sub query state, that is the lookup that fetched * the trust anchor data, it contains error information for the answer. */ static void process_prime_response(struct module_qstate* qstate, struct val_qstate* vq, int id, int rcode, struct dns_msg* msg, struct sock_list* origin, struct module_qstate* sub_qstate) { struct val_env* ve = (struct val_env*)qstate->env->modinfo[id]; struct ub_packed_rrset_key* dnskey_rrset = NULL; struct trust_anchor* ta = anchor_find(qstate->env->anchors, vq->trust_anchor_name, vq->trust_anchor_labs, vq->trust_anchor_len, vq->qchase.qclass); if(!ta) { /* trust anchor revoked, restart with less anchors */ vq->state = VAL_INIT_STATE; if(!vq->trust_anchor_name) vq->state = VAL_VALIDATE_STATE; /* break a loop */ vq->trust_anchor_name = NULL; return; } /* Fetch and validate the keyEntry that corresponds to the * current trust anchor. */ if(rcode == LDNS_RCODE_NOERROR) { dnskey_rrset = reply_find_rrset_section_an(msg->rep, ta->name, ta->namelen, LDNS_RR_TYPE_DNSKEY, ta->dclass); } if(ta->autr) { if(!autr_process_prime(qstate->env, ve, ta, dnskey_rrset, qstate)) { /* trust anchor revoked, restart with less anchors */ vq->state = VAL_INIT_STATE; vq->trust_anchor_name = NULL; return; } } vq->key_entry = primeResponseToKE(dnskey_rrset, ta, qstate, id, sub_qstate); lock_basic_unlock(&ta->lock); if(vq->key_entry) { if(key_entry_isbad(vq->key_entry) && vq->restart_count < ve->max_restart) { val_blacklist(&vq->chain_blacklist, qstate->region, origin, 1); qstate->errinf = NULL; vq->restart_count++; vq->key_entry = NULL; vq->state = VAL_INIT_STATE; return; } vq->chain_blacklist = NULL; errinf_origin(qstate, origin); errinf_dname(qstate, "for trust anchor", ta->name); /* store the freshly primed entry in the cache */ key_cache_insert(ve->kcache, vq->key_entry, qstate->env->cfg->val_log_level >= 2); } /* If the result of the prime is a null key, skip the FINDKEY state.*/ if(!vq->key_entry || key_entry_isnull(vq->key_entry) || key_entry_isbad(vq->key_entry)) { vq->state = VAL_VALIDATE_STATE; } /* the qstate will be reactivated after inform_super is done */ } /* * inform validator super. * * @param qstate: query state that finished. * @param id: module id. * @param super: the qstate to inform. */ void val_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) { struct val_qstate* vq = (struct val_qstate*)super->minfo[id]; log_query_info(VERB_ALGO, "validator: inform_super, sub is", &qstate->qinfo); log_query_info(VERB_ALGO, "super is", &super->qinfo); if(!vq) { verbose(VERB_ALGO, "super: has no validator state"); return; } if(vq->wait_prime_ta) { vq->wait_prime_ta = 0; process_prime_response(super, vq, id, qstate->return_rcode, qstate->return_msg, qstate->reply_origin, qstate); return; } if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) { int suspend; process_ds_response(super, vq, id, qstate->return_rcode, qstate->return_msg, &qstate->qinfo, qstate->reply_origin, &suspend, qstate); /* If NSEC3 was needed during validation, NULL the NSEC3 cache; * it will be re-initiated if needed later on. * Validation (and the cache table) are happening/allocated in * the super qstate whilst the RRs are allocated (and pointed * to) in this sub qstate. */ if(vq->nsec3_cache_table.ct) { vq->nsec3_cache_table.ct = NULL; } if(suspend) { /* deep copy the return_msg to vq->sub_ds_msg; it will * be resumed later in the super state with the caveat * that the initial calculations will be re-calculated * and re-suspended there before continuing. */ vq->sub_ds_msg = dns_msg_deepcopy_region( qstate->return_msg, super->region); } return; } else if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY) { process_dnskey_response(super, vq, id, qstate->return_rcode, qstate->return_msg, &qstate->qinfo, qstate->reply_origin, qstate); return; } log_err("internal error in validator: no inform_supers possible"); } void val_clear(struct module_qstate* qstate, int id) { struct val_qstate* vq; if(!qstate) return; vq = (struct val_qstate*)qstate->minfo[id]; if(vq) { if(vq->suspend_timer) { comm_timer_delete(vq->suspend_timer); } } /* everything is allocated in the region, so assign NULL */ qstate->minfo[id] = NULL; } size_t val_get_mem(struct module_env* env, int id) { struct val_env* ve = (struct val_env*)env->modinfo[id]; if(!ve) return 0; return sizeof(*ve) + key_cache_get_mem(ve->kcache) + val_neg_get_mem(ve->neg_cache) + sizeof(size_t)*2*ve->nsec3_keyiter_count; } /** * The validator function block */ static struct module_func_block val_block = { "validator", NULL, NULL, &val_init, &val_deinit, &val_operate, &val_inform_super, &val_clear, &val_get_mem }; struct module_func_block* val_get_funcblock(void) { return &val_block; } const char* val_state_to_string(enum val_state state) { switch(state) { case VAL_INIT_STATE: return "VAL_INIT_STATE"; case VAL_FINDKEY_STATE: return "VAL_FINDKEY_STATE"; case VAL_VALIDATE_STATE: return "VAL_VALIDATE_STATE"; case VAL_FINISHED_STATE: return "VAL_FINISHED_STATE"; } return "UNKNOWN VALIDATOR STATE"; } unbound-1.25.1/validator/val_neg.h0000644000175000017500000002416615203270263016470 0ustar wouterwouter/* * validator/val_neg.h - validator aggressive negative caching functions. * * Copyright (c) 2008, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with aggressive negative caching. * This creates new denials of existence, and proofs for absence of types * from cached NSEC records. */ #ifndef VALIDATOR_VAL_NEG_H #define VALIDATOR_VAL_NEG_H #include "util/locks.h" #include "util/rbtree.h" struct sldns_buffer; struct val_neg_data; struct config_file; struct reply_info; struct rrset_cache; struct regional; struct query_info; struct dns_msg; struct ub_packed_rrset_key; /** * The negative cache. It is shared between the threads, so locked. * Kept as validator-environ-state. It refers back to the rrset cache for * data elements. It can be out of date and contain conflicting data * from zone content changes. * It contains a tree of zones, every zone has a tree of data elements. * The data elements are part of one big LRU list, with one memory counter. */ struct val_neg_cache { /** the big lock on the negative cache. Because we use a rbtree * for the data (quick lookup), we need a big lock */ lock_basic_type lock; /** The zone rbtree. contents sorted canonical, type val_neg_zone */ rbtree_type tree; /** the first in linked list of LRU of val_neg_data */ struct val_neg_data* first; /** last in lru (least recently used element) */ struct val_neg_data* last; /** current memory in use (bytes) */ size_t use; /** max memory to use (bytes) */ size_t max; /** max nsec3 iterations allowed */ size_t nsec3_max_iter; /** number of times neg cache records were used to generate NOERROR * responses. */ size_t num_neg_cache_noerror; /** number of times neg cache records were used to generate NXDOMAIN * responses. */ size_t num_neg_cache_nxdomain; }; /** * Per Zone aggressive negative caching data. */ struct val_neg_zone { /** rbtree node element, key is this struct: the name, class */ rbnode_type node; /** name; the key */ uint8_t* name; /** length of name */ size_t len; /** labels in name */ int labs; /** pointer to parent zone in the negative cache */ struct val_neg_zone* parent; /** the number of elements, including this one and the ones whose * parents (-parents) include this one, that are in_use * No elements have a count of zero, those are removed. */ int count; /** if 0: NSEC zone, else NSEC3 hash algorithm in use */ int nsec3_hash; /** nsec3 iteration count in use */ size_t nsec3_iter; /** nsec3 salt in use */ uint8_t* nsec3_salt; /** length of salt in bytes */ size_t nsec3_saltlen; /** tree of NSEC data for this zone, sorted canonical * by NSEC owner name */ rbtree_type tree; /** class of node; host order */ uint16_t dclass; /** if this element is in use, boolean */ uint8_t in_use; }; /** * Data element for aggressive negative caching. * The tree of these elements acts as an index onto the rrset cache. * It shows the NSEC records that (may) exist and are (possibly) secure. * The rbtree allows for logN search for a covering NSEC record. * To make tree insertion and deletion logN too, all the parent (one label * less than the name) data elements are also in the rbtree, with a usage * count for every data element. * There is no actual data stored in this data element, if it is in_use, * then the data can (possibly) be found in the rrset cache. */ struct val_neg_data { /** rbtree node element, key is this struct: the name */ rbnode_type node; /** name; the key */ uint8_t* name; /** length of name */ size_t len; /** labels in name */ int labs; /** pointer to parent node in the negative cache */ struct val_neg_data* parent; /** the number of elements, including this one and the ones whose * parents (-parents) include this one, that are in use * No elements have a count of zero, those are removed. */ int count; /** the zone that this denial is part of */ struct val_neg_zone* zone; /** previous in LRU */ struct val_neg_data* prev; /** next in LRU (next element was less recently used) */ struct val_neg_data* next; /** if this element is in use, boolean */ uint8_t in_use; }; /** * Create negative cache * @param cfg: config options. * @param maxiter: max nsec3 iterations allowed. * @return neg cache, empty or NULL on failure. */ struct val_neg_cache* val_neg_create(struct config_file* cfg, size_t maxiter); /** * see how much memory is in use by the negative cache. * @param neg: negative cache * @return number of bytes in use. */ size_t val_neg_get_mem(struct val_neg_cache* neg); /** * Destroy negative cache. There must no longer be any other threads. * @param neg: negative cache. */ void neg_cache_delete(struct val_neg_cache* neg); /** * Comparison function for rbtree val neg data elements */ int val_neg_data_compare(const void* a, const void* b); /** * Comparison function for rbtree val neg zone elements */ int val_neg_zone_compare(const void* a, const void* b); /** * Insert NSECs from this message into the negative cache for reference. * @param neg: negative cache * @param rep: reply with NSECs. * Errors are ignored, means that storage is omitted. */ 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); /** * 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. * @param buf: temporary buffer. * @param now: to check TTLs against. * @param addsoa: if true, produce result for external consumption. * if false, do not add SOA - for unbound-internal consumption. * @param topname: do not look higher than this name, * so that the result cannot be taken from a zone above the current * trust anchor. Which could happen with multiple islands of trust. * if NULL, then no trust anchor is used, but also the algorithm becomes * more conservative, especially for opt-out zones, since the receiver * may have a trust-anchor below the optout and thus the optout cannot * be used to create a proof from the negative cache. * @param cfg: config options. * @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, struct sldns_buffer* buf, time_t now, int addsoa, uint8_t* topname, struct config_file* cfg); /**** functions exposed for unit test ****/ /** * Insert data into the data tree of a zone * Does not do locking. * @param neg: negative cache * @param zone: zone to insert into * @param nsec: record to insert. */ void neg_insert_data(struct val_neg_cache* neg, struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec); /** * Delete a data element from the negative cache. * May delete other data elements to keep tree coherent, or * only mark the element as 'not in use'. * Does not do locking. * @param neg: negative cache. * @param el: data element to delete. */ void neg_delete_data(struct val_neg_cache* neg, struct val_neg_data* el); /** * Find the given zone, from the SOA owner name and class * Does not do locking. * @param neg: negative cache * @param nm: what to look for. * @param len: length of nm * @param dclass: class to look for. * @return zone or NULL if not found. */ struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, uint8_t* nm, size_t len, uint16_t dclass); /** * Create a new zone. * Does not do locking. * @param neg: negative cache * @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. */ struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, uint8_t* nm, size_t nm_len, uint16_t dclass); /** * take a zone into use. increases counts of parents. * Does not do locking. * @param zone: zone to take into use. */ void val_neg_zone_take_inuse(struct val_neg_zone* zone); /** * Adjust the size of the negative cache. * @param neg: negative cache * @param max: new size for max mem. */ void val_neg_adjust_size(struct val_neg_cache* neg, size_t max); #endif /* VALIDATOR_VAL_NEG_H */ unbound-1.25.1/validator/autotrust.c0000644000175000017500000021472515203270263017124 0ustar wouterwouter/* * validator/autotrust.c - RFC5011 trust anchor management for unbound. * * Copyright (c) 2009, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * Contains autotrust implementation. The implementation was taken from * the autotrust daemon (BSD licensed), written by Matthijs Mekking. * It was modified to fit into unbound. The state table process is the same. */ #include "config.h" #include "validator/autotrust.h" #include "validator/val_anchor.h" #include "validator/val_utils.h" #include "validator/val_sigcrypt.h" #include "util/data/dname.h" #include "util/data/packed_rrset.h" #include "util/log.h" #include "util/module.h" #include "util/net_help.h" #include "util/config_file.h" #include "util/regional.h" #include "util/random.h" #include "util/data/msgparse.h" #include "services/mesh.h" #include "services/cache/rrset.h" #include "validator/val_kcache.h" #include "sldns/sbuffer.h" #include "sldns/wire2str.h" #include "sldns/str2wire.h" #include "sldns/keyraw.h" #include "sldns/rrdef.h" #include #include /** number of times a key must be seen before it can become valid */ #define MIN_PENDINGCOUNT 2 /** Event: Revoked */ static void do_revoked(struct module_env* env, struct autr_ta* anchor, int* c); struct autr_global_data* autr_global_create(void) { struct autr_global_data* global; global = (struct autr_global_data*)malloc(sizeof(*global)); if(!global) return NULL; rbtree_init(&global->probe, &probetree_cmp); return global; } void autr_global_delete(struct autr_global_data* global) { if(!global) return; /* elements deleted by parent */ free(global); } int probetree_cmp(const void* x, const void* y) { struct trust_anchor* a = (struct trust_anchor*)x; struct trust_anchor* b = (struct trust_anchor*)y; log_assert(a->autr && b->autr); if(a->autr->next_probe_time < b->autr->next_probe_time) return -1; if(a->autr->next_probe_time > b->autr->next_probe_time) return 1; /* time is equal, sort on trust point identity */ return anchor_cmp(x, y); } size_t autr_get_num_anchors(struct val_anchors* anchors) { size_t res = 0; if(!anchors) return 0; lock_basic_lock(&anchors->lock); if(anchors->autr) res = anchors->autr->probe.count; lock_basic_unlock(&anchors->lock); return res; } /** Position in string */ static int position_in_string(char *str, const char* sub) { char* pos = strstr(str, sub); if(pos) return (int)(pos-str)+(int)strlen(sub); return -1; } /** Debug routine to print pretty key information */ static void verbose_key(struct autr_ta* ta, enum verbosity_value level, const char* format, ...) ATTR_FORMAT(printf, 3, 4); /** * Implementation of debug pretty key print * @param ta: trust anchor key with DNSKEY data. * @param level: verbosity level to print at. * @param format: printf style format string. */ static void verbose_key(struct autr_ta* ta, enum verbosity_value level, const char* format, ...) { va_list args; va_start(args, format); if(verbosity >= level) { char* str = sldns_wire2str_dname(ta->rr, ta->dname_len); int keytag = (int)sldns_calc_keytag_raw(sldns_wirerr_get_rdata( ta->rr, ta->rr_len, ta->dname_len), sldns_wirerr_get_rdatalen(ta->rr, ta->rr_len, ta->dname_len)); char msg[MAXSYSLOGMSGLEN]; vsnprintf(msg, sizeof(msg), format, args); verbose(level, "%s key %d %s", str?str:"??", keytag, msg); free(str); } va_end(args); } /** * Parse comments * @param str: to parse * @param ta: trust key autotrust metadata * @return false on failure. */ static int parse_comments(char* str, struct autr_ta* ta) { int len = (int)strlen(str), pos = 0, timestamp = 0; char* comment = (char*) malloc(sizeof(char)*len+1); char* comments = comment; if(!comment) { log_err("malloc failure in parse"); return 0; } /* skip over whitespace and data at start of line */ while (*str != '\0' && *str != ';') str++; if (*str == ';') str++; /* copy comments */ while (*str != '\0') { *comments = *str; comments++; str++; } *comments = '\0'; comments = comment; /* read state */ pos = position_in_string(comments, "state="); if (pos >= (int) strlen(comments)) { log_err("parse error"); free(comment); return 0; } if (pos <= 0) ta->s = AUTR_STATE_VALID; else { int s = (int) comments[pos] - '0'; switch(s) { case AUTR_STATE_START: case AUTR_STATE_ADDPEND: case AUTR_STATE_VALID: case AUTR_STATE_MISSING: case AUTR_STATE_REVOKED: case AUTR_STATE_REMOVED: ta->s = s; break; default: verbose_key(ta, VERB_OPS, "has undefined " "state, considered NewKey"); ta->s = AUTR_STATE_START; break; } } /* read pending count */ pos = position_in_string(comments, "count="); if (pos >= (int) strlen(comments)) { log_err("parse error"); free(comment); return 0; } if (pos <= 0) ta->pending_count = 0; else { comments += pos; ta->pending_count = (uint8_t)atoi(comments); } /* read last change */ pos = position_in_string(comments, "lastchange="); if (pos >= (int) strlen(comments)) { log_err("parse error"); free(comment); return 0; } if (pos >= 0) { comments += pos; timestamp = atoi(comments); } if (pos < 0 || !timestamp) ta->last_change = 0; else ta->last_change = (time_t)timestamp; free(comment); return 1; } /** Check if a line contains data (besides comments) */ static int str_contains_data(char* str, char comment) { while (*str != '\0') { if (*str == comment || *str == '\n') return 0; if (*str != ' ' && *str != '\t') return 1; str++; } return 0; } /** Get DNSKEY flags * rdata without rdatalen in front of it. */ static int dnskey_flags(uint16_t t, uint8_t* rdata, size_t len) { uint16_t f; if(t != LDNS_RR_TYPE_DNSKEY) return 0; if(len < 2) return 0; memmove(&f, rdata, 2); f = ntohs(f); return (int)f; } /** Check if KSK DNSKEY. * pass rdata without rdatalen in front of it */ static int rr_is_dnskey_sep(uint16_t t, uint8_t* rdata, size_t len) { return (dnskey_flags(t, rdata, len)&DNSKEY_BIT_SEP); } /** Check if TA is KSK DNSKEY */ static int ta_is_dnskey_sep(struct autr_ta* ta) { return (dnskey_flags( sldns_wirerr_get_type(ta->rr, ta->rr_len, ta->dname_len), sldns_wirerr_get_rdata(ta->rr, ta->rr_len, ta->dname_len), sldns_wirerr_get_rdatalen(ta->rr, ta->rr_len, ta->dname_len) ) & DNSKEY_BIT_SEP); } /** Check if REVOKED DNSKEY * pass rdata without rdatalen in front of it */ static int rr_is_dnskey_revoked(uint16_t t, uint8_t* rdata, size_t len) { return (dnskey_flags(t, rdata, len)&LDNS_KEY_REVOKE_KEY); } /** create ta */ static struct autr_ta* autr_ta_create(uint8_t* rr, size_t rr_len, size_t dname_len) { struct autr_ta* ta = (struct autr_ta*)calloc(1, sizeof(*ta)); if(!ta) { free(rr); return NULL; } ta->rr = rr; ta->rr_len = rr_len; ta->dname_len = dname_len; return ta; } /** create tp */ static struct trust_anchor* autr_tp_create(struct val_anchors* anchors, uint8_t* own, size_t own_len, uint16_t dc) { struct trust_anchor* tp = (struct trust_anchor*)calloc(1, sizeof(*tp)); if(!tp) return NULL; tp->name = memdup(own, own_len); if(!tp->name) { free(tp); return NULL; } tp->namelen = own_len; tp->namelabs = dname_count_labels(tp->name); tp->node.key = tp; tp->dclass = dc; tp->autr = (struct autr_point_data*)calloc(1, sizeof(*tp->autr)); if(!tp->autr) { free(tp->name); free(tp); return NULL; } tp->autr->pnode.key = tp; lock_basic_lock(&anchors->lock); if(!rbtree_insert(anchors->tree, &tp->node)) { char buf[LDNS_MAX_DOMAINLEN]; lock_basic_unlock(&anchors->lock); dname_str(tp->name, buf); log_err("trust anchor for '%s' presented twice", buf); free(tp->name); free(tp->autr); free(tp); return NULL; } if(!rbtree_insert(&anchors->autr->probe, &tp->autr->pnode)) { char buf[LDNS_MAX_DOMAINLEN]; (void)rbtree_delete(anchors->tree, tp); lock_basic_unlock(&anchors->lock); dname_str(tp->name, buf); log_err("trust anchor for '%s' in probetree twice", buf); free(tp->name); free(tp->autr); free(tp); return NULL; } lock_basic_init(&tp->lock); lock_protect(&tp->lock, tp, sizeof(*tp)); lock_protect(&tp->lock, tp->autr, sizeof(*tp->autr)); lock_basic_unlock(&anchors->lock); return tp; } /** delete assembled rrsets */ static void autr_rrset_delete(struct ub_packed_rrset_key* r) { if(r) { free(r->rk.dname); free(r->entry.data); free(r); } } void autr_point_delete(struct trust_anchor* tp) { if(!tp) return; lock_unprotect(&tp->lock, tp); lock_unprotect(&tp->lock, tp->autr); lock_basic_destroy(&tp->lock); autr_rrset_delete(tp->ds_rrset); autr_rrset_delete(tp->dnskey_rrset); if(tp->autr) { struct autr_ta* p = tp->autr->keys, *np; while(p) { np = p->next; free(p->rr); free(p); p = np; } free(tp->autr->file); free(tp->autr); } free(tp->name); free(tp); } /** find or add a new trust point for autotrust */ static struct trust_anchor* find_add_tp(struct val_anchors* anchors, uint8_t* rr, size_t rr_len, size_t dname_len) { struct trust_anchor* tp; tp = anchor_find(anchors, rr, dname_count_labels(rr), dname_len, sldns_wirerr_get_class(rr, rr_len, dname_len)); if(tp) { if(!tp->autr) { log_err("anchor cannot be with and without autotrust"); lock_basic_unlock(&tp->lock); return NULL; } return tp; } tp = autr_tp_create(anchors, rr, dname_len, sldns_wirerr_get_class(rr, rr_len, dname_len)); if(!tp) return NULL; lock_basic_lock(&tp->lock); return tp; } /** Add trust anchor from RR */ static struct autr_ta* add_trustanchor_frm_rr(struct val_anchors* anchors, uint8_t* rr, size_t rr_len, size_t dname_len, struct trust_anchor** tp) { struct autr_ta* ta = autr_ta_create(rr, rr_len, dname_len); if(!ta) return NULL; *tp = find_add_tp(anchors, rr, rr_len, dname_len); if(!*tp) { free(ta->rr); free(ta); return NULL; } /* add ta to tp */ ta->next = (*tp)->autr->keys; (*tp)->autr->keys = ta; lock_basic_unlock(&(*tp)->lock); return ta; } /** * Add new trust anchor from a string in file. * @param anchors: all anchors * @param str: string with anchor and comments, if any comments. * @param tp: trust point returned. * @param origin: what to use for @ * @param origin_len: length of origin * @param prev: previous rr name * @param prev_len: length of prev * @param skip: if true, the result is NULL, but not an error, skip it. * @return new key in trust point. */ static struct autr_ta* add_trustanchor_frm_str(struct val_anchors* anchors, char* str, struct trust_anchor** tp, uint8_t* origin, size_t origin_len, uint8_t** prev, size_t* prev_len, int* skip) { uint8_t rr[LDNS_RR_BUF_SIZE]; size_t rr_len = sizeof(rr), dname_len; uint8_t* drr; int lstatus; if (!str_contains_data(str, ';')) { *skip = 1; return NULL; /* empty line */ } if(0 != (lstatus = sldns_str2wire_rr_buf(str, rr, &rr_len, &dname_len, 0, origin, origin_len, *prev, *prev_len))) { log_err("ldns error while converting string to RR at%d: %s: %s", LDNS_WIREPARSE_OFFSET(lstatus), sldns_get_errorstr_parse(lstatus), str); return NULL; } free(*prev); *prev = memdup(rr, dname_len); *prev_len = dname_len; if(!*prev) { log_err("malloc failure in add_trustanchor"); return NULL; } if(sldns_wirerr_get_type(rr, rr_len, dname_len)!=LDNS_RR_TYPE_DNSKEY && sldns_wirerr_get_type(rr, rr_len, dname_len)!=LDNS_RR_TYPE_DS) { *skip = 1; return NULL; /* only DS and DNSKEY allowed */ } drr = memdup(rr, rr_len); if(!drr) { log_err("malloc failure in add trustanchor"); return NULL; } return add_trustanchor_frm_rr(anchors, drr, rr_len, dname_len, tp); } /** * Load single anchor * @param anchors: all points. * @param str: comments line * @param fname: filename * @param origin: the $ORIGIN. * @param origin_len: length of origin * @param prev: passed to ldns. * @param prev_len: length of prev * @param skip: if true, the result is NULL, but not an error, skip it. * @return false on failure, otherwise the tp read. */ static struct trust_anchor* load_trustanchor(struct val_anchors* anchors, char* str, const char* fname, uint8_t* origin, size_t origin_len, uint8_t** prev, size_t* prev_len, int* skip) { struct autr_ta* ta = NULL; struct trust_anchor* tp = NULL; ta = add_trustanchor_frm_str(anchors, str, &tp, origin, origin_len, prev, prev_len, skip); if(!ta) return NULL; lock_basic_lock(&tp->lock); if(!parse_comments(str, ta)) { lock_basic_unlock(&tp->lock); return NULL; } if(!tp->autr->file) { tp->autr->file = strdup(fname); if(!tp->autr->file) { lock_basic_unlock(&tp->lock); log_err("malloc failure"); return NULL; } } lock_basic_unlock(&tp->lock); return tp; } /** iterator for DSes from keylist. return true if a next element exists */ static int assemble_iterate_ds(struct autr_ta** list, uint8_t** rr, size_t* rr_len, size_t* dname_len) { while(*list) { if(sldns_wirerr_get_type((*list)->rr, (*list)->rr_len, (*list)->dname_len) == LDNS_RR_TYPE_DS) { *rr = (*list)->rr; *rr_len = (*list)->rr_len; *dname_len = (*list)->dname_len; *list = (*list)->next; return 1; } *list = (*list)->next; } return 0; } /** iterator for DNSKEYs from keylist. return true if a next element exists */ static int assemble_iterate_dnskey(struct autr_ta** list, uint8_t** rr, size_t* rr_len, size_t* dname_len) { while(*list) { if(sldns_wirerr_get_type((*list)->rr, (*list)->rr_len, (*list)->dname_len) != LDNS_RR_TYPE_DS && ((*list)->s == AUTR_STATE_VALID || (*list)->s == AUTR_STATE_MISSING)) { *rr = (*list)->rr; *rr_len = (*list)->rr_len; *dname_len = (*list)->dname_len; *list = (*list)->next; return 1; } *list = (*list)->next; } return 0; } /** see if iterator-list has any elements in it, or it is empty */ static int assemble_iterate_hasfirst(int iter(struct autr_ta**, uint8_t**, size_t*, size_t*), struct autr_ta* list) { uint8_t* rr = NULL; size_t rr_len = 0, dname_len = 0; return iter(&list, &rr, &rr_len, &dname_len); } /** number of elements in iterator list */ static size_t assemble_iterate_count(int iter(struct autr_ta**, uint8_t**, size_t*, size_t*), struct autr_ta* list) { uint8_t* rr = NULL; size_t i = 0, rr_len = 0, dname_len = 0; while(iter(&list, &rr, &rr_len, &dname_len)) { i++; } return i; } /** * Create a ub_packed_rrset_key allocated on the heap. * It therefore does not have the correct ID value, and cannot be used * inside the cache. It can be used in storage outside of the cache. * Keys for the cache have to be obtained from alloc.h . * @param iter: iterator over the elements in the list. It filters elements. * @param list: the list. * @return key allocated or NULL on failure. */ static struct ub_packed_rrset_key* ub_packed_rrset_heap_key(int iter(struct autr_ta**, uint8_t**, size_t*, size_t*), struct autr_ta* list) { uint8_t* rr = NULL; size_t rr_len = 0, dname_len = 0; struct ub_packed_rrset_key* k; if(!iter(&list, &rr, &rr_len, &dname_len)) return NULL; k = (struct ub_packed_rrset_key*)calloc(1, sizeof(*k)); if(!k) return NULL; k->rk.type = htons(sldns_wirerr_get_type(rr, rr_len, dname_len)); k->rk.rrset_class = htons(sldns_wirerr_get_class(rr, rr_len, dname_len)); k->rk.dname_len = dname_len; k->rk.dname = memdup(rr, dname_len); if(!k->rk.dname) { free(k); return NULL; } return k; } /** * Create packed_rrset data on the heap. * @param iter: iterator over the elements in the list. It filters elements. * @param list: the list. * @return data allocated or NULL on failure. */ static struct packed_rrset_data* packed_rrset_heap_data(int iter(struct autr_ta**, uint8_t**, size_t*, size_t*), struct autr_ta* list) { uint8_t* rr = NULL; size_t rr_len = 0, dname_len = 0; struct packed_rrset_data* data; size_t count=0, rrsig_count=0, len=0, i, total; uint8_t* nextrdata; struct autr_ta* list_i; time_t ttl = 0; list_i = list; while(iter(&list_i, &rr, &rr_len, &dname_len)) { if(sldns_wirerr_get_type(rr, rr_len, dname_len) == LDNS_RR_TYPE_RRSIG) rrsig_count++; else count++; /* sizeof the rdlength + rdatalen */ len += 2 + sldns_wirerr_get_rdatalen(rr, rr_len, dname_len); ttl = (time_t)sldns_wirerr_get_ttl(rr, rr_len, dname_len); } if(count == 0 && rrsig_count == 0) return NULL; /* allocate */ total = count + rrsig_count; len += sizeof(*data) + total*(sizeof(size_t) + sizeof(time_t) + sizeof(uint8_t*)); data = (struct packed_rrset_data*)calloc(1, len); if(!data) return NULL; /* fill it */ data->ttl = ttl; data->count = count; data->rrsig_count = rrsig_count; data->rr_len = (size_t*)((uint8_t*)data + sizeof(struct packed_rrset_data)); data->rr_data = (uint8_t**)&(data->rr_len[total]); data->rr_ttl = (time_t*)&(data->rr_data[total]); nextrdata = (uint8_t*)&(data->rr_ttl[total]); /* fill out len, ttl, fields */ list_i = list; i = 0; while(iter(&list_i, &rr, &rr_len, &dname_len)) { data->rr_ttl[i] = (time_t)sldns_wirerr_get_ttl(rr, rr_len, dname_len); if(data->rr_ttl[i] < data->ttl) data->ttl = data->rr_ttl[i]; data->rr_len[i] = 2 /* the rdlength */ + sldns_wirerr_get_rdatalen(rr, rr_len, dname_len); i++; } /* fixup rest of ptrs */ for(i=0; irr_data[i] = nextrdata; nextrdata += data->rr_len[i]; } /* copy data in there */ list_i = list; i = 0; while(iter(&list_i, &rr, &rr_len, &dname_len)) { log_assert(data->rr_data[i]); memmove(data->rr_data[i], sldns_wirerr_get_rdatawl(rr, rr_len, dname_len), data->rr_len[i]); i++; } if(data->rrsig_count && data->count == 0) { data->count = data->rrsig_count; /* rrset type is RRSIG */ data->rrsig_count = 0; } return data; } /** * Assemble the trust anchors into DS and DNSKEY packed rrsets. * Uses only VALID and MISSING DNSKEYs. * Read the sldns_rrs and builds packed rrsets * @param tp: the trust point. Must be locked. * @return false on malloc failure. */ static int autr_assemble(struct trust_anchor* tp) { struct ub_packed_rrset_key* ubds=NULL, *ubdnskey=NULL; /* make packed rrset keys - malloced with no ID number, they * are not in the cache */ /* make packed rrset data (if there is a key) */ if(assemble_iterate_hasfirst(assemble_iterate_ds, tp->autr->keys)) { ubds = ub_packed_rrset_heap_key( assemble_iterate_ds, tp->autr->keys); if(!ubds) goto error_cleanup; ubds->entry.data = packed_rrset_heap_data( assemble_iterate_ds, tp->autr->keys); if(!ubds->entry.data) goto error_cleanup; } /* make packed DNSKEY data */ if(assemble_iterate_hasfirst(assemble_iterate_dnskey, tp->autr->keys)) { ubdnskey = ub_packed_rrset_heap_key( assemble_iterate_dnskey, tp->autr->keys); if(!ubdnskey) goto error_cleanup; ubdnskey->entry.data = packed_rrset_heap_data( assemble_iterate_dnskey, tp->autr->keys); if(!ubdnskey->entry.data) { error_cleanup: autr_rrset_delete(ubds); autr_rrset_delete(ubdnskey); return 0; } } /* we have prepared the new keys so nothing can go wrong any more. * And we are sure we cannot be left without trustanchor after * any errors. Put in the new keys and remove old ones. */ /* free the old data */ autr_rrset_delete(tp->ds_rrset); autr_rrset_delete(tp->dnskey_rrset); /* assign the data to replace the old */ tp->ds_rrset = ubds; tp->dnskey_rrset = ubdnskey; tp->numDS = assemble_iterate_count(assemble_iterate_ds, tp->autr->keys); tp->numDNSKEY = assemble_iterate_count(assemble_iterate_dnskey, tp->autr->keys); return 1; } /** parse integer */ static unsigned int parse_int(char* line, int* ret) { char *e; unsigned int x = (unsigned int)strtol(line, &e, 10); if(line == e) { *ret = -1; /* parse error */ return 0; } *ret = 1; /* matched */ return x; } /** parse id sequence for anchor */ static struct trust_anchor* parse_id(struct val_anchors* anchors, char* line) { struct trust_anchor *tp; int r; uint16_t dclass; uint8_t* dname; size_t dname_len; /* read the owner name */ char* next = strchr(line, ' '); if(!next) return NULL; next[0] = 0; dname = sldns_str2wire_dname(line, &dname_len); if(!dname) return NULL; /* read the class */ dclass = parse_int(next+1, &r); if(r == -1) { free(dname); return NULL; } /* find the trust point */ tp = autr_tp_create(anchors, dname, dname_len, dclass); free(dname); return tp; } /** * Parse variable from trustanchor header * @param line: to parse * @param anchors: the anchor is added to this, if "id:" is seen. * @param anchor: the anchor as result value or previously returned anchor * value to read the variable lines into. * @return: 0 no match, -1 failed syntax error, +1 success line read. * +2 revoked trust anchor file. */ static int parse_var_line(char* line, struct val_anchors* anchors, struct trust_anchor** anchor) { struct trust_anchor* tp = *anchor; int r = 0; if(strncmp(line, ";;id: ", 6) == 0) { *anchor = parse_id(anchors, line+6); if(!*anchor) return -1; else return 1; } else if(strncmp(line, ";;REVOKED", 9) == 0) { if(tp) { log_err("REVOKED statement must be at start of file"); return -1; } return 2; } else if(strncmp(line, ";;last_queried: ", 16) == 0) { if(!tp) return -1; lock_basic_lock(&tp->lock); tp->autr->last_queried = (time_t)parse_int(line+16, &r); lock_basic_unlock(&tp->lock); } else if(strncmp(line, ";;last_success: ", 16) == 0) { if(!tp) return -1; lock_basic_lock(&tp->lock); tp->autr->last_success = (time_t)parse_int(line+16, &r); lock_basic_unlock(&tp->lock); } else if(strncmp(line, ";;next_probe_time: ", 19) == 0) { if(!tp) return -1; lock_basic_lock(&anchors->lock); lock_basic_lock(&tp->lock); (void)rbtree_delete(&anchors->autr->probe, tp); tp->autr->next_probe_time = (time_t)parse_int(line+19, &r); (void)rbtree_insert(&anchors->autr->probe, &tp->autr->pnode); lock_basic_unlock(&tp->lock); lock_basic_unlock(&anchors->lock); } else if(strncmp(line, ";;query_failed: ", 16) == 0) { if(!tp) return -1; lock_basic_lock(&tp->lock); tp->autr->query_failed = (uint8_t)parse_int(line+16, &r); lock_basic_unlock(&tp->lock); } else if(strncmp(line, ";;query_interval: ", 18) == 0) { if(!tp) return -1; lock_basic_lock(&tp->lock); tp->autr->query_interval = (time_t)parse_int(line+18, &r); lock_basic_unlock(&tp->lock); } else if(strncmp(line, ";;retry_time: ", 14) == 0) { if(!tp) return -1; lock_basic_lock(&tp->lock); tp->autr->retry_time = (time_t)parse_int(line+14, &r); lock_basic_unlock(&tp->lock); } return r; } /** handle origin lines */ static int handle_origin(char* line, uint8_t** origin, size_t* origin_len) { size_t len = 0; while(isspace((unsigned char)*line)) line++; if(strncmp(line, "$ORIGIN", 7) != 0) return 0; free(*origin); line += 7; while(isspace((unsigned char)*line)) line++; *origin = sldns_str2wire_dname(line, &len); *origin_len = len; if(!*origin) log_warn("malloc failure or parse error in $ORIGIN"); return 1; } /** Read one line and put multiline RRs onto one line string */ static int read_multiline(char* buf, size_t len, FILE* in, int* linenr) { char* pos = buf; size_t left = len; int depth = 0; buf[len-1] = 0; while(left > 0 && fgets(pos, (int)left, in) != NULL) { size_t i, poslen = strlen(pos); (*linenr)++; /* check what the new depth is after the line */ /* this routine cannot handle braces inside quotes, say for TXT records, but this routine only has to read keys */ for(i=0; i0) pos[poslen-1] = 0; /* strip newline */ if(strchr(pos, ';')) strchr(pos, ';')[0] = 0; /* strip comments */ /* move to paste other lines behind this one */ poslen = strlen(pos); pos += poslen; left -= poslen; /* the newline is changed into a space */ if(left <= 2 /* space and eos */) { log_err("line too long"); return -1; } pos[0] = ' '; pos[1] = 0; pos += 1; left -= 1; } if(depth != 0) { log_err("mismatch: too many '('"); return -1; } if(pos != buf) return 1; return 0; } int autr_read_file(struct val_anchors* anchors, const char* nm) { /* the file descriptor */ FILE* fd; /* keep track of line numbers */ int line_nr = 0; /* single line */ char line[10240]; /* trust point being read */ struct trust_anchor *tp = NULL, *tp2; int r; /* for $ORIGIN parsing */ uint8_t *origin=NULL, *prev=NULL; size_t origin_len=0, prev_len=0; if (!(fd = fopen(nm, "r"))) { log_err("unable to open %s for reading: %s", nm, strerror(errno)); return 0; } verbose(VERB_ALGO, "reading autotrust anchor file %s", nm); while ( (r=read_multiline(line, sizeof(line), fd, &line_nr)) != 0) { if(r == -1 || (r = parse_var_line(line, anchors, &tp)) == -1) { log_err("could not parse auto-trust-anchor-file " "%s line %d", nm, line_nr); fclose(fd); free(origin); free(prev); return 0; } else if(r == 1) { continue; } else if(r == 2) { log_warn("trust anchor %s has been revoked", nm); fclose(fd); free(origin); free(prev); return 1; } if (!str_contains_data(line, ';')) continue; /* empty lines allowed */ if(handle_origin(line, &origin, &origin_len)) continue; r = 0; if(!(tp2=load_trustanchor(anchors, line, nm, origin, origin_len, &prev, &prev_len, &r))) { if(!r) log_err("failed to load trust anchor from %s " "at line %i, skipping", nm, line_nr); /* try to do the rest */ continue; } if(tp && tp != tp2) { log_err("file %s has mismatching data inside: " "the file may only contain keys for one name, " "remove keys for other domain names", nm); fclose(fd); free(origin); free(prev); return 0; } tp = tp2; } fclose(fd); free(origin); free(prev); if(!tp) { log_err("failed to read %s", nm); return 0; } /* now assemble the data into DNSKEY and DS packed rrsets */ lock_basic_lock(&tp->lock); if(!autr_assemble(tp)) { lock_basic_unlock(&tp->lock); log_err("malloc failure assembling %s", nm); return 0; } lock_basic_unlock(&tp->lock); return 1; } /** string for a trustanchor state */ static const char* trustanchor_state2str(autr_state_type s) { switch (s) { case AUTR_STATE_START: return " START "; case AUTR_STATE_ADDPEND: return " ADDPEND "; case AUTR_STATE_VALID: return " VALID "; case AUTR_STATE_MISSING: return " MISSING "; case AUTR_STATE_REVOKED: return " REVOKED "; case AUTR_STATE_REMOVED: return " REMOVED "; } return " UNKNOWN "; } /** ctime r for autotrust */ static char* autr_ctime_r(time_t* t, char* s) { ctime_r(t, s); #ifdef USE_WINSOCK if(strlen(s) > 10 && s[7]==' ' && s[8]=='0') s[8]=' '; /* fix error in windows ctime */ #endif return s; } /** print ID to file */ static int print_id(FILE* out, char* fname, uint8_t* nm, size_t nmlen, uint16_t dclass) { char* s = sldns_wire2str_dname(nm, nmlen); if(!s) { log_err("malloc failure in write to %s", fname); return 0; } if(fprintf(out, ";;id: %s %d\n", s, (int)dclass) < 0) { log_err("could not write to %s: %s", fname, strerror(errno)); free(s); return 0; } free(s); return 1; } static int autr_write_contents(FILE* out, char* fn, struct trust_anchor* tp) { char tmi[32]; struct autr_ta* ta; char* str; /* write pretty header */ if(fprintf(out, "; autotrust trust anchor file\n") < 0) { log_err("could not write to %s: %s", fn, strerror(errno)); return 0; } if(tp->autr->revoked) { if(fprintf(out, ";;REVOKED\n") < 0 || fprintf(out, "; The zone has all keys revoked, and is\n" "; considered as if it has no trust anchors.\n" "; the remainder of the file is the last probe.\n" "; to restart the trust anchor, overwrite this file.\n" "; with one containing valid DNSKEYs or DSes.\n") < 0) { log_err("could not write to %s: %s", fn, strerror(errno)); return 0; } } if(!print_id(out, fn, tp->name, tp->namelen, tp->dclass)) { return 0; } if(fprintf(out, ";;last_queried: %u ;;%s", (unsigned int)tp->autr->last_queried, autr_ctime_r(&(tp->autr->last_queried), tmi)) < 0 || fprintf(out, ";;last_success: %u ;;%s", (unsigned int)tp->autr->last_success, autr_ctime_r(&(tp->autr->last_success), tmi)) < 0 || fprintf(out, ";;next_probe_time: %u ;;%s", (unsigned int)tp->autr->next_probe_time, autr_ctime_r(&(tp->autr->next_probe_time), tmi)) < 0 || fprintf(out, ";;query_failed: %d\n", (int)tp->autr->query_failed)<0 || fprintf(out, ";;query_interval: %d\n", (int)tp->autr->query_interval) < 0 || fprintf(out, ";;retry_time: %d\n", (int)tp->autr->retry_time) < 0) { log_err("could not write to %s: %s", fn, strerror(errno)); return 0; } /* write anchors */ for(ta=tp->autr->keys; ta; ta=ta->next) { /* by default do not store START and REMOVED keys */ if(ta->s == AUTR_STATE_START) continue; if(ta->s == AUTR_STATE_REMOVED) continue; /* only store keys */ if(sldns_wirerr_get_type(ta->rr, ta->rr_len, ta->dname_len) != LDNS_RR_TYPE_DNSKEY) continue; str = sldns_wire2str_rr(ta->rr, ta->rr_len); if(!str || !str[0]) { free(str); log_err("malloc failure writing %s", fn); return 0; } str[strlen(str)-1] = 0; /* remove newline */ if(fprintf(out, "%s ;;state=%d [%s] ;;count=%d " ";;lastchange=%u ;;%s", str, (int)ta->s, trustanchor_state2str(ta->s), (int)ta->pending_count, (unsigned int)ta->last_change, autr_ctime_r(&(ta->last_change), tmi)) < 0) { log_err("could not write to %s: %s", fn, strerror(errno)); free(str); return 0; } free(str); } return 1; } void autr_write_file(struct module_env* env, struct trust_anchor* tp) { FILE* out; char* fname = tp->autr->file; #ifndef S_SPLINT_S long long llvalue; #endif char tempf[2048]; log_assert(tp->autr); if(!env) { log_err("autr_write_file: Module environment is NULL."); return; } /* unique name with pid number, thread number, and struct pointer * (the pointer uniquifies for multiple libunbound contexts) */ #ifndef S_SPLINT_S #if defined(SIZE_MAX) && defined(UINT32_MAX) && (UINT32_MAX == SIZE_MAX || INT32_MAX == SIZE_MAX) /* avoid warning about upcast on 32bit systems */ llvalue = (unsigned long)tp; #else llvalue = (unsigned long long)tp; #endif snprintf(tempf, sizeof(tempf), "%s.%d-%d-" ARG_LL "x", fname, (int)getpid(), env->worker?*(int*)env->worker:0, llvalue); #endif /* S_SPLINT_S */ verbose(VERB_ALGO, "autotrust: write to disk: %s", tempf); out = fopen(tempf, "w"); if(!out) { fatal_exit("could not open autotrust file for writing, %s: %s", tempf, strerror(errno)); return; } if(!autr_write_contents(out, tempf, tp)) { /* failed to write contents (completely) */ fclose(out); unlink(tempf); fatal_exit("could not completely write: %s", fname); return; } if(fflush(out) != 0) log_err("could not fflush(%s): %s", fname, strerror(errno)); #ifdef HAVE_FSYNC if(fsync(fileno(out)) != 0) log_err("could not fsync(%s): %s", fname, strerror(errno)); #else FlushFileBuffers((HANDLE)_get_osfhandle(_fileno(out))); #endif if(fclose(out) != 0) { fatal_exit("could not complete write: %s: %s", fname, strerror(errno)); unlink(tempf); return; } /* success; overwrite actual file */ verbose(VERB_ALGO, "autotrust: replaced %s", fname); #ifdef UB_ON_WINDOWS (void)unlink(fname); /* windows does not replace file with rename() */ #endif if(rename(tempf, fname) < 0) { fatal_exit("rename(%s to %s): %s", tempf, fname, strerror(errno)); } } /** * Verify if dnskey works for trust point * @param env: environment (with time) for verification * @param ve: validator environment (with options) for verification. * @param tp: trust point to verify with * @param rrset: DNSKEY rrset to verify. * @param qstate: qstate with region. * @return false on failure, true if verification successful. */ static int verify_dnskey(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* rrset, struct module_qstate* qstate) { char reasonbuf[256]; char* reason = NULL; uint8_t sigalg[ALGO_NEEDS_MAX+1]; int downprot = env->cfg->harden_algo_downgrade; enum sec_status sec = val_verify_DNSKEY_with_TA(env, ve, rrset, tp->ds_rrset, tp->dnskey_rrset, downprot?sigalg:NULL, &reason, NULL, qstate, reasonbuf, sizeof(reasonbuf)); /* sigalg is ignored, it returns algorithms signalled to exist, but * in 5011 there are no other rrsets to check. if downprot is * enabled, then it checks that the DNSKEY is signed with all * algorithms available in the trust store. */ verbose(VERB_ALGO, "autotrust: validate DNSKEY with anchor: %s", sec_status_to_string(sec)); return sec == sec_status_secure; } static int32_t rrsig_get_expiry(uint8_t* d, size_t len) { /* rrsig: 2(rdlen), 2(type) 1(alg) 1(v) 4(origttl), then 4(expi), (4)incep) */ if(len < 2+8+4) return 0; return sldns_read_uint32(d+2+8); } /** Find minimum expiration interval from signatures */ static time_t min_expiry(struct module_env* env, struct packed_rrset_data* dd) { size_t i; int32_t t, r = 15 * 24 * 3600; /* 15 days max */ for(i=dd->count; icount+dd->rrsig_count; i++) { t = rrsig_get_expiry(dd->rr_data[i], dd->rr_len[i]); if((int32_t)t - (int32_t)*env->now > 0) { t -= (int32_t)*env->now; if(t < r) r = t; } } return (time_t)r; } /** Is rr self-signed revoked key */ static int rr_is_selfsigned_revoked(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, size_t i, struct module_qstate* qstate) { enum sec_status sec; char* reason = NULL; verbose(VERB_ALGO, "seen REVOKE flag, check self-signed, rr %d", (int)i); /* no algorithm downgrade protection necessary, if it is selfsigned * revoked it can be removed. */ sec = dnskey_verify_rrset(env, ve, dnskey_rrset, dnskey_rrset, i, &reason, NULL, LDNS_SECTION_ANSWER, qstate); return (sec == sec_status_secure); } /** Set fetched value */ static void seen_trustanchor(struct autr_ta* ta, uint8_t seen) { ta->fetched = seen; if(ta->pending_count < 250) /* no numerical overflow, please */ ta->pending_count++; } /** set revoked value */ static void seen_revoked_trustanchor(struct autr_ta* ta, uint8_t revoked) { ta->revoked = revoked; } /** revoke a trust anchor */ static void revoke_dnskey(struct autr_ta* ta, int off) { uint16_t flags; uint8_t* data; if(sldns_wirerr_get_type(ta->rr, ta->rr_len, ta->dname_len) != LDNS_RR_TYPE_DNSKEY) return; if(sldns_wirerr_get_rdatalen(ta->rr, ta->rr_len, ta->dname_len) < 2) return; data = sldns_wirerr_get_rdata(ta->rr, ta->rr_len, ta->dname_len); flags = sldns_read_uint16(data); if (off && (flags&LDNS_KEY_REVOKE_KEY)) flags ^= LDNS_KEY_REVOKE_KEY; /* flip */ else flags |= LDNS_KEY_REVOKE_KEY; sldns_write_uint16(data, flags); } /** Compare two RRs skipping the REVOKED bit. Pass rdata(no len) */ static int dnskey_compare_skip_revbit(uint8_t* a, size_t a_len, uint8_t* b, size_t b_len) { size_t i; if(a_len != b_len) return -1; /* compare RRs RDATA byte for byte. */ for(i = 0; i < a_len; i++) { uint8_t rdf1, rdf2; rdf1 = a[i]; rdf2 = b[i]; if(i==1) { /* this is the second part of the flags field */ rdf1 |= LDNS_KEY_REVOKE_KEY; rdf2 |= LDNS_KEY_REVOKE_KEY; } if (rdf1 < rdf2) return -1; else if (rdf1 > rdf2) return 1; } return 0; } /** compare trust anchor with rdata, 0 if equal. Pass rdata(no len) */ static int ta_compare(struct autr_ta* a, uint16_t t, uint8_t* b, size_t b_len) { if(!a) return -1; else if(!b) return -1; else if(sldns_wirerr_get_type(a->rr, a->rr_len, a->dname_len) != t) return (int)sldns_wirerr_get_type(a->rr, a->rr_len, a->dname_len) - (int)t; else if(t == LDNS_RR_TYPE_DNSKEY) { return dnskey_compare_skip_revbit( sldns_wirerr_get_rdata(a->rr, a->rr_len, a->dname_len), sldns_wirerr_get_rdatalen(a->rr, a->rr_len, a->dname_len), b, b_len); } else if(t == LDNS_RR_TYPE_DS) { if(sldns_wirerr_get_rdatalen(a->rr, a->rr_len, a->dname_len) != b_len) return -1; return memcmp(sldns_wirerr_get_rdata(a->rr, a->rr_len, a->dname_len), b, b_len); } return -1; } /** * Find key * @param tp: to search in * @param t: rr type of the rdata. * @param rdata: to look for (no rdatalen in it) * @param rdata_len: length of rdata * @param result: returns NULL or the ta key looked for. * @return false on malloc failure during search. if true examine result. */ static int find_key(struct trust_anchor* tp, uint16_t t, uint8_t* rdata, size_t rdata_len, struct autr_ta** result) { struct autr_ta* ta; if(!tp || !rdata) { *result = NULL; return 0; } for(ta=tp->autr->keys; ta; ta=ta->next) { if(ta_compare(ta, t, rdata, rdata_len) == 0) { *result = ta; return 1; } } *result = NULL; return 1; } /** add key and clone RR and tp already locked. rdata without rdlen. */ static struct autr_ta* add_key(struct trust_anchor* tp, uint32_t ttl, uint8_t* rdata, size_t rdata_len) { struct autr_ta* ta; uint8_t* rr; size_t rr_len, dname_len; uint16_t rrtype = htons(LDNS_RR_TYPE_DNSKEY); uint16_t rrclass = htons(LDNS_RR_CLASS_IN); uint16_t rdlen = htons(rdata_len); dname_len = tp->namelen; ttl = htonl(ttl); rr_len = dname_len + 10 /* type,class,ttl,rdatalen */ + rdata_len; rr = (uint8_t*)malloc(rr_len); if(!rr) return NULL; memmove(rr, tp->name, tp->namelen); memmove(rr+dname_len, &rrtype, 2); memmove(rr+dname_len+2, &rrclass, 2); memmove(rr+dname_len+4, &ttl, 4); memmove(rr+dname_len+8, &rdlen, 2); memmove(rr+dname_len+10, rdata, rdata_len); ta = autr_ta_create(rr, rr_len, dname_len); if(!ta) { /* rr freed in autr_ta_create */ return NULL; } /* link in, tp already locked */ ta->next = tp->autr->keys; tp->autr->keys = ta; return ta; } /** get TTL from DNSKEY rrset */ static time_t key_ttl(struct ub_packed_rrset_key* k) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; return d->ttl; } /** update the time values for the trustpoint */ static void set_tp_times(struct trust_anchor* tp, time_t rrsig_exp_interval, time_t origttl, int* changed) { time_t x, qi = tp->autr->query_interval, rt = tp->autr->retry_time; /* x = MIN(15days, ttl/2, expire/2) */ x = 15 * 24 * 3600; if(origttl/2 < x) x = origttl/2; if(rrsig_exp_interval/2 < x) x = rrsig_exp_interval/2; /* MAX(1hr, x) */ if(!autr_permit_small_holddown) { if(x < 3600) tp->autr->query_interval = 3600; else tp->autr->query_interval = x; } else tp->autr->query_interval = x; /* x= MIN(1day, ttl/10, expire/10) */ x = 24 * 3600; if(origttl/10 < x) x = origttl/10; if(rrsig_exp_interval/10 < x) x = rrsig_exp_interval/10; /* MAX(1hr, x) */ if(!autr_permit_small_holddown) { if(x < 3600) tp->autr->retry_time = 3600; else tp->autr->retry_time = x; } else tp->autr->retry_time = x; if(qi != tp->autr->query_interval || rt != tp->autr->retry_time) { *changed = 1; verbose(VERB_ALGO, "orig_ttl is %d", (int)origttl); verbose(VERB_ALGO, "rrsig_exp_interval is %d", (int)rrsig_exp_interval); verbose(VERB_ALGO, "query_interval: %d, retry_time: %d", (int)tp->autr->query_interval, (int)tp->autr->retry_time); } } /** init events to zero */ static void init_events(struct trust_anchor* tp) { struct autr_ta* ta; for(ta=tp->autr->keys; ta; ta=ta->next) { ta->fetched = 0; } } /** check for revoked keys without trusting any other information */ static void check_contains_revoked(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, int* changed, struct module_qstate* qstate) { struct packed_rrset_data* dd = (struct packed_rrset_data*) dnskey_rrset->entry.data; size_t i; log_assert(ntohs(dnskey_rrset->rk.type) == LDNS_RR_TYPE_DNSKEY); for(i=0; icount; i++) { struct autr_ta* ta = NULL; if(!rr_is_dnskey_sep(ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2) || !rr_is_dnskey_revoked(ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2)) continue; /* not a revoked KSK */ if(!find_key(tp, ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2, &ta)) { log_err("malloc failure"); continue; /* malloc fail in compare*/ } if(!ta) continue; /* key not found */ if(rr_is_selfsigned_revoked(env, ve, dnskey_rrset, i, qstate)) { /* checked if there is an rrsig signed by this key. */ /* same keytag, but stored can be revoked already, so * compare keytags, with +0 or +128(REVOKE flag) */ log_assert(dnskey_calc_keytag(dnskey_rrset, i)-128 == sldns_calc_keytag_raw(sldns_wirerr_get_rdata( ta->rr, ta->rr_len, ta->dname_len), sldns_wirerr_get_rdatalen(ta->rr, ta->rr_len, ta->dname_len)) || dnskey_calc_keytag(dnskey_rrset, i) == sldns_calc_keytag_raw(sldns_wirerr_get_rdata( ta->rr, ta->rr_len, ta->dname_len), sldns_wirerr_get_rdatalen(ta->rr, ta->rr_len, ta->dname_len))); /* checks conversion*/ verbose_key(ta, VERB_ALGO, "is self-signed revoked"); if(!ta->revoked) *changed = 1; seen_revoked_trustanchor(ta, 1); do_revoked(env, ta, changed); } } } /** See if a DNSKEY is verified by one of the DSes */ static int key_matches_a_ds(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, size_t key_idx, struct ub_packed_rrset_key* ds_rrset) { struct packed_rrset_data* dd = (struct packed_rrset_data*) ds_rrset->entry.data; size_t ds_idx, num = dd->count; int d = val_favorite_ds_algo(ds_rrset); char* reason = ""; for(ds_idx=0; ds_idxentry.data; size_t i; log_assert(ntohs(dnskey_rrset->rk.type) == LDNS_RR_TYPE_DNSKEY); init_events(tp); for(i=0; icount; i++) { struct autr_ta* ta = NULL; if(!rr_is_dnskey_sep(ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2)) continue; if(rr_is_dnskey_revoked(ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2)) { /* self-signed revoked keys already detected before, * other revoked keys are not 'added' again */ continue; } /* is a key of this type supported?. Note rr_list and * packed_rrset are in the same order. */ if(!dnskey_algo_is_supported(dnskey_rrset, i) || !dnskey_size_is_supported(dnskey_rrset, i)) { /* skip unknown algorithm key, it is useless to us */ log_nametypeclass(VERB_DETAIL, "trust point has " "unsupported algorithm at", tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); continue; } /* is it new? if revocation bit set, find the unrevoked key */ if(!find_key(tp, ntohs(dnskey_rrset->rk.type), dd->rr_data[i]+2, dd->rr_len[i]-2, &ta)) { return 0; } if(!ta) { ta = add_key(tp, (uint32_t)dd->rr_ttl[i], dd->rr_data[i]+2, dd->rr_len[i]-2); *changed = 1; /* first time seen, do we have DSes? if match: VALID */ if(ta && tp->ds_rrset && key_matches_a_ds(env, ve, dnskey_rrset, i, tp->ds_rrset)) { verbose_key(ta, VERB_ALGO, "verified by DS"); ta->s = AUTR_STATE_VALID; } } if(!ta) { return 0; } seen_trustanchor(ta, 1); verbose_key(ta, VERB_ALGO, "in DNS response"); } set_tp_times(tp, min_expiry(env, dd), key_ttl(dnskey_rrset), changed); return 1; } /** * Check if the holddown time has already exceeded * setting: add-holddown: add holddown timer * setting: del-holddown: del holddown timer * @param env: environment with current time * @param ta: trust anchor to check for. * @param holddown: the timer value * @return number of seconds the holddown has passed. */ static time_t check_holddown(struct module_env* env, struct autr_ta* ta, unsigned int holddown) { time_t elapsed; if(*env->now < ta->last_change) { log_warn("time goes backwards. delaying key holddown"); return 0; } elapsed = *env->now - ta->last_change; if (elapsed > (time_t)holddown) { return elapsed-(time_t)holddown; } verbose_key(ta, VERB_ALGO, "holddown time " ARG_LL "d seconds to go", (long long) ((time_t)holddown-elapsed)); return 0; } /** Set last_change to now */ static void reset_holddown(struct module_env* env, struct autr_ta* ta, int* changed) { ta->last_change = *env->now; *changed = 1; } /** Set the state for this trust anchor */ static void set_trustanchor_state(struct module_env* env, struct autr_ta* ta, int* changed, autr_state_type s) { verbose_key(ta, VERB_ALGO, "update: %s to %s", trustanchor_state2str(ta->s), trustanchor_state2str(s)); ta->s = s; reset_holddown(env, ta, changed); } /** Event: NewKey */ static void do_newkey(struct module_env* env, struct autr_ta* anchor, int* c) { if (anchor->s == AUTR_STATE_START) set_trustanchor_state(env, anchor, c, AUTR_STATE_ADDPEND); } /** Event: AddTime */ static void do_addtime(struct module_env* env, struct autr_ta* anchor, int* c) { /* This not according to RFC, this is 30 days, but the RFC demands * MAX(30days, TTL expire time of first DNSKEY set with this key), * The value may be too small if a very large TTL was used. */ time_t exceeded = check_holddown(env, anchor, env->cfg->add_holddown); if (exceeded && anchor->s == AUTR_STATE_ADDPEND) { verbose_key(anchor, VERB_ALGO, "add-holddown time exceeded " ARG_LL "d seconds ago, and pending-count %d", (long long)exceeded, anchor->pending_count); if(anchor->pending_count >= MIN_PENDINGCOUNT) { set_trustanchor_state(env, anchor, c, AUTR_STATE_VALID); anchor->pending_count = 0; return; } verbose_key(anchor, VERB_ALGO, "add-holddown time sanity check " "failed (pending count: %d)", anchor->pending_count); } } /** Event: RemTime */ static void do_remtime(struct module_env* env, struct autr_ta* anchor, int* c) { time_t exceeded = check_holddown(env, anchor, env->cfg->del_holddown); if(exceeded && anchor->s == AUTR_STATE_REVOKED) { verbose_key(anchor, VERB_ALGO, "del-holddown time exceeded " ARG_LL "d seconds ago", (long long)exceeded); set_trustanchor_state(env, anchor, c, AUTR_STATE_REMOVED); } } /** Event: KeyRem */ static void do_keyrem(struct module_env* env, struct autr_ta* anchor, int* c) { if(anchor->s == AUTR_STATE_ADDPEND) { set_trustanchor_state(env, anchor, c, AUTR_STATE_START); anchor->pending_count = 0; } else if(anchor->s == AUTR_STATE_VALID) set_trustanchor_state(env, anchor, c, AUTR_STATE_MISSING); } /** Event: KeyPres */ static void do_keypres(struct module_env* env, struct autr_ta* anchor, int* c) { if(anchor->s == AUTR_STATE_MISSING) set_trustanchor_state(env, anchor, c, AUTR_STATE_VALID); } /* Event: Revoked */ static void do_revoked(struct module_env* env, struct autr_ta* anchor, int* c) { if(anchor->s == AUTR_STATE_VALID || anchor->s == AUTR_STATE_MISSING) { set_trustanchor_state(env, anchor, c, AUTR_STATE_REVOKED); verbose_key(anchor, VERB_ALGO, "old id, prior to revocation"); revoke_dnskey(anchor, 0); verbose_key(anchor, VERB_ALGO, "new id, after revocation"); } } /** Do statestable transition matrix for anchor */ static void anchor_state_update(struct module_env* env, struct autr_ta* anchor, int* c) { log_assert(anchor); switch(anchor->s) { /* START */ case AUTR_STATE_START: /* NewKey: ADDPEND */ if (anchor->fetched) do_newkey(env, anchor, c); break; /* ADDPEND */ case AUTR_STATE_ADDPEND: /* KeyRem: START */ if (!anchor->fetched) do_keyrem(env, anchor, c); /* AddTime: VALID */ else do_addtime(env, anchor, c); break; /* VALID */ case AUTR_STATE_VALID: /* RevBit: REVOKED */ if (anchor->revoked) do_revoked(env, anchor, c); /* KeyRem: MISSING */ else if (!anchor->fetched) do_keyrem(env, anchor, c); else if(!anchor->last_change) { verbose_key(anchor, VERB_ALGO, "first seen"); reset_holddown(env, anchor, c); } break; /* MISSING */ case AUTR_STATE_MISSING: /* RevBit: REVOKED */ if (anchor->revoked) do_revoked(env, anchor, c); /* KeyPres */ else if (anchor->fetched) do_keypres(env, anchor, c); break; /* REVOKED */ case AUTR_STATE_REVOKED: if (anchor->fetched) reset_holddown(env, anchor, c); /* RemTime: REMOVED */ else do_remtime(env, anchor, c); break; /* REMOVED */ case AUTR_STATE_REMOVED: default: break; } } /** if ZSK init then trust KSKs */ static int init_zsk_to_ksk(struct module_env* env, struct trust_anchor* tp, int* changed) { /* search for VALID ZSKs */ struct autr_ta* anchor; int validzsk = 0; int validksk = 0; for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { /* last_change test makes sure it was manually configured */ if(sldns_wirerr_get_type(anchor->rr, anchor->rr_len, anchor->dname_len) == LDNS_RR_TYPE_DNSKEY && anchor->last_change == 0 && !ta_is_dnskey_sep(anchor) && anchor->s == AUTR_STATE_VALID) validzsk++; } if(validzsk == 0) return 0; for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { if (ta_is_dnskey_sep(anchor) && anchor->s == AUTR_STATE_ADDPEND) { verbose_key(anchor, VERB_ALGO, "trust KSK from " "ZSK(config)"); set_trustanchor_state(env, anchor, changed, AUTR_STATE_VALID); validksk++; } } return validksk; } /** Remove missing trustanchors so the list does not grow forever */ static void remove_missing_trustanchors(struct module_env* env, struct trust_anchor* tp, int* changed) { struct autr_ta* anchor; time_t exceeded; int valid = 0; /* see if we have anchors that are valid */ for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { /* Only do KSKs */ if (!ta_is_dnskey_sep(anchor)) continue; if (anchor->s == AUTR_STATE_VALID) valid++; } /* if there are no SEP Valid anchors, see if we started out with * a ZSK (last-change=0) anchor, which is VALID and there are KSKs * now that can be made valid. Do this immediately because there * is no guarantee that the ZSKs get announced long enough. Usually * this is immediately after init with a ZSK trusted, unless the domain * was not advertising any KSKs at all. In which case we perfectly * track the zero number of KSKs. */ if(valid == 0) { valid = init_zsk_to_ksk(env, tp, changed); if(valid == 0) return; } for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { /* ignore ZSKs if newly added */ if(anchor->s == AUTR_STATE_START) continue; /* remove ZSKs if a KSK is present */ if (!ta_is_dnskey_sep(anchor)) { if(valid > 0) { verbose_key(anchor, VERB_ALGO, "remove ZSK " "[%d key(s) VALID]", valid); set_trustanchor_state(env, anchor, changed, AUTR_STATE_REMOVED); } continue; } /* Only do MISSING keys */ if (anchor->s != AUTR_STATE_MISSING) continue; if(env->cfg->keep_missing == 0) continue; /* keep forever */ exceeded = check_holddown(env, anchor, env->cfg->keep_missing); /* If keep_missing has exceeded and we still have more than * one valid KSK: remove missing trust anchor */ if (exceeded && valid > 0) { verbose_key(anchor, VERB_ALGO, "keep-missing time " "exceeded " ARG_LL "d seconds ago, [%d key(s) VALID]", (long long)exceeded, valid); set_trustanchor_state(env, anchor, changed, AUTR_STATE_REMOVED); } } } /** Do the statetable from RFC5011 transition matrix */ static int do_statetable(struct module_env* env, struct trust_anchor* tp, int* changed) { struct autr_ta* anchor; for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { /* Only do KSKs */ if(!ta_is_dnskey_sep(anchor)) continue; anchor_state_update(env, anchor, changed); } remove_missing_trustanchors(env, tp, changed); return 1; } /** See if time alone makes ADDPEND to VALID transition */ static void autr_holddown_exceed(struct module_env* env, struct trust_anchor* tp, int* c) { struct autr_ta* anchor; for(anchor = tp->autr->keys; anchor; anchor = anchor->next) { if(ta_is_dnskey_sep(anchor) && anchor->s == AUTR_STATE_ADDPEND) do_addtime(env, anchor, c); } } /** cleanup key list */ static void autr_cleanup_keys(struct trust_anchor* tp) { struct autr_ta* p, **prevp; prevp = &tp->autr->keys; p = tp->autr->keys; while(p) { /* do we want to remove this key? */ if(p->s == AUTR_STATE_START || p->s == AUTR_STATE_REMOVED || sldns_wirerr_get_type(p->rr, p->rr_len, p->dname_len) != LDNS_RR_TYPE_DNSKEY) { struct autr_ta* np = p->next; /* remove */ free(p->rr); free(p); /* snip and go to next item */ *prevp = np; p = np; continue; } /* remove pending counts if no longer pending */ if(p->s != AUTR_STATE_ADDPEND) p->pending_count = 0; prevp = &p->next; p = p->next; } } /** calculate next probe time */ static time_t calc_next_probe(struct module_env* env, time_t wait) { /* make it random, 90-100% */ time_t rnd, rest; if(!autr_permit_small_holddown) { if(wait < 3600) wait = 3600; } else { if(wait == 0) wait = 1; } rnd = wait/10; rest = wait-rnd; rnd = (time_t)ub_random_max(env->rnd, (long int)rnd); return (time_t)(*env->now + rest + rnd); } /** what is first probe time (anchors must be locked) */ static time_t wait_probe_time(struct val_anchors* anchors) { rbnode_type* t = rbtree_first(&anchors->autr->probe); if(t != RBTREE_NULL) return ((struct trust_anchor*)t->key)->autr->next_probe_time; return 0; } /** reset worker timer, at the time from wait_probe_time. */ static void reset_worker_timer_at(struct module_env* env, time_t next) { struct timeval tv; #ifndef S_SPLINT_S /* in case this is libunbound, no timer */ if(!env->probe_timer) return; if(next > *env->now) tv.tv_sec = (time_t)(next - *env->now); else tv.tv_sec = 0; #else (void)next; #endif tv.tv_usec = 0; comm_timer_set(env->probe_timer, &tv); verbose(VERB_ALGO, "scheduled next probe in " ARG_LL "d sec", (long long)tv.tv_sec); } /** reset worker timer. This routine manages the locks on acquiring the * next time for the timer. */ static void reset_worker_timer(struct module_env* env) { time_t next; if(!env->anchors) return; lock_basic_lock(&env->anchors->lock); next = wait_probe_time(env->anchors); lock_basic_unlock(&env->anchors->lock); reset_worker_timer_at(env, next); } /** set next probe for trust anchor */ static int set_next_probe(struct module_env* env, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset) { struct trust_anchor key, *tp2; time_t mold, mnew; /* use memory allocated in rrset for temporary name storage */ key.node.key = &key; key.name = dnskey_rrset->rk.dname; key.namelen = dnskey_rrset->rk.dname_len; key.namelabs = dname_count_labels(key.name); key.dclass = tp->dclass; lock_basic_unlock(&tp->lock); /* fetch tp again and lock anchors, so that we can modify the trees */ lock_basic_lock(&env->anchors->lock); tp2 = (struct trust_anchor*)rbtree_search(env->anchors->tree, &key); if(!tp2) { verbose(VERB_ALGO, "trustpoint was deleted in set_next_probe"); lock_basic_unlock(&env->anchors->lock); return 0; } log_assert(tp == tp2); lock_basic_lock(&tp->lock); /* schedule */ mold = wait_probe_time(env->anchors); (void)rbtree_delete(&env->anchors->autr->probe, tp); tp->autr->next_probe_time = calc_next_probe(env, tp->autr->query_interval); (void)rbtree_insert(&env->anchors->autr->probe, &tp->autr->pnode); mnew = wait_probe_time(env->anchors); lock_basic_unlock(&env->anchors->lock); verbose(VERB_ALGO, "next probe set in %d seconds", (int)tp->autr->next_probe_time - (int)*env->now); if(mold != mnew) { reset_worker_timer_at(env, mnew); } return 1; } /** Revoke and Delete a trust point */ static void autr_tp_remove(struct module_env* env, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset) { struct trust_anchor* del_tp; struct trust_anchor key; struct autr_point_data pd; time_t mold, mnew; log_nametypeclass(VERB_OPS, "trust point was revoked", tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); tp->autr->revoked = 1; /* use space allocated for dnskey_rrset to save name of anchor */ memset(&key, 0, sizeof(key)); memset(&pd, 0, sizeof(pd)); key.autr = &pd; key.node.key = &key; pd.pnode.key = &key; pd.next_probe_time = tp->autr->next_probe_time; key.name = dnskey_rrset->rk.dname; key.namelen = tp->namelen; key.namelabs = tp->namelabs; key.dclass = tp->dclass; /* unlock */ lock_basic_unlock(&tp->lock); /* take from tree. It could be deleted by someone else,hence (void). */ lock_basic_lock(&env->anchors->lock); del_tp = (struct trust_anchor*)rbtree_delete(env->anchors->tree, &key); mold = wait_probe_time(env->anchors); (void)rbtree_delete(&env->anchors->autr->probe, &key); mnew = wait_probe_time(env->anchors); anchors_init_parents_locked(env->anchors); lock_basic_unlock(&env->anchors->lock); /* if !del_tp then the trust point is no longer present in the tree, * it was deleted by someone else, who will write the zonefile and * clean up the structure */ if(del_tp) { /* save on disk */ del_tp->autr->next_probe_time = 0; /* no more probing for it */ autr_write_file(env, del_tp); /* delete */ autr_point_delete(del_tp); } if(mold != mnew) { reset_worker_timer_at(env, mnew); } } int autr_process_prime(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, struct module_qstate* qstate) { int changed = 0; log_assert(tp && tp->autr); /* autotrust update trust anchors */ /* the tp is locked, and stays locked unless it is deleted */ /* we could just catch the anchor here while another thread * is busy deleting it. Just unlock and let the other do its job */ if(tp->autr->revoked) { log_nametypeclass(VERB_ALGO, "autotrust not processed, " "trust point revoked", tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); lock_basic_unlock(&tp->lock); return 0; /* it is revoked */ } /* query_dnskeys(): */ tp->autr->last_queried = *env->now; log_nametypeclass(VERB_ALGO, "autotrust process for", tp->name, LDNS_RR_TYPE_DNSKEY, tp->dclass); /* see if time alone makes some keys valid */ autr_holddown_exceed(env, tp, &changed); if(changed) { verbose(VERB_ALGO, "autotrust: morekeys, reassemble"); if(!autr_assemble(tp)) { log_err("malloc failure assembling autotrust keys"); return 1; /* unchanged */ } } /* did we get any data? */ if(!dnskey_rrset) { verbose(VERB_ALGO, "autotrust: no dnskey rrset"); /* no update of query_failed, because then we would have * to write to disk. But we cannot because we maybe are * still 'initializing' with DS records, that we cannot write * in the full format (which only contains KSKs). */ return 1; /* trust point exists */ } /* check for revoked keys to remove immediately */ check_contains_revoked(env, ve, tp, dnskey_rrset, &changed, qstate); if(changed) { verbose(VERB_ALGO, "autotrust: revokedkeys, reassemble"); if(!autr_assemble(tp)) { log_err("malloc failure assembling autotrust keys"); return 1; /* unchanged */ } if(!tp->ds_rrset && !tp->dnskey_rrset) { /* no more keys, all are revoked */ /* this is a success for this probe attempt */ tp->autr->last_success = *env->now; autr_tp_remove(env, tp, dnskey_rrset); return 0; /* trust point removed */ } } /* verify the dnskey rrset and see if it is valid. */ if(!verify_dnskey(env, ve, tp, dnskey_rrset, qstate)) { verbose(VERB_ALGO, "autotrust: dnskey did not verify."); /* only increase failure count if this is not the first prime, * this means there was a previous successful probe */ if(tp->autr->last_success) { tp->autr->query_failed += 1; autr_write_file(env, tp); } return 1; /* trust point exists */ } tp->autr->last_success = *env->now; tp->autr->query_failed = 0; /* Add new trust anchors to the data structure * - note which trust anchors are seen this probe. * Set trustpoint query_interval and retry_time. * - find minimum rrsig expiration interval */ if(!update_events(env, ve, tp, dnskey_rrset, &changed)) { log_err("malloc failure in autotrust update_events. " "trust point unchanged."); return 1; /* trust point unchanged, so exists */ } /* - for every SEP key do the 5011 statetable. * - remove missing trustanchors (if veryold and we have new anchors). */ if(!do_statetable(env, tp, &changed)) { log_err("malloc failure in autotrust do_statetable. " "trust point unchanged."); return 1; /* trust point unchanged, so exists */ } autr_cleanup_keys(tp); if(!set_next_probe(env, tp, dnskey_rrset)) return 0; /* trust point does not exist */ autr_write_file(env, tp); if(changed) { verbose(VERB_ALGO, "autotrust: changed, reassemble"); if(!autr_assemble(tp)) { log_err("malloc failure assembling autotrust keys"); return 1; /* unchanged */ } if(!tp->ds_rrset && !tp->dnskey_rrset) { /* no more keys, all are revoked */ autr_tp_remove(env, tp, dnskey_rrset); return 0; /* trust point removed */ } } else verbose(VERB_ALGO, "autotrust: no changes"); return 1; /* trust point exists */ } /** debug print a trust anchor key */ static void autr_debug_print_ta(struct autr_ta* ta) { char buf[32]; char* str = sldns_wire2str_rr(ta->rr, ta->rr_len); if(!str) { log_info("out of memory in debug_print_ta"); return; } if(str[0]) str[strlen(str)-1]=0; /* remove newline */ (void)autr_ctime_r(&ta->last_change, buf); if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ log_info("[%s] %s ;;state:%d ;;pending_count:%d%s%s last:%s", trustanchor_state2str(ta->s), str, ta->s, ta->pending_count, ta->fetched?" fetched":"", ta->revoked?" revoked":"", buf); free(str); } /** debug print a trust point */ static void autr_debug_print_tp(struct trust_anchor* tp) { struct autr_ta* ta; /* Note: buf is also used for autr_ctime_r but that only needs a size * of 26, so LDNS_MAX_DOMAINLEN is enough. */ char buf[LDNS_MAX_DOMAINLEN]; if(!tp->autr) return; dname_str(tp->name, buf); log_info("trust point %s : %d", buf, (int)tp->dclass); log_info("assembled %d DS and %d DNSKEYs", (int)tp->numDS, (int)tp->numDNSKEY); if(tp->ds_rrset) { log_packed_rrset(NO_VERBOSE, "DS:", tp->ds_rrset); } if(tp->dnskey_rrset) { log_packed_rrset(NO_VERBOSE, "DNSKEY:", tp->dnskey_rrset); } log_info("file %s", tp->autr->file); (void)autr_ctime_r(&tp->autr->last_queried, buf); if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ log_info("last_queried: %u %s", (unsigned)tp->autr->last_queried, buf); (void)autr_ctime_r(&tp->autr->last_success, buf); if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ log_info("last_success: %u %s", (unsigned)tp->autr->last_success, buf); (void)autr_ctime_r(&tp->autr->next_probe_time, buf); if(buf[0]) buf[strlen(buf)-1]=0; /* remove newline */ log_info("next_probe_time: %u %s", (unsigned)tp->autr->next_probe_time, buf); log_info("query_interval: %u", (unsigned)tp->autr->query_interval); log_info("retry_time: %u", (unsigned)tp->autr->retry_time); log_info("query_failed: %u", (unsigned)tp->autr->query_failed); for(ta=tp->autr->keys; ta; ta=ta->next) { autr_debug_print_ta(ta); } } void autr_debug_print(struct val_anchors* anchors) { struct trust_anchor* tp; lock_basic_lock(&anchors->lock); RBTREE_FOR(tp, struct trust_anchor*, anchors->tree) { lock_basic_lock(&tp->lock); autr_debug_print_tp(tp); lock_basic_unlock(&tp->lock); } lock_basic_unlock(&anchors->lock); } void probe_answer_cb(void* arg, int ATTR_UNUSED(rcode), sldns_buffer* ATTR_UNUSED(buf), enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited)) { /* retry was set before the query was done, * re-querytime is set when query succeeded, but that may not * have reset this timer because the query could have been * handled by another thread. In that case, this callback would * get called after the original timeout is done. * By not resetting the timer, it may probe more often, but not * less often. * Unless the new lookup resulted in smaller TTLs and thus smaller * timeout values. In that case one old TTL could be mistakenly done. */ struct module_env* env = (struct module_env*)arg; verbose(VERB_ALGO, "autotrust probe answer cb"); reset_worker_timer(env); } /** probe a trust anchor DNSKEY and unlocks tp */ static void probe_anchor(struct module_env* env, struct trust_anchor* tp) { struct query_info qinfo; uint16_t qflags = BIT_RD; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; qinfo.qname = regional_alloc_init(env->scratch, tp->name, tp->namelen); if(!qinfo.qname) { log_err("out of memory making 5011 probe"); return; } 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); edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; edns.padding_block_size = 0; edns.cookie_present = 0; edns.cookie_valid = 0; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* can't hold the lock while mesh_run is processing */ lock_basic_unlock(&tp->lock); /* delete the DNSKEY from rrset and key cache so an active probe * is done. First the rrset so another thread does not use it * to recreate the key entry in a race condition. */ rrset_cache_remove(env->rrset_cache, qinfo.qname, qinfo.qname_len, qinfo.qtype, qinfo.qclass, 0); key_cache_remove(env->key_cache, qinfo.qname, qinfo.qname_len, qinfo.qclass); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &probe_answer_cb, env, 0)) { log_err("out of memory making 5011 probe"); } } /** fetch first to-probe trust-anchor and lock it and set retrytime */ static struct trust_anchor* todo_probe(struct module_env* env, time_t* next) { struct trust_anchor* tp; rbnode_type* el; /* get first one */ lock_basic_lock(&env->anchors->lock); if( (el=rbtree_first(&env->anchors->autr->probe)) == RBTREE_NULL) { /* in case of revoked anchors */ lock_basic_unlock(&env->anchors->lock); /* signal that there are no anchors to probe */ *next = 0; return NULL; } tp = (struct trust_anchor*)el->key; lock_basic_lock(&tp->lock); /* is it eligible? */ if((time_t)tp->autr->next_probe_time > *env->now) { /* no more to probe */ *next = (time_t)tp->autr->next_probe_time - *env->now; lock_basic_unlock(&tp->lock); lock_basic_unlock(&env->anchors->lock); return NULL; } /* reset its next probe time */ (void)rbtree_delete(&env->anchors->autr->probe, tp); tp->autr->next_probe_time = calc_next_probe(env, tp->autr->retry_time); (void)rbtree_insert(&env->anchors->autr->probe, &tp->autr->pnode); lock_basic_unlock(&env->anchors->lock); return tp; } time_t autr_probe_timer(struct module_env* env) { struct trust_anchor* tp; time_t next_probe = 3600; int num = 0; if(autr_permit_small_holddown) next_probe = 1; verbose(VERB_ALGO, "autotrust probe timer callback"); /* while there are still anchors to probe */ while( (tp = todo_probe(env, &next_probe)) ) { /* make a probe for this anchor */ probe_anchor(env, tp); num++; } regional_free_all(env->scratch); if(next_probe == 0) return 0; /* no trust points to probe */ verbose(VERB_ALGO, "autotrust probe timer %d callbacks done", num); return next_probe; } unbound-1.25.1/validator/val_kentry.h0000644000175000017500000001752315203270263017232 0ustar wouterwouter/* * validator/val_kentry.h - validator key entry definition. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions for dealing with validator key entries. */ #ifndef VALIDATOR_VAL_KENTRY_H #define VALIDATOR_VAL_KENTRY_H struct packed_rrset_data; struct regional; struct ub_packed_rrset_key; #include "util/storage/lruhash.h" #include "sldns/rrdef.h" /** * A key entry for the validator. * This may or may not be a trusted key. * This is what is stored in the key cache. * This is the key part for the cache; the key entry key. */ struct key_entry_key { /** lru hash entry */ struct lruhash_entry entry; /** name of the key */ uint8_t* name; /** length of name */ size_t namelen; /** class of the key, host byteorder */ uint16_t key_class; }; /** * Key entry for the validator. * Contains key status. * This is the data part for the cache, the key entry data. * * Can be in three basic states: * isbad=0: good key * isbad=1: bad key * isbad=0 && rrset=0: insecure space. */ struct key_entry_data { /** the TTL of this entry (absolute time) */ time_t ttl; /** the key rrdata. can be NULL to signal keyless name. */ struct packed_rrset_data* rrset_data; /** not NULL sometimes to give reason why bogus */ char* reason; /** not NULL to give reason why bogus */ sldns_ede_code reason_bogus; /** list of algorithms signalled, ends with 0, or NULL */ uint8_t* algo; /** DNS RR type of the rrset data (host order) */ uint16_t rrset_type; /** if the key is bad: Bogus or malformed */ uint8_t isbad; }; /** function for lruhash operation */ size_t key_entry_sizefunc(void* key, void* data); /** function for lruhash operation */ int key_entry_compfunc(void* k1, void* k2); /** function for lruhash operation */ void key_entry_delkeyfunc(void* key, void* userarg); /** function for lruhash operation */ void key_entry_deldatafunc(void* data, void* userarg); /** calculate hash for key entry * @param kk: key entry. The lruhash entry.hash value is filled in. */ void key_entry_hash(struct key_entry_key* kk); /** * Copy a key entry, to be region-allocated. * @param kkey: the key entry key (and data pointer) to copy. * @param region: where to allocate it * @return newly region-allocated entry or NULL on a failure to allocate. */ struct key_entry_key* key_entry_copy_toregion(struct key_entry_key* kkey, struct regional* region); /** * Copy a key entry, malloced. * @param kkey: the key entry key (and data pointer) to copy. * @param copy_reason: if the reason string needs to be copied (allocated). * @return newly allocated entry or NULL on a failure to allocate memory. */ struct key_entry_key* key_entry_copy(struct key_entry_key* kkey, int copy_reason); /** * See if this is a null entry. Does not do locking. * @param kkey: must have data pointer set correctly * @return true if it is a NULL rrset entry. */ int key_entry_isnull(struct key_entry_key* kkey); /** * See if this entry is good. Does not do locking. * @param kkey: must have data pointer set correctly * @return true if it is good. */ int key_entry_isgood(struct key_entry_key* kkey); /** * See if this entry is bad. Does not do locking. * @param kkey: must have data pointer set correctly * @return true if it is bad. */ int key_entry_isbad(struct key_entry_key* kkey); /** * Get reason why a key is bad. * @param kkey: bad key * @return pointer to string. * String is part of key entry and is deleted with it. */ char* key_entry_get_reason(struct key_entry_key* kkey); /** * Get the EDE (RFC8914) code why a key is bad. Can return LDNS_EDE_NONE. * @param kkey: bad key * @return the ede code. */ sldns_ede_code key_entry_get_reason_bogus(struct key_entry_key* kkey); /** * Create a null entry, in the given region. * @param region: where to allocate * @param name: the key name * @param namelen: length of name * @param dclass: class of key entry. (host order); * @param ttl: what ttl should the key have. relative. * @param reason_bogus: accompanying EDE code. * @param reason: accompanying NULL-terminated EDE string (or NULL). * @param now: current time (added to ttl). * @return new key entry or NULL on alloc failure */ struct key_entry_key* key_entry_create_null(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl, sldns_ede_code reason_bogus, const char* reason, time_t now); /** * Create a key entry from an rrset, in the given region. * @param region: where to allocate. * @param name: the key name * @param namelen: length of name * @param dclass: class of key entry. (host order); * @param rrset: data for key entry. This is copied to the region. * @param sigalg: signalled algorithm list (or NULL). * @param reason_bogus: accompanying EDE code (usually LDNS_EDE_NONE). * @param reason: accompanying NULL-terminated EDE string (or NULL). * @param now: current time (added to ttl of rrset) * @return new key entry or NULL on alloc failure */ struct key_entry_key* key_entry_create_rrset(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, struct ub_packed_rrset_key* rrset, uint8_t* sigalg, sldns_ede_code reason_bogus, const char* reason, time_t now); /** * Create a bad entry, in the given region. * @param region: where to allocate * @param name: the key name * @param namelen: length of name * @param dclass: class of key entry. (host order); * @param ttl: what ttl should the key have. relative. * @param reason_bogus: accompanying EDE code. * @param reason: accompanying NULL-terminated EDE string (or NULL). * @param now: current time (added to ttl). * @return new key entry or NULL on alloc failure */ struct key_entry_key* key_entry_create_bad(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl, sldns_ede_code reason_bogus, const char* reason, time_t now); /** * Obtain rrset from a key entry, allocated in region. * @param kkey: key entry to convert to a rrset. * @param region: where to allocate rrset * @return rrset copy; if no rrset or alloc error returns NULL. */ struct ub_packed_rrset_key* key_entry_get_rrset(struct key_entry_key* kkey, struct regional* region); /** * Get keysize of the keyentry. * @param kkey: key, must be a good key, with contents. * @return size in bits of the key. */ size_t key_entry_keysize(struct key_entry_key* kkey); #endif /* VALIDATOR_VAL_KENTRY_H */ unbound-1.25.1/validator/val_sigcrypt.c0000644000175000017500000014316215203270263017554 0ustar wouterwouter/* * validator/val_sigcrypt.c - validator signature crypto functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with signature verification and checking, the * bridging between RR wireformat data and crypto calls. */ #include "config.h" #include "validator/val_sigcrypt.h" #include "validator/val_secalgo.h" #include "validator/validator.h" #include "util/data/msgreply.h" #include "util/data/msgparse.h" #include "util/data/dname.h" #include "util/rbtree.h" #include "util/rfc_1982.h" #include "util/module.h" #include "util/net_help.h" #include "util/regional.h" #include "util/config_file.h" #include "sldns/keyraw.h" #include "sldns/sbuffer.h" #include "sldns/parseutil.h" #include "sldns/wire2str.h" #include "services/mesh.h" #include #if !defined(HAVE_SSL) && !defined(HAVE_NSS) && !defined(HAVE_NETTLE) #error "Need crypto library to do digital signature cryptography" #endif #ifdef HAVE_OPENSSL_ERR_H #include #endif #ifdef HAVE_OPENSSL_RAND_H #include #endif #ifdef HAVE_OPENSSL_CONF_H #include #endif #ifdef HAVE_OPENSSL_ENGINE_H #include #endif /** Maximum number of RRSIG validations for an RRset. */ #define MAX_VALIDATE_RRSIGS 8 /** return number of rrs in an rrset */ static size_t rrset_get_count(struct ub_packed_rrset_key* rrset) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; if(!d) return 0; return d->count; } /** * Get RR signature count */ static size_t rrset_get_sigcount(struct ub_packed_rrset_key* k) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; return d->rrsig_count; } /** * Get signature keytag value * @param k: rrset (with signatures) * @param sig_idx: signature index. * @return keytag or 0 if malformed rrsig. */ static uint16_t rrset_get_sig_keytag(struct ub_packed_rrset_key* k, size_t sig_idx) { uint16_t t; struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; log_assert(sig_idx < d->rrsig_count); if(d->rr_len[d->count + sig_idx] < 2+18) return 0; memmove(&t, d->rr_data[d->count + sig_idx]+2+16, 2); return ntohs(t); } /** * Get signature signing algorithm value * @param k: rrset (with signatures) * @param sig_idx: signature index. * @return algo or 0 if malformed rrsig. */ static int rrset_get_sig_algo(struct ub_packed_rrset_key* k, size_t sig_idx) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; log_assert(sig_idx < d->rrsig_count); if(d->rr_len[d->count + sig_idx] < 2+3) return 0; return (int)d->rr_data[d->count + sig_idx][2+2]; } /** get rdata pointer and size */ static void rrset_get_rdata(struct ub_packed_rrset_key* k, size_t idx, uint8_t** rdata, size_t* len) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; log_assert(d && idx < (d->count + d->rrsig_count)); *rdata = d->rr_data[idx]; *len = d->rr_len[idx]; } uint16_t dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx) { uint8_t* rdata; size_t len; uint16_t f; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+2) return 0; memmove(&f, rdata+2, 2); f = ntohs(f); return f; } /** * Get DNSKEY protocol value from rdata * @param k: DNSKEY rrset. * @param idx: which key. * @return protocol octet value */ static int dnskey_get_protocol(struct ub_packed_rrset_key* k, size_t idx) { uint8_t* rdata; size_t len; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+4) return 0; return (int)rdata[2+2]; } int dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx) { uint8_t* rdata; size_t len; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+4) return 0; return (int)rdata[2+3]; } /** get public key rdata field from a dnskey RR and do some checks */ static void dnskey_get_pubkey(struct ub_packed_rrset_key* k, size_t idx, unsigned char** pk, unsigned int* pklen) { uint8_t* rdata; size_t len; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+5) { *pk = NULL; *pklen = 0; return; } *pk = (unsigned char*)rdata+2+4; *pklen = (unsigned)len-2-4; } int ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx) { uint8_t* rdata; size_t len; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+3) return 0; return (int)rdata[2+2]; } int ds_get_digest_algo(struct ub_packed_rrset_key* k, size_t idx) { uint8_t* rdata; size_t len; rrset_get_rdata(k, idx, &rdata, &len); if(len < 2+4) return 0; return (int)rdata[2+3]; } uint16_t ds_get_keytag(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) { uint16_t t; uint8_t* rdata; size_t len; rrset_get_rdata(ds_rrset, ds_idx, &rdata, &len); if(len < 2+2) return 0; memmove(&t, rdata+2, 2); return ntohs(t); } /** * Return pointer to the digest in a DS RR. * @param k: DS rrset. * @param idx: which DS. * @param digest: digest data is returned. * on error, this is NULL. * @param len: length of digest is returned. * on error, the length is 0. */ static void ds_get_sigdata(struct ub_packed_rrset_key* k, size_t idx, uint8_t** digest, size_t* len) { uint8_t* rdata; size_t rdlen; rrset_get_rdata(k, idx, &rdata, &rdlen); if(rdlen < 2+5) { *digest = NULL; *len = 0; return; } *digest = rdata + 2 + 4; *len = rdlen - 2 - 4; } /** * Return size of DS digest according to its hash algorithm. * @param k: DS rrset. * @param idx: which DS. * @return size in bytes of digest, or 0 if not supported. */ static size_t ds_digest_size_algo(struct ub_packed_rrset_key* k, size_t idx) { return ds_digest_size_supported(ds_get_digest_algo(k, idx)); } /** * Create a DS digest for a DNSKEY entry. * * @param env: module environment. Uses scratch space. * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @param digest: digest is returned in here (must be correctly sized). * @return false on error. */ static int ds_create_dnskey_digest(struct module_env* env, struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx, uint8_t* digest) { sldns_buffer* b = env->scratch_buffer; uint8_t* dnskey_rdata; size_t dnskey_len; rrset_get_rdata(dnskey_rrset, dnskey_idx, &dnskey_rdata, &dnskey_len); /* create digest source material in buffer * digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA); * DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key. */ sldns_buffer_clear(b); sldns_buffer_write(b, dnskey_rrset->rk.dname, dnskey_rrset->rk.dname_len); query_dname_tolower(sldns_buffer_begin(b)); sldns_buffer_write(b, dnskey_rdata+2, dnskey_len-2); /* skip rdatalen*/ sldns_buffer_flip(b); return secalgo_ds_digest(ds_get_digest_algo(ds_rrset, ds_idx), (unsigned char*)sldns_buffer_begin(b), sldns_buffer_limit(b), (unsigned char*)digest); } int ds_digest_match_dnskey(struct module_env* env, struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) { uint8_t* ds; /* DS digest */ size_t dslen; uint8_t* digest; /* generated digest */ size_t digestlen = ds_digest_size_algo(ds_rrset, ds_idx); if(digestlen == 0) { verbose(VERB_QUERY, "DS fail: not supported, or DS RR " "format error"); return 0; /* not supported, or DS RR format error */ } #ifndef USE_SHA1 if(fake_sha1 && ds_get_digest_algo(ds_rrset, ds_idx)==LDNS_SHA1) return 1; #endif /* check digest length in DS with length from hash function */ ds_get_sigdata(ds_rrset, ds_idx, &ds, &dslen); if(!ds || dslen != digestlen) { verbose(VERB_QUERY, "DS fail: DS RR algo and digest do not " "match each other"); return 0; /* DS algorithm and digest do not match */ } digest = regional_alloc(env->scratch, digestlen); if(!digest) { verbose(VERB_QUERY, "DS fail: out of memory"); return 0; /* mem error */ } if(!ds_create_dnskey_digest(env, dnskey_rrset, dnskey_idx, ds_rrset, ds_idx, digest)) { verbose(VERB_QUERY, "DS fail: could not calc key digest"); return 0; /* digest algo failed */ } if(memcmp(digest, ds, dslen) != 0) { verbose(VERB_QUERY, "DS fail: digest is different"); return 0; /* digest different */ } return 1; } int ds_digest_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) { return (ds_digest_size_algo(ds_rrset, ds_idx) != 0); } int ds_key_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx) { return dnskey_algo_id_is_supported(ds_get_key_algo(ds_rrset, ds_idx)); } uint16_t dnskey_calc_keytag(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx) { uint8_t* data; size_t len; rrset_get_rdata(dnskey_rrset, dnskey_idx, &data, &len); /* do not pass rdatalen to ldns */ return sldns_calc_keytag_raw(data+2, len-2); } int dnskey_algo_is_supported(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx) { return dnskey_algo_id_is_supported(dnskey_get_algo(dnskey_rrset, dnskey_idx)); } int dnskey_size_is_supported(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx) { #ifdef DEPRECATE_RSA_1024 uint8_t* rdata; size_t len; int alg = dnskey_get_algo(dnskey_rrset, dnskey_idx); size_t keysize; rrset_get_rdata(dnskey_rrset, dnskey_idx, &rdata, &len); if(len < 2+4) return 0; keysize = sldns_rr_dnskey_key_size_raw(rdata+2+4, len-2-4, alg); switch((sldns_algorithm)alg) { case LDNS_RSAMD5: case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: case LDNS_RSASHA256: case LDNS_RSASHA512: /* reject RSA keys of 1024 bits and shorter */ if(keysize <= 1024) return 0; break; default: break; } #else (void)dnskey_rrset; (void)dnskey_idx; #endif /* DEPRECATE_RSA_1024 */ return 1; } int dnskeyset_size_is_supported(struct ub_packed_rrset_key* dnskey_rrset) { size_t i, num = rrset_get_count(dnskey_rrset); for(i=0; inum; size_t num = rrset_get_count(dnskey); for(i=0; ineeds[algo] == 0) { n->needs[algo] = 1; sigalg[total] = algo; total++; } } sigalg[total] = 0; n->num = total; } void algo_needs_init_list(struct algo_needs* n, uint8_t* sigalg) { uint8_t algo; size_t total = 0; memset(n->needs, 0, sizeof(uint8_t)*ALGO_NEEDS_MAX); while( (algo=*sigalg++) != 0) { log_assert(dnskey_algo_id_is_supported((int)algo)); log_assert(n->needs[algo] == 0); n->needs[algo] = 1; total++; } n->num = total; } void algo_needs_init_ds(struct algo_needs* n, struct ub_packed_rrset_key* ds, int fav_ds_algo, uint8_t* sigalg) { uint8_t algo; size_t i, total = 0; size_t num = rrset_get_count(ds); memset(n->needs, 0, sizeof(uint8_t)*ALGO_NEEDS_MAX); for(i=0; ineeds[algo] == 0) { n->needs[algo] = 1; sigalg[total] = algo; total++; } } sigalg[total] = 0; n->num = total; } int algo_needs_set_secure(struct algo_needs* n, uint8_t algo) { if(n->needs[algo]) { n->needs[algo] = 0; n->num --; if(n->num == 0) /* done! */ return 1; } return 0; } void algo_needs_set_bogus(struct algo_needs* n, uint8_t algo) { if(n->needs[algo]) n->needs[algo] = 2; /* need it, but bogus */ } size_t algo_needs_num_missing(struct algo_needs* n) { return n->num; } int algo_needs_missing(struct algo_needs* n) { int i, miss = -1; /* check if a needed algo was bogus - report that; * check the first missing algo - report that; * or return 0 */ for(i=0; ineeds[i] == 2) return 0; if(n->needs[i] == 1 && miss == -1) miss = i; } if(miss != -1) return miss; return 0; } /** * verify rrset, with dnskey rrset, for a specific rrsig in rrset * @param env: module environment, scratch space is used. * @param ve: validator environment, date settings. * @param now: current time for validation (can be overridden). * @param rrset: to be validated. * @param dnskey: DNSKEY rrset, keyset to try. * @param sig_idx: which signature to try to validate. * @param sortree: reused sorted order. Stored in region. Pass NULL at start, * and for a new rrset. * @param reason: if bogus, a string returned, fixed or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @param numverified: incremented when the number of RRSIG validations * increases. * @return secure if any key signs *this* signature. bogus if no key signs it, * unchecked on error, or indeterminate if all keys are not supported by * the crypto library (openssl3+ only). */ static enum sec_status dnskeyset_verify_rrset_sig(struct module_env* env, struct val_env* ve, time_t now, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t sig_idx, struct rbtree_type** sortree, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int* numverified) { /* find matching keys and check them */ enum sec_status sec = sec_status_bogus; uint16_t tag = rrset_get_sig_keytag(rrset, sig_idx); int algo = rrset_get_sig_algo(rrset, sig_idx); size_t i, num = rrset_get_count(dnskey); size_t numchecked = 0; size_t numindeterminate = 0; int buf_canon = 0; verbose(VERB_ALGO, "verify sig %d %d", (int)tag, algo); if(!dnskey_algo_id_is_supported(algo)) { if(reason_bogus) *reason_bogus = LDNS_EDE_UNSUPPORTED_DNSKEY_ALG; verbose(VERB_QUERY, "verify sig: unknown algorithm"); return sec_status_insecure; } for(i=0; iscratch, env->scratch_buffer, ve, now, rrset, dnskey, i, sig_idx, sortree, &buf_canon, reason, reason_bogus, section, qstate); if(sec == sec_status_secure) return sec; else if(sec == sec_status_indeterminate) numindeterminate ++; if(*numverified > MAX_VALIDATE_RRSIGS) { *reason = "too many RRSIG validations"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; verbose(VERB_ALGO, "verify sig: too many RRSIG validations"); return sec_status_bogus; } } if(numchecked == 0) { *reason = "signatures from unknown keys"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSKEY_MISSING; verbose(VERB_QUERY, "verify: could not find appropriate key"); return sec_status_bogus; } if(numindeterminate == numchecked) { *reason = "unsupported algorithm by crypto library"; if(reason_bogus) *reason_bogus = LDNS_EDE_UNSUPPORTED_DNSKEY_ALG; verbose(VERB_ALGO, "verify sig: unsupported algorithm by " "crypto library"); return sec_status_indeterminate; } return sec_status_bogus; } enum sec_status dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int* verified, char* reasonbuf, size_t reasonlen) { enum sec_status sec; size_t i, num; rbtree_type* sortree = NULL; /* make sure that for all DNSKEY algorithms there are valid sigs */ struct algo_needs needs; int alg; *verified = 0; num = rrset_get_sigcount(rrset); if(num == 0) { verbose(VERB_QUERY, "rrset failed to verify due to a lack of " "signatures"); *reason = "no signatures"; if(reason_bogus) *reason_bogus = LDNS_EDE_RRSIGS_MISSING; return sec_status_bogus; } if(sigalg) { algo_needs_init_list(&needs, sigalg); if(algo_needs_num_missing(&needs) == 0) { verbose(VERB_QUERY, "zone has no known algorithms"); *reason = "zone has no known algorithms"; if(reason_bogus) *reason_bogus = LDNS_EDE_UNSUPPORTED_DNSKEY_ALG; return sec_status_insecure; } } for(i=0; inow, rrset, dnskey, i, &sortree, reason, reason_bogus, section, qstate, verified); /* see which algorithm has been fixed up */ if(sec == sec_status_secure) { if(!sigalg) return sec; /* done! */ else if(algo_needs_set_secure(&needs, (uint8_t)rrset_get_sig_algo(rrset, i))) return sec; /* done! */ } else if(sigalg && sec == sec_status_bogus) { algo_needs_set_bogus(&needs, (uint8_t)rrset_get_sig_algo(rrset, i)); } if(*verified > MAX_VALIDATE_RRSIGS) { verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations"); *reason = "too many RRSIG validations"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } } if(sigalg && (alg=algo_needs_missing(&needs)) != 0) { verbose(VERB_ALGO, "rrset failed to verify: " "no valid signatures for %d algorithms", (int)algo_needs_num_missing(&needs)); algo_needs_reason(alg, reason, "no signatures", reasonbuf, reasonlen); } else { verbose(VERB_ALGO, "rrset failed to verify: " "no valid signatures"); } return sec_status_bogus; } void algo_needs_reason(int alg, char** reason, char* s, char* reasonbuf, size_t reasonlen) { sldns_lookup_table *t = sldns_lookup_by_id(sldns_algorithms, alg); if(t&&t->name) snprintf(reasonbuf, reasonlen, "%s with algorithm %s", s, t->name); else snprintf(reasonbuf, reasonlen, "%s with algorithm ALG%u", s, (unsigned)alg); *reason = reasonbuf; } enum sec_status dnskey_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate) { enum sec_status sec; size_t i, num, numchecked = 0, numindeterminate = 0; rbtree_type* sortree = NULL; int buf_canon = 0; uint16_t tag = dnskey_calc_keytag(dnskey, dnskey_idx); int algo = dnskey_get_algo(dnskey, dnskey_idx); int numverified = 0; num = rrset_get_sigcount(rrset); if(num == 0) { verbose(VERB_QUERY, "rrset failed to verify due to a lack of " "signatures"); *reason = "no signatures"; if(reason_bogus) *reason_bogus = LDNS_EDE_RRSIGS_MISSING; return sec_status_bogus; } for(i=0; iscratch, env->scratch_buffer, ve, *env->now, rrset, dnskey, dnskey_idx, i, &sortree, &buf_canon, reason, reason_bogus, section, qstate); if(sec == sec_status_secure) return sec; numchecked ++; numverified ++; if(sec == sec_status_indeterminate) numindeterminate ++; if(numverified > MAX_VALIDATE_RRSIGS) { verbose(VERB_QUERY, "rrset failed to verify, too many RRSIG validations"); *reason = "too many RRSIG validations"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } } if(!numchecked) { *reason = "signature for expected key and algorithm missing"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; } else if(numchecked == numindeterminate) { verbose(VERB_ALGO, "rrset failed to verify due to algorithm " "refusal by cryptolib"); if(reason_bogus) *reason_bogus = LDNS_EDE_UNSUPPORTED_DNSKEY_ALG; *reason = "algorithm refused by cryptolib"; return sec_status_indeterminate; } verbose(VERB_ALGO, "rrset failed to verify: all signatures are bogus"); return sec_status_bogus; } /** * RR entries in a canonical sorted tree of RRs */ struct canon_rr { /** rbtree node, key is this structure */ rbnode_type node; /** rrset the RR is in */ struct ub_packed_rrset_key* rrset; /** which RR in the rrset */ size_t rr_idx; }; /** * Compare two RR for canonical order, in a field-style sweep. * @param d: rrset data * @param desc: ldns wireformat descriptor. * @param i: first RR to compare * @param j: first RR to compare * @return comparison code. */ static int canonical_compare_byfield(struct packed_rrset_data* d, const sldns_rr_descriptor* desc, size_t i, size_t j) { /* sweep across rdata, keep track of some state: * which rr field, and bytes left in field. * current position in rdata, length left. * are we in a dname, length left in a label. */ int wfi = -1; /* current wireformat rdata field (rdf) */ int wfj = -1; uint8_t* di = d->rr_data[i]+2; /* ptr to current rdata byte */ uint8_t* dj = d->rr_data[j]+2; size_t ilen = d->rr_len[i]-2; /* length left in rdata */ size_t jlen = d->rr_len[j]-2; int dname_i = 0; /* true if these bytes are part of a name */ int dname_j = 0; size_t lablen_i = 0; /* 0 for label length byte,for first byte of rdf*/ size_t lablen_j = 0; /* otherwise remaining length of rdf or label */ int dname_num_i = (int)desc->_dname_count; /* decreased at root label */ int dname_num_j = (int)desc->_dname_count; /* loop while there are rdata bytes available for both rrs, * and still some lowercasing needs to be done; either the dnames * have not been reached yet, or they are currently being processed */ while(ilen > 0 && jlen > 0 && (dname_num_i > 0 || dname_num_j > 0)) { /* compare these two bytes */ /* lowercase if in a dname and not a label length byte */ if( ((dname_i && lablen_i)?(uint8_t)tolower((int)*di):*di) != ((dname_j && lablen_j)?(uint8_t)tolower((int)*dj):*dj) ) { if(((dname_i && lablen_i)?(uint8_t)tolower((int)*di):*di) < ((dname_j && lablen_j)?(uint8_t)tolower((int)*dj):*dj)) return -1; return 1; } ilen--; jlen--; /* bytes are equal */ /* advance field i */ /* lablen 0 means that this byte is the first byte of the * next rdata field; inspect this rdata field and setup * to process the rest of this rdata field. * The reason to first read the byte, then setup the rdf, * is that we are then sure the byte is available and short * rdata is handled gracefully (even if it is a formerr). */ if(lablen_i == 0) { if(dname_i) { /* scan this dname label */ /* capture length to lowercase */ lablen_i = (size_t)*di; if(lablen_i == 0) { /* end root label */ dname_i = 0; dname_num_i--; /* if dname num is 0, then the * remainder is binary only */ if(dname_num_i == 0) lablen_i = ilen; } } else { /* scan this rdata field */ wfi++; if(desc->_wireformat[wfi] == LDNS_RDF_TYPE_DNAME) { dname_i = 1; lablen_i = (size_t)*di; if(lablen_i == 0) { dname_i = 0; dname_num_i--; if(dname_num_i == 0) lablen_i = ilen; } } else if(desc->_wireformat[wfi] == LDNS_RDF_TYPE_STR) lablen_i = (size_t)*di; else lablen_i = get_rdf_size( desc->_wireformat[wfi]) - 1; } } else lablen_i--; /* advance field j; same as for i */ if(lablen_j == 0) { if(dname_j) { lablen_j = (size_t)*dj; if(lablen_j == 0) { dname_j = 0; dname_num_j--; if(dname_num_j == 0) lablen_j = jlen; } } else { wfj++; if(desc->_wireformat[wfj] == LDNS_RDF_TYPE_DNAME) { dname_j = 1; lablen_j = (size_t)*dj; if(lablen_j == 0) { dname_j = 0; dname_num_j--; if(dname_num_j == 0) lablen_j = jlen; } } else if(desc->_wireformat[wfj] == LDNS_RDF_TYPE_STR) lablen_j = (size_t)*dj; else lablen_j = get_rdf_size( desc->_wireformat[wfj]) - 1; } } else lablen_j--; di++; dj++; } /* end of the loop; because we advanced byte by byte; now we have * that the rdata has ended, or that there is a binary remainder */ /* shortest first */ if(ilen == 0 && jlen == 0) return 0; if(ilen == 0) return -1; if(jlen == 0) return 1; /* binary remainder, capture comparison in wfi variable */ if((wfi = memcmp(di, dj, (ilen. */ static int canonical_compare(struct ub_packed_rrset_key* rrset, size_t i, size_t j) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; const sldns_rr_descriptor* desc; uint16_t type = ntohs(rrset->rk.type); size_t minlen; int c; if(i==j) return 0; switch(type) { /* These RR types have only a name as RDATA. * This name has to be canonicalized.*/ case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_CNAME: case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MG: case LDNS_RR_TYPE_MR: case LDNS_RR_TYPE_PTR: case LDNS_RR_TYPE_DNAME: /* the wireread function has already checked these * dname's for correctness, and this double checks */ if(!dname_valid(d->rr_data[i]+2, d->rr_len[i]-2) || !dname_valid(d->rr_data[j]+2, d->rr_len[j]-2)) return 0; return query_dname_compare(d->rr_data[i]+2, d->rr_data[j]+2); /* These RR types have STR and fixed size rdata fields * before one or more name fields that need canonicalizing, * and after that a byte-for byte remainder can be compared. */ /* type starts with the name; remainder is binary compared */ case LDNS_RR_TYPE_NXT: /* use rdata field formats */ case LDNS_RR_TYPE_MINFO: case LDNS_RR_TYPE_RP: case LDNS_RR_TYPE_SOA: case LDNS_RR_TYPE_RT: case LDNS_RR_TYPE_AFSDB: case LDNS_RR_TYPE_KX: case LDNS_RR_TYPE_MX: case LDNS_RR_TYPE_SIG: /* RRSIG signer name has to be downcased */ case LDNS_RR_TYPE_RRSIG: case LDNS_RR_TYPE_PX: case LDNS_RR_TYPE_NAPTR: case LDNS_RR_TYPE_SRV: desc = sldns_rr_descript(type); log_assert(desc); /* this holds for the types that need canonicalizing */ log_assert(desc->_minimum == desc->_maximum); return canonical_compare_byfield(d, desc, i, j); case LDNS_RR_TYPE_HINFO: /* no longer downcased */ case LDNS_RR_TYPE_NSEC: default: /* For unknown RR types, or types not listed above, * no canonicalization is needed, do binary compare */ /* byte for byte compare, equal means shortest first*/ minlen = d->rr_len[i]-2; if(minlen > d->rr_len[j]-2) minlen = d->rr_len[j]-2; c = memcmp(d->rr_data[i]+2, d->rr_data[j]+2, minlen); if(c!=0) return c; /* rdata equal, shortest is first */ if(d->rr_len[i] < d->rr_len[j]) return -1; if(d->rr_len[i] > d->rr_len[j]) return 1; /* rdata equal, length equal */ break; } return 0; } int canonical_tree_compare(const void* k1, const void* k2) { struct canon_rr* r1 = (struct canon_rr*)k1; struct canon_rr* r2 = (struct canon_rr*)k2; log_assert(r1->rrset == r2->rrset); return canonical_compare(r1->rrset, r1->rr_idx, r2->rr_idx); } /** * Sort RRs for rrset in canonical order. * Does not actually canonicalize the RR rdatas. * Does not touch rrsigs. * @param rrset: to sort. * @param d: rrset data. * @param sortree: tree to sort into. * @param rrs: rr storage. */ static void canonical_sort(struct ub_packed_rrset_key* rrset, struct packed_rrset_data* d, rbtree_type* sortree, struct canon_rr* rrs) { size_t i; /* insert into rbtree to sort and detect duplicates */ for(i=0; icount; i++) { rrs[i].node.key = &rrs[i]; rrs[i].rrset = rrset; rrs[i].rr_idx = i; if(!rbtree_insert(sortree, &rrs[i].node)) { /* this was a duplicate */ } } } /** * Insert canonical owner name into buffer. * @param buf: buffer to insert into at current position. * @param k: rrset with its owner name. * @param sig: signature with signer name and label count. * must be length checked, at least 18 bytes long. * @param can_owner: position in buffer returned for future use. * @param can_owner_len: length of canonical owner name. */ static void insert_can_owner(sldns_buffer* buf, struct ub_packed_rrset_key* k, uint8_t* sig, uint8_t** can_owner, size_t* can_owner_len) { int rrsig_labels = (int)sig[3]; int fqdn_labels = dname_signame_label_count(k->rk.dname); *can_owner = sldns_buffer_current(buf); if(rrsig_labels == fqdn_labels) { /* no change */ sldns_buffer_write(buf, k->rk.dname, k->rk.dname_len); query_dname_tolower(*can_owner); *can_owner_len = k->rk.dname_len; return; } log_assert(rrsig_labels < fqdn_labels); /* *. | fqdn(rightmost rrsig_labels) */ if(rrsig_labels < fqdn_labels) { int i; uint8_t* nm = k->rk.dname; size_t len = k->rk.dname_len; /* so skip fqdn_labels-rrsig_labels */ for(i=0; irk.type)) { case LDNS_RR_TYPE_NXT: case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_CNAME: case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MG: case LDNS_RR_TYPE_MR: case LDNS_RR_TYPE_PTR: case LDNS_RR_TYPE_DNAME: /* type only has a single argument, the name */ query_dname_tolower(datstart); return; case LDNS_RR_TYPE_MINFO: case LDNS_RR_TYPE_RP: case LDNS_RR_TYPE_SOA: /* two names after another */ query_dname_tolower(datstart); query_dname_tolower(datstart + dname_valid(datstart, len-2)); return; case LDNS_RR_TYPE_RT: case LDNS_RR_TYPE_AFSDB: case LDNS_RR_TYPE_KX: case LDNS_RR_TYPE_MX: /* skip fixed part */ if(len < 2+2+1) /* rdlen, skiplen, 1byteroot */ return; datstart += 2; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_SIG: /* downcase the RRSIG, compat with BIND (kept it from SIG) */ case LDNS_RR_TYPE_RRSIG: /* skip fixed part */ if(len < 2+18+1) return; datstart += 18; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_PX: /* skip, then two names after another */ if(len < 2+2+1) return; datstart += 2; query_dname_tolower(datstart); query_dname_tolower(datstart + dname_valid(datstart, len-2-2)); return; case LDNS_RR_TYPE_NAPTR: if(len < 2+4) return; len -= 2+4; datstart += 4; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < (size_t)datstart[0]+1) /* skip text field */ return; len -= (size_t)datstart[0]+1; datstart += (size_t)datstart[0]+1; if(len < 1) /* check name is at least 1 byte*/ return; query_dname_tolower(datstart); return; case LDNS_RR_TYPE_SRV: /* skip fixed part */ if(len < 2+6+1) return; datstart += 6; query_dname_tolower(datstart); return; /* do not canonicalize NSEC rdata name, compat with * from bind 9.4 signer, where it does not do so */ case LDNS_RR_TYPE_NSEC: /* type starts with the name */ case LDNS_RR_TYPE_HINFO: /* not downcased */ /* A6 not supported */ default: /* nothing to do for unknown types */ return; } } int rrset_canonical_equal(struct regional* region, struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2) { struct rbtree_type sortree1, sortree2; struct canon_rr *rrs1, *rrs2, *p1, *p2; struct packed_rrset_data* d1=(struct packed_rrset_data*)k1->entry.data; struct packed_rrset_data* d2=(struct packed_rrset_data*)k2->entry.data; struct ub_packed_rrset_key fk; struct packed_rrset_data fd; size_t flen[2]; uint8_t* fdata[2]; /* basic compare */ if(k1->rk.dname_len != k2->rk.dname_len || k1->rk.flags != k2->rk.flags || k1->rk.type != k2->rk.type || k1->rk.rrset_class != k2->rk.rrset_class || query_dname_compare(k1->rk.dname, k2->rk.dname) != 0) return 0; if(d1->ttl != d2->ttl || d1->count != d2->count || d1->rrsig_count != d2->rrsig_count || d1->trust != d2->trust || d1->security != d2->security) return 0; /* init */ memset(&fk, 0, sizeof(fk)); memset(&fd, 0, sizeof(fd)); fk.entry.data = &fd; fd.count = 2; fd.rr_len = flen; fd.rr_data = fdata; rbtree_init(&sortree1, &canonical_tree_compare); rbtree_init(&sortree2, &canonical_tree_compare); if(d1->count > RR_COUNT_MAX || d2->count > RR_COUNT_MAX) return 1; /* protection against integer overflow */ rrs1 = regional_alloc(region, sizeof(struct canon_rr)*d1->count); rrs2 = regional_alloc(region, sizeof(struct canon_rr)*d2->count); if(!rrs1 || !rrs2) return 1; /* alloc failure */ /* sort */ canonical_sort(k1, d1, &sortree1, rrs1); canonical_sort(k2, d2, &sortree2, rrs2); /* compare canonical-sorted RRs for canonical-equality */ if(sortree1.count != sortree2.count) return 0; p1 = (struct canon_rr*)rbtree_first(&sortree1); p2 = (struct canon_rr*)rbtree_first(&sortree2); while(p1 != (struct canon_rr*)RBTREE_NULL && p2 != (struct canon_rr*)RBTREE_NULL) { flen[0] = d1->rr_len[p1->rr_idx]; flen[1] = d2->rr_len[p2->rr_idx]; fdata[0] = d1->rr_data[p1->rr_idx]; fdata[1] = d2->rr_data[p2->rr_idx]; if(canonical_compare(&fk, 0, 1) != 0) return 0; p1 = (struct canon_rr*)rbtree_next(&p1->node); p2 = (struct canon_rr*)rbtree_next(&p2->node); } return 1; } /** * Create canonical form of rrset in the scratch buffer. * @param region: temporary region. * @param buf: the buffer to use. * @param k: the rrset to insert. * @param sig: RRSIG rdata to include. * @param siglen: RRSIG rdata len excluding signature field, but inclusive * signer name length. * @param sortree: if NULL is passed a new sorted rrset tree is built. * Otherwise it is reused. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @return false on alloc error. */ static int rrset_canonical(struct regional* region, sldns_buffer* buf, struct ub_packed_rrset_key* k, uint8_t* sig, size_t siglen, struct rbtree_type** sortree, sldns_pkt_section section, struct module_qstate* qstate) { struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; uint8_t* can_owner = NULL; size_t can_owner_len = 0; struct canon_rr* walk; struct canon_rr* rrs; if(!*sortree) { *sortree = (struct rbtree_type*)regional_alloc(region, sizeof(rbtree_type)); if(!*sortree) return 0; if(d->count > RR_COUNT_MAX) return 0; /* integer overflow protection */ rrs = regional_alloc(region, sizeof(struct canon_rr)*d->count); if(!rrs) { *sortree = NULL; return 0; } rbtree_init(*sortree, &canonical_tree_compare); canonical_sort(k, d, *sortree, rrs); } sldns_buffer_clear(buf); sldns_buffer_write(buf, sig, siglen); /* canonicalize signer name */ query_dname_tolower(sldns_buffer_begin(buf)+18); RBTREE_FOR(walk, struct canon_rr*, (*sortree)) { /* see if there is enough space left in the buffer */ if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4 + d->rr_len[walk->rr_idx]) { log_err("verify: failed to canonicalize, " "rrset too big"); return 0; } /* determine canonical owner name */ if(can_owner) sldns_buffer_write(buf, can_owner, can_owner_len); else insert_can_owner(buf, k, sig, &can_owner, &can_owner_len); sldns_buffer_write(buf, &k->rk.type, 2); sldns_buffer_write(buf, &k->rk.rrset_class, 2); sldns_buffer_write(buf, sig+4, 4); sldns_buffer_write(buf, d->rr_data[walk->rr_idx], d->rr_len[walk->rr_idx]); canonicalize_rdata(buf, k, d->rr_len[walk->rr_idx]); } sldns_buffer_flip(buf); /* Replace RR owner with canonical owner for NSEC records in authority * section, to prevent that a wildcard synthesized NSEC can be used in * the non-existence proves. */ if(ntohs(k->rk.type) == LDNS_RR_TYPE_NSEC && section == LDNS_SECTION_AUTHORITY && qstate) { k->rk.dname = regional_alloc_init(qstate->region, can_owner, can_owner_len); if(!k->rk.dname) return 0; k->rk.dname_len = can_owner_len; } return 1; } int rrset_canonicalize_to_buffer(struct regional* region, sldns_buffer* buf, struct ub_packed_rrset_key* k) { struct rbtree_type* sortree = NULL; struct packed_rrset_data* d = (struct packed_rrset_data*)k->entry.data; uint8_t* can_owner = NULL; size_t can_owner_len = 0; struct canon_rr* walk; struct canon_rr* rrs; sortree = (struct rbtree_type*)regional_alloc(region, sizeof(rbtree_type)); if(!sortree) return 0; if(d->count > RR_COUNT_MAX) return 0; /* integer overflow protection */ rrs = regional_alloc(region, sizeof(struct canon_rr)*d->count); if(!rrs) { return 0; } rbtree_init(sortree, &canonical_tree_compare); canonical_sort(k, d, sortree, rrs); sldns_buffer_clear(buf); RBTREE_FOR(walk, struct canon_rr*, sortree) { /* see if there is enough space left in the buffer */ if(sldns_buffer_remaining(buf) < can_owner_len + 2 + 2 + 4 + d->rr_len[walk->rr_idx]) { log_err("verify: failed to canonicalize, " "rrset too big"); return 0; } /* determine canonical owner name */ if(can_owner) sldns_buffer_write(buf, can_owner, can_owner_len); else { can_owner = sldns_buffer_current(buf); sldns_buffer_write(buf, k->rk.dname, k->rk.dname_len); query_dname_tolower(can_owner); can_owner_len = k->rk.dname_len; } sldns_buffer_write(buf, &k->rk.type, 2); sldns_buffer_write(buf, &k->rk.rrset_class, 2); sldns_buffer_write_u32(buf, d->rr_ttl[walk->rr_idx]); sldns_buffer_write(buf, d->rr_data[walk->rr_idx], d->rr_len[walk->rr_idx]); canonicalize_rdata(buf, k, d->rr_len[walk->rr_idx]); } sldns_buffer_flip(buf); return 1; } /** pretty print rrsig error with dates */ static void sigdate_error(const char* str, int32_t expi, int32_t incep, int32_t now) { struct tm tm; char expi_buf[16]; char incep_buf[16]; char now_buf[16]; time_t te, ti, tn; if(verbosity < VERB_QUERY) return; te = (time_t)expi; ti = (time_t)incep; tn = (time_t)now; memset(&tm, 0, sizeof(tm)); if(gmtime_r(&te, &tm) && strftime(expi_buf, 15, "%Y%m%d%H%M%S", &tm) &&gmtime_r(&ti, &tm) && strftime(incep_buf, 15, "%Y%m%d%H%M%S", &tm) &&gmtime_r(&tn, &tm) && strftime(now_buf, 15, "%Y%m%d%H%M%S", &tm)) { log_info("%s expi=%s incep=%s now=%s", str, expi_buf, incep_buf, now_buf); } else log_info("%s expi=%u incep=%u now=%u", str, (unsigned)expi, (unsigned)incep, (unsigned)now); } /** check rrsig dates */ static int check_dates(struct val_env* ve, uint32_t unow, uint8_t* expi_p, uint8_t* incep_p, char** reason, sldns_ede_code *reason_bogus) { /* read out the dates */ uint32_t expi, incep, now; memmove(&expi, expi_p, sizeof(expi)); memmove(&incep, incep_p, sizeof(incep)); expi = ntohl(expi); incep = ntohl(incep); /* get current date */ if(ve->date_override) { if(ve->date_override == -1) { verbose(VERB_ALGO, "date override: ignore date"); return 1; } now = ve->date_override; verbose(VERB_ALGO, "date override option %d", (int)now); } else now = unow; /* check them */ if(compare_1982(incep, expi) > 0) { sigdate_error("verify: inception after expiration, " "signature bad", expi, incep, now); *reason = "signature inception after expiration"; if(reason_bogus){ /* from RFC8914 on Signature Not Yet Valid: The resolver * attempted to perform DNSSEC validation, but no * signatures are presently valid and at least some are * not yet valid. */ *reason_bogus = LDNS_EDE_SIGNATURE_NOT_YET_VALID; } return 0; } if(compare_1982(incep, now) > 0) { /* within skew ? (calc here to avoid calculation normally) */ uint32_t skew = subtract_1982(incep, expi)/10; if(skew < (uint32_t)ve->skew_min) skew = ve->skew_min; if(skew > (uint32_t)ve->skew_max) skew = ve->skew_max; if(subtract_1982(now, incep) > skew) { sigdate_error("verify: signature bad, current time is" " before inception date", expi, incep, now); *reason = "signature before inception date"; if(reason_bogus) *reason_bogus = LDNS_EDE_SIGNATURE_NOT_YET_VALID; return 0; } sigdate_error("verify warning suspicious signature inception " " or bad local clock", expi, incep, now); } if(compare_1982(now, expi) > 0) { uint32_t skew = subtract_1982(incep, expi)/10; if(skew < (uint32_t)ve->skew_min) skew = ve->skew_min; if(skew > (uint32_t)ve->skew_max) skew = ve->skew_max; if(subtract_1982(expi, now) > skew) { sigdate_error("verify: signature expired", expi, incep, now); *reason = "signature expired"; if(reason_bogus) *reason_bogus = LDNS_EDE_SIGNATURE_EXPIRED; return 0; } sigdate_error("verify warning suspicious signature expiration " " or bad local clock", expi, incep, now); } return 1; } /** adjust rrset TTL for verified rrset, compare to original TTL and expi */ static void adjust_ttl(struct val_env* ve, uint32_t unow, struct ub_packed_rrset_key* rrset, uint8_t* orig_p, uint8_t* expi_p, uint8_t* incep_p) { struct packed_rrset_data* d = (struct packed_rrset_data*)rrset->entry.data; /* read out the dates */ int32_t origttl, expittl, expi, incep, now; memmove(&origttl, orig_p, sizeof(origttl)); memmove(&expi, expi_p, sizeof(expi)); memmove(&incep, incep_p, sizeof(incep)); expi = ntohl(expi); incep = ntohl(incep); origttl = ntohl(origttl); /* get current date */ if(ve->date_override) { now = ve->date_override; } else now = (int32_t)unow; expittl = (int32_t)((uint32_t)expi - (uint32_t)now); /* so now: * d->ttl: rrset ttl read from message or cache. May be reduced * origttl: original TTL from signature, authoritative TTL max. * MIN_TTL: minimum TTL from config. * expittl: TTL until the signature expires. * * Use the smallest of these, but don't let origttl set the TTL * below the minimum. */ if(MIN_TTL > (time_t)origttl && d->ttl > MIN_TTL) { verbose(VERB_QUERY, "rrset TTL larger than original and minimum" " TTL, adjusting TTL downwards to minimum ttl"); d->ttl = MIN_TTL; } else if(MIN_TTL <= origttl && d->ttl > (time_t)origttl) { verbose(VERB_QUERY, "rrset TTL larger than original TTL, " "adjusting TTL downwards to original ttl"); d->ttl = origttl; } if(expittl > 0 && d->ttl > (time_t)expittl) { verbose(VERB_ALGO, "rrset TTL larger than sig expiration ttl," " adjusting TTL downwards"); d->ttl = expittl; } } enum sec_status dnskey_verify_rrset_sig(struct regional* region, sldns_buffer* buf, struct val_env* ve, time_t now, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, size_t sig_idx, struct rbtree_type** sortree, int* buf_canon, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate) { enum sec_status sec; uint8_t* sig; /* RRSIG rdata */ size_t siglen; size_t rrnum = rrset_get_count(rrset); uint8_t* signer; /* rrsig signer name */ size_t signer_len; unsigned char* sigblock; /* signature rdata field */ unsigned int sigblock_len; uint16_t ktag; /* DNSKEY key tag */ unsigned char* key; /* public key rdata field */ unsigned int keylen; rrset_get_rdata(rrset, rrnum + sig_idx, &sig, &siglen); /* min length of rdatalen, fixed rrsig, root signer, 1 byte sig */ if(siglen < 2+20) { verbose(VERB_QUERY, "verify: signature too short"); *reason = "signature too short"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } if(!(dnskey_get_flags(dnskey, dnskey_idx) & DNSKEY_BIT_ZSK)) { verbose(VERB_QUERY, "verify: dnskey without ZSK flag"); *reason = "dnskey without ZSK flag"; if(reason_bogus) *reason_bogus = LDNS_EDE_NO_ZONE_KEY_BIT_SET; return sec_status_bogus; } if((dnskey_get_flags(dnskey, dnskey_idx) & LDNS_KEY_REVOKE_KEY) && /* The REVOKE key is allowed to check sigs on itself. */ !(ntohs(rrset->rk.type) == LDNS_RR_TYPE_DNSKEY && query_dname_compare(rrset->rk.dname, dnskey->rk.dname)==0) ) { verbose(VERB_QUERY, "verify: dnskey has REVOKE bit set, " "not usable for data validation per RFC 5011 s2.1"); *reason = "dnskey revoked"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSKEY_MISSING; return sec_status_bogus; } if(dnskey_get_protocol(dnskey, dnskey_idx) != LDNS_DNSSEC_KEYPROTO) { /* RFC 4034 says DNSKEY PROTOCOL MUST be 3 */ verbose(VERB_QUERY, "verify: dnskey has wrong key protocol"); *reason = "dnskey has wrong protocolnumber"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } /* verify as many fields in rrsig as possible */ signer = sig+2+18; signer_len = dname_valid(signer, siglen-2-18); if(!signer_len) { verbose(VERB_QUERY, "verify: malformed signer name"); *reason = "signer name malformed"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; /* signer name invalid */ } if(!dname_subdomain_c(rrset->rk.dname, signer)) { verbose(VERB_QUERY, "verify: signer name is off-tree"); *reason = "signer name off-tree"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; /* signer name offtree */ } sigblock = (unsigned char*)signer+signer_len; if(siglen < 2+18+signer_len+1) { verbose(VERB_QUERY, "verify: too short, no signature data"); *reason = "signature too short, no signature data"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; /* sig rdf is < 1 byte */ } sigblock_len = (unsigned int)(siglen - 2 - 18 - signer_len); /* verify key dname == sig signer name */ if(query_dname_compare(signer, dnskey->rk.dname) != 0) { verbose(VERB_QUERY, "verify: wrong key for rrsig"); log_nametypeclass(VERB_QUERY, "RRSIG signername is", signer, 0, 0); log_nametypeclass(VERB_QUERY, "the key name is", dnskey->rk.dname, 0, 0); *reason = "signer name mismatches key name"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } /* verify covered type */ /* memcmp works because type is in network format for rrset */ if(memcmp(sig+2, &rrset->rk.type, 2) != 0) { verbose(VERB_QUERY, "verify: wrong type covered"); *reason = "signature covers wrong type"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } /* verify keytag and sig algo (possibly again) */ if((int)sig[2+2] != dnskey_get_algo(dnskey, dnskey_idx)) { verbose(VERB_QUERY, "verify: wrong algorithm"); *reason = "signature has wrong algorithm"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } ktag = htons(dnskey_calc_keytag(dnskey, dnskey_idx)); if(memcmp(sig+2+16, &ktag, 2) != 0) { verbose(VERB_QUERY, "verify: wrong keytag"); *reason = "signature has wrong keytag"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } /* verify labels is in a valid range */ if((int)sig[2+3] > dname_signame_label_count(rrset->rk.dname)) { verbose(VERB_QUERY, "verify: labelcount out of range"); *reason = "signature labelcount out of range"; if(reason_bogus) *reason_bogus = LDNS_EDE_DNSSEC_BOGUS; return sec_status_bogus; } /* original ttl, always ok */ if(!*buf_canon) { /* create rrset canonical format in buffer, ready for * signature */ if(!rrset_canonical(region, buf, rrset, sig+2, 18 + signer_len, sortree, section, qstate)) { log_err("verify: failed due to alloc error"); return sec_status_unchecked; } *buf_canon = 1; } /* check that dnskey is available */ dnskey_get_pubkey(dnskey, dnskey_idx, &key, &keylen); if(!key) { verbose(VERB_QUERY, "verify: short DNSKEY RR"); return sec_status_unchecked; } /* verify */ sec = verify_canonrrset(buf, (int)sig[2+2], sigblock, sigblock_len, key, keylen, reason); /* count validation operation */ if(qstate && qstate->env && qstate->env->mesh) qstate->env->mesh->val_ops++; if(sec == sec_status_secure) { /* check if TTL is too high - reduce if so */ adjust_ttl(ve, now, rrset, sig+2+4, sig+2+8, sig+2+12); /* verify inception, expiration dates * Do this last so that if you ignore expired-sigs the * rest is sure to be OK. */ if(!check_dates(ve, now, sig+2+8, sig+2+12, reason, reason_bogus)) { return sec_status_bogus; } } return sec; } unbound-1.25.1/validator/val_kcache.h0000644000175000017500000001002015203270263017115 0ustar wouterwouter/* * validator/val_kcache.h - validator key shared cache with validated keys * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions for caching validated key entries. */ #ifndef VALIDATOR_VAL_KCACHE_H #define VALIDATOR_VAL_KCACHE_H #include "util/storage/slabhash.h" struct key_entry_key; struct key_entry_data; struct config_file; struct regional; struct module_qstate; /** * Key cache */ struct key_cache { /** uses slabhash for storage, type key_entry_key, key_entry_data */ struct slabhash* slab; }; /** * Create the key cache * @param cfg: config settings for the key cache. * @return new key cache or NULL on malloc failure. */ struct key_cache* key_cache_create(struct config_file* cfg); /** * Delete the key cache * @param kcache: to delete */ void key_cache_delete(struct key_cache* kcache); /** * Insert or update a key cache entry. Note that the insert may silently * fail if there is not enough memory. * * @param kcache: the key cache. * @param kkey: key entry key, assumed malloced in a region, is copied * to perform update or insertion. Its data pointer is also copied. * @param copy_reason: if the reason string needs to be copied (allocated). */ void key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey, int copy_reason); /** * Remove an entry from the key cache. * @param kcache: the key cache. * @param name: for what name to look; uncompressed wireformat * @param namelen: length of the name. * @param key_class: class of the key. */ void key_cache_remove(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class); /** * Lookup key entry in the cache. Looks up the closest key entry above the * given name. * @param kcache: the key cache. * @param name: for what name to look; uncompressed wireformat * @param namelen: length of the name. * @param key_class: class of the key. * @param region: a copy of the key_entry is allocated in this region. * @param now: current time. * @return pointer to a newly allocated key_entry copy in the region, if * a key entry could be found, and allocation succeeded and TTL was OK. * Otherwise, NULL is returned. */ struct key_entry_key* key_cache_obtain(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class, struct regional* region, time_t now); /** * Get memory in use by the key cache. * @param kcache: the key cache. * @return memory in use in bytes. */ size_t key_cache_get_mem(struct key_cache* kcache); #endif /* VALIDATOR_VAL_KCACHE_H */ unbound-1.25.1/validator/val_nsec.h0000644000175000017500000001523515203270263016644 0ustar wouterwouter/* * validator/val_nsec.h - validator NSEC denial of existence functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with NSEC checking, the different NSEC proofs * for denial of existence, and proofs for presence of types. */ #ifndef VALIDATOR_VAL_NSEC_H #define VALIDATOR_VAL_NSEC_H #include "util/data/packed_rrset.h" #include "sldns/rrdef.h" struct val_env; struct module_env; struct module_qstate; struct ub_packed_rrset_key; struct reply_info; struct query_info; struct key_entry_key; /** * Check DS absence. * There is a NODATA reply to a DS that needs checking. * NSECs can prove this is not a delegation point, or successfully prove * that there is no DS. Or this fails. * * @param env: module env for rrsig verification routines. * @param ve: validator env for rrsig verification routines. * @param qinfo: the DS queried for. * @param rep: reply received. * @param kkey: key entry to use for verification of signatures. * @param proof_ttl: if secure, the TTL of how long this proof lasts. * @param reason: string explaining why bogus. * @param reason_bogus: relevant EDE code for validation failure. * @param qstate: qstate with region. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return security status. * SECURE: proved absence of DS. * INSECURE: proved that this was not a delegation point. * BOGUS: crypto bad, or no absence of DS proven. * UNCHECKED: there was no way to prove anything (no NSECs, unknown algo). */ enum sec_status val_nsec_prove_nodata_dsreply(struct module_env* env, struct val_env* ve, struct query_info* qinfo, struct reply_info* rep, struct key_entry_key* kkey, time_t* proof_ttl, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); /** * nsec typemap check, takes an NSEC-type bitmap as argument, checks for type. * @param bitmap: pointer to the bitmap part of wireformat rdata. * @param len: length of the bitmap, in bytes. * @param type: the type (in host order) to check for. * @return true if the type bit was set in the bitmap. false if not, or * if the bitmap was malformed in some way. */ int nsecbitmap_has_type_rdata(uint8_t* bitmap, size_t len, uint16_t type); /** * Check if type is present in the NSEC typemap * @param nsec: the nsec RRset. * If there are multiple RRs, then each must have the same typemap, * since the typemap represents the types at this domain node. * @param type: type to check for, host order. * @return true if present */ int nsec_has_type(struct ub_packed_rrset_key* nsec, uint16_t type); /** * Determine if a NSEC proves the NOERROR/NODATA conditions. This will also * handle the empty non-terminal (ENT) case and partially handle the * wildcard case. If the ownername of 'nsec' is a wildcard, the validator * must still be provided proof that qname did not directly exist and that * the wildcard is, in fact, *.closest_encloser. * * @param nsec: the nsec record to check against. * @param qinfo: the query info. * @param wc: if the nodata is proven for a wildcard match, the wildcard * closest encloser is returned, else NULL (wc is unchanged). * This closest encloser must then match the nameerror given for the * nextcloser of qname. * @return true if NSEC proves this. */ int nsec_proves_nodata(struct ub_packed_rrset_key* nsec, struct query_info* qinfo, uint8_t** wc); /** * Determine if the given NSEC proves a NameError (NXDOMAIN) for a given * qname. * * @param nsec: the nsec to check * @param qname: what was queried. * @return true if proven. */ int val_nsec_proves_name_error(struct ub_packed_rrset_key* nsec, uint8_t* qname); /** * Determine if the given NSEC proves a positive wildcard response. * @param nsec: the nsec to check * @param qinf: what was queried. * @param wc: wildcard (without *. label) * @return true if proven. */ int val_nsec_proves_positive_wildcard(struct ub_packed_rrset_key* nsec, struct query_info* qinf, uint8_t* wc); /** * Determine closest encloser of a query name and the NSEC that covers it * (and thus disproved it). * A name error must have been proven already, otherwise this will be invalid. * @param qname: the name queried for. * @param nsec: the nsec RRset. * @return closest encloser dname or NULL on error (bad nsec RRset). */ uint8_t* nsec_closest_encloser(uint8_t* qname, struct ub_packed_rrset_key* nsec); /** * Determine if the given NSEC proves that a wildcard match does not exist. * * @param nsec: the nsec RRset. * @param qname: the name queried for. * @param qnamelen: length of qname. * @return true if proven. */ int val_nsec_proves_no_wc(struct ub_packed_rrset_key* nsec, uint8_t* qname, size_t qnamelen); /** * Determine if an nsec proves an insecure delegation towards the qname. * @param nsec: nsec rrset. * @param qinfo: what was queries for. * @return 0 if not, 1 if an NSEC that signals an insecure delegation to * the qname. */ int val_nsec_proves_insecuredelegation(struct ub_packed_rrset_key* nsec, struct query_info* qinfo); #endif /* VALIDATOR_VAL_NSEC_H */ unbound-1.25.1/validator/val_anchor.c0000644000175000017500000011002615203270263017153 0ustar wouterwouter/* * validator/val_anchor.c - validator trust anchor storage. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains storage for the trust anchors for the validator. */ #include "config.h" #include #include "validator/val_anchor.h" #include "validator/val_sigcrypt.h" #include "validator/autotrust.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/log.h" #include "util/net_help.h" #include "util/config_file.h" #include "util/as112.h" #include "sldns/sbuffer.h" #include "sldns/rrdef.h" #include "sldns/str2wire.h" #ifdef HAVE_GLOB_H #include #endif int anchor_cmp(const void* k1, const void* k2) { int m; struct trust_anchor* n1 = (struct trust_anchor*)k1; struct trust_anchor* n2 = (struct trust_anchor*)k2; /* no need to ntohs(class) because sort order is irrelevant */ if(n1->dclass != n2->dclass) { if(n1->dclass < n2->dclass) return -1; return 1; } return dname_lab_cmp(n1->name, n1->namelabs, n2->name, n2->namelabs, &m); } struct val_anchors* anchors_create(void) { struct val_anchors* a = (struct val_anchors*)calloc(1, sizeof(*a)); if(!a) return NULL; a->tree = rbtree_create(anchor_cmp); if(!a->tree) { anchors_delete(a); return NULL; } a->autr = autr_global_create(); if(!a->autr) { anchors_delete(a); return NULL; } lock_basic_init(&a->lock); lock_protect(&a->lock, a, sizeof(*a)); lock_protect(&a->lock, a->autr, sizeof(*a->autr)); return a; } /** delete assembled rrset */ static void assembled_rrset_delete(struct ub_packed_rrset_key* pkey) { if(!pkey) return; if(pkey->entry.data) { struct packed_rrset_data* pd = (struct packed_rrset_data*) pkey->entry.data; free(pd->rr_data); free(pd->rr_ttl); free(pd->rr_len); free(pd); } free(pkey->rk.dname); free(pkey); } /** destroy locks in tree and delete autotrust anchors */ static void anchors_delfunc(rbnode_type* elem, void* ATTR_UNUSED(arg)) { struct trust_anchor* ta = (struct trust_anchor*)elem; if(!ta) return; if(ta->autr) { autr_point_delete(ta); } else { struct ta_key* p, *np; lock_basic_destroy(&ta->lock); free(ta->name); p = ta->keylist; while(p) { np = p->next; free(p->data); free(p); p = np; } assembled_rrset_delete(ta->ds_rrset); assembled_rrset_delete(ta->dnskey_rrset); free(ta); } } void anchors_delete(struct val_anchors* anchors) { if(!anchors) return; lock_unprotect(&anchors->lock, anchors->autr); lock_unprotect(&anchors->lock, anchors); lock_basic_destroy(&anchors->lock); if(anchors->tree) traverse_postorder(anchors->tree, anchors_delfunc, NULL); free(anchors->tree); autr_global_delete(anchors->autr); free(anchors); } void anchors_init_parents_locked(struct val_anchors* anchors) { struct trust_anchor* node, *prev = NULL, *p; int m; /* nobody else can grab locks because we hold the main lock. * Thus the previous items, after unlocked, are not deleted */ RBTREE_FOR(node, struct trust_anchor*, anchors->tree) { lock_basic_lock(&node->lock); node->parent = NULL; if(!prev || prev->dclass != node->dclass) { prev = node; lock_basic_unlock(&node->lock); continue; } (void)dname_lab_cmp(prev->name, prev->namelabs, node->name, node->namelabs, &m); /* we know prev is smaller */ /* sort order like: . com. bla.com. zwb.com. net. */ /* find the previous, or parent-parent-parent */ for(p = prev; p; p = p->parent) /* looking for name with few labels, a parent */ if(p->namelabs <= m) { /* ==: since prev matched m, this is closest*/ /* <: prev matches more, but is not a parent, * this one is a (grand)parent */ node->parent = p; break; } lock_basic_unlock(&node->lock); prev = node; } } /** initialise parent pointers in the tree */ static void init_parents(struct val_anchors* anchors) { lock_basic_lock(&anchors->lock); anchors_init_parents_locked(anchors); lock_basic_unlock(&anchors->lock); } struct trust_anchor* anchor_find(struct val_anchors* anchors, uint8_t* name, int namelabs, size_t namelen, uint16_t dclass) { struct trust_anchor key; rbnode_type* n; if(!name) return NULL; key.node.key = &key; key.name = name; key.namelabs = namelabs; key.namelen = namelen; key.dclass = dclass; lock_basic_lock(&anchors->lock); n = rbtree_search(anchors->tree, &key); if(n) { lock_basic_lock(&((struct trust_anchor*)n->key)->lock); } lock_basic_unlock(&anchors->lock); if(!n) return NULL; return (struct trust_anchor*)n->key; } /** create new trust anchor object */ static struct trust_anchor* anchor_new_ta(struct val_anchors* anchors, uint8_t* name, int namelabs, size_t namelen, uint16_t dclass, int lockit) { #ifdef UNBOUND_DEBUG rbnode_type* r; #endif struct trust_anchor* ta = (struct trust_anchor*)malloc( sizeof(struct trust_anchor)); if(!ta) return NULL; memset(ta, 0, sizeof(*ta)); ta->node.key = ta; ta->name = memdup(name, namelen); if(!ta->name) { free(ta); return NULL; } ta->namelabs = namelabs; ta->namelen = namelen; ta->dclass = dclass; lock_basic_init(&ta->lock); if(lockit) { lock_basic_lock(&anchors->lock); } #ifdef UNBOUND_DEBUG r = #else (void) #endif rbtree_insert(anchors->tree, &ta->node); if(lockit) { lock_basic_unlock(&anchors->lock); } log_assert(r != NULL); return ta; } /** find trustanchor key by exact data match */ static struct ta_key* anchor_find_key(struct trust_anchor* ta, uint8_t* rdata, size_t rdata_len, uint16_t type) { struct ta_key* k; for(k = ta->keylist; k; k = k->next) { if(k->type == type && k->len == rdata_len && memcmp(k->data, rdata, rdata_len) == 0) return k; } return NULL; } /** create new trustanchor key */ static struct ta_key* anchor_new_ta_key(uint8_t* rdata, size_t rdata_len, uint16_t type) { struct ta_key* k = (struct ta_key*)malloc(sizeof(*k)); if(!k) return NULL; memset(k, 0, sizeof(*k)); k->data = memdup(rdata, rdata_len); if(!k->data) { free(k); return NULL; } k->len = rdata_len; k->type = type; return k; } /** * This routine adds a new RR to a trust anchor. The trust anchor may not * exist yet, and is created if not. The RR can be DS or DNSKEY. * This routine will also remove duplicates; storing them only once. * @param anchors: anchor storage. * @param name: name of trust anchor (wireformat) * @param type: type or RR * @param dclass: class of RR * @param rdata: rdata wireformat, starting with rdlength. * If NULL, nothing is stored, but an entry is created. * @param rdata_len: length of rdata including rdlength. * @return: NULL on error, else the trust anchor. */ static struct trust_anchor* anchor_store_new_key(struct val_anchors* anchors, uint8_t* name, uint16_t type, uint16_t dclass, uint8_t* rdata, size_t rdata_len) { struct ta_key* k; struct trust_anchor* ta; int namelabs; size_t namelen; namelabs = dname_count_size_labels(name, &namelen); if(type != LDNS_RR_TYPE_DS && type != LDNS_RR_TYPE_DNSKEY) { log_err("Bad type for trust anchor"); return 0; } /* lookup or create trustanchor */ ta = anchor_find(anchors, name, namelabs, namelen, dclass); if(!ta) { ta = anchor_new_ta(anchors, name, namelabs, namelen, dclass, 1); if(!ta) return NULL; lock_basic_lock(&ta->lock); } if(!rdata) { lock_basic_unlock(&ta->lock); return ta; } /* look for duplicates */ if(anchor_find_key(ta, rdata, rdata_len, type)) { lock_basic_unlock(&ta->lock); return ta; } k = anchor_new_ta_key(rdata, rdata_len, type); if(!k) { lock_basic_unlock(&ta->lock); return NULL; } /* add new key */ if(type == LDNS_RR_TYPE_DS) ta->numDS++; else ta->numDNSKEY++; k->next = ta->keylist; ta->keylist = k; lock_basic_unlock(&ta->lock); return ta; } /** * Add new RR. It converts ldns RR to wire format. * @param anchors: anchor storage. * @param rr: the wirerr. * @param rl: length of rr. * @param dl: length of dname. * @return NULL on error, else the trust anchor. */ static struct trust_anchor* anchor_store_new_rr(struct val_anchors* anchors, uint8_t* rr, size_t rl, size_t dl) { struct trust_anchor* ta; if(!(ta=anchor_store_new_key(anchors, rr, sldns_wirerr_get_type(rr, rl, dl), sldns_wirerr_get_class(rr, rl, dl), sldns_wirerr_get_rdatawl(rr, rl, dl), sldns_wirerr_get_rdatalen(rr, rl, dl)+2))) { return NULL; } log_nametypeclass(VERB_QUERY, "adding trusted key", rr, sldns_wirerr_get_type(rr, rl, dl), sldns_wirerr_get_class(rr, rl, dl)); return ta; } /** * Insert insecure anchor * @param anchors: anchor storage. * @param str: the domain name. * @return NULL on error, Else last trust anchor point */ static struct trust_anchor* anchor_insert_insecure(struct val_anchors* anchors, const char* str) { struct trust_anchor* ta; size_t dname_len = 0; uint8_t* nm = sldns_str2wire_dname(str, &dname_len); if(!nm) { log_err("parse error in domain name '%s'", str); return NULL; } ta = anchor_store_new_key(anchors, nm, LDNS_RR_TYPE_DS, LDNS_RR_CLASS_IN, NULL, 0); free(nm); return ta; } struct trust_anchor* anchor_store_str(struct val_anchors* anchors, sldns_buffer* buffer, const char* str) { struct trust_anchor* ta; uint8_t* rr = sldns_buffer_begin(buffer); size_t len = sldns_buffer_capacity(buffer), dname_len = 0; int status = sldns_str2wire_rr_buf(str, rr, &len, &dname_len, 0, NULL, 0, NULL, 0); if(status != 0) { log_err("error parsing trust anchor %s: at %d: %s", str, LDNS_WIREPARSE_OFFSET(status), sldns_get_errorstr_parse(status)); return NULL; } if(!(ta=anchor_store_new_rr(anchors, rr, len, dname_len))) { log_err("out of memory"); return NULL; } return ta; } /** * Read a file with trust anchors * @param anchors: anchor storage. * @param buffer: parsing buffer. * @param fname: string. * @param onlyone: only one trust anchor allowed in file. * @return NULL on error. Else last trust-anchor point. */ static struct trust_anchor* anchor_read_file(struct val_anchors* anchors, sldns_buffer* buffer, const char* fname, int onlyone) { struct trust_anchor* ta = NULL, *tanew; struct sldns_file_parse_state pst; int status; size_t len, dname_len; uint8_t* rr = sldns_buffer_begin(buffer); int ok = 1; FILE* in = fopen(fname, "r"); if(!in) { log_err("error opening file %s: %s", fname, strerror(errno)); return 0; } memset(&pst, 0, sizeof(pst)); pst.default_ttl = 3600; pst.lineno = 1; while(!feof(in)) { len = sldns_buffer_capacity(buffer); dname_len = 0; status = sldns_fp2wire_rr_buf(in, rr, &len, &dname_len, &pst); if(len == 0) /* empty, $TTL, $ORIGIN */ continue; if(status != 0) { log_err("parse error in %s:%d:%d : %s", fname, pst.lineno, LDNS_WIREPARSE_OFFSET(status), sldns_get_errorstr_parse(status)); ok = 0; break; } if(sldns_wirerr_get_type(rr, len, dname_len) != LDNS_RR_TYPE_DS && sldns_wirerr_get_type(rr, len, dname_len) != LDNS_RR_TYPE_DNSKEY) { continue; } if(!(tanew=anchor_store_new_rr(anchors, rr, len, dname_len))) { log_err("mem error at %s line %d", fname, pst.lineno); ok = 0; break; } if(onlyone && ta && ta != tanew) { log_err("error at %s line %d: no multiple anchor " "domains allowed (you can have multiple " "keys, but they must have the same name).", fname, pst.lineno); ok = 0; break; } ta = tanew; } fclose(in); if(!ok) return NULL; /* empty file is OK when multiple anchors are allowed */ if(!onlyone && !ta) return (struct trust_anchor*)1; return ta; } /** skip file to end of line */ static void skip_to_eol(FILE* in, int *c) { while((*c = getc(in)) != EOF ) { if(*c == '\n') return; } } /** true for special characters in bind configs */ static int is_bind_special(int c) { switch(c) { case '{': case '}': case '"': case ';': return 1; } return 0; } /** * Read a keyword skipping bind comments; spaces, specials, restkeywords. * The file is split into the following tokens: * * special characters, on their own, rdlen=1, { } doublequote ; * * whitespace becomes a single ' ' or tab. Newlines become spaces. * * other words ('keywords') * * comments are skipped if desired * / / C++ style comment to end of line * # to end of line * / * C style comment * / * @param in: file to read from. * @param buf: buffer, what is read is stored after current buffer position. * Space is left in the buffer to write a terminating 0. * @param line: line number is increased per line, for error reports. * @param comments: if 0, comments are not possible and become text. * if 1, comments are skipped entirely. * In BIND files, this is when reading quoted strings, for example * " base 64 text with / / in there " * @return the number of character written to the buffer. * 0 on end of file. */ static int readkeyword_bindfile(FILE* in, sldns_buffer* buf, int* line, int comments) { int c; int numdone = 0; while((c = getc(in)) != EOF ) { if(comments && c == '#') { /* # blabla */ skip_to_eol(in, &c); if(c == EOF) return 0; (*line)++; continue; } else if(comments && c=='/' && numdone>0 && /* /_/ bla*/ sldns_buffer_read_u8_at(buf, sldns_buffer_position(buf)-1) == '/') { sldns_buffer_skip(buf, -1); numdone--; skip_to_eol(in, &c); if(c == EOF) return 0; (*line)++; continue; } else if(comments && c=='*' && numdone>0 && /* /_* bla *_/ */ sldns_buffer_read_u8_at(buf, sldns_buffer_position(buf)-1) == '/') { sldns_buffer_skip(buf, -1); numdone--; /* skip to end of comment */ while(c != EOF && (c=getc(in)) != EOF ) { if(c == '*') { if((c=getc(in)) == '/') break; } if(c == '\n') (*line)++; } if(c == EOF) return 0; continue; } /* not a comment, complete the keyword */ if(numdone > 0) { /* check same type */ if(isspace((unsigned char)c)) { ungetc(c, in); return numdone; } if(is_bind_special(c)) { ungetc(c, in); return numdone; } } if(c == '\n') { c = ' '; (*line)++; } /* space for 1 char + 0 string terminator */ if(sldns_buffer_remaining(buf) < 2) { fatal_exit("trusted-keys, %d, string too long", *line); } sldns_buffer_write_u8(buf, (uint8_t)c); numdone++; if(isspace((unsigned char)c)) { /* collate whitespace into ' ' */ while((c = getc(in)) != EOF ) { if(c == '\n') (*line)++; if(!isspace((unsigned char)c)) { ungetc(c, in); break; } } if(c == EOF) return 0; return numdone; } if(is_bind_special(c)) return numdone; } return numdone; } /** skip through file to { or ; */ static int skip_to_special(FILE* in, sldns_buffer* buf, int* line, int spec) { int rdlen; sldns_buffer_clear(buf); while((rdlen=readkeyword_bindfile(in, buf, line, 1))) { if(rdlen == 1 && isspace((unsigned char)*sldns_buffer_begin(buf))) { sldns_buffer_clear(buf); continue; } if(rdlen != 1 || *sldns_buffer_begin(buf) != (uint8_t)spec) { sldns_buffer_write_u8(buf, 0); log_err("trusted-keys, line %d, expected %c", *line, spec); return 0; } return 1; } log_err("trusted-keys, line %d, expected %c got EOF", *line, spec); return 0; } /** * read contents of trusted-keys{ ... ; clauses and insert keys into storage. * @param anchors: where to store keys * @param buf: buffer to use * @param line: line number in file * @param in: file to read from. * @return 0 on error. */ static int process_bind_contents(struct val_anchors* anchors, sldns_buffer* buf, int* line, FILE* in) { /* loop over contents, collate strings before ; */ /* contents is (numbered): 0 1 2 3 4 5 6 7 8 */ /* name. 257 3 5 base64 base64 */ /* quoted value: 0 "111" 0 0 0 0 0 0 0 */ /* comments value: 1 "000" 1 1 1 "0 0 0 0" 1 */ int contnum = 0; int quoted = 0; int comments = 1; int rdlen; char* str = 0; sldns_buffer_clear(buf); while((rdlen=readkeyword_bindfile(in, buf, line, comments))) { if(rdlen == 1 && sldns_buffer_position(buf) == 1 && isspace((unsigned char)*sldns_buffer_begin(buf))) { /* starting whitespace is removed */ sldns_buffer_clear(buf); continue; } else if(rdlen == 1 && sldns_buffer_current(buf)[-1] == '"') { /* remove " from the string */ if(contnum == 0) { quoted = 1; comments = 0; } sldns_buffer_skip(buf, -1); if(contnum > 0 && quoted) { if(sldns_buffer_remaining(buf) < 8+1) { log_err("line %d, too long", *line); return 0; } sldns_buffer_write(buf, " DNSKEY ", 8); quoted = 0; comments = 1; } else if(contnum > 0) comments = !comments; continue; } else if(rdlen == 1 && sldns_buffer_current(buf)[-1] == ';') { if(contnum < 5) { sldns_buffer_write_u8(buf, 0); log_err("line %d, bad key", *line); return 0; } sldns_buffer_skip(buf, -1); sldns_buffer_write_u8(buf, 0); str = strdup((char*)sldns_buffer_begin(buf)); if(!str) { log_err("line %d, allocation failure", *line); return 0; } if(!anchor_store_str(anchors, buf, str)) { log_err("line %d, bad key", *line); free(str); return 0; } free(str); sldns_buffer_clear(buf); contnum = 0; quoted = 0; comments = 1; continue; } else if(rdlen == 1 && sldns_buffer_current(buf)[-1] == '}') { if(contnum > 0) { sldns_buffer_write_u8(buf, 0); log_err("line %d, bad key before }", *line); return 0; } return 1; } else if(rdlen == 1 && isspace((unsigned char)sldns_buffer_current(buf)[-1])) { /* leave whitespace here */ } else { /* not space or whatnot, so actual content */ contnum ++; if(contnum == 1 && !quoted) { if(sldns_buffer_remaining(buf) < 8+1) { log_err("line %d, too long", *line); return 0; } sldns_buffer_write(buf, " DNSKEY ", 8); } } } log_err("line %d, EOF before }", *line); return 0; } /** * Read a BIND9 like file with trust anchors in named.conf format. * @param anchors: anchor storage. * @param buffer: parsing buffer. * @param fname: string. * @return false on error. */ static int anchor_read_bind_file(struct val_anchors* anchors, sldns_buffer* buffer, const char* fname) { int line_nr = 1; FILE* in = fopen(fname, "r"); int rdlen = 0; if(!in) { log_err("error opening file %s: %s", fname, strerror(errno)); return 0; } verbose(VERB_QUERY, "reading in bind-compat-mode: '%s'", fname); /* scan for trusted-keys keyword, ignore everything else */ sldns_buffer_clear(buffer); while((rdlen=readkeyword_bindfile(in, buffer, &line_nr, 1)) != 0) { if(rdlen != 12 || strncmp((char*)sldns_buffer_begin(buffer), "trusted-keys", 12) != 0) { sldns_buffer_clear(buffer); /* ignore everything but trusted-keys */ continue; } if(!skip_to_special(in, buffer, &line_nr, '{')) { log_err("error in trusted key: \"%s\"", fname); fclose(in); return 0; } /* process contents */ if(!process_bind_contents(anchors, buffer, &line_nr, in)) { log_err("error in trusted key: \"%s\"", fname); fclose(in); return 0; } if(!skip_to_special(in, buffer, &line_nr, ';')) { log_err("error in trusted key: \"%s\"", fname); fclose(in); return 0; } sldns_buffer_clear(buffer); } fclose(in); return 1; } /** * Read a BIND9 like files with trust anchors in named.conf format. * Performs wildcard processing of name. * @param anchors: anchor storage. * @param buffer: parsing buffer. * @param pat: pattern string. (can be wildcarded) * @return false on error. */ static int anchor_read_bind_file_wild(struct val_anchors* anchors, sldns_buffer* buffer, const char* pat) { #ifdef HAVE_GLOB glob_t g; size_t i; int r, flags; if(!strchr(pat, '*') && !strchr(pat, '?') && !strchr(pat, '[') && !strchr(pat, '{') && !strchr(pat, '~')) { return anchor_read_bind_file(anchors, buffer, pat); } verbose(VERB_QUERY, "wildcard found, processing %s", pat); flags = 0 #ifdef GLOB_ERR | GLOB_ERR #endif #ifdef GLOB_NOSORT | GLOB_NOSORT #endif #ifdef GLOB_BRACE | GLOB_BRACE #endif #ifdef GLOB_TILDE | GLOB_TILDE #endif ; memset(&g, 0, sizeof(g)); r = glob(pat, flags, NULL, &g); if(r) { /* some error */ if(r == GLOB_NOMATCH) { verbose(VERB_QUERY, "trusted-keys-file: " "no matches for %s", pat); return 1; } else if(r == GLOB_NOSPACE) { log_err("wildcard trusted-keys-file %s: " "pattern out of memory", pat); } else if(r == GLOB_ABORTED) { log_err("wildcard trusted-keys-file %s: expansion " "aborted (%s)", pat, strerror(errno)); } else { log_err("wildcard trusted-keys-file %s: expansion " "failed (%s)", pat, strerror(errno)); } /* ignore globs that yield no files */ return 1; } /* process files found, if any */ for(i=0; i<(size_t)g.gl_pathc; i++) { if(!anchor_read_bind_file(anchors, buffer, g.gl_pathv[i])) { log_err("error reading wildcard " "trusted-keys-file: %s", g.gl_pathv[i]); globfree(&g); return 0; } } globfree(&g); return 1; #else /* not HAVE_GLOB */ return anchor_read_bind_file(anchors, buffer, pat); #endif /* HAVE_GLOB */ } /** * Assemble an rrset structure for the type * @param ta: trust anchor. * @param num: number of items to fetch from list. * @param type: fetch only items of this type. * @return rrset or NULL on error. */ static struct ub_packed_rrset_key* assemble_it(struct trust_anchor* ta, size_t num, uint16_t type) { struct ub_packed_rrset_key* pkey = (struct ub_packed_rrset_key*) malloc(sizeof(*pkey)); struct packed_rrset_data* pd; struct ta_key* tk; size_t i; if(!pkey) return NULL; memset(pkey, 0, sizeof(*pkey)); pkey->rk.dname = memdup(ta->name, ta->namelen); if(!pkey->rk.dname) { free(pkey); return NULL; } pkey->rk.dname_len = ta->namelen; pkey->rk.type = htons(type); pkey->rk.rrset_class = htons(ta->dclass); /* The rrset is build in an uncompressed way. This means it * cannot be copied in the normal way. */ pd = (struct packed_rrset_data*)malloc(sizeof(*pd)); if(!pd) { free(pkey->rk.dname); free(pkey); return NULL; } memset(pd, 0, sizeof(*pd)); pd->count = num; pd->trust = rrset_trust_ultimate; pd->rr_len = (size_t*)reallocarray(NULL, num, sizeof(size_t)); if(!pd->rr_len) { free(pd); free(pkey->rk.dname); free(pkey); return NULL; } pd->rr_ttl = (time_t*)reallocarray(NULL, num, sizeof(time_t)); if(!pd->rr_ttl) { free(pd->rr_len); free(pd); free(pkey->rk.dname); free(pkey); return NULL; } pd->rr_data = (uint8_t**)reallocarray(NULL, num, sizeof(uint8_t*)); if(!pd->rr_data) { free(pd->rr_ttl); free(pd->rr_len); free(pd); free(pkey->rk.dname); free(pkey); return NULL; } /* fill in rrs */ i=0; for(tk = ta->keylist; tk; tk = tk->next) { if(tk->type != type) continue; pd->rr_len[i] = tk->len; /* reuse data ptr to allocation in talist */ pd->rr_data[i] = tk->data; pd->rr_ttl[i] = 0; i++; } pkey->entry.data = (void*)pd; return pkey; } /** * Assemble structures for the trust DS and DNSKEY rrsets. * @param ta: trust anchor * @return: false on error. */ static int anchors_assemble(struct trust_anchor* ta) { if(ta->numDS > 0) { ta->ds_rrset = assemble_it(ta, ta->numDS, LDNS_RR_TYPE_DS); if(!ta->ds_rrset) return 0; } if(ta->numDNSKEY > 0) { ta->dnskey_rrset = assemble_it(ta, ta->numDNSKEY, LDNS_RR_TYPE_DNSKEY); if(!ta->dnskey_rrset) return 0; } return 1; } /** * Check DS algos for support, warn if not. * @param ta: trust anchor * @return number of DS anchors with unsupported algorithms. */ static size_t anchors_ds_unsupported(struct trust_anchor* ta) { size_t i, num = 0; for(i=0; inumDS; i++) { if(!ds_digest_algo_is_supported(ta->ds_rrset, i) || !ds_key_algo_is_supported(ta->ds_rrset, i)) num++; } return num; } /** * Check DNSKEY algos for support, warn if not. * @param ta: trust anchor * @return number of DNSKEY anchors with unsupported algorithms. */ static size_t anchors_dnskey_unsupported(struct trust_anchor* ta) { size_t i, num = 0; for(i=0; inumDNSKEY; i++) { if(!dnskey_algo_is_supported(ta->dnskey_rrset, i) || !dnskey_size_is_supported(ta->dnskey_rrset, i)) num++; } return num; } /** * Assemble the rrsets in the anchors, ready for use by validator. * @param anchors: trust anchor storage. * @return: false on error. */ static int anchors_assemble_rrsets(struct val_anchors* anchors) { struct trust_anchor* ta; struct trust_anchor* next; size_t nods, nokey; lock_basic_lock(&anchors->lock); ta=(struct trust_anchor*)rbtree_first(anchors->tree); while((rbnode_type*)ta != RBTREE_NULL) { next = (struct trust_anchor*)rbtree_next(&ta->node); lock_basic_lock(&ta->lock); if(ta->autr || (ta->numDS == 0 && ta->numDNSKEY == 0)) { lock_basic_unlock(&ta->lock); ta = next; /* skip */ continue; } if(!anchors_assemble(ta)) { log_err("out of memory"); lock_basic_unlock(&ta->lock); lock_basic_unlock(&anchors->lock); return 0; } nods = anchors_ds_unsupported(ta); nokey = anchors_dnskey_unsupported(ta); if(nods) { log_nametypeclass(NO_VERBOSE, "warning: unsupported " "algorithm for trust anchor", ta->name, LDNS_RR_TYPE_DS, ta->dclass); } if(nokey) { log_nametypeclass(NO_VERBOSE, "warning: unsupported " "algorithm for trust anchor", ta->name, LDNS_RR_TYPE_DNSKEY, ta->dclass); } if(nods == ta->numDS && nokey == ta->numDNSKEY) { char b[LDNS_MAX_DOMAINLEN]; dname_str(ta->name, b); log_warn("trust anchor %s has no supported algorithms," " the anchor is ignored (check if you need to" " upgrade unbound and " #ifdef HAVE_LIBRESSL "libressl" #else "openssl" #endif ")", b); (void)rbtree_delete(anchors->tree, &ta->node); lock_basic_unlock(&ta->lock); anchors_delfunc(&ta->node, NULL); ta = next; continue; } lock_basic_unlock(&ta->lock); ta = next; } lock_basic_unlock(&anchors->lock); return 1; } int anchors_apply_cfg(struct val_anchors* anchors, struct config_file* cfg) { struct config_strlist* f; const char** zstr; char* nm; sldns_buffer* parsebuf = sldns_buffer_new(65535); if(!parsebuf) { log_err("malloc error in anchors_apply_cfg."); return 0; } if(cfg->insecure_lan_zones) { for(zstr = as112_zones; *zstr; zstr++) { if(!anchor_insert_insecure(anchors, *zstr)) { log_err("error in insecure-lan-zones: %s", *zstr); sldns_buffer_free(parsebuf); return 0; } } } for(f = cfg->domain_insecure; f; f = f->next) { if(!f->str || f->str[0] == 0) /* empty "" */ continue; if(!anchor_insert_insecure(anchors, f->str)) { log_err("error in domain-insecure: %s", f->str); sldns_buffer_free(parsebuf); return 0; } } for(f = cfg->trust_anchor_file_list; f; f = f->next) { if(!f->str || f->str[0] == 0) /* empty "" */ continue; nm = f->str; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) nm += strlen(cfg->chrootdir); if(!anchor_read_file(anchors, parsebuf, nm, 0)) { log_err("error reading trust-anchor-file: %s", f->str); sldns_buffer_free(parsebuf); return 0; } } for(f = cfg->trusted_keys_file_list; f; f = f->next) { if(!f->str || f->str[0] == 0) /* empty "" */ continue; nm = f->str; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) nm += strlen(cfg->chrootdir); if(!anchor_read_bind_file_wild(anchors, parsebuf, nm)) { log_err("error reading trusted-keys-file: %s", f->str); sldns_buffer_free(parsebuf); return 0; } } for(f = cfg->trust_anchor_list; f; f = f->next) { if(!f->str || f->str[0] == 0) /* empty "" */ continue; if(!anchor_store_str(anchors, parsebuf, f->str)) { log_err("error in trust-anchor: \"%s\"", f->str); sldns_buffer_free(parsebuf); return 0; } } /* do autr last, so that it sees what anchors are filled by other * means can can print errors about double config for the name */ for(f = cfg->auto_trust_anchor_file_list; f; f = f->next) { if(!f->str || f->str[0] == 0) /* empty "" */ continue; nm = f->str; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(nm, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) nm += strlen(cfg->chrootdir); if(!autr_read_file(anchors, nm)) { log_err("error reading auto-trust-anchor-file: %s", f->str); sldns_buffer_free(parsebuf); return 0; } } /* first assemble, since it may delete useless anchors */ anchors_assemble_rrsets(anchors); init_parents(anchors); sldns_buffer_free(parsebuf); if(verbosity >= VERB_ALGO) autr_debug_print(anchors); return 1; } struct trust_anchor* anchors_lookup(struct val_anchors* anchors, uint8_t* qname, size_t qname_len, uint16_t qclass) { struct trust_anchor key; struct trust_anchor* result; rbnode_type* res = NULL; key.node.key = &key; key.name = qname; key.namelabs = dname_count_labels(qname); key.namelen = qname_len; key.dclass = qclass; lock_basic_lock(&anchors->lock); if(rbtree_find_less_equal(anchors->tree, &key, &res)) { /* exact */ result = (struct trust_anchor*)res; } else { /* smaller element (or no element) */ int m; result = (struct trust_anchor*)res; if(!result || result->dclass != qclass) { lock_basic_unlock(&anchors->lock); return NULL; } /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->namelabs, key.name, key.namelabs, &m); while(result) { /* go up until qname is subdomain of stub */ if(result->namelabs <= m) break; result = result->parent; } } if(result) { lock_basic_lock(&result->lock); } lock_basic_unlock(&anchors->lock); return result; } /** Get memory usage of assembled key rrset */ static size_t assembled_rrset_get_mem(struct ub_packed_rrset_key* pkey) { size_t s; if(!pkey) return 0; s = sizeof(*pkey) + pkey->rk.dname_len; if(pkey->entry.data) { struct packed_rrset_data* pd = (struct packed_rrset_data*) pkey->entry.data; s += sizeof(*pd) + pd->count * (sizeof(size_t)+sizeof(time_t)+ sizeof(uint8_t*)); } return s; } size_t anchors_get_mem(struct val_anchors* anchors) { struct trust_anchor *ta; struct ta_key *k; size_t s; if(!anchors) return 0; s = sizeof(*anchors); lock_basic_lock(&anchors->lock); RBTREE_FOR(ta, struct trust_anchor*, anchors->tree) { lock_basic_lock(&ta->lock); s += sizeof(*ta) + ta->namelen; /* keys and so on */ for(k = ta->keylist; k; k = k->next) { s += sizeof(*k) + k->len; } s += assembled_rrset_get_mem(ta->ds_rrset); s += assembled_rrset_get_mem(ta->dnskey_rrset); if(ta->autr) { struct autr_ta* p; s += sizeof(*ta->autr); if(ta->autr->file) s += strlen(ta->autr->file); for(p = ta->autr->keys; p; p=p->next) { s += sizeof(*p) + p->rr_len; } } lock_basic_unlock(&ta->lock); } lock_basic_unlock(&anchors->lock); return s; } int anchors_add_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm) { struct trust_anchor key; key.node.key = &key; key.name = nm; key.namelabs = dname_count_size_labels(nm, &key.namelen); key.dclass = c; lock_basic_lock(&anchors->lock); if(rbtree_search(anchors->tree, &key)) { lock_basic_unlock(&anchors->lock); /* nothing to do, already an anchor or insecure point */ return 1; } if(!anchor_new_ta(anchors, nm, key.namelabs, key.namelen, c, 0)) { log_err("out of memory"); lock_basic_unlock(&anchors->lock); return 0; } /* no other contents in new ta, because it is insecure point */ anchors_init_parents_locked(anchors); lock_basic_unlock(&anchors->lock); return 1; } void anchors_delete_insecure(struct val_anchors* anchors, uint16_t c, uint8_t* nm) { struct trust_anchor key; struct trust_anchor* ta; key.node.key = &key; key.name = nm; key.namelabs = dname_count_size_labels(nm, &key.namelen); key.dclass = c; lock_basic_lock(&anchors->lock); if(!(ta=(struct trust_anchor*)rbtree_search(anchors->tree, &key))) { lock_basic_unlock(&anchors->lock); /* nothing there */ return; } /* lock it to drive away other threads that use it */ lock_basic_lock(&ta->lock); /* see if its really an insecure point */ if(ta->keylist || ta->autr || ta->numDS || ta->numDNSKEY) { lock_basic_unlock(&anchors->lock); lock_basic_unlock(&ta->lock); /* its not an insecure point, do not remove it */ return; } /* remove from tree */ (void)rbtree_delete(anchors->tree, &ta->node); anchors_init_parents_locked(anchors); lock_basic_unlock(&anchors->lock); /* actual free of data */ lock_basic_unlock(&ta->lock); anchors_delfunc(&ta->node, NULL); } /** compare two keytags, return -1, 0 or 1 */ static int keytag_compare(const void* x, const void* y) { if(*(uint16_t*)x == *(uint16_t*)y) return 0; if(*(uint16_t*)x > *(uint16_t*)y) return 1; return -1; } size_t anchor_list_keytags(struct trust_anchor* ta, uint16_t* list, size_t num) { size_t i, ret = 0; if(ta->numDS == 0 && ta->numDNSKEY == 0) return 0; /* insecure point */ if(ta->numDS != 0 && ta->ds_rrset) { struct packed_rrset_data* d=(struct packed_rrset_data*) ta->ds_rrset->entry.data; for(i=0; icount; i++) { if(ret == num) continue; list[ret++] = ds_get_keytag(ta->ds_rrset, i); } } if(ta->numDNSKEY != 0 && ta->dnskey_rrset) { struct packed_rrset_data* d=(struct packed_rrset_data*) ta->dnskey_rrset->entry.data; for(i=0; icount; i++) { if(ret == num) continue; list[ret++] = dnskey_calc_keytag(ta->dnskey_rrset, i); } } qsort(list, ret, sizeof(*list), keytag_compare); return ret; } int anchor_has_keytag(struct val_anchors* anchors, uint8_t* name, int namelabs, size_t namelen, uint16_t dclass, uint16_t keytag) { uint16_t* taglist; uint16_t* tl; size_t numtag, i; struct trust_anchor* anchor = anchor_find(anchors, name, namelabs, namelen, dclass); if(!anchor) return 0; if(!anchor->numDS && !anchor->numDNSKEY) { lock_basic_unlock(&anchor->lock); return 0; } taglist = calloc(anchor->numDS + anchor->numDNSKEY, sizeof(*taglist)); if(!taglist) { lock_basic_unlock(&anchor->lock); return 0; } numtag = anchor_list_keytags(anchor, taglist, anchor->numDS+anchor->numDNSKEY); lock_basic_unlock(&anchor->lock); if(!numtag) { free(taglist); return 0; } tl = taglist; for(i=0; ilock); ta=(struct trust_anchor*)rbtree_first(anchors->tree); while((rbnode_type*)ta != RBTREE_NULL) { next = (struct trust_anchor*)rbtree_next(&ta->node); lock_basic_lock(&ta->lock); if(ta->numDS != 0 || ta->numDNSKEY != 0) { /* not an insecurepoint */ lock_basic_unlock(&anchors->lock); return ta; } lock_basic_unlock(&ta->lock); ta = next; } lock_basic_unlock(&anchors->lock); return NULL; } void anchors_swap_tree(struct val_anchors* anchors, struct val_anchors* data) { rbtree_type* oldtree; rbtree_type oldprobe; if(!anchors || !data) return; /* If anchors is NULL, there is no validation. */ oldtree = anchors->tree; oldprobe = anchors->autr->probe; anchors->tree = data->tree; anchors->autr->probe = data->autr->probe; data->tree = oldtree; data->autr->probe = oldprobe; } unbound-1.25.1/validator/val_utils.h0000644000175000017500000004531615203270263017057 0ustar wouterwouter/* * validator/val_utils.h - validator utility functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. */ #ifndef VALIDATOR_VAL_UTILS_H #define VALIDATOR_VAL_UTILS_H #include "util/data/packed_rrset.h" #include "sldns/pkthdr.h" #include "sldns/rrdef.h" struct query_info; struct reply_info; struct val_env; struct module_env; struct module_qstate; struct ub_packed_rrset_key; struct key_entry_key; struct regional; struct val_anchors; struct rrset_cache; struct sock_list; /** * Response classifications for the validator. The different types of proofs. */ enum val_classification { /** Not subtyped yet. */ VAL_CLASS_UNTYPED = 0, /** Not a recognized subtype. */ VAL_CLASS_UNKNOWN, /** A positive, direct, response */ VAL_CLASS_POSITIVE, /** A positive response, with a CNAME/DNAME chain. */ VAL_CLASS_CNAME, /** A NOERROR/NODATA response. */ VAL_CLASS_NODATA, /** A NXDOMAIN response. */ VAL_CLASS_NAMEERROR, /** A CNAME/DNAME chain, and the offset is at the end of it, * but there is no answer here, it can be NAMEERROR or NODATA. */ VAL_CLASS_CNAMENOANSWER, /** A referral, from cache with a nonRD query. */ VAL_CLASS_REFERRAL, /** A response to a qtype=ANY query. */ VAL_CLASS_ANY }; /** * Given a response, classify ANSWER responses into a subtype. * @param query_flags: query flags for the original query. * @param origqinf: query info. The original query name. * @param qinf: query info. The chased query name. * @param rep: response. The original response. * @param skip: offset into the original response answer section. * @return A subtype, all values possible except UNTYPED . * Once CNAME type is returned you can increase skip. * Then, another CNAME type, CNAME_NOANSWER or POSITIVE are possible. */ enum val_classification val_classify_response(uint16_t query_flags, struct query_info* origqinf, struct query_info* qinf, struct reply_info* rep, size_t skip); /** * Given a response, determine the name of the "signer". This is primarily * to determine if the response is, in fact, signed at all, and, if so, what * is the name of the most pertinent keyset. * * @param subtype: the type from classify. * @param qinf: query, the chased query name. * @param rep: response to that, original response. * @param cname_skip: how many answer rrsets have been skipped due to CNAME * chains being chased around. * @param signer_name: signer name, if the response is signed * (even partially), or null if the response isn't signed. * @param signer_len: length of signer_name of 0 if signer_name is NULL. */ void val_find_signer(enum val_classification subtype, struct query_info* qinf, struct reply_info* rep, size_t cname_skip, uint8_t** signer_name, size_t* signer_len); /** * Verify RRset with keys from a keyset. * @param env: module environment (scratch buffer) * @param ve: validator environment (verification settings) * @param rrset: what to verify * @param kkey: key_entry to verify with. * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @param verified: if not NULL, the number of RRSIG validations is returned. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return security status of verification. */ enum sec_status val_verify_rrset_entry(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int* verified, char* reasonbuf, size_t reasonlen); /** * Verify DNSKEYs with DS rrset. Like val_verify_new_DNSKEYs but * returns a sec_status instead of a key_entry. * @param env: module environment (scratch buffer) * @param ve: validator environment (verification settings) * @param dnskey_rrset: DNSKEY rrset to verify * @param ds_rrset: DS rrset to verify with. * @param sigalg: if nonNULL provide downgrade protection otherwise one * algorithm is enough. The list of signalled algorithms is returned, * must have enough space for ALGO_NEEDS_MAX+1. * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return: sec_status_secure if a DS matches. * sec_status_insecure if end of trust (i.e., unknown algorithms). * sec_status_bogus if it fails. */ enum sec_status val_verify_DNSKEY_with_DS(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); /** * Verify DNSKEYs with DS and DNSKEY rrset. Like val_verify_DNSKEY_with_DS * but for a trust anchor. * @param env: module environment (scratch buffer) * @param ve: validator environment (verification settings) * @param dnskey_rrset: DNSKEY rrset to verify * @param ta_ds: DS rrset to verify with. * @param ta_dnskey: DNSKEY rrset to verify with. * @param sigalg: if nonNULL provide downgrade protection otherwise one * algorithm is enough. The list of signalled algorithms is returned, * must have enough space for ALGO_NEEDS_MAX+1. * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return: sec_status_secure if a DS matches. * sec_status_insecure if end of trust (i.e., unknown algorithms). * sec_status_bogus if it fails. */ enum sec_status val_verify_DNSKEY_with_TA(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ta_ds, struct ub_packed_rrset_key* ta_dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); /** * Verify new DNSKEYs with DS rrset. The DS contains hash values that should * match the DNSKEY keys. * match the DS to a DNSKEY and verify the DNSKEY rrset with that key. * * @param region: where to allocate key entry result. * @param env: module environment (scratch buffer) * @param ve: validator environment (verification settings) * @param dnskey_rrset: DNSKEY rrset to verify * @param ds_rrset: DS rrset to verify with. * @param downprot: if true provide downgrade protection otherwise one * algorithm is enough. * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return a KeyEntry. This will either contain the now trusted * dnskey_rrset, a "null" key entry indicating that this DS * rrset/DNSKEY pair indicate an secure end to the island of trust * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey * rrset fails to verify. Note that the "null" response should * generally only occur in a private algorithm scenario: normally * this sort of thing is checked before fetching the matching DNSKEY * rrset. * if downprot is set, a key entry with an algo list is made. */ struct key_entry_key* val_verify_new_DNSKEYs(struct regional* region, struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ds_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); /** * Verify rrset with trust anchor: DS and DNSKEY rrset. * * @param region: where to allocate key entry result. * @param env: module environment (scratch buffer) * @param ve: validator environment (verification settings) * @param dnskey_rrset: DNSKEY rrset to verify * @param ta_ds_rrset: DS rrset to verify with. * @param ta_dnskey_rrset: the DNSKEY rrset to verify with. * @param downprot: if true provide downgrade protection otherwise one * algorithm is enough. * @param reason: reason of failure. Fixed string or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param qstate: qstate with region. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return a KeyEntry. This will either contain the now trusted * dnskey_rrset, a "null" key entry indicating that this DS * rrset/DNSKEY pair indicate an secure end to the island of trust * (i.e., unknown algorithms), or a "bad" KeyEntry if the dnskey * rrset fails to verify. Note that the "null" response should * generally only occur in a private algorithm scenario: normally * this sort of thing is checked before fetching the matching DNSKEY * rrset. * if downprot is set, a key entry with an algo list is made. */ struct key_entry_key* val_verify_new_DNSKEYs_with_ta(struct regional* region, struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* dnskey_rrset, struct ub_packed_rrset_key* ta_ds_rrset, struct ub_packed_rrset_key* ta_dnskey_rrset, int downprot, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen); /** * Determine if DS rrset is usable for validator or not. * Returns true if the algorithms for key and DShash are supported, * for at least one RR. * * @param ds_rrset: the newly received DS rrset. * @return true or false if not usable. */ int val_dsset_isusable(struct ub_packed_rrset_key* ds_rrset); /** * Determine by looking at a signed RRset whether or not the RRset name was * the result of a wildcard expansion. If so, return the name of the * generating wildcard. * * @param rrset The rrset to check. * @param wc: the wildcard name, if the rrset was synthesized from a wildcard. * unchanged if not. The wildcard name, without "*." in front, is * returned. This is a pointer into the rrset owner name. * @param wc_len: the length of the returned wildcard name. * @return false if the signatures are inconsistent in indicating the * wildcard status; possible spoofing of wildcard response for other * responses is being tried. We lost the status which rrsig was verified * after the verification routine finished, so we simply check if * the signatures are consistent; inserting a fake signature is a denial * of service; but in that you could also have removed the real * signature anyway. */ int val_rrset_wildcard(struct ub_packed_rrset_key* rrset, uint8_t** wc, size_t* wc_len); /** * Chase the cname to the next query name. * @param qchase: the current query name, updated to next target. * @param rep: original message reply to look at CNAMEs. * @param cname_skip: the skip into the answer section. Updated to skip * DNAME and CNAME to the next part of the answer. * @return false on error (bad rdata). */ int val_chase_cname(struct query_info* qchase, struct reply_info* rep, size_t* cname_skip); /** * Fill up the chased reply with the content from the original reply; * as pointers to those rrsets. Select the part after the cname_skip into * the answer section, NS and AR sections that are signed with same signer. * * @param chase: chased reply, filled up. * @param orig: original reply. * @param cname_skip: which part of the answer section to skip. * The skipped part contains CNAME(and DNAME)s that have been chased. * @param name: the signer name to look for. * @param len: length of name. * @param signer: signer name or NULL if an unsigned RRset is considered. * If NULL, rrsets with the lookup name are copied over. */ void val_fill_reply(struct reply_info* chase, struct reply_info* orig, size_t cname_skip, uint8_t* name, size_t len, uint8_t* signer); /** * Remove rrset with index from reply, from the authority section. * @param rep: reply to remove it from. * @param index: rrset to remove, must be in the authority section. */ void val_reply_remove_auth(struct reply_info* rep, size_t index); /** * Remove all unsigned or non-secure status rrsets from NS and AR sections. * So that unsigned data does not get let through to clients, when we have * found the data to be secure. * * @param env: environment with cleaning options. * @param rep: reply to dump all nonsecure stuff out of. */ void val_check_nonsecure(struct module_env* env, struct reply_info* rep); /** * Mark all unchecked rrset entries not below a trust anchor as indeterminate. * Only security==unchecked rrsets are updated. * @param rep: the reply with rrsets. * @param anchors: the trust anchors. * @param r: rrset cache to store updated security status into. * @param env: module environment */ void val_mark_indeterminate(struct reply_info* rep, struct val_anchors* anchors, struct rrset_cache* r, struct module_env* env); /** * Mark all unchecked rrset entries below a NULL key entry as insecure. * Only security==unchecked rrsets are updated. * @param rep: the reply with rrsets. * @param kname: end of secure space name. * @param r: rrset cache to store updated security status into. * @param env: module environment */ void val_mark_insecure(struct reply_info* rep, uint8_t* kname, struct rrset_cache* r, struct module_env* env); /** * Find next unchecked rrset position, return it for skip. * @param rep: the original reply to look into. * @param skip: the skip now. * @return new skip, which may be at the rep->rrset_count position to signal * there are no unchecked items. */ size_t val_next_unchecked(struct reply_info* rep, size_t skip); /** * Find the signer name for an RRset. * @param rrset: the rrset. * @param sname: signer name is returned or NULL if not signed. * @param slen: length of sname (or 0). */ void val_find_rrset_signer(struct ub_packed_rrset_key* rrset, uint8_t** sname, size_t* slen); /** * Get string to denote the classification result. * @param subtype: from classification function. * @return static string to describe the classification. */ const char* val_classification_to_string(enum val_classification subtype); /** * Add existing list to blacklist. * @param blacklist: the blacklist with result * @param region: the region where blacklist is allocated. * Allocation failures are logged. * @param origin: origin list to add, if NULL, a cache-entry is added to * the blacklist to stop cache from being used. * @param cross: if true this is a cross-qstate copy, and the 'origin' * list is not allocated in the same region as the blacklist. */ void val_blacklist(struct sock_list** blacklist, struct regional* region, struct sock_list* origin, int cross); /** * check if has dnssec info, and if it has signed nsecs. gives error reason. * @param rep: reply to check. * @param reason: returned on fail. * @return false if message has no signed nsecs. Can not prove negatives. */ int val_has_signed_nsecs(struct reply_info* rep, char** reason); /** * Return algo number for favorite (best) algorithm that we support in DS. * @param ds_rrset: the DSes in this rrset are inspected and best algo chosen. * @return algo number or 0 if none supported. 0 is unused as algo number. */ int val_favorite_ds_algo(struct ub_packed_rrset_key* ds_rrset); /** * Find DS denial message in cache. Saves new qstate allocation and allows * the validator to use partial content which is not enough to construct a * message for network (or user) consumption. Without SOA for example, * which is a common occurrence in the unbound code since the referrals contain * NSEC/NSEC3 rrs without the SOA element, thus do not allow synthesis of a * full negative reply, but do allow synthesis of sufficient proof. * @param env: query env with caches and time. * @param nm: name of DS record sought. * @param nmlen: length of name. * @param c: class of DS RR. * @param region: where to allocate result. * @param topname: name of the key that is currently in use, that will get * used to validate the result, and thus no higher entries from the * negative cache need to be examined. * @return a dns_msg on success. NULL on failure. */ struct dns_msg* val_find_DS(struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t c, struct regional* region, uint8_t* topname); /** * Derive expected CNAME target from DNAME substitution per RFC 6672 s3.1 * @param cname: CNAME RRset, (e.g., b.d.a005.test CNAME 'some cname target') * @param dname: DNAME RRset, (e.g., d.a005.test DNAME tgt.a005.test) * @param out: Output buffer for expected CNAME target * @param outlen: Output buffer size * @return: 1 on success, 0 on error */ int derive_cname_from_dname(struct ub_packed_rrset_key* cname, struct ub_packed_rrset_key* dname, uint8_t* out, size_t outlen); #endif /* VALIDATOR_VAL_UTILS_H */ unbound-1.25.1/validator/val_secalgo.c0000644000175000017500000015272615203270263017333 0ustar wouterwouter/* * validator/val_secalgo.c - validator security algorithm functions. * * Copyright (c) 2012, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * These functions take raw data buffers, formatted for crypto verification, * and do the library calls (for the crypto library in use). */ #include "config.h" /* packed_rrset on top to define enum types (forced by c99 standard) */ #include "util/data/packed_rrset.h" #include "validator/val_secalgo.h" #include "validator/val_nsec3.h" #include "util/log.h" #include "sldns/rrdef.h" #include "sldns/keyraw.h" #include "sldns/sbuffer.h" #if !defined(HAVE_SSL) && !defined(HAVE_NSS) && !defined(HAVE_NETTLE) #error "Need crypto library to do digital signature cryptography" #endif /** fake DSA support for unit tests */ int fake_dsa = 0; /** fake SHA1 support for unit tests */ int fake_sha1 = 0; /* OpenSSL implementation */ #ifdef HAVE_SSL #ifdef HAVE_OPENSSL_ERR_H #include #endif #ifdef HAVE_OPENSSL_RAND_H #include #endif #ifdef HAVE_OPENSSL_CONF_H #include #endif #ifdef HAVE_OPENSSL_ENGINE_H #include #endif #if defined(HAVE_OPENSSL_DSA_H) && defined(USE_DSA) #include #endif /** * Output a libcrypto openssl error to the logfile. * @param str: string to add to it. * @param e: the error to output, error number from ERR_get_error(). */ static void log_crypto_error(const char* str, unsigned long e) { char buf[128]; /* or use ERR_error_string if ERR_error_string_n is not avail TODO */ ERR_error_string_n(e, buf, sizeof(buf)); /* buf now contains */ /* error:[error code]:[library name]:[function name]:[reason string] */ log_err("%s crypto %s", str, buf); } /** * Output a libcrypto openssl error to the logfile as a debug message. * @param level: debug level to use in verbose() call * @param str: string to add to it. * @param e: the error to output, error number from ERR_get_error(). */ static void log_crypto_verbose(enum verbosity_value level, const char* str, unsigned long e) { char buf[128]; /* or use ERR_error_string if ERR_error_string_n is not avail TODO */ ERR_error_string_n(e, buf, sizeof(buf)); /* buf now contains */ /* error:[error code]:[library name]:[function name]:[reason string] */ verbose(level, "%s crypto %s", str, buf); } /* return size of digest if supported, or 0 otherwise */ size_t nsec3_hash_algo_size_supported(int id) { switch(id) { case NSEC3_HASH_SHA1: return SHA_DIGEST_LENGTH; default: return 0; } } /* perform nsec3 hash. return false on failure */ int secalgo_nsec3_hash(int algo, unsigned char* buf, size_t len, unsigned char* res) { switch(algo) { case NSEC3_HASH_SHA1: #ifdef OPENSSL_FIPS if(!sldns_digest_evp(buf, len, res, EVP_sha1())) log_crypto_error("could not digest with EVP_sha1", ERR_get_error()); #else (void)SHA1(buf, len, res); #endif return 1; default: return 0; } } void secalgo_hash_sha256(unsigned char* buf, size_t len, unsigned char* res) { #ifdef OPENSSL_FIPS if(!sldns_digest_evp(buf, len, res, EVP_sha256())) log_crypto_error("could not digest with EVP_sha256", ERR_get_error()); #else (void)SHA256(buf, len, res); #endif } /** hash structure for keeping track of running hashes */ struct secalgo_hash { /** the openssl message digest context */ EVP_MD_CTX* ctx; }; /** create secalgo hash with hash type */ static struct secalgo_hash* secalgo_hash_create_md(const EVP_MD* md) { struct secalgo_hash* h; if(!md) return NULL; h = calloc(1, sizeof(*h)); if(!h) return NULL; h->ctx = EVP_MD_CTX_create(); if(!h->ctx) { free(h); return NULL; } if(!EVP_DigestInit_ex(h->ctx, md, NULL)) { EVP_MD_CTX_destroy(h->ctx); free(h); return NULL; } return h; } struct secalgo_hash* secalgo_hash_create_sha384(void) { return secalgo_hash_create_md(EVP_sha384()); } struct secalgo_hash* secalgo_hash_create_sha512(void) { return secalgo_hash_create_md(EVP_sha512()); } int secalgo_hash_update(struct secalgo_hash* hash, uint8_t* data, size_t len) { return EVP_DigestUpdate(hash->ctx, (unsigned char*)data, (unsigned int)len); } int secalgo_hash_final(struct secalgo_hash* hash, uint8_t* result, size_t maxlen, size_t* resultlen) { if(EVP_MD_CTX_size(hash->ctx) > (int)maxlen) { *resultlen = 0; log_err("secalgo_hash_final: hash buffer too small"); return 0; } *resultlen = EVP_MD_CTX_size(hash->ctx); return EVP_DigestFinal_ex(hash->ctx, result, NULL); } void secalgo_hash_delete(struct secalgo_hash* hash) { if(!hash) return; EVP_MD_CTX_destroy(hash->ctx); free(hash); } /** * Return size of DS digest according to its hash algorithm. * @param algo: DS digest algo. * @return size in bytes of digest, or 0 if not supported. */ size_t ds_digest_size_supported(int algo) { switch(algo) { case LDNS_SHA1: #if defined(HAVE_EVP_SHA1) && defined(USE_SHA1) #ifdef HAVE_EVP_DEFAULT_PROPERTIES_IS_FIPS_ENABLED if (EVP_default_properties_is_fips_enabled(NULL)) return 0; #endif return SHA_DIGEST_LENGTH; #else if(fake_sha1) return 20; return 0; #endif #ifdef HAVE_EVP_SHA256 case LDNS_SHA256: return SHA256_DIGEST_LENGTH; #endif #ifdef USE_GOST case LDNS_HASH_GOST: /* we support GOST if it can be loaded */ (void)sldns_key_EVP_load_gost_id(); if(EVP_get_digestbyname("md_gost94")) return 32; else return 0; #endif #ifdef USE_ECDSA case LDNS_SHA384: return SHA384_DIGEST_LENGTH; #endif default: break; } return 0; } #ifdef USE_GOST /** Perform GOST hash */ static int do_gost94(unsigned char* data, size_t len, unsigned char* dest) { const EVP_MD* md = EVP_get_digestbyname("md_gost94"); if(!md) return 0; return sldns_digest_evp(data, (unsigned int)len, dest, md); } #endif int secalgo_ds_digest(int algo, unsigned char* buf, size_t len, unsigned char* res) { switch(algo) { #if defined(HAVE_EVP_SHA1) && defined(USE_SHA1) case LDNS_SHA1: #ifdef OPENSSL_FIPS if(!sldns_digest_evp(buf, len, res, EVP_sha1())) log_crypto_error("could not digest with EVP_sha1", ERR_get_error()); #else (void)SHA1(buf, len, res); #endif return 1; #endif #ifdef HAVE_EVP_SHA256 case LDNS_SHA256: #ifdef OPENSSL_FIPS if(!sldns_digest_evp(buf, len, res, EVP_sha256())) log_crypto_error("could not digest with EVP_sha256", ERR_get_error()); #else (void)SHA256(buf, len, res); #endif return 1; #endif #ifdef USE_GOST case LDNS_HASH_GOST: if(do_gost94(buf, len, res)) return 1; break; #endif #ifdef USE_ECDSA case LDNS_SHA384: #ifdef OPENSSL_FIPS if(!sldns_digest_evp(buf, len, res, EVP_sha384())) log_crypto_error("could not digest with EVP_sha384", ERR_get_error()); #else (void)SHA384(buf, len, res); #endif return 1; #endif default: verbose(VERB_QUERY, "unknown DS digest algorithm %d", algo); break; } return 0; } /** return true if DNSKEY algorithm id is supported */ int dnskey_algo_id_is_supported(int id) { switch(id) { case LDNS_RSAMD5: /* RFC 6725 deprecates RSAMD5 */ return 0; case LDNS_DSA: case LDNS_DSA_NSEC3: #if defined(USE_DSA) && defined(USE_SHA1) return 1; #else if(fake_dsa || fake_sha1) return 1; return 0; #endif case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: #ifdef USE_SHA1 #ifdef HAVE_EVP_DEFAULT_PROPERTIES_IS_FIPS_ENABLED return !EVP_default_properties_is_fips_enabled(NULL); #else return 1; #endif #else if(fake_sha1) return 1; return 0; #endif #if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) case LDNS_RSASHA256: #endif #if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) case LDNS_RSASHA512: #endif #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: case LDNS_ECDSAP384SHA384: #endif #if (defined(HAVE_EVP_SHA256) && defined(USE_SHA2)) || (defined(HAVE_EVP_SHA512) && defined(USE_SHA2)) || defined(USE_ECDSA) return 1; #endif #ifdef USE_ED25519 case LDNS_ED25519: #endif #ifdef USE_ED448 case LDNS_ED448: #endif #if defined(USE_ED25519) || defined(USE_ED448) #ifdef HAVE_EVP_DEFAULT_PROPERTIES_IS_FIPS_ENABLED return !EVP_default_properties_is_fips_enabled(NULL); #else return 1; #endif #endif #ifdef USE_GOST case LDNS_ECC_GOST: /* we support GOST if it can be loaded */ return sldns_key_EVP_load_gost_id(); #endif default: return 0; } } #ifdef USE_DSA /** * Setup DSA key digest in DER encoding ... * @param sig: input is signature output alloced ptr (unless failure). * caller must free alloced ptr if this routine returns true. * @param len: input is initial siglen, output is output len. * @return false on failure. */ static int setup_dsa_sig(unsigned char** sig, unsigned int* len) { unsigned char* orig = *sig; unsigned int origlen = *len; int newlen; BIGNUM *R, *S; DSA_SIG *dsasig; /* extract the R and S field from the sig buffer */ if(origlen < 1 + 2*SHA_DIGEST_LENGTH) return 0; R = BN_new(); if(!R) return 0; (void) BN_bin2bn(orig + 1, SHA_DIGEST_LENGTH, R); S = BN_new(); if(!S) return 0; (void) BN_bin2bn(orig + 21, SHA_DIGEST_LENGTH, S); dsasig = DSA_SIG_new(); if(!dsasig) return 0; #ifdef HAVE_DSA_SIG_SET0 if(!DSA_SIG_set0(dsasig, R, S)) { DSA_SIG_free(dsasig); return 0; } #else # ifndef S_SPLINT_S dsasig->r = R; dsasig->s = S; # endif /* S_SPLINT_S */ #endif *sig = NULL; newlen = i2d_DSA_SIG(dsasig, sig); if(newlen < 0) { DSA_SIG_free(dsasig); free(*sig); return 0; } *len = (unsigned int)newlen; DSA_SIG_free(dsasig); return 1; } #endif /* USE_DSA */ #ifdef USE_ECDSA /** * Setup the ECDSA signature in its encoding that the library wants. * Converts from plain numbers to ASN formatted. * @param sig: input is signature, output alloced ptr (unless failure). * caller must free alloced ptr if this routine returns true. * @param len: input is initial siglen, output is output len. * @return false on failure. */ static int setup_ecdsa_sig(unsigned char** sig, unsigned int* len) { /* convert from two BIGNUMs in the rdata buffer, to ASN notation. * ASN preamble: 30440220 0220 * the '20' is the length of that field (=bnsize). i * the '44' is the total remaining length. * if negative, start with leading zero. * if starts with 00s, remove them from the number. */ uint8_t pre[] = {0x30, 0x44, 0x02, 0x20}; int pre_len = 4; uint8_t mid[] = {0x02, 0x20}; int mid_len = 2; int raw_sig_len, r_high, s_high, r_rem=0, s_rem=0; int bnsize = (int)((*len)/2); unsigned char* d = *sig; uint8_t* p; /* if too short or not even length, fails */ if(*len < 16 || bnsize*2 != (int)*len) return 0; /* strip leading zeroes from r (but not last one) */ while(r_rem < bnsize-1 && d[r_rem] == 0) r_rem++; /* strip leading zeroes from s (but not last one) */ while(s_rem < bnsize-1 && d[bnsize+s_rem] == 0) s_rem++; r_high = ((d[0+r_rem]&0x80)?1:0); s_high = ((d[bnsize+s_rem]&0x80)?1:0); raw_sig_len = pre_len + r_high + bnsize - r_rem + mid_len + s_high + bnsize - s_rem; *sig = (unsigned char*)malloc((size_t)raw_sig_len); if(!*sig) return 0; p = (uint8_t*)*sig; p[0] = pre[0]; p[1] = (uint8_t)(raw_sig_len-2); p[2] = pre[2]; p[3] = (uint8_t)(bnsize + r_high - r_rem); p += 4; if(r_high) { *p = 0; p += 1; } memmove(p, d+r_rem, (size_t)bnsize-r_rem); p += bnsize-r_rem; memmove(p, mid, (size_t)mid_len-1); p += mid_len-1; *p = (uint8_t)(bnsize + s_high - s_rem); p += 1; if(s_high) { *p = 0; p += 1; } memmove(p, d+bnsize+s_rem, (size_t)bnsize-s_rem); *len = (unsigned int)raw_sig_len; return 1; } #endif /* USE_ECDSA */ #ifdef USE_ECDSA_EVP_WORKAROUND static EVP_MD ecdsa_evp_256_md; static EVP_MD ecdsa_evp_384_md; void ecdsa_evp_workaround_init(void) { /* openssl before 1.0.0 fixes RSA with the SHA256 * hash in EVP. We create one for ecdsa_sha256 */ ecdsa_evp_256_md = *EVP_sha256(); ecdsa_evp_256_md.required_pkey_type[0] = EVP_PKEY_EC; ecdsa_evp_256_md.verify = (void*)ECDSA_verify; ecdsa_evp_384_md = *EVP_sha384(); ecdsa_evp_384_md.required_pkey_type[0] = EVP_PKEY_EC; ecdsa_evp_384_md.verify = (void*)ECDSA_verify; } #endif /* USE_ECDSA_EVP_WORKAROUND */ /** * Setup key and digest for verification. Adjust sig if necessary. * * @param algo: key algorithm * @param evp_key: EVP PKEY public key to create. * @param digest_type: digest type to use * @param key: key to setup for. * @param keylen: length of key. * @return false on failure. */ static int setup_key_digest(int algo, EVP_PKEY** evp_key, const EVP_MD** digest_type, unsigned char* key, size_t keylen) { switch(algo) { #if defined(USE_DSA) && defined(USE_SHA1) case LDNS_DSA: case LDNS_DSA_NSEC3: *evp_key = sldns_key_dsa2pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: sldns_key_dsa2pkey failed"); return 0; } #ifdef HAVE_EVP_DSS1 *digest_type = EVP_dss1(); #else *digest_type = EVP_sha1(); #endif break; #endif /* USE_DSA && USE_SHA1 */ #if defined(USE_SHA1) || (defined(HAVE_EVP_SHA256) && defined(USE_SHA2)) || (defined(HAVE_EVP_SHA512) && defined(USE_SHA2)) #ifdef USE_SHA1 case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: #endif #if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) case LDNS_RSASHA256: #endif #if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) case LDNS_RSASHA512: #endif *evp_key = sldns_key_rsa2pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: sldns_key_rsa2pkey SHA failed"); return 0; } /* select SHA version */ #if defined(HAVE_EVP_SHA256) && defined(USE_SHA2) if(algo == LDNS_RSASHA256) *digest_type = EVP_sha256(); else #endif #if defined(HAVE_EVP_SHA512) && defined(USE_SHA2) if(algo == LDNS_RSASHA512) *digest_type = EVP_sha512(); else #endif #ifdef USE_SHA1 *digest_type = EVP_sha1(); #else { verbose(VERB_QUERY, "no digest available"); return 0; } #endif break; #endif /* defined(USE_SHA1) || (defined(HAVE_EVP_SHA256) && defined(USE_SHA2)) || (defined(HAVE_EVP_SHA512) && defined(USE_SHA2)) */ case LDNS_RSAMD5: *evp_key = sldns_key_rsa2pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: sldns_key_rsa2pkey MD5 failed"); return 0; } *digest_type = EVP_md5(); break; #ifdef USE_GOST case LDNS_ECC_GOST: *evp_key = sldns_gost2pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: " "sldns_gost2pkey_raw failed"); return 0; } *digest_type = EVP_get_digestbyname("md_gost94"); if(!*digest_type) { verbose(VERB_QUERY, "verify: " "EVP_getdigest md_gost94 failed"); return 0; } break; #endif #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: *evp_key = sldns_ecdsa2pkey_raw(key, keylen, LDNS_ECDSAP256SHA256); if(!*evp_key) { verbose(VERB_QUERY, "verify: " "sldns_ecdsa2pkey_raw failed"); return 0; } #ifdef USE_ECDSA_EVP_WORKAROUND *digest_type = &ecdsa_evp_256_md; #else *digest_type = EVP_sha256(); #endif break; case LDNS_ECDSAP384SHA384: *evp_key = sldns_ecdsa2pkey_raw(key, keylen, LDNS_ECDSAP384SHA384); if(!*evp_key) { verbose(VERB_QUERY, "verify: " "sldns_ecdsa2pkey_raw failed"); return 0; } #ifdef USE_ECDSA_EVP_WORKAROUND *digest_type = &ecdsa_evp_384_md; #else *digest_type = EVP_sha384(); #endif break; #endif /* USE_ECDSA */ #ifdef USE_ED25519 case LDNS_ED25519: *evp_key = sldns_ed255192pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: " "sldns_ed255192pkey_raw failed"); return 0; } *digest_type = NULL; break; #endif /* USE_ED25519 */ #ifdef USE_ED448 case LDNS_ED448: *evp_key = sldns_ed4482pkey_raw(key, keylen); if(!*evp_key) { verbose(VERB_QUERY, "verify: " "sldns_ed4482pkey_raw failed"); return 0; } *digest_type = NULL; break; #endif /* USE_ED448 */ default: verbose(VERB_QUERY, "verify: unknown algorithm %d", algo); return 0; } return 1; } static void digest_ctx_free(EVP_MD_CTX* ctx, EVP_PKEY *evp_key, unsigned char* sigblock, int dofree, int docrypto_free) { #ifdef HAVE_EVP_MD_CTX_NEW EVP_MD_CTX_destroy(ctx); #else EVP_MD_CTX_cleanup(ctx); free(ctx); #endif EVP_PKEY_free(evp_key); if(dofree) free(sigblock); else if(docrypto_free) OPENSSL_free(sigblock); } static enum sec_status digest_error_status(const char *str) { unsigned long e = ERR_get_error(); #ifdef EVP_R_INVALID_DIGEST if (ERR_GET_LIB(e) == ERR_LIB_EVP && ERR_GET_REASON(e) == EVP_R_INVALID_DIGEST) { log_crypto_verbose(VERB_ALGO, str, e); return sec_status_indeterminate; } #endif log_crypto_verbose(VERB_QUERY, str, e); return sec_status_unchecked; } /** * Check a canonical sig+rrset and signature against a dnskey * @param buf: buffer with data to verify, the first rrsig part and the * canonicalized rrset. * @param algo: DNSKEY algorithm. * @param sigblock: signature rdata field from RRSIG * @param sigblock_len: length of sigblock data. * @param key: public key data from DNSKEY RR. * @param keylen: length of keydata. * @param reason: bogus reason in more detail. * @return secure if verification succeeded, bogus on crypto failure, * unchecked on format errors and alloc failures, indeterminate * if digest is not supported by the crypto library (openssl3+ only). */ enum sec_status verify_canonrrset(sldns_buffer* buf, int algo, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen, char** reason) { const EVP_MD *digest_type; EVP_MD_CTX* ctx; int res, dofree = 0, docrypto_free = 0; EVP_PKEY *evp_key = NULL; #ifndef USE_DSA if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3) &&(fake_dsa||fake_sha1)) return sec_status_secure; #endif #ifndef USE_SHA1 if(fake_sha1 && (algo == LDNS_DSA || algo == LDNS_DSA_NSEC3 || algo == LDNS_RSASHA1 || algo == LDNS_RSASHA1_NSEC3)) return sec_status_secure; #endif if(!setup_key_digest(algo, &evp_key, &digest_type, key, keylen)) { verbose(VERB_QUERY, "verify: failed to setup key"); *reason = "use of key for crypto failed"; EVP_PKEY_free(evp_key); return sec_status_bogus; } #ifdef USE_DSA /* if it is a DSA signature in bind format, convert to DER format */ if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3) && sigblock_len == 1+2*SHA_DIGEST_LENGTH) { if(!setup_dsa_sig(&sigblock, &sigblock_len)) { verbose(VERB_QUERY, "verify: failed to setup DSA sig"); *reason = "use of key for DSA crypto failed"; EVP_PKEY_free(evp_key); return sec_status_bogus; } docrypto_free = 1; } #endif #if defined(USE_ECDSA) && defined(USE_DSA) else #endif #ifdef USE_ECDSA if(algo == LDNS_ECDSAP256SHA256 || algo == LDNS_ECDSAP384SHA384) { /* EVP uses ASN prefix on sig, which is not in the wire data */ if(!setup_ecdsa_sig(&sigblock, &sigblock_len)) { verbose(VERB_QUERY, "verify: failed to setup ECDSA sig"); *reason = "use of signature for ECDSA crypto failed"; EVP_PKEY_free(evp_key); return sec_status_bogus; } dofree = 1; } #endif /* USE_ECDSA */ /* do the signature cryptography work */ #ifdef HAVE_EVP_MD_CTX_NEW ctx = EVP_MD_CTX_new(); #else ctx = (EVP_MD_CTX*)malloc(sizeof(*ctx)); if(ctx) EVP_MD_CTX_init(ctx); #endif if(!ctx) { log_err("EVP_MD_CTX_new: malloc failure"); EVP_PKEY_free(evp_key); if(dofree) free(sigblock); else if(docrypto_free) OPENSSL_free(sigblock); return sec_status_unchecked; } #ifndef HAVE_EVP_DIGESTVERIFY if(EVP_DigestInit(ctx, digest_type) == 0) { enum sec_status sec; sec = digest_error_status("verify: EVP_DigestInit failed"); digest_ctx_free(ctx, evp_key, sigblock, dofree, docrypto_free); return sec; } if(EVP_DigestUpdate(ctx, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf)) == 0) { log_crypto_verbose(VERB_QUERY, "verify: EVP_DigestUpdate failed", ERR_get_error()); digest_ctx_free(ctx, evp_key, sigblock, dofree, docrypto_free); return sec_status_unchecked; } res = EVP_VerifyFinal(ctx, sigblock, sigblock_len, evp_key); #else /* HAVE_EVP_DIGESTVERIFY */ if(EVP_DigestVerifyInit(ctx, NULL, digest_type, NULL, evp_key) == 0) { enum sec_status sec; sec = digest_error_status("verify: EVP_DigestVerifyInit failed"); digest_ctx_free(ctx, evp_key, sigblock, dofree, docrypto_free); return sec; } res = EVP_DigestVerify(ctx, sigblock, sigblock_len, (unsigned char*)sldns_buffer_begin(buf), sldns_buffer_limit(buf)); #endif digest_ctx_free(ctx, evp_key, sigblock, dofree, docrypto_free); if(res == 1) { return sec_status_secure; } else if(res == 0) { verbose(VERB_QUERY, "verify: signature mismatch"); *reason = "signature crypto failed"; return sec_status_bogus; } log_crypto_error("verify:", ERR_get_error()); return sec_status_unchecked; } /**************************************************/ #elif defined(HAVE_NSS) /* libnss implementation */ /* nss3 */ #include "sechash.h" #include "pk11pub.h" #include "keyhi.h" #include "secerr.h" #include "cryptohi.h" /* nspr4 */ #include "prerror.h" /* return size of digest if supported, or 0 otherwise */ size_t nsec3_hash_algo_size_supported(int id) { switch(id) { case NSEC3_HASH_SHA1: return SHA1_LENGTH; default: return 0; } } /* perform nsec3 hash. return false on failure */ int secalgo_nsec3_hash(int algo, unsigned char* buf, size_t len, unsigned char* res) { switch(algo) { case NSEC3_HASH_SHA1: (void)HASH_HashBuf(HASH_AlgSHA1, res, buf, (unsigned long)len); return 1; default: return 0; } } void secalgo_hash_sha256(unsigned char* buf, size_t len, unsigned char* res) { (void)HASH_HashBuf(HASH_AlgSHA256, res, buf, (unsigned long)len); } /** the secalgo hash structure */ struct secalgo_hash { /** hash context */ HASHContext* ctx; }; /** create hash struct of type */ static struct secalgo_hash* secalgo_hash_create_type(HASH_HashType tp) { struct secalgo_hash* h = calloc(1, sizeof(*h)); if(!h) return NULL; h->ctx = HASH_Create(tp); if(!h->ctx) { free(h); return NULL; } return h; } struct secalgo_hash* secalgo_hash_create_sha384(void) { return secalgo_hash_create_type(HASH_AlgSHA384); } struct secalgo_hash* secalgo_hash_create_sha512(void) { return secalgo_hash_create_type(HASH_AlgSHA512); } int secalgo_hash_update(struct secalgo_hash* hash, uint8_t* data, size_t len) { HASH_Update(hash->ctx, (unsigned char*)data, (unsigned int)len); return 1; } int secalgo_hash_final(struct secalgo_hash* hash, uint8_t* result, size_t maxlen, size_t* resultlen) { unsigned int reslen = 0; if(HASH_ResultLenContext(hash->ctx) > (unsigned int)maxlen) { *resultlen = 0; log_err("secalgo_hash_final: hash buffer too small"); return 0; } HASH_End(hash->ctx, (unsigned char*)result, &reslen, (unsigned int)maxlen); *resultlen = (size_t)reslen; return 1; } void secalgo_hash_delete(struct secalgo_hash* hash) { if(!hash) return; HASH_Destroy(hash->ctx); free(hash); } size_t ds_digest_size_supported(int algo) { /* uses libNSS */ switch(algo) { #ifdef USE_SHA1 case LDNS_SHA1: return SHA1_LENGTH; #endif #ifdef USE_SHA2 case LDNS_SHA256: return SHA256_LENGTH; #endif #ifdef USE_ECDSA case LDNS_SHA384: return SHA384_LENGTH; #endif /* GOST not supported in NSS */ case LDNS_HASH_GOST: default: break; } return 0; } int secalgo_ds_digest(int algo, unsigned char* buf, size_t len, unsigned char* res) { /* uses libNSS */ switch(algo) { #ifdef USE_SHA1 case LDNS_SHA1: return HASH_HashBuf(HASH_AlgSHA1, res, buf, len) == SECSuccess; #endif #if defined(USE_SHA2) case LDNS_SHA256: return HASH_HashBuf(HASH_AlgSHA256, res, buf, len) == SECSuccess; #endif #ifdef USE_ECDSA case LDNS_SHA384: return HASH_HashBuf(HASH_AlgSHA384, res, buf, len) == SECSuccess; #endif case LDNS_HASH_GOST: default: verbose(VERB_QUERY, "unknown DS digest algorithm %d", algo); break; } return 0; } int dnskey_algo_id_is_supported(int id) { /* uses libNSS */ switch(id) { case LDNS_RSAMD5: /* RFC 6725 deprecates RSAMD5 */ return 0; #if defined(USE_SHA1) || defined(USE_SHA2) #if defined(USE_DSA) && defined(USE_SHA1) case LDNS_DSA: case LDNS_DSA_NSEC3: #endif #ifdef USE_SHA1 case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: #endif #ifdef USE_SHA2 case LDNS_RSASHA256: #endif #ifdef USE_SHA2 case LDNS_RSASHA512: #endif return 1; #endif /* SHA1 or SHA2 */ #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: case LDNS_ECDSAP384SHA384: return PK11_TokenExists(CKM_ECDSA); #endif case LDNS_ECC_GOST: default: return 0; } } /* return a new public key for NSS */ static SECKEYPublicKey* nss_key_create(KeyType ktype) { SECKEYPublicKey* key; PLArenaPool* arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if(!arena) { log_err("out of memory, PORT_NewArena failed"); return NULL; } key = PORT_ArenaZNew(arena, SECKEYPublicKey); if(!key) { log_err("out of memory, PORT_ArenaZNew failed"); PORT_FreeArena(arena, PR_FALSE); return NULL; } key->arena = arena; key->keyType = ktype; key->pkcs11Slot = NULL; key->pkcs11ID = CK_INVALID_HANDLE; return key; } static SECKEYPublicKey* nss_buf2ecdsa(unsigned char* key, size_t len, int algo) { SECKEYPublicKey* pk; SECItem pub = {siBuffer, NULL, 0}; SECItem params = {siBuffer, NULL, 0}; static unsigned char param256[] = { /* OBJECTIDENTIFIER 1.2.840.10045.3.1.7 (P-256) * {iso(1) member-body(2) us(840) ansi-x962(10045) curves(3) prime(1) prime256v1(7)} */ 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07 }; static unsigned char param384[] = { /* OBJECTIDENTIFIER 1.3.132.0.34 (P-384) * {iso(1) identified-organization(3) certicom(132) curve(0) ansip384r1(34)} */ 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22 }; unsigned char buf[256+2]; /* sufficient for 2*384/8+1 */ /* check length, which uncompressed must be 2 bignums */ if(algo == LDNS_ECDSAP256SHA256) { if(len != 2*256/8) return NULL; /* ECCurve_X9_62_PRIME_256V1 */ } else if(algo == LDNS_ECDSAP384SHA384) { if(len != 2*384/8) return NULL; /* ECCurve_X9_62_PRIME_384R1 */ } else return NULL; buf[0] = 0x04; /* POINT_FORM_UNCOMPRESSED */ memmove(buf+1, key, len); pub.data = buf; pub.len = len+1; if(algo == LDNS_ECDSAP256SHA256) { params.data = param256; params.len = sizeof(param256); } else { params.data = param384; params.len = sizeof(param384); } pk = nss_key_create(ecKey); if(!pk) return NULL; pk->u.ec.size = (len/2)*8; if(SECITEM_CopyItem(pk->arena, &pk->u.ec.publicValue, &pub)) { SECKEY_DestroyPublicKey(pk); return NULL; } if(SECITEM_CopyItem(pk->arena, &pk->u.ec.DEREncodedParams, ¶ms)) { SECKEY_DestroyPublicKey(pk); return NULL; } return pk; } #if defined(USE_DSA) && defined(USE_SHA1) static SECKEYPublicKey* nss_buf2dsa(unsigned char* key, size_t len) { SECKEYPublicKey* pk; uint8_t T; uint16_t length; uint16_t offset; SECItem Q = {siBuffer, NULL, 0}; SECItem P = {siBuffer, NULL, 0}; SECItem G = {siBuffer, NULL, 0}; SECItem Y = {siBuffer, NULL, 0}; if(len == 0) return NULL; T = (uint8_t)key[0]; length = (64 + T * 8); offset = 1; if (T > 8) { return NULL; } if(len < (size_t)1 + SHA1_LENGTH + 3*length) return NULL; Q.data = key+offset; Q.len = SHA1_LENGTH; offset += SHA1_LENGTH; P.data = key+offset; P.len = length; offset += length; G.data = key+offset; G.len = length; offset += length; Y.data = key+offset; Y.len = length; offset += length; pk = nss_key_create(dsaKey); if(!pk) return NULL; if(SECITEM_CopyItem(pk->arena, &pk->u.dsa.params.prime, &P)) { SECKEY_DestroyPublicKey(pk); return NULL; } if(SECITEM_CopyItem(pk->arena, &pk->u.dsa.params.subPrime, &Q)) { SECKEY_DestroyPublicKey(pk); return NULL; } if(SECITEM_CopyItem(pk->arena, &pk->u.dsa.params.base, &G)) { SECKEY_DestroyPublicKey(pk); return NULL; } if(SECITEM_CopyItem(pk->arena, &pk->u.dsa.publicValue, &Y)) { SECKEY_DestroyPublicKey(pk); return NULL; } return pk; } #endif /* USE_DSA && USE_SHA1 */ static SECKEYPublicKey* nss_buf2rsa(unsigned char* key, size_t len) { SECKEYPublicKey* pk; uint16_t exp; uint16_t offset; uint16_t int16; SECItem modulus = {siBuffer, NULL, 0}; SECItem exponent = {siBuffer, NULL, 0}; if(len == 0) return NULL; if(key[0] == 0) { if(len < 3) return NULL; /* the exponent is too large so it's places further */ memmove(&int16, key+1, 2); exp = ntohs(int16); offset = 3; } else { exp = key[0]; offset = 1; } /* key length at least one */ if(len < (size_t)offset + exp + 1) return NULL; exponent.data = key+offset; exponent.len = exp; offset += exp; modulus.data = key+offset; modulus.len = (len - offset); pk = nss_key_create(rsaKey); if(!pk) return NULL; if(SECITEM_CopyItem(pk->arena, &pk->u.rsa.modulus, &modulus)) { SECKEY_DestroyPublicKey(pk); return NULL; } if(SECITEM_CopyItem(pk->arena, &pk->u.rsa.publicExponent, &exponent)) { SECKEY_DestroyPublicKey(pk); return NULL; } return pk; } /** * Setup key and digest for verification. Adjust sig if necessary. * * @param algo: key algorithm * @param evp_key: EVP PKEY public key to create. * @param digest_type: digest type to use * @param key: key to setup for. * @param keylen: length of key. * @param prefix: if returned, the ASN prefix for the hashblob. * @param prefixlen: length of the prefix. * @return false on failure. */ static int nss_setup_key_digest(int algo, SECKEYPublicKey** pubkey, HASH_HashType* htype, unsigned char* key, size_t keylen, unsigned char** prefix, size_t* prefixlen) { /* uses libNSS */ /* hash prefix for md5, RFC2537 */ static unsigned char p_md5[] = {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}; /* hash prefix to prepend to hash output, from RFC3110 */ static unsigned char p_sha1[] = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14}; /* from RFC5702 */ static unsigned char p_sha256[] = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}; static unsigned char p_sha512[] = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40}; /* from RFC6234 */ /* for future RSASHA384 .. static unsigned char p_sha384[] = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}; */ switch(algo) { #if defined(USE_SHA1) || defined(USE_SHA2) #if defined(USE_DSA) && defined(USE_SHA1) case LDNS_DSA: case LDNS_DSA_NSEC3: *pubkey = nss_buf2dsa(key, keylen); if(!*pubkey) { log_err("verify: malloc failure in crypto"); return 0; } *htype = HASH_AlgSHA1; /* no prefix for DSA verification */ break; #endif #ifdef USE_SHA1 case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: #endif #ifdef USE_SHA2 case LDNS_RSASHA256: #endif #ifdef USE_SHA2 case LDNS_RSASHA512: #endif *pubkey = nss_buf2rsa(key, keylen); if(!*pubkey) { log_err("verify: malloc failure in crypto"); return 0; } /* select SHA version */ #ifdef USE_SHA2 if(algo == LDNS_RSASHA256) { *htype = HASH_AlgSHA256; *prefix = p_sha256; *prefixlen = sizeof(p_sha256); } else #endif #ifdef USE_SHA2 if(algo == LDNS_RSASHA512) { *htype = HASH_AlgSHA512; *prefix = p_sha512; *prefixlen = sizeof(p_sha512); } else #endif #ifdef USE_SHA1 { *htype = HASH_AlgSHA1; *prefix = p_sha1; *prefixlen = sizeof(p_sha1); } #else { verbose(VERB_QUERY, "verify: no digest algo"); return 0; } #endif break; #endif /* SHA1 or SHA2 */ case LDNS_RSAMD5: *pubkey = nss_buf2rsa(key, keylen); if(!*pubkey) { log_err("verify: malloc failure in crypto"); return 0; } *htype = HASH_AlgMD5; *prefix = p_md5; *prefixlen = sizeof(p_md5); break; #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: *pubkey = nss_buf2ecdsa(key, keylen, LDNS_ECDSAP256SHA256); if(!*pubkey) { log_err("verify: malloc failure in crypto"); return 0; } *htype = HASH_AlgSHA256; /* no prefix for DSA verification */ break; case LDNS_ECDSAP384SHA384: *pubkey = nss_buf2ecdsa(key, keylen, LDNS_ECDSAP384SHA384); if(!*pubkey) { log_err("verify: malloc failure in crypto"); return 0; } *htype = HASH_AlgSHA384; /* no prefix for DSA verification */ break; #endif /* USE_ECDSA */ case LDNS_ECC_GOST: default: verbose(VERB_QUERY, "verify: unknown algorithm %d", algo); return 0; } return 1; } /** * Check a canonical sig+rrset and signature against a dnskey * @param buf: buffer with data to verify, the first rrsig part and the * canonicalized rrset. * @param algo: DNSKEY algorithm. * @param sigblock: signature rdata field from RRSIG * @param sigblock_len: length of sigblock data. * @param key: public key data from DNSKEY RR. * @param keylen: length of keydata. * @param reason: bogus reason in more detail. * @return secure if verification succeeded, bogus on crypto failure, * unchecked on format errors and alloc failures. */ enum sec_status verify_canonrrset(sldns_buffer* buf, int algo, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen, char** reason) { /* uses libNSS */ /* large enough for the different hashes */ unsigned char hash[HASH_LENGTH_MAX]; unsigned char hash2[HASH_LENGTH_MAX*2]; HASH_HashType htype = 0; SECKEYPublicKey* pubkey = NULL; SECItem secsig = {siBuffer, sigblock, sigblock_len}; SECItem sechash = {siBuffer, hash, 0}; SECStatus res; unsigned char* prefix = NULL; /* prefix for hash, RFC3110, RFC5702 */ size_t prefixlen = 0; int err; if(!nss_setup_key_digest(algo, &pubkey, &htype, key, keylen, &prefix, &prefixlen)) { verbose(VERB_QUERY, "verify: failed to setup key"); *reason = "use of key for crypto failed"; SECKEY_DestroyPublicKey(pubkey); return sec_status_bogus; } #if defined(USE_DSA) && defined(USE_SHA1) /* need to convert DSA, ECDSA signatures? */ if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3)) { if(sigblock_len == 1+2*SHA1_LENGTH) { secsig.data ++; secsig.len --; } else { SECItem* p = DSAU_DecodeDerSig(&secsig); if(!p) { verbose(VERB_QUERY, "verify: failed DER decode"); *reason = "signature DER decode failed"; SECKEY_DestroyPublicKey(pubkey); return sec_status_bogus; } if(SECITEM_CopyItem(pubkey->arena, &secsig, p)) { log_err("alloc failure in DER decode"); SECKEY_DestroyPublicKey(pubkey); SECITEM_FreeItem(p, PR_TRUE); return sec_status_unchecked; } SECITEM_FreeItem(p, PR_TRUE); } } #endif /* USE_DSA */ /* do the signature cryptography work */ /* hash the data */ sechash.len = HASH_ResultLen(htype); if(sechash.len > sizeof(hash)) { verbose(VERB_QUERY, "verify: hash too large for buffer"); SECKEY_DestroyPublicKey(pubkey); return sec_status_unchecked; } if(HASH_HashBuf(htype, hash, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf)) != SECSuccess) { verbose(VERB_QUERY, "verify: HASH_HashBuf failed"); SECKEY_DestroyPublicKey(pubkey); return sec_status_unchecked; } if(prefix) { int hashlen = sechash.len; if(prefixlen+hashlen > sizeof(hash2)) { verbose(VERB_QUERY, "verify: hashprefix too large"); SECKEY_DestroyPublicKey(pubkey); return sec_status_unchecked; } sechash.data = hash2; sechash.len = prefixlen+hashlen; memcpy(sechash.data, prefix, prefixlen); memmove(sechash.data+prefixlen, hash, hashlen); } /* verify the signature */ res = PK11_Verify(pubkey, &secsig, &sechash, NULL /*wincx*/); SECKEY_DestroyPublicKey(pubkey); if(res == SECSuccess) { return sec_status_secure; } err = PORT_GetError(); if(err != SEC_ERROR_BAD_SIGNATURE) { /* failed to verify */ verbose(VERB_QUERY, "verify: PK11_Verify failed: %s", PORT_ErrorToString(err)); /* if it is not supported, like ECC is removed, we get, * SEC_ERROR_NO_MODULE */ if(err == SEC_ERROR_NO_MODULE) return sec_status_unchecked; /* but other errors are commonly returned * for a bad signature from NSS. Thus we return bogus, * not unchecked */ *reason = "signature crypto failed"; return sec_status_bogus; } verbose(VERB_QUERY, "verify: signature mismatch: %s", PORT_ErrorToString(err)); *reason = "signature crypto failed"; return sec_status_bogus; } #elif defined(HAVE_NETTLE) #include "sha.h" #include "bignum.h" #include "macros.h" #include "rsa.h" #include "dsa.h" #ifdef HAVE_NETTLE_DSA_COMPAT_H #include "dsa-compat.h" #endif #include "asn1.h" #ifdef USE_ECDSA #include "ecdsa.h" #include "ecc-curve.h" #endif #ifdef HAVE_NETTLE_EDDSA_H #include "eddsa.h" #endif static int _digest_nettle(int algo, uint8_t* buf, size_t len, unsigned char* res) { switch(algo) { case SHA1_DIGEST_SIZE: { struct sha1_ctx ctx; sha1_init(&ctx); sha1_update(&ctx, len, buf); sha1_digest(&ctx, SHA1_DIGEST_SIZE, res); return 1; } case SHA256_DIGEST_SIZE: { struct sha256_ctx ctx; sha256_init(&ctx); sha256_update(&ctx, len, buf); sha256_digest(&ctx, SHA256_DIGEST_SIZE, res); return 1; } case SHA384_DIGEST_SIZE: { struct sha384_ctx ctx; sha384_init(&ctx); sha384_update(&ctx, len, buf); sha384_digest(&ctx, SHA384_DIGEST_SIZE, res); return 1; } case SHA512_DIGEST_SIZE: { struct sha512_ctx ctx; sha512_init(&ctx); sha512_update(&ctx, len, buf); sha512_digest(&ctx, SHA512_DIGEST_SIZE, res); return 1; } default: break; } return 0; } /* return size of digest if supported, or 0 otherwise */ size_t nsec3_hash_algo_size_supported(int id) { switch(id) { case NSEC3_HASH_SHA1: return SHA1_DIGEST_SIZE; default: return 0; } } /* perform nsec3 hash. return false on failure */ int secalgo_nsec3_hash(int algo, unsigned char* buf, size_t len, unsigned char* res) { switch(algo) { case NSEC3_HASH_SHA1: return _digest_nettle(SHA1_DIGEST_SIZE, (uint8_t*)buf, len, res); default: return 0; } } void secalgo_hash_sha256(unsigned char* buf, size_t len, unsigned char* res) { _digest_nettle(SHA256_DIGEST_SIZE, (uint8_t*)buf, len, res); } /** secalgo hash structure */ struct secalgo_hash { /** if it is 384 or 512 */ int active; /** context for sha384 */ struct sha384_ctx ctx384; /** context for sha512 */ struct sha512_ctx ctx512; }; struct secalgo_hash* secalgo_hash_create_sha384(void) { struct secalgo_hash* h = calloc(1, sizeof(*h)); if(!h) return NULL; h->active = 384; sha384_init(&h->ctx384); return h; } struct secalgo_hash* secalgo_hash_create_sha512(void) { struct secalgo_hash* h = calloc(1, sizeof(*h)); if(!h) return NULL; h->active = 512; sha512_init(&h->ctx512); return h; } int secalgo_hash_update(struct secalgo_hash* hash, uint8_t* data, size_t len) { if(hash->active == 384) { sha384_update(&hash->ctx384, len, data); } else if(hash->active == 512) { sha512_update(&hash->ctx512, len, data); } else { return 0; } return 1; } int secalgo_hash_final(struct secalgo_hash* hash, uint8_t* result, size_t maxlen, size_t* resultlen) { if(hash->active == 384) { if(SHA384_DIGEST_SIZE > maxlen) { *resultlen = 0; log_err("secalgo_hash_final: hash buffer too small"); return 0; } *resultlen = SHA384_DIGEST_SIZE; sha384_digest(&hash->ctx384, SHA384_DIGEST_SIZE, (unsigned char*)result); } else if(hash->active == 512) { if(SHA512_DIGEST_SIZE > maxlen) { *resultlen = 0; log_err("secalgo_hash_final: hash buffer too small"); return 0; } *resultlen = SHA512_DIGEST_SIZE; sha512_digest(&hash->ctx512, SHA512_DIGEST_SIZE, (unsigned char*)result); } else { *resultlen = 0; return 0; } return 1; } void secalgo_hash_delete(struct secalgo_hash* hash) { if(!hash) return; free(hash); } /** * Return size of DS digest according to its hash algorithm. * @param algo: DS digest algo. * @return size in bytes of digest, or 0 if not supported. */ size_t ds_digest_size_supported(int algo) { switch(algo) { case LDNS_SHA1: #ifdef USE_SHA1 return SHA1_DIGEST_SIZE; #else if(fake_sha1) return 20; return 0; #endif #ifdef USE_SHA2 case LDNS_SHA256: return SHA256_DIGEST_SIZE; #endif #ifdef USE_ECDSA case LDNS_SHA384: return SHA384_DIGEST_SIZE; #endif /* GOST not supported */ case LDNS_HASH_GOST: default: break; } return 0; } int secalgo_ds_digest(int algo, unsigned char* buf, size_t len, unsigned char* res) { switch(algo) { #ifdef USE_SHA1 case LDNS_SHA1: return _digest_nettle(SHA1_DIGEST_SIZE, buf, len, res); #endif #if defined(USE_SHA2) case LDNS_SHA256: return _digest_nettle(SHA256_DIGEST_SIZE, buf, len, res); #endif #ifdef USE_ECDSA case LDNS_SHA384: return _digest_nettle(SHA384_DIGEST_SIZE, buf, len, res); #endif case LDNS_HASH_GOST: default: verbose(VERB_QUERY, "unknown DS digest algorithm %d", algo); break; } return 0; } int dnskey_algo_id_is_supported(int id) { /* uses libnettle */ switch(id) { case LDNS_DSA: case LDNS_DSA_NSEC3: #if defined(USE_DSA) && defined(USE_SHA1) return 1; #else if(fake_dsa || fake_sha1) return 1; return 0; #endif case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: #ifdef USE_SHA1 return 1; #else if(fake_sha1) return 1; return 0; #endif #ifdef USE_SHA2 case LDNS_RSASHA256: case LDNS_RSASHA512: #endif #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: case LDNS_ECDSAP384SHA384: #endif return 1; #ifdef USE_ED25519 case LDNS_ED25519: return 1; #endif case LDNS_RSAMD5: /* RFC 6725 deprecates RSAMD5 */ case LDNS_ECC_GOST: default: return 0; } } #if defined(USE_DSA) && defined(USE_SHA1) static char * _verify_nettle_dsa(sldns_buffer* buf, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen) { uint8_t digest[SHA1_DIGEST_SIZE]; uint8_t key_t_value; int res = 0; size_t offset; struct dsa_public_key pubkey; struct dsa_signature signature; unsigned int expected_len; /* Extract DSA signature from the record */ nettle_dsa_signature_init(&signature); /* Signature length: 41 bytes - RFC 2536 sec. 3 */ if(sigblock_len == 41) { if(key[0] != sigblock[0]) return "invalid T value in DSA signature or pubkey"; nettle_mpz_set_str_256_u(signature.r, 20, sigblock+1); nettle_mpz_set_str_256_u(signature.s, 20, sigblock+1+20); } else { /* DER encoded, decode the ASN1 notated R and S bignums */ /* SEQUENCE { r INTEGER, s INTEGER } */ struct asn1_der_iterator i, seq; if(asn1_der_iterator_first(&i, sigblock_len, (uint8_t*)sigblock) != ASN1_ITERATOR_CONSTRUCTED || i.type != ASN1_SEQUENCE) return "malformed DER encoded DSA signature"; /* decode this element of i using the seq iterator */ if(asn1_der_decode_constructed(&i, &seq) != ASN1_ITERATOR_PRIMITIVE || seq.type != ASN1_INTEGER) return "malformed DER encoded DSA signature"; if(!asn1_der_get_bignum(&seq, signature.r, 20*8)) return "malformed DER encoded DSA signature"; if(asn1_der_iterator_next(&seq) != ASN1_ITERATOR_PRIMITIVE || seq.type != ASN1_INTEGER) return "malformed DER encoded DSA signature"; if(!asn1_der_get_bignum(&seq, signature.s, 20*8)) return "malformed DER encoded DSA signature"; if(asn1_der_iterator_next(&i) != ASN1_ITERATOR_END) return "malformed DER encoded DSA signature"; } /* Validate T values constraints - RFC 2536 sec. 2 & sec. 3 */ key_t_value = key[0]; if (key_t_value > 8) { return "invalid T value in DSA pubkey"; } /* Pubkey minimum length: 21 bytes - RFC 2536 sec. 2 */ if (keylen < 21) { return "DSA pubkey too short"; } expected_len = 1 + /* T */ 20 + /* Q */ (64 + key_t_value*8) + /* P */ (64 + key_t_value*8) + /* G */ (64 + key_t_value*8); /* Y */ if (keylen != expected_len ) { return "invalid DSA pubkey length"; } /* Extract DSA pubkey from the record */ nettle_dsa_public_key_init(&pubkey); offset = 1; nettle_mpz_set_str_256_u(pubkey.q, 20, key+offset); offset += 20; nettle_mpz_set_str_256_u(pubkey.p, (64 + key_t_value*8), key+offset); offset += (64 + key_t_value*8); nettle_mpz_set_str_256_u(pubkey.g, (64 + key_t_value*8), key+offset); offset += (64 + key_t_value*8); nettle_mpz_set_str_256_u(pubkey.y, (64 + key_t_value*8), key+offset); /* Digest content of "buf" and verify its DSA signature in "sigblock"*/ res = _digest_nettle(SHA1_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= dsa_sha1_verify_digest(&pubkey, digest, &signature); /* Clear and return */ nettle_dsa_signature_clear(&signature); nettle_dsa_public_key_clear(&pubkey); if (!res) return "DSA signature verification failed"; else return NULL; } #endif /* USE_DSA */ static char * _verify_nettle_rsa(sldns_buffer* buf, unsigned int digest_size, char* sigblock, unsigned int sigblock_len, uint8_t* key, unsigned int keylen) { uint16_t exp_len = 0; size_t exp_offset = 0, mod_offset = 0; struct rsa_public_key pubkey; mpz_t signature; int res = 0; /* RSA pubkey parsing as per RFC 3110 sec. 2 */ if( keylen <= 1) { return "null RSA key"; } if (key[0] != 0) { /* 1-byte length */ exp_len = key[0]; exp_offset = 1; } else { /* 1-byte NUL + 2-bytes exponent length */ if (keylen < 3) { return "incorrect RSA key length"; } exp_len = READ_UINT16(key+1); if (exp_len == 0) return "null RSA exponent length"; exp_offset = 3; } /* Check that we are not over-running input length */ if (keylen < exp_offset + exp_len + 1) { return "RSA key content shorter than expected"; } mod_offset = exp_offset + exp_len; nettle_rsa_public_key_init(&pubkey); pubkey.size = keylen - mod_offset; nettle_mpz_set_str_256_u(pubkey.e, exp_len, &key[exp_offset]); nettle_mpz_set_str_256_u(pubkey.n, pubkey.size, &key[mod_offset]); /* Digest content of "buf" and verify its RSA signature in "sigblock"*/ nettle_mpz_init_set_str_256_u(signature, sigblock_len, (uint8_t*)sigblock); switch (digest_size) { case SHA1_DIGEST_SIZE: { uint8_t digest[SHA1_DIGEST_SIZE]; res = _digest_nettle(SHA1_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= rsa_sha1_verify_digest(&pubkey, digest, signature); break; } case SHA256_DIGEST_SIZE: { uint8_t digest[SHA256_DIGEST_SIZE]; res = _digest_nettle(SHA256_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= rsa_sha256_verify_digest(&pubkey, digest, signature); break; } case SHA512_DIGEST_SIZE: { uint8_t digest[SHA512_DIGEST_SIZE]; res = _digest_nettle(SHA512_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= rsa_sha512_verify_digest(&pubkey, digest, signature); break; } default: break; } /* Clear and return */ nettle_rsa_public_key_clear(&pubkey); mpz_clear(signature); if (!res) { return "RSA signature verification failed"; } else { return NULL; } } #ifdef USE_ECDSA static char * _verify_nettle_ecdsa(sldns_buffer* buf, unsigned int digest_size, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen) { int res = 0; struct ecc_point pubkey; struct dsa_signature signature; /* Always matched strength, as per RFC 6605 sec. 1 */ if (sigblock_len != 2*digest_size || keylen != 2*digest_size) { return "wrong ECDSA signature length"; } /* Parse ECDSA signature as per RFC 6605 sec. 4 */ nettle_dsa_signature_init(&signature); switch (digest_size) { case SHA256_DIGEST_SIZE: { uint8_t digest[SHA256_DIGEST_SIZE]; mpz_t x, y; nettle_ecc_point_init(&pubkey, nettle_get_secp_256r1()); nettle_mpz_init_set_str_256_u(x, SHA256_DIGEST_SIZE, key); nettle_mpz_init_set_str_256_u(y, SHA256_DIGEST_SIZE, key+SHA256_DIGEST_SIZE); nettle_mpz_set_str_256_u(signature.r, SHA256_DIGEST_SIZE, sigblock); nettle_mpz_set_str_256_u(signature.s, SHA256_DIGEST_SIZE, sigblock+SHA256_DIGEST_SIZE); res = _digest_nettle(SHA256_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= nettle_ecc_point_set(&pubkey, x, y); res &= nettle_ecdsa_verify (&pubkey, SHA256_DIGEST_SIZE, digest, &signature); mpz_clear(x); mpz_clear(y); nettle_ecc_point_clear(&pubkey); break; } case SHA384_DIGEST_SIZE: { uint8_t digest[SHA384_DIGEST_SIZE]; mpz_t x, y; nettle_ecc_point_init(&pubkey, nettle_get_secp_384r1()); nettle_mpz_init_set_str_256_u(x, SHA384_DIGEST_SIZE, key); nettle_mpz_init_set_str_256_u(y, SHA384_DIGEST_SIZE, key+SHA384_DIGEST_SIZE); nettle_mpz_set_str_256_u(signature.r, SHA384_DIGEST_SIZE, sigblock); nettle_mpz_set_str_256_u(signature.s, SHA384_DIGEST_SIZE, sigblock+SHA384_DIGEST_SIZE); res = _digest_nettle(SHA384_DIGEST_SIZE, (unsigned char*)sldns_buffer_begin(buf), (unsigned int)sldns_buffer_limit(buf), (unsigned char*)digest); res &= nettle_ecc_point_set(&pubkey, x, y); res &= nettle_ecdsa_verify (&pubkey, SHA384_DIGEST_SIZE, digest, &signature); mpz_clear(x); mpz_clear(y); nettle_ecc_point_clear(&pubkey); break; } default: return "unknown ECDSA algorithm"; } /* Clear and return */ nettle_dsa_signature_clear(&signature); if (!res) return "ECDSA signature verification failed"; else return NULL; } #endif #ifdef USE_ED25519 static char * _verify_nettle_ed25519(sldns_buffer* buf, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen) { int res = 0; if(sigblock_len != ED25519_SIGNATURE_SIZE) { return "wrong ED25519 signature length"; } if(keylen != ED25519_KEY_SIZE) { return "wrong ED25519 key length"; } res = ed25519_sha512_verify((uint8_t*)key, sldns_buffer_limit(buf), sldns_buffer_begin(buf), (uint8_t*)sigblock); if (!res) return "ED25519 signature verification failed"; else return NULL; } #endif /** * Check a canonical sig+rrset and signature against a dnskey * @param buf: buffer with data to verify, the first rrsig part and the * canonicalized rrset. * @param algo: DNSKEY algorithm. * @param sigblock: signature rdata field from RRSIG * @param sigblock_len: length of sigblock data. * @param key: public key data from DNSKEY RR. * @param keylen: length of keydata. * @param reason: bogus reason in more detail. * @return secure if verification succeeded, bogus on crypto failure, * unchecked on format errors and alloc failures. */ enum sec_status verify_canonrrset(sldns_buffer* buf, int algo, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen, char** reason) { unsigned int digest_size = 0; if (sigblock_len == 0 || keylen == 0) { *reason = "null signature"; return sec_status_bogus; } #ifndef USE_DSA if((algo == LDNS_DSA || algo == LDNS_DSA_NSEC3) &&(fake_dsa||fake_sha1)) return sec_status_secure; #endif #ifndef USE_SHA1 if(fake_sha1 && (algo == LDNS_DSA || algo == LDNS_DSA_NSEC3 || algo == LDNS_RSASHA1 || algo == LDNS_RSASHA1_NSEC3)) return sec_status_secure; #endif switch(algo) { #if defined(USE_DSA) && defined(USE_SHA1) case LDNS_DSA: case LDNS_DSA_NSEC3: *reason = _verify_nettle_dsa(buf, sigblock, sigblock_len, key, keylen); if (*reason != NULL) return sec_status_bogus; else return sec_status_secure; #endif /* USE_DSA */ #ifdef USE_SHA1 case LDNS_RSASHA1: case LDNS_RSASHA1_NSEC3: digest_size = (digest_size ? digest_size : SHA1_DIGEST_SIZE); #endif /* double fallthrough annotation to please gcc parser */ ATTR_FALLTHROUGH /* fallthrough */ #ifdef USE_SHA2 /* fallthrough */ case LDNS_RSASHA256: digest_size = (digest_size ? digest_size : SHA256_DIGEST_SIZE); ATTR_FALLTHROUGH /* fallthrough */ case LDNS_RSASHA512: digest_size = (digest_size ? digest_size : SHA512_DIGEST_SIZE); #endif *reason = _verify_nettle_rsa(buf, digest_size, (char*)sigblock, sigblock_len, key, keylen); if (*reason != NULL) return sec_status_bogus; else return sec_status_secure; #ifdef USE_ECDSA case LDNS_ECDSAP256SHA256: digest_size = (digest_size ? digest_size : SHA256_DIGEST_SIZE); ATTR_FALLTHROUGH /* fallthrough */ case LDNS_ECDSAP384SHA384: digest_size = (digest_size ? digest_size : SHA384_DIGEST_SIZE); *reason = _verify_nettle_ecdsa(buf, digest_size, sigblock, sigblock_len, key, keylen); if (*reason != NULL) return sec_status_bogus; else return sec_status_secure; #endif #ifdef USE_ED25519 case LDNS_ED25519: *reason = _verify_nettle_ed25519(buf, sigblock, sigblock_len, key, keylen); if (*reason != NULL) return sec_status_bogus; else return sec_status_secure; #endif case LDNS_RSAMD5: case LDNS_ECC_GOST: default: *reason = "unable to verify signature, unknown algorithm"; return sec_status_bogus; } } #endif /* HAVE_SSL or HAVE_NSS or HAVE_NETTLE */ unbound-1.25.1/validator/val_kcache.c0000644000175000017500000001175415203270263017127 0ustar wouterwouter/* * validator/val_kcache.c - validator key shared cache with validated keys * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions for dealing with the validator key cache. */ #include "config.h" #include "validator/val_kcache.h" #include "validator/val_kentry.h" #include "util/log.h" #include "util/config_file.h" #include "util/data/dname.h" #include "util/module.h" struct key_cache* key_cache_create(struct config_file* cfg) { struct key_cache* kcache = (struct key_cache*)calloc(1, sizeof(*kcache)); size_t numtables, start_size, maxmem; if(!kcache) { log_err("malloc failure"); return NULL; } numtables = cfg->key_cache_slabs; start_size = HASH_DEFAULT_STARTARRAY; maxmem = cfg->key_cache_size; kcache->slab = slabhash_create(numtables, start_size, maxmem, &key_entry_sizefunc, &key_entry_compfunc, &key_entry_delkeyfunc, &key_entry_deldatafunc, NULL); if(!kcache->slab) { log_err("malloc failure"); free(kcache); return NULL; } return kcache; } void key_cache_delete(struct key_cache* kcache) { if(!kcache) return; slabhash_delete(kcache->slab); free(kcache); } void key_cache_insert(struct key_cache* kcache, struct key_entry_key* kkey, int copy_reason) { struct key_entry_key* k = key_entry_copy(kkey, copy_reason); if(!k) return; key_entry_hash(k); slabhash_insert(kcache->slab, k->entry.hash, &k->entry, k->entry.data, NULL); } /** * Lookup exactly in the key cache. Returns pointer to locked entry. * Caller must unlock it after use. * @param kcache: the key cache. * @param name: for what name to look; uncompressed wireformat * @param namelen: length of the name. * @param key_class: class of the key. * @param wr: set true to get a writelock. * @return key entry, locked, or NULL if not found. No TTL checking is * performed. */ static struct key_entry_key* key_cache_search(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class, int wr) { struct lruhash_entry* e; struct key_entry_key lookfor; lookfor.entry.key = &lookfor; lookfor.name = name; lookfor.namelen = namelen; lookfor.key_class = key_class; key_entry_hash(&lookfor); e = slabhash_lookup(kcache->slab, lookfor.entry.hash, &lookfor, wr); if(!e) return NULL; return (struct key_entry_key*)e->key; } struct key_entry_key* key_cache_obtain(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class, struct regional* region, time_t now) { /* keep looking until we find a nonexpired entry */ while(1) { struct key_entry_key* k = key_cache_search(kcache, name, namelen, key_class, 0); if(k) { /* see if TTL is OK */ struct key_entry_data* d = (struct key_entry_data*) k->entry.data; if(now <= d->ttl) { /* copy and return it */ struct key_entry_key* retkey = key_entry_copy_toregion(k, region); lock_rw_unlock(&k->entry.lock); return retkey; } lock_rw_unlock(&k->entry.lock); } /* snip off first label to continue */ if(dname_is_root(name)) break; dname_remove_label(&name, &namelen); } return NULL; } size_t key_cache_get_mem(struct key_cache* kcache) { return sizeof(*kcache) + slabhash_get_mem(kcache->slab); } void key_cache_remove(struct key_cache* kcache, uint8_t* name, size_t namelen, uint16_t key_class) { struct key_entry_key lookfor; lookfor.entry.key = &lookfor; lookfor.name = name; lookfor.namelen = namelen; lookfor.key_class = key_class; key_entry_hash(&lookfor); slabhash_remove(kcache->slab, lookfor.entry.hash, &lookfor); } unbound-1.25.1/validator/val_secalgo.h0000644000175000017500000001267515203270263017336 0ustar wouterwouter/* * validator/val_secalgo.h - validator security algorithm functions. * * Copyright (c) 2012, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions take buffers with raw data and convert to library calls. */ #ifndef VALIDATOR_VAL_SECALGO_H #define VALIDATOR_VAL_SECALGO_H struct sldns_buffer; struct secalgo_hash; /** Return size of nsec3 hash algorithm, 0 if not supported */ size_t nsec3_hash_algo_size_supported(int id); /** * Hash a single hash call of an NSEC3 hash algorithm. * Iterations and salt are done by the caller. * @param algo: nsec3 hash algorithm. * @param buf: the buffer to digest * @param len: length of buffer to digest. * @param res: result stored here (must have sufficient space). * @return false on failure. */ int secalgo_nsec3_hash(int algo, unsigned char* buf, size_t len, unsigned char* res); /** * Calculate the sha256 hash for the data buffer into the result. * @param buf: buffer to digest. * @param len: length of the buffer to digest. * @param res: result is stored here (space 256/8 bytes). */ void secalgo_hash_sha256(unsigned char* buf, size_t len, unsigned char* res); /** * Start a hash of type sha384. Allocates structure, then inits it, * so that a series of updates can be performed, before the final result. * @return hash structure. NULL on malloc failure or no support. */ struct secalgo_hash* secalgo_hash_create_sha384(void); /** * Start a hash of type sha512. Allocates structure, then inits it, * so that a series of updates can be performed, before the final result. * @return hash structure. NULL on malloc failure or no support. */ struct secalgo_hash* secalgo_hash_create_sha512(void); /** * Update a hash with more information to add to it. * @param hash: the hash that is updated. * @param data: data to add. * @param len: length of data. * @return false on failure. */ int secalgo_hash_update(struct secalgo_hash* hash, uint8_t* data, size_t len); /** * Get the final result of the hash. * @param hash: the hash that has had updates to it. * @param result: where to store the result. * @param maxlen: length of the result buffer, eg. size of the allocation. * If not large enough the routine fails. * @param resultlen: the length of the result, returned to the caller. * How much of maxlen is used. * @return false on failure. */ int secalgo_hash_final(struct secalgo_hash* hash, uint8_t* result, size_t maxlen, size_t* resultlen); /** * Delete the hash structure. * @param hash: the hash to delete. */ void secalgo_hash_delete(struct secalgo_hash* hash); /** * Return size of DS digest according to its hash algorithm. * @param algo: DS digest algo. * @return size in bytes of digest, or 0 if not supported. */ size_t ds_digest_size_supported(int algo); /** * @param algo: the DS digest algo * @param buf: the buffer to digest * @param len: length of buffer to digest. * @param res: result stored here (must have sufficient space). * @return false on failure. */ int secalgo_ds_digest(int algo, unsigned char* buf, size_t len, unsigned char* res); /** return true if DNSKEY algorithm id is supported */ int dnskey_algo_id_is_supported(int id); /** * Check a canonical sig+rrset and signature against a dnskey * @param buf: buffer with data to verify, the first rrsig part and the * canonicalized rrset. * @param algo: DNSKEY algorithm. * @param sigblock: signature rdata field from RRSIG * @param sigblock_len: length of sigblock data. * @param key: public key data from DNSKEY RR. * @param keylen: length of keydata. * @param reason: bogus reason in more detail. * @return secure if verification succeeded, bogus on crypto failure, * unchecked on format errors and alloc failures. */ enum sec_status verify_canonrrset(struct sldns_buffer* buf, int algo, unsigned char* sigblock, unsigned int sigblock_len, unsigned char* key, unsigned int keylen, char** reason); #endif /* VALIDATOR_VAL_SECALGO_H */ unbound-1.25.1/validator/val_nsec3.c0000644000175000017500000015167515203270263016733 0ustar wouterwouter/* * validator/val_nsec3.c - validator NSEC3 denial of existence functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with NSEC3 checking, the different NSEC3 proofs * for denial of existence, and proofs for presence of types. */ #include "config.h" #include #include "validator/val_nsec3.h" #include "validator/val_secalgo.h" #include "validator/validator.h" #include "validator/val_kentry.h" #include "services/cache/rrset.h" #include "util/regional.h" #include "util/rbtree.h" #include "util/module.h" #include "util/net_help.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/data/msgreply.h" /* we include nsec.h for the bitmap_has_type function */ #include "validator/val_nsec.h" #include "sldns/sbuffer.h" #include "util/config_file.h" /** * When all allowed NSEC3 calculations at once resulted in error treat as * bogus. NSEC3 hash errors are not cached and this helps breaks loops with * erroneous data. */ #define MAX_NSEC3_ERRORS -1 /** * This function we get from ldns-compat or from base system * it returns the number of data bytes stored at the target, or <0 on error. */ int sldns_b32_ntop_extended_hex(uint8_t const *src, size_t srclength, char *target, size_t targsize); /** * This function we get from ldns-compat or from base system * it returns the number of data bytes stored at the target, or <0 on error. */ int sldns_b32_pton_extended_hex(char const *src, size_t hashed_owner_str_len, uint8_t *target, size_t targsize); /** * Closest encloser (ce) proof results * Contains the ce and the next-closer (nc) proof. */ struct ce_response { /** the closest encloser name */ uint8_t* ce; /** length of ce */ size_t ce_len; /** NSEC3 record that proved ce. rrset */ struct ub_packed_rrset_key* ce_rrset; /** NSEC3 record that proved ce. rr number */ int ce_rr; /** NSEC3 record that proved nc. rrset */ struct ub_packed_rrset_key* nc_rrset; /** NSEC3 record that proved nc. rr*/ int nc_rr; }; /** * Filter conditions for NSEC3 proof * Used to iterate over the applicable NSEC3 RRs. */ struct nsec3_filter { /** Zone name, only NSEC3 records for this zone are considered */ uint8_t* zone; /** length of the zonename */ size_t zone_len; /** the list of NSEC3s to filter; array */ struct ub_packed_rrset_key** list; /** number of rrsets in list */ size_t num; /** class of records for the NSEC3, only this class applies */ uint16_t fclass; }; /** return number of rrs in an rrset */ static size_t rrset_get_count(struct ub_packed_rrset_key* rrset) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; if(!d) return 0; return d->count; } /** return if nsec3 RR has unknown flags */ static int nsec3_unknown_flags(struct ub_packed_rrset_key* rrset, int r) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+2) return 0; /* malformed */ return (int)(d->rr_data[r][2+1] & NSEC3_UNKNOWN_FLAGS); } int nsec3_has_optout(struct ub_packed_rrset_key* rrset, int r) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+2) return 0; /* malformed */ return (int)(d->rr_data[r][2+1] & NSEC3_OPTOUT); } /** return nsec3 RR algorithm */ static int nsec3_get_algo(struct ub_packed_rrset_key* rrset, int r) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+1) return 0; /* malformed */ return (int)(d->rr_data[r][2+0]); } /** return if nsec3 RR has known algorithm */ static int nsec3_known_algo(struct ub_packed_rrset_key* rrset, int r) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+1) return 0; /* malformed */ switch(d->rr_data[r][2+0]) { case NSEC3_HASH_SHA1: return 1; } return 0; } /** return nsec3 RR iteration count */ static size_t nsec3_get_iter(struct ub_packed_rrset_key* rrset, int r) { uint16_t i; struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+4) return 0; /* malformed */ memmove(&i, d->rr_data[r]+2+2, sizeof(i)); i = ntohs(i); return (size_t)i; } /** return nsec3 RR salt */ static int nsec3_get_salt(struct ub_packed_rrset_key* rrset, int r, uint8_t** salt, size_t* saltlen) { struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+5) { *salt = 0; *saltlen = 0; return 0; /* malformed */ } *saltlen = (size_t)d->rr_data[r][2+4]; if(d->rr_len[r] < 2+5+(size_t)*saltlen) { *salt = 0; *saltlen = 0; return 0; /* malformed */ } *salt = d->rr_data[r]+2+5; return 1; } int nsec3_get_params(struct ub_packed_rrset_key* rrset, int r, int* algo, size_t* iter, uint8_t** salt, size_t* saltlen) { if(!nsec3_known_algo(rrset, r) || nsec3_unknown_flags(rrset, r)) return 0; if(!nsec3_get_salt(rrset, r, salt, saltlen)) return 0; *algo = nsec3_get_algo(rrset, r); *iter = nsec3_get_iter(rrset, r); return 1; } int nsec3_get_nextowner(struct ub_packed_rrset_key* rrset, int r, uint8_t** next, size_t* nextlen) { size_t saltlen; struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); if(d->rr_len[r] < 2+5) { *next = 0; *nextlen = 0; return 0; /* malformed */ } saltlen = (size_t)d->rr_data[r][2+4]; if(d->rr_len[r] < 2+5+saltlen+1) { *next = 0; *nextlen = 0; return 0; /* malformed */ } *nextlen = (size_t)d->rr_data[r][2+5+saltlen]; if(d->rr_len[r] < 2+5+saltlen+1+*nextlen) { *next = 0; *nextlen = 0; return 0; /* malformed */ } *next = d->rr_data[r]+2+5+saltlen+1; return 1; } size_t nsec3_hash_to_b32(uint8_t* hash, size_t hashlen, uint8_t* zone, size_t zonelen, uint8_t* buf, size_t max) { /* write b32 of name, leave one for length */ int ret; if(max < hashlen*2+1) /* quick approx of b32, as if hexb16 */ return 0; ret = sldns_b32_ntop_extended_hex(hash, hashlen, (char*)buf+1, max-1); if(ret < 1) return 0; buf[0] = (uint8_t)ret; /* length of b32 label */ ret++; if(max - ret < zonelen) return 0; memmove(buf+ret, zone, zonelen); return zonelen+(size_t)ret; } size_t nsec3_get_nextowner_b32(struct ub_packed_rrset_key* rrset, int r, uint8_t* buf, size_t max) { uint8_t* nm, *zone; size_t nmlen, zonelen; if(!nsec3_get_nextowner(rrset, r, &nm, &nmlen)) return 0; /* append zone name; the owner name must be .zone */ zone = rrset->rk.dname; zonelen = rrset->rk.dname_len; dname_remove_label(&zone, &zonelen); return nsec3_hash_to_b32(nm, nmlen, zone, zonelen, buf, max); } int nsec3_has_type(struct ub_packed_rrset_key* rrset, int r, uint16_t type) { uint8_t* bitmap; size_t bitlen, skiplen; struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; log_assert(d && r < (int)d->count); skiplen = 2+4; /* skip salt */ if(d->rr_len[r] < skiplen+1) return 0; /* malformed, too short */ skiplen += 1+(size_t)d->rr_data[r][skiplen]; /* skip next hashed owner */ if(d->rr_len[r] < skiplen+1) return 0; /* malformed, too short */ skiplen += 1+(size_t)d->rr_data[r][skiplen]; if(d->rr_len[r] < skiplen) return 0; /* malformed, too short */ bitlen = d->rr_len[r] - skiplen; bitmap = d->rr_data[r]+skiplen; return nsecbitmap_has_type_rdata(bitmap, bitlen, type); } /** * Iterate through NSEC3 list, per RR * This routine gives the next RR in the list (or sets rrset null). * Usage: * * size_t rrsetnum; * int rrnum; * struct ub_packed_rrset_key* rrset; * for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; * rrset=filter_next(filter, &rrsetnum, &rrnum)) * do_stuff; * * Also filters out * o unknown flag NSEC3s * o unknown algorithm NSEC3s. * @param filter: nsec3 filter structure. * @param rrsetnum: in/out rrset number to look at. * @param rrnum: in/out rr number in rrset to look at. * @returns ptr to the next rrset (or NULL at end). */ static struct ub_packed_rrset_key* filter_next(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum) { size_t i; int r; uint8_t* nm; size_t nmlen; if(!filter->zone) /* empty list */ return NULL; for(i=*rrsetnum; inum; i++) { /* see if RRset qualifies */ if(ntohs(filter->list[i]->rk.type) != LDNS_RR_TYPE_NSEC3 || ntohs(filter->list[i]->rk.rrset_class) != filter->fclass) continue; /* check RRset zone */ nm = filter->list[i]->rk.dname; nmlen = filter->list[i]->rk.dname_len; dname_remove_label(&nm, &nmlen); if(query_dname_compare(nm, filter->zone) != 0) continue; if(i == *rrsetnum) r = (*rrnum) + 1; /* continue at next RR */ else r = 0; /* new RRset start at first RR */ for(; r < (int)rrset_get_count(filter->list[i]); r++) { /* skip unknown flags, algo */ if(nsec3_unknown_flags(filter->list[i], r) || !nsec3_known_algo(filter->list[i], r)) continue; /* this one is a good target */ *rrsetnum = i; *rrnum = r; return filter->list[i]; } } return NULL; } /** * Start iterating over NSEC3 records. * @param filter: the filter structure, must have been filter_init-ed. * @param rrsetnum: can be undefined on call, initialised. * @param rrnum: can be undefined on call, initialised. * @return first rrset of an NSEC3, together with rrnum this points to * the first RR to examine. Is NULL on empty list. */ static struct ub_packed_rrset_key* filter_first(struct nsec3_filter* filter, size_t* rrsetnum, int* rrnum) { *rrsetnum = 0; *rrnum = -1; return filter_next(filter, rrsetnum, rrnum); } /** see if at least one RR is known (flags, algo) */ static int nsec3_rrset_has_known(struct ub_packed_rrset_key* s) { int r; for(r=0; r < (int)rrset_get_count(s); r++) { if(!nsec3_unknown_flags(s, r) && nsec3_known_algo(s, r)) return 1; } return 0; } /** * Initialize the filter structure. * Finds the zone by looking at available NSEC3 records and best match. * (skips the unknown flag and unknown algo NSEC3s). * * @param filter: nsec3 filter structure. * @param list: list of rrsets, an array of them. * @param num: number of rrsets in list. * @param qinfo: * query name to match a zone for. * query type (if DS a higher zone must be chosen) * qclass, to filter NSEC3s with. */ static void filter_init(struct nsec3_filter* filter, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo) { size_t i; uint8_t* nm; size_t nmlen; filter->zone = NULL; filter->zone_len = 0; filter->list = list; filter->num = num; filter->fclass = qinfo->qclass; for(i=0; irk.type) != LDNS_RR_TYPE_NSEC3 || ntohs(list[i]->rk.rrset_class) != qinfo->qclass) continue; /* skip unknown flags, algo */ if(!nsec3_rrset_has_known(list[i])) continue; /* since NSEC3s are base32.zonename, we can find the zone * name by stripping off the first label of the record */ nm = list[i]->rk.dname; nmlen = list[i]->rk.dname_len; dname_remove_label(&nm, &nmlen); /* if we find a domain that can prove about the qname, * and if this domain is closer to the qname */ if(dname_subdomain_c(qinfo->qname, nm) && (!filter->zone || dname_subdomain_c(nm, filter->zone))) { /* for a type DS do not accept a zone equal to qname*/ if(qinfo->qtype == LDNS_RR_TYPE_DS && query_dname_compare(qinfo->qname, nm) == 0 && !dname_is_root(qinfo->qname)) continue; filter->zone = nm; filter->zone_len = nmlen; } } } /** Check if the NSEC3s have the same parameter set. */ static int param_set_same(struct nsec3_filter* flt, char** reason) { size_t rrsetnum; int rrnum; struct ub_packed_rrset_key* rrset; int have_params = 0; int first_algo = 0; size_t first_iter = 0; uint8_t* first_salt = NULL; size_t first_saltlen = 0; /* If the NSEC3 parameter sets have distinct values, then they are * from different NSEC3 chains, and we do not want that. */ for(rrset=filter_first(flt, &rrsetnum, &rrnum); rrset; rrset=filter_next(flt, &rrsetnum, &rrnum)) { if(!have_params) { first_algo = nsec3_get_algo(rrset, rrnum); first_iter = nsec3_get_iter(rrset, rrnum); if(!nsec3_get_salt(rrset, rrnum, &first_salt, &first_saltlen)) { verbose(VERB_ALGO, "NSEC3 salt malformed"); if(reason) *reason = "NSEC3 salt malformed"; return 0; } have_params = 1; } else { uint8_t* salt = NULL; size_t saltlen = 0; if(nsec3_get_algo(rrset, rrnum) != first_algo) { verbose(VERB_ALGO, "NSEC3 algorithm mismatch"); if(reason) *reason = "NSEC3 algorithm mismatch"; return 0; } if(nsec3_get_iter(rrset, rrnum) != first_iter) { verbose(VERB_ALGO, "NSEC3 iterations mismatch"); if(reason) *reason = "NSEC3 iterations mismatch"; return 0; } if(!nsec3_get_salt(rrset, rrnum, &salt, &saltlen)) { verbose(VERB_ALGO, "NSEC3 salt malformed"); if(reason) *reason = "NSEC3 salt malformed"; return 0; } if(saltlen != first_saltlen || memcmp(salt, first_salt, saltlen) != 0) { verbose(VERB_ALGO, "NSEC3 salt mismatch"); if(reason) *reason = "NSEC3 salt mismatch"; return 0; } } } return 1; } /** * Find max iteration count using config settings and key size * @param ve: validator environment with iteration count config settings. * @param bits: key size * @return max iteration count */ static size_t get_max_iter(struct val_env* ve, size_t bits) { int i; log_assert(ve->nsec3_keyiter_count > 0); /* round up to nearest config keysize, linear search, keep it small */ for(i=0; insec3_keyiter_count; i++) { if(bits <= ve->nsec3_keysize[i]) return ve->nsec3_maxiter[i]; } /* else, use value for biggest key */ return ve->nsec3_maxiter[ve->nsec3_keyiter_count-1]; } /** * Determine if any of the NSEC3 rrs iteration count is too high, from key. * @param ve: validator environment with iteration count config settings. * @param filter: what NSEC3s to loop over. * @param kkey: key entry used for verification; used for iteration counts. * @return 1 if some nsec3s are above the max iteration count. */ static int nsec3_iteration_count_high(struct val_env* ve, struct nsec3_filter* filter, struct key_entry_key* kkey) { size_t rrsetnum; int rrnum; struct ub_packed_rrset_key* rrset; /* first determine the max number of iterations */ size_t bits = key_entry_keysize(kkey); size_t max_iter = get_max_iter(ve, bits); verbose(VERB_ALGO, "nsec3: keysize %d bits, max iterations %d", (int)bits, (int)max_iter); for(rrset=filter_first(filter, &rrsetnum, &rrnum); rrset; rrset=filter_next(filter, &rrsetnum, &rrnum)) { if(nsec3_get_iter(rrset, rrnum) > max_iter) return 1; } return 0; } /* nsec3_cache_compare for rbtree */ int nsec3_hash_cmp(const void* c1, const void* c2) { struct nsec3_cached_hash* h1 = (struct nsec3_cached_hash*)c1; struct nsec3_cached_hash* h2 = (struct nsec3_cached_hash*)c2; uint8_t* s1, *s2; size_t s1len, s2len; int c = query_dname_compare(h1->dname, h2->dname); if(c != 0) return c; /* compare parameters */ /* if both malformed, its equal, robustness */ if(nsec3_get_algo(h1->nsec3, h1->rr) != nsec3_get_algo(h2->nsec3, h2->rr)) { if(nsec3_get_algo(h1->nsec3, h1->rr) < nsec3_get_algo(h2->nsec3, h2->rr)) return -1; return 1; } if(nsec3_get_iter(h1->nsec3, h1->rr) != nsec3_get_iter(h2->nsec3, h2->rr)) { if(nsec3_get_iter(h1->nsec3, h1->rr) < nsec3_get_iter(h2->nsec3, h2->rr)) return -1; return 1; } (void)nsec3_get_salt(h1->nsec3, h1->rr, &s1, &s1len); (void)nsec3_get_salt(h2->nsec3, h2->rr, &s2, &s2len); if(s1len == 0 && s2len == 0) return 0; if(!s1) return -1; if(!s2) return 1; if(s1len != s2len) { if(s1len < s2len) return -1; return 1; } return memcmp(s1, s2, s1len); } int nsec3_cache_table_init(struct nsec3_cache_table* ct, struct regional* region) { if(ct->ct) return 1; ct->ct = (rbtree_type*)regional_alloc(region, sizeof(*ct->ct)); if(!ct->ct) return 0; ct->region = region; rbtree_init(ct->ct, &nsec3_hash_cmp); return 1; } size_t nsec3_get_hashed(sldns_buffer* buf, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen, uint8_t* res, size_t max) { size_t i, hash_len; /* prepare buffer for first iteration */ sldns_buffer_clear(buf); sldns_buffer_write(buf, nm, nmlen); query_dname_tolower(sldns_buffer_begin(buf)); if(saltlen != 0) sldns_buffer_write(buf, salt, saltlen); sldns_buffer_flip(buf); hash_len = nsec3_hash_algo_size_supported(algo); if(hash_len == 0) { log_err("nsec3 hash of unknown algo %d", algo); return 0; } if(hash_len > max) return 0; if(!secalgo_nsec3_hash(algo, (unsigned char*)sldns_buffer_begin(buf), sldns_buffer_limit(buf), (unsigned char*)res)) return 0; for(i=0; insec3, c->rr); size_t iter = nsec3_get_iter(c->nsec3, c->rr); uint8_t* salt; size_t saltlen, i; if(!nsec3_get_salt(c->nsec3, c->rr, &salt, &saltlen)) return -1; /* prepare buffer for first iteration */ sldns_buffer_clear(buf); sldns_buffer_write(buf, c->dname, c->dname_len); query_dname_tolower(sldns_buffer_begin(buf)); sldns_buffer_write(buf, salt, saltlen); sldns_buffer_flip(buf); c->hash_len = nsec3_hash_algo_size_supported(algo); if(c->hash_len == 0) { log_err("nsec3 hash of unknown algo %d", algo); return -1; } c->hash = (uint8_t*)regional_alloc(region, c->hash_len); if(!c->hash) return 0; (void)secalgo_nsec3_hash(algo, (unsigned char*)sldns_buffer_begin(buf), sldns_buffer_limit(buf), (unsigned char*)c->hash); for(i=0; ihash, c->hash_len); sldns_buffer_write(buf, salt, saltlen); sldns_buffer_flip(buf); (void)secalgo_nsec3_hash(algo, (unsigned char*)sldns_buffer_begin(buf), sldns_buffer_limit(buf), (unsigned char*)c->hash); } return 1; } /** perform b32 encoding of hash */ static int nsec3_calc_b32(struct regional* region, sldns_buffer* buf, struct nsec3_cached_hash* c) { int r; sldns_buffer_clear(buf); r = sldns_b32_ntop_extended_hex(c->hash, c->hash_len, (char*)sldns_buffer_begin(buf), sldns_buffer_limit(buf)); if(r < 1) { log_err("b32_ntop_extended_hex: error in encoding: %d", r); return 0; } c->b32_len = (size_t)r; c->b32 = regional_alloc_init(region, sldns_buffer_begin(buf), c->b32_len); if(!c->b32) return 0; return 1; } int nsec3_hash_name(rbtree_type* table, struct regional* region, sldns_buffer* buf, struct ub_packed_rrset_key* nsec3, int rr, uint8_t* dname, size_t dname_len, struct nsec3_cached_hash** hash) { struct nsec3_cached_hash* c; struct nsec3_cached_hash looki; #ifdef UNBOUND_DEBUG rbnode_type* n; #endif int r; looki.node.key = &looki; looki.nsec3 = nsec3; looki.rr = rr; looki.dname = dname; looki.dname_len = dname_len; /* lookup first in cache */ c = (struct nsec3_cached_hash*)rbtree_search(table, &looki); if(c) { *hash = c; return 2; } /* create a new entry */ c = (struct nsec3_cached_hash*)regional_alloc(region, sizeof(*c)); if(!c) return 0; c->node.key = c; c->nsec3 = nsec3; c->rr = rr; c->dname = dname; c->dname_len = dname_len; r = nsec3_calc_hash(region, buf, c); if(r != 1) return r; /* returns -1 or 0 */ r = nsec3_calc_b32(region, buf, c); if(r != 1) return r; /* returns 0 */ #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(table, &c->node); log_assert(n); /* cannot be duplicate, just did lookup */ *hash = c; return 1; } /** * compare a label lowercased */ static int label_compare_lower(uint8_t* lab1, uint8_t* lab2, size_t lablen) { size_t i; for(i=0; irk.dname; if(!hash) return 0; /* please clang */ /* compare, does hash of name based on params in this NSEC3 * match the owner name of this NSEC3? * name must be: base32 . zone name * so; first label must not be root label (not zero length), * and match the b32 encoded hash length, * and the label content match the b32 encoded hash * and the rest must be the zone name. */ if(hash->b32_len != 0 && (size_t)nm[0] == hash->b32_len && label_compare_lower(nm+1, hash->b32, hash->b32_len) == 0 && query_dname_compare(nm+(size_t)nm[0]+1, flt->zone) == 0) { return 1; } return 0; } /** * Find matching NSEC3 * Find the NSEC3Record that matches a hash of a name. * @param env: module environment with temporary region and buffer. * @param flt: the NSEC3 RR filter, contains zone name and RRs. * @param ct: cached hashes table. * @param nm: name to look for. * @param nmlen: length of name. * @param rrset: nsec3 that matches is returned here. * @param rr: rr number in nsec3 rrset that matches. * @param calculations: current hash calculations. * @return true if a matching NSEC3 is found, false if not. */ static int find_matching_nsec3(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen, struct ub_packed_rrset_key** rrset, int* rr, int* calculations) { size_t i_rs; int i_rr; struct ub_packed_rrset_key* s; struct nsec3_cached_hash* hash = NULL; int r; int calc_errors = 0; /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ for(s=filter_first(flt, &i_rs, &i_rr); s; s=filter_next(flt, &i_rs, &i_rr)) { /* check if we are allowed more calculations */ if(*calculations >= MAX_NSEC3_CALCULATIONS) { if(calc_errors == *calculations) { *calculations = MAX_NSEC3_ERRORS; } break; } /* get name hashed for this NSEC3 RR */ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer, s, i_rr, nm, nmlen, &hash); if(r == 0) { log_err("nsec3: malloc failure"); break; /* alloc failure */ } else if(r < 0) { /* malformed NSEC3 */ calc_errors++; (*calculations)++; continue; } else { if(r == 1) (*calculations)++; if(nsec3_hash_matches_owner(flt, hash, s)) { *rrset = s; /* rrset with this name */ *rr = i_rr; /* matches hash with these parameters */ return 1; } } } *rrset = NULL; *rr = 0; return 0; } int nsec3_covers(uint8_t* zone, struct nsec3_cached_hash* hash, struct ub_packed_rrset_key* rrset, int rr, sldns_buffer* buf) { uint8_t* next, *owner; size_t nextlen; int len; if(!nsec3_get_nextowner(rrset, rr, &next, &nextlen)) return 0; /* malformed RR proves nothing */ if(!hash) return 0; /* please clang */ /* check the owner name is a hashed value . apex * base32 encoded values must have equal length. * hash_value and next hash value must have equal length. */ if(nextlen != hash->hash_len || hash->hash_len==0||hash->b32_len==0|| (size_t)*rrset->rk.dname != hash->b32_len || query_dname_compare(rrset->rk.dname+1+ (size_t)*rrset->rk.dname, zone) != 0) return 0; /* bad lengths or owner name */ /* This is the "normal case: owner < next and owner < hash < next */ if(label_compare_lower(rrset->rk.dname+1, hash->b32, hash->b32_len) < 0 && memcmp(hash->hash, next, nextlen) < 0) return 1; /* convert owner name from text to binary */ sldns_buffer_clear(buf); owner = sldns_buffer_begin(buf); len = sldns_b32_pton_extended_hex((char*)rrset->rk.dname+1, hash->b32_len, owner, sldns_buffer_limit(buf)); if(len<1) return 0; /* bad owner name in some way */ if((size_t)len != hash->hash_len || (size_t)len != nextlen) return 0; /* wrong length */ /* this is the end of zone case: next <= owner && * (hash > owner || hash < next) * this also covers the only-apex case of next==owner. */ if(memcmp(next, owner, nextlen) <= 0 && ( memcmp(hash->hash, owner, nextlen) > 0 || memcmp(hash->hash, next, nextlen) < 0)) { return 1; } return 0; } /** * findCoveringNSEC3 * Given a name, find a covering NSEC3 from among a list of NSEC3s. * * @param env: module environment with temporary region and buffer. * @param flt: the NSEC3 RR filter, contains zone name and RRs. * @param ct: cached hashes table. * @param nm: name to check if covered. * @param nmlen: length of name. * @param rrset: covering NSEC3 rrset is returned here. * @param rr: rr of cover is returned here. * @param calculations: current hash calculations. * @return true if a covering NSEC3 is found, false if not. */ static int find_covering_nsec3(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, uint8_t* nm, size_t nmlen, struct ub_packed_rrset_key** rrset, int* rr, int* calculations) { size_t i_rs; int i_rr; struct ub_packed_rrset_key* s; struct nsec3_cached_hash* hash = NULL; int r; int calc_errors = 0; /* this loop skips other-zone and unknown NSEC3s, also non-NSEC3 RRs */ for(s=filter_first(flt, &i_rs, &i_rr); s; s=filter_next(flt, &i_rs, &i_rr)) { /* check if we are allowed more calculations */ if(*calculations >= MAX_NSEC3_CALCULATIONS) { if(calc_errors == *calculations) { *calculations = MAX_NSEC3_ERRORS; } break; } /* get name hashed for this NSEC3 RR */ r = nsec3_hash_name(ct->ct, ct->region, env->scratch_buffer, s, i_rr, nm, nmlen, &hash); if(r == 0) { log_err("nsec3: malloc failure"); break; /* alloc failure */ } else if(r < 0) { /* malformed NSEC3 */ calc_errors++; (*calculations)++; continue; } else { if(r == 1) (*calculations)++; if(nsec3_covers(flt->zone, hash, s, i_rr, env->scratch_buffer)) { *rrset = s; /* rrset with this name */ *rr = i_rr; /* covers hash with these parameters */ return 1; } } } *rrset = NULL; *rr = 0; return 0; } /** * findClosestEncloser * Given a name and a list of NSEC3s, find the candidate closest encloser. * This will be the first ancestor of 'name' (including itself) to have a * matching NSEC3 RR. * @param env: module environment with temporary region and buffer. * @param flt: the NSEC3 RR filter, contains zone name and RRs. * @param ct: cached hashes table. * @param qinfo: query that is verified for. * @param ce: closest encloser information is returned in here. * @param calculations: current hash calculations. * @return true if a closest encloser candidate is found, false if not. */ static int nsec3_find_closest_encloser(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, struct query_info* qinfo, struct ce_response* ce, int* calculations) { uint8_t* nm = qinfo->qname; size_t nmlen = qinfo->qname_len; /* This scans from longest name to shortest, so the first match * we find is the only viable candidate. */ /* (David:) FIXME: modify so that the NSEC3 matching the zone apex need * not be present. (Mark Andrews idea). * (Wouter:) But make sure you check for DNAME bit in zone apex, * if the NSEC3 you find is the only NSEC3 in the zone, then this * may be the case. */ while(dname_subdomain_c(nm, flt->zone)) { if(*calculations >= MAX_NSEC3_CALCULATIONS || *calculations == MAX_NSEC3_ERRORS) { return 0; } if(find_matching_nsec3(env, flt, ct, nm, nmlen, &ce->ce_rrset, &ce->ce_rr, calculations)) { ce->ce = nm; ce->ce_len = nmlen; return 1; } dname_remove_label(&nm, &nmlen); } return 0; } /** * Given a qname and its proven closest encloser, calculate the "next * closest" name. Basically, this is the name that is one label longer than * the closest encloser that is still a subdomain of qname. * * @param qname: query name. * @param qnamelen: length of qname. * @param ce: closest encloser * @param nm: result name. * @param nmlen: length of nm. */ static void next_closer(uint8_t* qname, size_t qnamelen, uint8_t* ce, uint8_t** nm, size_t* nmlen) { int strip = dname_count_labels(qname) - dname_count_labels(ce) -1; *nm = qname; *nmlen = qnamelen; if(strip>0) dname_remove_labels(nm, nmlen, strip); } /** * proveClosestEncloser * Given a List of nsec3 RRs, find and prove the closest encloser to qname. * @param env: module environment with temporary region and buffer. * @param flt: the NSEC3 RR filter, contains zone name and RRs. * @param ct: cached hashes table. * @param qinfo: query that is verified for. * @param prove_does_not_exist: If true, then if the closest encloser * turns out to be qname, then null is returned. * If set true, and the return value is true, then you can be * certain that the ce.nc_rrset and ce.nc_rr are set properly. * @param ce: closest encloser information is returned in here. * @param calculations: pointer to the current NSEC3 hash calculations. * @return bogus if no closest encloser could be proven. * secure if a closest encloser could be proven, ce is set. * insecure if the closest-encloser candidate turns out to prove * that an insecure delegation exists above the qname. * unchecked if no more hash calculations are allowed at this point. */ static enum sec_status nsec3_prove_closest_encloser(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, struct query_info* qinfo, int prove_does_not_exist, struct ce_response* ce, int* calculations) { uint8_t* nc; size_t nc_len; /* robust: clean out ce, in case it gets abused later */ memset(ce, 0, sizeof(*ce)); if(!nsec3_find_closest_encloser(env, flt, ct, qinfo, ce, calculations)) { if(*calculations == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " "not find a candidate for the closest " "encloser; all attempted hash calculations " "were erroneous; bogus"); return sec_status_bogus; } else if(*calculations >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " "not find a candidate for the closest " "encloser; reached MAX_NSEC3_CALCULATIONS " "(%d); unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } verbose(VERB_ALGO, "nsec3 proveClosestEncloser: could " "not find a candidate for the closest encloser."); return sec_status_bogus; } log_nametypeclass(VERB_ALGO, "ce candidate", ce->ce, 0, 0); if(query_dname_compare(ce->ce, qinfo->qname) == 0) { if(prove_does_not_exist) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " "proved that qname existed, bad"); return sec_status_bogus; } /* otherwise, we need to nothing else to prove that qname * is its own closest encloser. */ return sec_status_secure; } /* If the closest encloser is actually a delegation, then the * response should have been a referral. If it is a DNAME, then * it should have been a DNAME response. */ if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_SOA)) { if(!nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: " "closest encloser is insecure delegation"); return sec_status_insecure; } verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " "encloser was a delegation, bad"); return sec_status_bogus; } if(nsec3_has_type(ce->ce_rrset, ce->ce_rr, LDNS_RR_TYPE_DNAME)) { verbose(VERB_ALGO, "nsec3 proveClosestEncloser: closest " "encloser was a DNAME, bad"); return sec_status_bogus; } /* Otherwise, we need to show that the next closer name is covered. */ next_closer(qinfo->qname, qinfo->qname_len, ce->ce, &nc, &nc_len); if(!find_covering_nsec3(env, flt, ct, nc, nc_len, &ce->nc_rrset, &ce->nc_rr, calculations)) { if(*calculations == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "nsec3: Could not find proof that the " "candidate encloser was the closest encloser; " "all attempted hash calculations were " "erroneous; bogus"); return sec_status_bogus; } else if(*calculations >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "nsec3: Could not find proof that the " "candidate encloser was the closest encloser; " "reached MAX_NSEC3_CALCULATIONS (%d); " "unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } verbose(VERB_ALGO, "nsec3: Could not find proof that the " "candidate encloser was the closest encloser"); return sec_status_bogus; } return sec_status_secure; } /** allocate a wildcard for the closest encloser */ static uint8_t* nsec3_ce_wildcard(struct regional* region, uint8_t* ce, size_t celen, size_t* len) { uint8_t* nm; if(celen > LDNS_MAX_DOMAINLEN - 2) return 0; /* too long */ nm = (uint8_t*)regional_alloc(region, celen+2); if(!nm) { log_err("nsec3 wildcard: out of memory"); return 0; /* alloc failure */ } nm[0] = 1; nm[1] = (uint8_t)'*'; /* wildcard label */ memmove(nm+2, ce, celen); *len = celen+2; return nm; } /** Do the name error proof */ static enum sec_status nsec3_do_prove_nameerror(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc) { struct ce_response ce; uint8_t* wc; size_t wclen; struct ub_packed_rrset_key* wc_rrset; int wc_rr; enum sec_status sec; /* First locate and prove the closest encloser to qname. We will * use the variant that fails if the closest encloser turns out * to be qname. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc); if(sec != sec_status_secure) { if(sec == sec_status_bogus) verbose(VERB_ALGO, "nsec3 nameerror proof: failed " "to prove a closest encloser"); else if(sec == sec_status_unchecked) verbose(VERB_ALGO, "nsec3 nameerror proof: will " "continue proving closest encloser after " "suspend"); else verbose(VERB_ALGO, "nsec3 nameerror proof: closest " "nsec3 is an insecure delegation"); return sec; } log_nametypeclass(VERB_ALGO, "nsec3 nameerror: proven ce=", ce.ce,0,0); /* At this point, we know that qname does not exist. Now we need * to prove that the wildcard does not exist. */ log_assert(ce.ce); wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen); if(!wc) { verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " "that the applicable wildcard did not exist."); return sec_status_bogus; } if(!find_covering_nsec3(env, flt, ct, wc, wclen, &wc_rrset, &wc_rr, calc)) { if(*calc == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " "that the applicable wildcard did not exist; " "all attempted hash calculations were " "erroneous; bogus"); return sec_status_bogus; } else if(*calc >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " "that the applicable wildcard did not exist; " "reached MAX_NSEC3_CALCULATIONS (%d); " "unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } verbose(VERB_ALGO, "nsec3 nameerror proof: could not prove " "that the applicable wildcard did not exist."); return sec_status_bogus; } if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nameerror proof: nc has optout"); return sec_status_insecure; } return sec_status_secure; } enum sec_status nsec3_prove_nameerror(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, struct nsec3_cache_table* ct, int* calc) { struct nsec3_filter flt; if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ log_nametypeclass(VERB_ALGO, "start nsec3 nameerror proof, zone", flt.zone, 0, 0); return nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc); } /* * No code to handle qtype=NSEC3 specially. * This existed in early drafts, but was later (-05) removed. */ /** Do the nodata proof */ static enum sec_status nsec3_do_prove_nodata(struct module_env* env, struct nsec3_filter* flt, struct nsec3_cache_table* ct, struct query_info* qinfo, int* calc) { struct ce_response ce; uint8_t* wc; size_t wclen; struct ub_packed_rrset_key* rrset; int rr; enum sec_status sec; if(find_matching_nsec3(env, flt, ct, qinfo->qname, qinfo->qname_len, &rrset, &rr, calc)) { /* cases 1 and 2 */ if(nsec3_has_type(rrset, rr, qinfo->qtype)) { verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " "proved that type existed, bogus"); return sec_status_bogus; } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { verbose(VERB_ALGO, "proveNodata: Matching NSEC3 " "proved that a CNAME existed, bogus"); return sec_status_bogus; } /* * If type DS: filter_init zone find already found a parent * zone, so this nsec3 is from a parent zone. * o can be not a delegation (unusual query for normal name, * no DS anyway, but we can verify that). * o can be a delegation (which is the usual DS check). * o may not have the SOA bit set (only the top of the * zone, which must have been above the name, has that). * Except for the root; which is checked by itself. * * If not type DS: matching nsec3 must not be a delegation. */ if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && !dname_is_root(qinfo->qname)) { verbose(VERB_ALGO, "proveNodata: apex NSEC3 " "abused for no DS proof, bogus"); return sec_status_bogus; } else if(qinfo->qtype != LDNS_RR_TYPE_DS && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "proveNodata: matching " "NSEC3 is insecure delegation"); return sec_status_insecure; } verbose(VERB_ALGO, "proveNodata: matching " "NSEC3 is a delegation, bogus"); return sec_status_bogus; } return sec_status_secure; } if(*calc == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "proveNodata: all attempted hash " "calculations were erroneous while finding a matching " "NSEC3, bogus"); return sec_status_bogus; } else if(*calc >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "proveNodata: reached " "MAX_NSEC3_CALCULATIONS (%d) while finding a " "matching NSEC3; unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } /* For cases 3 - 5, we need the proven closest encloser, and it * can't match qname. Although, at this point, we know that it * won't since we just checked that. */ sec = nsec3_prove_closest_encloser(env, flt, ct, qinfo, 1, &ce, calc); if(sec == sec_status_bogus) { verbose(VERB_ALGO, "proveNodata: did not match qname, " "nor found a proven closest encloser."); return sec_status_bogus; } else if(sec==sec_status_insecure && qinfo->qtype!=LDNS_RR_TYPE_DS){ verbose(VERB_ALGO, "proveNodata: closest nsec3 is insecure " "delegation."); return sec_status_insecure; } else if(sec==sec_status_unchecked) { return sec_status_unchecked; } /* Case 3: removed */ /* Case 4: */ log_assert(ce.ce); wc = nsec3_ce_wildcard(ct->region, ce.ce, ce.ce_len, &wclen); if(wc && find_matching_nsec3(env, flt, ct, wc, wclen, &rrset, &rr, calc)) { /* found wildcard */ if(nsec3_has_type(rrset, rr, qinfo->qtype)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard had qtype, bogus"); return sec_status_bogus; } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_CNAME)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard had a CNAME, bogus"); return sec_status_bogus; } if(qinfo->qtype == LDNS_RR_TYPE_DS && qinfo->qname_len != 1 && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard for no DS proof has a SOA, bogus"); return sec_status_bogus; } else if(qinfo->qtype != LDNS_RR_TYPE_DS && nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS) && !nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard is a delegation, bogus"); return sec_status_bogus; } /* everything is peachy keen, except for optout spans */ if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 nodata proof: matching " "wildcard is in optout range, insecure"); return sec_status_insecure; } return sec_status_secure; } if(*calc == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "nsec3 nodata proof: all attempted hash " "calculations were erroneous while matching " "wildcard, bogus"); return sec_status_bogus; } else if(*calc >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "nsec3 nodata proof: reached " "MAX_NSEC3_CALCULATIONS (%d) while matching " "wildcard, unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } /* Case 5: */ /* Due to forwarders, cnames, and other collating effects, we * can see the ordinary unsigned data from a zone beneath an * insecure delegation under an optout here */ if(!ce.nc_rrset) { verbose(VERB_ALGO, "nsec3 nodata proof: no next closer nsec3"); return sec_status_bogus; } /* We need to make sure that the covering NSEC3 is opt-out. */ log_assert(ce.nc_rrset); if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { if(qinfo->qtype == LDNS_RR_TYPE_DS) verbose(VERB_ALGO, "proveNodata: covering NSEC3 was not " "opt-out in an opt-out DS NOERROR/NODATA case."); else verbose(VERB_ALGO, "proveNodata: could not find matching " "NSEC3, nor matching wildcard, nor optout NSEC3 " "-- no more options, bogus."); return sec_status_bogus; } /* RFC5155 section 9.2: if nc has optout then no AD flag set */ return sec_status_insecure; } enum sec_status nsec3_prove_nodata(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, struct nsec3_cache_table* ct, int* calc) { struct nsec3_filter flt; if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ return nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc); } enum sec_status nsec3_prove_wildcard(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, uint8_t* wc, struct nsec3_cache_table* ct, int* calc) { struct nsec3_filter flt; struct ce_response ce; uint8_t* nc; size_t nc_len; size_t wclen; (void)dname_count_size_labels(wc, &wclen); if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ /* We know what the (purported) closest encloser is by just * looking at the supposed generating wildcard. * The *. has already been removed from the wc name. */ memset(&ce, 0, sizeof(ce)); ce.ce = wc; ce.ce_len = wclen; /* Now we still need to prove that the original data did not exist. * Otherwise, we need to show that the next closer name is covered. */ next_closer(qinfo->qname, qinfo->qname_len, ce.ce, &nc, &nc_len); if(!find_covering_nsec3(env, &flt, ct, nc, nc_len, &ce.nc_rrset, &ce.nc_rr, calc)) { if(*calc == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "proveWildcard: did not find a " "covering NSEC3 that covered the next closer " "name; all attempted hash calculations were " "erroneous; bogus"); return sec_status_bogus; } else if(*calc >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "proveWildcard: did not find a " "covering NSEC3 that covered the next closer " "name; reached MAX_NSEC3_CALCULATIONS " "(%d); unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } verbose(VERB_ALGO, "proveWildcard: did not find a covering " "NSEC3 that covered the next closer name."); return sec_status_bogus; } if(ce.nc_rrset && nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "proveWildcard: NSEC3 optout"); return sec_status_insecure; } return sec_status_secure; } /** test if list is all secure */ static int list_is_secure(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct key_entry_key* kkey, char** reason, sldns_ede_code *reason_bogus, struct module_qstate* qstate, char* reasonbuf, size_t reasonlen) { struct packed_rrset_data* d; size_t i; int verified = 0; for(i=0; ientry.data; if(list[i]->rk.type != htons(LDNS_RR_TYPE_NSEC3)) continue; if(d->security == sec_status_secure) continue; rrset_check_sec_status(env->rrset_cache, list[i], *env->now); if(d->security == sec_status_secure) continue; d->security = val_verify_rrset_entry(env, ve, list[i], kkey, reason, reason_bogus, LDNS_SECTION_AUTHORITY, qstate, &verified, reasonbuf, reasonlen); if(d->security != sec_status_secure) { verbose(VERB_ALGO, "NSEC3 did not verify"); return 0; } rrset_update_sec_status(env->rrset_cache, list[i], *env->now); } return 1; } enum sec_status nsec3_prove_nods(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, char** reason, sldns_ede_code* reason_bogus, struct module_qstate* qstate, struct nsec3_cache_table* ct, char* reasonbuf, size_t reasonlen) { struct nsec3_filter flt; struct ce_response ce; struct ub_packed_rrset_key* rrset; int rr; int calc = 0; enum sec_status sec; log_assert(qinfo->qtype == LDNS_RR_TYPE_DS); if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) { *reason = "no valid NSEC3s"; return sec_status_bogus; /* no valid NSEC3s, bogus */ } if(!list_is_secure(env, ve, list, num, kkey, reason, reason_bogus, qstate, reasonbuf, reasonlen)) { *reason = "not all NSEC3 records secure"; return sec_status_bogus; /* not all NSEC3 records secure */ } filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) { *reason = "no NSEC3 records"; return sec_status_bogus; /* no RRs */ } if(!param_set_same(&flt, reason)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ /* Look for a matching NSEC3 to qname -- this is the normal * NODATA case. */ if(find_matching_nsec3(env, &flt, ct, qinfo->qname, qinfo->qname_len, &rrset, &rr, &calc)) { /* If the matching NSEC3 has the SOA bit set, it is from * the wrong zone (the child instead of the parent). If * it has the DS bit set, then we were lied to. */ if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_SOA) && qinfo->qname_len != 1) { verbose(VERB_ALGO, "nsec3 provenods: NSEC3 is from" " child zone, bogus"); *reason = "NSEC3 from child zone"; return sec_status_bogus; } else if(nsec3_has_type(rrset, rr, LDNS_RR_TYPE_DS)) { verbose(VERB_ALGO, "nsec3 provenods: NSEC3 has qtype" " DS, bogus"); *reason = "NSEC3 has DS in bitmap"; return sec_status_bogus; } /* If the NSEC3 RR doesn't have the NS bit set, then * this wasn't a delegation point. */ if(!nsec3_has_type(rrset, rr, LDNS_RR_TYPE_NS)) return sec_status_indeterminate; /* Otherwise, this proves no DS. */ return sec_status_secure; } if(calc == MAX_NSEC3_ERRORS) { verbose(VERB_ALGO, "nsec3 provenods: all attempted hash " "calculations were erroneous while finding a matching " "NSEC3, bogus"); return sec_status_bogus; } else if(calc >= MAX_NSEC3_CALCULATIONS) { verbose(VERB_ALGO, "nsec3 provenods: reached " "MAX_NSEC3_CALCULATIONS (%d) while finding a " "matching NSEC3, unchecked still", MAX_NSEC3_CALCULATIONS); return sec_status_unchecked; } /* Otherwise, we are probably in the opt-out case. */ sec = nsec3_prove_closest_encloser(env, &flt, ct, qinfo, 1, &ce, &calc); if(sec == sec_status_unchecked) { return sec_status_unchecked; } else if(sec != sec_status_secure) { /* an insecure delegation *above* the qname does not prove * anything about this qname exactly, and bogus is bogus */ verbose(VERB_ALGO, "nsec3 provenods: did not match qname, " "nor found a proven closest encloser."); *reason = "no NSEC3 closest encloser"; return sec_status_bogus; } /* robust extra check */ if(!ce.nc_rrset) { verbose(VERB_ALGO, "nsec3 nods proof: no next closer nsec3"); *reason = "no NSEC3 next closer"; return sec_status_bogus; } /* we had the closest encloser proof, then we need to check that the * covering NSEC3 was opt-out -- the proveClosestEncloser step already * checked to see if the closest encloser was a delegation or DNAME. */ log_assert(ce.nc_rrset); if(!nsec3_has_optout(ce.nc_rrset, ce.nc_rr)) { verbose(VERB_ALGO, "nsec3 provenods: covering NSEC3 was not " "opt-out in an opt-out DS NOERROR/NODATA case."); *reason = "covering NSEC3 was not opt-out in an opt-out " "DS NOERROR/NODATA case"; return sec_status_bogus; } /* RFC5155 section 9.2: if nc has optout then no AD flag set */ return sec_status_insecure; } enum sec_status nsec3_prove_nxornodata(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key** list, size_t num, struct query_info* qinfo, struct key_entry_key* kkey, int* nodata, struct nsec3_cache_table* ct, int* calc) { enum sec_status sec, secnx; struct nsec3_filter flt; *nodata = 0; if(!list || num == 0 || !kkey || !key_entry_isgood(kkey)) return sec_status_bogus; /* no valid NSEC3s, bogus */ filter_init(&flt, list, num, qinfo); /* init RR iterator */ if(!flt.zone) return sec_status_bogus; /* no RRs */ if(!param_set_same(&flt, NULL)) return sec_status_bogus; /* nsec3 params from distinct chains*/ if(nsec3_iteration_count_high(ve, &flt, kkey)) return sec_status_insecure; /* iteration count too high */ /* try nxdomain and nodata after another, while keeping the * hash cache intact */ secnx = nsec3_do_prove_nameerror(env, &flt, ct, qinfo, calc); if(secnx==sec_status_secure) return sec_status_secure; else if(secnx == sec_status_unchecked) return sec_status_unchecked; sec = nsec3_do_prove_nodata(env, &flt, ct, qinfo, calc); if(sec==sec_status_secure) { *nodata = 1; } else if(sec == sec_status_insecure) { *nodata = 1; } else if(secnx == sec_status_insecure) { sec = sec_status_insecure; } else if(sec == sec_status_unchecked) { return sec_status_unchecked; } return sec; } unbound-1.25.1/validator/val_neg.c0000644000175000017500000013105215203270263016454 0ustar wouterwouter/* * validator/val_neg.c - validator aggressive negative caching functions. * * Copyright (c) 2008, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with aggressive negative caching. * This creates new denials of existence, and proofs for absence of types * from cached NSEC records. */ #include "config.h" #ifdef HAVE_OPENSSL_SSL_H #include #define NSEC3_SHA_LEN SHA_DIGEST_LENGTH #else #define NSEC3_SHA_LEN 20 #endif #include "validator/val_neg.h" #include "validator/val_nsec.h" #include "validator/val_nsec3.h" #include "validator/val_utils.h" #include "util/data/dname.h" #include "util/data/msgreply.h" #include "util/log.h" #include "util/net_help.h" #include "util/config_file.h" #include "services/cache/rrset.h" #include "services/cache/dns.h" #include "sldns/rrdef.h" #include "sldns/sbuffer.h" /** * The maximum salt length that the negative cache is willing to use. * Larger salt increases the computation time, while recommendations are * for zero salt length for zones. */ #define MAX_SALT_LENGTH 64 int val_neg_data_compare(const void* a, const void* b) { struct val_neg_data* x = (struct val_neg_data*)a; struct val_neg_data* y = (struct val_neg_data*)b; int m; return dname_canon_lab_cmp(x->name, x->labs, y->name, y->labs, &m); } int val_neg_zone_compare(const void* a, const void* b) { struct val_neg_zone* x = (struct val_neg_zone*)a; struct val_neg_zone* y = (struct val_neg_zone*)b; int m; if(x->dclass != y->dclass) { if(x->dclass < y->dclass) return -1; return 1; } return dname_canon_lab_cmp(x->name, x->labs, y->name, y->labs, &m); } struct val_neg_cache* val_neg_create(struct config_file* cfg, size_t maxiter) { struct val_neg_cache* neg = (struct val_neg_cache*)calloc(1, sizeof(*neg)); if(!neg) { log_err("Could not create neg cache: out of memory"); return NULL; } neg->nsec3_max_iter = maxiter; neg->max = 1024*1024; /* 1 M is thousands of entries */ if(cfg) neg->max = cfg->neg_cache_size; rbtree_init(&neg->tree, &val_neg_zone_compare); lock_basic_init(&neg->lock); lock_protect(&neg->lock, neg, sizeof(*neg)); return neg; } size_t val_neg_get_mem(struct val_neg_cache* neg) { size_t result; lock_basic_lock(&neg->lock); result = sizeof(*neg) + neg->use; lock_basic_unlock(&neg->lock); return result; } /** clear datas on cache deletion */ static void neg_clear_datas(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct val_neg_data* d = (struct val_neg_data*)n; free(d->name); free(d); } /** clear zones on cache deletion */ static void neg_clear_zones(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct val_neg_zone* z = (struct val_neg_zone*)n; /* delete all the rrset entries in the tree */ traverse_postorder(&z->tree, &neg_clear_datas, NULL); free(z->nsec3_salt); free(z->name); free(z); } void neg_cache_delete(struct val_neg_cache* neg) { if(!neg) return; lock_basic_destroy(&neg->lock); /* delete all the zones in the tree */ traverse_postorder(&neg->tree, &neg_clear_zones, NULL); free(neg); } /** * Put data element at the front of the LRU list. * @param neg: negative cache with LRU start and end. * @param data: this data is fronted. */ static void neg_lru_front(struct val_neg_cache* neg, struct val_neg_data* data) { data->prev = NULL; data->next = neg->first; if(!neg->first) neg->last = data; else neg->first->prev = data; neg->first = data; } /** * Remove data element from LRU list. * @param neg: negative cache with LRU start and end. * @param data: this data is removed from the list. */ static void neg_lru_remove(struct val_neg_cache* neg, struct val_neg_data* data) { if(data->prev) data->prev->next = data->next; else neg->first = data->next; if(data->next) data->next->prev = data->prev; else neg->last = data->prev; } /** * Touch LRU for data element, put it at the start of the LRU list. * @param neg: negative cache with LRU start and end. * @param data: this data is used. */ static void neg_lru_touch(struct val_neg_cache* neg, struct val_neg_data* data) { if(data == neg->first) return; /* nothing to do */ /* remove from current lru position */ neg_lru_remove(neg, data); /* add at front */ neg_lru_front(neg, data); } /** * Delete a zone element from the negative cache. * May delete other zone elements to keep tree coherent, or * only mark the element as 'not in use'. * @param neg: negative cache. * @param z: zone element to delete. */ static void neg_delete_zone(struct val_neg_cache* neg, struct val_neg_zone* z) { struct val_neg_zone* p, *np; if(!z) return; log_assert(z->in_use); log_assert(z->count > 0); z->in_use = 0; /* go up the tree and reduce counts */ p = z; while(p) { log_assert(p->count > 0); p->count --; p = p->parent; } /* remove zones with zero count */ p = z; while(p && p->count == 0) { np = p->parent; (void)rbtree_delete(&neg->tree, &p->node); neg->use -= p->len + sizeof(*p); free(p->nsec3_salt); free(p->name); free(p); p = np; } } void neg_delete_data(struct val_neg_cache* neg, struct val_neg_data* el) { struct val_neg_zone* z; struct val_neg_data* p, *np; if(!el) return; z = el->zone; log_assert(el->in_use); log_assert(el->count > 0); el->in_use = 0; /* remove it from the lru list */ neg_lru_remove(neg, el); log_assert(neg->first != el && neg->last != el); /* go up the tree and reduce counts */ p = el; while(p) { log_assert(p->count > 0); p->count --; p = p->parent; } /* delete 0 count items from tree */ p = el; while(p && p->count == 0) { np = p->parent; (void)rbtree_delete(&z->tree, &p->node); neg->use -= p->len + sizeof(*p); free(p->name); free(p); p = np; } /* check if the zone is now unused */ if(z->tree.count == 0) { neg_delete_zone(neg, z); } } /** * Create more space in negative cache * The oldest elements are deleted until enough space is present. * Empty zones are deleted. * @param neg: negative cache. * @param need: how many bytes are needed. */ static void neg_make_space(struct val_neg_cache* neg, size_t need) { /* delete elements until enough space or its empty */ while(neg->last && neg->max < neg->use + need) { neg_delete_data(neg, neg->last); } } struct val_neg_zone* neg_find_zone(struct val_neg_cache* neg, 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 = nm; lookfor.len = len; lookfor.labs = dname_count_labels(lookfor.name); lookfor.dclass = dclass; result = (struct val_neg_zone*) rbtree_search(&neg->tree, lookfor.node.key); return result; } /** * Find the given data * @param zone: negative zone * @param nm: what to look for. * @param len: length of nm * @param labs: labels in nm * @return data or NULL if not found. */ static struct val_neg_data* neg_find_data(struct val_neg_zone* zone, uint8_t* nm, size_t len, int labs) { struct val_neg_data lookfor; struct val_neg_data* result; lookfor.node.key = &lookfor; lookfor.name = nm; lookfor.len = len; lookfor.labs = labs; result = (struct val_neg_data*) rbtree_search(&zone->tree, lookfor.node.key); return result; } /** * Calculate space needed for the data and all its parents * @param rep: NSEC entries. * @return size. */ static size_t calc_data_need(struct reply_info* rep) { uint8_t* d; size_t i, len, res = 0; for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC) { d = rep->rrsets[i]->rk.dname; len = rep->rrsets[i]->rk.dname_len; res = sizeof(struct val_neg_data) + len; while(!dname_is_root(d)) { log_assert(len > 1); /* not root label */ dname_remove_label(&d, &len); res += sizeof(struct val_neg_data) + len; } } } return res; } /** * Calculate space needed for zone and all its parents * @param d: name of zone * @param len: length of name * @return size. */ static size_t calc_zone_need(uint8_t* d, size_t len) { size_t res = sizeof(struct val_neg_zone) + len; while(!dname_is_root(d)) { log_assert(len > 1); /* not root label */ dname_remove_label(&d, &len); res += sizeof(struct val_neg_zone) + len; } return res; } /** * Find closest existing parent zone of the given name. * @param neg: negative cache. * @param nm: name to look for * @param nm_len: length of nm * @param labs: labelcount of nm. * @param qclass: class. * @return the zone or NULL if none found. */ static struct val_neg_zone* neg_closest_zone_parent(struct val_neg_cache* neg, uint8_t* nm, size_t nm_len, int labs, uint16_t qclass) { struct val_neg_zone key; struct val_neg_zone* result; rbnode_type* res = NULL; key.node.key = &key; key.name = nm; key.len = nm_len; key.labs = labs; key.dclass = qclass; if(rbtree_find_less_equal(&neg->tree, &key, &res)) { /* exact match */ result = (struct val_neg_zone*)res; } else { /* smaller element (or no element) */ int m; result = (struct val_neg_zone*)res; if(!result || result->dclass != qclass) return NULL; /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->labs, key.name, key.labs, &m); while(result) { /* go up until qname is subdomain of stub */ if(result->labs <= m) break; result = result->parent; } } return result; } /** * Find closest existing parent data for the given name. * @param zone: to look in. * @param nm: name to look for * @param nm_len: length of nm * @param labs: labelcount of nm. * @return the data or NULL if none found. */ static struct val_neg_data* neg_closest_data_parent( struct val_neg_zone* zone, uint8_t* nm, size_t nm_len, int labs) { struct val_neg_data key; struct val_neg_data* result; rbnode_type* res = NULL; key.node.key = &key; key.name = nm; key.len = nm_len; key.labs = labs; if(rbtree_find_less_equal(&zone->tree, &key, &res)) { /* exact match */ result = (struct val_neg_data*)res; } else { /* smaller element (or no element) */ int m; result = (struct val_neg_data*)res; if(!result) return NULL; /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->labs, key.name, key.labs, &m); while(result) { /* go up until qname is subdomain of stub */ if(result->labs <= m) break; result = result->parent; } } return result; } /** * Create a single zone node * @param nm: name for zone (copied) * @param nm_len: length of name * @param labs: labels in name. * @param dclass: class of zone, host order. * @return new zone or NULL on failure */ static struct val_neg_zone* neg_setup_zone_node( uint8_t* nm, size_t nm_len, int labs, uint16_t dclass) { struct val_neg_zone* zone = (struct val_neg_zone*)calloc(1, sizeof(*zone)); if(!zone) { return NULL; } zone->node.key = zone; zone->name = memdup(nm, nm_len); if(!zone->name) { free(zone); return NULL; } zone->len = nm_len; zone->labs = labs; zone->dclass = dclass; rbtree_init(&zone->tree, &val_neg_data_compare); return zone; } /** * Create a linked list of parent zones, starting at longname ending on * the parent (can be NULL, creates to the root). * @param nm: name for lowest in chain * @param nm_len: length of name * @param labs: labels in name. * @param dclass: class of zone. * @param parent: NULL for to root, else so it fits under here. * @return zone; a chain of zones and their parents up to the parent. * or NULL on malloc failure */ static struct val_neg_zone* neg_zone_chain( uint8_t* nm, size_t nm_len, int labs, uint16_t dclass, struct val_neg_zone* parent) { int i; int tolabs = parent?parent->labs:0; struct val_neg_zone* zone, *prev = NULL, *first = NULL; /* create the new subtree, i is labelcount of current creation */ /* this creates a 'first' to z->parent=NULL list of zones */ for(i=labs; i!=tolabs; i--) { /* create new item */ zone = neg_setup_zone_node(nm, nm_len, i, dclass); if(!zone) { /* need to delete other allocations in this routine!*/ struct val_neg_zone* p=first, *np; while(p) { np = p->parent; free(p->name); free(p); p = np; } return NULL; } if(i == labs) { first = zone; } else { prev->parent = zone; } /* prepare for next name */ prev = zone; dname_remove_label(&nm, &nm_len); } return first; } void val_neg_zone_take_inuse(struct val_neg_zone* zone) { if(!zone->in_use) { struct val_neg_zone* p; zone->in_use = 1; /* increase usage count of all parents */ for(p=zone; p; p = p->parent) { p->count++; } } } struct val_neg_zone* neg_create_zone(struct val_neg_cache* neg, 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; int labs = dname_count_labels(nm); /* find closest enclosing parent zone that (still) exists */ parent = neg_closest_zone_parent(neg, nm, nm_len, labs, dclass); if(parent && query_dname_compare(parent->name, nm) == 0) return parent; /* already exists, weird */ /* if parent exists, it is in use */ log_assert(!parent || parent->count > 0); zone = neg_zone_chain(nm, nm_len, labs, dclass, parent); if(!zone) { return NULL; } /* insert the list of zones into the tree */ p = zone; while(p) { np = p->parent; /* mem use */ neg->use += sizeof(struct val_neg_zone) + p->len; /* insert in tree */ (void)rbtree_insert(&neg->tree, &p->node); /* last one needs proper parent pointer */ if(np == NULL) p->parent = parent; p = np; } return zone; } /** find zone name of message, returns the SOA record */ static struct ub_packed_rrset_key* reply_find_soa(struct reply_info* rep) { size_t i; for(i=rep->an_numrrsets; i< rep->an_numrrsets+rep->ns_numrrsets; i++){ if(ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_SOA) return rep->rrsets[i]; } return NULL; } /** see if the reply has NSEC records worthy of caching */ static int reply_has_nsec(struct reply_info* rep) { size_t i; struct packed_rrset_data* d; if(rep->security != sec_status_secure) return 0; 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; if(d->security == sec_status_secure) return 1; } } return 0; } /** * Create single node of data element. * @param nm: name (copied) * @param nm_len: length of name * @param labs: labels in name. * @return element with name nm, or NULL malloc failure. */ static struct val_neg_data* neg_setup_data_node( uint8_t* nm, size_t nm_len, int labs) { struct val_neg_data* el; el = (struct val_neg_data*)calloc(1, sizeof(*el)); if(!el) { return NULL; } el->node.key = el; el->name = memdup(nm, nm_len); if(!el->name) { free(el); return NULL; } el->len = nm_len; el->labs = labs; return el; } /** * Create chain of data element and parents * @param nm: name * @param nm_len: length of name * @param labs: labels in name. * @param parent: up to where to make, if NULL up to root label. * @return lowest element with name nm, or NULL malloc failure. */ static struct val_neg_data* neg_data_chain( uint8_t* nm, size_t nm_len, int labs, struct val_neg_data* parent) { int i; int tolabs = parent?parent->labs:0; struct val_neg_data* el, *first = NULL, *prev = NULL; /* create the new subtree, i is labelcount of current creation */ /* this creates a 'first' to z->parent=NULL list of zones */ for(i=labs; i!=tolabs; i--) { /* create new item */ el = neg_setup_data_node(nm, nm_len, i); if(!el) { /* need to delete other allocations in this routine!*/ struct val_neg_data* p = first, *np; while(p) { np = p->parent; free(p->name); free(p); p = np; } return NULL; } if(i == labs) { first = el; } else { prev->parent = el; } /* prepare for next name */ prev = el; dname_remove_label(&nm, &nm_len); } return first; } /** * Remove NSEC records between start and end points. * By walking the tree, the tree is sorted canonically. * @param neg: negative cache. * @param zone: the zone * @param el: element to start walking at. * @param nsec: the nsec record with the end point */ static void wipeout(struct val_neg_cache* neg, struct val_neg_zone* zone, struct val_neg_data* el, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d = (struct packed_rrset_data*)nsec-> entry.data; uint8_t* end; size_t end_len; int end_labs, m; rbnode_type* walk, *next; struct val_neg_data* cur; uint8_t buf[257]; /* get endpoint */ if(!d || d->count == 0 || d->rr_len[0] < 2+1) return; if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC) { end = d->rr_data[0]+2; end_len = dname_valid(end, d->rr_len[0]-2); end_labs = dname_count_labels(end); } else { /* NSEC3 */ if(!nsec3_get_nextowner_b32(nsec, 0, buf, sizeof(buf))) return; end = buf; end_labs = dname_count_size_labels(end, &end_len); } /* sanity check, both owner and end must be below the zone apex */ if(!dname_subdomain_c(el->name, zone->name) || !dname_subdomain_c(end, zone->name)) return; /* detect end of zone NSEC ; wipe until the end of zone */ if(query_dname_compare(end, zone->name) == 0) { end = NULL; } walk = rbtree_next(&el->node); while(walk && walk != RBTREE_NULL) { cur = (struct val_neg_data*)walk; /* sanity check: must be larger than start */ if(dname_canon_lab_cmp(cur->name, cur->labs, el->name, el->labs, &m) <= 0) { /* r == 0 skip original record. */ /* r < 0 too small! */ walk = rbtree_next(walk); continue; } /* stop at endpoint, also data at empty nonterminals must be * removed (no NSECs there) so everything between * start and end */ if(end && dname_canon_lab_cmp(cur->name, cur->labs, end, end_labs, &m) >= 0) { break; } /* this element has to be deleted, but we cannot do it * now, because we are walking the tree still ... */ /* get the next element: */ next = rbtree_next(walk); /* now delete the original element, this may trigger * rbtree rebalances, but really, the next element is * the one we need. * But it may trigger delete of other data and the * entire zone. However, if that happens, this is done * by deleting the *parents* of the element for deletion, * and maybe also the entire zone if it is empty. * But parents are smaller in canonical compare, thus, * if a larger element exists, then it is not a parent, * it cannot get deleted, the zone cannot get empty. * If the next==NULL, then zone can be empty. */ if(cur->in_use) neg_delete_data(neg, cur); walk = next; } } void neg_insert_data(struct val_neg_cache* neg, struct val_neg_zone* zone, struct ub_packed_rrset_key* nsec) { struct packed_rrset_data* d; struct val_neg_data* parent; struct val_neg_data* el; uint8_t* nm = nsec->rk.dname; size_t nm_len = nsec->rk.dname_len; int labs = dname_count_labels(nsec->rk.dname); d = (struct packed_rrset_data*)nsec->entry.data; if( !(d->security == sec_status_secure || (d->security == sec_status_unchecked && d->rrsig_count > 0))) return; log_nametypeclass(VERB_ALGO, "negcache rr", nsec->rk.dname, ntohs(nsec->rk.type), ntohs(nsec->rk.rrset_class)); /* find closest enclosing parent data that (still) exists */ parent = neg_closest_data_parent(zone, nm, nm_len, labs); if(parent && query_dname_compare(parent->name, nm) == 0) { /* perfect match already exists */ log_assert(parent->count > 0); el = parent; } else { struct val_neg_data* p, *np; /* create subtree for perfect match */ /* if parent exists, it is in use */ log_assert(!parent || parent->count > 0); el = neg_data_chain(nm, nm_len, labs, parent); if(!el) { log_err("out of memory inserting NSEC negative cache"); return; } el->in_use = 0; /* set on below */ /* insert the list of zones into the tree */ p = el; while(p) { np = p->parent; /* mem use */ neg->use += sizeof(struct val_neg_data) + p->len; /* insert in tree */ p->zone = zone; (void)rbtree_insert(&zone->tree, &p->node); /* last one needs proper parent pointer */ if(np == NULL) p->parent = parent; p = np; } } if(!el->in_use) { struct val_neg_data* p; el->in_use = 1; /* increase usage count of all parents */ for(p=el; p; p = p->parent) { p->count++; } neg_lru_front(neg, el); } else { /* in use, bring to front, lru */ neg_lru_touch(neg, el); } /* if nsec3 store last used parameters */ if(ntohs(nsec->rk.type) == LDNS_RR_TYPE_NSEC3) { int h; uint8_t* s; size_t slen, it; if(nsec3_get_params(nsec, 0, &h, &it, &s, &slen) && it <= neg->nsec3_max_iter && (h != zone->nsec3_hash || it != zone->nsec3_iter || slen != zone->nsec3_saltlen || (slen != 0 && zone->nsec3_salt && s && memcmp(zone->nsec3_salt, s, slen) != 0))) { if(slen > MAX_SALT_LENGTH) { /* RFC 9276 s3.1: operators SHOULD NOT use a salt; large * salts inflate per-hash block count. Decline to cache. */ return; } else if(slen > 0) { uint8_t* sa = memdup(s, slen); if(sa) { free(zone->nsec3_salt); zone->nsec3_salt = sa; zone->nsec3_saltlen = slen; zone->nsec3_iter = it; zone->nsec3_hash = h; } } else { free(zone->nsec3_salt); zone->nsec3_salt = NULL; zone->nsec3_saltlen = 0; zone->nsec3_iter = it; zone->nsec3_hash = h; } } } /* wipe out the cache items between NSEC start and end */ wipeout(neg, zone, el, nsec); } /** 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 || ntohs(rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_NSEC3) { 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_addreply(struct val_neg_cache* neg, struct reply_info* rep) { size_t i, need; struct ub_packed_rrset_key* soa; uint8_t* dname = NULL; size_t dname_len; uint16_t rrset_class; struct val_neg_zone* zone; /* see if secure nsecs inside */ if(!reply_has_nsec(rep)) return; /* find the zone name in message */ if((soa = reply_find_soa(rep))) { dname = soa->rk.dname; dname_len = soa->rk.dname_len; rrset_class = ntohs(soa->rk.rrset_class); } else { /* No SOA in positive (wildcard) answer. Use signer from the * validated answer RRsets' signature. */ if(!(dname = reply_nsec_signer(rep, &dname_len, &rrset_class))) return; } log_nametypeclass(VERB_ALGO, "negcache insert for zone", dname, LDNS_RR_TYPE_SOA, rrset_class); /* ask for enough space to store all of it */ need = calc_data_need(rep) + calc_zone_need(dname, dname_len); lock_basic_lock(&neg->lock); neg_make_space(neg, need); /* find or create the zone entry */ zone = neg_find_zone(neg, dname, dname_len, rrset_class); if(!zone) { if(!(zone = neg_create_zone(neg, dname, dname_len, rrset_class))) { lock_basic_unlock(&neg->lock); log_err("out of memory adding negative zone"); return; } } val_neg_zone_take_inuse(zone); /* 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]); } if(zone->tree.count == 0) { /* remove empty zone if inserts failed */ neg_delete_zone(neg, zone); } lock_basic_unlock(&neg->lock); } /** * Lookup closest data record. For NSEC denial. * @param zone: zone to look in * @param qname: name to look for. * @param len: length of name * @param labs: labels in name * @param data: data element, exact or smaller or NULL * @return true if exact match. */ static int neg_closest_data(struct val_neg_zone* zone, uint8_t* qname, size_t len, int labs, struct val_neg_data** data) { struct val_neg_data key; rbnode_type* r; key.node.key = &key; key.name = qname; key.len = len; key.labs = labs; if(rbtree_find_less_equal(&zone->tree, &key, &r)) { /* exact match */ *data = (struct val_neg_data*)r; return 1; } else { /* smaller match */ *data = (struct val_neg_data*)r; 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; } } val_neg_zone_take_inuse(zone); /* 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 && ntohs(rep->rrsets[i]->rk.type) != LDNS_RR_TYPE_NSEC3) 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]); } if(zone->tree.count == 0) { /* remove empty zone if inserts failed */ neg_delete_zone(neg, zone); } lock_basic_unlock(&neg->lock); } /** * Check that an NSEC3 rrset does not have a type set. * None of the nsec3s in a hash-collision are allowed to have the type. * (since we do not know which one is the nsec3 looked at, flags, ..., we * ignore the cached item and let it bypass negative caching). * @param k: the nsec3 rrset to check. * @param t: type to check * @return true if no RRs have the type. */ static int nsec3_no_type(struct ub_packed_rrset_key* k, uint16_t t) { int count = (int)((struct packed_rrset_data*)k->entry.data)->count; int i; for(i=0; ientry.data; /* only secure or unchecked records that have signatures. */ if( ! ( d->security == sec_status_secure || (d->security == sec_status_unchecked && d->rrsig_count > 0) ) ) { lock_rw_unlock(&k->entry.lock); return NULL; } /* check if checktype is absent */ if(checkbit && ( (qtype == LDNS_RR_TYPE_NSEC && nsec_has_type(k, checktype)) || (qtype == LDNS_RR_TYPE_NSEC3 && !nsec3_no_type(k, checktype)) )) { lock_rw_unlock(&k->entry.lock); return NULL; } /* looks OK! copy to region and return it */ r = packed_rrset_copy_region(k, region, now); /* if it failed, we return the NULL */ lock_rw_unlock(&k->entry.lock); return r; } /** * Get best NSEC record for qname. Might be matching, covering or totally * useless. * @param neg_cache: neg cache * @param qname: to lookup rrset name * @param qname_len: length of qname. * @param qclass: class of rrset to lookup, host order * @param rrset_cache: rrset cache * @param now: to check ttl against * @param region: where to alloc result * @return rrset or NULL */ static struct ub_packed_rrset_key* neg_find_nsec(struct val_neg_cache* neg_cache, uint8_t* qname, size_t qname_len, uint16_t qclass, struct rrset_cache* rrset_cache, time_t now, struct regional* region) { int labs; uint32_t flags; struct val_neg_zone* zone; struct val_neg_data* data; struct ub_packed_rrset_key* nsec; labs = dname_count_labels(qname); lock_basic_lock(&neg_cache->lock); zone = neg_closest_zone_parent(neg_cache, qname, qname_len, labs, qclass); while(zone && !zone->in_use) zone = zone->parent; if(!zone) { lock_basic_unlock(&neg_cache->lock); return NULL; } /* NSEC only for now */ if(zone->nsec3_hash) { lock_basic_unlock(&neg_cache->lock); return NULL; } /* ignore return value, don't care if it is an exact or smaller match */ (void)neg_closest_data(zone, qname, qname_len, labs, &data); if(!data) { lock_basic_unlock(&neg_cache->lock); return NULL; } /* ENT nodes are not in use, try the previous node. If the previous node * is not in use, we don't have an useful NSEC and give up. */ if(!data->in_use) { data = (struct val_neg_data*)rbtree_previous((rbnode_type*)data); if((rbnode_type*)data == RBTREE_NULL || !data->in_use) { lock_basic_unlock(&neg_cache->lock); return NULL; } } flags = 0; if(query_dname_compare(data->name, zone->name) == 0) flags = PACKED_RRSET_NSEC_AT_APEX; nsec = grab_nsec(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC, zone->dclass, flags, region, 0, 0, now); lock_basic_unlock(&neg_cache->lock); return nsec; } /** find nsec3 closest encloser in neg cache */ static struct val_neg_data* neg_find_nsec3_ce(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, int qlabs, sldns_buffer* buf, uint8_t* hashnc, size_t* nclen) { struct val_neg_data* data; uint8_t hashce[NSEC3_SHA_LEN]; uint8_t b32[257]; size_t celen, b32len; int hashmax = MAX_NSEC3_CALCULATIONS; if(qlabs > hashmax) { /* strip leading labels so the walk costs at most * MAX_NSEC3_CALCULATIONS hashes, mirroring val_nsec3.c */ while(qlabs > hashmax) { dname_remove_label(&qname, &qname_len); qlabs--; } } *nclen = 0; while(qlabs > 0) { /* hash */ if(!(celen=nsec3_get_hashed(buf, qname, qname_len, zone->nsec3_hash, zone->nsec3_iter, zone->nsec3_salt, zone->nsec3_saltlen, hashce, sizeof(hashce)))) return NULL; if(!(b32len=nsec3_hash_to_b32(hashce, celen, zone->name, zone->len, b32, sizeof(b32)))) return NULL; /* lookup (exact match only) */ data = neg_find_data(zone, b32, b32len, zone->labs+1); if(data && data->in_use) { /* found ce match! */ return data; } *nclen = celen; memmove(hashnc, hashce, celen); dname_remove_label(&qname, &qname_len); qlabs --; } return NULL; } /** check nsec3 parameters on nsec3 rrset with current zone values */ static int neg_params_ok(struct val_neg_zone* zone, struct ub_packed_rrset_key* rrset) { int h; uint8_t* s; size_t slen, it; if(!nsec3_get_params(rrset, 0, &h, &it, &s, &slen)) return 0; return (h == zone->nsec3_hash && it == zone->nsec3_iter && slen == zone->nsec3_saltlen && (slen != 0 && zone->nsec3_salt && s && memcmp(zone->nsec3_salt, s, slen) == 0)); } /** get next closer for nsec3 proof */ static struct ub_packed_rrset_key* neg_nsec3_getnc(struct val_neg_zone* zone, uint8_t* hashnc, size_t nclen, struct rrset_cache* rrset_cache, struct regional* region, time_t now, uint8_t* b32, size_t maxb32) { struct ub_packed_rrset_key* nc_rrset; struct val_neg_data* data; size_t b32len; if(!(b32len=nsec3_hash_to_b32(hashnc, nclen, zone->name, zone->len, b32, maxb32))) return NULL; (void)neg_closest_data(zone, b32, b32len, zone->labs+1, &data); if(!data && zone->tree.count != 0) { /* could be before the first entry ; return the last * entry (possibly the rollover nsec3 at end) */ data = (struct val_neg_data*)rbtree_last(&zone->tree); } while(data && !data->in_use) data = data->parent; if(!data) return NULL; /* got a data element in tree, grab it */ nc_rrset = grab_nsec(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 0, 0, now); if(!nc_rrset) return NULL; if(!neg_params_ok(zone, nc_rrset)) return NULL; return nc_rrset; } /** neg cache nsec3 proof procedure*/ static struct dns_msg* neg_nsec3_proof_ds(struct val_neg_zone* zone, uint8_t* qname, size_t qname_len, int qlabs, sldns_buffer* buf, struct rrset_cache* rrset_cache, struct regional* region, time_t now, uint8_t* topname) { struct dns_msg* msg; struct val_neg_data* data; uint8_t hashnc[NSEC3_SHA_LEN]; size_t nclen; struct ub_packed_rrset_key* ce_rrset, *nc_rrset; struct nsec3_cached_hash c; uint8_t nc_b32[257]; /* for NSEC3 ; determine the closest encloser for which we * can find an exact match. Remember the hashed lower name, * since that is the one we need a closest match for. * If we find a match straight away, then it becomes NODATA. * Otherwise, NXDOMAIN or if OPTOUT, an insecure delegation. * Also check that parameters are the same on closest encloser * and on closest match. */ if(!zone->nsec3_hash) return NULL; /* not nsec3 zone */ if(!topname && qlabs > zone->labs + 1) return NULL; /* iterator caller; opt-out proof would be discarded * at the !topname check below anyway. * The qlabs check allows the exact-match for * the one-label-below-zone case. */ if(!(data=neg_find_nsec3_ce(zone, qname, qname_len, qlabs, buf, hashnc, &nclen))) { return NULL; } /* grab the ce rrset */ ce_rrset = grab_nsec(rrset_cache, data->name, data->len, LDNS_RR_TYPE_NSEC3, zone->dclass, 0, region, 1, LDNS_RR_TYPE_DS, now); if(!ce_rrset) return NULL; if(!neg_params_ok(zone, ce_rrset)) return NULL; if(nclen == 0) { /* exact match, just check the type bits */ /* need: -SOA, -DS, +NS */ if(nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_SOA) || nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_DS) || !nsec3_has_type(ce_rrset, 0, LDNS_RR_TYPE_NS)) return NULL; if(!(msg = dns_msg_create(qname, qname_len, LDNS_RR_TYPE_DS, zone->dclass, region, 1))) return NULL; /* The cache response means recursion is available. */ msg->rep->flags |= BIT_RA; /* TTL reduced in grab_nsec */ if(!dns_msg_authadd(msg, region, ce_rrset, 0)) return NULL; return msg; } /* optout is not allowed without knowing the trust-anchor in use, * otherwise the optout could spoof away that anchor */ if(!topname) return NULL; /* if there is no exact match, it must be in an optout span * (an existing DS implies an NSEC3 must exist) */ nc_rrset = neg_nsec3_getnc(zone, hashnc, nclen, rrset_cache, region, now, nc_b32, sizeof(nc_b32)); if(!nc_rrset) return NULL; if(!neg_params_ok(zone, nc_rrset)) return NULL; if(!nsec3_has_optout(nc_rrset, 0)) return NULL; c.hash = hashnc; c.hash_len = nclen; c.b32 = nc_b32+1; c.b32_len = (size_t)nc_b32[0]; if(nsec3_covers(zone->name, &c, nc_rrset, 0, buf)) { /* nc_rrset covers the next closer name. * ce_rrset equals a closer encloser. * nc_rrset is optout. * No need to check wildcard for type DS */ /* capacity=3: ce + nc + soa(if needed) */ if(!(msg = dns_msg_create(qname, qname_len, LDNS_RR_TYPE_DS, zone->dclass, region, 3))) return NULL; /* The cache response means recursion is available. */ msg->rep->flags |= BIT_RA; /* now=0 because TTL was reduced in grab_nsec */ if(!dns_msg_authadd(msg, region, ce_rrset, 0)) return NULL; if(!dns_msg_authadd(msg, region, nc_rrset, 0)) return NULL; return msg; } return NULL; } /** * Add SOA record for external responses. * @param rrset_cache: to look into. * @param now: current time. * @param region: where to perform the allocation * @param msg: current msg with NSEC. * @param zone: val_neg_zone if we have one. * @return false on lookup or alloc failure. */ static int add_soa(struct rrset_cache* rrset_cache, time_t now, struct regional* region, struct dns_msg* msg, struct val_neg_zone* zone) { struct ub_packed_rrset_key* soa; uint8_t* nm; size_t nmlen; uint16_t dclass; if(zone) { nm = zone->name; nmlen = zone->len; dclass = zone->dclass; } else { /* Assumes the signer is the zone SOA to add */ nm = reply_nsec_signer(msg->rep, &nmlen, &dclass); if(!nm) return 0; } soa = rrset_cache_lookup(rrset_cache, nm, nmlen, LDNS_RR_TYPE_SOA, dclass, PACKED_RRSET_SOA_NEG, now, 0); if(!soa) return 0; if(!dns_msg_authadd(msg, region, soa, now)) { lock_rw_unlock(&soa->entry.lock); return 0; } lock_rw_unlock(&soa->entry.lock); return 1; } struct dns_msg* val_neg_getmsg(struct val_neg_cache* neg, struct query_info* qinfo, struct regional* region, struct rrset_cache* rrset_cache, sldns_buffer* buf, time_t now, int addsoa, uint8_t* topname, struct config_file* cfg) { struct dns_msg* msg; struct ub_packed_rrset_key* nsec; /* qname matching/covering nsec */ struct ub_packed_rrset_key* wcrr; /* wildcard record or nsec */ uint8_t* nodata_wc = NULL; uint8_t* ce = NULL; size_t ce_len; uint8_t wc_ce[LDNS_MAX_DOMAINLEN+3]; struct query_info wc_qinfo; struct ub_packed_rrset_key* cache_wc; struct packed_rrset_data* wcrr_data; int rcode = LDNS_RCODE_NOERROR; uint8_t* zname; size_t zname_len; int zname_labs; struct val_neg_zone* zone; /* only for DS queries when aggressive use of NSEC is disabled */ if(qinfo->qtype != LDNS_RR_TYPE_DS && !cfg->aggressive_nsec) return NULL; log_assert(!topname || dname_subdomain_c(qinfo->qname, topname)); /* Get best available NSEC for qname */ nsec = neg_find_nsec(neg, qinfo->qname, qinfo->qname_len, qinfo->qclass, rrset_cache, now, region); /* Matching NSEC, use to generate No Data answer. Not creating answers * yet for No Data proven using wildcard. */ if(nsec && nsec_proves_nodata(nsec, qinfo, &nodata_wc) && !nodata_wc) { /* do not create nodata answers for qtype ANY, it is a query * type, not an rrtype to disprove. Nameerrors are useful for * qtype ANY, in the else branch. */ if(qinfo->qtype == LDNS_RR_TYPE_ANY) return NULL; if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len, qinfo->qtype, qinfo->qclass, region, 2))) return NULL; /* The cache response means recursion is available. */ msg->rep->flags |= BIT_RA; if(!dns_msg_authadd(msg, region, nsec, 0)) return NULL; if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL)) return NULL; lock_basic_lock(&neg->lock); neg->num_neg_cache_noerror++; lock_basic_unlock(&neg->lock); return msg; } else if(nsec && val_nsec_proves_name_error(nsec, qinfo->qname)) { if(!(msg = dns_msg_create(qinfo->qname, qinfo->qname_len, qinfo->qtype, qinfo->qclass, region, 3))) return NULL; /* The cache response means recursion is available. */ msg->rep->flags |= BIT_RA; if(!(ce = nsec_closest_encloser(qinfo->qname, nsec))) return NULL; dname_count_size_labels(ce, &ce_len); /* No extra extra NSEC required if both nameerror qname and * nodata *.ce. are proven already. */ if(!nodata_wc || query_dname_compare(nodata_wc, ce) != 0) { /* Qname proven non existing, get wildcard record for * QTYPE or NSEC covering or matching wildcard. */ /* Num labels in ce is always smaller than in qname, * therefore adding the wildcard label cannot overflow * buffer. */ wc_ce[0] = 1; wc_ce[1] = (uint8_t)'*'; memmove(wc_ce+2, ce, ce_len); wc_qinfo.qname = wc_ce; wc_qinfo.qname_len = ce_len + 2; wc_qinfo.qtype = qinfo->qtype; if((cache_wc = rrset_cache_lookup(rrset_cache, wc_qinfo.qname, wc_qinfo.qname_len, wc_qinfo.qtype, qinfo->qclass, 0/*flags*/, now, 0/*read only*/))) { /* Synthesize wildcard answer */ wcrr_data = (struct packed_rrset_data*)cache_wc->entry.data; if(!(wcrr_data->security == sec_status_secure || (wcrr_data->security == sec_status_unchecked && wcrr_data->rrsig_count > 0))) { lock_rw_unlock(&cache_wc->entry.lock); return NULL; } if(!(wcrr = packed_rrset_copy_region(cache_wc, region, now))) { lock_rw_unlock(&cache_wc->entry.lock); return NULL; }; lock_rw_unlock(&cache_wc->entry.lock); wcrr->rk.dname = qinfo->qname; wcrr->rk.dname_len = qinfo->qname_len; if(!dns_msg_ansadd(msg, region, wcrr, 0)) return NULL; /* No SOA needed for wildcard synthesised * answer. */ addsoa = 0; } else { /* Get wildcard NSEC for possible non existence * proof */ if(!(wcrr = neg_find_nsec(neg, wc_qinfo.qname, wc_qinfo.qname_len, qinfo->qclass, rrset_cache, now, region))) return NULL; nodata_wc = NULL; if(val_nsec_proves_name_error(wcrr, wc_ce)) rcode = LDNS_RCODE_NXDOMAIN; else if(!nsec_proves_nodata(wcrr, &wc_qinfo, &nodata_wc) || nodata_wc) /* &nodata_wc shouldn't be set, wc_qinfo * already contains wildcard domain. */ /* NSEC doesn't prove anything for * wildcard. */ return NULL; if(query_dname_compare(wcrr->rk.dname, nsec->rk.dname) != 0) if(!dns_msg_authadd(msg, region, wcrr, 0)) return NULL; } } if(!dns_msg_authadd(msg, region, nsec, 0)) return NULL; if(addsoa && !add_soa(rrset_cache, now, region, msg, NULL)) return NULL; /* Increment statistic counters */ lock_basic_lock(&neg->lock); if(rcode == LDNS_RCODE_NOERROR) neg->num_neg_cache_noerror++; else if(rcode == LDNS_RCODE_NXDOMAIN) neg->num_neg_cache_nxdomain++; lock_basic_unlock(&neg->lock); FLAGS_SET_RCODE(msg->rep->flags, rcode); return msg; } /* No aggressive use of NSEC3 for now, only proceed for DS types. */ if(qinfo->qtype != LDNS_RR_TYPE_DS){ return NULL; } /* check NSEC3 neg cache for type DS */ /* need to look one zone higher for DS type */ zname = qinfo->qname; zname_len = qinfo->qname_len; dname_remove_label(&zname, &zname_len); zname_labs = dname_count_labels(zname); /* lookup closest zone */ lock_basic_lock(&neg->lock); zone = neg_closest_zone_parent(neg, zname, zname_len, zname_labs, qinfo->qclass); while(zone && !zone->in_use) zone = zone->parent; /* check that the zone is not too high up so that we do not pick data * out of a zone that is above the last-seen key (or trust-anchor). */ if(zone && topname) { if(!dname_subdomain_c(zone->name, topname)) zone = NULL; } if(!zone) { lock_basic_unlock(&neg->lock); return NULL; } msg = neg_nsec3_proof_ds(zone, qinfo->qname, qinfo->qname_len, zname_labs+1, buf, rrset_cache, region, now, topname); if(msg && addsoa && !add_soa(rrset_cache, now, region, msg, zone)) { lock_basic_unlock(&neg->lock); return NULL; } lock_basic_unlock(&neg->lock); return msg; } void val_neg_adjust_size(struct val_neg_cache* neg, size_t max) { lock_basic_lock(&neg->lock); neg->max = max; neg_make_space(neg, 0); lock_basic_unlock(&neg->lock); } unbound-1.25.1/validator/autotrust.h0000644000175000017500000001455515203270263017130 0ustar wouterwouter/* * validator/autotrust.h - RFC5011 trust anchor management for unbound. * * Copyright (c) 2009, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * Contains autotrust definitions. */ #ifndef VALIDATOR_AUTOTRUST_H #define VALIDATOR_AUTOTRUST_H #include "util/rbtree.h" #include "util/data/packed_rrset.h" struct val_anchors; struct trust_anchor; struct ub_packed_rrset_key; struct module_env; struct module_qstate; struct val_env; struct sldns_buffer; /** Autotrust anchor states */ typedef enum { AUTR_STATE_START = 0, AUTR_STATE_ADDPEND = 1, AUTR_STATE_VALID = 2, AUTR_STATE_MISSING = 3, AUTR_STATE_REVOKED = 4, AUTR_STATE_REMOVED = 5 } autr_state_type; /** * Autotrust metadata for one trust anchor key. */ struct autr_ta { /** next key */ struct autr_ta* next; /** the RR */ uint8_t* rr; /** length of rr */ size_t rr_len, dname_len; /** last update of key state (new pending count keeps date the same) */ time_t last_change; /** 5011 state */ autr_state_type s; /** pending count */ uint8_t pending_count; /** fresh TA was seen */ uint8_t fetched; /** revoked TA was seen */ uint8_t revoked; }; /** * Autotrust metadata for a trust point. * This is part of the struct trust_anchor data. */ struct autr_point_data { /** file to store the trust point in. chrootdir already applied. */ char* file; /** rbtree node for probe sort, key is struct trust_anchor */ rbnode_type pnode; /** the keys */ struct autr_ta* keys; /** last queried DNSKEY set * Not all failures are captured in this entry. * If the validator did not even start (e.g. timeout or localservfail), * then the last_queried and query_failed values are not updated. */ time_t last_queried; /** last successful DNSKEY set */ time_t last_success; /** next probe time */ time_t next_probe_time; /** when to query if !failed */ time_t query_interval; /** when to retry if failed */ time_t retry_time; /** * How many times did it fail. diagnostic only (has no effect). * Only updated if there was a dnskey rrset that failed to verify. */ uint8_t query_failed; /** true if the trust point has been revoked */ uint8_t revoked; }; /** * Autotrust global metadata. */ struct autr_global_data { /** rbtree of autotrust anchors sorted by next probe time. * When time is equal, sorted by anchor class, name. */ rbtree_type probe; }; /** * Create new global 5011 data structure. * @return new structure or NULL on malloc failure. */ struct autr_global_data* autr_global_create(void); /** * Delete global 5011 data structure. * @param global: global autotrust state to delete. */ void autr_global_delete(struct autr_global_data* global); /** * See if autotrust anchors are configured and how many. * @param anchors: the trust anchors structure. * @return number of autotrust trust anchors */ size_t autr_get_num_anchors(struct val_anchors* anchors); /** * Process probe timer. Add new probes if needed. * @param env: module environment with time, with anchors and with the mesh. * @return time of next probe (in seconds from now). * If 0, then there is no next probe anymore (trust points deleted). */ time_t autr_probe_timer(struct module_env* env); /** probe tree compare function */ int probetree_cmp(const void* x, const void* y); /** * Read autotrust file. * @param anchors: the anchors structure. * @param nm: name of the file (copied). * @return false on failure. */ int autr_read_file(struct val_anchors* anchors, const char* nm); /** * Write autotrust file. * @param env: environment with scratch space. * @param tp: trust point to write. */ void autr_write_file(struct module_env* env, struct trust_anchor* tp); /** * Delete autr anchor, deletes the autr data but does not do * unlinking from trees, caller does that. * @param tp: trust point to delete. */ void autr_point_delete(struct trust_anchor* tp); /** * Perform autotrust processing. * @param env: qstate environment with the anchors structure. * @param ve: validator environment for verification of rrsigs. * @param tp: trust anchor to process. * @param dnskey_rrset: DNSKEY rrset probed (can be NULL if bad prime result). * allocated in a region. Has not been validated yet. * @param qstate: qstate with region. * @return false if trust anchor was revoked completely. * Otherwise logs errors to log, does not change return value. * On errors, likely the trust point has been unchanged. */ int autr_process_prime(struct module_env* env, struct val_env* ve, struct trust_anchor* tp, struct ub_packed_rrset_key* dnskey_rrset, struct module_qstate* qstate); /** * Debug printout of rfc5011 tracked anchors * @param anchors: all the anchors. */ void autr_debug_print(struct val_anchors* anchors); /** callback for query answer to 5011 probe */ void probe_answer_cb(void* arg, int rcode, struct sldns_buffer* buf, enum sec_status sec, char* errinf, int was_ratelimited); #endif /* VALIDATOR_AUTOTRUST_H */ unbound-1.25.1/validator/val_kentry.c0000644000175000017500000002633115203270263017222 0ustar wouterwouter/* * validator/val_kentry.c - validator key entry definition. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions for dealing with validator key entries. */ #include "config.h" #include "validator/val_kentry.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/storage/lookup3.h" #include "util/regional.h" #include "util/net_help.h" #include "sldns/rrdef.h" #include "sldns/keyraw.h" size_t key_entry_sizefunc(void* key, void* data) { struct key_entry_key* kk = (struct key_entry_key*)key; struct key_entry_data* kd = (struct key_entry_data*)data; size_t s = sizeof(*kk) + kk->namelen; s += sizeof(*kd) + lock_get_mem(&kk->entry.lock); if(kd->rrset_data) s += packed_rrset_sizeof(kd->rrset_data); if(kd->reason) s += strlen(kd->reason)+1; if(kd->algo) s += strlen((char*)kd->algo)+1; return s; } int key_entry_compfunc(void* k1, void* k2) { struct key_entry_key* n1 = (struct key_entry_key*)k1; struct key_entry_key* n2 = (struct key_entry_key*)k2; if(n1->key_class != n2->key_class) { if(n1->key_class < n2->key_class) return -1; return 1; } return query_dname_compare(n1->name, n2->name); } void key_entry_delkeyfunc(void* key, void* ATTR_UNUSED(userarg)) { struct key_entry_key* kk = (struct key_entry_key*)key; if(!key) return; lock_rw_destroy(&kk->entry.lock); free(kk->name); free(kk); } void key_entry_deldatafunc(void* data, void* ATTR_UNUSED(userarg)) { struct key_entry_data* kd = (struct key_entry_data*)data; free(kd->reason); free(kd->rrset_data); free(kd->algo); free(kd); } void key_entry_hash(struct key_entry_key* kk) { kk->entry.hash = 0x654; kk->entry.hash = hashlittle(&kk->key_class, sizeof(kk->key_class), kk->entry.hash); kk->entry.hash = dname_query_hash(kk->name, kk->entry.hash); } struct key_entry_key* key_entry_copy_toregion(struct key_entry_key* kkey, struct regional* region) { struct key_entry_key* newk; newk = regional_alloc_init(region, kkey, sizeof(*kkey)); if(!newk) return NULL; newk->name = regional_alloc_init(region, kkey->name, kkey->namelen); if(!newk->name) return NULL; newk->entry.key = newk; if(newk->entry.data) { /* copy data element */ struct key_entry_data *d = (struct key_entry_data*) kkey->entry.data; struct key_entry_data *newd; newd = regional_alloc_init(region, d, sizeof(*d)); if(!newd) return NULL; /* copy rrset */ if(d->rrset_data) { newd->rrset_data = regional_alloc_init(region, d->rrset_data, packed_rrset_sizeof(d->rrset_data)); if(!newd->rrset_data) return NULL; packed_rrset_ptr_fixup(newd->rrset_data); } if(d->reason) { newd->reason = regional_strdup(region, d->reason); if(!newd->reason) return NULL; } if(d->algo) { newd->algo = (uint8_t*)regional_strdup(region, (char*)d->algo); if(!newd->algo) return NULL; } newk->entry.data = newd; } return newk; } struct key_entry_key* key_entry_copy(struct key_entry_key* kkey, int copy_reason) { struct key_entry_key* newk; if(!kkey) return NULL; newk = memdup(kkey, sizeof(*kkey)); if(!newk) return NULL; newk->name = memdup(kkey->name, kkey->namelen); if(!newk->name) { free(newk); return NULL; } lock_rw_init(&newk->entry.lock); newk->entry.key = newk; if(newk->entry.data) { /* copy data element */ struct key_entry_data *d = (struct key_entry_data*) kkey->entry.data; struct key_entry_data *newd; newd = memdup(d, sizeof(*d)); if(!newd) { free(newk->name); free(newk); return NULL; } /* copy rrset */ if(d->rrset_data) { newd->rrset_data = memdup(d->rrset_data, packed_rrset_sizeof(d->rrset_data)); if(!newd->rrset_data) { free(newd); free(newk->name); free(newk); return NULL; } packed_rrset_ptr_fixup(newd->rrset_data); } if(copy_reason && d->reason && *d->reason != 0) { newd->reason = strdup(d->reason); if(!newd->reason) { free(newd->rrset_data); free(newd); free(newk->name); free(newk); return NULL; } } else { newd->reason = NULL; } if(d->algo) { newd->algo = (uint8_t*)strdup((char*)d->algo); if(!newd->algo) { free(newd->rrset_data); free(newd->reason); free(newd); free(newk->name); free(newk); return NULL; } } newk->entry.data = newd; } return newk; } int key_entry_isnull(struct key_entry_key* kkey) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; return (!d->isbad && d->rrset_data == NULL); } int key_entry_isgood(struct key_entry_key* kkey) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; return (!d->isbad && d->rrset_data != NULL); } int key_entry_isbad(struct key_entry_key* kkey) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; return (int)(d->isbad); } char* key_entry_get_reason(struct key_entry_key* kkey) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; return d->reason; } sldns_ede_code key_entry_get_reason_bogus(struct key_entry_key* kkey) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; return d->reason_bogus; } /** setup key entry in region */ static int key_entry_setup(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, struct key_entry_key** k, struct key_entry_data** d) { *k = regional_alloc(region, sizeof(**k)); if(!*k) return 0; memset(*k, 0, sizeof(**k)); (*k)->entry.key = *k; (*k)->name = regional_alloc_init(region, name, namelen); if(!(*k)->name) return 0; (*k)->namelen = namelen; (*k)->key_class = dclass; *d = regional_alloc(region, sizeof(**d)); if(!*d) return 0; (*k)->entry.data = *d; return 1; } struct key_entry_key* key_entry_create_null(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl, sldns_ede_code reason_bogus, const char* reason, time_t now) { struct key_entry_key* k; struct key_entry_data* d; if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) return NULL; d->ttl = now + ttl; d->isbad = 0; d->reason = (!reason || *reason == 0) ?NULL :(char*)regional_strdup(region, reason); /* On allocation error we don't store the reason string */ d->reason_bogus = reason_bogus; d->rrset_type = LDNS_RR_TYPE_DNSKEY; d->rrset_data = NULL; d->algo = NULL; return k; } struct key_entry_key* key_entry_create_rrset(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, struct ub_packed_rrset_key* rrset, uint8_t* sigalg, sldns_ede_code reason_bogus, const char* reason, time_t now) { struct key_entry_key* k; struct key_entry_data* d; struct packed_rrset_data* rd = (struct packed_rrset_data*) rrset->entry.data; if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) return NULL; d->ttl = rd->ttl + now; d->isbad = 0; d->reason = (!reason || *reason == 0) ?NULL :(char*)regional_strdup(region, reason); /* On allocation error we don't store the reason string */ d->reason_bogus = reason_bogus; d->rrset_type = ntohs(rrset->rk.type); d->rrset_data = (struct packed_rrset_data*)regional_alloc_init(region, rd, packed_rrset_sizeof(rd)); if(!d->rrset_data) return NULL; if(sigalg) { d->algo = (uint8_t*)regional_strdup(region, (char*)sigalg); if(!d->algo) return NULL; } else d->algo = NULL; packed_rrset_ptr_fixup(d->rrset_data); return k; } struct key_entry_key* key_entry_create_bad(struct regional* region, uint8_t* name, size_t namelen, uint16_t dclass, time_t ttl, sldns_ede_code reason_bogus, const char* reason, time_t now) { struct key_entry_key* k; struct key_entry_data* d; if(!key_entry_setup(region, name, namelen, dclass, &k, &d)) return NULL; d->ttl = now + ttl; d->isbad = 1; d->reason = (!reason || *reason == 0) ?NULL :(char*)regional_strdup(region, reason); /* On allocation error we don't store the reason string */ d->reason_bogus = reason_bogus; d->rrset_type = LDNS_RR_TYPE_DNSKEY; d->rrset_data = NULL; d->algo = NULL; return k; } struct ub_packed_rrset_key* key_entry_get_rrset(struct key_entry_key* kkey, struct regional* region) { struct key_entry_data* d = (struct key_entry_data*)kkey->entry.data; struct ub_packed_rrset_key* rrk; struct packed_rrset_data* rrd; if(!d || !d->rrset_data) return NULL; rrk = regional_alloc(region, sizeof(*rrk)); if(!rrk) return NULL; memset(rrk, 0, sizeof(*rrk)); rrk->rk.dname = regional_alloc_init(region, kkey->name, kkey->namelen); if(!rrk->rk.dname) return NULL; rrk->rk.dname_len = kkey->namelen; rrk->rk.type = htons(d->rrset_type); rrk->rk.rrset_class = htons(kkey->key_class); rrk->entry.key = rrk; rrd = regional_alloc_init(region, d->rrset_data, packed_rrset_sizeof(d->rrset_data)); if(!rrd) return NULL; rrk->entry.data = rrd; packed_rrset_ptr_fixup(rrd); return rrk; } /** Get size of key in keyset */ static size_t dnskey_get_keysize(struct packed_rrset_data* data, size_t idx) { unsigned char* pk; unsigned int pklen = 0; int algo; if(data->rr_len[idx] < 2+5) return 0; algo = (int)data->rr_data[idx][2+3]; pk = (unsigned char*)data->rr_data[idx]+2+4; pklen = (unsigned)data->rr_len[idx]-2-4; return sldns_rr_dnskey_key_size_raw(pk, pklen, algo); } /** get dnskey flags from data */ static uint16_t kd_get_flags(struct packed_rrset_data* data, size_t idx) { uint16_t f; if(data->rr_len[idx] < 2+2) return 0; memmove(&f, data->rr_data[idx]+2, 2); f = ntohs(f); return f; } size_t key_entry_keysize(struct key_entry_key* kkey) { struct packed_rrset_data* d; /* compute size of smallest ZSK key in the rrset */ size_t i; size_t bits = 0; if(!key_entry_isgood(kkey)) return 0; d = ((struct key_entry_data*)kkey->entry.data)->rrset_data; for(i=0; icount; i++) { if(!(kd_get_flags(d, i) & DNSKEY_BIT_ZSK)) continue; if(i==0 || dnskey_get_keysize(d, i) < bits) bits = dnskey_get_keysize(d, i); } return bits; } unbound-1.25.1/validator/val_sigcrypt.h0000644000175000017500000003211615203270263017555 0ustar wouterwouter/* * validator/val_sigcrypt.h - validator signature crypto functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains helper functions for the validator module. * The functions help with signature verification and checking, the * bridging between RR wireformat data and crypto calls. */ #ifndef VALIDATOR_VAL_SIGCRYPT_H #define VALIDATOR_VAL_SIGCRYPT_H #include "util/data/packed_rrset.h" #include "sldns/pkthdr.h" #include "sldns/rrdef.h" struct val_env; struct module_env; struct module_qstate; struct ub_packed_rrset_key; struct rbtree_type; struct regional; struct sldns_buffer; /** number of entries in algorithm needs array */ #define ALGO_NEEDS_MAX 256 /** * Storage for algorithm needs. DNSKEY algorithms. */ struct algo_needs { /** the algorithms (8-bit) with each a number. * 0: not marked. * 1: marked 'necessary but not yet fulfilled' * 2: marked bogus. * Indexed by algorithm number. */ uint8_t needs[ALGO_NEEDS_MAX]; /** the number of entries in the array that are unfulfilled */ size_t num; }; /** * Initialize algo needs structure, set algos from rrset as needed. * Results are added to an existing need structure. * @param n: struct with storage. * @param dnskey: algos from this struct set as necessary. DNSKEY set. * @param sigalg: adds to signalled algorithm list too. */ void algo_needs_init_dnskey_add(struct algo_needs* n, struct ub_packed_rrset_key* dnskey, uint8_t* sigalg); /** * Initialize algo needs structure from a signalled algo list. * @param n: struct with storage. * @param sigalg: signalled algorithm list, numbers ends with 0. */ void algo_needs_init_list(struct algo_needs* n, uint8_t* sigalg); /** * Initialize algo needs structure, set algos from rrset as needed. * @param n: struct with storage. * @param ds: algos from this struct set as necessary. DS set. * @param fav_ds_algo: filter to use only this DS algo. * @param sigalg: list of signalled algos, constructed as output, * provide size ALGO_NEEDS_MAX+1. list of algonumbers, ends with a zero. */ void algo_needs_init_ds(struct algo_needs* n, struct ub_packed_rrset_key* ds, int fav_ds_algo, uint8_t* sigalg); /** * Mark this algorithm as a success, sec_secure, and see if we are done. * @param n: storage structure processed. * @param algo: the algorithm processed to be secure. * @return if true, processing has finished successfully, we are satisfied. */ int algo_needs_set_secure(struct algo_needs* n, uint8_t algo); /** * Mark this algorithm a failure, sec_bogus. It can later be overridden * by a success for this algorithm (with a different signature). * @param n: storage structure processed. * @param algo: the algorithm processed to be bogus. */ void algo_needs_set_bogus(struct algo_needs* n, uint8_t algo); /** * See how many algorithms are missing (not bogus or secure, but not processed) * @param n: storage structure processed. * @return number of algorithms missing after processing. */ size_t algo_needs_num_missing(struct algo_needs* n); /** * See which algo is missing. * @param n: struct after processing. * @return if 0 an algorithm was bogus, if a number, this algorithm was * missing. So on 0, report why that was bogus, on number report a missing * algorithm. There could be multiple missing, this reports the first one. */ int algo_needs_missing(struct algo_needs* n); /** * Format error reason for algorithm missing. * @param alg: DNSKEY-algorithm missing. * @param reason: destination. * @param s: string, appended with 'with algorithm ..'. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. */ void algo_needs_reason(int alg, char** reason, char* s, char* reasonbuf, size_t reasonlen); /** * Check if dnskey matches a DS digest * Does not check dnskey-keyid footprint, just the digest. * @param env: module environment. Uses scratch space. * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @return true if it matches, false on error, not supported or no match. */ int ds_digest_match_dnskey(struct module_env* env, struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx, struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); /** * Get dnskey keytag, footprint value * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @return the keytag or 0 for badly formatted DNSKEYs. */ uint16_t dnskey_calc_keytag(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx); /** * Get DS keytag, footprint value that matches the DNSKEY keytag it signs. * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @return the keytag or 0 for badly formatted DSs. */ uint16_t ds_get_keytag(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); /** * See if DNSKEY algorithm is supported * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @return true if supported. */ int dnskey_algo_is_supported(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx); /** * See if the DNSKEY size at that algorithm is supported. * @param dnskey_rrset: DNSKEY rrset. * @param dnskey_idx: index of RR in rrset. * @return true if supported. */ int dnskey_size_is_supported(struct ub_packed_rrset_key* dnskey_rrset, size_t dnskey_idx); /** * See if the DNSKEY size at that algorithm is supported for all the * RRs in the DNSKEY RRset. * @param dnskey_rrset: DNSKEY rrset. * @return true if supported. */ int dnskeyset_size_is_supported(struct ub_packed_rrset_key* dnskey_rrset); /** * See if DS digest algorithm is supported * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @return true if supported. */ int ds_digest_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); /** * Get DS RR digest algorithm * @param ds_rrset: DS rrset. * @param ds_idx: which DS. * @return algorithm or 0 if DS too short. */ int ds_get_digest_algo(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); /** * See if DS key algorithm is supported * @param ds_rrset: DS rrset * @param ds_idx: index of RR in DS rrset. * @return true if supported. */ int ds_key_algo_is_supported(struct ub_packed_rrset_key* ds_rrset, size_t ds_idx); /** * Get DS RR key algorithm. This value should match with the DNSKEY algo. * @param k: DS rrset. * @param idx: which DS. * @return algorithm or 0 if DS too short. */ int ds_get_key_algo(struct ub_packed_rrset_key* k, size_t idx); /** * Get DNSKEY RR signature algorithm * @param k: DNSKEY rrset. * @param idx: which DNSKEY RR. * @return algorithm or 0 if DNSKEY too short. */ int dnskey_get_algo(struct ub_packed_rrset_key* k, size_t idx); /** * Get DNSKEY RR flags * @param k: DNSKEY rrset. * @param idx: which DNSKEY RR. * @return flags or 0 if DNSKEY too short. */ uint16_t dnskey_get_flags(struct ub_packed_rrset_key* k, size_t idx); /** * Verify rrset against dnskey rrset. * @param env: module environment, scratch space is used. * @param ve: validator environment, date settings. * @param rrset: to be validated. * @param dnskey: DNSKEY rrset, keyset to try. * @param sigalg: if nonNULL provide downgrade protection otherwise one * algorithm is enough. * @param reason: if bogus, a string returned, fixed or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @param verified: if not NULL the number of RRSIG validations is returned. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return SECURE if one key in the set verifies one rrsig. * UNCHECKED on allocation errors, unsupported algorithms, malformed data, * and BOGUS on verification failures (no keys match any signatures). */ enum sec_status dnskeyset_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, uint8_t* sigalg, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate, int* verified, char* reasonbuf, size_t reasonlen); /** * verify rrset against one specific dnskey (from rrset) * @param env: module environment, scratch space is used. * @param ve: validator environment, date settings. * @param rrset: to be validated. * @param dnskey: DNSKEY rrset, keyset. * @param dnskey_idx: which key from the rrset to try. * @param reason: if bogus, a string returned, fixed or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @return secure if *this* key signs any of the signatures on rrset. * unchecked on error or and bogus on bad signature. */ enum sec_status dnskey_verify_rrset(struct module_env* env, struct val_env* ve, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate); /** * verify rrset, with specific dnskey(from set), for a specific rrsig * @param region: scratch region used for temporary allocation. * @param buf: scratch buffer used for canonicalized rrset data. * @param ve: validator environment, date settings. * @param now: current time for validation (can be overridden). * @param rrset: to be validated. * @param dnskey: DNSKEY rrset, keyset. * @param dnskey_idx: which key from the rrset to try. * @param sig_idx: which signature to try to validate. * @param sortree: pass NULL at start, the sorted rrset order is returned. * pass it again for the same rrset. * @param buf_canon: if true, the buffer is already canonical. * pass false at start. pass old value only for same rrset and same * signature (but perhaps different key) for reuse. * @param reason: if bogus, a string returned, fixed or alloced in scratch. * @param reason_bogus: EDE (RFC8914) code paired with the reason of failure. * @param section: section of packet where this rrset comes from. * @param qstate: qstate with region. * @return secure if this key signs this signature. unchecked on error or * bogus if it did not validate. */ enum sec_status dnskey_verify_rrset_sig(struct regional* region, struct sldns_buffer* buf, struct val_env* ve, time_t now, struct ub_packed_rrset_key* rrset, struct ub_packed_rrset_key* dnskey, size_t dnskey_idx, size_t sig_idx, struct rbtree_type** sortree, int* buf_canon, char** reason, sldns_ede_code *reason_bogus, sldns_pkt_section section, struct module_qstate* qstate); /** * canonical compare for two tree entries */ int canonical_tree_compare(const void* k1, const void* k2); /** * Compare two rrsets and see if they are the same, canonicalised. * The rrsets are not altered. * @param region: temporary region. * @param k1: rrset1 * @param k2: rrset2 * @return true if equal. */ int rrset_canonical_equal(struct regional* region, struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2); /** * Canonicalize an rrset into the buffer. For an auth zone record, so * this does not use a signature, or the RRSIG TTL or the wildcard label * count from the RRSIG. * @param region: temporary region. * @param buf: the buffer to use. * @param k: the rrset to insert. * @return false on alloc error. */ int rrset_canonicalize_to_buffer(struct regional* region, struct sldns_buffer* buf, struct ub_packed_rrset_key* k); #endif /* VALIDATOR_VAL_SIGCRYPT_H */ unbound-1.25.1/services/0000755000175000017500000000000015203270263014531 5ustar wouterwouterunbound-1.25.1/services/modstack.c0000644000175000017500000002200615203270263016502 0ustar wouterwouter/* * services/modstack.c - stack of modules * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to help maintain a stack of modules. */ #include "config.h" #include #include "services/modstack.h" #include "util/module.h" #include "util/fptr_wlist.h" #include "dns64/dns64.h" #include "iterator/iterator.h" #include "validator/validator.h" #include "respip/respip.h" #ifdef WITH_PYTHONMODULE #include "pythonmod/pythonmod.h" #endif #ifdef WITH_DYNLIBMODULE #include "dynlibmod/dynlibmod.h" #endif #ifdef USE_CACHEDB #include "cachedb/cachedb.h" #endif #ifdef USE_IPSECMOD #include "ipsecmod/ipsecmod.h" #endif #ifdef CLIENT_SUBNET #include "edns-subnet/subnetmod.h" #endif #ifdef USE_IPSET #include "ipset/ipset.h" #endif /** count number of modules (words) in the string */ static int count_modules(const char* s) { int num = 0; if(!s) return 0; while(*s) { /* skip whitespace */ while(*s && isspace((unsigned char)*s)) s++; if(*s && !isspace((unsigned char)*s)) { /* skip identifier */ num++; while(*s && !isspace((unsigned char)*s)) s++; } } return num; } void modstack_init(struct module_stack* stack) { stack->num = 0; stack->mod = NULL; } void modstack_free(struct module_stack* stack) { if(!stack) return; stack->num = 0; free(stack->mod); stack->mod = NULL; } int modstack_config(struct module_stack* stack, const char* module_conf) { int i; verbose(VERB_QUERY, "module config: \"%s\"", module_conf); stack->num = count_modules(module_conf); if(stack->num == 0) { log_err("error: no modules specified"); return 0; } if(stack->num > MAX_MODULE) { log_err("error: too many modules (%d max %d)", stack->num, MAX_MODULE); return 0; } stack->mod = (struct module_func_block**)calloc((size_t) stack->num, sizeof(struct module_func_block*)); if(!stack->mod) { log_err("out of memory"); return 0; } for(i=0; inum; i++) { stack->mod[i] = module_factory(&module_conf); if(!stack->mod[i]) { char md[256]; char * s = md; snprintf(md, sizeof(md), "%s", module_conf); /* Leading spaces are present on errors. */ while (*s && isspace((unsigned char)*s)) s++; if(strchr(s, ' ')) *(strchr(s, ' ')) = 0; if(strchr(s, '\t')) *(strchr(s, '\t')) = 0; log_err("Unknown value in module-config, module: '%s'." " This module is not present (not compiled in);" " see the list of linked modules with unbound -V", s); return 0; } } return 1; } /** The list of module names */ const char** module_list_avail(void) { /* these are the modules available */ static const char* names[] = { "dns64", #ifdef WITH_PYTHONMODULE "python", #endif #ifdef WITH_DYNLIBMODULE "dynlib", #endif #ifdef USE_CACHEDB "cachedb", #endif #ifdef USE_IPSECMOD "ipsecmod", #endif #ifdef CLIENT_SUBNET "subnetcache", #endif #ifdef USE_IPSET "ipset", #endif "respip", "validator", "iterator", NULL}; return names; } /** func block get function type */ typedef struct module_func_block* (*fbgetfunctype)(void); /** The list of module func blocks */ static fbgetfunctype* module_funcs_avail(void) { static struct module_func_block* (*fb[])(void) = { &dns64_get_funcblock, #ifdef WITH_PYTHONMODULE &pythonmod_get_funcblock, #endif #ifdef WITH_DYNLIBMODULE &dynlibmod_get_funcblock, #endif #ifdef USE_CACHEDB &cachedb_get_funcblock, #endif #ifdef USE_IPSECMOD &ipsecmod_get_funcblock, #endif #ifdef CLIENT_SUBNET &subnetmod_get_funcblock, #endif #ifdef USE_IPSET &ipset_get_funcblock, #endif &respip_get_funcblock, &val_get_funcblock, &iter_get_funcblock, NULL}; return fb; } struct module_func_block* module_factory(const char** str) { int i = 0; const char* s = *str; const char** names = module_list_avail(); fbgetfunctype* fb = module_funcs_avail(); while(*s && isspace((unsigned char)*s)) s++; while(names[i]) { if(strncmp(names[i], s, strlen(names[i])) == 0) { s += strlen(names[i]); *str = s; return (*fb[i])(); } i++; } return NULL; } int modstack_call_startup(struct module_stack* stack, const char* module_conf, struct module_env* env) { int i; if(stack->num != 0) fatal_exit("unexpected already initialised modules"); /* fixed setup of the modules */ if(!modstack_config(stack, module_conf)) { return 0; } for(i=0; inum; i++) { if(stack->mod[i]->startup == NULL) continue; verbose(VERB_OPS, "startup module %d: %s", i, stack->mod[i]->name); fptr_ok(fptr_whitelist_mod_startup(stack->mod[i]->startup)); if(!(*stack->mod[i]->startup)(env, i)) { log_err("module startup for module %s failed", stack->mod[i]->name); return 0; } } return 1; } int modstack_call_init(struct module_stack* stack, const char* module_conf, struct module_env* env) { const char* orig_module_conf = module_conf; int i, changed = 0; env->need_to_validate = 0; /* set by module init below */ for(i=0; inum; i++) { while(*module_conf && isspace((unsigned char)*module_conf)) module_conf++; if(strncmp(stack->mod[i]->name, module_conf, strlen(stack->mod[i]->name))) { if(stack->mod[i]->startup || stack->mod[i]->destartup) { log_err("changed module ordering during reload not supported, for module that needs startup"); return 0; } else { changed = 1; } } /* Skip this module name in module_conf. */ while(*module_conf && !isspace((unsigned char)*module_conf)) module_conf++; } if(changed) { modstack_free(stack); if(!modstack_config(stack, orig_module_conf)) { return 0; } } for(i=0; inum; i++) { verbose(VERB_OPS, "init module %d: %s", i, stack->mod[i]->name); fptr_ok(fptr_whitelist_mod_init(stack->mod[i]->init)); if(!(*stack->mod[i]->init)(env, i)) { log_err("module init for module %s failed", stack->mod[i]->name); return 0; } } return 1; } void modstack_call_deinit(struct module_stack* stack, struct module_env* env) { int i; for(i=0; inum; i++) { fptr_ok(fptr_whitelist_mod_deinit(stack->mod[i]->deinit)); (*stack->mod[i]->deinit)(env, i); } } void modstack_call_destartup(struct module_stack* stack, struct module_env* env) { int i; for(i=0; inum; i++) { if(stack->mod[i]->destartup == NULL) continue; fptr_ok(fptr_whitelist_mod_destartup(stack->mod[i]->destartup)); (*stack->mod[i]->destartup)(env, i); } } int modstack_find(struct module_stack* stack, const char* name) { int i; for(i=0; inum; i++) { if(strcmp(stack->mod[i]->name, name) == 0) return i; } return -1; } size_t mod_get_mem(struct module_env* env, const char* name) { int m = modstack_find(&env->mesh->mods, name); if(m != -1) { fptr_ok(fptr_whitelist_mod_get_mem(env->mesh-> mods.mod[m]->get_mem)); return (*env->mesh->mods.mod[m]->get_mem)(env, m); } return 0; } unbound-1.25.1/services/view.h0000644000175000017500000001113015203270263015650 0ustar wouterwouter/* * services/view.h - named views containing local zones authority service. * * Copyright (c) 2016, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable named views that can hold local zone * authority service. */ #ifndef SERVICES_VIEW_H #define SERVICES_VIEW_H #include "util/rbtree.h" #include "util/locks.h" struct regional; struct config_file; struct config_view; struct respip_set; /** * Views storage, shared. */ struct views { /** lock on the view tree. When locking order, the views lock * is before the forwards,hints,anchors,localzones lock. */ lock_rw_type lock; /** rbtree of struct view */ rbtree_type vtree; }; /** * View. Named structure holding local authority zones. */ struct view { /** rbtree node, key is name */ rbnode_type node; /** view name. * Has to be right after rbnode_t due to pointer arithmetic in * view_create's lock protect */ char* name; /** view specific local authority zones */ struct local_zones* local_zones; /** response-ip configuration data for this view */ struct respip_set* respip_set; /** Fallback to global local_zones when there is no match in the view * specific tree. 1 for yes, 0 for no */ int isfirst; /** lock on the data in the structure * For the node and name you need to also hold the views_tree lock to * change them. */ lock_rw_type lock; }; /** * Create views storage * @return new struct or NULL on error. */ struct views* views_create(void); /** * Delete views storage * @param v: views to delete. */ void views_delete(struct views* v); /** * Apply config settings; * Takes care of locking. * @param v: view is set up. * @param cfg: config data. * @return false on error. */ int views_apply_cfg(struct views* v, struct config_file* cfg); /** * Compare two view entries in rbtree. Sort canonical. * @param v1: view 1 * @param v2: view 2 * @return: negative, positive or 0 comparison value. */ int view_cmp(const void* v1, const void* v2); /** * Delete one view * @param v: view to delete. */ void view_delete(struct view* v); /** * Debug helper. Print all views * Takes care of locking. * @param v: the views tree */ void views_print(struct views* v); /** * Find a view by name. * @param vs: views * @param name: name of the view we are looking for * @param write: 1 for obtaining write lock on found view, 0 for read lock * @return: locked view or NULL. */ struct view* views_find_view(struct views* vs, const char* name, int write); /** * Calculate memory usage of views. * @param vs: the views tree. The routine locks and unlocks the structure * for reading. * @return memory in bytes. */ size_t views_get_mem(struct views* vs); /** * Calculate memory usage of view. * @param v: the view. The routine locks and unlocks the structure for reading. * @return memory in bytes. */ size_t view_get_mem(struct view* v); /** * Swap internal tree with preallocated entries. Caller should manage * the locks. * @param vs: views tree * @param data: preallocated information. */ void views_swap_tree(struct views* vs, struct views* data); #endif /* SERVICES_VIEW_H */ unbound-1.25.1/services/localzone.c0000644000175000017500000020301115203270263016660 0ustar wouterwouter/* * services/localzone.c - local zones authority service. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable local zone authority service. */ #include "config.h" #include "services/localzone.h" #include "sldns/str2wire.h" #include "util/regional.h" #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/netevent.h" #include "util/data/msgreply.h" #include "util/data/msgparse.h" #include "util/as112.h" /* maximum RRs in an RRset, to cap possible 'endless' list RRs. * with 16 bytes for an A record, a 64K packet has about 4000 max */ #define LOCALZONE_RRSET_COUNT_MAX 4096 static const char* default_zones_reverse_array[] = { "127.in-addr.arpa.", /* reverse ip4 zone */ "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.", /* reverse ip6 zone */ 0 }; const char** local_zones_default_reverse = default_zones_reverse_array; static const char* default_zones_special_array[] = { "test.", /* RFC 6761 */ "invalid.", /* RFC 6761 */ "onion.", /* RFC 7686 */ "home.arpa.", /* RFC 8375 */ "resolver.arpa.", /* RFC 9462 */ "service.arpa.", /* RFC 9665 */ 0 }; const char** local_zones_default_special = default_zones_special_array; /** print all RRsets in local zone */ static void local_zone_out(struct local_zone* z) { struct local_data* d; struct local_rrset* p; RBTREE_FOR(d, struct local_data*, &z->data) { for(p = d->rrsets; p; p = p->next) { log_nametypeclass(NO_VERBOSE, "rrset", d->name, ntohs(p->rrset->rk.type), ntohs(p->rrset->rk.rrset_class)); } } } static void local_zone_print(struct local_zone* z) { char buf[64]; lock_rw_rdlock(&z->lock); snprintf(buf, sizeof(buf), "%s zone", local_zone_type2str(z->type)); log_nametypeclass(NO_VERBOSE, buf, z->name, 0, z->dclass); local_zone_out(z); lock_rw_unlock(&z->lock); } void local_zones_print(struct local_zones* zones) { struct local_zone* z; lock_rw_rdlock(&zones->lock); log_info("number of auth zones %u", (unsigned)zones->ztree.count); RBTREE_FOR(z, struct local_zone*, &zones->ztree) { local_zone_print(z); } lock_rw_unlock(&zones->lock); } struct local_zones* local_zones_create(void) { struct local_zones* zones = (struct local_zones*)calloc(1, sizeof(*zones)); if(!zones) return NULL; rbtree_init(&zones->ztree, &local_zone_cmp); lock_rw_init(&zones->lock); lock_protect(&zones->lock, &zones->ztree, sizeof(zones->ztree)); /* also lock protects the rbnode's in struct local_zone */ return zones; } /** helper traverse to delete zones */ static void lzdel(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct local_zone* z = (struct local_zone*)n->key; local_zone_delete(z); } void local_zones_delete(struct local_zones* zones) { if(!zones) return; lock_rw_destroy(&zones->lock); /* walk through zones and delete them all */ traverse_postorder(&zones->ztree, lzdel, NULL); free(zones); } void local_zone_delete(struct local_zone* z) { if(!z) return; lock_rw_destroy(&z->lock); regional_destroy(z->region); free(z->name); free(z->taglist); free(z); } int local_zone_cmp(const void* z1, const void* z2) { /* first sort on class, so that hierarchy can be maintained within * a class */ struct local_zone* a = (struct local_zone*)z1; struct local_zone* b = (struct local_zone*)z2; int m; if(a->dclass != b->dclass) { if(a->dclass < b->dclass) return -1; return 1; } return dname_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } int local_data_cmp(const void* d1, const void* d2) { struct local_data* a = (struct local_data*)d1; struct local_data* b = (struct local_data*)d2; int m; return dname_canon_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } /* form wireformat from text format domain name */ int parse_dname(const char* str, uint8_t** res, size_t* len, int* labs) { *res = sldns_str2wire_dname(str, len); *labs = 0; if(!*res) { log_err("cannot parse name %s", str); return 0; } *labs = dname_count_size_labels(*res, len); return 1; } /** create a new localzone */ static struct local_zone* local_zone_create(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) { return NULL; } z->node.key = z; z->dclass = dclass; z->type = t; z->name = nm; z->namelen = len; z->namelabs = labs; lock_rw_init(&z->lock); z->region = regional_create_nochunk(sizeof(struct regional)); if(!z->region) { free(z); return NULL; } rbtree_init(&z->data, &local_data_cmp); lock_protect(&z->lock, &z->parent, sizeof(*z)-sizeof(rbnode_type)); /* also the zones->lock protects node, parent, name*, class */ 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(nm, len, labs, t, c); if(!z) { free(nm); log_err("out of memory"); return NULL; } /* add to rbtree */ lock_rw_wrlock(&zones->lock); lock_rw_wrlock(&z->lock); if(!rbtree_insert(&zones->ztree, &z->node)) { struct local_zone* oldz; char str[LDNS_MAX_DOMAINLEN]; dname_str(nm, str); log_warn("duplicate local-zone %s", str); lock_rw_unlock(&z->lock); /* save zone name locally before deallocation, * otherwise, nm is gone if we zone_delete now. */ oldz = z; /* find the correct zone, so not an error for duplicate */ z = local_zones_find(zones, nm, len, labs, c); lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); local_zone_delete(oldz); return z; } lock_rw_unlock(&zones->lock); return z; } /** enter a new zone */ struct local_zone* lz_enter_zone(struct local_zones* zones, const char* name, const char* type, uint16_t dclass) { struct local_zone* z; enum localzone_type t; uint8_t* nm; size_t len; int labs; if(!parse_dname(name, &nm, &len, &labs)) { log_err("bad zone name %s %s", name, type); return NULL; } if(!local_zone_str2type(type, &t)) { log_err("bad lz_enter_zone type %s %s", name, type); free(nm); return NULL; } if(!(z=lz_enter_zone_dname(zones, nm, len, labs, t, dclass))) { log_err("could not enter zone %s %s", name, type); return NULL; } return z; } int rrstr_get_rr_content(const char* str, uint8_t** nm, uint16_t* type, uint16_t* dclass, time_t* ttl, uint8_t* rr, size_t len, uint8_t** rdata, size_t* rdata_len) { size_t dname_len = 0; int e = sldns_str2wire_rr_buf(str, rr, &len, &dname_len, 3600, NULL, 0, NULL, 0); if(e) { log_err("error parsing local-data at %d: '%s': %s", LDNS_WIREPARSE_OFFSET(e), str, sldns_get_errorstr_parse(e)); return 0; } *nm = memdup(rr, dname_len); if(!*nm) { log_err("out of memory"); return 0; } *dclass = sldns_wirerr_get_class(rr, len, dname_len); *type = sldns_wirerr_get_type(rr, len, dname_len); *ttl = (time_t)sldns_wirerr_get_ttl(rr, len, dname_len); *rdata = sldns_wirerr_get_rdatawl(rr, len, dname_len); *rdata_len = sldns_wirerr_get_rdatalen(rr, len, dname_len)+2; return 1; } /** return name and class of rr; parses string */ static int get_rr_nameclass(const char* str, uint8_t** nm, uint16_t* dclass, uint16_t* dtype) { uint8_t rr[LDNS_RR_BUF_SIZE]; size_t len = sizeof(rr), dname_len = 0; int s = sldns_str2wire_rr_buf(str, rr, &len, &dname_len, 3600, NULL, 0, NULL, 0); if(s != 0) { log_err("error parsing local-data at %d '%s': %s", LDNS_WIREPARSE_OFFSET(s), str, sldns_get_errorstr_parse(s)); return 0; } *nm = memdup(rr, dname_len); *dclass = sldns_wirerr_get_class(rr, len, dname_len); *dtype = sldns_wirerr_get_type(rr, len, dname_len); if(!*nm) { log_err("out of memory"); return 0; } return 1; } /** * 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, int alias_ok) { struct local_rrset* p, *cname = NULL; 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)) cname = p; } if(alias_ok) return cname; return NULL; } /** check for RR duplicates */ static int rr_is_duplicate(struct packed_rrset_data* pd, uint8_t* rdata, size_t rdata_len) { size_t i; for(i=0; icount; i++) { if(pd->rr_len[i] == rdata_len && memcmp(pd->rr_data[i], rdata, rdata_len) == 0) return 1; } return 0; } /** new local_rrset */ static struct local_rrset* new_local_rrset(struct regional* region, struct local_data* node, uint16_t rrtype, uint16_t rrclass) { struct packed_rrset_data* pd; struct local_rrset* rrset = (struct local_rrset*) regional_alloc_zero(region, sizeof(*rrset)); if(!rrset) { log_err("out of memory"); return NULL; } rrset->next = node->rrsets; node->rrsets = rrset; rrset->rrset = (struct ub_packed_rrset_key*) regional_alloc_zero(region, sizeof(*rrset->rrset)); if(!rrset->rrset) { log_err("out of memory"); return NULL; } rrset->rrset->entry.key = rrset->rrset; pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); if(!pd) { log_err("out of memory"); return NULL; } pd->trust = rrset_trust_prim_noglue; pd->security = sec_status_insecure; rrset->rrset->entry.data = pd; rrset->rrset->rk.dname = node->name; rrset->rrset->rk.dname_len = node->namelen; rrset->rrset->rk.type = htons(rrtype); rrset->rrset->rk.rrset_class = htons(rrclass); return rrset; } /** insert RR into RRset data structure; Wastes a couple of bytes */ int rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr) { size_t* oldlen = pd->rr_len; time_t* oldttl = pd->rr_ttl; uint8_t** olddata = pd->rr_data; /* add RR to rrset */ if(pd->count > LOCALZONE_RRSET_COUNT_MAX) { log_warn("RRset '%s' has more than %d records, record ignored", rrstr, LOCALZONE_RRSET_COUNT_MAX); return 1; } pd->count++; pd->rr_len = regional_alloc(region, sizeof(*pd->rr_len)*pd->count); pd->rr_ttl = regional_alloc(region, sizeof(*pd->rr_ttl)*pd->count); pd->rr_data = regional_alloc(region, sizeof(*pd->rr_data)*pd->count); if(!pd->rr_len || !pd->rr_ttl || !pd->rr_data) { log_err("out of memory"); return 0; } if(pd->count > 1) { memcpy(pd->rr_len+1, oldlen, sizeof(*pd->rr_len)*(pd->count-1)); memcpy(pd->rr_ttl+1, oldttl, sizeof(*pd->rr_ttl)*(pd->count-1)); memcpy(pd->rr_data+1, olddata, sizeof(*pd->rr_data)*(pd->count-1)); } pd->rr_len[0] = rdata_len; pd->rr_ttl[0] = ttl; pd->rr_data[0] = regional_alloc_init(region, rdata, rdata_len); if(!pd->rr_data[0]) { log_err("out of memory"); return 0; } return 1; } /** Delete RR from local-zone RRset, wastes memory as the deleted RRs cannot be * free'd (regionally alloc'd) */ int local_rrset_remove_rr(struct packed_rrset_data* pd, size_t index) { log_assert(pd->count > 0); if(index >= pd->count) { log_warn("Trying to remove RR with out of bound index"); return 0; } if(index + 1 < pd->count) { /* not removing last element */ size_t nexti = index + 1; size_t num = pd->count - nexti; memmove(pd->rr_len+index, pd->rr_len+nexti, sizeof(*pd->rr_len)*num); memmove(pd->rr_ttl+index, pd->rr_ttl+nexti, sizeof(*pd->rr_ttl)*num); memmove(pd->rr_data+index, pd->rr_data+nexti, sizeof(*pd->rr_data)*num); } pd->count--; return 1; } struct local_data* local_zone_find_data(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* ld = local_zone_find_data(z, nm, nmlen, nmlabs); 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; } /* Mark the SOA record for the zone. This only marks the SOA rrset; the data * for the RR is entered later on local_zone_enter_rr() as with the other * records. An artificial soa_negative record with a modified TTL (minimum of * the TTL and the SOA.MINIMUM) is also created and marked for usage with * negative answers and to avoid allocations during those answers. */ static int lz_mark_soa_for_zone(struct local_zone* z, struct ub_packed_rrset_key* soa_rrset, uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr) { struct packed_rrset_data* pd = (struct packed_rrset_data*) regional_alloc_zero(z->region, sizeof(*pd)); struct ub_packed_rrset_key* rrset_negative = (struct ub_packed_rrset_key*) regional_alloc_zero(z->region, sizeof(*rrset_negative)); time_t minimum; if(!rrset_negative||!pd) { log_err("out of memory"); return 0; } /* Mark the original SOA record and then continue with the negative one. */ z->soa = soa_rrset; rrset_negative->entry.key = rrset_negative; pd->trust = rrset_trust_prim_noglue; pd->security = sec_status_insecure; rrset_negative->entry.data = pd; rrset_negative->rk.dname = soa_rrset->rk.dname; rrset_negative->rk.dname_len = soa_rrset->rk.dname_len; rrset_negative->rk.type = soa_rrset->rk.type; rrset_negative->rk.rrset_class = soa_rrset->rk.rrset_class; if(!rrset_insert_rr(z->region, pd, rdata, rdata_len, ttl, rrstr)) return 0; /* last 4 bytes are minimum ttl in network format */ if(pd->count == 0 || pd->rr_len[0] < 2+4) return 0; minimum = (time_t)sldns_read_uint32(pd->rr_data[0]+(pd->rr_len[0]-4)); minimum = ttlttl = minimum; pd->rr_ttl[0] = minimum; z->soa_negative = rrset_negative; return 1; } int local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs, uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, size_t rdata_len, const char* rrstr) { struct local_data* node; struct local_rrset* rrset; struct packed_rrset_data* pd; if(!lz_find_create_node(z, nm, nmlen, nmlabs, &node)) { return 0; } log_assert(node); /* 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 || z->type == local_zone_inform_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) return 0; if(query_dname_compare(node->name, z->name) == 0) { if(rrtype == LDNS_RR_TYPE_NSEC) rrset->rrset->rk.flags = PACKED_RRSET_NSEC_AT_APEX; if(rrtype == LDNS_RR_TYPE_SOA && !lz_mark_soa_for_zone(z, rrset->rrset, rdata, rdata_len, ttl, rrstr)) return 0; } } pd = (struct packed_rrset_data*)rrset->rrset->entry.data; log_assert(rrset && pd); /* check for duplicate RR */ if(rr_is_duplicate(pd, rdata, rdata_len)) { verbose(VERB_ALGO, "ignoring duplicate RR: %s", rrstr); return 1; } return rrset_insert_rr(z->region, pd, rdata, rdata_len, ttl, rrstr); } /** enter data RR into auth zone */ static int lz_enter_rr_into_zone(struct local_zone* z, const char* rrstr) { uint8_t* nm; size_t nmlen; int nmlabs, ret; uint16_t rrtype = 0, rrclass = 0; time_t ttl = 0; uint8_t rr[LDNS_RR_BUF_SIZE]; uint8_t* rdata; size_t rdata_len; if(!rrstr_get_rr_content(rrstr, &nm, &rrtype, &rrclass, &ttl, rr, sizeof(rr), &rdata, &rdata_len)) { log_err("bad local-data: %s", rrstr); return 0; } log_assert(z->dclass == rrclass); if((z->type == local_zone_redirect || z->type == local_zone_inform_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); free(nm); return 0; } nmlabs = dname_count_size_labels(nm, &nmlen); ret = local_zone_enter_rr(z, nm, nmlen, nmlabs, rrtype, rrclass, ttl, rdata, rdata_len, rrstr); free(nm); return ret; } /** enter a data RR into auth data; a zone for it must exist */ static int lz_enter_rr_str(struct local_zones* zones, const char* rr) { uint8_t* rr_name; uint16_t rr_class, rr_type; size_t len; int labs; struct local_zone* z; int r; if(!get_rr_nameclass(rr, &rr_name, &rr_class, &rr_type)) { log_err("bad rr %s", rr); return 0; } labs = dname_count_size_labels(rr_name, &len); lock_rw_rdlock(&zones->lock); z = local_zones_lookup(zones, rr_name, len, labs, rr_class, rr_type, 1); if(!z) { lock_rw_unlock(&zones->lock); fatal_exit("internal error: no zone for rr %s", rr); } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); free(rr_name); r = lz_enter_rr_into_zone(z, rr); lock_rw_unlock(&z->lock); return r; } /** enter tagstring into zone */ static int lz_enter_zone_tag(struct local_zones* zones, char* zname, uint8_t* list, size_t len, uint16_t rr_class) { uint8_t dname[LDNS_MAX_DOMAINLEN+1]; size_t dname_len = sizeof(dname); int dname_labs, r = 0; struct local_zone* z; if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { log_err("cannot parse zone name in local-zone-tag: %s", zname); return 0; } dname_labs = dname_count_labels(dname); lock_rw_rdlock(&zones->lock); z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); if(!z) { lock_rw_unlock(&zones->lock); log_err("no local-zone for tag %s", zname); return 0; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); free(z->taglist); z->taglist = memdup(list, len); z->taglen = len; if(z->taglist) r = 1; lock_rw_unlock(&z->lock); return r; } /** enter override into zone */ static int lz_enter_override(struct local_zones* zones, char* zname, char* netblock, char* type, uint16_t rr_class) { uint8_t dname[LDNS_MAX_DOMAINLEN+1]; size_t dname_len = sizeof(dname); int dname_labs; struct sockaddr_storage addr; int net; socklen_t addrlen; struct local_zone* z; enum localzone_type t; /* parse zone name */ if(sldns_str2wire_dname_buf(zname, dname, &dname_len) != 0) { log_err("cannot parse zone name in local-zone-override: %s %s", zname, netblock); return 0; } dname_labs = dname_count_labels(dname); /* parse netblock */ if(!netblockstrtoaddr(netblock, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) { log_err("cannot parse netblock in local-zone-override: %s %s", zname, netblock); return 0; } /* parse zone type */ if(!local_zone_str2type(type, &t)) { log_err("cannot parse type in local-zone-override: %s %s %s", zname, netblock, type); return 0; } /* find localzone entry */ lock_rw_rdlock(&zones->lock); z = local_zones_find(zones, dname, dname_len, dname_labs, rr_class); if(!z) { lock_rw_unlock(&zones->lock); log_err("no local-zone for local-zone-override %s", zname); return 0; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); /* create netblock addr_tree if not present yet */ if(!z->override_tree) { z->override_tree = (struct rbtree_type*)regional_alloc_zero( z->region, sizeof(*z->override_tree)); if(!z->override_tree) { lock_rw_unlock(&z->lock); log_err("out of memory"); return 0; } addr_tree_init(z->override_tree); } /* add new elem to tree */ if(z->override_tree) { struct local_zone_override* n; n = (struct local_zone_override*)regional_alloc_zero( z->region, sizeof(*n)); if(!n) { lock_rw_unlock(&z->lock); log_err("out of memory"); return 0; } n->type = t; if(!addr_tree_insert(z->override_tree, (struct addr_tree_node*)n, &addr, addrlen, net)) { lock_rw_unlock(&z->lock); log_err("duplicate local-zone-override %s %s", zname, netblock); return 1; } } lock_rw_unlock(&z->lock); return 1; } /** parse local-zone: statements */ static int lz_enter_zones(struct local_zones* zones, struct config_file* cfg) { struct config_str2list* p; #ifndef THREADS_DISABLED struct local_zone* z; #endif for(p = cfg->local_zones; p; p = p->next) { if(!( #ifndef THREADS_DISABLED z= #endif lz_enter_zone(zones, p->str, p->str2, LDNS_RR_CLASS_IN))) return 0; lock_rw_unlock(&z->lock); } return 1; } /** lookup a zone in rbtree; exact match only; SLOW due to parse */ static int lz_exists(struct local_zones* zones, const char* name) { struct local_zone z; z.node.key = &z; z.dclass = LDNS_RR_CLASS_IN; if(!parse_dname(name, &z.name, &z.namelen, &z.namelabs)) { log_err("bad name %s", name); return 0; } lock_rw_rdlock(&zones->lock); if(rbtree_search(&zones->ztree, &z.node)) { lock_rw_unlock(&zones->lock); free(z.name); return 1; } lock_rw_unlock(&zones->lock); free(z.name); return 0; } /** lookup a zone in cfg->nodefault list */ static int lz_nodefault(struct config_file* cfg, const char* name) { struct config_strlist* p; size_t len = strlen(name); if(len == 0) return 0; if(name[len-1] == '.') len--; for(p = cfg->local_zones_nodefault; p; p = p->next) { /* compare zone name, lowercase, compare without ending . */ if(strncasecmp(p->str, name, len) == 0 && (strlen(p->str) == len || (strlen(p->str)==len+1 && p->str[len] == '.'))) return 1; } return 0; } /** enter reverse default zone */ static int add_reverse_default(struct local_zones* zones, struct config_file* cfg, const char* name) { struct local_zone* z; char str[1024]; /* known long enough */ if(lz_exists(zones, name) || lz_nodefault(cfg, name)) return 1; /* do not enter default content */ if(!(z=lz_enter_zone(zones, name, "static", LDNS_RR_CLASS_IN))) 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, 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, str)) { lock_rw_unlock(&z->lock); return 0; } if(strncasecmp("127.in-addr.arpa.", name, 17) == 0) { if(!lz_enter_rr_into_zone(z, "1.0.0.127.in-addr.arpa. 10800 IN PTR localhost.")) { lock_rw_unlock(&z->lock); return 0; } } else if(strncasecmp("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.", name, 73) == 0) { snprintf(str, sizeof(str), "%s 10800 IN PTR localhost.", name); if(!lz_enter_rr_into_zone(z, str)) { lock_rw_unlock(&z->lock); return 0; } } lock_rw_unlock(&z->lock); return 1; } /** enter (AS112) empty default zone */ static int add_empty_default(struct local_zones* zones, struct config_file* cfg, const char* name) { struct local_zone* z; char str[1024]; /* known long enough */ if(lz_exists(zones, name) || lz_nodefault(cfg, name)) return 1; /* do not enter default content */ if(!(z=lz_enter_zone(zones, name, "static", LDNS_RR_CLASS_IN))) 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, 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, str)) { lock_rw_unlock(&z->lock); return 0; } lock_rw_unlock(&z->lock); return 1; } /** enter default zones */ int local_zone_enter_defaults(struct local_zones* zones, struct config_file* cfg) { struct local_zone* z; const char** zstr; /* Do not add any default */ if(cfg->local_zones_disable_default) return 1; /* this list of zones is from RFC 6303 and RFC 7686 */ /* block localhost level zones first, then onion and later the LAN zones */ /* localhost. zone */ if(!lz_exists(zones, "localhost.") && !lz_nodefault(cfg, "localhost.")) { if(!(z=lz_enter_zone(zones, "localhost.", "redirect", LDNS_RR_CLASS_IN)) || !lz_enter_rr_into_zone(z, "localhost. 10800 IN NS localhost.") || !lz_enter_rr_into_zone(z, "localhost. 10800 IN SOA localhost. nobody.invalid. " "1 3600 1200 604800 10800") || !lz_enter_rr_into_zone(z, "localhost. 10800 IN A 127.0.0.1") || !lz_enter_rr_into_zone(z, "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); } /* ip4 and ip6 reverse */ for(zstr = local_zones_default_reverse; *zstr; zstr++) { if(!add_reverse_default(zones, cfg, *zstr)) { log_err("out of memory adding default zone"); return 0; } } /* special-use zones */ for(zstr = local_zones_default_special; *zstr; zstr++) { if(!add_empty_default(zones, cfg, *zstr)) { log_err("out of memory adding default zone"); return 0; } } /* block AS112 zones, unless asked not to */ if(!cfg->unblock_lan_zones) { for(zstr = as112_zones; *zstr; zstr++) { if(!add_empty_default(zones, cfg, *zstr)) { log_err("out of memory adding default zone"); return 0; } } } return 1; } /** parse local-zone-override: statements */ static int lz_enter_overrides(struct local_zones* zones, struct config_file* cfg) { struct config_str3list* p; for(p = cfg->local_zone_overrides; p; p = p->next) { if(!lz_enter_override(zones, p->str, p->str2, p->str3, LDNS_RR_CLASS_IN)) return 0; } return 1; } /* return closest parent in the tree, NULL if none */ static struct local_zone* find_closest_parent(struct local_zone* curr, struct local_zone* prev) { struct local_zone* p; int m; if(!prev || prev->dclass != curr->dclass) return NULL; (void)dname_lab_cmp(prev->name, prev->namelabs, curr->name, curr->namelabs, &m); /* we know prev is smaller */ /* sort order like: . com. bla.com. zwb.com. net. */ /* find the previous, or parent-parent-parent */ for(p = prev; p; p = p->parent) { /* looking for name with few labels, a parent */ if(p->namelabs <= m) { /* ==: since prev matched m, this is closest*/ /* <: prev matches more, but is not a parent, * this one is a (grand)parent */ return p; } } return NULL; } /** setup parent pointers, so that a lookup can be done for closest match */ void lz_init_parents(struct local_zones* zones) { struct local_zone* node, *prev = NULL; lock_rw_wrlock(&zones->lock); RBTREE_FOR(node, struct local_zone*, &zones->ztree) { lock_rw_wrlock(&node->lock); node->parent = find_closest_parent(node, prev); prev = node; if(node->override_tree) addr_tree_init_parents(node->override_tree); lock_rw_unlock(&node->lock); } lock_rw_unlock(&zones->lock); } /** enter implicit transparent zone for local-data: without local-zone: */ static int lz_setup_implicit(struct local_zones* zones, struct config_file* cfg) { /* walk over all items that have no parent zone and find * the name that covers them all (could be the root) and * add that as a transparent zone */ struct config_strlist* p; int have_name = 0; int have_other_classes = 0; uint16_t dclass = 0; uint8_t* nm = 0; size_t nmlen = 0; int nmlabs = 0; int match = 0; /* number of labels match count */ lz_init_parents(zones); /* to enable local_zones_lookup() */ for(p = cfg->local_data; p; p = p->next) { uint8_t* rr_name; uint16_t rr_class, rr_type; size_t len; int labs; if(!get_rr_nameclass(p->str, &rr_name, &rr_class, &rr_type)) { log_err("Bad local-data RR %s", p->str); return 0; } labs = dname_count_size_labels(rr_name, &len); lock_rw_rdlock(&zones->lock); if(!local_zones_lookup(zones, rr_name, len, labs, rr_class, rr_type, 1)) { /* Check if there is a zone that this could go * under but for different class; created zones are * always for LDNS_RR_CLASS_IN. Create the zone with * a different class but the same configured * local_zone_type. */ struct local_zone* z = local_zones_lookup(zones, rr_name, len, labs, LDNS_RR_CLASS_IN, rr_type, 1); if(z) { uint8_t* name = memdup(z->name, z->namelen); size_t znamelen = z->namelen; int znamelabs = z->namelabs; enum localzone_type ztype = z->type; lock_rw_unlock(&zones->lock); if(!name) { log_err("out of memory"); free(rr_name); return 0; } if(!( #ifndef THREADS_DISABLED z = #endif lz_enter_zone_dname(zones, name, znamelen, znamelabs, ztype, rr_class))) { free(rr_name); return 0; } lock_rw_unlock(&z->lock); free(rr_name); continue; } if(!have_name) { dclass = rr_class; nm = rr_name; nmlen = len; nmlabs = labs; match = labs; have_name = 1; } else { int m; if(rr_class != dclass) { /* process other classes later */ free(rr_name); have_other_classes = 1; lock_rw_unlock(&zones->lock); continue; } /* find smallest shared topdomain */ (void)dname_lab_cmp(nm, nmlabs, rr_name, labs, &m); free(rr_name); if(m < match) match = m; } } else free(rr_name); lock_rw_unlock(&zones->lock); } if(have_name) { uint8_t* n2; #ifndef THREADS_DISABLED struct local_zone* z; #endif /* allocate zone of smallest shared topdomain to contain em */ n2 = nm; dname_remove_labels(&n2, &nmlen, nmlabs - match); n2 = memdup(n2, nmlen); free(nm); if(!n2) { log_err("out of memory"); return 0; } log_nametypeclass(VERB_ALGO, "implicit transparent local-zone", n2, 0, dclass); if(!( #ifndef THREADS_DISABLED z= #endif 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 */ return lz_setup_implicit(zones, cfg); } return 1; } /** enter local-zone-tag info */ static int lz_enter_zone_tags(struct local_zones* zones, struct config_file* cfg) { struct config_strbytelist* p; int c = 0; for(p = cfg->local_zone_tags; p; p = p->next) { if(!lz_enter_zone_tag(zones, p->str, p->str2, p->str2len, LDNS_RR_CLASS_IN)) return 0; c++; } if(c) verbose(VERB_ALGO, "applied tags to %d local zones", c); return 1; } /** enter auth data */ static int lz_enter_data(struct local_zones* zones, struct config_file* cfg) { struct config_strlist* p; for(p = cfg->local_data; p; p = p->next) { if(!lz_enter_rr_str(zones, p->str)) return 0; } return 1; } /** free memory from config */ static void lz_freeup_cfg(struct config_file* cfg) { config_deldblstrlist(cfg->local_zones); cfg->local_zones = NULL; config_delstrlist(cfg->local_zones_nodefault); cfg->local_zones_nodefault = NULL; config_delstrlist(cfg->local_data); cfg->local_data = NULL; } int local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg) { /* create zones from zone statements. */ if(!lz_enter_zones(zones, cfg)) { return 0; } /* apply default zones+content (unless disabled, or overridden) */ if(!local_zone_enter_defaults(zones, cfg)) { return 0; } /* enter local zone overrides */ if(!lz_enter_overrides(zones, cfg)) { return 0; } /* create implicit transparent zone from data. */ if(!lz_setup_implicit(zones, cfg)) { return 0; } /* setup parent ptrs for lookup during data entry */ lz_init_parents(zones); /* insert local zone tags */ if(!lz_enter_zone_tags(zones, cfg)) { return 0; } /* insert local data */ if(!lz_enter_data(zones, cfg)) { return 0; } /* freeup memory from cfg struct. */ lz_freeup_cfg(cfg); return 1; } struct local_zone* local_zones_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, uint16_t dtype, int foradd) { return local_zones_tags_lookup(zones, name, len, labs, dclass, dtype, NULL, 0, 1, foradd); } struct local_zone* local_zones_tags_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, uint16_t dtype, uint8_t* taglist, size_t taglen, int ignoretags, int foradd) { rbnode_type* res = NULL; struct local_zone *result; struct local_zone key; int m; key.node.key = &key; key.dclass = dclass; /* for type DS use a zone higher when on a zonecut */ if(dtype == LDNS_RR_TYPE_DS && !dname_is_root(name)) { /* If this is at a zone cut, of a local-zone, and it is * of type always_refuse. Then also refuse the type DS * for it. That could make it DNSSEC bogus, but it is * REFUSED anyway. It stops CNAME type answers in the * type DS lookup. */ key.name = name; key.namelen = len; key.namelabs = labs; /* For additions and removals, use the ordinary rule, * to remove a label for type DS to locate the parent zone. * That is where the DS RR needs to be put. */ if(!foradd && (result=(struct local_zone*)rbtree_search( &zones->ztree, &key)) != NULL && result->type == local_zone_always_refuse) { /* The type DS does not go up one label. */ return result; } else { dname_remove_label(&name, &len); labs--; } } key.name = name; key.namelen = len; key.namelabs = labs; rbtree_find_less_equal(&zones->ztree, &key, &res); result = (struct local_zone*)res; /* exact or smaller element (or no element) */ if(!result || result->dclass != dclass) return NULL; /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->namelabs, key.name, key.namelabs, &m); while(result) { /* go up until qname is zone or subdomain of zone */ if(result->namelabs <= m) if(ignoretags || !result->taglist || taglist_intersect(result->taglist, result->taglen, taglist, taglen)) break; result = result->parent; } return result; } 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); } struct local_zone* local_zones_find_le(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, int* exact) { struct local_zone key; rbnode_type *node; key.node.key = &key; key.dclass = dclass; key.name = name; key.namelen = len; key.namelabs = labs; *exact = rbtree_find_less_equal(&zones->ztree, &key, &node); return (struct local_zone*)node; } /** encode answer consisting of 1 rrset */ static int local_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_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; rep.reason_bogus = LDNS_EDE_NONE; 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(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } return 1; } /** encode local error answer */ static void local_error_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, int rcode, int r, int ede_code, const char* ede_txt) { edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; if(!inplace_cb_reply_local_call(env, qinfo, NULL, NULL, rcode, edns, repinfo, temp, env->now_tv)) edns->opt_list_inplace_cb_out = NULL; if(ede_code != LDNS_EDE_NONE && env->cfg->ede) { edns_opt_list_append_ede(&edns->opt_list_out, temp, ede_code, ede_txt); } error_encode(buf, r, qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } /** find local data tag string match for the given type in the list */ int local_data_find_tag_datas(const struct query_info* qinfo, struct config_strlist* list, struct ub_packed_rrset_key* r, struct regional* temp) { struct config_strlist* p; char buf[65536]; uint8_t rr[LDNS_RR_BUF_SIZE]; size_t len; 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); res = sldns_str2wire_rr_buf(buf, rr, &len, NULL, 3600, NULL, 0, NULL, 0); if(res != 0) /* parse errors are already checked before, in * acllist check_data, skip this for robustness */ continue; if(len < 1 /* . */ + 8 /* typeclassttl*/ + 2 /*rdatalen*/) continue; 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 */ if(r->rk.dname == NULL) { r->entry.key = r; r->rk.dname = qinfo->qname; r->rk.dname_len = qinfo->qname_len; 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( temp, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)); if(!d) return 0; /* out of memory */ r->entry.data = d; d->ttl = sldns_wirerr_get_ttl(rr, len, 1); 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 = (struct packed_rrset_data*)r->entry.data; /* add entry to the data */ if(d->count != 0) { size_t* oldlen = d->rr_len; uint8_t** olddata = d->rr_data; time_t* oldttl = d->rr_ttl; /* increase arrays for lookup */ /* this is of course slow for very many records, * but most redirects are expected with few records */ d->rr_len = (size_t*)regional_alloc_zero(temp, (d->count+1)*sizeof(size_t)); d->rr_data = (uint8_t**)regional_alloc_zero(temp, (d->count+1)*sizeof(uint8_t*)); d->rr_ttl = (time_t*)regional_alloc_zero(temp, (d->count+1)*sizeof(time_t)); if(!d->rr_len || !d->rr_data || !d->rr_ttl) return 0; /* out of memory */ /* first one was allocated after struct d, but new * ones get their own array increment alloc, so * copy old content */ memmove(d->rr_len, oldlen, d->count*sizeof(size_t)); memmove(d->rr_data, olddata, d->count*sizeof(uint8_t*)); memmove(d->rr_ttl, oldttl, d->count*sizeof(time_t)); } d->rr_len[d->count] = sldns_wirerr_get_rdatalen(rr, len, 1)+2; d->rr_ttl[d->count] = sldns_wirerr_get_ttl(rr, len, 1); d->rr_data[d->count] = regional_alloc_init(temp, sldns_wirerr_get_rdatawl(rr, len, 1), d->rr_len[d->count]); if(!d->rr_data[d->count]) return 0; /* out of memory */ d->count++; } if(r->rk.dname) return 1; return 0; } static int find_tag_datas(struct query_info* qinfo, struct config_strlist* list, struct ub_packed_rrset_key* r, struct regional* temp) { int result = local_data_find_tag_datas(qinfo, list, r, temp); /* 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(result && 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 */ } return result; } int local_data_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, int labs, struct local_data** ldp, enum localzone_type lz_type, int tag, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags) { 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(lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect) { key.name = z->name; key.namelen = z->namelen; key.namelabs = z->namelabs; if(tag != -1 && (size_t)taglocal_alias) return 1; return local_encode(qinfo, env, edns, repinfo, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } } } ld = (struct local_data*)rbtree_search(&z->data, &key.node); *ldp = ld; if(!ld) { return 0; } 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 || lz_type == local_zone_inform_redirect) && qinfo->qtype != LDNS_RR_TYPE_CNAME && lr->rrset->rk.type == htons(LDNS_RR_TYPE_CNAME)) { uint8_t* ctarget; size_t ctargetlen = 0; 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; get_cname_target(lr->rrset, &ctarget, &ctargetlen); if(!ctargetlen) return 0; /* invalid cname */ if(dname_is_wild(ctarget)) { /* synthesize cname target */ struct packed_rrset_data* d, *lr_d; /* -3 for wildcard label and root label from qname */ size_t newtargetlen = qinfo->qname_len + ctargetlen - 3; log_assert(ctargetlen >= 3); log_assert(qinfo->qname_len >= 1); if(newtargetlen > LDNS_MAX_DOMAINLEN) { qinfo->local_alias = NULL; local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_YXDOMAIN, (LDNS_RCODE_YXDOMAIN|BIT_AA), LDNS_EDE_OTHER, "DNAME expansion became too large"); return 1; } memset(&qinfo->local_alias->rrset->entry, 0, sizeof(qinfo->local_alias->rrset->entry)); qinfo->local_alias->rrset->entry.key = qinfo->local_alias->rrset; qinfo->local_alias->rrset->entry.hash = rrset_key_hash(&qinfo->local_alias->rrset->rk); d = (struct packed_rrset_data*)regional_alloc_zero(temp, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t) + newtargetlen); if(!d) return 0; /* out of memory */ lr_d = (struct packed_rrset_data*)lr->rrset->entry.data; qinfo->local_alias->rrset->entry.data = d; d->ttl = lr_d->rr_ttl[0]; /* RFC6672-like behavior: synth CNAME TTL uses original TTL*/ d->count = 1; d->rrsig_count = 0; d->trust = rrset_trust_ans_noAA; d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); d->rr_len[0] = newtargetlen + sizeof(uint16_t); packed_rrset_ptr_fixup(d); d->rr_ttl[0] = d->ttl; sldns_write_uint16(d->rr_data[0], newtargetlen); /* write qname */ memmove(d->rr_data[0] + sizeof(uint16_t), qinfo->qname, qinfo->qname_len - 1); /* write cname target wildcard label */ memmove(d->rr_data[0] + sizeof(uint16_t) + qinfo->qname_len - 1, ctarget + 2, ctargetlen - 2); } return 1; } if(lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect) { /* convert rrset name to query name; like a wildcard */ struct ub_packed_rrset_key r = *lr->rrset; r.rk.dname = qinfo->qname; r.rk.dname_len = qinfo->qname_len; return local_encode(qinfo, env, edns, repinfo, buf, temp, &r, 1, LDNS_RCODE_NOERROR); } return local_encode(qinfo, env, edns, repinfo, buf, temp, lr->rrset, 1, LDNS_RCODE_NOERROR); } /** * See if the local zone does not cover the name, eg. the name is not * in the zone and the zone is transparent */ static int local_zone_does_not_cover(struct local_zone* z, struct query_info* qinfo, int labs) { struct local_data key; struct local_data* ld = NULL; struct local_rrset* lr = NULL; if(z->type == local_zone_always_transparent || z->type == local_zone_block_a) return 1; if(z->type != local_zone_transparent && z->type != local_zone_typetransparent && z->type != local_zone_inform) return 0; key.node.key = &key; key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = labs; ld = (struct local_data*)rbtree_search(&z->data, &key.node); if(z->type == local_zone_transparent || z->type == local_zone_inform) return (ld == NULL); if(ld) lr = local_data_find_type(ld, qinfo->qtype, 1); /* local_zone_typetransparent */ return (lr == NULL); } static inline int local_zone_is_udp_query(struct comm_reply* repinfo) { return repinfo != NULL ? (repinfo->c != NULL ? repinfo->c->type == comm_udp : 0) : 0; } int local_zones_zone_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct local_data* ld, enum localzone_type lz_type) { if(lz_type == local_zone_deny || lz_type == local_zone_always_deny || lz_type == local_zone_inform_deny) { /** no reply at all, signal caller by clearing buffer. */ sldns_buffer_clear(buf); sldns_buffer_flip(buf); return 1; } else if(lz_type == local_zone_refuse || lz_type == local_zone_always_refuse) { local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_REFUSED, (LDNS_RCODE_REFUSED|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } else if(lz_type == local_zone_static || lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect || lz_type == local_zone_always_nxdomain || lz_type == local_zone_always_nodata || (lz_type == local_zone_truncate && local_zone_is_udp_query(repinfo))) { /* 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 || lz_type == local_zone_redirect || lz_type == local_zone_inform_redirect || lz_type == local_zone_always_nodata || lz_type == local_zone_truncate)? LDNS_RCODE_NOERROR:LDNS_RCODE_NXDOMAIN; rcode = (lz_type == local_zone_truncate ? (rcode|BIT_TC) : rcode); if(z != NULL && z->soa && z->soa_negative) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa_negative, 0, rcode); local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, (rcode|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } else if(lz_type == local_zone_typetransparent || lz_type == local_zone_always_transparent) { /* no NODATA or NXDOMAINS for this zone type */ return 0; } else if(lz_type == local_zone_block_a) { /* Return NODATA for all A queries */ if(qinfo->qtype == LDNS_RR_TYPE_A) { local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_NOERROR, (LDNS_RCODE_NOERROR|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } return 0; } else if(lz_type == local_zone_always_null) { /* 0.0.0.0 or ::0 or noerror/nodata for this zone type, * used for blocklists. */ if(qinfo->qtype == LDNS_RR_TYPE_A || qinfo->qtype == LDNS_RR_TYPE_AAAA) { struct ub_packed_rrset_key lrr; struct packed_rrset_data d; time_t rr_ttl = 3600; size_t rr_len = 0; uint8_t rr_data[2+16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; uint8_t* rr_datas = rr_data; memset(&lrr, 0, sizeof(lrr)); memset(&d, 0, sizeof(d)); lrr.entry.data = &d; lrr.rk.dname = qinfo->qname; lrr.rk.dname_len = qinfo->qname_len; lrr.rk.type = htons(qinfo->qtype); lrr.rk.rrset_class = htons(qinfo->qclass); if(qinfo->qtype == LDNS_RR_TYPE_A) { rr_len = 4; sldns_write_uint16(rr_data, rr_len); rr_len += 2; } else { rr_len = 16; sldns_write_uint16(rr_data, rr_len); rr_len += 2; } d.ttl = rr_ttl; d.count = 1; d.rr_len = &rr_len; d.rr_data = &rr_datas; d.rr_ttl = &rr_ttl; return local_encode(qinfo, env, edns, repinfo, buf, temp, &lrr, 1, LDNS_RCODE_NOERROR); } else { /* NODATA: No EDE needed */ local_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_NOERROR, (LDNS_RCODE_NOERROR|BIT_AA), -1, NULL); } return 1; } /* else lz_type == local_zone_transparent */ /* if the zone is transparent and the name exists, but the type * does not, then we should make this noerror/nodata */ if(ld && ld->rrsets) { int rcode = LDNS_RCODE_NOERROR; if(z != NULL && z->soa && z->soa_negative) return local_encode(qinfo, env, edns, repinfo, buf, temp, z->soa_negative, 0, rcode); /* NODATA: No EDE needed */ local_error_encode(qinfo, env, edns, repinfo, buf, temp, rcode, (rcode|BIT_AA), LDNS_EDE_NONE, NULL); return 1; } /* stop here, and resolve further on */ return 0; } /** print log information for an inform zone query */ static void lz_inform_print(struct local_zone* z, struct query_info* qinfo, struct sockaddr_storage* addr, socklen_t addrlen) { char ip[128], txt[512]; char zname[LDNS_MAX_DOMAINLEN]; uint16_t port = ntohs(((struct sockaddr_in*)addr)->sin_port); dname_str(z->name, zname); addr_to_str(addr, addrlen, ip, sizeof(ip)); snprintf(txt, sizeof(txt), "%s %s %s@%u", zname, local_zone_type2str(z->type), ip, (unsigned)port); log_nametypeclass(NO_VERBOSE, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } static enum localzone_type lz_type(uint8_t *taglist, size_t taglen, uint8_t *taglist2, size_t taglen2, uint8_t *tagactions, size_t tagactionssize, enum localzone_type lzt, struct comm_reply* repinfo, struct rbtree_type* override_tree, int* tag, char** tagname, int num_tags) { struct local_zone_override* lzo; if(repinfo && override_tree) { lzo = (struct local_zone_override*)addr_tree_lookup( override_tree, &repinfo->client_addr, repinfo->client_addrlen); if(lzo && lzo->type) { verbose(VERB_ALGO, "local zone override to type %s", local_zone_type2str(lzo->type)); return lzo->type; } } if(!taglist || !taglist2) return lzt; return local_data_find_tag_action(taglist, taglen, taglist2, taglen2, tagactions, tagactionssize, lzt, tag, tagname, num_tags); } enum localzone_type local_data_find_tag_action(const uint8_t* taglist, size_t taglen, const uint8_t* taglist2, size_t taglen2, const uint8_t* tagactions, size_t tagactionssize, enum localzone_type lzt, int* tag, char* const* tagname, int num_tags) { size_t i, j; uint8_t tagmatch; for(i=0; i0; j++) { if((tagmatch & 0x1)) { *tag = (int)(i*8+j); verbose(VERB_ALGO, "matched tag [%d] %s", *tag, (*tag>= 1; } } return lzt; } int local_zones_answer(struct local_zones* zones, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, uint8_t* tagactions, size_t tagactionssize, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags, struct view* view) { /* 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 = NULL; struct local_zone* z = NULL; enum localzone_type lzt = local_zone_transparent; int r, tag = -1; if(view) { lock_rw_rdlock(&view->lock); if(view->local_zones && (z = local_zones_lookup(view->local_zones, qinfo->qname, qinfo->qname_len, labs, qinfo->qclass, qinfo->qtype, 0))) { lock_rw_rdlock(&z->lock); lzt = z->type; } if(lzt == local_zone_noview) { lock_rw_unlock(&z->lock); z = NULL; } if(z && (lzt == local_zone_transparent || lzt == local_zone_typetransparent || lzt == local_zone_inform || lzt == local_zone_always_transparent || lzt == local_zone_block_a) && local_zone_does_not_cover(z, qinfo, labs)) { lock_rw_unlock(&z->lock); z = NULL; } if(view->local_zones && !z && !view->isfirst){ lock_rw_unlock(&view->lock); return 0; } if(z && verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zname); verbose(VERB_ALGO, "using localzone %s %s from view %s", zname, local_zone_type2str(lzt), view->name); } lock_rw_unlock(&view->lock); } if(!z) { /* try global local_zones tree */ lock_rw_rdlock(&zones->lock); if(!(z = local_zones_tags_lookup(zones, qinfo->qname, qinfo->qname_len, labs, qinfo->qclass, qinfo->qtype, taglist, taglen, 0, 0))) { lock_rw_unlock(&zones->lock); return 0; } lock_rw_rdlock(&z->lock); lzt = lz_type(taglist, taglen, z->taglist, z->taglen, tagactions, tagactionssize, z->type, repinfo, z->override_tree, &tag, tagname, num_tags); lock_rw_unlock(&zones->lock); if(z && verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zname); verbose(VERB_ALGO, "using localzone %s %s", zname, local_zone_type2str(lzt)); } } if((env->cfg->log_local_actions || lzt == local_zone_inform || lzt == local_zone_inform_deny || lzt == local_zone_inform_redirect) && repinfo) lz_inform_print(z, qinfo, &repinfo->client_addr, repinfo->client_addrlen); if(lzt != local_zone_always_refuse && lzt != local_zone_always_transparent && lzt != local_zone_block_a && lzt != local_zone_always_nxdomain && lzt != local_zone_always_nodata && lzt != local_zone_always_deny && local_data_answer(z, env, qinfo, edns, repinfo, buf, temp, labs, &ld, lzt, tag, tag_datas, tag_datas_size, tagname, num_tags)) { lock_rw_unlock(&z->lock); /* We should tell the caller that encode is deferred if we found * a local alias. */ return !qinfo->local_alias; } r = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, ld, lzt); lock_rw_unlock(&z->lock); return r && !qinfo->local_alias; /* see above */ } const char* local_zone_type2str(enum localzone_type t) { switch(t) { case local_zone_unset: return "unset"; case local_zone_deny: return "deny"; case local_zone_refuse: return "refuse"; case local_zone_redirect: return "redirect"; case local_zone_transparent: return "transparent"; case local_zone_typetransparent: return "typetransparent"; case local_zone_static: return "static"; case local_zone_nodefault: return "nodefault"; case local_zone_inform: return "inform"; case local_zone_inform_deny: return "inform_deny"; case local_zone_inform_redirect: return "inform_redirect"; case local_zone_always_transparent: return "always_transparent"; case local_zone_block_a: return "block_a"; case local_zone_always_refuse: return "always_refuse"; case local_zone_always_nxdomain: return "always_nxdomain"; case local_zone_always_nodata: return "always_nodata"; case local_zone_always_deny: return "always_deny"; case local_zone_always_null: return "always_null"; case local_zone_noview: return "noview"; case local_zone_truncate: return "truncate"; case local_zone_invalid: return "invalid"; } return "badtyped"; } 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, "typetransparent") == 0) *t = local_zone_typetransparent; else if(strcmp(type, "redirect") == 0) *t = local_zone_redirect; else if(strcmp(type, "inform") == 0) *t = local_zone_inform; else if(strcmp(type, "inform_deny") == 0) *t = local_zone_inform_deny; else if(strcmp(type, "inform_redirect") == 0) *t = local_zone_inform_redirect; else if(strcmp(type, "always_transparent") == 0) *t = local_zone_always_transparent; else if(strcmp(type, "block_a") == 0) *t = local_zone_block_a; else if(strcmp(type, "always_refuse") == 0) *t = local_zone_always_refuse; else if(strcmp(type, "always_nxdomain") == 0) *t = local_zone_always_nxdomain; else if(strcmp(type, "always_nodata") == 0) *t = local_zone_always_nodata; else if(strcmp(type, "always_deny") == 0) *t = local_zone_always_deny; else if(strcmp(type, "always_null") == 0) *t = local_zone_always_null; else if(strcmp(type, "noview") == 0) *t = local_zone_noview; else if(strcmp(type, "truncate") == 0) *t = local_zone_truncate; else if(strcmp(type, "nodefault") == 0) *t = local_zone_nodefault; 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) { int exact; /* create */ struct local_zone *prev; struct local_zone* z = local_zone_create(name, len, labs, tp, dclass); if(!z) { free(name); return NULL; } lock_rw_wrlock(&z->lock); /* find the closest parent */ prev = local_zones_find_le(zones, name, len, labs, dclass, &exact); if(!exact) z->parent = find_closest_parent(z, prev); /* insert into the tree */ if(exact||!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) { uint8_t* rr_name; uint16_t rr_class, rr_type; size_t len; int labs; struct local_zone* z; int r; if(!get_rr_nameclass(rr, &rr_name, &rr_class, &rr_type)) { return 0; } labs = dname_count_size_labels(rr_name, &len); /* could first try readlock then get writelock if zone does not exist, * but we do not add enough RRs (from multiple threads) to optimize */ lock_rw_wrlock(&zones->lock); z = local_zones_lookup(zones, rr_name, len, labs, rr_class, rr_type, 1); if(!z) { z = local_zones_add_zone(zones, rr_name, len, labs, rr_class, local_zone_transparent); if(!z) { lock_rw_unlock(&zones->lock); return 0; } } else { free(rr_name); } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); r = lz_enter_rr_into_zone(z, 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 = local_zone_find_data(z, name, len, labs); } } /** find and remove type from list in domain struct */ static void del_local_rrset(struct local_data* d, uint16_t dtype) { struct local_rrset* prev=NULL, *p=d->rrsets; while(p && ntohs(p->rrset->rk.type) != dtype) { prev = p; p = p->next; } if(!p) return; /* rrset type not found */ /* unlink it */ if(prev) prev->next = p->next; else d->rrsets = p->next; /* no memory recycling for zone deletions ... */ } 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; /* remove DS */ lock_rw_rdlock(&zones->lock); z = local_zones_lookup(zones, name, len, labs, dclass, LDNS_RR_TYPE_DS, 1); if(z) { lock_rw_wrlock(&z->lock); d = local_zone_find_data(z, name, len, labs); if(d) { del_local_rrset(d, LDNS_RR_TYPE_DS); del_empty_term(z, d, name, len, labs); } lock_rw_unlock(&z->lock); } lock_rw_unlock(&zones->lock); /* remove other types */ lock_rw_rdlock(&zones->lock); z = local_zones_lookup(zones, name, len, labs, dclass, 0, 1); if(!z) { /* no such zone, we're done */ lock_rw_unlock(&zones->lock); return; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&zones->lock); /* find the domain */ d = local_zone_find_data(z, name, len, labs); if(d) { /* 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; z->soa_negative = NULL; } /* cleanup the empty nonterminals for this name */ del_empty_term(z, d, name, len, labs); } lock_rw_unlock(&z->lock); } /** Get memory usage for local_zone */ static size_t local_zone_get_mem(struct local_zone* z) { size_t m = sizeof(*z); lock_rw_rdlock(&z->lock); m += z->namelen + z->taglen + regional_get_mem(z->region); lock_rw_unlock(&z->lock); return m; } size_t local_zones_get_mem(struct local_zones* zones) { struct local_zone* z; size_t m; if(!zones) return 0; m = sizeof(*zones); lock_rw_rdlock(&zones->lock); RBTREE_FOR(z, struct local_zone*, &zones->ztree) { m += local_zone_get_mem(z); } lock_rw_unlock(&zones->lock); return m; } void local_zones_swap_tree(struct local_zones* zones, struct local_zones* data) { rbtree_type oldtree = zones->ztree; zones->ztree = data->ztree; data->ztree = oldtree; } unbound-1.25.1/services/mesh.h0000644000175000017500000006557415203270263015657 0ustar wouterwouter/* * services/mesh.h - deal with mesh of query states and handle events for that. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist in dealing with a mesh of * query states. This mesh is supposed to be thread-specific. * It consists of query states (per qname, qtype, qclass) and connections * between query states and the super and subquery states, and replies to * send back to clients. */ #ifndef SERVICES_MESH_H #define SERVICES_MESH_H #include "util/rbtree.h" #include "util/netevent.h" #include "util/data/msgparse.h" #include "util/module.h" #include "services/modstack.h" #include "services/rpz.h" #include "libunbound/unbound.h" struct sldns_buffer; struct mesh_state; struct mesh_reply; struct mesh_cb; struct query_info; struct reply_info; struct outbound_entry; struct timehist; struct respip_client_info; /** * Maximum number of mesh state activations. Any more is likely an * infinite loop in the module. It is then terminated. */ #define MESH_MAX_ACTIVATION 10000 /** * Max number of references-to-references-to-references.. search size. * Any more is treated like 'too large', and the creation of a new * dependency is failed (so that no loops can be created). */ #define MESH_MAX_SUBSUB 1024 /** * Mesh of query states */ struct mesh_area { /** active module stack */ struct module_stack mods; /** environment for new states */ struct module_env* env; /** set of runnable queries (mesh_state.run_node) */ rbtree_type run; /** rbtree of all current queries (mesh_state.node)*/ rbtree_type all; /** number of queries for unbound's auth_zones, upstream query */ size_t num_query_authzone_up; /** number of queries for unbound's auth_zones, downstream answers */ size_t num_query_authzone_down; /** count of the total number of mesh_reply entries */ size_t num_reply_addrs; /** count of the number of mesh_states that have mesh_replies * Because a state can send results to multiple reply addresses, * this number must be equal or lower than num_reply_addrs. */ size_t num_reply_states; /** number of mesh_states that have no mesh_replies, and also * an empty set of super-states, thus are 'toplevel' or detached * internal opportunistic queries */ size_t num_detached_states; /** number of reply states in the forever list */ size_t num_forever_states; /** max total number of reply states to have */ size_t max_reply_states; /** max forever number of reply states to have */ size_t max_forever_states; /** stats, cumulative number of reply states jostled out */ size_t stats_jostled; /** stats, cumulative number of incoming client msgs dropped */ size_t stats_dropped; /** stats, number of expired replies sent */ size_t ans_expired; /** stats, number of cached replies from cachedb */ size_t ans_cachedb; /** number of replies sent */ size_t replies_sent; /** sum of waiting times for the replies */ struct timeval replies_sum_wait; /** histogram of time values */ struct timehist* histogram; /** (extended stats) secure replies */ size_t ans_secure; /** (extended stats) bogus replies */ size_t ans_bogus; /** (extended stats) number of validation operations */ size_t val_ops; /** (extended stats) rcodes in replies */ size_t ans_rcode[UB_STATS_RCODE_NUM]; /** (extended stats) rcode nodata in replies */ size_t ans_nodata; /** (extended stats) type of applied RPZ action */ size_t rpz_action[UB_STATS_RPZ_ACTION_NUM]; /** stats, number of queries removed due to discard-timeout */ size_t num_queries_discard_timeout; /** stats, number of queries removed due to replyaddr limit */ size_t num_queries_replyaddr_limit; /** stats, number of queries removed due to wait-limit */ size_t num_queries_wait_limit; /** stats, number of dns error reports generated */ size_t num_dns_error_reports; /** backup of query if other operations recurse and need the * network buffers */ struct sldns_buffer* qbuf_bak; /** double linked list of the run-to-completion query states. * These are query states with a reply */ struct mesh_state* forever_first; /** last entry in run forever list */ struct mesh_state* forever_last; /** double linked list of the query states that can be jostled out * by new queries if too old. These are query states with a reply */ struct mesh_state* jostle_first; /** last entry in jostle list - this is the entry that is newest */ struct mesh_state* jostle_last; /** timeout for jostling. if age is lower, it does not get jostled. */ struct timeval jostle_max; /** If we need to use response ip (value passed from daemon)*/ int use_response_ip; /** If we need to use RPZ (value passed from daemon) */ int use_rpz; }; /** * A mesh query state * Unique per qname, qtype, qclass (from the qstate). * And RD / CD flag; in case a client turns it off. * And priming queries are different from ordinary queries (because of hints). * * The entire structure is allocated in a region, this region is the qstate * region. All parts (rbtree nodes etc) are also allocated in the region. */ struct mesh_state { /** node in mesh_area all tree, key is this struct. Must be first. */ rbnode_type node; /** node in mesh_area runnable tree, key is this struct */ rbnode_type run_node; /** the query state. Note that the qinfo and query_flags * may not change. */ struct module_qstate s; /** the list of replies to clients for the results */ struct mesh_reply* reply_list; /** if it has a first reply time */ int has_first_reply_time; /** wall-clock time the first client reply was attached; * used by mesh_make_new_space() so duplicate retransmits * cannot reset jostle aging. */ struct timeval first_reply_time; /** the list of callbacks for the results */ struct mesh_cb* cb_list; /** set of superstates (that want this state's result) * contains struct mesh_state_ref* */ rbtree_type super_set; /** set of substates (that this state needs to continue) * contains struct mesh_state_ref* */ rbtree_type sub_set; /** number of activations for the mesh state */ size_t num_activated; /** previous in linked list for reply states */ struct mesh_state* prev; /** next in linked list for reply states */ struct mesh_state* next; /** if this state is in the forever list, jostle list, or neither */ enum mesh_list_select { mesh_no_list, mesh_forever_list, mesh_jostle_list } list_select; /** pointer to this state for uniqueness or NULL */ struct mesh_state* unique; /** true if replies have been sent out (at end for alignment) */ uint8_t replies_sent; }; /** * Rbtree reference to a mesh_state. * Used in super_set and sub_set. */ struct mesh_state_ref { /** node in rbtree for set, key is this structure */ rbnode_type node; /** the mesh state */ struct mesh_state* s; }; /** * Reply to a client */ struct mesh_reply { /** next in reply list */ struct mesh_reply* next; /** the query reply destination, packet buffer and where to send. */ struct comm_reply query_reply; /** edns data from query */ struct edns_data edns; /** the time when request was entered */ struct timeval start_time; /** id of query, in network byteorder. */ uint16_t qid; /** flags of query, for reply flags */ 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; /** send query to this http2 stream, if set */ struct http2_stream* h2_stream; }; /** * Mesh result callback func. * called as func(cb_arg, rcode, buffer_with_reply, security, why_bogus, * was_ratelimited); */ typedef void (*mesh_cb_func_type)(void* cb_arg, int rcode, struct sldns_buffer*, enum sec_status, char* why_bogus, int was_ratelimited); /** * Callback to result routine */ struct mesh_cb { /** next in list */ struct mesh_cb* next; /** edns data from query */ struct edns_data edns; /** id of query, in network byteorder. */ uint16_t qid; /** flags of query, for reply flags */ uint16_t qflags; /** buffer for reply */ struct sldns_buffer* buf; /** callback routine for results. if rcode != 0 buf has message. * called as cb(cb_arg, rcode, buf, sec_state, why_bogus, was_ratelimited); */ mesh_cb_func_type cb; /** user arg for callback */ void* cb_arg; }; /* ------------------- Functions for worker -------------------- */ /** * Allocate mesh, to empty. * @param stack: module stack to activate, copied (as readonly reference). * @param env: environment for new queries. * @return mesh: the new mesh or NULL on error. */ struct mesh_area* mesh_create(struct module_stack* stack, struct module_env* env); /** * Delete mesh, and all query states and replies in it. * @param mesh: the mesh to delete. */ void mesh_delete(struct mesh_area* mesh); /** * New query incoming from clients. Create new query state if needed, and * add mesh_reply to it. Returns error to client on malloc failures. * Will run the mesh area queries to process if a new query state is created. * * @param mesh: the mesh. * @param qinfo: query from client. * @param cinfo: additional information associated with the query client. * 'cinfo' itself is ephemeral but data pointed to by its members * can be assumed to be valid and unchanged until the query processing is * completed. * @param qflags: flags from client query. * @param edns: edns data from client query. * @param rep: where to reply to. * @param qid: query id to reply with. * @param rpz_passthru: if true, the rpz passthru was previously found and * further rpz processing is stopped. */ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, uint16_t qid, int rpz_passthru); /** * New query with callback. Create new query state if needed, and * add mesh_cb to it. * Will run the mesh area queries to process if a new query state is created. * * @param mesh: the mesh. * @param qinfo: query from client. * @param qflags: flags from client query. * @param edns: edns data from client query. * @param buf: buffer for reply contents. * @param qid: query id to reply with. * @param cb: callback function. * @param cb_arg: callback user arg. * @param rpz_passthru: if true, the rpz passthru was previously found and * further rpz processing is stopped. * @return 0 on error. */ int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, struct sldns_buffer* buf, uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru); /** * New prefetch message. Create new query state if needed. * Will run the mesh area queries to process if a new query state is created. * * @param mesh: the mesh. * @param qinfo: query from client. * @param qflags: flags from client query. * @param leeway: TTL leeway what to expire earlier for this update. * @param rpz_passthru: if true, the rpz passthru was previously found and * further rpz processing is stopped. * @param addr: sockaddr_storage for the client; to be used with subnet. * @param opt_list: edns opt_list from the client; to be used when subnet is * enabled. */ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway, int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* opt_list); /** * Handle new event from the wire. A serviced query has returned. * The query state will be made runnable, and the mesh_area will process * query states until processing is complete. * * @param mesh: the query mesh. * @param e: outbound entry, with query state to run and reply pointer. * @param reply: the comm point reply info. * @param what: NETEVENT_* error code (if not 0, what is wrong, TIMEOUT). */ void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, struct comm_reply* reply, int what); /* ------------------- Functions for module environment --------------- */ /** * Detach-subqueries. * Remove all sub-query references from this query state. * Keeps super-references of those sub-queries correct. * Updates stat items in mesh_area structure. * @param qstate: used to find mesh state. */ void mesh_detach_subs(struct module_qstate* qstate); /** * Attach subquery. * Creates it if it does not exist already. * Keeps sub and super references correct. * Performs a cycle detection - for double check - and fails if there is one. * Also fails if the sub-sub-references become too large. * Updates stat items in mesh_area structure. * Pass if it is priming query or not. * return: * o if error (malloc) happened. * o need to initialise the new state (module init; it is a new state). * so that the next run of the query with this module is successful. * o no init needed, attachment successful. * * @param qstate: the state to find mesh state, and that wants to receive * the results from the new subquery. * @param qinfo: what to query for (copied). * @param cinfo: if non-NULL client specific info that may affect IP-based * actions that apply to the query result. It is copied. * @param qflags: what flags to use (RD / CD flag or not). * @param prime: if it is a (stub) priming query. * @param valrec: if it is a validation recursion query (lookup of key, DS). * @param newq: If the new subquery needs initialisation, it is returned, * otherwise NULL is returned. * @return: false on error, true if success (and init may be needed). */ int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec, struct module_qstate** newq); /** * Add detached query. * Creates it if it does not exist already. * Does not make super/sub references. * Performs a cycle detection - for double check - and fails if there is one. * Updates stat items in mesh_area structure. * Pass if it is priming query or not. * return: * o if error (malloc) happened. * o need to initialise the new state (module init; it is a new state). * so that the next run of the query with this module is successful. * o no init needed, attachment successful. * o added subquery, created if it did not exist already. * * @param qstate: the state to find mesh state, and that wants to receive * the results from the new subquery. * @param qinfo: what to query for (copied). * @param cinfo: if non-NULL client specific info that may affect IP-based * actions that apply to the query result. It is copied. * @param qflags: what flags to use (RD / CD flag or not). * @param prime: if it is a (stub) priming query. * @param valrec: if it is a validation recursion query (lookup of key, DS). * @param newq: If the new subquery needs initialisation, it is returned, * otherwise NULL is returned. * @param sub: The added mesh state, created if it did not exist already. * @return: false on error, true if success (and init may be needed). */ int mesh_add_sub(struct module_qstate* qstate, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec, struct module_qstate** newq, struct mesh_state** sub); /** * Query state is done, send messages to reply entries. * Encode messages using reply entry values and the querystate (with original * qinfo), using given reply_info. * Pass errcode != 0 if an error reply is needed. * If no reply entries, nothing is done. * Must be called before a module can module_finished or return module_error. * The module must handle the super query states itself as well. * * @param mstate: mesh state that is done. return_rcode and return_msg * are used for replies. * return_rcode: if not 0 (NOERROR) an error is sent back (and * return_msg is ignored). * return_msg: reply to encode and send back to clients. */ void mesh_query_done(struct mesh_state* mstate); /** * Call inform_super for the super query states that are interested in the * results from this query state. These can then be changed for error * or results. * Called when a module is module_finished or returns module_error. * The super query states become runnable with event module_event_pass, * it calls the current module for the super with the inform_super event. * * @param mesh: mesh area to add newly runnable modules to. * @param mstate: the state that has results, used to find mesh state. */ void mesh_walk_supers(struct mesh_area* mesh, struct mesh_state* mstate); /** * Delete mesh state, cleanup and also rbtrees and so on. * Will detach from all super/subnodes. * @param qstate: to remove. */ void mesh_state_delete(struct module_qstate* qstate); /* ------------------- Functions for mesh -------------------- */ /** * Create and initialize a new mesh state and its query state * Does not put the mesh state into rbtrees and so on. * @param env: module environment to set. * @param qinfo: query info that the mesh is for. * @param cinfo: control info for the query client (can be NULL). * @param qflags: flags for query (RD / CD flag). * @param prime: if true, it is a priming query, set is_priming on mesh state. * @param valrec: if true, it is a validation recursion query, and sets * is_valrec on the mesh state. * @return: new mesh state or NULL on allocation error. */ struct mesh_state* mesh_state_create(struct module_env* env, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec); /** * Make a mesh state unique. * A unique mesh state uses it's unique member to point to itself. * @param mstate: mesh state to check. */ void mesh_state_make_unique(struct mesh_state* mstate); /** * Cleanup a mesh state and its query state. Does not do rbtree or * reference cleanup. * @param mstate: mesh state to cleanup. Its pointer may no longer be used * afterwards. Cleanup rbtrees before calling this function. */ void mesh_state_cleanup(struct mesh_state* mstate); /** * Delete all mesh states from the mesh. * @param mesh: the mesh area to clear */ void mesh_delete_all(struct mesh_area* mesh); /** * Find a mesh state in the mesh area. Pass relevant flags. * * @param mesh: the mesh area to look in. * @param cinfo: if non-NULL client specific info that may affect IP-based * actions that apply to the query result. * @param qinfo: what query * @param qflags: if RD / CD bit is set or not. * @param prime: if it is a priming query. * @param valrec: if it is a validation-recursion query. * @return: mesh state or NULL if not found. */ struct mesh_state* mesh_area_find(struct mesh_area* mesh, struct respip_client_info* cinfo, struct query_info* qinfo, uint16_t qflags, int prime, int valrec); /** * Setup attachment super/sub relation between super and sub mesh state. * The relation must not be present when calling the function. * Does not update stat items in mesh_area. * @param super: super state. * @param sub: sub state. * @return: 0 on alloc error. */ int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub); /** * Create new reply structure and attach it to a mesh state. * Does not update stat items in mesh area. * @param s: the mesh state. * @param edns: edns data for reply (bufsize). * @param rep: comm point reply info. * @param qid: ID of reply. * @param qflags: original query flags. * @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, const struct query_info* qinfo); /** * Create new callback structure and attach it to a mesh state. * Does not update stat items in mesh area. * @param s: the mesh state. * @param edns: edns data for reply (bufsize). * @param buf: buffer for reply * @param cb: callback to call with results. * @param cb_arg: callback user arg. * @param qid: ID of reply. * @param qflags: original query flags. * @return: 0 on alloc error. */ int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, struct sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, uint16_t qid, uint16_t qflags); /** * Run the mesh. Run all runnable mesh states. Which can create new * runnable mesh states. Until completion. Automatically called by * mesh_report_reply and mesh_new_client as needed. * @param mesh: mesh area. * @param mstate: first mesh state to run. * @param ev: event the mstate. Others get event_pass. * @param e: if a reply, its outbound entry. */ void mesh_run(struct mesh_area* mesh, struct mesh_state* mstate, enum module_ev ev, struct outbound_entry* e); /** * Print some stats about the mesh to the log. * @param mesh: the mesh to print it for. * @param str: descriptive string to go with it. */ void mesh_stats(struct mesh_area* mesh, const char* str); /** * Clear the stats that the mesh keeps (number of queries serviced) * @param mesh: the mesh */ void mesh_stats_clear(struct mesh_area* mesh); /** * Print all the states in the mesh to the log. * @param mesh: the mesh to print all states of. */ void mesh_log_list(struct mesh_area* mesh); /** * Calculate memory size in use by mesh and all queries inside it. * @param mesh: the mesh to examine. * @return size in bytes. */ size_t mesh_get_mem(struct mesh_area* mesh); /** * Find cycle; see if the given mesh is in the targets sub, or sub-sub, ... * trees. * If the sub-sub structure is too large, it returns 'a cycle'=2. * @param qstate: given mesh querystate. * @param qinfo: query info for dependency. * @param flags: query flags of dependency. * @param prime: if dependency is a priming query or not. * @param valrec: if it is a validation recursion query (lookup of key, DS). * @return true if the name,type,class exists and the given qstate mesh exists * as a dependency of that name. Thus if qstate becomes dependent on * name,type,class then a cycle is created, this is return value 1. * Too large to search is value 2 (also true). */ int mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo, uint16_t flags, int prime, int valrec); /** compare two mesh_states */ int mesh_state_compare(const void* ap, const void* bp); /** compare two mesh references */ int mesh_state_ref_compare(const void* ap, const void* bp); /** * Make space for another recursion state for a reply in the mesh * @param mesh: mesh area * @param qbuf: query buffer to save if recursion is invoked to make space. * This buffer is necessary, because the following sequence in calls * can result in an overwrite of the incoming query: * delete_other_mesh_query - iter_clean - serviced_delete - waiting * udp query is sent - on error callback - callback sends SERVFAIL reply * over the same network channel, and shared UDP buffer is overwritten. * You can pass NULL if there is no buffer that must be backed up. * @return false if no space is available. */ int mesh_make_new_space(struct mesh_area* mesh, struct sldns_buffer* qbuf); /** * Insert mesh state into a double linked list. Inserted at end. * @param m: mesh state. * @param fp: pointer to the first-elem-pointer of the list. * @param lp: pointer to the last-elem-pointer of the list. */ void mesh_list_insert(struct mesh_state* m, struct mesh_state** fp, struct mesh_state** lp); /** * Remove mesh state from a double linked list. Remove from any position. * @param m: mesh state. * @param fp: pointer to the first-elem-pointer of the list. * @param lp: pointer to the last-elem-pointer of the list. */ void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, struct mesh_state** lp); /** * Remove mesh reply entry from the reply entry list. Searches for * the comm_point pointer. * @param mesh: to update the counters. * @param m: the mesh state. * @param cp: the comm_point to remove from the list. */ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, struct comm_point* cp); /** Callback for when the serve expired client timer has run out. Tries to * find an expired answer in the cache and reply that to the client. * @param arg: the argument passed to the callback. */ void mesh_serve_expired_callback(void* arg); /** * Try to get a (expired) cached answer. * This needs to behave like the worker's answer_from_cache() in order to have * the same behavior as when replying from cache. * @param qstate: the module qstate. * @param lookup_qinfo: the query info to look for in the cache. * @param is_expired: set if the cached answer is expired. * @return dns_msg if a cached answer was found, otherwise NULL. */ struct dns_msg* mesh_serve_expired_lookup(struct module_qstate* qstate, struct query_info* lookup_qinfo, int* is_expired); /** * See if the mesh has space for more queries. You can allocate queries * anyway, but this checks for the allocated space. * @param mesh: mesh area. * @return true if the query list is full. * It checks the number of all queries, not just number of reply states, * that have a client address. So that spawned queries count too, * that were created by the iterator, or other modules. */ int mesh_jostle_exceeded(struct mesh_area* mesh); /** * Give the serve expired responses. * @param mstate: mesh state for query that has serve_expired_data. */ void mesh_respond_serve_expired(struct mesh_state* mstate); /** * Remove callback from mesh. Removes the callback from the state. * The state itself is left to run. Searches for the pointer values. * * @param mesh: the mesh. * @param qinfo: query from client. * @param qflags: flags from client query. * @param cb: callback function. * @param cb_arg: callback user arg. */ void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, mesh_cb_func_type cb, void* cb_arg); #endif /* SERVICES_MESH_H */ unbound-1.25.1/services/listen_dnsport.c0000644000175000017500000050751015203270263017754 0ustar wouterwouter/* * services/listen_dnsport.c - listen on port 53 for incoming DNS queries. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has functions to get queries from clients. */ #include "config.h" #ifdef HAVE_SYS_TYPES_H # include #endif #include #include #ifdef USE_TCP_FASTOPEN #include #endif #include #include "services/listen_dnsport.h" #include "services/outside_network.h" #include "util/netevent.h" #include "util/log.h" #include "util/config_file.h" #include "util/net_help.h" #include "sldns/sbuffer.h" #include "sldns/parseutil.h" #include "sldns/wire2str.h" #include "services/mesh.h" #include "util/fptr_wlist.h" #include "util/locks.h" #include "util/timeval_func.h" #ifdef HAVE_NETDB_H #include #endif #include #ifdef HAVE_SYS_UN_H #include #endif #ifdef HAVE_SYSTEMD #include #endif #ifdef HAVE_IFADDRS_H #include #endif #ifdef HAVE_NET_IF_H #include #endif #ifdef HAVE_TIME_H #include #endif #include #ifdef HAVE_NGTCP2 #include #include #ifdef HAVE_NGTCP2_NGTCP2_CRYPTO_OSSL_H #include #elif defined(HAVE_NGTCP2_NGTCP2_CRYPTO_QUICTLS_H) #include #elif defined(HAVE_NGTCP2_NGTCP2_CRYPTO_OPENSSL_H) #include #define MAKE_QUIC_METHOD 1 #endif #endif #ifdef HAVE_OPENSSL_SSL_H #include #endif #ifdef HAVE_LINUX_NET_TSTAMP_H #include #endif /** number of queued TCP connections for listen() */ #define TCP_BACKLOG 256 #ifndef THREADS_DISABLED /** lock on the counter of stream buffer memory */ static lock_basic_type stream_wait_count_lock; /** lock on the counter of HTTP2 query buffer memory */ static lock_basic_type http2_query_buffer_count_lock; /** lock on the counter of HTTP2 response buffer memory */ static lock_basic_type http2_response_buffer_count_lock; #endif /** size (in bytes) of stream wait buffers */ static size_t stream_wait_count = 0; /** is the lock initialised for stream wait buffers */ static int stream_wait_lock_inited = 0; /** size (in bytes) of HTTP2 query buffers */ static size_t http2_query_buffer_count = 0; /** is the lock initialised for HTTP2 query buffers */ static int http2_query_buffer_lock_inited = 0; /** size (in bytes) of HTTP2 response buffers */ static size_t http2_response_buffer_count = 0; /** is the lock initialised for HTTP2 response buffers */ static int http2_response_buffer_lock_inited = 0; /** * Debug print of the getaddrinfo returned address. * @param addr: the address returned. * @param additional: additional text that describes the type of socket, * or NULL for no text. */ static void verbose_print_addr(struct addrinfo *addr, const char* additional) { if(verbosity >= VERB_ALGO) { char buf[100]; void* sinaddr = &((struct sockaddr_in*)addr->ai_addr)->sin_addr; #ifdef INET6 if(addr->ai_family == AF_INET6) sinaddr = &((struct sockaddr_in6*)addr->ai_addr)-> sin6_addr; #endif /* INET6 */ if(inet_ntop(addr->ai_family, sinaddr, buf, (socklen_t)sizeof(buf)) == 0) { (void)strlcpy(buf, "(null)", sizeof(buf)); } buf[sizeof(buf)-1] = 0; verbose(VERB_ALGO, "creating %s%s socket %s %d%s%s", addr->ai_socktype==SOCK_DGRAM?"udp": addr->ai_socktype==SOCK_STREAM?"tcp":"otherproto", addr->ai_family==AF_INET?"4": addr->ai_family==AF_INET6?"6": "_otherfam", buf, ntohs(((struct sockaddr_in*)addr->ai_addr)->sin_port), (additional?" ":""), (additional?additional:"")); } } void verbose_print_unbound_socket(struct unbound_socket* ub_sock) { if(verbosity >= VERB_ALGO) { char buf[256]; log_info("listing of unbound_socket structure:"); addr_to_str((void*)ub_sock->addr, ub_sock->addrlen, buf, sizeof(buf)); log_info("%s s is: %d, fam is: %s, acl: %s", buf, ub_sock->s, ub_sock->fam == AF_INET?"AF_INET":"AF_INET6", ub_sock->acl?"yes":"no"); } } #ifdef HAVE_SYSTEMD static int systemd_get_activated(int family, int socktype, int listen, struct sockaddr *addr, socklen_t addrlen, const char *path) { int i = 0; int r = 0; int s = -1; const char* listen_pid, *listen_fds; /* We should use "listen" option only for stream protocols. For UDP it should be -1 */ if((r = sd_booted()) < 1) { if(r == 0) log_warn("systemd is not running"); else log_err("systemd sd_booted(): %s", strerror(-r)); return -1; } listen_pid = getenv("LISTEN_PID"); listen_fds = getenv("LISTEN_FDS"); if (!listen_pid) { log_warn("Systemd mandatory ENV variable is not defined: LISTEN_PID"); return -1; } if (!listen_fds) { log_warn("Systemd mandatory ENV variable is not defined: LISTEN_FDS"); return -1; } if((r = sd_listen_fds(0)) < 1) { if(r == 0) log_warn("systemd: did not return socket, check unit configuration"); else log_err("systemd sd_listen_fds(): %s", strerror(-r)); return -1; } for(i = 0; i < r; i++) { if(sd_is_socket(SD_LISTEN_FDS_START + i, family, socktype, listen)) { s = SD_LISTEN_FDS_START + i; break; } } if (s == -1) { if (addr) log_err_addr("systemd sd_listen_fds()", "no such socket", (struct sockaddr_storage *)addr, addrlen); else log_err("systemd sd_listen_fds(): %s", path); } return s; } #endif int create_udp_sock(int family, int socktype, struct sockaddr* addr, socklen_t addrlen, int v6only, int* inuse, int* noproto, int rcv, int snd, int listen, int* reuseport, int transparent, int freebind, int use_systemd, int dscp) { int s; char* err; #if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) || defined(IPV6_USE_MIN_MTU) || defined(IP_TRANSPARENT) || defined(IP_BINDANY) || defined(IP_FREEBIND) || defined (SO_BINDANY) int on=1; #endif #ifdef IPV6_MTU int mtu = IPV6_MIN_MTU; #endif #if !defined(SO_RCVBUFFORCE) && !defined(SO_RCVBUF) (void)rcv; #endif #if !defined(SO_SNDBUFFORCE) && !defined(SO_SNDBUF) (void)snd; #endif #ifndef IPV6_V6ONLY (void)v6only; #endif #if !defined(IP_TRANSPARENT) && !defined(IP_BINDANY) && !defined(SO_BINDANY) (void)transparent; #endif #if !defined(IP_FREEBIND) (void)freebind; #endif #ifdef HAVE_SYSTEMD int got_fd_from_systemd = 0; if (!use_systemd || (use_systemd && (s = systemd_get_activated(family, socktype, -1, addr, addrlen, NULL)) == -1)) { #else (void)use_systemd; #endif if((s = socket(family, socktype, 0)) == -1) { *inuse = 0; #ifndef USE_WINSOCK if(errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { *noproto = 1; return -1; } #else if(WSAGetLastError() == WSAEAFNOSUPPORT || WSAGetLastError() == WSAEPROTONOSUPPORT) { *noproto = 1; return -1; } #endif log_err("can't create socket: %s", sock_strerror(errno)); *noproto = 0; return -1; } #ifdef HAVE_SYSTEMD } else { got_fd_from_systemd = 1; } #endif if(listen) { #ifdef SO_REUSEADDR if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s", sock_strerror(errno)); #ifndef USE_WINSOCK if(errno != ENOSYS) { close(s); *noproto = 0; *inuse = 0; return -1; } #else closesocket(s); *noproto = 0; *inuse = 0; return -1; #endif } #endif /* SO_REUSEADDR */ #ifdef SO_REUSEPORT # ifdef SO_REUSEPORT_LB /* on FreeBSD 12 we have SO_REUSEPORT_LB that does loadbalance * like SO_REUSEPORT on Linux. This is what the users want * with the config option in unbound.conf; if we actually * need local address and port reuse they'll also need to * have SO_REUSEPORT set for them, assume it was _LB they want. */ if (reuseport && *reuseport && setsockopt(s, SOL_SOCKET, SO_REUSEPORT_LB, (void*)&on, (socklen_t)sizeof(on)) < 0) { #ifdef ENOPROTOOPT if(errno != ENOPROTOOPT || verbosity >= 3) log_warn("setsockopt(.. SO_REUSEPORT_LB ..) failed: %s", strerror(errno)); #endif /* this option is not essential, we can continue */ *reuseport = 0; } # else /* no SO_REUSEPORT_LB */ /* try to set SO_REUSEPORT so that incoming * queries are distributed evenly among the receiving threads. * Each thread must have its own socket bound to the same port, * with SO_REUSEPORT set on each socket. */ if (reuseport && *reuseport && setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (void*)&on, (socklen_t)sizeof(on)) < 0) { #ifdef ENOPROTOOPT if(errno != ENOPROTOOPT || verbosity >= 3) log_warn("setsockopt(.. SO_REUSEPORT ..) failed: %s", strerror(errno)); #endif /* this option is not essential, we can continue */ *reuseport = 0; } # endif /* SO_REUSEPORT_LB */ #else (void)reuseport; #endif /* defined(SO_REUSEPORT) */ #ifdef IP_TRANSPARENT if (transparent && setsockopt(s, IPPROTO_IP, IP_TRANSPARENT, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP_TRANSPARENT ..) failed: %s", strerror(errno)); } #elif defined(IP_BINDANY) if (transparent && setsockopt(s, (family==AF_INET6? IPPROTO_IPV6:IPPROTO_IP), (family == AF_INET6? IPV6_BINDANY:IP_BINDANY), (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP%s_BINDANY ..) failed: %s", (family==AF_INET6?"V6":""), strerror(errno)); } #elif defined(SO_BINDANY) if (transparent && setsockopt(s, SOL_SOCKET, SO_BINDANY, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. SO_BINDANY ..) failed: %s", strerror(errno)); } #endif /* IP_TRANSPARENT || IP_BINDANY || SO_BINDANY */ } #ifdef IP_FREEBIND if(freebind && setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", strerror(errno)); } #endif /* IP_FREEBIND */ if(rcv) { #ifdef SO_RCVBUF int got; socklen_t slen = (socklen_t)sizeof(got); # ifdef SO_RCVBUFFORCE /* Linux specific: try to use root permission to override * system limits on rcvbuf. The limit is stored in * /proc/sys/net/core/rmem_max or sysctl net.core.rmem_max */ if(setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, (void*)&rcv, (socklen_t)sizeof(rcv)) < 0) { if(errno != EPERM) { log_err("setsockopt(..., SO_RCVBUFFORCE, " "...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } # endif /* SO_RCVBUFFORCE */ if(setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&rcv, (socklen_t)sizeof(rcv)) < 0) { log_err("setsockopt(..., SO_RCVBUF, " "...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } /* check if we got the right thing or if system * reduced to some system max. Warn if so */ if(getsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&got, &slen) >= 0 && got < rcv/2) { log_warn("so-rcvbuf %u was not granted. " "Got %u. To fix: start with " "root permissions(linux) or sysctl " "bigger net.core.rmem_max(linux) or " "kern.ipc.maxsockbuf(bsd) values.", (unsigned)rcv, (unsigned)got); } # ifdef SO_RCVBUFFORCE } # endif #endif /* SO_RCVBUF */ } /* first do RCVBUF as the receive buffer is more important */ if(snd) { #ifdef SO_SNDBUF int got; socklen_t slen = (socklen_t)sizeof(got); # ifdef SO_SNDBUFFORCE /* Linux specific: try to use root permission to override * system limits on sndbuf. The limit is stored in * /proc/sys/net/core/wmem_max or sysctl net.core.wmem_max */ if(setsockopt(s, SOL_SOCKET, SO_SNDBUFFORCE, (void*)&snd, (socklen_t)sizeof(snd)) < 0) { if(errno != EPERM && errno != ENOBUFS) { log_err("setsockopt(..., SO_SNDBUFFORCE, " "...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } if(errno != EPERM) { verbose(VERB_ALGO, "setsockopt(..., SO_SNDBUFFORCE, " "...) was not granted: %s", sock_strerror(errno)); } # endif /* SO_SNDBUFFORCE */ if(setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&snd, (socklen_t)sizeof(snd)) < 0) { if(errno != ENOSYS && errno != ENOBUFS) { log_err("setsockopt(..., SO_SNDBUF, " "...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } log_warn("setsockopt(..., SO_SNDBUF, " "...) was not granted: %s", sock_strerror(errno)); } /* check if we got the right thing or if system * reduced to some system max. Warn if so */ if(getsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&got, &slen) >= 0 && got < snd/2) { log_warn("so-sndbuf %u was not granted. " "Got %u. To fix: start with " "root permissions(linux) or sysctl " "bigger net.core.wmem_max(linux) or " "kern.ipc.maxsockbuf(bsd) values. or " "set so-sndbuf: 0 (use system value).", (unsigned)snd, (unsigned)got); } # ifdef SO_SNDBUFFORCE } # endif #endif /* SO_SNDBUF */ } err = set_ip_dscp(s, family, dscp); if(err != NULL) log_warn("error setting IP DiffServ codepoint %d on UDP socket: %s", dscp, err); if(family == AF_INET6) { # if defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) int omit6_set = 0; int action; # endif # if defined(IPV6_V6ONLY) if(v6only # ifdef HAVE_SYSTEMD /* Systemd wants to control if the socket is v6 only * or both, with BindIPv6Only=default, ipv6-only or * both in systemd.socket, so it is not set here. */ && !got_fd_from_systemd # endif ) { int val=(v6only==2)?0:1; if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&val, (socklen_t)sizeof(val)) < 0) { log_err("setsockopt(..., IPV6_V6ONLY" ", ...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } # endif # if defined(IPV6_USE_MIN_MTU) /* * There is no fragmentation of IPv6 datagrams * during forwarding in the network. Therefore * we do not send UDP datagrams larger than * the minimum IPv6 MTU of 1280 octets. The * EDNS0 message length can be larger if the * network stack supports IPV6_USE_MIN_MTU. */ if (setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IPV6_USE_MIN_MTU, " "...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } # elif defined(IPV6_MTU) # ifndef USE_WINSOCK /* * On Linux, to send no larger than 1280, the PMTUD is * disabled by default for datagrams anyway, so we set * the MTU to use. */ if (setsockopt(s, IPPROTO_IPV6, IPV6_MTU, (void*)&mtu, (socklen_t)sizeof(mtu)) < 0) { log_err("setsockopt(..., IPV6_MTU, ...) failed: %s", sock_strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } # elif defined(IPV6_USER_MTU) /* As later versions of the mingw crosscompiler define * IPV6_MTU, do the same for windows but use IPV6_USER_MTU * instead which is writable; IPV6_MTU is readonly there. */ if (setsockopt(s, IPPROTO_IPV6, IPV6_USER_MTU, (void*)&mtu, (socklen_t)sizeof(mtu)) < 0) { if (WSAGetLastError() != WSAENOPROTOOPT) { log_err("setsockopt(..., IPV6_USER_MTU, ...) failed: %s", wsa_strerror(WSAGetLastError())); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } # endif /* USE_WINSOCK */ # endif /* IPv6 MTU */ # if defined(IPV6_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) # if defined(IP_PMTUDISC_OMIT) action = IP_PMTUDISC_OMIT; if (setsockopt(s, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &action, (socklen_t)sizeof(action)) < 0) { if (errno != EINVAL) { log_err("setsockopt(..., IPV6_MTU_DISCOVER, IP_PMTUDISC_OMIT...) failed: %s", strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } else { omit6_set = 1; } # endif if (omit6_set == 0) { action = IP_PMTUDISC_DONT; if (setsockopt(s, IPPROTO_IPV6, IPV6_MTU_DISCOVER, &action, (socklen_t)sizeof(action)) < 0) { log_err("setsockopt(..., IPV6_MTU_DISCOVER, IP_PMTUDISC_DONT...) failed: %s", strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } # endif /* IPV6_MTU_DISCOVER */ } else if(family == AF_INET) { # if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) /* linux 3.15 has IP_PMTUDISC_OMIT, Hannes Frederic Sowa made it so that * PMTU information is not accepted, but fragmentation is allowed * if and only if the packet size exceeds the outgoing interface MTU * (and also uses the interface mtu to determine the size of the packets). * So there won't be any EMSGSIZE error. Against DNS fragmentation attacks. * FreeBSD already has same semantics without setting the option. */ int omit_set = 0; int action; # if defined(IP_PMTUDISC_OMIT) action = IP_PMTUDISC_OMIT; if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action, (socklen_t)sizeof(action)) < 0) { if (errno != EINVAL) { log_err("setsockopt(..., IP_MTU_DISCOVER, IP_PMTUDISC_OMIT...) failed: %s", strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } else { omit_set = 1; } # endif if (omit_set == 0) { action = IP_PMTUDISC_DONT; if (setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action, (socklen_t)sizeof(action)) < 0) { log_err("setsockopt(..., IP_MTU_DISCOVER, IP_PMTUDISC_DONT...) failed: %s", strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } } # elif defined(IP_DONTFRAG) && !defined(__APPLE__) /* the IP_DONTFRAG option if defined in the 11.0 OSX headers, * but does not work on that version, so we exclude it */ /* a nonzero value disables fragmentation, according to * docs.oracle.com for ip(4). */ int off = 1; if (setsockopt(s, IPPROTO_IP, IP_DONTFRAG, &off, (socklen_t)sizeof(off)) < 0) { log_err("setsockopt(..., IP_DONTFRAG, ...) failed: %s", strerror(errno)); sock_close(s); *noproto = 0; *inuse = 0; return -1; } # endif /* IPv4 MTU */ } if( #ifdef HAVE_SYSTEMD !got_fd_from_systemd && #endif bind(s, (struct sockaddr*)addr, addrlen) != 0) { *noproto = 0; *inuse = 0; #ifndef USE_WINSOCK #ifdef EADDRINUSE *inuse = (errno == EADDRINUSE); /* detect freebsd jail with no ipv6 permission */ if(family==AF_INET6 && errno==EINVAL) *noproto = 1; else if(errno != EADDRINUSE && !(errno == EACCES && verbosity < 4 && !listen) #ifdef EADDRNOTAVAIL && !(errno == EADDRNOTAVAIL && verbosity < 4 && !listen) #endif ) { log_err_addr("can't bind socket", strerror(errno), (struct sockaddr_storage*)addr, addrlen); } #endif /* EADDRINUSE */ #else /* USE_WINSOCK */ if(WSAGetLastError() != WSAEADDRINUSE && WSAGetLastError() != WSAEADDRNOTAVAIL && !(WSAGetLastError() == WSAEACCES && verbosity < 4 && !listen)) { log_err_addr("can't bind socket", wsa_strerror(WSAGetLastError()), (struct sockaddr_storage*)addr, addrlen); } #endif /* USE_WINSOCK */ sock_close(s); return -1; } if(!fd_set_nonblock(s)) { *noproto = 0; *inuse = 0; sock_close(s); return -1; } return s; } int create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, int* reuseport, int transparent, int mss, int nodelay, int freebind, int use_systemd, int dscp, const char* additional) { int s = -1; char* err; #if defined(SO_REUSEADDR) || defined(SO_REUSEPORT) \ || defined(IPV6_V6ONLY) || defined(IP_TRANSPARENT) \ || defined(IP_BINDANY) || defined(IP_FREEBIND) \ || defined(SO_BINDANY) || defined(TCP_NODELAY) int on = 1; #endif #ifdef HAVE_SYSTEMD int got_fd_from_systemd = 0; #endif #ifdef USE_TCP_FASTOPEN int qlen; #endif #if !defined(IP_TRANSPARENT) && !defined(IP_BINDANY) && !defined(SO_BINDANY) (void)transparent; #endif #if !defined(IP_FREEBIND) (void)freebind; #endif verbose_print_addr(addr, additional); *noproto = 0; #ifdef HAVE_SYSTEMD if (!use_systemd || (use_systemd && (s = systemd_get_activated(addr->ai_family, addr->ai_socktype, 1, addr->ai_addr, addr->ai_addrlen, NULL)) == -1)) { #else (void)use_systemd; #endif if((s = socket(addr->ai_family, addr->ai_socktype, 0)) == -1) { #ifndef USE_WINSOCK if(errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT) { *noproto = 1; return -1; } #else if(WSAGetLastError() == WSAEAFNOSUPPORT || WSAGetLastError() == WSAEPROTONOSUPPORT) { *noproto = 1; return -1; } #endif log_err("can't create socket: %s", sock_strerror(errno)); return -1; } if(nodelay) { #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void*)&on, (socklen_t)sizeof(on)) < 0) { #ifndef USE_WINSOCK log_err(" setsockopt(.. TCP_NODELAY ..) failed: %s", strerror(errno)); #else log_err(" setsockopt(.. TCP_NODELAY ..) failed: %s", wsa_strerror(WSAGetLastError())); #endif } #else log_warn(" setsockopt(TCP_NODELAY) unsupported"); #endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */ } if (mss > 0) { #if defined(IPPROTO_TCP) && defined(TCP_MAXSEG) if(setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, (void*)&mss, (socklen_t)sizeof(mss)) < 0) { log_err(" setsockopt(.. TCP_MAXSEG ..) failed: %s", sock_strerror(errno)); } else { verbose(VERB_ALGO, " tcp socket mss set to %d", mss); } #else log_warn(" setsockopt(TCP_MAXSEG) unsupported"); #endif /* defined(IPPROTO_TCP) && defined(TCP_MAXSEG) */ } #ifdef HAVE_SYSTEMD } else { got_fd_from_systemd = 1; } #endif #ifdef SO_REUSEADDR if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(.. SO_REUSEADDR ..) failed: %s", sock_strerror(errno)); sock_close(s); return -1; } #endif /* SO_REUSEADDR */ #ifdef IP_FREEBIND if (freebind && setsockopt(s, IPPROTO_IP, IP_FREEBIND, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP_FREEBIND ..) failed: %s", strerror(errno)); } #endif /* IP_FREEBIND */ #ifdef SO_REUSEPORT /* try to set SO_REUSEPORT so that incoming * connections are distributed evenly among the receiving threads. * Each thread must have its own socket bound to the same port, * with SO_REUSEPORT set on each socket. */ if (reuseport && *reuseport && setsockopt(s, SOL_SOCKET, SO_REUSEPORT, (void*)&on, (socklen_t)sizeof(on)) < 0) { #ifdef ENOPROTOOPT if(errno != ENOPROTOOPT || verbosity >= 3) log_warn("setsockopt(.. SO_REUSEPORT ..) failed: %s", strerror(errno)); #endif /* this option is not essential, we can continue */ *reuseport = 0; } #else (void)reuseport; #endif /* defined(SO_REUSEPORT) */ #if defined(IPV6_V6ONLY) if(addr->ai_family == AF_INET6 && v6only # ifdef HAVE_SYSTEMD /* Systemd wants to control if the socket is v6 only * or both, with BindIPv6Only=default, ipv6-only or * both in systemd.socket, so it is not set here. */ && !got_fd_from_systemd # endif ) { if(setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IPV6_V6ONLY, ...) failed: %s", sock_strerror(errno)); sock_close(s); return -1; } } #else (void)v6only; #endif /* IPV6_V6ONLY */ #ifdef IP_TRANSPARENT if (transparent && setsockopt(s, IPPROTO_IP, IP_TRANSPARENT, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP_TRANSPARENT ..) failed: %s", strerror(errno)); } #elif defined(IP_BINDANY) if (transparent && setsockopt(s, (addr->ai_family==AF_INET6? IPPROTO_IPV6:IPPROTO_IP), (addr->ai_family == AF_INET6? IPV6_BINDANY:IP_BINDANY), (void*)&on, (socklen_t)sizeof(on)) < 0) { log_warn("setsockopt(.. IP%s_BINDANY ..) failed: %s", (addr->ai_family==AF_INET6?"V6":""), strerror(errno)); } #elif defined(SO_BINDANY) if (transparent && setsockopt(s, SOL_SOCKET, SO_BINDANY, (void*)&on, (socklen_t) sizeof(on)) < 0) { log_warn("setsockopt(.. SO_BINDANY ..) failed: %s", strerror(errno)); } #endif /* IP_TRANSPARENT || IP_BINDANY || SO_BINDANY */ err = set_ip_dscp(s, addr->ai_family, dscp); if(err != NULL) log_warn("error setting IP DiffServ codepoint %d on TCP socket: %s", dscp, err); if( #ifdef HAVE_SYSTEMD !got_fd_from_systemd && #endif bind(s, addr->ai_addr, addr->ai_addrlen) != 0) { #ifndef USE_WINSOCK /* detect freebsd jail with no ipv6 permission */ if(addr->ai_family==AF_INET6 && errno==EINVAL) *noproto = 1; else { log_err_addr("can't bind socket", strerror(errno), (struct sockaddr_storage*)addr->ai_addr, addr->ai_addrlen); } #else log_err_addr("can't bind socket", wsa_strerror(WSAGetLastError()), (struct sockaddr_storage*)addr->ai_addr, addr->ai_addrlen); #endif sock_close(s); return -1; } if(!fd_set_nonblock(s)) { sock_close(s); return -1; } if(listen(s, TCP_BACKLOG) == -1) { log_err("can't listen: %s", sock_strerror(errno)); sock_close(s); return -1; } #ifdef USE_TCP_FASTOPEN /* qlen specifies how many outstanding TFO requests to allow. Limit is a defense against IP spoofing attacks as suggested in RFC7413 */ #ifdef __APPLE__ /* OS X implementation only supports qlen of 1 via this call. Actual value is configured by the net.inet.tcp.fastopen_backlog kernel param. */ qlen = 1; #else /* 5 is recommended on linux */ qlen = 5; #endif if ((setsockopt(s, IPPROTO_TCP, TCP_FASTOPEN, &qlen, sizeof(qlen))) == -1 ) { #ifdef ENOPROTOOPT /* squelch ENOPROTOOPT: freebsd server mode with kernel support disabled, except when verbosity enabled for debugging */ if(errno != ENOPROTOOPT || verbosity >= 3) { #endif if(errno == EPERM) { log_warn("Setting TCP Fast Open as server failed: %s ; this could likely be because sysctl net.inet.tcp.fastopen.enabled, net.inet.tcp.fastopen.server_enable, or net.ipv4.tcp_fastopen is disabled", strerror(errno)); } else { log_err("Setting TCP Fast Open as server failed: %s", strerror(errno)); } #ifdef ENOPROTOOPT } #endif } #endif return s; } char* set_ip_dscp(int socket, int addrfamily, int dscp) { int ds; if(dscp == 0) return NULL; ds = dscp << 2; switch(addrfamily) { case AF_INET6: #ifdef IPV6_TCLASS if(setsockopt(socket, IPPROTO_IPV6, IPV6_TCLASS, (void*)&ds, sizeof(ds)) < 0) return sock_strerror(errno); break; #else return "IPV6_TCLASS not defined on this system"; #endif default: if(setsockopt(socket, IPPROTO_IP, IP_TOS, (void*)&ds, sizeof(ds)) < 0) return sock_strerror(errno); break; } return NULL; } int create_local_accept_sock(const char *path, int* noproto, int use_systemd) { #ifdef HAVE_SYSTEMD int ret; if (use_systemd && (ret = systemd_get_activated(AF_LOCAL, SOCK_STREAM, 1, NULL, 0, path)) != -1) return ret; else { #endif #ifdef HAVE_SYS_UN_H int s; struct sockaddr_un usock; #ifndef HAVE_SYSTEMD (void)use_systemd; #endif verbose(VERB_ALGO, "creating unix socket %s", path); #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN /* this member exists on BSDs, not Linux */ usock.sun_len = (unsigned)sizeof(usock); #endif usock.sun_family = AF_LOCAL; /* length is 92-108, 104 on FreeBSD */ (void)strlcpy(usock.sun_path, path, sizeof(usock.sun_path)); if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) == -1) { log_err("Cannot create local socket %s (%s)", path, strerror(errno)); return -1; } if (unlink(path) && errno != ENOENT) { /* The socket already exists and cannot be removed */ log_err("Cannot remove old local socket %s (%s)", path, strerror(errno)); goto err; } if (bind(s, (struct sockaddr *)&usock, (socklen_t)sizeof(struct sockaddr_un)) == -1) { log_err("Cannot bind local socket %s (%s)", path, strerror(errno)); goto err; } if (!fd_set_nonblock(s)) { log_err("Cannot set non-blocking mode"); goto err; } if (listen(s, TCP_BACKLOG) == -1) { log_err("can't listen: %s", strerror(errno)); goto err; } (void)noproto; /*unused*/ return s; err: sock_close(s); return -1; #ifdef HAVE_SYSTEMD } #endif #else (void)use_systemd; (void)path; log_err("Local sockets are not supported"); *noproto = 1; return -1; #endif } /** * Create socket from getaddrinfo results */ static int make_sock(int stype, const char* ifname, int port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind, int use_systemd, int dscp, struct unbound_socket* ub_sock, const char* additional) { struct addrinfo *res = NULL; int r, s, inuse, noproto; char portbuf[32]; snprintf(portbuf, sizeof(portbuf), "%d", port); hints->ai_socktype = stype; *noip6 = 0; if((r=getaddrinfo(ifname, portbuf, hints, &res)) != 0 || !res) { #ifdef USE_WINSOCK if(r == EAI_NONAME && hints->ai_family == AF_INET6){ *noip6 = 1; /* 'Host not found' for IP6 on winXP */ return -1; } #endif log_err("node %s:%s getaddrinfo: %s %s", ifname?ifname:"default", portbuf, gai_strerror(r), #ifdef EAI_SYSTEM (r==EAI_SYSTEM?(char*)strerror(errno):"") #else "" #endif ); return -1; } if(stype == SOCK_DGRAM) { verbose_print_addr(res, additional); s = create_udp_sock(res->ai_family, res->ai_socktype, (struct sockaddr*)res->ai_addr, res->ai_addrlen, v6only, &inuse, &noproto, (int)rcv, (int)snd, 1, reuseport, transparent, freebind, use_systemd, dscp); if(s == -1 && inuse) { log_err("bind: address already in use"); } else if(s == -1 && noproto && hints->ai_family == AF_INET6){ *noip6 = 1; } } else { s = create_tcp_accept_sock(res, v6only, &noproto, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, additional); if(s == -1 && noproto && hints->ai_family == AF_INET6){ *noip6 = 1; } } if(!res->ai_addr) { log_err("getaddrinfo returned no address"); freeaddrinfo(res); sock_close(s); return -1; } ub_sock->addr = memdup(res->ai_addr, res->ai_addrlen); ub_sock->addrlen = res->ai_addrlen; if(!ub_sock->addr) { log_err("out of memory: allocate listening address"); freeaddrinfo(res); sock_close(s); return -1; } freeaddrinfo(res); ub_sock->s = s; ub_sock->fam = hints->ai_family; ub_sock->acl = NULL; return s; } /** make socket and first see if ifname contains port override info */ static int make_sock_port(int stype, const char* ifname, int port, struct addrinfo *hints, int v6only, int* noip6, size_t rcv, size_t snd, int* reuseport, int transparent, int tcp_mss, int nodelay, int freebind, int use_systemd, int dscp, struct unbound_socket* ub_sock, const char* additional) { char* s = strchr(ifname, '@'); if(s) { /* override port with ifspec@port */ int port; char newif[128]; if((size_t)(s-ifname) >= sizeof(newif)) { log_err("ifname too long: %s", ifname); *noip6 = 0; return -1; } port = atoi(s+1); if(port < 0 || 0 == port || port > 65535) { log_err("invalid portnumber in interface: %s", ifname); *noip6 = 0; return -1; } (void)strlcpy(newif, ifname, sizeof(newif)); newif[s-ifname] = 0; return make_sock(stype, newif, port, hints, v6only, noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, additional); } return make_sock(stype, ifname, port, hints, v6only, noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, additional); } /** * Add port to open ports list. * @param list: list head. changed. * @param s: fd. * @param ftype: if fd is UDP. * @param pp2_enabled: if PROXYv2 is enabled for this port. * @param ub_sock: socket with address. * @return false on failure. list in unchanged then. */ static int port_insert(struct listen_port** list, int s, enum listen_type ftype, int pp2_enabled, struct unbound_socket* ub_sock) { struct listen_port* item = (struct listen_port*)malloc( sizeof(struct listen_port)); if(!item) return 0; item->next = *list; item->fd = s; item->ftype = ftype; item->pp2_enabled = pp2_enabled; item->socket = ub_sock; *list = item; return 1; } /** set fd to receive software timestamps */ static int set_recvtimestamp(int s) { #ifdef HAVE_LINUX_NET_TSTAMP_H int opt = SOF_TIMESTAMPING_RX_SOFTWARE | SOF_TIMESTAMPING_SOFTWARE; if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMPNS, (void*)&opt, (socklen_t)sizeof(opt)) < 0) { log_err("setsockopt(..., SO_TIMESTAMPNS, ...) failed: %s", strerror(errno)); return 0; } return 1; #elif defined(SO_TIMESTAMP) && defined(SCM_TIMESTAMP) int on = 1; /* FreeBSD and also Linux. */ if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., SO_TIMESTAMP, ...) failed: %s", strerror(errno)); return 0; } return 1; #else log_err("packets timestamping is not supported on this platform"); (void)s; return 0; #endif } /** set fd to receive source address packet info */ static int set_recvpktinfo(int s, int family) { #if defined(IPV6_RECVPKTINFO) || defined(IPV6_PKTINFO) || (defined(IP_RECVDSTADDR) && defined(IP_SENDSRCADDR)) || defined(IP_PKTINFO) int on = 1; #else (void)s; #endif if(family == AF_INET6) { # ifdef IPV6_RECVPKTINFO if(setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IPV6_RECVPKTINFO, ...) failed: %s", strerror(errno)); return 0; } # elif defined(IPV6_PKTINFO) if(setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IPV6_PKTINFO, ...) failed: %s", strerror(errno)); return 0; } # else log_err("no IPV6_RECVPKTINFO and IPV6_PKTINFO options, please " "disable interface-automatic or do-ip6 in config"); return 0; # endif /* defined IPV6_RECVPKTINFO */ } else if(family == AF_INET) { # ifdef IP_PKTINFO if(setsockopt(s, IPPROTO_IP, IP_PKTINFO, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IP_PKTINFO, ...) failed: %s", strerror(errno)); return 0; } # elif defined(IP_RECVDSTADDR) && defined(IP_SENDSRCADDR) if(setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, (void*)&on, (socklen_t)sizeof(on)) < 0) { log_err("setsockopt(..., IP_RECVDSTADDR, ...) failed: %s", strerror(errno)); return 0; } # else log_err("no IP_SENDSRCADDR or IP_PKTINFO option, please disable " "interface-automatic or do-ip4 in config"); return 0; # endif /* IP_PKTINFO */ } return 1; } /** * Helper for ports_open. Creates one interface (or NULL for default). * @param ifname: The interface ip address. * @param do_auto: use automatic interface detection. * If enabled, then ifname must be the wildcard name. * @param do_udp: if udp should be used. * @param do_tcp: if tcp should be used. * @param hints: for getaddrinfo. family and flags have to be set by caller. * @param port: Port number to use. * @param list: list of open ports, appended to, changed to point to list head. * @param rcv: receive buffer size for UDP * @param snd: send buffer size for UDP * @param ssl_port: ssl service port number * @param tls_additional_port: list of additional ssl service port numbers. * @param https_port: DoH service port number * @param proxy_protocol_port: list of PROXYv2 port numbers. * @param reuseport: try to set SO_REUSEPORT if nonNULL and true. * set to false on exit if reuseport failed due to no kernel support. * @param transparent: set IP_TRANSPARENT socket option. * @param tcp_mss: maximum segment size of tcp socket. default if zero. * @param freebind: set IP_FREEBIND socket option. * @param http2_nodelay: set TCP_NODELAY on HTTP/2 connection * @param use_systemd: if true, fetch sockets from systemd. * @param dnscrypt_port: dnscrypt service port number * @param dscp: DSCP to use. * @param quic_port: dns over quic port number. * @param http_notls_downstream: if no tls is used for https downstream. * @param sock_queue_timeout: the sock_queue_timeout from config. Seconds to * wait to discard if UDP packets have waited for long in the socket * buffer. * @return: returns false on error. */ static int ports_create_if(const char* ifname, int do_auto, int do_udp, int do_tcp, struct addrinfo *hints, int port, struct listen_port** list, size_t rcv, size_t snd, int ssl_port, struct config_strlist* tls_additional_port, int https_port, struct config_strlist* proxy_protocol_port, int* reuseport, int transparent, int tcp_mss, int freebind, int http2_nodelay, int use_systemd, int dnscrypt_port, int dscp, int quic_port, int http_notls_downstream, int sock_queue_timeout) { int s, noip6=0; int is_ssl = if_is_ssl(ifname, port, ssl_port, tls_additional_port); int is_https = if_is_https(ifname, port, https_port); int is_dnscrypt = if_is_dnscrypt(ifname, port, dnscrypt_port); int is_pp2 = if_is_pp2(ifname, port, proxy_protocol_port); int is_doq = if_is_quic(ifname, port, quic_port); /* Always set TCP_NODELAY on TLS connection as it speeds up the TLS * handshake. DoH had already such option so we respect it. * Otherwise the server waits before sending more handshake data for * the client ACK (Nagle's algorithm), which is delayed because the * client waits for more data before ACKing (delayed ACK). */ int nodelay = is_https?http2_nodelay:is_ssl; struct unbound_socket* ub_sock; const char* add = NULL; if(!do_udp && !do_tcp) return 0; if(is_pp2) { if(is_dnscrypt) { fatal_exit("PROXYv2 and DNSCrypt combination not " "supported!"); } else if(is_https) { fatal_exit("PROXYv2 and DoH combination not " "supported!"); } else if(is_doq) { fatal_exit("PROXYv2 and DoQ combination not " "supported!"); } } /* Check if both UDP and TCP ports should be open. * In the case of encrypted channels, probably an unencrypted channel * at the same port is not desired. */ if((is_ssl || is_https) && !is_doq) do_udp = do_auto = 0; if((is_doq) && !(is_https || is_ssl)) do_tcp = 0; if(do_auto) { ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) return 0; if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1, &noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, (is_dnscrypt?"udpancil_dnscrypt":"udpancil"))) == -1) { free(ub_sock->addr); free(ub_sock); if(noip6) { log_warn("IPv6 protocol not available"); return 1; } return 0; } /* getting source addr packet info is highly non-portable */ if(!set_recvpktinfo(s, hints->ai_family)) { sock_close(s); free(ub_sock->addr); free(ub_sock); return 0; } if (sock_queue_timeout && !set_recvtimestamp(s)) { log_warn("socket timestamping is not available"); } if(!port_insert(list, s, is_dnscrypt ?listen_type_udpancil_dnscrypt:listen_type_udpancil, is_pp2, ub_sock)) { sock_close(s); free(ub_sock->addr); free(ub_sock); return 0; } } else if(do_udp) { enum listen_type udp_port_type; ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) return 0; if(is_dnscrypt) { udp_port_type = listen_type_udp_dnscrypt; add = "dnscrypt"; } else if(is_doq) { udp_port_type = listen_type_doq; add = "doq"; if(if_listens_on(ifname, port, 53, NULL)) { log_err("DNS over QUIC is strictly not " "allowed on port 53 as per RFC 9250. " "Port 53 is for DNS datagrams. Error " "for interface '%s'.", ifname); free(ub_sock->addr); free(ub_sock); return 0; } } else { udp_port_type = listen_type_udp; add = NULL; } /* regular udp socket */ if((s = make_sock_port(SOCK_DGRAM, ifname, port, hints, 1, &noip6, rcv, snd, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, add)) == -1) { free(ub_sock->addr); free(ub_sock); if(noip6) { log_warn("IPv6 protocol not available"); return 1; } return 0; } if(udp_port_type == listen_type_doq) { if(!set_recvpktinfo(s, hints->ai_family)) { sock_close(s); free(ub_sock->addr); free(ub_sock); return 0; } } if(udp_port_type == listen_type_udp && sock_queue_timeout) udp_port_type = listen_type_udpancil; if (sock_queue_timeout) { if(!set_recvtimestamp(s)) { log_warn("socket timestamping is not available"); } else { if(udp_port_type == listen_type_udp) udp_port_type = listen_type_udpancil; } } if(!port_insert(list, s, udp_port_type, is_pp2, ub_sock)) { sock_close(s); free(ub_sock->addr); free(ub_sock); return 0; } } if(do_tcp) { enum listen_type port_type; ub_sock = calloc(1, sizeof(struct unbound_socket)); if(!ub_sock) return 0; if(is_ssl) { port_type = listen_type_ssl; add = "tls"; } else if(is_https) { port_type = listen_type_http; add = "https"; if(http_notls_downstream) add = "http"; } else if(is_dnscrypt) { port_type = listen_type_tcp_dnscrypt; add = "dnscrypt"; } else { port_type = listen_type_tcp; add = NULL; } if((s = make_sock_port(SOCK_STREAM, ifname, port, hints, 1, &noip6, 0, 0, reuseport, transparent, tcp_mss, nodelay, freebind, use_systemd, dscp, ub_sock, add)) == -1) { free(ub_sock->addr); free(ub_sock); if(noip6) { /*log_warn("IPv6 protocol not available");*/ return 1; } return 0; } if(is_ssl) verbose(VERB_ALGO, "setup TCP for SSL service"); if(!port_insert(list, s, port_type, is_pp2, ub_sock)) { sock_close(s); free(ub_sock->addr); free(ub_sock); return 0; } } return 1; } /** * Add items to commpoint list in front. * @param c: commpoint to add. * @param front: listen struct. * @return: false on failure. */ static int listen_cp_insert(struct comm_point* c, struct listen_dnsport* front) { struct listen_list* item = (struct listen_list*)malloc( sizeof(struct listen_list)); if(!item) return 0; item->com = c; item->next = front->cps; front->cps = item; return 1; } void listen_setup_locks(void) { if(!stream_wait_lock_inited) { lock_basic_init(&stream_wait_count_lock); stream_wait_lock_inited = 1; } if(!http2_query_buffer_lock_inited) { lock_basic_init(&http2_query_buffer_count_lock); http2_query_buffer_lock_inited = 1; } if(!http2_response_buffer_lock_inited) { lock_basic_init(&http2_response_buffer_count_lock); http2_response_buffer_lock_inited = 1; } } void listen_desetup_locks(void) { if(stream_wait_lock_inited) { stream_wait_lock_inited = 0; lock_basic_destroy(&stream_wait_count_lock); } if(http2_query_buffer_lock_inited) { http2_query_buffer_lock_inited = 0; lock_basic_destroy(&http2_query_buffer_count_lock); } if(http2_response_buffer_lock_inited) { http2_response_buffer_lock_inited = 0; lock_basic_destroy(&http2_response_buffer_count_lock); } } struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, void* dot_sslctx, void* doh_sslctx, void* quic_sslctx, struct dt_env* dtenv, struct doq_table* doq_table, struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg) { struct listen_dnsport* front = (struct listen_dnsport*) malloc(sizeof(struct listen_dnsport)); if(!front) return NULL; front->cps = NULL; front->udp_buff = sldns_buffer_new(bufsize); #ifdef USE_DNSCRYPT front->dnscrypt_udp_buff = NULL; #endif if(!front->udp_buff) { free(front); return NULL; } /* create comm points as needed */ while(ports) { struct comm_point* cp = NULL; if(ports->ftype == listen_type_udp || ports->ftype == listen_type_udp_dnscrypt) { cp = comm_point_create_udp(base, ports->fd, front->udp_buff, ports->pp2_enabled, cb, cb_arg, ports->socket); } else if(ports->ftype == listen_type_doq && doq_table) { #ifndef HAVE_NGTCP2 log_warn("Unbound is not compiled with " "ngtcp2. This is required to use DNS " "over QUIC."); #endif cp = comm_point_create_doq(base, ports->fd, front->udp_buff, cb, cb_arg, ports->socket, doq_table, rnd, quic_sslctx, cfg); } else if(ports->ftype == listen_type_tcp || ports->ftype == listen_type_tcp_dnscrypt) { cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, tcp_idle_timeout, harden_large_queries, 0, NULL, tcp_conn_limit, bufsize, front->udp_buff, ports->ftype, ports->pp2_enabled, cb, cb_arg, ports->socket); } else if(ports->ftype == listen_type_ssl || ports->ftype == listen_type_http) { cp = comm_point_create_tcp(base, ports->fd, tcp_accept_count, tcp_idle_timeout, harden_large_queries, http_max_streams, http_endpoint, tcp_conn_limit, bufsize, front->udp_buff, ports->ftype, ports->pp2_enabled, cb, cb_arg, ports->socket); if(ports->ftype == listen_type_http) { if(!doh_sslctx && !http_notls) { log_warn("HTTPS port configured, but " "no TLS tls-service-key or " "tls-service-pem set"); } #ifndef HAVE_SSL_CTX_SET_ALPN_SELECT_CB if(!http_notls) { log_warn("Unbound is not compiled " "with an OpenSSL version " "supporting ALPN " "(OpenSSL >= 1.0.2). This " "is required to use " "DNS-over-HTTPS"); } #endif #ifndef HAVE_NGHTTP2_NGHTTP2_H log_warn("Unbound is not compiled with " "nghttp2. This is required to use " "DNS-over-HTTPS."); #endif } } else if(ports->ftype == listen_type_udpancil || ports->ftype == listen_type_udpancil_dnscrypt) { #if defined(AF_INET6) && defined(IPV6_PKTINFO) && defined(HAVE_RECVMSG) cp = comm_point_create_udp_ancil(base, ports->fd, front->udp_buff, ports->pp2_enabled, cb, cb_arg, ports->socket); #else log_warn("This system does not support UDP ancillary data."); #endif } if(!cp) { log_err("can't create commpoint"); listen_delete(front); return NULL; } if((http_notls && ports->ftype == listen_type_http) || (ports->ftype == listen_type_tcp) || (ports->ftype == listen_type_udp) || (ports->ftype == listen_type_udpancil) || (ports->ftype == listen_type_tcp_dnscrypt) || (ports->ftype == listen_type_udp_dnscrypt) || (ports->ftype == listen_type_udpancil_dnscrypt)) { cp->ssl = NULL; } else if(ports->ftype == listen_type_doq) { cp->ssl = quic_sslctx; } else if(ports->ftype == listen_type_http) { cp->ssl = doh_sslctx; } else { cp->ssl = dot_sslctx; } cp->dtenv = dtenv; cp->do_not_close = 1; #ifdef USE_DNSCRYPT if (ports->ftype == listen_type_udp_dnscrypt || ports->ftype == listen_type_tcp_dnscrypt || ports->ftype == listen_type_udpancil_dnscrypt) { cp->dnscrypt = 1; cp->dnscrypt_buffer = sldns_buffer_new(bufsize); if(!cp->dnscrypt_buffer) { log_err("can't alloc dnscrypt_buffer"); comm_point_delete(cp); listen_delete(front); return NULL; } front->dnscrypt_udp_buff = cp->dnscrypt_buffer; } #endif if(!listen_cp_insert(cp, front)) { log_err("malloc failed"); comm_point_delete(cp); listen_delete(front); return NULL; } ports = ports->next; } if(!front->cps) { log_err("Could not open sockets to accept queries."); listen_delete(front); return NULL; } return front; } void listen_list_delete(struct listen_list* list) { struct listen_list *p = list, *pn; while(p) { pn = p->next; comm_point_delete(p->com); free(p); p = pn; } } void listen_delete(struct listen_dnsport* front) { if(!front) return; listen_list_delete(front->cps); #ifdef USE_DNSCRYPT if(front->dnscrypt_udp_buff && front->udp_buff != front->dnscrypt_udp_buff) { sldns_buffer_free(front->dnscrypt_udp_buff); } #endif sldns_buffer_free(front->udp_buff); free(front); } #ifdef HAVE_GETIFADDRS static int resolve_ifa_name(struct ifaddrs *ifas, const char *search_ifa, char ***ip_addresses, int *ip_addresses_size) { struct ifaddrs *ifa; void *tmpbuf; int last_ip_addresses_size = *ip_addresses_size; for(ifa = ifas; ifa != NULL; ifa = ifa->ifa_next) { sa_family_t family; const char* atsign; #ifdef INET6 /* | address ip | % | ifa name | @ | port | nul */ char addr_buf[INET6_ADDRSTRLEN + 1 + IF_NAMESIZE + 1 + 16 + 1]; #else char addr_buf[INET_ADDRSTRLEN + 1 + 16 + 1]; #endif if((atsign=strrchr(search_ifa, '@')) != NULL) { if(strlen(ifa->ifa_name) != (size_t)(atsign-search_ifa) || strncmp(ifa->ifa_name, search_ifa, atsign-search_ifa) != 0) continue; } else { if(strcmp(ifa->ifa_name, search_ifa) != 0) continue; atsign = ""; } if(ifa->ifa_addr == NULL) continue; family = ifa->ifa_addr->sa_family; if(family == AF_INET) { char a4[INET_ADDRSTRLEN + 1]; struct sockaddr_in *in4 = (struct sockaddr_in *) ifa->ifa_addr; if(!inet_ntop(family, &in4->sin_addr, a4, sizeof(a4))) { log_err("inet_ntop failed"); return 0; } snprintf(addr_buf, sizeof(addr_buf), "%s%s", a4, atsign); } #ifdef INET6 else if(family == AF_INET6) { struct sockaddr_in6 *in6 = (struct sockaddr_in6 *) ifa->ifa_addr; char a6[INET6_ADDRSTRLEN + 1]; char if_index_name[IF_NAMESIZE + 1]; if_index_name[0] = 0; if(!inet_ntop(family, &in6->sin6_addr, a6, sizeof(a6))) { log_err("inet_ntop failed"); return 0; } (void)if_indextoname(in6->sin6_scope_id, (char *)if_index_name); if (strlen(if_index_name) != 0) { snprintf(addr_buf, sizeof(addr_buf), "%s%%%s%s", a6, if_index_name, atsign); } else { snprintf(addr_buf, sizeof(addr_buf), "%s%s", a6, atsign); } } #endif else { continue; } verbose(4, "interface %s has address %s", search_ifa, addr_buf); tmpbuf = realloc(*ip_addresses, sizeof(char *) * (*ip_addresses_size + 1)); if(!tmpbuf) { log_err("realloc failed: out of memory"); return 0; } else { *ip_addresses = tmpbuf; } (*ip_addresses)[*ip_addresses_size] = strdup(addr_buf); if(!(*ip_addresses)[*ip_addresses_size]) { log_err("strdup failed: out of memory"); return 0; } (*ip_addresses_size)++; } if (*ip_addresses_size == last_ip_addresses_size) { tmpbuf = realloc(*ip_addresses, sizeof(char *) * (*ip_addresses_size + 1)); if(!tmpbuf) { log_err("realloc failed: out of memory"); return 0; } else { *ip_addresses = tmpbuf; } (*ip_addresses)[*ip_addresses_size] = strdup(search_ifa); if(!(*ip_addresses)[*ip_addresses_size]) { log_err("strdup failed: out of memory"); return 0; } (*ip_addresses_size)++; } return 1; } #endif /* HAVE_GETIFADDRS */ int resolve_interface_names(char** ifs, int num_ifs, struct config_strlist* list, char*** resif, int* num_resif) { #ifdef HAVE_GETIFADDRS struct ifaddrs *addrs = NULL; if(num_ifs == 0 && list == NULL) { *resif = NULL; *num_resif = 0; return 1; } if(getifaddrs(&addrs) == -1) { log_err("failed to list interfaces: getifaddrs: %s", strerror(errno)); freeifaddrs(addrs); return 0; } if(ifs) { int i; for(i=0; inext) { if(!resolve_ifa_name(addrs, p->str, resif, num_resif)) { freeifaddrs(addrs); config_del_strarray(*resif, *num_resif); *resif = NULL; *num_resif = 0; return 0; } } } freeifaddrs(addrs); return 1; #else struct config_strlist* p; if(num_ifs == 0 && list == NULL) { *resif = NULL; *num_resif = 0; return 1; } *num_resif = num_ifs; for(p = list; p; p = p->next) { (*num_resif)++; } *resif = calloc(*num_resif, sizeof(**resif)); if(!*resif) { log_err("out of memory"); return 0; } if(ifs) { int i; for(i=0; inext) { (*resif)[idx] = strdup(p->str); if(!((*resif)[idx])) { log_err("out of memory"); config_del_strarray(*resif, *num_resif); *resif = NULL; *num_resif = 0; return 0; } idx++; } } return 1; #endif /* HAVE_GETIFADDRS */ } struct listen_port* listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, int* reuseport) { struct listen_port* list = NULL; struct addrinfo hints; int i, do_ip4, do_ip6; int do_tcp, do_auto; do_ip4 = cfg->do_ip4; do_ip6 = cfg->do_ip6; do_tcp = cfg->do_tcp; do_auto = cfg->if_automatic && cfg->do_udp; if(cfg->incoming_num_tcp == 0) do_tcp = 0; /* getaddrinfo */ memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE; /* no name lookups on our listening ports */ if(num_ifs > 0) hints.ai_flags |= AI_NUMERICHOST; hints.ai_family = AF_UNSPEC; #ifndef INET6 do_ip6 = 0; #endif if(!do_ip4 && !do_ip6) { return NULL; } /* create ip4 and ip6 ports so that return addresses are nice. */ if(do_auto || num_ifs == 0) { if(do_auto && cfg->if_automatic_ports && cfg->if_automatic_ports[0]!=0) { char* now = cfg->if_automatic_ports; while(now && *now) { char* after; int extraport; while(isspace((unsigned char)*now)) now++; if(!*now) break; after = now; extraport = (int)strtol(now, &after, 10); if(extraport < 0 || extraport > 65535) { log_err("interface-automatic-ports port number out of range, at position %d of '%s'", (int)(now-cfg->if_automatic_ports)+1, cfg->if_automatic_ports); listening_ports_free(list); return NULL; } if(extraport == 0 && now == after) { log_err("interface-automatic-ports could not be parsed, at position %d of '%s'", (int)(now-cfg->if_automatic_ports)+1, cfg->if_automatic_ports); listening_ports_free(list); return NULL; } now = after; if(do_ip6) { hints.ai_family = AF_INET6; if(!ports_create_if("::0", do_auto, cfg->do_udp, do_tcp, &hints, extraport, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } if(do_ip4) { hints.ai_family = AF_INET; if(!ports_create_if("0.0.0.0", do_auto, cfg->do_udp, do_tcp, &hints, extraport, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } } return list; } if(do_ip6) { hints.ai_family = AF_INET6; if(!ports_create_if(do_auto?"::0":"::1", do_auto, cfg->do_udp, do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } if(do_ip4) { hints.ai_family = AF_INET; if(!ports_create_if(do_auto?"0.0.0.0":"127.0.0.1", do_auto, cfg->do_udp, do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } } else for(i = 0; ido_udp, do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } else { if(!do_ip4) continue; hints.ai_family = AF_INET; if(!ports_create_if(ifs[i], 0, cfg->do_udp, do_tcp, &hints, cfg->port, &list, cfg->so_rcvbuf, cfg->so_sndbuf, cfg->ssl_port, cfg->tls_additional_port, cfg->https_port, cfg->proxy_protocol_port, reuseport, cfg->ip_transparent, cfg->tcp_mss, cfg->ip_freebind, cfg->http_nodelay, cfg->use_systemd, cfg->dnscrypt_port, cfg->ip_dscp, cfg->quic_port, cfg->http_notls_downstream, cfg->sock_queue_timeout)) { listening_ports_free(list); return NULL; } } } return list; } void listening_ports_free(struct listen_port* list) { struct listen_port* nx; while(list) { nx = list->next; if(list->fd != -1) { sock_close(list->fd); } /* rc_ports don't have ub_socket */ if(list->socket) { free(list->socket->addr); free(list->socket); } free(list); list = nx; } } size_t listen_get_mem(struct listen_dnsport* listen) { struct listen_list* p; size_t s = sizeof(*listen) + sizeof(*listen->base) + sizeof(*listen->udp_buff) + sldns_buffer_capacity(listen->udp_buff); #ifdef USE_DNSCRYPT s += sizeof(*listen->dnscrypt_udp_buff); if(listen->udp_buff != listen->dnscrypt_udp_buff){ s += sldns_buffer_capacity(listen->dnscrypt_udp_buff); } #endif for(p = listen->cps; p; p = p->next) { s += sizeof(*p); s += comm_point_get_mem(p->com); } return s; } void listen_stop_accept(struct listen_dnsport* listen) { /* do not stop the ones that have no tcp_free list * (they have already stopped listening) */ struct listen_list* p; for(p=listen->cps; p; p=p->next) { if(p->com->type == comm_tcp_accept && p->com->tcp_free != NULL) { comm_point_stop_listening(p->com); } } } void listen_start_accept(struct listen_dnsport* listen) { /* do not start the ones that have no tcp_free list, it is no * use to listen to them because they have no free tcp handlers */ struct listen_list* p; for(p=listen->cps; p; p=p->next) { if(p->com->type == comm_tcp_accept && p->com->tcp_free != NULL) { comm_point_start_listening(p->com, -1, -1); } } } struct tcp_req_info* tcp_req_info_create(struct sldns_buffer* spoolbuf) { struct tcp_req_info* req = (struct tcp_req_info*)malloc(sizeof(*req)); if(!req) { log_err("malloc failure for new stream outoforder processing structure"); return NULL; } memset(req, 0, sizeof(*req)); req->spool_buffer = spoolbuf; return req; } void tcp_req_info_delete(struct tcp_req_info* req) { if(!req) return; tcp_req_info_clear(req); /* cp is pointer back to commpoint that owns this struct and * called delete on us */ /* spool_buffer is shared udp buffer, not deleted here */ free(req); } void tcp_req_info_clear(struct tcp_req_info* req) { struct tcp_req_open_item* open, *nopen; struct tcp_req_done_item* item, *nitem; if(!req) return; /* free outstanding request mesh reply entries */ open = req->open_req_list; while(open) { nopen = open->next; mesh_state_remove_reply(open->mesh, open->mesh_state, req->cp); free(open); open = nopen; } req->open_req_list = NULL; req->num_open_req = 0; /* free pending writable result packets */ item = req->done_req_list; while(item) { nitem = item->next; lock_basic_lock(&stream_wait_count_lock); stream_wait_count -= (sizeof(struct tcp_req_done_item) +item->len); lock_basic_unlock(&stream_wait_count_lock); free(item->buf); free(item); item = nitem; } req->done_req_list = NULL; req->num_done_req = 0; req->read_is_closed = 0; } void tcp_req_info_remove_mesh_state(struct tcp_req_info* req, struct mesh_state* m) { struct tcp_req_open_item* open, *prev = NULL; if(!req || !m) return; open = req->open_req_list; while(open) { if(open->mesh_state == m) { struct tcp_req_open_item* next; if(prev) prev->next = open->next; else req->open_req_list = open->next; /* caller has to manage the mesh state reply entry */ next = open->next; free(open); req->num_open_req --; /* prev = prev; */ open = next; continue; } prev = open; open = open->next; } } /** setup listening for read or write */ static void tcp_req_info_setup_listen(struct tcp_req_info* req) { int wr = 0; int rd = 0; if(req->cp->tcp_byte_count != 0) { /* cannot change, halfway through */ return; } if(!req->cp->tcp_is_reading) wr = 1; if(!req->read_is_closed) rd = 1; if(wr) { req->cp->tcp_is_reading = 0; comm_point_stop_listening(req->cp); comm_point_start_listening(req->cp, -1, adjusted_tcp_timeout(req->cp)); } else if(rd) { req->cp->tcp_is_reading = 1; comm_point_stop_listening(req->cp); comm_point_start_listening(req->cp, -1, adjusted_tcp_timeout(req->cp)); /* and also read it (from SSL stack buffers), so * no event read event is expected since the remainder of * the TLS frame is sitting in the buffers. */ req->read_again = 1; } else { comm_point_stop_listening(req->cp); comm_point_start_listening(req->cp, -1, adjusted_tcp_timeout(req->cp)); comm_point_listen_for_rw(req->cp, 0, 0); } } /** remove first item from list of pending results */ static struct tcp_req_done_item* tcp_req_info_pop_done(struct tcp_req_info* req) { struct tcp_req_done_item* item; log_assert(req->num_done_req > 0 && req->done_req_list); item = req->done_req_list; lock_basic_lock(&stream_wait_count_lock); stream_wait_count -= (sizeof(struct tcp_req_done_item)+item->len); lock_basic_unlock(&stream_wait_count_lock); req->done_req_list = req->done_req_list->next; req->num_done_req --; return item; } /** Send given buffer and setup to write */ static void tcp_req_info_start_write_buf(struct tcp_req_info* req, uint8_t* buf, size_t len) { sldns_buffer_clear(req->cp->buffer); sldns_buffer_write(req->cp->buffer, buf, len); sldns_buffer_flip(req->cp->buffer); req->cp->tcp_is_reading = 0; /* we are now writing */ } /** pick up the next result and start writing it to the channel */ static void tcp_req_pickup_next_result(struct tcp_req_info* req) { if(req->num_done_req > 0) { /* unlist the done item from the list of pending results */ struct tcp_req_done_item* item = tcp_req_info_pop_done(req); tcp_req_info_start_write_buf(req, item->buf, item->len); free(item->buf); free(item); } } /** the read channel has closed */ int tcp_req_info_handle_read_close(struct tcp_req_info* req) { verbose(VERB_ALGO, "tcp channel read side closed %d", req->cp->fd); /* RFC 7766 6.2.4 says to drop pending replies when client closes. */ return 0; /* drop connection */ } void tcp_req_info_handle_writedone(struct tcp_req_info* req) { /* back to reading state, we finished this write event */ sldns_buffer_clear(req->cp->buffer); if(req->num_done_req == 0 && req->read_is_closed) { /* no more to write and nothing to read, close it */ comm_point_drop_reply(&req->cp->repinfo); return; } req->cp->tcp_is_reading = 1; /* see if another result needs writing */ tcp_req_pickup_next_result(req); /* see if there is more to write, if not stop_listening for writing */ /* see if new requests are allowed, if so, start_listening * for reading */ tcp_req_info_setup_listen(req); } void tcp_req_info_handle_readdone(struct tcp_req_info* req) { struct comm_point* c = req->cp; /* we want to read up several requests, unless there are * pending answers */ req->is_drop = 0; req->is_reply = 0; req->in_worker_handle = 1; sldns_buffer_set_limit(req->spool_buffer, 0); /* handle the current request */ /* this calls the worker handle request routine that could give * a cache response, or localdata response, or drop the reply, * or schedule a mesh entry for later */ fptr_ok(fptr_whitelist_comm_point(c->callback)); if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo) ) { req->in_worker_handle = 0; /* there is an answer, put it up. It is already in the * c->buffer, just send it. */ /* since we were just reading a query, the channel is * clear to write to */ send_it: c->tcp_is_reading = 0; comm_point_stop_listening(c); comm_point_start_listening(c, -1, adjusted_tcp_timeout(c)); return; } req->in_worker_handle = 0; /* it should be waiting in the mesh for recursion. * If mesh failed to add a new entry and called commpoint_drop_reply. * Then the mesh state has been cleared. */ if(req->is_drop) { /* the reply has been dropped, stream has been closed. */ return; } /* If mesh failed(mallocfail) and called commpoint_send_reply with * something like servfail then we pick up that reply below. */ if(req->is_reply) { goto send_it; } sldns_buffer_clear(c->buffer); /* if pending answers, pick up an answer and start sending it */ tcp_req_pickup_next_result(req); /* if answers pending, start sending answers */ /* read more requests if we can have more requests */ tcp_req_info_setup_listen(req); } int tcp_req_info_add_meshstate(struct tcp_req_info* req, struct mesh_area* mesh, struct mesh_state* m) { struct tcp_req_open_item* item; log_assert(req && mesh && m); item = (struct tcp_req_open_item*)malloc(sizeof(*item)); if(!item) return 0; item->next = req->open_req_list; item->mesh = mesh; item->mesh_state = m; req->open_req_list = item; req->num_open_req++; return 1; } /** Add a result to the result list. At the end. */ static int tcp_req_info_add_result(struct tcp_req_info* req, uint8_t* buf, size_t len) { struct tcp_req_done_item* last = NULL; struct tcp_req_done_item* item; size_t space; /* see if we have space */ space = sizeof(struct tcp_req_done_item) + len; lock_basic_lock(&stream_wait_count_lock); if(stream_wait_count + space > stream_wait_max) { lock_basic_unlock(&stream_wait_count_lock); verbose(VERB_ALGO, "drop stream reply, no space left, in stream-wait-size"); return 0; } stream_wait_count += space; lock_basic_unlock(&stream_wait_count_lock); /* find last element */ last = req->done_req_list; while(last && last->next) last = last->next; /* create new element */ item = (struct tcp_req_done_item*)malloc(sizeof(*item)); if(!item) { log_err("malloc failure, for stream result list"); return 0; } item->next = NULL; item->len = len; item->buf = memdup(buf, len); if(!item->buf) { free(item); log_err("malloc failure, adding reply to stream result list"); return 0; } /* link in */ if(last) last->next = item; else req->done_req_list = item; req->num_done_req++; return 1; } void tcp_req_info_send_reply(struct tcp_req_info* req) { if(req->in_worker_handle) { /* reply from mesh is in the spool_buffer */ /* copy now, so that the spool buffer is free for other tasks * before the callback is done */ sldns_buffer_clear(req->cp->buffer); sldns_buffer_write(req->cp->buffer, sldns_buffer_begin(req->spool_buffer), sldns_buffer_limit(req->spool_buffer)); sldns_buffer_flip(req->cp->buffer); req->is_reply = 1; return; } /* now that the query has been handled, that mesh_reply entry * should be removed, from the tcp_req_info list, * the mesh state cleanup removes then with region_cleanup and * replies_sent true. */ /* see if we can send it straight away (we are not doing * anything else). If so, copy to buffer and start */ if(req->cp->tcp_is_reading && req->cp->tcp_byte_count == 0) { /* buffer is free, and was ready to read new query into, * but we are now going to use it to send this answer */ tcp_req_info_start_write_buf(req, sldns_buffer_begin(req->spool_buffer), sldns_buffer_limit(req->spool_buffer)); /* switch to listen to write events */ comm_point_stop_listening(req->cp); comm_point_start_listening(req->cp, -1, adjusted_tcp_timeout(req->cp)); return; } /* queue up the answer behind the others already pending */ if(!tcp_req_info_add_result(req, sldns_buffer_begin(req->spool_buffer), sldns_buffer_limit(req->spool_buffer))) { /* drop the connection, we are out of resources */ comm_point_drop_reply(&req->cp->repinfo); } } size_t tcp_req_info_get_stream_buffer_size(void) { size_t s; if(!stream_wait_lock_inited) return stream_wait_count; lock_basic_lock(&stream_wait_count_lock); s = stream_wait_count; lock_basic_unlock(&stream_wait_count_lock); return s; } size_t http2_get_query_buffer_size(void) { size_t s; if(!http2_query_buffer_lock_inited) return http2_query_buffer_count; lock_basic_lock(&http2_query_buffer_count_lock); s = http2_query_buffer_count; lock_basic_unlock(&http2_query_buffer_count_lock); return s; } size_t http2_get_response_buffer_size(void) { size_t s; if(!http2_response_buffer_lock_inited) return http2_response_buffer_count; lock_basic_lock(&http2_response_buffer_count_lock); s = http2_response_buffer_count; lock_basic_unlock(&http2_response_buffer_count_lock); return s; } #ifdef HAVE_NGHTTP2 /** nghttp2 callback. Used to copy response from rbuffer to nghttp2 session */ static ssize_t http2_submit_response_read_callback( nghttp2_session* ATTR_UNUSED(session), int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* ATTR_UNUSED(cb_arg)) { struct http2_stream* h2_stream; struct http2_session* h2_session = source->ptr; size_t copylen = length; if(!(h2_stream = nghttp2_session_get_stream_user_data( h2_session->session, stream_id))) { verbose(VERB_QUERY, "http2: cannot get stream data, closing " "stream"); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } if(!h2_stream->rbuffer || sldns_buffer_remaining(h2_stream->rbuffer) == 0) { verbose(VERB_QUERY, "http2: cannot submit buffer. No data " "available in rbuffer"); /* rbuffer will be free'd in frame close cb */ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } if(copylen > sldns_buffer_remaining(h2_stream->rbuffer)) copylen = sldns_buffer_remaining(h2_stream->rbuffer); if(copylen > SSIZE_MAX) copylen = SSIZE_MAX; /* will probably never happen */ memcpy(buf, sldns_buffer_current(h2_stream->rbuffer), copylen); sldns_buffer_skip(h2_stream->rbuffer, copylen); if(sldns_buffer_remaining(h2_stream->rbuffer) == 0) { *data_flags |= NGHTTP2_DATA_FLAG_EOF; lock_basic_lock(&http2_response_buffer_count_lock); http2_response_buffer_count -= sldns_buffer_capacity(h2_stream->rbuffer); lock_basic_unlock(&http2_response_buffer_count_lock); sldns_buffer_free(h2_stream->rbuffer); h2_stream->rbuffer = NULL; } return copylen; } /** * Send RST_STREAM frame for stream. * @param h2_session: http2 session to submit frame to * @param h2_stream: http2 stream containing frame ID to use in RST_STREAM * @return 0 on error, 1 otherwise */ static int http2_submit_rst_stream(struct http2_session* h2_session, struct http2_stream* h2_stream) { int ret = nghttp2_submit_rst_stream(h2_session->session, NGHTTP2_FLAG_NONE, h2_stream->stream_id, NGHTTP2_INTERNAL_ERROR); if(ret) { verbose(VERB_QUERY, "http2: nghttp2_submit_rst_stream failed, " "error: %s", nghttp2_strerror(ret)); return 0; } return 1; } /** * DNS response ready to be submitted to nghttp2, to be prepared for sending * out. Response is stored in c->buffer. Copy to rbuffer because the c->buffer * might be used before this will be sent out. * @param h2_session: http2 session, containing c->buffer which contains answer * @return 0 on error, 1 otherwise */ int http2_submit_dns_response(struct http2_session* h2_session) { int ret; nghttp2_data_provider data_prd; char status[4]; nghttp2_nv headers[3]; struct http2_stream* h2_stream = h2_session->c->h2_stream; size_t rlen; char rlen_str[32]; if(h2_stream->rbuffer) { log_err("http2 submit response error: rbuffer already " "exists"); return 0; } if(sldns_buffer_remaining(h2_session->c->buffer) == 0) { log_err("http2 submit response error: c->buffer not complete"); return 0; } if(snprintf(status, 4, "%d", h2_stream->status) != 3) { verbose(VERB_QUERY, "http2: submit response error: " "invalid status"); return 0; } rlen = sldns_buffer_remaining(h2_session->c->buffer); snprintf(rlen_str, sizeof(rlen_str), "%u", (unsigned)rlen); lock_basic_lock(&http2_response_buffer_count_lock); if(http2_response_buffer_count + rlen > http2_response_buffer_max) { lock_basic_unlock(&http2_response_buffer_count_lock); verbose(VERB_ALGO, "reset HTTP2 stream, no space left, " "in https-response-buffer-size"); return http2_submit_rst_stream(h2_session, h2_stream); } http2_response_buffer_count += rlen; lock_basic_unlock(&http2_response_buffer_count_lock); if(!(h2_stream->rbuffer = sldns_buffer_new(rlen))) { lock_basic_lock(&http2_response_buffer_count_lock); http2_response_buffer_count -= rlen; lock_basic_unlock(&http2_response_buffer_count_lock); log_err("http2 submit response error: malloc failure"); return 0; } headers[0].name = (uint8_t*)":status"; headers[0].namelen = 7; headers[0].value = (uint8_t*)status; headers[0].valuelen = 3; headers[0].flags = NGHTTP2_NV_FLAG_NONE; headers[1].name = (uint8_t*)"content-type"; headers[1].namelen = 12; headers[1].value = (uint8_t*)"application/dns-message"; headers[1].valuelen = 23; headers[1].flags = NGHTTP2_NV_FLAG_NONE; headers[2].name = (uint8_t*)"content-length"; headers[2].namelen = 14; headers[2].value = (uint8_t*)rlen_str; headers[2].valuelen = strlen(rlen_str); headers[2].flags = NGHTTP2_NV_FLAG_NONE; sldns_buffer_write(h2_stream->rbuffer, sldns_buffer_current(h2_session->c->buffer), sldns_buffer_remaining(h2_session->c->buffer)); sldns_buffer_flip(h2_stream->rbuffer); data_prd.source.ptr = h2_session; data_prd.read_callback = http2_submit_response_read_callback; ret = nghttp2_submit_response(h2_session->session, h2_stream->stream_id, headers, 3, &data_prd); if(ret) { verbose(VERB_QUERY, "http2: set_stream_user_data failed, " "error: %s", nghttp2_strerror(ret)); return 0; } return 1; } #else int http2_submit_dns_response(void* ATTR_UNUSED(v)) { return 0; } #endif #ifdef HAVE_NGHTTP2 /** HTTP status to descriptive string */ static char* http_status_to_str(enum http_status s) { switch(s) { case HTTP_STATUS_OK: return "OK"; case HTTP_STATUS_BAD_REQUEST: return "Bad Request"; case HTTP_STATUS_NOT_FOUND: return "Not Found"; case HTTP_STATUS_PAYLOAD_TOO_LARGE: return "Payload Too Large"; case HTTP_STATUS_URI_TOO_LONG: return "URI Too Long"; case HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: return "Unsupported Media Type"; case HTTP_STATUS_NOT_IMPLEMENTED: return "Not Implemented"; } return "Status Unknown"; } /** nghttp2 callback. Used to copy error message to nghttp2 session */ static ssize_t http2_submit_error_read_callback( nghttp2_session* ATTR_UNUSED(session), int32_t stream_id, uint8_t* buf, size_t length, uint32_t* data_flags, nghttp2_data_source* source, void* ATTR_UNUSED(cb_arg)) { struct http2_stream* h2_stream; struct http2_session* h2_session = source->ptr; char* msg; if(!(h2_stream = nghttp2_session_get_stream_user_data( h2_session->session, stream_id))) { verbose(VERB_QUERY, "http2: cannot get stream data, closing " "stream"); return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; } *data_flags |= NGHTTP2_DATA_FLAG_EOF; msg = http_status_to_str(h2_stream->status); if(length < strlen(msg)) return 0; /* not worth trying over multiple frames */ memcpy(buf, msg, strlen(msg)); return strlen(msg); } /** * HTTP error response ready to be submitted to nghttp2, to be prepared for * sending out. Message body will contain descriptive string for HTTP status. * @param h2_session: http2 session to submit to * @param h2_stream: http2 stream containing HTTP status to use for error * @return 0 on error, 1 otherwise */ static int http2_submit_error(struct http2_session* h2_session, struct http2_stream* h2_stream) { int ret; char status[4]; nghttp2_data_provider data_prd; nghttp2_nv headers[1]; /* will be copied by nghttp */ if(snprintf(status, 4, "%d", h2_stream->status) != 3) { verbose(VERB_QUERY, "http2: submit error failed, " "invalid status"); return 0; } headers[0].name = (uint8_t*)":status"; headers[0].namelen = 7; headers[0].value = (uint8_t*)status; headers[0].valuelen = 3; headers[0].flags = NGHTTP2_NV_FLAG_NONE; data_prd.source.ptr = h2_session; data_prd.read_callback = http2_submit_error_read_callback; ret = nghttp2_submit_response(h2_session->session, h2_stream->stream_id, headers, 1, &data_prd); if(ret) { verbose(VERB_QUERY, "http2: submit error failed, " "error: %s", nghttp2_strerror(ret)); return 0; } return 1; } /** * Start query handling. Query is stored in the stream, and will be free'd here. * @param h2_session: http2 session, containing comm point * @param h2_stream: stream containing buffered query * @return: -1 on error, 1 if answer is stored in c->buffer, 0 if there is no * reply available (yet). */ static int http2_query_read_done(struct http2_session* h2_session, struct http2_stream* h2_stream) { log_assert(h2_stream->qbuffer); if(h2_session->c->h2_stream) { verbose(VERB_ALGO, "http2_query_read_done failure: shared " "buffer already assigned to stream"); return -1; } /* the c->buffer might be used by mesh_send_reply and no be cleard * need to be cleared before use */ sldns_buffer_clear(h2_session->c->buffer); if(sldns_buffer_remaining(h2_session->c->buffer) < sldns_buffer_remaining(h2_stream->qbuffer)) { /* qbuffer will be free'd in frame close cb */ sldns_buffer_clear(h2_session->c->buffer); verbose(VERB_ALGO, "http2_query_read_done failure: can't fit " "qbuffer in c->buffer"); return -1; } sldns_buffer_write(h2_session->c->buffer, sldns_buffer_current(h2_stream->qbuffer), sldns_buffer_remaining(h2_stream->qbuffer)); lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= sldns_buffer_capacity(h2_stream->qbuffer); lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; sldns_buffer_flip(h2_session->c->buffer); h2_session->c->h2_stream = h2_stream; fptr_ok(fptr_whitelist_comm_point(h2_session->c->callback)); if((*h2_session->c->callback)(h2_session->c, h2_session->c->cb_arg, NETEVENT_NOERROR, &h2_session->c->repinfo)) { return 1; /* answer in c->buffer */ } sldns_buffer_clear(h2_session->c->buffer); h2_session->c->h2_stream = NULL; return 0; /* mesh state added, or dropped */ } /** nghttp2 callback. Used to check if the received frame indicates the end of a * stream. Gather collected request data and start query handling. */ static int http2_req_frame_recv_cb(nghttp2_session* session, const nghttp2_frame* frame, void* cb_arg) { struct http2_session* h2_session = (struct http2_session*)cb_arg; struct http2_stream* h2_stream; int query_read_done; if((frame->hd.type != NGHTTP2_DATA && frame->hd.type != NGHTTP2_HEADERS) || !(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { return 0; } if(!(h2_stream = nghttp2_session_get_stream_user_data( session, frame->hd.stream_id))) return 0; if(h2_stream->invalid_endpoint) { h2_stream->status = HTTP_STATUS_NOT_FOUND; goto submit_http_error; } if(h2_stream->invalid_content_type) { h2_stream->status = HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE; goto submit_http_error; } if(h2_stream->http_method != HTTP_METHOD_GET && h2_stream->http_method != HTTP_METHOD_POST) { h2_stream->status = HTTP_STATUS_NOT_IMPLEMENTED; goto submit_http_error; } if(h2_stream->query_too_large) { if(h2_stream->http_method == HTTP_METHOD_POST) h2_stream->status = HTTP_STATUS_PAYLOAD_TOO_LARGE; else h2_stream->status = HTTP_STATUS_URI_TOO_LONG; goto submit_http_error; } if(!h2_stream->qbuffer) { h2_stream->status = HTTP_STATUS_BAD_REQUEST; goto submit_http_error; } if(h2_stream->status) { submit_http_error: verbose(VERB_QUERY, "http2 request invalid, returning :status=" "%d", h2_stream->status); if(!http2_submit_error(h2_session, h2_stream)) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } h2_stream->status = HTTP_STATUS_OK; sldns_buffer_flip(h2_stream->qbuffer); h2_session->postpone_drop = 1; query_read_done = http2_query_read_done(h2_session, h2_stream); h2_session->postpone_drop = 0; if(query_read_done < 0) return NGHTTP2_ERR_CALLBACK_FAILURE; else if(!query_read_done) { if(h2_session->is_drop) { /* connection needs to be closed. Return failure to make * sure no other action are taken anymore on comm point. * failure will result in reclaiming (and closing) * of comm point. */ verbose(VERB_QUERY, "http2 query dropped in worker cb"); return NGHTTP2_ERR_CALLBACK_FAILURE; } /* nothing to submit right now, query added to mesh. */ return 0; } if(!http2_submit_dns_response(h2_session)) { sldns_buffer_clear(h2_session->c->buffer); h2_session->c->h2_stream = NULL; return NGHTTP2_ERR_CALLBACK_FAILURE; } verbose(VERB_QUERY, "http2 query submitted to session"); sldns_buffer_clear(h2_session->c->buffer); h2_session->c->h2_stream = NULL; return 0; } /** nghttp2 callback. Used to detect start of new streams. */ static int http2_req_begin_headers_cb(nghttp2_session* session, const nghttp2_frame* frame, void* cb_arg) { struct http2_session* h2_session = (struct http2_session*)cb_arg; struct http2_stream* h2_stream; int ret; if(frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { /* only interested in request headers */ return 0; } if(!(h2_stream = http2_stream_create(frame->hd.stream_id))) { log_err("malloc failure while creating http2 stream"); return NGHTTP2_ERR_CALLBACK_FAILURE; } http2_session_add_stream(h2_session, h2_stream); ret = nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, h2_stream); if(ret) { /* stream does not exist */ verbose(VERB_QUERY, "http2: set_stream_user_data failed, " "error: %s", nghttp2_strerror(ret)); return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } /** * base64url decode, store in qbuffer * @param h2_session: http2 session * @param h2_stream: http2 stream * @param start: start of the base64 string * @param length: length of the base64 string * @return: 0 on error, 1 otherwise. query will be stored in h2_stream->qbuffer, * buffer will be NULL is unparseble. */ static int http2_buffer_uri_query(struct http2_session* h2_session, struct http2_stream* h2_stream, const uint8_t* start, size_t length) { size_t expectb64len; int b64len; if(h2_stream->http_method == HTTP_METHOD_POST) return 1; if(length == 0) return 1; if(h2_stream->qbuffer) { verbose(VERB_ALGO, "http2_req_header fail, " "qbuffer already set"); return 0; } /* calculate size, might be a bit bigger than the real * decoded buffer size */ expectb64len = sldns_b64_pton_calculate_size(length); log_assert(expectb64len > 0); if(expectb64len > h2_session->c->http2_stream_max_qbuffer_size) { h2_stream->query_too_large = 1; return 1; } lock_basic_lock(&http2_query_buffer_count_lock); if(http2_query_buffer_count + expectb64len > http2_query_buffer_max) { lock_basic_unlock(&http2_query_buffer_count_lock); verbose(VERB_ALGO, "reset HTTP2 stream, no space left, " "in http2-query-buffer-size"); return http2_submit_rst_stream(h2_session, h2_stream); } http2_query_buffer_count += expectb64len; lock_basic_unlock(&http2_query_buffer_count_lock); if(!(h2_stream->qbuffer = sldns_buffer_new(expectb64len))) { lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= expectb64len; lock_basic_unlock(&http2_query_buffer_count_lock); log_err("http2_req_header fail, qbuffer " "malloc failure"); return 0; } if(sldns_b64_contains_nonurl((char const*)start, length)) { char buf[65536+4]; verbose(VERB_ALGO, "HTTP2 stream contains wrong b64 encoding"); /* copy to the scratch buffer temporarily to terminate the * string with a zero */ if(length+1 > sizeof(buf)) { /* too long */ lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= expectb64len; lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; return 1; } memmove(buf, start, length); buf[length] = 0; if(!(b64len = sldns_b64_pton(buf, sldns_buffer_current( h2_stream->qbuffer), expectb64len)) || b64len < 0) { lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= expectb64len; lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; return 1; } } else { if(!(b64len = sldns_b64url_pton( (char const *)start, length, sldns_buffer_current(h2_stream->qbuffer), expectb64len)) || b64len < 0) { lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= expectb64len; lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; /* return without error, method can be an * unknown POST */ return 1; } } sldns_buffer_skip(h2_stream->qbuffer, (size_t)b64len); return 1; } /** nghttp2 callback. Used to parse headers from HEADER frames. */ static int http2_req_header_cb(nghttp2_session* session, const nghttp2_frame* frame, const uint8_t* name, size_t namelen, const uint8_t* value, size_t valuelen, uint8_t ATTR_UNUSED(flags), void* cb_arg) { struct http2_stream* h2_stream = NULL; struct http2_session* h2_session = (struct http2_session*)cb_arg; /* nghttp2 deals with CONTINUATION frames and provides them as part of * the HEADER */ if(frame->hd.type != NGHTTP2_HEADERS || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { /* only interested in request headers */ return 0; } if(!(h2_stream = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id))) return 0; /* earlier checks already indicate we can stop handling this query */ if(h2_stream->http_method == HTTP_METHOD_UNSUPPORTED || h2_stream->invalid_content_type || h2_stream->invalid_endpoint) return 0; /* nghttp2 performs some sanity checks in the headers, including: * name and value are guaranteed to be null terminated * name is guaranteed to be lowercase * content-length value is guaranteed to contain digits */ if(!h2_stream->http_method && namelen == 7 && memcmp(":method", name, namelen) == 0) { /* Case insensitive check on :method value to be on the safe * side. I failed to find text about case sensitivity in specs. */ if(valuelen == 3 && strcasecmp("GET", (const char*)value) == 0) h2_stream->http_method = HTTP_METHOD_GET; else if(valuelen == 4 && strcasecmp("POST", (const char*)value) == 0) { h2_stream->http_method = HTTP_METHOD_POST; if(h2_stream->qbuffer) { /* POST method uses query from DATA frames */ lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= sldns_buffer_capacity(h2_stream->qbuffer); lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; } } else h2_stream->http_method = HTTP_METHOD_UNSUPPORTED; return 0; } if(namelen == 5 && memcmp(":path", name, namelen) == 0) { /* :path may contain DNS query, depending on method. Method might * not be known yet here, so check after finishing receiving * stream. */ #define HTTP_QUERY_PARAM "?dns=" size_t el = strlen(h2_session->c->http_endpoint); size_t qpl = strlen(HTTP_QUERY_PARAM); if(valuelen < el || memcmp(h2_session->c->http_endpoint, value, el) != 0) { h2_stream->invalid_endpoint = 1; return 0; } /* larger than endpoint only allowed if it is for the query * parameter */ if(valuelen <= el+qpl || memcmp(HTTP_QUERY_PARAM, value+el, qpl) != 0) { if(valuelen != el) h2_stream->invalid_endpoint = 1; return 0; } if(!http2_buffer_uri_query(h2_session, h2_stream, value+(el+qpl), valuelen-(el+qpl))) { return NGHTTP2_ERR_CALLBACK_FAILURE; } return 0; } /* Content type is a SHOULD (rfc7231#section-3.1.1.5) when using POST, * and not needed when using GET. Don't enforce. * If set only allow lowercase "application/dns-message". * * Clients SHOULD (rfc8484#section-4.1) set an accept header, but MUST * be able to handle "application/dns-message". Since that is the only * content-type supported we can ignore the accept header. */ if((namelen == 12 && memcmp("content-type", name, namelen) == 0)) { if(valuelen != 23 || memcmp("application/dns-message", value, valuelen) != 0) { h2_stream->invalid_content_type = 1; } } /* Only interested in content-lentg for POST (on not yet known) method. */ if((!h2_stream->http_method || h2_stream->http_method == HTTP_METHOD_POST) && !h2_stream->content_length && namelen == 14 && memcmp("content-length", name, namelen) == 0) { if(valuelen > 5) { h2_stream->query_too_large = 1; return 0; } /* guaranteed to only contain digits and be null terminated */ h2_stream->content_length = atoi((const char*)value); if(h2_stream->content_length > h2_session->c->http2_stream_max_qbuffer_size) { h2_stream->query_too_large = 1; return 0; } } return 0; } /** nghttp2 callback. Used to get data from DATA frames, which can contain * queries in POST requests. */ static int http2_req_data_chunk_recv_cb(nghttp2_session* ATTR_UNUSED(session), uint8_t ATTR_UNUSED(flags), int32_t stream_id, const uint8_t* data, size_t len, void* cb_arg) { struct http2_session* h2_session = (struct http2_session*)cb_arg; struct http2_stream* h2_stream; size_t qlen = 0; if(!(h2_stream = nghttp2_session_get_stream_user_data( h2_session->session, stream_id))) { return 0; } if(h2_stream->query_too_large) return 0; if(!h2_stream->qbuffer) { if(h2_stream->content_length) { if(h2_stream->content_length < len) /* getting more data in DATA frame than * advertised in content-length header. */ return NGHTTP2_ERR_CALLBACK_FAILURE; qlen = h2_stream->content_length; } else if(len <= h2_session->c->http2_stream_max_qbuffer_size) { /* setting this to msg-buffer-size can result in a lot * of memory consumption. Most queries should fit in a * single DATA frame, and most POST queries will * contain content-length which does not impose this * limit. */ qlen = len; } } if(!h2_stream->qbuffer && qlen) { lock_basic_lock(&http2_query_buffer_count_lock); if(http2_query_buffer_count + qlen > http2_query_buffer_max) { lock_basic_unlock(&http2_query_buffer_count_lock); verbose(VERB_ALGO, "reset HTTP2 stream, no space left, " "in http2-query-buffer-size"); return http2_submit_rst_stream(h2_session, h2_stream); } http2_query_buffer_count += qlen; lock_basic_unlock(&http2_query_buffer_count_lock); if(!(h2_stream->qbuffer = sldns_buffer_new(qlen))) { lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= qlen; lock_basic_unlock(&http2_query_buffer_count_lock); } } if(!h2_stream->qbuffer || sldns_buffer_remaining(h2_stream->qbuffer) < len) { verbose(VERB_ALGO, "http2 data_chunk_recv failed. Not enough " "buffer space for POST query. Can happen on multi " "frame requests without content-length header"); h2_stream->query_too_large = 1; return 0; } sldns_buffer_write(h2_stream->qbuffer, data, len); return 0; } void http2_req_stream_clear(struct http2_stream* h2_stream) { if(h2_stream->qbuffer) { lock_basic_lock(&http2_query_buffer_count_lock); http2_query_buffer_count -= sldns_buffer_capacity(h2_stream->qbuffer); lock_basic_unlock(&http2_query_buffer_count_lock); sldns_buffer_free(h2_stream->qbuffer); h2_stream->qbuffer = NULL; } if(h2_stream->rbuffer) { lock_basic_lock(&http2_response_buffer_count_lock); http2_response_buffer_count -= sldns_buffer_capacity(h2_stream->rbuffer); lock_basic_unlock(&http2_response_buffer_count_lock); sldns_buffer_free(h2_stream->rbuffer); h2_stream->rbuffer = NULL; } } nghttp2_session_callbacks* http2_req_callbacks_create(void) { nghttp2_session_callbacks *callbacks; if(nghttp2_session_callbacks_new(&callbacks) == NGHTTP2_ERR_NOMEM) { log_err("failed to initialize nghttp2 callback"); return NULL; } /* reception of header block started, used to create h2_stream */ nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, http2_req_begin_headers_cb); /* complete frame received, used to get data from stream if frame * has end stream flag, and start processing query */ nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, http2_req_frame_recv_cb); /* get request info from headers */ nghttp2_session_callbacks_set_on_header_callback(callbacks, http2_req_header_cb); /* get data from DATA frames, containing POST query */ nghttp2_session_callbacks_set_on_data_chunk_recv_callback(callbacks, http2_req_data_chunk_recv_cb); /* generic HTTP2 callbacks */ nghttp2_session_callbacks_set_recv_callback(callbacks, http2_recv_cb); nghttp2_session_callbacks_set_send_callback(callbacks, http2_send_cb); nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, http2_stream_close_cb); return callbacks; } #endif /* HAVE_NGHTTP2 */ #ifdef HAVE_NGTCP2 struct doq_table* doq_table_create(struct config_file* cfg, struct ub_randstate* rnd) { struct doq_table* table; if (!cfg->quic_port) return NULL; table = calloc(1, sizeof(*table)); if(!table) return NULL; #ifdef USE_NGTCP2_CRYPTO_OSSL /* Initialize the ossl crypto, it is harmless to call twice, * and this is before use of doq connections. */ if(ngtcp2_crypto_ossl_init() != 0) { log_err("ngtcp2_crypto_ossl_init failed"); free(table); return NULL; } #elif defined(HAVE_NGTCP2_CRYPTO_QUICTLS_INIT) if(ngtcp2_crypto_quictls_init() != 0) { log_err("ngtcp2_crypto_quictls_init failed"); free(table); return NULL; } #endif table->idle_timeout = ((uint64_t)cfg->tcp_idle_timeout)* NGTCP2_MILLISECONDS; table->sv_scidlen = 16; table->static_secret_len = 16; table->static_secret = malloc(table->static_secret_len); if(!table->static_secret) { free(table); return NULL; } doq_fill_rand(rnd, table->static_secret, table->static_secret_len); table->conn_tree = rbtree_create(doq_conn_cmp); if(!table->conn_tree) { free(table->static_secret); free(table); return NULL; } table->conid_tree = rbtree_create(doq_conid_cmp); if(!table->conid_tree) { free(table->static_secret); free(table->conn_tree); free(table); return NULL; } table->timer_tree = rbtree_create(doq_timer_cmp); if(!table->timer_tree) { free(table->static_secret); free(table->conn_tree); free(table->conid_tree); free(table); return NULL; } lock_rw_init(&table->lock); lock_rw_init(&table->conid_lock); lock_basic_init(&table->size_lock); lock_protect(&table->lock, &table->static_secret, sizeof(table->static_secret)); lock_protect(&table->lock, &table->static_secret_len, sizeof(table->static_secret_len)); lock_protect(&table->lock, table->static_secret, table->static_secret_len); lock_protect(&table->lock, &table->sv_scidlen, sizeof(table->sv_scidlen)); lock_protect(&table->lock, &table->idle_timeout, sizeof(table->idle_timeout)); lock_protect(&table->lock, &table->conn_tree, sizeof(table->conn_tree)); lock_protect(&table->lock, table->conn_tree, sizeof(*table->conn_tree)); lock_protect(&table->conid_lock, table->conid_tree, sizeof(*table->conid_tree)); lock_protect(&table->lock, table->timer_tree, sizeof(*table->timer_tree)); lock_protect(&table->size_lock, &table->current_size, sizeof(table->current_size)); return table; } /** delete elements from the connection tree */ static void conn_tree_del(rbnode_type* node, void* arg) { struct doq_table* table = (struct doq_table*)arg; struct doq_conn* conn; if(!node || !table) return; conn = (struct doq_conn*)node->key; if(conn->timer.timer_in_list) { /* Remove timer from list first, because finding the rbnode * element of the setlist of same timeouts needs tree lookup. * Edit the tree structure after that lookup. */ doq_timer_list_remove(conn->table, &conn->timer); } if(conn->timer.timer_in_tree) doq_timer_tree_remove(conn->table, &conn->timer); doq_table_quic_size_subtract(table, sizeof(*conn)+conn->key.dcidlen); doq_conn_delete(conn, table); } /** delete elements from the connection id tree */ static void conid_tree_del(rbnode_type* node, void* ATTR_UNUSED(arg)) { if(!node) return; doq_conid_delete((struct doq_conid*)node->key); } void doq_table_delete(struct doq_table* table) { if(!table) return; lock_rw_destroy(&table->lock); free(table->static_secret); if(table->conn_tree) { traverse_postorder(table->conn_tree, conn_tree_del, table); free(table->conn_tree); } lock_rw_destroy(&table->conid_lock); if(table->conid_tree) { /* The tree should be empty, because the doq_conn_delete calls * above should have also removed their conid elements. */ traverse_postorder(table->conid_tree, conid_tree_del, NULL); free(table->conid_tree); } lock_basic_destroy(&table->size_lock); if(table->timer_tree) { /* The tree should be empty, because the conn_tree_del calls * above should also have removed them. Also the doq_timer * is part of the doq_conn struct, so is already freed. */ free(table->timer_tree); } table->write_list_first = NULL; table->write_list_last = NULL; free(table); } struct doq_timer* doq_timer_find_time(struct doq_table* table, struct timeval* tv) { struct doq_timer key; struct rbnode_type* node; log_assert(table != NULL); memset(&key, 0, sizeof(key)); key.time.tv_sec = tv->tv_sec; key.time.tv_usec = tv->tv_usec; node = rbtree_search(table->timer_tree, &key); if(node) return (struct doq_timer*)node->key; return NULL; } void doq_timer_tree_remove(struct doq_table* table, struct doq_timer* timer) { if(!timer->timer_in_tree) return; rbtree_delete(table->timer_tree, timer); timer->timer_in_tree = 0; /* This item could have more timers in the same set. */ if(timer->setlist_first) { struct doq_timer* rb_timer = timer->setlist_first; /* del first element from setlist */ if(rb_timer->setlist_next) rb_timer->setlist_next->setlist_prev = NULL; else timer->setlist_last = NULL; timer->setlist_first = rb_timer->setlist_next; rb_timer->setlist_prev = NULL; rb_timer->setlist_next = NULL; rb_timer->timer_in_list = 0; /* insert it into the tree as new rb element */ memset(&rb_timer->node, 0, sizeof(rb_timer->node)); rb_timer->node.key = rb_timer; rbtree_insert(table->timer_tree, &rb_timer->node); rb_timer->timer_in_tree = 1; /* the setlist, if any remainder, moves to the rb element */ rb_timer->setlist_first = timer->setlist_first; rb_timer->setlist_last = timer->setlist_last; timer->setlist_first = NULL; timer->setlist_last = NULL; rb_timer->worker_doq_socket = timer->worker_doq_socket; } timer->worker_doq_socket = NULL; } void doq_timer_list_remove(struct doq_table* table, struct doq_timer* timer) { struct doq_timer* rb_timer; if(!timer->timer_in_list) return; /* The item in the rbtree has the list start and end. */ rb_timer = doq_timer_find_time(table, &timer->time); if(rb_timer) { if(timer->setlist_prev) timer->setlist_prev->setlist_next = timer->setlist_next; else rb_timer->setlist_first = timer->setlist_next; if(timer->setlist_next) timer->setlist_next->setlist_prev = timer->setlist_prev; else rb_timer->setlist_last = timer->setlist_prev; timer->setlist_prev = NULL; timer->setlist_next = NULL; } timer->timer_in_list = 0; } /** doq append timer to setlist */ static void doq_timer_list_append(struct doq_timer* rb_timer, struct doq_timer* timer) { log_assert(timer->timer_in_list == 0); timer->timer_in_list = 1; timer->setlist_next = NULL; timer->setlist_prev = rb_timer->setlist_last; if(rb_timer->setlist_last) rb_timer->setlist_last->setlist_next = timer; else rb_timer->setlist_first = timer; rb_timer->setlist_last = timer; } void doq_timer_unset(struct doq_table* table, struct doq_timer* timer) { if(timer->timer_in_list) { /* Remove timer from list first, because finding the rbnode * element of the setlist of same timeouts needs tree lookup. * Edit the tree structure after that lookup. */ doq_timer_list_remove(table, timer); } if(timer->timer_in_tree) doq_timer_tree_remove(table, timer); timer->worker_doq_socket = NULL; } void doq_timer_set(struct doq_table* table, struct doq_timer* timer, struct doq_server_socket* worker_doq_socket, struct timeval* tv) { struct doq_timer* rb_timer; if(verbosity >= VERB_ALGO && timer->conn) { char a[256]; struct timeval rel; addr_to_str((void*)&timer->conn->key.paddr.addr, timer->conn->key.paddr.addrlen, a, sizeof(a)); timeval_subtract(&rel, tv, worker_doq_socket->now_tv); verbose(VERB_ALGO, "doq %s timer set %d.%6.6d in %d.%6.6d", a, (int)tv->tv_sec, (int)tv->tv_usec, (int)rel.tv_sec, (int)rel.tv_usec); } if(timer->timer_in_tree || timer->timer_in_list) { if(timer->time.tv_sec == tv->tv_sec && timer->time.tv_usec == tv->tv_usec) return; /* already set on that time */ doq_timer_unset(table, timer); } timer->time.tv_sec = tv->tv_sec; timer->time.tv_usec = tv->tv_usec; rb_timer = doq_timer_find_time(table, tv); if(rb_timer) { /* There is a timeout already with this value. Timer is * added to the setlist. */ doq_timer_list_append(rb_timer, timer); } else { /* There is no timeout with this value. Make timer a new * tree element. */ memset(&timer->node, 0, sizeof(timer->node)); timer->node.key = timer; rbtree_insert(table->timer_tree, &timer->node); timer->timer_in_tree = 1; timer->setlist_first = NULL; timer->setlist_last = NULL; timer->worker_doq_socket = worker_doq_socket; } } struct doq_conn* doq_conn_create(struct comm_point* c, struct doq_pkt_addr* paddr, const uint8_t* dcid, size_t dcidlen, uint32_t version) { struct doq_conn* conn = calloc(1, sizeof(*conn)); if(!conn) return NULL; conn->node.key = conn; conn->doq_socket = c->doq_socket; conn->table = c->doq_socket->table; memmove(&conn->key.paddr.addr, &paddr->addr, paddr->addrlen); conn->key.paddr.addrlen = paddr->addrlen; memmove(&conn->key.paddr.localaddr, &paddr->localaddr, paddr->localaddrlen); conn->key.paddr.localaddrlen = paddr->localaddrlen; conn->key.paddr.ifindex = paddr->ifindex; conn->key.dcid = memdup((void*)dcid, dcidlen); if(!conn->key.dcid) { free(conn); return NULL; } conn->key.dcidlen = dcidlen; conn->version = version; #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_default(&conn->ccerr); #else ngtcp2_connection_close_error_default(&conn->last_error); #endif rbtree_init(&conn->stream_tree, &doq_stream_cmp); conn->timer.conn = conn; lock_basic_init(&conn->lock); lock_protect(&conn->lock, &conn->key, sizeof(conn->key)); lock_protect(&conn->lock, &conn->doq_socket, sizeof(conn->doq_socket)); lock_protect(&conn->lock, &conn->table, sizeof(conn->table)); lock_protect(&conn->lock, &conn->is_deleted, sizeof(conn->is_deleted)); lock_protect(&conn->lock, &conn->version, sizeof(conn->version)); lock_protect(&conn->lock, &conn->conn, sizeof(conn->conn)); lock_protect(&conn->lock, &conn->conid_list, sizeof(conn->conid_list)); #ifdef HAVE_NGTCP2_CCERR_DEFAULT lock_protect(&conn->lock, &conn->ccerr, sizeof(conn->ccerr)); #else lock_protect(&conn->lock, &conn->last_error, sizeof(conn->last_error)); #endif lock_protect(&conn->lock, &conn->tls_alert, sizeof(conn->tls_alert)); lock_protect(&conn->lock, &conn->ssl, sizeof(conn->ssl)); lock_protect(&conn->lock, &conn->close_pkt, sizeof(conn->close_pkt)); lock_protect(&conn->lock, &conn->close_pkt_len, sizeof(conn->close_pkt_len)); lock_protect(&conn->lock, &conn->close_ecn, sizeof(conn->close_ecn)); lock_protect(&conn->lock, &conn->stream_tree, sizeof(conn->stream_tree)); lock_protect(&conn->lock, &conn->stream_write_first, sizeof(conn->stream_write_first)); lock_protect(&conn->lock, &conn->stream_write_last, sizeof(conn->stream_write_last)); lock_protect(&conn->lock, &conn->write_interest, sizeof(conn->write_interest)); lock_protect(&conn->lock, &conn->on_write_list, sizeof(conn->on_write_list)); lock_protect(&conn->lock, &conn->write_prev, sizeof(conn->write_prev)); lock_protect(&conn->lock, &conn->write_next, sizeof(conn->write_next)); return conn; } /** delete stream tree node */ static void stream_tree_del(rbnode_type* node, void* arg) { struct doq_table* table = (struct doq_table*)arg; struct doq_stream* stream; if(!node) return; stream = (struct doq_stream*)node; if(stream->in) doq_table_quic_size_subtract(table, stream->inlen); if(stream->out) doq_table_quic_size_subtract(table, stream->outlen); doq_table_quic_size_subtract(table, sizeof(*stream)); doq_stream_delete(stream); } void doq_conn_delete(struct doq_conn* conn, struct doq_table* table) { if(!conn) return; lock_basic_destroy(&conn->lock); lock_rw_wrlock(&conn->table->conid_lock); doq_conn_clear_conids(conn); lock_rw_unlock(&conn->table->conid_lock); /* Remove the app data from ngtcp2 before SSL_free of conn->ssl, * because the ngtcp2 conn is deleted. */ SSL_set_app_data(conn->ssl, NULL); if(conn->stream_tree.count != 0) { traverse_postorder(&conn->stream_tree, stream_tree_del, table); } free(conn->key.dcid); SSL_free(conn->ssl); #ifdef USE_NGTCP2_CRYPTO_OSSL ngtcp2_crypto_ossl_ctx_del(conn->ossl_ctx); #endif ngtcp2_conn_del(conn->conn); free(conn->close_pkt); free(conn); } int doq_conn_cmp(const void* key1, const void* key2) { struct doq_conn* c = (struct doq_conn*)key1; struct doq_conn* d = (struct doq_conn*)key2; int r; /* Compared in the order destination address, then * local address, ifindex and then dcid. * So that for a search for findlessorequal for the destination * address will find connections to that address, with different * dcids. * Also a printout in sorted order prints the connections by IP * address of destination, and then a number of them depending on the * dcids. */ if(c->key.paddr.addrlen != d->key.paddr.addrlen) { if(c->key.paddr.addrlen < d->key.paddr.addrlen) return -1; return 1; } if((r=memcmp(&c->key.paddr.addr, &d->key.paddr.addr, c->key.paddr.addrlen))!=0) return r; if(c->key.paddr.localaddrlen != d->key.paddr.localaddrlen) { if(c->key.paddr.localaddrlen < d->key.paddr.localaddrlen) return -1; return 1; } if((r=memcmp(&c->key.paddr.localaddr, &d->key.paddr.localaddr, c->key.paddr.localaddrlen))!=0) return r; if(c->key.paddr.ifindex != d->key.paddr.ifindex) { if(c->key.paddr.ifindex < d->key.paddr.ifindex) return -1; return 1; } if(c->key.dcidlen != d->key.dcidlen) { if(c->key.dcidlen < d->key.dcidlen) return -1; return 1; } if((r=memcmp(c->key.dcid, d->key.dcid, c->key.dcidlen))!=0) return r; return 0; } int doq_conid_cmp(const void* key1, const void* key2) { struct doq_conid* c = (struct doq_conid*)key1; struct doq_conid* d = (struct doq_conid*)key2; if(c->cidlen != d->cidlen) { if(c->cidlen < d->cidlen) return -1; return 1; } return memcmp(c->cid, d->cid, c->cidlen); } int doq_timer_cmp(const void* key1, const void* key2) { struct doq_timer* e = (struct doq_timer*)key1; struct doq_timer* f = (struct doq_timer*)key2; if(e->time.tv_sec < f->time.tv_sec) return -1; if(e->time.tv_sec > f->time.tv_sec) return 1; if(e->time.tv_usec < f->time.tv_usec) return -1; if(e->time.tv_usec > f->time.tv_usec) return 1; return 0; } int doq_stream_cmp(const void* key1, const void* key2) { struct doq_stream* c = (struct doq_stream*)key1; struct doq_stream* d = (struct doq_stream*)key2; if(c->stream_id != d->stream_id) { if(c->stream_id < d->stream_id) return -1; return 1; } return 0; } /** doq store a local address in repinfo */ static void doq_repinfo_store_localaddr(struct comm_reply* repinfo, struct doq_addr_storage* localaddr, socklen_t localaddrlen) { /* use the pktinfo that we have for ancillary udp data otherwise, * this saves space for a sockaddr */ memset(&repinfo->pktinfo, 0, sizeof(repinfo->pktinfo)); if(addr_is_ip6((void*)localaddr, localaddrlen)) { #ifdef IPV6_PKTINFO struct sockaddr_in6* sa6 = (struct sockaddr_in6*)localaddr; memmove(&repinfo->pktinfo.v6info.ipi6_addr, &sa6->sin6_addr, sizeof(struct in6_addr)); repinfo->doq_srcport = sa6->sin6_port; #endif repinfo->srctype = 6; } else { #ifdef IP_PKTINFO struct sockaddr_in* sa = (struct sockaddr_in*)localaddr; memmove(&repinfo->pktinfo.v4info.ipi_addr, &sa->sin_addr, sizeof(struct in_addr)); repinfo->doq_srcport = sa->sin_port; #elif defined(IP_RECVDSTADDR) struct sockaddr_in* sa = (struct sockaddr_in*)localaddr; memmove(&repinfo->pktinfo.v4addr, &sa->sin_addr, sizeof(struct in_addr)); repinfo->doq_srcport = sa->sin_port; #endif repinfo->srctype = 4; } } /** doq retrieve localaddr from repinfo */ static void doq_repinfo_retrieve_localaddr(struct comm_reply* repinfo, struct doq_addr_storage* localaddr, socklen_t* localaddrlen) { if(repinfo->srctype == 6) { #ifdef IPV6_PKTINFO struct sockaddr_in6* sa6 = (struct sockaddr_in6*)localaddr; *localaddrlen = (socklen_t)sizeof(struct sockaddr_in6); memset(sa6, 0, *localaddrlen); sa6->sin6_family = AF_INET6; memmove(&sa6->sin6_addr, &repinfo->pktinfo.v6info.ipi6_addr, sizeof(struct in6_addr)); sa6->sin6_port = repinfo->doq_srcport; #endif } else { #ifdef IP_PKTINFO struct sockaddr_in* sa = (struct sockaddr_in*)localaddr; *localaddrlen = (socklen_t)sizeof(struct sockaddr_in); memset(sa, 0, *localaddrlen); sa->sin_family = AF_INET; memmove(&sa->sin_addr, &repinfo->pktinfo.v4info.ipi_addr, sizeof(struct in_addr)); sa->sin_port = repinfo->doq_srcport; #elif defined(IP_RECVDSTADDR) struct sockaddr_in* sa = (struct sockaddr_in*)localaddr; *localaddrlen = (socklen_t)sizeof(struct sockaddr_in); memset(sa, 0, *localaddrlen); sa->sin_family = AF_INET; memmove(&sa->sin_addr, &repinfo->pktinfo.v4addr, sizeof(struct in_addr)); sa->sin_port = repinfo->doq_srcport; #endif } } /** doq write a connection key into repinfo, false if it does not fit */ static int doq_conn_key_store_repinfo(struct doq_conn_key* key, struct comm_reply* repinfo) { repinfo->is_proxied = 0; repinfo->doq_ifindex = key->paddr.ifindex; repinfo->remote_addrlen = key->paddr.addrlen; memmove(&repinfo->remote_addr, &key->paddr.addr, repinfo->remote_addrlen); repinfo->client_addrlen = key->paddr.addrlen; memmove(&repinfo->client_addr, &key->paddr.addr, repinfo->client_addrlen); doq_repinfo_store_localaddr(repinfo, &key->paddr.localaddr, key->paddr.localaddrlen); if(key->dcidlen > sizeof(repinfo->doq_dcid)) return 0; repinfo->doq_dcidlen = key->dcidlen; memmove(repinfo->doq_dcid, key->dcid, key->dcidlen); return 1; } void doq_conn_key_from_repinfo(struct doq_conn_key* key, struct comm_reply* repinfo) { key->paddr.ifindex = repinfo->doq_ifindex; key->paddr.addrlen = repinfo->remote_addrlen; memmove(&key->paddr.addr, &repinfo->remote_addr, repinfo->remote_addrlen); doq_repinfo_retrieve_localaddr(repinfo, &key->paddr.localaddr, &key->paddr.localaddrlen); key->dcidlen = repinfo->doq_dcidlen; key->dcid = repinfo->doq_dcid; } /** doq add a stream to the connection */ static void doq_conn_add_stream(struct doq_conn* conn, struct doq_stream* stream) { (void)rbtree_insert(&conn->stream_tree, &stream->node); } /** doq delete a stream from the connection */ static void doq_conn_del_stream(struct doq_conn* conn, struct doq_stream* stream) { (void)rbtree_delete(&conn->stream_tree, &stream->node); } /** doq create new stream */ static struct doq_stream* doq_stream_create(int64_t stream_id) { struct doq_stream* stream = calloc(1, sizeof(*stream)); if(!stream) return NULL; stream->node.key = stream; stream->stream_id = stream_id; return stream; } void doq_stream_delete(struct doq_stream* stream) { if(!stream) return; free(stream->in); free(stream->out); free(stream); } struct doq_stream* doq_stream_find(struct doq_conn* conn, int64_t stream_id) { rbnode_type* node; struct doq_stream key; key.node.key = &key; key.stream_id = stream_id; node = rbtree_search(&conn->stream_tree, &key); if(node) return (struct doq_stream*)node->key; return NULL; } /** doq put stream on the conn write list */ static void doq_stream_on_write_list(struct doq_conn* conn, struct doq_stream* stream) { if(stream->on_write_list) return; stream->write_prev = conn->stream_write_last; if(conn->stream_write_last) conn->stream_write_last->write_next = stream; else conn->stream_write_first = stream; conn->stream_write_last = stream; stream->write_next = NULL; stream->on_write_list = 1; } /** doq remove stream from the conn write list */ static void doq_stream_off_write_list(struct doq_conn* conn, struct doq_stream* stream) { if(!stream->on_write_list) return; if(stream->write_next) stream->write_next->write_prev = stream->write_prev; else conn->stream_write_last = stream->write_prev; if(stream->write_prev) stream->write_prev->write_next = stream->write_next; else conn->stream_write_first = stream->write_next; stream->write_prev = NULL; stream->write_next = NULL; stream->on_write_list = 0; } /** doq stream remove in buffer */ static void doq_stream_remove_in_buffer(struct doq_stream* stream, struct doq_table* table) { if(stream->in) { doq_table_quic_size_subtract(table, stream->inlen); free(stream->in); stream->in = NULL; stream->inlen = 0; } } /** doq stream remove out buffer */ static void doq_stream_remove_out_buffer(struct doq_stream* stream, struct doq_table* table) { if(stream->out) { doq_table_quic_size_subtract(table, stream->outlen); free(stream->out); stream->out = NULL; stream->outlen = 0; } } int doq_stream_close(struct doq_conn* conn, struct doq_stream* stream, int send_shutdown) { int ret; if(stream->is_closed) return 1; stream->is_closed = 1; doq_stream_off_write_list(conn, stream); if(send_shutdown) { verbose(VERB_ALGO, "doq: shutdown stream_id %d with app_error_code %d", (int)stream->stream_id, (int)DOQ_APP_ERROR_CODE); ret = ngtcp2_conn_shutdown_stream(conn->conn, #ifdef HAVE_NGTCP2_CONN_SHUTDOWN_STREAM4 0, #endif stream->stream_id, DOQ_APP_ERROR_CODE); if(ret != 0) { log_err("doq ngtcp2_conn_shutdown_stream %d failed: %s", (int)stream->stream_id, ngtcp2_strerror(ret)); return 0; } doq_conn_write_enable(conn); } verbose(VERB_ALGO, "doq: conn extend max streams bidi by 1"); ngtcp2_conn_extend_max_streams_bidi(conn->conn, 1); doq_conn_write_enable(conn); doq_stream_remove_in_buffer(stream, conn->doq_socket->table); doq_stream_remove_out_buffer(stream, conn->doq_socket->table); doq_table_quic_size_subtract(conn->doq_socket->table, sizeof(*stream)); doq_conn_del_stream(conn, stream); doq_stream_delete(stream); return 1; } /** doq stream pick up answer data from buffer */ static int doq_stream_pickup_answer(struct doq_stream* stream, struct sldns_buffer* buf) { stream->is_answer_available = 1; if(stream->out) { free(stream->out); stream->out = NULL; stream->outlen = 0; } stream->nwrite = 0; stream->outlen = sldns_buffer_limit(buf); /* For quic the output bytes have to stay allocated and available, * for potential resends, until the remote end has acknowledged them. * This includes the tcplen start uint16_t, in outlen_wire. */ stream->outlen_wire = htons(stream->outlen); stream->out = memdup(sldns_buffer_begin(buf), sldns_buffer_limit(buf)); if(!stream->out) { log_err("doq could not send answer: out of memory"); return 0; } return 1; } int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, struct sldns_buffer* buf) { if(verbosity >= VERB_ALGO) { char* s = sldns_wire2str_pkt(sldns_buffer_begin(buf), sldns_buffer_limit(buf)); verbose(VERB_ALGO, "doq stream %d response\n%s", (int)stream->stream_id, (s?s:"null")); free(s); } if(stream->out) doq_table_quic_size_subtract(conn->doq_socket->table, stream->outlen); if(!doq_stream_pickup_answer(stream, buf)) return 0; doq_table_quic_size_add(conn->doq_socket->table, stream->outlen); doq_stream_on_write_list(conn, stream); doq_conn_write_enable(conn); return 1; } /** doq stream data length has completed, allocations can be done. False on * allocation failure. */ static int doq_stream_datalen_complete(struct doq_stream* stream, struct doq_table* table) { if(stream->inlen > 1024*1024) { log_err("doq stream in length too large %d", (int)stream->inlen); return 0; } stream->in = calloc(1, stream->inlen); if(!stream->in) { log_err("doq could not read stream, calloc failed: " "out of memory"); return 0; } doq_table_quic_size_add(table, stream->inlen); return 1; } /** doq stream data is complete, the input data has been received. */ static int doq_stream_data_complete(struct doq_conn* conn, struct doq_stream* stream) { struct comm_point* c; if(verbosity >= VERB_ALGO) { char* s = sldns_wire2str_pkt(stream->in, stream->inlen); char a[128]; addr_to_str((void*)&conn->key.paddr.addr, conn->key.paddr.addrlen, a, sizeof(a)); verbose(VERB_ALGO, "doq %s stream %d incoming query\n%s", a, (int)stream->stream_id, (s?s:"null")); free(s); } stream->is_query_complete = 1; c = conn->doq_socket->cp; if(!stream->in) { verbose(VERB_ALGO, "doq_stream_data_complete: no in buffer"); return 0; } if(stream->inlen > sldns_buffer_capacity(c->buffer)) { verbose(VERB_ALGO, "doq_stream_data_complete: query too long"); return 0; } sldns_buffer_clear(c->buffer); sldns_buffer_write(c->buffer, stream->in, stream->inlen); sldns_buffer_flip(c->buffer); c->repinfo.c = c; if(!doq_conn_key_store_repinfo(&conn->key, &c->repinfo)) { verbose(VERB_ALGO, "doq_stream_data_complete: connection " "DCID too long"); return 0; } c->repinfo.doq_streamid = stream->stream_id; conn->doq_socket->current_conn = conn; fptr_ok(fptr_whitelist_comm_point(c->callback)); if( (*c->callback)(c, c->cb_arg, NETEVENT_NOERROR, &c->repinfo)) { conn->doq_socket->current_conn = NULL; if(!doq_stream_send_reply(conn, stream, c->buffer)) { verbose(VERB_ALGO, "doq: failed to send_reply"); return 0; } return 1; } conn->doq_socket->current_conn = NULL; return 1; } /** doq receive data for a stream, more bytes of the incoming data */ static int doq_stream_recv_data(struct doq_stream* stream, const uint8_t* data, size_t datalen, int* recv_done, struct doq_table* table) { int got_data = 0; /* read the tcplength uint16_t at the start */ if(stream->nread < 2) { uint16_t tcplen = 0; size_t todolen = 2 - stream->nread; if(stream->nread > 0) { /* put in the already read byte if there is one */ tcplen = stream->inlen; } if(datalen < todolen) todolen = datalen; memmove(((uint8_t*)&tcplen)+stream->nread, data, todolen); stream->nread += todolen; data += todolen; datalen -= todolen; if(stream->nread == 2) { /* the initial length value is completed */ stream->inlen = ntohs(tcplen); if(!doq_stream_datalen_complete(stream, table)) return 0; } else { /* store for later */ stream->inlen = tcplen; return 1; } } /* if there are more data bytes */ if(datalen > 0) { size_t to_write = datalen; if(stream->nread-2 > stream->inlen) { verbose(VERB_ALGO, "doq stream buffer too small"); return 0; } if(datalen > stream->inlen - (stream->nread-2)) to_write = stream->inlen - (stream->nread-2); if(to_write > 0) { if(!stream->in) { verbose(VERB_ALGO, "doq: stream has " "no buffer"); return 0; } memmove(stream->in+(stream->nread-2), data, to_write); stream->nread += to_write; data += to_write; datalen -= to_write; got_data = 1; } } /* Are there extra bytes received after the end? If so, log them. */ if(datalen > 0) { if(verbosity >= VERB_ALGO) log_hex("doq stream has extra bytes received after end", (void*)data, datalen); } /* Is the input data complete? */ if(got_data && stream->nread >= stream->inlen+2) { if(!stream->in) { verbose(VERB_ALGO, "doq: completed stream has " "no buffer"); return 0; } *recv_done = 1; } return 1; } /** doq receive FIN for a stream. No more bytes are going to arrive. */ static int doq_stream_recv_fin(struct doq_conn* conn, struct doq_stream* stream, int recv_done) { if(!stream->is_query_complete && !recv_done) { verbose(VERB_ALGO, "doq: stream recv FIN, but is " "not complete, have %d of %d bytes", ((int)stream->nread)-2, (int)stream->inlen); if(!doq_stream_close(conn, stream, 1)) return 0; } return 1; } void doq_fill_rand(struct ub_randstate* rnd, uint8_t* buf, size_t len) { size_t i; for(i=0; idoq_socket->rnd, data, datalen); if(!doq_conid_find(conn->table, data, datalen)) { /* Found an unused connection id. */ return 1; } } verbose(VERB_ALGO, "doq_conn_generate_new_conid failed: could not " "generate random unused connection id value in %d attempts.", max_try); return 0; } /** ngtcp2 rand callback function */ static void doq_rand_cb(uint8_t* dest, size_t destlen, const ngtcp2_rand_ctx* rand_ctx) { struct ub_randstate* rnd = (struct ub_randstate*) rand_ctx->native_handle; doq_fill_rand(rnd, dest, destlen); } /** ngtcp2 get_new_connection_id callback function */ static int doq_get_new_connection_id_cb(ngtcp2_conn* ATTR_UNUSED(conn), ngtcp2_cid* cid, uint8_t* token, size_t cidlen, void* user_data) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; /* Lock the conid tree, so we can check for duplicates while * generating the id, and then insert it, whilst keeping the tree * locked against other modifications, guaranteeing uniqueness. */ lock_rw_wrlock(&doq_conn->table->conid_lock); if(!doq_conn_generate_new_conid(doq_conn, cid->data, cidlen)) { lock_rw_unlock(&doq_conn->table->conid_lock); return NGTCP2_ERR_CALLBACK_FAILURE; } cid->datalen = cidlen; if(ngtcp2_crypto_generate_stateless_reset_token(token, doq_conn->doq_socket->static_secret, doq_conn->doq_socket->static_secret_len, cid) != 0) { lock_rw_unlock(&doq_conn->table->conid_lock); return NGTCP2_ERR_CALLBACK_FAILURE; } if(!doq_conn_associate_conid(doq_conn, cid->data, cid->datalen)) { lock_rw_unlock(&doq_conn->table->conid_lock); return NGTCP2_ERR_CALLBACK_FAILURE; } lock_rw_unlock(&doq_conn->table->conid_lock); return 0; } /** ngtcp2 remove_connection_id callback function */ static int doq_remove_connection_id_cb(ngtcp2_conn* ATTR_UNUSED(conn), const ngtcp2_cid* cid, void* user_data) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; lock_rw_wrlock(&doq_conn->table->conid_lock); doq_conn_dissociate_conid(doq_conn, cid->data, cid->datalen); lock_rw_unlock(&doq_conn->table->conid_lock); return 0; } /** doq submit a new token */ static int doq_submit_new_token(struct doq_conn* conn) { uint8_t token[NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN]; ngtcp2_ssize tokenlen; int ret; const ngtcp2_path* path = ngtcp2_conn_get_path(conn->conn); ngtcp2_tstamp ts = doq_get_timestamp_nanosec(); tokenlen = ngtcp2_crypto_generate_regular_token(token, conn->doq_socket->static_secret, conn->doq_socket->static_secret_len, path->remote.addr, path->remote.addrlen, ts); if(tokenlen < 0) { log_err("doq ngtcp2_crypto_generate_regular_token failed"); return 1; } verbose(VERB_ALGO, "doq submit new token"); ret = ngtcp2_conn_submit_new_token(conn->conn, token, tokenlen); if(ret != 0) { log_err("doq ngtcp2_conn_submit_new_token failed: %s", ngtcp2_strerror(ret)); return 0; } return 1; } /** ngtcp2 handshake_completed callback function */ static int doq_handshake_completed_cb(ngtcp2_conn* ATTR_UNUSED(conn), void* user_data) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; verbose(VERB_ALGO, "doq handshake_completed callback"); verbose(VERB_ALGO, "ngtcp2_conn_get_max_data_left is %d", (int)ngtcp2_conn_get_max_data_left(doq_conn->conn)); #ifdef HAVE_NGTCP2_CONN_GET_MAX_LOCAL_STREAMS_UNI verbose(VERB_ALGO, "ngtcp2_conn_get_max_local_streams_uni is %d", (int)ngtcp2_conn_get_max_local_streams_uni(doq_conn->conn)); #endif verbose(VERB_ALGO, "ngtcp2_conn_get_streams_uni_left is %d", (int)ngtcp2_conn_get_streams_uni_left(doq_conn->conn)); verbose(VERB_ALGO, "ngtcp2_conn_get_streams_bidi_left is %d", (int)ngtcp2_conn_get_streams_bidi_left(doq_conn->conn)); verbose(VERB_ALGO, "negotiated cipher name is %s", SSL_get_cipher_name(doq_conn->ssl)); if(verbosity > VERB_ALGO) { const unsigned char* alpn = NULL; unsigned int alpnlen = 0; char alpnstr[128]; SSL_get0_alpn_selected(doq_conn->ssl, &alpn, &alpnlen); if(alpnlen > sizeof(alpnstr)-1) alpnlen = sizeof(alpnstr)-1; memmove(alpnstr, alpn, alpnlen); alpnstr[alpnlen]=0; verbose(VERB_ALGO, "negotiated ALPN is '%s'", alpnstr); } if(!doq_submit_new_token(doq_conn)) return -1; return 0; } /** ngtcp2 stream_open callback function */ static int doq_stream_open_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id, void* user_data) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; struct doq_stream* stream; verbose(VERB_ALGO, "doq new stream %x", (int)stream_id); if(doq_stream_find(doq_conn, stream_id)) { verbose(VERB_ALGO, "doq: stream with this id already exists"); return 0; } if(stream_id != 0 && stream_id != 4 && /* allow one stream on a new connection */ !doq_table_quic_size_available(doq_conn->doq_socket->table, doq_conn->doq_socket->cfg, sizeof(*stream) + 100 /* estimated query in */ + 512 /* estimated response out */ )) { int rv; verbose(VERB_ALGO, "doq: no mem for new stream"); rv = ngtcp2_conn_shutdown_stream(doq_conn->conn, #ifdef HAVE_NGTCP2_CONN_SHUTDOWN_STREAM4 0, #endif stream_id, NGTCP2_CONNECTION_REFUSED); if(rv != 0) { log_err("ngtcp2_conn_shutdown_stream failed: %s", ngtcp2_strerror(rv)); return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } stream = doq_stream_create(stream_id); if(!stream) { log_err("doq: could not doq_stream_create: out of memory"); return NGTCP2_ERR_CALLBACK_FAILURE; } doq_table_quic_size_add(doq_conn->doq_socket->table, sizeof(*stream)); doq_conn_add_stream(doq_conn, stream); return 0; } /** ngtcp2 recv_stream_data callback function */ static int doq_recv_stream_data_cb(ngtcp2_conn* ATTR_UNUSED(conn), uint32_t flags, int64_t stream_id, uint64_t offset, const uint8_t* data, size_t datalen, void* user_data, void* ATTR_UNUSED(stream_user_data)) { int recv_done = 0; struct doq_conn* doq_conn = (struct doq_conn*)user_data; struct doq_stream* stream; verbose(VERB_ALGO, "doq recv stream data stream id %d offset %d " "datalen %d%s%s", (int)stream_id, (int)offset, (int)datalen, ((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0?" FIN":""), #ifdef NGTCP2_STREAM_DATA_FLAG_0RTT ((flags&NGTCP2_STREAM_DATA_FLAG_0RTT)!=0?" 0RTT":"") #else ((flags&NGTCP2_STREAM_DATA_FLAG_EARLY)!=0?" EARLY":"") #endif ); stream = doq_stream_find(doq_conn, stream_id); if(!stream) { verbose(VERB_ALGO, "doq: received stream data for " "unknown stream %d", (int)stream_id); return 0; } if(stream->is_closed) { verbose(VERB_ALGO, "doq: stream is closed, ignore recv data"); return 0; } if(datalen != 0) { if(!doq_stream_recv_data(stream, data, datalen, &recv_done, doq_conn->doq_socket->table)) return NGTCP2_ERR_CALLBACK_FAILURE; } if((flags&NGTCP2_STREAM_DATA_FLAG_FIN)!=0) { if(!doq_stream_recv_fin(doq_conn, stream, recv_done)) return NGTCP2_ERR_CALLBACK_FAILURE; } ngtcp2_conn_extend_max_stream_offset(doq_conn->conn, stream_id, datalen); ngtcp2_conn_extend_max_offset(doq_conn->conn, datalen); if(recv_done) { if(!doq_stream_data_complete(doq_conn, stream)) return NGTCP2_ERR_CALLBACK_FAILURE; } return 0; } /** ngtcp2 stream_close callback function */ static int doq_stream_close_cb(ngtcp2_conn* ATTR_UNUSED(conn), uint32_t flags, int64_t stream_id, uint64_t app_error_code, void* user_data, void* ATTR_UNUSED(stream_user_data)) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; struct doq_stream* stream; if((flags&NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)!=0) verbose(VERB_ALGO, "doq stream close for stream id %d %sapp_error_code %d", (int)stream_id, (((flags&NGTCP2_STREAM_CLOSE_FLAG_APP_ERROR_CODE_SET)!=0)? "APP_ERROR_CODE_SET ":""), (int)app_error_code); else verbose(VERB_ALGO, "doq stream close for stream id %d", (int)stream_id); stream = doq_stream_find(doq_conn, stream_id); if(!stream) { verbose(VERB_ALGO, "doq: stream close for " "unknown stream %d", (int)stream_id); return 0; } if(!doq_stream_close(doq_conn, stream, 0)) return NGTCP2_ERR_CALLBACK_FAILURE; return 0; } /** ngtcp2 stream_reset callback function */ static int doq_stream_reset_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id, uint64_t final_size, uint64_t app_error_code, void* user_data, void* ATTR_UNUSED(stream_user_data)) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; struct doq_stream* stream; verbose(VERB_ALGO, "doq stream reset for stream id %d final_size %d " "app_error_code %d", (int)stream_id, (int)final_size, (int)app_error_code); stream = doq_stream_find(doq_conn, stream_id); if(!stream) { verbose(VERB_ALGO, "doq: stream reset for " "unknown stream %d", (int)stream_id); return 0; } if(!doq_stream_close(doq_conn, stream, 0)) return NGTCP2_ERR_CALLBACK_FAILURE; return 0; } /** ngtcp2 acked_stream_data_offset callback function */ static int doq_acked_stream_data_offset_cb(ngtcp2_conn* ATTR_UNUSED(conn), int64_t stream_id, uint64_t offset, uint64_t datalen, void* user_data, void* ATTR_UNUSED(stream_user_data)) { struct doq_conn* doq_conn = (struct doq_conn*)user_data; struct doq_stream* stream; verbose(VERB_ALGO, "doq stream acked data for stream id %d offset %d " "datalen %d", (int)stream_id, (int)offset, (int)datalen); stream = doq_stream_find(doq_conn, stream_id); if(!stream) { verbose(VERB_ALGO, "doq: stream acked data for " "unknown stream %d", (int)stream_id); return 0; } /* Acked the data from [offset .. offset+datalen). */ if(stream->is_closed) return 0; if(offset+datalen >= stream->outlen) { doq_stream_remove_in_buffer(stream, doq_conn->doq_socket->table); doq_stream_remove_out_buffer(stream, doq_conn->doq_socket->table); } return 0; } /** ngtc2p log_printf callback function */ static void doq_log_printf_cb(void* ATTR_UNUSED(user_data), const char* fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); verbose(VERB_ALGO, "libngtcp2: %s", buf); va_end(ap); } #ifdef MAKE_QUIC_METHOD /** the doq application tx key callback, false on failure */ static int doq_application_tx_key_cb(struct doq_conn* conn) { verbose(VERB_ALGO, "doq application tx key cb"); /* The server does not want to open streams to the client, * the client instead initiates by opening bidi streams. */ verbose(VERB_ALGO, "doq ngtcp2_conn_get_max_data_left is %d", (int)ngtcp2_conn_get_max_data_left(conn->conn)); #ifdef HAVE_NGTCP2_CONN_GET_MAX_LOCAL_STREAMS_UNI verbose(VERB_ALGO, "doq ngtcp2_conn_get_max_local_streams_uni is %d", (int)ngtcp2_conn_get_max_local_streams_uni(conn->conn)); #endif verbose(VERB_ALGO, "doq ngtcp2_conn_get_streams_uni_left is %d", (int)ngtcp2_conn_get_streams_uni_left(conn->conn)); verbose(VERB_ALGO, "doq ngtcp2_conn_get_streams_bidi_left is %d", (int)ngtcp2_conn_get_streams_bidi_left(conn->conn)); return 1; } /** quic_method set_encryption_secrets function */ static int doq_set_encryption_secrets(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *read_secret, const uint8_t *write_secret, size_t secret_len) { struct doq_conn* doq_conn = (struct doq_conn*)SSL_get_app_data(ssl); #ifdef HAVE_NGTCP2_ENCRYPTION_LEVEL ngtcp2_encryption_level #else ngtcp2_crypto_level #endif level = #ifdef USE_NGTCP2_CRYPTO_OSSL ngtcp2_crypto_ossl_from_ossl_encryption_level(ossl_level); #elif defined(HAVE_NGTCP2_CRYPTO_QUICTLS_FROM_OSSL_ENCRYPTION_LEVEL) ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level); #else ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); #endif if(read_secret) { verbose(VERB_ALGO, "doq: ngtcp2_crypto_derive_and_install_rx_key for level %d ossl %d", (int)level, (int)ossl_level); if(ngtcp2_crypto_derive_and_install_rx_key(doq_conn->conn, NULL, NULL, NULL, level, read_secret, secret_len) != 0) { log_err("ngtcp2_crypto_derive_and_install_rx_key " "failed"); return 0; } } if(write_secret) { verbose(VERB_ALGO, "doq: ngtcp2_crypto_derive_and_install_tx_key for level %d ossl %d", (int)level, (int)ossl_level); if(ngtcp2_crypto_derive_and_install_tx_key(doq_conn->conn, NULL, NULL, NULL, level, write_secret, secret_len) != 0) { log_err("ngtcp2_crypto_derive_and_install_tx_key " "failed"); return 0; } if(level == NGTCP2_CRYPTO_LEVEL_APPLICATION) { if(!doq_application_tx_key_cb(doq_conn)) return 0; } } return 1; } /** quic_method add_handshake_data function */ static int doq_add_handshake_data(SSL *ssl, OSSL_ENCRYPTION_LEVEL ossl_level, const uint8_t *data, size_t len) { struct doq_conn* doq_conn = (struct doq_conn*)SSL_get_app_data(ssl); #ifdef HAVE_NGTCP2_ENCRYPTION_LEVEL ngtcp2_encryption_level #else ngtcp2_crypto_level #endif level = #ifdef USE_NGTCP2_CRYPTO_OSSL ngtcp2_crypto_ossl_from_ossl_encryption_level(ossl_level); #elif defined(HAVE_NGTCP2_CRYPTO_QUICTLS_FROM_OSSL_ENCRYPTION_LEVEL) ngtcp2_crypto_quictls_from_ossl_encryption_level(ossl_level); #else ngtcp2_crypto_openssl_from_ossl_encryption_level(ossl_level); #endif int rv; verbose(VERB_ALGO, "doq_add_handshake_data: " "ngtcp2_con_submit_crypto_data level %d", (int)level); rv = ngtcp2_conn_submit_crypto_data(doq_conn->conn, level, data, len); if(rv != 0) { log_err("ngtcp2_conn_submit_crypto_data failed: %s", ngtcp2_strerror(rv)); ngtcp2_conn_set_tls_error(doq_conn->conn, rv); return 0; } return 1; } /** quic_method flush_flight function */ static int doq_flush_flight(SSL* ATTR_UNUSED(ssl)) { return 1; } /** quic_method send_alert function */ static int doq_send_alert(SSL *ssl, enum ssl_encryption_level_t ATTR_UNUSED(level), uint8_t alert) { struct doq_conn* doq_conn = (struct doq_conn*)SSL_get_app_data(ssl); doq_conn->tls_alert = alert; return 1; } #endif /* MAKE_QUIC_METHOD */ /** ALPN select callback for the doq SSL context */ static int doq_alpn_select_cb(SSL* ATTR_UNUSED(ssl), const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* ATTR_UNUSED(arg)) { /* select "doq" */ int ret = SSL_select_next_proto((void*)out, outlen, (const unsigned char*)"\x03""doq", 4, in, inlen); if(ret == OPENSSL_NPN_NEGOTIATED) return SSL_TLSEXT_ERR_OK; verbose(VERB_ALGO, "doq alpn_select_cb: ALPN from client does " "not have 'doq'"); return SSL_TLSEXT_ERR_ALERT_FATAL; } void* quic_sslctx_create(char* key, char* pem, char* verifypem) { #ifdef HAVE_NGTCP2 char* sid_ctx = "unbound server"; #ifdef MAKE_QUIC_METHOD SSL_QUIC_METHOD* quic_method; #endif SSL_CTX* ctx = SSL_CTX_new(TLS_server_method()); if(!ctx) { log_crypto_err("Could not SSL_CTX_new"); return NULL; } if(!key || key[0] == 0) { log_err("doq: error, no tls-service-key file specified"); SSL_CTX_free(ctx); return NULL; } if(!pem || pem[0] == 0) { log_err("doq: error, no tls-service-pem file specified"); SSL_CTX_free(ctx); return NULL; } SSL_CTX_set_options(ctx, (SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS) | SSL_OP_SINGLE_ECDH_USE | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_ANTI_REPLAY); SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); SSL_CTX_set_min_proto_version(ctx, TLS1_3_VERSION); SSL_CTX_set_max_proto_version(ctx, TLS1_3_VERSION); #ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB SSL_CTX_set_alpn_select_cb(ctx, doq_alpn_select_cb, NULL); #endif SSL_CTX_set_default_verify_paths(ctx); if(!SSL_CTX_use_certificate_chain_file(ctx, pem)) { log_err("doq: error for cert file: %s", pem); log_crypto_err("doq: error in " "SSL_CTX_use_certificate_chain_file"); SSL_CTX_free(ctx); return NULL; } if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) { log_err("doq: error for private key file: %s", key); log_crypto_err("doq: error in SSL_CTX_use_PrivateKey_file"); SSL_CTX_free(ctx); return NULL; } if(!SSL_CTX_check_private_key(ctx)) { log_err("doq: error for key file: %s", key); log_crypto_err("doq: error in SSL_CTX_check_private_key"); SSL_CTX_free(ctx); return NULL; } SSL_CTX_set_session_id_context(ctx, (void*)sid_ctx, strlen(sid_ctx)); if(verifypem && verifypem[0]) { if(!SSL_CTX_load_verify_locations(ctx, verifypem, NULL)) { log_err("doq: error for verify pem file: %s", verifypem); log_crypto_err("doq: error in " "SSL_CTX_load_verify_locations"); SSL_CTX_free(ctx); return NULL; } SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file( verifypem)); SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER| SSL_VERIFY_CLIENT_ONCE| SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); } SSL_CTX_set_max_early_data(ctx, 0xffffffff); #ifdef HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT if(ngtcp2_crypto_quictls_configure_server_context(ctx) != 0) { log_err("ngtcp2_crypto_quictls_configure_server_context failed"); SSL_CTX_free(ctx); return NULL; } #elif defined(MAKE_QUIC_METHOD) /* The quic_method needs to remain valid during the SSL_CTX * lifetime, so we allocate it. It is freed with the * doq_server_socket. */ quic_method = calloc(1, sizeof(SSL_QUIC_METHOD)); if(!quic_method) { log_err("calloc failed: out of memory"); SSL_CTX_free(ctx); return NULL; } doq_socket->quic_method = quic_method; quic_method->set_encryption_secrets = doq_set_encryption_secrets; quic_method->add_handshake_data = doq_add_handshake_data; quic_method->flush_flight = doq_flush_flight; quic_method->send_alert = doq_send_alert; SSL_CTX_set_quic_method(ctx, doq_socket->quic_method); #endif return ctx; #else /* HAVE_NGTCP2 */ (void)key; (void)pem; (void)verifypem; return NULL; #endif /* HAVE_NGTCP2 */ } /** Get the ngtcp2_conn from ssl userdata of type ngtcp2_conn_ref */ static ngtcp2_conn* doq_conn_ref_get_conn(ngtcp2_crypto_conn_ref* conn_ref) { struct doq_conn* conn = (struct doq_conn*)conn_ref->user_data; return conn->conn; } /** create new SSL session for server connection */ static SSL* doq_ssl_server_setup(SSL_CTX* ctx, struct doq_conn* conn) { #ifdef USE_NGTCP2_CRYPTO_OSSL int ret; #endif SSL* ssl = SSL_new(ctx); if(!ssl) { log_crypto_err("doq: SSL_new failed"); return NULL; } #ifdef USE_NGTCP2_CRYPTO_OSSL if((ret=ngtcp2_crypto_ossl_ctx_new(&conn->ossl_ctx, NULL)) != 0) { log_err("doq: ngtcp2_crypto_ossl_ctx_new failed: %s", ngtcp2_strerror(ret)); SSL_free(ssl); return NULL; } ngtcp2_crypto_ossl_ctx_set_ssl(conn->ossl_ctx, ssl); if(ngtcp2_crypto_ossl_configure_server_session(ssl) != 0) { log_err("doq: ngtcp2_crypto_ossl_configure_server_session failed"); SSL_free(ssl); return NULL; } #endif #if defined(USE_NGTCP2_CRYPTO_OSSL) || defined(HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT) conn->conn_ref.get_conn = &doq_conn_ref_get_conn; conn->conn_ref.user_data = conn; SSL_set_app_data(ssl, &conn->conn_ref); #else SSL_set_app_data(ssl, conn); #endif SSL_set_accept_state(ssl); #ifdef USE_NGTCP2_CRYPTO_OSSL SSL_set_quic_tls_early_data_enabled(ssl, 1); #else SSL_set_quic_early_data_enabled(ssl, 1); #endif return ssl; } int doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen, uint8_t* ocid, size_t ocidlen, const uint8_t* token, size_t tokenlen) { int rv; struct ngtcp2_cid dcid, sv_scid, scid_cid; struct ngtcp2_path path; struct ngtcp2_callbacks callbacks; struct ngtcp2_settings settings; struct ngtcp2_transport_params params; memset(&dcid, 0, sizeof(dcid)); memset(&sv_scid, 0, sizeof(sv_scid)); memset(&scid_cid, 0, sizeof(scid_cid)); memset(&path, 0, sizeof(path)); memset(&callbacks, 0, sizeof(callbacks)); memset(&settings, 0, sizeof(settings)); memset(¶ms, 0, sizeof(params)); ngtcp2_cid_init(&scid_cid, scid, scidlen); ngtcp2_cid_init(&dcid, conn->key.dcid, conn->key.dcidlen); path.remote.addr = (struct sockaddr*)&conn->key.paddr.addr; path.remote.addrlen = conn->key.paddr.addrlen; path.local.addr = (struct sockaddr*)&conn->key.paddr.localaddr; path.local.addrlen = conn->key.paddr.localaddrlen; callbacks.recv_client_initial = ngtcp2_crypto_recv_client_initial_cb; callbacks.recv_crypto_data = ngtcp2_crypto_recv_crypto_data_cb; callbacks.encrypt = ngtcp2_crypto_encrypt_cb; callbacks.decrypt = ngtcp2_crypto_decrypt_cb; callbacks.hp_mask = ngtcp2_crypto_hp_mask; callbacks.update_key = ngtcp2_crypto_update_key_cb; callbacks.delete_crypto_aead_ctx = ngtcp2_crypto_delete_crypto_aead_ctx_cb; callbacks.delete_crypto_cipher_ctx = ngtcp2_crypto_delete_crypto_cipher_ctx_cb; callbacks.get_path_challenge_data = ngtcp2_crypto_get_path_challenge_data_cb; callbacks.version_negotiation = ngtcp2_crypto_version_negotiation_cb; callbacks.rand = doq_rand_cb; callbacks.get_new_connection_id = doq_get_new_connection_id_cb; callbacks.remove_connection_id = doq_remove_connection_id_cb; callbacks.handshake_completed = doq_handshake_completed_cb; callbacks.stream_open = doq_stream_open_cb; callbacks.stream_close = doq_stream_close_cb; callbacks.stream_reset = doq_stream_reset_cb; callbacks.acked_stream_data_offset = doq_acked_stream_data_offset_cb; callbacks.recv_stream_data = doq_recv_stream_data_cb; ngtcp2_settings_default(&settings); if(verbosity >= VERB_ALGO) { settings.log_printf = doq_log_printf_cb; } settings.rand_ctx.native_handle = conn->doq_socket->rnd; settings.initial_ts = doq_get_timestamp_nanosec(); settings.max_stream_window = 6*1024*1024; settings.max_window = 6*1024*1024; #ifdef HAVE_STRUCT_NGTCP2_SETTINGS_TOKENLEN settings.token = (void*)token; settings.tokenlen = tokenlen; #else settings.token.base = (void*)token; settings.token.len = tokenlen; #endif ngtcp2_transport_params_default(¶ms); params.max_idle_timeout = conn->doq_socket->idle_timeout; params.active_connection_id_limit = 7; params.initial_max_stream_data_bidi_local = 256*1024; params.initial_max_stream_data_bidi_remote = 256*1024; params.initial_max_data = 1024*1024; /* DoQ uses bidi streams, so we allow 0 uni streams. */ params.initial_max_streams_uni = 0; /* Initial max on number of bidi streams the remote end can open. * That is the number of queries it can make, at first. */ params.initial_max_streams_bidi = 10; if(ocid) { ngtcp2_cid_init(¶ms.original_dcid, ocid, ocidlen); ngtcp2_cid_init(¶ms.retry_scid, conn->key.dcid, conn->key.dcidlen); params.retry_scid_present = 1; } else { ngtcp2_cid_init(¶ms.original_dcid, conn->key.dcid, conn->key.dcidlen); } #ifdef HAVE_STRUCT_NGTCP2_TRANSPORT_PARAMS_ORIGINAL_DCID_PRESENT params.original_dcid_present = 1; #endif doq_fill_rand(conn->doq_socket->rnd, params.stateless_reset_token, sizeof(params.stateless_reset_token)); sv_scid.datalen = conn->doq_socket->sv_scidlen; lock_rw_wrlock(&conn->table->conid_lock); if(!doq_conn_generate_new_conid(conn, sv_scid.data, sv_scid.datalen)) { lock_rw_unlock(&conn->table->conid_lock); return 0; } rv = ngtcp2_conn_server_new(&conn->conn, &scid_cid, &sv_scid, &path, conn->version, &callbacks, &settings, ¶ms, NULL, conn); if(rv != 0) { lock_rw_unlock(&conn->table->conid_lock); log_err("ngtcp2_conn_server_new failed: %s", ngtcp2_strerror(rv)); return 0; } if(!doq_conn_setup_conids(conn)) { lock_rw_unlock(&conn->table->conid_lock); log_err("doq_conn_setup_conids failed: out of memory"); return 0; } lock_rw_unlock(&conn->table->conid_lock); conn->ssl = doq_ssl_server_setup((SSL_CTX*)conn->doq_socket->ctx, conn); if(!conn->ssl) { log_err("doq_ssl_server_setup failed"); return 0; } #ifdef USE_NGTCP2_CRYPTO_OSSL ngtcp2_conn_set_tls_native_handle(conn->conn, conn->ossl_ctx); #else ngtcp2_conn_set_tls_native_handle(conn->conn, conn->ssl); #endif doq_conn_write_enable(conn); return 1; } struct doq_conid* doq_conid_find(struct doq_table* table, const uint8_t* data, size_t datalen) { struct rbnode_type* node; struct doq_conid key; key.node.key = &key; key.cid = (void*)data; key.cidlen = datalen; log_assert(table != NULL); node = rbtree_search(table->conid_tree, &key); if(node) return (struct doq_conid*)node->key; return NULL; } /** insert conid in the conid list */ static void doq_conid_list_insert(struct doq_conn* conn, struct doq_conid* conid) { conid->prev = NULL; conid->next = conn->conid_list; if(conn->conid_list) conn->conid_list->prev = conid; conn->conid_list = conid; } /** remove conid from the conid list */ static void doq_conid_list_remove(struct doq_conn* conn, struct doq_conid* conid) { if(conid->prev) conid->prev->next = conid->next; else conn->conid_list = conid->next; if(conid->next) conid->next->prev = conid->prev; } /** create a doq_conid */ static struct doq_conid* doq_conid_create(uint8_t* data, size_t datalen, struct doq_conn_key* key) { struct doq_conid* conid; conid = calloc(1, sizeof(*conid)); if(!conid) return NULL; conid->cid = memdup(data, datalen); if(!conid->cid) { free(conid); return NULL; } conid->cidlen = datalen; conid->node.key = conid; conid->key = *key; conid->key.dcid = memdup(key->dcid, key->dcidlen); if(!conid->key.dcid) { free(conid->cid); free(conid); return NULL; } return conid; } void doq_conid_delete(struct doq_conid* conid) { if(!conid) return; free(conid->key.dcid); free(conid->cid); free(conid); } /** return true if the conid is for the conn. */ static int conid_is_for_conn(struct doq_conn* conn, struct doq_conid* conid) { if(conid->key.dcidlen == conn->key.dcidlen && memcmp(conid->key.dcid, conn->key.dcid, conid->key.dcidlen)==0 && conid->key.paddr.addrlen == conn->key.paddr.addrlen && memcmp(&conid->key.paddr.addr, &conn->key.paddr.addr, conid->key.paddr.addrlen) == 0 && conid->key.paddr.localaddrlen == conn->key.paddr.localaddrlen && memcmp(&conid->key.paddr.localaddr, &conn->key.paddr.localaddr, conid->key.paddr.localaddrlen) == 0 && conid->key.paddr.ifindex == conn->key.paddr.ifindex) return 1; return 0; } int doq_conn_associate_conid(struct doq_conn* conn, uint8_t* data, size_t datalen) { struct doq_conid* conid; conid = doq_conid_find(conn->table, data, datalen); if(conid && !conid_is_for_conn(conn, conid)) { verbose(VERB_ALGO, "doq connection id already exists for " "another doq_conn. Ignoring second connection id."); /* Already exists to another conn, ignore it. * This works, in that the conid is listed in the doq_conn * conid_list element, and removed from there. So our conid * tree and list are fine, when created and removed. * The tree now does not have the lookup element pointing * to this connection. */ return 1; } if(conid) return 1; /* already inserted */ conid = doq_conid_create(data, datalen, &conn->key); if(!conid) return 0; doq_conid_list_insert(conn, conid); (void)rbtree_insert(conn->table->conid_tree, &conid->node); return 1; } void doq_conn_dissociate_conid(struct doq_conn* conn, const uint8_t* data, size_t datalen) { struct doq_conid* conid; conid = doq_conid_find(conn->table, data, datalen); if(conid && !conid_is_for_conn(conn, conid)) return; if(conid) { (void)rbtree_delete(conn->table->conid_tree, conid->node.key); doq_conid_list_remove(conn, conid); doq_conid_delete(conid); } } /** associate the scid array and also the dcid. * caller must hold the locks on conn and doq_table.conid_lock. */ static int doq_conn_setup_id_array_and_dcid(struct doq_conn* conn, struct ngtcp2_cid* scids, size_t num_scid) { size_t i; for(i=0; ikey.dcid, conn->key.dcidlen)) return 0; return 1; } int doq_conn_setup_conids(struct doq_conn* conn) { size_t num_scid = #ifndef HAVE_NGTCP2_CONN_GET_NUM_SCID ngtcp2_conn_get_scid(conn->conn, NULL); #else ngtcp2_conn_get_num_scid(conn->conn); #endif if(num_scid <= 4) { struct ngtcp2_cid ids[4]; /* Usually there are not that many scids when just accepted, * like only 2. */ ngtcp2_conn_get_scid(conn->conn, ids); return doq_conn_setup_id_array_and_dcid(conn, ids, num_scid); } else { struct ngtcp2_cid *scids = calloc(num_scid, sizeof(struct ngtcp2_cid)); if(!scids) return 0; ngtcp2_conn_get_scid(conn->conn, scids); if(!doq_conn_setup_id_array_and_dcid(conn, scids, num_scid)) { free(scids); return 0; } free(scids); } return 1; } void doq_conn_clear_conids(struct doq_conn* conn) { struct doq_conid* p, *next; if(!conn) return; p = conn->conid_list; while(p) { next = p->next; (void)rbtree_delete(conn->table->conid_tree, p->node.key); doq_conid_delete(p); p = next; } conn->conid_list = NULL; } ngtcp2_tstamp doq_get_timestamp_nanosec(void) { #ifdef CLOCK_REALTIME struct timespec tp; memset(&tp, 0, sizeof(tp)); /* Get a nanosecond time, that can be compared with the event base. */ if(clock_gettime(CLOCK_REALTIME, &tp) == -1) { log_err("clock_gettime failed: %s", strerror(errno)); } return ((uint64_t)tp.tv_sec)*((uint64_t)1000000000) + ((uint64_t)tp.tv_nsec); #else struct timeval tv; if(gettimeofday(&tv, NULL) < 0) { log_err("gettimeofday failed: %s", strerror(errno)); } return ((uint64_t)tv.tv_sec)*((uint64_t)1000000000) + ((uint64_t)tv.tv_usec)*((uint64_t)1000); #endif /* CLOCK_REALTIME */ } /** doq start the closing period for the connection. */ static int doq_conn_start_closing_period(struct comm_point* c, struct doq_conn* conn) { struct ngtcp2_path_storage ps; struct ngtcp2_pkt_info pi; ngtcp2_ssize ret; if(!conn) return 1; if( #ifdef HAVE_NGTCP2_CONN_IN_CLOSING_PERIOD ngtcp2_conn_in_closing_period(conn->conn) #else ngtcp2_conn_is_in_closing_period(conn->conn) #endif ) return 1; if( #ifdef HAVE_NGTCP2_CONN_IN_DRAINING_PERIOD ngtcp2_conn_in_draining_period(conn->conn) #else ngtcp2_conn_is_in_draining_period(conn->conn) #endif ) { doq_conn_write_disable(conn); return 1; } ngtcp2_path_storage_zero(&ps); sldns_buffer_clear(c->doq_socket->pkt_buf); /* the call to ngtcp2_conn_write_connection_close causes the * conn to be closed. It is now in the closing period. */ ret = ngtcp2_conn_write_connection_close(conn->conn, &ps.path, &pi, sldns_buffer_begin(c->doq_socket->pkt_buf), sldns_buffer_remaining(c->doq_socket->pkt_buf), #ifdef HAVE_NGTCP2_CCERR_DEFAULT &conn->ccerr #else &conn->last_error #endif , doq_get_timestamp_nanosec()); if(ret < 0) { log_err("doq ngtcp2_conn_write_connection_close failed: %s", ngtcp2_strerror(ret)); return 0; } if(ret == 0) { return 0; } sldns_buffer_set_position(c->doq_socket->pkt_buf, ret); sldns_buffer_flip(c->doq_socket->pkt_buf); /* The close packet is allocated, because it may have to be repeated. * When incoming packets have this connection dcid. */ conn->close_pkt = memdup(sldns_buffer_begin(c->doq_socket->pkt_buf), sldns_buffer_limit(c->doq_socket->pkt_buf)); if(!conn->close_pkt) { log_err("doq: could not allocate close packet: out of memory"); return 0; } conn->close_pkt_len = sldns_buffer_limit(c->doq_socket->pkt_buf); conn->close_ecn = pi.ecn; return 1; } /** doq send the close packet for the connection, perhaps again. */ int doq_conn_send_close(struct comm_point* c, struct doq_conn* conn) { if(!conn) return 0; if(!conn->close_pkt) return 0; if(conn->close_pkt_len > sldns_buffer_capacity(c->doq_socket->pkt_buf)) return 0; sldns_buffer_clear(c->doq_socket->pkt_buf); sldns_buffer_write(c->doq_socket->pkt_buf, conn->close_pkt, conn->close_pkt_len); sldns_buffer_flip(c->doq_socket->pkt_buf); verbose(VERB_ALGO, "doq send connection close"); doq_send_pkt(c, &conn->key.paddr, conn->close_ecn); doq_conn_write_disable(conn); return 1; } /** doq close the connection on error. If it returns a failure, it * does not wait to send a close, and the connection can be dropped. */ static int doq_conn_close_error(struct comm_point* c, struct doq_conn* conn) { #ifdef HAVE_NGTCP2_CCERR_DEFAULT if(conn->ccerr.type == NGTCP2_CCERR_TYPE_IDLE_CLOSE) return 0; #else if(conn->last_error.type == NGTCP2_CONNECTION_CLOSE_ERROR_CODE_TYPE_TRANSPORT_IDLE_CLOSE) return 0; #endif if(!doq_conn_start_closing_period(c, conn)) return 0; if( #ifdef HAVE_NGTCP2_CONN_IN_DRAINING_PERIOD ngtcp2_conn_in_draining_period(conn->conn) #else ngtcp2_conn_is_in_draining_period(conn->conn) #endif ) { doq_conn_write_disable(conn); return 1; } doq_conn_write_enable(conn); if(!doq_conn_send_close(c, conn)) return 0; return 1; } int doq_conn_recv(struct comm_point* c, struct doq_pkt_addr* paddr, struct doq_conn* conn, struct ngtcp2_pkt_info* pi, int* err_retry, int* err_drop) { int ret; ngtcp2_tstamp ts; struct ngtcp2_path path; memset(&path, 0, sizeof(path)); path.remote.addr = (struct sockaddr*)&paddr->addr; path.remote.addrlen = paddr->addrlen; path.local.addr = (struct sockaddr*)&paddr->localaddr; path.local.addrlen = paddr->localaddrlen; ts = doq_get_timestamp_nanosec(); ret = ngtcp2_conn_read_pkt(conn->conn, &path, pi, sldns_buffer_begin(c->doq_socket->pkt_buf), sldns_buffer_limit(c->doq_socket->pkt_buf), ts); if(ret != 0) { if(err_retry) *err_retry = 0; if(err_drop) *err_drop = 0; if(ret == NGTCP2_ERR_DRAINING) { verbose(VERB_ALGO, "ngtcp2_conn_read_pkt returned %s", ngtcp2_strerror(ret)); doq_conn_write_disable(conn); return 0; } else if(ret == NGTCP2_ERR_DROP_CONN) { verbose(VERB_ALGO, "ngtcp2_conn_read_pkt returned %s", ngtcp2_strerror(ret)); if(err_drop) *err_drop = 1; return 0; } else if(ret == NGTCP2_ERR_RETRY) { verbose(VERB_ALGO, "ngtcp2_conn_read_pkt returned %s", ngtcp2_strerror(ret)); if(err_retry) *err_retry = 1; if(err_drop) *err_drop = 1; return 0; } else if(ret == NGTCP2_ERR_CRYPTO) { if( #ifdef HAVE_NGTCP2_CCERR_DEFAULT !conn->ccerr.error_code #else !conn->last_error.error_code #endif ) { /* in picotls the tls alert may need to be * copied, but this is with openssl. And there * is conn->tls_alert. */ #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_tls_alert(&conn->ccerr, conn->tls_alert, NULL, 0); #else ngtcp2_connection_close_error_set_transport_error_tls_alert( &conn->last_error, conn->tls_alert, NULL, 0); #endif } } else { if( #ifdef HAVE_NGTCP2_CCERR_DEFAULT !conn->ccerr.error_code #else !conn->last_error.error_code #endif ) { #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_liberr(&conn->ccerr, ret, NULL, 0); #else ngtcp2_connection_close_error_set_transport_error_liberr( &conn->last_error, ret, NULL, 0); #endif } } log_err("ngtcp2_conn_read_pkt failed: %s", ngtcp2_strerror(ret)); if(!doq_conn_close_error(c, conn)) { if(err_drop) *err_drop = 1; } return 0; } doq_conn_write_enable(conn); return 1; } /** doq stream write is done */ static void doq_stream_write_is_done(struct doq_conn* conn, struct doq_stream* stream) { /* Cannot deallocate, the buffer may be needed for resends. */ doq_stream_off_write_list(conn, stream); } int doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, int* err_drop) { struct doq_stream* stream = conn->stream_write_first; ngtcp2_path_storage ps; ngtcp2_tstamp ts = doq_get_timestamp_nanosec(); size_t num_packets = 0, max_packets = 65535; ngtcp2_path_storage_zero(&ps); for(;;) { int64_t stream_id; uint32_t flags = 0; ngtcp2_pkt_info pi; ngtcp2_vec datav[2]; size_t datav_count = 0; ngtcp2_ssize ret, ndatalen = 0; int fin; if(stream) { /* data to send */ verbose(VERB_ALGO, "doq: doq_conn write stream %d", (int)stream->stream_id); stream_id = stream->stream_id; fin = 1; if(stream->nwrite < 2) { datav[0].base = ((uint8_t*)&stream-> outlen_wire) + stream->nwrite; datav[0].len = 2 - stream->nwrite; datav[1].base = stream->out; datav[1].len = stream->outlen; datav_count = 2; } else { datav[0].base = stream->out + (stream->nwrite-2); datav[0].len = stream->outlen - (stream->nwrite-2); datav_count = 1; } } else { /* no data to send */ verbose(VERB_ALGO, "doq: doq_conn write stream -1"); stream_id = -1; fin = 0; datav[0].base = NULL; datav[0].len = 0; datav_count = 1; } /* if more streams, set it to write more */ if(stream && stream->write_next) flags |= NGTCP2_WRITE_STREAM_FLAG_MORE; if(fin) flags |= NGTCP2_WRITE_STREAM_FLAG_FIN; sldns_buffer_clear(c->doq_socket->pkt_buf); ret = ngtcp2_conn_writev_stream(conn->conn, &ps.path, &pi, sldns_buffer_begin(c->doq_socket->pkt_buf), sldns_buffer_remaining(c->doq_socket->pkt_buf), &ndatalen, flags, stream_id, datav, datav_count, ts); if(ret < 0) { if(ret == NGTCP2_ERR_WRITE_MORE) { verbose(VERB_ALGO, "doq: write more, ndatalen %d", (int)ndatalen); if(stream) { if(ndatalen >= 0) stream->nwrite += ndatalen; if(stream->nwrite >= stream->outlen+2) doq_stream_write_is_done( conn, stream); stream = stream->write_next; } continue; } else if(ret == NGTCP2_ERR_STREAM_DATA_BLOCKED) { verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_DATA_BLOCKED"); #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_application_error( &conn->ccerr, -1, NULL, 0); #else ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0); #endif if(err_drop) *err_drop = 0; if(!doq_conn_close_error(c, conn)) { if(err_drop) *err_drop = 1; } return 0; } else if(ret == NGTCP2_ERR_STREAM_SHUT_WR) { verbose(VERB_ALGO, "doq: ngtcp2_conn_writev_stream returned NGTCP2_ERR_STREAM_SHUT_WR"); #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_application_error( &conn->ccerr, -1, NULL, 0); #else ngtcp2_connection_close_error_set_application_error(&conn->last_error, -1, NULL, 0); #endif if(err_drop) *err_drop = 0; if(!doq_conn_close_error(c, conn)) { if(err_drop) *err_drop = 1; } return 0; } log_err("doq: ngtcp2_conn_writev_stream failed: %s", ngtcp2_strerror(ret)); #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_liberr(&conn->ccerr, ret, NULL, 0); #else ngtcp2_connection_close_error_set_transport_error_liberr( &conn->last_error, ret, NULL, 0); #endif if(err_drop) *err_drop = 0; if(!doq_conn_close_error(c, conn)) { if(err_drop) *err_drop = 1; } return 0; } verbose(VERB_ALGO, "doq: writev_stream pkt size %d ndatawritten %d", (int)ret, (int)ndatalen); if(ndatalen >= 0 && stream) { stream->nwrite += ndatalen; if(stream->nwrite >= stream->outlen+2) doq_stream_write_is_done(conn, stream); } if(ret == 0) { /* congestion limited */ doq_conn_write_disable(conn); ngtcp2_conn_update_pkt_tx_time(conn->conn, ts); return 1; } sldns_buffer_set_position(c->doq_socket->pkt_buf, ret); sldns_buffer_flip(c->doq_socket->pkt_buf); doq_send_pkt(c, &conn->key.paddr, pi.ecn); if(c->doq_socket->have_blocked_pkt) break; if(++num_packets == max_packets) break; if(stream) stream = stream->write_next; } ngtcp2_conn_update_pkt_tx_time(conn->conn, ts); return 1; } void doq_conn_write_enable(struct doq_conn* conn) { conn->write_interest = 1; } void doq_conn_write_disable(struct doq_conn* conn) { conn->write_interest = 0; } /** doq append the connection to the write list */ static void doq_conn_write_list_append(struct doq_table* table, struct doq_conn* conn) { if(conn->on_write_list) return; conn->write_prev = table->write_list_last; if(table->write_list_last) table->write_list_last->write_next = conn; else table->write_list_first = conn; conn->write_next = NULL; table->write_list_last = conn; conn->on_write_list = 1; } void doq_conn_write_list_remove(struct doq_table* table, struct doq_conn* conn) { if(!conn->on_write_list) return; if(conn->write_next) conn->write_next->write_prev = conn->write_prev; else table->write_list_last = conn->write_prev; if(conn->write_prev) conn->write_prev->write_next = conn->write_next; else table->write_list_first = conn->write_next; conn->write_prev = NULL; conn->write_next = NULL; conn->on_write_list = 0; } void doq_conn_set_write_list(struct doq_table* table, struct doq_conn* conn) { if(conn->write_interest && conn->on_write_list) return; if(!conn->write_interest && !conn->on_write_list) return; if(conn->write_interest) doq_conn_write_list_append(table, conn); else doq_conn_write_list_remove(table, conn); } struct doq_conn* doq_table_pop_first(struct doq_table* table) { struct doq_conn* conn = table->write_list_first; if(!conn) return NULL; lock_basic_lock(&conn->lock); table->write_list_first = conn->write_next; if(conn->write_next) conn->write_next->write_prev = NULL; else table->write_list_last = NULL; conn->write_next = NULL; conn->write_prev = NULL; conn->on_write_list = 0; return conn; } int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv) { ngtcp2_tstamp expiry = ngtcp2_conn_get_expiry(conn->conn); ngtcp2_tstamp now = doq_get_timestamp_nanosec(); ngtcp2_tstamp t; if(expiry <= now) { /* The timer has already expired, add with zero timeout. * This should call the callback straight away. Calling it * from the event callbacks is cleaner than calling it here, * because then it is always called with the same locks and * so on. This routine only has the conn.lock. */ t = now; } else { t = expiry; } /* convert to timeval */ memset(tv, 0, sizeof(*tv)); tv->tv_sec = t / NGTCP2_SECONDS; tv->tv_usec = (t / NGTCP2_MICROSECONDS)%1000000; /* If we already have a timer, is it the right value? */ if(conn->timer.timer_in_tree || conn->timer.timer_in_list) { if(conn->timer.time.tv_sec == tv->tv_sec && conn->timer.time.tv_usec == tv->tv_usec) return 0; } return 1; } /* doq print connection log */ static void doq_conn_log_line(struct doq_conn* conn, char* s) { char remotestr[256], localstr[256]; addr_to_str((void*)&conn->key.paddr.addr, conn->key.paddr.addrlen, remotestr, sizeof(remotestr)); addr_to_str((void*)&conn->key.paddr.localaddr, conn->key.paddr.localaddrlen, localstr, sizeof(localstr)); log_info("doq conn %s %s %s", remotestr, localstr, s); } int doq_conn_handle_timeout(struct doq_conn* conn) { ngtcp2_tstamp now = doq_get_timestamp_nanosec(); int rv; if(verbosity >= VERB_ALGO) doq_conn_log_line(conn, "timeout"); rv = ngtcp2_conn_handle_expiry(conn->conn, now); if(rv != 0) { verbose(VERB_ALGO, "ngtcp2_conn_handle_expiry failed: %s", ngtcp2_strerror(rv)); #ifdef HAVE_NGTCP2_CCERR_DEFAULT ngtcp2_ccerr_set_liberr(&conn->ccerr, rv, NULL, 0); #else ngtcp2_connection_close_error_set_transport_error_liberr( &conn->last_error, rv, NULL, 0); #endif if(!doq_conn_close_error(conn->doq_socket->cp, conn)) { /* failed, return for deletion */ return 0; } return 1; } doq_conn_write_enable(conn); if(!doq_conn_write_streams(conn->doq_socket->cp, conn, NULL)) { /* failed, return for deletion. */ return 0; } return 1; } void doq_table_quic_size_add(struct doq_table* table, size_t add) { lock_basic_lock(&table->size_lock); table->current_size += add; lock_basic_unlock(&table->size_lock); } void doq_table_quic_size_subtract(struct doq_table* table, size_t subtract) { lock_basic_lock(&table->size_lock); if(table->current_size < subtract) table->current_size = 0; else table->current_size -= subtract; lock_basic_unlock(&table->size_lock); } int doq_table_quic_size_available(struct doq_table* table, struct config_file* cfg, size_t mem) { size_t cur; if (!table) return 0; lock_basic_lock(&table->size_lock); cur = table->current_size; lock_basic_unlock(&table->size_lock); if(cur + mem > cfg->quic_size) return 0; return 1; } size_t doq_table_quic_size_get(struct doq_table* table) { size_t sz; if(!table) return 0; lock_basic_lock(&table->size_lock); sz = table->current_size; lock_basic_unlock(&table->size_lock); return sz; } #endif /* HAVE_NGTCP2 */ unbound-1.25.1/services/view.c0000644000175000017500000001572415203270263015660 0ustar wouterwouter/* * services/view.c - named views containing local zones authority service. * * Copyright (c) 2016, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable named views that can hold local zone * authority service. */ #include "config.h" #include "services/view.h" #include "services/localzone.h" #include "util/config_file.h" #include "respip/respip.h" int view_cmp(const void* v1, const void* v2) { struct view* a = (struct view*)v1; struct view* b = (struct view*)v2; return strcmp(a->name, b->name); } struct views* views_create(void) { struct views* v = (struct views*)calloc(1, sizeof(*v)); if(!v) return NULL; rbtree_init(&v->vtree, &view_cmp); lock_rw_init(&v->lock); lock_protect(&v->lock, &v->vtree, sizeof(v->vtree)); return v; } void view_delete(struct view* v) { if(!v) return; lock_rw_destroy(&v->lock); local_zones_delete(v->local_zones); respip_set_delete(v->respip_set); free(v->name); free(v); } static void delviewnode(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct view* v = (struct view*)n; view_delete(v); } void views_delete(struct views* v) { if(!v) return; lock_rw_destroy(&v->lock); traverse_postorder(&v->vtree, delviewnode, NULL); free(v); } /** create a new view */ static struct view* view_create(char* name) { struct view* v = (struct view*)calloc(1, sizeof(*v)); if(!v) return NULL; v->node.key = v; if(!(v->name = strdup(name))) { free(v); return NULL; } lock_rw_init(&v->lock); lock_protect(&v->lock, &v->name, sizeof(*v)-sizeof(rbnode_type)); return v; } /** enter a new view returns with WRlock */ static struct view* views_enter_view_name(struct views* vs, char* name) { struct view* v = view_create(name); if(!v) { log_err("out of memory"); return NULL; } /* add to rbtree */ lock_rw_wrlock(&vs->lock); lock_rw_wrlock(&v->lock); if(!rbtree_insert(&vs->vtree, &v->node)) { log_warn("duplicate view: %s", name); lock_rw_unlock(&v->lock); view_delete(v); lock_rw_unlock(&vs->lock); return NULL; } lock_rw_unlock(&vs->lock); return v; } int views_apply_cfg(struct views* vs, struct config_file* cfg) { struct config_view* cv; struct view* v; struct config_file lz_cfg; /* Check existence of name in first view (last in config). Rest of * views are already checked when parsing config. */ if(cfg->views && !cfg->views->name) { log_err("view without a name"); return 0; } for(cv = cfg->views; cv; cv = cv->next) { /* create and enter view */ if(!(v = views_enter_view_name(vs, cv->name))) return 0; v->isfirst = cv->isfirst; if(cv->local_zones || cv->local_data) { if(!(v->local_zones = local_zones_create())){ lock_rw_unlock(&v->lock); return 0; } memset(&lz_cfg, 0, sizeof(lz_cfg)); lz_cfg.local_zones = cv->local_zones; lz_cfg.local_data = cv->local_data; lz_cfg.local_zones_nodefault = cv->local_zones_nodefault; if(v->isfirst) { /* Do not add defaults to view-specific * local-zone when global local zone will be * used. */ struct config_strlist* nd; lz_cfg.local_zones_disable_default = 1; /* Add nodefault zones to list of zones to add, * so they will be used as if they are * configured as type transparent */ for(nd = cv->local_zones_nodefault; nd; nd = nd->next) { char* nd_str, *nd_type; nd_str = strdup(nd->str); if(!nd_str) { log_err("out of memory"); lock_rw_unlock(&v->lock); return 0; } nd_type = strdup("nodefault"); if(!nd_type) { log_err("out of memory"); free(nd_str); lock_rw_unlock(&v->lock); return 0; } if(!cfg_str2list_insert( &lz_cfg.local_zones, nd_str, nd_type)) { log_err("failed to insert " "default zones into " "local-zone list"); lock_rw_unlock(&v->lock); return 0; } } } if(!local_zones_apply_cfg(v->local_zones, &lz_cfg)){ lock_rw_unlock(&v->lock); return 0; } /* local_zones, local_zones_nodefault and local_data * are free'd from config_view by local_zones_apply_cfg. * Set pointers to NULL. */ cv->local_zones = NULL; cv->local_data = NULL; cv->local_zones_nodefault = NULL; } lock_rw_unlock(&v->lock); } return 1; } /** find a view by name */ struct view* views_find_view(struct views* vs, const char* name, int write) { struct view* v; struct view key; key.node.key = &v; key.name = (char *)name; lock_rw_rdlock(&vs->lock); if(!(v = (struct view*)rbtree_search(&vs->vtree, &key.node))) { lock_rw_unlock(&vs->lock); return 0; } if(write) { lock_rw_wrlock(&v->lock); } else { lock_rw_rdlock(&v->lock); } lock_rw_unlock(&vs->lock); return v; } void views_print(struct views* v) { /* TODO implement print */ (void)v; } size_t views_get_mem(struct views* vs) { struct view* v; size_t m; if(!vs) return 0; m = sizeof(struct views); lock_rw_rdlock(&vs->lock); RBTREE_FOR(v, struct view*, &vs->vtree) { m += view_get_mem(v); } lock_rw_unlock(&vs->lock); return m; } size_t view_get_mem(struct view* v) { size_t m = sizeof(*v); lock_rw_rdlock(&v->lock); m += getmem_str(v->name); m += local_zones_get_mem(v->local_zones); m += respip_set_get_mem(v->respip_set); lock_rw_unlock(&v->lock); return m; } void views_swap_tree(struct views* vs, struct views* data) { rbnode_type* oldroot = vs->vtree.root; size_t oldcount = vs->vtree.count; vs->vtree.root = data->vtree.root; vs->vtree.count = data->vtree.count; data->vtree.root = oldroot; data->vtree.count = oldcount; } unbound-1.25.1/services/listen_dnsport.h0000644000175000017500000007420515203270263017761 0ustar wouterwouter/* * services/listen_dnsport.h - listen on port 53 for incoming DNS queries. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has functions to get queries from clients. */ #ifndef LISTEN_DNSPORT_H #define LISTEN_DNSPORT_H #include "util/netevent.h" #include "util/rbtree.h" #include "util/locks.h" #include "daemon/acl_list.h" #ifdef HAVE_NGHTTP2_NGHTTP2_H #include #endif #ifdef HAVE_NGTCP2 #include #include #ifdef USE_NGTCP2_CRYPTO_OSSL struct ngtcp2_crypto_ossl_ctx; #endif #endif struct listen_list; struct config_file; struct addrinfo; struct sldns_buffer; struct tcl_list; /** * Listening for queries structure. * Contains list of query-listen sockets. */ struct listen_dnsport { /** Base for select calls */ struct comm_base* base; /** buffer shared by UDP connections, since there is only one datagram at any time. */ struct sldns_buffer* udp_buff; #ifdef USE_DNSCRYPT struct sldns_buffer* dnscrypt_udp_buff; #endif /** list of comm points used to get incoming events */ struct listen_list* cps; }; /** * Single linked list to store event points. */ struct listen_list { /** next in list */ struct listen_list* next; /** event info */ struct comm_point* com; }; /** * type of ports */ enum listen_type { /** udp type */ listen_type_udp, /** tcp type */ listen_type_tcp, /** udp ipv6 (v4mapped) for use with ancillary data */ listen_type_udpancil, /** ssl over tcp type */ listen_type_ssl, /** udp type + dnscrypt*/ listen_type_udp_dnscrypt, /** tcp type + dnscrypt */ listen_type_tcp_dnscrypt, /** udp ipv6 (v4mapped) for use with ancillary data + dnscrypt*/ listen_type_udpancil_dnscrypt, /** HTTP(2) over TLS over TCP */ listen_type_http, /** DNS over QUIC */ listen_type_doq }; /* * socket properties (just like NSD nsd_socket structure definition) */ struct unbound_socket { /** the address of the socket */ struct sockaddr* addr; /** length of the address */ socklen_t addrlen; /** socket descriptor returned by socket() syscall */ int s; /** address family (AF_INET/AF_INET6) */ int fam; /** ACL on the socket (listening interface) */ struct acl_addr* acl; }; /** * Single linked list to store shared ports that have been * opened for use by all threads. */ struct listen_port { /** next in list */ struct listen_port* next; /** file descriptor, open and ready for use */ int fd; /** type of file descriptor, udp or tcp */ enum listen_type ftype; /** if the port should support PROXYv2 */ int pp2_enabled; /** fill in unbound_socket structure for every opened socket at * Unbound startup */ struct unbound_socket* socket; }; /** * Create shared listening ports * Getaddrinfo, create socket, bind and listen to zero or more * interfaces for IP4 and/or IP6, for UDP and/or TCP. * On the given port number. It creates the sockets. * @param cfg: settings on what ports to open. * @param ifs: interfaces to open, array of IP addresses, "ip[@port]". * @param num_ifs: length of ifs. * @param reuseport: set to true if you want reuseport, or NULL to not have it, * set to false on exit if reuseport failed to apply (because of no * kernel support). * @return: linked list of ports or NULL on error. */ struct listen_port* listening_ports_open(struct config_file* cfg, char** ifs, int num_ifs, int* reuseport); /** * Close and delete the (list of) listening ports. */ void listening_ports_free(struct listen_port* list); struct config_strlist; /** * Resolve interface names in config and store result IP addresses * @param ifs: array of interfaces. The list of interface names, if not NULL. * @param num_ifs: length of ifs array. * @param list: if not NULL, this is used as the list of interface names. * @param resif: string array (malloced array of malloced strings) with * result. NULL if cfg has none. * @param num_resif: length of resif. Zero if cfg has zero num_ifs. * @return 0 on failure. */ int resolve_interface_names(char** ifs, int num_ifs, struct config_strlist* list, char*** resif, int* num_resif); /** * Create commpoints with for this thread for the shared ports. * @param base: the comm_base that provides event functionality. * for default all ifs. * @param ports: the list of shared ports. * @param bufsize: size of datagram buffer. * @param tcp_accept_count: max number of simultaneous TCP connections * from clients. * @param tcp_idle_timeout: idle timeout for TCP connections in msec. * @param harden_large_queries: whether query size should be limited. * @param http_max_streams: maximum number of HTTP/2 streams per connection. * @param http_endpoint: HTTP endpoint to service queries on * @param http_notls: no TLS for http downstream * @param tcp_conn_limit: TCP connection limit info. * @param dot_sslctx: nonNULL if dot ssl context. * @param doh_sslctx: nonNULL if doh ssl context. * @param quic_sslctx: nonNULL if quic ssl context. * @param dtenv: nonNULL if dnstap enabled. * @param doq_table: the doq connection table, with shared information. * @param rnd: random state. * @param cfg: config file struct. * @param cb: callback function when a request arrives. It is passed * the packet and user argument. Return true to send a reply. * @param cb_arg: user data argument for callback function. * @return: the malloced listening structure, ready for use. NULL on error. */ struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ports, size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, int harden_large_queries, uint32_t http_max_streams, char* http_endpoint, int http_notls, struct tcl_list* tcp_conn_limit, void* dot_sslctx, void* doh_sslctx, void* quic_sslctx, struct dt_env* dtenv, struct doq_table* doq_table, struct ub_randstate* rnd,struct config_file* cfg, comm_point_callback_type* cb, void *cb_arg); /** * delete the listening structure * @param listen: listening structure. */ void listen_delete(struct listen_dnsport* listen); /** setup the locks for the listen ports */ void listen_setup_locks(void); /** desetup the locks for the listen ports */ void listen_desetup_locks(void); /** * delete listen_list of commpoints. Calls commpointdelete() on items. * This may close the fds or not depending on flags. * @param list: to delete. */ void listen_list_delete(struct listen_list* list); /** * get memory size used by the listening structs * @param listen: listening structure. * @return: size in bytes. */ size_t listen_get_mem(struct listen_dnsport* listen); /** * stop accept handlers for TCP (until enabled again) * @param listen: listening structure. */ void listen_stop_accept(struct listen_dnsport* listen); /** * start accept handlers for TCP (was stopped before) * @param listen: listening structure. */ void listen_start_accept(struct listen_dnsport* listen); /** * Create and bind nonblocking UDP socket * @param family: for socket call. * @param socktype: for socket call. * @param addr: for bind call. * @param addrlen: for bind call. * @param v6only: if enabled, IP6 sockets get IP6ONLY option set. * if enabled with value 2 IP6ONLY option is disabled. * @param inuse: on error, this is set true if the port was in use. * @param noproto: on error, this is set true if cause is that the IPv6 proto (family) is not available. * @param rcv: set size on rcvbuf with socket option, if 0 it is not set. * @param snd: set size on sndbuf with socket option, if 0 it is not set. * @param listen: if true, this is a listening UDP port, eg port 53, and * set SO_REUSEADDR on it. * @param reuseport: if nonNULL and true, try to set SO_REUSEPORT on * listening UDP port. Set to false on return if it failed to do so. * @param transparent: set IP_TRANSPARENT socket option. * @param freebind: set IP_FREEBIND socket option. * @param use_systemd: if true, fetch sockets from systemd. * @param dscp: DSCP to use. * @return: the socket. -1 on error. */ int create_udp_sock(int family, int socktype, struct sockaddr* addr, socklen_t addrlen, int v6only, int* inuse, int* noproto, int rcv, int snd, int listen, int* reuseport, int transparent, int freebind, int use_systemd, int dscp); /** * Create and bind TCP listening socket * @param addr: address info ready to make socket. * @param v6only: enable ip6 only flag on ip6 sockets. * @param noproto: if error caused by lack of protocol support. * @param reuseport: if nonNULL and true, try to set SO_REUSEPORT on * listening UDP port. Set to false on return if it failed to do so. * @param transparent: set IP_TRANSPARENT socket option. * @param mss: maximum segment size of the socket. if zero, leaves the default. * @param nodelay: if true set TCP_NODELAY and TCP_QUICKACK socket options. * @param freebind: set IP_FREEBIND socket option. * @param use_systemd: if true, fetch sockets from systemd. * @param dscp: DSCP to use. * @param additional: additional log information for the socket type. * @return: the socket. -1 on error. */ int create_tcp_accept_sock(struct addrinfo *addr, int v6only, int* noproto, int* reuseport, int transparent, int mss, int nodelay, int freebind, int use_systemd, int dscp, const char* additional); /** * Create and bind local listening socket * @param path: path to the socket. * @param noproto: on error, this is set true if cause is that local sockets * are not supported. * @param use_systemd: if true, fetch sockets from systemd. * @return: the socket. -1 on error. */ int create_local_accept_sock(const char* path, int* noproto, int use_systemd); /** * TCP request info. List of requests outstanding on the channel, that * are asked for but not yet answered back. */ struct tcp_req_info { /** the TCP comm point for this. Its buffer is used for read/write */ struct comm_point* cp; /** the buffer to use to spool reply from mesh into, * it can then be copied to the result list and written. * it is a pointer to the shared udp buffer. */ struct sldns_buffer* spool_buffer; /** are we in worker_handle function call (for recursion callback)*/ int in_worker_handle; /** is the comm point dropped (by worker handle). * That means we have to disconnect the channel. */ int is_drop; /** is the comm point set to send_reply (by mesh new client in worker * handle), if so answer is available in c.buffer */ int is_reply; /** read channel has closed, just write pending results */ int read_is_closed; /** read again */ int read_again; /** number of outstanding requests */ int num_open_req; /** list of outstanding requests */ struct tcp_req_open_item* open_req_list; /** number of pending writeable results */ int num_done_req; /** list of pending writable result packets, malloced one at a time */ struct tcp_req_done_item* done_req_list; }; /** * List of open items in TCP channel */ struct tcp_req_open_item { /** next in list */ struct tcp_req_open_item* next; /** the mesh area of the mesh_state */ struct mesh_area* mesh; /** the mesh state */ struct mesh_state* mesh_state; }; /** * List of done items in TCP channel */ struct tcp_req_done_item { /** next in list */ struct tcp_req_done_item* next; /** the buffer with packet contents */ uint8_t* buf; /** length of the buffer */ size_t len; }; /** * Create tcp request info structure that keeps track of open * requests on the TCP channel that are resolved at the same time, * and the pending results that have to get written back to that client. * @param spoolbuf: shared buffer * @return new structure or NULL on alloc failure. */ struct tcp_req_info* tcp_req_info_create(struct sldns_buffer* spoolbuf); /** * Delete tcp request structure. Called by owning commpoint. * Removes mesh entry references and stored results from the lists. * @param req: the tcp request info */ void tcp_req_info_delete(struct tcp_req_info* req); /** * Clear tcp request structure. Removes list entries, sets it up ready * for the next connection. * @param req: tcp request info structure. */ void tcp_req_info_clear(struct tcp_req_info* req); /** * Remove mesh state entry from list in tcp_req_info. * caller has to manage the mesh state reply entry in the mesh state. * @param req: the tcp req info that has the entry removed from the list. * @param m: the state removed from the list. */ void tcp_req_info_remove_mesh_state(struct tcp_req_info* req, struct mesh_state* m); /** * Handle write done of the last result packet * @param req: the tcp req info. */ void tcp_req_info_handle_writedone(struct tcp_req_info* req); /** * Handle read done of a new request from the client * @param req: the tcp req info. */ void tcp_req_info_handle_readdone(struct tcp_req_info* req); /** * Add mesh state to the tcp req list of open requests. * So the comm_reply can be removed off the mesh reply list when * the tcp channel has to be closed (for other reasons then that that * request was done, eg. channel closed by client or some format error). * @param req: tcp req info structure. It keeps track of the simultaneous * requests and results on a tcp (or TLS) channel. * @param mesh: mesh area for the state. * @param m: mesh state to add. * @return 0 on failure (malloc failure). */ int tcp_req_info_add_meshstate(struct tcp_req_info* req, struct mesh_area* mesh, struct mesh_state* m); /** * Send reply on tcp simultaneous answer channel. May queue it up. * @param req: request info structure. */ void tcp_req_info_send_reply(struct tcp_req_info* req); /** the read channel has closed * @param req: request. remaining queries are looked up and answered. * @return zero if nothing to do, just close the tcp. */ int tcp_req_info_handle_read_close(struct tcp_req_info* req); /** get the size of currently used tcp stream wait buffers (in bytes) */ size_t tcp_req_info_get_stream_buffer_size(void); /** get the size of currently used HTTP2 query buffers (in bytes) */ size_t http2_get_query_buffer_size(void); /** get the size of currently used HTTP2 response buffers (in bytes) */ size_t http2_get_response_buffer_size(void); #ifdef HAVE_NGHTTP2 /** * Create nghttp2 callbacks to handle HTTP2 requests. * @return malloc'ed struct, NULL on failure */ nghttp2_session_callbacks* http2_req_callbacks_create(void); /** Free http2 stream buffers and decrease buffer counters */ void http2_req_stream_clear(struct http2_stream* h2_stream); /** * DNS response ready to be submitted to nghttp2, to be prepared for sending * out. Response is stored in c->buffer. Copy to rbuffer because the c->buffer * might be used before this will be send out. * @param h2_session: http2 session, containing c->buffer which contains answer * @param h2_stream: http2 stream, containing buffer to store answer in * @return 0 on error, 1 otherwise */ int http2_submit_dns_response(struct http2_session* h2_session); #else int http2_submit_dns_response(void* v); #endif /* HAVE_NGHTTP2 */ #ifdef HAVE_NGTCP2 struct doq_conid; struct doq_server_socket; /** * DoQ shared connection table. This is the connections for the host. * And some config parameter values for connections. The host has to * respond on that ip,port for those connections, so they are shared * between threads. */ struct doq_table { /** the lock on the tree and config elements. insert and deletion, * also lookup in the tree needs to hold the lock. */ lock_rw_type lock; /** rbtree of doq_conn, the connections to different destination * addresses, and can be found by dcid. */ struct rbtree_type* conn_tree; /** lock for the conid tree, needed for the conid tree and also * the conid elements */ lock_rw_type conid_lock; /** rbtree of doq_conid, connections can be found by their * connection ids. Lookup by connection id, finds doq_conn. */ struct rbtree_type* conid_tree; /** the server scid length */ int sv_scidlen; /** the static secret for the server */ uint8_t* static_secret; /** length of the static secret */ size_t static_secret_len; /** the idle timeout in nanoseconds */ uint64_t idle_timeout; /** the list of write interested connections, hold the doq_table.lock * to change them */ struct doq_conn* write_list_first, *write_list_last; /** rbtree of doq_timer. */ struct rbtree_type* timer_tree; /** lock on the current_size counter. */ lock_basic_type size_lock; /** current use, in bytes, of QUIC buffers. * The doq_conn ngtcp2_conn structure, SSL structure and conid structs * are not counted. */ size_t current_size; }; /** * create SSL context for QUIC * @param key: private key file. * @param pem: public key cert. * @param verifypem: if nonNULL, verifylocation file. * return SSL_CTX* or NULL on failure (logged). */ void* quic_sslctx_create(char* key, char* pem, char* verifypem); /** create doq table */ struct doq_table* doq_table_create(struct config_file* cfg, struct ub_randstate* rnd); /** delete doq table */ void doq_table_delete(struct doq_table* table); /** * Timer information for doq timer. */ struct doq_timer { /** The rbnode in the tree sorted by timeout value. Key this struct. */ struct rbnode_type node; /** The timeout value. Absolute time value. */ struct timeval time; /** If the timer is in the time tree, with the node. */ int timer_in_tree; /** If there are more timers with the exact same timeout value, * they form a set of timers. The rbnode timer has a link to the list * with the other timers in the set. The rbnode timer is not a * member of the list with the other timers. The other timers are not * linked into the tree. */ struct doq_timer* setlist_first, *setlist_last; /** If the timer is on the setlist. */ int timer_in_list; /** If in the setlist, the next and prev element. */ struct doq_timer* setlist_next, *setlist_prev; /** The connection that is timeouted. */ struct doq_conn* conn; /** The worker that is waiting for the timeout event. * Set for the rbnode tree linked element. If a worker is waiting * for the event. If NULL, no worker is waiting for this timeout. */ struct doq_server_socket* worker_doq_socket; }; /** * Key information that makes a doq_conn node in the tree lookup. */ struct doq_conn_key { /** the remote endpoint and local endpoint and ifindex */ struct doq_pkt_addr paddr; /** the doq connection dcid */ uint8_t* dcid; /** length of dcid */ size_t dcidlen; }; /** * DoQ connection, for DNS over QUIC. One connection to a remote endpoint * with a number of streams in it. Every stream is like a tcp stream with * a uint16_t length, query read, and a uint16_t length and answer written. */ struct doq_conn { /** rbtree node, key is addresses and dcid */ struct rbnode_type node; /** lock on the connection */ lock_basic_type lock; /** the key information, with dcid and address endpoint */ struct doq_conn_key key; /** the doq server socket for inside callbacks */ struct doq_server_socket* doq_socket; /** the doq table this connection is part of */ struct doq_table* table; /** if the connection is about to be deleted. */ uint8_t is_deleted; /** the version, the client chosen version of QUIC */ uint32_t version; /** the ngtcp2 connection, a server connection */ struct ngtcp2_conn* conn; /** the connection ids that are associated with this doq_conn. * There can be a number, that can change. They are linked here, * so that upon removal, the list of actually associated conid * elements can be removed as well. */ struct doq_conid* conid_list; /** the ngtcp2 last error for the connection */ #ifdef HAVE_NGTCP2_CCERR_DEFAULT struct ngtcp2_ccerr ccerr; #else struct ngtcp2_connection_close_error last_error; #endif /** the recent tls alert error code */ uint8_t tls_alert; /** the ssl context, SSL* */ void* ssl; #if defined(USE_NGTCP2_CRYPTO_OSSL) || defined(HAVE_NGTCP2_CRYPTO_QUICTLS_CONFIGURE_SERVER_CONTEXT) /** the connection reference for ngtcp2_conn and userdata in ssl */ struct ngtcp2_crypto_conn_ref conn_ref; #endif #ifdef USE_NGTCP2_CRYPTO_OSSL /** the per-connection state for ngtcp2_crypto_ossl */ struct ngtcp2_crypto_ossl_ctx* ossl_ctx; #endif /** closure packet, if any */ uint8_t* close_pkt; /** length of closure packet. */ size_t close_pkt_len; /** closure ecn */ uint32_t close_ecn; /** the streams for this connection, of type doq_stream */ struct rbtree_type stream_tree; /** the streams that want write, they have something to write. * The list is ordered, the last have to wait for the first to * get their data written. */ struct doq_stream* stream_write_first, *stream_write_last; /** the conn has write interest if true, no write interest if false. */ uint8_t write_interest; /** if the conn is on the connection write list */ uint8_t on_write_list; /** the connection write list prev and next, if on the write list */ struct doq_conn* write_prev, *write_next; /** The timer for the connection. If unused, it is not in the tree * and not in the list. It is alloced here, so that it is prealloced. * It has to be set after every read and write on the connection, so * this improves performance, but also the allocation does not fail. */ struct doq_timer timer; }; /** * Connection ID and the doq_conn that is that connection. A connection * has an original dcid, and then more connection ids associated. */ struct doq_conid { /** rbtree node, key is the connection id. */ struct rbnode_type node; /** the next and prev in the list of conids for the doq_conn */ struct doq_conid* next, *prev; /** key to the doq_conn that is the connection */ struct doq_conn_key key; /** the connection id, byte string */ uint8_t* cid; /** the length of cid */ size_t cidlen; }; /** * DoQ stream, for DNS over QUIC. */ struct doq_stream { /** the rbtree node for the stream, key is the stream_id */ rbnode_type node; /** the stream id */ int64_t stream_id; /** if the stream is closed */ uint8_t is_closed; /** if the query is complete */ uint8_t is_query_complete; /** the number of bytes read on the stream, up to querylen+2. */ size_t nread; /** the length of the input query bytes */ size_t inlen; /** the input bytes */ uint8_t* in; /** does the stream have an answer to send */ uint8_t is_answer_available; /** the answer bytes sent, up to outlen+2. */ size_t nwrite; /** the length of the output answer bytes */ size_t outlen; /** the output length in network wireformat */ uint16_t outlen_wire; /** the output packet bytes */ uint8_t* out; /** if the stream is on the write list */ uint8_t on_write_list; /** the prev and next on the write list, if on the list */ struct doq_stream* write_prev, *write_next; }; /** doq application error code that is sent when a stream is closed */ #define DOQ_APP_ERROR_CODE 1 /** * Create the doq connection. * @param c: the comm point for the listening doq socket. * @param paddr: with remote and local address and ifindex for the * connection destination. This is where packets are sent. * @param dcid: the dcid, Destination Connection ID. * @param dcidlen: length of dcid. * @param version: client chosen version. * @return new doq connection or NULL on allocation failure. */ struct doq_conn* doq_conn_create(struct comm_point* c, struct doq_pkt_addr* paddr, const uint8_t* dcid, size_t dcidlen, uint32_t version); /** * Delete the doq connection structure. * @param conn: to delete. * @param table: with memory size. */ void doq_conn_delete(struct doq_conn* conn, struct doq_table* table); /** compare function of doq_conn */ int doq_conn_cmp(const void* key1, const void* key2); /** compare function of doq_conid */ int doq_conid_cmp(const void* key1, const void* key2); /** compare function of doq_timer */ int doq_timer_cmp(const void* key1, const void* key2); /** compare function of doq_stream */ int doq_stream_cmp(const void* key1, const void* key2); /** setup the doq connection callbacks, and settings. */ int doq_conn_setup(struct doq_conn* conn, uint8_t* scid, size_t scidlen, uint8_t* ocid, size_t ocidlen, const uint8_t* token, size_t tokenlen); /** fill a buffer with random data */ void doq_fill_rand(struct ub_randstate* rnd, uint8_t* buf, size_t len); /** delete a doq_conid */ void doq_conid_delete(struct doq_conid* conid); /** add a connection id to the doq_conn. * caller must hold doq_table.conid_lock. */ int doq_conn_associate_conid(struct doq_conn* conn, uint8_t* data, size_t datalen); /** remove a connection id from the doq_conn. * caller must hold doq_table.conid_lock. */ void doq_conn_dissociate_conid(struct doq_conn* conn, const uint8_t* data, size_t datalen); /** initial setup to link current connection ids to the doq_conn */ int doq_conn_setup_conids(struct doq_conn* conn); /** remove the connection ids from the doq_conn. * caller must hold doq_table.conid_lock. */ void doq_conn_clear_conids(struct doq_conn* conn); /** find a conid in the doq_conn connection. * caller must hold table.conid_lock. */ struct doq_conid* doq_conid_find(struct doq_table* doq_table, const uint8_t* data, size_t datalen); /** receive a packet for a connection */ int doq_conn_recv(struct comm_point* c, struct doq_pkt_addr* paddr, struct doq_conn* conn, struct ngtcp2_pkt_info* pi, int* err_retry, int* err_drop); /** send packets for a connection */ int doq_conn_write_streams(struct comm_point* c, struct doq_conn* conn, int* err_drop); /** send the close packet for the connection, perhaps again. */ int doq_conn_send_close(struct comm_point* c, struct doq_conn* conn); /** delete doq stream */ void doq_stream_delete(struct doq_stream* stream); /** doq read a connection key from repinfo. It is not malloced, but points * into the repinfo for the dcid. */ void doq_conn_key_from_repinfo(struct doq_conn_key* key, struct comm_reply* repinfo); /** doq find a stream in the connection */ struct doq_stream* doq_stream_find(struct doq_conn* conn, int64_t stream_id); /** doq shutdown the stream. */ int doq_stream_close(struct doq_conn* conn, struct doq_stream* stream, int send_shutdown); /** send reply for a connection */ int doq_stream_send_reply(struct doq_conn* conn, struct doq_stream* stream, struct sldns_buffer* buf); /** the connection has write interest, wants to write packets */ void doq_conn_write_enable(struct doq_conn* conn); /** the connection has no write interest, does not want to write packets */ void doq_conn_write_disable(struct doq_conn* conn); /** set the connection on or off the write list, depending on write interest */ void doq_conn_set_write_list(struct doq_table* table, struct doq_conn* conn); /** doq remove the connection from the write list */ void doq_conn_write_list_remove(struct doq_table* table, struct doq_conn* conn); /** doq get the first conn from the write list, if any, popped from list. * Locks the conn that is returned. */ struct doq_conn* doq_table_pop_first(struct doq_table* table); /** * doq check if the timer for the conn needs to be changed. * @param conn: connection, caller must hold lock on it. * @param tv: time value, absolute time, returned. * @return true if timer needs to be set to tv, false if no change is needed * to the timer. The timer is already set to the right time in that case. */ int doq_conn_check_timer(struct doq_conn* conn, struct timeval* tv); /** doq remove timer from tree */ void doq_timer_tree_remove(struct doq_table* table, struct doq_timer* timer); /** doq remove timer from list */ void doq_timer_list_remove(struct doq_table* table, struct doq_timer* timer); /** doq unset the timer if it was set. */ void doq_timer_unset(struct doq_table* table, struct doq_timer* timer); /** doq set the timer and add it. */ void doq_timer_set(struct doq_table* table, struct doq_timer* timer, struct doq_server_socket* worker_doq_socket, struct timeval* tv); /** doq find a timeout in the timer tree */ struct doq_timer* doq_timer_find_time(struct doq_table* table, struct timeval* tv); /** doq handle timeout for a connection. Pass conn locked. Returns false for * deletion. */ int doq_conn_handle_timeout(struct doq_conn* conn); /** doq add size to the current quic buffer counter */ void doq_table_quic_size_add(struct doq_table* table, size_t add); /** doq subtract size from the current quic buffer counter */ void doq_table_quic_size_subtract(struct doq_table* table, size_t subtract); /** doq check if mem is available for quic. */ int doq_table_quic_size_available(struct doq_table* table, struct config_file* cfg, size_t mem); /** doq get the quic size value */ size_t doq_table_quic_size_get(struct doq_table* table); #endif /* HAVE_NGTCP2 */ char* set_ip_dscp(int socket, int addrfamily, int ds); /** for debug and profiling purposes only * @param ub_sock: the structure containing created socket info we want to print or log for */ void verbose_print_unbound_socket(struct unbound_socket* ub_sock); /** event callback for testcode/doqclient */ void doq_client_event_cb(int fd, short event, void* arg); /** timer event callback for testcode/doqclient */ void doq_client_timer_cb(int fd, short event, void* arg); #ifdef HAVE_NGTCP2 /** get a timestamp in nanoseconds */ ngtcp2_tstamp doq_get_timestamp_nanosec(void); #endif #endif /* LISTEN_DNSPORT_H */ unbound-1.25.1/services/cache/0000755000175000017500000000000015203270263015574 5ustar wouterwouterunbound-1.25.1/services/cache/infra.h0000644000175000017500000004513515203270263017054 0ustar wouterwouter/* * services/cache/infra.h - infrastructure cache, server rtt and capabilities * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the infrastructure cache, as well as rate limiting. * Note that there are two sorts of rate-limiting here: * - Pre-cache, per-query rate limiting (query ratelimits) * - Post-cache, per-domain name rate limiting (infra-ratelimits) */ #ifndef SERVICES_CACHE_INFRA_H #define SERVICES_CACHE_INFRA_H #include "util/storage/lruhash.h" #include "util/storage/dnstree.h" #include "util/rtt.h" #include "util/netevent.h" #include "util/data/msgreply.h" struct slabhash; struct config_file; /** number of timeouts for a type when the domain can be blocked ; * even if another type has completely rtt maxed it, the different type * can do this number of packets (until those all timeout too) */ #define TIMEOUT_COUNT_MAX 3 /** Timeout when only a single probe query per IP is allowed. * Any RTO above this number is considered a probe. * It is synchronized (caped) with USEFUL_SERVER_TOP_TIMEOUT so that probing * keeps working even if that configurable number drops below the default * 12000 ms of probing. */ extern int PROBE_MAXRTO; /** * Host information kept for every server, per zone. */ struct infra_key { /** the host address. */ struct sockaddr_storage addr; /** length of addr. */ socklen_t addrlen; /** zone name in wireformat */ uint8_t* zonename; /** length of zonename */ size_t namelen; /** hash table entry, data of type infra_data. */ struct lruhash_entry entry; }; /** * Host information encompasses host capabilities and retransmission timeouts. * And lameness information (notAuthoritative, noEDNS, Recursive) */ struct infra_data { /** TTL value for this entry. absolute time. */ time_t ttl; /** time in seconds (absolute) when probing re-commences, 0 disabled */ time_t probedelay; /** round trip times for timeout calculation */ struct rtt_info rtt; /** edns version that the host supports, -1 means no EDNS */ int edns_version; /** if the EDNS lameness is already known or not. * EDNS lame is when EDNS queries or replies are dropped, * and cause a timeout */ uint8_t edns_lame_known; /** is the host lame (does not serve the zone authoritatively), * or is the host dnssec lame (does not serve DNSSEC data) */ uint8_t isdnsseclame; /** is the host recursion lame (not AA, but RA) */ uint8_t rec_lame; /** the host is lame (not authoritative) for A records */ uint8_t lame_type_A; /** the host is lame (not authoritative) for other query types */ uint8_t lame_other; /** timeouts counter for type A */ uint8_t timeout_A; /** timeouts counter for type AAAA */ uint8_t timeout_AAAA; /** timeouts counter for others */ uint8_t timeout_other; }; /** * Infra cache */ struct infra_cache { /** The hash table with hosts */ struct slabhash* hosts; /** TTL value for host information, in seconds */ int host_ttl; /** the hosts that are down are kept probed for recovery */ int infra_keep_probing; /** hash table with query rates per name: rate_key, rate_data */ struct slabhash* domain_rates; /** ratelimit settings for domains, struct domain_limit_data */ rbtree_type domain_limits; /** hash table with query rates per client ip: ip_rate_key, ip_rate_data */ struct slabhash* client_ip_rates; /** tree of addr_tree_node, with wait_limit_netblock_info information */ rbtree_type wait_limits_netblock; /** tree of addr_tree_node, with wait_limit_netblock_info information */ rbtree_type wait_limits_cookie_netblock; }; /** ratelimit, unless overridden by domain_limits, 0 is off */ extern int infra_dp_ratelimit; /** * ratelimit settings for domains */ struct domain_limit_data { /** key for rbtree, must be first in struct, name of domain */ struct name_tree_node node; /** ratelimit for exact match with this name, -1 if not set */ int lim; /** ratelimit for names below this name, -1 if not set */ int below; }; /** * key for ratelimit lookups, a domain name */ struct rate_key { /** lruhash key entry */ struct lruhash_entry entry; /** domain name in uncompressed wireformat */ uint8_t* name; /** length of name */ size_t namelen; }; /** ip ratelimit, 0 is off */ extern int infra_ip_ratelimit; /** ip ratelimit for DNS Cookie clients, 0 is off */ extern int infra_ip_ratelimit_cookie; /** * key for ip_ratelimit lookups, a source IP. */ struct ip_rate_key { /** lruhash key entry */ struct lruhash_entry entry; /** client ip information */ struct sockaddr_storage addr; /** length of address */ socklen_t addrlen; }; /** number of seconds to track qps rate */ #define RATE_WINDOW 2 /** * Data for ratelimits per domain name * It is incremented when a non-cache-lookup happens for that domain name. * The name is the delegation point we have for the name. * If a new delegation point is found (a referral reply), the previous * delegation point is decremented, and the new one is charged with the query. */ struct rate_data { /** queries counted, for that second. 0 if not in use. */ int qps[RATE_WINDOW]; /** what the timestamp is of the qps array members, counter is * valid for that timestamp. Usually now and now-1. */ time_t timestamp[RATE_WINDOW]; /** the number of queries waiting in the mesh */ int mesh_wait; }; #define ip_rate_data rate_data /** * Data to store the configuration per netblock for the wait limit */ struct wait_limit_netblock_info { /** The addr tree node, this must be first. */ struct addr_tree_node node; /** the limit on the amount */ int limit; }; /** infra host cache default hash lookup size */ #define INFRA_HOST_STARTSIZE 32 /** bytes per zonename reserved in the hostcache, dnamelen(zonename.com.) */ #define INFRA_BYTES_NAME 14 /** * Create infra cache. * @param cfg: config parameters or NULL for defaults. * @return: new infra cache, or NULL. */ struct infra_cache* infra_create(struct config_file* cfg); /** * Delete infra cache. * @param infra: infrastructure cache to delete. */ void infra_delete(struct infra_cache* infra); /** * Adjust infra cache to use updated configuration settings. * This may clean the cache. Operates a bit like realloc. * There may be no threading or use by other threads. * @param infra: existing cache. If NULL a new infra cache is returned. * @param cfg: config options. * @return the new infra cache pointer or NULL on error. */ struct infra_cache* infra_adjust(struct infra_cache* infra, struct config_file* cfg); /** * Plain find infra data function (used by the other functions) * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: domain name of zone. * @param namelen: length of domain name. * @param wr: if true, writelock, else readlock. * @return the entry, could be expired (this is not checked) or NULL. */ struct lruhash_entry* infra_lookup_nottl(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, int wr); /** * Find host information to send a packet. Creates new entry if not found. * Lameness is empty. EDNS is 0 (try with first), and rtt is returned for * the first message to it. * Use this to send a packet only, because it also locks out others when * probing is restricted. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: domain name of zone. * @param namelen: length of domain name. * @param timenow: what time it is now. * @param edns_vs: edns version it supports, is returned. * @param edns_lame_known: if EDNS lame (EDNS is dropped in transit) has * already been probed, is returned. * @param to: timeout to use, is returned. * @return: 0 on error. */ int infra_host(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, time_t timenow, int* edns_vs, uint8_t* edns_lame_known, int* to); /** * Set a host to be lame for the given zone. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: domain name of zone apex. * @param namelen: length of domain name. * @param timenow: what time it is now. * @param dnsseclame: if true the host is set dnssec lame. * if false, the host is marked lame (not serving the zone). * @param reclame: if true host is a recursor not AA server. * if false, dnsseclame or marked lame. * @param qtype: the query type for which it is lame. * @return: 0 on error. */ int infra_set_lame(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, time_t timenow, int dnsseclame, int reclame, uint16_t qtype); /** * Update rtt information for the host. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: zone name * @param namelen: zone name length * @param qtype: query type. * @param roundtrip: estimate of roundtrip time in milliseconds or -1 for * timeout. * @param orig_rtt: original rtt for the query that timed out (roundtrip==-1). * ignored if roundtrip != -1. * @param timenow: what time it is now. * @return: 0 on error. new rto otherwise. */ int infra_rtt_update(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, int qtype, int roundtrip, int orig_rtt, time_t timenow); /** * Update information for the host, store that a TCP transaction works. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: name of zone * @param namelen: length of name */ void infra_update_tcp_works(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen); /** * Update edns information for the host. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: name of zone * @param namelen: length of name * @param edns_version: the version that it publishes. * If it is known to support EDNS then no-EDNS is not stored over it. * @param timenow: what time it is now. * @return: 0 on error. */ int infra_edns_update(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, int edns_version, time_t timenow); /** * Get Lameness information and average RTT if host is in the cache. * This information is to be used for server selection. * @param infra: infrastructure cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: zone name. * @param namelen: zone name length. * @param qtype: the query to be made. * @param lame: if function returns true, this returns lameness of the zone. * @param dnsseclame: if function returns true, this returns if the zone * is dnssec-lame. * @param reclame: if function returns true, this is if it is recursion lame. * @param rtt: if function returns true, this returns avg rtt of the server. * The rtt value is unclamped and reflects recent timeouts. * @param timenow: what time it is now. * @return if found in cache, or false if not (or TTL bad). */ int infra_get_lame_rtt(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, uint16_t qtype, int* lame, int* dnsseclame, int* reclame, int* rtt, time_t timenow); /** * Get additional (debug) info on timing. * @param infra: infra cache. * @param addr: host address. * @param addrlen: length of addr. * @param name: zone name * @param namelen: zone name length * @param rtt: the rtt_info is copied into here (caller alloced return struct). * @param delay: probe delay (if any). * @param timenow: what time it is now. * @param tA: timeout counter on type A. * @param tAAAA: timeout counter on type AAAA. * @param tother: timeout counter on type other. * @return TTL the infra host element is valid for. If -1: not found in cache. * TTL -2: found but expired. */ long long infra_get_host_rto(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, struct rtt_info* rtt, int* delay, time_t timenow, int* tA, int* tAAAA, int* tother); /** * Increment the query rate counter for a delegation point. * @param infra: infra cache. * @param name: zone name * @param namelen: zone name length * @param timenow: what time it is now. * @param backoff: if backoff is enabled. * @param qinfo: for logging, query name. * @param replylist: for logging, querier's address (if any). * @return 1 if it could be incremented. 0 if the increment overshot the * ratelimit or if in the previous second the ratelimit was exceeded. * Failures like alloc failures are not returned (probably as 1). */ int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow, int backoff, struct query_info* qinfo, struct comm_reply* replylist); /** * Decrement the query rate counter for a delegation point. * Because the reply received for the delegation point was pleasant, * we do not charge this delegation point with it (i.e. it was a referral). * Should call it with same second as when inc() was called. * @param infra: infra cache. * @param name: zone name * @param namelen: zone name length * @param timenow: what time it is now. */ void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow); /** * See if the query rate counter for a delegation point is exceeded. * So, no queries are going to be allowed. * @param infra: infra cache. * @param name: zone name * @param namelen: zone name length * @param timenow: what time it is now. * @param backoff: if backoff is enabled. * @return true if exceeded. */ int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow, int backoff); /** find the maximum rate stored. 0 if no information. * When backoff is enabled look for the maximum in the whole RATE_WINDOW. */ int infra_rate_max(void* data, time_t now, int backoff); /** find the ratelimit in qps for a domain. 0 if no limit for domain. */ int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, size_t namelen); /** Update query ratelimit hash and decide * whether or not a query should be dropped. * @param infra: infra cache * @param addr: client address * @param addrlen: client address length * @param timenow: what time it is now. * @param has_cookie: if the request came with a DNS Cookie. * @param backoff: if backoff is enabled. * @param buffer: with query for logging. * @return 1 if it could be incremented. 0 if the increment overshot the * ratelimit and the query should be dropped. */ int infra_ip_ratelimit_inc(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow, int has_cookie, int backoff, struct sldns_buffer* buffer); /** * Get memory used by the infra cache. * @param infra: infrastructure cache. * @return memory in use in bytes. */ size_t infra_get_mem(struct infra_cache* infra); /** calculate size for the hashtable, does not count size of lameness, * so the hashtable is a fixed number of items */ size_t infra_sizefunc(void* k, void* d); /** compare two addresses, returns -1, 0, or +1 */ int infra_compfunc(void* key1, void* key2); /** delete key, and destroy the lock */ void infra_delkeyfunc(void* k, void* arg); /** delete data and destroy the lameness hashtable */ void infra_deldatafunc(void* d, void* arg); /** calculate size for the hashtable */ size_t rate_sizefunc(void* k, void* d); /** compare two names, returns -1, 0, or +1 */ int rate_compfunc(void* key1, void* key2); /** delete key, and destroy the lock */ void rate_delkeyfunc(void* k, void* arg); /** delete data */ void rate_deldatafunc(void* d, void* arg); /* calculate size for the client ip hashtable */ size_t ip_rate_sizefunc(void* k, void* d); /* compare two addresses */ int ip_rate_compfunc(void* key1, void* key2); /* delete key, and destroy the lock */ void ip_rate_delkeyfunc(void* d, void* arg); /* delete data */ #define ip_rate_deldatafunc rate_deldatafunc /** See if the IP address can have another reply in the wait limit */ int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep, int cookie_valid, struct config_file* cfg); /** Increment number of waiting replies for IP */ void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep, time_t timenow, struct config_file* cfg); /** Decrement number of waiting replies for IP */ void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep, struct config_file* cfg); /** setup wait limits tree (0 on failure) */ int setup_wait_limits(struct rbtree_type* wait_limits_netblock, struct rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg); /** Free the wait limits and wait cookie limits tree. */ void wait_limits_free(struct rbtree_type* wait_limits_tree); /** setup domain limits tree (0 on failure) */ int setup_domain_limits(struct rbtree_type* domain_limits, struct config_file* cfg); /** Free the domain limits tree. */ void domain_limits_free(struct rbtree_type* domain_limits); /** exported for unit test */ int still_useful_timeout(); #endif /* SERVICES_CACHE_INFRA_H */ unbound-1.25.1/services/cache/dns.c0000644000175000017500000012212115203270263016523 0ustar wouterwouter/* * services/cache/dns.c - Cache services for DNS using msg and rrset caches. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the DNS cache. */ #include "config.h" #include "iterator/iter_delegpt.h" #include "iterator/iter_utils.h" #include "validator/val_nsec.h" #include "validator/val_utils.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "util/data/msgparse.h" #include "util/data/msgreply.h" #include "util/data/packed_rrset.h" #include "util/data/dname.h" #include "util/module.h" #include "util/net_help.h" #include "util/regional.h" #include "util/config_file.h" #include "sldns/sbuffer.h" /** store rrsets in the rrset cache. * @param env: module environment with caches. * @param rep: contains list of rrsets to store. * @param now: current time. * @param leeway: during prefetch how much leeway to update TTLs. * This makes rrsets expire sooner so they get updated with a new full * TTL. * Child side type NS does get this but TTL checks are done using the time * the query was created rather than the time the answer was received. * @param pside: if from parentside discovered NS, so that its NS is okay * in a prefetch situation to be updated (without becoming sticky). * @param qrep: update rrsets here if cache is better * @param region: for qrep allocs. * @param qstarttime: time when delegations were looked up, this is perhaps * earlier than the time in now. The time is used to determine if RRsets * of type NS have expired, so that they can only be updated using * lookups of delegation points that did not use them, since they had * expired then. */ static void store_rrsets(struct module_env* env, struct reply_info* rep, time_t now, time_t leeway, int pside, struct reply_info* qrep, struct regional* region, time_t qstarttime) { size_t i; time_t ttl, min_ttl = rep->ttl; /* see if rrset already exists in cache, if not insert it. */ for(i=0; irrset_count; i++) { rep->ref[i].key = rep->rrsets[i]; rep->ref[i].id = rep->rrsets[i]->id; /* update ref if it was in the cache */ switch(rrset_cache_update(env->rrset_cache, &rep->ref[i], env->alloc, ((ntohs(rep->ref[i].key->rk.type)== LDNS_RR_TYPE_NS && !pside)?qstarttime:now) + leeway)) { case 0: /* ref unchanged, item inserted */ break; case 2: /* ref updated, cache is superior */ if(region) { struct ub_packed_rrset_key* ck; lock_rw_rdlock(&rep->ref[i].key->entry.lock); /* if deleted rrset, do not copy it */ if(rep->ref[i].key->id == 0 || rep->ref[i].id != rep->ref[i].key->id) ck = NULL; else ck = packed_rrset_copy_region( rep->ref[i].key, region, ((ntohs(rep->ref[i].key->rk.type)== LDNS_RR_TYPE_NS && !pside)?qstarttime:now)); lock_rw_unlock(&rep->ref[i].key->entry.lock); if(ck) { /* use cached copy if memory allows */ qrep->rrsets[i] = ck; ttl = ((struct packed_rrset_data*) ck->entry.data)->ttl; if(ttl < qrep->ttl) { qrep->ttl = ttl; qrep->prefetch_ttl = PREFETCH_TTL_CALC(qrep->ttl); qrep->serve_expired_ttl = qrep->ttl + SERVE_EXPIRED_TTL; } } } /* no break: also copy key item */ /* the line below is matched by gcc regex and silences * the fallthrough warning */ ATTR_FALLTHROUGH /* fallthrough */ case 1: /* ref updated, item inserted */ rep->rrsets[i] = rep->ref[i].key; /* ref was updated; make sure the message ttl is * updated to the minimum of the current rrsets. */ lock_rw_rdlock(&rep->ref[i].key->entry.lock); /* if deleted, skip ttl update. */ if(rep->ref[i].key->id != 0 && rep->ref[i].id == rep->ref[i].key->id) { ttl = ((struct packed_rrset_data*) rep->rrsets[i]->entry.data)->ttl; if(ttl < min_ttl) min_ttl = ttl; } lock_rw_unlock(&rep->ref[i].key->entry.lock); } } if(min_ttl < rep->ttl) { rep->ttl = min_ttl; rep->prefetch_ttl = PREFETCH_TTL_CALC(rep->ttl); rep->serve_expired_ttl = rep->ttl + SERVE_EXPIRED_TTL; } } /** delete message from message cache */ void msg_cache_remove(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags) { struct query_info k; hashvalue_type h; k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; k.local_alias = NULL; h = query_info_hash(&k, flags); slabhash_remove(env->msg_cache, h, &k); } void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, struct reply_info* qrep, uint32_t flags, struct regional* region, time_t qstarttime) { struct msgreply_entry* e; time_t ttl = rep->ttl; size_t i; /* store RRsets */ for(i=0; irrset_count; i++) { rep->ref[i].key = rep->rrsets[i]; rep->ref[i].id = rep->rrsets[i]->id; } /* there was a reply_info_sortref(rep) here but it seems to be * unnecessary, because the cache gets locked per rrset. */ if((flags & DNSCACHE_STORE_EXPIRED_MSG_CACHEDB)) { reply_info_absolute_ttls(rep, *env->now, *env->now - ttl); } else reply_info_set_ttls(rep, *env->now); store_rrsets(env, rep, *env->now, leeway, pside, qrep, region, qstarttime); if(ttl == 0) { /* we do not store the message, but we did store the RRs, * which could be useful for delegation information */ verbose(VERB_ALGO, "TTL 0: dropped msg from cache"); reply_info_delete(rep, NULL); /* if the message is in the cache, remove that msg, * so that the TTL 0 response can be returned for future * responses (i.e. don't get answered from * cache, but instead go to recursion to get this TTL0 * response). * Possible messages that could be in the cache: * - SERVFAIL * - NXDOMAIN * - NODATA * - an older record that is expired * - an older record that did not yet expire */ msg_cache_remove(env, qinfo->qname, qinfo->qname_len, qinfo->qtype, qinfo->qclass, flags); return; } /* store msg in the cache */ reply_info_sortref(rep); if(!(e = query_info_entrysetup(qinfo, rep, hash))) { log_err("store_msg: malloc failed"); reply_info_delete(rep, NULL); return; } slabhash_insert(env->msg_cache, hash, &e->entry, rep, env->alloc); } /** find closest NS or DNAME and returns the rrset (locked) */ static struct ub_packed_rrset_key* find_closest_of_type(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qclass, time_t now, uint16_t searchtype, int stripfront, int noexpiredabove, uint8_t* expiretop, size_t expiretoplen) { struct ub_packed_rrset_key *rrset; uint8_t lablen; if(stripfront) { /* strip off so that DNAMEs have strict subdomain match */ lablen = *qname; qname += lablen + 1; qnamelen -= lablen + 1; } /* snip off front part of qname until the type is found */ while(qnamelen > 0) { rrset = rrset_cache_lookup(env->rrset_cache, qname, qnamelen, searchtype, qclass, 0, now, 0); if(!rrset && searchtype == LDNS_RR_TYPE_DNAME) /* If not found, for type DNAME, try 0TTL stored, * for its grace period. */ rrset = rrset_cache_lookup(env->rrset_cache, qname, qnamelen, searchtype, qclass, PACKED_RRSET_UPSTREAM_0TTL, now, 0); if(rrset) { uint8_t* origqname = qname; size_t origqnamelen = qnamelen; if(!noexpiredabove) return rrset; /* if expiretop set, do not look above it, but * qname is equal, so the just found result is also * the nonexpired above part. */ if(expiretop && qnamelen == expiretoplen && query_dname_compare(qname, expiretop)==0) return rrset; /* check for expiry, but we have to let go of the rrset * for the lock ordering */ lock_rw_unlock(&rrset->entry.lock); /* the rrset_cache_expired_above function always takes * off one label (if qnamelen>0) and returns the final * qname where it searched, so we can continue from * there turning the O N*N search into O N. */ if(!rrset_cache_expired_above(env->rrset_cache, &qname, &qnamelen, searchtype, qclass, now, expiretop, expiretoplen)) { /* we want to return rrset, but it may be * gone from cache, if so, just loop like * it was not in the cache in the first place. */ if((rrset = rrset_cache_lookup(env-> rrset_cache, origqname, origqnamelen, searchtype, qclass, 0, now, 0))) { return rrset; } } log_nametypeclass(VERB_ALGO, "ignoring rrset because expired rrsets exist above it", origqname, searchtype, qclass); continue; } /* snip off front label */ lablen = *qname; qname += lablen + 1; qnamelen -= lablen + 1; } return NULL; } /** add addr to additional section */ static void addr_to_additional(struct ub_packed_rrset_key* rrset, struct regional* region, struct dns_msg* msg, time_t now) { if((msg->rep->rrsets[msg->rep->rrset_count] = packed_rrset_copy_region(rrset, region, now))) { struct packed_rrset_data* d = rrset->entry.data; msg->rep->ar_numrrsets++; msg->rep->rrset_count++; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } } /** lookup message in message cache */ struct msgreply_entry* msg_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, time_t now, int wr) { struct lruhash_entry* e; struct query_info k; hashvalue_type h; k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; k.local_alias = NULL; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, wr); if(!e) return NULL; if( now > ((struct reply_info*)e->data)->ttl ) { lock_rw_unlock(&e->lock); return NULL; } return (struct msgreply_entry*)e->key; } /** find and add A and AAAA records for nameservers in delegpt */ static int find_add_addrs(struct module_env* env, uint16_t qclass, struct regional* region, struct delegpt* dp, time_t now, struct dns_msg** msg) { struct delegpt_ns* ns; struct msgreply_entry* neg; struct ub_packed_rrset_key* akey; for(ns = dp->nslist; ns; ns = ns->next) { akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(akey) { if(!delegpt_add_rrset_A(dp, region, akey, 0, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } if(msg) addr_to_additional(akey, region, *msg, now); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); if(akey) { if(!delegpt_add_rrset_AAAA(dp, region, akey, 0, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } if(msg) addr_to_additional(akey, region, *msg, now); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); /* Because recursion for lookup uses BIT_CD, check * for that so it stops the recursion lookup, if a * negative answer is cached. Because the cache uses * the CD flag for type AAAA. */ if(!neg) neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, BIT_CD, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } } return 1; } /** find and add A and AAAA records for missing nameservers in delegpt */ int cache_fill_missing(struct module_env* env, uint16_t qclass, struct regional* region, struct delegpt* dp, uint32_t flags) { struct delegpt_ns* ns; struct msgreply_entry* neg; struct ub_packed_rrset_key* akey; time_t now = *env->now; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->cache_lookup_count > ITERATOR_NAME_CACHELOOKUP_MAX) continue; ns->cache_lookup_count++; akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, flags, now, 0); if(akey) { if(!delegpt_add_rrset_A(dp, region, akey, ns->lame, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } log_nametypeclass(VERB_ALGO, "found in cache", ns->name, LDNS_RR_TYPE_A, qclass); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_A, qclass, 0, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, flags, now, 0); if(akey) { if(!delegpt_add_rrset_AAAA(dp, region, akey, ns->lame, NULL)) { lock_rw_unlock(&akey->entry.lock); return 0; } log_nametypeclass(VERB_ALGO, "found in cache", ns->name, LDNS_RR_TYPE_AAAA, qclass); lock_rw_unlock(&akey->entry.lock); } else { /* BIT_CD on false because delegpt lookup does * not use dns64 translation */ neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, 0, now, 0); /* Because recursion for lookup uses BIT_CD, check * for that so it stops the recursion lookup, if a * negative answer is cached. Because the cache uses * the CD flag for type AAAA. */ if(!neg) neg = msg_cache_lookup(env, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qclass, BIT_CD, now, 0); if(neg) { delegpt_add_neg_msg(dp, neg); lock_rw_unlock(&neg->entry.lock); } } } return 1; } /** find and add DS or NSEC to delegation msg */ static void find_add_ds(struct module_env* env, struct regional* region, struct dns_msg* msg, struct delegpt* dp, time_t now) { /* Lookup the DS or NSEC at the delegation point. */ struct ub_packed_rrset_key* rrset = rrset_cache_lookup( env->rrset_cache, dp->name, dp->namelen, LDNS_RR_TYPE_DS, msg->qinfo.qclass, 0, now, 0); if(!rrset) { /* NOTE: this won't work for alternate NSEC schemes * (opt-in, NSEC3) */ rrset = rrset_cache_lookup(env->rrset_cache, dp->name, dp->namelen, LDNS_RR_TYPE_NSEC, msg->qinfo.qclass, 0, now, 0); /* Note: the PACKED_RRSET_NSEC_AT_APEX flag is not used. * since this is a referral, we need the NSEC at the parent * side of the zone cut, not the NSEC at apex side. */ if(rrset && nsec_has_type(rrset, LDNS_RR_TYPE_DS)) { lock_rw_unlock(&rrset->entry.lock); rrset = NULL; /* discard wrong NSEC */ } } if(rrset) { /* add it to auth section. This is the second rrset. */ if((msg->rep->rrsets[msg->rep->rrset_count] = packed_rrset_copy_region(rrset, region, now))) { struct packed_rrset_data* d = rrset->entry.data; msg->rep->ns_numrrsets++; msg->rep->rrset_count++; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } lock_rw_unlock(&rrset->entry.lock); } } struct dns_msg* dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region, size_t capacity) { struct dns_msg* msg = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!msg) return NULL; msg->qinfo.qname = regional_alloc_init(region, qname, qnamelen); if(!msg->qinfo.qname) return NULL; msg->qinfo.qname_len = qnamelen; msg->qinfo.qtype = qtype; msg->qinfo.qclass = qclass; msg->qinfo.local_alias = NULL; /* non-packed reply_info, because it needs to grow the array */ msg->rep = (struct reply_info*)regional_alloc_zero(region, sizeof(struct reply_info)-sizeof(struct rrset_ref)); if(!msg->rep) return NULL; if(capacity > RR_COUNT_MAX) return NULL; /* integer overflow protection */ msg->rep->flags = BIT_QR; /* with QR, no AA */ msg->rep->qdcount = 1; msg->rep->ttl = MAX_TTL; /* will be updated (brought down) while we add * rrsets to the message */ msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets = (struct ub_packed_rrset_key**) regional_alloc(region, capacity*sizeof(struct ub_packed_rrset_key*)); if(!msg->rep->rrsets) return NULL; return msg; } int dns_msg_authadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now) { struct packed_rrset_data* d = rrset->entry.data; if(!(msg->rep->rrsets[msg->rep->rrset_count++] = packed_rrset_copy_region(rrset, region, now))) return 0; msg->rep->ns_numrrsets++; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return 1; } int dns_msg_ansadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now) { struct packed_rrset_data* d = rrset->entry.data; if(!(msg->rep->rrsets[msg->rep->rrset_count++] = packed_rrset_copy_region(rrset, region, now))) return 0; msg->rep->an_numrrsets++; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); return 1; } struct delegpt* dns_cache_find_delegation(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region, struct dns_msg** msg, time_t now, int noexpiredabove, uint8_t* expiretop, size_t expiretoplen) { /* try to find closest NS rrset */ struct ub_packed_rrset_key* nskey; struct packed_rrset_data* nsdata; struct delegpt* dp; nskey = find_closest_of_type(env, qname, qnamelen, qclass, now, LDNS_RR_TYPE_NS, 0, noexpiredabove, expiretop, expiretoplen); if(!nskey) /* hope the caller has hints to prime or something */ return NULL; nsdata = (struct packed_rrset_data*)nskey->entry.data; /* got the NS key, create delegation point */ dp = delegpt_create(region); if(!dp || !delegpt_set_name(dp, region, nskey->rk.dname)) { lock_rw_unlock(&nskey->entry.lock); log_err("find_delegation: out of memory"); return NULL; } /* create referral message */ if(msg) { /* allocate the array to as much as we could need: * NS rrset + DS/NSEC rrset + * A rrset for every NS RR * AAAA rrset for every NS RR */ *msg = dns_msg_create(qname, qnamelen, qtype, qclass, region, 2 + nsdata->count*2); if(!*msg || !dns_msg_authadd(*msg, region, nskey, now)) { lock_rw_unlock(&nskey->entry.lock); log_err("find_delegation: out of memory"); return NULL; } } if(!delegpt_rrset_add_ns(dp, region, nskey, 0)) log_err("find_delegation: addns out of memory"); lock_rw_unlock(&nskey->entry.lock); /* first unlock before next lookup*/ /* find and add DS/NSEC (if any) */ if(msg) find_add_ds(env, region, *msg, dp, now); /* find and add A entries */ if(!find_add_addrs(env, qclass, region, dp, now, msg)) log_err("find_delegation: addrs out of memory"); return dp; } /** allocate dns_msg from query_info and reply_info */ static struct dns_msg* gen_dns_msg(struct regional* region, struct query_info* q, size_t num) { struct dns_msg* msg = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!msg) return NULL; memcpy(&msg->qinfo, q, sizeof(struct query_info)); msg->qinfo.qname = regional_alloc_init(region, q->qname, q->qname_len); if(!msg->qinfo.qname) return NULL; /* allocate replyinfo struct and rrset key array separately */ msg->rep = (struct reply_info*)regional_alloc(region, sizeof(struct reply_info) - sizeof(struct rrset_ref)); if(!msg->rep) return NULL; msg->rep->ttl = MAX_TTL; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->reason_bogus_str = NULL; if(num > RR_COUNT_MAX) return NULL; /* integer overflow protection */ msg->rep->rrsets = (struct ub_packed_rrset_key**) regional_alloc(region, num * sizeof(struct ub_packed_rrset_key*)); if(!msg->rep->rrsets) return NULL; return msg; } struct dns_msg* tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, struct regional* region, time_t now, int allow_expired, struct regional* scratch) { struct dns_msg* msg; size_t i; int is_expired = 0; time_t now_control = now; if(TTL_IS_EXPIRED(r->ttl, now)) { /* Check if we are allowed to serve expired */ if(!allow_expired || !reply_info_can_answer_expired(r, now)) return NULL; /* Change the current time so we can pass the below TTL checks * when serving expired data. */ now_control = 0; is_expired = 1; } msg = gen_dns_msg(region, q, r->rrset_count); if(!msg) return NULL; msg->rep->flags = r->flags; msg->rep->qdcount = r->qdcount; msg->rep->security = r->security; msg->rep->an_numrrsets = r->an_numrrsets; msg->rep->ns_numrrsets = r->ns_numrrsets; msg->rep->ar_numrrsets = r->ar_numrrsets; msg->rep->rrset_count = r->rrset_count; msg->rep->authoritative = r->authoritative; msg->rep->reason_bogus = r->reason_bogus; if(r->reason_bogus_str) { msg->rep->reason_bogus_str = regional_strdup(region, r->reason_bogus_str); } if(!rrset_array_lock(r->ref, r->rrset_count, now_control)) { return NULL; } if(r->an_numrrsets > 0 && (r->rrsets[0]->rk.type == htons( LDNS_RR_TYPE_CNAME) || r->rrsets[0]->rk.type == htons( LDNS_RR_TYPE_DNAME)) && !reply_check_cname_chain(q, r)) { /* cname chain is now invalid, reconstruct msg */ rrset_array_unlock(r->ref, r->rrset_count); return NULL; } if(r->security == sec_status_secure && !reply_all_rrsets_secure(r)) { /* message rrsets have changed status, revalidate */ rrset_array_unlock(r->ref, r->rrset_count); return NULL; } for(i=0; irep->rrset_count; i++) { struct packed_rrset_data* d; msg->rep->rrsets[i] = packed_rrset_copy_region(r->rrsets[i], region, now); if(!msg->rep->rrsets[i]) { rrset_array_unlock(r->ref, r->rrset_count); return NULL; } d = msg->rep->rrsets[i]->entry.data; UPDATE_TTL_FROM_RRSET(msg->rep->ttl, d->ttl); } if(msg->rep->rrset_count < 1) { msg->rep->ttl = is_expired ?SERVE_EXPIRED_REPLY_TTL :r->ttl - now; if(r->prefetch_ttl > now) msg->rep->prefetch_ttl = r->prefetch_ttl - now; else msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); } else { /* msg->rep->ttl has been updated through the RRSets above */ msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); } msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; msg->rep->serve_expired_norec_ttl = 0; if(env) rrset_array_unlock_touch(env->rrset_cache, scratch, r->ref, r->rrset_count); else rrset_array_unlock(r->ref, r->rrset_count); return msg; } struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region) { size_t i; struct ub_packed_rrset_key** saved_rrsets; struct dns_msg* res = NULL; size_t rep_alloc_size = sizeof(struct reply_info) - sizeof(struct rrset_ref); /* this is the size of res->rep allocated in gen_dns_msg() */ res = gen_dns_msg(region, &origin->qinfo, origin->rep->rrset_count); if(!res) return NULL; saved_rrsets = res->rep->rrsets; /* save rrsets alloc by gen_dns_msg */ memcpy(res->rep, origin->rep, rep_alloc_size); res->rep->rrsets = saved_rrsets; if(origin->rep->reason_bogus_str) { res->rep->reason_bogus_str = regional_strdup(region, origin->rep->reason_bogus_str); } for(i=0; irep->rrset_count; i++) { res->rep->rrsets[i] = packed_rrset_copy_region( origin->rep->rrsets[i], region, 0); if(!res->rep->rrsets[i]) { return NULL; } } return res; } /** synthesize RRset-only response from cached RRset item */ static struct dns_msg* rrset_msg(struct ub_packed_rrset_key* rrset, struct regional* region, time_t now, struct query_info* q) { struct dns_msg* msg; struct packed_rrset_data* d = (struct packed_rrset_data*) rrset->entry.data; if(TTL_IS_EXPIRED(d->ttl, now)) return NULL; msg = gen_dns_msg(region, q, 1); /* only the CNAME (or other) RRset */ if(!msg) return NULL; msg->rep->flags = BIT_QR; /* reply, no AA, no error */ msg->rep->authoritative = 0; /* reply stored in cache can't be authoritative */ msg->rep->qdcount = 1; msg->rep->ttl = d->ttl - now; msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; msg->rep->serve_expired_norec_ttl = 0; msg->rep->security = sec_status_unchecked; msg->rep->an_numrrsets = 1; msg->rep->ns_numrrsets = 0; msg->rep->ar_numrrsets = 0; msg->rep->rrset_count = 1; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now); if(!msg->rep->rrsets[0]) /* copy CNAME */ return NULL; return msg; } /** synthesize DNAME+CNAME response from cached DNAME item */ static struct dns_msg* synth_dname_msg(struct ub_packed_rrset_key* rrset, struct regional* region, time_t now, struct query_info* q, enum sec_status* sec_status) { struct dns_msg* msg; struct ub_packed_rrset_key* ck; struct packed_rrset_data* newd, *d = (struct packed_rrset_data*) rrset->entry.data; uint8_t* newname, *dtarg = NULL; size_t newlen, dtarglen; time_t rr_ttl; if(TTL_IS_EXPIRED(d->ttl, now)) { /* Allow TTL=0 DNAME from upstream within grace period */ if(!(rrset->rk.flags & PACKED_RRSET_UPSTREAM_0TTL)) return NULL; rr_ttl = 0; } else { rr_ttl = d->ttl - now; } /* only allow validated (with DNSSEC) DNAMEs used from cache * for insecure DNAMEs, query again. */ *sec_status = d->security; /* return sec status, so the status of the CNAME can be checked * by the calling routine. */ msg = gen_dns_msg(region, q, 2); /* DNAME + CNAME RRset */ if(!msg) return NULL; msg->rep->flags = BIT_QR; /* reply, no AA, no error */ msg->rep->authoritative = 0; /* reply stored in cache can't be authoritative */ msg->rep->qdcount = 1; msg->rep->ttl = rr_ttl; msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; msg->rep->serve_expired_norec_ttl = 0; msg->rep->security = sec_status_unchecked; msg->rep->an_numrrsets = 1; msg->rep->ns_numrrsets = 0; msg->rep->ar_numrrsets = 0; msg->rep->rrset_count = 1; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->rrsets[0] = packed_rrset_copy_region(rrset, region, now); if(!msg->rep->rrsets[0]) /* copy DNAME */ return NULL; /* synth CNAME rrset */ get_cname_target(rrset, &dtarg, &dtarglen); if(!dtarg) return NULL; newlen = q->qname_len + dtarglen - rrset->rk.dname_len; if(newlen > LDNS_MAX_DOMAINLEN) { msg->rep->flags |= LDNS_RCODE_YXDOMAIN; return msg; } newname = (uint8_t*)regional_alloc(region, newlen); if(!newname) return NULL; /* new name is concatenation of qname front (without DNAME owner) * and DNAME target name */ memcpy(newname, q->qname, q->qname_len-rrset->rk.dname_len); memmove(newname+(q->qname_len-rrset->rk.dname_len), dtarg, dtarglen); /* create rest of CNAME rrset */ ck = (struct ub_packed_rrset_key*)regional_alloc(region, sizeof(struct ub_packed_rrset_key)); if(!ck) return NULL; memset(&ck->entry, 0, sizeof(ck->entry)); msg->rep->rrsets[1] = ck; ck->entry.key = ck; ck->rk.type = htons(LDNS_RR_TYPE_CNAME); ck->rk.rrset_class = rrset->rk.rrset_class; ck->rk.flags = 0; ck->rk.dname = regional_alloc_init(region, q->qname, q->qname_len); if(!ck->rk.dname) return NULL; ck->rk.dname_len = q->qname_len; ck->entry.hash = rrset_key_hash(&ck->rk); newd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t) + newlen); if(!newd) return NULL; ck->entry.data = newd; newd->ttl = rr_ttl; /* RFC6672: synth CNAME TTL == DNAME TTL */ newd->count = 1; newd->rrsig_count = 0; newd->trust = rrset_trust_ans_noAA; newd->rr_len = (size_t*)((uint8_t*)newd + sizeof(struct packed_rrset_data)); newd->rr_len[0] = newlen + sizeof(uint16_t); packed_rrset_ptr_fixup(newd); newd->rr_ttl[0] = newd->ttl; msg->rep->ttl = newd->ttl; msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(newd->ttl); msg->rep->serve_expired_ttl = newd->ttl + SERVE_EXPIRED_TTL; sldns_write_uint16(newd->rr_data[0], newlen); memmove(newd->rr_data[0] + sizeof(uint16_t), newname, newlen); msg->rep->an_numrrsets ++; msg->rep->rrset_count ++; return msg; } /** Fill TYPE_ANY response with some data from cache */ static struct dns_msg* fill_any(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region) { time_t now = *env->now; struct dns_msg* msg = NULL; uint16_t lookup[] = {LDNS_RR_TYPE_A, LDNS_RR_TYPE_AAAA, LDNS_RR_TYPE_MX, LDNS_RR_TYPE_SOA, LDNS_RR_TYPE_NS, LDNS_RR_TYPE_DNAME, 0}; int i, num=6; /* number of RR types to look up */ log_assert(lookup[num] == 0); if(env->cfg->deny_any) { /* return empty message */ msg = dns_msg_create(qname, qnamelen, qtype, qclass, region, 0); if(!msg) { return NULL; } /* set NOTIMPL for RFC 8482 */ msg->rep->flags |= LDNS_RCODE_NOTIMPL; msg->rep->security = sec_status_indeterminate; msg->rep->ttl = 1; /* empty NOTIMPL response will never be * updated with rrsets, set TTL to 1 */ return msg; } for(i=0; irrset_cache, qname, qnamelen, lookup[i], qclass, 0, now, 0); struct packed_rrset_data *d; if(!rrset) continue; /* only if rrset from answer section */ d = (struct packed_rrset_data*)rrset->entry.data; if(d->trust == rrset_trust_add_noAA || d->trust == rrset_trust_auth_noAA || d->trust == rrset_trust_add_AA || d->trust == rrset_trust_auth_AA) { lock_rw_unlock(&rrset->entry.lock); continue; } /* create msg if none */ if(!msg) { msg = dns_msg_create(qname, qnamelen, qtype, qclass, region, (size_t)(num-i)); if(!msg) { lock_rw_unlock(&rrset->entry.lock); return NULL; } } /* add RRset to response */ if(!dns_msg_ansadd(msg, region, rrset, now)) { lock_rw_unlock(&rrset->entry.lock); return NULL; } lock_rw_unlock(&rrset->entry.lock); } return msg; } struct dns_msg* dns_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, struct regional* region, struct regional* scratch, int no_partial, uint8_t* dpname, size_t dpnamelen) { struct lruhash_entry* e; struct query_info k; hashvalue_type h; time_t now = *env->now; struct ub_packed_rrset_key* rrset; /* lookup first, this has both NXdomains and ANSWER responses */ k.qname = qname; k.qname_len = qnamelen; k.qtype = qtype; k.qclass = qclass; k.local_alias = NULL; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(e) { struct msgreply_entry* key = (struct msgreply_entry*)e->key; struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg = tomsg(env, &key->key, data, region, now, 0, scratch); if(msg) { lock_rw_unlock(&e->lock); return msg; } /* could be msg==NULL; due to TTL or not all rrsets available */ lock_rw_unlock(&e->lock); } /* see if a DNAME exists. Checked for first, to enforce that DNAMEs * are more important, the CNAME is resynthesized and thus * consistent with the DNAME */ if(!no_partial && (rrset=find_closest_of_type(env, qname, qnamelen, qclass, now, LDNS_RR_TYPE_DNAME, 1, 0, NULL, 0))) { /* synthesize a DNAME+CNAME message based on this */ enum sec_status sec_status = sec_status_unchecked; struct dns_msg* msg = synth_dname_msg(rrset, region, now, &k, &sec_status); if(msg) { struct ub_packed_rrset_key* cname_rrset; lock_rw_unlock(&rrset->entry.lock); /* now, after unlocking the DNAME rrset lock, * check the sec_status, and see if we need to look * up the CNAME record associated before it can * be used */ /* normally, only secure DNAMEs allowed from cache*/ if(sec_status == sec_status_secure) return msg; /* but if we have a CNAME cached with this name, then we * have previously already allowed this name to pass. * the next cache lookup is going to fetch that CNAME itself, * but it is better to have the (unsigned)DNAME + CNAME in * that case */ cname_rrset = rrset_cache_lookup( env->rrset_cache, qname, qnamelen, LDNS_RR_TYPE_CNAME, qclass, 0, now, 0); if(cname_rrset) { /* CNAME already synthesized by * synth_dname_msg routine, so we can * straight up return the msg */ lock_rw_unlock(&cname_rrset->entry.lock); return msg; } } else { lock_rw_unlock(&rrset->entry.lock); } } /* see if we have CNAME for this domain, * but not for DS records (which are part of the parent) */ if(!no_partial && qtype != LDNS_RR_TYPE_DS && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, LDNS_RR_TYPE_CNAME, qclass, 0, now, 0))) { uint8_t* wc = NULL; size_t wl; /* if the rrset is not a wildcard expansion, with wcname */ /* because, if we return that CNAME rrset on its own, it is * missing the NSEC or NSEC3 proof */ if(!(val_rrset_wildcard(rrset, &wc, &wl) && wc != NULL)) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } } lock_rw_unlock(&rrset->entry.lock); } /* construct DS, DNSKEY messages from rrset cache. */ if((qtype == LDNS_RR_TYPE_DS || qtype == LDNS_RR_TYPE_DNSKEY) && (rrset=rrset_cache_lookup(env->rrset_cache, qname, qnamelen, qtype, qclass, 0, now, 0))) { /* if the rrset is from the additional section, and the * signatures have fallen off, then do not synthesize a msg * instead, allow a full query for signed results to happen. * Forego all rrset data from additional section, because * some signatures may not be present and cause validation * failure. */ struct packed_rrset_data *d = (struct packed_rrset_data*) rrset->entry.data; if(d->trust != rrset_trust_add_noAA && d->trust != rrset_trust_add_AA && (qtype == LDNS_RR_TYPE_DS || (d->trust != rrset_trust_auth_noAA && d->trust != rrset_trust_auth_AA) )) { struct dns_msg* msg = rrset_msg(rrset, region, now, &k); if(msg) { lock_rw_unlock(&rrset->entry.lock); return msg; } } lock_rw_unlock(&rrset->entry.lock); } /* stop downwards cache search on NXDOMAIN. * Empty nonterminals are NOERROR, so an NXDOMAIN for foo * means bla.foo also does not exist. The DNSSEC proofs are * the same. We search upwards for NXDOMAINs. */ if(env->cfg->harden_below_nxdomain) { while(!dname_is_root(k.qname)) { if(dpname && dpnamelen && !dname_subdomain_c(k.qname, dpname)) break; /* no synth nxdomain above the stub */ dname_remove_label(&k.qname, &k.qname_len); h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); if(!e && k.qtype != LDNS_RR_TYPE_A && env->cfg->qname_minimisation) { k.qtype = LDNS_RR_TYPE_A; h = query_info_hash(&k, flags); e = slabhash_lookup(env->msg_cache, h, &k, 0); } if(e) { struct reply_info* data = (struct reply_info*)e->data; struct dns_msg* msg; if(FLAGS_GET_RCODE(data->flags) == LDNS_RCODE_NXDOMAIN && data->security == sec_status_secure && (data->an_numrrsets == 0 || ntohs(data->rrsets[0]->rk.type) != LDNS_RR_TYPE_CNAME) && (msg=tomsg(env, &k, data, region, now, 0, scratch))) { lock_rw_unlock(&e->lock); msg->qinfo.qname=qname; msg->qinfo.qname_len=qnamelen; /* check that DNSSEC really works out */ msg->rep->security = sec_status_unchecked; iter_scrub_nxdomain(msg); return msg; } lock_rw_unlock(&e->lock); } k.qtype = qtype; } } /* fill common RR types for ANY response to avoid requery */ if(qtype == LDNS_RR_TYPE_ANY) { return fill_any(env, qname, qnamelen, qtype, qclass, region); } return NULL; } int dns_cache_store(struct module_env* env, struct query_info* msgqinf, struct reply_info* msgrep, int is_referral, time_t leeway, int pside, struct regional* region, uint32_t flags, time_t qstarttime, int is_valrec) { struct reply_info* rep = NULL; if(SERVE_EXPIRED) { /* We are serving expired records. Before caching, check if a * useful expired record exists. */ struct msgreply_entry* e = msg_cache_lookup(env, msgqinf->qname, msgqinf->qname_len, msgqinf->qtype, msgqinf->qclass, flags, 0, 1); if(e) { struct reply_info* cached = e->entry.data; if(TTL_IS_EXPIRED(cached->ttl, *env->now) && reply_info_could_use_expired(cached, *env->now) /* If we are validating make sure only * validating modules can update such messages. * In that case don't cache it and let a * subsequent module handle the caching. For * example, the iterator should not replace an * expired secure answer with a fresh unchecked * one and let the validator manage caching. */ && cached->security != sec_status_bogus && (env->need_to_validate && msgrep->security == sec_status_unchecked) /* Exceptions to that rule are: * o recursions that don't need validation but * need to update the cache for coherence * (delegation information while iterating, * DNSKEY and DS lookups from validator) * o explicit RRSIG queries that are not * validated. */ && !is_valrec && msgqinf->qtype != LDNS_RR_TYPE_RRSIG) { if((int)FLAGS_GET_RCODE(msgrep->flags) != LDNS_RCODE_NOERROR && (int)FLAGS_GET_RCODE(msgrep->flags) != LDNS_RCODE_NXDOMAIN) { /* The current response has an * erroneous rcode. Adjust norec time * so that additional lookups are not * performed for some time. */ verbose(VERB_ALGO, "set " "serve-expired-norec-ttl for " "response in cache"); cached->serve_expired_norec_ttl = NORR_TTL + *env->now; if(env->cfg->serve_expired_ttl_reset && cached->serve_expired_ttl < *env->now + env->cfg->serve_expired_ttl) { /* Reset serve-expired-ttl for * valid response in cache. */ verbose(VERB_ALGO, "reset " "serve-expired-ttl " "for response in cache"); cached->serve_expired_ttl = *env->now + env->cfg->serve_expired_ttl; } } verbose(VERB_ALGO, "a validated expired entry " "could be overwritten, skip caching " "the new message at this stage"); lock_rw_unlock(&e->entry.lock); return 1; } lock_rw_unlock(&e->entry.lock); } } /* alloc, malloc properly (not in region, like msg is) */ rep = reply_info_copy(msgrep, env->alloc, NULL); if(!rep) return 0; /* ttl must be relative ;i.e. 0..86400 not time(0)+86400. * the env->now is added to message and RRsets in this routine. */ /* the leeway is used to invalidate other rrsets earlier */ if(is_referral) { /* store rrsets */ struct rrset_ref ref; size_t i; for(i=0; irrset_count; i++) { packed_rrset_ttl_add((struct packed_rrset_data*) rep->rrsets[i]->entry.data, *env->now); ref.key = rep->rrsets[i]; ref.id = rep->rrsets[i]->id; /*ignore ret: it was in the cache, ref updated */ /* no leeway for typeNS */ (void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, ((ntohs(ref.key->rk.type)==LDNS_RR_TYPE_NS && !pside) ? qstarttime:*env->now + leeway)); } reply_info_delete(rep, NULL); return 1; } else { /* store msg, and rrsets */ struct query_info qinf; hashvalue_type h; qinf = *msgqinf; qinf.qname = memdup(msgqinf->qname, msgqinf->qname_len); if(!qinf.qname) { reply_info_parsedelete(rep, env->alloc); return 0; } /* fixup flags to be sensible for a reply based on the cache */ /* this module means that RA is available. It is an answer QR. * Not AA from cache. Not CD in cache (depends on client bit). */ rep->flags |= (BIT_RA | BIT_QR); rep->flags &= ~(BIT_AA | BIT_CD); h = query_info_hash(&qinf, (uint16_t)flags); dns_cache_store_msg(env, &qinf, h, rep, leeway, pside, msgrep, flags, region, qstarttime); /* qname is used inside query_info_entrysetup, and set to * NULL. If it has not been used, free it. free(0) is safe. */ free(qinf.qname); } return 1; } int dns_cache_prefetch_adjust(struct module_env* env, struct query_info* qinfo, time_t adjust, uint16_t flags) { struct msgreply_entry* msg; msg = msg_cache_lookup(env, qinfo->qname, qinfo->qname_len, qinfo->qtype, qinfo->qclass, flags, *env->now, 1); if(msg) { struct reply_info* rep = (struct reply_info*)msg->entry.data; if(rep) { rep->prefetch_ttl += adjust; lock_rw_unlock(&msg->entry.lock); return 1; } lock_rw_unlock(&msg->entry.lock); } return 0; } unbound-1.25.1/services/cache/dns.h0000644000175000017500000002741515203270263016542 0ustar wouterwouter/* * services/cache/dns.h - Cache services for DNS using msg and rrset caches. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the DNS cache. */ #ifndef SERVICES_CACHE_DNS_H #define SERVICES_CACHE_DNS_H #include "util/storage/lruhash.h" #include "util/data/msgreply.h" struct module_env; struct query_info; struct reply_info; struct regional; struct delegpt; /** Flags to control behavior of dns_cache_store() and dns_cache_store_msg(). * Must be an unsigned 32-bit value larger than 0xffff */ /** Allow caching a DNS message with a zero TTL. */ #define DNSCACHE_STORE_EXPIRED_MSG_CACHEDB 0x100000 /** * Region allocated message reply */ struct dns_msg { /** query info */ struct query_info qinfo; /** reply info - ptr to packed repinfo structure */ struct reply_info *rep; }; /** * Allocate a dns_msg with malloc/alloc structure and store in dns cache. * * @param env: environment, with alloc structure and dns cache. * @param qinf: query info, the query for which answer is stored. * this is allocated in a region, and will be copied to malloc area * before insertion. * @param rep: reply in dns_msg from dns_alloc_msg for example. * this is allocated in a region, and will be copied to malloc area * before insertion. * @param is_referral: If true, then the given message to be stored is a * referral. The cache implementation may use this as a hint. * It will store only the RRsets, not the message. * @param leeway: TTL value, if not 0, other rrsets are considered expired * that many seconds before actual TTL expiry. * @param pside: if true, information came from a server which was fetched * from the parentside of the zonecut. This means that the type NS * can be updated to full TTL even in prefetch situations. * @param region: region to allocate better entries from cache into. * (used when is_referral is false). * @param flags: flags with BIT_CD for AAAA queries in dns64 translation. * The higher 16 bits are used internally to customize the cache policy. * (See DNSCACHE_STORE_xxx flags). * @param qstarttime: time when the query was started, and thus when the * delegations were looked up. * @param is_valrec: if the query is validation recursion and does not get * dnssec validation itself. * @return 0 on alloc error (out of memory). */ int dns_cache_store(struct module_env* env, struct query_info* qinf, struct reply_info* rep, int is_referral, time_t leeway, int pside, struct regional* region, uint32_t flags, time_t qstarttime, int is_valrec); /** * Store message in the cache. Stores in message cache and rrset cache. * Both qinfo and rep should be malloced and are put in the cache. * They should not be used after this call, as they are then in shared cache. * Does not return errors, they are logged and only lead to less cache. * * @param env: module environment with the DNS cache. * @param qinfo: query info * @param hash: hash over qinfo. * @param rep: reply info, together with qinfo makes up the message. * Adjusts the reply info TTLs to absolute time. * @param leeway: TTL value, if not 0, other rrsets are considered expired * that many seconds before actual TTL expiry. * @param pside: if true, information came from a server which was fetched * from the parentside of the zonecut. This means that the type NS * can be updated to full TTL even in prefetch situations. * @param qrep: message that can be altered with better rrs from cache. * @param flags: customization flags for the cache policy. * @param qstarttime: time when the query was started, and thus when the * delegations were looked up. * @param region: to allocate into for qmsg. */ void dns_cache_store_msg(struct module_env* env, struct query_info* qinfo, hashvalue_type hash, struct reply_info* rep, time_t leeway, int pside, struct reply_info* qrep, uint32_t flags, struct regional* region, time_t qstarttime); /** * Find a delegation from the cache. * @param env: module environment with the DNS cache. * @param qname: query name. * @param qnamelen: length of qname. * @param qtype: query type. * @param qclass: query class. * @param region: where to allocate result delegation. * @param msg: if not NULL, delegation message is returned here, synthesized * from the cache. * @param timenow: the time now, for checking if TTL on cache entries is OK. * @param noexpiredabove: if set, no expired NS rrsets above the one found * are tolerated. It only returns delegations where the delegations above * it are valid. * @param expiretop: if not NULL, name where check for expiry ends for * noexpiredabove. * @param expiretoplen: length of expiretop dname. * @return new delegation or NULL on error or if not found in cache. */ struct delegpt* dns_cache_find_delegation(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region, struct dns_msg** msg, time_t timenow, int noexpiredabove, uint8_t* expiretop, size_t expiretoplen); /** * generate dns_msg from cached message * @param env: module environment with the DNS cache. NULL if the LRU from cache * does not need to be touched. * @param q: query info, contains qname that will make up the dns message. * @param r: reply info that, together with qname, will make up the dns message. * @param region: where to allocate dns message. * @param now: the time now, for check if TTL on cache entry is ok. * @param allow_expired: if true and serve-expired is enabled, it will allow * for expired dns_msg to be generated based on the configured serve-expired * logic. * @param scratch: where to allocate temporary data. * */ struct dns_msg* tomsg(struct module_env* env, struct query_info* q, struct reply_info* r, struct regional* region, time_t now, int allow_expired, struct regional* scratch); /** * Deep copy a dns_msg to a region. * @param origin: the dns_msg to copy. * @param region: the region to copy all the data to. * @return the new dns_msg or NULL on malloc error. */ struct dns_msg* dns_msg_deepcopy_region(struct dns_msg* origin, struct regional* region); /** * Find cached message * @param env: module environment with the DNS cache. * @param qname: query name. * @param qnamelen: length of qname. * @param qtype: query type. * @param qclass: query class. * @param flags: flags with BIT_CD for AAAA queries in dns64 translation. * @param region: where to allocate result. * @param scratch: where to allocate temporary data. * @param no_partial: if true, only complete messages and not a partial * one (with only the start of the CNAME chain and not the rest). * @param dpname: if not NULL, do not return NXDOMAIN above this name. * @param dpnamelen: length of dpname. * @return new response message (alloced in region, rrsets do not have IDs). * or NULL on error or if not found in cache. * TTLs are made relative to the current time. */ struct dns_msg* dns_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, struct regional* region, struct regional* scratch, int no_partial, uint8_t* dpname, size_t dpnamelen); /** * find and add A and AAAA records for missing nameservers in delegpt * @param env: module environment with rrset cache * @param qclass: which class to look in. * @param region: where to store new dp info. * @param dp: delegation point to fill missing entries. * @param flags: rrset flags, or 0. * @return false on alloc failure. */ int cache_fill_missing(struct module_env* env, uint16_t qclass, struct regional* region, struct delegpt* dp, uint32_t flags); /** * Utility, create new, unpacked data structure for cache response. * QR bit set, no AA. Query set as indicated. Space for number of rrsets. * @param qname: query section name * @param qnamelen: len of qname * @param qtype: query section type * @param qclass: query section class * @param region: where to alloc. * @param capacity: number of rrsets space to create in the array. * @return new dns_msg struct or NULL on mem fail. */ struct dns_msg* dns_msg_create(uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct regional* region, size_t capacity); /** * Add rrset to authority section in unpacked dns_msg message. Must have enough * space left, does not grow the array. * @param msg: msg to put it in. * @param region: region to alloc in * @param rrset: to add in authority section * @param now: now. * @return true if worked, false on fail */ int dns_msg_authadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now); /** * Add rrset to authority section in unpacked dns_msg message. Must have enough * space left, does not grow the array. * @param msg: msg to put it in. * @param region: region to alloc in * @param rrset: to add in authority section * @param now: now. * @return true if worked, false on fail */ int dns_msg_ansadd(struct dns_msg* msg, struct regional* region, struct ub_packed_rrset_key* rrset, time_t now); /** * Adjust the prefetch_ttl for a cached message. This adds a value to the * prefetch ttl - postponing the time when it will be prefetched for future * incoming queries. * @param env: module environment with caches and time. * @param qinfo: query info for the query that needs adjustment. * @param adjust: time in seconds to add to the prefetch_leeway. * @param flags: flags with BIT_CD for AAAA queries in dns64 translation. * @return false if not in cache. true if added. */ int dns_cache_prefetch_adjust(struct module_env* env, struct query_info* qinfo, time_t adjust, uint16_t flags); /** lookup message in message cache * the returned nonNULL entry is locked and has to be unlocked by the caller */ struct msgreply_entry* msg_cache_lookup(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags, time_t now, int wr); /** * Remove entry from the message cache. For unwanted entries. * @param env: with message cache. * @param qname: query name, in wireformat * @param qnamelen: length of qname, including terminating 0. * @param qtype: query type, host order. * @param qclass: query class, host order. * @param flags: flags */ void msg_cache_remove(struct module_env* env, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags); #endif /* SERVICES_CACHE_DNS_H */ unbound-1.25.1/services/cache/rrset.c0000644000175000017500000004306115203270263017103 0ustar wouterwouter/* * services/cache/rrset.c - Resource record set cache. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the rrset cache. */ #include "config.h" #include "services/cache/rrset.h" #include "sldns/rrdef.h" #include "util/storage/slabhash.h" #include "util/config_file.h" #include "util/data/packed_rrset.h" #include "util/data/msgreply.h" #include "util/data/msgparse.h" #include "util/data/dname.h" #include "util/regional.h" #include "util/alloc.h" #include "util/net_help.h" void rrset_markdel(void* key) { struct ub_packed_rrset_key* r = (struct ub_packed_rrset_key*)key; r->id = 0; } struct rrset_cache* rrset_cache_create(struct config_file* cfg, struct alloc_cache* alloc) { size_t slabs = (cfg?cfg->rrset_cache_slabs:HASH_DEFAULT_SLABS); size_t startarray = HASH_DEFAULT_STARTARRAY; size_t maxmem = (cfg?cfg->rrset_cache_size:HASH_DEFAULT_MAXMEM); struct rrset_cache *r = (struct rrset_cache*)slabhash_create(slabs, startarray, maxmem, ub_rrset_sizefunc, ub_rrset_compare, ub_rrset_key_delete, rrset_data_delete, alloc); if(!r) return NULL; slabhash_setmarkdel(&r->table, &rrset_markdel); return r; } void rrset_cache_delete(struct rrset_cache* r) { if(!r) return; slabhash_delete(&r->table); /* slabhash delete also does free(r), since table is first in struct*/ } struct rrset_cache* rrset_cache_adjust(struct rrset_cache *r, struct config_file* cfg, struct alloc_cache* alloc) { if(!r || !cfg || !slabhash_is_size(&r->table, cfg->rrset_cache_size, cfg->rrset_cache_slabs)) { rrset_cache_delete(r); r = rrset_cache_create(cfg, alloc); } return r; } void rrset_cache_touch(struct rrset_cache* r, struct ub_packed_rrset_key* key, hashvalue_type hash, rrset_id_type id) { struct lruhash* table = slabhash_gettable(&r->table, hash); /* * This leads to locking problems, deadlocks, if the caller is * holding any other rrset lock. * Because a lookup through the hashtable does: * tablelock -> entrylock (for that entry caller holds) * And this would do * entrylock(already held) -> tablelock * And if two threads do this, it results in deadlock. * So, the caller must not hold entrylock. */ lock_quick_lock(&table->lock); /* we have locked the hash table, the item can still be deleted. * because it could already have been reclaimed, but not yet set id=0. * This is because some lruhash routines have lazy deletion. * so, we must acquire a lock on the item to verify the id != 0. * also, with hash not changed, we are using the right slab. */ lock_rw_rdlock(&key->entry.lock); if(key->id == id && key->entry.hash == hash) { lru_touch(table, &key->entry); } lock_rw_unlock(&key->entry.lock); lock_quick_unlock(&table->lock); } /** see if rrset needs to be updated in the cache */ static int need_to_update_rrset(void* nd, void* cd, time_t timenow, int equal, int ns) { struct packed_rrset_data* newd = (struct packed_rrset_data*)nd; struct packed_rrset_data* cached = (struct packed_rrset_data*)cd; /* o if new data is expired, cached data is better */ if( TTL_IS_EXPIRED(newd->ttl, timenow) && !TTL_IS_EXPIRED(cached->ttl, timenow)) return 0; /* o store if rrset has been validated * everything better than bogus data * secure is preferred */ if( newd->security == sec_status_secure && cached->security != sec_status_secure) return 1; if( cached->security == sec_status_bogus && newd->security != sec_status_bogus && !equal) return 1; /* o if new RRset is more trustworthy - insert it */ if( newd->trust > cached->trust ) { /* if the cached rrset is bogus, and new is equal, * do not update the TTL - let it expire. */ if(equal && !TTL_IS_EXPIRED(cached->ttl, timenow) && cached->security == sec_status_bogus) return 0; /* ghost-domain: never let an NS overwrite extend lifetime * past the entry it replaces, regardless of trust. */ if(ns && !TTL_IS_EXPIRED(cached->ttl, timenow) && newd->ttl > cached->ttl) { size_t i; newd->ttl = cached->ttl; for(i=0; i<(newd->count+newd->rrsig_count); i++) if(newd->rr_ttl[i] > newd->ttl) newd->rr_ttl[i] = newd->ttl; } return 1; } /* o item in cache has expired */ if( TTL_IS_EXPIRED(cached->ttl, timenow) ) return 1; /* o same trust, but different in data - insert it */ if( newd->trust == cached->trust && !equal ) { /* if this is type NS, do not 'stick' to owner that changes * the NS RRset, but use the cached TTL for the new data, and * update to fetch the latest data. ttl is not expired, because * that check was before this one. */ if(ns) { size_t i; newd->ttl = cached->ttl; for(i=0; i<(newd->count+newd->rrsig_count); i++) if(newd->rr_ttl[i] > newd->ttl) newd->rr_ttl[i] = newd->ttl; } return 1; } return 0; } /** Update RRSet special key ID */ static void rrset_update_id(struct rrset_ref* ref, struct alloc_cache* alloc) { /* this may clear the cache and invalidate lock below */ uint64_t newid = alloc_get_id(alloc); /* obtain writelock */ lock_rw_wrlock(&ref->key->entry.lock); /* check if it was deleted in the meantime, if so, skip update */ if(ref->key->id == ref->id) { ref->key->id = newid; ref->id = newid; } lock_rw_unlock(&ref->key->entry.lock); } int rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref, struct alloc_cache* alloc, time_t timenow) { struct lruhash_entry* e; struct ub_packed_rrset_key* k = ref->key; hashvalue_type h = k->entry.hash; uint16_t rrset_type = ntohs(k->rk.type); int equal = 0; log_assert(ref->id != 0 && k->id != 0); log_assert(k->rk.dname != NULL); /* looks up item with a readlock - no editing! */ if((e=slabhash_lookup(&r->table, h, k, 0)) != 0) { /* return id and key as they will be used in the cache * since the lruhash_insert, if item already exists, deallocs * the passed key in favor of the already stored key. * because of the small gap (see below) this key ptr and id * may prove later to be already deleted, which is no problem * as it only makes a cache miss. */ ref->key = (struct ub_packed_rrset_key*)e->key; ref->id = ref->key->id; equal = rrsetdata_equal((struct packed_rrset_data*)k->entry. data, (struct packed_rrset_data*)e->data); if(!need_to_update_rrset(k->entry.data, e->data, timenow, equal, (rrset_type==LDNS_RR_TYPE_NS))) { /* cache is superior, return that value */ lock_rw_unlock(&e->lock); ub_packed_rrset_parsedelete(k, alloc); if(equal) return 2; return 1; } lock_rw_unlock(&e->lock); /* Go on and insert the passed item. * small gap here, where entry is not locked. * possibly entry is updated with something else. * we then overwrite that with our data. * this is just too bad, its cache anyway. */ /* use insert to update entry to manage lruhash * cache size values nicely. */ } log_assert(ref->key->id != 0); slabhash_insert(&r->table, h, &k->entry, k->entry.data, alloc); if(e) { /* For NSEC, NSEC3, DNAME, when rdata is updated, update * the ID number so that proofs in message cache are * invalidated */ if((rrset_type == LDNS_RR_TYPE_NSEC || rrset_type == LDNS_RR_TYPE_NSEC3 || rrset_type == LDNS_RR_TYPE_DNAME) && !equal) { rrset_update_id(ref, alloc); } return 1; } return 0; } void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache, struct ub_packed_rrset_key* rrset, uint8_t* ce, size_t ce_len, struct alloc_cache* alloc, time_t timenow) { struct rrset_ref ref; uint8_t wc_dname[LDNS_MAX_DOMAINLEN+3]; rrset = packed_rrset_copy_alloc(rrset, alloc, timenow); if(!rrset) { log_err("malloc failure in rrset_cache_update_wildcard"); return; } /* ce has at least one label less then qname, we can therefore safely * add the wildcard label. */ wc_dname[0] = 1; wc_dname[1] = (uint8_t)'*'; memmove(wc_dname+2, ce, ce_len); free(rrset->rk.dname); rrset->rk.dname_len = ce_len + 2; rrset->rk.dname = (uint8_t*)memdup(wc_dname, rrset->rk.dname_len); if(!rrset->rk.dname) { alloc_special_release(alloc, rrset); log_err("memdup failure in rrset_cache_update_wildcard"); return; } rrset->entry.hash = rrset_key_hash(&rrset->rk); ref.key = rrset; ref.id = rrset->id; /* ignore ret: if it was in the cache, ref updated */ (void)rrset_cache_update(rrset_cache, &ref, alloc, timenow); } /** Grace period in seconds for TTL=0 DNAME rrsets (RFC 2308: do not cache). * Allows synthesis from cache within this window to reduce recursion load. */ #define DNAME_TTL0_GRACE_SECONDS 1 struct ub_packed_rrset_key* rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint32_t flags, time_t timenow, int wr) { struct lruhash_entry* e; struct ub_packed_rrset_key key; key.entry.key = &key; key.entry.data = NULL; key.rk.dname = qname; key.rk.dname_len = qnamelen; key.rk.type = htons(qtype); key.rk.rrset_class = htons(qclass); key.rk.flags = flags; key.entry.hash = rrset_key_hash(&key.rk); if((e = slabhash_lookup(&r->table, key.entry.hash, &key, wr))) { /* check TTL */ struct packed_rrset_data* data = (struct packed_rrset_data*)e->data; struct ub_packed_rrset_key* k = (struct ub_packed_rrset_key*)e->key; if(TTL_IS_EXPIRED(data->ttl, timenow)) { /* Allow TTL=0 DNAME within grace period for synthesis */ if(qtype == LDNS_RR_TYPE_DNAME && (k->rk.flags & PACKED_RRSET_UPSTREAM_0TTL) && (timenow - data->ttl_add) <= DNAME_TTL0_GRACE_SECONDS) { /* within grace: allow for synthesis */ } else { lock_rw_unlock(&e->lock); return NULL; } } /* we're done */ return k; } return NULL; } int rrset_array_lock(struct rrset_ref* ref, size_t count, time_t timenow) { size_t i; struct packed_rrset_data* d; for(i=0; i0 && ref[i].key == ref[i-1].key) continue; /* only lock items once */ lock_rw_rdlock(&ref[i].key->entry.lock); d = ref[i].key->entry.data; if(ref[i].id != ref[i].key->id || TTL_IS_EXPIRED(d->ttl, timenow)) { /* failure! rollback our readlocks */ rrset_array_unlock(ref, i+1); return 0; } } return 1; } void rrset_array_unlock(struct rrset_ref* ref, size_t count) { size_t i; for(i=0; i0 && ref[i].key == ref[i-1].key) continue; /* only unlock items once */ lock_rw_unlock(&ref[i].key->entry.lock); } } void rrset_array_unlock_touch(struct rrset_cache* r, struct regional* scratch, struct rrset_ref* ref, size_t count) { hashvalue_type* h; size_t i; if(count > RR_COUNT_MAX || !(h = (hashvalue_type*)regional_alloc( scratch, sizeof(hashvalue_type)*count))) { log_warn("rrset LRU: memory allocation failed"); h = NULL; } else /* store hash values */ for(i=0; ientry.hash; /* unlock */ for(i=0; i0 && ref[i].key == ref[i-1].key) continue; /* only unlock items once */ lock_rw_unlock(&ref[i].key->entry.lock); } if(h) { /* LRU touch, with no rrset locks held */ for(i=0; i0 && ref[i].key == ref[i-1].key) continue; /* only touch items once */ rrset_cache_touch(r, ref[i].key, h[i], ref[i].id); } } } void rrset_update_sec_status(struct rrset_cache* r, struct ub_packed_rrset_key* rrset, time_t now) { struct packed_rrset_data* updata = (struct packed_rrset_data*)rrset->entry.data; struct lruhash_entry* e; struct packed_rrset_data* cachedata; /* hash it again to make sure it has a hash */ rrset->entry.hash = rrset_key_hash(&rrset->rk); e = slabhash_lookup(&r->table, rrset->entry.hash, rrset, 1); if(!e) return; /* not in the cache anymore */ cachedata = (struct packed_rrset_data*)e->data; if(!rrsetdata_equal(updata, cachedata)) { lock_rw_unlock(&e->lock); return; /* rrset has changed in the meantime */ } /* update the cached rrset */ if(updata->security > cachedata->security) { size_t i; if(updata->trust > cachedata->trust) cachedata->trust = updata->trust; cachedata->security = updata->security; /* for NS records only shorter TTLs, other types: update it */ if(ntohs(rrset->rk.type) != LDNS_RR_TYPE_NS || updata->ttl+now < cachedata->ttl || cachedata->ttl < now || updata->security == sec_status_bogus) { cachedata->ttl = updata->ttl + now; for(i=0; icount+cachedata->rrsig_count; i++) cachedata->rr_ttl[i] = updata->rr_ttl[i]+now; cachedata->ttl_add = now; } } lock_rw_unlock(&e->lock); } void rrset_check_sec_status(struct rrset_cache* r, struct ub_packed_rrset_key* rrset, time_t now) { struct packed_rrset_data* updata = (struct packed_rrset_data*)rrset->entry.data; struct lruhash_entry* e; struct packed_rrset_data* cachedata; /* hash it again to make sure it has a hash */ rrset->entry.hash = rrset_key_hash(&rrset->rk); e = slabhash_lookup(&r->table, rrset->entry.hash, rrset, 0); if(!e) return; /* not in the cache anymore */ cachedata = (struct packed_rrset_data*)e->data; if(now > cachedata->ttl || !rrsetdata_equal(updata, cachedata)) { lock_rw_unlock(&e->lock); return; /* expired, or rrset has changed in the meantime */ } if(cachedata->security > updata->security) { updata->security = cachedata->security; if(cachedata->security == sec_status_bogus) { size_t i; updata->ttl = cachedata->ttl - now; for(i=0; icount+cachedata->rrsig_count; i++) if(cachedata->rr_ttl[i] < now) updata->rr_ttl[i] = 0; else updata->rr_ttl[i] = cachedata->rr_ttl[i]-now; } if(cachedata->trust > updata->trust) updata->trust = cachedata->trust; } lock_rw_unlock(&e->lock); } void rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname, size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* qnametop, size_t qnametoplen) { struct ub_packed_rrset_key *rrset; uint8_t lablen; while(*qnamelen > 0) { /* look one label higher */ lablen = **qname; *qname += lablen + 1; *qnamelen -= lablen + 1; if(*qnamelen <= 0) return; /* stop at qnametop */ if(qnametop && *qnamelen == qnametoplen && query_dname_compare(*qname, qnametop)==0) return; if(verbosity >= VERB_ALGO) { /* looks up with a time of 0, to see expired entries */ if((rrset = rrset_cache_lookup(r, *qname, *qnamelen, searchtype, qclass, 0, 0, 0))) { struct packed_rrset_data* data = (struct packed_rrset_data*)rrset->entry.data; int expired = (now > data->ttl); lock_rw_unlock(&rrset->entry.lock); if(expired) log_nametypeclass(verbosity, "this " "(grand)parent rrset will be " "removed (expired)", *qname, searchtype, qclass); else log_nametypeclass(verbosity, "this " "(grand)parent rrset will be " "removed", *qname, searchtype, qclass); } } rrset_cache_remove(r, *qname, *qnamelen, searchtype, qclass, 0); } } int rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname, size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* qnametop, size_t qnametoplen) { struct ub_packed_rrset_key *rrset; uint8_t lablen; while(*qnamelen > 0) { /* look one label higher */ lablen = **qname; *qname += lablen + 1; *qnamelen -= lablen + 1; if(*qnamelen <= 0) break; /* looks up with a time of 0, to see expired entries */ if((rrset = rrset_cache_lookup(r, *qname, *qnamelen, searchtype, qclass, 0, 0, 0))) { struct packed_rrset_data* data = (struct packed_rrset_data*)rrset->entry.data; if(TTL_IS_EXPIRED(data->ttl, now)) { /* it is expired, this is not wanted */ lock_rw_unlock(&rrset->entry.lock); log_nametypeclass(VERB_ALGO, "this rrset is expired", *qname, searchtype, qclass); return 1; } /* it is not expired, continue looking */ lock_rw_unlock(&rrset->entry.lock); } /* do not look above the qnametop. */ if(qnametop && *qnamelen == qnametoplen && query_dname_compare(*qname, qnametop)==0) break; } return 0; } void rrset_cache_remove(struct rrset_cache* r, uint8_t* nm, size_t nmlen, uint16_t type, uint16_t dclass, uint32_t flags) { struct ub_packed_rrset_key key; key.entry.key = &key; key.rk.dname = nm; key.rk.dname_len = nmlen; key.rk.rrset_class = htons(dclass); key.rk.type = htons(type); key.rk.flags = flags; key.entry.hash = rrset_key_hash(&key.rk); slabhash_remove(&r->table, key.entry.hash, &key); } unbound-1.25.1/services/cache/infra.c0000644000175000017500000011302715203270263017043 0ustar wouterwouter/* * services/cache/infra.c - infrastructure cache, server rtt and capabilities * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the infrastructure cache. */ #include "config.h" #include "sldns/rrdef.h" #include "sldns/str2wire.h" #include "sldns/sbuffer.h" #include "sldns/wire2str.h" #include "services/cache/infra.h" #include "util/storage/slabhash.h" #include "util/storage/lookup3.h" #include "util/data/dname.h" #include "util/log.h" #include "util/net_help.h" #include "util/config_file.h" #include "iterator/iterator.h" /** ratelimit value for delegation point */ int infra_dp_ratelimit = 0; /** ratelimit value for client ip addresses, * in queries per second. */ int infra_ip_ratelimit = 0; /** ratelimit value for client ip addresses, * in queries per second. * For clients with a valid DNS Cookie. */ int infra_ip_ratelimit_cookie = 0; /** Minus 1000 because that is outside of the RTTBAND, so * blacklisted servers stay blacklisted if this is chosen. * If USEFUL_SERVER_TOP_TIMEOUT is below 1000 (configured via RTT_MAX_TIMEOUT, * infra-cache-max-rtt) change it to just above the RTT_BAND. */ int still_useful_timeout() { return USEFUL_SERVER_TOP_TIMEOUT < 1000 || USEFUL_SERVER_TOP_TIMEOUT - 1000 <= RTT_BAND ?RTT_BAND + 1 :USEFUL_SERVER_TOP_TIMEOUT - 1000; } size_t infra_sizefunc(void* k, void* ATTR_UNUSED(d)) { struct infra_key* key = (struct infra_key*)k; return sizeof(*key) + sizeof(struct infra_data) + key->namelen + lock_get_mem(&key->entry.lock); } int infra_compfunc(void* key1, void* key2) { struct infra_key* k1 = (struct infra_key*)key1; struct infra_key* k2 = (struct infra_key*)key2; int r = sockaddr_cmp(&k1->addr, k1->addrlen, &k2->addr, k2->addrlen); if(r != 0) return r; if(k1->namelen != k2->namelen) { if(k1->namelen < k2->namelen) return -1; return 1; } return query_dname_compare(k1->zonename, k2->zonename); } void infra_delkeyfunc(void* k, void* ATTR_UNUSED(arg)) { struct infra_key* key = (struct infra_key*)k; if(!key) return; lock_rw_destroy(&key->entry.lock); free(key->zonename); free(key); } void infra_deldatafunc(void* d, void* ATTR_UNUSED(arg)) { struct infra_data* data = (struct infra_data*)d; free(data); } size_t rate_sizefunc(void* k, void* ATTR_UNUSED(d)) { struct rate_key* key = (struct rate_key*)k; return sizeof(*key) + sizeof(struct rate_data) + key->namelen + lock_get_mem(&key->entry.lock); } int rate_compfunc(void* key1, void* key2) { struct rate_key* k1 = (struct rate_key*)key1; struct rate_key* k2 = (struct rate_key*)key2; if(k1->namelen != k2->namelen) { if(k1->namelen < k2->namelen) return -1; return 1; } return query_dname_compare(k1->name, k2->name); } void rate_delkeyfunc(void* k, void* ATTR_UNUSED(arg)) { struct rate_key* key = (struct rate_key*)k; if(!key) return; lock_rw_destroy(&key->entry.lock); free(key->name); free(key); } void rate_deldatafunc(void* d, void* ATTR_UNUSED(arg)) { struct rate_data* data = (struct rate_data*)d; free(data); } /** find or create element in domainlimit tree */ static struct domain_limit_data* domain_limit_findcreate( struct rbtree_type* domain_limits, char* name) { uint8_t* nm; int labs; size_t nmlen; struct domain_limit_data* d; /* parse name */ nm = sldns_str2wire_dname(name, &nmlen); if(!nm) { log_err("could not parse %s", name); return NULL; } labs = dname_count_labels(nm); /* can we find it? */ d = (struct domain_limit_data*)name_tree_find(domain_limits, nm, nmlen, labs, LDNS_RR_CLASS_IN); if(d) { free(nm); return d; } /* create it */ d = (struct domain_limit_data*)calloc(1, sizeof(*d)); if(!d) { free(nm); return NULL; } d->node.node.key = &d->node; d->node.name = nm; d->node.len = nmlen; d->node.labs = labs; d->node.dclass = LDNS_RR_CLASS_IN; d->lim = -1; d->below = -1; if(!name_tree_insert(domain_limits, &d->node, nm, nmlen, labs, LDNS_RR_CLASS_IN)) { log_err("duplicate element in domainlimit tree"); free(nm); free(d); return NULL; } return d; } /** insert rate limit configuration into lookup tree */ static int infra_ratelimit_cfg_insert(struct rbtree_type* domain_limits, struct config_file* cfg) { struct config_str2list* p; struct domain_limit_data* d; for(p = cfg->ratelimit_for_domain; p; p = p->next) { d = domain_limit_findcreate(domain_limits, p->str); if(!d) return 0; d->lim = atoi(p->str2); } for(p = cfg->ratelimit_below_domain; p; p = p->next) { d = domain_limit_findcreate(domain_limits, p->str); if(!d) return 0; d->below = atoi(p->str2); } return 1; } int setup_domain_limits(struct rbtree_type* domain_limits, struct config_file* cfg) { name_tree_init(domain_limits); if(!infra_ratelimit_cfg_insert(domain_limits, cfg)) { return 0; } name_tree_init_parents(domain_limits); return 1; } /** find or create element in wait limit netblock tree */ static struct wait_limit_netblock_info* wait_limit_netblock_findcreate(struct rbtree_type* tree, char* str) { struct sockaddr_storage addr; int net; socklen_t addrlen; struct wait_limit_netblock_info* d; if(!netblockstrtoaddr(str, 0, &addr, &addrlen, &net)) { log_err("cannot parse wait limit netblock '%s'", str); return 0; } /* can we find it? */ d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr, addrlen, net); if(d) return d; /* create it */ d = (struct wait_limit_netblock_info*)calloc(1, sizeof(*d)); if(!d) return NULL; d->limit = -1; if(!addr_tree_insert(tree, &d->node, &addr, addrlen, net)) { log_err("duplicate element in domainlimit tree"); free(d); return NULL; } return d; } /** insert wait limit information into lookup tree */ static int infra_wait_limit_netblock_insert(rbtree_type* wait_limits_netblock, rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg) { struct config_str2list* p; struct wait_limit_netblock_info* d; for(p = cfg->wait_limit_netblock; p; p = p->next) { d = wait_limit_netblock_findcreate(wait_limits_netblock, p->str); if(!d) return 0; d->limit = atoi(p->str2); } for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) { d = wait_limit_netblock_findcreate(wait_limits_cookie_netblock, p->str); if(!d) return 0; d->limit = atoi(p->str2); } return 1; } /** Add a default wait limit netblock */ static int wait_limit_netblock_default(struct rbtree_type* tree, char* str, int limit) { struct wait_limit_netblock_info* d; d = wait_limit_netblock_findcreate(tree, str); if(!d) return 0; d->limit = limit; return 1; } int setup_wait_limits(rbtree_type* wait_limits_netblock, rbtree_type* wait_limits_cookie_netblock, struct config_file* cfg) { addr_tree_init(wait_limits_netblock); addr_tree_init(wait_limits_cookie_netblock); /* Insert defaults */ /* The loopback address is separated from the rest of the network. */ /* wait-limit-netblock: 127.0.0.0/8 -1 */ if(!wait_limit_netblock_default(wait_limits_netblock, "127.0.0.0/8", -1)) return 0; /* wait-limit-netblock: ::1/128 -1 */ if(!wait_limit_netblock_default(wait_limits_netblock, "::1/128", -1)) return 0; /* wait-limit-cookie-netblock: 127.0.0.0/8 -1 */ if(!wait_limit_netblock_default(wait_limits_cookie_netblock, "127.0.0.0/8", -1)) return 0; /* wait-limit-cookie-netblock: ::1/128 -1 */ if(!wait_limit_netblock_default(wait_limits_cookie_netblock, "::1/128", -1)) return 0; if(!infra_wait_limit_netblock_insert(wait_limits_netblock, wait_limits_cookie_netblock, cfg)) return 0; addr_tree_init_parents(wait_limits_netblock); addr_tree_init_parents(wait_limits_cookie_netblock); return 1; } struct infra_cache* infra_create(struct config_file* cfg) { struct infra_cache* infra = (struct infra_cache*)calloc(1, sizeof(struct infra_cache)); size_t maxmem = cfg->infra_cache_numhosts * (sizeof(struct infra_key)+ sizeof(struct infra_data)+INFRA_BYTES_NAME); if(!infra) { return NULL; } infra->hosts = slabhash_create(cfg->infra_cache_slabs, INFRA_HOST_STARTSIZE, maxmem, &infra_sizefunc, &infra_compfunc, &infra_delkeyfunc, &infra_deldatafunc, NULL); if(!infra->hosts) { free(infra); return NULL; } infra->host_ttl = cfg->host_ttl; infra->infra_keep_probing = cfg->infra_keep_probing; infra_dp_ratelimit = cfg->ratelimit; infra->domain_rates = slabhash_create(cfg->ratelimit_slabs, INFRA_HOST_STARTSIZE, cfg->ratelimit_size, &rate_sizefunc, &rate_compfunc, &rate_delkeyfunc, &rate_deldatafunc, NULL); if(!infra->domain_rates) { infra_delete(infra); return NULL; } /* insert config data into ratelimits */ if(!setup_domain_limits(&infra->domain_limits, cfg)) { infra_delete(infra); return NULL; } if(!setup_wait_limits(&infra->wait_limits_netblock, &infra->wait_limits_cookie_netblock, cfg)) { infra_delete(infra); return NULL; } infra_ip_ratelimit = cfg->ip_ratelimit; infra_ip_ratelimit_cookie = cfg->ip_ratelimit_cookie; infra->client_ip_rates = slabhash_create(cfg->ip_ratelimit_slabs, INFRA_HOST_STARTSIZE, cfg->ip_ratelimit_size, &ip_rate_sizefunc, &ip_rate_compfunc, &ip_rate_delkeyfunc, &ip_rate_deldatafunc, NULL); if(!infra->client_ip_rates) { infra_delete(infra); return NULL; } return infra; } /** delete domain_limit entries */ static void domain_limit_free(rbnode_type* n, void* ATTR_UNUSED(arg)) { if(n) { free(((struct domain_limit_data*)n)->node.name); free(n); } } void domain_limits_free(struct rbtree_type* domain_limits) { if(!domain_limits) return; traverse_postorder(domain_limits, domain_limit_free, NULL); } /** delete wait_limit_netblock_info entries */ static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { free(n); } void wait_limits_free(struct rbtree_type* wait_limits_tree) { if(!wait_limits_tree) return; traverse_postorder(wait_limits_tree, wait_limit_netblock_del, NULL); } void infra_delete(struct infra_cache* infra) { if(!infra) return; slabhash_delete(infra->hosts); slabhash_delete(infra->domain_rates); domain_limits_free(&infra->domain_limits); slabhash_delete(infra->client_ip_rates); wait_limits_free(&infra->wait_limits_netblock); wait_limits_free(&infra->wait_limits_cookie_netblock); free(infra); } struct infra_cache* infra_adjust(struct infra_cache* infra, struct config_file* cfg) { size_t maxmem; if(!infra) return infra_create(cfg); infra->host_ttl = cfg->host_ttl; infra->infra_keep_probing = cfg->infra_keep_probing; infra_dp_ratelimit = cfg->ratelimit; infra_ip_ratelimit = cfg->ip_ratelimit; infra_ip_ratelimit_cookie = cfg->ip_ratelimit_cookie; maxmem = cfg->infra_cache_numhosts * (sizeof(struct infra_key)+ sizeof(struct infra_data)+INFRA_BYTES_NAME); /* divide cachesize by slabs and multiply by slabs, because if the * cachesize is not an even multiple of slabs, that is the resulting * size of the slabhash */ if(!slabhash_is_size(infra->hosts, maxmem, cfg->infra_cache_slabs) || !slabhash_is_size(infra->domain_rates, cfg->ratelimit_size, cfg->ratelimit_slabs) || !slabhash_is_size(infra->client_ip_rates, cfg->ip_ratelimit_size, cfg->ip_ratelimit_slabs)) { infra_delete(infra); infra = infra_create(cfg); } else { /* reapply domain limits */ traverse_postorder(&infra->domain_limits, domain_limit_free, NULL); if(!setup_domain_limits(&infra->domain_limits, cfg)) { infra_delete(infra); return NULL; } } return infra; } /** calculate the hash value for a host key * set use_port to a non-0 number to use the port in * the hash calculation; 0 to ignore the port.*/ static hashvalue_type hash_addr(struct sockaddr_storage* addr, socklen_t addrlen, int use_port) { hashvalue_type h = 0xab; /* select the pieces to hash, some OS have changing data inside */ if(addr_is_ip6(addr, addrlen)) { struct sockaddr_in6* in6 = (struct sockaddr_in6*)addr; h = hashlittle(&in6->sin6_family, sizeof(in6->sin6_family), h); if(use_port){ h = hashlittle(&in6->sin6_port, sizeof(in6->sin6_port), h); } h = hashlittle(&in6->sin6_addr, INET6_SIZE, h); } else { struct sockaddr_in* in = (struct sockaddr_in*)addr; h = hashlittle(&in->sin_family, sizeof(in->sin_family), h); if(use_port){ h = hashlittle(&in->sin_port, sizeof(in->sin_port), h); } h = hashlittle(&in->sin_addr, INET_SIZE, h); } return h; } /** calculate infra hash for a key */ static hashvalue_type hash_infra(struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name) { return dname_query_hash(name, hash_addr(addr, addrlen, 1)); } /** lookup version that does not check host ttl (you check it) */ struct lruhash_entry* infra_lookup_nottl(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, int wr) { struct infra_key k; k.addrlen = addrlen; memcpy(&k.addr, addr, addrlen); k.namelen = namelen; k.zonename = name; k.entry.hash = hash_infra(addr, addrlen, name); k.entry.key = (void*)&k; k.entry.data = NULL; return slabhash_lookup(infra->hosts, k.entry.hash, &k, wr); } /** init the data elements */ static void data_entry_init(struct infra_cache* infra, struct lruhash_entry* e, time_t timenow) { struct infra_data* data = (struct infra_data*)e->data; data->ttl = timenow + infra->host_ttl; rtt_init(&data->rtt); data->edns_version = 0; data->edns_lame_known = 0; data->probedelay = 0; data->isdnsseclame = 0; data->rec_lame = 0; data->lame_type_A = 0; data->lame_other = 0; data->timeout_A = 0; data->timeout_AAAA = 0; data->timeout_other = 0; } /** * Create and init a new entry for a host * @param infra: infra structure with config parameters. * @param addr: host address. * @param addrlen: length of addr. * @param name: name of zone * @param namelen: length of name. * @param tm: time now. * @return: the new entry or NULL on malloc failure. */ static struct lruhash_entry* new_entry(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, time_t tm) { struct infra_data* data; struct infra_key* key = (struct infra_key*)malloc(sizeof(*key)); if(!key) return NULL; data = (struct infra_data*)malloc(sizeof(struct infra_data)); if(!data) { free(key); return NULL; } key->zonename = memdup(name, namelen); if(!key->zonename) { free(key); free(data); return NULL; } key->namelen = namelen; lock_rw_init(&key->entry.lock); key->entry.hash = hash_infra(addr, addrlen, name); key->entry.key = (void*)key; key->entry.data = (void*)data; key->addrlen = addrlen; memcpy(&key->addr, addr, addrlen); data_entry_init(infra, &key->entry, tm); return &key->entry; } int infra_host(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen, time_t timenow, int* edns_vs, uint8_t* edns_lame_known, int* to) { struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 0); struct infra_data* data; int wr = 0; if(e && ((struct infra_data*)e->data)->ttl < timenow) { /* it expired, try to reuse existing entry */ int old = ((struct infra_data*)e->data)->rtt.rto; time_t tprobe = ((struct infra_data*)e->data)->probedelay; uint8_t tA = ((struct infra_data*)e->data)->timeout_A; uint8_t tAAAA = ((struct infra_data*)e->data)->timeout_AAAA; uint8_t tother = ((struct infra_data*)e->data)->timeout_other; lock_rw_unlock(&e->lock); e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 1); if(e) { /* if its still there we have a writelock, init */ /* re-initialise */ /* do not touch lameness, it may be valid still */ data_entry_init(infra, e, timenow); wr = 1; /* TOP_TIMEOUT remains on reuse */ if(old >= USEFUL_SERVER_TOP_TIMEOUT) { ((struct infra_data*)e->data)->rtt.rto = USEFUL_SERVER_TOP_TIMEOUT; ((struct infra_data*)e->data)->probedelay = tprobe; ((struct infra_data*)e->data)->timeout_A = tA; ((struct infra_data*)e->data)->timeout_AAAA = tAAAA; ((struct infra_data*)e->data)->timeout_other = tother; } } } if(!e) { /* insert new entry */ if(!(e = new_entry(infra, addr, addrlen, nm, nmlen, timenow))) return 0; data = (struct infra_data*)e->data; *edns_vs = data->edns_version; *edns_lame_known = data->edns_lame_known; *to = rtt_timeout(&data->rtt); slabhash_insert(infra->hosts, e->hash, e, data, NULL); return 1; } /* use existing entry */ data = (struct infra_data*)e->data; *edns_vs = data->edns_version; *edns_lame_known = data->edns_lame_known; *to = rtt_timeout(&data->rtt); if(*to >= PROBE_MAXRTO && (infra->infra_keep_probing || rtt_notimeout(&data->rtt)*4 <= *to)) { /* delay other queries, this is the probe query */ if(!wr) { lock_rw_unlock(&e->lock); e = infra_lookup_nottl(infra, addr,addrlen,nm,nmlen, 1); if(!e) { /* flushed from cache real fast, no use to allocate just for the probedelay */ return 1; } data = (struct infra_data*)e->data; } /* add 999 to round up the timeout value from msec to sec, * then add a whole second so it is certain that this probe * has timed out before the next is allowed */ data->probedelay = timenow + ((*to)+1999)/1000; } lock_rw_unlock(&e->lock); return 1; } int infra_set_lame(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen, time_t timenow, int dnsseclame, int reclame, uint16_t qtype) { struct infra_data* data; struct lruhash_entry* e; int needtoinsert = 0; e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 1); if(!e) { /* insert it */ if(!(e = new_entry(infra, addr, addrlen, nm, nmlen, timenow))) { log_err("set_lame: malloc failure"); return 0; } needtoinsert = 1; } else if( ((struct infra_data*)e->data)->ttl < timenow) { /* expired, reuse existing entry */ data_entry_init(infra, e, timenow); } /* got an entry, now set the zone lame */ data = (struct infra_data*)e->data; /* merge data (if any) */ if(dnsseclame) data->isdnsseclame = 1; if(reclame) data->rec_lame = 1; if(!dnsseclame && !reclame && qtype == LDNS_RR_TYPE_A) data->lame_type_A = 1; if(!dnsseclame && !reclame && qtype != LDNS_RR_TYPE_A) data->lame_other = 1; /* done */ if(needtoinsert) slabhash_insert(infra->hosts, e->hash, e, e->data, NULL); else { lock_rw_unlock(&e->lock); } return 1; } void infra_update_tcp_works(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen) { struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 1); struct infra_data* data; if(!e) return; /* doesn't exist */ data = (struct infra_data*)e->data; if(data->rtt.rto >= RTT_MAX_TIMEOUT) /* do not disqualify this server altogether, it is better * than nothing */ data->rtt.rto = still_useful_timeout(); lock_rw_unlock(&e->lock); } int infra_rtt_update(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen, int qtype, int roundtrip, int orig_rtt, time_t timenow) { struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 1); struct infra_data* data; int needtoinsert = 0, expired = 0; int rto = 1; time_t oldprobedelay = 0; if(!e) { if(!(e = new_entry(infra, addr, addrlen, nm, nmlen, timenow))) return 0; needtoinsert = 1; } else if(((struct infra_data*)e->data)->ttl < timenow) { oldprobedelay = ((struct infra_data*)e->data)->probedelay; data_entry_init(infra, e, timenow); expired = 1; } /* have an entry, update the rtt */ data = (struct infra_data*)e->data; if(roundtrip == -1) { if(needtoinsert || expired) { /* timeout on entry that has expired before the timer * keep old timeout from the function caller */ data->rtt.rto = orig_rtt; data->probedelay = oldprobedelay; } rtt_lost(&data->rtt, orig_rtt); if(qtype == LDNS_RR_TYPE_A) { if(data->timeout_A < TIMEOUT_COUNT_MAX) data->timeout_A++; } else if(qtype == LDNS_RR_TYPE_AAAA) { if(data->timeout_AAAA < TIMEOUT_COUNT_MAX) data->timeout_AAAA++; } else { if(data->timeout_other < TIMEOUT_COUNT_MAX) data->timeout_other++; } } else { /* if we got a reply, but the old timeout was above server * selection height, delete the timeout so the server is * fully available again */ if(rtt_unclamped(&data->rtt) >= USEFUL_SERVER_TOP_TIMEOUT) rtt_init(&data->rtt); rtt_update(&data->rtt, roundtrip); data->probedelay = 0; if(qtype == LDNS_RR_TYPE_A) data->timeout_A = 0; else if(qtype == LDNS_RR_TYPE_AAAA) data->timeout_AAAA = 0; else data->timeout_other = 0; } if(data->rtt.rto > 0) rto = data->rtt.rto; if(needtoinsert) slabhash_insert(infra->hosts, e->hash, e, e->data, NULL); else { lock_rw_unlock(&e->lock); } return rto; } long long infra_get_host_rto(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen, struct rtt_info* rtt, int* delay, time_t timenow, int* tA, int* tAAAA, int* tother) { struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 0); struct infra_data* data; long long ttl = -2; if(!e) return -1; data = (struct infra_data*)e->data; if(data->ttl >= timenow) { ttl = (long long)(data->ttl - timenow); memmove(rtt, &data->rtt, sizeof(*rtt)); if(timenow < data->probedelay) *delay = (int)(data->probedelay - timenow); else *delay = 0; } *tA = (int)data->timeout_A; *tAAAA = (int)data->timeout_AAAA; *tother = (int)data->timeout_other; lock_rw_unlock(&e->lock); return ttl; } int infra_edns_update(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* nm, size_t nmlen, int edns_version, time_t timenow) { struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, nm, nmlen, 1); struct infra_data* data; int needtoinsert = 0; if(!e) { if(!(e = new_entry(infra, addr, addrlen, nm, nmlen, timenow))) return 0; needtoinsert = 1; } else if(((struct infra_data*)e->data)->ttl < timenow) { data_entry_init(infra, e, timenow); } /* have an entry, update the rtt, and the ttl */ data = (struct infra_data*)e->data; /* do not update if noEDNS and stored is yesEDNS */ if(!(edns_version == -1 && (data->edns_version != -1 && data->edns_lame_known))) { data->edns_version = edns_version; data->edns_lame_known = 1; } if(needtoinsert) slabhash_insert(infra->hosts, e->hash, e, e->data, NULL); else { lock_rw_unlock(&e->lock); } return 1; } int infra_get_lame_rtt(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* name, size_t namelen, uint16_t qtype, int* lame, int* dnsseclame, int* reclame, int* rtt, time_t timenow) { struct infra_data* host; struct lruhash_entry* e = infra_lookup_nottl(infra, addr, addrlen, name, namelen, 0); if(!e) return 0; host = (struct infra_data*)e->data; *rtt = rtt_unclamped(&host->rtt); if(host->rtt.rto >= PROBE_MAXRTO && timenow >= host->probedelay && infra->infra_keep_probing) { /* single probe, keep probing */ if(*rtt >= USEFUL_SERVER_TOP_TIMEOUT) *rtt = still_useful_timeout(); } else if(host->rtt.rto >= PROBE_MAXRTO && timenow < host->probedelay && rtt_notimeout(&host->rtt)*4 <= host->rtt.rto) { /* single probe for this domain, and we are not probing */ /* unless the query type allows a probe to happen */ if(qtype == LDNS_RR_TYPE_A) { if(host->timeout_A >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; else *rtt = still_useful_timeout(); } else if(qtype == LDNS_RR_TYPE_AAAA) { if(host->timeout_AAAA >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; else *rtt = still_useful_timeout(); } else { if(host->timeout_other >= TIMEOUT_COUNT_MAX) *rtt = USEFUL_SERVER_TOP_TIMEOUT; else *rtt = still_useful_timeout(); } } /* expired entry */ if(timenow > host->ttl) { /* see if this can be a re-probe of an unresponsive server */ if(host->rtt.rto >= USEFUL_SERVER_TOP_TIMEOUT) { lock_rw_unlock(&e->lock); *rtt = still_useful_timeout(); *lame = 0; *dnsseclame = 0; *reclame = 0; return 1; } lock_rw_unlock(&e->lock); return 0; } /* check lameness first */ if(host->lame_type_A && qtype == LDNS_RR_TYPE_A) { lock_rw_unlock(&e->lock); *lame = 1; *dnsseclame = 0; *reclame = 0; return 1; } else if(host->lame_other && qtype != LDNS_RR_TYPE_A) { lock_rw_unlock(&e->lock); *lame = 1; *dnsseclame = 0; *reclame = 0; return 1; } else if(host->isdnsseclame) { lock_rw_unlock(&e->lock); *lame = 0; *dnsseclame = 1; *reclame = 0; return 1; } else if(host->rec_lame) { lock_rw_unlock(&e->lock); *lame = 0; *dnsseclame = 0; *reclame = 1; return 1; } /* no lameness for this type of query */ lock_rw_unlock(&e->lock); *lame = 0; *dnsseclame = 0; *reclame = 0; return 1; } int infra_find_ratelimit(struct infra_cache* infra, uint8_t* name, size_t namelen) { int labs = dname_count_labels(name); struct domain_limit_data* d = (struct domain_limit_data*) name_tree_lookup(&infra->domain_limits, name, namelen, labs, LDNS_RR_CLASS_IN); if(!d) return infra_dp_ratelimit; if(d->node.labs == labs && d->lim != -1) return d->lim; /* exact match */ /* find 'below match' */ if(d->node.labs == labs) d = (struct domain_limit_data*)d->node.parent; while(d) { if(d->below != -1) return d->below; d = (struct domain_limit_data*)d->node.parent; } return infra_dp_ratelimit; } size_t ip_rate_sizefunc(void* k, void* ATTR_UNUSED(d)) { struct ip_rate_key* key = (struct ip_rate_key*)k; return sizeof(*key) + sizeof(struct ip_rate_data) + lock_get_mem(&key->entry.lock); } int ip_rate_compfunc(void* key1, void* key2) { struct ip_rate_key* k1 = (struct ip_rate_key*)key1; struct ip_rate_key* k2 = (struct ip_rate_key*)key2; return sockaddr_cmp_addr(&k1->addr, k1->addrlen, &k2->addr, k2->addrlen); } void ip_rate_delkeyfunc(void* k, void* ATTR_UNUSED(arg)) { struct ip_rate_key* key = (struct ip_rate_key*)k; if(!key) return; lock_rw_destroy(&key->entry.lock); free(key); } /** find data item in array, for write access, caller unlocks */ static struct lruhash_entry* infra_find_ratedata(struct infra_cache* infra, uint8_t* name, size_t namelen, int wr) { struct rate_key key; hashvalue_type h = dname_query_hash(name, 0xab); memset(&key, 0, sizeof(key)); key.name = name; key.namelen = namelen; key.entry.hash = h; return slabhash_lookup(infra->domain_rates, h, &key, wr); } /** find data item in array for ip addresses */ static struct lruhash_entry* infra_find_ip_ratedata(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, int wr) { struct ip_rate_key key; hashvalue_type h = hash_addr(addr, addrlen, 0); memset(&key, 0, sizeof(key)); key.addr = *addr; key.addrlen = addrlen; key.entry.hash = h; return slabhash_lookup(infra->client_ip_rates, h, &key, wr); } /** create rate data item for name, number 1 in now */ static void infra_create_ratedata(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow) { hashvalue_type h = dname_query_hash(name, 0xab); struct rate_key* k = (struct rate_key*)calloc(1, sizeof(*k)); struct rate_data* d = (struct rate_data*)calloc(1, sizeof(*d)); if(!k || !d) { free(k); free(d); return; /* alloc failure */ } k->namelen = namelen; k->name = memdup(name, namelen); if(!k->name) { free(k); free(d); return; /* alloc failure */ } lock_rw_init(&k->entry.lock); k->entry.hash = h; k->entry.key = k; k->entry.data = d; d->qps[0] = 1; d->timestamp[0] = timenow; slabhash_insert(infra->domain_rates, h, &k->entry, d, NULL); } /** create rate data item for ip address */ static void infra_ip_create_ratedata(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow, int mesh_wait) { hashvalue_type h = hash_addr(addr, addrlen, 0); struct ip_rate_key* k = (struct ip_rate_key*)calloc(1, sizeof(*k)); struct ip_rate_data* d = (struct ip_rate_data*)calloc(1, sizeof(*d)); if(!k || !d) { free(k); free(d); return; /* alloc failure */ } k->addr = *addr; k->addrlen = addrlen; lock_rw_init(&k->entry.lock); k->entry.hash = h; k->entry.key = k; k->entry.data = d; d->qps[0] = 1; d->timestamp[0] = timenow; d->mesh_wait = mesh_wait; slabhash_insert(infra->client_ip_rates, h, &k->entry, d, NULL); } /** Find the second and return its rate counter. If none and should_add, remove * oldest to accommodate. Else return none. */ static int* infra_rate_find_second_or_none(void* data, time_t t, int should_add) { struct rate_data* d = (struct rate_data*)data; int i, oldest; for(i=0; itimestamp[i] == t) return &(d->qps[i]); } if(!should_add) return NULL; /* remove oldest timestamp, and insert it at t with 0 qps */ oldest = 0; for(i=0; itimestamp[i] < d->timestamp[oldest]) oldest = i; } d->timestamp[oldest] = t; d->qps[oldest] = 0; return &(d->qps[oldest]); } /** find the second and return its rate counter, if none, remove oldest to * accommodate */ static int* infra_rate_give_second(void* data, time_t t) { return infra_rate_find_second_or_none(data, t, 1); } /** find the second and return its rate counter only if it exists. Caller * should check for NULL return value */ static int* infra_rate_get_second(void* data, time_t t) { return infra_rate_find_second_or_none(data, t, 0); } int infra_rate_max(void* data, time_t now, int backoff) { struct rate_data* d = (struct rate_data*)data; int i, max = 0; for(i=0; itimestamp[i] <= RATE_WINDOW && d->qps[i] > max) { max = d->qps[i]; } } else { if(now == d->timestamp[i]) { return d->qps[i]; } } } return max; } int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow, int backoff, struct query_info* qinfo, struct comm_reply* replylist) { int lim, max; struct lruhash_entry* entry; if(!infra_dp_ratelimit) return 1; /* not enabled */ /* find ratelimit */ lim = infra_find_ratelimit(infra, name, namelen); if(!lim) return 1; /* disabled for this domain */ /* find or insert ratedata */ entry = infra_find_ratedata(infra, name, namelen, 1); if(entry) { int premax = infra_rate_max(entry->data, timenow, backoff); int* cur = infra_rate_give_second(entry->data, timenow); (*cur)++; max = infra_rate_max(entry->data, timenow, backoff); lock_rw_unlock(&entry->lock); if(premax <= lim && max > lim) { char buf[LDNS_MAX_DOMAINLEN], qnm[LDNS_MAX_DOMAINLEN]; char ts[12], cs[12], ip[128]; dname_str(name, buf); dname_str(qinfo->qname, qnm); sldns_wire2str_type_buf(qinfo->qtype, ts, sizeof(ts)); sldns_wire2str_class_buf(qinfo->qclass, cs, sizeof(cs)); ip[0]=0; if(replylist) { addr_to_str((struct sockaddr_storage *)&replylist->remote_addr, replylist->remote_addrlen, ip, sizeof(ip)); verbose(VERB_OPS, "ratelimit exceeded %s %d query %s %s %s from %s", buf, lim, qnm, cs, ts, ip); } else { verbose(VERB_OPS, "ratelimit exceeded %s %d query %s %s %s", buf, lim, qnm, cs, ts); } } return (max <= lim); } /* create */ infra_create_ratedata(infra, name, namelen, timenow); return (1 <= lim); } void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow) { struct lruhash_entry* entry; int* cur; if(!infra_dp_ratelimit) return; /* not enabled */ entry = infra_find_ratedata(infra, name, namelen, 1); if(!entry) return; /* not cached */ cur = infra_rate_get_second(entry->data, timenow); if(cur == NULL) { /* our timenow is not available anymore; nothing to decrease */ lock_rw_unlock(&entry->lock); return; } if((*cur) > 0) (*cur)--; lock_rw_unlock(&entry->lock); } int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow, int backoff) { struct lruhash_entry* entry; int lim, max; if(!infra_dp_ratelimit) return 0; /* not enabled */ /* find ratelimit */ lim = infra_find_ratelimit(infra, name, namelen); if(!lim) return 0; /* disabled for this domain */ /* find current rate */ entry = infra_find_ratedata(infra, name, namelen, 0); if(!entry) return 0; /* not cached */ max = infra_rate_max(entry->data, timenow, backoff); lock_rw_unlock(&entry->lock); return (max > lim); } size_t infra_get_mem(struct infra_cache* infra) { size_t s = sizeof(*infra) + slabhash_get_mem(infra->hosts); if(infra->domain_rates) s += slabhash_get_mem(infra->domain_rates); if(infra->client_ip_rates) s += slabhash_get_mem(infra->client_ip_rates); /* ignore domain_limits because walk through tree is big */ return s; } /* Returns 1 if the limit has not been exceeded, 0 otherwise. */ static int check_ip_ratelimit(struct sockaddr_storage* addr, socklen_t addrlen, struct sldns_buffer* buffer, int premax, int max, int has_cookie) { int limit; if(has_cookie) limit = infra_ip_ratelimit_cookie; else limit = infra_ip_ratelimit; /* Disabled */ if(limit == 0) return 1; if(premax <= limit && max > limit) { char client_ip[128], qnm[LDNS_MAX_DOMAINLEN+1+12+12]; addr_to_str(addr, addrlen, client_ip, sizeof(client_ip)); qnm[0]=0; if(sldns_buffer_limit(buffer)>LDNS_HEADER_SIZE && LDNS_QDCOUNT(sldns_buffer_begin(buffer))!=0) { (void)sldns_wire2str_rrquestion_buf( sldns_buffer_at(buffer, LDNS_HEADER_SIZE), sldns_buffer_limit(buffer)-LDNS_HEADER_SIZE, qnm, sizeof(qnm)); if(strlen(qnm)>0 && qnm[strlen(qnm)-1]=='\n') qnm[strlen(qnm)-1] = 0; /*remove newline*/ if(strchr(qnm, '\t')) *strchr(qnm, '\t') = ' '; if(strchr(qnm, '\t')) *strchr(qnm, '\t') = ' '; verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s %s", client_ip, limit, has_cookie?"(cookie)":"", qnm); } else { verbose(VERB_OPS, "ip_ratelimit exceeded %s %d%s (no query name)", client_ip, limit, has_cookie?"(cookie)":""); } } return (max <= limit); } int infra_ip_ratelimit_inc(struct infra_cache* infra, struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow, int has_cookie, int backoff, struct sldns_buffer* buffer) { int max; struct lruhash_entry* entry; /* not enabled */ if(!infra_ip_ratelimit) { return 1; } /* find or insert ratedata */ entry = infra_find_ip_ratedata(infra, addr, addrlen, 1); if(entry) { int premax = infra_rate_max(entry->data, timenow, backoff); int* cur = infra_rate_give_second(entry->data, timenow); (*cur)++; max = infra_rate_max(entry->data, timenow, backoff); lock_rw_unlock(&entry->lock); return check_ip_ratelimit(addr, addrlen, buffer, premax, max, has_cookie); } /* create */ infra_ip_create_ratedata(infra, addr, addrlen, timenow, 0); return 1; } int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep, int cookie_valid, struct config_file* cfg) { struct lruhash_entry* entry; if(cfg->wait_limit == 0 || (cookie_valid && cfg->wait_limit_cookie == 0)) return 1; entry = infra_find_ip_ratedata(infra, &rep->client_addr, rep->client_addrlen, 0); if(entry) { rbtree_type* tree; struct wait_limit_netblock_info* w; struct rate_data* d = (struct rate_data*)entry->data; int mesh_wait = d->mesh_wait; lock_rw_unlock(&entry->lock); /* have the wait amount, check how much is allowed */ if(cookie_valid) tree = &infra->wait_limits_cookie_netblock; else tree = &infra->wait_limits_netblock; w = (struct wait_limit_netblock_info*)addr_tree_lookup(tree, &rep->client_addr, rep->client_addrlen); if(w) { if(w->limit != -1 && mesh_wait > w->limit) return 0; } else { /* if there is no IP netblock specific information, * use the configured value. */ if(mesh_wait > (cookie_valid?cfg->wait_limit_cookie: cfg->wait_limit)) return 0; } } return 1; } void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep, time_t timenow, struct config_file* cfg) { struct lruhash_entry* entry; if(cfg->wait_limit == 0) return; /* Find it */ entry = infra_find_ip_ratedata(infra, &rep->client_addr, rep->client_addrlen, 1); if(entry) { struct rate_data* d = (struct rate_data*)entry->data; d->mesh_wait++; lock_rw_unlock(&entry->lock); return; } /* Create it */ infra_ip_create_ratedata(infra, &rep->client_addr, rep->client_addrlen, timenow, 1); } void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep, struct config_file* cfg) { struct lruhash_entry* entry; if(cfg->wait_limit == 0) return; entry = infra_find_ip_ratedata(infra, &rep->client_addr, rep->client_addrlen, 1); if(entry) { struct rate_data* d = (struct rate_data*)entry->data; if(d->mesh_wait > 0) d->mesh_wait--; lock_rw_unlock(&entry->lock); } } unbound-1.25.1/services/cache/rrset.h0000644000175000017500000002607515203270263017116 0ustar wouterwouter/* * services/cache/rrset.h - Resource record set cache. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the rrset cache. */ #ifndef SERVICES_CACHE_RRSET_H #define SERVICES_CACHE_RRSET_H #include "util/storage/lruhash.h" #include "util/storage/slabhash.h" #include "util/data/packed_rrset.h" struct config_file; struct alloc_cache; struct rrset_ref; struct regional; /** * The rrset cache * Thin wrapper around hashtable, like a typedef. */ struct rrset_cache { /** uses partitioned hash table */ struct slabhash table; }; /** * Create rrset cache * @param cfg: config settings or NULL for defaults. * @param alloc: initial default rrset key allocation. * @return: NULL on error. */ struct rrset_cache* rrset_cache_create(struct config_file* cfg, struct alloc_cache* alloc); /** * Delete rrset cache * @param r: rrset cache to delete. */ void rrset_cache_delete(struct rrset_cache* r); /** * Adjust settings of the cache to settings from the config file. * May purge the cache. May recreate the cache. * There may be no threading or use by other threads. * @param r: rrset cache to adjust (like realloc). * @param cfg: config settings or NULL for defaults. * @param alloc: initial default rrset key allocation. * @return 0 on error, or new rrset cache pointer on success. */ struct rrset_cache* rrset_cache_adjust(struct rrset_cache* r, struct config_file* cfg, struct alloc_cache* alloc); /** * Touch rrset, with given pointer and id. * Caller may not hold a lock on ANY rrset, this could give deadlock. * * This routine is faster than a hashtable lookup: * o no bin_lock is acquired. * o no walk through the bin-overflow-list. * o no comparison of the entry key to find it. * * @param r: rrset cache. * @param key: rrset key. Marked recently used (if it was not deleted * before the lock is acquired, in that case nothing happens). * @param hash: hash value of the item. Please read it from the key when * you have it locked. Used to find slab from slabhash. * @param id: used to check that the item is unchanged and not deleted. */ void rrset_cache_touch(struct rrset_cache* r, struct ub_packed_rrset_key* key, hashvalue_type hash, rrset_id_type id); /** * Update an rrset in the rrset cache. Stores the information for later use. * Will lookup if the rrset is in the cache and perform an update if necessary. * If the item was present, and superior, references are returned to that. * The passed item is then deallocated with rrset_parsedelete. * * A superior rrset is: * o rrset with better trust value. * o same trust value, different rdata, newly passed rrset is inserted. * If rdata is the same, TTL in the cache is updated. * * @param r: the rrset cache. * @param ref: reference (ptr and id) to the rrset. Pass reference setup for * the new rrset. The reference may be changed if the cached rrset is * superior. * Before calling the rrset is presumed newly allocated and changeable. * After calling you do not hold a lock, and the rrset is inserted in * the hashtable so you need a lock to change it. * @param alloc: how to allocate (and deallocate) the special rrset key. * @param timenow: current time (to see if ttl in cache is expired). * @return: true if the passed reference is updated, false if it is unchanged. * 0: reference unchanged, inserted in cache. * 1: reference updated, item is inserted in cache. * 2: reference updated, item in cache is considered superior. * also the rdata is equal (but other parameters in cache are superior). */ int rrset_cache_update(struct rrset_cache* r, struct rrset_ref* ref, struct alloc_cache* alloc, time_t timenow); /** * Update or add an rrset in the rrset cache using a wildcard dname. * Generates wildcard dname by prepending the wildcard label to the closest * encloser. Will lookup if the rrset is in the cache and perform an update if * necessary. * * @param rrset_cache: the rrset cache. * @param rrset: which rrset to cache as wildcard. This rrset is left * untouched. * @param ce: the closest encloser, will be uses to generate the wildcard dname. * @param ce_len: the closest encloser length. * @param alloc: how to allocate (and deallocate) the special rrset key. * @param timenow: current time (to see if ttl in cache is expired). */ void rrset_cache_update_wildcard(struct rrset_cache* rrset_cache, struct ub_packed_rrset_key* rrset, uint8_t* ce, size_t ce_len, struct alloc_cache* alloc, time_t timenow); /** * Lookup rrset. You obtain read/write lock. You must unlock before lookup * anything of else. * @param r: the rrset cache. * @param qname: name of rrset to lookup. * @param qnamelen: length of name of rrset to lookup. * @param qtype: type of rrset to lookup (host order). * @param qclass: class of rrset to lookup (host order). * @param flags: rrset flags, or 0. * @param timenow: used to compare with TTL. * @param wr: set true to get writelock. * @return packed rrset key pointer. Remember to unlock the key.entry.lock. * or NULL if could not be found or it was timed out. */ struct ub_packed_rrset_key* rrset_cache_lookup(struct rrset_cache* r, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint32_t flags, time_t timenow, int wr); /** * Obtain readlock on a (sorted) list of rrset references. * Checks TTLs and IDs of the rrsets and rollbacks locking if not Ok. * @param ref: array of rrset references (key pointer and ID value). * duplicate references are allowed and handled. * @param count: size of array. * @param timenow: used to compare with TTL. * @return true on success, false on a failure, which can be that some * RRsets have timed out, or that they do not exist any more, the * RRsets have been purged from the cache. * If true, you hold readlocks on all the ref items. */ int rrset_array_lock(struct rrset_ref* ref, size_t count, time_t timenow); /** * Unlock array (sorted) of rrset references. * @param ref: array of rrset references (key pointer and ID value). * duplicate references are allowed and handled. * @param count: size of array. */ void rrset_array_unlock(struct rrset_ref* ref, size_t count); /** * Unlock array (sorted) of rrset references and at the same time * touch LRU on the rrsets. It needs the scratch region for temporary * storage as it uses the initial locks to obtain hash values. * @param r: the rrset cache. In this cache LRU is updated. * @param scratch: region for temporary storage of hash values. * if memory allocation fails, the lru touch fails silently, * but locks are released. memory errors are logged. * @param ref: array of rrset references (key pointer and ID value). * duplicate references are allowed and handled. * @param count: size of array. */ void rrset_array_unlock_touch(struct rrset_cache* r, struct regional* scratch, struct rrset_ref* ref, size_t count); /** * Update security status of an rrset. Looks up the rrset. * If found, checks if rdata is equal. * If so, it will update the security, trust and rrset-ttl values. * The values are only updated if security is increased (towards secure). * @param r: the rrset cache. * @param rrset: which rrset to attempt to update. This rrset is left * untouched. The rrset in the cache is updated in-place. * @param now: current time. */ void rrset_update_sec_status(struct rrset_cache* r, struct ub_packed_rrset_key* rrset, time_t now); /** * Looks up security status of an rrset. Looks up the rrset. * If found, checks if rdata is equal, and entry did not expire. * If so, it will update the security, trust and rrset-ttl values. * @param r: the rrset cache. * @param rrset: This rrset may change security status due to the cache. * But its status will only improve, towards secure. * @param now: current time. */ void rrset_check_sec_status(struct rrset_cache* r, struct ub_packed_rrset_key* rrset, time_t now); /** * Removes rrsets above the qname, returns upper qname. * @param r: the rrset cache. * @param qname: the start qname, also used as the output. * @param qnamelen: length of qname, updated when it returns. * @param searchtype: qtype to search for. * @param qclass: qclass to search for. * @param now: current time. * @param qnametop: the top qname to stop removal (it is not removed). * @param qnametoplen: length of qnametop. */ void rrset_cache_remove_above(struct rrset_cache* r, uint8_t** qname, size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* qnametop, size_t qnametoplen); /** * Sees if an rrset is expired above the qname, returns upper qname. * @param r: the rrset cache. * @param qname: the start qname, also used as the output. * @param qnamelen: length of qname, updated when it returns. * @param searchtype: qtype to search for. * @param qclass: qclass to search for. * @param now: current time. * @param qnametop: the top qname, don't look farther than that. * @param qnametoplen: length of qnametop. * @return true if there is an expired rrset above, false otherwise. */ int rrset_cache_expired_above(struct rrset_cache* r, uint8_t** qname, size_t* qnamelen, uint16_t searchtype, uint16_t qclass, time_t now, uint8_t* qnametop, size_t qnametoplen); /** * Remove an rrset from the cache, by name and type and flags * @param r: rrset cache * @param nm: name of rrset * @param nmlen: length of name * @param type: type of rrset * @param dclass: class of rrset, host order * @param flags: flags of rrset, host order */ void rrset_cache_remove(struct rrset_cache* r, uint8_t* nm, size_t nmlen, uint16_t type, uint16_t dclass, uint32_t flags); /** mark rrset to be deleted, set id=0 */ void rrset_markdel(void* key); #endif /* SERVICES_CACHE_RRSET_H */ unbound-1.25.1/services/outside_network.c0000644000175000017500000036354015203270263020135 0ustar wouterwouter/* * services/outside_network.c - implement sending of queries and wait answer. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has functions to send queries to authoritative servers and * wait for the pending answer events. */ #include "config.h" #include #ifdef HAVE_SYS_TYPES_H # include #endif #include #include "services/outside_network.h" #include "services/listen_dnsport.h" #include "services/cache/infra.h" #include "iterator/iterator.h" #include "util/data/msgparse.h" #include "util/data/msgreply.h" #include "util/data/msgencode.h" #include "util/data/dname.h" #include "util/netevent.h" #include "util/log.h" #include "util/net_help.h" #include "util/random.h" #include "util/fptr_wlist.h" #include "util/edns.h" #include "sldns/sbuffer.h" #include "dnstap/dnstap.h" #ifdef HAVE_OPENSSL_SSL_H #include #endif #ifdef HAVE_X509_VERIFY_PARAM_SET1_HOST #include #endif #ifdef HAVE_NETDB_H #include #endif #include /** number of times to retry making a random ID that is unique. */ #define MAX_ID_RETRY 1000 /** number of times to retry finding interface, port that can be opened. */ #define MAX_PORT_RETRY 10000 /** number of retries on outgoing UDP queries */ #define OUTBOUND_UDP_RETRY 1 /** initiate TCP transaction for serviced query */ static void serviced_tcp_initiate(struct serviced_query* sq, sldns_buffer* buff); /** with a fd available, randomize and send UDP */ static int randomize_and_send_udp(struct pending* pend, sldns_buffer* packet, int timeout); /** select a DNS ID for a TCP stream */ static uint16_t tcp_select_id(struct outside_network* outnet, struct reuse_tcp* reuse); /** Perform serviced query UDP sending operation */ static int serviced_udp_send(struct serviced_query* sq, sldns_buffer* buff); /** Send serviced query over TCP return false on initial failure */ static int serviced_tcp_send(struct serviced_query* sq, sldns_buffer* buff); /** call the callbacks for a serviced query */ static void serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c, struct comm_reply* rep); int pending_cmp(const void* key1, const void* key2) { struct pending *p1 = (struct pending*)key1; struct pending *p2 = (struct pending*)key2; if(p1->id < p2->id) return -1; if(p1->id > p2->id) return 1; log_assert(p1->id == p2->id); return sockaddr_cmp(&p1->addr, p1->addrlen, &p2->addr, p2->addrlen); } int serviced_cmp(const void* key1, const void* key2) { struct serviced_query* q1 = (struct serviced_query*)key1; struct serviced_query* q2 = (struct serviced_query*)key2; int r; if(q1->qbuflen < q2->qbuflen) return -1; if(q1->qbuflen > q2->qbuflen) return 1; log_assert(q1->qbuflen == q2->qbuflen); log_assert(q1->qbuflen >= 15 /* 10 header, root, type, class */); /* alternate casing of qname is still the same query */ if((r = memcmp(q1->qbuf, q2->qbuf, 10)) != 0) return r; if((r = memcmp(q1->qbuf+q1->qbuflen-4, q2->qbuf+q2->qbuflen-4, 4)) != 0) return r; if(q1->dnssec != q2->dnssec) { if(q1->dnssec < q2->dnssec) return -1; return 1; } if((r = query_dname_compare(q1->qbuf+10, q2->qbuf+10)) != 0) return r; if((r = edns_opt_list_compare(q1->opt_list, q2->opt_list)) != 0) return r; return sockaddr_cmp(&q1->addr, q1->addrlen, &q2->addr, q2->addrlen); } /** compare if the reuse element has the same address, port and same ssl-is * used-for-it characteristic */ static int reuse_cmp_addrportssl(const void* key1, const void* key2) { struct reuse_tcp* r1 = (struct reuse_tcp*)key1; struct reuse_tcp* r2 = (struct reuse_tcp*)key2; int r; /* compare address and port */ r = sockaddr_cmp(&r1->addr, r1->addrlen, &r2->addr, r2->addrlen); if(r != 0) return r; /* compare if SSL-enabled */ if(r1->is_ssl && !r2->is_ssl) return 1; if(!r1->is_ssl && r2->is_ssl) return -1; /* compare tls_auth_name if SSL-enabled */ if(r1->is_ssl) { if(r1->tls_auth_name && !r2->tls_auth_name) return 1; if(!r1->tls_auth_name && r2->tls_auth_name) return -1; if(r1->tls_auth_name && r2->tls_auth_name) { r = strcmp(r1->tls_auth_name, r2->tls_auth_name); if(r != 0) return r; } } return 0; } int reuse_cmp(const void* key1, const void* key2) { int r; r = reuse_cmp_addrportssl(key1, key2); if(r != 0) return r; /* compare ptr value */ if(key1 < key2) return -1; if(key1 > key2) return 1; return 0; } int reuse_id_cmp(const void* key1, const void* key2) { struct waiting_tcp* w1 = (struct waiting_tcp*)key1; struct waiting_tcp* w2 = (struct waiting_tcp*)key2; if(w1->id < w2->id) return -1; if(w1->id > w2->id) return 1; return 0; } /** delete waiting_tcp entry. Does not unlink from waiting list. * @param w: to delete. */ static void waiting_tcp_delete(struct waiting_tcp* w) { if(!w) return; if(w->timer) comm_timer_delete(w->timer); free(w); } /** * Pick random outgoing-interface of that family, and bind it. * port set to 0 so OS picks a port number for us. * if it is the ANY address, do not bind. * @param pend: pending tcp structure, for storing the local address choice. * @param w: tcp structure with destination address. * @param s: socket fd. * @return false on error, socket closed. */ static int pick_outgoing_tcp(struct pending_tcp* pend, struct waiting_tcp* w, int s) { struct port_if* pi = NULL; int num; pend->pi = NULL; #ifdef INET6 if(addr_is_ip6(&w->addr, w->addrlen)) num = w->outnet->num_ip6; else #endif num = w->outnet->num_ip4; if(num == 0) { log_err("no TCP outgoing interfaces of family"); log_addr(VERB_OPS, "for addr", &w->addr, w->addrlen); sock_close(s); return 0; } #ifdef INET6 if(addr_is_ip6(&w->addr, w->addrlen)) pi = &w->outnet->ip6_ifs[ub_random_max(w->outnet->rnd, num)]; else #endif pi = &w->outnet->ip4_ifs[ub_random_max(w->outnet->rnd, num)]; log_assert(pi); pend->pi = pi; if(addr_is_any(&pi->addr, pi->addrlen)) { /* binding to the ANY interface is for listening sockets */ return 1; } /* set port to 0 */ if(addr_is_ip6(&pi->addr, pi->addrlen)) ((struct sockaddr_in6*)&pi->addr)->sin6_port = 0; else ((struct sockaddr_in*)&pi->addr)->sin_port = 0; if(bind(s, (struct sockaddr*)&pi->addr, pi->addrlen) != 0) { #ifndef USE_WINSOCK #ifdef EADDRNOTAVAIL if(!(verbosity < 4 && errno == EADDRNOTAVAIL)) #endif #else /* USE_WINSOCK */ if(!(verbosity < 4 && WSAGetLastError() == WSAEADDRNOTAVAIL)) #endif log_err("outgoing tcp: bind: %s", sock_strerror(errno)); sock_close(s); return 0; } log_addr(VERB_ALGO, "tcp bound to src", &pi->addr, pi->addrlen); return 1; } /** get TCP file descriptor for address, returns -1 on failure, * tcp_mss is 0 or maxseg size to set for TCP packets. */ int outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss, int dscp, int nodelay) { int s; int af; char* err; #if defined(SO_REUSEADDR) || defined(IP_BIND_ADDRESS_NO_PORT) \ || defined(TCP_NODELAY) int on = 1; #endif #ifdef INET6 if(addr_is_ip6(addr, addrlen)){ s = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); af = AF_INET6; } else { #else { #endif af = AF_INET; s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); } if(s == -1) { log_err_addr("outgoing tcp: socket", sock_strerror(errno), addr, addrlen); return -1; } #ifdef SO_REUSEADDR if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void*)&on, (socklen_t)sizeof(on)) < 0) { verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(.. SO_REUSEADDR ..) failed"); } #endif err = set_ip_dscp(s, af, dscp); if(err != NULL) { verbose(VERB_ALGO, "outgoing tcp:" "error setting IP DiffServ codepoint on socket"); } if(tcp_mss > 0) { #if defined(IPPROTO_TCP) && defined(TCP_MAXSEG) if(setsockopt(s, IPPROTO_TCP, TCP_MAXSEG, (void*)&tcp_mss, (socklen_t)sizeof(tcp_mss)) < 0) { verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(.. TCP_MAXSEG ..) failed"); } #else verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(TCP_MAXSEG) unsupported"); #endif /* defined(IPPROTO_TCP) && defined(TCP_MAXSEG) */ } #ifdef IP_BIND_ADDRESS_NO_PORT if(setsockopt(s, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, (void*)&on, (socklen_t)sizeof(on)) < 0) { verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(.. IP_BIND_ADDRESS_NO_PORT ..) failed"); } #endif /* IP_BIND_ADDRESS_NO_PORT */ if(nodelay) { #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) if(setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void*)&on, (socklen_t)sizeof(on)) < 0) { verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(.. TCP_NODELAY ..) failed"); } #else verbose(VERB_ALGO, "outgoing tcp:" " setsockopt(.. TCP_NODELAY ..) unsupported"); #endif /* defined(IPPROTO_TCP) && defined(TCP_NODELAY) */ } return s; } /** connect tcp connection to addr, 0 on failure */ int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen) { if(connect(s, (struct sockaddr*)addr, addrlen) == -1) { #ifndef USE_WINSOCK #ifdef EINPROGRESS if(errno != EINPROGRESS) { #endif if(tcp_connect_errno_needs_log( (struct sockaddr*)addr, addrlen)) log_err_addr("outgoing tcp: connect", strerror(errno), addr, addrlen); close(s); return 0; #ifdef EINPROGRESS } #endif #else /* USE_WINSOCK */ if(WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) { closesocket(s); return 0; } #endif } return 1; } /** log reuse item addr and ptr with message */ static void log_reuse_tcp(enum verbosity_value v, const char* msg, struct reuse_tcp* reuse) { uint16_t port; char addrbuf[128]; if(verbosity < v) return; if(!reuse || !reuse->pending || !reuse->pending->c) return; addr_to_str(&reuse->addr, reuse->addrlen, addrbuf, sizeof(addrbuf)); port = ntohs(((struct sockaddr_in*)&reuse->addr)->sin_port); verbose(v, "%s %s#%u fd %d", msg, addrbuf, (unsigned)port, reuse->pending->c->fd); } /** pop the first element from the writewait list */ struct waiting_tcp* reuse_write_wait_pop(struct reuse_tcp* reuse) { struct waiting_tcp* w = reuse->write_wait_first; if(!w) return NULL; log_assert(w->write_wait_queued); log_assert(!w->write_wait_prev); reuse->write_wait_first = w->write_wait_next; if(w->write_wait_next) w->write_wait_next->write_wait_prev = NULL; else reuse->write_wait_last = NULL; w->write_wait_queued = 0; w->write_wait_next = NULL; w->write_wait_prev = NULL; return w; } /** remove the element from the writewait list */ void reuse_write_wait_remove(struct reuse_tcp* reuse, struct waiting_tcp* w) { log_assert(w); log_assert(w->write_wait_queued); if(!w) return; if(!w->write_wait_queued) return; if(w->write_wait_prev) w->write_wait_prev->write_wait_next = w->write_wait_next; else reuse->write_wait_first = w->write_wait_next; log_assert(!w->write_wait_prev || w->write_wait_prev->write_wait_next != w->write_wait_prev); if(w->write_wait_next) w->write_wait_next->write_wait_prev = w->write_wait_prev; else reuse->write_wait_last = w->write_wait_prev; log_assert(!w->write_wait_next || w->write_wait_next->write_wait_prev != w->write_wait_next); w->write_wait_queued = 0; w->write_wait_next = NULL; w->write_wait_prev = NULL; } /** push the element after the last on the writewait list */ void reuse_write_wait_push_back(struct reuse_tcp* reuse, struct waiting_tcp* w) { if(!w) return; log_assert(!w->write_wait_queued); if(reuse->write_wait_last) { reuse->write_wait_last->write_wait_next = w; log_assert(reuse->write_wait_last->write_wait_next != reuse->write_wait_last); w->write_wait_prev = reuse->write_wait_last; } else { reuse->write_wait_first = w; w->write_wait_prev = NULL; } w->write_wait_next = NULL; reuse->write_wait_last = w; w->write_wait_queued = 1; } /** insert element in tree by id */ void reuse_tree_by_id_insert(struct reuse_tcp* reuse, struct waiting_tcp* w) { #ifdef UNBOUND_DEBUG rbnode_type* added; #endif log_assert(w->id_node.key == NULL); w->id_node.key = w; #ifdef UNBOUND_DEBUG added = #else (void) #endif rbtree_insert(&reuse->tree_by_id, &w->id_node); log_assert(added); /* should have been added */ } /** find element in tree by id */ struct waiting_tcp* reuse_tcp_by_id_find(struct reuse_tcp* reuse, uint16_t id) { struct waiting_tcp key_w; rbnode_type* n; memset(&key_w, 0, sizeof(key_w)); key_w.id_node.key = &key_w; key_w.id = id; n = rbtree_search(&reuse->tree_by_id, &key_w); if(!n) return NULL; return (struct waiting_tcp*)n->key; } /** return ID value of rbnode in tree_by_id */ static uint16_t tree_by_id_get_id(rbnode_type* node) { struct waiting_tcp* w = (struct waiting_tcp*)node->key; return w->id; } /** insert into reuse tcp tree and LRU, false on failure (duplicate) */ int reuse_tcp_insert(struct outside_network* outnet, struct pending_tcp* pend_tcp) { log_reuse_tcp(VERB_CLIENT, "reuse_tcp_insert", &pend_tcp->reuse); if(pend_tcp->reuse.item_on_lru_list) { if(!pend_tcp->reuse.node.key) log_err("internal error: reuse_tcp_insert: " "in lru list without key"); return 1; } pend_tcp->reuse.node.key = &pend_tcp->reuse; pend_tcp->reuse.pending = pend_tcp; if(!rbtree_insert(&outnet->tcp_reuse, &pend_tcp->reuse.node)) { /* We are not in the LRU list but we are already in the * tcp_reuse tree, strange. * Continue to add ourselves to the LRU list. */ log_err("internal error: reuse_tcp_insert: in lru list but " "not in the tree"); } /* insert into LRU, first is newest */ pend_tcp->reuse.lru_prev = NULL; if(outnet->tcp_reuse_first) { pend_tcp->reuse.lru_next = outnet->tcp_reuse_first; log_assert(pend_tcp->reuse.lru_next != &pend_tcp->reuse); outnet->tcp_reuse_first->lru_prev = &pend_tcp->reuse; log_assert(outnet->tcp_reuse_first->lru_prev != outnet->tcp_reuse_first); } else { pend_tcp->reuse.lru_next = NULL; outnet->tcp_reuse_last = &pend_tcp->reuse; } outnet->tcp_reuse_first = &pend_tcp->reuse; pend_tcp->reuse.item_on_lru_list = 1; log_assert((!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); log_assert(outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_next && outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_prev); log_assert(outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_next && outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_prev); return 1; } /** find reuse tcp stream to destination for query, or NULL if none */ static struct reuse_tcp* reuse_tcp_find(struct outside_network* outnet, struct sockaddr_storage* addr, socklen_t addrlen, int use_ssl, char* tls_auth_name) { struct waiting_tcp key_w; struct pending_tcp key_p; struct comm_point c; rbnode_type* result = NULL, *prev; verbose(VERB_CLIENT, "reuse_tcp_find"); memset(&key_w, 0, sizeof(key_w)); memset(&key_p, 0, sizeof(key_p)); memset(&c, 0, sizeof(c)); key_p.query = &key_w; key_p.c = &c; key_p.reuse.pending = &key_p; key_p.reuse.node.key = &key_p.reuse; if(use_ssl) { key_p.reuse.is_ssl = 1; key_p.reuse.tls_auth_name = tls_auth_name; } if(addrlen > (socklen_t)sizeof(key_p.reuse.addr)) return NULL; memmove(&key_p.reuse.addr, addr, addrlen); key_p.reuse.addrlen = addrlen; verbose(VERB_CLIENT, "reuse_tcp_find: num reuse streams %u", (unsigned)outnet->tcp_reuse.count); if(outnet->tcp_reuse.root == NULL || outnet->tcp_reuse.root == RBTREE_NULL) return NULL; if(rbtree_find_less_equal(&outnet->tcp_reuse, &key_p.reuse, &result)) { /* exact match */ /* but the key is on stack, and ptr is compared, impossible */ log_assert(&key_p.reuse != (struct reuse_tcp*)result); log_assert(&key_p != ((struct reuse_tcp*)result)->pending); } /* It is possible that we search for something before the first element * in the tree. Replace a null pointer with the first element. */ if (!result) { verbose(VERB_CLIENT, "reuse_tcp_find: taking first"); result = rbtree_first(&outnet->tcp_reuse); } /* not found, return null */ if(!result || result == RBTREE_NULL) return NULL; /* It is possible that we got the previous address, but that the * address we are looking for is in the tree. If the address we got * is less than the address we are looking, then take the next entry. */ if (reuse_cmp_addrportssl(result->key, &key_p.reuse) < 0) { verbose(VERB_CLIENT, "reuse_tcp_find: key too low"); result = rbtree_next(result); } verbose(VERB_CLIENT, "reuse_tcp_find check inexact match"); /* inexact match, find one of possibly several connections to the * same destination address, with the correct port, ssl, and * also less than max number of open queries, or else, fail to open * a new one */ /* rewind to start of sequence of same address,port,ssl */ prev = rbtree_previous(result); while(prev && prev != RBTREE_NULL && reuse_cmp_addrportssl(prev->key, &key_p.reuse) == 0) { result = prev; prev = rbtree_previous(result); } /* loop to find first one that has correct characteristics */ while(result && result != RBTREE_NULL && reuse_cmp_addrportssl(result->key, &key_p.reuse) == 0) { if(((struct reuse_tcp*)result)->tree_by_id.count < outnet->max_reuse_tcp_queries) { /* same address, port, ssl-yes-or-no, and has * space for another query */ return (struct reuse_tcp*)result; } result = rbtree_next(result); } return NULL; } /** use the buffer to setup writing the query */ static void outnet_tcp_take_query_setup(int s, struct pending_tcp* pend, struct waiting_tcp* w) { struct timeval tv; verbose(VERB_CLIENT, "outnet_tcp_take_query_setup: setup packet to write " "len %d timeout %d msec", (int)w->pkt_len, w->timeout); pend->c->tcp_write_pkt = w->pkt; pend->c->tcp_write_pkt_len = w->pkt_len; pend->c->tcp_write_and_read = 1; pend->c->tcp_write_byte_count = 0; pend->c->tcp_is_reading = 0; comm_point_start_listening(pend->c, s, -1); /* set timer on the waiting_tcp entry, this is the write timeout * for the written packet. The timer on pend->c is the timer * for when there is no written packet and we have readtimeouts */ #ifndef S_SPLINT_S tv.tv_sec = w->timeout/1000; tv.tv_usec = (w->timeout%1000)*1000; #endif /* if the waiting_tcp was previously waiting for a buffer in the * outside_network.tcpwaitlist, then the timer is reset now that * we start writing it */ comm_timer_set(w->timer, &tv); } /** use next free buffer to service a tcp query */ static int outnet_tcp_take_into_use(struct waiting_tcp* w) { struct pending_tcp* pend = w->outnet->tcp_free; char* tls_auth_name = NULL; int s; log_assert(pend); log_assert(w->pkt); log_assert(w->pkt_len > 0); log_assert(w->addrlen > 0); pend->c->tcp_do_toggle_rw = 0; pend->c->tcp_do_close = 0; /* Consistency check, if we have ssl_upstream but no sslctx, then * log an error and return failure. */ if (w->ssl_upstream && !w->outnet->sslctx) { log_err("SSL upstream requested but no SSL context"); return 0; } /* open socket */ s = outnet_get_tcp_fd(&w->addr, w->addrlen, w->outnet->tcp_mss, w->outnet->ip_dscp, w->ssl_upstream); if(s == -1) return 0; if(!pick_outgoing_tcp(pend, w, s)) return 0; fd_set_nonblock(s); #ifdef USE_OSX_MSG_FASTOPEN /* API for fast open is different here. We use a connectx() function and then writes can happen as normal even using SSL.*/ /* connectx requires that the len be set in the sockaddr struct*/ struct sockaddr_in *addr_in = (struct sockaddr_in *)&w->addr; addr_in->sin_len = w->addrlen; sa_endpoints_t endpoints; endpoints.sae_srcif = 0; endpoints.sae_srcaddr = NULL; endpoints.sae_srcaddrlen = 0; endpoints.sae_dstaddr = (struct sockaddr *)&w->addr; endpoints.sae_dstaddrlen = w->addrlen; if (connectx(s, &endpoints, SAE_ASSOCID_ANY, CONNECT_DATA_IDEMPOTENT | CONNECT_RESUME_ON_READ_WRITE, NULL, 0, NULL, NULL) == -1) { /* if fails, failover to connect for OSX 10.10 */ #ifdef EINPROGRESS if(errno != EINPROGRESS) { #else if(1) { #endif if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) { #else /* USE_OSX_MSG_FASTOPEN*/ #ifdef USE_MSG_FASTOPEN pend->c->tcp_do_fastopen = 1; /* Only do TFO for TCP in which case no connect() is required here. Don't combine client TFO with SSL, since OpenSSL can't currently support doing a handshake on fd that already isn't connected*/ if (w->outnet->sslctx && w->ssl_upstream) { if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) { #else /* USE_MSG_FASTOPEN*/ if(connect(s, (struct sockaddr*)&w->addr, w->addrlen) == -1) { #endif /* USE_MSG_FASTOPEN*/ #endif /* USE_OSX_MSG_FASTOPEN*/ #ifndef USE_WINSOCK #ifdef EINPROGRESS if(errno != EINPROGRESS) { #else if(1) { #endif if(tcp_connect_errno_needs_log( (struct sockaddr*)&w->addr, w->addrlen)) log_err_addr("outgoing tcp: connect", strerror(errno), &w->addr, w->addrlen); close(s); #else /* USE_WINSOCK */ if(WSAGetLastError() != WSAEINPROGRESS && WSAGetLastError() != WSAEWOULDBLOCK) { closesocket(s); #endif return 0; } } #ifdef USE_MSG_FASTOPEN } #endif /* USE_MSG_FASTOPEN */ #ifdef USE_OSX_MSG_FASTOPEN } } #endif /* USE_OSX_MSG_FASTOPEN */ if(w->outnet->sslctx && w->ssl_upstream) { pend->c->ssl = outgoing_ssl_fd(w->outnet->sslctx, s); if(!pend->c->ssl) { pend->c->fd = s; comm_point_close(pend->c); return 0; } verbose(VERB_ALGO, "the query is using TLS encryption, for %s", (w->tls_auth_name?w->tls_auth_name:"an unauthenticated connection")); #ifdef USE_WINSOCK comm_point_tcp_win_bio_cb(pend->c, pend->c->ssl); #endif pend->c->ssl_shake_state = comm_ssl_shake_write; if(w->tls_auth_name) { /* strdup the auth name, while not linked the list yet, * in case of failure, easy cleanup. */ tls_auth_name = strdup(w->tls_auth_name); if(!tls_auth_name) { log_err("out of memory: alloc tls auth name"); pend->c->fd = s; #ifdef HAVE_SSL SSL_free(pend->c->ssl); #endif pend->c->ssl = NULL; comm_point_close(pend->c); return 0; } } if(!set_auth_name_on_ssl(pend->c->ssl, tls_auth_name, w->outnet->tls_use_sni)) { pend->c->fd = s; #ifdef HAVE_SSL SSL_free(pend->c->ssl); #endif pend->c->ssl = NULL; comm_point_close(pend->c); free(tls_auth_name); return 0; } } w->next_waiting = (void*)pend; w->outnet->num_tcp_outgoing++; w->outnet->tcp_free = pend->next_free; pend->next_free = NULL; pend->query = w; pend->reuse.outnet = w->outnet; pend->c->repinfo.remote_addrlen = w->addrlen; pend->c->tcp_more_read_again = &pend->reuse.cp_more_read_again; pend->c->tcp_more_write_again = &pend->reuse.cp_more_write_again; pend->reuse.cp_more_read_again = 0; pend->reuse.cp_more_write_again = 0; memcpy(&pend->c->repinfo.remote_addr, &w->addr, w->addrlen); pend->reuse.pending = pend; /* Remove from tree in case the is_ssl will be different and causes the * identity of the reuse_tcp to change; could result in nodes not being * deleted from the tree (because the new identity does not match the * previous node) but their ->key would be changed to NULL. */ if(pend->reuse.node.key) reuse_tcp_remove_tree_list(w->outnet, &pend->reuse); if(pend->c->ssl) { pend->reuse.is_ssl = 1; if(pend->reuse.tls_auth_name) free(pend->reuse.tls_auth_name); pend->reuse.tls_auth_name = tls_auth_name; tls_auth_name = NULL; } else { pend->reuse.is_ssl = 0; if(pend->reuse.tls_auth_name) free(pend->reuse.tls_auth_name); pend->reuse.tls_auth_name = NULL; } /* free tls auth name if nonNULL */ free(tls_auth_name); /* insert in reuse by address tree if not already inserted there */ (void)reuse_tcp_insert(w->outnet, pend); reuse_tree_by_id_insert(&pend->reuse, w); outnet_tcp_take_query_setup(s, pend, w); return 1; } /** Touch the lru of a reuse_tcp element, it is in use. * This moves it to the front of the list, where it is not likely to * be closed. Items at the back of the list are closed to make space. */ void reuse_tcp_lru_touch(struct outside_network* outnet, struct reuse_tcp* reuse) { if(!reuse->item_on_lru_list) { log_err("internal error: we need to touch the lru_list but item not in list"); return; /* not on the list, no lru to modify */ } log_assert(reuse->lru_prev || (!reuse->lru_prev && outnet->tcp_reuse_first == reuse)); if(!reuse->lru_prev) return; /* already first in the list */ /* remove at current position */ /* since it is not first, there is a previous element */ reuse->lru_prev->lru_next = reuse->lru_next; log_assert(reuse->lru_prev->lru_next != reuse->lru_prev); if(reuse->lru_next) reuse->lru_next->lru_prev = reuse->lru_prev; else outnet->tcp_reuse_last = reuse->lru_prev; log_assert(!reuse->lru_next || reuse->lru_next->lru_prev != reuse->lru_next); log_assert(outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_next && outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_prev); /* insert at the front */ reuse->lru_prev = NULL; reuse->lru_next = outnet->tcp_reuse_first; if(outnet->tcp_reuse_first) { outnet->tcp_reuse_first->lru_prev = reuse; } log_assert(reuse->lru_next != reuse); /* since it is not first, it is not the only element and * lru_next is thus not NULL and thus reuse is now not the last in * the list, so outnet->tcp_reuse_last does not need to be modified */ outnet->tcp_reuse_first = reuse; log_assert(outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_next && outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_prev); log_assert((!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); } /** Snip the last reuse_tcp element off of the LRU list */ struct reuse_tcp* reuse_tcp_lru_snip(struct outside_network* outnet) { struct reuse_tcp* reuse = outnet->tcp_reuse_last; if(!reuse) return NULL; /* snip off of LRU */ log_assert(reuse->lru_next == NULL); if(reuse->lru_prev) { outnet->tcp_reuse_last = reuse->lru_prev; reuse->lru_prev->lru_next = NULL; } else { outnet->tcp_reuse_last = NULL; outnet->tcp_reuse_first = NULL; } log_assert((!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); reuse->item_on_lru_list = 0; reuse->lru_next = NULL; reuse->lru_prev = NULL; return reuse; } /** remove waiting tcp from the outnet waiting list */ void outnet_waiting_tcp_list_remove(struct outside_network* outnet, struct waiting_tcp* w) { struct waiting_tcp* p = outnet->tcp_wait_first, *prev = NULL; w->on_tcp_waiting_list = 0; while(p) { if(p == w) { /* remove w */ if(prev) prev->next_waiting = w->next_waiting; else outnet->tcp_wait_first = w->next_waiting; if(outnet->tcp_wait_last == w) outnet->tcp_wait_last = prev; w->next_waiting = NULL; return; } prev = p; p = p->next_waiting; } /* outnet_waiting_tcp_list_remove is currently called only with items * that are already in the waiting list. */ log_assert(0); } /** pop the first waiting tcp from the outnet waiting list */ struct waiting_tcp* outnet_waiting_tcp_list_pop(struct outside_network* outnet) { struct waiting_tcp* w = outnet->tcp_wait_first; if(!outnet->tcp_wait_first) return NULL; log_assert(w->on_tcp_waiting_list); outnet->tcp_wait_first = w->next_waiting; if(outnet->tcp_wait_last == w) outnet->tcp_wait_last = NULL; w->on_tcp_waiting_list = 0; w->next_waiting = NULL; return w; } /** add waiting_tcp element to the outnet tcp waiting list */ void outnet_waiting_tcp_list_add(struct outside_network* outnet, struct waiting_tcp* w, int set_timer) { struct timeval tv; log_assert(!w->on_tcp_waiting_list); if(w->on_tcp_waiting_list) return; w->next_waiting = NULL; if(outnet->tcp_wait_last) outnet->tcp_wait_last->next_waiting = w; else outnet->tcp_wait_first = w; outnet->tcp_wait_last = w; w->on_tcp_waiting_list = 1; if(set_timer) { #ifndef S_SPLINT_S tv.tv_sec = w->timeout/1000; tv.tv_usec = (w->timeout%1000)*1000; #endif comm_timer_set(w->timer, &tv); } } /** add waiting_tcp element as first to the outnet tcp waiting list */ void outnet_waiting_tcp_list_add_first(struct outside_network* outnet, struct waiting_tcp* w, int reset_timer) { struct timeval tv; log_assert(!w->on_tcp_waiting_list); if(w->on_tcp_waiting_list) return; w->next_waiting = outnet->tcp_wait_first; log_assert(w->next_waiting != w); if(!outnet->tcp_wait_last) outnet->tcp_wait_last = w; outnet->tcp_wait_first = w; w->on_tcp_waiting_list = 1; if(reset_timer) { #ifndef S_SPLINT_S tv.tv_sec = w->timeout/1000; tv.tv_usec = (w->timeout%1000)*1000; #endif comm_timer_set(w->timer, &tv); } log_assert( (!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); } /** call callback on waiting_tcp, if not NULL */ static void waiting_tcp_callback(struct waiting_tcp* w, struct comm_point* c, int error, struct comm_reply* reply_info) { if(w && w->cb) { fptr_ok(fptr_whitelist_pending_tcp(w->cb)); (void)(*w->cb)(c, w->cb_arg, error, reply_info); } } /** see if buffers can be used to service TCP queries */ static void use_free_buffer(struct outside_network* outnet) { struct waiting_tcp* w; while(outnet->tcp_wait_first && !outnet->want_to_quit) { #ifdef USE_DNSTAP struct pending_tcp* pend_tcp = NULL; #endif struct reuse_tcp* reuse = NULL; w = outnet_waiting_tcp_list_pop(outnet); log_assert( (!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); reuse = reuse_tcp_find(outnet, &w->addr, w->addrlen, w->ssl_upstream, w->tls_auth_name); /* re-select an ID when moving to a new TCP buffer */ w->id = tcp_select_id(outnet, reuse); LDNS_ID_SET(w->pkt, w->id); if(reuse) { log_reuse_tcp(VERB_CLIENT, "use free buffer for waiting tcp: " "found reuse", reuse); #ifdef USE_DNSTAP pend_tcp = reuse->pending; #endif reuse_tcp_lru_touch(outnet, reuse); comm_timer_disable(w->timer); w->next_waiting = (void*)reuse->pending; reuse_tree_by_id_insert(reuse, w); if(reuse->pending->query) { /* on the write wait list */ reuse_write_wait_push_back(reuse, w); } else { /* write straight away */ /* stop the timer on read of the fd */ comm_point_stop_listening(reuse->pending->c); reuse->pending->query = w; outnet_tcp_take_query_setup( reuse->pending->c->fd, reuse->pending, w); } } else if(outnet->tcp_free) { struct pending_tcp* pend = w->outnet->tcp_free; rbtree_init(&pend->reuse.tree_by_id, reuse_id_cmp); pend->reuse.pending = pend; memcpy(&pend->reuse.addr, &w->addr, w->addrlen); pend->reuse.addrlen = w->addrlen; if(!outnet_tcp_take_into_use(w)) { waiting_tcp_callback(w, NULL, NETEVENT_CLOSED, NULL); waiting_tcp_delete(w); #ifdef USE_DNSTAP w = NULL; #endif } #ifdef USE_DNSTAP pend_tcp = pend; #endif } else { /* no reuse and no free buffer, put back at the start */ outnet_waiting_tcp_list_add_first(outnet, w, 0); break; } #ifdef USE_DNSTAP if(outnet->dtenv && pend_tcp && w && w->sq && (outnet->dtenv->log_resolver_query_messages || outnet->dtenv->log_forwarder_query_messages)) { sldns_buffer tmp; sldns_buffer_init_frm_data(&tmp, w->pkt, w->pkt_len); dt_msg_send_outside_query(outnet->dtenv, &w->sq->addr, &pend_tcp->pi->addr, comm_tcp, NULL, w->sq->zone, w->sq->zonelen, &tmp); } #endif } } /** delete element from tree by id */ static void reuse_tree_by_id_delete(struct reuse_tcp* reuse, struct waiting_tcp* w) { #ifdef UNBOUND_DEBUG rbnode_type* rem; #endif log_assert(w->id_node.key != NULL); #ifdef UNBOUND_DEBUG rem = #else (void) #endif rbtree_delete(&reuse->tree_by_id, w); log_assert(rem); /* should have been there */ w->id_node.key = NULL; } /** move writewait list to go for another connection. */ static void reuse_move_writewait_away(struct outside_network* outnet, struct pending_tcp* pend) { /* the writewait list has not been written yet, so if the * stream was closed, they have not actually been failed, only * the queries written. Other queries can get written to another * stream. For upstreams that do not support multiple queries * and answers, the stream can get closed, and then the queries * can get written on a new socket */ struct waiting_tcp* w; if(pend->query && pend->query->error_count == 0 && pend->c->tcp_write_pkt == pend->query->pkt && pend->c->tcp_write_pkt_len == pend->query->pkt_len) { /* since the current query is not written, it can also * move to a free buffer */ if(verbosity >= VERB_CLIENT && pend->query->pkt_len > 12+2+2 && LDNS_QDCOUNT(pend->query->pkt) > 0 && dname_valid(pend->query->pkt+12, pend->query->pkt_len-12)) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(pend->query->pkt+12, buf); verbose(VERB_CLIENT, "reuse_move_writewait_away current %s %d bytes were written", buf, (int)pend->c->tcp_write_byte_count); } pend->c->tcp_write_pkt = NULL; pend->c->tcp_write_pkt_len = 0; pend->c->tcp_write_and_read = 0; pend->reuse.cp_more_read_again = 0; pend->reuse.cp_more_write_again = 0; pend->c->tcp_is_reading = 1; w = pend->query; pend->query = NULL; /* increase error count, so that if the next socket fails too * the server selection is run again with this query failed * and it can select a different server (if possible), or * fail the query */ w->error_count ++; reuse_tree_by_id_delete(&pend->reuse, w); outnet_waiting_tcp_list_add(outnet, w, 1); } while((w = reuse_write_wait_pop(&pend->reuse)) != NULL) { if(verbosity >= VERB_CLIENT && w->pkt_len > 12+2+2 && LDNS_QDCOUNT(w->pkt) > 0 && dname_valid(w->pkt+12, w->pkt_len-12)) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(w->pkt+12, buf); verbose(VERB_CLIENT, "reuse_move_writewait_away item %s", buf); } reuse_tree_by_id_delete(&pend->reuse, w); outnet_waiting_tcp_list_add(outnet, w, 1); } } /** remove reused element from tree and lru list */ void reuse_tcp_remove_tree_list(struct outside_network* outnet, struct reuse_tcp* reuse) { verbose(VERB_CLIENT, "reuse_tcp_remove_tree_list"); if(reuse->node.key) { /* delete it from reuse tree */ if(!rbtree_delete(&outnet->tcp_reuse, reuse)) { /* should not be possible, it should be there */ char buf[256]; addr_to_str(&reuse->addr, reuse->addrlen, buf, sizeof(buf)); log_err("reuse tcp delete: node not present, internal error, %s ssl %d lru %d", buf, reuse->is_ssl, reuse->item_on_lru_list); } reuse->node.key = NULL; /* defend against loops on broken tree by zeroing the * rbnode structure */ memset(&reuse->node, 0, sizeof(reuse->node)); } /* delete from reuse list */ if(reuse->item_on_lru_list) { if(reuse->lru_prev) { /* assert that members of the lru list are waiting * and thus have a pending pointer to the struct */ log_assert(reuse->lru_prev->pending); reuse->lru_prev->lru_next = reuse->lru_next; log_assert(reuse->lru_prev->lru_next != reuse->lru_prev); } else { log_assert(!reuse->lru_next || reuse->lru_next->pending); outnet->tcp_reuse_first = reuse->lru_next; log_assert(!outnet->tcp_reuse_first || (outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_next && outnet->tcp_reuse_first != outnet->tcp_reuse_first->lru_prev)); } if(reuse->lru_next) { /* assert that members of the lru list are waiting * and thus have a pending pointer to the struct */ log_assert(reuse->lru_next->pending); reuse->lru_next->lru_prev = reuse->lru_prev; log_assert(reuse->lru_next->lru_prev != reuse->lru_next); } else { log_assert(!reuse->lru_prev || reuse->lru_prev->pending); outnet->tcp_reuse_last = reuse->lru_prev; log_assert(!outnet->tcp_reuse_last || (outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_next && outnet->tcp_reuse_last != outnet->tcp_reuse_last->lru_prev)); } log_assert((!outnet->tcp_reuse_first && !outnet->tcp_reuse_last) || (outnet->tcp_reuse_first && outnet->tcp_reuse_last)); reuse->item_on_lru_list = 0; reuse->lru_next = NULL; reuse->lru_prev = NULL; } reuse->pending = NULL; } /** helper function that deletes an element from the tree of readwait * elements in tcp reuse structure */ static void reuse_del_readwait_elem(rbnode_type* node, void* ATTR_UNUSED(arg)) { struct waiting_tcp* w = (struct waiting_tcp*)node->key; waiting_tcp_delete(w); } /** delete readwait waiting_tcp elements, deletes the elements in the list */ void reuse_del_readwait(rbtree_type* tree_by_id) { if(tree_by_id->root == NULL || tree_by_id->root == RBTREE_NULL) return; traverse_postorder(tree_by_id, &reuse_del_readwait_elem, NULL); rbtree_init(tree_by_id, reuse_id_cmp); } /** decommission a tcp buffer, closes commpoint and frees waiting_tcp entry */ static void decommission_pending_tcp(struct outside_network* outnet, struct pending_tcp* pend) { verbose(VERB_CLIENT, "decommission_pending_tcp"); /* A certain code path can lead here twice for the same pending_tcp * creating a loop in the free pending_tcp list. */ if(outnet->tcp_free != pend) { pend->next_free = outnet->tcp_free; outnet->tcp_free = pend; } if(pend->reuse.node.key) { /* needs unlink from the reuse tree to get deleted */ reuse_tcp_remove_tree_list(outnet, &pend->reuse); } if(pend->reuse.tls_auth_name) { free(pend->reuse.tls_auth_name); pend->reuse.tls_auth_name = NULL; } /* free SSL structure after remove from outnet tcp reuse tree, * because the c->ssl null or not is used for sorting in the tree */ if(pend->c->ssl) { #ifdef HAVE_SSL SSL_shutdown(pend->c->ssl); SSL_free(pend->c->ssl); pend->c->ssl = NULL; #endif } comm_point_close(pend->c); pend->reuse.cp_more_read_again = 0; pend->reuse.cp_more_write_again = 0; /* unlink the query and writewait list, it is part of the tree * nodes and is deleted */ pend->query = NULL; pend->reuse.write_wait_first = NULL; pend->reuse.write_wait_last = NULL; reuse_del_readwait(&pend->reuse.tree_by_id); } /** perform failure callbacks for waiting queries in reuse read rbtree */ static void reuse_cb_readwait_for_failure(rbtree_type* tree_by_id, int err) { rbnode_type* node; if(tree_by_id->root == NULL || tree_by_id->root == RBTREE_NULL) return; node = rbtree_first(tree_by_id); while(node && node != RBTREE_NULL) { struct waiting_tcp* w = (struct waiting_tcp*)node->key; waiting_tcp_callback(w, NULL, err, NULL); node = rbtree_next(node); } } /** mark the entry for being in the cb_and_decommission stage */ static void mark_for_cb_and_decommission(rbnode_type* node, void* ATTR_UNUSED(arg)) { struct waiting_tcp* w = (struct waiting_tcp*)node->key; /* Mark the waiting_tcp to signal later code (serviced_delete) that * this item is part of the backed up tree_by_id and will be deleted * later. */ w->in_cb_and_decommission = 1; /* Mark the serviced_query for deletion so that later code through * callbacks (iter_clear .. outnet_serviced_query_stop) won't * prematurely delete it. */ if(w->cb) ((struct serviced_query*)w->cb_arg)->to_be_deleted = 1; } /** perform callbacks for failure and also decommission pending tcp. * the callbacks remove references in sq->pending to the waiting_tcp * members of the tree_by_id in the pending tcp. The pending_tcp is * removed before the callbacks, so that the callbacks do not modify * the pending_tcp due to its reference in the outside_network reuse tree */ static void reuse_cb_and_decommission(struct outside_network* outnet, struct pending_tcp* pend, int error) { rbtree_type store; store = pend->reuse.tree_by_id; pend->query = NULL; rbtree_init(&pend->reuse.tree_by_id, reuse_id_cmp); pend->reuse.write_wait_first = NULL; pend->reuse.write_wait_last = NULL; decommission_pending_tcp(outnet, pend); if(store.root != NULL && store.root != RBTREE_NULL) { traverse_postorder(&store, &mark_for_cb_and_decommission, NULL); } reuse_cb_readwait_for_failure(&store, error); reuse_del_readwait(&store); } /** set timeout on tcp fd and setup read event to catch incoming dns msgs */ static void reuse_tcp_setup_timeout(struct pending_tcp* pend_tcp, int tcp_reuse_timeout) { log_reuse_tcp(VERB_CLIENT, "reuse_tcp_setup_timeout", &pend_tcp->reuse); comm_point_start_listening(pend_tcp->c, -1, tcp_reuse_timeout); } /** set timeout on tcp fd and setup read event to catch incoming dns msgs */ static void reuse_tcp_setup_read_and_timeout(struct pending_tcp* pend_tcp, int tcp_reuse_timeout) { log_reuse_tcp(VERB_CLIENT, "reuse_tcp_setup_readtimeout", &pend_tcp->reuse); sldns_buffer_clear(pend_tcp->c->buffer); pend_tcp->c->tcp_is_reading = 1; pend_tcp->c->tcp_byte_count = 0; comm_point_stop_listening(pend_tcp->c); comm_point_start_listening(pend_tcp->c, -1, tcp_reuse_timeout); } int outnet_tcp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info) { struct pending_tcp* pend = (struct pending_tcp*)arg; struct outside_network* outnet = pend->reuse.outnet; struct waiting_tcp* w = NULL; log_assert(pend->reuse.item_on_lru_list && pend->reuse.node.key); verbose(VERB_ALGO, "outnettcp cb"); if(error == NETEVENT_TIMEOUT) { if(pend->c->tcp_write_and_read) { verbose(VERB_QUERY, "outnettcp got tcp timeout " "for read, ignored because write underway"); /* if we are writing, ignore readtimer, wait for write timer * or write is done */ return 0; } else { verbose(VERB_QUERY, "outnettcp got tcp timeout %s", (pend->reuse.tree_by_id.count?"for reading pkt": "for keepalive for reuse")); } /* must be timeout for reading or keepalive reuse, * close it. */ reuse_tcp_remove_tree_list(outnet, &pend->reuse); } else if(error == NETEVENT_PKT_WRITTEN) { /* the packet we want to write has been written. */ verbose(VERB_ALGO, "outnet tcp pkt was written event"); log_assert(c == pend->c); log_assert(pend->query->pkt == pend->c->tcp_write_pkt); log_assert(pend->query->pkt_len == pend->c->tcp_write_pkt_len); pend->c->tcp_write_pkt = NULL; pend->c->tcp_write_pkt_len = 0; /* the pend.query is already in tree_by_id */ log_assert(pend->query->id_node.key); pend->query = NULL; /* setup to write next packet or setup read timeout */ if(pend->reuse.write_wait_first) { verbose(VERB_ALGO, "outnet tcp setup next pkt"); /* we can write it straight away perhaps, set flag * because this callback called after a tcp write * succeeded and likely more buffer space is available * and we can write some more. */ pend->reuse.cp_more_write_again = 1; pend->query = reuse_write_wait_pop(&pend->reuse); comm_point_stop_listening(pend->c); outnet_tcp_take_query_setup(pend->c->fd, pend, pend->query); } else { verbose(VERB_ALGO, "outnet tcp writes done, wait"); pend->c->tcp_write_and_read = 0; pend->reuse.cp_more_read_again = 0; pend->reuse.cp_more_write_again = 0; pend->c->tcp_is_reading = 1; comm_point_stop_listening(pend->c); reuse_tcp_setup_timeout(pend, outnet->tcp_reuse_timeout); } return 0; } else if(error != NETEVENT_NOERROR) { verbose(VERB_QUERY, "outnettcp got tcp error %d", error); reuse_move_writewait_away(outnet, pend); /* pass error below and exit */ } else { /* check ID */ if(sldns_buffer_limit(c->buffer) < sizeof(uint16_t)) { log_addr(VERB_QUERY, "outnettcp: bad ID in reply, too short, from:", &pend->reuse.addr, pend->reuse.addrlen); error = NETEVENT_CLOSED; } else { uint16_t id = LDNS_ID_WIRE(sldns_buffer_begin( c->buffer)); /* find the query the reply is for */ w = reuse_tcp_by_id_find(&pend->reuse, id); /* Make sure that the reply we got is at least for a * sent query with the same ID; the waiting_tcp that * gets a reply is assumed to not be waiting to be * sent. */ if(w && (w->on_tcp_waiting_list || w->write_wait_queued)) w = NULL; } } if(error == NETEVENT_NOERROR && !w) { /* no struct waiting found in tree, no reply to call */ log_addr(VERB_QUERY, "outnettcp: bad ID in reply, from:", &pend->reuse.addr, pend->reuse.addrlen); error = NETEVENT_CLOSED; } if(error == NETEVENT_NOERROR) { /* add to reuse tree so it can be reused, if not a failure. * This is possible if the state machine wants to make a tcp * query again to the same destination. */ if(outnet->tcp_reuse.count < outnet->tcp_reuse_max) { (void)reuse_tcp_insert(outnet, pend); } } if(w) { log_assert(!w->on_tcp_waiting_list); log_assert(!w->write_wait_queued); reuse_tree_by_id_delete(&pend->reuse, w); verbose(VERB_CLIENT, "outnet tcp callback query err %d buflen %d", error, (int)sldns_buffer_limit(c->buffer)); waiting_tcp_callback(w, c, error, reply_info); waiting_tcp_delete(w); } verbose(VERB_CLIENT, "outnet_tcp_cb reuse after cb"); if(error == NETEVENT_NOERROR && pend->reuse.node.key) { verbose(VERB_CLIENT, "outnet_tcp_cb reuse after cb: keep it"); /* it is in the reuse_tcp tree, with other queries, or * on the empty list. do not decommission it */ /* if there are more outstanding queries, we could try to * read again, to see if it is on the input, * because this callback called after a successful read * and there could be more bytes to read on the input */ if(pend->reuse.tree_by_id.count != 0) pend->reuse.cp_more_read_again = 1; reuse_tcp_setup_read_and_timeout(pend, outnet->tcp_reuse_timeout); return 0; } verbose(VERB_CLIENT, "outnet_tcp_cb reuse after cb: decommission it"); /* no queries on it, no space to keep it. or timeout or closed due * to error. Close it */ reuse_cb_and_decommission(outnet, pend, (error==NETEVENT_TIMEOUT? NETEVENT_TIMEOUT:NETEVENT_CLOSED)); use_free_buffer(outnet); return 0; } /** lower use count on pc, see if it can be closed */ static void portcomm_loweruse(struct outside_network* outnet, struct port_comm* pc) { struct port_if* pif; pc->num_outstanding--; if(pc->num_outstanding > 0) { return; } /* close it and replace in unused list */ verbose(VERB_ALGO, "close of port %d", pc->number); comm_point_close(pc->cp); pif = pc->pif; log_assert(pif->inuse > 0); #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION pif->avail_ports[pif->avail_total - pif->inuse] = pc->number; #endif pif->inuse--; pif->out[pc->index] = pif->out[pif->inuse]; pif->out[pc->index]->index = pc->index; pc->next = outnet->unused_fds; outnet->unused_fds = pc; } /** try to send waiting UDP queries */ static void outnet_send_wait_udp(struct outside_network* outnet) { struct pending* pend; /* process waiting queries */ while(outnet->udp_wait_first && outnet->unused_fds && !outnet->want_to_quit) { pend = outnet->udp_wait_first; outnet->udp_wait_first = pend->next_waiting; if(!pend->next_waiting) outnet->udp_wait_last = NULL; sldns_buffer_clear(outnet->udp_buff); sldns_buffer_write(outnet->udp_buff, pend->pkt, pend->pkt_len); sldns_buffer_flip(outnet->udp_buff); free(pend->pkt); /* freeing now makes get_mem correct */ pend->pkt = NULL; pend->pkt_len = 0; log_assert(!pend->sq->busy); pend->sq->busy = 1; if(!randomize_and_send_udp(pend, outnet->udp_buff, pend->timeout)) { /* callback error on pending */ if(pend->cb) { fptr_ok(fptr_whitelist_pending_udp(pend->cb)); (void)(*pend->cb)(outnet->unused_fds->cp, pend->cb_arg, NETEVENT_CLOSED, NULL); } pending_delete(outnet, pend); } else { pend->sq->busy = 0; } } } int outnet_udp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info) { struct outside_network* outnet = (struct outside_network*)arg; struct pending key; struct pending* p; verbose(VERB_ALGO, "answer cb"); if(error != NETEVENT_NOERROR) { verbose(VERB_QUERY, "outnetudp got udp error %d", error); return 0; } if(sldns_buffer_limit(c->buffer) < LDNS_HEADER_SIZE) { verbose(VERB_QUERY, "outnetudp udp too short"); return 0; } log_assert(reply_info); /* setup lookup key */ key.id = (unsigned)LDNS_ID_WIRE(sldns_buffer_begin(c->buffer)); memcpy(&key.addr, &reply_info->remote_addr, reply_info->remote_addrlen); key.addrlen = reply_info->remote_addrlen; verbose(VERB_ALGO, "Incoming reply id = %4.4x", key.id); log_addr(VERB_ALGO, "Incoming reply addr =", &reply_info->remote_addr, reply_info->remote_addrlen); /* find it, see if this thing is a valid query response */ verbose(VERB_ALGO, "lookup size is %d entries", (int)outnet->pending->count); p = (struct pending*)rbtree_search(outnet->pending, &key); if(!p) { verbose(VERB_QUERY, "received unwanted or unsolicited udp reply dropped."); log_buf(VERB_ALGO, "dropped message", c->buffer); outnet->unwanted_replies++; if(outnet->unwanted_threshold && ++outnet->unwanted_total >= outnet->unwanted_threshold) { log_warn("unwanted reply total reached threshold (%u)" " you may be under attack." " defensive action: clearing the cache", (unsigned)outnet->unwanted_threshold); fptr_ok(fptr_whitelist_alloc_cleanup( outnet->unwanted_action)); (*outnet->unwanted_action)(outnet->unwanted_param); outnet->unwanted_total = 0; } return 0; } verbose(VERB_ALGO, "received udp reply."); log_buf(VERB_ALGO, "udp message", c->buffer); if(p->pc->cp != c) { verbose(VERB_QUERY, "received reply id,addr on wrong port. " "dropped."); outnet->unwanted_replies++; if(outnet->unwanted_threshold && ++outnet->unwanted_total >= outnet->unwanted_threshold) { log_warn("unwanted reply total reached threshold (%u)" " you may be under attack." " defensive action: clearing the cache", (unsigned)outnet->unwanted_threshold); fptr_ok(fptr_whitelist_alloc_cleanup( outnet->unwanted_action)); (*outnet->unwanted_action)(outnet->unwanted_param); outnet->unwanted_total = 0; } return 0; } comm_timer_disable(p->timer); verbose(VERB_ALGO, "outnet handle udp reply"); /* delete from tree first in case callback creates a retry */ (void)rbtree_delete(outnet->pending, p->node.key); if(p->cb) { fptr_ok(fptr_whitelist_pending_udp(p->cb)); (void)(*p->cb)(p->pc->cp, p->cb_arg, NETEVENT_NOERROR, reply_info); } portcomm_loweruse(outnet, p->pc); pending_delete(NULL, p); outnet_send_wait_udp(outnet); return 0; } /** calculate number of ip4 and ip6 interfaces*/ static void calc_num46(char** ifs, int num_ifs, int do_ip4, int do_ip6, int* num_ip4, int* num_ip6) { int i; *num_ip4 = 0; *num_ip6 = 0; if(num_ifs <= 0) { if(do_ip4) *num_ip4 = 1; if(do_ip6) *num_ip6 = 1; return; } for(i=0; ioutnet; verbose(VERB_ALGO, "timeout udp with delay"); portcomm_loweruse(outnet, p->pc); pending_delete(outnet, p); outnet_send_wait_udp(outnet); } void pending_udp_timer_cb(void *arg) { struct pending* p = (struct pending*)arg; struct outside_network* outnet = p->outnet; /* it timed out */ verbose(VERB_ALGO, "timeout udp"); if(p->cb) { fptr_ok(fptr_whitelist_pending_udp(p->cb)); (void)(*p->cb)(p->pc->cp, p->cb_arg, NETEVENT_TIMEOUT, NULL); } /* if delayclose, keep port open for a longer time. * But if the udpwaitlist exists, then we are struggling to * keep up with demand for sockets, so do not wait, but service * the customer (customer service more important than portICMPs) */ if(outnet->delayclose && !outnet->udp_wait_first) { p->cb = NULL; p->timer->callback = &pending_udp_timer_delay_cb; comm_timer_set(p->timer, &outnet->delay_tv); return; } portcomm_loweruse(outnet, p->pc); pending_delete(outnet, p); outnet_send_wait_udp(outnet); } /** create pending_tcp buffers */ static int create_pending_tcp(struct outside_network* outnet, size_t bufsize) { size_t i; if(outnet->num_tcp == 0) return 1; /* no tcp needed, nothing to do */ if(!(outnet->tcp_conns = (struct pending_tcp **)calloc( outnet->num_tcp, sizeof(struct pending_tcp*)))) return 0; for(i=0; inum_tcp; i++) { if(!(outnet->tcp_conns[i] = (struct pending_tcp*)calloc(1, sizeof(struct pending_tcp)))) return 0; outnet->tcp_conns[i]->next_free = outnet->tcp_free; outnet->tcp_free = outnet->tcp_conns[i]; outnet->tcp_conns[i]->c = comm_point_create_tcp_out( outnet->base, bufsize, outnet_tcp_cb, outnet->tcp_conns[i]); if(!outnet->tcp_conns[i]->c) return 0; } return 1; } /** setup an outgoing interface, ready address */ static int setup_if(struct port_if* pif, const char* addrstr, int* avail, int numavail, size_t numfd) { #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION pif->avail_total = numavail; pif->avail_ports = (int*)memdup(avail, (size_t)numavail*sizeof(int)); if(!pif->avail_ports) return 0; #endif if(!ipstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen) && !netblockstrtoaddr(addrstr, UNBOUND_DNS_PORT, &pif->addr, &pif->addrlen, &pif->pfxlen)) return 0; pif->maxout = (int)numfd; pif->inuse = 0; pif->out = (struct port_comm**)calloc(numfd, sizeof(struct port_comm*)); if(!pif->out) return 0; return 1; } struct outside_network* outside_network_create(struct comm_base *base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra, struct ub_randstate* rnd, int use_caps_for_id, int* availports, int numavailports, size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, int tls_use_sni, struct dt_env* dtenv, int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout, int tcp_auth_query_timeout) { struct outside_network* outnet = (struct outside_network*) calloc(1, sizeof(struct outside_network)); size_t k; if(!outnet) { log_err("malloc failed"); return NULL; } comm_base_timept(base, &outnet->now_secs, &outnet->now_tv); outnet->base = base; outnet->num_tcp = num_tcp; outnet->max_reuse_tcp_queries = max_reuse_tcp_queries; outnet->tcp_reuse_timeout= tcp_reuse_timeout; outnet->tcp_auth_query_timeout = tcp_auth_query_timeout; outnet->num_tcp_outgoing = 0; outnet->num_udp_outgoing = 0; outnet->infra = infra; outnet->rnd = rnd; outnet->sslctx = sslctx; outnet->tls_use_sni = tls_use_sni; #ifdef USE_DNSTAP outnet->dtenv = dtenv; #else (void)dtenv; #endif outnet->svcd_overhead = 0; outnet->want_to_quit = 0; outnet->unwanted_threshold = unwanted_threshold; outnet->unwanted_action = unwanted_action; outnet->unwanted_param = unwanted_param; outnet->use_caps_for_id = use_caps_for_id; outnet->do_udp = do_udp; outnet->tcp_mss = tcp_mss; outnet->ip_dscp = dscp; #ifndef S_SPLINT_S if(delayclose) { outnet->delayclose = 1; outnet->delay_tv.tv_sec = delayclose/1000; outnet->delay_tv.tv_usec = (delayclose%1000)*1000; } #endif if(udp_connect) { outnet->udp_connect = 1; } if(numavailports == 0 || num_ports == 0) { log_err("no outgoing ports available"); outside_network_delete(outnet); return NULL; } #ifndef INET6 do_ip6 = 0; #endif calc_num46(ifs, num_ifs, do_ip4, do_ip6, &outnet->num_ip4, &outnet->num_ip6); if(outnet->num_ip4 != 0) { if(!(outnet->ip4_ifs = (struct port_if*)calloc( (size_t)outnet->num_ip4, sizeof(struct port_if)))) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } } if(outnet->num_ip6 != 0) { if(!(outnet->ip6_ifs = (struct port_if*)calloc( (size_t)outnet->num_ip6, sizeof(struct port_if)))) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } } if( !(outnet->udp_buff = sldns_buffer_new(bufsize)) || !(outnet->pending = rbtree_create(pending_cmp)) || !(outnet->serviced = rbtree_create(serviced_cmp)) || !create_pending_tcp(outnet, bufsize)) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } rbtree_init(&outnet->tcp_reuse, reuse_cmp); outnet->tcp_reuse_max = num_tcp; /* allocate commpoints */ for(k=0; kcp = comm_point_create_udp(outnet->base, -1, outnet->udp_buff, 0, outnet_udp_cb, outnet, NULL); if(!pc->cp) { log_err("malloc failed"); free(pc); outside_network_delete(outnet); return NULL; } pc->next = outnet->unused_fds; outnet->unused_fds = pc; } /* allocate interfaces */ if(num_ifs == 0) { if(do_ip4 && !setup_if(&outnet->ip4_ifs[0], "0.0.0.0", availports, numavailports, num_ports)) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } if(do_ip6 && !setup_if(&outnet->ip6_ifs[0], "::", availports, numavailports, num_ports)) { log_err("malloc failed"); outside_network_delete(outnet); return NULL; } } else { size_t done_4 = 0, done_6 = 0; int i; for(i=0; iip6_ifs[done_6], ifs[i], availports, numavailports, num_ports)){ log_err("malloc failed"); outside_network_delete(outnet); return NULL; } done_6++; } if(!str_is_ip6(ifs[i]) && do_ip4) { if(!setup_if(&outnet->ip4_ifs[done_4], ifs[i], availports, numavailports, num_ports)){ log_err("malloc failed"); outside_network_delete(outnet); return NULL; } done_4++; } } } return outnet; } /** helper pending delete */ static void pending_node_del(rbnode_type* node, void* arg) { struct pending* pend = (struct pending*)node; struct outside_network* outnet = (struct outside_network*)arg; pending_delete(outnet, pend); } /** helper serviced delete */ static void serviced_node_del(rbnode_type* node, void* ATTR_UNUSED(arg)) { struct serviced_query* sq = (struct serviced_query*)node; alloc_reg_release(sq->alloc, sq->region); if(sq->timer) comm_timer_delete(sq->timer); free(sq); } void outside_network_quit_prepare(struct outside_network* outnet) { if(!outnet) return; /* prevent queued items from being sent */ outnet->want_to_quit = 1; } void outside_network_delete(struct outside_network* outnet) { if(!outnet) return; outnet->want_to_quit = 1; /* check every element, since we can be called on malloc error */ if(outnet->pending) { /* free pending elements, but do no unlink from tree. */ traverse_postorder(outnet->pending, pending_node_del, NULL); free(outnet->pending); } if(outnet->serviced) { traverse_postorder(outnet->serviced, serviced_node_del, NULL); free(outnet->serviced); } if(outnet->udp_buff) sldns_buffer_free(outnet->udp_buff); if(outnet->unused_fds) { struct port_comm* p = outnet->unused_fds, *np; while(p) { np = p->next; comm_point_delete(p->cp); free(p); p = np; } outnet->unused_fds = NULL; } if(outnet->ip4_ifs) { int i, k; for(i=0; inum_ip4; i++) { for(k=0; kip4_ifs[i].inuse; k++) { struct port_comm* pc = outnet->ip4_ifs[i]. out[k]; comm_point_delete(pc->cp); free(pc); } #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION free(outnet->ip4_ifs[i].avail_ports); #endif free(outnet->ip4_ifs[i].out); } free(outnet->ip4_ifs); } if(outnet->ip6_ifs) { int i, k; for(i=0; inum_ip6; i++) { for(k=0; kip6_ifs[i].inuse; k++) { struct port_comm* pc = outnet->ip6_ifs[i]. out[k]; comm_point_delete(pc->cp); free(pc); } #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION free(outnet->ip6_ifs[i].avail_ports); #endif free(outnet->ip6_ifs[i].out); } free(outnet->ip6_ifs); } if(outnet->tcp_conns) { size_t i; for(i=0; inum_tcp; i++) if(outnet->tcp_conns[i]) { struct pending_tcp* pend; pend = outnet->tcp_conns[i]; if(pend->reuse.item_on_lru_list) { /* delete waiting_tcp elements that * the tcp conn is working on */ decommission_pending_tcp(outnet, pend); } if(pend->reuse.tls_auth_name) { free(pend->reuse.tls_auth_name); pend->reuse.tls_auth_name = NULL; } comm_point_delete(outnet->tcp_conns[i]->c); free(outnet->tcp_conns[i]); outnet->tcp_conns[i] = NULL; } free(outnet->tcp_conns); outnet->tcp_conns = NULL; } if(outnet->tcp_wait_first) { struct waiting_tcp* p = outnet->tcp_wait_first, *np; while(p) { np = p->next_waiting; waiting_tcp_delete(p); p = np; } } /* was allocated in struct pending that was deleted above */ rbtree_init(&outnet->tcp_reuse, reuse_cmp); outnet->tcp_reuse_first = NULL; outnet->tcp_reuse_last = NULL; if(outnet->udp_wait_first) { struct pending* p = outnet->udp_wait_first, *np; while(p) { np = p->next_waiting; pending_delete(NULL, p); p = np; } } free(outnet); } void pending_delete(struct outside_network* outnet, struct pending* p) { if(!p) return; if(outnet && outnet->udp_wait_first && (p->next_waiting || p == outnet->udp_wait_last) ) { /* delete from waiting list, if it is in the waiting list */ struct pending* prev = NULL, *x = outnet->udp_wait_first; while(x && x != p) { prev = x; x = x->next_waiting; } if(x) { log_assert(x == p); if(prev) prev->next_waiting = p->next_waiting; else outnet->udp_wait_first = p->next_waiting; if(outnet->udp_wait_last == p) outnet->udp_wait_last = prev; } } if(outnet) { (void)rbtree_delete(outnet->pending, p->node.key); } if(p->timer) comm_timer_delete(p->timer); free(p->pkt); free(p); } static void sai6_putrandom(struct sockaddr_in6 *sa, int pfxlen, struct ub_randstate *rnd) { int i, last; if(!(pfxlen > 0 && pfxlen < 128)) return; for(i = 0; i < (128 - pfxlen) / 8; i++) { sa->sin6_addr.s6_addr[15-i] = (uint8_t)ub_random_max(rnd, 256); } last = pfxlen & 7; if(last != 0) { sa->sin6_addr.s6_addr[15-i] |= ((0xFF >> last) & ub_random_max(rnd, 256)); } } /** * Try to open a UDP socket for outgoing communication. * Sets sockets options as needed. * @param addr: socket address. * @param addrlen: length of address. * @param pfxlen: length of network prefix (for address randomisation). * @param port: port override for addr. * @param inuse: if -1 is returned, this bool means the port was in use. * @param rnd: random state (for address randomisation). * @param dscp: DSCP to use. * @return fd or -1 */ static int udp_sockport(struct sockaddr_storage* addr, socklen_t addrlen, int pfxlen, int port, int* inuse, struct ub_randstate* rnd, int dscp) { int fd, noproto; if(addr_is_ip6(addr, addrlen)) { int freebind = 0; struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr; sa.sin6_port = (in_port_t)htons((uint16_t)port); sa.sin6_flowinfo = 0; sa.sin6_scope_id = 0; if(pfxlen != 0) { freebind = 1; sai6_putrandom(&sa, pfxlen, rnd); } fd = create_udp_sock(AF_INET6, SOCK_DGRAM, (struct sockaddr*)&sa, addrlen, 1, inuse, &noproto, 0, 0, 0, NULL, 0, freebind, 0, dscp); } else { struct sockaddr_in* sa = (struct sockaddr_in*)addr; sa->sin_port = (in_port_t)htons((uint16_t)port); fd = create_udp_sock(AF_INET, SOCK_DGRAM, (struct sockaddr*)addr, addrlen, 1, inuse, &noproto, 0, 0, 0, NULL, 0, 0, 0, dscp); } return fd; } /** Select random ID */ static int select_id(struct outside_network* outnet, struct pending* pend, sldns_buffer* packet) { int id_tries = 0; pend->id = GET_RANDOM_ID(outnet->rnd); LDNS_ID_SET(sldns_buffer_begin(packet), pend->id); /* insert in tree */ pend->node.key = pend; while(!rbtree_insert(outnet->pending, &pend->node)) { /* change ID to avoid collision */ pend->id = GET_RANDOM_ID(outnet->rnd); LDNS_ID_SET(sldns_buffer_begin(packet), pend->id); id_tries++; if(id_tries == MAX_ID_RETRY) { pend->id=99999; /* non existent ID */ log_err("failed to generate unique ID, drop msg"); return 0; } } verbose(VERB_ALGO, "inserted new pending reply id=%4.4x", pend->id); return 1; } /** return true is UDP connect error needs to be logged */ static int udp_connect_needs_log(int err, struct sockaddr_storage* addr, socklen_t addrlen) { switch(err) { case ECONNREFUSED: # ifdef ENETUNREACH case ENETUNREACH: # endif # ifdef EHOSTDOWN case EHOSTDOWN: # endif # ifdef EHOSTUNREACH case EHOSTUNREACH: # endif # ifdef ENETDOWN case ENETDOWN: # endif # ifdef EADDRNOTAVAIL case EADDRNOTAVAIL: # endif case EPERM: case EACCES: if(verbosity >= VERB_ALGO) return 1; return 0; case EINVAL: /* Stop 'Invalid argument for fe80::/10' addresses appearing * in the logs, at low verbosity. They cannot be sent to. */ if(addr_is_ip6linklocal(addr, addrlen)) { if(verbosity >= VERB_ALGO) return 1; return 0; } break; default: break; } return 1; } /** Select random interface and port */ static int select_ifport(struct outside_network* outnet, struct pending* pend, int num_if, struct port_if* ifs) { int my_if, my_port, fd, portno, inuse, tries=0; struct port_if* pif; /* randomly select interface and port */ if(num_if == 0) { verbose(VERB_QUERY, "Need to send query but have no " "outgoing interfaces of that family"); return 0; } log_assert(outnet->unused_fds); tries = 0; while(1) { my_if = ub_random_max(outnet->rnd, num_if); pif = &ifs[my_if]; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION if(outnet->udp_connect) { /* if we connect() we cannot reuse fds for a port */ if(pif->inuse >= pif->avail_total) { tries++; if(tries < MAX_PORT_RETRY) continue; log_err("failed to find an open port, drop msg"); return 0; } my_port = pif->inuse + ub_random_max(outnet->rnd, pif->avail_total - pif->inuse); } else { my_port = ub_random_max(outnet->rnd, pif->avail_total); if(my_port < pif->inuse) { /* port already open */ pend->pc = pif->out[my_port]; verbose(VERB_ALGO, "using UDP if=%d port=%d", my_if, pend->pc->number); break; } } /* try to open new port, if fails, loop to try again */ log_assert(pif->inuse < pif->maxout); portno = pif->avail_ports[my_port - pif->inuse]; #else my_port = portno = 0; #endif fd = udp_sockport(&pif->addr, pif->addrlen, pif->pfxlen, portno, &inuse, outnet->rnd, outnet->ip_dscp); if(fd == -1 && !inuse) { /* nonrecoverable error making socket */ return 0; } if(fd != -1) { verbose(VERB_ALGO, "opened UDP if=%d port=%d", my_if, portno); if(outnet->udp_connect) { /* connect() to the destination */ if(connect(fd, (struct sockaddr*)&pend->addr, pend->addrlen) < 0) { if(udp_connect_needs_log(errno, &pend->addr, pend->addrlen)) { log_err_addr("udp connect failed", strerror(errno), &pend->addr, pend->addrlen); } sock_close(fd); return 0; } } /* grab fd */ pend->pc = outnet->unused_fds; outnet->unused_fds = pend->pc->next; /* setup portcomm */ pend->pc->next = NULL; pend->pc->number = portno; pend->pc->pif = pif; pend->pc->index = pif->inuse; pend->pc->num_outstanding = 0; comm_point_start_listening(pend->pc->cp, fd, -1); /* grab port in interface */ pif->out[pif->inuse] = pend->pc; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION pif->avail_ports[my_port - pif->inuse] = pif->avail_ports[pif->avail_total-pif->inuse-1]; #endif pif->inuse++; break; } /* failed, already in use */ verbose(VERB_QUERY, "port %d in use, trying another", portno); tries++; if(tries == MAX_PORT_RETRY) { log_err("failed to find an open port, drop msg"); return 0; } } log_assert(pend->pc); pend->pc->num_outstanding++; return 1; } static int randomize_and_send_udp(struct pending* pend, sldns_buffer* packet, int timeout) { struct timeval tv; struct outside_network* outnet = pend->sq->outnet; /* select id */ if(!select_id(outnet, pend, packet)) { return 0; } /* select src_if, port */ if(addr_is_ip6(&pend->addr, pend->addrlen)) { if(!select_ifport(outnet, pend, outnet->num_ip6, outnet->ip6_ifs)) return 0; } else { if(!select_ifport(outnet, pend, outnet->num_ip4, outnet->ip4_ifs)) return 0; } log_assert(pend->pc && pend->pc->cp); /* send it over the commlink */ if(!comm_point_send_udp_msg(pend->pc->cp, packet, (struct sockaddr*)&pend->addr, pend->addrlen, outnet->udp_connect)) { portcomm_loweruse(outnet, pend->pc); return 0; } outnet->num_udp_outgoing++; /* system calls to set timeout after sending UDP to make roundtrip smaller. */ #ifndef S_SPLINT_S tv.tv_sec = timeout/1000; tv.tv_usec = (timeout%1000)*1000; #endif comm_timer_set(pend->timer, &tv); #ifdef USE_DNSTAP /* * sending src (local service)/dst (upstream) addresses over DNSTAP * There are no chances to get the src (local service) addr if unbound * is not configured with specific outgoing IP-addresses. So we will * pass 0.0.0.0 (::) to argument for * dt_msg_send_outside_query()/dt_msg_send_outside_response() calls. */ if(outnet->dtenv && (outnet->dtenv->log_resolver_query_messages || outnet->dtenv->log_forwarder_query_messages)) { log_addr(VERB_ALGO, "from local addr", &pend->pc->pif->addr, pend->pc->pif->addrlen); log_addr(VERB_ALGO, "request to upstream", &pend->addr, pend->addrlen); dt_msg_send_outside_query(outnet->dtenv, &pend->addr, &pend->pc->pif->addr, comm_udp, NULL, pend->sq->zone, pend->sq->zonelen, packet); } #endif return 1; } struct pending* pending_udp_query(struct serviced_query* sq, struct sldns_buffer* packet, int timeout, comm_point_callback_type* cb, void* cb_arg) { struct pending* pend = (struct pending*)calloc(1, sizeof(*pend)); if(!pend) return NULL; pend->outnet = sq->outnet; pend->sq = sq; pend->addrlen = sq->addrlen; memmove(&pend->addr, &sq->addr, sq->addrlen); pend->cb = cb; pend->cb_arg = cb_arg; pend->node.key = pend; pend->timer = comm_timer_create(sq->outnet->base, pending_udp_timer_cb, pend); if(!pend->timer) { free(pend); return NULL; } if(sq->outnet->unused_fds == NULL) { /* no unused fd, cannot create a new port (randomly) */ verbose(VERB_ALGO, "no fds available, udp query waiting"); pend->timeout = timeout; pend->pkt_len = sldns_buffer_limit(packet); pend->pkt = (uint8_t*)memdup(sldns_buffer_begin(packet), pend->pkt_len); if(!pend->pkt) { comm_timer_delete(pend->timer); free(pend); return NULL; } /* put at end of waiting list */ if(sq->outnet->udp_wait_last) sq->outnet->udp_wait_last->next_waiting = pend; else sq->outnet->udp_wait_first = pend; sq->outnet->udp_wait_last = pend; return pend; } log_assert(!sq->busy); sq->busy = 1; if(!randomize_and_send_udp(pend, packet, timeout)) { pending_delete(sq->outnet, pend); return NULL; } sq->busy = 0; return pend; } void outnet_tcptimer(void* arg) { struct waiting_tcp* w = (struct waiting_tcp*)arg; struct outside_network* outnet = w->outnet; verbose(VERB_CLIENT, "outnet_tcptimer"); if(w->on_tcp_waiting_list) { /* it is on the waiting list */ outnet_waiting_tcp_list_remove(outnet, w); waiting_tcp_callback(w, NULL, NETEVENT_TIMEOUT, NULL); waiting_tcp_delete(w); } else { /* it was in use */ struct pending_tcp* pend=(struct pending_tcp*)w->next_waiting; reuse_cb_and_decommission(outnet, pend, NETEVENT_TIMEOUT); } use_free_buffer(outnet); } /** close the oldest reuse_tcp connection to make a fd and struct pend * available for a new stream connection */ static void reuse_tcp_close_oldest(struct outside_network* outnet) { struct reuse_tcp* reuse; verbose(VERB_CLIENT, "reuse_tcp_close_oldest"); reuse = reuse_tcp_lru_snip(outnet); if(!reuse) return; /* free up */ reuse_cb_and_decommission(outnet, reuse->pending, NETEVENT_CLOSED); } static uint16_t tcp_select_id(struct outside_network* outnet, struct reuse_tcp* reuse) { if(reuse) return reuse_tcp_select_id(reuse, outnet); return GET_RANDOM_ID(outnet->rnd); } /** find spare ID value for reuse tcp stream. That is random and also does * not collide with an existing query ID that is in use or waiting */ uint16_t reuse_tcp_select_id(struct reuse_tcp* reuse, struct outside_network* outnet) { uint16_t id = 0, curid, nextid; const int try_random = 2000; int i; unsigned select, count, space; rbnode_type* node; /* make really sure the tree is not empty */ if(reuse->tree_by_id.count == 0) { id = GET_RANDOM_ID(outnet->rnd); return id; } /* try to find random empty spots by picking them */ for(i = 0; irnd); if(!reuse_tcp_by_id_find(reuse, id)) { return id; } } /* equally pick a random unused element from the tree that is * not in use. Pick a the n-th index of an unused number, * then loop over the empty spaces in the tree and find it */ log_assert(reuse->tree_by_id.count < 0xffff); select = ub_random_max(outnet->rnd, 0xffff - reuse->tree_by_id.count); /* select value now in 0 .. num free - 1 */ count = 0; /* number of free spaces passed by */ node = rbtree_first(&reuse->tree_by_id); log_assert(node && node != RBTREE_NULL); /* tree not empty */ /* see if select is before first node */ if(select < (unsigned)tree_by_id_get_id(node)) return select; count += tree_by_id_get_id(node); /* perhaps select is between nodes */ while(node && node != RBTREE_NULL) { rbnode_type* next = rbtree_next(node); if(next && next != RBTREE_NULL) { curid = tree_by_id_get_id(node); nextid = tree_by_id_get_id(next); log_assert(curid < nextid); if(curid != 0xffff && curid + 1 < nextid) { /* space between nodes */ space = nextid - curid - 1; log_assert(select >= count); if(select < count + space) { /* here it is */ return curid + 1 + (select - count); } count += space; } } node = next; } /* select is after the last node */ /* count is the number of free positions before the nodes in the * tree */ node = rbtree_last(&reuse->tree_by_id); log_assert(node && node != RBTREE_NULL); /* tree not empty */ curid = tree_by_id_get_id(node); log_assert(count + (0xffff-curid) + reuse->tree_by_id.count == 0xffff); return curid + 1 + (select - count); } struct waiting_tcp* pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, int timeout, comm_point_callback_type* callback, void* callback_arg) { struct pending_tcp* pend = sq->outnet->tcp_free; struct reuse_tcp* reuse = NULL; struct waiting_tcp* w; verbose(VERB_CLIENT, "pending_tcp_query"); if(sldns_buffer_limit(packet) < sizeof(uint16_t)) { verbose(VERB_ALGO, "pending tcp query with too short buffer < 2"); return NULL; } /* find out if a reused stream to the target exists */ /* if so, take it into use */ reuse = reuse_tcp_find(sq->outnet, &sq->addr, sq->addrlen, sq->ssl_upstream, sq->tls_auth_name); if(reuse) { log_reuse_tcp(VERB_CLIENT, "pending_tcp_query: found reuse", reuse); log_assert(reuse->pending); pend = reuse->pending; reuse_tcp_lru_touch(sq->outnet, reuse); } log_assert(!reuse || (reuse && pend)); /* if !pend but we have reuse streams, close a reuse stream * to be able to open a new one to this target, no use waiting * to reuse a file descriptor while another query needs to use * that buffer and file descriptor now. */ if(!pend) { reuse_tcp_close_oldest(sq->outnet); pend = sq->outnet->tcp_free; log_assert(!reuse || (pend == reuse->pending)); } /* allocate space to store query */ w = (struct waiting_tcp*)malloc(sizeof(struct waiting_tcp) + sldns_buffer_limit(packet)); if(!w) { return NULL; } if(!(w->timer = comm_timer_create(sq->outnet->base, outnet_tcptimer, w))) { free(w); return NULL; } w->pkt = (uint8_t*)w + sizeof(struct waiting_tcp); w->pkt_len = sldns_buffer_limit(packet); memmove(w->pkt, sldns_buffer_begin(packet), w->pkt_len); w->id = tcp_select_id(sq->outnet, reuse); LDNS_ID_SET(w->pkt, w->id); memcpy(&w->addr, &sq->addr, sq->addrlen); w->addrlen = sq->addrlen; w->outnet = sq->outnet; w->on_tcp_waiting_list = 0; w->next_waiting = NULL; w->cb = callback; w->cb_arg = callback_arg; w->ssl_upstream = sq->ssl_upstream; w->tls_auth_name = sq->tls_auth_name; w->timeout = timeout; w->id_node.key = NULL; w->write_wait_prev = NULL; w->write_wait_next = NULL; w->write_wait_queued = 0; w->error_count = 0; #ifdef USE_DNSTAP w->sq = NULL; #endif w->in_cb_and_decommission = 0; if(pend) { /* we have a buffer available right now */ if(reuse) { log_assert(reuse == &pend->reuse); /* reuse existing fd, write query and continue */ /* store query in tree by id */ verbose(VERB_CLIENT, "pending_tcp_query: reuse, store"); w->next_waiting = (void*)pend; reuse_tree_by_id_insert(&pend->reuse, w); /* can we write right now? */ if(pend->query == NULL) { /* write straight away */ /* stop the timer on read of the fd */ comm_point_stop_listening(pend->c); pend->query = w; outnet_tcp_take_query_setup(pend->c->fd, pend, w); } else { /* put it in the waiting list for * this stream */ reuse_write_wait_push_back(&pend->reuse, w); } } else { /* create new fd and connect to addr, setup to * write query */ verbose(VERB_CLIENT, "pending_tcp_query: new fd, connect"); rbtree_init(&pend->reuse.tree_by_id, reuse_id_cmp); pend->reuse.pending = pend; memcpy(&pend->reuse.addr, &sq->addr, sq->addrlen); pend->reuse.addrlen = sq->addrlen; if(!outnet_tcp_take_into_use(w)) { waiting_tcp_delete(w); return NULL; } } #ifdef USE_DNSTAP if(sq->outnet->dtenv && (sq->outnet->dtenv->log_resolver_query_messages || sq->outnet->dtenv->log_forwarder_query_messages)) { /* use w->pkt, because it has the ID value */ sldns_buffer tmp; sldns_buffer_init_frm_data(&tmp, w->pkt, w->pkt_len); dt_msg_send_outside_query(sq->outnet->dtenv, &sq->addr, &pend->pi->addr, comm_tcp, NULL, sq->zone, sq->zonelen, &tmp); } #endif } else { /* queue up */ /* waiting for a buffer on the outside network buffer wait * list */ verbose(VERB_CLIENT, "pending_tcp_query: queue to wait"); #ifdef USE_DNSTAP w->sq = sq; #endif outnet_waiting_tcp_list_add(sq->outnet, w, 1); } return w; } /** create query for serviced queries */ static void serviced_gen_query(sldns_buffer* buff, uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, uint16_t flags) { sldns_buffer_clear(buff); /* skip id */ sldns_buffer_write_u16(buff, flags); sldns_buffer_write_u16(buff, 1); /* qdcount */ sldns_buffer_write_u16(buff, 0); /* ancount */ sldns_buffer_write_u16(buff, 0); /* nscount */ sldns_buffer_write_u16(buff, 0); /* arcount */ sldns_buffer_write(buff, qname, qnamelen); sldns_buffer_write_u16(buff, qtype); sldns_buffer_write_u16(buff, qclass); sldns_buffer_flip(buff); } /** lookup serviced query in serviced query rbtree */ static struct serviced_query* lookup_serviced(struct outside_network* outnet, sldns_buffer* buff, int dnssec, struct sockaddr_storage* addr, socklen_t addrlen, struct edns_option* opt_list) { struct serviced_query key; key.node.key = &key; key.qbuf = sldns_buffer_begin(buff); key.qbuflen = sldns_buffer_limit(buff); key.dnssec = dnssec; memcpy(&key.addr, addr, addrlen); key.addrlen = addrlen; key.outnet = outnet; key.opt_list = opt_list; return (struct serviced_query*)rbtree_search(outnet->serviced, &key); } void serviced_timer_cb(void* arg) { struct serviced_query* sq = (struct serviced_query*)arg; struct outside_network* outnet = sq->outnet; verbose(VERB_ALGO, "serviced send timer"); /* By the time this cb is called, if we don't have any registered * callbacks for this serviced_query anymore; do not send. */ if(!sq->cblist) goto delete; /* perform first network action */ if(outnet->do_udp && !(sq->tcp_upstream || sq->ssl_upstream)) { if(!serviced_udp_send(sq, outnet->udp_buff)) goto delete; } else { if(!serviced_tcp_send(sq, outnet->udp_buff)) goto delete; } /* Maybe by this time we don't have callbacks attached anymore. Don't * proactively try to delete; let it run and maybe another callback * will get attached by the time we get an answer. */ return; delete: serviced_callbacks(sq, NETEVENT_CLOSED, NULL, NULL); } /** Create new serviced entry */ static struct serviced_query* serviced_create(struct outside_network* outnet, sldns_buffer* buff, int dnssec, int want_dnssec, int nocaps, int tcp_upstream, int ssl_upstream, char* tls_auth_name, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int qtype, struct edns_option* opt_list, size_t pad_queries_block_size, struct alloc_cache* alloc, struct regional* region) { struct serviced_query* sq = (struct serviced_query*)malloc(sizeof(*sq)); struct timeval t; #ifdef UNBOUND_DEBUG rbnode_type* ins; #endif if(!sq) { alloc_reg_release(alloc, region); return NULL; } sq->node.key = sq; sq->alloc = alloc; sq->region = region; sq->qbuf = regional_alloc_init(region, sldns_buffer_begin(buff), sldns_buffer_limit(buff)); if(!sq->qbuf) { alloc_reg_release(alloc, region); free(sq); return NULL; } sq->qbuflen = sldns_buffer_limit(buff); sq->zone = regional_alloc_init(region, zone, zonelen); if(!sq->zone) { alloc_reg_release(alloc, region); free(sq); return NULL; } sq->zonelen = zonelen; sq->qtype = qtype; sq->dnssec = dnssec; sq->want_dnssec = want_dnssec; sq->nocaps = nocaps; sq->tcp_upstream = tcp_upstream; sq->ssl_upstream = ssl_upstream; if(tls_auth_name) { sq->tls_auth_name = regional_strdup(region, tls_auth_name); if(!sq->tls_auth_name) { alloc_reg_release(alloc, region); free(sq); return NULL; } } else { sq->tls_auth_name = NULL; } memcpy(&sq->addr, addr, addrlen); sq->addrlen = addrlen; sq->opt_list = opt_list; sq->busy = 0; sq->timer = comm_timer_create(outnet->base, serviced_timer_cb, sq); if(!sq->timer) { alloc_reg_release(alloc, region); free(sq); return NULL; } memset(&t, 0, sizeof(t)); comm_timer_set(sq->timer, &t); sq->outnet = outnet; sq->cblist = NULL; sq->pending = NULL; sq->status = serviced_initial; sq->retry = 0; sq->to_be_deleted = 0; sq->padding_block_size = pad_queries_block_size; #ifdef UNBOUND_DEBUG ins = #else (void) #endif rbtree_insert(outnet->serviced, &sq->node); log_assert(ins != NULL); /* must not be already present */ return sq; } /** reuse tcp stream, remove serviced query from stream, * return true if the stream is kept, false if it is to be closed */ static int reuse_tcp_remove_serviced_keep(struct waiting_tcp* w, struct serviced_query* sq) { struct pending_tcp* pend_tcp = (struct pending_tcp*)w->next_waiting; verbose(VERB_CLIENT, "reuse_tcp_remove_serviced_keep"); /* remove the callback. let query continue to write to not cancel * the stream itself. also keep it as an entry in the tree_by_id, * in case the answer returns (that we no longer want), but we cannot * pick the same ID number meanwhile */ w->cb = NULL; /* see if can be entered in reuse tree * for that the FD has to be non-1 */ if(pend_tcp->c->fd == -1) { verbose(VERB_CLIENT, "reuse_tcp_remove_serviced_keep: -1 fd"); return 0; } /* if in tree and used by other queries */ if(pend_tcp->reuse.node.key) { verbose(VERB_CLIENT, "reuse_tcp_remove_serviced_keep: in use by other queries"); /* do not reset the keepalive timer, for that * we'd need traffic, and this is where the serviced is * removed due to state machine internal reasons, * eg. iterator no longer interested in this query */ return 1; } /* if still open and want to keep it open */ if(pend_tcp->c->fd != -1 && sq->outnet->tcp_reuse.count < sq->outnet->tcp_reuse_max) { verbose(VERB_CLIENT, "reuse_tcp_remove_serviced_keep: keep open"); /* set a keepalive timer on it */ if(!reuse_tcp_insert(sq->outnet, pend_tcp)) { return 0; } reuse_tcp_setup_timeout(pend_tcp, sq->outnet->tcp_reuse_timeout); return 1; } return 0; } /** cleanup serviced query entry */ static void serviced_delete(struct serviced_query* sq) { verbose(VERB_CLIENT, "serviced_delete"); if(sq->pending) { /* clear up the pending query */ if(sq->status == serviced_query_UDP_EDNS || sq->status == serviced_query_UDP || sq->status == serviced_query_UDP_EDNS_FRAG || sq->status == serviced_query_UDP_EDNS_fallback) { struct pending* p = (struct pending*)sq->pending; verbose(VERB_CLIENT, "serviced_delete: UDP"); if(p->pc) portcomm_loweruse(sq->outnet, p->pc); pending_delete(sq->outnet, p); /* this call can cause reentrant calls back into the * mesh */ outnet_send_wait_udp(sq->outnet); } else { struct waiting_tcp* w = (struct waiting_tcp*) sq->pending; verbose(VERB_CLIENT, "serviced_delete: TCP"); log_assert(!(w->write_wait_queued && w->on_tcp_waiting_list)); /* if on stream-write-waiting list then * remove from waiting list and waiting_tcp_delete */ if(w->write_wait_queued) { struct pending_tcp* pend = (struct pending_tcp*)w->next_waiting; verbose(VERB_CLIENT, "serviced_delete: writewait"); if(!w->in_cb_and_decommission) reuse_tree_by_id_delete(&pend->reuse, w); reuse_write_wait_remove(&pend->reuse, w); if(!w->in_cb_and_decommission) waiting_tcp_delete(w); } else if(!w->on_tcp_waiting_list) { struct pending_tcp* pend = (struct pending_tcp*)w->next_waiting; verbose(VERB_CLIENT, "serviced_delete: tcpreusekeep"); /* w needs to stay on tree_by_id to not assign * the same ID; remove the callback since its * serviced_query will be gone. */ w->cb = NULL; if(!reuse_tcp_remove_serviced_keep(w, sq)) { if(!w->in_cb_and_decommission) reuse_cb_and_decommission(sq->outnet, pend, NETEVENT_CLOSED); use_free_buffer(sq->outnet); } sq->pending = NULL; } else { verbose(VERB_CLIENT, "serviced_delete: tcpwait"); outnet_waiting_tcp_list_remove(sq->outnet, w); if(!w->in_cb_and_decommission) waiting_tcp_delete(w); } } } /* does not delete from tree, caller has to do that */ serviced_node_del(&sq->node, NULL); } /** perturb a dname capitalization randomly */ static void serviced_perturb_qname(struct ub_randstate* rnd, uint8_t* qbuf, size_t len) { uint8_t lablen; uint8_t* d = qbuf + 10; long int random = 0; int bits = 0; log_assert(len >= 10 + 5 /* offset qname, root, qtype, qclass */); (void)len; lablen = *d++; while(lablen) { while(lablen--) { /* only perturb A-Z, a-z */ if(isalpha((unsigned char)*d)) { /* get a random bit */ if(bits == 0) { random = ub_random(rnd); bits = 30; } if((random & 0x1)) { *d = (uint8_t)toupper((unsigned char)*d); } else { *d = (uint8_t)tolower((unsigned char)*d); } random >>= 1; bits--; } d++; } lablen = *d++; } if(verbosity >= VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(qbuf+10, buf); verbose(VERB_ALGO, "qname perturbed to %s", buf); } } static uint16_t serviced_query_udp_size(struct serviced_query* sq, enum serviced_query_status status) { uint16_t udp_size; if(status == serviced_query_UDP_EDNS_FRAG) { if(addr_is_ip6(&sq->addr, sq->addrlen)) { if(EDNS_FRAG_SIZE_IP6 < EDNS_ADVERTISED_SIZE) udp_size = EDNS_FRAG_SIZE_IP6; else udp_size = EDNS_ADVERTISED_SIZE; } else { if(EDNS_FRAG_SIZE_IP4 < EDNS_ADVERTISED_SIZE) udp_size = EDNS_FRAG_SIZE_IP4; else udp_size = EDNS_ADVERTISED_SIZE; } } else { udp_size = EDNS_ADVERTISED_SIZE; } return udp_size; } /** put serviced query into a buffer */ static void serviced_encode(struct serviced_query* sq, sldns_buffer* buff, int with_edns) { /* if we are using 0x20 bits for ID randomness, perturb them */ if(sq->outnet->use_caps_for_id && !sq->nocaps) { serviced_perturb_qname(sq->outnet->rnd, sq->qbuf, sq->qbuflen); } /* generate query */ sldns_buffer_clear(buff); sldns_buffer_write_u16(buff, 0); /* id placeholder */ sldns_buffer_write(buff, sq->qbuf, sq->qbuflen); sldns_buffer_flip(buff); if(with_edns) { /* add edns section */ struct edns_data edns; struct edns_option padding_option; edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = EDNS_ADVERTISED_VERSION; edns.opt_list_in = NULL; edns.opt_list_out = sq->opt_list; edns.opt_list_inplace_cb_out = NULL; edns.udp_size = serviced_query_udp_size(sq, sq->status); edns.bits = 0; if((sq->dnssec & EDNS_DO)) edns.bits = EDNS_DO; if((sq->dnssec & BIT_CD)) LDNS_CD_SET(sldns_buffer_begin(buff)); if (sq->ssl_upstream && sq->padding_block_size) { padding_option.opt_code = LDNS_EDNS_PADDING; padding_option.opt_len = 0; padding_option.opt_data = NULL; padding_option.next = edns.opt_list_out; edns.opt_list_out = &padding_option; edns.padding_block_size = sq->padding_block_size; } attach_edns_record(buff, &edns); } } /** * Perform serviced query UDP sending operation. * Sends UDP with EDNS, unless infra host marked non EDNS. * @param sq: query to send. * @param buff: buffer scratch space. * @return 0 on error. */ static int serviced_udp_send(struct serviced_query* sq, sldns_buffer* buff) { int rtt, vs; uint8_t edns_lame_known; time_t now = *sq->outnet->now_secs; if(!infra_host(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, now, &vs, &edns_lame_known, &rtt)) return 0; sq->last_rtt = rtt; verbose(VERB_ALGO, "EDNS lookup known=%d vs=%d", edns_lame_known, vs); if(sq->status == serviced_initial) { if(vs != -1) { sq->status = serviced_query_UDP_EDNS; } else { sq->status = serviced_query_UDP; } } serviced_encode(sq, buff, (sq->status == serviced_query_UDP_EDNS) || (sq->status == serviced_query_UDP_EDNS_FRAG)); sq->last_sent_time = *sq->outnet->now_tv; sq->edns_lame_known = (int)edns_lame_known; verbose(VERB_ALGO, "serviced query UDP timeout=%d msec", rtt); sq->pending = pending_udp_query(sq, buff, rtt, serviced_udp_callback, sq); if(!sq->pending) return 0; return 1; } /** check that perturbed qname is identical */ static int serviced_check_qname(sldns_buffer* pkt, uint8_t* qbuf, size_t qbuflen) { uint8_t* d1 = sldns_buffer_begin(pkt)+12; uint8_t* d2 = qbuf+10; uint8_t len1, len2; int count = 0; if(sldns_buffer_limit(pkt) < 12+1+4) /* packet too small for qname */ return 0; log_assert(qbuflen >= 15 /* 10 header, root, type, class */); len1 = *d1++; len2 = *d2++; while(len1 != 0 || len2 != 0) { if(LABEL_IS_PTR(len1)) { /* check if we can read *d1 with compression ptr rest */ if(d1 >= sldns_buffer_at(pkt, sldns_buffer_limit(pkt))) return 0; d1 = sldns_buffer_begin(pkt)+PTR_OFFSET(len1, *d1); /* check if we can read the destination *d1 */ if(d1 >= sldns_buffer_at(pkt, sldns_buffer_limit(pkt))) return 0; len1 = *d1++; if(count++ > MAX_COMPRESS_PTRS) return 0; continue; } if(d2 > qbuf+qbuflen) return 0; if(len1 != len2) return 0; if(len1 > LDNS_MAX_LABELLEN) return 0; /* check len1 + 1(next length) are okay to read */ if(d1+len1 >= sldns_buffer_at(pkt, sldns_buffer_limit(pkt))) return 0; log_assert(len1 <= LDNS_MAX_LABELLEN); log_assert(len2 <= LDNS_MAX_LABELLEN); log_assert(len1 == len2 && len1 != 0); /* compare the labels - bitwise identical */ if(memcmp(d1, d2, len1) != 0) return 0; d1 += len1; d2 += len2; len1 = *d1++; len2 = *d2++; } return 1; } /** call the callbacks for a serviced query */ static void serviced_callbacks(struct serviced_query* sq, int error, struct comm_point* c, struct comm_reply* rep) { struct service_callback* p; int dobackup = (sq->cblist && sq->cblist->next); /* >1 cb*/ uint8_t *backup_p = NULL; size_t backlen = 0; #ifdef UNBOUND_DEBUG rbnode_type* rem = #else (void) #endif /* remove from tree, and schedule for deletion, so that callbacks * can safely deregister themselves and even create new serviced * queries that are identical to this one. */ rbtree_delete(sq->outnet->serviced, sq); log_assert(rem); /* should have been present */ sq->to_be_deleted = 1; verbose(VERB_ALGO, "svcd callbacks start"); if(sq->outnet->use_caps_for_id && error == NETEVENT_NOERROR && c && !sq->nocaps && sq->qtype != LDNS_RR_TYPE_PTR) { /* for type PTR do not check perturbed name in answer, * compatibility with cisco dns guard boxes that mess up * reverse queries 0x20 contents */ /* noerror and nxdomain must have a qname in reply */ if(sldns_buffer_read_u16_at(c->buffer, 4) == 0 && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOERROR || LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NXDOMAIN)) { verbose(VERB_DETAIL, "no qname in reply to check 0x20ID"); log_addr(VERB_DETAIL, "from server", &sq->addr, sq->addrlen); log_buf(VERB_DETAIL, "for packet", c->buffer); error = NETEVENT_CLOSED; c = NULL; } else if(sldns_buffer_read_u16_at(c->buffer, 4) > 0 && !serviced_check_qname(c->buffer, sq->qbuf, sq->qbuflen)) { verbose(VERB_DETAIL, "wrong 0x20-ID in reply qname"); log_addr(VERB_DETAIL, "from server", &sq->addr, sq->addrlen); log_buf(VERB_DETAIL, "for packet", c->buffer); error = NETEVENT_CAPSFAIL; /* and cleanup too */ pkt_dname_tolower(c->buffer, sldns_buffer_at(c->buffer, 12)); } else { verbose(VERB_ALGO, "good 0x20-ID in reply qname"); /* cleanup caps, prettier cache contents. */ pkt_dname_tolower(c->buffer, sldns_buffer_at(c->buffer, 12)); } } if(dobackup && c) { /* make a backup of the query, since the querystate processing * may send outgoing queries that overwrite the buffer. * use secondary buffer to store the query. * This is a data copy, but faster than packet to server */ backlen = sldns_buffer_limit(c->buffer); backup_p = regional_alloc_init(sq->region, sldns_buffer_begin(c->buffer), backlen); if(!backup_p) { log_err("malloc failure in serviced query callbacks"); error = NETEVENT_CLOSED; c = NULL; } sq->outnet->svcd_overhead = backlen; } /* test the actual sq->cblist, because the next elem could be deleted*/ while((p=sq->cblist) != NULL) { sq->cblist = p->next; /* remove this element */ if(dobackup && c) { sldns_buffer_clear(c->buffer); sldns_buffer_write(c->buffer, backup_p, backlen); sldns_buffer_flip(c->buffer); } fptr_ok(fptr_whitelist_serviced_query(p->cb)); (void)(*p->cb)(c, p->cb_arg, error, rep); } if(backup_p) { sq->outnet->svcd_overhead = 0; } verbose(VERB_ALGO, "svcd callbacks end"); log_assert(sq->cblist == NULL); serviced_delete(sq); } int serviced_tcp_callback(struct comm_point* c, void* arg, int error, struct comm_reply* rep) { struct serviced_query* sq = (struct serviced_query*)arg; struct comm_reply r2; #ifdef USE_DNSTAP struct waiting_tcp* w = (struct waiting_tcp*)sq->pending; struct pending_tcp* pend_tcp = NULL; struct port_if* pi = NULL; if(w && !w->on_tcp_waiting_list && w->next_waiting) { pend_tcp = (struct pending_tcp*)w->next_waiting; pi = pend_tcp->pi; } #endif sq->pending = NULL; /* removed after this callback */ if(error != NETEVENT_NOERROR) log_addr(VERB_QUERY, "tcp error for address", &sq->addr, sq->addrlen); if(error==NETEVENT_NOERROR) infra_update_tcp_works(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen); #ifdef USE_DNSTAP /* * sending src (local service)/dst (upstream) addresses over DNSTAP */ if(error==NETEVENT_NOERROR && pi && sq->outnet->dtenv && (sq->outnet->dtenv->log_resolver_response_messages || sq->outnet->dtenv->log_forwarder_response_messages)) { log_addr(VERB_ALGO, "response from upstream", &sq->addr, sq->addrlen); log_addr(VERB_ALGO, "to local addr", &pi->addr, pi->addrlen); dt_msg_send_outside_response(sq->outnet->dtenv, &sq->addr, &pi->addr, c->type, c->ssl, sq->zone, sq->zonelen, sq->qbuf, sq->qbuflen, &sq->last_sent_time, sq->outnet->now_tv, c->buffer); } #endif if(error==NETEVENT_NOERROR && sq->status == serviced_query_TCP_EDNS && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_FORMERR || LDNS_RCODE_WIRE(sldns_buffer_begin( c->buffer)) == LDNS_RCODE_NOTIMPL) ) { /* attempt to fallback to nonEDNS */ sq->status = serviced_query_TCP_EDNS_fallback; serviced_tcp_initiate(sq, c->buffer); return 0; } else if(error==NETEVENT_NOERROR && sq->status == serviced_query_TCP_EDNS_fallback && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOERROR || LDNS_RCODE_WIRE( sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NXDOMAIN || LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_YXDOMAIN)) { /* the fallback produced a result that looks promising, note * that this server should be approached without EDNS */ /* only store noEDNS in cache if domain is noDNSSEC */ if(!sq->want_dnssec) if(!infra_edns_update(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, -1, *sq->outnet->now_secs)) log_err("Out of memory caching no edns for host"); sq->status = serviced_query_TCP; } if(sq->tcp_upstream || sq->ssl_upstream) { struct timeval now = *sq->outnet->now_tv; if(error!=NETEVENT_NOERROR) { if(!infra_rtt_update(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, sq->qtype, -1, sq->last_rtt, (time_t)now.tv_sec)) log_err("out of memory in TCP exponential backoff."); } else if(now.tv_sec > sq->last_sent_time.tv_sec || (now.tv_sec == sq->last_sent_time.tv_sec && now.tv_usec > sq->last_sent_time.tv_usec)) { /* convert from microseconds to milliseconds */ int roundtime = ((int)(now.tv_sec - sq->last_sent_time.tv_sec))*1000 + ((int)now.tv_usec - (int)sq->last_sent_time.tv_usec)/1000; verbose(VERB_ALGO, "measured TCP-time at %d msec", roundtime); log_assert(roundtime >= 0); /* only store if less then AUTH_TIMEOUT seconds, it could be * huge due to system-hibernated and we woke up */ if(roundtime < 60000) { if(!infra_rtt_update(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, sq->qtype, roundtime, sq->last_rtt, (time_t)now.tv_sec)) log_err("out of memory noting rtt."); } } } /* insert address into reply info */ if(!rep) { /* create one if there isn't (on errors) */ rep = &r2; r2.c = c; } memcpy(&rep->remote_addr, &sq->addr, sq->addrlen); rep->remote_addrlen = sq->addrlen; serviced_callbacks(sq, error, c, rep); return 0; } static void serviced_tcp_initiate(struct serviced_query* sq, sldns_buffer* buff) { verbose(VERB_ALGO, "initiate TCP query %s", sq->status==serviced_query_TCP_EDNS?"EDNS":""); serviced_encode(sq, buff, sq->status == serviced_query_TCP_EDNS); sq->last_sent_time = *sq->outnet->now_tv; log_assert(!sq->busy); sq->busy = 1; sq->pending = pending_tcp_query(sq, buff, sq->outnet->tcp_auth_query_timeout, serviced_tcp_callback, sq); sq->busy = 0; if(!sq->pending) { /* delete from tree so that a retry by above layer does not * clash with this entry */ verbose(VERB_ALGO, "serviced_tcp_initiate: failed to send tcp query"); serviced_callbacks(sq, NETEVENT_CLOSED, NULL, NULL); } } /** Send serviced query over TCP return false on initial failure */ static int serviced_tcp_send(struct serviced_query* sq, sldns_buffer* buff) { int vs, rtt, timeout; uint8_t edns_lame_known; if(!infra_host(sq->outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, *sq->outnet->now_secs, &vs, &edns_lame_known, &rtt)) return 0; sq->last_rtt = rtt; if(vs != -1) sq->status = serviced_query_TCP_EDNS; else sq->status = serviced_query_TCP; serviced_encode(sq, buff, sq->status == serviced_query_TCP_EDNS); sq->last_sent_time = *sq->outnet->now_tv; if(sq->tcp_upstream || sq->ssl_upstream) { timeout = rtt; if(rtt >= UNKNOWN_SERVER_NICENESS && rtt < sq->outnet->tcp_auth_query_timeout) timeout = sq->outnet->tcp_auth_query_timeout; } else { timeout = sq->outnet->tcp_auth_query_timeout; } log_assert(!sq->busy); sq->busy = 1; sq->pending = pending_tcp_query(sq, buff, timeout, serviced_tcp_callback, sq); sq->busy = 0; return sq->pending != NULL; } /* see if packet is edns malformed; got zeroes at start. * This is from servers that return malformed packets to EDNS0 queries, * but they return good packets for nonEDNS0 queries. * We try to detect their output; without resorting to a full parse or * check for too many bytes after the end of the packet. */ static int packet_edns_malformed(struct sldns_buffer* buf, int qtype) { size_t len; if(sldns_buffer_limit(buf) < LDNS_HEADER_SIZE) return 1; /* malformed */ /* they have NOERROR rcode, 1 answer. */ if(LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) != LDNS_RCODE_NOERROR) return 0; /* one query (to skip) and answer records */ if(LDNS_QDCOUNT(sldns_buffer_begin(buf)) != 1 || LDNS_ANCOUNT(sldns_buffer_begin(buf)) == 0) return 0; /* skip qname */ len = dname_valid(sldns_buffer_at(buf, LDNS_HEADER_SIZE), sldns_buffer_limit(buf)-LDNS_HEADER_SIZE); if(len == 0) return 0; if(len == 1 && qtype == 0) return 0; /* we asked for '.' and type 0 */ /* and then 4 bytes (type and class of query) */ if(sldns_buffer_limit(buf) < LDNS_HEADER_SIZE + len + 4 + 3) return 0; /* and start with 11 zeroes as the answer RR */ /* so check the qtype of the answer record, qname=0, type=0 */ if(sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[0] == 0 && sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[1] == 0 && sldns_buffer_at(buf, LDNS_HEADER_SIZE+len+4)[2] == 0) return 1; return 0; } int serviced_udp_callback(struct comm_point* c, void* arg, int error, struct comm_reply* rep) { struct serviced_query* sq = (struct serviced_query*)arg; struct outside_network* outnet = sq->outnet; struct timeval now = *sq->outnet->now_tv; #ifdef USE_DNSTAP struct pending* p = (struct pending*)sq->pending; #endif sq->pending = NULL; /* removed after callback */ if(error == NETEVENT_TIMEOUT) { if(sq->status == serviced_query_UDP_EDNS && sq->last_rtt < 5000 && (serviced_query_udp_size(sq, serviced_query_UDP_EDNS_FRAG) < serviced_query_udp_size(sq, serviced_query_UDP_EDNS))) { /* fallback to 1480/1280 */ sq->status = serviced_query_UDP_EDNS_FRAG; log_name_addr(VERB_ALGO, "try edns1xx0", sq->qbuf+10, &sq->addr, sq->addrlen); if(!serviced_udp_send(sq, c->buffer)) { serviced_callbacks(sq, NETEVENT_CLOSED, c, rep); } return 0; } if(sq->status == serviced_query_UDP_EDNS_FRAG) { /* fragmentation size did not fix it */ sq->status = serviced_query_UDP_EDNS; } sq->retry++; if(!infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, sq->qtype, -1, sq->last_rtt, (time_t)now.tv_sec)) log_err("out of memory in UDP exponential backoff"); if(sq->retry < OUTBOUND_UDP_RETRY) { log_name_addr(VERB_ALGO, "retry query", sq->qbuf+10, &sq->addr, sq->addrlen); if(!serviced_udp_send(sq, c->buffer)) { serviced_callbacks(sq, NETEVENT_CLOSED, c, rep); } return 0; } } if(error != NETEVENT_NOERROR) { /* udp returns error (due to no ID or interface available) */ serviced_callbacks(sq, error, c, rep); return 0; } #ifdef USE_DNSTAP /* * sending src (local service)/dst (upstream) addresses over DNSTAP */ if(error == NETEVENT_NOERROR && outnet->dtenv && p->pc && (outnet->dtenv->log_resolver_response_messages || outnet->dtenv->log_forwarder_response_messages)) { log_addr(VERB_ALGO, "response from upstream", &sq->addr, sq->addrlen); log_addr(VERB_ALGO, "to local addr", &p->pc->pif->addr, p->pc->pif->addrlen); dt_msg_send_outside_response(outnet->dtenv, &sq->addr, &p->pc->pif->addr, c->type, c->ssl, sq->zone, sq->zonelen, sq->qbuf, sq->qbuflen, &sq->last_sent_time, sq->outnet->now_tv, c->buffer); } #endif if( (sq->status == serviced_query_UDP_EDNS ||sq->status == serviced_query_UDP_EDNS_FRAG) && (LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_FORMERR || LDNS_RCODE_WIRE( sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOTIMPL || packet_edns_malformed(c->buffer, sq->qtype) )) { /* try to get an answer by falling back without EDNS */ verbose(VERB_ALGO, "serviced query: attempt without EDNS"); sq->status = serviced_query_UDP_EDNS_fallback; sq->retry = 0; if(!serviced_udp_send(sq, c->buffer)) { serviced_callbacks(sq, NETEVENT_CLOSED, c, rep); } return 0; } else if(sq->status == serviced_query_UDP_EDNS && !sq->edns_lame_known) { /* now we know that edns queries received answers store that */ log_addr(VERB_ALGO, "serviced query: EDNS works for", &sq->addr, sq->addrlen); if(!infra_edns_update(outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, 0, (time_t)now.tv_sec)) { log_err("Out of memory caching edns works"); } sq->edns_lame_known = 1; } else if(sq->status == serviced_query_UDP_EDNS_fallback && !sq->edns_lame_known && (LDNS_RCODE_WIRE( sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NOERROR || LDNS_RCODE_WIRE(sldns_buffer_begin(c->buffer)) == LDNS_RCODE_NXDOMAIN || LDNS_RCODE_WIRE(sldns_buffer_begin( c->buffer)) == LDNS_RCODE_YXDOMAIN)) { /* the fallback produced a result that looks promising, note * that this server should be approached without EDNS */ /* only store noEDNS in cache if domain is noDNSSEC */ if(!sq->want_dnssec) { log_addr(VERB_ALGO, "serviced query: EDNS fails for", &sq->addr, sq->addrlen); if(!infra_edns_update(outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, -1, (time_t)now.tv_sec)) { log_err("Out of memory caching no edns for host"); } } else { log_addr(VERB_ALGO, "serviced query: EDNS fails, but " "not stored because need DNSSEC for", &sq->addr, sq->addrlen); } sq->status = serviced_query_UDP; } if(now.tv_sec > sq->last_sent_time.tv_sec || (now.tv_sec == sq->last_sent_time.tv_sec && now.tv_usec > sq->last_sent_time.tv_usec)) { /* convert from microseconds to milliseconds */ int roundtime = ((int)(now.tv_sec - sq->last_sent_time.tv_sec))*1000 + ((int)now.tv_usec - (int)sq->last_sent_time.tv_usec)/1000; verbose(VERB_ALGO, "measured roundtrip at %d msec", roundtime); log_assert(roundtime >= 0); /* in case the system hibernated, do not enter a huge value, * above this value gives trouble with server selection */ if(roundtime < 60000) { if(!infra_rtt_update(outnet->infra, &sq->addr, sq->addrlen, sq->zone, sq->zonelen, sq->qtype, roundtime, sq->last_rtt, (time_t)now.tv_sec)) log_err("out of memory noting rtt."); } } /* perform TC flag check and TCP fallback after updating our * cache entries for EDNS status and RTT times */ if(LDNS_TC_WIRE(sldns_buffer_begin(c->buffer))) { /* fallback to TCP */ /* this discards partial UDP contents */ if(sq->status == serviced_query_UDP_EDNS || sq->status == serviced_query_UDP_EDNS_FRAG || sq->status == serviced_query_UDP_EDNS_fallback) /* if we have unfinished EDNS_fallback, start again */ sq->status = serviced_query_TCP_EDNS; else sq->status = serviced_query_TCP; serviced_tcp_initiate(sq, c->buffer); return 0; } /* yay! an answer */ serviced_callbacks(sq, error, c, rep); return 0; } struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, int check_ratelimit, int tcp_upstream, int ssl_upstream, char* tls_auth_name, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, struct module_qstate* qstate, comm_point_callback_type* callback, void* callback_arg, sldns_buffer* buff, struct module_env* env, int* was_ratelimited) { struct serviced_query* sq; struct service_callback* cb; struct edns_string_addr* client_string_addr; struct regional* region; struct edns_option* backed_up_opt_list = qstate->edns_opts_back_out; struct edns_option* per_upstream_opt_list = NULL; time_t timenow = 0; /* If we have an already populated EDNS option list make a copy since * we may now add upstream specific EDNS options. */ /* Use a region that could be attached to a serviced_query, if it needs * to be created. If an existing one is found then this region will be * destroyed here. */ region = alloc_reg_obtain(env->alloc); if(!region) return NULL; if(qstate->edns_opts_back_out) { per_upstream_opt_list = edns_opt_copy_region( qstate->edns_opts_back_out, region); if(!per_upstream_opt_list) { alloc_reg_release(env->alloc, region); return NULL; } qstate->edns_opts_back_out = per_upstream_opt_list; } if(!inplace_cb_query_call(env, qinfo, flags, addr, addrlen, zone, zonelen, qstate, region)) { alloc_reg_release(env->alloc, region); return NULL; } /* Restore the option list; we can explicitly use the copied one from * now on. */ per_upstream_opt_list = qstate->edns_opts_back_out; qstate->edns_opts_back_out = backed_up_opt_list; if((client_string_addr = edns_string_addr_lookup( &env->edns_strings->client_strings, addr, addrlen))) { edns_opt_list_append(&per_upstream_opt_list, env->edns_strings->client_string_opcode, client_string_addr->string_len, client_string_addr->string, region); } serviced_gen_query(buff, qinfo->qname, qinfo->qname_len, qinfo->qtype, qinfo->qclass, flags); sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen, per_upstream_opt_list); if(!sq) { /* Check ratelimit only for new serviced_query */ if(check_ratelimit) { timenow = *env->now; if(!infra_ratelimit_inc(env->infra_cache, zone, zonelen, timenow, env->cfg->ratelimit_backoff, &qstate->qinfo, qstate->mesh_info->reply_list ?&qstate->mesh_info->reply_list->query_reply :NULL)) { /* Can we pass through with slip factor? */ if(env->cfg->ratelimit_factor == 0 || ub_random_max(env->rnd, env->cfg->ratelimit_factor) != 1) { *was_ratelimited = 1; alloc_reg_release(env->alloc, region); return NULL; } log_nametypeclass(VERB_ALGO, "ratelimit allowed through for " "delegation point", zone, LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); } } /* make new serviced query entry */ sq = serviced_create(outnet, buff, dnssec, want_dnssec, nocaps, tcp_upstream, ssl_upstream, tls_auth_name, addr, addrlen, zone, zonelen, (int)qinfo->qtype, per_upstream_opt_list, ( ssl_upstream && env->cfg->pad_queries ? env->cfg->pad_queries_block_size : 0 ), env->alloc, region); if(!sq) { if(check_ratelimit) { infra_ratelimit_dec(env->infra_cache, zone, zonelen, timenow); } return NULL; } if(!(cb = (struct service_callback*)regional_alloc( sq->region, sizeof(*cb)))) { if(check_ratelimit) { infra_ratelimit_dec(env->infra_cache, zone, zonelen, timenow); } (void)rbtree_delete(outnet->serviced, sq); serviced_node_del(&sq->node, NULL); return NULL; } /* No network action at this point; it will be invoked with the * serviced_query timer instead to run outside of the mesh. */ } else { /* We don't need this region anymore. */ alloc_reg_release(env->alloc, region); /* duplicate entries are included in the callback list, because * there is a counterpart registration by our caller that needs * to be doubly-removed (with callbacks perhaps). */ if(!(cb = (struct service_callback*)regional_alloc( sq->region, sizeof(*cb)))) { return NULL; } } /* add callback to list of callbacks */ cb->cb = callback; cb->cb_arg = callback_arg; cb->next = sq->cblist; sq->cblist = cb; return sq; } /** remove callback from list */ static void callback_list_remove(struct serviced_query* sq, void* cb_arg) { struct service_callback** pp = &sq->cblist; while(*pp) { if((*pp)->cb_arg == cb_arg) { struct service_callback* del = *pp; *pp = del->next; return; } pp = &(*pp)->next; } } void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg) { if(!sq) return; callback_list_remove(sq, cb_arg); /* if callbacks() routine scheduled deletion, let it do that */ if(!sq->cblist && !sq->busy && !sq->to_be_deleted) { (void)rbtree_delete(sq->outnet->serviced, sq); serviced_delete(sq); } } /** create fd to send to this destination */ static int fd_for_dest(struct outside_network* outnet, struct sockaddr_storage* to_addr, socklen_t to_addrlen) { struct sockaddr_storage* addr; socklen_t addrlen; int i, try, pnum, dscp; struct port_if* pif; /* create fd */ dscp = outnet->ip_dscp; for(try = 0; try<1000; try++) { int port = 0; int freebind = 0; int noproto = 0; int inuse = 0; int fd = -1; /* select interface */ if(addr_is_ip6(to_addr, to_addrlen)) { if(outnet->num_ip6 == 0) { char to[64]; addr_to_str(to_addr, to_addrlen, to, sizeof(to)); verbose(VERB_QUERY, "need ipv6 to send, but no ipv6 outgoing interfaces, for %s", to); return -1; } i = ub_random_max(outnet->rnd, outnet->num_ip6); pif = &outnet->ip6_ifs[i]; } else { if(outnet->num_ip4 == 0) { char to[64]; addr_to_str(to_addr, to_addrlen, to, sizeof(to)); verbose(VERB_QUERY, "need ipv4 to send, but no ipv4 outgoing interfaces, for %s", to); return -1; } i = ub_random_max(outnet->rnd, outnet->num_ip4); pif = &outnet->ip4_ifs[i]; } addr = &pif->addr; addrlen = pif->addrlen; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION pnum = ub_random_max(outnet->rnd, pif->avail_total); if(pnum < pif->inuse) { /* port already open */ port = pif->out[pnum]->number; } else { /* unused ports in start part of array */ port = pif->avail_ports[pnum - pif->inuse]; } #else pnum = port = 0; #endif if(addr_is_ip6(to_addr, to_addrlen)) { struct sockaddr_in6 sa = *(struct sockaddr_in6*)addr; sa.sin6_port = (in_port_t)htons((uint16_t)port); fd = create_udp_sock(AF_INET6, SOCK_DGRAM, (struct sockaddr*)&sa, addrlen, 1, &inuse, &noproto, 0, 0, 0, NULL, 0, freebind, 0, dscp); } else { struct sockaddr_in* sa = (struct sockaddr_in*)addr; sa->sin_port = (in_port_t)htons((uint16_t)port); fd = create_udp_sock(AF_INET, SOCK_DGRAM, (struct sockaddr*)addr, addrlen, 1, &inuse, &noproto, 0, 0, 0, NULL, 0, freebind, 0, dscp); } if(fd != -1) { return fd; } if(!inuse) { return -1; } } /* too many tries */ log_err("cannot send probe, ports are in use"); return -1; } struct comm_point* outnet_comm_point_for_udp(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen) { struct comm_point* cp; int fd = fd_for_dest(outnet, to_addr, to_addrlen); if(fd == -1) { return NULL; } cp = comm_point_create_udp(outnet->base, fd, outnet->udp_buff, 0, cb, cb_arg, NULL); if(!cp) { log_err("malloc failure"); close(fd); return NULL; } return cp; } /** setup SSL for comm point */ static int setup_comm_ssl(struct comm_point* cp, struct outside_network* outnet, int fd, char* host) { cp->ssl = outgoing_ssl_fd(outnet->sslctx, fd); if(!cp->ssl) { log_err("cannot create SSL object"); return 0; } #ifdef USE_WINSOCK comm_point_tcp_win_bio_cb(cp, cp->ssl); #endif cp->ssl_shake_state = comm_ssl_shake_write; /* https verification */ #ifdef HAVE_SSL if(outnet->tls_use_sni) { (void)SSL_set_tlsext_host_name(cp->ssl, host); } #endif #ifdef HAVE_SSL_SET1_HOST if((SSL_CTX_get_verify_mode(outnet->sslctx)&SSL_VERIFY_PEER)) { /* because we set SSL_VERIFY_PEER, in netevent in * ssl_handshake, it'll check if the certificate * verification has succeeded */ /* SSL_VERIFY_PEER is set on the sslctx */ /* and the certificates to verify with are loaded into * it with SSL_load_verify_locations or * SSL_CTX_set_default_verify_paths */ /* setting the hostname makes openssl verify the * host name in the x509 certificate in the * SSL connection*/ if(!SSL_set1_host(cp->ssl, host)) { log_err("SSL_set1_host failed"); return 0; } } #elif defined(HAVE_X509_VERIFY_PARAM_SET1_HOST) /* openssl 1.0.2 has this function that can be used for * set1_host like verification */ if((SSL_CTX_get_verify_mode(outnet->sslctx)&SSL_VERIFY_PEER)) { X509_VERIFY_PARAM* param = SSL_get0_param(cp->ssl); # ifdef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); # endif if(!X509_VERIFY_PARAM_set1_host(param, host, strlen(host))) { log_err("X509_VERIFY_PARAM_set1_host failed"); return 0; } } #else (void)host; #endif /* HAVE_SSL_SET1_HOST */ return 1; } struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen, sldns_buffer* query, int timeout, int ssl, char* host) { struct comm_point* cp; int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp, ssl); if(fd == -1) { return 0; } fd_set_nonblock(fd); if(!outnet_tcp_connect(fd, to_addr, to_addrlen)) { /* outnet_tcp_connect has closed fd on error for us */ return 0; } cp = comm_point_create_tcp_out(outnet->base, 65552, cb, cb_arg); if(!cp) { log_err("malloc failure"); close(fd); return 0; } cp->repinfo.remote_addrlen = to_addrlen; memcpy(&cp->repinfo.remote_addr, to_addr, to_addrlen); /* setup for SSL (if needed) */ if(ssl) { if(!setup_comm_ssl(cp, outnet, fd, host)) { log_err("cannot setup XoT"); comm_point_delete(cp); return NULL; } } /* set timeout on TCP connection */ comm_point_start_listening(cp, fd, timeout); /* copy scratch buffer to cp->buffer */ sldns_buffer_copy(cp->buffer, query); return cp; } /** setup the User-Agent HTTP header based on http-user-agent configuration */ static void setup_http_user_agent(sldns_buffer* buf, struct config_file* cfg) { if(cfg->hide_http_user_agent) return; if(cfg->http_user_agent==NULL || cfg->http_user_agent[0] == 0) { sldns_buffer_printf(buf, "User-Agent: %s/%s\r\n", PACKAGE_NAME, PACKAGE_VERSION); } else { sldns_buffer_printf(buf, "User-Agent: %s\r\n", cfg->http_user_agent); } } /** setup http request headers in buffer for sending query to destination */ static int setup_http_request(sldns_buffer* buf, char* host, char* path, struct config_file* cfg) { sldns_buffer_clear(buf); sldns_buffer_printf(buf, "GET /%s HTTP/1.1\r\n", path); sldns_buffer_printf(buf, "Host: %s\r\n", host); setup_http_user_agent(buf, cfg); /* We do not really do multiple queries per connection, * but this header setting is also not needed. * sldns_buffer_printf(buf, "Connection: close\r\n") */ sldns_buffer_printf(buf, "\r\n"); if(sldns_buffer_position(buf)+10 > sldns_buffer_capacity(buf)) return 0; /* somehow buffer too short, but it is about 60K and the request is only a couple bytes long. */ sldns_buffer_flip(buf); return 1; } struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout, int ssl, char* host, char* path, struct config_file* cfg) { /* cp calls cb with err=NETEVENT_DONE when transfer is done */ struct comm_point* cp; int fd = outnet_get_tcp_fd(to_addr, to_addrlen, outnet->tcp_mss, outnet->ip_dscp, ssl); if(fd == -1) { return 0; } fd_set_nonblock(fd); if(!outnet_tcp_connect(fd, to_addr, to_addrlen)) { /* outnet_tcp_connect has closed fd on error for us */ return 0; } cp = comm_point_create_http_out(outnet->base, 65552, cb, cb_arg, outnet->udp_buff); if(!cp) { log_err("malloc failure"); close(fd); return 0; } cp->repinfo.remote_addrlen = to_addrlen; memcpy(&cp->repinfo.remote_addr, to_addr, to_addrlen); /* setup for SSL (if needed) */ if(ssl) { if(!setup_comm_ssl(cp, outnet, fd, host)) { log_err("cannot setup https"); comm_point_delete(cp); return NULL; } } /* set timeout on TCP connection */ comm_point_start_listening(cp, fd, timeout); /* setup http request in cp->buffer */ if(!setup_http_request(cp->buffer, host, path, cfg)) { log_err("error setting up http request"); comm_point_delete(cp); return NULL; } return cp; } /** get memory used by waiting tcp entry (in use or not) */ static size_t waiting_tcp_get_mem(struct waiting_tcp* w) { size_t s; if(!w) return 0; s = sizeof(*w) + w->pkt_len; if(w->timer) s += comm_timer_get_mem(w->timer); return s; } /** get memory used by port if */ static size_t if_get_mem(struct port_if* pif) { size_t s; int i; s = sizeof(*pif) + #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION sizeof(int)*pif->avail_total + #endif sizeof(struct port_comm*)*pif->maxout; for(i=0; iinuse; i++) s += sizeof(*pif->out[i]) + comm_point_get_mem(pif->out[i]->cp); return s; } /** get memory used by waiting udp */ static size_t waiting_udp_get_mem(struct pending* w) { size_t s; s = sizeof(*w) + comm_timer_get_mem(w->timer) + w->pkt_len; return s; } size_t outnet_get_mem(struct outside_network* outnet) { size_t i; int k; struct waiting_tcp* w; struct pending* u; struct serviced_query* sq; struct service_callback* sb; struct port_comm* pc; size_t s = sizeof(*outnet) + sizeof(*outnet->base) + sizeof(*outnet->udp_buff) + sldns_buffer_capacity(outnet->udp_buff); /* second buffer is not ours */ for(pc = outnet->unused_fds; pc; pc = pc->next) { s += sizeof(*pc) + comm_point_get_mem(pc->cp); } for(k=0; knum_ip4; k++) s += if_get_mem(&outnet->ip4_ifs[k]); for(k=0; knum_ip6; k++) s += if_get_mem(&outnet->ip6_ifs[k]); for(u=outnet->udp_wait_first; u; u=u->next_waiting) s += waiting_udp_get_mem(u); s += sizeof(struct pending_tcp*)*outnet->num_tcp; for(i=0; inum_tcp; i++) { s += sizeof(struct pending_tcp); s += comm_point_get_mem(outnet->tcp_conns[i]->c); if(outnet->tcp_conns[i]->query) s += waiting_tcp_get_mem(outnet->tcp_conns[i]->query); } for(w=outnet->tcp_wait_first; w; w = w->next_waiting) s += waiting_tcp_get_mem(w); s += sizeof(*outnet->pending); s += (sizeof(struct pending) + comm_timer_get_mem(NULL)) * outnet->pending->count; s += sizeof(*outnet->serviced); s += outnet->svcd_overhead; RBTREE_FOR(sq, struct serviced_query*, outnet->serviced) { s += sizeof(*sq) + sq->qbuflen; for(sb = sq->cblist; sb; sb = sb->next) s += sizeof(*sb); } return s; } size_t serviced_get_mem(struct serviced_query* sq) { struct service_callback* sb; size_t s; s = sizeof(*sq) + sq->qbuflen; for(sb = sq->cblist; sb; sb = sb->next) s += sizeof(*sb); if(sq->status == serviced_query_UDP_EDNS || sq->status == serviced_query_UDP || sq->status == serviced_query_UDP_EDNS_FRAG || sq->status == serviced_query_UDP_EDNS_fallback) { s += sizeof(struct pending); s += comm_timer_get_mem(NULL); } else { /* does not have size of the pkt pointer */ /* always has a timer except on malloc failures */ /* these sizes are part of the main outside network mem */ /* s += sizeof(struct waiting_tcp); s += comm_timer_get_mem(NULL); */ } return s; } unbound-1.25.1/services/authzone.c0000644000175000017500000100501415203270263016533 0ustar wouterwouter/* * services/authzone.c - authoritative zone that is locally hosted. * * Copyright (c) 2017, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the functions for an authority zone. This zone * is queried by the iterator, just like a stub or forward zone, but then * the data is locally held. */ #include "config.h" #include "services/authzone.h" #include "util/data/dname.h" #include "util/data/msgparse.h" #include "util/data/msgreply.h" #include "util/data/msgencode.h" #include "util/data/packed_rrset.h" #include "util/regional.h" #include "util/net_help.h" #include "util/netevent.h" #include "util/config_file.h" #include "util/log.h" #include "util/module.h" #include "util/random.h" #include "services/cache/dns.h" #include "services/outside_network.h" #include "services/listen_dnsport.h" #include "services/mesh.h" #include "sldns/rrdef.h" #include "sldns/pkthdr.h" #include "sldns/sbuffer.h" #include "sldns/str2wire.h" #include "sldns/wire2str.h" #include "sldns/parseutil.h" #include "sldns/keyraw.h" #include "validator/val_nsec3.h" #include "validator/val_nsec.h" #include "validator/val_secalgo.h" #include "validator/val_sigcrypt.h" #include "validator/val_anchor.h" #include "validator/val_utils.h" #include /** bytes to use for NSEC3 hash buffer. 20 for sha1 */ #define N3HASHBUFLEN 32 /** max number of CNAMEs we are willing to follow (in one answer) */ #define MAX_CNAME_CHAIN 8 /** timeout for probe packets for SOA */ #define AUTH_PROBE_TIMEOUT 100 /* msec */ /** when to stop with SOA probes (when exponential timeouts exceed this) */ #define AUTH_PROBE_TIMEOUT_STOP 1000 /* msec */ /* auth transfer timeout for TCP connections, in msec */ #define AUTH_TRANSFER_TIMEOUT 10000 /* msec */ /* auth transfer max backoff for failed transfers and probes */ #define AUTH_TRANSFER_MAX_BACKOFF 86400 /* sec */ /* auth http port number */ #define AUTH_HTTP_PORT 80 /* auth https port number */ #define AUTH_HTTPS_PORT 443 /* max depth for nested $INCLUDEs */ #define MAX_INCLUDE_DEPTH 10 /** number of timeouts before we fallback from IXFR to AXFR, * because some versions of servers (eg. dnsmasq) drop IXFR packets. */ #define NUM_TIMEOUTS_FALLBACK_IXFR 3 /** pick up nextprobe task to start waiting to perform transfer actions */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, int failure, int lookup_only); /** move to sending the probe packets, next if fails. task_probe */ static void xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env); /** pick up probe task with specified(or NULL) destination first, * or transfer task if nothing to probe, or false if already in progress */ static int xfr_start_probe(struct auth_xfer* xfr, struct module_env* env, struct auth_master* spec); /** delete xfer structure (not its tree entry) */ void auth_xfer_delete(struct auth_xfer* xfr); /** create new dns_msg */ static struct dns_msg* msg_create(struct regional* region, struct query_info* qinfo) { struct dns_msg* msg = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!msg) return NULL; msg->qinfo.qname = regional_alloc_init(region, qinfo->qname, qinfo->qname_len); if(!msg->qinfo.qname) return NULL; msg->qinfo.qname_len = qinfo->qname_len; msg->qinfo.qtype = qinfo->qtype; msg->qinfo.qclass = qinfo->qclass; msg->qinfo.local_alias = NULL; /* non-packed reply_info, because it needs to grow the array */ msg->rep = (struct reply_info*)regional_alloc_zero(region, sizeof(struct reply_info)-sizeof(struct rrset_ref)); if(!msg->rep) return NULL; msg->rep->flags = (uint16_t)(BIT_QR | BIT_AA); msg->rep->authoritative = 1; msg->rep->reason_bogus = LDNS_EDE_NONE; msg->rep->qdcount = 1; /* rrsets is NULL, no rrsets yet */ return msg; } /** grow rrset array by one in msg */ static int msg_grow_array(struct regional* region, struct dns_msg* msg) { if(msg->rep->rrsets == NULL) { msg->rep->rrsets = regional_alloc_zero(region, sizeof(struct ub_packed_rrset_key*)*(msg->rep->rrset_count+1)); if(!msg->rep->rrsets) return 0; } else { struct ub_packed_rrset_key** rrsets_old = msg->rep->rrsets; msg->rep->rrsets = regional_alloc_zero(region, sizeof(struct ub_packed_rrset_key*)*(msg->rep->rrset_count+1)); if(!msg->rep->rrsets) return 0; memmove(msg->rep->rrsets, rrsets_old, sizeof(struct ub_packed_rrset_key*)*msg->rep->rrset_count); } return 1; } /** get ttl of rrset */ static time_t get_rrset_ttl(struct ub_packed_rrset_key* k) { struct packed_rrset_data* d = (struct packed_rrset_data*) k->entry.data; return d->ttl; } /** Copy rrset into region from domain-datanode and packet rrset */ static struct ub_packed_rrset_key* auth_packed_rrset_copy_region(struct auth_zone* z, struct auth_data* node, struct auth_rrset* rrset, struct regional* region) { struct ub_packed_rrset_key key; memset(&key, 0, sizeof(key)); key.entry.key = &key; key.entry.data = rrset->data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(rrset->type); key.rk.rrset_class = htons(z->dclass); key.entry.hash = rrset_key_hash(&key.rk); return packed_rrset_copy_region(&key, region, 0); } /** fix up msg->rep TTL and prefetch ttl */ static void msg_ttl(struct dns_msg* msg) { if(msg->rep->rrset_count == 0) return; if(msg->rep->rrset_count == 1) { msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[0]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } else if(get_rrset_ttl(msg->rep->rrsets[msg->rep->rrset_count-1]) < msg->rep->ttl) { msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[ msg->rep->rrset_count-1]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } } /** see if rrset is a duplicate in the answer message */ static int msg_rrset_duplicate(struct dns_msg* msg, uint8_t* nm, size_t nmlen, uint16_t type, uint16_t dclass) { size_t i; for(i=0; irep->rrset_count; i++) { struct ub_packed_rrset_key* k = msg->rep->rrsets[i]; if(ntohs(k->rk.type) == type && k->rk.dname_len == nmlen && ntohs(k->rk.rrset_class) == dclass && query_dname_compare(k->rk.dname, nm) == 0) return 1; } return 0; } /** add rrset to answer section (no auth, add rrsets yet) */ static int msg_add_rrset_an(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { log_assert(msg->rep->ns_numrrsets == 0); log_assert(msg->rep->ar_numrrsets == 0); if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->an_numrrsets++; msg_ttl(msg); return 1; } /** add rrset to authority section (no additional section rrsets yet) */ static int msg_add_rrset_ns(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { log_assert(msg->rep->ar_numrrsets == 0); if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->ns_numrrsets++; msg_ttl(msg); return 1; } /** add rrset to additional section */ static int msg_add_rrset_ar(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!rrset || !node) return 1; if(msg_rrset_duplicate(msg, node->name, node->namelen, rrset->type, z->dclass)) return 1; /* grow array */ if(!msg_grow_array(region, msg)) return 0; /* copy it */ if(!(msg->rep->rrsets[msg->rep->rrset_count] = auth_packed_rrset_copy_region(z, node, rrset, region))) return 0; msg->rep->rrset_count++; msg->rep->ar_numrrsets++; msg_ttl(msg); return 1; } struct auth_zones* auth_zones_create(void) { struct auth_zones* az = (struct auth_zones*)calloc(1, sizeof(*az)); if(!az) { log_err("out of memory"); return NULL; } rbtree_init(&az->ztree, &auth_zone_cmp); rbtree_init(&az->xtree, &auth_xfer_cmp); lock_rw_init(&az->lock); lock_protect(&az->lock, &az->ztree, sizeof(az->ztree)); lock_protect(&az->lock, &az->xtree, sizeof(az->xtree)); /* also lock protects the rbnode's in struct auth_zone, auth_xfer */ lock_rw_init(&az->rpz_lock); lock_protect(&az->rpz_lock, &az->rpz_first, sizeof(az->rpz_first)); return az; } int auth_zone_cmp(const void* z1, const void* z2) { /* first sort on class, so that hierarchy can be maintained within * a class */ struct auth_zone* a = (struct auth_zone*)z1; struct auth_zone* b = (struct auth_zone*)z2; int m; if(a->dclass != b->dclass) { if(a->dclass < b->dclass) return -1; return 1; } /* sorted such that higher zones sort before lower zones (their * contents) */ return dname_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } int auth_data_cmp(const void* z1, const void* z2) { struct auth_data* a = (struct auth_data*)z1; struct auth_data* b = (struct auth_data*)z2; int m; /* canonical sort, because DNSSEC needs that */ return dname_canon_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } int auth_xfer_cmp(const void* z1, const void* z2) { /* first sort on class, so that hierarchy can be maintained within * a class */ struct auth_xfer* a = (struct auth_xfer*)z1; struct auth_xfer* b = (struct auth_xfer*)z2; int m; if(a->dclass != b->dclass) { if(a->dclass < b->dclass) return -1; return 1; } /* sorted such that higher zones sort before lower zones (their * contents) */ return dname_lab_cmp(a->name, a->namelabs, b->name, b->namelabs, &m); } /** delete auth rrset node */ static void auth_rrset_delete(struct auth_rrset* rrset) { if(!rrset) return; free(rrset->data); free(rrset); } /** delete auth data domain node */ static void auth_data_delete(struct auth_data* n) { struct auth_rrset* p, *np; if(!n) return; p = n->rrsets; while(p) { np = p->next; auth_rrset_delete(p); p = np; } free(n->name); free(n); } /** helper traverse to delete zones */ static void auth_data_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_data* z = (struct auth_data*)n->key; auth_data_delete(z); } /** delete an auth zone structure (tree remove must be done elsewhere) */ static void auth_zone_delete(struct auth_zone* z, struct auth_zones* az) { if(!z) return; lock_rw_destroy(&z->lock); traverse_postorder(&z->data, auth_data_del, NULL); if(az && z->rpz) { /* keep RPZ linked list intact */ lock_rw_wrlock(&az->rpz_lock); if(z->rpz_az_prev) z->rpz_az_prev->rpz_az_next = z->rpz_az_next; else az->rpz_first = z->rpz_az_next; if(z->rpz_az_next) z->rpz_az_next->rpz_az_prev = z->rpz_az_prev; lock_rw_unlock(&az->rpz_lock); } if(z->rpz) rpz_delete(z->rpz); free(z->name); free(z->zonefile); free(z); } struct auth_zone* auth_zone_create(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_zone* z = (struct auth_zone*)calloc(1, sizeof(*z)); if(!z) { return NULL; } z->node.key = z; z->dclass = dclass; z->namelen = nmlen; z->namelabs = dname_count_labels(nm); z->name = memdup(nm, nmlen); if(!z->name) { free(z); return NULL; } rbtree_init(&z->data, &auth_data_cmp); lock_rw_init(&z->lock); lock_protect(&z->lock, &z->name, sizeof(*z)-sizeof(rbnode_type)- sizeof(&z->rpz_az_next)-sizeof(&z->rpz_az_prev)); lock_rw_wrlock(&z->lock); /* z lock protects all, except rbtree itself and the rpz linked list * pointers, which are protected using az->lock */ if(!rbtree_insert(&az->ztree, &z->node)) { lock_rw_unlock(&z->lock); auth_zone_delete(z, NULL); log_warn("duplicate auth zone"); return NULL; } return z; } struct auth_zone* auth_zone_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_zone key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_zone*)rbtree_search(&az->ztree, &key); } struct auth_xfer* auth_xfer_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_xfer key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_xfer*)rbtree_search(&az->xtree, &key); } /** find an auth zone or sorted less-or-equal, return true if exact */ static int auth_zone_find_less_equal(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass, struct auth_zone** z) { struct auth_zone key; key.node.key = &key; key.dclass = dclass; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return rbtree_find_less_equal(&az->ztree, &key, (rbnode_type**)z); } /** find the auth zone that is above the given name */ struct auth_zone* auth_zones_find_zone(struct auth_zones* az, uint8_t* name, size_t name_len, uint16_t dclass) { uint8_t* nm = name; size_t nmlen = name_len; struct auth_zone* z; if(auth_zone_find_less_equal(az, nm, nmlen, dclass, &z)) { /* exact match */ return z; } else { /* less-or-nothing */ if(!z) return NULL; /* nothing smaller, nothing above it */ /* we found smaller name; smaller may be above the name, * but not below it. */ nm = dname_get_shared_topdomain(z->name, name); dname_count_size_labels(nm, &nmlen); z = NULL; } /* search up */ while(!z) { z = auth_zone_find(az, nm, nmlen, dclass); if(z) return z; if(dname_is_root(nm)) break; dname_remove_label(&nm, &nmlen); } return NULL; } /** find or create zone with name str. caller must have lock on az. * returns a wrlocked zone */ static struct auth_zone* auth_zones_find_or_add_zone(struct auth_zones* az, char* name) { uint8_t nm[LDNS_MAX_DOMAINLEN+1]; size_t nmlen = sizeof(nm); struct auth_zone* z; if(sldns_str2wire_dname_buf(name, nm, &nmlen) != 0) { log_err("cannot parse auth zone name: %s", name); return 0; } z = auth_zone_find(az, nm, nmlen, LDNS_RR_CLASS_IN); if(!z) { /* not found, create the zone */ z = auth_zone_create(az, nm, nmlen, LDNS_RR_CLASS_IN); } else { lock_rw_wrlock(&z->lock); } return z; } /** find or create xfer zone with name str. caller must have lock on az. * returns a locked xfer */ static struct auth_xfer* auth_zones_find_or_add_xfer(struct auth_zones* az, struct auth_zone* z) { struct auth_xfer* x; x = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(!x) { /* not found, create the zone */ x = auth_xfer_create(az, z); } else { lock_basic_lock(&x->lock); } return x; } int auth_zone_set_zonefile(struct auth_zone* z, char* zonefile) { if(z->zonefile) free(z->zonefile); if(zonefile == NULL) { z->zonefile = NULL; } else { z->zonefile = strdup(zonefile); if(!z->zonefile) { log_err("malloc failure"); return 0; } } return 1; } /** set auth zone fallback. caller must have lock on zone */ int auth_zone_set_fallback(struct auth_zone* z, char* fallbackstr) { if(strcmp(fallbackstr, "yes") != 0 && strcmp(fallbackstr, "no") != 0){ log_err("auth zone fallback, expected yes or no, got %s", fallbackstr); return 0; } z->fallback_enabled = (strcmp(fallbackstr, "yes")==0); return 1; } /** create domain with the given name */ static struct auth_data* az_domain_create(struct auth_zone* z, uint8_t* nm, size_t nmlen) { struct auth_data* n = (struct auth_data*)malloc(sizeof(*n)); if(!n) return NULL; memset(n, 0, sizeof(*n)); n->node.key = n; n->name = memdup(nm, nmlen); if(!n->name) { free(n); return NULL; } n->namelen = nmlen; n->namelabs = dname_count_labels(nm); if(!rbtree_insert(&z->data, &n->node)) { log_warn("duplicate auth domain name"); free(n->name); free(n); return NULL; } return n; } /** find domain with exactly the given name */ static struct auth_data* az_find_name(struct auth_zone* z, uint8_t* nm, size_t nmlen) { struct auth_zone key; key.node.key = &key; key.name = nm; key.namelen = nmlen; key.namelabs = dname_count_labels(nm); return (struct auth_data*)rbtree_search(&z->data, &key); } /** Find domain name (or closest match) */ static void az_find_domain(struct auth_zone* z, struct query_info* qinfo, int* node_exact, struct auth_data** node) { struct auth_zone key; key.node.key = &key; key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = dname_count_labels(key.name); *node_exact = rbtree_find_less_equal(&z->data, &key, (rbnode_type**)node); } /** find or create domain with name in zone */ static struct auth_data* az_domain_find_or_create(struct auth_zone* z, uint8_t* dname, size_t dname_len) { struct auth_data* n = az_find_name(z, dname, dname_len); if(!n) { n = az_domain_create(z, dname, dname_len); } return n; } /** find rrset of given type in the domain */ static struct auth_rrset* az_domain_rrset(struct auth_data* n, uint16_t t) { struct auth_rrset* rrset; if(!n) return NULL; rrset = n->rrsets; while(rrset) { if(rrset->type == t) return rrset; rrset = rrset->next; } return NULL; } /** remove rrset of this type from domain */ static void domain_remove_rrset(struct auth_data* node, uint16_t rr_type) { struct auth_rrset* rrset, *prev; if(!node) return; prev = NULL; rrset = node->rrsets; while(rrset) { if(rrset->type == rr_type) { /* found it, now delete it */ if(prev) prev->next = rrset->next; else node->rrsets = rrset->next; auth_rrset_delete(rrset); return; } prev = rrset; rrset = rrset->next; } } /** find an rrsig index in the rrset. returns true if found */ static int az_rrset_find_rrsig(struct packed_rrset_data* d, uint8_t* rdata, size_t len, size_t* index) { size_t i; for(i=d->count; icount + d->rrsig_count; i++) { if(d->rr_len[i] != len) continue; if(memcmp(d->rr_data[i], rdata, len) == 0) { *index = i; return 1; } } return 0; } /** see if rdata is duplicate */ static int rdata_duplicate(struct packed_rrset_data* d, uint8_t* rdata, size_t len) { size_t i; for(i=0; icount + d->rrsig_count; i++) { if(d->rr_len[i] != len) continue; if(memcmp(d->rr_data[i], rdata, len) == 0) return 1; } return 0; } /** get rrsig type covered from rdata. * @param rdata: rdata in wireformat, starting with 16bit rdlength. * @param rdatalen: length of rdata buffer. * @return type covered (or 0). */ static uint16_t rrsig_rdata_get_type_covered(uint8_t* rdata, size_t rdatalen) { if(rdatalen < 4) return 0; return sldns_read_uint16(rdata+2); } /** remove RR from existing RRset. Also sig, if it is a signature. * reallocates the packed rrset for a new one, false on alloc failure */ static int rrset_remove_rr(struct auth_rrset* rrset, size_t index) { struct packed_rrset_data* d, *old = rrset->data; size_t i; if(index >= old->count + old->rrsig_count) return 0; /* index out of bounds */ d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) - ( sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + old->rr_len[index])); if(!d) { log_err("malloc failure"); return 0; } d->ttl = old->ttl; d->count = old->count; d->rrsig_count = old->rrsig_count; if(index < d->count) d->count--; else d->rrsig_count--; d->trust = old->trust; d->security = old->security; /* set rr_len, needed for ptr_fixup */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(index > 0) memmove(d->rr_len, old->rr_len, (index)*sizeof(size_t)); if(index+1 < old->count+old->rrsig_count) memmove(&d->rr_len[index], &old->rr_len[index+1], (old->count+old->rrsig_count - (index+1))*sizeof(size_t)); packed_rrset_ptr_fixup(d); /* move over ttls */ if(index > 0) memmove(d->rr_ttl, old->rr_ttl, (index)*sizeof(time_t)); if(index+1 < old->count+old->rrsig_count) memmove(&d->rr_ttl[index], &old->rr_ttl[index+1], (old->count+old->rrsig_count - (index+1))*sizeof(time_t)); /* move over rr_data */ for(i=0; icount+d->rrsig_count; i++) { size_t oldi; if(i < index) oldi = i; else oldi = i+1; memmove(d->rr_data[i], old->rr_data[oldi], d->rr_len[i]); } /* recalc ttl (lowest of remaining RR ttls) */ if(d->count + d->rrsig_count > 0) d->ttl = d->rr_ttl[0]; for(i=0; icount+d->rrsig_count; i++) { if(d->rr_ttl[i] < d->ttl) d->ttl = d->rr_ttl[i]; } free(rrset->data); rrset->data = d; return 1; } /** add RR to existing RRset. If insert_sig is true, add to rrsigs. * This reallocates the packed rrset for a new one */ static int rrset_add_rr(struct auth_rrset* rrset, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen, int insert_sig) { struct packed_rrset_data* d, *old = rrset->data; size_t total, old_total; d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + rdatalen); if(!d) { log_err("out of memory"); return 0; } /* copy base values */ memcpy(d, old, sizeof(struct packed_rrset_data)); if(!insert_sig) { d->count++; } else { d->rrsig_count++; } old_total = old->count + old->rrsig_count; total = d->count + d->rrsig_count; /* set rr_len, needed for ptr_fixup */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(old->count != 0) memmove(d->rr_len, old->rr_len, old->count*sizeof(size_t)); if(old->rrsig_count != 0) memmove(d->rr_len+d->count, old->rr_len+old->count, old->rrsig_count*sizeof(size_t)); if(!insert_sig) d->rr_len[d->count-1] = rdatalen; else d->rr_len[total-1] = rdatalen; packed_rrset_ptr_fixup(d); if((time_t)rr_ttl < d->ttl) d->ttl = rr_ttl; /* copy old values into new array */ if(old->count != 0) { memmove(d->rr_ttl, old->rr_ttl, old->count*sizeof(time_t)); /* all the old rr pieces are allocated sequential, so we * can copy them in one go */ memmove(d->rr_data[0], old->rr_data[0], (old->rr_data[old->count-1] - old->rr_data[0]) + old->rr_len[old->count-1]); } if(old->rrsig_count != 0) { memmove(d->rr_ttl+d->count, old->rr_ttl+old->count, old->rrsig_count*sizeof(time_t)); memmove(d->rr_data[d->count], old->rr_data[old->count], (old->rr_data[old_total-1] - old->rr_data[old->count]) + old->rr_len[old_total-1]); } /* insert new value */ if(!insert_sig) { d->rr_ttl[d->count-1] = rr_ttl; memmove(d->rr_data[d->count-1], rdata, rdatalen); } else { d->rr_ttl[total-1] = rr_ttl; memmove(d->rr_data[total-1], rdata, rdatalen); } rrset->data = d; free(old); return 1; } /** Create new rrset for node with packed rrset with one RR element */ static struct auth_rrset* rrset_create(struct auth_data* node, uint16_t rr_type, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen) { struct auth_rrset* rrset = (struct auth_rrset*)calloc(1, sizeof(*rrset)); struct auth_rrset* p, *prev; struct packed_rrset_data* d; if(!rrset) { log_err("out of memory"); return NULL; } rrset->type = rr_type; /* the rrset data structure, with one RR */ d = (struct packed_rrset_data*)calloc(1, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + rdatalen); if(!d) { free(rrset); log_err("out of memory"); return NULL; } rrset->data = d; d->ttl = rr_ttl; d->trust = rrset_trust_prim_noglue; 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_data[0] = (uint8_t*)&(d->rr_ttl[1]); /* insert the RR */ d->rr_len[0] = rdatalen; d->rr_ttl[0] = rr_ttl; memmove(d->rr_data[0], rdata, rdatalen); d->count++; /* insert rrset into linked list for domain */ /* find sorted place to link the rrset into the list */ prev = NULL; p = node->rrsets; while(p && p->type<=rr_type) { prev = p; p = p->next; } /* so, prev is smaller, and p is larger than rr_type */ rrset->next = p; if(prev) prev->next = rrset; else node->rrsets = rrset; return rrset; } /** count number (and size) of rrsigs that cover a type */ static size_t rrsig_num_that_cover(struct auth_rrset* rrsig, uint16_t rr_type, size_t* sigsz) { struct packed_rrset_data* d = rrsig->data; size_t i, num = 0; *sigsz = 0; log_assert(d && rrsig->type == LDNS_RR_TYPE_RRSIG); for(i=0; icount+d->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(d->rr_data[i], d->rr_len[i]) == rr_type) { num++; (*sigsz) += d->rr_len[i]; } } return num; } /** See if rrsig set has covered sigs for rrset and move them over */ static int rrset_moveover_rrsigs(struct auth_data* node, uint16_t rr_type, struct auth_rrset* rrset, struct auth_rrset* rrsig) { size_t sigs, sigsz, i, j, total; struct packed_rrset_data* sigold = rrsig->data; struct packed_rrset_data* old = rrset->data; struct packed_rrset_data* d, *sigd; log_assert(rrset->type == rr_type); log_assert(rrsig->type == LDNS_RR_TYPE_RRSIG); sigs = rrsig_num_that_cover(rrsig, rr_type, &sigsz); if(sigs == 0) { /* 0 rrsigs to move over, done */ return 1; } /* allocate rrset sigsz larger for extra sigs elements, and * allocate rrsig sigsz smaller for less sigs elements. */ d = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(old) + sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) + sigsz); if(!d) { log_err("out of memory"); return 0; } /* copy base values */ total = old->count + old->rrsig_count; memcpy(d, old, sizeof(struct packed_rrset_data)); d->rrsig_count += sigs; /* setup rr_len */ d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); if(total != 0) memmove(d->rr_len, old->rr_len, total*sizeof(size_t)); j = d->count+d->rrsig_count-sigs; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) == rr_type) { d->rr_len[j] = sigold->rr_len[i]; j++; } } packed_rrset_ptr_fixup(d); /* copy old values into new array */ if(total != 0) { memmove(d->rr_ttl, old->rr_ttl, total*sizeof(time_t)); /* all the old rr pieces are allocated sequential, so we * can copy them in one go */ memmove(d->rr_data[0], old->rr_data[0], (old->rr_data[total-1] - old->rr_data[0]) + old->rr_len[total-1]); } /* move over the rrsigs to the larger rrset*/ j = d->count+d->rrsig_count-sigs; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) == rr_type) { /* move this one over to location j */ d->rr_ttl[j] = sigold->rr_ttl[i]; memmove(d->rr_data[j], sigold->rr_data[i], sigold->rr_len[i]); if(d->rr_ttl[j] < d->ttl) d->ttl = d->rr_ttl[j]; j++; } } /* put it in and deallocate the old rrset */ rrset->data = d; free(old); /* now make rrsig set smaller */ if(sigold->count+sigold->rrsig_count == sigs) { /* remove all sigs from rrsig, remove it entirely */ domain_remove_rrset(node, LDNS_RR_TYPE_RRSIG); return 1; } log_assert(packed_rrset_sizeof(sigold) > sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) + sigsz); sigd = (struct packed_rrset_data*)calloc(1, packed_rrset_sizeof(sigold) - sigs*(sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t)) - sigsz); if(!sigd) { /* no need to free up d, it has already been placed in the * node->rrset structure */ log_err("out of memory"); return 0; } /* copy base values */ memcpy(sigd, sigold, sizeof(struct packed_rrset_data)); /* in sigd the RRSIGs are stored in the base of the RR, in count */ sigd->count -= sigs; /* setup rr_len */ sigd->rr_len = (size_t*)((uint8_t*)sigd + sizeof(struct packed_rrset_data)); j = 0; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) != rr_type) { sigd->rr_len[j] = sigold->rr_len[i]; j++; } } packed_rrset_ptr_fixup(sigd); /* copy old values into new rrsig array */ j = 0; for(i=0; icount+sigold->rrsig_count; i++) { if(rrsig_rdata_get_type_covered(sigold->rr_data[i], sigold->rr_len[i]) != rr_type) { /* move this one over to location j */ sigd->rr_ttl[j] = sigold->rr_ttl[i]; memmove(sigd->rr_data[j], sigold->rr_data[i], sigold->rr_len[i]); if(j==0) sigd->ttl = sigd->rr_ttl[j]; else { if(sigd->rr_ttl[j] < sigd->ttl) sigd->ttl = sigd->rr_ttl[j]; } j++; } } /* put it in and deallocate the old rrset */ rrsig->data = sigd; free(sigold); return 1; } /** copy the rrsigs from the rrset to the rrsig rrset, because the rrset * is going to be deleted. reallocates the RRSIG rrset data. */ static int rrsigs_copy_from_rrset_to_rrsigset(struct auth_rrset* rrset, struct auth_rrset* rrsigset) { size_t i; if(rrset->data->rrsig_count == 0) return 1; /* move them over one by one, because there might be duplicates, * duplicates are ignored */ for(i=rrset->data->count; idata->count+rrset->data->rrsig_count; i++) { uint8_t* rdata = rrset->data->rr_data[i]; size_t rdatalen = rrset->data->rr_len[i]; time_t rr_ttl = rrset->data->rr_ttl[i]; if(rdata_duplicate(rrsigset->data, rdata, rdatalen)) { continue; } if(!rrset_add_rr(rrsigset, rr_ttl, rdata, rdatalen, 0)) return 0; } return 1; } /** Add rr to node, ignores duplicate RRs, * rdata points to buffer with rdatalen octets, starts with 2bytelength. */ static int az_domain_add_rr(struct auth_data* node, uint16_t rr_type, uint32_t rr_ttl, uint8_t* rdata, size_t rdatalen, int* duplicate) { struct auth_rrset* rrset; /* packed rrsets have their rrsigs along with them, sort them out */ if(rr_type == LDNS_RR_TYPE_RRSIG) { uint16_t ctype = rrsig_rdata_get_type_covered(rdata, rdatalen); if((rrset=az_domain_rrset(node, ctype))!= NULL) { /* a node of the correct type exists, add the RRSIG * to the rrset of the covered data type */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 1)) return 0; } else if((rrset=az_domain_rrset(node, rr_type))!= NULL) { /* add RRSIG to rrset of type RRSIG */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 0)) return 0; } else { /* create rrset of type RRSIG */ if(!rrset_create(node, rr_type, rr_ttl, rdata, rdatalen)) return 0; } } else { /* normal RR type */ if((rrset=az_domain_rrset(node, rr_type))!= NULL) { /* add data to existing node with data type */ if(rdata_duplicate(rrset->data, rdata, rdatalen)) { if(duplicate) *duplicate = 1; return 1; } if(!rrset_add_rr(rrset, rr_ttl, rdata, rdatalen, 0)) return 0; } else { struct auth_rrset* rrsig; /* create new node with data type */ if(!(rrset=rrset_create(node, rr_type, rr_ttl, rdata, rdatalen))) return 0; /* see if node of type RRSIG has signatures that * cover the data type, and move them over */ /* and then make the RRSIG type smaller */ if((rrsig=az_domain_rrset(node, LDNS_RR_TYPE_RRSIG)) != NULL) { if(!rrset_moveover_rrsigs(node, rr_type, rrset, rrsig)) return 0; } } } return 1; } /** insert RR into zone, ignore duplicates */ static int az_insert_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, size_t dname_len, int* duplicate) { struct auth_data* node; uint8_t* dname = rr; uint16_t rr_type = sldns_wirerr_get_type(rr, rr_len, dname_len); uint16_t rr_class = sldns_wirerr_get_class(rr, rr_len, dname_len); uint32_t rr_ttl = sldns_wirerr_get_ttl(rr, rr_len, dname_len); size_t rdatalen = ((size_t)sldns_wirerr_get_rdatalen(rr, rr_len, dname_len))+2; /* rdata points to rdata prefixed with uint16 rdatalength */ uint8_t* rdata = sldns_wirerr_get_rdatawl(rr, rr_len, dname_len); if(rr_class != z->dclass) { log_err("wrong class for RR"); return 0; } if(!(node=az_domain_find_or_create(z, dname, dname_len))) { log_err("cannot create domain"); return 0; } if(!az_domain_add_rr(node, rr_type, rr_ttl, rdata, rdatalen, duplicate)) { log_err("cannot add RR to domain"); return 0; } if(z->rpz) { if(!(rpz_insert_rr(z->rpz, z->name, z->namelen, dname, dname_len, rr_type, rr_class, rr_ttl, rdata, rdatalen, rr, rr_len))) return 0; } return 1; } /** Remove rr from node, ignores nonexisting RRs, * rdata points to buffer with rdatalen octets, starts with 2bytelength. */ static int az_domain_remove_rr(struct auth_data* node, uint16_t rr_type, uint8_t* rdata, size_t rdatalen, int* nonexist) { struct auth_rrset* rrset; size_t index = 0; /* find the plain RR of the given type */ if((rrset=az_domain_rrset(node, rr_type))!= NULL) { if(packed_rrset_find_rr(rrset->data, rdata, rdatalen, &index)) { if(rrset->data->count == 1 && rrset->data->rrsig_count == 0) { /* last RR, delete the rrset */ domain_remove_rrset(node, rr_type); } else if(rrset->data->count == 1 && rrset->data->rrsig_count != 0) { /* move RRSIGs to the RRSIG rrset, or * this one becomes that RRset */ struct auth_rrset* rrsigset = az_domain_rrset( node, LDNS_RR_TYPE_RRSIG); if(rrsigset) { /* move left over rrsigs to the * existing rrset of type RRSIG */ rrsigs_copy_from_rrset_to_rrsigset( rrset, rrsigset); /* and then delete the rrset */ domain_remove_rrset(node, rr_type); } else { /* no rrset of type RRSIG, this * set is now of that type, * just remove the rr */ if(!rrset_remove_rr(rrset, index)) return 0; rrset->type = LDNS_RR_TYPE_RRSIG; rrset->data->count = rrset->data->rrsig_count; rrset->data->rrsig_count = 0; } } else { /* remove the RR from the rrset */ if(!rrset_remove_rr(rrset, index)) return 0; } return 1; } /* rr not found in rrset */ } /* is it a type RRSIG, look under the covered type */ if(rr_type == LDNS_RR_TYPE_RRSIG) { uint16_t ctype = rrsig_rdata_get_type_covered(rdata, rdatalen); if((rrset=az_domain_rrset(node, ctype))!= NULL) { if(az_rrset_find_rrsig(rrset->data, rdata, rdatalen, &index)) { /* rrsig should have d->count > 0, be * over some rr of that type */ /* remove the rrsig from the rrsigs list of the * rrset */ if(!rrset_remove_rr(rrset, index)) return 0; return 1; } } /* also RRSIG not found */ } /* nothing found to delete */ if(nonexist) *nonexist = 1; return 1; } /** remove RR from zone, ignore if it does not exist, false on alloc failure*/ static int az_remove_rr(struct auth_zone* z, uint8_t* rr, size_t rr_len, size_t dname_len, int* nonexist) { struct auth_data* node; uint8_t* dname = rr; uint16_t rr_type = sldns_wirerr_get_type(rr, rr_len, dname_len); uint16_t rr_class = sldns_wirerr_get_class(rr, rr_len, dname_len); size_t rdatalen = ((size_t)sldns_wirerr_get_rdatalen(rr, rr_len, dname_len))+2; /* rdata points to rdata prefixed with uint16 rdatalength */ uint8_t* rdata = sldns_wirerr_get_rdatawl(rr, rr_len, dname_len); if(rr_class != z->dclass) { log_err("wrong class for RR"); /* really also a nonexisting entry, because no records * of that class in the zone, but return an error because * getting records of the wrong class is a failure of the * zone transfer */ return 0; } node = az_find_name(z, dname, dname_len); if(!node) { /* node with that name does not exist */ /* nonexisting entry, because no such name */ *nonexist = 1; return 1; } if(!az_domain_remove_rr(node, rr_type, rdata, rdatalen, nonexist)) { /* alloc failure or so */ return 0; } /* remove the node, if necessary */ /* an rrsets==NULL entry is not kept around for empty nonterminals, * and also parent nodes are not kept around, so we just delete it */ if(node->rrsets == NULL) { (void)rbtree_delete(&z->data, node); auth_data_delete(node); } if(z->rpz) { rpz_remove_rr(z->rpz, z->name, z->namelen, dname, dname_len, rr_type, rr_class, rdata, rdatalen); } return 1; } /** decompress an RR into the buffer where it'll be an uncompressed RR * with uncompressed dname and uncompressed rdata (dnames) */ static int decompress_rr_into_buffer(struct sldns_buffer* buf, uint8_t* pkt, size_t pktlen, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen) { sldns_buffer pktbuf; size_t dname_len = 0; size_t rdlenpos; size_t rdlen; uint8_t* rd; const sldns_rr_descriptor* desc; sldns_buffer_init_frm_data(&pktbuf, pkt, pktlen); sldns_buffer_clear(buf); /* decompress dname */ sldns_buffer_set_position(&pktbuf, (size_t)(dname - sldns_buffer_current(&pktbuf))); dname_len = pkt_dname_len(&pktbuf); if(dname_len == 0) return 0; /* parse fail on dname */ if(!sldns_buffer_available(buf, dname_len)) return 0; dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), dname); sldns_buffer_skip(buf, (ssize_t)dname_len); /* type, class, ttl and rdatalength fields */ if(!sldns_buffer_available(buf, 10)) return 0; sldns_buffer_write_u16(buf, rr_type); sldns_buffer_write_u16(buf, rr_class); sldns_buffer_write_u32(buf, rr_ttl); rdlenpos = sldns_buffer_position(buf); sldns_buffer_write_u16(buf, 0); /* rd length position */ /* decompress rdata */ desc = sldns_rr_descript(rr_type); rd = rr_data; rdlen = rr_rdlen; if(rdlen > 0 && desc && desc->_dname_count > 0) { int count = (int)desc->_dname_count; int rdf = 0; size_t len; /* how much rdata to plain copy */ size_t uncompressed_len, compressed_len; size_t oldpos; /* decompress dnames. */ while(rdlen > 0 && count) { switch(desc->_wireformat[rdf]) { case LDNS_RDF_TYPE_DNAME: sldns_buffer_set_position(&pktbuf, (size_t)(rd - sldns_buffer_begin(&pktbuf))); oldpos = sldns_buffer_position(&pktbuf); /* moves pktbuf to right after the * compressed dname, and returns uncompressed * dname length */ uncompressed_len = pkt_dname_len(&pktbuf); if(!uncompressed_len) return 0; /* parse error in dname */ compressed_len = sldns_buffer_position( &pktbuf) - oldpos; if(compressed_len > rdlen) return 0; /* dname exceeds rdata */ if(!sldns_buffer_available(buf, uncompressed_len)) /* dname too long for buffer */ return 0; dname_pkt_copy(&pktbuf, sldns_buffer_current(buf), rd); sldns_buffer_skip(buf, (ssize_t)uncompressed_len); rd += compressed_len; rdlen -= compressed_len; count--; len = 0; break; case LDNS_RDF_TYPE_STR: /* Check rdlen for resilience, because it is * checked above, that rdlen > 0 */ if(rdlen < 1) return 0; /* malformed */ len = rd[0] + 1; break; default: len = get_rdf_size(desc->_wireformat[rdf]); break; } if(len) { if(len > rdlen) return 0; /* malformed */ if(!sldns_buffer_available(buf, len)) return 0; /* too long for buffer */ sldns_buffer_write(buf, rd, len); rd += len; rdlen -= len; } rdf++; } } /* copy remaining data */ if(rdlen > 0) { if(!sldns_buffer_available(buf, rdlen)) return 0; sldns_buffer_write(buf, rd, rdlen); } /* fixup rdlength */ sldns_buffer_write_u16_at(buf, rdlenpos, sldns_buffer_position(buf)-rdlenpos-2); sldns_buffer_flip(buf); return 1; } /** insert RR into zone, from packet, decompress RR, * if duplicate is nonNULL set the flag but otherwise ignore duplicates */ static int az_insert_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* duplicate) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { log_err("could not decompress RR"); return 0; } rr = sldns_buffer_begin(scratch_buffer); rr_len = sldns_buffer_limit(scratch_buffer); dname_len = dname_valid(rr, rr_len); return az_insert_rr(z, rr, rr_len, dname_len, duplicate); } /** remove RR from zone, from packet, decompress RR, * if nonexist is nonNULL set the flag but otherwise ignore nonexisting entries*/ static int az_remove_rr_decompress(struct auth_zone* z, uint8_t* pkt, size_t pktlen, struct sldns_buffer* scratch_buffer, uint8_t* dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rr_data, uint16_t rr_rdlen, int* nonexist) { uint8_t* rr; size_t rr_len; size_t dname_len; if(!decompress_rr_into_buffer(scratch_buffer, pkt, pktlen, dname, rr_type, rr_class, rr_ttl, rr_data, rr_rdlen)) { log_err("could not decompress RR"); return 0; } rr = sldns_buffer_begin(scratch_buffer); rr_len = sldns_buffer_limit(scratch_buffer); dname_len = dname_valid(rr, rr_len); return az_remove_rr(z, rr, rr_len, dname_len, nonexist); } /** * Parse zonefile * @param z: zone to read in. * @param in: file to read from (just opened). * @param rr: buffer to use for RRs, 64k. * passed so that recursive includes can use the same buffer and do * not grow the stack too much. * @param rrbuflen: sizeof rr buffer. * @param state: parse state with $ORIGIN, $TTL and 'prev-dname' and so on, * that is kept between includes. * The lineno is set at 1 and then increased by the function. * @param fname: file name. * @param depth: recursion depth for includes * @param cfg: config for chroot. * returns false on failure, has printed an error message */ static int az_parse_file(struct auth_zone* z, FILE* in, uint8_t* rr, size_t rrbuflen, struct sldns_file_parse_state* state, char* fname, int depth, struct config_file* cfg) { size_t rr_len, dname_len; int status; state->lineno = 1; while(!feof(in)) { rr_len = rrbuflen; dname_len = 0; status = sldns_fp2wire_rr_buf(in, rr, &rr_len, &dname_len, state); if(status == LDNS_WIREPARSE_ERR_INCLUDE && rr_len == 0) { /* we have $INCLUDE or $something */ if(strncmp((char*)rr, "$INCLUDE ", 9) == 0 || strncmp((char*)rr, "$INCLUDE\t", 9) == 0) { FILE* inc; int lineno_orig = state->lineno; char* incfile = (char*)rr + 8; if(depth > MAX_INCLUDE_DEPTH) { log_err("%s:%d max include depth" "exceeded", fname, state->lineno); return 0; } /* skip spaces */ while(*incfile == ' ' || *incfile == '\t') incfile++; /* adjust for chroot on include file */ if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(incfile, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) incfile += strlen(cfg->chrootdir); incfile = strdup(incfile); if(!incfile) { log_err("malloc failure"); return 0; } verbose(VERB_ALGO, "opening $INCLUDE %s", incfile); inc = fopen(incfile, "r"); if(!inc) { log_err("%s:%d cannot open include " "file %s: %s", fname, lineno_orig, incfile, strerror(errno)); free(incfile); return 0; } /* recurse read that file now */ if(!az_parse_file(z, inc, rr, rrbuflen, state, incfile, depth+1, cfg)) { log_err("%s:%d cannot parse include " "file %s", fname, lineno_orig, incfile); fclose(inc); free(incfile); return 0; } fclose(inc); verbose(VERB_ALGO, "done with $INCLUDE %s", incfile); free(incfile); state->lineno = lineno_orig; } continue; } if(status != 0) { log_err("parse error %s %d:%d: %s", fname, state->lineno, LDNS_WIREPARSE_OFFSET(status), sldns_get_errorstr_parse(status)); return 0; } if(rr_len == 0) { /* EMPTY line, TTL or ORIGIN */ continue; } /* insert wirerr in rrbuf */ if(!az_insert_rr(z, rr, rr_len, dname_len, NULL)) { char buf[17]; sldns_wire2str_type_buf(sldns_wirerr_get_type(rr, rr_len, dname_len), buf, sizeof(buf)); log_err("%s:%d cannot insert RR of type %s", fname, state->lineno, buf); return 0; } } return 1; } int auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg) { uint8_t rr[LDNS_RR_BUF_SIZE]; struct sldns_file_parse_state state; char* zfilename; FILE* in; if(!z || !z->zonefile || z->zonefile[0]==0) return 1; /* no file, or "", nothing to read */ zfilename = z->zonefile; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(zfilename, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { char nm[LDNS_MAX_DOMAINLEN]; dname_str(z->name, nm); verbose(VERB_ALGO, "read zonefile %s for %s", zfilename, nm); } in = fopen(zfilename, "r"); if(!in) { char* n = sldns_wire2str_dname(z->name, z->namelen); if(z->zone_is_slave && errno == ENOENT) { /* we fetch the zone contents later, no file yet */ verbose(VERB_ALGO, "no zonefile %s for %s", zfilename, n?n:"error"); free(n); return 1; } log_err("cannot open zonefile %s for %s: %s", zfilename, n?n:"error", strerror(errno)); free(n); return 0; } /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); memset(&state, 0, sizeof(state)); /* default TTL to 3600 */ state.default_ttl = 3600; /* set $ORIGIN to the zone name */ if(z->namelen <= sizeof(state.origin)) { memcpy(state.origin, z->name, z->namelen); state.origin_len = z->namelen; } /* parse the (toplevel) file */ if(!az_parse_file(z, in, rr, sizeof(rr), &state, zfilename, 0, cfg)) { char* n = sldns_wire2str_dname(z->name, z->namelen); log_err("error parsing zonefile %s for %s", zfilename, n?n:"error"); free(n); fclose(in); return 0; } fclose(in); if(z->rpz) rpz_finish_config(z->rpz); return 1; } /** write buffer to file and check return codes */ static int write_out(FILE* out, const char* str, size_t len) { size_t r; if(len == 0) return 1; r = fwrite(str, 1, len, out); if(r == 0) { log_err("write failed: %s", strerror(errno)); return 0; } else if(r < len) { log_err("write failed: too short (disk full?)"); return 0; } return 1; } /** convert auth rr to string */ static int auth_rr_to_string(uint8_t* nm, size_t nmlen, uint16_t tp, uint16_t cl, struct packed_rrset_data* data, size_t i, char* s, size_t buflen) { int w = 0; size_t slen = buflen, datlen; uint8_t* dat; if(i >= data->count) tp = LDNS_RR_TYPE_RRSIG; dat = nm; datlen = nmlen; w += sldns_wire2str_dname_scan(&dat, &datlen, &s, &slen, NULL, 0, NULL); w += sldns_str_print(&s, &slen, "\t"); w += sldns_str_print(&s, &slen, "%lu\t", (unsigned long)data->rr_ttl[i]); w += sldns_wire2str_class_print(&s, &slen, cl); w += sldns_str_print(&s, &slen, "\t"); w += sldns_wire2str_type_print(&s, &slen, tp); w += sldns_str_print(&s, &slen, "\t"); datlen = data->rr_len[i]-2; dat = data->rr_data[i]+2; w += sldns_wire2str_rdata_scan(&dat, &datlen, &s, &slen, tp, NULL, 0, NULL); if(tp == LDNS_RR_TYPE_DNSKEY) { w += sldns_str_print(&s, &slen, " ;{id = %u}", sldns_calc_keytag_raw(data->rr_data[i]+2, data->rr_len[i]-2)); } w += sldns_str_print(&s, &slen, "\n"); if(w >= (int)buflen) { log_nametypeclass(NO_VERBOSE, "RR too long to print", nm, tp, cl); return 0; } return 1; } /** write rrset to file */ static int auth_zone_write_rrset(struct auth_zone* z, struct auth_data* node, struct auth_rrset* r, FILE* out) { size_t i, count = r->data->count + r->data->rrsig_count; char buf[LDNS_RR_BUF_SIZE]; for(i=0; iname, node->namelen, r->type, z->dclass, r->data, i, buf, sizeof(buf))) { verbose(VERB_ALGO, "failed to rr2str rr %d", (int)i); continue; } if(!write_out(out, buf, strlen(buf))) return 0; } return 1; } /** write domain to file */ static int auth_zone_write_domain(struct auth_zone* z, struct auth_data* n, FILE* out) { struct auth_rrset* r; /* if this is zone apex, write SOA first */ if(z->namelen == n->namelen) { struct auth_rrset* soa = az_domain_rrset(n, LDNS_RR_TYPE_SOA); if(soa) { if(!auth_zone_write_rrset(z, n, soa, out)) return 0; } } /* write all the RRsets for this domain */ for(r = n->rrsets; r; r = r->next) { if(z->namelen == n->namelen && r->type == LDNS_RR_TYPE_SOA) continue; /* skip SOA here */ if(!auth_zone_write_rrset(z, n, r, out)) return 0; } return 1; } int auth_zone_write_file(struct auth_zone* z, const char* fname) { FILE* out; struct auth_data* n; out = fopen(fname, "w"); if(!out) { log_err("could not open %s: %s", fname, strerror(errno)); return 0; } RBTREE_FOR(n, struct auth_data*, &z->data) { if(!auth_zone_write_domain(z, n, out)) { log_err("could not write domain to %s", fname); fclose(out); return 0; } } fclose(out); return 1; } /** offline verify for zonemd, while reading a zone file to immediately * spot bad hashes in zonefile as they are read. * Creates temp buffers, but uses anchors and validation environment * from the module_env. */ static void zonemd_offline_verify(struct auth_zone* z, struct module_env* env_for_val, struct module_stack* mods) { struct module_env env; time_t now = 0; if(!z->zonemd_check) return; env = *env_for_val; env.scratch_buffer = sldns_buffer_new(env.cfg->msg_buffer_size); if(!env.scratch_buffer) { log_err("out of memory"); goto clean_exit; } env.scratch = regional_create(); if(!env.now) { env.now = &now; now = time(NULL); } if(!env.scratch) { log_err("out of memory"); goto clean_exit; } auth_zone_verify_zonemd(z, &env, mods, NULL, 1, 0); clean_exit: /* clean up and exit */ sldns_buffer_free(env.scratch_buffer); regional_destroy(env.scratch); } /** read all auth zones from file (if they have) */ static int auth_zones_read_zones(struct auth_zones* az, struct config_file* cfg, struct module_env* env, struct module_stack* mods) { struct auth_zone* z; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(!auth_zone_read_zonefile(z, cfg)) { lock_rw_unlock(&z->lock); lock_rw_unlock(&az->lock); return 0; } if(z->zonefile && z->zonefile[0]!=0 && env) zonemd_offline_verify(z, env, mods); lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); return 1; } /** fetch the content of a ZONEMD RR from the rdata */ static int zonemd_fetch_parameters(struct auth_rrset* zonemd_rrset, size_t i, uint32_t* serial, int* scheme, int* hashalgo, uint8_t** hash, size_t* hashlen) { size_t rr_len; uint8_t* rdata; if(i >= zonemd_rrset->data->count) return 0; rr_len = zonemd_rrset->data->rr_len[i]; if(rr_len < 2+4+1+1) return 0; /* too short, for rdlen+serial+scheme+algo */ rdata = zonemd_rrset->data->rr_data[i]; *serial = sldns_read_uint32(rdata+2); *scheme = rdata[6]; *hashalgo = rdata[7]; *hashlen = rr_len - 8; if(*hashlen == 0) *hash = NULL; else *hash = rdata+8; return 1; } /** * See if the ZONEMD scheme, hash occurs more than once. * @param zonemd_rrset: the zonemd rrset to check with the RRs in it. * @param index: index of the original, this is allowed to have that * scheme and hashalgo, but other RRs should not have it. * @param scheme: the scheme to check for. * @param hashalgo: the hash algorithm to check for. * @return true if it occurs more than once. */ static int zonemd_is_duplicate_scheme_hash(struct auth_rrset* zonemd_rrset, size_t index, int scheme, int hashalgo) { size_t j; for(j=0; jdata->count; j++) { uint32_t serial2 = 0; int scheme2 = 0, hashalgo2 = 0; uint8_t* hash2 = NULL; size_t hashlen2 = 0; if(index == j) { /* this is the original */ continue; } if(!zonemd_fetch_parameters(zonemd_rrset, j, &serial2, &scheme2, &hashalgo2, &hash2, &hashlen2)) { /* malformed, skip it */ continue; } if(scheme == scheme2 && hashalgo == hashalgo2) { /* duplicate scheme, hash */ verbose(VERB_ALGO, "zonemd duplicate for scheme %d " "and hash %d", scheme, hashalgo); return 1; } } return 0; } /** * Check ZONEMDs if present for the auth zone. Depending on config * it can warn or fail on that. Checks the hash of the ZONEMD. * @param z: auth zone to check for. * caller must hold lock on zone. * @param env: module env for temp buffers. * @param reason: returned on failure. * @return false on failure, true if hash checks out. */ static int auth_zone_zonemd_check_hash(struct auth_zone* z, struct module_env* env, char** reason) { /* loop over ZONEMDs and see which one is valid. if not print * failure (depending on config) */ struct auth_data* apex; struct auth_rrset* zonemd_rrset; size_t i; struct regional* region = NULL; struct sldns_buffer* buf = NULL; uint32_t soa_serial = 0; char* unsupported_reason = NULL; int only_unsupported = 1; region = env->scratch; regional_free_all(region); buf = env->scratch_buffer; if(!auth_zone_get_serial(z, &soa_serial)) { *reason = "zone has no SOA serial"; return 0; } apex = az_find_name(z, z->name, z->namelen); if(!apex) { *reason = "zone has no apex"; return 0; } zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); if(!zonemd_rrset || zonemd_rrset->data->count==0) { *reason = "zone has no ZONEMD"; return 0; /* no RRset or no RRs in rrset */ } /* we have a ZONEMD, check if it is correct */ for(i=0; idata->count; i++) { uint32_t serial = 0; int scheme = 0, hashalgo = 0; uint8_t* hash = NULL; size_t hashlen = 0; if(!zonemd_fetch_parameters(zonemd_rrset, i, &serial, &scheme, &hashalgo, &hash, &hashlen)) { /* malformed RR */ *reason = "ZONEMD rdata malformed"; only_unsupported = 0; continue; } /* check for duplicates */ if(zonemd_is_duplicate_scheme_hash(zonemd_rrset, i, scheme, hashalgo)) { /* duplicate hash of the same scheme,hash * is not allowed. */ *reason = "ZONEMD RRSet contains more than one RR " "with the same scheme and hash algorithm"; only_unsupported = 0; continue; } regional_free_all(region); if(serial != soa_serial) { *reason = "ZONEMD serial is wrong"; only_unsupported = 0; continue; } *reason = NULL; if(auth_zone_generate_zonemd_check(z, scheme, hashalgo, hash, hashlen, region, buf, reason)) { /* success */ if(*reason) { if(!unsupported_reason) unsupported_reason = *reason; /* continue to check for valid ZONEMD */ if(verbosity >= VERB_ALGO) { char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD %d %d is unsupported: %s", zstr, (int)scheme, (int)hashalgo, *reason); } *reason = NULL; continue; } if(verbosity >= VERB_ALGO) { char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); if(!*reason) verbose(VERB_ALGO, "auth-zone %s ZONEMD hash is correct", zstr); } return 1; } only_unsupported = 0; /* try next one */ } /* have we seen no failures but only unsupported algo, * and one unsupported algorithm, or more. */ if(only_unsupported && unsupported_reason) { /* only unsupported algorithms, with valid serial, not * malformed. Did not see supported algorithms, failed or * successful ones. */ *reason = unsupported_reason; return 1; } /* fail, we may have reason */ if(!*reason) *reason = "no ZONEMD records found"; if(verbosity >= VERB_ALGO) { char zstr[LDNS_MAX_DOMAINLEN]; dname_str(z->name, zstr); verbose(VERB_ALGO, "auth-zone %s ZONEMD failed: %s", zstr, *reason); } return 0; } /** find the apex SOA RRset, if it exists */ struct auth_rrset* auth_zone_get_soa_rrset(struct auth_zone* z) { struct auth_data* apex; struct auth_rrset* soa; apex = az_find_name(z, z->name, z->namelen); if(!apex) return NULL; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); return soa; } /** find serial number of zone or false if none */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial) { struct auth_data* apex; struct auth_rrset* soa; struct packed_rrset_data* d; size_t primlen, mboxlen; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa || soa->data->count==0) return 0; /* no RRset or no RRs in rrset */ if(soa->data->rr_len[0] < 2+4*5) return 0; /* SOA too short */ if((primlen = dname_valid(soa->data->rr_data[0]+2, soa->data->rr_len[0]-2)) == 0) return 0; /* primary dname malformed */ if((mboxlen = dname_valid(soa->data->rr_data[0]+2+primlen, soa->data->rr_len[0]-2-primlen)) == 0) return 0; /* mailbox dname malformed */ if(2+primlen+mboxlen+4*5 != soa->data->rr_len[0]) return 0; /* rdata malformed */ d = soa->data; *serial = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-20)); return 1; } /** Find auth_zone SOA and populate the values in xfr(soa values). */ int xfr_find_soa(struct auth_zone* z, struct auth_xfer* xfr) { struct auth_data* apex; struct auth_rrset* soa; struct packed_rrset_data* d; size_t primlen, mboxlen; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa || soa->data->count==0) return 0; /* no RRset or no RRs in rrset */ if(soa->data->rr_len[0] < 2+4*5) return 0; /* SOA too short */ if((primlen = dname_valid(soa->data->rr_data[0]+2, soa->data->rr_len[0]-2)) == 0) return 0; /* primary dname malformed */ if((mboxlen = dname_valid(soa->data->rr_data[0]+2+primlen, soa->data->rr_len[0]-2-primlen)) == 0) return 0; /* mailbox dname malformed */ if(2+primlen+mboxlen+4*5 != soa->data->rr_len[0]) return 0; /* rdata malformed */ /* SOA record ends with serial, refresh, retry, expiry, minimum, * as 4 byte fields */ d = soa->data; xfr->have_zone = 1; xfr->serial = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-20)); xfr->refresh = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-16)); xfr->retry = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-12)); xfr->expiry = sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-8)); /* soa minimum at d->rr_len[0]-4 */ return 1; } /** * Setup auth_xfer zone * This populates the have_zone, soa values, and so on times. * Doesn't do network traffic yet, can set option flags. * @param z: locked by caller, and modified for setup * @param x: locked by caller, and modified. * @return false on failure. */ static int auth_xfer_setup(struct auth_zone* z, struct auth_xfer* x) { /* for a zone without zone transfers, x==NULL, so skip them, * i.e. the zone config is fixed with no masters or urls */ if(!z || !x) return 1; if(!xfr_find_soa(z, x)) { return 1; } /* nothing for probe, nextprobe and transfer tasks */ return 1; } /** * Setup all zones * @param az: auth zones structure * @return false on failure. */ static int auth_zones_setup_zones(struct auth_zones* az) { struct auth_zone* z; struct auth_xfer* x; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); x = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(x) { lock_basic_lock(&x->lock); } if(!auth_xfer_setup(z, x)) { if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); lock_rw_unlock(&az->lock); return 0; } if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); return 1; } /** set config items and create zones */ static int auth_zones_cfg(struct auth_zones* az, struct config_auth* c) { struct auth_zone* z; struct auth_xfer* x = NULL; /* create zone */ if(c->isrpz) { /* if the rpz lock is needed, grab it before the other * locks to avoid a lock dependency cycle */ lock_rw_wrlock(&az->rpz_lock); } lock_rw_wrlock(&az->lock); if(!(z=auth_zones_find_or_add_zone(az, c->name))) { lock_rw_unlock(&az->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } if(c->masters || c->urls) { if(!(x=auth_zones_find_or_add_xfer(az, z))) { lock_rw_unlock(&az->lock); lock_rw_unlock(&z->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } } if(c->for_downstream) az->have_downstream = 1; lock_rw_unlock(&az->lock); /* set options */ z->zone_deleted = 0; if(!auth_zone_set_zonefile(z, c->zonefile)) { if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } return 0; } z->for_downstream = c->for_downstream; z->for_upstream = c->for_upstream; z->fallback_enabled = c->fallback_enabled; z->zonemd_check = c->zonemd_check; z->zonemd_reject_absence = c->zonemd_reject_absence; if(c->isrpz && !z->rpz){ if(!(z->rpz = rpz_create(c))){ fatal_exit("Could not setup RPZ zones"); return 0; } lock_protect(&z->lock, &z->rpz->local_zones, sizeof(*z->rpz)); /* the az->rpz_lock is locked above */ z->rpz_az_next = az->rpz_first; if(az->rpz_first) az->rpz_first->rpz_az_prev = z; az->rpz_first = z; } else if(c->isrpz && z->rpz) { if(!rpz_config(z->rpz, c)) { log_err("Could not change rpz config"); if(x) { lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); lock_rw_unlock(&az->rpz_lock); return 0; } } if(c->isrpz) { lock_rw_unlock(&az->rpz_lock); } /* xfer zone */ if(x) { z->zone_is_slave = 1; /* set options on xfer zone */ if(!xfer_set_masters(&x->task_probe->masters, c, 0)) { lock_basic_unlock(&x->lock); lock_rw_unlock(&z->lock); return 0; } if(!xfer_set_masters(&x->task_transfer->masters, c, 1)) { lock_basic_unlock(&x->lock); lock_rw_unlock(&z->lock); return 0; } lock_basic_unlock(&x->lock); } lock_rw_unlock(&z->lock); return 1; } /** set all auth zones deleted, then in auth_zones_cfg, it marks them * as nondeleted (if they are still in the config), and then later * we can find deleted zones */ static void az_setall_deleted(struct auth_zones* az) { struct auth_zone* z; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); z->zone_deleted = 1; lock_rw_unlock(&z->lock); } lock_rw_unlock(&az->lock); } /** find zones that are marked deleted and delete them. * This is called from apply_cfg, and there are no threads and no * workers, so the xfr can just be deleted. */ static void az_delete_deleted_zones(struct auth_zones* az) { struct auth_zone* z; struct auth_zone* delete_list = NULL, *next; struct auth_xfer* xfr; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(z->zone_deleted) { /* we cannot alter the rbtree right now, but * we can put it on a linked list and then * delete it */ z->delete_next = delete_list; delete_list = z; } lock_rw_unlock(&z->lock); } /* now we are out of the tree loop and we can loop and delete * the zones */ z = delete_list; while(z) { next = z->delete_next; xfr = auth_xfer_find(az, z->name, z->namelen, z->dclass); if(xfr) { (void)rbtree_delete(&az->xtree, &xfr->node); auth_xfer_delete(xfr); } (void)rbtree_delete(&az->ztree, &z->node); auth_zone_delete(z, az); z = next; } lock_rw_unlock(&az->lock); } int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, int setup, int* is_rpz, struct module_env* env, struct module_stack* mods) { struct config_auth* p; az_setall_deleted(az); for(p = cfg->auths; p; p = p->next) { if(!p->name || p->name[0] == 0) { log_warn("auth-zone without a name, skipped"); continue; } *is_rpz = (*is_rpz || p->isrpz); if(!auth_zones_cfg(az, p)) { log_err("cannot config auth zone %s", p->name); return 0; } } az_delete_deleted_zones(az); if(!auth_zones_read_zones(az, cfg, env, mods)) return 0; if(setup) { if(!auth_zones_setup_zones(az)) return 0; } return 1; } /** delete chunks * @param at: transfer structure with chunks list. The chunks and their * data are freed. */ static void auth_chunks_delete(struct auth_transfer* at) { if(at->chunks_first) { struct auth_chunk* c, *cn; c = at->chunks_first; while(c) { cn = c->next; free(c->data); free(c); c = cn; } } at->chunks_first = NULL; at->chunks_last = NULL; } /** free master addr list */ static void auth_free_master_addrs(struct auth_addr* list) { struct auth_addr *n; while(list) { n = list->next; free(list); list = n; } } /** free the masters list */ static void auth_free_masters(struct auth_master* list) { struct auth_master* n; while(list) { n = list->next; auth_free_master_addrs(list->list); free(list->host); free(list->file); free(list); list = n; } } void auth_xfer_delete(struct auth_xfer* xfr) { if(!xfr) return; lock_basic_destroy(&xfr->lock); free(xfr->name); if(xfr->task_nextprobe) { comm_timer_delete(xfr->task_nextprobe->timer); free(xfr->task_nextprobe); } if(xfr->task_probe) { auth_free_masters(xfr->task_probe->masters); comm_point_delete(xfr->task_probe->cp); comm_timer_delete(xfr->task_probe->timer); free(xfr->task_probe); } if(xfr->task_transfer) { auth_free_masters(xfr->task_transfer->masters); comm_point_delete(xfr->task_transfer->cp); comm_timer_delete(xfr->task_transfer->timer); if(xfr->task_transfer->chunks_first) { auth_chunks_delete(xfr->task_transfer); } free(xfr->task_transfer); } auth_free_masters(xfr->allow_notify_list); free(xfr); } /** helper traverse to delete zones */ static void auth_zone_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_zone* z = (struct auth_zone*)n->key; auth_zone_delete(z, NULL); } /** helper traverse to delete xfer zones */ static void auth_xfer_del(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct auth_xfer* z = (struct auth_xfer*)n->key; auth_xfer_delete(z); } void auth_zones_delete(struct auth_zones* az) { if(!az) return; lock_rw_destroy(&az->lock); lock_rw_destroy(&az->rpz_lock); traverse_postorder(&az->ztree, auth_zone_del, NULL); traverse_postorder(&az->xtree, auth_xfer_del, NULL); free(az); } /** true if domain has only nsec3 */ static int domain_has_only_nsec3(struct auth_data* n) { struct auth_rrset* rrset = n->rrsets; int nsec3_seen = 0; while(rrset) { if(rrset->type == LDNS_RR_TYPE_NSEC3) { nsec3_seen = 1; } else if(rrset->type != LDNS_RR_TYPE_RRSIG) { return 0; } rrset = rrset->next; } return nsec3_seen; } /** see if the domain has a wildcard child '*.domain' */ static struct auth_data* az_find_wildcard_domain(struct auth_zone* z, uint8_t* nm, size_t nmlen) { uint8_t wc[LDNS_MAX_DOMAINLEN]; if(nmlen+2 > sizeof(wc)) return NULL; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, nm, nmlen); return az_find_name(z, wc, nmlen+2); } /** find wildcard between qname and cename */ static struct auth_data* az_find_wildcard(struct auth_zone* z, struct query_info* qinfo, struct auth_data* ce) { uint8_t* nm = qinfo->qname; size_t nmlen = qinfo->qname_len; struct auth_data* node; if(!dname_subdomain_c(nm, z->name)) return NULL; /* out of zone */ while((node=az_find_wildcard_domain(z, nm, nmlen))==NULL) { if(nmlen == z->namelen) return NULL; /* top of zone reached */ if(ce && nmlen == ce->namelen) return NULL; /* ce reached */ if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) return NULL; /* can't go up */ } return node; } /** domain is not exact, find first candidate ce (name that matches * a part of qname) in tree */ static struct auth_data* az_find_candidate_ce(struct auth_zone* z, struct query_info* qinfo, struct auth_data* n) { uint8_t* nm; size_t nmlen; if(n) { nm = dname_get_shared_topdomain(qinfo->qname, n->name); } else { nm = qinfo->qname; } dname_count_size_labels(nm, &nmlen); n = az_find_name(z, nm, nmlen); /* delete labels and go up on name */ while(!n) { if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) return NULL; /* can't go up */ n = az_find_name(z, nm, nmlen); } return n; } /** go up the auth tree to next existing name. */ static struct auth_data* az_domain_go_up(struct auth_zone* z, struct auth_data* n) { uint8_t* nm = n->name; size_t nmlen = n->namelen; while(dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) { if((n=az_find_name(z, nm, nmlen)) != NULL) return n; } return NULL; } /** Find the closest encloser, an name that exists and is above the * qname. * return true if the node (param node) is existing, nonobscured and * can be used to generate answers from. It is then also node_exact. * returns false if the node is not good enough (or it wasn't node_exact) * in this case the ce can be filled. * if ce is NULL, no ce exists, and likely the zone is completely empty, * not even with a zone apex. * if ce is nonNULL it is the closest enclosing upper name (that exists * itself for answer purposes). That name may have DNAME, NS or wildcard * rrset is the closest DNAME or NS rrset that was found. */ static int az_find_ce(struct auth_zone* z, struct query_info* qinfo, struct auth_data* node, int node_exact, struct auth_data** ce, struct auth_rrset** rrset) { struct auth_data* n = node; struct auth_rrset* lookrrset; *ce = NULL; *rrset = NULL; if(!node_exact) { /* if not exact, lookup closest exact match */ n = az_find_candidate_ce(z, qinfo, n); } else { /* if exact, the node itself is the first candidate ce */ *ce = n; } /* no direct answer from nsec3-only domains */ if(n && domain_has_only_nsec3(n)) { node_exact = 0; *ce = NULL; } /* with exact matches, walk up the labels until we find the * delegation, or DNAME or zone end */ while(n) { /* see if the current candidate has issues */ /* not zone apex and has type NS */ if(n->namelen != z->namelen && (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_NS)) && /* delegate here, but DS at exact the dp has notype */ (qinfo->qtype != LDNS_RR_TYPE_DS || n->namelen != qinfo->qname_len)) { /* referral */ /* this is ce and the lowernode is nonexisting */ *ce = n; *rrset = lookrrset; node_exact = 0; } /* not equal to qname and has type DNAME */ if(n->namelen != qinfo->qname_len && (lookrrset=az_domain_rrset(n, LDNS_RR_TYPE_DNAME))) { /* this is ce and the lowernode is nonexisting */ *ce = n; *rrset = lookrrset; node_exact = 0; } if(*ce == NULL && !domain_has_only_nsec3(n)) { /* if not found yet, this exact name must be * our lowest match (but not nsec3onlydomain) */ *ce = n; } /* walk up the tree by removing labels from name and lookup */ n = az_domain_go_up(z, n); } /* found no problems, if it was an exact node, it is fine to use */ return node_exact; } /** add additional A/AAAA from domain names in rrset rdata (+offset) * offset is number of bytes in rdata where the dname is located. */ static int az_add_additionals_from(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_rrset* rrset, size_t offset) { struct packed_rrset_data* d = rrset->data; size_t i; if(!d) return 0; for(i=0; icount; i++) { size_t dlen; struct auth_data* domain; struct auth_rrset* ref; if(d->rr_len[i] < 2+offset) continue; /* too short */ if(!(dlen = dname_valid(d->rr_data[i]+2+offset, d->rr_len[i]-2-offset))) continue; /* malformed */ domain = az_find_name(z, d->rr_data[i]+2+offset, dlen); if(!domain) continue; if((ref=az_domain_rrset(domain, LDNS_RR_TYPE_A)) != NULL) { if(!msg_add_rrset_ar(z, region, msg, domain, ref)) return 0; } if((ref=az_domain_rrset(domain, LDNS_RR_TYPE_AAAA)) != NULL) { if(!msg_add_rrset_ar(z, region, msg, domain, ref)) return 0; } } return 1; } /** add negative SOA record (with negative TTL) */ static int az_add_negative_soa(struct auth_zone* z, struct regional* region, struct dns_msg* msg) { time_t minimum; size_t i; struct packed_rrset_data* d; struct auth_rrset* soa; struct auth_data* apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa) return 0; /* must be first to put in message; we want to fix the TTL with * one RRset here, otherwise we'd need to loop over the RRs to get * the resulting lower TTL */ log_assert(msg->rep->rrset_count == 0); if(!msg_add_rrset_ns(z, region, msg, apex, soa)) return 0; /* fixup TTL */ d = (struct packed_rrset_data*)msg->rep->rrsets[msg->rep->rrset_count-1]->entry.data; /* last 4 bytes are minimum ttl in network format */ if(d->count == 0) return 0; if(d->rr_len[0] < 2+4) return 0; minimum = (time_t)sldns_read_uint32(d->rr_data[0]+(d->rr_len[0]-4)); minimum = d->ttlttl:minimum; d->ttl = minimum; for(i=0; i < d->count + d->rrsig_count; i++) d->rr_ttl[i] = minimum; msg->rep->ttl = get_rrset_ttl(msg->rep->rrsets[0]); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; return 1; } /** See if the query goes to empty nonterminal (that has no auth_data, * but there are nodes underneath. We already checked that there are * not NS, or DNAME above, so that we only need to check if some node * exists below (with nonempty rr list), return true if emptynonterminal */ static int az_empty_nonterminal(struct auth_zone* z, struct query_info* qinfo, struct auth_data* node) { struct auth_data* next; if(!node) { /* no smaller was found, use first (smallest) node as the * next one */ next = (struct auth_data*)rbtree_first(&z->data); } else { next = (struct auth_data*)rbtree_next(&node->node); } while(next && (rbnode_type*)next != RBTREE_NULL && next->rrsets == NULL) { /* the next name has empty rrsets, is an empty nonterminal * itself, see if there exists something below it */ next = (struct auth_data*)rbtree_next(&node->node); } if((rbnode_type*)next == RBTREE_NULL || !next) { /* there is no next node, so something below it cannot * exist */ return 0; } /* a next node exists, if there was something below the query, * this node has to be it. See if it is below the query name */ if(dname_strict_subdomain_c(next->name, qinfo->qname)) return 1; return 0; } /** create synth cname target name in buffer, or fail if too long */ static size_t synth_cname_buf(uint8_t* qname, size_t qname_len, size_t dname_len, uint8_t* dtarg, size_t dtarglen, uint8_t* buf, size_t buflen) { size_t newlen = qname_len + dtarglen - dname_len; if(newlen > buflen) { /* YXDOMAIN error */ return 0; } /* new name is concatenation of qname front (without DNAME owner) * and DNAME target name */ memcpy(buf, qname, qname_len-dname_len); memmove(buf+(qname_len-dname_len), dtarg, dtarglen); return newlen; } /** create synthetic CNAME rrset for in a DNAME answer in region, * false on alloc failure, cname==NULL when name too long. */ static int create_synth_cname(uint8_t* qname, size_t qname_len, struct regional* region, struct auth_data* node, struct auth_rrset* dname, uint16_t dclass, struct ub_packed_rrset_key** cname) { uint8_t buf[LDNS_MAX_DOMAINLEN]; uint8_t* dtarg; size_t dtarglen, newlen; struct packed_rrset_data* d; /* get DNAME target name */ if(dname->data->count < 1) return 0; if(dname->data->rr_len[0] < 3) return 0; /* at least rdatalen +1 */ dtarg = dname->data->rr_data[0]+2; dtarglen = dname->data->rr_len[0]-2; if(sldns_read_uint16(dname->data->rr_data[0]) != dtarglen) return 0; /* rdatalen in DNAME rdata is malformed */ if(dname_valid(dtarg, dtarglen) != dtarglen) return 0; /* DNAME RR has malformed rdata */ if(qname_len == 0) return 0; /* too short */ if(qname_len <= node->namelen) return 0; /* qname too short for dname removal */ /* synthesize a CNAME */ newlen = synth_cname_buf(qname, qname_len, node->namelen, dtarg, dtarglen, buf, sizeof(buf)); if(newlen == 0) { /* YXDOMAIN error */ *cname = NULL; return 1; } *cname = (struct ub_packed_rrset_key*)regional_alloc(region, sizeof(struct ub_packed_rrset_key)); if(!*cname) return 0; /* out of memory */ memset(&(*cname)->entry, 0, sizeof((*cname)->entry)); (*cname)->entry.key = (*cname); (*cname)->rk.type = htons(LDNS_RR_TYPE_CNAME); (*cname)->rk.rrset_class = htons(dclass); (*cname)->rk.flags = 0; (*cname)->rk.dname = regional_alloc_init(region, qname, qname_len); if(!(*cname)->rk.dname) return 0; /* out of memory */ (*cname)->rk.dname_len = qname_len; (*cname)->entry.hash = rrset_key_hash(&(*cname)->rk); d = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t) + newlen); if(!d) return 0; /* out of memory */ (*cname)->entry.data = d; d->ttl = dname->data->ttl; /* RFC6672: synth CNAME TTL == DNAME TTL */ d->count = 1; d->rrsig_count = 0; d->trust = rrset_trust_ans_noAA; d->rr_len = (size_t*)((uint8_t*)d + sizeof(struct packed_rrset_data)); d->rr_len[0] = newlen + sizeof(uint16_t); packed_rrset_ptr_fixup(d); d->rr_ttl[0] = d->ttl; sldns_write_uint16(d->rr_data[0], newlen); memmove(d->rr_data[0] + sizeof(uint16_t), buf, newlen); return 1; } /** add a synthesized CNAME to the answer section */ static int add_synth_cname(struct auth_zone* z, uint8_t* qname, size_t qname_len, struct regional* region, struct dns_msg* msg, struct auth_data* dname, struct auth_rrset* rrset) { struct ub_packed_rrset_key* cname; /* synthesize a CNAME */ if(!create_synth_cname(qname, qname_len, region, dname, rrset, z->dclass, &cname)) { /* out of memory */ return 0; } if(!cname) { /* cname cannot be create because of YXDOMAIN */ msg->rep->flags |= LDNS_RCODE_YXDOMAIN; return 1; } /* add cname to message */ if(!msg_grow_array(region, msg)) return 0; msg->rep->rrsets[msg->rep->rrset_count] = cname; msg->rep->rrset_count++; msg->rep->an_numrrsets++; msg_ttl(msg); return 1; } /** Change a dname to a different one, for wildcard namechange */ static void az_change_dnames(struct dns_msg* msg, uint8_t* oldname, uint8_t* newname, size_t newlen, int an_only) { size_t i; size_t start = 0, end = msg->rep->rrset_count; if(!an_only) start = msg->rep->an_numrrsets; if(an_only) end = msg->rep->an_numrrsets; for(i=start; irep->rrsets[i]->rk.dname, oldname) == 0) { msg->rep->rrsets[i]->rk.dname = newname; msg->rep->rrsets[i]->rk.dname_len = newlen; msg->rep->rrsets[i]->entry.hash = rrset_key_hash(&msg->rep->rrsets[i]->rk); } } } /** find NSEC record covering the query, with the given node in the zone */ static struct auth_rrset* az_find_nsec_cover(struct auth_zone* z, struct auth_data** node) { uint8_t* nm; size_t nmlen; struct auth_rrset* rrset; log_assert(*node); /* we already have a node when calling this */ nm = (*node)->name; nmlen = (*node)->namelen; /* find the NSEC for the smallest-or-equal node */ /* But there could be glue, and then it has no NSEC. * Go up to find nonglue (previous) NSEC-holding nodes */ while((rrset=az_domain_rrset(*node, LDNS_RR_TYPE_NSEC)) == NULL) { if(nmlen == z->namelen) return NULL; if(!dname_remove_label_limit_len(&nm, &nmlen, z->namelen)) return NULL; /* can't go up */ /* adjust *node for the nsec rrset to find in */ *node = az_find_name(z, nm, nmlen); } return rrset; } /** Find NSEC and add for wildcard denial */ static int az_nsec_wildcard_denial(struct auth_zone* z, struct regional* region, struct dns_msg* msg, uint8_t* cenm, size_t cenmlen) { struct query_info qinfo; int node_exact; struct auth_data* node; struct auth_rrset* nsec; uint8_t wc[LDNS_MAX_DOMAINLEN]; if(cenmlen+2 > sizeof(wc)) return 0; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, cenm, cenmlen); /* we have '*.ce' in wc wildcard name buffer */ /* get nsec cover for that */ qinfo.qname = wc; qinfo.qname_len = cenmlen+2; qinfo.qtype = 0; qinfo.qclass = 0; az_find_domain(z, &qinfo, &node_exact, &node); if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; } return 1; } /** Find the NSEC3PARAM rrset (if any) and if true you have the parameters */ static int az_nsec3_param(struct auth_zone* z, int* algo, size_t* iter, uint8_t** salt, size_t* saltlen) { struct auth_data* apex; struct auth_rrset* param; size_t i; apex = az_find_name(z, z->name, z->namelen); if(!apex) return 0; param = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC3PARAM); if(!param || param->data->count==0) return 0; /* no RRset or no RRs in rrset */ /* find out which NSEC3PARAM RR has supported parameters */ /* skip unknown flags (dynamic signer is recalculating nsec3 chain) */ for(i=0; idata->count; i++) { uint8_t* rdata = param->data->rr_data[i]+2; size_t rdatalen = param->data->rr_len[i]; if(rdatalen < 2+5) continue; /* too short */ if(!nsec3_hash_algo_size_supported((int)(rdata[0]))) continue; /* unsupported algo */ if(rdatalen < (size_t)(2+5+(size_t)rdata[4])) continue; /* salt missing */ if((rdata[1]&NSEC3_UNKNOWN_FLAGS)!=0) continue; /* unknown flags */ *algo = (int)(rdata[0]); *iter = sldns_read_uint16(rdata+2); *saltlen = rdata[4]; if(*saltlen == 0) *salt = NULL; else *salt = rdata+5; return 1; } /* no supported params */ return 0; } /** Hash a name with nsec3param into buffer, it has zone name appended. * return length of hash */ static size_t az_nsec3_hash(uint8_t* buf, size_t buflen, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { size_t hlen = nsec3_hash_algo_size_supported(algo); /* buffer has domain name, nsec3hash, and 256 is for max saltlen * (salt has 0-255 length) */ unsigned char p[LDNS_MAX_DOMAINLEN+1+N3HASHBUFLEN+256]; size_t i; if(nmlen+saltlen > sizeof(p) || hlen+saltlen > sizeof(p)) return 0; if(hlen > buflen) return 0; /* somehow too large for destination buffer */ /* hashfunc(name, salt) */ memmove(p, nm, nmlen); query_dname_tolower(p); if(salt && saltlen > 0) memmove(p+nmlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, nmlen+saltlen, (unsigned char*)buf); for(i=0; i 0) memmove(p+hlen, salt, saltlen); (void)secalgo_nsec3_hash(algo, p, hlen+saltlen, (unsigned char*)buf); } return hlen; } /** Hash name and return b32encoded hashname for lookup, zone name appended */ static int az_nsec3_hashname(struct auth_zone* z, uint8_t* hashname, size_t* hashnmlen, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { uint8_t hash[N3HASHBUFLEN]; size_t hlen; int ret; hlen = az_nsec3_hash(hash, sizeof(hash), nm, nmlen, algo, iter, salt, saltlen); if(!hlen) return 0; /* b32 encode */ if(*hashnmlen < hlen*2+1+z->namelen) /* approx b32 as hexb16 */ return 0; ret = sldns_b32_ntop_extended_hex(hash, hlen, (char*)(hashname+1), (*hashnmlen)-1); if(ret<1) return 0; hashname[0] = (uint8_t)ret; ret++; if((*hashnmlen) - ret < z->namelen) return 0; memmove(hashname+ret, z->name, z->namelen); *hashnmlen = z->namelen+(size_t)ret; return 1; } /** Find the datanode that covers the nsec3hash-name */ static struct auth_data* az_nsec3_findnode(struct auth_zone* z, uint8_t* hashnm, size_t hashnmlen) { struct query_info qinfo; struct auth_data* node; int node_exact; qinfo.qclass = 0; qinfo.qtype = 0; qinfo.qname = hashnm; qinfo.qname_len = hashnmlen; /* because canonical ordering and b32 nsec3 ordering are the same. * this is a good lookup to find the nsec3 name. */ az_find_domain(z, &qinfo, &node_exact, &node); /* but we may have to skip non-nsec3 nodes */ /* this may be a lot, the way to speed that up is to have a * separate nsec3 tree with nsec3 nodes */ while(node && (rbnode_type*)node != RBTREE_NULL && !az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) { node = (struct auth_data*)rbtree_previous(&node->node); } if((rbnode_type*)node == RBTREE_NULL) node = NULL; return node; } /** Find cover for hashed(nm, nmlen) (or NULL) */ static struct auth_data* az_nsec3_find_cover(struct auth_zone* z, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; uint8_t hname[LDNS_MAX_DOMAINLEN]; size_t hlen = sizeof(hname); if(!az_nsec3_hashname(z, hname, &hlen, nm, nmlen, algo, iter, salt, saltlen)) return NULL; node = az_nsec3_findnode(z, hname, hlen); if(node) return node; /* we did not find any, perhaps because the NSEC3 hash is before * the first hash, we have to find the 'last hash' in the zone */ node = (struct auth_data*)rbtree_last(&z->data); while(node && (rbnode_type*)node != RBTREE_NULL && !az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) { node = (struct auth_data*)rbtree_previous(&node->node); } if((rbnode_type*)node == RBTREE_NULL) node = NULL; return node; } /** Find exact match for hashed(nm, nmlen) NSEC3 record or NULL */ static struct auth_data* az_nsec3_find_exact(struct auth_zone* z, uint8_t* nm, size_t nmlen, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; uint8_t hname[LDNS_MAX_DOMAINLEN]; size_t hlen = sizeof(hname); if(!az_nsec3_hashname(z, hname, &hlen, nm, nmlen, algo, iter, salt, saltlen)) return NULL; node = az_find_name(z, hname, hlen); if(az_domain_rrset(node, LDNS_RR_TYPE_NSEC3)) return node; return NULL; } /** Return nextcloser name (as a ref into the qname). This is one label * more than the cenm (cename must be a suffix of qname) */ static void az_nsec3_get_nextcloser(uint8_t* cenm, uint8_t* qname, size_t qname_len, uint8_t** nx, size_t* nxlen) { int celabs = dname_count_labels(cenm); int qlabs = dname_count_labels(qname); int strip = qlabs - celabs -1; log_assert(dname_strict_subdomain(qname, qlabs, cenm, celabs)); *nx = qname; *nxlen = qname_len; if(strip>0) dname_remove_labels(nx, nxlen, strip); } /** Find the closest encloser that has exact NSEC3. * updated cenm to the new name. If it went up no-exact-ce is true. */ static struct auth_data* az_nsec3_find_ce(struct auth_zone* z, uint8_t** cenm, size_t* cenmlen, int* no_exact_ce, int algo, size_t iter, uint8_t* salt, size_t saltlen) { struct auth_data* node; while((node = az_nsec3_find_exact(z, *cenm, *cenmlen, algo, iter, salt, saltlen)) == NULL) { if(!dname_remove_label_limit_len(cenm, cenmlen, z->namelen)) return NULL; /* can't go up */ *no_exact_ce = 1; } return node; } /* Insert NSEC3 record in authority section, if NULL does nothing */ static int az_nsec3_insert(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* nsec3; if(!node) return 1; /* no node, skip this */ nsec3 = az_domain_rrset(node, LDNS_RR_TYPE_NSEC3); if(!nsec3) return 1; /* if no nsec3 RR, skip it */ if(!msg_add_rrset_ns(z, region, msg, node, nsec3)) return 0; return 1; } /** add NSEC3 records to the zone for the nsec3 proof. * Specify with the flags with parts of the proof are required. * the ce is the exact matching name (for notype) but also delegation points. * qname is the one where the nextcloser name can be derived from. * If NSEC3 is not properly there (in the zone) nothing is added. * always enabled: include nsec3 proving about the Closest Encloser. * that is an exact match that should exist for it. * If that does not exist, a higher exact match + nxproof is enabled * (for some sort of opt-out empty nonterminal cases). * nodataproof: search for exact match and include that instead. * ceproof: include ce proof NSEC3 (omitted for wildcard replies). * nxproof: include denial of the qname. * wcproof: include denial of wildcard (wildcard.ce). */ static int az_add_nsec3_proof(struct auth_zone* z, struct regional* region, struct dns_msg* msg, uint8_t* cenm, size_t cenmlen, uint8_t* qname, size_t qname_len, int nodataproof, int ceproof, int nxproof, int wcproof) { int algo; size_t iter, saltlen; uint8_t* salt; int no_exact_ce = 0; struct auth_data* node; /* find parameters of nsec3 proof */ if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) return 1; /* no nsec3 */ if(nodataproof) { /* see if the node has a hash of itself for the nodata * proof nsec3, this has to be an exact match nsec3. */ struct auth_data* match; match = az_nsec3_find_exact(z, qname, qname_len, algo, iter, salt, saltlen); if(match) { if(!az_nsec3_insert(z, region, msg, match)) return 0; /* only nodata NSEC3 needed, no CE or others. */ return 1; } } /* find ce that has an NSEC3 */ if(ceproof) { node = az_nsec3_find_ce(z, &cenm, &cenmlen, &no_exact_ce, algo, iter, salt, saltlen); if(no_exact_ce) nxproof = 1; if(!az_nsec3_insert(z, region, msg, node)) return 0; } if(nxproof) { uint8_t* nx; size_t nxlen; /* create nextcloser domain name */ az_nsec3_get_nextcloser(cenm, qname, qname_len, &nx, &nxlen); /* find nsec3 that matches or covers it */ node = az_nsec3_find_cover(z, nx, nxlen, algo, iter, salt, saltlen); if(!az_nsec3_insert(z, region, msg, node)) return 0; } if(wcproof) { /* create wildcard name *.ce */ uint8_t wc[LDNS_MAX_DOMAINLEN]; size_t wclen; if(cenmlen+2 > sizeof(wc)) return 0; /* result would be too long */ wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, cenm, cenmlen); wclen = cenmlen+2; /* find nsec3 that matches or covers it */ node = az_nsec3_find_cover(z, wc, wclen, algo, iter, salt, saltlen); if(!az_nsec3_insert(z, region, msg, node)) return 0; } return 1; } /** generate answer for positive answer */ static int az_generate_positive_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; /* see if we want additional rrs */ if(rrset->type == LDNS_RR_TYPE_MX) { if(!az_add_additionals_from(z, region, msg, rrset, 2)) return 0; } else if(rrset->type == LDNS_RR_TYPE_SRV) { if(!az_add_additionals_from(z, region, msg, rrset, 6)) return 0; } else if(rrset->type == LDNS_RR_TYPE_NS) { if(!az_add_additionals_from(z, region, msg, rrset, 0)) return 0; } return 1; } /** generate answer for type ANY answer */ static int az_generate_any_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; int added = 0; /* add a couple (at least one) RRs */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_SOA)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_MX)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_A)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_AAAA)) != NULL) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; added++; } if(added == 0 && node && node->rrsets) { if(!msg_add_rrset_an(z, region, msg, node, node->rrsets)) return 0; } return 1; } /** follow cname chain and add more data to the answer section */ static int follow_cname_chain(struct auth_zone* z, uint16_t qtype, struct regional* region, struct dns_msg* msg, struct packed_rrset_data* d) { int maxchain = 0; /* see if we can add the target of the CNAME into the answer */ while(maxchain++ < MAX_CNAME_CHAIN) { struct auth_data* node; struct auth_rrset* rrset; size_t clen; /* d has cname rdata */ if(d->count == 0) break; /* no CNAME */ if(d->rr_len[0] < 2+1) break; /* too small */ if((clen=dname_valid(d->rr_data[0]+2, d->rr_len[0]-2))==0) break; /* malformed */ if(!dname_subdomain_c(d->rr_data[0]+2, z->name)) break; /* target out of zone */ if((node = az_find_name(z, d->rr_data[0]+2, clen))==NULL) break; /* no such target name */ if((rrset=az_domain_rrset(node, qtype))!=NULL) { /* done we found the target */ if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; break; } if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_CNAME))==NULL) break; /* no further CNAME chain, notype */ if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; d = rrset->data; } return 1; } /** generate answer for cname answer */ static int az_generate_cname_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* node, struct auth_rrset* rrset) { if(!msg_add_rrset_an(z, region, msg, node, rrset)) return 0; if(!rrset) return 1; if(!follow_cname_chain(z, qinfo->qtype, region, msg, rrset->data)) return 0; return 1; } /** generate answer for notype answer */ static int az_generate_notype_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; if(!az_add_negative_soa(z, region, msg)) return 0; /* DNSSEC denial NSEC */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_NSEC))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, node, rrset)) return 0; } else if(node) { /* DNSSEC denial NSEC3 */ if(!az_add_nsec3_proof(z, region, msg, node->name, node->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 1, 1, 0, 0)) return 0; } return 1; } /** generate answer for referral answer */ static int az_generate_referral_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset) { struct auth_rrset* ds, *nsec; /* turn off AA flag, referral is nonAA because it leaves the zone */ log_assert(ce); msg->rep->flags &= ~BIT_AA; if(!msg_add_rrset_ns(z, region, msg, ce, rrset)) return 0; /* add DS or deny it */ if((ds=az_domain_rrset(ce, LDNS_RR_TYPE_DS))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, ce, ds)) return 0; } else { /* deny the DS */ if((nsec=az_domain_rrset(ce, LDNS_RR_TYPE_NSEC))!=NULL) { if(!msg_add_rrset_ns(z, region, msg, ce, nsec)) return 0; } else { if(!az_add_nsec3_proof(z, region, msg, ce->name, ce->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 1, 1, 0, 0)) return 0; } } /* add additional rrs for type NS */ if(!az_add_additionals_from(z, region, msg, rrset, 0)) return 0; return 1; } /** generate answer for DNAME answer */ static int az_generate_dname_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset) { log_assert(ce); /* add the DNAME and then a CNAME */ if(!msg_add_rrset_an(z, region, msg, ce, rrset)) return 0; if(!add_synth_cname(z, qinfo->qname, qinfo->qname_len, region, msg, ce, rrset)) return 0; if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_YXDOMAIN) return 1; if(msg->rep->rrset_count == 0 || !msg->rep->rrsets[msg->rep->rrset_count-1]) return 0; if(!follow_cname_chain(z, qinfo->qtype, region, msg, (struct packed_rrset_data*)msg->rep->rrsets[ msg->rep->rrset_count-1]->entry.data)) return 0; return 1; } /** generate answer for wildcard answer */ static int az_generate_wildcard_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_data* wildcard, struct auth_data* node) { struct auth_rrset* rrset, *nsec; int insert_ce = 0; if((rrset=az_domain_rrset(wildcard, qinfo->qtype)) != NULL) { /* wildcard has type, add it */ if(!msg_add_rrset_an(z, region, msg, wildcard, rrset)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); } else if((rrset=az_domain_rrset(wildcard, LDNS_RR_TYPE_CNAME))!=NULL) { /* wildcard has cname instead, do that */ if(!msg_add_rrset_an(z, region, msg, wildcard, rrset)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); if(!follow_cname_chain(z, qinfo->qtype, region, msg, rrset->data)) return 0; } else if(qinfo->qtype == LDNS_RR_TYPE_ANY && wildcard->rrsets) { /* add ANY rrsets from wildcard node */ if(!az_generate_any_answer(z, region, msg, wildcard)) return 0; az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 1); } else { /* wildcard has nodata, notype answer */ /* call other notype routine for dnssec notype denials */ if(!az_generate_notype_answer(z, region, msg, wildcard)) return 0; /* because the notype, there is no positive data with an * RRSIG that indicates the wildcard position. Thus the * wildcard qname denial needs to have a CE nsec3. */ insert_ce = 1; } /* ce and node for dnssec denial of wildcard original name */ if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; } else if(ce) { uint8_t* wildup = wildcard->name; size_t wilduplen= wildcard->namelen; if(!dname_remove_label_limit_len(&wildup, &wilduplen, z->namelen)) return 0; /* can't go up */ if(!az_add_nsec3_proof(z, region, msg, wildup, wilduplen, msg->qinfo.qname, msg->qinfo.qname_len, 0, insert_ce, 1, 0)) return 0; } /* fixup name of wildcard from *.zone to qname, use already allocated * pointer to msg qname */ az_change_dnames(msg, wildcard->name, msg->qinfo.qname, msg->qinfo.qname_len, 0); return 1; } /** generate answer for nxdomain answer */ static int az_generate_nxdomain_answer(struct auth_zone* z, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_data* node) { struct auth_rrset* nsec; msg->rep->flags |= LDNS_RCODE_NXDOMAIN; if(!az_add_negative_soa(z, region, msg)) return 0; if((nsec=az_find_nsec_cover(z, &node)) != NULL) { if(!msg_add_rrset_ns(z, region, msg, node, nsec)) return 0; if(ce && !az_nsec_wildcard_denial(z, region, msg, ce->name, ce->namelen)) return 0; } else if(ce) { if(!az_add_nsec3_proof(z, region, msg, ce->name, ce->namelen, msg->qinfo.qname, msg->qinfo.qname_len, 0, 1, 1, 1)) return 0; } return 1; } /** Create answers when an exact match exists for the domain name */ static int az_generate_answer_with_node(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* node) { struct auth_rrset* rrset; /* positive answer, rrset we are looking for exists */ if((rrset=az_domain_rrset(node, qinfo->qtype)) != NULL) { return az_generate_positive_answer(z, region, msg, node, rrset); } /* CNAME? */ if((rrset=az_domain_rrset(node, LDNS_RR_TYPE_CNAME)) != NULL) { return az_generate_cname_answer(z, qinfo, region, msg, node, rrset); } /* type ANY ? */ if(qinfo->qtype == LDNS_RR_TYPE_ANY) { return az_generate_any_answer(z, region, msg, node); } /* NOERROR/NODATA (no such type at domain name) */ return az_generate_notype_answer(z, region, msg, node); } /** Generate answer without an existing-node that we can use. * So it'll be a referral, DNAME, notype, wildcard or nxdomain */ static int az_generate_answer_nonexistnode(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg* msg, struct auth_data* ce, struct auth_rrset* rrset, struct auth_data* node) { struct auth_data* wildcard; /* we do not have an exact matching name (that exists) */ /* see if we have a NS or DNAME in the ce */ if(ce && rrset && rrset->type == LDNS_RR_TYPE_NS) { return az_generate_referral_answer(z, region, msg, ce, rrset); } if(ce && rrset && rrset->type == LDNS_RR_TYPE_DNAME) { return az_generate_dname_answer(z, qinfo, region, msg, ce, rrset); } /* if there is an empty nonterminal, wildcard and nxdomain don't * happen, it is a notype answer */ if(az_empty_nonterminal(z, qinfo, node)) { return az_generate_notype_answer(z, region, msg, node); } /* see if we have a wildcard under the ce */ if((wildcard=az_find_wildcard(z, qinfo, ce)) != NULL) { return az_generate_wildcard_answer(z, qinfo, region, msg, ce, wildcard, node); } /* generate nxdomain answer */ return az_generate_nxdomain_answer(z, region, msg, ce, node); } /** Lookup answer in a zone. */ static int auth_zone_generate_answer(struct auth_zone* z, struct query_info* qinfo, struct regional* region, struct dns_msg** msg, int* fallback) { struct auth_data* node, *ce; struct auth_rrset* rrset; int node_exact, node_exists; /* does the zone want fallback in case of failure? */ *fallback = z->fallback_enabled; if(!(*msg=msg_create(region, qinfo))) return 0; /* lookup if there is a matching domain name for the query */ az_find_domain(z, qinfo, &node_exact, &node); /* see if node exists for generating answers from (i.e. not glue and * obscured by NS or DNAME or NSEC3-only), and also return the * closest-encloser from that, closest node that should be used * to generate answers from that is above the query */ node_exists = az_find_ce(z, qinfo, node, node_exact, &ce, &rrset); if(verbosity >= VERB_ALGO) { char zname[256], qname[256], nname[256], cename[256], tpstr[32], rrstr[32]; sldns_wire2str_dname_buf(qinfo->qname, qinfo->qname_len, qname, sizeof(qname)); sldns_wire2str_type_buf(qinfo->qtype, tpstr, sizeof(tpstr)); sldns_wire2str_dname_buf(z->name, z->namelen, zname, sizeof(zname)); if(node) sldns_wire2str_dname_buf(node->name, node->namelen, nname, sizeof(nname)); else snprintf(nname, sizeof(nname), "NULL"); if(ce) sldns_wire2str_dname_buf(ce->name, ce->namelen, cename, sizeof(cename)); else snprintf(cename, sizeof(cename), "NULL"); if(rrset) sldns_wire2str_type_buf(rrset->type, rrstr, sizeof(rrstr)); else snprintf(rrstr, sizeof(rrstr), "NULL"); log_info("auth_zone %s query %s %s, domain %s %s %s, " "ce %s, rrset %s", zname, qname, tpstr, nname, (node_exact?"exact":"notexact"), (node_exists?"exist":"notexist"), cename, rrstr); } if(node_exists) { /* the node is fine, generate answer from node */ return az_generate_answer_with_node(z, qinfo, region, *msg, node); } return az_generate_answer_nonexistnode(z, qinfo, region, *msg, ce, rrset, node); } int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, struct regional* region, struct dns_msg** msg, int* fallback, uint8_t* dp_nm, size_t dp_nmlen) { int r; struct auth_zone* z; /* find the zone that should contain the answer. */ lock_rw_rdlock(&az->lock); z = auth_zone_find(az, dp_nm, dp_nmlen, qinfo->qclass); if(!z) { lock_rw_unlock(&az->lock); /* no auth zone, fallback to internet */ *fallback = 1; return 0; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); /* if not for upstream queries, fallback */ if(!z->for_upstream) { lock_rw_unlock(&z->lock); *fallback = 1; return 0; } if(z->zone_expired) { *fallback = z->fallback_enabled; lock_rw_unlock(&z->lock); return 0; } /* see what answer that zone would generate */ r = auth_zone_generate_answer(z, qinfo, region, msg, fallback); lock_rw_unlock(&z->lock); return r; } /** encode auth answer */ static void auth_answer_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct dns_msg* msg) { uint16_t udpsize; 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(!inplace_cb_reply_local_call(env, qinfo, NULL, msg->rep, (int)FLAGS_GET_RCODE(msg->rep->flags), edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, msg->rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } } /** encode auth error answer */ static void auth_error_encode(struct query_info* qinfo, struct module_env* env, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, int rcode) { edns->edns_version = EDNS_ADVERTISED_VERSION; edns->udp_size = EDNS_ADVERTISED_SIZE; edns->ext_rcode = 0; edns->bits &= EDNS_DO; if(!inplace_cb_reply_local_call(env, qinfo, NULL, NULL, rcode, edns, repinfo, temp, env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(buf, rcode|BIT_AA, qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } int auth_zones_downstream_answer(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, struct sldns_buffer* buf, struct regional* temp) { struct dns_msg* msg = NULL; struct auth_zone* z; int r; int fallback = 0; /* Copy the qinfo in case of cname aliasing from local-zone */ struct query_info zqinfo = *qinfo; lock_rw_rdlock(&az->lock); if(!az->have_downstream) { /* no downstream auth zones */ lock_rw_unlock(&az->lock); return 0; } if(qinfo->qtype == LDNS_RR_TYPE_DS) { uint8_t* delname = qinfo->qname; size_t delnamelen = qinfo->qname_len; dname_remove_label(&delname, &delnamelen); z = auth_zones_find_zone(az, delname, delnamelen, qinfo->qclass); } else { if(zqinfo.local_alias && !local_alias_shallow_copy_qname( zqinfo.local_alias, &zqinfo.qname, &zqinfo.qname_len)) { lock_rw_unlock(&az->lock); return 0; } z = auth_zones_find_zone(az, zqinfo.qname, zqinfo.qname_len, zqinfo.qclass); } if(!z) { /* no zone above it */ lock_rw_unlock(&az->lock); return 0; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); if(!z->for_downstream) { lock_rw_unlock(&z->lock); return 0; } if(z->zone_expired) { if(z->fallback_enabled) { lock_rw_unlock(&z->lock); return 0; } lock_rw_unlock(&z->lock); env->mesh->num_query_authzone_down++; auth_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_SERVFAIL); return 1; } /* answer it from zone z */ r = auth_zone_generate_answer(z, &zqinfo, temp, &msg, &fallback); lock_rw_unlock(&z->lock); if(!r && fallback) { /* fallback to regular answering (recursive) */ return 0; } env->mesh->num_query_authzone_down++; /* encode answer */ if(!r) auth_error_encode(qinfo, env, edns, repinfo, buf, temp, LDNS_RCODE_SERVFAIL); else auth_answer_encode(qinfo, env, edns, repinfo, buf, temp, msg); return 1; } int auth_zones_can_fallback(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass) { int r; struct auth_zone* z; lock_rw_rdlock(&az->lock); z = auth_zone_find(az, nm, nmlen, dclass); if(!z) { lock_rw_unlock(&az->lock); /* no such auth zone, fallback */ return 1; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&az->lock); r = z->fallback_enabled || (!z->for_upstream); lock_rw_unlock(&z->lock); return r; } int auth_zone_parse_notify_serial(sldns_buffer* pkt, uint32_t *serial) { struct query_info q; uint16_t rdlen; memset(&q, 0, sizeof(q)); sldns_buffer_set_position(pkt, 0); if(!query_info_parse(&q, pkt)) return 0; if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) == 0) return 0; /* skip name of RR in answer section */ if(sldns_buffer_remaining(pkt) < 1) return 0; if(pkt_dname_len(pkt) == 0) return 0; /* check type */ if(sldns_buffer_remaining(pkt) < 10 /* type,class,ttl,rdatalen*/) return 0; if(sldns_buffer_read_u16(pkt) != LDNS_RR_TYPE_SOA) return 0; sldns_buffer_skip(pkt, 2); /* class */ sldns_buffer_skip(pkt, 4); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); /* rdatalen */ if(sldns_buffer_remaining(pkt) < rdlen) return 0; if(rdlen < 22) return 0; /* bad soa length */ sldns_buffer_skip(pkt, (ssize_t)(rdlen-20)); *serial = sldns_buffer_read_u32(pkt); /* return true when has serial in answer section */ return 1; } /** print addr to str, and if not 53, append "@port_number", for logs. */ static void addr_port_to_str(struct sockaddr_storage* addr, socklen_t addrlen, char* buf, size_t len) { uint16_t port = 0; if(addr_is_ip6(addr, addrlen)) { struct sockaddr_in6* sa = (struct sockaddr_in6*)addr; port = ntohs((uint16_t)sa->sin6_port); } else { struct sockaddr_in* sa = (struct sockaddr_in*)addr; port = ntohs((uint16_t)sa->sin_port); } if(port == UNBOUND_DNS_PORT) { /* If it is port 53, print it plainly. */ addr_to_str(addr, addrlen, buf, len); } else { char a[256]; a[0]=0; addr_to_str(addr, addrlen, a, sizeof(a)); snprintf(buf, len, "%s@%d", a, (int)port); } } /** see if addr appears in the list */ static int addr_in_list(struct auth_addr* list, struct sockaddr_storage* addr, socklen_t addrlen) { struct auth_addr* p; for(p=list; p; p=p->next) { if(sockaddr_cmp_addr(addr, addrlen, &p->addr, p->addrlen)==0) return 1; } return 0; } /** check if an address matches a master specification (or one of its * addresses in the addr list) */ static int addr_matches_master(struct auth_master* master, struct sockaddr_storage* addr, socklen_t addrlen, struct auth_master** fromhost) { struct sockaddr_storage a; socklen_t alen = 0; int net = 0; if(addr_in_list(master->list, addr, addrlen)) { *fromhost = master; return 1; } /* compare address (but not port number, that is the destination * port of the master, the port number of the received notify is * allowed to by any port on that master) */ if(extstrtoaddr(master->host, &a, &alen, UNBOUND_DNS_PORT) && sockaddr_cmp_addr(addr, addrlen, &a, alen)==0) { *fromhost = master; return 1; } /* prefixes, addr/len, like 10.0.0.0/8 */ /* not http and has a / and there is one / */ if(master->allow_notify && !master->http && strchr(master->host, '/') != NULL && strchr(master->host, '/') == strrchr(master->host, '/') && netblockstrtoaddr(master->host, UNBOUND_DNS_PORT, &a, &alen, &net) && alen == addrlen) { if(addr_in_common(addr, (addr_is_ip6(addr, addrlen)?128:32), &a, net, alen) >= net) { *fromhost = NULL; /* prefix does not have destination to send the probe or transfer with */ return 1; /* matches the netblock */ } } return 0; } /** check access list for notifies */ static int az_xfr_allowed_notify(struct auth_xfer* xfr, struct sockaddr_storage* addr, socklen_t addrlen, struct auth_master** fromhost) { struct auth_master* p; for(p=xfr->allow_notify_list; p; p=p->next) { if(addr_matches_master(p, addr, addrlen, fromhost)) { return 1; } } return 0; } /** see if the serial means the zone has to be updated, i.e. the serial * is newer than the zone serial, or we have no zone */ static int xfr_serial_means_update(struct auth_xfer* xfr, uint32_t serial) { if(!xfr->have_zone) return 1; /* no zone, anything is better */ if(xfr->zone_expired) return 1; /* expired, the sent serial is better than expired data */ if(compare_serial(xfr->serial, serial) < 0) return 1; /* our serial is smaller than the sent serial, the data is newer, fetch it */ return 0; } /** note notify serial, updates the notify information in the xfr struct */ static void xfr_note_notify_serial(struct auth_xfer* xfr, int has_serial, uint32_t serial) { if(xfr->notify_received && xfr->notify_has_serial && has_serial) { /* see if this serial is newer */ if(compare_serial(xfr->notify_serial, serial) < 0) xfr->notify_serial = serial; } else if(xfr->notify_received && xfr->notify_has_serial && !has_serial) { /* remove serial, we have notify without serial */ xfr->notify_has_serial = 0; xfr->notify_serial = 0; } else if(xfr->notify_received && !xfr->notify_has_serial) { /* we already have notify without serial, keep it * that way; no serial check when current operation * is done */ } else { xfr->notify_received = 1; xfr->notify_has_serial = has_serial; xfr->notify_serial = serial; } } /** process a notify serial, start new probe or note serial. xfr is locked */ static void xfr_process_notify(struct auth_xfer* xfr, struct module_env* env, int has_serial, uint32_t serial, struct auth_master* fromhost) { /* if the serial of notify is older than we have, don't fetch * a zone, we already have it */ if(has_serial && !xfr_serial_means_update(xfr, serial)) { lock_basic_unlock(&xfr->lock); return; } /* start new probe with this addr src, or note serial */ if(!xfr_start_probe(xfr, env, fromhost)) { /* not started because already in progress, note the serial */ xfr_note_notify_serial(xfr, has_serial, serial); lock_basic_unlock(&xfr->lock); } /* successful end of start_probe unlocked xfr->lock */ } int auth_zones_notify(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass, struct sockaddr_storage* addr, socklen_t addrlen, int has_serial, uint32_t serial, int* refused) { struct auth_xfer* xfr; struct auth_master* fromhost = NULL; /* see which zone this is */ lock_rw_rdlock(&az->lock); xfr = auth_xfer_find(az, nm, nmlen, dclass); if(!xfr) { lock_rw_unlock(&az->lock); /* no such zone, refuse the notify */ *refused = 1; return 0; } lock_basic_lock(&xfr->lock); lock_rw_unlock(&az->lock); /* check access list for notifies */ if(!az_xfr_allowed_notify(xfr, addr, addrlen, &fromhost)) { lock_basic_unlock(&xfr->lock); /* notify not allowed, refuse the notify */ *refused = 1; return 0; } /* process the notify */ xfr_process_notify(xfr, env, has_serial, serial, fromhost); return 1; } int auth_zones_startprobesequence(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass) { struct auth_xfer* xfr; lock_rw_rdlock(&az->lock); xfr = auth_xfer_find(az, nm, nmlen, dclass); if(!xfr) { lock_rw_unlock(&az->lock); return 0; } lock_basic_lock(&xfr->lock); lock_rw_unlock(&az->lock); xfr_process_notify(xfr, env, 0, 0, NULL); return 1; } /** set a zone expired */ static void auth_xfer_set_expired(struct auth_xfer* xfr, struct module_env* env, int expired) { struct auth_zone* z; /* expire xfr */ lock_basic_lock(&xfr->lock); xfr->zone_expired = expired; lock_basic_unlock(&xfr->lock); /* find auth_zone */ lock_rw_rdlock(&env->auth_zones->lock); z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!z) { lock_rw_unlock(&env->auth_zones->lock); return; } lock_rw_wrlock(&z->lock); lock_rw_unlock(&env->auth_zones->lock); /* expire auth_zone */ z->zone_expired = expired; lock_rw_unlock(&z->lock); } /** find master (from notify or probe) in list of masters */ static struct auth_master* find_master_by_host(struct auth_master* list, char* host) { struct auth_master* p; for(p=list; p; p=p->next) { if(strcmp(p->host, host) == 0) return p; } return NULL; } /** delete the looked up auth_addrs for all the masters in the list */ static void xfr_masterlist_free_addrs(struct auth_master* list) { struct auth_master* m; for(m=list; m; m=m->next) { if(m->list) { auth_free_master_addrs(m->list); m->list = NULL; } } } /** copy a list of auth_addrs */ static struct auth_addr* auth_addr_list_copy(struct auth_addr* source) { struct auth_addr* list = NULL, *last = NULL; struct auth_addr* p; for(p=source; p; p=p->next) { struct auth_addr* a = (struct auth_addr*)memdup(p, sizeof(*p)); if(!a) { log_err("malloc failure"); auth_free_master_addrs(list); return NULL; } a->next = NULL; if(last) last->next = a; if(!list) list = a; last = a; } return list; } /** copy a master to a new structure, NULL on alloc failure */ static struct auth_master* auth_master_copy(struct auth_master* o) { struct auth_master* m; if(!o) return NULL; m = (struct auth_master*)memdup(o, sizeof(*o)); if(!m) { log_err("malloc failure"); return NULL; } m->next = NULL; if(m->host) { m->host = strdup(m->host); if(!m->host) { free(m); log_err("malloc failure"); return NULL; } } if(m->file) { m->file = strdup(m->file); if(!m->file) { free(m->host); free(m); log_err("malloc failure"); return NULL; } } if(m->list) { m->list = auth_addr_list_copy(m->list); if(!m->list) { free(m->file); free(m->host); free(m); return NULL; } } return m; } /** append the master to the copied list. */ static int auth_master_copy_and_append(struct auth_master* p, struct auth_master** list, struct auth_master** last) { struct auth_master* m = auth_master_copy(p); if(!m) { return 0; } m->next = NULL; if(*last) (*last)->next = m; if(!*list) *list = m; *last = m; return 1; } /** copy the master addresses from the task_probe lookups to the allow_notify * list of masters */ static void probe_copy_masters_for_allow_notify(struct auth_xfer* xfr) { struct auth_master* list = NULL, *last = NULL; struct auth_master* p; /* build up new list with copies */ /* The list in task probe has been looked up before the list in * task transfer. */ for(p = xfr->task_probe->masters; p; p=p->next) { if(!auth_master_copy_and_append(p, &list, &last)) { auth_free_masters(list); /* failed because of malloc failure, use old list */ return; } } /* The list in task transfer also contains the http entries. */ for(p = xfr->task_transfer->masters; p; p=p->next) { /* Copy the http entries from this lookup. The allow_notify * entries are not looked up from this list. The other * ones are already in from the probe lookups. */ if(!p->http) continue; if(!auth_master_copy_and_append(p, &list, &last)) { auth_free_masters(list); /* failed because of malloc failure, use old list */ return; } } /* success, replace list */ auth_free_masters(xfr->allow_notify_list); xfr->allow_notify_list = list; } /** start the lookups for task_transfer */ static void xfr_transfer_start_lookups(struct auth_xfer* xfr) { /* delete all the looked up addresses in the list */ xfr->task_transfer->scan_addr = NULL; xfr_masterlist_free_addrs(xfr->task_transfer->masters); /* start lookup at the first master */ xfr->task_transfer->lookup_target = xfr->task_transfer->masters; xfr->task_transfer->lookup_aaaa = 0; } /** move to the next lookup of hostname for task_transfer */ static void xfr_transfer_move_to_next_lookup(struct auth_xfer* xfr, struct module_env* env) { if(!xfr->task_transfer->lookup_target) return; /* already at end of list */ if(!xfr->task_transfer->lookup_aaaa && env->cfg->do_ip6) { /* move to lookup AAAA */ xfr->task_transfer->lookup_aaaa = 1; return; } xfr->task_transfer->lookup_target = xfr->task_transfer->lookup_target->next; xfr->task_transfer->lookup_aaaa = 0; if(!env->cfg->do_ip4 && xfr->task_transfer->lookup_target!=NULL) xfr->task_transfer->lookup_aaaa = 1; } /** start the lookups for task_probe */ static void xfr_probe_start_lookups(struct auth_xfer* xfr) { /* delete all the looked up addresses in the list */ xfr->task_probe->scan_addr = NULL; xfr_masterlist_free_addrs(xfr->task_probe->masters); /* start lookup at the first master */ xfr->task_probe->lookup_target = xfr->task_probe->masters; xfr->task_probe->lookup_aaaa = 0; } /** move to the next lookup of hostname for task_probe */ static void xfr_probe_move_to_next_lookup(struct auth_xfer* xfr, struct module_env* env) { if(!xfr->task_probe->lookup_target) return; /* already at end of list */ if(!xfr->task_probe->lookup_aaaa && env->cfg->do_ip6) { /* move to lookup AAAA */ xfr->task_probe->lookup_aaaa = 1; return; } xfr->task_probe->lookup_target = xfr->task_probe->lookup_target->next; xfr->task_probe->lookup_aaaa = 0; if(!env->cfg->do_ip4 && xfr->task_probe->lookup_target!=NULL) xfr->task_probe->lookup_aaaa = 1; } /** start the iteration of the task_transfer list of masters */ static void xfr_transfer_start_list(struct auth_xfer* xfr, struct auth_master* spec) { if(spec) { xfr->task_transfer->scan_specific = find_master_by_host( xfr->task_transfer->masters, spec->host); if(xfr->task_transfer->scan_specific) { xfr->task_transfer->scan_target = NULL; xfr->task_transfer->scan_addr = NULL; if(xfr->task_transfer->scan_specific->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_specific->list; return; } } /* no specific (notified) host to scan */ xfr->task_transfer->scan_specific = NULL; xfr->task_transfer->scan_addr = NULL; /* pick up first scan target */ xfr->task_transfer->scan_target = xfr->task_transfer->masters; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; } /** start the iteration of the task_probe list of masters */ static void xfr_probe_start_list(struct auth_xfer* xfr, struct auth_master* spec) { if(spec) { xfr->task_probe->scan_specific = find_master_by_host( xfr->task_probe->masters, spec->host); if(xfr->task_probe->scan_specific) { xfr->task_probe->scan_target = NULL; xfr->task_probe->scan_addr = NULL; if(xfr->task_probe->scan_specific->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_specific->list; return; } } /* no specific (notified) host to scan */ xfr->task_probe->scan_specific = NULL; xfr->task_probe->scan_addr = NULL; /* pick up first scan target */ xfr->task_probe->scan_target = xfr->task_probe->masters; if(xfr->task_probe->scan_target && xfr->task_probe->scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; } /** pick up the master that is being scanned right now, task_transfer */ static struct auth_master* xfr_transfer_current_master(struct auth_xfer* xfr) { if(xfr->task_transfer->scan_specific) return xfr->task_transfer->scan_specific; return xfr->task_transfer->scan_target; } /** pick up the master that is being scanned right now, task_probe */ static struct auth_master* xfr_probe_current_master(struct auth_xfer* xfr) { if(xfr->task_probe->scan_specific) return xfr->task_probe->scan_specific; return xfr->task_probe->scan_target; } /** true if at end of list, task_transfer */ static int xfr_transfer_end_of_list(struct auth_xfer* xfr) { return !xfr->task_transfer->scan_specific && !xfr->task_transfer->scan_target; } /** true if at end of list, task_probe */ static int xfr_probe_end_of_list(struct auth_xfer* xfr) { return !xfr->task_probe->scan_specific && !xfr->task_probe->scan_target; } /** move to next master in list, task_transfer */ static void xfr_transfer_nextmaster(struct auth_xfer* xfr) { if(!xfr->task_transfer->scan_specific && !xfr->task_transfer->scan_target) return; if(xfr->task_transfer->scan_addr) { xfr->task_transfer->scan_addr = xfr->task_transfer->scan_addr->next; if(xfr->task_transfer->scan_addr) return; } if(xfr->task_transfer->scan_specific) { xfr->task_transfer->scan_specific = NULL; xfr->task_transfer->scan_target = xfr->task_transfer->masters; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; return; } if(!xfr->task_transfer->scan_target) return; xfr->task_transfer->scan_target = xfr->task_transfer->scan_target->next; if(xfr->task_transfer->scan_target && xfr->task_transfer-> scan_target->list) xfr->task_transfer->scan_addr = xfr->task_transfer->scan_target->list; return; } /** move to next master in list, task_probe */ static void xfr_probe_nextmaster(struct auth_xfer* xfr) { if(!xfr->task_probe->scan_specific && !xfr->task_probe->scan_target) return; if(xfr->task_probe->scan_addr) { xfr->task_probe->scan_addr = xfr->task_probe->scan_addr->next; if(xfr->task_probe->scan_addr) return; } if(xfr->task_probe->scan_specific) { xfr->task_probe->scan_specific = NULL; xfr->task_probe->scan_target = xfr->task_probe->masters; if(xfr->task_probe->scan_target && xfr->task_probe-> scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; return; } if(!xfr->task_probe->scan_target) return; xfr->task_probe->scan_target = xfr->task_probe->scan_target->next; if(xfr->task_probe->scan_target && xfr->task_probe-> scan_target->list) xfr->task_probe->scan_addr = xfr->task_probe->scan_target->list; return; } /** create SOA probe packet for xfr */ static void xfr_create_soa_probe_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id) { struct query_info qinfo; memset(&qinfo, 0, sizeof(qinfo)); qinfo.qname = xfr->name; qinfo.qname_len = xfr->namelen; qinfo.qtype = LDNS_RR_TYPE_SOA; qinfo.qclass = xfr->dclass; qinfo_query_encode(buf, &qinfo); sldns_buffer_write_u16_at(buf, 0, id); } /** create IXFR/AXFR packet for xfr */ static void xfr_create_ixfr_packet(struct auth_xfer* xfr, sldns_buffer* buf, uint16_t id, struct auth_master* master) { struct query_info qinfo; uint32_t serial; int have_zone; have_zone = xfr->have_zone; serial = xfr->serial; memset(&qinfo, 0, sizeof(qinfo)); qinfo.qname = xfr->name; qinfo.qname_len = xfr->namelen; xfr->task_transfer->got_xfr_serial = 0; xfr->task_transfer->rr_scan_num = 0; xfr->task_transfer->incoming_xfr_serial = 0; xfr->task_transfer->on_ixfr_is_axfr = 0; xfr->task_transfer->on_ixfr = 1; qinfo.qtype = LDNS_RR_TYPE_IXFR; if(!have_zone || xfr->task_transfer->ixfr_fail || !master->ixfr) { qinfo.qtype = LDNS_RR_TYPE_AXFR; xfr->task_transfer->ixfr_fail = 0; xfr->task_transfer->on_ixfr = 0; } qinfo.qclass = xfr->dclass; qinfo_query_encode(buf, &qinfo); sldns_buffer_write_u16_at(buf, 0, id); /* append serial for IXFR */ if(qinfo.qtype == LDNS_RR_TYPE_IXFR) { size_t end = sldns_buffer_limit(buf); sldns_buffer_clear(buf); sldns_buffer_set_position(buf, end); /* auth section count 1 */ sldns_buffer_write_u16_at(buf, LDNS_NSCOUNT_OFF, 1); /* write SOA */ sldns_buffer_write_u8(buf, 0xC0); /* compressed ptr to qname */ sldns_buffer_write_u8(buf, 0x0C); sldns_buffer_write_u16(buf, LDNS_RR_TYPE_SOA); sldns_buffer_write_u16(buf, qinfo.qclass); sldns_buffer_write_u32(buf, 0); /* ttl */ sldns_buffer_write_u16(buf, 22); /* rdata length */ sldns_buffer_write_u8(buf, 0); /* . */ sldns_buffer_write_u8(buf, 0); /* . */ sldns_buffer_write_u32(buf, serial); /* serial */ sldns_buffer_write_u32(buf, 0); /* refresh */ sldns_buffer_write_u32(buf, 0); /* retry */ sldns_buffer_write_u32(buf, 0); /* expire */ sldns_buffer_write_u32(buf, 0); /* minimum */ sldns_buffer_flip(buf); } } /** check if returned packet is OK */ static int check_packet_ok(sldns_buffer* pkt, uint16_t qtype, struct auth_xfer* xfr, uint32_t* serial) { /* parse to see if packet worked, valid reply */ /* check serial number of SOA */ if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) return 0; /* check ID */ if(LDNS_ID_WIRE(sldns_buffer_begin(pkt)) != xfr->task_probe->id) return 0; /* check flag bits and rcode */ if(!LDNS_QR_WIRE(sldns_buffer_begin(pkt))) return 0; if(LDNS_OPCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_PACKET_QUERY) return 0; if(LDNS_RCODE_WIRE(sldns_buffer_begin(pkt)) != LDNS_RCODE_NOERROR) return 0; /* check qname */ if(LDNS_QDCOUNT(sldns_buffer_begin(pkt)) != 1) return 0; sldns_buffer_skip(pkt, LDNS_HEADER_SIZE); if(sldns_buffer_remaining(pkt) < xfr->namelen) return 0; if(query_dname_compare(sldns_buffer_current(pkt), xfr->name) != 0) return 0; sldns_buffer_skip(pkt, (ssize_t)xfr->namelen); /* check qtype, qclass */ if(sldns_buffer_remaining(pkt) < 4) return 0; if(sldns_buffer_read_u16(pkt) != qtype) return 0; if(sldns_buffer_read_u16(pkt) != xfr->dclass) return 0; if(serial) { uint16_t rdlen; /* read serial number, from answer section SOA */ if(LDNS_ANCOUNT(sldns_buffer_begin(pkt)) == 0) return 0; /* read from first record SOA record */ if(sldns_buffer_remaining(pkt) < 1) return 0; if(dname_pkt_compare(pkt, sldns_buffer_current(pkt), xfr->name) != 0) return 0; if(!pkt_dname_len(pkt)) return 0; /* type, class, ttl, rdatalen */ if(sldns_buffer_remaining(pkt) < 4+4+2) return 0; if(sldns_buffer_read_u16(pkt) != qtype) return 0; if(sldns_buffer_read_u16(pkt) != xfr->dclass) return 0; sldns_buffer_skip(pkt, 4); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) return 0; if(sldns_buffer_remaining(pkt) < 1) return 0; if(!pkt_dname_len(pkt)) /* soa name */ return 0; if(sldns_buffer_remaining(pkt) < 1) return 0; if(!pkt_dname_len(pkt)) /* soa name */ return 0; if(sldns_buffer_remaining(pkt) < 20) return 0; *serial = sldns_buffer_read_u32(pkt); } return 1; } /** read one line from chunks into buffer at current position */ static int chunkline_get_line(struct auth_chunk** chunk, size_t* chunk_pos, sldns_buffer* buf) { int readsome = 0; while(*chunk) { /* more text in this chunk? */ if(*chunk_pos < (*chunk)->len) { readsome = 1; while(*chunk_pos < (*chunk)->len) { char c = (char)((*chunk)->data[*chunk_pos]); (*chunk_pos)++; if(sldns_buffer_remaining(buf) < 2) { /* buffer too short */ verbose(VERB_ALGO, "http chunkline, " "line too long"); return 0; } sldns_buffer_write_u8(buf, (uint8_t)c); if(c == '\n') { /* we are done */ return 1; } } } /* move to next chunk */ *chunk = (*chunk)->next; *chunk_pos = 0; } /* no more text */ if(readsome) return 1; return 0; } /** count number of open and closed parenthesis in a chunkline */ static int chunkline_count_parens(sldns_buffer* buf, size_t start) { size_t end = sldns_buffer_position(buf); size_t i; int count = 0; int squote = 0, dquote = 0; for(i=start; i 0) { chunkline_remove_trailcomment(buf, pos); pos = sldns_buffer_position(buf); if(!chunkline_get_line(chunk, chunk_pos, buf)) { if(sldns_buffer_position(buf) < sldns_buffer_limit(buf)) sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf), 0); else sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf)-1, 0); sldns_buffer_flip(buf); return 0; } parens += chunkline_count_parens(buf, pos); } if(sldns_buffer_remaining(buf) < 1) { verbose(VERB_ALGO, "http chunkline: " "line too long"); return 0; } sldns_buffer_write_u8_at(buf, sldns_buffer_position(buf), 0); sldns_buffer_flip(buf); return 1; } /** process $ORIGIN for http, 0 nothing, 1 done, 2 error */ static int http_parse_origin(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { char* line = (char*)sldns_buffer_begin(buf); if(strncmp(line, "$ORIGIN", 7) == 0 && isspace((unsigned char)line[7])) { int s; pstate->origin_len = sizeof(pstate->origin); s = sldns_str2wire_dname_buf(sldns_strip_ws(line+8), pstate->origin, &pstate->origin_len); if(s) { pstate->origin_len = 0; return 2; } return 1; } return 0; } /** process $TTL for http, 0 nothing, 1 done, 2 error */ static int http_parse_ttl(sldns_buffer* buf, struct sldns_file_parse_state* pstate) { char* line = (char*)sldns_buffer_begin(buf); if(strncmp(line, "$TTL", 4) == 0 && isspace((unsigned char)line[4])) { const char* end = NULL; int overflow = 0; pstate->default_ttl = sldns_str2period( sldns_strip_ws(line+5), &end, &overflow); if(overflow) { return 2; } return 1; } return 0; } /** remove newlines from collated line */ static void chunkline_newline_removal(sldns_buffer* buf) { size_t i, end=sldns_buffer_limit(buf); for(i=0; inamelen < sizeof(pstate.origin)) { pstate.origin_len = xfr->namelen; memmove(pstate.origin, xfr->name, xfr->namelen); } chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; if(!chunkline_non_comment_RR(&chunk, &chunk_pos, buf, &pstate)) { return 0; } rr_len = sizeof(rr); e=sldns_str2wire_rr_buf((char*)sldns_buffer_begin(buf), rr, &rr_len, &dname_len, pstate.default_ttl, pstate.origin_len?pstate.origin:NULL, pstate.origin_len, pstate.prev_rr_len?pstate.prev_rr:NULL, pstate.prev_rr_len); if(e != 0) { log_err("parse failure on first RR[%d]: %s", LDNS_WIREPARSE_OFFSET(e), sldns_get_errorstr_parse(LDNS_WIREPARSE_ERROR(e))); return 0; } /* check that class is correct */ if(sldns_wirerr_get_class(rr, rr_len, dname_len) != xfr->dclass) { log_err("parse failure: first record in downloaded zonefile " "from wrong RR class"); return 0; } return 1; } /** sum sizes of chunklist */ static size_t chunklist_sum(struct auth_chunk* list) { struct auth_chunk* p; size_t s = 0; for(p=list; p; p=p->next) { s += p->len; } return s; } /** for http download, parse and add RR to zone */ static int http_parse_add_rr(struct auth_xfer* xfr, struct auth_zone* z, sldns_buffer* buf, struct sldns_file_parse_state* pstate) { uint8_t rr[LDNS_RR_BUF_SIZE]; size_t rr_len, dname_len = 0; int e; char* line = (char*)sldns_buffer_begin(buf); rr_len = sizeof(rr); e = sldns_str2wire_rr_buf(line, rr, &rr_len, &dname_len, pstate->default_ttl, pstate->origin_len?pstate->origin:NULL, pstate->origin_len, pstate->prev_rr_len?pstate->prev_rr:NULL, pstate->prev_rr_len); if(e != 0) { log_err("%s/%s parse failure RR[%d]: %s in '%s'", xfr->task_transfer->master->host, xfr->task_transfer->master->file, LDNS_WIREPARSE_OFFSET(e), sldns_get_errorstr_parse(LDNS_WIREPARSE_ERROR(e)), line); return 0; } if(rr_len == 0) return 1; /* empty line or so */ /* set prev */ if(dname_len < sizeof(pstate->prev_rr)) { memmove(pstate->prev_rr, rr, dname_len); pstate->prev_rr_len = dname_len; } return az_insert_rr(z, rr, rr_len, dname_len, NULL); } /** RR list iterator, returns RRs from answer section one by one from the * dns packets in the chunklist */ static void chunk_rrlist_start(struct auth_xfer* xfr, struct auth_chunk** rr_chunk, int* rr_num, size_t* rr_pos) { *rr_chunk = xfr->task_transfer->chunks_first; *rr_num = 0; *rr_pos = 0; } /** RR list iterator, see if we are at the end of the list */ static int chunk_rrlist_end(struct auth_chunk* rr_chunk, int rr_num) { while(rr_chunk) { if(rr_chunk->len < LDNS_HEADER_SIZE) return 1; if(rr_num < (int)LDNS_ANCOUNT(rr_chunk->data)) return 0; /* no more RRs in this chunk */ /* continue with next chunk, see if it has RRs */ rr_chunk = rr_chunk->next; rr_num = 0; } return 1; } /** RR list iterator, move to next RR */ static void chunk_rrlist_gonext(struct auth_chunk** rr_chunk, int* rr_num, size_t* rr_pos, size_t rr_nextpos) { /* already at end of chunks? */ if(!*rr_chunk) return; /* move within this chunk */ if((*rr_chunk)->len >= LDNS_HEADER_SIZE && (*rr_num)+1 < (int)LDNS_ANCOUNT((*rr_chunk)->data)) { (*rr_num) += 1; *rr_pos = rr_nextpos; return; } /* no more RRs in this chunk */ /* continue with next chunk, see if it has RRs */ if(*rr_chunk) *rr_chunk = (*rr_chunk)->next; while(*rr_chunk) { *rr_num = 0; *rr_pos = 0; if((*rr_chunk)->len >= LDNS_HEADER_SIZE && LDNS_ANCOUNT((*rr_chunk)->data) > 0) { return; } *rr_chunk = (*rr_chunk)->next; } } /** RR iterator, get current RR information, false on parse error */ static int chunk_rrlist_get_current(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos, uint8_t** rr_dname, uint16_t* rr_type, uint16_t* rr_class, uint32_t* rr_ttl, uint16_t* rr_rdlen, uint8_t** rr_rdata, size_t* rr_nextpos) { sldns_buffer pkt; /* integrity checks on position */ if(!rr_chunk) return 0; if(rr_chunk->len < LDNS_HEADER_SIZE) return 0; if(rr_num >= (int)LDNS_ANCOUNT(rr_chunk->data)) return 0; if(rr_pos >= rr_chunk->len) return 0; /* fetch rr information */ sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len); if(rr_pos == 0) { size_t i; /* skip question section */ sldns_buffer_set_position(&pkt, LDNS_HEADER_SIZE); for(i=0; idata); i++) { if(pkt_dname_len(&pkt) == 0) return 0; if(sldns_buffer_remaining(&pkt) < 4) return 0; sldns_buffer_skip(&pkt, 4); /* type and class */ } } else { sldns_buffer_set_position(&pkt, rr_pos); } *rr_dname = sldns_buffer_current(&pkt); if(pkt_dname_len(&pkt) == 0) return 0; if(sldns_buffer_remaining(&pkt) < 10) return 0; *rr_type = sldns_buffer_read_u16(&pkt); *rr_class = sldns_buffer_read_u16(&pkt); *rr_ttl = sldns_buffer_read_u32(&pkt); *rr_rdlen = sldns_buffer_read_u16(&pkt); if(sldns_buffer_remaining(&pkt) < (*rr_rdlen)) return 0; *rr_rdata = sldns_buffer_current(&pkt); sldns_buffer_skip(&pkt, (ssize_t)(*rr_rdlen)); *rr_nextpos = sldns_buffer_position(&pkt); return 1; } /** print log message where we are in parsing the zone transfer */ static void log_rrlist_position(const char* label, struct auth_chunk* rr_chunk, uint8_t* rr_dname, uint16_t rr_type, size_t rr_counter) { sldns_buffer pkt; size_t dlen; uint8_t buf[LDNS_MAX_DOMAINLEN]; char str[LDNS_MAX_DOMAINLEN]; char typestr[32]; sldns_buffer_init_frm_data(&pkt, rr_chunk->data, rr_chunk->len); sldns_buffer_set_position(&pkt, (size_t)(rr_dname - sldns_buffer_begin(&pkt))); if((dlen=pkt_dname_len(&pkt)) == 0) return; if(dlen >= sizeof(buf)) return; dname_pkt_copy(&pkt, buf, rr_dname); dname_str(buf, str); (void)sldns_wire2str_type_buf(rr_type, typestr, sizeof(typestr)); verbose(VERB_ALGO, "%s at[%d] %s %s", label, (int)rr_counter, str, typestr); } /** check that start serial is OK for ixfr. we are at rr_counter == 0, * and we are going to check rr_counter == 1 (has to be type SOA) serial */ static int ixfr_start_serial(struct auth_chunk* rr_chunk, int rr_num, size_t rr_pos, uint8_t* rr_dname, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint16_t rr_rdlen, uint8_t* rr_rdata, size_t rr_nextpos, uint32_t transfer_serial, uint32_t xfr_serial) { uint32_t startserial; /* move forward on RR */ chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); if(chunk_rrlist_end(rr_chunk, rr_num)) { /* no second SOA */ verbose(VERB_OPS, "IXFR has no second SOA record"); return 0; } if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { verbose(VERB_OPS, "IXFR cannot parse second SOA record"); /* failed to parse RR */ return 0; } if(rr_type != LDNS_RR_TYPE_SOA) { verbose(VERB_OPS, "IXFR second record is not type SOA"); return 0; } if(rr_rdlen < 22) { verbose(VERB_OPS, "IXFR, second SOA has short rdlength"); return 0; /* bad SOA rdlen */ } startserial = sldns_read_uint32(rr_rdata+rr_rdlen-20); if(startserial == transfer_serial) { /* empty AXFR, not an IXFR */ verbose(VERB_OPS, "IXFR second serial same as first"); return 0; } if(startserial != xfr_serial) { /* wrong start serial, it does not match the serial in * memory */ verbose(VERB_OPS, "IXFR is from serial %u to %u but %u " "in memory, rejecting the zone transfer", (unsigned)startserial, (unsigned)transfer_serial, (unsigned)xfr_serial); return 0; } /* everything OK in second SOA serial */ return 1; } /** apply IXFR to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_ixfr(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { struct auth_chunk* rr_chunk; int rr_num; size_t rr_pos; uint8_t* rr_dname, *rr_rdata; uint16_t rr_type, rr_class, rr_rdlen; uint32_t rr_ttl; size_t rr_nextpos; int have_transfer_serial = 0; uint32_t transfer_serial = 0; size_t rr_counter = 0; int delmode = 0; int softfail = 0; /* start RR iterator over chunklist of packets */ chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); while(!chunk_rrlist_end(rr_chunk, rr_num)) { if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { /* failed to parse RR */ return 0; } if(verbosity>=7) log_rrlist_position("apply ixfr", rr_chunk, rr_dname, rr_type, rr_counter); /* twiddle add/del mode and check for start and end */ if(rr_counter == 0 && rr_type != LDNS_RR_TYPE_SOA) return 0; if(rr_counter == 1 && rr_type != LDNS_RR_TYPE_SOA) { /* this is an AXFR returned from the IXFR master */ /* but that should already have been detected, by * on_ixfr_is_axfr */ return 0; } if(rr_type == LDNS_RR_TYPE_SOA) { uint32_t serial; if(rr_rdlen < 22) return 0; /* bad SOA rdlen */ serial = sldns_read_uint32(rr_rdata+rr_rdlen-20); if(have_transfer_serial == 0) { have_transfer_serial = 1; transfer_serial = serial; delmode = 1; /* gets negated below */ /* check second RR before going any further */ if(!ixfr_start_serial(rr_chunk, rr_num, rr_pos, rr_dname, rr_type, rr_class, rr_ttl, rr_rdlen, rr_rdata, rr_nextpos, transfer_serial, xfr->serial)) { return 0; } } else if(transfer_serial == serial) { have_transfer_serial++; if(rr_counter == 1) { /* empty AXFR, with SOA; SOA; */ /* should have been detected by * on_ixfr_is_axfr */ return 0; } if(have_transfer_serial == 3) { /* see serial three times for end */ /* eg. IXFR: * SOA 3 start * SOA 1 second RR, followed by del * SOA 2 followed by add * SOA 2 followed by del * SOA 3 followed by add * SOA 3 end */ /* ended by SOA record */ xfr->serial = transfer_serial; break; } } /* twiddle add/del mode */ /* switch from delete part to add part and back again * just before the soa, it gets deleted and added too * this means we switch to delete mode for the final * SOA(so skip that one) */ delmode = !delmode; } /* process this RR */ /* if the RR is deleted twice or added twice, then we * softfail, and continue with the rest of the IXFR, so * that we serve something fairly nice during the refetch */ if(verbosity>=7) log_rrlist_position((delmode?"del":"add"), rr_chunk, rr_dname, rr_type, rr_counter); if(delmode) { /* delete this RR */ int nonexist = 0; if(!az_remove_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, &nonexist)) { /* failed, malloc error or so */ return 0; } if(nonexist) { /* it was removal of a nonexisting RR */ if(verbosity>=4) log_rrlist_position( "IXFR error nonexistent RR", rr_chunk, rr_dname, rr_type, rr_counter); softfail = 1; } } else if(rr_counter != 0) { /* skip first SOA RR for addition, it is added in * the addition part near the end of the ixfr, when * that serial is seen the second time. */ int duplicate = 0; /* add this RR */ if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, &duplicate)) { /* failed, malloc error or so */ return 0; } if(duplicate) { /* it was a duplicate */ if(verbosity>=4) log_rrlist_position( "IXFR error duplicate RR", rr_chunk, rr_dname, rr_type, rr_counter); softfail = 1; } } rr_counter++; chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); } if(softfail) { verbose(VERB_ALGO, "IXFR did not apply cleanly, fetching full zone"); return 0; } return 1; } /** apply AXFR to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_axfr(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { struct auth_chunk* rr_chunk; int rr_num; size_t rr_pos; uint8_t* rr_dname, *rr_rdata; uint16_t rr_type, rr_class, rr_rdlen; uint32_t rr_ttl; uint32_t serial = 0; size_t rr_nextpos; size_t rr_counter = 0; int have_end_soa = 0; /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; xfr->soa_zone_acquired = 0; /* insert all RRs in to the zone */ /* insert the SOA only once, skip the last one */ /* start RR iterator over chunklist of packets */ chunk_rrlist_start(xfr, &rr_chunk, &rr_num, &rr_pos); while(!chunk_rrlist_end(rr_chunk, rr_num)) { if(!chunk_rrlist_get_current(rr_chunk, rr_num, rr_pos, &rr_dname, &rr_type, &rr_class, &rr_ttl, &rr_rdlen, &rr_rdata, &rr_nextpos)) { /* failed to parse RR */ return 0; } if(verbosity>=7) log_rrlist_position("apply_axfr", rr_chunk, rr_dname, rr_type, rr_counter); if(rr_type == LDNS_RR_TYPE_SOA) { if(rr_counter != 0) { /* end of the axfr */ have_end_soa = 1; break; } if(rr_rdlen < 22) return 0; /* bad SOA rdlen */ serial = sldns_read_uint32(rr_rdata+rr_rdlen-20); } /* add this RR */ if(!az_insert_rr_decompress(z, rr_chunk->data, rr_chunk->len, scratch_buffer, rr_dname, rr_type, rr_class, rr_ttl, rr_rdata, rr_rdlen, NULL)) { /* failed, malloc error or so */ return 0; } rr_counter++; chunk_rrlist_gonext(&rr_chunk, &rr_num, &rr_pos, rr_nextpos); } if(!have_end_soa) { log_err("no end SOA record for AXFR"); return 0; } xfr->serial = serial; xfr->have_zone = 1; return 1; } /** apply HTTP to zone in memory. z is locked. false on failure(mallocfail) */ static int apply_http(struct auth_xfer* xfr, struct auth_zone* z, struct sldns_buffer* scratch_buffer) { /* parse data in chunks */ /* parse RR's and read into memory. ignore $INCLUDE from the * downloaded file*/ struct sldns_file_parse_state pstate; struct auth_chunk* chunk; size_t chunk_pos; int ret; memset(&pstate, 0, sizeof(pstate)); pstate.default_ttl = 3600; if(xfr->namelen < sizeof(pstate.origin)) { pstate.origin_len = xfr->namelen; memmove(pstate.origin, xfr->name, xfr->namelen); } if(verbosity >= VERB_ALGO) verbose(VERB_ALGO, "http download %s of size %d", xfr->task_transfer->master->file, (int)chunklist_sum(xfr->task_transfer->chunks_first)); if(xfr->task_transfer->chunks_first && verbosity >= VERB_ALGO) { char preview[1024]; if(xfr->task_transfer->chunks_first->len+1 > sizeof(preview)) { memmove(preview, xfr->task_transfer->chunks_first->data, sizeof(preview)-1); preview[sizeof(preview)-1]=0; } else { memmove(preview, xfr->task_transfer->chunks_first->data, xfr->task_transfer->chunks_first->len); preview[xfr->task_transfer->chunks_first->len]=0; } log_info("auth zone http downloaded content preview: %s", preview); } /* perhaps a little syntax check before we try to apply the data? */ if(!http_zonefile_syntax_check(xfr, scratch_buffer)) { log_err("http download %s/%s does not contain a zonefile, " "but got '%s'", xfr->task_transfer->master->host, xfr->task_transfer->master->file, sldns_buffer_begin(scratch_buffer)); return 0; } /* clear the data tree */ traverse_postorder(&z->data, auth_data_del, NULL); rbtree_init(&z->data, &auth_data_cmp); /* clear the RPZ policies */ if(z->rpz) rpz_clear(z->rpz); xfr->have_zone = 0; xfr->serial = 0; xfr->soa_zone_acquired = 0; chunk = xfr->task_transfer->chunks_first; chunk_pos = 0; pstate.lineno = 0; while(chunkline_get_line_collated(&chunk, &chunk_pos, scratch_buffer)) { /* process this line */ pstate.lineno++; chunkline_newline_removal(scratch_buffer); if(chunkline_is_comment_line_or_empty(scratch_buffer)) { continue; } /* parse line and add RR */ if((ret=http_parse_origin(scratch_buffer, &pstate))!=0) { if(ret == 2) { verbose(VERB_ALGO, "error parsing ORIGIN on line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } continue; /* $ORIGIN has been handled */ } if((ret=http_parse_ttl(scratch_buffer, &pstate))!=0) { if(ret == 2) { verbose(VERB_ALGO, "error parsing TTL on line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } continue; /* $TTL has been handled */ } if(!http_parse_add_rr(xfr, z, scratch_buffer, &pstate)) { verbose(VERB_ALGO, "error parsing line [%s:%d] %s", xfr->task_transfer->master->file, pstate.lineno, sldns_buffer_begin(scratch_buffer)); return 0; } } return 1; } /** write http chunks to zonefile to create downloaded file */ static int auth_zone_write_chunks(struct auth_xfer* xfr, const char* fname) { FILE* out; struct auth_chunk* p; out = fopen(fname, "w"); if(!out) { log_err("could not open %s: %s", fname, strerror(errno)); return 0; } for(p = xfr->task_transfer->chunks_first; p ; p = p->next) { if(!write_out(out, (char*)p->data, p->len)) { log_err("could not write http download to %s", fname); fclose(out); return 0; } } fclose(out); return 1; } /** write to zonefile after zone has been updated */ static void xfr_write_after_update(struct auth_xfer* xfr, struct module_env* env) { struct config_file* cfg = env->cfg; struct auth_zone* z; char tmpfile[1024]; char* zfilename; lock_basic_unlock(&xfr->lock); /* get lock again, so it is a readlock and concurrently queries * can be answered */ lock_rw_rdlock(&env->auth_zones->lock); z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!z) { lock_rw_unlock(&env->auth_zones->lock); /* the zone is gone, ignore xfr results */ lock_basic_lock(&xfr->lock); return; } lock_rw_rdlock(&z->lock); lock_basic_lock(&xfr->lock); lock_rw_unlock(&env->auth_zones->lock); if(z->zonefile == NULL || z->zonefile[0] == 0) { lock_rw_unlock(&z->lock); /* no write needed, no zonefile set */ return; } zfilename = z->zonefile; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(zfilename, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) zfilename += strlen(cfg->chrootdir); if(verbosity >= VERB_ALGO) { char nm[LDNS_MAX_DOMAINLEN]; dname_str(z->name, nm); verbose(VERB_ALGO, "write zonefile %s for %s", zfilename, nm); } /* write to tempfile first */ if((size_t)strlen(zfilename) + 16 > sizeof(tmpfile)) { verbose(VERB_ALGO, "tmpfilename too long, cannot update " " zonefile %s", zfilename); lock_rw_unlock(&z->lock); return; } snprintf(tmpfile, sizeof(tmpfile), "%s.tmp%u", zfilename, (unsigned)getpid()); if(xfr->task_transfer->master->http) { /* use the stored chunk list to write them */ if(!auth_zone_write_chunks(xfr, tmpfile)) { unlink(tmpfile); lock_rw_unlock(&z->lock); return; } } else if(!auth_zone_write_file(z, tmpfile)) { unlink(tmpfile); lock_rw_unlock(&z->lock); return; } #ifdef UB_ON_WINDOWS (void)unlink(zfilename); /* windows does not replace file with rename() */ #endif if(rename(tmpfile, zfilename) < 0) { log_err("could not rename(%s, %s): %s", tmpfile, zfilename, strerror(errno)); unlink(tmpfile); lock_rw_unlock(&z->lock); return; } lock_rw_unlock(&z->lock); } /** reacquire locks and structures. Starts with no locks, ends * with xfr and z locks, if fail, no z lock */ static int xfr_process_reacquire_locks(struct auth_xfer* xfr, struct module_env* env, struct auth_zone** z) { /* release xfr lock, then, while holding az->lock grab both * z->lock and xfr->lock */ lock_rw_rdlock(&env->auth_zones->lock); *z = auth_zone_find(env->auth_zones, xfr->name, xfr->namelen, xfr->dclass); if(!*z) { lock_rw_unlock(&env->auth_zones->lock); lock_basic_lock(&xfr->lock); *z = NULL; return 0; } lock_rw_wrlock(&(*z)->lock); lock_basic_lock(&xfr->lock); lock_rw_unlock(&env->auth_zones->lock); return 1; } /** process chunk list and update zone in memory, * return false if it did not work */ static int xfr_process_chunk_list(struct auth_xfer* xfr, struct module_env* env, int* ixfr_fail) { struct auth_zone* z; /* obtain locks and structures */ lock_basic_unlock(&xfr->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) { /* the zone is gone, ignore xfr results */ return 0; } /* holding xfr and z locks */ /* apply data */ if(xfr->task_transfer->master->http) { if(!apply_http(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "http from %s: could not store data", xfr->task_transfer->master->host); return 0; } } else if(xfr->task_transfer->on_ixfr && !xfr->task_transfer->on_ixfr_is_axfr) { if(!apply_ixfr(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: could not store IXFR" " data", xfr->task_transfer->master->host); *ixfr_fail = 1; return 0; } } else { if(!apply_axfr(xfr, z, env->scratch_buffer)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: could not store AXFR" " data", xfr->task_transfer->master->host); return 0; } } xfr->zone_expired = 0; z->zone_expired = 0; if(!xfr_find_soa(z, xfr)) { lock_rw_unlock(&z->lock); verbose(VERB_ALGO, "xfr from %s: no SOA in zone after update" " (or malformed RR)", xfr->task_transfer->master->host); return 0; } z->soa_zone_acquired = *env->now; xfr->soa_zone_acquired = *env->now; /* release xfr lock while verifying zonemd because it may have * to spawn lookups in the state machines */ lock_basic_unlock(&xfr->lock); /* holding z lock */ auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 0); if(z->zone_expired) { char zname[LDNS_MAX_DOMAINLEN]; /* ZONEMD must have failed */ /* reacquire locks, so we hold xfr lock on exit of routine, * and both xfr and z again after releasing xfr for potential * state machine mesh callbacks */ lock_rw_unlock(&z->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) return 0; dname_str(xfr->name, zname); verbose(VERB_ALGO, "xfr from %s: ZONEMD failed for %s, transfer is failed", xfr->task_transfer->master->host, zname); xfr->zone_expired = 1; lock_rw_unlock(&z->lock); return 0; } /* reacquire locks, so we hold xfr lock on exit of routine, * and both xfr and z again after releasing xfr for potential * state machine mesh callbacks */ lock_rw_unlock(&z->lock); if(!xfr_process_reacquire_locks(xfr, env, &z)) return 0; /* holding xfr and z locks */ if(xfr->have_zone) xfr->lease_time = *env->now; if(z->rpz) rpz_finish_config(z->rpz); /* unlock */ lock_rw_unlock(&z->lock); if(verbosity >= VERB_QUERY && xfr->have_zone) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_QUERY, "auth zone %s updated to serial %u", zname, (unsigned)xfr->serial); } /* see if we need to write to a zonefile */ xfr_write_after_update(xfr, env); return 1; } /** disown task_transfer. caller must hold xfr.lock */ static void xfr_transfer_disown(struct auth_xfer* xfr) { /* remove timer (from this worker's event base) */ comm_timer_delete(xfr->task_transfer->timer); xfr->task_transfer->timer = NULL; /* remove the commpoint */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; /* we don't own this item anymore */ xfr->task_transfer->worker = NULL; xfr->task_transfer->env = NULL; } /** lookup a host name for its addresses, if needed */ static int xfr_transfer_lookup_host(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_transfer->lookup_target; struct query_info qinfo; uint16_t qflags = BIT_RD; uint8_t dname[LDNS_MAX_DOMAINLEN+1]; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; if(!master) return 0; if(extstrtoaddr(master->host, &addr, &addrlen, UNBOUND_DNS_PORT)) { /* not needed, host is in IP addr format */ return 0; } if(master->allow_notify) return 0; /* allow-notifies are not transferred from, no lookup is needed */ /* use mesh_new_callback to probe for non-addr hosts, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = sizeof(dname); if(sldns_str2wire_dname_buf(master->host, dname, &qinfo.qname_len) != 0) { log_err("cannot parse host name of master %s", master->host); return 0; } qinfo.qname = dname; qinfo.qclass = xfr->dclass; qinfo.qtype = LDNS_RR_TYPE_A; if(xfr->task_transfer->lookup_aaaa) qinfo.qtype = LDNS_RR_TYPE_AAAA; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_transfer", buf2); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; edns.padding_block_size = 0; edns.cookie_present = 0; edns.cookie_valid = 0; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* unlock xfr during mesh_new_callback() because the callback can be * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_xfer_transfer_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; } lock_basic_lock(&xfr->lock); return 1; } /** initiate TCP to the target and fetch zone. * returns true if that was successfully started, and timeout setup. */ static int xfr_transfer_init_fetch(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_transfer->master; char *auth_name = NULL; struct timeval t; int timeout; if(!master) return 0; if(master->allow_notify) return 0; /* only for notify */ /* get master addr */ if(xfr->task_transfer->scan_addr) { addrlen = xfr->task_transfer->scan_addr->addrlen; memmove(&addr, &xfr->task_transfer->scan_addr->addr, addrlen); } else { if(!authextstrtoaddr(master->host, &addr, &addrlen, &auth_name)) { /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot transfer from master %s", zname, master->host); return 0; } } /* remove previous TCP connection (if any) */ if(xfr->task_transfer->cp) { comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; } if(!xfr->task_transfer->timer) { xfr->task_transfer->timer = comm_timer_create(env->worker_base, auth_xfer_transfer_timer_callback, xfr); if(!xfr->task_transfer->timer) { log_err("malloc failure"); return 0; } } timeout = AUTH_TRANSFER_TIMEOUT; #ifndef S_SPLINT_S t.tv_sec = timeout/1000; t.tv_usec = (timeout%1000)*1000; #endif if(master->http) { /* perform http fetch */ /* store http port number into sockaddr, * unless someone used unbound's host@port notation */ xfr->task_transfer->on_ixfr = 0; if(strchr(master->host, '@') == NULL) sockaddr_store_port(&addr, addrlen, master->port); xfr->task_transfer->cp = outnet_comm_point_for_http( env->outnet, auth_xfer_transfer_http_callback, xfr, &addr, addrlen, -1, master->ssl, master->host, master->file, env->cfg); if(!xfr->task_transfer->cp) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create http cp " "connection for %s to %s", zname, as); return 0; } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next HTTP fetch from %s started", zname, as); } /* Create or refresh the list of allow_notify addrs */ probe_copy_masters_for_allow_notify(xfr); return 1; } /* perform AXFR/IXFR */ /* set the packet to be written */ /* create new ID */ xfr->task_transfer->id = GET_RANDOM_ID(env->rnd); xfr_create_ixfr_packet(xfr, env->scratch_buffer, xfr->task_transfer->id, master); /* connect on fd */ xfr->task_transfer->cp = outnet_comm_point_for_tcp(env->outnet, auth_xfer_transfer_tcp_callback, xfr, &addr, addrlen, env->scratch_buffer, -1, auth_name != NULL, auth_name); if(!xfr->task_transfer->cp) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create tcp cp connection for " "xfr %s to %s", zname, as); return 0; } comm_timer_set(xfr->task_transfer->timer, &t); if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s transfer next %s fetch from %s started", zname, (xfr->task_transfer->on_ixfr?"IXFR":"AXFR"), as); } return 1; } /** perform next lookup, next transfer TCP, or end and resume wait time task */ static void xfr_transfer_nexttarget_or_end(struct auth_xfer* xfr, struct module_env* env) { log_assert(xfr->task_transfer->worker == env->worker); /* are we performing lookups? */ while(xfr->task_transfer->lookup_target) { if(xfr_transfer_lookup_host(xfr, env)) { /* wait for lookup to finish, * note that the hostname may be in unbound's cache * and we may then get an instant cache response, * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer next target lookup", zname); } lock_basic_unlock(&xfr->lock); return; } xfr_transfer_move_to_next_lookup(xfr, env); } /* initiate TCP and fetch the zone from the master */ /* and set timeout on it */ while(!xfr_transfer_end_of_list(xfr)) { xfr->task_transfer->master = xfr_transfer_current_master(xfr); if(xfr_transfer_init_fetch(xfr, env)) { /* successfully started, wait for callback */ lock_basic_unlock(&xfr->lock); return; } /* failed to fetch, next master */ xfr_transfer_nextmaster(xfr); } if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s transfer failed, wait", zname); } /* we failed to fetch the zone, move to wait task * use the shorter retry timeout */ xfr_transfer_disown(xfr); /* pick up the nextprobe task and wait */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 1, 0); lock_basic_unlock(&xfr->lock); } /** add addrs from A or AAAA rrset to the master */ static void xfr_master_add_addrs(struct auth_master* m, struct ub_packed_rrset_key* rrset, uint16_t rrtype) { size_t i; struct packed_rrset_data* data; if(!m || !rrset) return; if(rrtype != LDNS_RR_TYPE_A && rrtype != LDNS_RR_TYPE_AAAA) return; data = (struct packed_rrset_data*)rrset->entry.data; for(i=0; icount; i++) { struct auth_addr* a; size_t len = data->rr_len[i] - 2; uint8_t* rdata = data->rr_data[i]+2; if(rrtype == LDNS_RR_TYPE_A && len != INET_SIZE) continue; /* wrong length for A */ if(rrtype == LDNS_RR_TYPE_AAAA && len != INET6_SIZE) continue; /* wrong length for AAAA */ /* add and alloc it */ a = (struct auth_addr*)calloc(1, sizeof(*a)); if(!a) { log_err("out of memory"); return; } if(rrtype == LDNS_RR_TYPE_A) { struct sockaddr_in* sa; a->addrlen = (socklen_t)sizeof(*sa); sa = (struct sockaddr_in*)&a->addr; sa->sin_family = AF_INET; sa->sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa->sin_addr, rdata, INET_SIZE); } else { struct sockaddr_in6* sa; a->addrlen = (socklen_t)sizeof(*sa); sa = (struct sockaddr_in6*)&a->addr; sa->sin6_family = AF_INET6; sa->sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa->sin6_addr, rdata, INET6_SIZE); } if(verbosity >= VERB_ALGO) { char s[64]; addr_port_to_str(&a->addr, a->addrlen, s, sizeof(s)); verbose(VERB_ALGO, "auth host %s lookup %s", m->host, s); } /* append to list */ a->next = m->list; m->list = a; } } /** callback for task_transfer lookup of host name, of A or AAAA */ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* process result */ if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; if(xfr->task_transfer->lookup_aaaa) wanted_qtype = LDNS_RR_TYPE_AAAA; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer) { xfr_master_add_addrs(xfr->task_transfer-> lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has nodata", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup has no answer", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s transfer lookup failed", zname, xfr->task_transfer->lookup_target->host, (xfr->task_transfer->lookup_aaaa?"AAAA":"A")); } } if(xfr->task_transfer->lookup_target->list && xfr->task_transfer->lookup_target == xfr_transfer_current_master(xfr)) xfr->task_transfer->scan_addr = xfr->task_transfer->lookup_target->list; /* move to lookup AAAA after A lookup, move to next hostname lookup, * or move to fetch the zone, or, if nothing to do, end task_transfer */ xfr_transfer_move_to_next_lookup(xfr, env); xfr_transfer_nexttarget_or_end(xfr, env); } /** check if xfer (AXFR or IXFR) packet is OK. * return false if we lost connection (SERVFAIL, or unreadable). * return false if we need to move from IXFR to AXFR, with gonextonfail * set to false, so the same master is tried again, but with AXFR. * return true if fine to link into data. * return true with transferdone=true when the transfer has ended. */ static int check_xfer_packet(sldns_buffer* pkt, struct auth_xfer* xfr, int* gonextonfail, int* transferdone) { uint8_t* wire = sldns_buffer_begin(pkt); int i; if(sldns_buffer_limit(pkt) < LDNS_HEADER_SIZE) { verbose(VERB_ALGO, "xfr to %s failed, packet too small", xfr->task_transfer->master->host); return 0; } if(!LDNS_QR_WIRE(wire)) { verbose(VERB_ALGO, "xfr to %s failed, packet has no QR flag", xfr->task_transfer->master->host); return 0; } if(LDNS_TC_WIRE(wire)) { verbose(VERB_ALGO, "xfr to %s failed, packet has TC flag", xfr->task_transfer->master->host); return 0; } /* check ID */ if(LDNS_ID_WIRE(wire) != xfr->task_transfer->id) { verbose(VERB_ALGO, "xfr to %s failed, packet wrong ID", xfr->task_transfer->master->host); return 0; } if(LDNS_RCODE_WIRE(wire) != LDNS_RCODE_NOERROR) { char rcode[32]; sldns_wire2str_rcode_buf((int)LDNS_RCODE_WIRE(wire), rcode, sizeof(rcode)); /* if we are doing IXFR, check for fallback */ if(xfr->task_transfer->on_ixfr) { if(LDNS_RCODE_WIRE(wire) == LDNS_RCODE_NOTIMPL || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_SERVFAIL || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_REFUSED || LDNS_RCODE_WIRE(wire) == LDNS_RCODE_FORMERR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (with rcode %s)", xfr->task_transfer->master->host, rcode); xfr->task_transfer->ixfr_fail = 1; *gonextonfail = 0; return 0; } } verbose(VERB_ALGO, "xfr to %s failed, packet with rcode %s", xfr->task_transfer->master->host, rcode); return 0; } if(LDNS_OPCODE_WIRE(wire) != LDNS_PACKET_QUERY) { verbose(VERB_ALGO, "xfr to %s failed, packet with bad opcode", xfr->task_transfer->master->host); return 0; } if(LDNS_QDCOUNT(wire) > 1) { verbose(VERB_ALGO, "xfr to %s failed, packet has qdcount %d", xfr->task_transfer->master->host, (int)LDNS_QDCOUNT(wire)); return 0; } /* check qname */ sldns_buffer_set_position(pkt, LDNS_HEADER_SIZE); for(i=0; i<(int)LDNS_QDCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t qtype, qclass; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname", xfr->task_transfer->master->host); return 0; } if(dname_pkt_compare(pkt, sldns_buffer_at(pkt, pos), xfr->name) != 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "wrong qname", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 4) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated query RR", xfr->task_transfer->master->host); return 0; } qtype = sldns_buffer_read_u16(pkt); qclass = sldns_buffer_read_u16(pkt); if(qclass != xfr->dclass) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "wrong qclass", xfr->task_transfer->master->host); return 0; } if(xfr->task_transfer->on_ixfr) { if(qtype != LDNS_RR_TYPE_IXFR) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with wrong qtype, expected IXFR", xfr->task_transfer->master->host); return 0; } } else { if(qtype != LDNS_RR_TYPE_AXFR) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with wrong qtype, expected AXFR", xfr->task_transfer->master->host); return 0; } } } /* check parse of RRs in packet, store first SOA serial * to be able to detect last SOA (with that serial) to see if done */ /* also check for IXFR 'zone up to date' reply */ for(i=0; i<(int)LDNS_ANCOUNT(wire); i++) { size_t pos = sldns_buffer_position(pkt); uint16_t tp, rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in answer section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } tp = sldns_buffer_read_u16(pkt); (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* RR parses (haven't checked rdata itself), now look at * SOA records to see serial number */ if(xfr->task_transfer->rr_scan_num == 0 && tp != LDNS_RR_TYPE_SOA) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed zone transfer, no start SOA", xfr->task_transfer->master->host); return 0; } if(xfr->task_transfer->rr_scan_num == 1 && tp != LDNS_RR_TYPE_SOA) { /* second RR is not a SOA record, this is not an IXFR * the master is replying with an AXFR */ xfr->task_transfer->on_ixfr_is_axfr = 1; } if(tp == LDNS_RR_TYPE_SOA) { uint32_t serial; if(rdlen < 22) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with SOA with malformed rdata", xfr->task_transfer->master->host); return 0; } if(dname_pkt_compare(pkt, sldns_buffer_at(pkt, pos), xfr->name) != 0) { verbose(VERB_ALGO, "xfr to %s failed, packet " "with SOA with wrong dname", xfr->task_transfer->master->host); return 0; } /* read serial number of SOA */ serial = sldns_buffer_read_u32_at(pkt, sldns_buffer_position(pkt)+rdlen-20); /* check for IXFR 'zone has SOA x' reply */ if(xfr->task_transfer->on_ixfr && xfr->task_transfer->rr_scan_num == 0 && LDNS_ANCOUNT(wire)==1) { verbose(VERB_ALGO, "xfr to %s ended, " "IXFR reply that zone has serial %u," " fallback from IXFR to AXFR", xfr->task_transfer->master->host, (unsigned)serial); xfr->task_transfer->ixfr_fail = 1; *gonextonfail = 0; return 0; } /* if first SOA, store serial number */ if(xfr->task_transfer->got_xfr_serial == 0) { xfr->task_transfer->got_xfr_serial = 1; xfr->task_transfer->incoming_xfr_serial = serial; verbose(VERB_ALGO, "xfr %s: contains " "SOA serial %u", xfr->task_transfer->master->host, (unsigned)serial); /* see if end of AXFR */ } else if(!xfr->task_transfer->on_ixfr || xfr->task_transfer->on_ixfr_is_axfr) { /* second SOA with serial is the end * for AXFR */ *transferdone = 1; verbose(VERB_ALGO, "xfr %s: last AXFR packet", xfr->task_transfer->master->host); /* for IXFR, count SOA records with that serial */ } else if(xfr->task_transfer->incoming_xfr_serial == serial && xfr->task_transfer->got_xfr_serial == 1) { xfr->task_transfer->got_xfr_serial++; /* if not first soa, if serial==firstserial, the * third time we are at the end, for IXFR */ } else if(xfr->task_transfer->incoming_xfr_serial == serial && xfr->task_transfer->got_xfr_serial == 2) { verbose(VERB_ALGO, "xfr %s: last IXFR packet", xfr->task_transfer->master->host); *transferdone = 1; /* continue parse check, if that succeeds, * transfer is done */ } } xfr->task_transfer->rr_scan_num++; /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } /* check authority section */ /* we skip over the RRs checking packet format */ for(i=0; i<(int)LDNS_NSCOUNT(wire); i++) { uint16_t rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in authority section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } (void)sldns_buffer_read_u16(pkt); /* type */ (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } /* check additional section */ for(i=0; i<(int)LDNS_ARCOUNT(wire); i++) { uint16_t rdlen; if(pkt_dname_len(pkt) == 0) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "malformed dname in additional section", xfr->task_transfer->master->host); return 0; } if(sldns_buffer_remaining(pkt) < 10) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR", xfr->task_transfer->master->host); return 0; } (void)sldns_buffer_read_u16(pkt); /* type */ (void)sldns_buffer_read_u16(pkt); /* class */ (void)sldns_buffer_read_u32(pkt); /* ttl */ rdlen = sldns_buffer_read_u16(pkt); if(sldns_buffer_remaining(pkt) < rdlen) { verbose(VERB_ALGO, "xfr to %s failed, packet with " "truncated RR rdata", xfr->task_transfer->master->host); return 0; } /* skip over RR rdata to go to the next RR */ sldns_buffer_skip(pkt, (ssize_t)rdlen); } return 1; } /** Link the data from this packet into the worklist of transferred data */ static int xfer_link_data(sldns_buffer* pkt, struct auth_xfer* xfr) { /* alloc it */ struct auth_chunk* e; e = (struct auth_chunk*)calloc(1, sizeof(*e)); if(!e) return 0; e->next = NULL; e->len = sldns_buffer_limit(pkt); e->data = memdup(sldns_buffer_begin(pkt), e->len); if(!e->data) { free(e); return 0; } /* alloc succeeded, link into list */ if(!xfr->task_transfer->chunks_first) xfr->task_transfer->chunks_first = e; if(xfr->task_transfer->chunks_last) xfr->task_transfer->chunks_last->next = e; xfr->task_transfer->chunks_last = e; return 1; } /** task transfer. the list of data is complete. process it and if failed * move to next master, if succeeded, end the task transfer */ static void process_list_end_transfer(struct auth_xfer* xfr, struct module_env* env) { int ixfr_fail = 0; if(xfr_process_chunk_list(xfr, env, &ixfr_fail)) { /* it worked! */ auth_chunks_delete(xfr->task_transfer); /* we fetched the zone, move to wait task */ xfr_transfer_disown(xfr); if(xfr->notify_received && (!xfr->notify_has_serial || (xfr->notify_has_serial && xfr_serial_means_update(xfr, xfr->notify_serial)))) { uint32_t sr = xfr->notify_serial; int has_sr = xfr->notify_has_serial; /* we received a notify while probe/transfer was * in progress. start a new probe and transfer */ xfr->notify_received = 0; xfr->notify_has_serial = 0; xfr->notify_serial = 0; if(!xfr_start_probe(xfr, env, NULL)) { /* if we couldn't start it, already in * progress; restore notify serial, * while xfr still locked */ xfr->notify_received = 1; xfr->notify_has_serial = has_sr; xfr->notify_serial = sr; lock_basic_unlock(&xfr->lock); } return; } else { /* pick up the nextprobe task and wait (normail wait time) */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); } lock_basic_unlock(&xfr->lock); return; } /* processing failed */ /* when done, delete data from list */ auth_chunks_delete(xfr->task_transfer); if(ixfr_fail) { xfr->task_transfer->ixfr_fail = 1; } else { xfr_transfer_nextmaster(xfr); } xfr_transfer_nexttarget_or_end(xfr, env); } /** callback for the task_transfer timer */ void auth_xfer_transfer_timer_callback(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; int gonextonfail = 1; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } verbose(VERB_ALGO, "xfr stopped, connection timeout to %s", xfr->task_transfer->master->host); /* see if IXFR caused the failure, if so, try AXFR */ if(xfr->task_transfer->on_ixfr) { xfr->task_transfer->ixfr_possible_timeout_count++; if(xfr->task_transfer->ixfr_possible_timeout_count >= NUM_TIMEOUTS_FALLBACK_IXFR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (because of timeouts)", xfr->task_transfer->master->host); xfr->task_transfer->ixfr_fail = 1; gonextonfail = 0; } } /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; if(gonextonfail) xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); } /** callback for task_transfer tcp connections */ int auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* ATTR_UNUSED(repinfo)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; int gonextonfail = 1; int transferdone = 0; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } /* stop the timer */ comm_timer_disable(xfr->task_transfer->timer); if(err != NETEVENT_NOERROR) { /* connection failed, closed, or timeout */ /* stop this transfer, cleanup * and continue task_transfer*/ verbose(VERB_ALGO, "xfr stopped, connection lost to %s", xfr->task_transfer->master->host); /* see if IXFR caused the failure, if so, try AXFR */ if(xfr->task_transfer->on_ixfr) { xfr->task_transfer->ixfr_possible_timeout_count++; if(xfr->task_transfer->ixfr_possible_timeout_count >= NUM_TIMEOUTS_FALLBACK_IXFR) { verbose(VERB_ALGO, "xfr to %s, fallback " "from IXFR to AXFR (because of timeouts)", xfr->task_transfer->master->host); xfr->task_transfer->ixfr_fail = 1; gonextonfail = 0; } } failed: /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; if(gonextonfail) xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); return 0; } /* note that IXFR worked without timeout */ if(xfr->task_transfer->on_ixfr) xfr->task_transfer->ixfr_possible_timeout_count = 0; /* handle returned packet */ /* if it fails, cleanup and end this transfer */ /* if it needs to fallback from IXFR to AXFR, do that */ if(!check_xfer_packet(c->buffer, xfr, &gonextonfail, &transferdone)) { goto failed; } /* if it is good, link it into the list of data */ /* if the link into list of data fails (malloc fail) cleanup and end */ if(!xfer_link_data(c->buffer, xfr)) { verbose(VERB_ALGO, "xfr stopped to %s, malloc failed", xfr->task_transfer->master->host); goto failed; } /* if the transfer is done now, disconnect and process the list */ if(transferdone) { comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; process_list_end_transfer(xfr, env); return 0; } /* if we want to read more messages, setup the commpoint to read * a DNS packet, and the timeout */ lock_basic_unlock(&xfr->lock); c->tcp_is_reading = 1; sldns_buffer_clear(c->buffer); comm_point_start_listening(c, -1, AUTH_TRANSFER_TIMEOUT); return 0; } /** callback for task_transfer http connections */ int auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_transfer); lock_basic_lock(&xfr->lock); env = xfr->task_transfer->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } verbose(VERB_ALGO, "auth zone transfer http callback"); /* stop the timer */ comm_timer_disable(xfr->task_transfer->timer); if(err != NETEVENT_NOERROR && err != NETEVENT_DONE) { /* connection failed, closed, or timeout */ /* stop this transfer, cleanup * and continue task_transfer*/ verbose(VERB_ALGO, "http stopped, connection lost to %s", xfr->task_transfer->master->host); failed: /* delete transferred data from list */ auth_chunks_delete(xfr->task_transfer); if(repinfo) repinfo->c = NULL; /* signal cp deleted to the routine calling this callback */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; xfr_transfer_nextmaster(xfr); xfr_transfer_nexttarget_or_end(xfr, env); return 0; } /* if it is good, link it into the list of data */ /* if the link into list of data fails (malloc fail) cleanup and end */ if(sldns_buffer_limit(c->buffer) > 0) { verbose(VERB_ALGO, "auth zone http queued up %d bytes", (int)sldns_buffer_limit(c->buffer)); if(!xfer_link_data(c->buffer, xfr)) { verbose(VERB_ALGO, "http stopped to %s, malloc failed", xfr->task_transfer->master->host); goto failed; } } /* if the transfer is done now, disconnect and process the list */ if(err == NETEVENT_DONE) { if(repinfo) repinfo->c = NULL; /* signal cp deleted to the routine calling this callback */ comm_point_delete(xfr->task_transfer->cp); xfr->task_transfer->cp = NULL; process_list_end_transfer(xfr, env); return 0; } /* if we want to read more messages, setup the commpoint to read * a DNS packet, and the timeout */ lock_basic_unlock(&xfr->lock); c->tcp_is_reading = 1; sldns_buffer_clear(c->buffer); comm_point_start_listening(c, -1, AUTH_TRANSFER_TIMEOUT); return 0; } /** start transfer task by this worker , xfr is locked. */ static void xfr_start_transfer(struct auth_xfer* xfr, struct module_env* env, struct auth_master* master) { log_assert(xfr->task_transfer != NULL); log_assert(xfr->task_transfer->worker == NULL); log_assert(xfr->task_transfer->chunks_first == NULL); log_assert(xfr->task_transfer->chunks_last == NULL); xfr->task_transfer->worker = env->worker; xfr->task_transfer->env = env; /* init transfer process */ /* find that master in the transfer's list of masters? */ xfr_transfer_start_list(xfr, master); /* start lookup for hostnames in transfer master list */ xfr_transfer_start_lookups(xfr); /* initiate TCP, and set timeout on it */ xfr_transfer_nexttarget_or_end(xfr, env); } /** disown task_probe. caller must hold xfr.lock */ static void xfr_probe_disown(struct auth_xfer* xfr) { /* remove timer (from this worker's event base) */ comm_timer_delete(xfr->task_probe->timer); xfr->task_probe->timer = NULL; /* remove the commpoint */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* we don't own this item anymore */ xfr->task_probe->worker = NULL; xfr->task_probe->env = NULL; } /** send the UDP probe to the master, this is part of task_probe */ static int xfr_probe_send_probe(struct auth_xfer* xfr, struct module_env* env, int timeout) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct timeval t; /* pick master */ struct auth_master* master = xfr_probe_current_master(xfr); char *auth_name = NULL; if(!master) return 0; if(master->allow_notify) return 0; /* only for notify */ if(master->http) return 0; /* only masters get SOA UDP probe, not urls, if those are in this list */ /* get master addr */ if(xfr->task_probe->scan_addr) { addrlen = xfr->task_probe->scan_addr->addrlen; memmove(&addr, &xfr->task_probe->scan_addr->addr, addrlen); } else { if(!authextstrtoaddr(master->host, &addr, &addrlen, &auth_name)) { /* the ones that are not in addr format are supposed * to be looked up. The lookup has failed however, * so skip them */ char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("%s: failed lookup, cannot probe to master %s", zname, master->host); return 0; } if (auth_name != NULL) { if (addr.ss_family == AF_INET && (int)ntohs(((struct sockaddr_in *)&addr)->sin_port) == env->cfg->ssl_port) ((struct sockaddr_in *)&addr)->sin_port = htons((uint16_t)env->cfg->port); else if (addr.ss_family == AF_INET6 && (int)ntohs(((struct sockaddr_in6 *)&addr)->sin6_port) == env->cfg->ssl_port) ((struct sockaddr_in6 *)&addr)->sin6_port = htons((uint16_t)env->cfg->port); } } /* create packet */ /* create new ID for new probes, but not on timeout retries, * this means we'll accept replies to previous retries to same ip */ if(timeout == AUTH_PROBE_TIMEOUT) xfr->task_probe->id = GET_RANDOM_ID(env->rnd); xfr_create_soa_probe_packet(xfr, env->scratch_buffer, xfr->task_probe->id); /* we need to remove the cp if we have a different ip4/ip6 type now */ if(xfr->task_probe->cp && ((xfr->task_probe->cp_is_ip6 && !addr_is_ip6(&addr, addrlen)) || (!xfr->task_probe->cp_is_ip6 && addr_is_ip6(&addr, addrlen))) ) { comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; } if(!xfr->task_probe->cp) { if(addr_is_ip6(&addr, addrlen)) xfr->task_probe->cp_is_ip6 = 1; else xfr->task_probe->cp_is_ip6 = 0; xfr->task_probe->cp = outnet_comm_point_for_udp(env->outnet, auth_xfer_probe_udp_callback, xfr, &addr, addrlen); if(!xfr->task_probe->cp) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "cannot create udp cp for " "probe %s to %s", zname, as); return 0; } } if(!xfr->task_probe->timer) { xfr->task_probe->timer = comm_timer_create(env->worker_base, auth_xfer_probe_timer_callback, xfr); if(!xfr->task_probe->timer) { log_err("malloc failure"); return 0; } } /* send udp packet */ if(!comm_point_send_udp_msg(xfr->task_probe->cp, env->scratch_buffer, (struct sockaddr*)&addr, addrlen, 0)) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "failed to send soa probe for %s to %s", zname, as); return 0; } if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN], as[256]; dname_str(xfr->name, zname); addr_port_to_str(&addr, addrlen, as, sizeof(as)); verbose(VERB_ALGO, "auth zone %s soa probe sent to %s", zname, as); } xfr->task_probe->timeout = timeout; #ifndef S_SPLINT_S t.tv_sec = timeout/1000; t.tv_usec = (timeout%1000)*1000; #endif comm_timer_set(xfr->task_probe->timer, &t); return 1; } /** callback for task_probe timer */ void auth_xfer_probe_timer_callback(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe timeout", zname); } if(xfr->task_probe->timeout <= AUTH_PROBE_TIMEOUT_STOP) { /* try again with bigger timeout */ if(xfr_probe_send_probe(xfr, env, xfr->task_probe->timeout*2)) { lock_basic_unlock(&xfr->lock); return; } } /* delete commpoint so a new one is created, with a fresh port nr */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* too many timeouts (or fail to send), move to next or end */ xfr_probe_nextmaster(xfr); xfr_probe_send_or_end(xfr, env); } /** callback for task_probe udp packets */ int auth_xfer_probe_udp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return 0; /* stop on quit */ } /* the comm_point_udp_callback is in a for loop for NUM_UDP_PER_SELECT * and we set rep.c=NULL to stop if from looking inside the commpoint*/ repinfo->c = NULL; /* stop the timer */ comm_timer_disable(xfr->task_probe->timer); /* see if we got a packet and what that means */ if(err == NETEVENT_NOERROR) { uint32_t serial = 0; if(check_packet_ok(c->buffer, LDNS_RR_TYPE_SOA, xfr, &serial)) { /* successful lookup */ if(verbosity >= VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe " "serial is %u", buf, (unsigned)serial); } /* see if this serial indicates that the zone has * to be updated */ if(xfr_serial_means_update(xfr, serial)) { /* if updated, start the transfer task, if needed */ verbose(VERB_ALGO, "auth_zone updated, start transfer"); if(xfr->task_transfer->worker == NULL) { struct auth_master* master = xfr_probe_current_master(xfr); /* if we have download URLs use them * in preference to this master we * just probed the SOA from */ if(xfr->task_transfer->masters && xfr->task_transfer->masters->http) master = NULL; xfr_probe_disown(xfr); xfr_start_transfer(xfr, env, master); return 0; } /* other tasks are running, we don't do this anymore */ xfr_probe_disown(xfr); lock_basic_unlock(&xfr->lock); /* return, we don't sent a reply to this udp packet, * and we setup the tasks to do next */ return 0; } else { verbose(VERB_ALGO, "auth_zone master reports unchanged soa serial"); /* we if cannot find updates amongst the * masters, this means we then have a new lease * on the zone */ xfr->task_probe->have_new_lease = 1; } } else { if(verbosity >= VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: bad reply to soa probe", buf); } } } else { if(verbosity >= VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf); verbose(VERB_ALGO, "auth zone %s: soa probe failed", buf); } } /* failed lookup or not an update */ /* delete commpoint so a new one is created, with a fresh port nr */ comm_point_delete(xfr->task_probe->cp); xfr->task_probe->cp = NULL; /* if the result was not a successful probe, we need * to send the next one */ xfr_probe_nextmaster(xfr); xfr_probe_send_or_end(xfr, env); return 0; } /** lookup a host name for its addresses, if needed */ static int xfr_probe_lookup_host(struct auth_xfer* xfr, struct module_env* env) { struct sockaddr_storage addr; socklen_t addrlen = 0; struct auth_master* master = xfr->task_probe->lookup_target; struct query_info qinfo; uint16_t qflags = BIT_RD; uint8_t dname[LDNS_MAX_DOMAINLEN+1]; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; if(!master) return 0; if(extstrtoaddr(master->host, &addr, &addrlen, UNBOUND_DNS_PORT)) { /* not needed, host is in IP addr format */ return 0; } if(master->allow_notify && !master->http && strchr(master->host, '/') != NULL && strchr(master->host, '/') == strrchr(master->host, '/')) { return 0; /* is IP/prefix format, not something to look up */ } /* use mesh_new_callback to probe for non-addr hosts, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = sizeof(dname); if(sldns_str2wire_dname_buf(master->host, dname, &qinfo.qname_len) != 0) { log_err("cannot parse host name of master %s", master->host); return 0; } qinfo.qname = dname; qinfo.qclass = xfr->dclass; qinfo.qtype = LDNS_RR_TYPE_A; if(xfr->task_probe->lookup_aaaa) qinfo.qtype = LDNS_RR_TYPE_AAAA; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: master lookup" " for task_probe", buf2); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; edns.padding_block_size = 0; edns.cookie_present = 0; edns.cookie_valid = 0; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* unlock xfr during mesh_new_callback() because the callback can be * called straight away */ lock_basic_unlock(&xfr->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_xfer_probe_lookup_callback, xfr, 0)) { lock_basic_lock(&xfr->lock); log_err("out of memory lookup up master %s", master->host); return 0; } lock_basic_lock(&xfr->lock); return 1; } /** return true if there are probe (SOA UDP query) targets in the master list*/ static int have_probe_targets(struct auth_master* list) { struct auth_master* p; for(p=list; p; p = p->next) { if(!p->allow_notify && p->host) return 1; } return 0; } /** move to sending the probe packets, next if fails. task_probe */ static void xfr_probe_send_or_end(struct auth_xfer* xfr, struct module_env* env) { /* are we doing hostname lookups? */ while(xfr->task_probe->lookup_target) { if(xfr_probe_lookup_host(xfr, env)) { /* wait for lookup to finish, * note that the hostname may be in unbound's cache * and we may then get an instant cache response, * and that calls the callback just like a full * lookup and lookup failures also call callback */ if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe next target lookup", zname); } lock_basic_unlock(&xfr->lock); return; } xfr_probe_move_to_next_lookup(xfr, env); } /* probe of list has ended. Create or refresh the list of of * allow_notify addrs */ probe_copy_masters_for_allow_notify(xfr); if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: notify addrs updated", zname); } if(xfr->task_probe->only_lookup) { /* only wanted lookups for copy, stop probe and start wait */ xfr->task_probe->only_lookup = 0; if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s probe: finished only_lookup", zname); } xfr_probe_disown(xfr); if(!have_probe_targets(xfr->task_probe->masters)) { /* If there are no masters to probe, go to transfer. */ if(xfr->task_transfer->worker == NULL) { xfr_start_transfer(xfr, env, NULL); return; } /* The transfer is already in progress. */ lock_basic_unlock(&xfr->lock); return; } if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); lock_basic_unlock(&xfr->lock); return; } /* send probe packets */ while(!xfr_probe_end_of_list(xfr)) { if(xfr_probe_send_probe(xfr, env, AUTH_PROBE_TIMEOUT)) { /* successfully sent probe, wait for callback */ lock_basic_unlock(&xfr->lock); return; } /* failed to send probe, next master */ xfr_probe_nextmaster(xfr); } /* done with probe sequence, wait */ if(xfr->task_probe->have_new_lease) { /* if zone not updated, start the wait timer again */ if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth_zone %s unchanged, new lease, wait", zname); } xfr_probe_disown(xfr); if(xfr->have_zone) xfr->lease_time = *env->now; if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 0, 0); } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s soa probe failed, wait to retry", zname); } /* we failed to send this as well, move to the wait task, * use the shorter retry timeout */ xfr_probe_disown(xfr); /* pick up the nextprobe task and wait */ if(xfr->task_nextprobe->worker == NULL) xfr_set_timeout(xfr, env, 1, 0); } lock_basic_unlock(&xfr->lock); } /** callback for task_probe lookup of host name, of A or AAAA */ void auth_xfer_probe_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status ATTR_UNUSED(sec), char* ATTR_UNUSED(why_bogus), int ATTR_UNUSED(was_ratelimited)) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_probe); lock_basic_lock(&xfr->lock); env = xfr->task_probe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* process result */ if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = LDNS_RR_TYPE_A; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; if(xfr->task_probe->lookup_aaaa) wanted_qtype = LDNS_RR_TYPE_AAAA; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer) { xfr_master_add_addrs(xfr->task_probe-> lookup_target, answer, wanted_qtype); } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has nodata", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup has no address", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } regional_free_all(temp); } else { if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s host %s type %s probe lookup failed", zname, xfr->task_probe->lookup_target->host, (xfr->task_probe->lookup_aaaa?"AAAA":"A")); } } if(xfr->task_probe->lookup_target->list && xfr->task_probe->lookup_target == xfr_probe_current_master(xfr)) xfr->task_probe->scan_addr = xfr->task_probe->lookup_target->list; /* move to lookup AAAA after A lookup, move to next hostname lookup, * or move to send the probes, or, if nothing to do, end task_probe */ xfr_probe_move_to_next_lookup(xfr, env); xfr_probe_send_or_end(xfr, env); } /** disown task_nextprobe. caller must hold xfr.lock */ static void xfr_nextprobe_disown(struct auth_xfer* xfr) { /* delete the timer, because the next worker to pick this up may * not have the same event base */ comm_timer_delete(xfr->task_nextprobe->timer); xfr->task_nextprobe->timer = NULL; xfr->task_nextprobe->next_probe = 0; /* we don't own this item anymore */ xfr->task_nextprobe->worker = NULL; xfr->task_nextprobe->env = NULL; } /** xfer nextprobe timeout callback, this is part of task_nextprobe */ void auth_xfer_timer(void* arg) { struct auth_xfer* xfr = (struct auth_xfer*)arg; struct module_env* env; log_assert(xfr->task_nextprobe); lock_basic_lock(&xfr->lock); env = xfr->task_nextprobe->env; if(!env || env->outnet->want_to_quit) { lock_basic_unlock(&xfr->lock); return; /* stop on quit */ } /* see if zone has expired, and if so, also set auth_zone expired */ if(xfr->have_zone && !xfr->zone_expired && *env->now >= xfr->lease_time + xfr->expiry) { lock_basic_unlock(&xfr->lock); auth_xfer_set_expired(xfr, env, 1); lock_basic_lock(&xfr->lock); } xfr_nextprobe_disown(xfr); if(!xfr_start_probe(xfr, env, NULL)) { /* not started because already in progress */ lock_basic_unlock(&xfr->lock); } } /** start task_probe if possible, if no masters for probe start task_transfer * returns true if task has been started, and false if the task is already * in progress. */ static int xfr_start_probe(struct auth_xfer* xfr, struct module_env* env, struct auth_master* spec) { /* see if we need to start a probe (or maybe it is already in * progress (due to notify)) */ if(xfr->task_probe->worker == NULL) { if(!have_probe_targets(xfr->task_probe->masters) && xfr->task_probe->masters != NULL) xfr->task_probe->only_lookup = 1; if(!(xfr->task_probe->only_lookup && xfr->task_probe->masters != NULL)) { /* useless to pick up task_probe, no masters to * probe. Instead attempt to pick up task transfer */ if(xfr->task_transfer->worker == NULL) { xfr_start_transfer(xfr, env, spec); return 1; } /* task transfer already in progress */ return 0; } /* pick up the probe task ourselves */ xfr->task_probe->worker = env->worker; xfr->task_probe->env = env; xfr->task_probe->cp = NULL; /* start the task */ /* have not seen a new lease yet, this scan */ xfr->task_probe->have_new_lease = 0; /* if this was a timeout, no specific first master to scan */ /* otherwise, spec is nonNULL the notified master, scan * first and also transfer first from it */ xfr_probe_start_list(xfr, spec); /* setup to start the lookup of hostnames of masters afresh */ xfr_probe_start_lookups(xfr); /* send the probe packet or next send, or end task */ xfr_probe_send_or_end(xfr, env); return 1; } return 0; } /** for task_nextprobe. * determine next timeout for auth_xfer. Also (re)sets timer. * @param xfr: task structure * @param env: module environment, with worker and time. * @param failure: set true if timer should be set for failure retry. * @param lookup_only: only perform lookups when timer done, 0 sec timeout */ static void xfr_set_timeout(struct auth_xfer* xfr, struct module_env* env, int failure, int lookup_only) { struct timeval tv; log_assert(xfr->task_nextprobe != NULL); log_assert(xfr->task_nextprobe->worker == NULL || xfr->task_nextprobe->worker == env->worker); /* normally, nextprobe = startoflease + refresh, * but if expiry is sooner, use that one. * after a failure, use the retry timer instead. */ xfr->task_nextprobe->next_probe = *env->now; if(xfr->lease_time && !failure) xfr->task_nextprobe->next_probe = xfr->lease_time; if(!failure) { xfr->task_nextprobe->backoff = 0; } else { if(xfr->task_nextprobe->backoff == 0) xfr->task_nextprobe->backoff = 3; else xfr->task_nextprobe->backoff *= 2; if(xfr->task_nextprobe->backoff > AUTH_TRANSFER_MAX_BACKOFF) xfr->task_nextprobe->backoff = AUTH_TRANSFER_MAX_BACKOFF; } if(xfr->have_zone) { time_t wait = xfr->refresh; if(failure) wait = xfr->retry; if(xfr->expiry < wait) xfr->task_nextprobe->next_probe += xfr->expiry; else xfr->task_nextprobe->next_probe += wait; if(failure) xfr->task_nextprobe->next_probe += xfr->task_nextprobe->backoff; /* put the timer exactly on expiry, if possible */ if(xfr->lease_time && xfr->lease_time+xfr->expiry < xfr->task_nextprobe->next_probe && xfr->lease_time+xfr->expiry > *env->now) xfr->task_nextprobe->next_probe = xfr->lease_time+xfr->expiry; } else { xfr->task_nextprobe->next_probe += xfr->task_nextprobe->backoff; } if(!xfr->task_nextprobe->timer) { xfr->task_nextprobe->timer = comm_timer_create( env->worker_base, auth_xfer_timer, xfr); if(!xfr->task_nextprobe->timer) { /* failed to malloc memory. likely zone transfer * also fails for that. skip the timeout */ char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); log_err("cannot allocate timer, no refresh for %s", zname); return; } } xfr->task_nextprobe->worker = env->worker; xfr->task_nextprobe->env = env; if(*(xfr->task_nextprobe->env->now) <= xfr->task_nextprobe->next_probe) tv.tv_sec = xfr->task_nextprobe->next_probe - *(xfr->task_nextprobe->env->now); else tv.tv_sec = 0; if(tv.tv_sec != 0 && lookup_only && xfr->task_probe->masters) { /* don't lookup_only, if lookup timeout is 0 anyway, * or if we don't have masters to lookup */ tv.tv_sec = 0; if(xfr->task_probe->worker == NULL) xfr->task_probe->only_lookup = 1; } if(verbosity >= VERB_ALGO) { char zname[LDNS_MAX_DOMAINLEN]; dname_str(xfr->name, zname); verbose(VERB_ALGO, "auth zone %s timeout in %d seconds", zname, (int)tv.tv_sec); } tv.tv_usec = 0; comm_timer_set(xfr->task_nextprobe->timer, &tv); } void auth_zone_pickup_initial_zone(struct auth_zone* z, struct module_env* env) { /* Set the time, because we now have timestamp in env, * (not earlier during startup and apply_cfg), and this * notes the start time when the data was acquired. */ z->soa_zone_acquired = *env->now; } void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env) { /* set lease_time, because we now have timestamp in env, * (not earlier during startup and apply_cfg), and this * notes the start time when the data was acquired */ if(x->have_zone) { x->lease_time = *env->now; x->soa_zone_acquired = *env->now; } if(x->task_nextprobe && x->task_nextprobe->worker == NULL) { xfr_set_timeout(x, env, 0, 1); } } /** initial pick up of worker timeouts, ties events to worker event loop */ void auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env) { struct auth_xfer* x; struct auth_zone* z; lock_rw_wrlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); auth_zone_pickup_initial_zone(z, env); lock_rw_unlock(&z->lock); } RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); auth_xfer_pickup_initial_zone(x, env); lock_basic_unlock(&x->lock); } lock_rw_unlock(&az->lock); } void auth_zones_cleanup(struct auth_zones* az) { struct auth_xfer* x; lock_rw_wrlock(&az->lock); RBTREE_FOR(x, struct auth_xfer*, &az->xtree) { lock_basic_lock(&x->lock); if(x->task_nextprobe && x->task_nextprobe->worker != NULL) { xfr_nextprobe_disown(x); } if(x->task_probe && x->task_probe->worker != NULL) { xfr_probe_disown(x); } if(x->task_transfer && x->task_transfer->worker != NULL) { auth_chunks_delete(x->task_transfer); xfr_transfer_disown(x); } lock_basic_unlock(&x->lock); } lock_rw_unlock(&az->lock); } /** * malloc the xfer and tasks * @param z: auth_zone with name of zone. */ static struct auth_xfer* auth_xfer_new(struct auth_zone* z) { struct auth_xfer* xfr; xfr = (struct auth_xfer*)calloc(1, sizeof(*xfr)); if(!xfr) return NULL; xfr->name = memdup(z->name, z->namelen); if(!xfr->name) { free(xfr); return NULL; } xfr->node.key = xfr; xfr->namelen = z->namelen; xfr->namelabs = z->namelabs; xfr->dclass = z->dclass; xfr->task_nextprobe = (struct auth_nextprobe*)calloc(1, sizeof(struct auth_nextprobe)); if(!xfr->task_nextprobe) { free(xfr->name); free(xfr); return NULL; } xfr->task_probe = (struct auth_probe*)calloc(1, sizeof(struct auth_probe)); if(!xfr->task_probe) { free(xfr->task_nextprobe); free(xfr->name); free(xfr); return NULL; } xfr->task_transfer = (struct auth_transfer*)calloc(1, sizeof(struct auth_transfer)); if(!xfr->task_transfer) { free(xfr->task_probe); free(xfr->task_nextprobe); free(xfr->name); free(xfr); return NULL; } lock_basic_init(&xfr->lock); lock_protect(&xfr->lock, &xfr->name, sizeof(xfr->name)); lock_protect(&xfr->lock, &xfr->namelen, sizeof(xfr->namelen)); lock_protect(&xfr->lock, xfr->name, xfr->namelen); lock_protect(&xfr->lock, &xfr->namelabs, sizeof(xfr->namelabs)); lock_protect(&xfr->lock, &xfr->dclass, sizeof(xfr->dclass)); lock_protect(&xfr->lock, &xfr->notify_received, sizeof(xfr->notify_received)); lock_protect(&xfr->lock, &xfr->notify_serial, sizeof(xfr->notify_serial)); lock_protect(&xfr->lock, &xfr->zone_expired, sizeof(xfr->zone_expired)); lock_protect(&xfr->lock, &xfr->have_zone, sizeof(xfr->have_zone)); lock_protect(&xfr->lock, &xfr->soa_zone_acquired, sizeof(xfr->soa_zone_acquired)); lock_protect(&xfr->lock, &xfr->serial, sizeof(xfr->serial)); lock_protect(&xfr->lock, &xfr->retry, sizeof(xfr->retry)); lock_protect(&xfr->lock, &xfr->refresh, sizeof(xfr->refresh)); lock_protect(&xfr->lock, &xfr->expiry, sizeof(xfr->expiry)); lock_protect(&xfr->lock, &xfr->lease_time, sizeof(xfr->lease_time)); lock_protect(&xfr->lock, &xfr->task_nextprobe->worker, sizeof(xfr->task_nextprobe->worker)); lock_protect(&xfr->lock, &xfr->task_probe->worker, sizeof(xfr->task_probe->worker)); lock_protect(&xfr->lock, &xfr->task_transfer->worker, sizeof(xfr->task_transfer->worker)); lock_basic_lock(&xfr->lock); return xfr; } /** Create auth_xfer structure. * This populates the have_zone, soa values, and so on times. * and sets the timeout, if a zone transfer is needed a short timeout is set. * For that the auth_zone itself must exist (and read in zonefile) * returns false on alloc failure. */ struct auth_xfer* auth_xfer_create(struct auth_zones* az, struct auth_zone* z) { struct auth_xfer* xfr; /* malloc it */ xfr = auth_xfer_new(z); if(!xfr) { log_err("malloc failure"); return NULL; } /* insert in tree */ (void)rbtree_insert(&az->xtree, &xfr->node); return xfr; } /** create new auth_master structure */ static struct auth_master* auth_master_new(struct auth_master*** list) { struct auth_master *m; m = (struct auth_master*)calloc(1, sizeof(*m)); if(!m) { log_err("malloc failure"); return NULL; } /* set first pointer to m, or next pointer of previous element to m */ (**list) = m; /* store m's next pointer as future point to store at */ (*list) = &(m->next); return m; } /** dup_prefix : create string from initial part of other string, malloced */ static char* dup_prefix(char* str, size_t num) { char* result; size_t len = strlen(str); if(len < num) num = len; /* not more than strlen */ result = (char*)malloc(num+1); if(!result) { log_err("malloc failure"); return result; } memmove(result, str, num); result[num] = 0; return result; } /** dup string and print error on error */ static char* dup_all(char* str) { char* result = strdup(str); if(!result) { log_err("malloc failure"); return NULL; } return result; } /** find first of two characters */ static char* str_find_first_of_chars(char* s, char a, char b) { char* ra = strchr(s, a); char* rb = strchr(s, b); if(!ra) return rb; if(!rb) return ra; if(ra < rb) return ra; return rb; } /** parse URL into host and file parts, false on malloc or parse error */ static int parse_url(char* url, char** host, char** file, int* port, int* ssl) { char* p = url; /* parse http://www.example.com/file.htm * or http://127.0.0.1 (index.html) * or https://[::1@1234]/a/b/c/d */ *ssl = 1; *port = AUTH_HTTPS_PORT; /* parse http:// or https:// */ if(strncmp(p, "http://", 7) == 0) { p += 7; *ssl = 0; *port = AUTH_HTTP_PORT; } else if(strncmp(p, "https://", 8) == 0) { p += 8; } else if(strstr(p, "://") && strchr(p, '/') > strstr(p, "://") && strchr(p, ':') >= strstr(p, "://")) { char* uri = dup_prefix(p, (size_t)(strstr(p, "://")-p)); log_err("protocol %s:// not supported (for url %s)", uri?uri:"", p); free(uri); return 0; } /* parse hostname part */ if(p[0] == '[') { char* end = strchr(p, ']'); p++; /* skip over [ */ if(end) { *host = dup_prefix(p, (size_t)(end-p)); if(!*host) return 0; p = end+1; /* skip over ] */ } else { *host = dup_all(p); if(!*host) return 0; p = end; } } else { char* end = str_find_first_of_chars(p, ':', '/'); if(end) { *host = dup_prefix(p, (size_t)(end-p)); if(!*host) return 0; } else { *host = dup_all(p); if(!*host) return 0; } p = end; /* at next : or / or NULL */ } /* parse port number */ if(p && p[0] == ':') { char* end = NULL; *port = strtol(p+1, &end, 10); p = end; } /* parse filename part */ while(p && *p == '/') p++; if(!p || p[0] == 0) *file = strdup("/"); else *file = strdup(p); if(!*file) { log_err("malloc failure"); return 0; } return 1; } int xfer_set_masters(struct auth_master** list, struct config_auth* c, int with_http) { struct auth_master* m; struct config_strlist* p; /* list points to the first, or next pointer for the new element */ while(*list) { list = &( (*list)->next ); } if(with_http) for(p = c->urls; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->http = 1; if(!parse_url(p->str, &m->host, &m->file, &m->port, &m->ssl)) return 0; } for(p = c->masters; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->ixfr = 1; /* this flag is not configurable */ m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); return 0; } } for(p = c->allow_notify; p; p = p->next) { m = auth_master_new(&list); if(!m) return 0; m->allow_notify = 1; m->host = strdup(p->str); if(!m->host) { log_err("malloc failure"); return 0; } } return 1; } #define SERIAL_BITS 32 int compare_serial(uint32_t a, uint32_t b) { const uint32_t cutoff = ((uint32_t) 1 << (SERIAL_BITS - 1)); if (a == b) { return 0; } else if ((a < b && b - a < cutoff) || (a > b && a - b > cutoff)) { return -1; } else { return 1; } } int zonemd_hashalgo_supported(int hashalgo) { if(hashalgo == ZONEMD_ALGO_SHA384) return 1; if(hashalgo == ZONEMD_ALGO_SHA512) return 1; return 0; } int zonemd_scheme_supported(int scheme) { if(scheme == ZONEMD_SCHEME_SIMPLE) return 1; return 0; } /** initialize hash for hashing with zonemd hash algo */ static struct secalgo_hash* zonemd_digest_init(int hashalgo, char** reason) { struct secalgo_hash *h; if(hashalgo == ZONEMD_ALGO_SHA384) { /* sha384 */ h = secalgo_hash_create_sha384(); if(!h) *reason = "digest sha384 could not be created"; return h; } else if(hashalgo == ZONEMD_ALGO_SHA512) { /* sha512 */ h = secalgo_hash_create_sha512(); if(!h) *reason = "digest sha512 could not be created"; return h; } /* unknown hash algo */ *reason = "unsupported algorithm"; return NULL; } /** update the hash for zonemd */ static int zonemd_digest_update(int hashalgo, struct secalgo_hash* h, uint8_t* data, size_t len, char** reason) { if(hashalgo == ZONEMD_ALGO_SHA384) { if(!secalgo_hash_update(h, data, len)) { *reason = "digest sha384 failed"; return 0; } return 1; } else if(hashalgo == ZONEMD_ALGO_SHA512) { if(!secalgo_hash_update(h, data, len)) { *reason = "digest sha512 failed"; return 0; } return 1; } /* unknown hash algo */ *reason = "unsupported algorithm"; return 0; } /** finish the hash for zonemd */ static int zonemd_digest_finish(int hashalgo, struct secalgo_hash* h, uint8_t* result, size_t hashlen, size_t* resultlen, char** reason) { if(hashalgo == ZONEMD_ALGO_SHA384) { if(hashlen < 384/8) { *reason = "digest buffer too small for sha384"; return 0; } if(!secalgo_hash_final(h, result, hashlen, resultlen)) { *reason = "digest sha384 finish failed"; return 0; } return 1; } else if(hashalgo == ZONEMD_ALGO_SHA512) { if(hashlen < 512/8) { *reason = "digest buffer too small for sha512"; return 0; } if(!secalgo_hash_final(h, result, hashlen, resultlen)) { *reason = "digest sha512 finish failed"; return 0; } return 1; } /* unknown algo */ *reason = "unsupported algorithm"; return 0; } /** add rrsets from node to the list */ static size_t authdata_rrsets_to_list(struct auth_rrset** array, size_t arraysize, struct auth_rrset* first) { struct auth_rrset* rrset = first; size_t num = 0; while(rrset) { if(num >= arraysize) return num; array[num] = rrset; num++; rrset = rrset->next; } return num; } /** compare rr list entries */ static int rrlist_compare(const void* arg1, const void* arg2) { struct auth_rrset* r1 = *(struct auth_rrset**)arg1; struct auth_rrset* r2 = *(struct auth_rrset**)arg2; uint16_t t1, t2; if(r1 == NULL) t1 = LDNS_RR_TYPE_RRSIG; else t1 = r1->type; if(r2 == NULL) t2 = LDNS_RR_TYPE_RRSIG; else t2 = r2->type; if(t1 < t2) return -1; if(t1 > t2) return 1; return 0; } /** add type RRSIG to rr list if not one there already, * this is to perform RRSIG collate processing at that point. */ static void addrrsigtype_if_needed(struct auth_rrset** array, size_t arraysize, size_t* rrnum, struct auth_data* node) { if(az_domain_rrset(node, LDNS_RR_TYPE_RRSIG)) return; /* already one there */ if((*rrnum) >= arraysize) return; /* array too small? */ array[*rrnum] = NULL; /* nothing there, but need entry in list */ (*rrnum)++; } /** collate the RRs in an RRset using the simple scheme */ static int zonemd_simple_rrset(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct auth_rrset* rrset, struct regional* region, struct sldns_buffer* buf, char** reason) { /* canonicalize */ struct ub_packed_rrset_key key; memset(&key, 0, sizeof(key)); key.entry.key = &key; key.entry.data = rrset->data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(rrset->type); key.rk.rrset_class = htons(z->dclass); if(!rrset_canonicalize_to_buffer(region, buf, &key)) { *reason = "out of memory"; return 0; } regional_free_all(region); /* hash */ if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), sldns_buffer_limit(buf), reason)) { return 0; } return 1; } /** count number of RRSIGs in a domain name rrset list */ static size_t zonemd_simple_count_rrsig(struct auth_rrset* rrset, struct auth_rrset** rrlist, size_t rrnum, struct auth_zone* z, struct auth_data* node) { size_t i, count = 0; if(rrset) { size_t j; for(j = 0; jdata->count; j++) { if(rrsig_rdata_get_type_covered(rrset->data-> rr_data[j], rrset->data->rr_len[j]) == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } count++; } } for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } count += (rrlist[i]?rrlist[i]->data->rrsig_count:0); } return count; } /** allocate sparse rrset data for the number of entries in tepm region */ static int zonemd_simple_rrsig_allocs(struct regional* region, struct packed_rrset_data* data, size_t count) { data->rr_len = regional_alloc(region, sizeof(*data->rr_len) * count); if(!data->rr_len) { return 0; } data->rr_ttl = regional_alloc(region, sizeof(*data->rr_ttl) * count); if(!data->rr_ttl) { return 0; } data->rr_data = regional_alloc(region, sizeof(*data->rr_data) * count); if(!data->rr_data) { return 0; } return 1; } /** add the RRSIGs from the rrs in the domain into the data */ static void add_rrlist_rrsigs_into_data(struct packed_rrset_data* data, size_t* done, struct auth_rrset** rrlist, size_t rrnum, struct auth_zone* z, struct auth_data* node) { size_t i; for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } for(j = 0; jdata->rrsig_count; j++) { data->rr_len[*done] = rrlist[i]->data->rr_len[rrlist[i]->data->count + j]; data->rr_ttl[*done] = rrlist[i]->data->rr_ttl[rrlist[i]->data->count + j]; /* reference the rdata in the rrset, no need to * copy it, it is no longer needed at the end of * the routine */ data->rr_data[*done] = rrlist[i]->data->rr_data[rrlist[i]->data->count + j]; (*done)++; } } } static void add_rrset_into_data(struct packed_rrset_data* data, size_t* done, struct auth_rrset* rrset, struct auth_zone* z, struct auth_data* node) { if(rrset) { size_t j; for(j = 0; jdata->count; j++) { if(rrsig_rdata_get_type_covered(rrset->data-> rr_data[j], rrset->data->rr_len[j]) == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name)==0) { /* omit RRSIGs over type ZONEMD at apex */ continue; } data->rr_len[*done] = rrset->data->rr_len[j]; data->rr_ttl[*done] = rrset->data->rr_ttl[j]; /* reference the rdata in the rrset, no need to * copy it, it is no longer need at the end of * the routine */ data->rr_data[*done] = rrset->data->rr_data[j]; (*done)++; } } } /** collate the RRSIGs using the simple scheme */ static int zonemd_simple_rrsig(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct auth_rrset* rrset, struct auth_rrset** rrlist, size_t rrnum, struct regional* region, struct sldns_buffer* buf, char** reason) { /* the rrset pointer can be NULL, this means it is type RRSIG and * there is no ordinary type RRSIG there. The RRSIGs are stored * with the RRsets in their data. * * The RRset pointer can be nonNULL. This happens if there is * no RR that is covered by the RRSIG for the domain. Then this * RRSIG RR is stored in an rrset of type RRSIG. The other RRSIGs * are stored in the rrset entries for the RRs in the rr list for * the domain node. We need to collate the rrset's data, if any, and * the rrlist's rrsigs */ /* if this is the apex, omit RRSIGs that cover type ZONEMD */ /* build rrsig rrset */ size_t done = 0; struct ub_packed_rrset_key key; struct packed_rrset_data data; memset(&key, 0, sizeof(key)); memset(&data, 0, sizeof(data)); key.entry.key = &key; key.entry.data = &data; key.rk.dname = node->name; key.rk.dname_len = node->namelen; key.rk.type = htons(LDNS_RR_TYPE_RRSIG); key.rk.rrset_class = htons(z->dclass); data.count = zonemd_simple_count_rrsig(rrset, rrlist, rrnum, z, node); if(!zonemd_simple_rrsig_allocs(region, &data, data.count)) { *reason = "out of memory"; regional_free_all(region); return 0; } /* all the RRSIGs stored in the other rrsets for this domain node */ add_rrlist_rrsigs_into_data(&data, &done, rrlist, rrnum, z, node); /* plus the RRSIGs stored in an rrset of type RRSIG for this node */ add_rrset_into_data(&data, &done, rrset, z, node); /* canonicalize */ if(!rrset_canonicalize_to_buffer(region, buf, &key)) { *reason = "out of memory"; regional_free_all(region); return 0; } regional_free_all(region); /* hash */ if(!zonemd_digest_update(hashalgo, h, sldns_buffer_begin(buf), sldns_buffer_limit(buf), reason)) { return 0; } return 1; } /** collate a domain's rrsets using the simple scheme */ static int zonemd_simple_domain(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct auth_data* node, struct regional* region, struct sldns_buffer* buf, char** reason) { const size_t rrlistsize = 65536; struct auth_rrset* rrlist[rrlistsize]; size_t i, rrnum = 0; /* see if the domain is out of scope, the zone origin, * that would be omitted */ if(!dname_subdomain_c(node->name, z->name)) return 1; /* continue */ /* loop over the rrsets in ascending order. */ rrnum = authdata_rrsets_to_list(rrlist, rrlistsize, node->rrsets); addrrsigtype_if_needed(rrlist, rrlistsize, &rrnum, node); qsort(rrlist, rrnum, sizeof(*rrlist), rrlist_compare); for(i=0; itype == LDNS_RR_TYPE_ZONEMD && query_dname_compare(z->name, node->name) == 0) { /* omit type ZONEMD at apex */ continue; } if(rrlist[i] == NULL || rrlist[i]->type == LDNS_RR_TYPE_RRSIG) { if(!zonemd_simple_rrsig(z, hashalgo, h, node, rrlist[i], rrlist, rrnum, region, buf, reason)) return 0; } else if(!zonemd_simple_rrset(z, hashalgo, h, node, rrlist[i], region, buf, reason)) { return 0; } } return 1; } /** collate the zone using the simple scheme */ static int zonemd_simple_collate(struct auth_zone* z, int hashalgo, struct secalgo_hash* h, struct regional* region, struct sldns_buffer* buf, char** reason) { /* our tree is sorted in canonical order, so we can just loop over * the tree */ struct auth_data* n; RBTREE_FOR(n, struct auth_data*, &z->data) { if(!zonemd_simple_domain(z, hashalgo, h, n, region, buf, reason)) return 0; } return 1; } int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, size_t* resultlen, struct regional* region, struct sldns_buffer* buf, char** reason) { struct secalgo_hash* h = zonemd_digest_init(hashalgo, reason); if(!h) { if(!*reason) *reason = "digest init fail"; return 0; } if(scheme == ZONEMD_SCHEME_SIMPLE) { if(!zonemd_simple_collate(z, hashalgo, h, region, buf, reason)) { if(!*reason) *reason = "scheme simple collate fail"; secalgo_hash_delete(h); return 0; } } if(!zonemd_digest_finish(hashalgo, h, hash, hashlen, resultlen, reason)) { secalgo_hash_delete(h); *reason = "digest finish fail"; return 0; } secalgo_hash_delete(h); return 1; } int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region, struct sldns_buffer* buf, char** reason) { uint8_t gen[512]; size_t genlen = 0; *reason = NULL; if(!zonemd_hashalgo_supported(hashalgo)) { /* allow it */ *reason = "unsupported algorithm"; return 1; } if(!zonemd_scheme_supported(scheme)) { /* allow it */ *reason = "unsupported scheme"; return 1; } if(hashlen < 12) { /* the ZONEMD draft requires digests to fail if too small */ *reason = "digest length too small, less than 12"; return 0; } /* generate digest */ if(!auth_zone_generate_zonemd_hash(z, scheme, hashalgo, gen, sizeof(gen), &genlen, region, buf, reason)) { /* reason filled in by zonemd hash routine */ return 0; } /* check digest length */ if(hashlen != genlen) { *reason = "incorrect digest length"; if(verbosity >= VERB_ALGO) { verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", scheme, hashalgo); log_hex("ZONEMD should be ", gen, genlen); log_hex("ZONEMD to check is", hash, hashlen); } return 0; } /* check digest */ if(memcmp(hash, gen, genlen) != 0) { *reason = "incorrect digest"; if(verbosity >= VERB_ALGO) { verbose(VERB_ALGO, "zonemd scheme=%d hashalgo=%d", scheme, hashalgo); log_hex("ZONEMD should be ", gen, genlen); log_hex("ZONEMD to check is", hash, hashlen); } return 0; } return 1; } /** log auth zone message with zone name in front. */ static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) ATTR_FORMAT(printf, 3, 4); static void auth_zone_log(uint8_t* name, enum verbosity_value level, const char* format, ...) { va_list args; va_start(args, format); if(verbosity >= level) { char str[LDNS_MAX_DOMAINLEN]; char msg[MAXSYSLOGMSGLEN]; dname_str(name, str); vsnprintf(msg, sizeof(msg), format, args); verbose(level, "auth zone %s %s", str, msg); } va_end(args); } /** ZONEMD, dnssec verify the rrset with the dnskey */ static int zonemd_dnssec_verify_rrset(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, struct auth_data* node, struct auth_rrset* rrset, char** why_bogus, uint8_t* sigalg, char* reasonbuf, size_t reasonlen) { struct ub_packed_rrset_key pk; enum sec_status sec; struct val_env* ve; int m; int verified = 0; m = modstack_find(mods, "validator"); if(m == -1) { auth_zone_log(z->name, VERB_ALGO, "zonemd dnssec verify: have " "DNSKEY chain of trust, but no validator module"); return 0; } ve = (struct val_env*)env->modinfo[m]; memset(&pk, 0, sizeof(pk)); pk.entry.key = &pk; pk.entry.data = rrset->data; pk.rk.dname = node->name; pk.rk.dname_len = node->namelen; pk.rk.type = htons(rrset->type); pk.rk.rrset_class = htons(z->dclass); if(verbosity >= VERB_ALGO) { char typestr[32]; typestr[0]=0; sldns_wire2str_type_buf(rrset->type, typestr, sizeof(typestr)); auth_zone_log(z->name, VERB_ALGO, "zonemd: verify %s RRset with DNSKEY", typestr); } sec = dnskeyset_verify_rrset(env, ve, &pk, dnskey, sigalg, why_bogus, NULL, LDNS_SECTION_ANSWER, NULL, &verified, reasonbuf, reasonlen); if(sec == sec_status_secure) { return 1; } if(why_bogus) auth_zone_log(z->name, VERB_ALGO, "DNSSEC verify was bogus: %s", *why_bogus); return 0; } /** check for nsec3, the RR with params equal, if bitmap has the type */ static int nsec3_of_param_has_type(struct auth_rrset* nsec3, int algo, size_t iter, uint8_t* salt, size_t saltlen, uint16_t rrtype) { int i, count = (int)nsec3->data->count; struct ub_packed_rrset_key pk; memset(&pk, 0, sizeof(pk)); pk.entry.data = nsec3->data; for(i=0; idata; if(nsec_has_type(&pk, LDNS_RR_TYPE_ZONEMD)) { *reason = "DNSSEC NSEC bitmap says type ZONEMD exists"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC verification of absence of ZONEMD secure"); } else { /* NSEC3 perhaps ? */ int algo; size_t iter, saltlen; uint8_t* salt; struct auth_rrset* nsec3param = az_domain_rrset(apex, LDNS_RR_TYPE_NSEC3PARAM); struct auth_data* match; struct auth_rrset* nsec3; if(!nsec3param) { *reason = "zone has no NSEC information but ZONEMD missing"; return 0; } if(!az_nsec3_param(z, &algo, &iter, &salt, &saltlen)) { *reason = "zone has no NSEC information but ZONEMD missing"; return 0; } /* find the NSEC3 record */ match = az_nsec3_find_exact(z, z->name, z->namelen, algo, iter, salt, saltlen); if(!match) { *reason = "zone has no NSEC3 domain for the apex but ZONEMD missing"; return 0; } nsec3 = az_domain_rrset(match, LDNS_RR_TYPE_NSEC3); if(!nsec3) { *reason = "zone has no NSEC3 RRset for the apex but ZONEMD missing"; return 0; } /* dnssec verify the NSEC3 */ if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, match, nsec3, why_bogus, sigalg, reasonbuf, reasonlen)) { *reason = "DNSSEC verify failed for NSEC3 RRset"; return 0; } /* check type bitmap */ if(nsec3_of_param_has_type(nsec3, algo, iter, salt, saltlen, LDNS_RR_TYPE_ZONEMD)) { *reason = "DNSSEC NSEC3 bitmap says type ZONEMD exists"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC NSEC3 verification of absence of ZONEMD secure"); } return 1; } /** Verify the SOA and ZONEMD DNSSEC signatures. * return false on failure, reason contains description of failure. */ static int zonemd_check_dnssec_soazonemd(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, struct auth_data* apex, struct auth_rrset* zonemd_rrset, char** reason, char** why_bogus, uint8_t* sigalg, char* reasonbuf, size_t reasonlen) { struct auth_rrset* soa; if(!apex) { *reason = "zone has no apex domain"; return 0; } soa = az_domain_rrset(apex, LDNS_RR_TYPE_SOA); if(!soa) { *reason = "zone has no SOA RRset"; return 0; } if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, soa, why_bogus, sigalg, reasonbuf, reasonlen)) { *reason = "DNSSEC verify failed for SOA RRset"; return 0; } if(!zonemd_dnssec_verify_rrset(z, env, mods, dnskey, apex, zonemd_rrset, why_bogus, sigalg, reasonbuf, reasonlen)) { *reason = "DNSSEC verify failed for ZONEMD RRset"; return 0; } auth_zone_log(z->name, VERB_ALGO, "zonemd DNSSEC verification of SOA and ZONEMD RRsets secure"); return 1; } /** * Fail the ZONEMD verification. * @param z: auth zone that fails. * @param env: environment with config, to ignore failure or not. * @param reason: failure string description. * @param why_bogus: failure string for DNSSEC verification failure. * @param result: strdup result in here if not NULL. */ static void auth_zone_zonemd_fail(struct auth_zone* z, struct module_env* env, char* reason, char* why_bogus, char** result) { char zstr[LDNS_MAX_DOMAINLEN]; /* if fail: log reason, and depending on config also take action * and drop the zone, eg. it is gone from memory, set zone_expired */ dname_str(z->name, zstr); if(!reason) reason = "verification failed"; if(result) { if(why_bogus) { char res[1024]; snprintf(res, sizeof(res), "%s: %s", reason, why_bogus); *result = strdup(res); } else { *result = strdup(reason); } if(!*result) log_err("out of memory"); } else { log_warn("auth zone %s: ZONEMD verification failed: %s", zstr, reason); } if(env->cfg->zonemd_permissive_mode) { verbose(VERB_ALGO, "zonemd-permissive-mode enabled, " "not blocking zone %s", zstr); return; } /* expired means the zone gives servfail and is not used by * lookup if fallback_enabled*/ z->zone_expired = 1; } /** * Verify the zonemd with DNSSEC and hash check, with given key. * @param z: auth zone. * @param env: environment with config and temp buffers. * @param mods: module stack with validator env for verification. * @param dnskey: dnskey that we can use, or NULL. If nonnull, the key * has been verified and is the start of the chain of trust. * @param is_insecure: if true, the dnskey is not used, the zone is insecure. * And dnssec is not used. It is DNSSEC secure insecure or not under * a trust anchor. * @param sigalg: if nonNULL provide algorithm downgrade protection. * Otherwise one algorithm is enough. Must have space of ALGO_NEEDS_MAX+1. * @param result: if not NULL result reason copied here. */ static void auth_zone_verify_zonemd_with_key(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* dnskey, int is_insecure, char** result, uint8_t* sigalg) { char reasonbuf[256]; char* reason = NULL, *why_bogus = NULL; struct auth_data* apex = NULL; struct auth_rrset* zonemd_rrset = NULL; int zonemd_absent = 0, zonemd_absence_dnssecok = 0; /* see if ZONEMD is present or absent. */ apex = az_find_name(z, z->name, z->namelen); if(!apex) { zonemd_absent = 1; } else { zonemd_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_ZONEMD); if(!zonemd_rrset || zonemd_rrset->data->count==0) { zonemd_absent = 1; zonemd_rrset = NULL; } } /* if no DNSSEC, done. */ /* if no ZONEMD, and DNSSEC, use DNSKEY to verify NSEC or NSEC3 for * zone apex. Check ZONEMD bit is turned off or else fail */ /* if ZONEMD, and DNSSEC, check DNSSEC signature on SOA and ZONEMD, * or else fail */ if(!dnskey && !is_insecure) { auth_zone_zonemd_fail(z, env, "DNSKEY missing", NULL, result); return; } else if(!zonemd_rrset && dnskey && !is_insecure) { /* fetch, DNSSEC verify, and check NSEC/NSEC3 */ if(!zonemd_check_dnssec_absence(z, env, mods, dnskey, apex, &reason, &why_bogus, sigalg, reasonbuf, sizeof(reasonbuf))) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); return; } zonemd_absence_dnssecok = 1; } else if(zonemd_rrset && dnskey && !is_insecure) { /* check DNSSEC verify of SOA and ZONEMD */ if(!zonemd_check_dnssec_soazonemd(z, env, mods, dnskey, apex, zonemd_rrset, &reason, &why_bogus, sigalg, reasonbuf, sizeof(reasonbuf))) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); return; } } if(zonemd_absent && z->zonemd_reject_absence) { auth_zone_zonemd_fail(z, env, "ZONEMD absent and that is not allowed by config", NULL, result); return; } if(zonemd_absent && zonemd_absence_dnssecok) { auth_zone_log(z->name, VERB_ALGO, "DNSSEC verified nonexistence of ZONEMD"); if(result) { *result = strdup("DNSSEC verified nonexistence of ZONEMD"); if(!*result) log_err("out of memory"); } return; } if(zonemd_absent) { auth_zone_log(z->name, VERB_ALGO, "no ZONEMD present"); if(result) { *result = strdup("no ZONEMD present"); if(!*result) log_err("out of memory"); } return; } /* check ZONEMD checksum and report or else fail. */ if(!auth_zone_zonemd_check_hash(z, env, &reason)) { auth_zone_zonemd_fail(z, env, reason, NULL, result); return; } /* success! log the success */ if(reason) auth_zone_log(z->name, VERB_ALGO, "ZONEMD %s", reason); else auth_zone_log(z->name, VERB_ALGO, "ZONEMD verification successful"); if(result) { if(reason) *result = strdup(reason); else *result = strdup("ZONEMD verification successful"); if(!*result) log_err("out of memory"); } } /** * verify the zone DNSKEY rrset from the trust anchor * This is possible because the anchor is for the zone itself, and can * thus apply straight to the zone DNSKEY set. * @param z: the auth zone. * @param env: environment with time and temp buffers. * @param mods: module stack for validator environment for dnssec validation. * @param anchor: trust anchor to use * @param is_insecure: returned, true if the zone is securely insecure. * @param why_bogus: if the routine fails, returns the failure reason. * @param keystorage: where to store the ub_packed_rrset_key that is created * on success. A pointer to it is returned on success. * @param reasonbuf: buffer to use for fail reason string print. * @param reasonlen: length of reasonbuf. * @return the dnskey RRset, reference to zone data and keystorage, or * NULL on failure. */ static struct ub_packed_rrset_key* zonemd_get_dnskey_from_anchor(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct trust_anchor* anchor, int* is_insecure, char** why_bogus, struct ub_packed_rrset_key* keystorage, char* reasonbuf, size_t reasonlen) { struct auth_data* apex; struct auth_rrset* dnskey_rrset; enum sec_status sec; struct val_env* ve; int m; apex = az_find_name(z, z->name, z->namelen); if(!apex) { *why_bogus = "have trust anchor, but zone has no apex domain for DNSKEY"; return 0; } dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); if(!dnskey_rrset || dnskey_rrset->data->count==0) { *why_bogus = "have trust anchor, but zone has no DNSKEY"; return 0; } m = modstack_find(mods, "validator"); if(m == -1) { *why_bogus = "have trust anchor, but no validator module"; return 0; } ve = (struct val_env*)env->modinfo[m]; memset(keystorage, 0, sizeof(*keystorage)); keystorage->entry.key = keystorage; keystorage->entry.data = dnskey_rrset->data; keystorage->rk.dname = apex->name; keystorage->rk.dname_len = apex->namelen; keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify DNSKEY RRset with trust anchor"); sec = val_verify_DNSKEY_with_TA(env, ve, keystorage, anchor->ds_rrset, anchor->dnskey_rrset, NULL, why_bogus, NULL, NULL, reasonbuf, reasonlen); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ *is_insecure = 0; return keystorage; } else if(sec == sec_status_insecure) { /* insecure */ *is_insecure = 1; } else { /* bogus */ *is_insecure = 0; auth_zone_log(z->name, VERB_ALGO, "zonemd: verify DNSKEY RRset with trust anchor failed: %s", *why_bogus); } return NULL; } /** verify the DNSKEY from the zone with looked up DS record */ static struct ub_packed_rrset_key* auth_zone_verify_zonemd_key_with_ds(struct auth_zone* z, struct module_env* env, struct module_stack* mods, struct ub_packed_rrset_key* ds, int* is_insecure, char** why_bogus, struct ub_packed_rrset_key* keystorage, uint8_t* sigalg, char* reasonbuf, size_t reasonlen) { struct auth_data* apex; struct auth_rrset* dnskey_rrset; enum sec_status sec; struct val_env* ve; int m; /* fetch DNSKEY from zone data */ apex = az_find_name(z, z->name, z->namelen); if(!apex) { *why_bogus = "in verifywithDS, zone has no apex"; return NULL; } dnskey_rrset = az_domain_rrset(apex, LDNS_RR_TYPE_DNSKEY); if(!dnskey_rrset || dnskey_rrset->data->count==0) { *why_bogus = "in verifywithDS, zone has no DNSKEY"; return NULL; } m = modstack_find(mods, "validator"); if(m == -1) { *why_bogus = "in verifywithDS, have no validator module"; return NULL; } ve = (struct val_env*)env->modinfo[m]; memset(keystorage, 0, sizeof(*keystorage)); keystorage->entry.key = keystorage; keystorage->entry.data = dnskey_rrset->data; keystorage->rk.dname = apex->name; keystorage->rk.dname_len = apex->namelen; keystorage->rk.type = htons(LDNS_RR_TYPE_DNSKEY); keystorage->rk.rrset_class = htons(z->dclass); auth_zone_log(z->name, VERB_QUERY, "zonemd: verify zone DNSKEY with DS"); sec = val_verify_DNSKEY_with_DS(env, ve, keystorage, ds, sigalg, why_bogus, NULL, NULL, reasonbuf, reasonlen); regional_free_all(env->scratch); if(sec == sec_status_secure) { /* success */ return keystorage; } else if(sec == sec_status_insecure) { /* insecure */ *is_insecure = 1; } else { /* bogus */ *is_insecure = 0; if(*why_bogus == NULL) *why_bogus = "verify failed"; auth_zone_log(z->name, VERB_ALGO, "zonemd: verify DNSKEY RRset with DS failed: %s", *why_bogus); } return NULL; } /** callback for ZONEMD lookup of DNSKEY */ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, sldns_buffer* buf, enum sec_status sec, char* why_bogus, int ATTR_UNUSED(was_ratelimited)) { struct auth_zone* z = (struct auth_zone*)arg; struct module_env* env; char reasonbuf[256]; char* reason = NULL, *ds_bogus = NULL, *typestr="DNSKEY"; struct ub_packed_rrset_key* dnskey = NULL, *ds = NULL; int is_insecure = 0, downprot; struct ub_packed_rrset_key keystorage; uint8_t sigalg[ALGO_NEEDS_MAX+1]; lock_rw_wrlock(&z->lock); env = z->zonemd_callback_env; /* release the env variable so another worker can pick up the * ZONEMD verification task if it wants to */ z->zonemd_callback_env = NULL; if(!env || env->outnet->want_to_quit || z->zone_deleted) { lock_rw_unlock(&z->lock); return; /* stop on quit */ } if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DS) typestr = "DS"; downprot = env->cfg->harden_algo_downgrade; /* process result */ if(sec == sec_status_bogus) { reason = why_bogus; if(!reason) { if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY was bogus"; else reason = "lookup of DS was bogus"; } auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was bogus: %s", typestr, reason); } else if(rcode == LDNS_RCODE_NOERROR) { uint16_t wanted_qtype = z->zonemd_callback_qtype; struct regional* temp = env->scratch; struct query_info rq; struct reply_info* rep; memset(&rq, 0, sizeof(rq)); rep = parse_reply_in_temp_region(buf, temp, &rq); if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR) { /* parsed successfully */ struct ub_packed_rrset_key* answer = reply_find_answer_rrset(&rq, rep); if(answer && sec == sec_status_secure) { if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) dnskey = answer; else ds = answer; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was secure", typestr); } else if(sec == sec_status_secure && !answer) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has no content, but is secure, treat as insecure", typestr); } else if(sec == sec_status_insecure) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was insecure", typestr); } else if(sec == sec_status_indeterminate) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was indeterminate, treat as insecure", typestr); } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has nodata", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY has nodata"; else reason = "lookup of DS has nodata"; } } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_secure) { /* secure nxdomain, so the zone is like some RPZ zone * that does not exist in the wider internet, with * a secure nxdomain answer outside of it. So we * treat the zonemd zone without a dnssec chain of * trust, as insecure. */ is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was secure NXDOMAIN, treat as insecure", typestr); } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_insecure) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was insecure NXDOMAIN, treat as insecure", typestr); } else if(rep && rq.qtype == wanted_qtype && query_dname_compare(z->name, rq.qname) == 0 && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN && sec == sec_status_indeterminate) { is_insecure = 1; auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s was indeterminate NXDOMAIN, treat as insecure", typestr); } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s has no answer", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY has no answer"; else reason = "lookup of DS has no answer"; } } else { auth_zone_log(z->name, VERB_ALGO, "zonemd lookup of %s failed", typestr); if(z->zonemd_callback_qtype == LDNS_RR_TYPE_DNSKEY) reason = "lookup of DNSKEY failed"; else reason = "lookup of DS failed"; } if(!reason && !is_insecure && !dnskey && ds) { dnskey = auth_zone_verify_zonemd_key_with_ds(z, env, &env->mesh->mods, ds, &is_insecure, &ds_bogus, &keystorage, downprot?sigalg:NULL, reasonbuf, sizeof(reasonbuf)); if(!dnskey && !is_insecure && !reason) reason = "DNSKEY verify with DS failed"; } if(reason) { auth_zone_zonemd_fail(z, env, reason, ds_bogus, NULL); lock_rw_unlock(&z->lock); regional_free_all(env->scratch); return; } auth_zone_verify_zonemd_with_key(z, env, &env->mesh->mods, dnskey, is_insecure, NULL, downprot?sigalg:NULL); regional_free_all(env->scratch); lock_rw_unlock(&z->lock); } /** lookup DNSKEY for ZONEMD verification */ static int zonemd_lookup_dnskey(struct auth_zone* z, struct module_env* env) { struct query_info qinfo; uint16_t qflags = BIT_RD; struct edns_data edns; sldns_buffer* buf = env->scratch_buffer; int fetch_ds = 0; if(!z->fallback_enabled) { /* we cannot actually get the DNSKEY, because it is in the * zone we have ourselves, and it is not served yet * (possibly), so fetch type DS */ fetch_ds = 1; } if(z->zonemd_callback_env) { /* another worker is already working on the callback * for the DNSKEY lookup for ZONEMD verification. * We do not also have to do ZONEMD verification, let that * worker do it */ auth_zone_log(z->name, VERB_ALGO, "zonemd needs lookup of %s and that already is worked on by another worker", (fetch_ds?"DS":"DNSKEY")); return 1; } /* use mesh_new_callback to lookup the DNSKEY, * and then wait for them to be looked up (in cache, or query) */ qinfo.qname_len = z->namelen; qinfo.qname = z->name; qinfo.qclass = z->dclass; if(fetch_ds) qinfo.qtype = LDNS_RR_TYPE_DS; else qinfo.qtype = LDNS_RR_TYPE_DNSKEY; qinfo.local_alias = NULL; if(verbosity >= VERB_ALGO) { char buf1[512]; char buf2[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf2); snprintf(buf1, sizeof(buf1), "auth zone %s: lookup %s " "for zonemd verification", buf2, (fetch_ds?"DS":"DNSKEY")); log_query_info(VERB_ALGO, buf1, &qinfo); } edns.edns_present = 1; edns.ext_rcode = 0; edns.edns_version = 0; edns.bits = EDNS_DO; edns.opt_list_in = NULL; edns.opt_list_out = NULL; edns.opt_list_inplace_cb_out = NULL; if(sldns_buffer_capacity(buf) < 65535) edns.udp_size = (uint16_t)sldns_buffer_capacity(buf); else edns.udp_size = 65535; /* store the worker-specific module env for the callback. * We can then reference this when the callback executes */ z->zonemd_callback_env = env; z->zonemd_callback_qtype = qinfo.qtype; /* the callback can be called straight away */ lock_rw_unlock(&z->lock); if(!mesh_new_callback(env->mesh, &qinfo, qflags, &edns, buf, 0, &auth_zonemd_dnskey_lookup_callback, z, 0)) { lock_rw_wrlock(&z->lock); log_err("out of memory lookup of %s for zonemd", (fetch_ds?"DS":"DNSKEY")); return 0; } lock_rw_wrlock(&z->lock); return 1; } void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env, struct module_stack* mods, char** result, int offline, int only_online) { char reasonbuf[256]; char* reason = NULL, *why_bogus = NULL; struct trust_anchor* anchor = NULL; struct ub_packed_rrset_key* dnskey = NULL; struct ub_packed_rrset_key keystorage; int is_insecure = 0; /* verify the ZONEMD if present. * If not present check if absence is allowed by DNSSEC */ if(!z->zonemd_check) return; if(z->data.count == 0) return; /* no data */ /* if zone is under a trustanchor */ /* is it equal to trustanchor - get dnskey's verified */ /* else, find chain of trust by fetching DNSKEYs lookup for zone */ /* result if that, if insecure, means no DNSSEC for the ZONEMD, * otherwise we have the zone DNSKEY for the DNSSEC verification. */ if(env->anchors) anchor = anchors_lookup(env->anchors, z->name, z->namelen, z->dclass); if(anchor && anchor->numDS == 0 && anchor->numDNSKEY == 0) { /* domain-insecure trust anchor for unsigned zones */ lock_basic_unlock(&anchor->lock); if(only_online) return; dnskey = NULL; is_insecure = 1; } else if(anchor && query_dname_compare(z->name, anchor->name) == 0) { if(only_online) { lock_basic_unlock(&anchor->lock); return; } /* equal to trustanchor, no need for online lookups */ dnskey = zonemd_get_dnskey_from_anchor(z, env, mods, anchor, &is_insecure, &why_bogus, &keystorage, reasonbuf, sizeof(reasonbuf)); lock_basic_unlock(&anchor->lock); if(!dnskey && !reason && !is_insecure) { reason = "verify DNSKEY RRset with trust anchor failed"; } } else if(anchor) { lock_basic_unlock(&anchor->lock); /* perform online lookups */ if(offline) return; /* setup online lookups, and wait for them */ if(zonemd_lookup_dnskey(z, env)) { /* wait for the lookup */ return; } reason = "could not lookup DNSKEY for chain of trust"; } else { /* the zone is not under a trust anchor */ if(only_online) return; dnskey = NULL; is_insecure = 1; } if(reason) { auth_zone_zonemd_fail(z, env, reason, why_bogus, result); regional_free_all(env->scratch); return; } auth_zone_verify_zonemd_with_key(z, env, mods, dnskey, is_insecure, result, NULL); regional_free_all(env->scratch); } void auth_zones_pickup_zonemd_verify(struct auth_zones* az, struct module_env* env) { struct auth_zone key; uint8_t savezname[255+1]; size_t savezname_len; struct auth_zone* z; key.node.key = &key; lock_rw_rdlock(&az->lock); RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_wrlock(&z->lock); if(!z->zonemd_check) { lock_rw_unlock(&z->lock); continue; } key.dclass = z->dclass; key.namelabs = z->namelabs; if(z->namelen > sizeof(savezname)) { lock_rw_unlock(&z->lock); log_err("auth_zones_pickup_zonemd_verify: zone name too long"); continue; } savezname_len = z->namelen; memmove(savezname, z->name, z->namelen); lock_rw_unlock(&az->lock); auth_zone_verify_zonemd(z, env, &env->mesh->mods, NULL, 0, 1); lock_rw_unlock(&z->lock); lock_rw_rdlock(&az->lock); /* find the zone we had before, it is not deleted, * because we have a flag for that that is processed at * apply_cfg time */ key.namelen = savezname_len; key.name = savezname; z = (struct auth_zone*)rbtree_search(&az->ztree, &key); if(!z) break; } lock_rw_unlock(&az->lock); } /** Get memory usage of auth rrset */ static size_t auth_rrset_get_mem(struct auth_rrset* rrset) { size_t m = sizeof(*rrset) + packed_rrset_sizeof(rrset->data); return m; } /** Get memory usage of auth data */ static size_t auth_data_get_mem(struct auth_data* node) { size_t m = sizeof(*node) + node->namelen; struct auth_rrset* rrset; for(rrset = node->rrsets; rrset; rrset = rrset->next) { m += auth_rrset_get_mem(rrset); } return m; } /** Get memory usage of auth zone */ static size_t auth_zone_get_mem(struct auth_zone* z) { size_t m = sizeof(*z) + z->namelen; struct auth_data* node; if(z->zonefile) m += strlen(z->zonefile)+1; RBTREE_FOR(node, struct auth_data*, &z->data) { m += auth_data_get_mem(node); } if(z->rpz) m += rpz_get_mem(z->rpz); return m; } /** Get memory usage of list of auth addr */ static size_t auth_addrs_get_mem(struct auth_addr* list) { size_t m = 0; struct auth_addr* a; for(a = list; a; a = a->next) { m += sizeof(*a); } return m; } /** Get memory usage of list of primaries for auth xfer */ static size_t auth_primaries_get_mem(struct auth_master* list) { size_t m = 0; struct auth_master* n; for(n = list; n; n = n->next) { m += sizeof(*n); m += auth_addrs_get_mem(n->list); if(n->host) m += strlen(n->host)+1; if(n->file) m += strlen(n->file)+1; } return m; } /** Get memory usage or list of auth chunks */ static size_t auth_chunks_get_mem(struct auth_chunk* list) { size_t m = 0; struct auth_chunk* chunk; for(chunk = list; chunk; chunk = chunk->next) { m += sizeof(*chunk) + chunk->len; } return m; } /** Get memory usage of auth xfer */ static size_t auth_xfer_get_mem(struct auth_xfer* xfr) { size_t m = sizeof(*xfr) + xfr->namelen; /* auth_nextprobe */ m += comm_timer_get_mem(xfr->task_nextprobe->timer); /* auth_probe */ m += auth_primaries_get_mem(xfr->task_probe->masters); m += comm_point_get_mem(xfr->task_probe->cp); m += comm_timer_get_mem(xfr->task_probe->timer); /* auth_transfer */ m += auth_chunks_get_mem(xfr->task_transfer->chunks_first); m += auth_primaries_get_mem(xfr->task_transfer->masters); m += comm_point_get_mem(xfr->task_transfer->cp); m += comm_timer_get_mem(xfr->task_transfer->timer); /* allow_notify_list */ m += auth_primaries_get_mem(xfr->allow_notify_list); return m; } /** Get memory usage of auth zones ztree */ static size_t az_ztree_get_mem(struct auth_zones* az) { size_t m = 0; struct auth_zone* z; RBTREE_FOR(z, struct auth_zone*, &az->ztree) { lock_rw_rdlock(&z->lock); m += auth_zone_get_mem(z); lock_rw_unlock(&z->lock); } return m; } /** Get memory usage of auth zones xtree */ static size_t az_xtree_get_mem(struct auth_zones* az) { size_t m = 0; struct auth_xfer* xfr; RBTREE_FOR(xfr, struct auth_xfer*, &az->xtree) { lock_basic_lock(&xfr->lock); m += auth_xfer_get_mem(xfr); lock_basic_unlock(&xfr->lock); } return m; } size_t auth_zones_get_mem(struct auth_zones* zones) { size_t m; if(!zones) return 0; m = sizeof(*zones); lock_rw_rdlock(&zones->rpz_lock); lock_rw_rdlock(&zones->lock); m += az_ztree_get_mem(zones); m += az_xtree_get_mem(zones); lock_rw_unlock(&zones->lock); lock_rw_unlock(&zones->rpz_lock); return m; } void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker) { if(xfr->task_nextprobe->worker == worker) { xfr_nextprobe_disown(xfr); } if(xfr->task_probe->worker == worker) { xfr_probe_disown(xfr); } if(xfr->task_transfer->worker == worker) { xfr_transfer_disown(xfr); } } unbound-1.25.1/services/rpz.h0000644000175000017500000002152015203270263015515 0ustar wouterwouter/* * services/rpz.h - rpz service * * Copyright (c) 2019, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable RPZ service. */ #ifndef SERVICES_RPZ_H #define SERVICES_RPZ_H #include "services/localzone.h" #include "util/locks.h" #include "util/log.h" #include "util/config_file.h" #include "services/authzone.h" #include "sldns/sbuffer.h" #include "daemon/stats.h" #include "respip/respip.h" struct iter_qstate; /** * RPZ triggers, only the QNAME trigger is currently supported in Unbound. */ enum rpz_trigger { RPZ_QNAME_TRIGGER = 0, /* unsupported triggers */ RPZ_CLIENT_IP_TRIGGER, /* rpz-client-ip */ RPZ_RESPONSE_IP_TRIGGER, /* rpz-ip */ RPZ_NSDNAME_TRIGGER, /* rpz-nsdname */ RPZ_NSIP_TRIGGER, /* rpz-nsip */ RPZ_INVALID_TRIGGER, /* dname does not contain valid trigger */ }; /** * RPZ actions. */ enum rpz_action { RPZ_NXDOMAIN_ACTION = 0,/* CNAME . */ RPZ_NODATA_ACTION, /* CNAME *. */ RPZ_PASSTHRU_ACTION, /* CNAME rpz-passthru. */ RPZ_DROP_ACTION, /* CNAME rpz-drop. */ RPZ_TCP_ONLY_ACTION, /* CNAME rpz-tcp-only. */ RPZ_INVALID_ACTION, /* CNAME with (child of) TLD starting with "rpz-" in target, SOA, NS, DNAME and DNSSEC-related records. */ RPZ_LOCAL_DATA_ACTION, /* anything else */ /* RPZ override actions */ RPZ_DISABLED_ACTION, /* RPZ action disabled using override */ RPZ_NO_OVERRIDE_ACTION, /* RPZ action no override*/ RPZ_CNAME_OVERRIDE_ACTION, /* RPZ CNAME action override*/ }; struct clientip_synthesized_rrset { struct regional* region; struct rbtree_type entries; /** lock on the entries tree */ lock_rw_type lock; }; struct clientip_synthesized_rr { /** node in address tree */ struct addr_tree_node node; /** lock on the node item */ lock_rw_type lock; /** action for this address span */ enum rpz_action action; /** "local data" for this node */ struct local_rrset* data; }; /** * RPZ containing policies. Pointed to from corresponding auth-zone. Part of a * linked list to keep configuration order. Iterating or changing the linked * list requires the rpz_lock from struct auth_zones. Changing items in this * struct require the lock from struct auth_zone. */ struct rpz { struct local_zones* local_zones; struct respip_set* respip_set; struct clientip_synthesized_rrset* client_set; struct clientip_synthesized_rrset* ns_set; struct local_zones* nsdname_zones; uint8_t* taglist; size_t taglistlen; enum rpz_action action_override; struct ub_packed_rrset_key* cname_override; int log; char* log_name; /** signal NXDOMAIN blocked with unset RA flag */ int signal_nxdomain_ra; struct regional* region; int disabled; }; /** * Create policy from RR and add to this RPZ. * @param r: the rpz to add the policy to. * @param azname: dname of the auth-zone * @param aznamelen: the length of the auth-zone name * @param dname: dname of the RR * @param dnamelen: length of the dname * @param rr_type: RR type of the RR * @param rr_class: RR class of the RR * @param rr_ttl: TTL of the RR * @param rdatawl: rdata of the RR, prepended with the rdata size * @param rdatalen: length if the RR, including the prepended rdata size * @param rr: the complete RR, for logging purposes * @param rr_len: the length of the complete RR * @return: 0 on error */ int rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len); /** * Delete policy matching RR, used for IXFR. * @param r: the rpz to add the policy to. * @param azname: dname of the auth-zone * @param aznamelen: the length of the auth-zone name * @param dname: dname of the RR * @param dnamelen: length of the dname * @param rr_type: RR type of the RR * @param rr_class: RR class of the RR * @param rdatawl: rdata of the RR, prepended with the rdata size * @param rdatalen: length if the RR, including the prepended rdata size */ void rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen); /** * Walk over the RPZ zones to find and apply a QNAME trigger policy. * @param az: auth_zones struct, containing first RPZ item and RPZ lock * @param env: module env * @param qinfo: qinfo containing qname and qtype * @param edns: edns data * @param buf: buffer to write answer to * @param temp: scratchpad * @param repinfo: reply info * @param taglist: taglist to lookup. * @param taglen: length of taglist. * @param stats: worker stats struct * @param passthru: returns if the query can passthru further rpz processing. * @return: 1 if client answer is ready, 0 to continue resolving */ int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, int* passthru); /** * Callback to process when the iterator module is about to send queries. * Checks for nsip and nsdname triggers. * @param qstate: the query state. * @param iq: iterator module query state. * @return NULL if nothing is done. Or a new message with the contents from * the rpz, based on the delegation point. It is allocated in the * qstate region. */ struct dns_msg* rpz_callback_from_iterator_module(struct module_qstate* qstate, struct iter_qstate* iq); /** * Callback to process when the iterator module has followed a cname. * There can be a qname trigger for the new query name. * @param qstate: the query state. * @param iq: iterator module query state. * @return NULL if nothing is done. Or a new message with the contents from * the rpz, based on the iq.qchase. It is allocated in the qstate region. */ struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* qstate, struct iter_qstate* iq); /** * Delete RPZ * @param r: RPZ struct to delete */ void rpz_delete(struct rpz* r); /** * Clear local-zones and respip data in RPZ, used after reloading file or * AXFR/HTTP transfer. * @param r: RPZ to use */ int rpz_clear(struct rpz* r); /** * Create RPZ. RPZ must be added to linked list after creation. * @return: the newly created RPZ */ struct rpz* rpz_create(struct config_auth* p); /** * Change config on rpz, after reload. * @param r: the rpz structure. * @param p: the config that was read. * @return false on failure. */ int rpz_config(struct rpz* r, struct config_auth* p); /** * String for RPZ action enum * @param a: RPZ action to get string for * @return: string for RPZ action */ const char* rpz_action_to_string(enum rpz_action a); enum rpz_action respip_action_to_rpz_action(enum respip_action a); /** * Prepare RPZ after processing feed content. * @param r: RPZ to use */ void rpz_finish_config(struct rpz* r); /** * Classify respip action for RPZ action * @param a: RPZ action * @return: the respip action */ enum respip_action rpz_action_to_respip_action(enum rpz_action a); /** * Enable RPZ * @param r: RPZ struct to enable */ void rpz_enable(struct rpz* r); /** * Disable RPZ * @param r: RPZ struct to disable */ void rpz_disable(struct rpz* r); /** * Get memory usage of rpz. Caller must manage locks. * @param r: RPZ struct. * @return memory usage. */ size_t rpz_get_mem(struct rpz* r); #endif /* SERVICES_RPZ_H */ unbound-1.25.1/services/modstack.h0000644000175000017500000001123315203270263016507 0ustar wouterwouter/* * services/modstack.h - stack of modules * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to help maintain a stack of modules. */ #ifndef SERVICES_MODSTACK_H #define SERVICES_MODSTACK_H struct module_func_block; struct module_env; /** * Stack of modules. */ struct module_stack { /** the number of modules */ int num; /** the module callbacks, array of num_modules length (ref only) */ struct module_func_block** mod; }; /** * Init a stack of modules * @param stack: initialised as empty. */ void modstack_init(struct module_stack* stack); /** * Free the stack of modules * @param stack: stack that frees up memory. */ void modstack_free(struct module_stack* stack); /** * Initialises modules and assigns ids. Calls module_startup(). * @param stack: Expected empty, filled according to module_conf * @param module_conf: string what modules to initialize * @param env: module environment which is inited by the modules. * environment should have a superalloc, cfg, * @return on false a module init failed. */ int modstack_call_startup(struct module_stack* stack, const char* module_conf, struct module_env* env); /** * Read config file module settings and set up the modfunc block * @param stack: the stack of modules (empty before call). * @param module_conf: string what modules to insert. * @return false on error */ int modstack_config(struct module_stack* stack, const char* module_conf); /** * Get funcblock for module name * @param str: string with module name. Advanced to next value on success. * The string is assumed whitespace separated list of module names. * @return funcblock or NULL on error. */ struct module_func_block* module_factory(const char** str); /** * Get list of modules available. * @return list of modules available. Static strings, ends with NULL. */ const char** module_list_avail(void); /** * Init modules. Calls module_init(). * @param stack: It is modstack_setupped(). * @param module_conf: module ordering to check against the ordering in stack. * fails on changed ordering. * @param env: module environment which is inited by the modules. * environment should have a superalloc, cfg, * env.need_to_validate is set by the modules. * @return on false a module init failed. */ int modstack_call_init(struct module_stack* stack, const char* module_conf, struct module_env* env); /** * Deinit the modules. * @param stack: made empty. * @param env: module env for module deinit() calls. */ void modstack_call_deinit(struct module_stack* stack, struct module_env* env); /** * Destartup the modules, close, delete. * @param stack: made empty. * @param env: module env for module destartup() calls. */ void modstack_call_destartup(struct module_stack* stack, struct module_env* env); /** * Find index of module by name. * @param stack: to look in * @param name: the name to look for * @return -1 on failure, otherwise index number. */ int modstack_find(struct module_stack* stack, const char* name); /** fetch memory for a module by name, returns 0 if module not there */ size_t mod_get_mem(struct module_env* env, const char* name); #endif /* SERVICES_MODSTACK_H */ unbound-1.25.1/services/outbound_list.c0000644000175000017500000000525715203270263017600 0ustar wouterwouter/* * services/outbound_list.c - keep list of outbound serviced queries. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to help a module keep track of the * queries it has outstanding to authoritative servers. */ #include "config.h" #include #include "services/outbound_list.h" #include "services/outside_network.h" void outbound_list_init(struct outbound_list* list) { list->first = NULL; } void outbound_list_clear(struct outbound_list* list) { struct outbound_entry *p, *np; p = list->first; while(p) { np = p->next; outnet_serviced_query_stop(p->qsent, p); /* in region, no free needed */ p = np; } outbound_list_init(list); } void outbound_list_insert(struct outbound_list* list, struct outbound_entry* e) { if(list->first) list->first->prev = e; e->next = list->first; e->prev = NULL; list->first = e; } void outbound_list_remove(struct outbound_list* list, struct outbound_entry* e) { if(!e) return; outnet_serviced_query_stop(e->qsent, e); if(e->next) e->next->prev = e->prev; if(e->prev) e->prev->next = e->next; else list->first = e->next; /* in region, no free needed */ } unbound-1.25.1/services/outbound_list.h0000644000175000017500000000704015203270263017575 0ustar wouterwouter/* * services/outbound_list.h - keep list of outbound serviced queries. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to help a module keep track of the * queries it has outstanding to authoritative servers. */ #ifndef SERVICES_OUTBOUND_LIST_H #define SERVICES_OUTBOUND_LIST_H struct outbound_entry; struct serviced_query; struct module_qstate; /** * The outbound list. This structure is part of the module specific query * state. */ struct outbound_list { /** The linked list of outbound query entries. */ struct outbound_entry* first; }; /** * Outbound list entry. A serviced query sent by a module processing the * query from the qstate. Double linked list to aid removal. */ struct outbound_entry { /** next in list */ struct outbound_entry* next; /** prev in list */ struct outbound_entry* prev; /** The query that was sent out */ struct serviced_query* qsent; /** the module query state that sent it */ struct module_qstate* qstate; }; /** * Init the user allocated outbound list structure * @param list: the list structure. */ void outbound_list_init(struct outbound_list* list); /** * Clear the user owner outbound list structure. * Deletes serviced queries. * @param list: the list structure. It is cleared, but the list struct itself * is callers responsibility to delete. */ void outbound_list_clear(struct outbound_list* list); /** * Insert new entry into the list. Caller must allocate the entry with malloc. * qstate and qsent are set by caller. * @param list: the list to add to. * @param e: entry to add, it is only half initialised at call start, fully * initialised at call end. */ void outbound_list_insert(struct outbound_list* list, struct outbound_entry* e); /** * Remove an entry from the list, and deletes it. * Deletes serviced query in the entry. * @param list: the list to remove from. * @param e: the entry to remove. */ void outbound_list_remove(struct outbound_list* list, struct outbound_entry* e); #endif /* SERVICES_OUTBOUND_LIST_H */ unbound-1.25.1/services/outside_network.h0000644000175000017500000010024215203270263020126 0ustar wouterwouter/* * services/outside_network.h - listen to answers from the network * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has functions to send queries to authoritative servers, * and wait for the pending answer, with timeouts. */ #ifndef OUTSIDE_NETWORK_H #define OUTSIDE_NETWORK_H #include "util/alloc.h" #include "util/rbtree.h" #include "util/regional.h" #include "util/netevent.h" #include "dnstap/dnstap_config.h" #ifdef __QNX__ /* For struct timeval */ #include #endif /* __QNX__ */ struct pending; struct pending_timeout; struct ub_randstate; struct pending_tcp; struct waiting_tcp; struct waiting_udp; struct reuse_tcp; struct infra_cache; struct port_comm; struct port_if; struct sldns_buffer; struct serviced_query; struct dt_env; struct edns_option; struct module_env; struct module_qstate; struct query_info; struct config_file; /** * Send queries to outside servers and wait for answers from servers. * Contains answer-listen sockets. */ struct outside_network { /** Base for select calls */ struct comm_base* base; /** pointer to time in seconds */ time_t* now_secs; /** pointer to time in microseconds */ struct timeval* now_tv; /** buffer shared by UDP connections, since there is only one datagram at any time. */ struct sldns_buffer* udp_buff; /** serviced_callbacks malloc overhead when processing multiple * identical serviced queries to the same server. */ size_t svcd_overhead; /** use x20 bits to encode additional ID random bits */ int use_caps_for_id; /** outside network wants to quit. Stop queued msgs from sent. */ int want_to_quit; /** number of unwanted replies received (for statistics) */ size_t unwanted_replies; /** cumulative total of unwanted replies (for defense) */ size_t unwanted_total; /** threshold when to take defensive action. If 0 then never. */ size_t unwanted_threshold; /** what action to take, called when defensive action is needed */ void (*unwanted_action)(void*); /** user param for action */ void* unwanted_param; /** linked list of available commpoints, unused file descriptors, * for use as outgoing UDP ports. cp.fd=-1 in them. */ struct port_comm* unused_fds; /** if udp is done */ int do_udp; /** if udp is delay-closed (delayed answers do not meet closed port)*/ int delayclose; /** timeout for delayclose */ struct timeval delay_tv; /** if we perform udp-connect, connect() for UDP socket to mitigate * ICMP side channel leakage */ int udp_connect; /** number of udp packets sent. */ size_t num_udp_outgoing; /** array of outgoing IP4 interfaces */ struct port_if* ip4_ifs; /** number of outgoing IP4 interfaces */ int num_ip4; /** array of outgoing IP6 interfaces */ struct port_if* ip6_ifs; /** number of outgoing IP6 interfaces */ int num_ip6; /** pending udp queries waiting to be sent out, waiting for fd */ struct pending* udp_wait_first; /** last pending udp query in list */ struct pending* udp_wait_last; /** pending udp answers. sorted by id, addr */ rbtree_type* pending; /** serviced queries, sorted by qbuf, addr, dnssec */ rbtree_type* serviced; /** host cache, pointer but not owned by outnet. */ struct infra_cache* infra; /** where to get random numbers */ struct ub_randstate* rnd; /** ssl context to create ssl wrapped TCP with DNS connections */ void* sslctx; /** if SNI will be used for TLS connections */ int tls_use_sni; #ifdef USE_DNSTAP /** dnstap environment */ struct dt_env* dtenv; #endif /** maximum segment size of tcp socket */ int tcp_mss; /** IP_TOS socket option requested on the sockets */ int ip_dscp; /** * Array of tcp pending used for outgoing TCP connections. * Each can be used to establish a TCP connection with a server. * The file descriptors are -1 if they are free, and need to be * opened for the tcp connection. Can be used for ip4 and ip6. */ struct pending_tcp **tcp_conns; /** number of tcp communication points. */ size_t num_tcp; /** number of tcp communication points in use. */ size_t num_tcp_outgoing; /** max number of queries on a reuse connection */ size_t max_reuse_tcp_queries; /** timeout for REUSE entries in milliseconds. */ int tcp_reuse_timeout; /** timeout in milliseconds for TCP queries to auth servers. */ int tcp_auth_query_timeout; /** * tree of still-open and waiting tcp connections for reuse. * can be closed and reopened to get a new tcp connection. * or reused to the same destination again. with timeout to close. * Entries are of type struct reuse_tcp. * The entries are both active and empty connections. */ rbtree_type tcp_reuse; /** max number of tcp_reuse entries we want to keep open */ size_t tcp_reuse_max; /** first and last(oldest) in lru list of reuse connections. * the oldest can be closed to get a new free pending_tcp if needed * The list contains empty connections, that wait for timeout or * a new query that can use the existing connection. */ struct reuse_tcp* tcp_reuse_first, *tcp_reuse_last; /** list of tcp comm points that are free for use */ struct pending_tcp* tcp_free; /** list of tcp queries waiting for a buffer */ struct waiting_tcp* tcp_wait_first; /** last of waiting query list */ struct waiting_tcp* tcp_wait_last; }; /** * Outgoing interface. Ports available and currently used are tracked * per interface */ struct port_if { /** address ready to allocate new socket (except port no). */ struct sockaddr_storage addr; /** length of addr field */ socklen_t addrlen; /** prefix length of network address (in bits), for randomisation. * if 0, no randomisation. */ int pfxlen; #ifndef DISABLE_EXPLICIT_PORT_RANDOMISATION /** the available ports array. These are unused. * Only the first total-inuse part is filled. */ int* avail_ports; /** the total number of available ports (size of the array) */ int avail_total; #endif /** array of the commpoints currently in use. * allocated for max number of fds, first part in use. */ struct port_comm** out; /** max number of fds, size of out array */ int maxout; /** number of commpoints (and thus also ports) in use */ int inuse; }; /** * Outgoing commpoint for UDP port. */ struct port_comm { /** next in free list */ struct port_comm* next; /** which port number (when in use) */ int number; /** interface it is used in */ struct port_if* pif; /** index in the out array of the interface */ int index; /** number of outstanding queries on this port */ int num_outstanding; /** UDP commpoint, fd=-1 if not in use */ struct comm_point* cp; }; /** * Reuse TCP connection, still open can be used again. */ struct reuse_tcp { /** rbtree node with links in tcp_reuse tree. key is NULL when not * in tree. Both active and empty connections are in the tree. * key is a pointer to this structure, the members used to compare * are the sockaddr and and then is-ssl bool, and then ptr value is * used in case the same address exists several times in the tree * when there are multiple connections to the same destination to * make the rbtree items unique. */ rbnode_type node; /** the key for the tcp_reuse tree. address of peer, ip4 or ip6, * and port number of peer */ struct sockaddr_storage addr; /** length of addr */ socklen_t addrlen; /** also key for tcp_reuse tree, if ssl is used */ int is_ssl; /** If is_ssl is enabled, tls_auth_name is part of the key for * tcp_reuse tree. If the string is NULL, it without a tls_auth_name */ char* tls_auth_name; /** lru chain, so that the oldest can be removed to get a new * connection when all are in (re)use. oldest is last in list. * The lru only contains empty connections waiting for reuse, * the ones with active queries are not on the list because they * do not need to be closed to make space for others. They already * service a query so the close for another query does not help * service a larger number of queries. */ struct reuse_tcp* lru_next, *lru_prev; /** true if the reuse_tcp item is on the lru list with empty items */ int item_on_lru_list; /** the connection to reuse, the fd is non-1 and is open. * the addr and port determine where the connection is going, * and is key to the rbtree. The SSL ptr determines if it is * a TLS connection or a plain TCP connection there. And TLS * or not is also part of the key to the rbtree. * There is a timeout and read event on the fd, to close it. */ struct pending_tcp* pending; /** * The more read again value pointed to by the commpoint * tcp_more_read_again pointer, so that it exists after commpoint * delete */ int cp_more_read_again; /** * The more write again value pointed to by the commpoint * tcp_more_write_again pointer, so that it exists after commpoint * delete */ int cp_more_write_again; /** rbtree with other queries waiting on the connection, by ID number, * of type struct waiting_tcp. It is for looking up received * answers to the structure for callback. And also to see if ID * numbers are unused and can be used for a new query. * The write_wait elements are also in the tree, so that ID numbers * can be looked up also for them. They are bool write_wait_queued. */ rbtree_type tree_by_id; /** list of queries waiting to be written on the channel, * if NULL no queries are waiting to be written and the pending->query * is the query currently serviced. The first is the next in line. * They are also in the tree_by_id. Once written, the are removed * from this list, but stay in the tree. */ struct waiting_tcp* write_wait_first, *write_wait_last; /** the outside network it is part of */ struct outside_network* outnet; }; /** * A query that has an answer pending for it. */ struct pending { /** redblacktree entry, key is the pending struct(id, addr). */ rbnode_type node; /** the ID for the query. int so that a value out of range can * be used to signify a pending that is for certain not present in * the rbtree. (and for which deletion is safe). */ unsigned int id; /** remote address. */ struct sockaddr_storage addr; /** length of addr field in use. */ socklen_t addrlen; /** comm point it was sent on (and reply must come back on). */ struct port_comm* pc; /** timeout event */ struct comm_timer* timer; /** callback for the timeout, error or reply to the message */ comm_point_callback_type* cb; /** callback user argument */ void* cb_arg; /** the outside network it is part of */ struct outside_network* outnet; /** the corresponding serviced_query */ struct serviced_query* sq; /*---- filled if udp pending is waiting -----*/ /** next in waiting list. */ struct pending* next_waiting; /** timeout in msec */ int timeout; /** The query itself, the query packet to send. */ uint8_t* pkt; /** length of query packet. */ size_t pkt_len; }; /** * Pending TCP query to server. */ struct pending_tcp { /** next in list of free tcp comm points, or NULL. */ struct pending_tcp* next_free; /** port for of the outgoing interface that is used */ struct port_if* pi; /** tcp comm point it was sent on (and reply must come back on). */ struct comm_point* c; /** the query being serviced, NULL if the pending_tcp is unused. */ struct waiting_tcp* query; /** the pre-allocated reuse tcp structure. if ->pending is nonNULL * it is in use and the connection is waiting for reuse. * It is here for memory pre-allocation, and used to make this * pending_tcp wait for reuse. */ struct reuse_tcp reuse; }; /** * Query waiting for TCP buffer. */ struct waiting_tcp { /** * next in waiting list. * if on_tcp_waiting_list==0, this points to the pending_tcp structure. */ struct waiting_tcp* next_waiting; /** if true the item is on the tcp waiting list and next_waiting * is used for that. If false, the next_waiting points to the * pending_tcp */ int on_tcp_waiting_list; /** next and prev in query waiting list for stream connection */ struct waiting_tcp* write_wait_prev, *write_wait_next; /** true if the waiting_tcp structure is on the write_wait queue */ int write_wait_queued; /** entry in reuse.tree_by_id, if key is NULL, not in tree, otherwise, * this struct is key and sorted by ID (from waiting_tcp.id). */ rbnode_type id_node; /** the ID for the query; checked in reply */ uint16_t id; /** timeout event; timer keeps running whether the query is * waiting for a buffer or the tcp reply is pending */ struct comm_timer* timer; /** timeout in msec */ int timeout; /** the outside network it is part of */ struct outside_network* outnet; /** remote address. */ struct sockaddr_storage addr; /** length of addr field in use. */ socklen_t addrlen; /** * The query itself, the query packet to send. * allocated after the waiting_tcp structure. */ uint8_t* pkt; /** length of query packet. */ size_t pkt_len; /** callback for the timeout, error or reply to the message, * or NULL if no user is waiting. the entry uses an ID number. * a query that was written is no longer needed, but the ID number * and a reply will come back and can be ignored if NULL */ comm_point_callback_type* cb; /** callback user argument */ void* cb_arg; /** if it uses ssl upstream */ int ssl_upstream; /** ref to the tls_auth_name from the serviced_query */ char* tls_auth_name; /** the packet was involved in an error, to stop looping errors */ int error_count; /** if true, the item is at the cb_and_decommission stage */ int in_cb_and_decommission; #ifdef USE_DNSTAP /** serviced query pointer for dnstap to get logging info, if nonNULL*/ struct serviced_query* sq; #endif }; /** * Callback to party interested in serviced query results. */ struct service_callback { /** next in callback list */ struct service_callback* next; /** callback function */ comm_point_callback_type* cb; /** user argument for callback function */ void* cb_arg; }; /** fallback size for fragmentation for EDNS in IPv4 */ #define EDNS_FRAG_SIZE_IP4 1472 /** fallback size for EDNS in IPv6, fits one fragment with ip6-tunnel-ids */ #define EDNS_FRAG_SIZE_IP6 1232 /** * Query service record. * Contains query and destination. UDP, TCP, EDNS are all tried. * complete with retries and timeouts. A number of interested parties can * receive a callback. */ struct serviced_query { /** The rbtree node, key is this record */ rbnode_type node; /** The query that needs to be answered. Starts with flags u16, * then qdcount, ..., including qname, qtype, qclass. Does not include * EDNS record. */ uint8_t* qbuf; /** length of qbuf. */ size_t qbuflen; /** If an EDNS section is included, the DO/CD bit will be turned on. */ int dnssec; /** We want signatures, or else the answer is likely useless */ int want_dnssec; /** ignore capsforid */ int nocaps; /** tcp upstream used, use tcp, or ssl_upstream for SSL */ int tcp_upstream, ssl_upstream; /** the name of the tls authentication name, eg. 'ns.example.com' * or NULL */ char* tls_auth_name; /** where to send it */ struct sockaddr_storage addr; /** length of addr field in use. */ socklen_t addrlen; /** zone name, uncompressed domain name in wireformat */ uint8_t* zone; /** length of zone name */ size_t zonelen; /** qtype */ int qtype; /** current status */ enum serviced_query_status { /** initial status */ serviced_initial, /** UDP with EDNS sent */ serviced_query_UDP_EDNS, /** UDP without EDNS sent */ serviced_query_UDP, /** TCP with EDNS sent */ serviced_query_TCP_EDNS, /** TCP without EDNS sent */ serviced_query_TCP, /** probe to test noEDNS0 (EDNS gives FORMERRorNOTIMP) */ serviced_query_UDP_EDNS_fallback, /** probe to test TCP noEDNS0 (EDNS gives FORMERRorNOTIMP) */ serviced_query_TCP_EDNS_fallback, /** send UDP query with EDNS1480 (or 1280) */ serviced_query_UDP_EDNS_FRAG } /** variable with current status */ status; /** true if serviced_query is scheduled for deletion already */ int to_be_deleted; /** number of UDP retries */ int retry; /** time last UDP was sent */ struct timeval last_sent_time; /** rtt of last message */ int last_rtt; /** do we know edns probe status already, for UDP_EDNS queries */ int edns_lame_known; /** edns options to use for sending upstream packet */ struct edns_option* opt_list; /** outside network this is part of */ struct outside_network* outnet; /** list of interested parties that need callback on results. */ struct service_callback* cblist; /** the UDP or TCP query that is pending, see status which */ void* pending; /** block size with which to pad encrypted queries (default: 128) */ size_t padding_block_size; /** region for this serviced query. Will be cleared when this * serviced_query will be deleted */ struct regional* region; /** allocation service for the region */ struct alloc_cache* alloc; /** flash timer to start the net I/O as a separate event */ struct comm_timer* timer; /** true if serviced_query is currently doing net I/O and may block */ int busy; }; /** * Create outside_network structure with N udp ports. * @param base: the communication base to use for event handling. * @param bufsize: size for network buffers. * @param num_ports: number of udp ports to open per interface. * @param ifs: interface names (or NULL for default interface). * These interfaces must be able to access all authoritative servers. * @param num_ifs: number of names in array ifs. * @param do_ip4: service IP4. * @param do_ip6: service IP6. * @param num_tcp: number of outgoing tcp buffers to preallocate. * @param dscp: DSCP to use. * @param infra: pointer to infra cached used for serviced queries. * @param rnd: stored to create random numbers for serviced queries. * @param use_caps_for_id: enable to use 0x20 bits to encode id randomness. * @param availports: array of available ports. * @param numavailports: number of available ports in array. * @param unwanted_threshold: when to take defensive action. * @param unwanted_action: the action to take. * @param unwanted_param: user parameter to action. * @param tcp_mss: maximum segment size of tcp socket. * @param do_udp: if udp is done. * @param sslctx: context to create outgoing connections with (if enabled). * @param delayclose: if not 0, udp sockets are delayed before timeout closure. * msec to wait on timeouted udp sockets. * @param tls_use_sni: if SNI is used for TLS connections. * @param dtenv: environment to send dnstap events with (if enabled). * @param udp_connect: if the udp_connect option is enabled. * @param max_reuse_tcp_queries: max number of queries on a reuse connection. * @param tcp_reuse_timeout: timeout for REUSE entries in milliseconds. * @param tcp_auth_query_timeout: timeout in milliseconds for TCP queries to auth servers. * @return: the new structure (with no pending answers) or NULL on error. */ struct outside_network* outside_network_create(struct comm_base* base, size_t bufsize, size_t num_ports, char** ifs, int num_ifs, int do_ip4, int do_ip6, size_t num_tcp, int dscp, struct infra_cache* infra, struct ub_randstate* rnd, int use_caps_for_id, int* availports, int numavailports, size_t unwanted_threshold, int tcp_mss, void (*unwanted_action)(void*), void* unwanted_param, int do_udp, void* sslctx, int delayclose, int tls_use_sni, struct dt_env *dtenv, int udp_connect, int max_reuse_tcp_queries, int tcp_reuse_timeout, int tcp_auth_query_timeout); /** * Delete outside_network structure. * @param outnet: object to delete. */ void outside_network_delete(struct outside_network* outnet); /** * Prepare for quit. Sends no more queries, even if queued up. * @param outnet: object to prepare for removal */ void outside_network_quit_prepare(struct outside_network* outnet); /** * Send UDP query, create pending answer. * Changes the ID for the query to be random and unique for that destination. * @param sq: serviced query. * @param packet: wireformat query to send to destination. * @param timeout: in milliseconds from now. * @param callback: function to call on error, timeout or reply. * @param callback_arg: user argument for callback function. * @return: NULL on error for malloc or socket. Else the pending query object. */ struct pending* pending_udp_query(struct serviced_query* sq, struct sldns_buffer* packet, int timeout, comm_point_callback_type* callback, void* callback_arg); /** * Send TCP query. May wait for TCP buffer. Selects ID to be random, and * checks id. * @param sq: serviced query. * @param packet: wireformat query to send to destination. copied from. * @param timeout: in milliseconds from now. * Timer starts running now. Timer may expire if all buffers are used, * without any query been sent to the server yet. * @param callback: function to call on error, timeout or reply. * @param callback_arg: user argument for callback function. * @return: false on error for malloc or socket. Else the pending TCP object. */ struct waiting_tcp* pending_tcp_query(struct serviced_query* sq, struct sldns_buffer* packet, int timeout, comm_point_callback_type* callback, void* callback_arg); /** * Delete pending answer. * @param outnet: outside network the pending query is part of. * Internal feature: if outnet is NULL, p is not unlinked from rbtree. * @param p: deleted */ void pending_delete(struct outside_network* outnet, struct pending* p); /** * Perform a serviced query to the authoritative servers. * Duplicate efforts are detected, and EDNS, TCP and UDP retry is performed. * @param outnet: outside network, with rbtree of serviced queries. * @param qinfo: query info. * @param flags: flags u16 (host format), includes opcode, CD bit. * @param dnssec: if set, DO bit is set in EDNS queries. * If the value includes BIT_CD, CD bit is set when in EDNS queries. * If the value includes BIT_DO, DO bit is set when in EDNS queries. * @param want_dnssec: signatures are needed, without EDNS the answer is * likely to be useless. * @param nocaps: ignore use_caps_for_id and use unperturbed qname. * @param check_ratelimit: if set, will check ratelimit before sending out. * @param tcp_upstream: use TCP for upstream queries. * @param ssl_upstream: use SSL for upstream queries. * @param tls_auth_name: when ssl_upstream is true, use this name to check * the server's peer certificate. * @param addr: to which server to send the query. * @param addrlen: length of addr. * @param zone: name of the zone of the delegation point. wireformat dname. This is the delegation point name for which the server is deemed authoritative. * @param zonelen: length of zone. * @param qstate: module qstate. Mainly for inspecting the available * edns_opts_lists. * @param callback: callback function. * @param callback_arg: user argument to callback function. * @param buff: scratch buffer to create query contents in. Empty on exit. * @param env: the module environment. * @param was_ratelimited: it will signal back if the query failed to pass the * ratelimit check. * @return 0 on error, or pointer to serviced query that is used to answer * this serviced query may be shared with other callbacks as well. */ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, int check_ratelimit, int tcp_upstream, int ssl_upstream, char* tls_auth_name, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, struct module_qstate* qstate, comm_point_callback_type* callback, void* callback_arg, struct sldns_buffer* buff, struct module_env* env, int* was_ratelimited); /** * Remove service query callback. * If that leads to zero callbacks, the query is completely cancelled. * @param sq: serviced query to adjust. * @param cb_arg: callback argument of callback that needs removal. * same as the callback_arg to outnet_serviced_query(). */ void outnet_serviced_query_stop(struct serviced_query* sq, void* cb_arg); /** * Get memory size in use by outside network. * Counts buffers and outstanding query (serviced queries) malloced data. * @param outnet: outside network structure. * @return size in bytes. */ size_t outnet_get_mem(struct outside_network* outnet); /** * Get memory size in use by serviced query while it is servicing callbacks. * This takes into account the pre-deleted status of it; it will be deleted * when the callbacks are done. * @param sq: serviced query. * @return size in bytes. */ size_t serviced_get_mem(struct serviced_query* sq); /** Pick random ID value for a tcp stream, avoids existing IDs. */ uint16_t reuse_tcp_select_id(struct reuse_tcp* reuse, struct outside_network* outnet); /** find element in tree by id */ struct waiting_tcp* reuse_tcp_by_id_find(struct reuse_tcp* reuse, uint16_t id); /** insert element in tree by id */ void reuse_tree_by_id_insert(struct reuse_tcp* reuse, struct waiting_tcp* w); /** insert element in tcp_reuse tree and LRU list */ int reuse_tcp_insert(struct outside_network* outnet, struct pending_tcp* pend_tcp); /** touch the LRU of the element */ void reuse_tcp_lru_touch(struct outside_network* outnet, struct reuse_tcp* reuse); /** remove element from tree and LRU list */ void reuse_tcp_remove_tree_list(struct outside_network* outnet, struct reuse_tcp* reuse); /** snip the last reuse_tcp element off of the LRU list if any */ struct reuse_tcp* reuse_tcp_lru_snip(struct outside_network* outnet); /** delete readwait waiting_tcp elements, deletes the elements in the list */ void reuse_del_readwait(rbtree_type* tree_by_id); /** remove waiting tcp from the outnet waiting list */ void outnet_waiting_tcp_list_remove(struct outside_network* outnet, struct waiting_tcp* w); /** pop the first waiting tcp from the outnet waiting list */ struct waiting_tcp* outnet_waiting_tcp_list_pop(struct outside_network* outnet); /** add waiting_tcp element to the outnet tcp waiting list */ void outnet_waiting_tcp_list_add(struct outside_network* outnet, struct waiting_tcp* w, int set_timer); /** add waiting_tcp element as first to the outnet tcp waiting list */ void outnet_waiting_tcp_list_add_first(struct outside_network* outnet, struct waiting_tcp* w, int reset_timer); /** pop the first element from the writewait list */ struct waiting_tcp* reuse_write_wait_pop(struct reuse_tcp* reuse); /** remove the element from the writewait list */ void reuse_write_wait_remove(struct reuse_tcp* reuse, struct waiting_tcp* w); /** push the element after the last on the writewait list */ void reuse_write_wait_push_back(struct reuse_tcp* reuse, struct waiting_tcp* w); /** get TCP file descriptor for address, returns -1 on failure, * tcp_mss is 0 or maxseg size to set for TCP packets, * nodelay (TCP_NODELAY) should be set for TLS connections to speed up the TLS * handshake.*/ int outnet_get_tcp_fd(struct sockaddr_storage* addr, socklen_t addrlen, int tcp_mss, int dscp, int nodelay); /** * Create udp commpoint suitable for sending packets to the destination. * @param outnet: outside_network with the comm_base it is attached to, * with the outgoing interfaces chosen from, and rnd gen for random. * @param cb: callback function for the commpoint. * @param cb_arg: callback argument for cb. * @param to_addr: intended destination. * @param to_addrlen: length of to_addr. * @return commpoint that you can comm_point_send_udp_msg with, or NULL. */ struct comm_point* outnet_comm_point_for_udp(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen); /** * Create tcp commpoint suitable for communication to the destination. * It also performs connect() to the to_addr. * @param outnet: outside_network with the comm_base it is attached to, * and the tcp_mss. * @param cb: callback function for the commpoint. * @param cb_arg: callback argument for cb. * @param to_addr: intended destination. * @param to_addrlen: length of to_addr. * @param query: initial packet to send writing, in buffer. It is copied * to the commpoint buffer that is created. * @param timeout: timeout for the TCP connection. * timeout in milliseconds, or -1 for no (change to the) timeout. * So seconds*1000. * @param ssl: set to true for TLS. * @param host: hostname for host name verification of TLS (or NULL if no TLS). * @return tcp_out commpoint, or NULL. */ struct comm_point* outnet_comm_point_for_tcp(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen, struct sldns_buffer* query, int timeout, int ssl, char* host); /** * Create http commpoint suitable for communication to the destination. * Creates the http request buffer. It also performs connect() to the to_addr. * @param outnet: outside_network with the comm_base it is attached to, * and the tcp_mss. * @param cb: callback function for the commpoint. * @param cb_arg: callback argument for cb. * @param to_addr: intended destination. * @param to_addrlen: length of to_addr. * @param timeout: timeout for the TCP connection. * timeout in milliseconds, or -1 for no (change to the) timeout. * So seconds*1000. * @param ssl: set to true for https. * @param host: hostname to use for the destination. part of http request. * @param path: pathname to lookup, eg. name of the file on the destination. * @param cfg: running configuration for User-Agent setup. * @return http_out commpoint, or NULL. */ struct comm_point* outnet_comm_point_for_http(struct outside_network* outnet, comm_point_callback_type* cb, void* cb_arg, struct sockaddr_storage* to_addr, socklen_t to_addrlen, int timeout, int ssl, char* host, char* path, struct config_file* cfg); /** connect tcp connection to addr, 0 on failure */ int outnet_tcp_connect(int s, struct sockaddr_storage* addr, socklen_t addrlen); /** callback for incoming udp answers from the network */ int outnet_udp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info); /** callback for pending tcp connections */ int outnet_tcp_cb(struct comm_point* c, void* arg, int error, struct comm_reply *reply_info); /** callback for udp timeout */ void pending_udp_timer_cb(void *arg); /** callback for udp delay for timeout */ void pending_udp_timer_delay_cb(void *arg); /** callback for outgoing TCP timer event */ void outnet_tcptimer(void* arg); /** callback to send serviced queries */ void serviced_timer_cb(void *arg); /** callback for serviced query UDP answers */ int serviced_udp_callback(struct comm_point* c, void* arg, int error, struct comm_reply* rep); /** TCP reply or error callback for serviced queries */ int serviced_tcp_callback(struct comm_point* c, void* arg, int error, struct comm_reply* rep); /** compare function of pending rbtree */ int pending_cmp(const void* key1, const void* key2); /** compare function of serviced query rbtree */ int serviced_cmp(const void* key1, const void* key2); /** compare function of reuse_tcp rbtree in outside_network struct */ int reuse_cmp(const void* key1, const void* key2); /** compare function of reuse_tcp tree_by_id rbtree */ int reuse_id_cmp(const void* key1, const void* key2); #endif /* OUTSIDE_NETWORK_H */ unbound-1.25.1/services/rpz.c0000644000175000017500000023761015203270263015521 0ustar wouterwouter/* * services/rpz.c - rpz service * * Copyright (c) 2019, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable RPZ service. */ #include "config.h" #include "services/rpz.h" #include "util/config_file.h" #include "sldns/wire2str.h" #include "sldns/str2wire.h" #include "util/data/dname.h" #include "util/net_help.h" #include "util/log.h" #include "util/data/dname.h" #include "util/locks.h" #include "util/regional.h" #include "util/data/msgencode.h" #include "services/cache/dns.h" #include "iterator/iterator.h" #include "iterator/iter_delegpt.h" #include "daemon/worker.h" typedef struct resp_addr rpz_aclnode_type; struct matched_delegation_point { uint8_t* dname; size_t dname_len; }; /** string for RPZ action enum */ const char* rpz_action_to_string(enum rpz_action a) { switch(a) { case RPZ_NXDOMAIN_ACTION: return "rpz-nxdomain"; case RPZ_NODATA_ACTION: return "rpz-nodata"; case RPZ_PASSTHRU_ACTION: return "rpz-passthru"; case RPZ_DROP_ACTION: return "rpz-drop"; case RPZ_TCP_ONLY_ACTION: return "rpz-tcp-only"; case RPZ_INVALID_ACTION: return "rpz-invalid"; case RPZ_LOCAL_DATA_ACTION: return "rpz-local-data"; case RPZ_DISABLED_ACTION: return "rpz-disabled"; case RPZ_CNAME_OVERRIDE_ACTION: return "rpz-cname-override"; case RPZ_NO_OVERRIDE_ACTION: return "rpz-no-override"; default: return "rpz-unknown-action"; } } /** RPZ action enum for config string */ static enum rpz_action rpz_config_to_action(char* a) { if(strcmp(a, "nxdomain") == 0) return RPZ_NXDOMAIN_ACTION; else if(strcmp(a, "nodata") == 0) return RPZ_NODATA_ACTION; else if(strcmp(a, "passthru") == 0) return RPZ_PASSTHRU_ACTION; else if(strcmp(a, "drop") == 0) return RPZ_DROP_ACTION; else if(strcmp(a, "tcp_only") == 0) return RPZ_TCP_ONLY_ACTION; else if(strcmp(a, "cname") == 0) return RPZ_CNAME_OVERRIDE_ACTION; else if(strcmp(a, "disabled") == 0) return RPZ_DISABLED_ACTION; else return RPZ_INVALID_ACTION; } /** string for RPZ trigger enum */ static const char* rpz_trigger_to_string(enum rpz_trigger r) { switch(r) { case RPZ_QNAME_TRIGGER: return "rpz-qname"; case RPZ_CLIENT_IP_TRIGGER: return "rpz-client-ip"; case RPZ_RESPONSE_IP_TRIGGER: return "rpz-response-ip"; case RPZ_NSDNAME_TRIGGER: return "rpz-nsdname"; case RPZ_NSIP_TRIGGER: return "rpz-nsip"; case RPZ_INVALID_TRIGGER: return "rpz-invalid"; default: return "rpz-unknown-trigger"; } } /** * Get the label that is just before the root label. * @param dname: dname to work on * @param maxdnamelen: maximum length of the dname * @return: pointer to TLD label, NULL if not found or invalid dname */ static uint8_t* get_tld_label(uint8_t* dname, size_t maxdnamelen) { uint8_t* prevlab = dname; size_t dnamelen = 0; /* one byte needed for label length */ if(dnamelen+1 > maxdnamelen) return NULL; /* only root label */ if(*dname == 0) return NULL; while(*dname) { dnamelen += ((size_t)*dname)+1; if(dnamelen+1 > maxdnamelen) return NULL; dname = dname+((size_t)*dname)+1; if(*dname != 0) prevlab = dname; } return prevlab; } /** * The RR types that are to be ignored. * DNSSEC RRs at the apex, and SOA and NS are ignored. */ static int rpz_type_ignored(uint16_t rr_type) { switch(rr_type) { case LDNS_RR_TYPE_SOA: case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_DNAME: case LDNS_RR_TYPE_ZONEMD: /* all DNSSEC-related RRs must be ignored */ case LDNS_RR_TYPE_DNSKEY: case LDNS_RR_TYPE_DS: case LDNS_RR_TYPE_RRSIG: case LDNS_RR_TYPE_NSEC: case LDNS_RR_TYPE_NSEC3: case LDNS_RR_TYPE_NSEC3PARAM: return 1; default: break; } return 0; } /** * Classify RPZ action for RR type/rdata * @param rr_type: the RR type * @param rdatawl: RDATA with 2 bytes length * @param rdatalen: the length of rdatawl (including its 2 bytes length) * @return: the RPZ action */ static enum rpz_action rpz_rr_to_action(uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { char* endptr; uint8_t* rdata; int rdatalabs; uint8_t* tldlab = NULL; switch(rr_type) { case LDNS_RR_TYPE_SOA: case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_DNAME: /* all DNSSEC-related RRs must be ignored */ case LDNS_RR_TYPE_DNSKEY: case LDNS_RR_TYPE_DS: case LDNS_RR_TYPE_RRSIG: case LDNS_RR_TYPE_NSEC: case LDNS_RR_TYPE_NSEC3: case LDNS_RR_TYPE_NSEC3PARAM: return RPZ_INVALID_ACTION; case LDNS_RR_TYPE_CNAME: break; default: return RPZ_LOCAL_DATA_ACTION; } /* use CNAME target to determine RPZ action */ log_assert(rr_type == LDNS_RR_TYPE_CNAME); if(rdatalen < 3) return RPZ_INVALID_ACTION; rdata = rdatawl + 2; /* 2 bytes of rdata length */ if(dname_valid(rdata, rdatalen-2) != rdatalen-2) return RPZ_INVALID_ACTION; rdatalabs = dname_count_labels(rdata); if(rdatalabs == 1) return RPZ_NXDOMAIN_ACTION; else if(rdatalabs == 2) { if(dname_subdomain_c(rdata, (uint8_t*)&"\001*\000")) return RPZ_NODATA_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\014rpz-passthru\000")) return RPZ_PASSTHRU_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\010rpz-drop\000")) return RPZ_DROP_ACTION; else if(dname_subdomain_c(rdata, (uint8_t*)&"\014rpz-tcp-only\000")) return RPZ_TCP_ONLY_ACTION; } /* all other TLDs starting with "rpz-" are invalid */ tldlab = get_tld_label(rdata, rdatalen-2); if(tldlab && dname_lab_startswith(tldlab, "rpz-", &endptr)) return RPZ_INVALID_ACTION; /* no special label found */ return RPZ_LOCAL_DATA_ACTION; } static enum localzone_type rpz_action_to_localzone_type(enum rpz_action a) { switch(a) { case RPZ_NXDOMAIN_ACTION: return local_zone_always_nxdomain; case RPZ_NODATA_ACTION: return local_zone_always_nodata; case RPZ_DROP_ACTION: return local_zone_always_deny; case RPZ_PASSTHRU_ACTION: return local_zone_always_transparent; case RPZ_LOCAL_DATA_ACTION: ATTR_FALLTHROUGH /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return local_zone_redirect; case RPZ_TCP_ONLY_ACTION: return local_zone_truncate; case RPZ_INVALID_ACTION: ATTR_FALLTHROUGH /* fallthrough */ default: return local_zone_invalid; } } enum respip_action rpz_action_to_respip_action(enum rpz_action a) { switch(a) { case RPZ_NXDOMAIN_ACTION: return respip_always_nxdomain; case RPZ_NODATA_ACTION: return respip_always_nodata; case RPZ_DROP_ACTION: return respip_always_deny; case RPZ_PASSTHRU_ACTION: return respip_always_transparent; case RPZ_LOCAL_DATA_ACTION: ATTR_FALLTHROUGH /* fallthrough */ case RPZ_CNAME_OVERRIDE_ACTION: return respip_redirect; case RPZ_TCP_ONLY_ACTION: return respip_truncate; case RPZ_INVALID_ACTION: ATTR_FALLTHROUGH /* fallthrough */ default: return respip_invalid; } } static enum rpz_action localzone_type_to_rpz_action(enum localzone_type lzt) { switch(lzt) { case local_zone_always_nxdomain: return RPZ_NXDOMAIN_ACTION; case local_zone_always_nodata: return RPZ_NODATA_ACTION; case local_zone_always_deny: return RPZ_DROP_ACTION; case local_zone_always_transparent: return RPZ_PASSTHRU_ACTION; case local_zone_redirect: return RPZ_LOCAL_DATA_ACTION; case local_zone_truncate: return RPZ_TCP_ONLY_ACTION; case local_zone_invalid: ATTR_FALLTHROUGH /* fallthrough */ default: return RPZ_INVALID_ACTION; } } enum rpz_action respip_action_to_rpz_action(enum respip_action a) { switch(a) { case respip_always_nxdomain: return RPZ_NXDOMAIN_ACTION; case respip_always_nodata: return RPZ_NODATA_ACTION; case respip_always_deny: return RPZ_DROP_ACTION; case respip_always_transparent: return RPZ_PASSTHRU_ACTION; case respip_redirect: return RPZ_LOCAL_DATA_ACTION; case respip_truncate: return RPZ_TCP_ONLY_ACTION; case respip_invalid: ATTR_FALLTHROUGH /* fallthrough */ default: return RPZ_INVALID_ACTION; } } /** * Get RPZ trigger for dname * @param dname: dname containing RPZ trigger * @param dname_len: length of the dname * @return: RPZ trigger enum */ static enum rpz_trigger rpz_dname_to_trigger(uint8_t* dname, size_t dname_len) { uint8_t* tldlab; char* endptr; if(dname_valid(dname, dname_len) != dname_len) return RPZ_INVALID_TRIGGER; tldlab = get_tld_label(dname, dname_len); if(!tldlab || !dname_lab_startswith(tldlab, "rpz-", &endptr)) return RPZ_QNAME_TRIGGER; if(dname_subdomain_c(tldlab, (uint8_t*)&"\015rpz-client-ip\000")) return RPZ_CLIENT_IP_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\006rpz-ip\000")) return RPZ_RESPONSE_IP_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\013rpz-nsdname\000")) return RPZ_NSDNAME_TRIGGER; else if(dname_subdomain_c(tldlab, (uint8_t*)&"\010rpz-nsip\000")) return RPZ_NSIP_TRIGGER; return RPZ_QNAME_TRIGGER; } static inline struct clientip_synthesized_rrset* rpz_clientip_synthesized_set_create(void) { struct clientip_synthesized_rrset* set = calloc(1, sizeof(*set)); if(set == NULL) { return NULL; } set->region = regional_create(); if(set->region == NULL) { free(set); return NULL; } addr_tree_init(&set->entries); lock_rw_init(&set->lock); return set; } static void rpz_clientip_synthesized_rr_delete(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct clientip_synthesized_rr* r = (struct clientip_synthesized_rr*)n->key; lock_rw_destroy(&r->lock); #ifdef THREADS_DISABLED (void)r; #endif } static inline void rpz_clientip_synthesized_set_delete(struct clientip_synthesized_rrset* set) { if(set == NULL) { return; } lock_rw_destroy(&set->lock); traverse_postorder(&set->entries, rpz_clientip_synthesized_rr_delete, NULL); regional_destroy(set->region); free(set); } void rpz_delete(struct rpz* r) { if(!r) return; local_zones_delete(r->local_zones); local_zones_delete(r->nsdname_zones); respip_set_delete(r->respip_set); rpz_clientip_synthesized_set_delete(r->client_set); rpz_clientip_synthesized_set_delete(r->ns_set); regional_destroy(r->region); free(r->taglist); free(r->log_name); free(r); } int rpz_clear(struct rpz* r) { /* must hold write lock on auth_zone */ local_zones_delete(r->local_zones); r->local_zones = NULL; local_zones_delete(r->nsdname_zones); r->nsdname_zones = NULL; respip_set_delete(r->respip_set); r->respip_set = NULL; rpz_clientip_synthesized_set_delete(r->client_set); r->client_set = NULL; rpz_clientip_synthesized_set_delete(r->ns_set); r->ns_set = NULL; if(!(r->local_zones = local_zones_create())){ return 0; } r->nsdname_zones = local_zones_create(); if(r->nsdname_zones == NULL) { return 0; } if(!(r->respip_set = respip_set_create())) { return 0; } if(!(r->client_set = rpz_clientip_synthesized_set_create())) { return 0; } if(!(r->ns_set = rpz_clientip_synthesized_set_create())) { return 0; } return 1; } void rpz_finish_config(struct rpz* r) { lock_rw_wrlock(&r->respip_set->lock); addr_tree_init_parents(&r->respip_set->ip_tree); lock_rw_unlock(&r->respip_set->lock); lock_rw_wrlock(&r->client_set->lock); addr_tree_init_parents(&r->client_set->entries); lock_rw_unlock(&r->client_set->lock); lock_rw_wrlock(&r->ns_set->lock); addr_tree_init_parents(&r->ns_set->entries); lock_rw_unlock(&r->ns_set->lock); } /** new rrset containing CNAME override, does not yet contain a dname */ static struct ub_packed_rrset_key* new_cname_override(struct regional* region, uint8_t* ct, size_t ctlen) { struct ub_packed_rrset_key* rrset; struct packed_rrset_data* pd; uint16_t rdlength = htons(ctlen); rrset = (struct ub_packed_rrset_key*)regional_alloc_zero(region, sizeof(*rrset)); if(!rrset) { log_err("out of memory"); return NULL; } rrset->entry.key = rrset; pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); if(!pd) { log_err("out of memory"); return NULL; } pd->trust = rrset_trust_prim_noglue; pd->security = sec_status_insecure; pd->count = 1; pd->rr_len = regional_alloc_zero(region, sizeof(*pd->rr_len)); pd->rr_ttl = regional_alloc_zero(region, sizeof(*pd->rr_ttl)); pd->rr_data = regional_alloc_zero(region, sizeof(*pd->rr_data)); if(!pd->rr_len || !pd->rr_ttl || !pd->rr_data) { log_err("out of memory"); return NULL; } pd->rr_len[0] = ctlen+2; pd->rr_ttl[0] = 3600; pd->rr_data[0] = regional_alloc_zero(region, 2 /* rdlength */ + ctlen); if(!pd->rr_data[0]) { log_err("out of memory"); return NULL; } memmove(pd->rr_data[0], &rdlength, 2); memmove(pd->rr_data[0]+2, ct, ctlen); rrset->entry.data = pd; rrset->rk.type = htons(LDNS_RR_TYPE_CNAME); rrset->rk.rrset_class = htons(LDNS_RR_CLASS_IN); return rrset; } /** delete the cname override */ static void delete_cname_override(struct rpz* r) { if(r->cname_override) { /* The cname override is what is allocated in the region. */ regional_free_all(r->region); r->cname_override = NULL; } } /** Apply rpz config elements to the rpz structure, false on failure. */ static int rpz_apply_cfg_elements(struct rpz* r, struct config_auth* p) { if(p->rpz_taglist && p->rpz_taglistlen) { r->taglistlen = p->rpz_taglistlen; r->taglist = memdup(p->rpz_taglist, r->taglistlen); if(!r->taglist) { log_err("malloc failure on RPZ taglist alloc"); return 0; } } if(p->rpz_action_override) { r->action_override = rpz_config_to_action(p->rpz_action_override); } else r->action_override = RPZ_NO_OVERRIDE_ACTION; if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { uint8_t nm[LDNS_MAX_DOMAINLEN+1]; size_t nmlen = sizeof(nm); if(!p->rpz_cname) { log_err("rpz: override with cname action found, but no " "rpz-cname-override configured"); return 0; } if(sldns_str2wire_dname_buf(p->rpz_cname, nm, &nmlen) != 0) { log_err("rpz: cannot parse cname override: %s", p->rpz_cname); return 0; } r->cname_override = new_cname_override(r->region, nm, nmlen); if(!r->cname_override) { return 0; } } r->log = p->rpz_log; r->signal_nxdomain_ra = p->rpz_signal_nxdomain_ra; if(p->rpz_log_name) { if(!(r->log_name = strdup(p->rpz_log_name))) { log_err("malloc failure on RPZ log_name strdup"); return 0; } } return 1; } struct rpz* rpz_create(struct config_auth* p) { struct rpz* r = calloc(1, sizeof(*r)); if(!r) goto err; r->region = regional_create_custom(sizeof(struct regional)); if(!r->region) { goto err; } if(!(r->local_zones = local_zones_create())){ goto err; } r->nsdname_zones = local_zones_create(); if(r->local_zones == NULL){ goto err; } if(!(r->respip_set = respip_set_create())) { goto err; } r->client_set = rpz_clientip_synthesized_set_create(); if(r->client_set == NULL) { goto err; } r->ns_set = rpz_clientip_synthesized_set_create(); if(r->ns_set == NULL) { goto err; } if(!rpz_apply_cfg_elements(r, p)) goto err; return r; err: if(r) { if(r->local_zones) local_zones_delete(r->local_zones); if(r->nsdname_zones) local_zones_delete(r->nsdname_zones); if(r->respip_set) respip_set_delete(r->respip_set); if(r->client_set != NULL) rpz_clientip_synthesized_set_delete(r->client_set); if(r->ns_set != NULL) rpz_clientip_synthesized_set_delete(r->ns_set); if(r->taglist) free(r->taglist); if(r->region) regional_destroy(r->region); free(r); } return NULL; } int rpz_config(struct rpz* r, struct config_auth* p) { /* If the zonefile changes, it is read later, after which * rpz_clear and rpz_finish_config is called. */ /* free taglist, if any */ if(r->taglist) { free(r->taglist); r->taglist = NULL; r->taglistlen = 0; } /* free logname, if any */ if(r->log_name) { free(r->log_name); r->log_name = NULL; } delete_cname_override(r); if(!rpz_apply_cfg_elements(r, p)) return 0; return 1; } /** * Remove RPZ zone name from dname * Copy dname to newdname, without the originlen number of trailing bytes */ static size_t strip_dname_origin(uint8_t* dname, size_t dnamelen, size_t originlen, uint8_t* newdname, size_t maxnewdnamelen) { size_t newdnamelen; if(dnamelen < originlen) return 0; newdnamelen = dnamelen - originlen; if(newdnamelen+1 > maxnewdnamelen) return 0; memmove(newdname, dname, newdnamelen); newdname[newdnamelen] = 0; return newdnamelen + 1; /* + 1 for root label */ } static void rpz_insert_local_zones_trigger(struct local_zones* lz, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct local_zone* z; enum localzone_type tp = local_zone_always_transparent; int dnamelabs = dname_count_labels(dname); int newzone = 0; if(a == RPZ_INVALID_ACTION) { char str[LDNS_MAX_DOMAINLEN]; if(rrtype == LDNS_RR_TYPE_SOA || rrtype == LDNS_RR_TYPE_NS || rrtype == LDNS_RR_TYPE_DNAME || rrtype == LDNS_RR_TYPE_DNSKEY || rrtype == LDNS_RR_TYPE_RRSIG || rrtype == LDNS_RR_TYPE_NSEC || rrtype == LDNS_RR_TYPE_NSEC3PARAM || rrtype == LDNS_RR_TYPE_NSEC3 || rrtype == LDNS_RR_TYPE_DS) { free(dname); return; /* no need to log these types as unsupported */ } dname_str(dname, str); verbose(VERB_ALGO, "rpz: qname trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); free(dname); return; } lock_rw_wrlock(&lz->lock); /* exact match */ z = local_zones_find(lz, dname, dnamelen, dnamelabs, LDNS_RR_CLASS_IN); if(z != NULL && a != RPZ_LOCAL_DATA_ACTION) { char* rrstr = sldns_wire2str_rr(rr, rr_len); if(rrstr == NULL) { log_err("malloc error while inserting rpz nsdname trigger"); free(dname); lock_rw_unlock(&lz->lock); return; } if(rrstr[0]) rrstr[strlen(rrstr)-1]=0; /* remove newline */ verbose(VERB_ALGO, "rpz: skipping duplicate record: '%s'", rrstr); free(rrstr); free(dname); lock_rw_unlock(&lz->lock); return; } if(z == NULL) { tp = rpz_action_to_localzone_type(a); z = local_zones_add_zone(lz, dname, dnamelen, dnamelabs, rrclass, tp); if(z == NULL) { log_warn("rpz: create failed"); lock_rw_unlock(&lz->lock); /* dname will be free'd in failed local_zone_create() */ return; } newzone = 1; } if(a == RPZ_LOCAL_DATA_ACTION) { char* rrstr = sldns_wire2str_rr(rr, rr_len); if(rrstr == NULL) { log_err("malloc error while inserting rpz nsdname trigger"); free(dname); lock_rw_unlock(&lz->lock); return; } lock_rw_wrlock(&z->lock); local_zone_enter_rr(z, dname, dnamelen, dnamelabs, rrtype, rrclass, ttl, rdata, rdata_len, rrstr); lock_rw_unlock(&z->lock); free(rrstr); } if(!newzone) { free(dname); } lock_rw_unlock(&lz->lock); } static void rpz_log_dname(char const* msg, uint8_t* dname, size_t dname_len) { char buf[LDNS_MAX_DOMAINLEN]; (void)dname_len; dname_str(dname, buf); verbose(VERB_ALGO, "rpz: %s: <%s>", msg, buf); } static void rpz_insert_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { if(a == RPZ_INVALID_ACTION) { verbose(VERB_ALGO, "rpz: skipping invalid action"); free(dname); return; } rpz_insert_local_zones_trigger(r->local_zones, dname, dnamelen, a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } static int rpz_strip_nsdname_suffix(uint8_t* dname, size_t maxdnamelen, uint8_t** stripdname, size_t* stripdnamelen) { uint8_t* tldstart = get_tld_label(dname, maxdnamelen); uint8_t swap; if(tldstart == NULL) { if(dname == NULL) { *stripdname = NULL; *stripdnamelen = 0; return 0; } *stripdname = memdup(dname, maxdnamelen); if(!*stripdname) { *stripdnamelen = 0; log_err("malloc failure for rpz strip suffix"); return 0; } *stripdnamelen = maxdnamelen; return 1; } /* shorten the domain name briefly, * then we allocate a new name with the correct length */ swap = *tldstart; *tldstart = 0; (void)dname_count_size_labels(dname, stripdnamelen); *stripdname = memdup(dname, *stripdnamelen); *tldstart = swap; if(!*stripdname) { *stripdnamelen = 0; log_err("malloc failure for rpz strip suffix"); return 0; } return 1; } static void rpz_insert_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { uint8_t* dname_stripped = NULL; size_t dnamelen_stripped = 0; rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, &dnamelen_stripped); if(a == RPZ_INVALID_ACTION) { verbose(VERB_ALGO, "rpz: skipping invalid action"); free(dname_stripped); return; } /* dname_stripped is consumed or freed by the insert routine */ rpz_insert_local_zones_trigger(r->nsdname_zones, dname_stripped, dnamelen_stripped, a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } static int rpz_insert_ipaddr_based_trigger(struct respip_set* set, struct sockaddr_storage* addr, socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct resp_addr* node; char* rrstr; enum respip_action respa = rpz_action_to_respip_action(a); lock_rw_wrlock(&set->lock); rrstr = sldns_wire2str_rr(rr, rr_len); if(rrstr == NULL) { log_err("malloc error while inserting rpz ipaddr based trigger"); lock_rw_unlock(&set->lock); return 0; } node = respip_sockaddr_find_or_create(set, addr, addrlen, net, 1, rrstr); if(node == NULL) { lock_rw_unlock(&set->lock); free(rrstr); return 0; } lock_rw_wrlock(&node->lock); lock_rw_unlock(&set->lock); node->action = respa; if(a == RPZ_LOCAL_DATA_ACTION) { respip_enter_rr(set->region, node, rrtype, rrclass, ttl, rdata, rdata_len, rrstr, ""); } lock_rw_unlock(&node->lock); free(rrstr); return 1; } static inline struct clientip_synthesized_rr* rpz_clientip_ensure_entry(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, int net) { int insert_ok; struct clientip_synthesized_rr* node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, addr, addrlen, net); if(node != NULL) { return node; } /* node does not yet exist => allocate one */ node = regional_alloc_zero(set->region, sizeof(*node)); if(node == NULL) { log_err("out of memory"); return NULL; } lock_rw_init(&node->lock); node->action = RPZ_INVALID_ACTION; insert_ok = addr_tree_insert(&set->entries, &node->node, addr, addrlen, net); if (!insert_ok) { log_warn("rpz: unexpected: unable to insert clientip address node"); /* we can not free the just allocated node. * theoretically a memleak */ return NULL; } return node; } static void rpz_report_rrset_error(const char* msg, uint8_t* rr, size_t rr_len) { char* rrstr = sldns_wire2str_rr(rr, rr_len); if(rrstr == NULL) { log_err("malloc error while inserting rpz clientip based record"); return; } log_err("rpz: unexpected: unable to insert %s: %s", msg, rrstr); free(rrstr); } /* from localzone.c; difference is we don't have a dname */ static struct local_rrset* rpz_clientip_new_rrset(struct regional* region, struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t rrclass) { struct packed_rrset_data* pd; struct local_rrset* rrset = (struct local_rrset*) regional_alloc_zero(region, sizeof(*rrset)); if(rrset == NULL) { log_err("out of memory"); return NULL; } rrset->next = raddr->data; raddr->data = rrset; rrset->rrset = (struct ub_packed_rrset_key*) regional_alloc_zero(region, sizeof(*rrset->rrset)); if(rrset->rrset == NULL) { log_err("out of memory"); return NULL; } rrset->rrset->entry.key = rrset->rrset; pd = (struct packed_rrset_data*)regional_alloc_zero(region, sizeof(*pd)); if(pd == NULL) { log_err("out of memory"); return NULL; } pd->trust = rrset_trust_prim_noglue; pd->security = sec_status_insecure; rrset->rrset->entry.data = pd; rrset->rrset->rk.type = htons(rrtype); rrset->rrset->rk.rrset_class = htons(rrclass); rrset->rrset->rk.dname = regional_alloc_zero(region, 1); if(rrset->rrset->rk.dname == NULL) { log_err("out of memory"); return NULL; } rrset->rrset->rk.dname_len = 1; return rrset; } static int rpz_clientip_enter_rr(struct regional* region, struct clientip_synthesized_rr* raddr, uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, size_t rdata_len) { struct local_rrset* rrset; if (rrtype == LDNS_RR_TYPE_CNAME && raddr->data != NULL) { log_err("CNAME response-ip data can not co-exist with other " "client-ip data"); return 0; } rrset = rpz_clientip_new_rrset(region, raddr, rrtype, rrclass); if(raddr->data == NULL) { return 0; } return rrset_insert_rr(region, rrset->rrset->entry.data, rdata, rdata_len, ttl, ""); } static int rpz_clientip_insert_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, int net, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct clientip_synthesized_rr* node; lock_rw_wrlock(&set->lock); node = rpz_clientip_ensure_entry(set, addr, addrlen, net); if(node == NULL) { lock_rw_unlock(&set->lock); rpz_report_rrset_error("client ip address", rr, rr_len); return 0; } lock_rw_wrlock(&node->lock); lock_rw_unlock(&set->lock); node->action = a; if(a == RPZ_LOCAL_DATA_ACTION) { if(!rpz_clientip_enter_rr(set->region, node, rrtype, rrclass, ttl, rdata, rdata_len)) { verbose(VERB_ALGO, "rpz: unable to insert clientip rr"); lock_rw_unlock(&node->lock); return 0; } } lock_rw_unlock(&node->lock); return 1; } static int rpz_insert_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) { return 0; } if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { verbose(VERB_ALGO, "rpz: unable to parse client ip"); return 0; } return rpz_clientip_insert_trigger_rr(r->client_set, &addr, addrlen, net, a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } static int rpz_insert_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) { return 0; } if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { verbose(VERB_ALGO, "rpz: unable to parse ns ip"); return 0; } return rpz_clientip_insert_trigger_rr(r->ns_set, &addr, addrlen, net, a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } /** Insert RR into RPZ's respip_set */ static int rpz_insert_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, uint8_t* rdata, size_t rdata_len, uint8_t* rr, size_t rr_len) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) { return 0; } if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) { verbose(VERB_ALGO, "rpz: unable to parse response ip"); return 0; } if(a == RPZ_INVALID_ACTION || rpz_action_to_respip_action(a) == respip_invalid) { char str[LDNS_MAX_DOMAINLEN]; dname_str(dname, str); verbose(VERB_ALGO, "rpz: respip trigger, %s skipping unsupported action: %s", str, rpz_action_to_string(a)); return 0; } return rpz_insert_ipaddr_based_trigger(r->respip_set, &addr, addrlen, net, a, rrtype, rrclass, ttl, rdata, rdata_len, rr, rr_len); } int rpz_insert_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint32_t rr_ttl, uint8_t* rdatawl, size_t rdatalen, uint8_t* rr, size_t rr_len) { size_t policydnamelen; /* name is free'd in local_zone delete */ enum rpz_trigger t; enum rpz_action a; uint8_t* policydname; if(rpz_type_ignored(rr_type)) { /* this rpz action is not valid, eg. this is the SOA or NS RR */ return 1; } if(!dname_subdomain_c(dname, azname)) { char* dname_str = sldns_wire2str_dname(dname, dnamelen); char* azname_str = sldns_wire2str_dname(azname, aznamelen); if(dname_str && azname_str) { log_err("rpz: name of record (%s) to insert into RPZ is not a " "subdomain of the configured name of the RPZ zone (%s)", dname_str, azname_str); } else { log_err("rpz: name of record to insert into RPZ is not a " "subdomain of the configured name of the RPZ zone"); } free(dname_str); free(azname_str); return 0; } log_assert(dnamelen >= aznamelen); if(!(policydname = calloc(1, (dnamelen-aznamelen)+1))) { log_err("malloc error while inserting RPZ RR"); return 0; } a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, policydname, (dnamelen-aznamelen)+1))) { free(policydname); return 0; } t = rpz_dname_to_trigger(policydname, policydnamelen); if(t == RPZ_INVALID_TRIGGER) { free(policydname); verbose(VERB_ALGO, "rpz: skipping invalid trigger"); return 1; } if(t == RPZ_QNAME_TRIGGER) { /* policydname will be consumed, no free */ rpz_insert_qname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); } else if(t == RPZ_RESPONSE_IP_TRIGGER) { rpz_insert_response_ip_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); } else if(t == RPZ_CLIENT_IP_TRIGGER) { rpz_insert_clientip_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); } else if(t == RPZ_NSIP_TRIGGER) { rpz_insert_nsip_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); } else if(t == RPZ_NSDNAME_TRIGGER) { rpz_insert_nsdname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rr_ttl, rdatawl, rdatalen, rr, rr_len); free(policydname); } else { free(policydname); verbose(VERB_ALGO, "rpz: skipping unsupported trigger: %s", rpz_trigger_to_string(t)); } return 1; } /** * Find RPZ local-zone by qname. * @param zones: local-zone tree * @param qname: qname * @param qname_len: length of qname * @param qclass: qclass * @param only_exact: if 1 only exact (non wildcard) matches are returned * @param wr: get write lock for local-zone if 1, read lock if 0 * @param zones_keep_lock: if set do not release the r->local_zones lock, this * makes the caller of this function responsible for releasing the lock. * @return: NULL or local-zone holding rd or wr lock */ static struct local_zone* rpz_find_zone(struct local_zones* zones, uint8_t* qname, size_t qname_len, uint16_t qclass, int only_exact, int wr, int zones_keep_lock) { uint8_t* ce; size_t ce_len; int ce_labs; uint8_t wc[LDNS_MAX_DOMAINLEN+1]; int exact; struct local_zone* z = NULL; if(wr) { lock_rw_wrlock(&zones->lock); } else { lock_rw_rdlock(&zones->lock); } z = local_zones_find_le(zones, qname, qname_len, dname_count_labels(qname), LDNS_RR_CLASS_IN, &exact); if(!z || (only_exact && !exact)) { if(!zones_keep_lock) { lock_rw_unlock(&zones->lock); } return NULL; } if(wr) { lock_rw_wrlock(&z->lock); } else { lock_rw_rdlock(&z->lock); } if(!zones_keep_lock) { lock_rw_unlock(&zones->lock); } if(exact) return z; /* No exact match found, lookup wildcard. closest encloser must * be the shared parent between the qname and the best local * zone match, append '*' to that and do another lookup. */ ce = dname_get_shared_topdomain(z->name, qname); if(!ce /* should not happen */) { lock_rw_unlock(&z->lock); if(zones_keep_lock) { lock_rw_unlock(&zones->lock); } return NULL; } ce_labs = dname_count_size_labels(ce, &ce_len); if(ce_len+2 > sizeof(wc)) { lock_rw_unlock(&z->lock); if(zones_keep_lock) { lock_rw_unlock(&zones->lock); } return NULL; } wc[0] = 1; /* length of wildcard label */ wc[1] = (uint8_t)'*'; /* wildcard label */ memmove(wc+2, ce, ce_len); lock_rw_unlock(&z->lock); if(!zones_keep_lock) { if(wr) { lock_rw_wrlock(&zones->lock); } else { lock_rw_rdlock(&zones->lock); } } z = local_zones_find_le(zones, wc, ce_len+2, ce_labs+1, qclass, &exact); if(!z || !exact) { lock_rw_unlock(&zones->lock); return NULL; } if(wr) { lock_rw_wrlock(&z->lock); } else { lock_rw_rdlock(&z->lock); } if(!zones_keep_lock) { lock_rw_unlock(&zones->lock); } return z; } /** Find entry for RR type in the list of rrsets for the clientip. */ static struct local_rrset* rpz_find_synthesized_rrset(uint16_t qtype, struct clientip_synthesized_rr* data, int alias_ok) { struct local_rrset* cursor = data->data, *cname = NULL; while( cursor != NULL) { struct packed_rrset_key* packed_rrset = &cursor->rrset->rk; if(htons(qtype) == packed_rrset->type) { return cursor; } if(ntohs(packed_rrset->type) == LDNS_RR_TYPE_CNAME && alias_ok) cname = cursor; cursor = cursor->next; } if(alias_ok) return cname; return NULL; } /** * Remove RR from RPZ's local-data * @param z: local-zone for RPZ, holding write lock * @param policydname: dname of RR to remove * @param policydnamelen: length of policydname * @param rr_type: RR type of RR to remove * @param rdata: rdata of RR to remove * @param rdatalen: length of rdata * @return: 1 if zone must be removed after RR deletion */ static int rpz_data_delete_rr(struct local_zone* z, uint8_t* policydname, size_t policydnamelen, uint16_t rr_type, uint8_t* rdata, size_t rdatalen) { struct local_data* ld; struct packed_rrset_data* d; size_t index; ld = local_zone_find_data(z, policydname, policydnamelen, dname_count_labels(policydname)); if(ld) { struct local_rrset* prev=NULL, *p=ld->rrsets; while(p && ntohs(p->rrset->rk.type) != rr_type) { prev = p; p = p->next; } if(!p) return 0; d = (struct packed_rrset_data*)p->rrset->entry.data; if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { if(d->count == 1) { /* no memory recycling for zone deletions ... */ if(prev) prev->next = p->next; else ld->rrsets = p->next; } if(d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } } } if(ld && ld->rrsets) return 0; return 1; } /** * Remove RR from RPZ's respip set * @param raddr: respip node * @param rr_type: RR type of RR to remove * @param rdata: rdata of RR to remove * @param rdatalen: length of rdata * @return: 1 if zone must be removed after RR deletion */ static int rpz_rrset_delete_rr(struct resp_addr* raddr, uint16_t rr_type, uint8_t* rdata, size_t rdatalen) { size_t index; struct packed_rrset_data* d; if(!raddr->data) return 1; d = raddr->data->entry.data; if(ntohs(raddr->data->rk.type) != rr_type) { return 0; } if(packed_rrset_find_rr(d, rdata, rdatalen, &index)) { if(d->count == 1) { /* regional alloc'd */ raddr->data->entry.data = NULL; raddr->data = NULL; return 1; } if(d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } } return 0; } /** Remove RR from rpz localzones structure */ static void rpz_remove_local_zones_trigger(struct local_zones* zones, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { struct local_zone* z; int delete_zone = 1; z = rpz_find_zone(zones, dname, dnamelen, rr_class, 1 /* only exact */, 1 /* wr lock */, 1 /* keep lock*/); if(!z) { verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); return; } if(a == RPZ_LOCAL_DATA_ACTION) delete_zone = rpz_data_delete_rr(z, dname, dnamelen, rr_type, rdatawl, rdatalen); else if(a != localzone_type_to_rpz_action(z->type)) { lock_rw_unlock(&z->lock); lock_rw_unlock(&zones->lock); return; } lock_rw_unlock(&z->lock); if(delete_zone) { local_zones_del_zone(zones, z); } lock_rw_unlock(&zones->lock); } /** Remove RR from RPZ's local-zone */ static void rpz_remove_qname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { rpz_remove_local_zones_trigger(r->local_zones, dname, dnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } static void rpz_remove_response_ip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct resp_addr* node; struct sockaddr_storage addr; socklen_t addrlen; int net, af; int delete_respip = 1; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; lock_rw_wrlock(&r->respip_set->lock); if(!(node = (struct resp_addr*)addr_tree_find( &r->respip_set->ip_tree, &addr, addrlen, net))) { verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ domain not found"); lock_rw_unlock(&r->respip_set->lock); return; } lock_rw_wrlock(&node->lock); if(a == RPZ_LOCAL_DATA_ACTION) { /* remove RR, signal whether RR can be removed */ delete_respip = rpz_rrset_delete_rr(node, rr_type, rdatawl, rdatalen); } lock_rw_unlock(&node->lock); if(delete_respip) respip_sockaddr_delete(r->respip_set, node); lock_rw_unlock(&r->respip_set->lock); } /** find and remove type from list of local_rrset entries*/ static void del_local_rrset_from_list(struct local_rrset** list_head, uint16_t dtype) { struct local_rrset* prev=NULL, *p=*list_head; while(p && ntohs(p->rrset->rk.type) != dtype) { prev = p; p = p->next; } if(!p) return; /* rrset type not found */ /* unlink it */ if(prev) prev->next = p->next; else *list_head = p->next; /* no memory recycling for zone deletions ... */ } /** Delete client-ip trigger RR from its RRset and perhaps also the rrset * from the linked list. Returns if the local data is empty and the node can * be deleted too, or not. */ static int rpz_remove_clientip_rr(struct clientip_synthesized_rr* node, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct local_rrset* rrset; struct packed_rrset_data* d; size_t index; rrset = rpz_find_synthesized_rrset(rr_type, node, 0); if(rrset == NULL) return 0; /* type not found, ignore */ d = (struct packed_rrset_data*)rrset->rrset->entry.data; if(!packed_rrset_find_rr(d, rdatawl, rdatalen, &index)) return 0; /* RR not found, ignore */ if(d->count == 1) { /* regional alloc'd */ /* delete the type entry from the list */ del_local_rrset_from_list(&node->data, rr_type); /* if the list is empty, the node can be removed too */ if(node->data == NULL) return 1; } else if (d->count > 1) { if(!local_rrset_remove_rr(d, index)) return 0; } return 0; } /** remove trigger RR from clientip_syntheized set tree. */ static void rpz_clientip_remove_trigger_rr(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, int net, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct clientip_synthesized_rr* node; int delete_node = 1; lock_rw_wrlock(&set->lock); node = (struct clientip_synthesized_rr*)addr_tree_find(&set->entries, addr, addrlen, net); if(node == NULL) { /* netblock not found */ verbose(VERB_ALGO, "rpz: cannot remove RR from IXFR, " "RPZ address, netblock not found"); lock_rw_unlock(&set->lock); return; } lock_rw_wrlock(&node->lock); if(a == RPZ_LOCAL_DATA_ACTION) { /* remove RR, signal whether entry can be removed */ delete_node = rpz_remove_clientip_rr(node, rr_type, rdatawl, rdatalen); } else if(a != node->action) { /* ignore the RR with different action specification */ delete_node = 0; } if(delete_node) { rbtree_delete(&set->entries, node->node.node.key); } lock_rw_unlock(&set->lock); lock_rw_unlock(&node->lock); if(delete_node) { lock_rw_destroy(&node->lock); } } /** Remove clientip trigger RR from RPZ. */ static void rpz_remove_clientip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) return; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; rpz_clientip_remove_trigger_rr(r->client_set, &addr, addrlen, net, a, rr_type, rdatawl, rdatalen); } /** Remove nsip trigger RR from RPZ. */ static void rpz_remove_nsip_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint8_t* rdatawl, size_t rdatalen) { struct sockaddr_storage addr; socklen_t addrlen; int net, af; if(a == RPZ_INVALID_ACTION) return; if(!netblockdnametoaddr(dname, dnamelen, &addr, &addrlen, &net, &af)) return; rpz_clientip_remove_trigger_rr(r->ns_set, &addr, addrlen, net, a, rr_type, rdatawl, rdatalen); } /** Remove nsdname trigger RR from RPZ. */ static void rpz_remove_nsdname_trigger(struct rpz* r, uint8_t* dname, size_t dnamelen, enum rpz_action a, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { uint8_t* dname_stripped = NULL; size_t dnamelen_stripped = 0; if(a == RPZ_INVALID_ACTION) return; if(!rpz_strip_nsdname_suffix(dname, dnamelen, &dname_stripped, &dnamelen_stripped)) return; rpz_remove_local_zones_trigger(r->nsdname_zones, dname_stripped, dnamelen_stripped, a, rr_type, rr_class, rdatawl, rdatalen); free(dname_stripped); } void rpz_remove_rr(struct rpz* r, uint8_t* azname, size_t aznamelen, uint8_t* dname, size_t dnamelen, uint16_t rr_type, uint16_t rr_class, uint8_t* rdatawl, size_t rdatalen) { size_t policydnamelen; enum rpz_trigger t; enum rpz_action a; uint8_t* policydname; if(rpz_type_ignored(rr_type)) { /* this rpz action is not valid, eg. this is the SOA or NS RR */ return; } if(!dname_subdomain_c(dname, azname)) { /* not subdomain of the RPZ zone. */ return; } if(!(policydname = calloc(1, LDNS_MAX_DOMAINLEN + 1))) return; a = rpz_rr_to_action(rr_type, rdatawl, rdatalen); if(a == RPZ_INVALID_ACTION) { free(policydname); return; } if(!(policydnamelen = strip_dname_origin(dname, dnamelen, aznamelen, policydname, LDNS_MAX_DOMAINLEN + 1))) { free(policydname); return; } t = rpz_dname_to_trigger(policydname, policydnamelen); if(t == RPZ_INVALID_TRIGGER) { /* skipping invalid trigger */ free(policydname); return; } if(t == RPZ_QNAME_TRIGGER) { rpz_remove_qname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } else if(t == RPZ_RESPONSE_IP_TRIGGER) { rpz_remove_response_ip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_CLIENT_IP_TRIGGER) { rpz_remove_clientip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_NSIP_TRIGGER) { rpz_remove_nsip_trigger(r, policydname, policydnamelen, a, rr_type, rdatawl, rdatalen); } else if(t == RPZ_NSDNAME_TRIGGER) { rpz_remove_nsdname_trigger(r, policydname, policydnamelen, a, rr_type, rr_class, rdatawl, rdatalen); } /* else it was an unsupported trigger, also skipped. */ free(policydname); } /** print log information for an applied RPZ policy. Based on local-zone's * lz_inform_print(). * The repinfo contains the reply address. If it is NULL, the module * state is used to report the first IP address (if any). * The dname is used, for the applied rpz, if NULL, addrnode is used. */ static void log_rpz_apply(char* trigger, uint8_t* dname, struct addr_tree_node* addrnode, enum rpz_action a, struct query_info* qinfo, struct comm_reply* repinfo, struct module_qstate* ms, char* log_name) { char ip[128], txt[512], portstr[32]; char dnamestr[LDNS_MAX_DOMAINLEN]; uint16_t port = 0; if(dname) { dname_str(dname, dnamestr); } else if(addrnode) { char addrbuf[128]; addr_to_str(&addrnode->addr, addrnode->addrlen, addrbuf, sizeof(addrbuf)); snprintf(dnamestr, sizeof(dnamestr), "%s/%d", addrbuf, addrnode->net); } else { dnamestr[0]=0; } if(repinfo) { addr_to_str(&repinfo->client_addr, repinfo->client_addrlen, ip, sizeof(ip)); port = ntohs(((struct sockaddr_in*)&repinfo->client_addr)->sin_port); } else if(ms && ms->mesh_info && ms->mesh_info->reply_list) { addr_to_str(&ms->mesh_info->reply_list->query_reply.client_addr, ms->mesh_info->reply_list->query_reply.client_addrlen, ip, sizeof(ip)); port = ntohs(((struct sockaddr_in*)&ms->mesh_info->reply_list->query_reply.client_addr)->sin_port); } else { ip[0]=0; port = 0; } snprintf(portstr, sizeof(portstr), "@%u", (unsigned)port); snprintf(txt, sizeof(txt), "rpz: applied %s%s%s%s%s%s %s %s%s", (log_name?"[":""), (log_name?log_name:""), (log_name?"] ":""), (strcmp(trigger,"qname")==0?"":trigger), (strcmp(trigger,"qname")==0?"":" "), dnamestr, rpz_action_to_string(a), (ip[0]?ip:""), (ip[0]?portstr:"")); log_nametypeclass(0, txt, qinfo->qname, qinfo->qtype, qinfo->qclass); } static struct clientip_synthesized_rr* rpz_ipbased_trigger_lookup(struct clientip_synthesized_rrset* set, struct sockaddr_storage* addr, socklen_t addrlen, char* triggername) { struct clientip_synthesized_rr* raddr = NULL; enum rpz_action action = RPZ_INVALID_ACTION; lock_rw_rdlock(&set->lock); raddr = (struct clientip_synthesized_rr*)addr_tree_lookup(&set->entries, addr, addrlen); if(raddr != NULL) { lock_rw_rdlock(&raddr->lock); action = raddr->action; if(verbosity >= VERB_ALGO) { char ip[256], net[256]; addr_to_str(addr, addrlen, ip, sizeof(ip)); addr_to_str(&raddr->node.addr, raddr->node.addrlen, net, sizeof(net)); verbose(VERB_ALGO, "rpz: trigger %s %s/%d on %s action=%s", triggername, net, raddr->node.net, ip, rpz_action_to_string(action)); } } lock_rw_unlock(&set->lock); return raddr; } static inline struct clientip_synthesized_rr* rpz_resolve_client_action_and_zone(struct auth_zones* az, struct query_info* qinfo, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, /* output parameters */ struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out) { struct clientip_synthesized_rr* node = NULL; struct auth_zone* a = NULL; struct rpz* r = NULL; struct local_zone* z = NULL; lock_rw_rdlock(&az->rpz_lock); for(a = az->rpz_first; a; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; if(r->disabled) { lock_rw_unlock(&a->lock); continue; } if(r->taglist && !taglist_intersect(r->taglist, r->taglistlen, taglist, taglen)) { lock_rw_unlock(&a->lock); continue; } z = rpz_find_zone(r->local_zones, qinfo->qname, qinfo->qname_len, qinfo->qclass, 0, 0, 0); node = rpz_ipbased_trigger_lookup(r->client_set, &repinfo->client_addr, repinfo->client_addrlen, "clientip"); if((z || node) && r->action_override == RPZ_DISABLED_ACTION) { if(r->log) log_rpz_apply((node?"clientip":"qname"), (z?z->name:NULL), (node?&node->node:NULL), r->action_override, qinfo, repinfo, NULL, r->log_name); stats->rpz_action[r->action_override]++; if(z != NULL) { lock_rw_unlock(&z->lock); z = NULL; } if(node != NULL) { lock_rw_unlock(&node->lock); node = NULL; } } if(z || node) { break; } /* not found in this auth_zone */ lock_rw_unlock(&a->lock); } lock_rw_unlock(&az->rpz_lock); *r_out = r; *a_out = a; *z_out = z; return node; } static inline int rpz_is_udp_query(struct comm_reply* repinfo) { return repinfo != NULL ? (repinfo->c != NULL ? repinfo->c->type == comm_udp : 0) : 0; } /** encode answer consisting of 1 rrset */ static int rpz_local_encode(struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct ub_packed_rrset_key* rrset, int ansec, int rcode, struct ub_packed_rrset_key* soa_rrset) { struct reply_info rep; uint16_t udpsize; struct ub_packed_rrset_key* rrsetlist[3]; memset(&rep, 0, sizeof(rep)); rep.flags = (uint16_t)((BIT_QR | BIT_AA | BIT_RA) | rcode); rep.qdcount = 1; rep.rrset_count = ansec; rep.rrsets = rrsetlist; if(ansec > 0) { rep.an_numrrsets = 1; rep.rrsets[0] = rrset; rep.ttl = ((struct packed_rrset_data*)rrset->entry.data)->rr_ttl[0]; } if(soa_rrset != NULL) { rep.ar_numrrsets = 1; rep.rrsets[rep.rrset_count] = soa_rrset; rep.rrset_count ++; if(rep.ttl < ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]) { rep.ttl = ((struct packed_rrset_data*)soa_rrset->entry.data)->rr_ttl[0]; } } 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(!inplace_cb_reply_local_call(env, qinfo, NULL, &rep, rcode, edns, repinfo, temp, env->now_tv) || !reply_info_answer_encode(qinfo, &rep, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), buf, 0, 0, temp, udpsize, edns, (int)(edns->bits&EDNS_DO), 0)) { error_encode(buf, (LDNS_RCODE_SERVFAIL|BIT_AA), qinfo, *(uint16_t*)sldns_buffer_begin(buf), sldns_buffer_read_u16_at(buf, 2), edns); } return 1; } /** allocate SOA record ubrrsetkey in region */ static struct ub_packed_rrset_key* make_soa_ubrrset(struct auth_zone* auth_zone, struct auth_rrset* soa, struct regional* temp) { struct ub_packed_rrset_key csoa; if(!soa) return NULL; memset(&csoa, 0, sizeof(csoa)); csoa.entry.key = &csoa; csoa.rk.rrset_class = htons(LDNS_RR_CLASS_IN); csoa.rk.type = htons(LDNS_RR_TYPE_SOA); csoa.rk.flags |= PACKED_RRSET_FIXEDTTL | PACKED_RRSET_RPZ; csoa.rk.dname = auth_zone->name; csoa.rk.dname_len = auth_zone->namelen; csoa.entry.hash = rrset_key_hash(&csoa.rk); csoa.entry.data = soa->data; return respip_copy_rrset(&csoa, temp); } static void rpz_apply_clientip_localdata_action(struct clientip_synthesized_rr* raddr, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct auth_zone* auth_zone) { struct local_rrset* rrset; enum rpz_action action = RPZ_INVALID_ACTION; struct ub_packed_rrset_key* rp = NULL; struct ub_packed_rrset_key* rsoa = NULL; int rcode = LDNS_RCODE_NOERROR|BIT_AA; int rrset_count = 1; /* prepare synthesized answer for client */ action = raddr->action; if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL ) { verbose(VERB_ALGO, "rpz: bug: local-data action but no local data"); return; } /* check query type / rr type */ rrset = rpz_find_synthesized_rrset(qinfo->qtype, raddr, 1); if(rrset == NULL) { verbose(VERB_ALGO, "rpz: unable to find local-data for query"); rrset_count = 0; goto nodata; } rp = respip_copy_rrset(rrset->rrset, temp); if(!rp) { verbose(VERB_ALGO, "rpz: local data action: out of memory"); return; } rp->rk.flags |= PACKED_RRSET_FIXEDTTL | PACKED_RRSET_RPZ; rp->rk.dname = qinfo->qname; rp->rk.dname_len = qinfo->qname_len; rp->entry.hash = rrset_key_hash(&rp->rk); nodata: if(auth_zone) { struct auth_rrset* soa = NULL; soa = auth_zone_get_soa_rrset(auth_zone); if(soa) { rsoa = make_soa_ubrrset(auth_zone, soa, temp); if(!rsoa) { verbose(VERB_ALGO, "rpz: local data action soa: out of memory"); return; } } } rpz_local_encode(env, qinfo, edns, repinfo, buf, temp, rp, rrset_count, rcode, rsoa); } /** Apply the cname override action, during worker request callback. * false on failure. */ static int rpz_apply_cname_override_action(struct rpz* r, struct query_info* qinfo, struct regional* temp) { if(!r) return 0; qinfo->local_alias = regional_alloc_zero(temp, sizeof(struct local_rrset)); if(qinfo->local_alias == NULL) return 0; /* out of memory */ qinfo->local_alias->rrset = respip_copy_rrset(r->cname_override, temp); if(qinfo->local_alias->rrset == NULL) { qinfo->local_alias = NULL; 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; } /** add additional section SOA record to the reply. * Since this gets fed into the normal iterator answer creation, it * gets minimal-responses applied to it, that can remove the additional SOA * again. */ static int rpz_add_soa(struct reply_info* rep, struct module_qstate* ms, struct auth_zone* az) { struct auth_rrset* soa = NULL; struct ub_packed_rrset_key* rsoa = NULL; struct ub_packed_rrset_key** prevrrsets; if(!az) return 1; soa = auth_zone_get_soa_rrset(az); if(!soa) return 1; if(!rep) return 0; rsoa = make_soa_ubrrset(az, soa, ms->region); if(!rsoa) return 0; prevrrsets = rep->rrsets; rep->rrsets = regional_alloc_zero(ms->region, sizeof(*rep->rrsets)*(rep->rrset_count+1)); if(!rep->rrsets) return 0; if(prevrrsets && rep->rrset_count > 0) memcpy(rep->rrsets, prevrrsets, rep->rrset_count*sizeof(*rep->rrsets)); rep->rrset_count++; rep->ar_numrrsets++; rep->rrsets[rep->rrset_count-1] = rsoa; return 1; } static inline struct dns_msg* rpz_dns_msg_new(struct regional* region) { struct dns_msg* msg = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(msg == NULL) { return NULL; } memset(msg, 0, sizeof(struct dns_msg)); return msg; } static inline struct dns_msg* rpz_synthesize_nodata(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, struct query_info* qinfo, struct auth_zone* az) { struct dns_msg* msg = rpz_dns_msg_new(ms->region); if(msg == NULL) { return msg; } msg->qinfo = *qinfo; msg->rep = construct_reply_info_base(ms->region, LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA, 1, /* qd */ 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ 0, /* norecttl */ 0, /* an */ 0, /* ns */ 0, /* ar */ 0, /* total */ sec_status_insecure, LDNS_EDE_NONE); if(msg->rep) msg->rep->authoritative = 1; if(!rpz_add_soa(msg->rep, ms, az)) return NULL; return msg; } static inline struct dns_msg* rpz_synthesize_nxdomain(struct rpz* r, struct module_qstate* ms, struct query_info* qinfo, struct auth_zone* az) { struct dns_msg* msg = rpz_dns_msg_new(ms->region); uint16_t flags; if(msg == NULL) { return msg; } msg->qinfo = *qinfo; flags = LDNS_RCODE_NXDOMAIN | BIT_QR | BIT_AA | BIT_RA; if(r->signal_nxdomain_ra) flags &= ~BIT_RA; msg->rep = construct_reply_info_base(ms->region, flags, 1, /* qd */ 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ 0, /* norecttl */ 0, /* an */ 0, /* ns */ 0, /* ar */ 0, /* total */ sec_status_insecure, LDNS_EDE_NONE); if(msg->rep) msg->rep->authoritative = 1; if(!rpz_add_soa(msg->rep, ms, az)) return NULL; return msg; } static inline struct dns_msg* rpz_synthesize_localdata_from_rrset(struct rpz* ATTR_UNUSED(r), struct module_qstate* ms, struct query_info* qi, struct local_rrset* rrset, struct auth_zone* az) { struct dns_msg* msg = NULL; struct reply_info* new_reply_info; struct ub_packed_rrset_key* rp; msg = rpz_dns_msg_new(ms->region); if(msg == NULL) { return NULL; } msg->qinfo = *qi; new_reply_info = construct_reply_info_base(ms->region, LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA, 1, /* qd */ 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ 0, /* norecttl */ 1, /* an */ 0, /* ns */ 0, /* ar */ 1, /* total */ sec_status_insecure, LDNS_EDE_NONE); if(new_reply_info == NULL) { log_err("out of memory"); return NULL; } new_reply_info->authoritative = 1; rp = respip_copy_rrset(rrset->rrset, ms->region); if(rp == NULL) { log_err("out of memory"); return NULL; } rp->rk.dname = qi->qname; rp->rk.dname_len = qi->qname_len; /* this rrset is from the rpz data, or synthesized. * It is not actually from the network, so we flag it with this * flags as a fake RRset. If later the cache is used to look up * rrsets, then the fake ones are not returned (if you look without * the flag). For like CNAME lookups from the iterator or A, AAAA * lookups for nameserver targets, it would use the without flag * actual data. So that the actual network data and fake data * are kept track of separately. */ rp->rk.flags |= PACKED_RRSET_RPZ; new_reply_info->rrsets[0] = rp; msg->rep = new_reply_info; if(!rpz_add_soa(msg->rep, ms, az)) return NULL; return msg; } static inline struct dns_msg* rpz_synthesize_nsip_localdata(struct rpz* r, struct module_qstate* ms, struct query_info* qi, struct clientip_synthesized_rr* data, struct auth_zone* az) { struct local_rrset* rrset; rrset = rpz_find_synthesized_rrset(qi->qtype, data, 1); if(rrset == NULL) { verbose(VERB_ALGO, "rpz: nsip: no matching local data found"); return NULL; } return rpz_synthesize_localdata_from_rrset(r, ms, qi, rrset, az); } /* copy'n'paste from localzone.c */ static struct local_rrset* local_data_find_type(struct local_data* data, uint16_t type, int alias_ok) { struct local_rrset* p, *cname = NULL; 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)) cname = p; } if(alias_ok) return cname; return NULL; } /* based on localzone.c:local_data_answer() */ static inline struct dns_msg* rpz_synthesize_nsdname_localdata(struct rpz* r, struct module_qstate* ms, struct query_info* qi, struct local_zone* z, struct matched_delegation_point const* match, struct auth_zone* az) { struct local_data key; struct local_data* ld; struct local_rrset* rrset; if(match->dname == NULL) { return NULL; } key.node.key = &key; key.name = match->dname; key.namelen = match->dname_len; key.namelabs = dname_count_labels(match->dname); rpz_log_dname("nsdname local data", key.name, key.namelen); ld = (struct local_data*)rbtree_search(&z->data, &key.node); if(ld == NULL && dname_is_wild(z->name)) { key.name = z->name; key.namelen = z->namelen; key.namelabs = z->namelabs; ld = (struct local_data*)rbtree_search(&z->data, &key.node); /* rpz_synthesize_localdata_from_rrset is going to make * the rrset source name equal to the query name. So no need * to make the wildcard rrset here. */ } if(ld == NULL) { verbose(VERB_ALGO, "rpz: nsdname: qname not found"); return NULL; } rrset = local_data_find_type(ld, qi->qtype, 1); if(rrset == NULL) { verbose(VERB_ALGO, "rpz: nsdname: no matching local data found"); return NULL; } return rpz_synthesize_localdata_from_rrset(r, ms, qi, rrset, az); } /* like local_data_answer for qname triggers after a cname */ static struct dns_msg* rpz_synthesize_qname_localdata_msg(struct rpz* r, struct module_qstate* ms, struct query_info* qinfo, struct local_zone* z, struct auth_zone* az) { struct local_data key; struct local_data* ld; struct local_rrset* rrset; key.node.key = &key; key.name = qinfo->qname; key.namelen = qinfo->qname_len; key.namelabs = dname_count_labels(qinfo->qname); ld = (struct local_data*)rbtree_search(&z->data, &key.node); if(ld == NULL && dname_is_wild(z->name)) { key.name = z->name; key.namelen = z->namelen; key.namelabs = z->namelabs; ld = (struct local_data*)rbtree_search(&z->data, &key.node); /* rpz_synthesize_localdata_from_rrset is going to make * the rrset source name equal to the query name. So no need * to make the wildcard rrset here. */ } if(ld == NULL) { verbose(VERB_ALGO, "rpz: qname: name not found"); return NULL; } rrset = local_data_find_type(ld, qinfo->qtype, 1); if(rrset == NULL) { verbose(VERB_ALGO, "rpz: qname: type not found"); return NULL; } return rpz_synthesize_localdata_from_rrset(r, ms, qinfo, rrset, az); } /** Synthesize a CNAME message for RPZ action override */ static struct dns_msg* rpz_synthesize_cname_override_msg(struct rpz* r, struct module_qstate* ms, struct query_info* qinfo) { struct dns_msg* msg = NULL; struct reply_info* new_reply_info; struct ub_packed_rrset_key* rp; msg = rpz_dns_msg_new(ms->region); if(msg == NULL) { return NULL; } msg->qinfo = *qinfo; new_reply_info = construct_reply_info_base(ms->region, LDNS_RCODE_NOERROR | BIT_QR | BIT_AA | BIT_RA, 1, /* qd */ 0, /* ttl */ 0, /* prettl */ 0, /* expttl */ 0, /* norecttl */ 1, /* an */ 0, /* ns */ 0, /* ar */ 1, /* total */ sec_status_insecure, LDNS_EDE_NONE); if(new_reply_info == NULL) { log_err("out of memory"); return NULL; } new_reply_info->authoritative = 1; rp = respip_copy_rrset(r->cname_override, ms->region); if(rp == NULL) { log_err("out of memory"); return NULL; } rp->rk.dname = qinfo->qname; rp->rk.dname_len = qinfo->qname_len; /* this rrset is from the rpz data, or synthesized. * It is not actually from the network, so we flag it with this * flags as a fake RRset. If later the cache is used to look up * rrsets, then the fake ones are not returned (if you look without * the flag). For like CNAME lookups from the iterator or A, AAAA * lookups for nameserver targets, it would use the without flag * actual data. So that the actual network data and fake data * are kept track of separately. */ rp->rk.flags |= PACKED_RRSET_RPZ; new_reply_info->rrsets[0] = rp; msg->rep = new_reply_info; return msg; } static int rpz_synthesize_qname_localdata(struct module_env* env, struct rpz* r, struct local_zone* z, enum localzone_type lzt, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, struct ub_server_stats* stats) { struct local_data* ld = NULL; int ret = 0; if(r->action_override == RPZ_CNAME_OVERRIDE_ACTION) { if(!rpz_apply_cname_override_action(r, qinfo, temp)) return 0; if(r->log) { log_rpz_apply("qname", z->name, NULL, RPZ_CNAME_OVERRIDE_ACTION, qinfo, repinfo, NULL, r->log_name); } stats->rpz_action[RPZ_CNAME_OVERRIDE_ACTION]++; return 0; } if(lzt == local_zone_redirect && local_data_answer(z, env, qinfo, edns, repinfo, buf, temp, dname_count_labels(qinfo->qname), &ld, lzt, -1, NULL, 0, NULL, 0)) { if(r->log) { log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), qinfo, repinfo, NULL, r->log_name); } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; return !qinfo->local_alias; } ret = local_zones_zone_answer(z, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, lzt); if(r->signal_nxdomain_ra && LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) == LDNS_RCODE_NXDOMAIN) LDNS_RA_CLR(sldns_buffer_begin(buf)); if(r->log) { log_rpz_apply("qname", z->name, NULL, localzone_type_to_rpz_action(lzt), qinfo, repinfo, NULL, r->log_name); } stats->rpz_action[localzone_type_to_rpz_action(lzt)]++; return ret; } static struct clientip_synthesized_rr* rpz_delegation_point_ipbased_trigger_lookup(struct rpz* rpz, struct iter_qstate* is) { struct delegpt_addr* cursor; struct clientip_synthesized_rr* action = NULL; if(is->dp == NULL) { return NULL; } for(cursor = is->dp->target_list; cursor != NULL; cursor = cursor->next_target) { if(cursor->bogus) { continue; } action = rpz_ipbased_trigger_lookup(rpz->ns_set, &cursor->addr, cursor->addrlen, "nsip"); if(action != NULL) { return action; } } return NULL; } static struct dns_msg* rpz_apply_nsip_trigger(struct module_qstate* ms, struct query_info* qchase, struct rpz* r, struct clientip_synthesized_rr* raddr, struct auth_zone* az) { enum rpz_action action = raddr->action; struct dns_msg* ret = NULL; if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", rpz_action_to_string(r->action_override), rpz_action_to_string(action)); action = r->action_override; } if(action == RPZ_LOCAL_DATA_ACTION && raddr->data == NULL) { verbose(VERB_ALGO, "rpz: bug: nsip local data action but no local data"); ret = rpz_synthesize_nodata(r, ms, qchase, az); ms->rpz_applied = 1; goto done; } switch(action) { case RPZ_NXDOMAIN_ACTION: ret = rpz_synthesize_nxdomain(r, ms, qchase, az); ms->rpz_applied = 1; break; case RPZ_NODATA_ACTION: ret = rpz_synthesize_nodata(r, ms, qchase, az); ms->rpz_applied = 1; break; case RPZ_TCP_ONLY_ACTION: /* basically a passthru here but the tcp-only will be * honored before the query gets sent. */ ms->tcp_required = 1; ret = NULL; break; case RPZ_DROP_ACTION: ret = rpz_synthesize_nodata(r, ms, qchase, az); ms->rpz_applied = 1; ms->is_drop = 1; break; case RPZ_LOCAL_DATA_ACTION: ret = rpz_synthesize_nsip_localdata(r, ms, qchase, raddr, az); if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, qchase, az); } ms->rpz_applied = 1; break; case RPZ_PASSTHRU_ACTION: ret = NULL; ms->rpz_passthru = 1; break; case RPZ_CNAME_OVERRIDE_ACTION: ret = rpz_synthesize_cname_override_msg(r, ms, qchase); ms->rpz_applied = 1; break; default: verbose(VERB_ALGO, "rpz: nsip: bug: unhandled or invalid action: '%s'", rpz_action_to_string(action)); ret = NULL; } done: if(r->log) log_rpz_apply("nsip", NULL, &raddr->node, action, &ms->qinfo, NULL, ms, r->log_name); if(ms->env->worker) ms->env->worker->stats.rpz_action[action]++; lock_rw_unlock(&raddr->lock); return ret; } static struct dns_msg* rpz_apply_nsdname_trigger(struct module_qstate* ms, struct query_info* qchase, struct rpz* r, struct local_zone* z, struct matched_delegation_point const* match, struct auth_zone* az) { struct dns_msg* ret = NULL; enum rpz_action action = localzone_type_to_rpz_action(z->type); if(r->action_override != RPZ_NO_OVERRIDE_ACTION) { verbose(VERB_ALGO, "rpz: using override action=%s (replaces=%s)", rpz_action_to_string(r->action_override), rpz_action_to_string(action)); action = r->action_override; } switch(action) { case RPZ_NXDOMAIN_ACTION: ret = rpz_synthesize_nxdomain(r, ms, qchase, az); ms->rpz_applied = 1; break; case RPZ_NODATA_ACTION: ret = rpz_synthesize_nodata(r, ms, qchase, az); ms->rpz_applied = 1; break; case RPZ_TCP_ONLY_ACTION: /* basically a passthru here but the tcp-only will be * honored before the query gets sent. */ ms->tcp_required = 1; ret = NULL; break; case RPZ_DROP_ACTION: ret = rpz_synthesize_nodata(r, ms, qchase, az); ms->rpz_applied = 1; ms->is_drop = 1; break; case RPZ_LOCAL_DATA_ACTION: ret = rpz_synthesize_nsdname_localdata(r, ms, qchase, z, match, az); if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, qchase, az); } ms->rpz_applied = 1; break; case RPZ_PASSTHRU_ACTION: ret = NULL; ms->rpz_passthru = 1; break; case RPZ_CNAME_OVERRIDE_ACTION: ret = rpz_synthesize_cname_override_msg(r, ms, qchase); ms->rpz_applied = 1; break; default: verbose(VERB_ALGO, "rpz: nsdname: bug: unhandled or invalid action: '%s'", rpz_action_to_string(action)); ret = NULL; } if(r->log) log_rpz_apply("nsdname", match->dname, NULL, action, &ms->qinfo, NULL, ms, r->log_name); if(ms->env->worker) ms->env->worker->stats.rpz_action[action]++; lock_rw_unlock(&z->lock); return ret; } static struct local_zone* rpz_delegation_point_zone_lookup(struct delegpt* dp, struct local_zones* zones, uint16_t qclass, /* output parameter */ struct matched_delegation_point* match) { struct delegpt_ns* nameserver; struct local_zone* z = NULL; /* the rpz specs match the nameserver names (NS records), not the * name of the delegation point itself, to the nsdname triggers */ for(nameserver = dp->nslist; nameserver != NULL; nameserver = nameserver->next) { z = rpz_find_zone(zones, nameserver->name, nameserver->namelen, qclass, 0, 0, 0); if(z != NULL) { match->dname = nameserver->name; match->dname_len = nameserver->namelen; if(verbosity >= VERB_ALGO) { char nm[LDNS_MAX_DOMAINLEN]; char zn[LDNS_MAX_DOMAINLEN]; dname_str(match->dname, nm); dname_str(z->name, zn); if(strcmp(nm, zn) != 0) verbose(VERB_ALGO, "rpz: trigger nsdname %s on %s action=%s", zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); else verbose(VERB_ALGO, "rpz: trigger nsdname %s action=%s", nm, rpz_action_to_string(localzone_type_to_rpz_action(z->type))); } break; } } return z; } struct dns_msg* rpz_callback_from_iterator_module(struct module_qstate* ms, struct iter_qstate* is) { struct auth_zones* az; struct auth_zone* a; struct dns_msg* ret = NULL; struct clientip_synthesized_rr* raddr = NULL; struct rpz* r = NULL; struct local_zone* z = NULL; struct matched_delegation_point match = {0}; if(ms->rpz_passthru) { verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); return NULL; } if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } az = ms->env->auth_zones; lock_rw_rdlock(&az->rpz_lock); verbose(VERB_ALGO, "rpz: iterator module callback: have_rpz=%d", az->rpz_first != NULL); /* precedence of RPZ works, loosely, like this: * CNAMEs in order of the CNAME chain. rpzs in the order they are * configured. In an RPZ: first client-IP addr, then QNAME, then * response IP, then NSDNAME, then NSIP. Longest match first. Smallest * one from a set. */ /* we use the precedence rules for the topics and triggers that * are pertinent at this stage of the resolve processing */ for(a = az->rpz_first; a != NULL; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; if(r->disabled) { lock_rw_unlock(&a->lock); continue; } if(r->taglist && (!ms->client_info || !taglist_intersect(r->taglist, r->taglistlen, ms->client_info->taglist, ms->client_info->taglen))) { lock_rw_unlock(&a->lock); continue; } /* the nsdname has precedence over the nsip triggers */ z = rpz_delegation_point_zone_lookup(is->dp, r->nsdname_zones, is->qchase.qclass, &match); if(z != NULL) { break; } raddr = rpz_delegation_point_ipbased_trigger_lookup(r, is); if(raddr != NULL) { break; } lock_rw_unlock(&a->lock); } lock_rw_unlock(&az->rpz_lock); if(raddr == NULL && z == NULL) return NULL; if(raddr != NULL) { if(z) { lock_rw_unlock(&z->lock); } ret = rpz_apply_nsip_trigger(ms, &is->qchase, r, raddr, a); } else { ret = rpz_apply_nsdname_trigger(ms, &is->qchase, r, z, &match, a); } lock_rw_unlock(&a->lock); return ret; } struct dns_msg* rpz_callback_from_iterator_cname(struct module_qstate* ms, struct iter_qstate* is) { struct auth_zones* az; struct auth_zone* a = NULL; struct rpz* r = NULL; struct local_zone* z = NULL; enum localzone_type lzt; struct dns_msg* ret = NULL; if(ms->rpz_passthru) { verbose(VERB_ALGO, "query is rpz_passthru, no further processing"); return NULL; } if(ms->env == NULL || ms->env->auth_zones == NULL) { return 0; } az = ms->env->auth_zones; lock_rw_rdlock(&az->rpz_lock); for(a = az->rpz_first; a; a = a->rpz_az_next) { lock_rw_rdlock(&a->lock); r = a->rpz; if(r->disabled) { lock_rw_unlock(&a->lock); continue; } if(r->taglist && (!ms->client_info || !taglist_intersect(r->taglist, r->taglistlen, ms->client_info->taglist, ms->client_info->taglen))) { lock_rw_unlock(&a->lock); continue; } z = rpz_find_zone(r->local_zones, is->qchase.qname, is->qchase.qname_len, is->qchase.qclass, 0, 0, 0); if(z && r->action_override == RPZ_DISABLED_ACTION) { if(r->log) log_rpz_apply("qname", z->name, NULL, r->action_override, &ms->qinfo, NULL, ms, r->log_name); if(ms->env->worker) ms->env->worker->stats.rpz_action[r->action_override]++; lock_rw_unlock(&z->lock); z = NULL; } if(z) { break; } /* not found in this auth_zone */ lock_rw_unlock(&a->lock); } lock_rw_unlock(&az->rpz_lock); if(z == NULL) return NULL; if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { lzt = z->type; } else { lzt = rpz_action_to_localzone_type(r->action_override); } if(verbosity >= VERB_ALGO) { char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN]; dname_str(is->qchase.qname, nm); dname_str(z->name, zn); if(strcmp(zn, nm) != 0) verbose(VERB_ALGO, "rpz: qname trigger %s on %s, with action=%s", zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); else verbose(VERB_ALGO, "rpz: qname trigger %s, with action=%s", nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); } switch(localzone_type_to_rpz_action(lzt)) { case RPZ_NXDOMAIN_ACTION: ret = rpz_synthesize_nxdomain(r, ms, &is->qchase, a); ms->rpz_applied = 1; break; case RPZ_NODATA_ACTION: ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); ms->rpz_applied = 1; break; case RPZ_TCP_ONLY_ACTION: /* basically a passthru here but the tcp-only will be * honored before the query gets sent. */ ms->tcp_required = 1; ret = NULL; break; case RPZ_DROP_ACTION: ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); ms->rpz_applied = 1; ms->is_drop = 1; break; case RPZ_LOCAL_DATA_ACTION: ret = rpz_synthesize_qname_localdata_msg(r, ms, &is->qchase, z, a); if(ret == NULL) { ret = rpz_synthesize_nodata(r, ms, &is->qchase, a); } ms->rpz_applied = 1; break; case RPZ_PASSTHRU_ACTION: ret = NULL; ms->rpz_passthru = 1; break; default: verbose(VERB_ALGO, "rpz: qname trigger: bug: unhandled or invalid action: '%s'", rpz_action_to_string(localzone_type_to_rpz_action(lzt))); ret = NULL; } if(r->log) log_rpz_apply("qname", (z?z->name:NULL), NULL, localzone_type_to_rpz_action(lzt), &is->qchase, NULL, ms, r->log_name); lock_rw_unlock(&z->lock); lock_rw_unlock(&a->lock); return ret; } static int rpz_apply_maybe_clientip_trigger(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, sldns_buffer* buf, struct regional* temp, /* output parameters */ struct local_zone** z_out, struct auth_zone** a_out, struct rpz** r_out, int* passthru) { int ret = 0; enum rpz_action client_action; struct clientip_synthesized_rr* node = rpz_resolve_client_action_and_zone( az, qinfo, repinfo, taglist, taglen, stats, z_out, a_out, r_out); client_action = ((node == NULL) ? RPZ_INVALID_ACTION : node->action); if(node != NULL && *r_out && (*r_out)->action_override != RPZ_NO_OVERRIDE_ACTION) { client_action = (*r_out)->action_override; } if(client_action == RPZ_PASSTHRU_ACTION) { if(*r_out && (*r_out)->log) log_rpz_apply( (node?"clientip":"qname"), ((*z_out)?(*z_out)->name:NULL), (node?&node->node:NULL), client_action, qinfo, repinfo, NULL, (*r_out)->log_name); *passthru = 1; ret = 0; goto done; } if(*z_out == NULL || (client_action != RPZ_INVALID_ACTION && client_action != RPZ_PASSTHRU_ACTION)) { if(client_action == RPZ_PASSTHRU_ACTION || client_action == RPZ_INVALID_ACTION || (client_action == RPZ_TCP_ONLY_ACTION && !rpz_is_udp_query(repinfo))) { ret = 0; goto done; } stats->rpz_action[client_action]++; if(client_action == RPZ_LOCAL_DATA_ACTION) { rpz_apply_clientip_localdata_action(node, env, qinfo, edns, repinfo, buf, temp, *a_out); ret = 1; } else if(client_action == RPZ_CNAME_OVERRIDE_ACTION) { if(!rpz_apply_cname_override_action(*r_out, qinfo, temp)) { ret = 0; goto done; } ret = 0; } else { local_zones_zone_answer(*z_out /*likely NULL, no zone*/, env, qinfo, edns, repinfo, buf, temp, 0 /* no local data used */, rpz_action_to_localzone_type(client_action)); if(*r_out && (*r_out)->signal_nxdomain_ra && LDNS_RCODE_WIRE(sldns_buffer_begin(buf)) == LDNS_RCODE_NXDOMAIN) LDNS_RA_CLR(sldns_buffer_begin(buf)); ret = 1; } if(*r_out && (*r_out)->log) log_rpz_apply( (node?"clientip":"qname"), ((*z_out)?(*z_out)->name:NULL), (node?&node->node:NULL), client_action, qinfo, repinfo, NULL, (*r_out)->log_name); goto done; } ret = -1; done: if(node != NULL) { lock_rw_unlock(&node->lock); } return ret; } int rpz_callback_from_worker_request(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, struct ub_server_stats* stats, int* passthru) { struct rpz* r = NULL; struct auth_zone* a = NULL; struct local_zone* z = NULL; int ret; enum localzone_type lzt; int clientip_trigger = rpz_apply_maybe_clientip_trigger(az, env, qinfo, edns, repinfo, taglist, taglen, stats, buf, temp, &z, &a, &r, passthru); if(clientip_trigger >= 0) { if(a) { lock_rw_unlock(&a->lock); } if(z) { lock_rw_unlock(&z->lock); } return clientip_trigger; } if(z == NULL) { if(a) { lock_rw_unlock(&a->lock); } return 0; } log_assert(r); if(r->action_override == RPZ_NO_OVERRIDE_ACTION) { lzt = z->type; } else { lzt = rpz_action_to_localzone_type(r->action_override); } if(r->action_override == RPZ_PASSTHRU_ACTION || lzt == local_zone_always_transparent /* RPZ_PASSTHRU_ACTION */) { *passthru = 1; } if(verbosity >= VERB_ALGO) { char nm[LDNS_MAX_DOMAINLEN], zn[LDNS_MAX_DOMAINLEN]; dname_str(qinfo->qname, nm); dname_str(z->name, zn); if(strcmp(zn, nm) != 0) verbose(VERB_ALGO, "rpz: qname trigger %s on %s with action=%s", zn, nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); else verbose(VERB_ALGO, "rpz: qname trigger %s with action=%s", nm, rpz_action_to_string(localzone_type_to_rpz_action(lzt))); } ret = rpz_synthesize_qname_localdata(env, r, z, lzt, qinfo, edns, buf, temp, repinfo, stats); lock_rw_unlock(&z->lock); lock_rw_unlock(&a->lock); return ret; } void rpz_enable(struct rpz* r) { if(!r) return; r->disabled = 0; } void rpz_disable(struct rpz* r) { if(!r) return; r->disabled = 1; } /** Get memory usage for clientip_synthesized_rrset. Ignores memory usage * of locks. */ static size_t rpz_clientip_synthesized_set_get_mem(struct clientip_synthesized_rrset* set) { size_t m = sizeof(*set); lock_rw_rdlock(&set->lock); m += regional_get_mem(set->region); lock_rw_unlock(&set->lock); return m; } size_t rpz_get_mem(struct rpz* r) { size_t m = sizeof(*r); if(r->taglist) m += r->taglistlen; if(r->log_name) m += strlen(r->log_name) + 1; m += regional_get_mem(r->region); m += local_zones_get_mem(r->local_zones); m += local_zones_get_mem(r->nsdname_zones); m += respip_set_get_mem(r->respip_set); m += rpz_clientip_synthesized_set_get_mem(r->client_set); m += rpz_clientip_synthesized_set_get_mem(r->ns_set); return m; } unbound-1.25.1/services/localzone.h0000644000175000017500000005733315203270263016703 0ustar wouterwouter/* * services/localzone.h - local zones authority service. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to enable local zone authority service. */ #ifndef SERVICES_LOCALZONE_H #define SERVICES_LOCALZONE_H #include "util/rbtree.h" #include "util/locks.h" #include "util/storage/dnstree.h" #include "util/module.h" #include "services/view.h" #include "sldns/sbuffer.h" struct packed_rrset_data; struct ub_packed_rrset_key; struct regional; struct config_file; struct edns_data; struct query_info; struct sldns_buffer; struct comm_reply; struct config_strlist; extern const char** local_zones_default_special; extern const char** local_zones_default_reverse; /** * Local zone type * This type determines processing for queries that did not match * local-data directly. */ enum localzone_type { /** unset type, used for unset tag_action elements */ local_zone_unset = 0, /** drop query */ local_zone_deny, /** answer with error */ local_zone_refuse, /** answer nxdomain or nodata */ local_zone_static, /** resolve normally */ local_zone_transparent, /** do not block types at localdata names */ local_zone_typetransparent, /** answer with data at zone apex */ local_zone_redirect, /** remove default AS112 blocking contents for zone * nodefault is used in config not during service. */ local_zone_nodefault, /** log client address, but no block (transparent) */ local_zone_inform, /** log client address, and block (drop) */ local_zone_inform_deny, /** log client address, and direct */ local_zone_inform_redirect, /** resolve normally, even when there is local data */ local_zone_always_transparent, /** resolve normally, even when there is local data but return NODATA for A queries */ local_zone_block_a, /** answer with error, even when there is local data */ local_zone_always_refuse, /** answer with nxdomain, even when there is local data */ local_zone_always_nxdomain, /** answer with noerror/nodata, even when there is local data */ local_zone_always_nodata, /** drop query, even when there is local data */ local_zone_always_deny, /** answer with 0.0.0.0 or ::0 or noerror/nodata, even when there is * local data */ local_zone_always_null, /** answer not from the view, but global or no-answer */ local_zone_noview, /** truncate the response; client should retry via tcp */ local_zone_truncate, /** Invalid type, cannot be used to generate answer */ local_zone_invalid }; /** * Authoritative local zones storage, shared. */ struct local_zones { /** lock on the localzone tree */ lock_rw_type lock; /** rbtree of struct local_zone */ rbtree_type ztree; }; /** * Local zone. A locally served authoritative zone. */ struct local_zone { /** rbtree node, key is name and class */ rbnode_type node; /** parent zone, if any. */ struct local_zone* parent; /** zone name, in uncompressed wireformat */ uint8_t* name; /** length of zone name */ size_t namelen; /** number of labels in zone name */ int namelabs; /** the class of this 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_type lock; /** how to process zone */ enum localzone_type type; /** tag bitlist */ uint8_t* taglist; /** length of the taglist (in bytes) */ size_t taglen; /** netblock addr_tree with struct local_zone_override information * or NULL if there are no override elements */ struct rbtree_type* override_tree; /** in this region the zone's data is allocated. * the struct local_zone itself is malloced. */ struct regional* region; /** local data for this zone * rbtree of struct local_data */ rbtree_type data; /** if data contains zone apex SOA data, this is a ptr to it. */ struct ub_packed_rrset_key* soa; /** if data contains zone apex SOA data, this is a ptr to an * artificial negative SOA rrset (TTL is the minimum of the TTL and the * SOA.MINIMUM). */ struct ub_packed_rrset_key* soa_negative; }; /** * Local data. One domain name, and the RRs to go with it. */ struct local_data { /** rbtree node, key is name only */ rbnode_type node; /** domain name */ uint8_t* name; /** length of name */ size_t namelen; /** number of labels in name */ int namelabs; /** the data rrsets, with different types, linked list. * If this list is NULL, the node is an empty non-terminal. */ struct local_rrset* rrsets; }; /** * A local data RRset */ struct local_rrset { /** next in list */ struct local_rrset* next; /** RRset data item */ struct ub_packed_rrset_key* rrset; }; /** * Local zone override information */ struct local_zone_override { /** node in addrtree */ struct addr_tree_node node; /** override for local zone type */ enum localzone_type type; }; /** * Create local zones storage * @return new struct or NULL on error. */ struct local_zones* local_zones_create(void); /** * Delete local zones storage * @param zones: to delete. */ 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. */ int local_zones_apply_cfg(struct local_zones* zones, struct config_file* cfg); /** * Compare two local_zone entries in rbtree. Sort hierarchical but not * canonical * @param z1: zone 1 * @param z2: zone 2 * @return: -1, 0, +1 comparison value. */ int local_zone_cmp(const void* z1, const void* z2); /** * Compare two local_data entries in rbtree. Sort canonical. * @param d1: data 1 * @param d2: data 2 * @return: -1, 0, +1 comparison value. */ int local_data_cmp(const void* d1, const void* d2); /** * Delete one zone * @param z: to delete. */ void local_zone_delete(struct local_zone* z); /** * Lookup zone that contains the given name, class and taglist. * 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. * @param dtype: type to lookup, if type DS a zone higher is used for zonecuts. * @param taglist: taglist to lookup. * @param taglen: length of taglist. * @param ignoretags: lookup zone by name and class, regardless the * local-zone's tags. * @param foradd: if the lookup is for addition or removal of the type. * Used for type DS. The lookup for answers turns this off. * @return closest local_zone or NULL if no covering zone is found. */ struct local_zone* local_zones_tags_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, uint16_t dtype, uint8_t* taglist, size_t taglen, int ignoretags, int foradd); /** * 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. * @param labs: labelcount of name. * @param dclass: class to lookup. * @param dtype: type of the record, if type DS then a zone higher up is found * pass 0 to just plain find a zone for a name. * @param foradd: if the lookup is for addition or removal of the type. * Used for type DS. The lookup for answers turns this off. * @return closest local_zone or NULL if no covering zone is found. */ struct local_zone* local_zones_lookup(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, uint16_t dtype, int foradd); /** * 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 env: the module environment. * @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. * @param repinfo: source address for checks. may be NULL. * @param taglist: taglist for checks. May be NULL. * @param taglen: length of the taglist. * @param tagactions: local zone actions for tags. May be NULL. * @param tagactionssize: length of the tagactions. * @param tag_datas: array per tag of strlist with rdata strings. or NULL. * @param tag_datas_size: size of tag_datas array. * @param tagname: array of tag name strings (for debug output). * @param num_tags: number of items in tagname array. * @param view: answer using this view. May be NULL. * @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 module_env* env, struct query_info* qinfo, struct edns_data* edns, struct sldns_buffer* buf, struct regional* temp, struct comm_reply* repinfo, uint8_t* taglist, size_t taglen, uint8_t* tagactions, size_t tagactionssize, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags, struct view* view); /** * Answer using the local zone only (not local data used). * @param z: zone for query. * @param env: module environment. * @param qinfo: query. * @param edns: edns from query. * @param repinfo: source address for checks. may be NULL. * @param buf: buffer for answer. * @param temp: temp region for encoding. * @param ld: local data, if NULL, no such name exists in localdata. * @param lz_type: type of the local zone. * @return 1 if a reply is to be sent, 0 if not. */ int local_zones_zone_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, struct local_data* ld, enum localzone_type lz_type); /** * 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); /** * Print localzone type to a string. Pointer to a constant string. * * @param t: local zone type. * @return constant string that describes type. */ const char* local_zone_type2str(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); /** * Find zone that with exactly or smaller 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. * @param exact: 1 on return is this is an exact match. * @return the exact or smaller local_zone or NULL. */ struct local_zone* local_zones_find_le(struct local_zones* zones, uint8_t* name, size_t len, int labs, uint16_t dclass, int* exact); /** * 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. * @return false on failure. */ int local_zones_add_RR(struct local_zones* zones, const char* rr); /** * 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); /** * Form wireformat from text format domain name. * @param str: the domain name in text "www.example.com" * @param res: resulting wireformat is stored here with malloc. * @param len: length of resulting wireformat. * @param labs: number of labels in resulting wireformat. * @return false on error, syntax or memory. Also logged. */ int parse_dname(const char* str, uint8_t** res, size_t* len, int* labs); /** * Find local data tag string match for the given type (in qinfo) in the list. * If found, 'r' will be filled with corresponding rrset information. * @param qinfo: contains name, type, and class for the data * @param list: stores local tag data to be searched * @param r: rrset key to be filled for matched data * @param temp: region to allocate rrset in 'r' * @return 1 if a match is found and rrset is built; otherwise 0 including * errors. */ int local_data_find_tag_datas(const struct query_info* qinfo, struct config_strlist* list, struct ub_packed_rrset_key* r, struct regional* temp); /** * See if two sets of tag lists (in the form of bitmap) have the same tag that * has an action. If so, '*tag' will be set to the found tag index, and the * corresponding action will be returned in the form of local zone type. * Otherwise the passed type (lzt) will be returned as the default action. * Pointers except tagactions must not be NULL. * @param taglist: 1st list of tags * @param taglen: size of taglist in bytes * @param taglist2: 2nd list of tags * @param taglen2: size of taglist2 in bytes * @param tagactions: local data actions for tags. May be NULL. * @param tagactionssize: length of the tagactions. * @param lzt: default action (local zone type) if no tag action is found. * @param tag: see above. * @param tagname: array of tag name strings (for debug output). * @param num_tags: number of items in tagname array. * @return found tag action or the default action. */ enum localzone_type local_data_find_tag_action(const uint8_t* taglist, size_t taglen, const uint8_t* taglist2, size_t taglen2, const uint8_t* tagactions, size_t tagactionssize, enum localzone_type lzt, int* tag, char* const* tagname, int num_tags); /** * Enter defaults to local zone. * @param zones: to add defaults to * @param cfg: containing list of zones to exclude from default set. * @return 1 on success; 0 otherwise. */ int local_zone_enter_defaults(struct local_zones* zones, struct config_file* cfg); /** * Parses resource record string into wire format, also returning its field values. * @param str: input resource record * @param nm: domain name field * @param type: record type field * @param dclass: record class field * @param ttl: ttl field * @param rr: buffer for the parsed rr in wire format * @param len: buffer length * @param rdata: rdata field * @param rdata_len: rdata field length * @return 1 on success; 0 otherwise. */ int rrstr_get_rr_content(const char* str, uint8_t** nm, uint16_t* type, uint16_t* dclass, time_t* ttl, uint8_t* rr, size_t len, uint8_t** rdata, size_t* rdata_len); /** * Insert specified rdata into the specified resource record. * @param region: allocator * @param pd: data portion of the destination resource record * @param rdata: source rdata * @param rdata_len: source rdata length * @param ttl: time to live * @param rrstr: resource record in text form (for logging) * @return 1 on success; 0 otherwise. */ int rrset_insert_rr(struct regional* region, struct packed_rrset_data* pd, uint8_t* rdata, size_t rdata_len, time_t ttl, const char* rrstr); /** * Remove RR from rrset that is created using localzone's rrset_insert_rr. * @param pd: the RRset containing the RR to remove * @param index: index of RR to remove * @return: 1 on success; 0 otherwise. */ int local_rrset_remove_rr(struct packed_rrset_data* pd, size_t index); /** * Valid response ip actions for the IP-response-driven-action feature; * defined here instead of in the respip module to enable sharing of enum * values with the localzone_type enum. * Note that these values except 'none' are the same as localzone types of * the 'same semantics'. It's intentional as we use these values via * access-control-tags, which can be shared for both response ip actions and * local zones. */ enum respip_action { /** no respip action */ respip_none = local_zone_unset, /** don't answer */ respip_deny = local_zone_deny, /** redirect as per provided data */ respip_redirect = local_zone_redirect, /** log query source and answer query */ respip_inform = local_zone_inform, /** log query source and don't answer query */ respip_inform_deny = local_zone_inform_deny, /** log query source and redirect */ respip_inform_redirect = local_zone_inform_redirect, /** resolve normally, even when there is response-ip data */ respip_always_transparent = local_zone_always_transparent, /** answer with 'refused' response */ respip_always_refuse = local_zone_always_refuse, /** answer with 'no such domain' response */ respip_always_nxdomain = local_zone_always_nxdomain, /** answer with nodata response */ respip_always_nodata = local_zone_always_nodata, /** answer with nodata response */ respip_always_deny = local_zone_always_deny, /** RPZ: truncate answer in order to force switch to tcp */ respip_truncate = local_zone_truncate, /* The rest of the values are only possible as * access-control-tag-action */ /** serves response data (if any), else, drops queries. */ respip_refuse = local_zone_refuse, /** serves response data, else, nodata answer. */ respip_static = local_zone_static, /** gives response data (if any), else nodata answer. */ respip_transparent = local_zone_transparent, /** gives response data (if any), else nodata answer. */ respip_typetransparent = local_zone_typetransparent, /** type invalid */ respip_invalid = local_zone_invalid, }; /** * Get local data from local zone and encode answer. * @param z: local zone to use * @param env: module env * @param qinfo: qinfo * @param edns: edns data, for message encoding * @param repinfo: reply info, for message encoding * @param buf: commpoint buffer * @param temp: scratchpad region * @param labs: number of labels in qname * @param ldp: where to store local data * @param lz_type: type of local zone * @param tag: matching tag index * @param tag_datas: alc specific tag data list * @param tag_datas_size: size of tag_datas * @param tagname: list of names of tags, for logging purpose * @param num_tags: number of tags * @return 1 on success */ int local_data_answer(struct local_zone* z, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, sldns_buffer* buf, struct regional* temp, int labs, struct local_data** ldp, enum localzone_type lz_type, int tag, struct config_strlist** tag_datas, size_t tag_datas_size, char** tagname, int num_tags); /** * Add RR to local zone. * @param z: local zone to add RR to * @param nm: dname of RR * @param nmlen: length of nm * @param nmlabs: number of labels of nm * @param rrtype: RR type * @param rrclass: RR class * @param ttl: TTL of RR to add * @param rdata: RDATA of RR to add * @param rdata_len: length of rdata * @param rrstr: RR in string format, for logging * @return: 1 on success */ int local_zone_enter_rr(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs, uint16_t rrtype, uint16_t rrclass, time_t ttl, uint8_t* rdata, size_t rdata_len, const char* rrstr); /** * Find a data node by exact name for a local zone * @param z: local_zone containing data tree * @param nm: name of local-data element to find * @param nmlen: length of nm * @param nmlabs: labs of nm * @return local_data on exact match, NULL otherwise. */ struct local_data* local_zone_find_data(struct local_zone* z, uint8_t* nm, size_t nmlen, int nmlabs); /** Get memory usage for local_zones tree. The routine locks and unlocks * the tree for reading. */ size_t local_zones_get_mem(struct local_zones* zones); /** * Swap internal tree with preallocated entries. Caller should manage * the locks. * @param zones: the local zones structure. * @param data: the data structure used to take elements from. This contains * the old elements on return. */ void local_zones_swap_tree(struct local_zones* zones, struct local_zones* data); /** Enter a new zone; returns with WRlock * Made public for unit testing * @param zones: the local zones tree * @param name: name of the zone * @param type: type of the zone * @param dclass: class of the zone * @return local_zone (or duplicate), NULL on parse and malloc failures */ struct local_zone* lz_enter_zone(struct local_zones* zones, const char* name, const char* type, uint16_t dclass); /** Setup parent pointers, so that a lookup can be done for closest match * Made public for unit testing * @param zones: the local zones tree */ void lz_init_parents(struct local_zones* zones); #endif /* SERVICES_LOCALZONE_H */ unbound-1.25.1/services/mesh.c0000644000175000017500000024670515203270263015647 0ustar wouterwouter/* * services/mesh.c - deal with mesh of query states and handle events for that. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist in dealing with a mesh of * query states. This mesh is supposed to be thread-specific. * It consists of query states (per qname, qtype, qclass) and connections * between query states and the super and subquery states, and replies to * send back to clients. */ #include "config.h" #include "services/mesh.h" #include "services/outbound_list.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "services/cache/infra.h" #include "util/log.h" #include "util/net_help.h" #include "util/module.h" #include "util/regional.h" #include "util/data/msgencode.h" #include "util/timehist.h" #include "util/fptr_wlist.h" #include "util/alloc.h" #include "util/config_file.h" #include "util/edns.h" #include "sldns/sbuffer.h" #include "sldns/wire2str.h" #include "services/localzone.h" #include "util/data/dname.h" #include "respip/respip.h" #include "services/listen_dnsport.h" #include "util/timeval_func.h" #ifdef CLIENT_SUBNET #include "edns-subnet/subnetmod.h" #include "edns-subnet/edns-subnet.h" #endif #ifdef HAVE_SYS_TYPES_H # include #endif #ifdef HAVE_NETDB_H #include #endif /** Compare two views by name */ static int view_name_compare(const char* v_a, const char* v_b) { if(v_a == NULL && v_b == NULL) return 0; /* The NULL name is smaller than if the name is set. */ if(v_a == NULL) return -1; if(v_b == NULL) return 1; return strcmp(v_a, v_b); } /** * Compare two response-ip client info entries for the purpose of mesh state * compare. It returns 0 if ci_a and ci_b are considered equal; otherwise * 1 or -1 (they mean 'ci_a is larger/smaller than ci_b', respectively, but * in practice it should be only used to mean they are different). * We cannot share the mesh state for two queries if different response-ip * actions can apply in the end, even if those queries are otherwise identical. * For this purpose we compare tag lists and tag action lists; they should be * identical to share the same state. * For tag data, we don't look into the data content, as it can be * expensive; unless tag data are not defined for both or they point to the * exact same data in memory (i.e., they come from the same ACL entry), we * consider these data different. * Likewise, if the client info is associated with views, we don't look into * the views. They are considered different unless they are exactly the same * even if the views only differ in the names. */ static int client_info_compare(const struct respip_client_info* ci_a, const struct respip_client_info* ci_b) { int cmp; if(!ci_a && !ci_b) return 0; if(ci_a && !ci_b) return -1; if(!ci_a && ci_b) return 1; if(ci_a->taglen != ci_b->taglen) return (ci_a->taglen < ci_b->taglen) ? -1 : 1; if(ci_a->taglist && !ci_b->taglist) return -1; if(!ci_a->taglist && ci_b->taglist) return 1; if(ci_a->taglist && ci_b->taglist) { cmp = memcmp(ci_a->taglist, ci_b->taglist, ci_a->taglen); if(cmp != 0) return cmp; } if(ci_a->tag_actions_size != ci_b->tag_actions_size) return (ci_a->tag_actions_size < ci_b->tag_actions_size) ? -1 : 1; if(ci_a->tag_actions && !ci_b->tag_actions) return -1; if(!ci_a->tag_actions && ci_b->tag_actions) return 1; if(ci_a->tag_actions && ci_b->tag_actions) { cmp = memcmp(ci_a->tag_actions, ci_b->tag_actions, ci_a->tag_actions_size); if(cmp != 0) return cmp; } if(ci_a->tag_datas != ci_b->tag_datas) return ci_a->tag_datas < ci_b->tag_datas ? -1 : 1; if(ci_a->view || ci_a->view_name || ci_b->view || ci_b->view_name) { /* Compare the views by name. */ cmp = view_name_compare( (ci_a->view?ci_a->view->name:ci_a->view_name), (ci_b->view?ci_b->view->name:ci_b->view_name)); if(cmp != 0) return cmp; } return 0; } int mesh_state_compare(const void* ap, const void* bp) { struct mesh_state* a = (struct mesh_state*)ap; struct mesh_state* b = (struct mesh_state*)bp; int cmp; if(a->unique < b->unique) return -1; if(a->unique > b->unique) return 1; if(a->s.is_priming && !b->s.is_priming) return -1; if(!a->s.is_priming && b->s.is_priming) return 1; if(a->s.is_valrec && !b->s.is_valrec) return -1; if(!a->s.is_valrec && b->s.is_valrec) return 1; if((a->s.query_flags&BIT_RD) && !(b->s.query_flags&BIT_RD)) return -1; if(!(a->s.query_flags&BIT_RD) && (b->s.query_flags&BIT_RD)) return 1; if((a->s.query_flags&BIT_CD) && !(b->s.query_flags&BIT_CD)) return -1; if(!(a->s.query_flags&BIT_CD) && (b->s.query_flags&BIT_CD)) return 1; cmp = query_info_compare(&a->s.qinfo, &b->s.qinfo); if(cmp != 0) return cmp; return client_info_compare(a->s.client_info, b->s.client_info); } int mesh_state_ref_compare(const void* ap, const void* bp) { struct mesh_state_ref* a = (struct mesh_state_ref*)ap; struct mesh_state_ref* b = (struct mesh_state_ref*)bp; return mesh_state_compare(a->s, b->s); } struct mesh_area* mesh_create(struct module_stack* stack, struct module_env* env) { struct mesh_area* mesh = calloc(1, sizeof(struct mesh_area)); if(!mesh) { log_err("mesh area alloc: out of memory"); return NULL; } mesh->histogram = timehist_setup(); mesh->qbuf_bak = sldns_buffer_new(env->cfg->msg_buffer_size); if(!mesh->histogram || !mesh->qbuf_bak) { free(mesh); log_err("mesh area alloc: out of memory"); return NULL; } mesh->mods = *stack; mesh->env = env; rbtree_init(&mesh->run, &mesh_state_compare); rbtree_init(&mesh->all, &mesh_state_compare); mesh->num_reply_addrs = 0; mesh->num_reply_states = 0; mesh->num_detached_states = 0; mesh->num_forever_states = 0; mesh->stats_jostled = 0; mesh->stats_dropped = 0; mesh->ans_expired = 0; mesh->ans_cachedb = 0; mesh->num_queries_discard_timeout = 0; mesh->num_queries_replyaddr_limit = 0; mesh->num_queries_wait_limit = 0; mesh->num_dns_error_reports = 0; mesh->max_reply_states = env->cfg->num_queries_per_thread; mesh->max_forever_states = (mesh->max_reply_states+1)/2; #ifndef S_SPLINT_S mesh->jostle_max.tv_sec = (time_t)(env->cfg->jostle_time / 1000); mesh->jostle_max.tv_usec = (time_t)((env->cfg->jostle_time % 1000) *1000); #endif return mesh; } /** help mesh delete delete mesh states */ static void mesh_delete_helper(rbnode_type* n) { struct mesh_state* mstate = (struct mesh_state*)n->key; /* perform a full delete, not only 'cleanup' routine, * because other callbacks expect a clean state in the mesh. * For 're-entrant' calls */ mesh_state_delete(&mstate->s); /* but because these delete the items from the tree, postorder * traversal and rbtree rebalancing do not work together */ } void mesh_delete(struct mesh_area* mesh) { if(!mesh) return; /* free all query states */ while(mesh->all.count) mesh_delete_helper(mesh->all.root); timehist_delete(mesh->histogram); sldns_buffer_free(mesh->qbuf_bak); free(mesh); } void mesh_delete_all(struct mesh_area* mesh) { /* free all query states */ while(mesh->all.count) mesh_delete_helper(mesh->all.root); mesh->stats_dropped += mesh->num_reply_addrs; /* clear mesh area references */ rbtree_init(&mesh->run, &mesh_state_compare); rbtree_init(&mesh->all, &mesh_state_compare); mesh->num_reply_addrs = 0; mesh->num_reply_states = 0; mesh->num_detached_states = 0; mesh->num_forever_states = 0; mesh->forever_first = NULL; mesh->forever_last = NULL; mesh->jostle_first = NULL; mesh->jostle_last = NULL; } int mesh_make_new_space(struct mesh_area* mesh, sldns_buffer* qbuf) { struct mesh_state* m = mesh->jostle_first; /* free space is available */ if(mesh->num_reply_states < mesh->max_reply_states) return 1; /* try to kick out a jostle-list item */ if(m && m->list_select == mesh_jostle_list) { /* how old is it? */ struct timeval age; if(m->has_first_reply_time) timeval_subtract(&age, mesh->env->now_tv, &m->first_reply_time); if(!m->has_first_reply_time || timeval_smaller(&mesh->jostle_max, &age)) { /* its a goner */ log_nametypeclass(VERB_ALGO, "query jostled out to " "make space for a new one", m->s.qinfo.qname, m->s.qinfo.qtype, m->s.qinfo.qclass); /* backup the query */ if(qbuf) sldns_buffer_copy(mesh->qbuf_bak, qbuf); /* notify supers */ if(m->super_set.count > 0) { verbose(VERB_ALGO, "notify supers of failure"); m->s.return_msg = NULL; m->s.return_rcode = LDNS_RCODE_SERVFAIL; mesh_walk_supers(mesh, m); } mesh->stats_jostled ++; mesh_state_delete(&m->s); /* restore the query - note that the qinfo ptr to * the querybuffer is then correct again. */ if(qbuf) sldns_buffer_copy(qbuf, mesh->qbuf_bak); return 1; } } /* no space for new item */ return 0; } struct dns_msg* mesh_serve_expired_lookup(struct module_qstate* qstate, struct query_info* lookup_qinfo, int* is_expired) { hashvalue_type h; struct lruhash_entry* e; struct dns_msg* msg; struct reply_info* data; struct msgreply_entry* key; time_t timenow = *qstate->env->now; int must_validate = (!(qstate->query_flags&BIT_CD) || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; *is_expired = 0; /* Lookup cache */ h = query_info_hash(lookup_qinfo, qstate->query_flags); e = slabhash_lookup(qstate->env->msg_cache, h, lookup_qinfo, 0); if(!e) return NULL; key = (struct msgreply_entry*)e->key; data = (struct reply_info*)e->data; if(TTL_IS_EXPIRED(data->ttl, timenow)) *is_expired = 1; msg = tomsg(qstate->env, &key->key, data, qstate->region, timenow, qstate->env->cfg->serve_expired, qstate->env->scratch); if(!msg) goto bail_out; /* Check CNAME chain (if any) * This is part of tomsg above; no need to check now. */ /* Check security status of the cached answer. * tomsg above has a subset of these checks, so we are leaving * these as is. * In case of bogus or revalidation we don't care to reply here. */ if(must_validate && (msg->rep->security == sec_status_bogus || msg->rep->security == sec_status_secure_sentinel_fail)) { verbose(VERB_ALGO, "Serve expired: bogus answer found in cache"); goto bail_out; } else if(msg->rep->security == sec_status_unchecked && must_validate) { verbose(VERB_ALGO, "Serve expired: unchecked entry needs " "validation"); goto bail_out; /* need to validate cache entry first */ } else if(msg->rep->security == sec_status_secure && !reply_all_rrsets_secure(msg->rep) && must_validate) { verbose(VERB_ALGO, "Serve expired: secure entry" " changed status"); goto bail_out; /* rrset changed, re-verify */ } lock_rw_unlock(&e->lock); return msg; bail_out: lock_rw_unlock(&e->lock); return NULL; } /** Init the serve expired data structure */ static int mesh_serve_expired_init(struct mesh_state* mstate, int timeout) { struct timeval t; /* Create serve_expired_data if not there yet */ if(!mstate->s.serve_expired_data) { mstate->s.serve_expired_data = (struct serve_expired_data*) regional_alloc_zero( mstate->s.region, sizeof(struct serve_expired_data)); if(!mstate->s.serve_expired_data) return 0; } /* Don't overwrite the function if already set */ mstate->s.serve_expired_data->get_cached_answer = mstate->s.serve_expired_data->get_cached_answer? mstate->s.serve_expired_data->get_cached_answer: &mesh_serve_expired_lookup; /* In case this timer already popped, start it again */ if(!mstate->s.serve_expired_data->timer && timeout != -1) { mstate->s.serve_expired_data->timer = comm_timer_create( mstate->s.env->worker_base, mesh_serve_expired_callback, mstate); if(!mstate->s.serve_expired_data->timer) return 0; #ifndef S_SPLINT_S t.tv_sec = timeout/1000; t.tv_usec = (timeout%1000)*1000; #endif comm_timer_set(mstate->s.serve_expired_data->timer, &t); } return 1; } void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, struct edns_data* edns, struct comm_reply* rep, uint16_t qid, int rpz_passthru) { struct mesh_state* s = NULL; int unique = unique_mesh_state(edns->opt_list_in, mesh->env); int was_detached = 0; int was_noreply = 0; int added = 0; int timeout = mesh->env->cfg->serve_expired? mesh->env->cfg->serve_expired_client_timeout:0; struct sldns_buffer* r_buffer = rep->c->buffer; uint16_t mesh_flags = qflags&(BIT_RD|BIT_CD); if(rep->c->tcp_req_info) { r_buffer = rep->c->tcp_req_info->spool_buffer; } if(!infra_wait_limit_allowed(mesh->env->infra_cache, rep, edns->cookie_valid, mesh->env->cfg)) { verbose(VERB_ALGO, "Too many queries waiting from the IP. " "servfail incoming query."); mesh->num_queries_wait_limit++; edns_opt_list_append_ede(&edns->opt_list_out, mesh->env->scratch, LDNS_EDE_OTHER, "Too many queries queued up and waiting from the IP"); if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(r_buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); regional_free_all(mesh->env->scratch); comm_point_send_reply(rep); return; } if(!unique) s = mesh_area_find(mesh, cinfo, qinfo, mesh_flags, 0, 0); /* does this create a new reply state? */ if(!s || s->list_select == mesh_no_list) { if(!mesh_make_new_space(mesh, rep->c->buffer)) { verbose(VERB_ALGO, "Too many queries. dropping " "incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); comm_point_drop_reply(rep); mesh->stats_dropped++; return; } /* for this new reply state, the reply address is free, * so the limit of reply addresses does not stop reply states*/ } else { /* protect our memory usage from storing reply addresses */ if(mesh->num_reply_addrs > mesh->max_reply_states*16) { verbose(VERB_ALGO, "Too many requests queued. " "dropping incoming query."); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); comm_point_drop_reply(rep); mesh->num_queries_replyaddr_limit++; return; } } /* see if it already exists, if not, create one */ if(!s) { #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif s = mesh_state_create(mesh->env, qinfo, cinfo, mesh_flags, 0, 0); if(!s) { log_err("mesh_state_create: out of memory; SERVFAIL"); if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(r_buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); return; } /* set detached (it is now) */ mesh->num_detached_states++; if(unique) mesh_state_make_unique(s); s->s.rpz_passthru = rpz_passthru; /* copy the edns options we got from the front */ if(edns->opt_list_in) { s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in, s->s.region); if(!s->s.edns_opts_front_in) { log_err("edns_opt_copy_region: out of memory; SERVFAIL"); if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, NULL, NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(r_buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); comm_point_send_reply(rep); mesh_state_delete(&s->s); return; } } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->all, &s->node); log_assert(n != NULL); added = 1; } if(!s->reply_list && !s->cb_list) { was_noreply = 1; if(s->super_set.count == 0) { was_detached = 1; } } /* add reply to s */ if(!mesh_state_add_reply(s, edns, rep, qid, qflags, qinfo)) { log_err("mesh_new_client: out of memory; SERVFAIL"); goto servfail_mem; } if(rep->c->tcp_req_info) { if(!tcp_req_info_add_meshstate(rep->c->tcp_req_info, mesh, s)) { log_err("mesh_new_client: out of memory add tcpreqinfo"); goto servfail_mem; } } if(rep->c->use_h2) { http2_stream_add_meshstate(rep->c->h2_stream, mesh, s); } /* add serve expired timer if required and not already there */ if(timeout && !mesh_serve_expired_init(s, timeout)) { log_err("mesh_new_client: out of memory initializing serve expired"); goto servfail_mem; } #ifdef USE_CACHEDB if(!timeout && mesh->env->cfg->serve_expired && !mesh->env->cfg->serve_expired_client_timeout && (mesh->env->cachedb_enabled && mesh->env->cfg->cachedb_check_when_serve_expired)) { if(!mesh_serve_expired_init(s, -1)) { log_err("mesh_new_client: out of memory initializing serve expired"); goto servfail_mem; } } #endif infra_wait_limit_inc(mesh->env->infra_cache, rep, *mesh->env->now, mesh->env->cfg); /* update statistics */ if(was_detached) { log_assert(mesh->num_detached_states > 0); mesh->num_detached_states--; } if(was_noreply) { mesh->num_reply_states ++; } mesh->num_reply_addrs++; if(s->list_select == mesh_no_list) { /* move to either the forever or the jostle_list */ if(mesh->num_forever_states < mesh->max_forever_states) { mesh->num_forever_states ++; mesh_list_insert(s, &mesh->forever_first, &mesh->forever_last); s->list_select = mesh_forever_list; } else { mesh_list_insert(s, &mesh->jostle_first, &mesh->jostle_last); s->list_select = mesh_jostle_list; } } if(added) mesh_run(mesh, s, module_event_new, NULL); return; servfail_mem: if(!inplace_cb_reply_servfail_call(mesh->env, qinfo, &s->s, NULL, LDNS_RCODE_SERVFAIL, edns, rep, mesh->env->scratch, mesh->env->now_tv)) edns->opt_list_inplace_cb_out = NULL; error_encode(r_buffer, LDNS_RCODE_SERVFAIL, qinfo, qid, qflags, edns); if(rep->c->use_h2) http2_stream_remove_mesh_state(rep->c->h2_stream); comm_point_send_reply(rep); if(added) mesh_state_delete(&s->s); return; } int mesh_new_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, struct edns_data* edns, sldns_buffer* buf, uint16_t qid, mesh_cb_func_type cb, void* cb_arg, int rpz_passthru) { struct mesh_state* s = NULL; int unique = unique_mesh_state(edns->opt_list_in, mesh->env); int timeout = mesh->env->cfg->serve_expired? mesh->env->cfg->serve_expired_client_timeout:0; int was_detached = 0; int was_noreply = 0; int added = 0; uint16_t mesh_flags = qflags&(BIT_RD|BIT_CD); if(!unique) s = mesh_area_find(mesh, NULL, qinfo, mesh_flags, 0, 0); /* there are no limits on the number of callbacks */ /* see if it already exists, if not, create one */ if(!s) { #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif s = mesh_state_create(mesh->env, qinfo, NULL, mesh_flags, 0, 0); if(!s) { return 0; } /* set detached (it is now) */ mesh->num_detached_states++; if(unique) mesh_state_make_unique(s); s->s.rpz_passthru = rpz_passthru; if(edns->opt_list_in) { s->s.edns_opts_front_in = edns_opt_copy_region(edns->opt_list_in, s->s.region); if(!s->s.edns_opts_front_in) { mesh_state_delete(&s->s); return 0; } } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->all, &s->node); log_assert(n != NULL); added = 1; } if(!s->reply_list && !s->cb_list) { was_noreply = 1; if(s->super_set.count == 0) { was_detached = 1; } } /* add reply to s */ if(!mesh_state_add_cb(s, edns, buf, cb, cb_arg, qid, qflags)) { if(added) mesh_state_delete(&s->s); return 0; } /* add serve expired timer if not already there */ if(timeout && !mesh_serve_expired_init(s, timeout)) { if(added) mesh_state_delete(&s->s); return 0; } #ifdef USE_CACHEDB if(!timeout && mesh->env->cfg->serve_expired && !mesh->env->cfg->serve_expired_client_timeout && (mesh->env->cachedb_enabled && mesh->env->cfg->cachedb_check_when_serve_expired)) { if(!mesh_serve_expired_init(s, -1)) { if(added) mesh_state_delete(&s->s); return 0; } } #endif /* update statistics */ if(was_detached) { log_assert(mesh->num_detached_states > 0); mesh->num_detached_states--; } if(was_noreply) { mesh->num_reply_states ++; } mesh->num_reply_addrs++; if(added) mesh_run(mesh, s, module_event_new, NULL); return 1; } /* Internal backend routine of mesh_new_prefetch(). It takes one additional * parameter, 'run', which controls whether to run the prefetch state * immediately. When this function is called internally 'run' could be * 0 (false), in which case the new state is only made runnable so it * will not be run recursively on top of the current state. */ static void mesh_schedule_prefetch(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway, int run, int rpz_passthru) { /* Explicitly set the BIT_RD regardless of the client's flags. This is * for a prefetch query (no client attached) but it needs to be treated * as a recursion query. */ uint16_t mesh_flags = BIT_RD|(qflags&BIT_CD); struct mesh_state* s = mesh_area_find(mesh, NULL, qinfo, mesh_flags, 0, 0); #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif /* already exists, and for a different purpose perhaps. * if mesh_no_list, keep it that way. */ if(s) { /* make it ignore the cache from now on */ if(!s->s.blacklist) sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region); if(s->s.prefetch_leeway < leeway) s->s.prefetch_leeway = leeway; return; } if(!mesh_make_new_space(mesh, NULL)) { verbose(VERB_ALGO, "Too many queries. dropped prefetch."); mesh->stats_dropped ++; return; } s = mesh_state_create(mesh->env, qinfo, NULL, mesh_flags, 0, 0); if(!s) { log_err("prefetch mesh_state_create: out of memory"); return; } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->all, &s->node); log_assert(n != NULL); /* set detached (it is now) */ mesh->num_detached_states++; /* make it ignore the cache */ sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region); s->s.prefetch_leeway = leeway; if(s->list_select == mesh_no_list) { /* move to either the forever or the jostle_list */ if(mesh->num_forever_states < mesh->max_forever_states) { mesh->num_forever_states ++; mesh_list_insert(s, &mesh->forever_first, &mesh->forever_last); s->list_select = mesh_forever_list; } else { mesh_list_insert(s, &mesh->jostle_first, &mesh->jostle_last); s->list_select = mesh_jostle_list; } } s->s.rpz_passthru = rpz_passthru; if(!run) { #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->run, &s->run_node); log_assert(n != NULL); return; } mesh_run(mesh, s, module_event_new, NULL); } #ifdef CLIENT_SUBNET /* Same logic as mesh_schedule_prefetch but tailored to the subnet module logic * like passing along the comm_reply info. This will be faked into an EDNS * option for processing by the subnet module if the client has not already * attached its own ECS data. */ static void mesh_schedule_prefetch_subnet(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway, int run, int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* edns_list) { struct mesh_state* s = NULL; struct edns_option* opt = NULL; #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif /* Explicitly set the BIT_RD regardless of the client's flags. This is * for a prefetch query (no client attached) but it needs to be treated * as a recursion query. */ uint16_t mesh_flags = BIT_RD|(qflags&BIT_CD); if(!mesh_make_new_space(mesh, NULL)) { verbose(VERB_ALGO, "Too many queries. dropped prefetch."); mesh->stats_dropped ++; return; } s = mesh_state_create(mesh->env, qinfo, NULL, mesh_flags, 0, 0); if(!s) { log_err("prefetch_subnet mesh_state_create: out of memory"); return; } mesh_state_make_unique(s); opt = edns_opt_list_find(edns_list, mesh->env->cfg->client_subnet_opcode); if(opt) { /* Use the client's ECS data */ if(!edns_opt_list_append(&s->s.edns_opts_front_in, opt->opt_code, opt->opt_len, opt->opt_data, s->s.region)) { log_err("prefetch_subnet edns_opt_list_append: out of memory"); return; } } else { /* Store the client's address. Later in the subnet module, * it is decided whether to include an ECS option or not. */ s->s.client_addr = *addr; } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->all, &s->node); log_assert(n != NULL); /* set detached (it is now) */ mesh->num_detached_states++; /* make it ignore the cache */ sock_list_insert(&s->s.blacklist, NULL, 0, s->s.region); s->s.prefetch_leeway = leeway; if(s->list_select == mesh_no_list) { /* move to either the forever or the jostle_list */ if(mesh->num_forever_states < mesh->max_forever_states) { mesh->num_forever_states ++; mesh_list_insert(s, &mesh->forever_first, &mesh->forever_last); s->list_select = mesh_forever_list; } else { mesh_list_insert(s, &mesh->jostle_first, &mesh->jostle_last); s->list_select = mesh_jostle_list; } } s->s.rpz_passthru = rpz_passthru; if(!run) { #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->run, &s->run_node); log_assert(n != NULL); return; } mesh_run(mesh, s, module_event_new, NULL); } #endif /* CLIENT_SUBNET */ void mesh_new_prefetch(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, time_t leeway, int rpz_passthru, struct sockaddr_storage* addr, struct edns_option* opt_list) { (void)addr; (void)opt_list; #ifdef CLIENT_SUBNET if(addr) mesh_schedule_prefetch_subnet(mesh, qinfo, qflags, leeway, 1, rpz_passthru, addr, opt_list); else #endif mesh_schedule_prefetch(mesh, qinfo, qflags, leeway, 1, rpz_passthru); } void mesh_report_reply(struct mesh_area* mesh, struct outbound_entry* e, struct comm_reply* reply, int what) { enum module_ev event = module_event_reply; e->qstate->reply = reply; if(what != NETEVENT_NOERROR) { event = module_event_noreply; if(what == NETEVENT_CAPSFAIL) event = module_event_capsfail; } mesh_run(mesh, e->qstate->mesh_info, event, e); } /** copy strlist to region */ static struct config_strlist* cfg_region_strlist_copy(struct regional* region, struct config_strlist* list) { struct config_strlist* result = NULL, *last = NULL, *s = list; while(s) { struct config_strlist* n = regional_alloc_zero(region, sizeof(*n)); if(!n) return NULL; n->str = regional_strdup(region, s->str); if(!n->str) return NULL; if(last) last->next = n; else result = n; last = n; s = s->next; } return result; } /** Copy the client info to the query region. */ static struct respip_client_info* mesh_copy_client_info(struct regional* region, struct respip_client_info* cinfo) { size_t i; struct respip_client_info* client_info; client_info = regional_alloc_init(region, cinfo, sizeof(*cinfo)); if(!client_info) return NULL; /* Copy the client_info so that if the configuration changes, * then the data stays valid. */ if(cinfo->taglist) { client_info->taglist = regional_alloc_init(region, cinfo->taglist, cinfo->taglen); if(!client_info->taglist) return NULL; } if(cinfo->tag_actions) { client_info->tag_actions = regional_alloc_init(region, cinfo->tag_actions, cinfo->tag_actions_size); if(!client_info->tag_actions) return NULL; } if(cinfo->tag_datas) { client_info->tag_datas = regional_alloc_zero(region, sizeof(struct config_strlist*)*cinfo->tag_datas_size); if(!client_info->tag_datas) return NULL; for(i=0; itag_datas_size; i++) { if(cinfo->tag_datas[i]) { client_info->tag_datas[i] = cfg_region_strlist_copy( region, cinfo->tag_datas[i]); if(!client_info->tag_datas[i]) return NULL; } } } if(cinfo->view) { /* Do not copy the view pointer but store a name instead. * The name is looked up later when done, this means that * the view tree can be changed, by reloads. */ client_info->view = NULL; client_info->view_name = regional_strdup(region, cinfo->view->name); if(!client_info->view_name) return NULL; } return client_info; } struct mesh_state* mesh_state_create(struct module_env* env, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec) { struct regional* region = alloc_reg_obtain(env->alloc); struct mesh_state* mstate; int i; if(!region) return NULL; mstate = (struct mesh_state*)regional_alloc(region, sizeof(struct mesh_state)); if(!mstate) { alloc_reg_release(env->alloc, region); return NULL; } memset(mstate, 0, sizeof(*mstate)); mstate->node = *RBTREE_NULL; mstate->run_node = *RBTREE_NULL; mstate->node.key = mstate; mstate->run_node.key = mstate; mstate->reply_list = NULL; mstate->list_select = mesh_no_list; mstate->replies_sent = 0; rbtree_init(&mstate->super_set, &mesh_state_ref_compare); rbtree_init(&mstate->sub_set, &mesh_state_ref_compare); mstate->num_activated = 0; mstate->unique = NULL; /* init module qstate */ mstate->s.qinfo.qtype = qinfo->qtype; mstate->s.qinfo.qclass = qinfo->qclass; mstate->s.qinfo.local_alias = NULL; mstate->s.qinfo.qname_len = qinfo->qname_len; mstate->s.qinfo.qname = regional_alloc_init(region, qinfo->qname, qinfo->qname_len); if(!mstate->s.qinfo.qname) { alloc_reg_release(env->alloc, region); return NULL; } if(cinfo) { mstate->s.client_info = mesh_copy_client_info(region, cinfo); if(!mstate->s.client_info) { alloc_reg_release(env->alloc, region); return NULL; } } /* remove all weird bits from qflags */ mstate->s.query_flags = (qflags & (BIT_RD|BIT_CD)); mstate->s.is_priming = prime; mstate->s.is_valrec = valrec; mstate->s.reply = NULL; mstate->s.region = region; mstate->s.curmod = 0; mstate->s.return_msg = 0; mstate->s.return_rcode = LDNS_RCODE_NOERROR; mstate->s.env = env; mstate->s.mesh_info = mstate; mstate->s.prefetch_leeway = 0; mstate->s.serve_expired_data = NULL; mstate->s.no_cache_lookup = 0; mstate->s.no_cache_store = 0; mstate->s.need_refetch = 0; mstate->s.was_ratelimited = 0; mstate->s.error_response_cache = 0; mstate->s.qstarttime = *env->now; /* init modules */ for(i=0; imesh->mods.num; i++) { mstate->s.minfo[i] = NULL; mstate->s.ext_state[i] = module_state_initial; } /* init edns option lists */ mstate->s.edns_opts_front_in = NULL; mstate->s.edns_opts_back_out = NULL; mstate->s.edns_opts_back_in = NULL; mstate->s.edns_opts_front_out = NULL; return mstate; } void mesh_state_make_unique(struct mesh_state* mstate) { mstate->unique = mstate; } void mesh_state_cleanup(struct mesh_state* mstate) { struct mesh_area* mesh; int i; if(!mstate) return; mesh = mstate->s.env->mesh; /* Stop and delete the serve expired timer */ if(mstate->s.serve_expired_data && mstate->s.serve_expired_data->timer) { comm_timer_delete(mstate->s.serve_expired_data->timer); mstate->s.serve_expired_data->timer = NULL; } /* drop unsent replies */ if(!mstate->replies_sent) { struct mesh_reply* rep = mstate->reply_list; struct mesh_cb* cb; /* One http2 stream could bring down its comm_point along with * the other streams which could share the same query. Do all * the http2 stream bookkeeping upfront. */ for(; rep; rep=rep->next) { if(rep->query_reply.c->use_h2) http2_stream_remove_mesh_state(rep->h2_stream); } rep = mstate->reply_list; /* in tcp_req_info, the mstates linked are removed, but * the reply_list is now NULL, so the remove-from-empty-list * takes no time and also it does not do the mesh accounting */ mstate->reply_list = NULL; for(; rep; rep=rep->next) { infra_wait_limit_dec(mesh->env->infra_cache, &rep->query_reply, mesh->env->cfg); comm_point_drop_reply(&rep->query_reply); log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; } while((cb = mstate->cb_list)!=NULL) { mstate->cb_list = cb->next; fptr_ok(fptr_whitelist_mesh_cb(cb->cb)); (*cb->cb)(cb->cb_arg, LDNS_RCODE_SERVFAIL, NULL, sec_status_unchecked, NULL, 0); log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; } } /* de-init modules */ for(i=0; imods.num; i++) { fptr_ok(fptr_whitelist_mod_clear(mesh->mods.mod[i]->clear)); (*mesh->mods.mod[i]->clear)(&mstate->s, i); mstate->s.minfo[i] = NULL; mstate->s.ext_state[i] = module_finished; } alloc_reg_release(mstate->s.env->alloc, mstate->s.region); } void mesh_state_delete(struct module_qstate* qstate) { struct mesh_area* mesh; struct mesh_state_ref* super, ref; struct mesh_state* mstate; if(!qstate) return; mstate = qstate->mesh_info; mesh = mstate->s.env->mesh; mesh_detach_subs(&mstate->s); if(mstate->list_select == mesh_forever_list) { mesh->num_forever_states --; mesh_list_remove(mstate, &mesh->forever_first, &mesh->forever_last); } else if(mstate->list_select == mesh_jostle_list) { mesh_list_remove(mstate, &mesh->jostle_first, &mesh->jostle_last); } if(!mstate->reply_list && !mstate->cb_list && mstate->super_set.count == 0) { log_assert(mesh->num_detached_states > 0); mesh->num_detached_states--; } if(mstate->reply_list || mstate->cb_list) { log_assert(mesh->num_reply_states > 0); mesh->num_reply_states--; } ref.node.key = &ref; ref.s = mstate; RBTREE_FOR(super, struct mesh_state_ref*, &mstate->super_set) { (void)rbtree_delete(&super->s->sub_set, &ref); } (void)rbtree_delete(&mesh->run, mstate); (void)rbtree_delete(&mesh->all, mstate); mesh_state_cleanup(mstate); } /** helper recursive rbtree find routine */ static int find_in_subsub(struct mesh_state* m, struct mesh_state* tofind, size_t *c) { struct mesh_state_ref* r; if((*c)++ > MESH_MAX_SUBSUB) return 1; RBTREE_FOR(r, struct mesh_state_ref*, &m->sub_set) { if(r->s == tofind || find_in_subsub(r->s, tofind, c)) return 1; } return 0; } /** find cycle for already looked up mesh_state */ static int mesh_detect_cycle_found(struct module_qstate* qstate, struct mesh_state* dep_m) { struct mesh_state* cyc_m = qstate->mesh_info; size_t counter = 0; log_assert(dep_m); if(dep_m == cyc_m || find_in_subsub(dep_m, cyc_m, &counter)) { if(counter > MESH_MAX_SUBSUB) return 2; return 1; } return 0; } void mesh_detach_subs(struct module_qstate* qstate) { struct mesh_area* mesh = qstate->env->mesh; struct mesh_state_ref* ref, lookup; #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif lookup.node.key = &lookup; lookup.s = qstate->mesh_info; RBTREE_FOR(ref, struct mesh_state_ref*, &qstate->mesh_info->sub_set) { #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_delete(&ref->s->super_set, &lookup); log_assert(n != NULL); /* must have been present */ if(!ref->s->reply_list && !ref->s->cb_list && ref->s->super_set.count == 0) { mesh->num_detached_states++; log_assert(mesh->num_detached_states + mesh->num_reply_states <= mesh->all.count); } } rbtree_init(&qstate->mesh_info->sub_set, &mesh_state_ref_compare); } int mesh_add_sub(struct module_qstate* qstate, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec, struct module_qstate** newq, struct mesh_state** sub) { /* find it, if not, create it */ struct mesh_area* mesh = qstate->env->mesh; *sub = mesh_area_find(mesh, cinfo, qinfo, qflags, prime, valrec); if(!*sub) { #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif /* create a new one */ *sub = mesh_state_create(qstate->env, qinfo, cinfo, qflags, prime, valrec); if(!*sub) { log_err("mesh_attach_sub: out of memory"); return 0; } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->all, &(*sub)->node); log_assert(n != NULL); /* set detached (it is now) */ mesh->num_detached_states++; /* set new query state to run */ #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&mesh->run, &(*sub)->run_node); log_assert(n != NULL); *newq = &(*sub)->s; } else { *newq = NULL; if(mesh_detect_cycle_found(qstate, *sub)) { verbose(VERB_ALGO, "attach failed, cycle detected"); return 0; } } return 1; } int mesh_attach_sub(struct module_qstate* qstate, struct query_info* qinfo, struct respip_client_info* cinfo, uint16_t qflags, int prime, int valrec, struct module_qstate** newq) { struct mesh_area* mesh = qstate->env->mesh; struct mesh_state* sub = NULL; int was_detached; if(!mesh_add_sub(qstate, qinfo, cinfo, qflags, prime, valrec, newq, &sub)) return 0; was_detached = (sub->super_set.count == 0); if(!mesh_state_attachment(qstate->mesh_info, sub)) return 0; /* if it was a duplicate attachment, the count was not zero before */ if(!sub->reply_list && !sub->cb_list && was_detached && sub->super_set.count == 1) { /* it used to be detached, before this one got added */ log_assert(mesh->num_detached_states > 0); mesh->num_detached_states--; } /* *newq will be run when inited after the current module stops */ return 1; } int mesh_state_attachment(struct mesh_state* super, struct mesh_state* sub) { #ifdef UNBOUND_DEBUG struct rbnode_type* n; #endif struct mesh_state_ref* subref; /* points to sub, inserted in super */ struct mesh_state_ref* superref; /* points to super, inserted in sub */ if( !(subref = regional_alloc(super->s.region, sizeof(struct mesh_state_ref))) || !(superref = regional_alloc(sub->s.region, sizeof(struct mesh_state_ref))) ) { log_err("mesh_state_attachment: out of memory"); return 0; } superref->node.key = superref; superref->s = super; subref->node.key = subref; subref->s = sub; if(!rbtree_insert(&sub->super_set, &superref->node)) { /* this should not happen, iterator and validator do not * attach subqueries that are identical. */ /* already attached, we are done, nothing todo. * since superref and subref already allocated in region, * we cannot free them */ return 1; } #ifdef UNBOUND_DEBUG n = #else (void) #endif rbtree_insert(&super->sub_set, &subref->node); log_assert(n != NULL); /* we checked above if statement, the reverse administration should not fail now, unless they are out of sync */ return 1; } /** * callback results to mesh cb entry * @param m: mesh state to send it for. * @param rcode: if not 0, error code. * @param rep: reply to send (or NULL if rcode is set). * @param r: callback entry * @param start_time: the time to pass to callback functions, it is 0 or * a value from one of the packets if the mesh state had packets. */ static void mesh_do_callback(struct mesh_state* m, int rcode, struct reply_info* rep, struct mesh_cb* r, struct timeval* start_time) { int secure; char* reason = NULL; int was_ratelimited = m->s.was_ratelimited; /* bogus messages are not made into servfail, sec_status passed * to the callback function */ if(rep && rep->security == sec_status_secure) secure = 1; else secure = 0; if(!rep && rcode == LDNS_RCODE_NOERROR) rcode = LDNS_RCODE_SERVFAIL; if(!rcode && rep && (rep->security == sec_status_bogus || rep->security == sec_status_secure_sentinel_fail)) { if(!(reason = errinf_to_str_bogus(&m->s, NULL))) rcode = LDNS_RCODE_SERVFAIL; } /* send the reply */ if(rcode) { if(rcode == LDNS_RCODE_SERVFAIL) { if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, &r->edns, NULL, m->s.region, start_time)) r->edns.opt_list_inplace_cb_out = NULL; } else { if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, &r->edns, NULL, m->s.region, start_time)) r->edns.opt_list_inplace_cb_out = NULL; } fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, rcode, r->buf, sec_status_unchecked, NULL, was_ratelimited); } else { size_t udp_size = r->edns.udp_size; sldns_buffer_clear(r->buf); r->edns.edns_version = EDNS_ADVERTISED_VERSION; r->edns.udp_size = EDNS_ADVERTISED_SIZE; r->edns.ext_rcode = 0; r->edns.bits &= EDNS_DO; if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO)) r->edns.edns_present = 0; if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_NOERROR, &r->edns, NULL, m->s.region, start_time) || !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r->buf, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) { fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, LDNS_RCODE_SERVFAIL, r->buf, sec_status_unchecked, NULL, 0); } else { fptr_ok(fptr_whitelist_mesh_cb(r->cb)); (*r->cb)(r->cb_arg, LDNS_RCODE_NOERROR, r->buf, (rep?rep->security:sec_status_unchecked), reason, was_ratelimited); } } free(reason); log_assert(m->s.env->mesh->num_reply_addrs > 0); m->s.env->mesh->num_reply_addrs--; } static inline int mesh_is_rpz_respip_tcponly_action(struct mesh_state const* m) { struct respip_action_info const* respip_info = m->s.respip_action_info; return (respip_info == NULL ? 0 : (respip_info->rpz_used && !respip_info->rpz_disabled && respip_info->action == respip_truncate)) || m->s.tcp_required; } static inline int mesh_is_udp(struct mesh_reply const* r) { return r->query_reply.c->type == comm_udp; } static inline void mesh_find_and_attach_ede_and_reason(struct mesh_state* m, struct reply_info* rep, struct mesh_reply* r) { /* OLD note: * During validation the EDE code can be received via two * code paths. One code path fills the reply_info EDE, and * the other fills it in the errinf_strlist. These paths * intersect at some points, but where is opaque due to * the complexity of the validator. At the time of writing * we make the choice to prefer the EDE from errinf_strlist * but a compelling reason to do otherwise is just as valid * NEW note: * The compelling reason is that with caching support, the value * in the reply_info is cached. * The reason members of the reply_info struct should be * updated as they are already cached. No reason to * try and find the EDE information in errinf anymore. */ if(rep->reason_bogus != LDNS_EDE_NONE) { edns_opt_list_append_ede(&r->edns.opt_list_out, m->s.region, rep->reason_bogus, rep->reason_bogus_str); } } /** * Send reply to mesh reply entry * @param m: mesh state to send it for. * @param rcode: if not 0, error code. * @param rep: reply to send (or NULL if rcode is set). * @param r: reply entry * @param r_buffer: buffer to use for reply entry. * @param prev: previous reply, already has its answer encoded in buffer. * @param prev_buffer: buffer for previous reply. */ static void mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep, struct mesh_reply* r, struct sldns_buffer* r_buffer, struct mesh_reply* prev, struct sldns_buffer* prev_buffer) { struct timeval end_time; struct timeval duration; int secure; /* briefly set the replylist to null in case the * meshsendreply calls tcpreqinfo sendreply that * comm_point_drops because of size, and then the * null stops the mesh state remove and thus * reply_list modification and accounting */ struct mesh_reply* rlist = m->reply_list; /* rpz: apply actions */ rcode = mesh_is_udp(r) && mesh_is_rpz_respip_tcponly_action(m) ? (rcode|BIT_TC) : rcode; /* examine security status */ if(m->s.env->need_to_validate && (!(r->qflags&BIT_CD) || m->s.env->cfg->ignore_cd) && rep && (rep->security <= sec_status_bogus || rep->security == sec_status_secure_sentinel_fail)) { rcode = LDNS_RCODE_SERVFAIL; if(m->s.env->cfg->stat_extended) m->s.env->mesh->ans_bogus++; } if(rep && rep->security == sec_status_secure) secure = 1; else secure = 0; if(!rep && rcode == LDNS_RCODE_NOERROR) rcode = LDNS_RCODE_SERVFAIL; if(r->query_reply.c->use_h2) { r->query_reply.c->h2_stream = r->h2_stream; /* Mesh reply won't exist for long anymore. Make it impossible * for HTTP/2 stream to refer to mesh state, in case * connection gets cleanup before HTTP/2 stream close. */ r->h2_stream->mesh_state = NULL; } /* 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. * - there are registered callback functions for the given rcode, as these * need to be called for each reply. */ if(((rcode != LDNS_RCODE_SERVFAIL && !m->s.env->inplace_cb_lists[inplace_cb_reply]) || (rcode == LDNS_RCODE_SERVFAIL && !m->s.env->inplace_cb_lists[inplace_cb_reply_servfail])) && prev && prev_buffer && 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 && edns_opt_list_compare(prev->edns.opt_list_out, r->edns.opt_list_out) == 0 && edns_opt_list_compare(prev->edns.opt_list_inplace_cb_out, r->edns.opt_list_inplace_cb_out) == 0 ) { /* if the previous reply is identical to this one, fix ID */ if(prev_buffer != r_buffer) sldns_buffer_copy(r_buffer, prev_buffer); sldns_buffer_write_at(r_buffer, 0, &r->qid, sizeof(uint16_t)); sldns_buffer_write_at(r_buffer, 12, r->qname, m->s.qinfo.qname_len); m->reply_list = NULL; comm_point_send_reply(&r->query_reply); m->reply_list = rlist; } else if(rcode) { m->s.qinfo.qname = r->qname; m->s.qinfo.local_alias = r->local_alias; if(rcode == LDNS_RCODE_SERVFAIL) { if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, &r->edns, &r->query_reply, m->s.region, &r->start_time)) r->edns.opt_list_inplace_cb_out = NULL; } else { if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, rcode, &r->edns, &r->query_reply, m->s.region, &r->start_time)) r->edns.opt_list_inplace_cb_out = NULL; } /* Send along EDE EDNS0 option when SERVFAILing; usually * DNSSEC validation failures */ /* Since we are SERVFAILing here, CD bit and rep->security * is already handled. */ if(m->s.env->cfg->ede && rep) { mesh_find_and_attach_ede_and_reason(m, rep, r); } error_encode(r_buffer, rcode, &m->s.qinfo, r->qid, r->qflags, &r->edns); m->reply_list = NULL; comm_point_send_reply(&r->query_reply); m->reply_list = rlist; } else { size_t udp_size = r->edns.udp_size; r->edns.edns_version = EDNS_ADVERTISED_VERSION; r->edns.udp_size = EDNS_ADVERTISED_SIZE; r->edns.ext_rcode = 0; r->edns.bits &= EDNS_DO; if(m->s.env->cfg->disable_edns_do && (r->edns.bits&EDNS_DO)) r->edns.edns_present = 0; m->s.qinfo.qname = r->qname; m->s.qinfo.local_alias = r->local_alias; /* Attach EDE without SERVFAIL if the validation failed. * Need to explicitly check for rep->security otherwise failed * validation paths may attach to a secure answer. */ if(m->s.env->cfg->ede && rep && (rep->security <= sec_status_bogus || rep->security == sec_status_secure_sentinel_fail)) { mesh_find_and_attach_ede_and_reason(m, rep, r); } if(!inplace_cb_reply_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_NOERROR, &r->edns, &r->query_reply, m->s.region, &r->start_time) || !reply_info_answer_encode(&m->s.qinfo, rep, r->qid, r->qflags, r_buffer, 0, 1, m->s.env->scratch, udp_size, &r->edns, (int)(r->edns.bits & EDNS_DO), secure)) { if(!inplace_cb_reply_servfail_call(m->s.env, &m->s.qinfo, &m->s, rep, LDNS_RCODE_SERVFAIL, &r->edns, &r->query_reply, m->s.region, &r->start_time)) r->edns.opt_list_inplace_cb_out = NULL; /* internal server error (probably malloc failure) so no * EDE (RFC8914) needed */ error_encode(r_buffer, LDNS_RCODE_SERVFAIL, &m->s.qinfo, r->qid, r->qflags, &r->edns); } m->reply_list = NULL; comm_point_send_reply(&r->query_reply); m->reply_list = rlist; } infra_wait_limit_dec(m->s.env->infra_cache, &r->query_reply, m->s.env->cfg); /* account */ log_assert(m->s.env->mesh->num_reply_addrs > 0); m->s.env->mesh->num_reply_addrs--; end_time = *m->s.env->now_tv; timeval_subtract(&duration, &end_time, &r->start_time); verbose(VERB_ALGO, "query took " ARG_LL "d.%6.6d sec", (long long)duration.tv_sec, (int)duration.tv_usec); m->s.env->mesh->replies_sent++; timeval_add(&m->s.env->mesh->replies_sum_wait, &duration); timehist_insert(m->s.env->mesh->histogram, &duration); if(m->s.env->cfg->stat_extended) { uint16_t rc = FLAGS_GET_RCODE(sldns_buffer_read_u16_at( r_buffer, 2)); if(secure) m->s.env->mesh->ans_secure++; m->s.env->mesh->ans_rcode[ rc ] ++; if(rc == 0 && LDNS_ANCOUNT(sldns_buffer_begin(r_buffer)) == 0) m->s.env->mesh->ans_nodata++; } /* Log reply sent */ if(m->s.env->cfg->log_replies) { log_reply_info(NO_VERBOSE, &m->s.qinfo, &r->query_reply.client_addr, r->query_reply.client_addrlen, duration, 0, r_buffer, (m->s.env->cfg->log_destaddr?(void*)r->query_reply.c->socket->addr:NULL), r->query_reply.c->type, r->query_reply.c->ssl); } } /** * Generate the DNS Error Report (RFC9567). * If there is an EDE attached for this reply and there was a Report-Channel * EDNS0 option from the upstream, fire up a report query. * @param qstate: module qstate. * @param rep: prepared reply to be sent. */ static void dns_error_reporting(struct module_qstate* qstate, struct reply_info* rep) { struct query_info qinfo; struct mesh_state* sub; struct module_qstate* newq; uint8_t buf[LDNS_MAX_DOMAINLEN]; size_t count = 0; int written; size_t expected_length; struct edns_option* opt; sldns_ede_code reason_bogus = LDNS_EDE_NONE; sldns_rr_type qtype = qstate->qinfo.qtype; uint8_t* qname = qstate->qinfo.qname; size_t qname_len = qstate->qinfo.qname_len-1; /* skip the trailing \0 */ uint8_t* agent_domain; size_t agent_domain_len; /* We need a valid reporting agent; * this is based on qstate->edns_opts_back_in that will probably have * the latest reporting agent we found while iterating */ opt = edns_opt_list_find(qstate->edns_opts_back_in, LDNS_EDNS_REPORT_CHANNEL); if(!opt) return; agent_domain_len = opt->opt_len; agent_domain = opt->opt_data; if(dname_valid(agent_domain, agent_domain_len) < 3) { /* The agent domain needs to be a valid dname that is not the * root; from RFC9567. */ return; } /* Get the EDE generated from the mesh state, these are mostly * validator errors. If other errors are produced in the future (e.g., * RPZ) we would not want them to result in error reports. */ reason_bogus = errinf_to_reason_bogus(qstate); if(rep && ((reason_bogus == LDNS_EDE_DNSSEC_BOGUS && rep->reason_bogus != LDNS_EDE_NONE) || reason_bogus == LDNS_EDE_NONE)) { reason_bogus = rep->reason_bogus; } if(reason_bogus == LDNS_EDE_NONE || /* other, does not make sense without the text that comes * with it */ reason_bogus == LDNS_EDE_OTHER) return; /* Synthesize the error report query in the format: * "_er.$qtype.$qname.$ede._er.$reporting-agent-domain" */ /* First check if the static length parts fit in the buffer. * That is everything except for qtype and ede that need to be * converted to decimal and checked further on. */ expected_length = 4/*_er*/+qname_len+4/*_er*/+agent_domain_len; if(expected_length > LDNS_MAX_DOMAINLEN) goto skip; memmove(buf+count, "\3_er", 4); count += 4; written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count, "X%d", qtype); expected_length += written; /* Skip on error, truncation or long expected length */ if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count || expected_length > LDNS_MAX_DOMAINLEN ) goto skip; /* Put in the label length */ *(buf+count) = (char)(written - 1); count += written; memmove(buf+count, qname, qname_len); count += qname_len; written = snprintf((char*)buf+count, LDNS_MAX_DOMAINLEN-count, "X%d", reason_bogus); expected_length += written; /* Skip on error, truncation or long expected length */ if(written < 0 || (size_t)written >= LDNS_MAX_DOMAINLEN-count || expected_length > LDNS_MAX_DOMAINLEN ) goto skip; *(buf+count) = (char)(written - 1); count += written; memmove(buf+count, "\3_er", 4); count += 4; /* Copy the agent domain */ memmove(buf+count, agent_domain, agent_domain_len); count += agent_domain_len; qinfo.qname = buf; qinfo.qname_len = count; qinfo.qtype = LDNS_RR_TYPE_TXT; qinfo.qclass = qstate->qinfo.qclass; qinfo.local_alias = NULL; log_query_info(VERB_ALGO, "DNS Error Reporting: generating report " "query for", &qinfo); if(mesh_add_sub(qstate, &qinfo, NULL, BIT_RD, 0, 0, &newq, &sub)) { qstate->env->mesh->num_dns_error_reports++; } return; skip: verbose(VERB_ALGO, "DNS Error Reporting: report query qname too long; " "skip"); return; } void mesh_query_done(struct mesh_state* mstate) { struct mesh_reply* r; struct mesh_reply* prev = NULL; struct sldns_buffer* prev_buffer = NULL; struct mesh_cb* c; struct reply_info* rep = (mstate->s.return_msg? mstate->s.return_msg->rep:NULL); struct timeval tv = {0, 0}; int i = 0; /* No need for the serve expired timer anymore; we are going to reply. */ if(mstate->s.serve_expired_data) { comm_timer_delete(mstate->s.serve_expired_data->timer); mstate->s.serve_expired_data->timer = NULL; } if(mstate->s.return_rcode == LDNS_RCODE_SERVFAIL || (rep && FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_SERVFAIL)) { if(mstate->s.env->cfg->serve_expired) { /* we are SERVFAILing; check for expired answer here */ mesh_respond_serve_expired(mstate); } if((mstate->reply_list || mstate->cb_list) && mstate->s.env->cfg->log_servfail && !mstate->s.env->cfg->val_log_squelch) { char* err = errinf_to_str_servfail(&mstate->s); if(err) { log_err("%s", err); } } } if(mstate->reply_list && mstate->s.env->cfg->dns_error_reporting) dns_error_reporting(&mstate->s, rep); for(r = mstate->reply_list; r; r = r->next) { if(mesh_is_udp(r)) { /* For UDP queries, the old replies are discarded. * This stops a large volume of old replies from * building up. * The stream replies, are not discarded. The * stream is open, the other side is waiting. * Some answer is needed, even if servfail, but the * real reply is ready to go, so that is given. */ struct timeval old; timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time); if(mstate->s.env->cfg->discard_timeout != 0 && ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 > mstate->s.env->cfg->discard_timeout) { /* Drop the reply, it is too old */ /* briefly set the reply_list to NULL, so that the * tcp req info cleanup routine that calls the mesh * to deregister the meshstate for it is not done * because the list is NULL and also accounting is not * done there, but instead we do that here. */ struct mesh_reply* reply_list = mstate->reply_list; verbose(VERB_ALGO, "drop reply, it is older than discard-timeout"); infra_wait_limit_dec(mstate->s.env->infra_cache, &r->query_reply, mstate->s.env->cfg); mstate->reply_list = NULL; if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; log_assert(mstate->s.env->mesh->num_reply_addrs > 0); mstate->s.env->mesh->num_reply_addrs--; mstate->s.env->mesh->num_queries_discard_timeout++; continue; } } i++; tv = r->start_time; /* if a response-ip address block has been stored the * information should be logged for each client. */ if(mstate->s.respip_action_info && mstate->s.respip_action_info->addrinfo) { respip_inform_print(mstate->s.respip_action_info, r->qname, mstate->s.qinfo.qtype, mstate->s.qinfo.qclass, r->local_alias, &r->query_reply.client_addr, r->query_reply.client_addrlen); } /* if this query is determined to be dropped during the * mesh processing, this is the point to take that action. */ if(mstate->s.is_drop) { /* briefly set the reply_list to NULL, so that the * tcp req info cleanup routine that calls the mesh * to deregister the meshstate for it is not done * because the list is NULL and also accounting is not * done there, but instead we do that here. */ struct mesh_reply* reply_list = mstate->reply_list; infra_wait_limit_dec(mstate->s.env->infra_cache, &r->query_reply, mstate->s.env->cfg); mstate->reply_list = NULL; if(r->query_reply.c->use_h2) { http2_stream_remove_mesh_state(r->h2_stream); } comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; log_assert(mstate->s.env->mesh->num_reply_addrs > 0); mstate->s.env->mesh->num_reply_addrs--; } else { struct sldns_buffer* r_buffer = r->query_reply.c->buffer; if(r->query_reply.c->tcp_req_info) { r_buffer = r->query_reply.c->tcp_req_info->spool_buffer; prev_buffer = NULL; } mesh_send_reply(mstate, mstate->s.return_rcode, rep, r, r_buffer, prev, prev_buffer); if(r->query_reply.c->tcp_req_info) { tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate); r_buffer = NULL; } /* mesh_send_reply removed mesh state from * http2_stream. */ prev = r; prev_buffer = r_buffer; } } /* Account for each reply sent. */ if(i > 0 && mstate->s.respip_action_info && mstate->s.respip_action_info->addrinfo && mstate->s.env->cfg->stat_extended && mstate->s.respip_action_info->rpz_used) { if(mstate->s.respip_action_info->rpz_disabled) mstate->s.env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i; if(mstate->s.respip_action_info->rpz_cname_override) mstate->s.env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i; else mstate->s.env->mesh->rpz_action[respip_action_to_rpz_action( mstate->s.respip_action_info->action)] += i; } if(!mstate->s.is_drop && i > 0) { if(mstate->s.env->cfg->stat_extended && mstate->s.is_cachedb_answer) { mstate->s.env->mesh->ans_cachedb += i; } } /* Mesh area accounting */ if(mstate->reply_list) { mstate->reply_list = NULL; if(!mstate->reply_list && !mstate->cb_list) { /* was a reply state, not anymore */ log_assert(mstate->s.env->mesh->num_reply_states > 0); mstate->s.env->mesh->num_reply_states--; } if(!mstate->reply_list && !mstate->cb_list && mstate->super_set.count == 0) mstate->s.env->mesh->num_detached_states++; } mstate->replies_sent = 1; while((c = mstate->cb_list) != NULL) { /* take this cb off the list; so that the list can be * changed, eg. by adds from the callback routine */ if(!mstate->reply_list && mstate->cb_list && !c->next) { /* was a reply state, not anymore */ log_assert(mstate->s.env->mesh->num_reply_states > 0); mstate->s.env->mesh->num_reply_states--; } mstate->cb_list = c->next; if(!mstate->reply_list && !mstate->cb_list && mstate->super_set.count == 0) mstate->s.env->mesh->num_detached_states++; mesh_do_callback(mstate, mstate->s.return_rcode, rep, c, &tv); } } void mesh_walk_supers(struct mesh_area* mesh, struct mesh_state* mstate) { struct mesh_state_ref* ref; RBTREE_FOR(ref, struct mesh_state_ref*, &mstate->super_set) { /* make super runnable */ (void)rbtree_insert(&mesh->run, &ref->s->run_node); /* callback the function to inform super of result */ fptr_ok(fptr_whitelist_mod_inform_super( mesh->mods.mod[ref->s->s.curmod]->inform_super)); (*mesh->mods.mod[ref->s->s.curmod]->inform_super)(&mstate->s, ref->s->s.curmod, &ref->s->s); /* copy state that is always relevant to super */ copy_state_to_super(&mstate->s, ref->s->s.curmod, &ref->s->s); } } struct mesh_state* mesh_area_find(struct mesh_area* mesh, struct respip_client_info* cinfo, struct query_info* qinfo, uint16_t qflags, int prime, int valrec) { struct mesh_state key; struct mesh_state* result; key.node.key = &key; key.s.is_priming = prime; key.s.is_valrec = valrec; key.s.qinfo = *qinfo; key.s.query_flags = qflags; /* We are searching for a similar mesh state when we DO want to * aggregate the state. Thus unique is set to NULL. (default when we * desire aggregation).*/ key.unique = NULL; key.s.client_info = cinfo; result = (struct mesh_state*)rbtree_search(&mesh->all, &key); return result; } /** remove mesh state callback */ int mesh_state_del_cb(struct mesh_state* s, mesh_cb_func_type cb, void* cb_arg) { struct mesh_cb* r, *prev = NULL; r = s->cb_list; while(r) { if(r->cb == cb && r->cb_arg == cb_arg) { /* Delete this entry. */ /* It was allocated in the s.region, so no free. */ if(prev) prev->next = r->next; else s->cb_list = r->next; return 1; } prev = r; r = r->next; } return 0; } int mesh_state_add_cb(struct mesh_state* s, struct edns_data* edns, sldns_buffer* buf, mesh_cb_func_type cb, void* cb_arg, uint16_t qid, uint16_t qflags) { struct mesh_cb* r = regional_alloc(s->s.region, sizeof(struct mesh_cb)); if(!r) return 0; r->buf = buf; log_assert(fptr_whitelist_mesh_cb(cb)); /* early failure ifmissing*/ r->cb = cb; r->cb_arg = cb_arg; r->edns = *edns; if(edns->opt_list_in && !(r->edns.opt_list_in = edns_opt_copy_region(edns->opt_list_in, s->s.region))) return 0; if(edns->opt_list_out && !(r->edns.opt_list_out = edns_opt_copy_region(edns->opt_list_out, s->s.region))) return 0; if(edns->opt_list_inplace_cb_out && !(r->edns.opt_list_inplace_cb_out = edns_opt_copy_region(edns->opt_list_inplace_cb_out, s->s.region))) return 0; r->qid = qid; r->qflags = qflags; r->next = s->cb_list; s->cb_list = r; return 1; } 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) { struct mesh_reply* r = regional_alloc(s->s.region, sizeof(struct mesh_reply)); if(!r) return 0; r->query_reply = *rep; r->edns = *edns; if(edns->opt_list_in && !(r->edns.opt_list_in = edns_opt_copy_region(edns->opt_list_in, s->s.region))) return 0; if(edns->opt_list_out && !(r->edns.opt_list_out = edns_opt_copy_region(edns->opt_list_out, s->s.region))) return 0; if(edns->opt_list_inplace_cb_out && !(r->edns.opt_list_inplace_cb_out = edns_opt_copy_region(edns->opt_list_inplace_cb_out, s->s.region))) return 0; r->qid = qid; r->qflags = qflags; r->start_time = *s->s.env->now_tv; if(s->reply_list == NULL && !s->has_first_reply_time) { s->first_reply_time = r->start_time; s->has_first_reply_time = 1; } r->next = s->reply_list; r->qname = regional_alloc_init(s->s.region, qinfo->qname, s->s.qinfo.qname_len); if(!r->qname) return 0; if(rep->c->use_h2) r->h2_stream = rep->c->h2_stream; else r->h2_stream = NULL; /* 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)); /* we should make a local copy for the owner name of * the RRset */ r->local_alias->rrset->rk.dname_len = qinfo->local_alias->rrset->rk.dname_len; r->local_alias->rrset->rk.dname = regional_alloc_init( s->s.region, qinfo->local_alias->rrset->rk.dname, qinfo->local_alias->rrset->rk.dname_len); if(!r->local_alias->rrset->rk.dname) return 0; /* the rrset is not packed, like in the cache, but it is * individually allocated with an allocator from localzone. */ d = regional_alloc_zero(s->s.region, sizeof(*d)); if(!d) return 0; r->local_alias->rrset->entry.data = d; if(!rrset_insert_rr(s->s.region, d, dsrc->rr_data[0], dsrc->rr_len[0], dsrc->rr_ttl[0], "CNAME local alias")) return 0; } else r->local_alias = NULL; s->reply_list = r; return 1; } /* Extract the query info and flags from 'mstate' into '*qinfop' and '*qflags'. * Since this is only used for internal refetch of otherwise-expired answer, * we simply ignore the rare failure mode when memory allocation fails. */ static void mesh_copy_qinfo(struct mesh_state* mstate, struct query_info** qinfop, uint16_t* qflags) { struct regional* region = mstate->s.env->scratch; struct query_info* qinfo; qinfo = regional_alloc_init(region, &mstate->s.qinfo, sizeof(*qinfo)); if(!qinfo) return; qinfo->qname = regional_alloc_init(region, qinfo->qname, qinfo->qname_len); if(!qinfo->qname) return; *qinfop = qinfo; *qflags = mstate->s.query_flags; } /** * Continue processing the mesh state at another module. * Handles module to modules transfer of control. * Handles module finished. * @param mesh: the mesh area. * @param mstate: currently active mesh state. * Deleted if finished, calls _done and _supers to * send replies to clients and inform other mesh states. * This in turn may create additional runnable mesh states. * @param s: state at which the current module exited. * @param ev: the event sent to the module. * returned is the event to send to the next module. * @return true if continue processing at the new module. * false if not continued processing is needed. */ static int mesh_continue(struct mesh_area* mesh, struct mesh_state* mstate, enum module_ext_state s, enum module_ev* ev) { mstate->num_activated++; if(mstate->num_activated > MESH_MAX_ACTIVATION) { /* module is looping. Stop it. */ log_err("internal error: looping module (%s) stopped", mesh->mods.mod[mstate->s.curmod]->name); log_query_info(NO_VERBOSE, "pass error for qstate", &mstate->s.qinfo); s = module_error; } if(s == module_wait_module || s == module_restart_next) { /* start next module */ mstate->s.curmod++; if(mesh->mods.num == mstate->s.curmod) { log_err("Cannot pass to next module; at last module"); log_query_info(VERB_QUERY, "pass error for qstate", &mstate->s.qinfo); mstate->s.curmod--; return mesh_continue(mesh, mstate, module_error, ev); } if(s == module_restart_next) { int curmod = mstate->s.curmod; for(; mstate->s.curmod < mesh->mods.num; mstate->s.curmod++) { fptr_ok(fptr_whitelist_mod_clear( mesh->mods.mod[mstate->s.curmod]->clear)); (*mesh->mods.mod[mstate->s.curmod]->clear) (&mstate->s, mstate->s.curmod); mstate->s.minfo[mstate->s.curmod] = NULL; } mstate->s.curmod = curmod; } *ev = module_event_pass; return 1; } if(s == module_wait_subquery && mstate->sub_set.count == 0) { log_err("module cannot wait for subquery, subquery list empty"); log_query_info(VERB_QUERY, "pass error for qstate", &mstate->s.qinfo); s = module_error; } if(s == module_error && mstate->s.return_rcode == LDNS_RCODE_NOERROR) { /* error is bad, handle pass back up below */ mstate->s.return_rcode = LDNS_RCODE_SERVFAIL; } if(s == module_error) { mesh_query_done(mstate); mesh_walk_supers(mesh, mstate); mesh_state_delete(&mstate->s); return 0; } if(s == module_finished) { if(mstate->s.curmod == 0) { struct query_info* qinfo = NULL; struct edns_option* opt_list = NULL; struct sockaddr_storage addr; uint16_t qflags; int rpz_p = 0; #ifdef CLIENT_SUBNET struct edns_option* ecs; if(mstate->s.need_refetch && mstate->reply_list && modstack_find(&mesh->mods, "subnetcache") != -1 && mstate->s.env->unique_mesh) { addr = mstate->reply_list->query_reply.client_addr; } else #endif memset(&addr, 0, sizeof(addr)); mesh_query_done(mstate); mesh_walk_supers(mesh, mstate); /* If the answer to the query needs to be refetched * from an external DNS server, we'll need to schedule * a prefetch after removing the current state, so * we need to make a copy of the query info here. */ if(mstate->s.need_refetch) { mesh_copy_qinfo(mstate, &qinfo, &qflags); #ifdef CLIENT_SUBNET /* Make also a copy of the ecs option if any */ if((ecs = edns_opt_list_find( mstate->s.edns_opts_front_in, mstate->s.env->cfg->client_subnet_opcode)) != NULL) { (void)edns_opt_list_append(&opt_list, ecs->opt_code, ecs->opt_len, ecs->opt_data, mstate->s.env->scratch); } #endif rpz_p = mstate->s.rpz_passthru; } if(qinfo) { mesh_state_delete(&mstate->s); mesh_new_prefetch(mesh, qinfo, qflags, 0, rpz_p, addr.ss_family!=AF_UNSPEC?&addr:NULL, opt_list); } else { mesh_state_delete(&mstate->s); } return 0; } /* pass along the locus of control */ mstate->s.curmod --; *ev = module_event_moddone; return 1; } return 0; } void mesh_run(struct mesh_area* mesh, struct mesh_state* mstate, enum module_ev ev, struct outbound_entry* e) { enum module_ext_state s; verbose(VERB_ALGO, "mesh_run: start"); while(mstate) { /* run the module */ fptr_ok(fptr_whitelist_mod_operate( mesh->mods.mod[mstate->s.curmod]->operate)); (*mesh->mods.mod[mstate->s.curmod]->operate) (&mstate->s, ev, mstate->s.curmod, e); /* examine results */ mstate->s.reply = NULL; regional_free_all(mstate->s.env->scratch); s = mstate->s.ext_state[mstate->s.curmod]; verbose(VERB_ALGO, "mesh_run: %s module exit state is %s", mesh->mods.mod[mstate->s.curmod]->name, strextstate(s)); e = NULL; if(mesh_continue(mesh, mstate, s, &ev)) continue; /* run more modules */ ev = module_event_pass; if(mesh->run.count > 0) { /* pop random element off the runnable tree */ mstate = (struct mesh_state*)mesh->run.root->key; (void)rbtree_delete(&mesh->run, mstate); } else mstate = NULL; } if(verbosity >= VERB_ALGO) { mesh_stats(mesh, "mesh_run: end"); mesh_log_list(mesh); } } void mesh_log_list(struct mesh_area* mesh) { char buf[30]; struct mesh_state* m; int num = 0; RBTREE_FOR(m, struct mesh_state*, &mesh->all) { snprintf(buf, sizeof(buf), "%d%s%s%s%s%s%s mod%d %s%s", num++, (m->s.is_priming)?"p":"", /* prime */ (m->s.is_valrec)?"v":"", /* prime */ (m->s.query_flags&BIT_RD)?"RD":"", (m->s.query_flags&BIT_CD)?"CD":"", (m->super_set.count==0)?"d":"", /* detached */ (m->sub_set.count!=0)?"c":"", /* children */ m->s.curmod, (m->reply_list)?"rep":"", /*hasreply*/ (m->cb_list)?"cb":"" /* callbacks */ ); log_query_info(VERB_ALGO, buf, &m->s.qinfo); } } void mesh_stats(struct mesh_area* mesh, const char* str) { verbose(VERB_DETAIL, "%s %u recursion states (%u with reply, " "%u detached), %u waiting replies, %u recursion replies " "sent, %d replies dropped, %d states jostled out", str, (unsigned)mesh->all.count, (unsigned)mesh->num_reply_states, (unsigned)mesh->num_detached_states, (unsigned)mesh->num_reply_addrs, (unsigned)mesh->replies_sent, (unsigned)mesh->stats_dropped, (unsigned)mesh->stats_jostled); if(mesh->replies_sent > 0) { struct timeval avg; timeval_divide(&avg, &mesh->replies_sum_wait, mesh->replies_sent); log_info("average recursion processing time " ARG_LL "d.%6.6d sec", (long long)avg.tv_sec, (int)avg.tv_usec); log_info("histogram of recursion processing times"); timehist_log(mesh->histogram, "recursions"); } } void mesh_stats_clear(struct mesh_area* mesh) { if(!mesh) return; mesh->num_query_authzone_up = 0; mesh->num_query_authzone_down = 0; mesh->replies_sent = 0; mesh->replies_sum_wait.tv_sec = 0; mesh->replies_sum_wait.tv_usec = 0; mesh->stats_jostled = 0; mesh->stats_dropped = 0; timehist_clear(mesh->histogram); mesh->ans_secure = 0; mesh->ans_bogus = 0; mesh->val_ops = 0; mesh->ans_expired = 0; mesh->ans_cachedb = 0; memset(&mesh->ans_rcode[0], 0, sizeof(size_t)*UB_STATS_RCODE_NUM); memset(&mesh->rpz_action[0], 0, sizeof(size_t)*UB_STATS_RPZ_ACTION_NUM); mesh->ans_nodata = 0; mesh->num_queries_discard_timeout = 0; mesh->num_queries_replyaddr_limit = 0; mesh->num_queries_wait_limit = 0; mesh->num_dns_error_reports = 0; } size_t mesh_get_mem(struct mesh_area* mesh) { struct mesh_state* m; size_t s = sizeof(*mesh) + sizeof(struct timehist) + sizeof(struct th_buck)*mesh->histogram->num + sizeof(sldns_buffer) + sldns_buffer_capacity(mesh->qbuf_bak); RBTREE_FOR(m, struct mesh_state*, &mesh->all) { /* all, including m itself allocated in qstate region */ s += regional_get_mem(m->s.region); } return s; } int mesh_detect_cycle(struct module_qstate* qstate, struct query_info* qinfo, uint16_t flags, int prime, int valrec) { struct mesh_area* mesh = qstate->env->mesh; struct mesh_state* dep_m = NULL; dep_m = mesh_area_find(mesh, NULL, qinfo, flags, prime, valrec); return dep_m?mesh_detect_cycle_found(qstate, dep_m):0; } void mesh_list_insert(struct mesh_state* m, struct mesh_state** fp, struct mesh_state** lp) { /* insert as last element */ m->prev = *lp; m->next = NULL; if(*lp) (*lp)->next = m; else *fp = m; *lp = m; } void mesh_list_remove(struct mesh_state* m, struct mesh_state** fp, struct mesh_state** lp) { if(m->next) m->next->prev = m->prev; else *lp = m->prev; if(m->prev) m->prev->next = m->next; else *fp = m->next; } void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m, struct comm_point* cp) { struct mesh_reply* n, *prev = NULL; n = m->reply_list; /* when in mesh_cleanup, it sets the reply_list to NULL, so that * there is no accounting twice */ if(!n) return; /* nothing to remove, also no accounting needed */ while(n) { if(n->query_reply.c == cp) { /* unlink it */ if(prev) prev->next = n->next; else m->reply_list = n->next; /* delete it, but allocated in m region */ log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; infra_wait_limit_dec(mesh->env->infra_cache, &n->query_reply, mesh->env->cfg); /* We may be removing more than one http2 stream (they * share the same comm_point); make sure the streams * don't point back. */ if(n->h2_stream) n->h2_stream->mesh_state = NULL; /* prev = prev; */ n = n->next; continue; } prev = n; n = n->next; } /* it was not detached (because it had a reply list), could be now */ if(!m->reply_list && !m->cb_list && m->super_set.count == 0) { mesh->num_detached_states++; } /* if not replies any more in mstate, it is no longer a reply_state */ if(!m->reply_list && !m->cb_list) { log_assert(mesh->num_reply_states > 0); mesh->num_reply_states--; } } static int apply_respip_action(struct module_qstate* qstate, const struct query_info* qinfo, struct respip_client_info* cinfo, struct respip_action_info* actinfo, struct reply_info* rep, struct ub_packed_rrset_key** alias_rrset, struct reply_info** encode_repp, struct auth_zones* az) { if(qinfo->qtype != LDNS_RR_TYPE_A && qinfo->qtype != LDNS_RR_TYPE_AAAA && qinfo->qtype != LDNS_RR_TYPE_ANY) return 1; if(!respip_rewrite_reply(qinfo, cinfo, rep, encode_repp, actinfo, alias_rrset, 0, qstate->region, az, NULL, qstate->env->views, qstate->env->respip_set)) return 0; /* xxx_deny actions mean dropping the reply, unless the original reply * was redirected to response-ip data. */ if((actinfo->action == respip_deny || actinfo->action == respip_inform_deny) && *encode_repp == rep) *encode_repp = NULL; return 1; } void mesh_serve_expired_callback(void* arg) { struct mesh_state* mstate = (struct mesh_state*) arg; struct module_qstate* qstate = &mstate->s; struct mesh_reply* r; struct mesh_area* mesh = qstate->env->mesh; struct dns_msg* msg; struct mesh_cb* c; struct mesh_reply* prev = NULL; struct sldns_buffer* prev_buffer = NULL; struct sldns_buffer* r_buffer = NULL; struct reply_info* partial_rep = NULL; struct ub_packed_rrset_key* alias_rrset = NULL; struct reply_info* encode_rep = NULL; struct respip_action_info actinfo; struct query_info* lookup_qinfo = &qstate->qinfo; struct query_info qinfo_tmp; struct timeval tv = {0, 0}; int must_validate = (!(qstate->query_flags&BIT_CD) || qstate->env->cfg->ignore_cd) && qstate->env->need_to_validate; int i = 0, for_count; int is_expired; if(!qstate->serve_expired_data) return; verbose(VERB_ALGO, "Serve expired: Trying to reply with expired data"); comm_timer_delete(qstate->serve_expired_data->timer); qstate->serve_expired_data->timer = NULL; /* If is_drop or no_cache_lookup (modules that handle their own cache e.g., * subnetmod) ignore stale data from the main cache. */ if(qstate->no_cache_lookup || qstate->is_drop) { verbose(VERB_ALGO, "Serve expired: Not allowed to look into cache for stale"); return; } /* The following for is used instead of the `goto lookup_cache` * like in the worker. This loop should get max 2 passes if we need to * do any aliasing. */ for(for_count = 0; for_count < 2; for_count++) { fptr_ok(fptr_whitelist_serve_expired_lookup( qstate->serve_expired_data->get_cached_answer)); msg = (*qstate->serve_expired_data->get_cached_answer)(qstate, lookup_qinfo, &is_expired); if(!msg || (FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR && FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NXDOMAIN && FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_YXDOMAIN)) { /* We don't care for cached failure answers at this * stage. */ return; } /* Reset these in case we pass a second time from here. */ encode_rep = msg->rep; memset(&actinfo, 0, sizeof(actinfo)); actinfo.action = respip_none; alias_rrset = NULL; if((mesh->use_response_ip || mesh->use_rpz) && !partial_rep && !apply_respip_action(qstate, &qstate->qinfo, qstate->client_info, &actinfo, msg->rep, &alias_rrset, &encode_rep, qstate->env->auth_zones)) { return; } else if(partial_rep && !respip_merge_cname(partial_rep, &qstate->qinfo, msg->rep, qstate->client_info, must_validate, &encode_rep, qstate->region, qstate->env->auth_zones, qstate->env->views, qstate->env->respip_set)) { return; } if(!encode_rep || alias_rrset) { if(!encode_rep) { /* Needs drop */ return; } else { /* A partial CNAME chain is found. */ partial_rep = encode_rep; } } /* We've found a partial reply ending with an * alias. Replace the lookup qinfo for the * alias target and lookup the cache again to * (possibly) complete the reply. As we're * passing the "base" reply, there will be no * more alias chasing. */ if(partial_rep) { memset(&qinfo_tmp, 0, sizeof(qinfo_tmp)); get_cname_target(alias_rrset, &qinfo_tmp.qname, &qinfo_tmp.qname_len); if(!qinfo_tmp.qname) { log_err("Serve expired: unexpected: invalid answer alias"); return; } qinfo_tmp.qtype = qstate->qinfo.qtype; qinfo_tmp.qclass = qstate->qinfo.qclass; lookup_qinfo = &qinfo_tmp; continue; } break; } if(verbosity >= VERB_ALGO) log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep); for(r = mstate->reply_list; r; r = r->next) { struct timeval old; timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time); if(mstate->s.env->cfg->discard_timeout != 0 && ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 > mstate->s.env->cfg->discard_timeout) { /* Drop the reply, it is too old */ /* briefly set the reply_list to NULL, so that the * tcp req info cleanup routine that calls the mesh * to deregister the meshstate for it is not done * because the list is NULL and also accounting is not * done there, but instead we do that here. */ struct mesh_reply* reply_list = mstate->reply_list; verbose(VERB_ALGO, "drop reply, it is older than discard-timeout"); infra_wait_limit_dec(mstate->s.env->infra_cache, &r->query_reply, mstate->s.env->cfg); mstate->reply_list = NULL; if(r->query_reply.c->use_h2) http2_stream_remove_mesh_state(r->h2_stream); comm_point_drop_reply(&r->query_reply); mstate->reply_list = reply_list; mstate->s.env->mesh->num_queries_discard_timeout++; continue; } i++; tv = r->start_time; /* If address info is returned, it means the action should be an * 'inform' variant and the information should be logged. */ if(actinfo.addrinfo) { respip_inform_print(&actinfo, r->qname, qstate->qinfo.qtype, qstate->qinfo.qclass, r->local_alias, &r->query_reply.client_addr, r->query_reply.client_addrlen); } /* Add EDE Stale Answer (RCF8914). Ignore global ede as this is * warning instead of an error */ if(r->edns.edns_present && qstate->env->cfg->ede_serve_expired && qstate->env->cfg->ede && is_expired) { edns_opt_list_append_ede(&r->edns.opt_list_out, mstate->s.region, LDNS_EDE_STALE_ANSWER, NULL); } r_buffer = r->query_reply.c->buffer; if(r->query_reply.c->tcp_req_info) r_buffer = r->query_reply.c->tcp_req_info->spool_buffer; mesh_send_reply(mstate, LDNS_RCODE_NOERROR, msg->rep, r, r_buffer, prev, prev_buffer); if(r->query_reply.c->tcp_req_info) tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate); /* mesh_send_reply removed mesh state from http2_stream. */ infra_wait_limit_dec(mstate->s.env->infra_cache, &r->query_reply, mstate->s.env->cfg); prev = r; prev_buffer = r_buffer; } /* Account for each reply sent. */ if(i > 0) { mesh->ans_expired += i; if(actinfo.addrinfo && qstate->env->cfg->stat_extended && actinfo.rpz_used) { if(actinfo.rpz_disabled) qstate->env->mesh->rpz_action[RPZ_DISABLED_ACTION] += i; if(actinfo.rpz_cname_override) qstate->env->mesh->rpz_action[RPZ_CNAME_OVERRIDE_ACTION] += i; else qstate->env->mesh->rpz_action[ respip_action_to_rpz_action(actinfo.action)] += i; } } /* Mesh area accounting */ if(mstate->reply_list) { mstate->reply_list = NULL; if(!mstate->reply_list && !mstate->cb_list) { log_assert(mesh->num_reply_states > 0); mesh->num_reply_states--; if(mstate->super_set.count == 0) { mesh->num_detached_states++; } } } while((c = mstate->cb_list) != NULL) { /* take this cb off the list; so that the list can be * changed, eg. by adds from the callback routine */ if(!mstate->reply_list && mstate->cb_list && !c->next) { /* was a reply state, not anymore */ log_assert(qstate->env->mesh->num_reply_states > 0); qstate->env->mesh->num_reply_states--; } mstate->cb_list = c->next; if(!mstate->reply_list && !mstate->cb_list && mstate->super_set.count == 0) qstate->env->mesh->num_detached_states++; mesh_do_callback(mstate, LDNS_RCODE_NOERROR, msg->rep, c, &tv); } } void mesh_respond_serve_expired(struct mesh_state* mstate) { if(!mstate->s.serve_expired_data) mesh_serve_expired_init(mstate, -1); mesh_serve_expired_callback(mstate); } int mesh_jostle_exceeded(struct mesh_area* mesh) { if(mesh->all.count < mesh->max_reply_states) return 0; return 1; } void mesh_remove_callback(struct mesh_area* mesh, struct query_info* qinfo, uint16_t qflags, mesh_cb_func_type cb, void* cb_arg) { struct mesh_state* s = NULL; s = mesh_area_find(mesh, NULL, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0); if(!s) return; if(!mesh_state_del_cb(s, cb, cb_arg)) return; /* It was in the list and removed. */ log_assert(mesh->num_reply_addrs > 0); mesh->num_reply_addrs--; if(!s->reply_list && !s->cb_list) { /* was a reply state, not anymore */ log_assert(mesh->num_reply_states > 0); mesh->num_reply_states--; } if(!s->reply_list && !s->cb_list && s->super_set.count == 0) { mesh->num_detached_states++; } } unbound-1.25.1/services/authzone.h0000644000175000017500000007403715203270263016552 0ustar wouterwouter/* * services/authzone.h - authoritative zone that is locally hosted. * * Copyright (c) 2017, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains the functions for an authority zone. This zone * is queried by the iterator, just like a stub or forward zone, but then * the data is locally held. */ #ifndef SERVICES_AUTHZONE_H #define SERVICES_AUTHZONE_H #include "util/rbtree.h" #include "util/locks.h" #include "services/mesh.h" #include "services/rpz.h" struct ub_packed_rrset_key; struct regional; struct config_file; struct config_auth; struct query_info; struct dns_msg; struct edns_data; struct module_env; struct worker; struct comm_point; struct comm_timer; struct comm_reply; struct auth_rrset; struct auth_nextprobe; struct auth_probe; struct auth_transfer; struct auth_master; struct auth_chunk; /** * Authoritative zones, shared. */ struct auth_zones { /** lock on the authzone trees. It is locked after views, respip, * local_zones and before fwds and stubs. */ lock_rw_type lock; /** rbtree of struct auth_zone */ rbtree_type ztree; /** rbtree of struct auth_xfer */ rbtree_type xtree; /** do we have downstream enabled */ int have_downstream; /** first auth zone containing rpz item in linked list */ struct auth_zone* rpz_first; /** rw lock for rpz linked list, needed when iterating or editing linked * list. */ lock_rw_type rpz_lock; }; /** * Auth zone. Authoritative data, that is fetched from instead of sending * packets to the internet. */ struct auth_zone { /** rbtree node, key is name and class */ rbnode_type node; /** zone name, in uncompressed wireformat */ uint8_t* name; /** length of zone name */ size_t namelen; /** number of labels in zone name */ int namelabs; /** the class of this zone, in host byteorder. * 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_type lock; /** auth data for this zone * rbtree of struct auth_data */ rbtree_type data; /** zonefile name (or NULL for no zonefile) */ char* zonefile; /** fallback to the internet on failure or ttl-expiry of auth zone */ int fallback_enabled; /** the time when zone was transferred from upstream */ time_t soa_zone_acquired; /** the zone has expired (enabled by the xfer worker), fallback * happens if that option is enabled. */ int zone_expired; /** zone is a slave zone (it has masters) */ int zone_is_slave; /** for downstream: this zone answers queries towards the downstream * clients */ int for_downstream; /** for upstream: this zone answers queries that unbound intends to * send upstream. */ int for_upstream; /** check ZONEMD records */ int zonemd_check; /** reject absence of ZONEMD records */ int zonemd_reject_absence; /** RPZ zones */ struct rpz* rpz; /** store the env (worker thread specific) for the zonemd callbacks * from the mesh with the results of the lookup, if nonNULL, some * worker has already picked up the zonemd verification task and * this worker does not have to do it as well. */ struct module_env* zonemd_callback_env; /** for the zonemd callback, the type of data looked up */ uint16_t zonemd_callback_qtype; /** zone has been deleted */ int zone_deleted; /** deletelist pointer, unused normally except during delete */ struct auth_zone* delete_next; /* not protected by auth_zone lock, must be last items in struct */ /** next auth zone containing RPZ data, or NULL */ struct auth_zone* rpz_az_next; /** previous auth zone containing RPZ data, or NULL */ struct auth_zone* rpz_az_prev; }; /** * Auth data. One domain name, and the RRs to go with it. */ struct auth_data { /** rbtree node, key is name only */ rbnode_type node; /** domain name */ uint8_t* name; /** length of name */ size_t namelen; /** number of labels in name */ int namelabs; /** the data rrsets, with different types, linked list. * if the list if NULL the node would be an empty non-terminal, * but in this data structure such nodes that represent an empty * non-terminal are not needed; they just don't exist. */ struct auth_rrset* rrsets; }; /** * A auth data RRset */ struct auth_rrset { /** next in list */ struct auth_rrset* next; /** RR type in host byteorder */ uint16_t type; /** RRset data item */ struct packed_rrset_data* data; }; /** * Authoritative zone transfer structure. * Create and destroy needs the auth_zones* biglock. * The structure consists of different tasks. Each can be unowned (-1) or * owner by a worker (worker-num). A worker can pick up a task and then do * it. This means the events (timeouts, sockets) are for that worker. * * (move this to tasks). * They don't have locks themselves, the worker (that owns it) uses it, * also as part of callbacks, hence it has separate zonename pointers for * lookup in the main zonetree. If the zone has no transfers, this * structure is not created. */ struct auth_xfer { /** rbtree node, key is name and class */ rbnode_type node; /** lock on this structure, and on the workernum elements of the * tasks. First hold the tree-lock in auth_zones, find the auth_xfer, * lock this lock. Then a worker can reassign itself to fill up * one of the tasks. * Once it has the task assigned to it, the worker can access the * other elements of the task structure without a lock, because that * is necessary for the eventloop and callbacks from that. * The auth_zone->lock is locked before this lock. */ lock_basic_type lock; /** zone name, in uncompressed wireformat */ uint8_t* name; /** length of zone name */ size_t namelen; /** number of labels in zone name */ int namelabs; /** the class of this zone, in host byteorder. * uses 'dclass' to not conflict with c++ keyword class. */ uint16_t dclass; /** task to wait for next-probe-timeout, * once timeouted, see if a SOA probe is needed, or already * in progress */ struct auth_nextprobe* task_nextprobe; /** task for SOA probe. Check if the zone can be updated */ struct auth_probe* task_probe; /** Task for transfer. Transferring and updating the zone. This * includes trying (potentially) several upstream masters. Downloading * and storing the zone */ struct auth_transfer* task_transfer; /** a notify was received, but a zone transfer or probe was already * acted on. * However, the zone transfer could signal a newer serial number. * The serial number of that notify is saved below. The transfer and * probe tasks should check this once done to see if they need to * restart the transfer task for the newer notify serial. * Hold the lock to access this member (and the serial). */ int notify_received; /** true if the notify_received has a serial number */ int notify_has_serial; /** serial number of the notify */ uint32_t notify_serial; /** the list of masters for checking notifies. This list is * empty on start, and a copy of the list from the probe_task when * it is done looking them up. */ struct auth_master* allow_notify_list; /* protected by the lock on the structure, information about * the loaded authority zone. */ /** is the zone currently considered expired? after expiry also older * serial numbers are allowed (not just newer) */ int zone_expired; /** do we have a zone (if 0, no zone data at all) */ int have_zone; /** the time when zone was transferred from upstream */ time_t soa_zone_acquired; /** current serial (from SOA), if we have no zone, 0 */ uint32_t serial; /** retry time (from SOA), time to wait with next_probe * if no master responds */ time_t retry; /** refresh time (from SOA), time to wait with next_probe * if everything is fine */ time_t refresh; /** expiry time (from SOA), time until zone data is not considered * valid any more, if no master responds within this time, either * with the current zone or a new zone. */ time_t expiry; /** zone lease start time (start+expiry is expiration time). * this is renewed every SOA probe and transfer. On zone load * from zonefile it is also set (with probe set soon to check) */ time_t lease_time; }; /** * The next probe task. * This task consists of waiting for the probetimeout. It is a task because * it needs an event in the eventtable. Once the timeout has passed, that * worker can (potentially) become the auth_probe worker, or if another worker * is already doing that, do nothing. Tasks becomes unowned. * The probe worker, if it detects nothing has to be done picks up this task, * if unowned. */ struct auth_nextprobe { /* Worker pointer. NULL means unowned. */ struct worker* worker; /* module env for this task */ struct module_env* env; /** increasing backoff for failures */ time_t backoff; /** Timeout for next probe (for SOA) */ time_t next_probe; /** timeout callback for next_probe or expiry(if that is sooner). * it is on the worker's event_base */ struct comm_timer* timer; }; /** * The probe task. * Send a SOA UDP query to see if the zone needs to be updated (or similar, * potential, HTTP probe query) and check serial number. * If yes, start the auth_transfer task. If no, make sure auth_nextprobe * timeout wait task is running. * Needs to be a task, because the UDP query needs an event entry. * This task could also be started by eg. a NOTIFY being received, even though * another worker is performing the nextprobe task (and that worker keeps * waiting uninterrupted). */ struct auth_probe { /* Worker pointer. NULL means unowned. */ struct worker* worker; /* module env for this task */ struct module_env* env; /** list of upstream masters for this zone, from config */ struct auth_master* masters; /** for the hostname lookups, which master is current */ struct auth_master* lookup_target; /** are we looking up A or AAAA, first A, then AAAA (if ip6 enabled) */ int lookup_aaaa; /** we only want to do lookups for making config work (for notify), * don't proceed with UDP SOA probe queries */ int only_lookup; /** we have seen a new lease this scan, because one of the masters * replied with the current SOA serial version */ int have_new_lease; /** once notified, or the timeout has been reached. a scan starts. */ /** the scan specific target (notify source), or NULL if none */ struct auth_master* scan_specific; /** scan tries all the upstream masters. the scan current target. * or NULL if not working on sequential scan */ struct auth_master* scan_target; /** if not NULL, the specific addr for the current master */ struct auth_addr* scan_addr; /** dns id of packet in flight */ uint16_t id; /** the SOA probe udp event. * on the workers event base. */ struct comm_point* cp; /** is the cp for ip6 or ip4 */ int cp_is_ip6; /** timeout for packets. * on the workers event base. */ struct comm_timer* timer; /** timeout in msec */ int timeout; }; /** * The transfer task. * Once done, make sure the nextprobe waiting task is running, whether done * with failure or success. If failure, use shorter timeout for wait time. */ struct auth_transfer { /* Worker pointer. NULL means unowned. */ struct worker* worker; /* module env for this task */ struct module_env* env; /** xfer data that has been transferred, the data is applied * once the transfer has completed correctly */ struct auth_chunk* chunks_first; /** last element in chunks list (to append new data at the end) */ struct auth_chunk* chunks_last; /** list of upstream masters for this zone, from config */ struct auth_master* masters; /** for the hostname lookups, which master is current */ struct auth_master* lookup_target; /** are we looking up A or AAAA, first A, then AAAA (if ip6 enabled) */ int lookup_aaaa; /** once notified, or the timeout has been reached. a scan starts. */ /** the scan specific target (notify source), or NULL if none */ struct auth_master* scan_specific; /** scan tries all the upstream masters. the scan current target. * or NULL if not working on sequential scan */ struct auth_master* scan_target; /** what address we are scanning for the master, or NULL if the * master is in IP format itself */ struct auth_addr* scan_addr; /** the zone transfer in progress (or NULL if in scan). It is * from this master */ struct auth_master* master; /** failed ixfr transfer, retry with axfr (to the current master), * the IXFR was 'REFUSED', 'SERVFAIL', 'NOTIMPL' or the contents of * the IXFR did not apply cleanly (out of sync, delete of nonexistent * data or add of duplicate data). Flag is cleared once the retry * with axfr is done. */ int ixfr_fail; /** we saw an ixfr-indicating timeout, count of them */ int ixfr_possible_timeout_count; /** we are doing IXFR right now */ int on_ixfr; /** did we detect the current AXFR/IXFR serial number yet, 0 not yet, * 1 we saw the first, 2 we saw the second, 3 must be last SOA in xfr*/ int got_xfr_serial; /** number of RRs scanned for AXFR/IXFR detection */ size_t rr_scan_num; /** we are doing an IXFR but we detected an AXFR contents */ int on_ixfr_is_axfr; /** the serial number for the current AXFR/IXFR incoming reply, * for IXFR, the outermost SOA records serial */ uint32_t incoming_xfr_serial; /** dns id of AXFR query */ uint16_t id; /** the transfer (TCP) to the master. * on the workers event base. */ struct comm_point* cp; /** timeout for the transfer. * on the workers event base. */ struct comm_timer* timer; }; /** list of addresses */ struct auth_addr { /** next in list */ struct auth_addr* next; /** IP address */ struct sockaddr_storage addr; /** addr length */ socklen_t addrlen; }; /** auth zone master upstream, and the config settings for it */ struct auth_master { /** next master in list */ struct auth_master* next; /** master IP address (and port), or hostname, string */ char* host; /** for http, filename */ char* file; /** use HTTP for this master */ int http; /** use IXFR for this master */ int ixfr; /** this is an allow notify member, the master can send notifies * to us, but we don't send SOA probes, or zone transfer from it */ int allow_notify; /** use ssl for channel */ int ssl; /** the port number (for urls) */ int port; /** if the host is a hostname, the list of resolved addrs, if any*/ struct auth_addr* list; }; /** auth zone master zone transfer data chunk */ struct auth_chunk { /** next chunk in list */ struct auth_chunk* next; /** the data from this chunk, this is what was received. * for an IXFR that means results from comm_net tcp actions, * packets. also for an AXFR. For HTTP a zonefile chunk. */ uint8_t* data; /** length of allocated data */ size_t len; }; /** * Create auth zones structure */ struct auth_zones* auth_zones_create(void); /** * Apply configuration to auth zones. Reads zonefiles. * @param az: auth zones structure * @param cfg: config to apply. * @param setup: if true, also sets up values in the auth zones structure * @param is_rpz: set to 1 if at least one RPZ zone is configured. * @param env: environment for offline verification. * @param mods: modules in environment. * @return false on failure. */ int auth_zones_apply_cfg(struct auth_zones* az, struct config_file* cfg, int setup, int* is_rpz, struct module_env* env, struct module_stack* mods); /** initial pick up of worker timeouts, ties events to worker event loop * @param az: auth zones structure * @param env: worker env, of first worker that receives the events (if any) * in its eventloop. */ void auth_xfer_pickup_initial(struct auth_zones* az, struct module_env* env); /** * Cleanup auth zones. This removes all events from event bases. * Stops the xfr tasks. But leaves zone data. * @param az: auth zones structure. */ void auth_zones_cleanup(struct auth_zones* az); /** * Delete auth zones structure */ void auth_zones_delete(struct auth_zones* az); /** * Write auth zone data to file, in zonefile format. */ int auth_zone_write_file(struct auth_zone* z, const char* fname); /** * Use auth zones to lookup the answer to a query. * The query is from the iterator. And the auth zones attempts to provide * the answer instead of going to the internet. * * @param az: auth zones structure. * @param qinfo: query info to lookup. * @param region: region to use to allocate the reply in. * @param msg: reply is stored here (if one). * @param fallback: if true, fallback to making a query to the internet. * @param dp_nm: name of delegation point to look for. This zone is used * to answer the query. * If the dp_nm is not found, fallback is set to true and false returned. * @param dp_nmlen: length of dp_nm. * @return 0: failure (an error of some sort, like servfail). * if 0 and fallback is true, fallback to the internet. * if 0 and fallback is false, like getting servfail. * If true, an answer is available. */ int auth_zones_lookup(struct auth_zones* az, struct query_info* qinfo, struct regional* region, struct dns_msg** msg, int* fallback, uint8_t* dp_nm, size_t dp_nmlen); /** * Answer query from auth zone. Create authoritative answer. * @param az: auth zones structure. * @param env: the module environment. * @param qinfo: query info (parsed). * @param edns: edns info (parsed). * @param buf: buffer with query ID and flags, also for reply. * @param repinfo: reply information for a communication point. * @param temp: temporary storage region. * @return false if not answered */ int auth_zones_downstream_answer(struct auth_zones* az, struct module_env* env, struct query_info* qinfo, struct edns_data* edns, struct comm_reply* repinfo, struct sldns_buffer* buf, struct regional* temp); /** * Find the auth zone that is above the given qname. * Return NULL when there is no auth_zone above the give name, otherwise * returns the closest auth_zone above the qname that pertains to it. * @param az: auth zones structure. * @param name: query to look up for. * @param name_len: length of name. * @param dclass: class of zone to find. * @return NULL or auth_zone that pertains to the query. */ struct auth_zone* auth_zones_find_zone(struct auth_zones* az, uint8_t* name, size_t name_len, uint16_t dclass); /** find an auth zone by name (exact match by name or NULL returned) */ struct auth_zone* auth_zone_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass); /** find an xfer zone by name (exact match by name or NULL returned) */ struct auth_xfer* auth_xfer_find(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass); /** create an auth zone. returns wrlocked zone. caller must have wrlock * on az. returns NULL on malloc failure */ struct auth_zone* auth_zone_create(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass); /** set auth zone zonefile string. caller must have lock on zone */ int auth_zone_set_zonefile(struct auth_zone* z, char* zonefile); /** set auth zone fallback. caller must have lock on zone. * fallbackstr is "yes" or "no". false on parse failure. */ int auth_zone_set_fallback(struct auth_zone* z, char* fallbackstr); /** see if the auth zone for the name can fallback * @param az: auth zones * @param nm: name of delegation point. * @param nmlen: length of nm. * @param dclass: class of zone to look for. * @return true if fallback_enabled is true. false if not. * if the zone does not exist, fallback is true (more lenient) * also true if zone does not do upstream requests. */ int auth_zones_can_fallback(struct auth_zones* az, uint8_t* nm, size_t nmlen, uint16_t dclass); /** process notify for auth zones. * first checks the access list. Then processes the notify. This starts * the probe sequence or it notes the serial number (if any) * @param az: auth zones structure. * @param env: module env of the worker that is handling the notify. it will * pick up the task probe (or transfer), unless already in progress by * another worker. * @param nm: name of the zone. Uncompressed. from query. * @param nmlen: length of name. * @param dclass: class of zone. * @param addr: source address of notify * @param addrlen: length of addr. * @param has_serial: if true, the notify has a serial attached. * @param serial: the serial number, if has_serial is true. * @param refused: is set to true on failure to note refused access. * @return fail on failures (refused is false) and when access is * denied (refused is true). True when processed. */ int auth_zones_notify(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass, struct sockaddr_storage* addr, socklen_t addrlen, int has_serial, uint32_t serial, int* refused); /** process notify packet and read serial number from SOA. * returns 0 if no soa record in the notify */ int auth_zone_parse_notify_serial(struct sldns_buffer* pkt, uint32_t *serial); /** for the zone and if not already going, starts the probe sequence. * false if zone cannot be found. This is like a notify arrived and was * accepted for that zone. */ int auth_zones_startprobesequence(struct auth_zones* az, struct module_env* env, uint8_t* nm, size_t nmlen, uint16_t dclass); /** read auth zone from zonefile. caller must lock zone. false on failure */ int auth_zone_read_zonefile(struct auth_zone* z, struct config_file* cfg); /** find the apex SOA RRset, if it exists. NULL if no SOA RRset. */ struct auth_rrset* auth_zone_get_soa_rrset(struct auth_zone* z); /** find serial number of zone or false if none (no SOA record) */ int auth_zone_get_serial(struct auth_zone* z, uint32_t* serial); /** Find auth_zone SOA and populate the values in xfr(soa values). */ int xfr_find_soa(struct auth_zone* z, struct auth_xfer* xfr); /** compare auth_zones for sorted rbtree */ int auth_zone_cmp(const void* z1, const void* z2); /** compare auth_data for sorted rbtree */ int auth_data_cmp(const void* z1, const void* z2); /** compare auth_xfer for sorted rbtree */ int auth_xfer_cmp(const void* z1, const void* z2); /** Create auth_xfer structure. * Caller must have wrlock on az. Returns locked xfer zone. * @param az: zones structure. * @param z: zone with name and class * @return xfer zone or NULL */ struct auth_xfer* auth_xfer_create(struct auth_zones* az, struct auth_zone* z); /** * Set masters in auth xfer structure from config. * @param list: pointer to start of list. The malloced list is returned here. * @param c: the config items to copy over. * @param with_http: if true, http urls are also included, before the masters. * @return false on failure. */ int xfer_set_masters(struct auth_master** list, struct config_auth* c, int with_http); /** xfer nextprobe timeout callback, this is part of task_nextprobe */ void auth_xfer_timer(void* arg); /** callback for commpoint udp replies to task_probe */ int auth_xfer_probe_udp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo); /** callback for task_transfer tcp connections */ int auth_xfer_transfer_tcp_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo); /** callback for task_transfer http connections */ int auth_xfer_transfer_http_callback(struct comm_point* c, void* arg, int err, struct comm_reply* repinfo); /** xfer probe timeout callback, part of task_probe */ void auth_xfer_probe_timer_callback(void* arg); /** xfer transfer timeout callback, part of task_transfer */ void auth_xfer_transfer_timer_callback(void* arg); /** mesh callback for task_probe on lookup of host names */ void auth_xfer_probe_lookup_callback(void* arg, int rcode, struct sldns_buffer* buf, enum sec_status sec, char* why_bogus, int was_ratelimited); /** mesh callback for task_transfer on lookup of host names */ void auth_xfer_transfer_lookup_callback(void* arg, int rcode, struct sldns_buffer* buf, enum sec_status sec, char* why_bogus, int was_ratelimited); /* * Compares two 32-bit serial numbers as defined in RFC1982. Returns * <0 if a < b, 0 if a == b, and >0 if a > b. The result is undefined * if a != b but neither is greater or smaller (see RFC1982 section * 3.2.). */ int compare_serial(uint32_t a, uint32_t b); /** * Generate ZONEMD digest for the auth zone. * @param z: the auth zone to digest. * omits zonemd at apex and its RRSIG from the digest. * @param scheme: the collation scheme to use. Numbers as defined for ZONEMD. * @param hashalgo: the hash algo, from the registry defined for ZONEMD type. * @param hash: the result buffer. * @param buflen: size of the result buffer, must be large enough. or the * routine fails. * @param resultlen: size of the hash in the result buffer of the result. * @param region: temp region for allocs during canonicalisation. * @param buf: temp buffer during canonicalisation. * @param reason: failure reason, returns a string, NULL on success. * @return false on failure. */ int auth_zone_generate_zonemd_hash(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t buflen, size_t* resultlen, struct regional* region, struct sldns_buffer* buf, char** reason); /** ZONEMD scheme definitions */ #define ZONEMD_SCHEME_SIMPLE 1 /** ZONEMD hash algorithm definition for SHA384 */ #define ZONEMD_ALGO_SHA384 1 /** ZONEMD hash algorithm definition for SHA512 */ #define ZONEMD_ALGO_SHA512 2 /** returns true if a zonemd hash algo is supported */ int zonemd_hashalgo_supported(int hashalgo); /** returns true if a zonemd scheme is supported */ int zonemd_scheme_supported(int scheme); /** * Check ZONEMD digest for the auth zone. * @param z: auth zone to digest. * @param scheme: zonemd scheme. * @param hashalgo: zonemd hash algorithm. * @param hash: the hash to check. * @param hashlen: length of hash buffer. * @param region: temp region for allocs during canonicalisation. * @param buf: temp buffer during canonicalisation. * @param reason: string returned with failure reason. * If the hash cannot be checked, but it is allowed, for unknown * algorithms, the routine returns success, and the reason is nonNULL, * with the allowance reason. * @return false on failure. */ int auth_zone_generate_zonemd_check(struct auth_zone* z, int scheme, int hashalgo, uint8_t* hash, size_t hashlen, struct regional* region, struct sldns_buffer* buf, char** reason); /** * Perform ZONEMD checks and verification for the auth zone. * This includes DNSSEC verification if applicable. * @param z: auth zone to check. Caller holds lock. wrlock. * @param env: with temp region, buffer and config. * @param mods: module stack for validator env. * @param result: if not NULL, result string strdupped in here. * @param offline: if true, there is no spawned lookup when online is needed. * Those zones are skipped for ZONEMD checking. * @param only_online: if true, only for ZONEMD that need online lookup * of DNSKEY chain of trust are processed. */ void auth_zone_verify_zonemd(struct auth_zone* z, struct module_env* env, struct module_stack* mods, char** result, int offline, int only_online); /** mesh callback for zonemd on lookup of dnskey */ void auth_zonemd_dnskey_lookup_callback(void* arg, int rcode, struct sldns_buffer* buf, enum sec_status sec, char* why_bogus, int was_ratelimited); /** * Check the ZONEMD records that need online DNSSEC chain lookups, * for them spawn the lookup process to get it checked out. * Attaches the lookup process to the worker event base and mesh state. * @param az: auth zones, every zones is checked. * @param env: env of the worker where the task is attached. */ void auth_zones_pickup_zonemd_verify(struct auth_zones* az, struct module_env* env); /** Get memory usage for auth zones. The routine locks and unlocks * for reading. */ size_t auth_zones_get_mem(struct auth_zones* zones); /** * Initial pick up of the auth zone nextprobe timeout and that turns * into further zone transfer work, if any. Also sets the lease time. * @param x: xfer structure, locked by caller. * @param env: environment of the worker that picks up the task. */ void auth_xfer_pickup_initial_zone(struct auth_xfer* x, struct module_env* env); /** * Initial pick up of the auth zone, it sets the acquired time. * @param z: the zone, write locked by caller. * @param env: environment of the worker, with current time. */ void auth_zone_pickup_initial_zone(struct auth_zone* z, struct module_env* env); /** * Delete auth xfer structure * @param xfr: delete this xfer and its tasks. */ void auth_xfer_delete(struct auth_xfer* xfr); /** * Disown tasks from the xfr that belong to this worker. * Only tasks for the worker in question, the comm point and timer * delete functions need to run in the thread of that worker to be * able to delete the callback from the event base. * @param xfr: xfr structure * @param worker: the worker for which to stop tasks. */ void xfr_disown_tasks(struct auth_xfer* xfr, struct worker* worker); #endif /* SERVICES_AUTHZONE_H */ unbound-1.25.1/iterator/0000755000175000017500000000000015203270263014537 5ustar wouterwouterunbound-1.25.1/iterator/iter_resptype.h0000644000175000017500000001062715203270263017614 0ustar wouterwouter/* * iterator/iter_resptype.h - response type information and classification. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file defines the response type. DNS Responses can be classified as * one of the response types. */ #ifndef ITERATOR_ITER_RESPTYPE_H #define ITERATOR_ITER_RESPTYPE_H struct dns_msg; struct query_info; struct delegpt; /** * The response type is used to interpret the response. */ enum response_type { /** * 'untyped' means that the type of this response hasn't been * assigned. */ RESPONSE_TYPE_UNTYPED = 0, /** * 'answer' means that the response terminates the resolution * process. */ RESPONSE_TYPE_ANSWER, /** 'delegation' means that the response is a delegation. */ RESPONSE_TYPE_REFERRAL, /** * 'cname' means that the response is a cname without the final * answer, and thus must be restarted. */ RESPONSE_TYPE_CNAME, /** * 'throwaway' means that this particular response should be * discarded and the next nameserver should be contacted */ RESPONSE_TYPE_THROWAWAY, /** * 'lame' means that this particular response indicates that * the nameserver knew nothing about the question. */ RESPONSE_TYPE_LAME, /** * Recursion lame means that the nameserver is some sort of * open recursor, and not authoritative for the question. * It may know something, but not authoritatively. */ RESPONSE_TYPE_REC_LAME }; /** * Classifies a response message from cache based on the current request. * Note that this routine assumes that THROWAWAY or LAME responses will not * occur. Also, it will not detect REFERRAL type messages, since those are * (currently) automatically classified based on how they came from the * cache (findDelegation() instead of lookup()). * * @param msg: the message from the cache. * @param request: the request that generated the response. * @return the response type (CNAME or ANSWER). */ enum response_type response_type_from_cache(struct dns_msg* msg, struct query_info* request); /** * Classifies a response message (from the wire) based on the current * request. * * NOTE: currently this routine uses the AA bit in the response to help * distinguish between some non-standard referrals and answers. It also * relies somewhat on the originating zone to be accurate (for lameness * detection, mostly). * * @param rdset: if RD bit was sent in query sent by unbound. * @param msg: the message from the cache. * @param request: the request that generated the response. * @param dp: The delegation point that was being queried * when the response was returned. * @param empty_nodata_found: flag to keep track of empty nodata detection. * @return the response type (CNAME or ANSWER). */ enum response_type response_type_from_server(int rdset, struct dns_msg* msg, struct query_info* request, struct delegpt* dp, int* empty_nodata_found); #endif /* ITERATOR_ITER_RESPTYPE_H */ unbound-1.25.1/iterator/iter_fwd.c0000644000175000017500000004302415203270263016511 0ustar wouterwouter/* * iterator/iter_fwd.c - iterative resolver module forward zones. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of forward zones and config settings. */ #include "config.h" #include "iterator/iter_fwd.h" #include "iterator/iter_delegpt.h" #include "util/log.h" #include "util/config_file.h" #include "util/net_help.h" #include "util/data/dname.h" #include "sldns/rrdef.h" #include "sldns/str2wire.h" int fwd_cmp(const void* k1, const void* k2) { int m; struct iter_forward_zone* n1 = (struct iter_forward_zone*)k1; struct iter_forward_zone* n2 = (struct iter_forward_zone*)k2; if(n1->dclass != n2->dclass) { if(n1->dclass < n2->dclass) return -1; return 1; } return dname_lab_cmp(n1->name, n1->namelabs, n2->name, n2->namelabs, &m); } struct iter_forwards* forwards_create(void) { struct iter_forwards* fwd = (struct iter_forwards*)calloc(1, sizeof(struct iter_forwards)); if(!fwd) return NULL; lock_rw_init(&fwd->lock); return fwd; } static void fwd_zone_free(struct iter_forward_zone* n) { if(!n) return; delegpt_free_mlc(n->dp); free(n->name); free(n); } static void delfwdnode(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct iter_forward_zone* node = (struct iter_forward_zone*)n; fwd_zone_free(node); } static void fwd_del_tree(struct iter_forwards* fwd) { if(fwd->tree) traverse_postorder(fwd->tree, &delfwdnode, NULL); free(fwd->tree); } void forwards_delete(struct iter_forwards* fwd) { if(!fwd) return; lock_rw_destroy(&fwd->lock); fwd_del_tree(fwd); free(fwd); } /** insert info into forward structure */ static int forwards_insert_data(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, size_t nmlen, int nmlabs, struct delegpt* dp) { struct iter_forward_zone* node = (struct iter_forward_zone*)malloc( sizeof(struct iter_forward_zone)); if(!node) { delegpt_free_mlc(dp); return 0; } node->node.key = node; node->dclass = c; node->name = memdup(nm, nmlen); if(!node->name) { delegpt_free_mlc(dp); free(node); return 0; } node->namelen = nmlen; node->namelabs = nmlabs; node->dp = dp; if(!rbtree_insert(fwd->tree, &node->node)) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(nm, buf); log_err("duplicate forward zone %s ignored.", buf); delegpt_free_mlc(dp); free(node->name); free(node); } return 1; } static struct iter_forward_zone* fwd_zone_find(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) { struct iter_forward_zone key; key.node.key = &key; key.dclass = c; key.name = nm; key.namelabs = dname_count_size_labels(nm, &key.namelen); return (struct iter_forward_zone*)rbtree_search(fwd->tree, &key); } /** insert new info into forward structure given dp */ static int forwards_insert(struct iter_forwards* fwd, uint16_t c, struct delegpt* dp) { return forwards_insert_data(fwd, c, dp->name, dp->namelen, dp->namelabs, dp); } /** initialise parent pointers in the tree */ static void fwd_init_parents(struct iter_forwards* fwd) { struct iter_forward_zone* node, *prev = NULL, *p; int m; RBTREE_FOR(node, struct iter_forward_zone*, fwd->tree) { node->parent = NULL; if(!prev || prev->dclass != node->dclass) { prev = node; continue; } (void)dname_lab_cmp(prev->name, prev->namelabs, node->name, node->namelabs, &m); /* we know prev is smaller */ /* sort order like: . com. bla.com. zwb.com. net. */ /* find the previous, or parent-parent-parent */ for(p = prev; p; p = p->parent) /* looking for name with few labels, a parent */ if(p->namelabs <= m) { /* ==: since prev matched m, this is closest*/ /* <: prev matches more, but is not a parent, * this one is a (grand)parent */ node->parent = p; break; } prev = node; } } /** set zone name */ static struct delegpt* read_fwds_name(struct config_stub* s) { struct delegpt* dp; uint8_t* dname; size_t dname_len; if(!s->name) { log_err("forward zone without a name (use name \".\" to forward everything)"); return NULL; } dname = sldns_str2wire_dname(s->name, &dname_len); if(!dname) { log_err("cannot parse forward zone name %s", s->name); return NULL; } if(!(dp=delegpt_create_mlc(dname))) { free(dname); log_err("out of memory"); return NULL; } free(dname); return dp; } /** set fwd host names */ static int read_fwds_host(struct config_stub* s, struct delegpt* dp) { struct config_strlist* p; uint8_t* dname; char* tls_auth_name; int port; for(p = s->hosts; p; p = p->next) { log_assert(p->str); dname = authextstrtodname(p->str, &port, &tls_auth_name); if(!dname) { log_err("cannot parse forward %s server name: '%s'", s->name, p->str); return 0; } if(dname_subdomain_c(dname, dp->name)) { log_warn("forward-host '%s' may have a circular " "dependency on forward-zone '%s'", p->str, s->name); } #if ! defined(HAVE_SSL_SET1_HOST) && ! defined(HAVE_X509_VERIFY_PARAM_SET1_HOST) if(tls_auth_name) log_err("no name verification functionality in " "ssl library, ignored name for %s", p->str); #endif if(!delegpt_add_ns_mlc(dp, dname, 0, tls_auth_name, port)) { free(dname); log_err("out of memory"); return 0; } free(dname); } return 1; } /** set fwd server addresses */ static int read_fwds_addr(struct config_stub* s, struct delegpt* dp) { struct config_strlist* p; struct sockaddr_storage addr; socklen_t addrlen; char* tls_auth_name; for(p = s->addrs; p; p = p->next) { log_assert(p->str); if(!authextstrtoaddr(p->str, &addr, &addrlen, &tls_auth_name)) { log_err("cannot parse forward %s ip address: '%s'", s->name, p->str); return 0; } #if ! defined(HAVE_SSL_SET1_HOST) && ! defined(HAVE_X509_VERIFY_PARAM_SET1_HOST) if(tls_auth_name) log_err("no name verification functionality in " "ssl library, ignored name for %s", p->str); #endif if(!delegpt_add_addr_mlc(dp, &addr, addrlen, 0, 0, tls_auth_name, -1)) { log_err("out of memory"); return 0; } } return 1; } /** read forwards config */ static int read_forwards(struct iter_forwards* fwd, struct config_file* cfg) { struct config_stub* s; for(s = cfg->forwards; s; s = s->next) { struct delegpt* dp; if(!(dp=read_fwds_name(s))) return 0; if(!read_fwds_host(s, dp) || !read_fwds_addr(s, dp)) { delegpt_free_mlc(dp); return 0; } /* set flag that parent side NS information is included. * Asking a (higher up) server on the internet is not useful */ /* the flag is turned off for 'forward-first' so that the * last resort will ask for parent-side NS record and thus * fallback to the internet name servers on a failure */ dp->has_parent_side_NS = (uint8_t)!s->isfirst; /* Do not cache if set. */ dp->no_cache = s->no_cache; /* use SSL for queries to this forwarder */ dp->ssl_upstream = (uint8_t)s->ssl_upstream; /* use TCP for queries to this forwarder */ dp->tcp_upstream = (uint8_t)s->tcp_upstream; verbose(VERB_QUERY, "Forward zone server list:"); delegpt_log(VERB_QUERY, dp); if(!forwards_insert(fwd, LDNS_RR_CLASS_IN, dp)) return 0; } return 1; } /** insert a stub hole (if necessary) for stub name */ static int fwd_add_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm) { struct iter_forward_zone key; key.node.key = &key; key.dclass = c; key.name = nm; key.namelabs = dname_count_size_labels(key.name, &key.namelen); return forwards_insert_data(fwd, key.dclass, key.name, key.namelen, key.namelabs, NULL); } /** make NULL entries for stubs */ static int make_stub_holes(struct iter_forwards* fwd, struct config_file* cfg) { struct config_stub* s; uint8_t* dname; size_t dname_len; for(s = cfg->stubs; s; s = s->next) { if(!s->name) continue; dname = sldns_str2wire_dname(s->name, &dname_len); if(!dname) { log_err("cannot parse stub name '%s'", s->name); return 0; } if(fwd_zone_find(fwd, LDNS_RR_CLASS_IN, dname) != NULL) { /* Already a forward zone there. */ free(dname); continue; } if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) { free(dname); log_err("out of memory"); return 0; } free(dname); } return 1; } /** make NULL entries for auths */ static int make_auth_holes(struct iter_forwards* fwd, struct config_file* cfg) { struct config_auth* a; uint8_t* dname; size_t dname_len; for(a = cfg->auths; a; a = a->next) { if(!a->name) continue; dname = sldns_str2wire_dname(a->name, &dname_len); if(!dname) { log_err("cannot parse auth name '%s'", a->name); return 0; } if(fwd_zone_find(fwd, LDNS_RR_CLASS_IN, dname) != NULL) { /* Already a forward zone there. */ free(dname); continue; } if(!fwd_add_stub_hole(fwd, LDNS_RR_CLASS_IN, dname)) { free(dname); log_err("out of memory"); return 0; } free(dname); } return 1; } int forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg) { if(fwd->tree) { lock_unprotect(&fwd->lock, fwd->tree); } fwd_del_tree(fwd); fwd->tree = rbtree_create(fwd_cmp); if(!fwd->tree) return 0; lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree)); lock_rw_wrlock(&fwd->lock); /* read forward zones */ if(!read_forwards(fwd, cfg)) { lock_rw_unlock(&fwd->lock); return 0; } if(!make_stub_holes(fwd, cfg)) { lock_rw_unlock(&fwd->lock); return 0; } /* TODO: Now we punch holes for auth zones as well so that in * iterator:forward_request() we see the configured * delegation point, but code flow/naming is hard to follow. * Consider having a single tree with configured * delegation points for all categories * (stubs, forwards, auths). */ if(!make_auth_holes(fwd, cfg)) { lock_rw_unlock(&fwd->lock); return 0; } fwd_init_parents(fwd); lock_rw_unlock(&fwd->lock); return 1; } struct delegpt* forwards_find(struct iter_forwards* fwd, uint8_t* qname, uint16_t qclass, int nolock) { struct iter_forward_zone* res; struct iter_forward_zone key; int has_dp; key.node.key = &key; key.dclass = qclass; key.name = qname; key.namelabs = dname_count_size_labels(qname, &key.namelen); /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&fwd->lock); } res = (struct iter_forward_zone*)rbtree_search(fwd->tree, &key); has_dp = res && res->dp; if(!has_dp && !nolock) { lock_rw_unlock(&fwd->lock); } return has_dp?res->dp:NULL; } struct delegpt* forwards_lookup(struct iter_forwards* fwd, uint8_t* qname, uint16_t qclass, int nolock) { /* lookup the forward zone in the tree */ rbnode_type* res = NULL; struct iter_forward_zone *result; struct iter_forward_zone key; int has_dp; key.node.key = &key; key.dclass = qclass; key.name = qname; key.namelabs = dname_count_size_labels(qname, &key.namelen); /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&fwd->lock); } if(rbtree_find_less_equal(fwd->tree, &key, &res)) { /* exact */ result = (struct iter_forward_zone*)res; } else { /* smaller element (or no element) */ int m; result = (struct iter_forward_zone*)res; if(!result || result->dclass != qclass) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return NULL; } /* count number of labels matched */ (void)dname_lab_cmp(result->name, result->namelabs, key.name, key.namelabs, &m); while(result) { /* go up until qname is subdomain of stub */ if(result->namelabs <= m) break; result = result->parent; } } has_dp = result && result->dp; if(!has_dp && !nolock) { lock_rw_unlock(&fwd->lock); } return has_dp?result->dp:NULL; } struct delegpt* forwards_lookup_root(struct iter_forwards* fwd, uint16_t qclass, int nolock) { uint8_t root = 0; return forwards_lookup(fwd, &root, qclass, nolock); } /* Finds next root item in forwards lookup tree. * Caller needs to handle locking of the forwards structure. */ static int next_root_locked(struct iter_forwards* fwd, uint16_t* dclass) { struct iter_forward_zone key; rbnode_type* n; struct iter_forward_zone* p; if(*dclass == 0) { /* first root item is first item in tree */ n = rbtree_first(fwd->tree); if(n == RBTREE_NULL) return 0; p = (struct iter_forward_zone*)n; if(dname_is_root(p->name)) { *dclass = p->dclass; return 1; } /* root not first item? search for higher items */ *dclass = p->dclass + 1; return next_root_locked(fwd, dclass); } /* find class n in tree, we may get a direct hit, or if we don't * this is the last item of the previous class so rbtree_next() takes * us to the next root (if any) */ key.node.key = &key; key.name = (uint8_t*)"\000"; key.namelen = 1; key.namelabs = 0; key.dclass = *dclass; n = NULL; if(rbtree_find_less_equal(fwd->tree, &key, &n)) { /* exact */ return 1; } else { /* smaller element */ if(!n || n == RBTREE_NULL) return 0; /* nothing found */ n = rbtree_next(n); if(n == RBTREE_NULL) return 0; /* no higher */ p = (struct iter_forward_zone*)n; if(dname_is_root(p->name)) { *dclass = p->dclass; return 1; } /* not a root node, return next higher item */ *dclass = p->dclass+1; return next_root_locked(fwd, dclass); } } int forwards_next_root(struct iter_forwards* fwd, uint16_t* dclass, int nolock) { int ret; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&fwd->lock); } ret = next_root_locked(fwd, dclass); if(!nolock) { lock_rw_unlock(&fwd->lock); } return ret; } size_t forwards_get_mem(struct iter_forwards* fwd) { struct iter_forward_zone* p; size_t s; if(!fwd) return 0; lock_rw_rdlock(&fwd->lock); s = sizeof(*fwd) + sizeof(*fwd->tree); RBTREE_FOR(p, struct iter_forward_zone*, fwd->tree) { s += sizeof(*p) + p->namelen + delegpt_get_mem(p->dp); } lock_rw_unlock(&fwd->lock); return s; } int forwards_add_zone(struct iter_forwards* fwd, uint16_t c, struct delegpt* dp, int nolock) { struct iter_forward_zone *z; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&fwd->lock); } if((z=fwd_zone_find(fwd, c, dp->name)) != NULL) { (void)rbtree_delete(fwd->tree, &z->node); fwd_zone_free(z); } if(!forwards_insert(fwd, c, dp)) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return 0; } fwd_init_parents(fwd); if(!nolock) { lock_rw_unlock(&fwd->lock); } return 1; } void forwards_delete_zone(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock) { struct iter_forward_zone *z; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&fwd->lock); } if(!(z=fwd_zone_find(fwd, c, nm))) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return; /* nothing to do */ } (void)rbtree_delete(fwd->tree, &z->node); fwd_zone_free(z); fwd_init_parents(fwd); if(!nolock) { lock_rw_unlock(&fwd->lock); } } int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock) { /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&fwd->lock); } if(fwd_zone_find(fwd, c, nm) != NULL) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return 1; /* already a stub zone there */ } if(!fwd_add_stub_hole(fwd, c, nm)) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return 0; } fwd_init_parents(fwd); if(!nolock) { lock_rw_unlock(&fwd->lock); } return 1; } void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock) { struct iter_forward_zone *z; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&fwd->lock); } if(!(z=fwd_zone_find(fwd, c, nm))) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return; /* nothing to do */ } if(z->dp != NULL) { if(!nolock) { lock_rw_unlock(&fwd->lock); } return; /* not a stub hole */ } (void)rbtree_delete(fwd->tree, &z->node); fwd_zone_free(z); fwd_init_parents(fwd); if(!nolock) { lock_rw_unlock(&fwd->lock); } } void forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data) { rbtree_type* oldtree = fwd->tree; if(oldtree) { lock_unprotect(&fwd->lock, oldtree); } if(data->tree) { lock_unprotect(&data->lock, data->tree); } fwd->tree = data->tree; data->tree = oldtree; lock_protect(&fwd->lock, fwd->tree, sizeof(*fwd->tree)); lock_protect(&data->lock, data->tree, sizeof(*data->tree)); } unbound-1.25.1/iterator/iter_utils.c0000644000175000017500000014501315203270263017072 0ustar wouterwouter/* * iterator/iter_utils.c - iterative resolver module utility functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Configuration options. Forward zones. */ #include "config.h" #include "iterator/iter_utils.h" #include "iterator/iterator.h" #include "iterator/iter_hints.h" #include "iterator/iter_fwd.h" #include "iterator/iter_donotq.h" #include "iterator/iter_delegpt.h" #include "iterator/iter_priv.h" #include "services/cache/infra.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "services/outside_network.h" #include "util/net_help.h" #include "util/module.h" #include "util/log.h" #include "util/config_file.h" #include "util/regional.h" #include "util/data/msgparse.h" #include "util/data/dname.h" #include "util/random.h" #include "util/fptr_wlist.h" #include "validator/val_anchor.h" #include "validator/val_kcache.h" #include "validator/val_kentry.h" #include "validator/val_utils.h" #include "validator/val_sigcrypt.h" #include "sldns/sbuffer.h" #include "sldns/str2wire.h" /** time when nameserver glue is said to be 'recent' */ #define SUSPICION_RECENT_EXPIRY 86400 /** if NAT64 is enabled and no NAT64 prefix is configured, first fall back to * DNS64 prefix. If that is not configured, fall back to this default value. */ static const char DEFAULT_NAT64_PREFIX[] = "64:ff9b::/96"; /** fillup fetch policy array */ static int fetch_fill(int* target_fetch_policy, int max_dependency_depth, const char* str) { char* s = (char*)str, *e; int i; for(i=0; iname); free(n); } } void caps_white_delete(struct rbtree_type* caps_white) { if(!caps_white) return; traverse_postorder(caps_white, caps_free, NULL); free(caps_white); } int caps_white_apply_cfg(rbtree_type* ntree, struct config_file* cfg) { struct config_strlist* p; for(p=cfg->caps_whitelist; p; p=p->next) { struct name_tree_node* n; size_t len; uint8_t* nm = sldns_str2wire_dname(p->str, &len); if(!nm) { log_err("could not parse %s", p->str); return 0; } n = (struct name_tree_node*)calloc(1, sizeof(*n)); if(!n) { log_err("out of memory"); free(nm); return 0; } n->node.key = n; n->name = nm; n->len = len; n->labs = dname_count_labels(nm); n->dclass = LDNS_RR_CLASS_IN; if(!name_tree_insert(ntree, n, nm, len, n->labs, n->dclass)) { /* duplicate element ignored, idempotent */ free(n->name); free(n); } } name_tree_init_parents(ntree); return 1; } int nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg) { const char *nat64_prefix; nat64_prefix = cfg->nat64_prefix; if(!nat64_prefix) nat64_prefix = cfg->dns64_prefix; if(!nat64_prefix) nat64_prefix = DEFAULT_NAT64_PREFIX; if(!netblockstrtoaddr(nat64_prefix, 0, &nat64->nat64_prefix_addr, &nat64->nat64_prefix_addrlen, &nat64->nat64_prefix_net)) { log_err("cannot parse nat64-prefix netblock: %s", nat64_prefix); return 0; } if(!addr_is_ip6(&nat64->nat64_prefix_addr, nat64->nat64_prefix_addrlen)) { log_err("nat64-prefix is not IPv6: %s", cfg->nat64_prefix); return 0; } if(!prefixnet_is_nat64(nat64->nat64_prefix_net)) { log_err("nat64-prefix length it not 32, 40, 48, 56, 64 or 96: %s", nat64_prefix); return 0; } nat64->use_nat64 = cfg->do_nat64; return 1; } int iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg) { int i; /* target fetch policy */ if(!read_fetch_policy(&iter_env->target_fetch_policy, &iter_env->max_dependency_depth, cfg->target_fetch_policy)) return 0; for(i=0; imax_dependency_depth+1; i++) verbose(VERB_QUERY, "target fetch policy for level %d is %d", i, iter_env->target_fetch_policy[i]); if(!iter_env->donotq) iter_env->donotq = donotq_create(); if(!iter_env->donotq || !donotq_apply_cfg(iter_env->donotq, cfg)) { log_err("Could not set donotqueryaddresses"); return 0; } if(!iter_env->priv) iter_env->priv = priv_create(); if(!iter_env->priv || !priv_apply_cfg(iter_env->priv, cfg)) { log_err("Could not set private addresses"); return 0; } if(cfg->caps_whitelist) { if(!iter_env->caps_white) iter_env->caps_white = caps_white_create(); if(!iter_env->caps_white || !caps_white_apply_cfg( iter_env->caps_white, cfg)) { log_err("Could not set capsforid whitelist"); return 0; } } if(!nat64_apply_cfg(&iter_env->nat64, cfg)) { log_err("Could not setup nat64"); return 0; } iter_env->supports_ipv6 = cfg->do_ip6; iter_env->supports_ipv4 = cfg->do_ip4; iter_env->outbound_msg_retry = cfg->outbound_msg_retry; iter_env->max_sent_count = cfg->max_sent_count; iter_env->max_query_restarts = cfg->max_query_restarts; return 1; } /** filter out unsuitable targets. * Applies NAT64 if needed as well by replacing the IPv4 with the synthesized * IPv6 address. * @param iter_env: iterator environment with ipv6-support flag. * @param env: module environment with infra cache. * @param name: zone name * @param namelen: length of name * @param qtype: query type (host order). * @param now: current time * @param a: address in delegation point we are examining. * @return an integer that signals the target suitability. * as follows: * -1: The address should be omitted from the list. * Because: * o The address is bogus (DNSSEC validation failure). * o Listed as donotquery * o is ipv6 but no ipv6 support (in operating system). * o is ipv4 but no ipv4 support (in operating system). * o is lame * Otherwise, an rtt in milliseconds. * 0 .. USEFUL_SERVER_TOP_TIMEOUT-1 * The roundtrip time timeout estimate. less than 2 minutes. * Note that util/rtt.c has a MIN_TIMEOUT of 50 msec, thus * values 0 .. 49 are not used, unless that is changed. * USEFUL_SERVER_TOP_TIMEOUT * This value exactly is given for unresponsive blacklisted. * USEFUL_SERVER_TOP_TIMEOUT+1 * For non-blacklisted servers: huge timeout, but has traffic. * USEFUL_SERVER_TOP_TIMEOUT*1 .. * parent-side lame servers get this penalty. A dispreferential * server. (lame in delegpt). * USEFUL_SERVER_TOP_TIMEOUT*2 .. * dnsseclame servers get penalty * USEFUL_SERVER_TOP_TIMEOUT*3 .. * recursion lame servers get penalty * UNKNOWN_SERVER_NICENESS * If no information is known about the server, this is * returned. 376 msec or so. * +BLACKLIST_PENALTY (of USEFUL_TOP_TIMEOUT*4) for dnssec failed IPs. * * When a final value is chosen that is dnsseclame ; dnsseclameness checking * is turned off (so we do not discard the reply). * When a final value is chosen that is recursionlame; RD bit is set on query. * Because of the numbers this means recursionlame also have dnssec lameness * checking turned off. */ static int iter_filter_unsuitable(struct iter_env* iter_env, struct module_env* env, uint8_t* name, size_t namelen, uint16_t qtype, time_t now, struct delegpt_addr* a) { int rtt, lame, reclame, dnsseclame; if(a->bogus) return -1; /* address of server is bogus */ if(donotq_lookup(iter_env->donotq, &a->addr, a->addrlen)) { if(iter_env->nat64.use_nat64 && addr_is_ip6(&a->addr, a->addrlen) && a->addrlen == iter_env->nat64.nat64_prefix_addrlen && addr_in_common(&a->addr, 128, &iter_env->nat64.nat64_prefix_addr, iter_env->nat64.nat64_prefix_net, iter_env->nat64.nat64_prefix_addrlen) == iter_env->nat64.nat64_prefix_net) { /* The NAT64 is enabled, and address is IPv6, it is * in the NAT64 prefix. It is allowed. * So that in an IPv6-only cluster without internet * access, that makes the NAT64 translation continue * to work. The NAT64 prefix is allowed. */ /* Otherwise, after a timeout, the already NAT64 * translated address would be treated differently, * and that causes confusion. */ log_addr(VERB_ALGO, "the addr is on the donotquery " "list, but allowed because it is NAT64", &a->addr, a->addrlen); } else { log_addr(VERB_ALGO, "skip addr on the donotquery list", &a->addr, a->addrlen); return -1; /* server is on the donotquery list */ } } if(!iter_env->supports_ipv6 && addr_is_ip6(&a->addr, a->addrlen)) { return -1; /* there is no ip6 available */ } if(!iter_env->supports_ipv4 && !iter_env->nat64.use_nat64 && !addr_is_ip6(&a->addr, a->addrlen)) { return -1; /* there is no ip4 available */ } if(iter_env->nat64.use_nat64 && !addr_is_ip6(&a->addr, a->addrlen)) { struct sockaddr_storage real_addr; socklen_t real_addrlen; addr_to_nat64(&a->addr, &iter_env->nat64.nat64_prefix_addr, iter_env->nat64.nat64_prefix_addrlen, iter_env->nat64.nat64_prefix_net, &real_addr, &real_addrlen); log_name_addr(VERB_QUERY, "NAT64 apply: from: ", name, &a->addr, a->addrlen); log_name_addr(VERB_QUERY, "NAT64 apply: to: ", name, &real_addr, real_addrlen); a->addr = real_addr; a->addrlen = real_addrlen; } /* check lameness - need zone , class info */ if(infra_get_lame_rtt(env->infra_cache, &a->addr, a->addrlen, name, namelen, qtype, &lame, &dnsseclame, &reclame, &rtt, now)) { log_addr(VERB_ALGO, "servselect", &a->addr, a->addrlen); verbose(VERB_ALGO, " rtt=%d%s%s%s%s%s", rtt, lame?" LAME":"", dnsseclame?" DNSSEC_LAME":"", a->dnsseclame?" ADDR_DNSSEC_LAME":"", reclame?" REC_LAME":"", a->lame?" ADDR_LAME":""); if(lame) return -1; /* server is lame */ else if(rtt >= USEFUL_SERVER_TOP_TIMEOUT) /* server is unresponsive, * we used to return TOP_TIMEOUT, but fairly useless, * because if == TOP_TIMEOUT is dropped because * blacklisted later, instead, remove it here, so * other choices (that are not blacklisted) can be * tried */ return -1; /* select remainder from worst to best */ else if(reclame) return rtt+USEFUL_SERVER_TOP_TIMEOUT*3; /* nonpref */ else if(dnsseclame || a->dnsseclame) return rtt+USEFUL_SERVER_TOP_TIMEOUT*2; /* nonpref */ else if(a->lame) return rtt+USEFUL_SERVER_TOP_TIMEOUT+1; /* nonpref */ else return rtt; } /* no server information present */ if(a->dnsseclame) return UNKNOWN_SERVER_NICENESS+USEFUL_SERVER_TOP_TIMEOUT*2; /* nonpref */ else if(a->lame) return USEFUL_SERVER_TOP_TIMEOUT+1+UNKNOWN_SERVER_NICENESS; /* nonpref */ return UNKNOWN_SERVER_NICENESS; } /** lookup RTT information, and also store fastest rtt (if any) */ static int iter_fill_rtt(struct iter_env* iter_env, struct module_env* env, uint8_t* name, size_t namelen, uint16_t qtype, time_t now, struct delegpt* dp, int* best_rtt, struct sock_list* blacklist, size_t* num_suitable_results) { int got_it = 0; struct delegpt_addr* a; *num_suitable_results = 0; if(dp->bogus) return 0; /* NS bogus, all bogus, nothing found */ for(a=dp->result_list; a; a = a->next_result) { a->sel_rtt = iter_filter_unsuitable(iter_env, env, name, namelen, qtype, now, a); if(a->sel_rtt != -1) { if(sock_list_find(blacklist, &a->addr, a->addrlen)) a->sel_rtt += BLACKLIST_PENALTY; if(!got_it) { *best_rtt = a->sel_rtt; got_it = 1; } else if(a->sel_rtt < *best_rtt) { *best_rtt = a->sel_rtt; } (*num_suitable_results)++; } } return got_it; } /** compare two rtts, return -1, 0 or 1 */ static int rtt_compare(const void* x, const void* y) { if(*(int*)x == *(int*)y) return 0; if(*(int*)x > *(int*)y) return 1; return -1; } /** get RTT for the Nth fastest server */ static int nth_rtt(struct delegpt_addr* result_list, size_t num_results, size_t n) { int rtt_band; size_t i; int* rtt_list, *rtt_index; if(num_results < 1 || n >= num_results) { return -1; } rtt_list = calloc(num_results, sizeof(int)); if(!rtt_list) { log_err("malloc failure: allocating rtt_list"); return -1; } rtt_index = rtt_list; for(i=0; isel_rtt != -1) { *rtt_index = result_list->sel_rtt; rtt_index++; } result_list=result_list->next_result; } qsort(rtt_list, num_results, sizeof(*rtt_list), rtt_compare); log_assert(n > 0); rtt_band = rtt_list[n-1]; free(rtt_list); return rtt_band; } /** filter the address list, putting best targets at front, * returns number of best targets (or 0, no suitable targets) */ static int iter_filter_order(struct iter_env* iter_env, struct module_env* env, uint8_t* name, size_t namelen, uint16_t qtype, time_t now, struct delegpt* dp, int* selected_rtt, int open_target, struct sock_list* blacklist, time_t prefetch) { int got_num = 0, low_rtt = 0, swap_to_front, rtt_band = RTT_BAND, nth; int alllame = 0; size_t num_results; struct delegpt_addr* a, *n, *prev=NULL; /* fillup sel_rtt and find best rtt in the bunch */ got_num = iter_fill_rtt(iter_env, env, name, namelen, qtype, now, dp, &low_rtt, blacklist, &num_results); if(got_num == 0) return 0; if(low_rtt >= USEFUL_SERVER_TOP_TIMEOUT && /* If all missing (or not fully resolved) targets are lame, * then use the remaining lame address. */ ((delegpt_count_missing_targets(dp, &alllame) > 0 && !alllame) || open_target > 0)) { verbose(VERB_ALGO, "Bad choices, trying to get more choice"); return 0; /* we want more choice. The best choice is a bad one. return 0 to force the caller to fetch more */ } if(env->cfg->fast_server_permil != 0 && prefetch == 0 && num_results > env->cfg->fast_server_num && ub_random_max(env->rnd, 1000) < env->cfg->fast_server_permil) { /* the query is not prefetch, but for a downstream client, * there are more servers available then the fastest N we want * to choose from. Limit our choice to the fastest servers. */ nth = nth_rtt(dp->result_list, num_results, env->cfg->fast_server_num); if(nth > 0) { rtt_band = nth - low_rtt; if(rtt_band > RTT_BAND) rtt_band = RTT_BAND; } } got_num = 0; a = dp->result_list; while(a) { /* skip unsuitable targets */ if(a->sel_rtt == -1) { prev = a; a = a->next_result; continue; } /* classify the server address and determine what to do */ swap_to_front = 0; if(a->sel_rtt >= low_rtt && a->sel_rtt - low_rtt <= rtt_band) { got_num++; swap_to_front = 1; } else if(a->sel_rttsel_rtt<=rtt_band) { got_num++; swap_to_front = 1; } /* swap to front if necessary, or move to next result */ if(swap_to_front && prev) { n = a->next_result; prev->next_result = n; a->next_result = dp->result_list; dp->result_list = a; a = n; } else { prev = a; a = a->next_result; } } *selected_rtt = low_rtt; if (env->cfg->prefer_ip6) { int got_num6 = 0; int low_rtt6 = 0; int i; int attempt = -1; /* filter to make sure addresses have less attempts on them than the first, to force round robin when all the IPv6 addresses fail */ int num4ok = 0; /* number ip4 at low attempt count */ int num4_lowrtt = 0; prev = NULL; a = dp->result_list; for(i = 0; i < got_num; i++) { if(!a) break; /* robustness */ swap_to_front = 0; if(a->addr.ss_family != AF_INET6 && attempt == -1) { /* if we only have ip4 at low attempt count, * then ip6 is failing, and we need to * select one of the remaining IPv4 addrs */ attempt = a->attempts; num4ok++; num4_lowrtt = a->sel_rtt; } else if(a->addr.ss_family != AF_INET6 && attempt == a->attempts) { num4ok++; if(num4_lowrtt == 0 || a->sel_rtt < num4_lowrtt) { num4_lowrtt = a->sel_rtt; } } if(a->addr.ss_family == AF_INET6) { if(attempt == -1) { attempt = a->attempts; } else if(a->attempts > attempt) { break; } got_num6++; swap_to_front = 1; if(low_rtt6 == 0 || a->sel_rtt < low_rtt6) { low_rtt6 = a->sel_rtt; } } /* swap to front if IPv6, or move to next result */ if(swap_to_front && prev) { n = a->next_result; prev->next_result = n; a->next_result = dp->result_list; dp->result_list = a; a = n; } else { prev = a; a = a->next_result; } } if(got_num6 > 0) { got_num = got_num6; *selected_rtt = low_rtt6; } else if(num4ok > 0) { got_num = num4ok; *selected_rtt = num4_lowrtt; } } else if (env->cfg->prefer_ip4) { int got_num4 = 0; int low_rtt4 = 0; int i; int attempt = -1; /* filter to make sure addresses have less attempts on them than the first, to force round robin when all the IPv4 addresses fail */ int num6ok = 0; /* number ip6 at low attempt count */ int num6_lowrtt = 0; prev = NULL; a = dp->result_list; for(i = 0; i < got_num; i++) { if(!a) break; /* robustness */ swap_to_front = 0; if(a->addr.ss_family != AF_INET && attempt == -1) { /* if we only have ip6 at low attempt count, * then ip4 is failing, and we need to * select one of the remaining IPv6 addrs */ attempt = a->attempts; num6ok++; num6_lowrtt = a->sel_rtt; } else if(a->addr.ss_family != AF_INET && attempt == a->attempts) { num6ok++; if(num6_lowrtt == 0 || a->sel_rtt < num6_lowrtt) { num6_lowrtt = a->sel_rtt; } } if(a->addr.ss_family == AF_INET) { if(attempt == -1) { attempt = a->attempts; } else if(a->attempts > attempt) { break; } got_num4++; swap_to_front = 1; if(low_rtt4 == 0 || a->sel_rtt < low_rtt4) { low_rtt4 = a->sel_rtt; } } /* swap to front if IPv4, or move to next result */ if(swap_to_front && prev) { n = a->next_result; prev->next_result = n; a->next_result = dp->result_list; dp->result_list = a; a = n; } else { prev = a; a = a->next_result; } } if(got_num4 > 0) { got_num = got_num4; *selected_rtt = low_rtt4; } else if(num6ok > 0) { got_num = num6ok; *selected_rtt = num6_lowrtt; } } return got_num; } struct delegpt_addr* iter_server_selection(struct iter_env* iter_env, struct module_env* env, struct delegpt* dp, uint8_t* name, size_t namelen, uint16_t qtype, int* dnssec_lame, int* chase_to_rd, int open_target, struct sock_list* blacklist, time_t prefetch) { int sel; int selrtt; struct delegpt_addr* a, *prev; int num = iter_filter_order(iter_env, env, name, namelen, qtype, *env->now, dp, &selrtt, open_target, blacklist, prefetch); if(num == 0) return NULL; verbose(VERB_ALGO, "selrtt %d", selrtt); if(selrtt > BLACKLIST_PENALTY) { if(selrtt-BLACKLIST_PENALTY > USEFUL_SERVER_TOP_TIMEOUT*3) { verbose(VERB_ALGO, "chase to " "blacklisted recursion lame server"); *chase_to_rd = 1; } if(selrtt-BLACKLIST_PENALTY > USEFUL_SERVER_TOP_TIMEOUT*2) { verbose(VERB_ALGO, "chase to " "blacklisted dnssec lame server"); *dnssec_lame = 1; } } else { if(selrtt > USEFUL_SERVER_TOP_TIMEOUT*3) { verbose(VERB_ALGO, "chase to recursion lame server"); *chase_to_rd = 1; } if(selrtt > USEFUL_SERVER_TOP_TIMEOUT*2) { verbose(VERB_ALGO, "chase to dnssec lame server"); *dnssec_lame = 1; } if(selrtt == USEFUL_SERVER_TOP_TIMEOUT) { verbose(VERB_ALGO, "chase to blacklisted lame server"); return NULL; } } if(num == 1) { a = dp->result_list; if(++a->attempts < iter_env->outbound_msg_retry) return a; dp->result_list = a->next_result; return a; } /* randomly select a target from the list */ log_assert(num > 1); /* grab secure random number, to pick unexpected server. * also we need it to be threadsafe. */ sel = ub_random_max(env->rnd, num); a = dp->result_list; prev = NULL; while(sel > 0 && a) { prev = a; a = a->next_result; sel--; } if(!a) /* robustness */ return NULL; if(++a->attempts < iter_env->outbound_msg_retry) return a; /* remove it from the delegation point result list */ if(prev) prev->next_result = a->next_result; else dp->result_list = a->next_result; return a; } struct dns_msg* dns_alloc_msg(sldns_buffer* pkt, struct msg_parse* msg, struct regional* region) { struct dns_msg* m = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!m) return NULL; memset(m, 0, sizeof(*m)); if(!parse_create_msg(pkt, msg, NULL, &m->qinfo, &m->rep, region)) { log_err("malloc failure: allocating incoming dns_msg"); return NULL; } return m; } struct dns_msg* dns_copy_msg(struct dns_msg* from, struct regional* region) { struct dns_msg* m = (struct dns_msg*)regional_alloc(region, sizeof(struct dns_msg)); if(!m) return NULL; m->qinfo = from->qinfo; if(!(m->qinfo.qname = regional_alloc_init(region, from->qinfo.qname, from->qinfo.qname_len))) return NULL; if(!(m->rep = reply_info_copy(from->rep, NULL, region))) return NULL; return m; } void iter_dns_store(struct module_env* env, struct query_info* msgqinf, struct reply_info* msgrep, int is_referral, time_t leeway, int pside, struct regional* region, uint16_t flags, time_t qstarttime, int is_valrec) { if(!dns_cache_store(env, msgqinf, msgrep, is_referral, leeway, pside, region, flags, qstarttime, is_valrec)) log_err("out of memory: cannot store data in cache"); } int iter_ns_probability(struct ub_randstate* rnd, int n, int m) { int sel; if(n == m) /* 100% chance */ return 1; /* we do not need secure random numbers here, but * we do need it to be threadsafe, so we use this */ sel = ub_random_max(rnd, m); return (sel < n); } /** detect dependency cycle for query and target */ static int causes_cycle(struct module_qstate* qstate, uint8_t* name, size_t namelen, uint16_t t, uint16_t c) { struct query_info qinf; qinf.qname = name; qinf.qname_len = namelen; qinf.qtype = t; qinf.qclass = c; qinf.local_alias = NULL; fptr_ok(fptr_whitelist_modenv_detect_cycle( qstate->env->detect_cycle)); return (*qstate->env->detect_cycle)(qstate, &qinf, (uint16_t)(BIT_RD|BIT_CD), qstate->is_priming, qstate->is_valrec); } void iter_mark_cycle_targets(struct module_qstate* qstate, struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->resolved) continue; /* see if this ns as target causes dependency cycle */ if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass) || causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_A, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle (harden-glue: no may " "fix some of the cycles)", ns->name, LDNS_RR_TYPE_A, qstate->qinfo.qclass); ns->resolved = 1; } } } void iter_mark_pside_cycle_targets(struct module_qstate* qstate, struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->done_pside4 && ns->done_pside6) continue; /* see if this ns as target causes dependency cycle */ if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_A, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle", ns->name, LDNS_RR_TYPE_A, qstate->qinfo.qclass); ns->done_pside4 = 1; } if(causes_cycle(qstate, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass)) { log_nametypeclass(VERB_QUERY, "skipping target due " "to dependency cycle", ns->name, LDNS_RR_TYPE_AAAA, qstate->qinfo.qclass); ns->done_pside6 = 1; } } } int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags, struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64) { struct delegpt_ns* ns; struct delegpt_addr* a; if(supports_ipv6 && use_nat64) supports_ipv4 = 1; /* check: * o RD qflag is on. * o no addresses are provided. * o all NS items are required glue. * OR * o RD qflag is on. * o no addresses are provided. * o the query is for one of the nameservers in dp, * and that nameserver is a glue-name for this dp. */ if(!(qflags&BIT_RD)) return 0; /* either available or unused targets, * if they exist, the dp is not useless. */ for(a = dp->usable_list; a; a = a->next_usable) { if(!addr_is_ip6(&a->addr, a->addrlen) && supports_ipv4) return 0; else if(addr_is_ip6(&a->addr, a->addrlen) && supports_ipv6) return 0; } for(a = dp->result_list; a; a = a->next_result) { if(!addr_is_ip6(&a->addr, a->addrlen) && supports_ipv4) return 0; else if(addr_is_ip6(&a->addr, a->addrlen) && supports_ipv6) return 0; } /* see if query is for one of the nameservers, which is glue */ if( ((qinfo->qtype == LDNS_RR_TYPE_A && supports_ipv4) || (qinfo->qtype == LDNS_RR_TYPE_AAAA && supports_ipv6)) && dname_subdomain_c(qinfo->qname, dp->name) && delegpt_find_ns(dp, qinfo->qname, qinfo->qname_len)) return 1; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->resolved) /* skip failed targets */ continue; if(!dname_subdomain_c(ns->name, dp->name)) return 0; /* one address is not required glue */ } return 1; } int iter_qname_indicates_dnssec(struct module_env* env, struct query_info *qinfo) { struct trust_anchor* a; if(!env || !env->anchors || !qinfo || !qinfo->qname) return 0; /* a trust anchor exists above the name? */ if((a=anchors_lookup(env->anchors, qinfo->qname, qinfo->qname_len, qinfo->qclass))) { if(a->numDS == 0 && a->numDNSKEY == 0) { /* insecure trust point */ lock_basic_unlock(&a->lock); return 0; } lock_basic_unlock(&a->lock); return 1; } /* no trust anchor above it. */ return 0; } int iter_indicates_dnssec(struct module_env* env, struct delegpt* dp, struct dns_msg* msg, uint16_t dclass) { struct trust_anchor* a; /* information not available, !env->anchors can be common */ if(!env || !env->anchors || !dp || !dp->name) return 0; /* a trust anchor exists with this name, RRSIGs expected */ if((a=anchor_find(env->anchors, dp->name, dp->namelabs, dp->namelen, dclass))) { if(a->numDS == 0 && a->numDNSKEY == 0) { /* insecure trust point */ lock_basic_unlock(&a->lock); return 0; } lock_basic_unlock(&a->lock); return 1; } /* see if DS rrset was given, in AUTH section */ if(msg && msg->rep && reply_find_rrset_section_ns(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_DS, dclass)) return 1; /* look in key cache */ if(env->key_cache) { struct key_entry_key* kk = key_cache_obtain(env->key_cache, dp->name, dp->namelen, dclass, env->scratch, *env->now); if(kk) { if(query_dname_compare(kk->name, dp->name) == 0) { if(key_entry_isgood(kk) || key_entry_isbad(kk)) { regional_free_all(env->scratch); return 1; } else if(key_entry_isnull(kk)) { regional_free_all(env->scratch); return 0; } } regional_free_all(env->scratch); } } return 0; } int iter_msg_has_dnssec(struct dns_msg* msg) { size_t i; if(!msg || !msg->rep) return 0; for(i=0; irep->an_numrrsets + msg->rep->ns_numrrsets; i++) { if(((struct packed_rrset_data*)msg->rep->rrsets[i]-> entry.data)->rrsig_count > 0) return 1; } /* empty message has no DNSSEC info, with DNSSEC the reply is * not empty (NSEC) */ return 0; } int iter_msg_from_zone(struct dns_msg* msg, struct delegpt* dp, enum response_type type, uint16_t dclass) { if(!msg || !dp || !msg->rep || !dp->name) return 0; /* SOA RRset - always from reply zone */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_SOA, dclass) || reply_find_rrset_section_ns(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_SOA, dclass)) return 1; if(type == RESPONSE_TYPE_REFERRAL) { size_t i; /* if it adds a single label, i.e. we expect .com, * and referral to example.com. NS ... , then origin zone * is .com. For a referral to sub.example.com. NS ... then * we do not know, since example.com. may be in between. */ for(i=0; irep->an_numrrsets+msg->rep->ns_numrrsets; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS && ntohs(s->rk.rrset_class) == dclass) { int l = dname_count_labels(s->rk.dname); if(l == dp->namelabs + 1 && dname_strict_subdomain(s->rk.dname, l, dp->name, dp->namelabs)) return 1; } } return 0; } log_assert(type==RESPONSE_TYPE_ANSWER || type==RESPONSE_TYPE_CNAME); /* not a referral, and not lame delegation (upwards), so, * any NS rrset must be from the zone itself */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_NS, dclass) || reply_find_rrset_section_ns(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_NS, dclass)) return 1; /* a DNSKEY set is expected at the zone apex as well */ /* this is for 'minimal responses' for DNSKEYs */ if(reply_find_rrset_section_an(msg->rep, dp->name, dp->namelen, LDNS_RR_TYPE_DNSKEY, dclass)) return 1; return 0; } /** * check equality of two rrsets * @param k1: rrset * @param k2: rrset * @return true if equal */ static int rrset_equal(struct ub_packed_rrset_key* k1, struct ub_packed_rrset_key* k2) { struct packed_rrset_data* d1 = (struct packed_rrset_data*) k1->entry.data; struct packed_rrset_data* d2 = (struct packed_rrset_data*) k2->entry.data; size_t i, t; if(k1->rk.dname_len != k2->rk.dname_len || k1->rk.flags != k2->rk.flags || k1->rk.type != k2->rk.type || k1->rk.rrset_class != k2->rk.rrset_class || query_dname_compare(k1->rk.dname, k2->rk.dname) != 0) return 0; if( /* do not check ttl: d1->ttl != d2->ttl || */ d1->count != d2->count || d1->rrsig_count != d2->rrsig_count || d1->trust != d2->trust || d1->security != d2->security) return 0; t = d1->count + d1->rrsig_count; for(i=0; irr_len[i] != d2->rr_len[i] || /* no ttl check: d1->rr_ttl[i] != d2->rr_ttl[i] ||*/ memcmp(d1->rr_data[i], d2->rr_data[i], d1->rr_len[i]) != 0) return 0; } return 1; } /** compare rrsets and sort canonically. Compares rrset name, type, class. * return 0 if equal, +1 if x > y, and -1 if x < y. */ static int rrset_canonical_sort_cmp(const void* x, const void* y) { struct ub_packed_rrset_key* rrx = *(struct ub_packed_rrset_key**)x; struct ub_packed_rrset_key* rry = *(struct ub_packed_rrset_key**)y; int r = dname_canonical_compare(rrx->rk.dname, rry->rk.dname); if(r != 0) return r; if(rrx->rk.type != rry->rk.type) { if(ntohs(rrx->rk.type) > ntohs(rry->rk.type)) return 1; else return -1; } if(rrx->rk.rrset_class != rry->rk.rrset_class) { if(ntohs(rrx->rk.rrset_class) > ntohs(rry->rk.rrset_class)) return 1; else return -1; } return 0; } int reply_equal(struct reply_info* p, struct reply_info* q, struct regional* region) { size_t i; struct ub_packed_rrset_key** sorted_p, **sorted_q; if(p->flags != q->flags || p->qdcount != q->qdcount || /* do not check TTL, this may differ */ /* p->ttl != q->ttl || p->prefetch_ttl != q->prefetch_ttl || */ p->security != q->security || p->an_numrrsets != q->an_numrrsets || p->ns_numrrsets != q->ns_numrrsets || p->ar_numrrsets != q->ar_numrrsets || p->rrset_count != q->rrset_count) return 0; /* sort the rrsets in the authority and additional sections before * compare, the query and answer sections are ordered in the sequence * they should have (eg. one after the other for aliases). */ sorted_p = (struct ub_packed_rrset_key**)regional_alloc_init( region, p->rrsets, sizeof(*sorted_p)*p->rrset_count); if(!sorted_p) return 0; log_assert(p->an_numrrsets + p->ns_numrrsets + p->ar_numrrsets <= p->rrset_count); qsort(sorted_p + p->an_numrrsets, p->ns_numrrsets, sizeof(*sorted_p), rrset_canonical_sort_cmp); qsort(sorted_p + p->an_numrrsets + p->ns_numrrsets, p->ar_numrrsets, sizeof(*sorted_p), rrset_canonical_sort_cmp); sorted_q = (struct ub_packed_rrset_key**)regional_alloc_init( region, q->rrsets, sizeof(*sorted_q)*q->rrset_count); if(!sorted_q) { regional_free_all(region); return 0; } log_assert(q->an_numrrsets + q->ns_numrrsets + q->ar_numrrsets <= q->rrset_count); qsort(sorted_q + q->an_numrrsets, q->ns_numrrsets, sizeof(*sorted_q), rrset_canonical_sort_cmp); qsort(sorted_q + q->an_numrrsets + q->ns_numrrsets, q->ar_numrrsets, sizeof(*sorted_q), rrset_canonical_sort_cmp); /* compare the rrsets */ for(i=0; irrset_count; i++) { if(!rrset_equal(sorted_p[i], sorted_q[i])) { if(!rrset_canonical_equal(region, sorted_p[i], sorted_q[i])) { regional_free_all(region); return 0; } } } regional_free_all(region); return 1; } void caps_strip_reply(struct reply_info* rep) { size_t i; if(!rep) return; /* see if message is a referral, in which case the additional and * NS record cannot be removed */ /* referrals have the AA flag unset (strict check, not elsewhere in * unbound, but for 0x20 this is very convenient). */ if(!(rep->flags&BIT_AA)) return; /* remove the additional section from the reply */ if(rep->ar_numrrsets != 0) { verbose(VERB_ALGO, "caps fallback: removing additional section"); rep->rrset_count -= rep->ar_numrrsets; rep->ar_numrrsets = 0; } /* is there an NS set in the authority section to remove? */ /* the failure case (Cisco firewalls) only has one rrset in authsec */ for(i=rep->an_numrrsets; ian_numrrsets+rep->ns_numrrsets; i++) { struct ub_packed_rrset_key* s = rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS) { /* remove NS rrset and break from loop (loop limits * have changed) */ /* move last rrset into this position (there is no * additional section any more) */ verbose(VERB_ALGO, "caps fallback: removing NS rrset"); if(i < rep->rrset_count-1) rep->rrsets[i]=rep->rrsets[rep->rrset_count-1]; rep->rrset_count --; rep->ns_numrrsets --; break; } } } int caps_failed_rcode(struct reply_info* rep) { return !(FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR || FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN); } void iter_store_parentside_rrset(struct module_env* env, struct ub_packed_rrset_key* rrset) { struct rrset_ref ref; rrset = packed_rrset_copy_alloc(rrset, env->alloc, *env->now); if(!rrset) { log_err("malloc failure in store_parentside_rrset"); return; } rrset->rk.flags |= PACKED_RRSET_PARENT_SIDE; rrset->entry.hash = rrset_key_hash(&rrset->rk); ref.key = rrset; ref.id = rrset->id; /* ignore ret: if it was in the cache, ref updated */ (void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, *env->now); } /** fetch NS record from reply, if any */ static struct ub_packed_rrset_key* reply_get_NS_rrset(struct reply_info* rep) { size_t i; for(i=0; irrset_count; i++) { if(rep->rrsets[i]->rk.type == htons(LDNS_RR_TYPE_NS)) { return rep->rrsets[i]; } } return NULL; } void iter_store_parentside_NS(struct module_env* env, struct reply_info* rep) { struct ub_packed_rrset_key* rrset = reply_get_NS_rrset(rep); if(rrset) { log_rrset_key(VERB_ALGO, "store parent-side NS", rrset); iter_store_parentside_rrset(env, rrset); } } void iter_store_parentside_neg(struct module_env* env, struct query_info* qinfo, struct reply_info* rep) { /* TTL: NS from referral in iq->deleg_msg, * or first RR from iq->response, * or servfail5secs if !iq->response */ time_t ttl = NORR_TTL; struct ub_packed_rrset_key* neg; struct packed_rrset_data* newd; if(rep) { struct ub_packed_rrset_key* rrset = reply_get_NS_rrset(rep); if(!rrset && rep->rrset_count != 0) rrset = rep->rrsets[0]; if(rrset) ttl = ub_packed_rrset_ttl(rrset); } /* create empty rrset to store */ neg = (struct ub_packed_rrset_key*)regional_alloc(env->scratch, sizeof(struct ub_packed_rrset_key)); if(!neg) { log_err("out of memory in store_parentside_neg"); return; } memset(&neg->entry, 0, sizeof(neg->entry)); neg->entry.key = neg; neg->rk.type = htons(qinfo->qtype); neg->rk.rrset_class = htons(qinfo->qclass); neg->rk.flags = 0; neg->rk.dname = regional_alloc_init(env->scratch, qinfo->qname, qinfo->qname_len); if(!neg->rk.dname) { log_err("out of memory in store_parentside_neg"); return; } neg->rk.dname_len = qinfo->qname_len; neg->entry.hash = rrset_key_hash(&neg->rk); newd = (struct packed_rrset_data*)regional_alloc_zero(env->scratch, sizeof(struct packed_rrset_data) + sizeof(size_t) + sizeof(uint8_t*) + sizeof(time_t) + sizeof(uint16_t)); if(!newd) { log_err("out of memory in store_parentside_neg"); return; } neg->entry.data = newd; newd->ttl = ttl; /* entry must have one RR, otherwise not valid in cache. * put in one RR with empty rdata: those are ignored as nameserver */ newd->count = 1; newd->rrsig_count = 0; newd->trust = rrset_trust_ans_noAA; newd->rr_len = (size_t*)((uint8_t*)newd + sizeof(struct packed_rrset_data)); newd->rr_len[0] = 0 /* zero len rdata */ + sizeof(uint16_t); packed_rrset_ptr_fixup(newd); newd->rr_ttl[0] = newd->ttl; sldns_write_uint16(newd->rr_data[0], 0 /* zero len rdata */); /* store it */ log_rrset_key(VERB_ALGO, "store parent-side negative", neg); iter_store_parentside_rrset(env, neg); } int iter_lookup_parent_NS_from_cache(struct module_env* env, struct delegpt* dp, struct regional* region, struct query_info* qinfo) { struct ub_packed_rrset_key* akey; akey = rrset_cache_lookup(env->rrset_cache, dp->name, dp->namelen, LDNS_RR_TYPE_NS, qinfo->qclass, PACKED_RRSET_PARENT_SIDE, *env->now, 0); if(akey) { log_rrset_key(VERB_ALGO, "found parent-side NS in cache", akey); dp->has_parent_side_NS = 1; /* and mark the new names as lame */ if(!delegpt_rrset_add_ns(dp, region, akey, 1)) { lock_rw_unlock(&akey->entry.lock); return 0; } lock_rw_unlock(&akey->entry.lock); } return 1; } int iter_lookup_parent_glue_from_cache(struct module_env* env, struct delegpt* dp, struct regional* region, struct query_info* qinfo) { struct ub_packed_rrset_key* akey; struct delegpt_ns* ns; size_t num = delegpt_count_targets(dp); for(ns = dp->nslist; ns; ns = ns->next) { if(ns->cache_lookup_count > ITERATOR_NAME_CACHELOOKUP_MAX_PSIDE) continue; ns->cache_lookup_count++; /* get cached parentside A */ akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_A, qinfo->qclass, PACKED_RRSET_PARENT_SIDE, *env->now, 0); if(akey) { log_rrset_key(VERB_ALGO, "found parent-side", akey); ns->done_pside4 = 1; /* a negative-cache-element has no addresses it adds */ if(!delegpt_add_rrset_A(dp, region, akey, 1, NULL)) log_err("malloc failure in lookup_parent_glue"); lock_rw_unlock(&akey->entry.lock); } /* get cached parentside AAAA */ akey = rrset_cache_lookup(env->rrset_cache, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, qinfo->qclass, PACKED_RRSET_PARENT_SIDE, *env->now, 0); if(akey) { log_rrset_key(VERB_ALGO, "found parent-side", akey); ns->done_pside6 = 1; /* a negative-cache-element has no addresses it adds */ if(!delegpt_add_rrset_AAAA(dp, region, akey, 1, NULL)) log_err("malloc failure in lookup_parent_glue"); lock_rw_unlock(&akey->entry.lock); } } /* see if new (but lame) addresses have become available */ return delegpt_count_targets(dp) != num; } int iter_get_next_root(struct iter_hints* hints, struct iter_forwards* fwd, uint16_t* c) { uint16_t c1 = *c, c2 = *c; int r1, r2; int nolock = 1; /* prelock both forwards and hints for atomic read. */ lock_rw_rdlock(&fwd->lock); lock_rw_rdlock(&hints->lock); r1 = hints_next_root(hints, &c1, nolock); r2 = forwards_next_root(fwd, &c2, nolock); lock_rw_unlock(&fwd->lock); lock_rw_unlock(&hints->lock); if(!r1 && !r2) /* got none, end of list */ return 0; else if(!r1) /* got one, return that */ *c = c2; else if(!r2) *c = c1; else if(c1 < c2) /* got both take smallest */ *c = c1; else *c = c2; return 1; } void iter_scrub_ds(struct dns_msg* msg, struct ub_packed_rrset_key* ns, uint8_t* z) { /* Only the DS record for the delegation itself is expected. * We allow DS for everything between the bailiwick and the * zonecut, thus DS records must be at or above the zonecut. * And the DS records must be below the server authority zone. * The answer section is already scrubbed. */ size_t i = msg->rep->an_numrrsets; while(i < (msg->rep->an_numrrsets + msg->rep->ns_numrrsets)) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_DS && (!ns || !dname_subdomain_c(ns->rk.dname, s->rk.dname) || query_dname_compare(z, s->rk.dname) == 0)) { log_nametypeclass(VERB_ALGO, "removing irrelevant DS", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); memmove(msg->rep->rrsets+i, msg->rep->rrsets+i+1, sizeof(struct ub_packed_rrset_key*) * (msg->rep->rrset_count-i-1)); msg->rep->ns_numrrsets--; msg->rep->rrset_count--; /* stay at same i, but new record */ continue; } i++; } } void iter_scrub_nxdomain(struct dns_msg* msg) { if(msg->rep->an_numrrsets == 0) return; memmove(msg->rep->rrsets, msg->rep->rrsets+msg->rep->an_numrrsets, sizeof(struct ub_packed_rrset_key*) * (msg->rep->rrset_count-msg->rep->an_numrrsets)); msg->rep->rrset_count -= msg->rep->an_numrrsets; msg->rep->an_numrrsets = 0; } void iter_dec_attempts(struct delegpt* dp, int d, int outbound_msg_retry) { struct delegpt_addr* a; for(a=dp->target_list; a; a = a->next_target) { if(a->attempts >= outbound_msg_retry) { /* add back to result list */ delegpt_add_to_result_list(dp, a); } if(a->attempts > d) a->attempts -= d; else a->attempts = 0; } } void iter_merge_retry_counts(struct delegpt* dp, struct delegpt* old, int outbound_msg_retry) { struct delegpt_addr* a, *o, *prev; for(a=dp->target_list; a; a = a->next_target) { o = delegpt_find_addr(old, &a->addr, a->addrlen); if(o) { log_addr(VERB_ALGO, "copy attempt count previous dp", &a->addr, a->addrlen); a->attempts = o->attempts; } } prev = NULL; a = dp->usable_list; while(a) { if(a->attempts >= outbound_msg_retry) { log_addr(VERB_ALGO, "remove from usable list dp", &a->addr, a->addrlen); /* remove from result list */ if(prev) prev->next_usable = a->next_usable; else dp->usable_list = a->next_usable; /* prev stays the same */ a = a->next_usable; continue; } prev = a; a = a->next_usable; } } int iter_ds_toolow(struct dns_msg* msg, struct delegpt* dp) { /* if for query example.com, there is example.com SOA or a subdomain * of example.com, then we are too low and need to fetch NS. */ size_t i; /* if we have a DNAME or CNAME we are probably wrong */ /* if we have a qtype DS in the answer section, its fine */ for(i=0; i < msg->rep->an_numrrsets; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_DNAME || ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME) { /* not the right answer, maybe too low, check the * RRSIG signer name (if there is any) for a hint * that it is from the dp zone anyway */ uint8_t* sname; size_t slen; val_find_rrset_signer(s, &sname, &slen); if(sname && query_dname_compare(dp->name, sname)==0) return 0; /* it is fine, from the right dp */ return 1; } if(ntohs(s->rk.type) == LDNS_RR_TYPE_DS) return 0; /* fine, we have a DS record */ } for(i=msg->rep->an_numrrsets; i < msg->rep->an_numrrsets + msg->rep->ns_numrrsets; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_SOA) { if(dname_subdomain_c(s->rk.dname, msg->qinfo.qname)) return 1; /* point is too low */ if(query_dname_compare(s->rk.dname, dp->name)==0) return 0; /* right dp */ } if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC || ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { uint8_t* sname; size_t slen; val_find_rrset_signer(s, &sname, &slen); if(sname && query_dname_compare(dp->name, sname)==0) return 0; /* it is fine, from the right dp */ return 1; } } /* we do not know */ return 1; } int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp) { /* no delegation point, do not see how we can go down, * robust check, it should really exist */ if(!dp) return 0; /* see if dp equals the qname, then we cannot go down further */ if(query_dname_compare(qinfo->qname, dp->name) == 0) return 0; /* if dp is one label above the name we also cannot go down further */ if(dname_count_labels(qinfo->qname) == dp->namelabs+1) return 0; return 1; } int iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen, uint8_t* dpname_storage, size_t dpname_storage_len) { struct iter_hints_stub *stub; struct delegpt *dp; int nolock = 1; log_assert((retdpname && retdpnamelen && dpname_storage && dpname_storage_len > 0) || (retdpname == NULL && retdpnamelen == NULL && dpname_storage == NULL && dpname_storage_len == 0)); /* Check for stub. */ /* Lock both forwards and hints for atomic read. */ lock_rw_rdlock(&qstate->env->fwds->lock); lock_rw_rdlock(&qstate->env->hints->lock); stub = hints_lookup_stub(qstate->env->hints, qinf->qname, qinf->qclass, NULL, nolock); dp = forwards_lookup(qstate->env->fwds, qinf->qname, qinf->qclass, nolock); /* see if forward or stub is more pertinent */ if(stub && stub->dp && dp) { if(dname_strict_subdomain(dp->name, dp->namelabs, stub->dp->name, stub->dp->namelabs)) { stub = NULL; /* ignore stub, forward is lower */ } else { dp = NULL; /* ignore forward, stub is lower */ } } /* check stub */ if (stub != NULL && stub->dp != NULL) { enum verbosity_value level = VERB_ALGO; int stub_no_cache = stub->dp->no_cache; lock_rw_unlock(&qstate->env->fwds->lock); if(verbosity >= level && stub_no_cache) { char qname[LDNS_MAX_DOMAINLEN]; char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(stub->dp->name, dpname); verbose(level, "stub for %s %s has no_cache", qname, dpname); } if(retdpname) { if(stub->dp->namelen > dpname_storage_len) { verbose(VERB_ALGO, "no cache stub dpname too long"); lock_rw_unlock(&qstate->env->hints->lock); *retdpname = NULL; *retdpnamelen = 0; return stub_no_cache; } memmove(dpname_storage, stub->dp->name, stub->dp->namelen); *retdpname = dpname_storage; *retdpnamelen = stub->dp->namelen; } lock_rw_unlock(&qstate->env->hints->lock); return stub_no_cache; } /* Check for forward. */ if (dp) { enum verbosity_value level = VERB_ALGO; int dp_no_cache = dp->no_cache; lock_rw_unlock(&qstate->env->hints->lock); if(verbosity >= level && dp_no_cache) { char qname[LDNS_MAX_DOMAINLEN]; char dpname[LDNS_MAX_DOMAINLEN]; dname_str(qinf->qname, qname); dname_str(dp->name, dpname); verbose(level, "forward for %s %s has no_cache", qname, dpname); } if(retdpname) { if(dp->namelen > dpname_storage_len) { verbose(VERB_ALGO, "no cache dpname too long"); lock_rw_unlock(&qstate->env->fwds->lock); *retdpname = NULL; *retdpnamelen = 0; return dp_no_cache; } memmove(dpname_storage, dp->name, dp->namelen); *retdpname = dpname_storage; *retdpnamelen = dp->namelen; } lock_rw_unlock(&qstate->env->fwds->lock); return dp_no_cache; } lock_rw_unlock(&qstate->env->fwds->lock); lock_rw_unlock(&qstate->env->hints->lock); if(retdpname) { *retdpname = NULL; *retdpnamelen = 0; } return 0; } void iterator_set_ip46_support(struct module_stack* mods, struct module_env* env, struct outside_network* outnet) { int m = modstack_find(mods, "iterator"); struct iter_env* ie = NULL; if(m == -1) return; ie = (struct iter_env*)env->modinfo[m]; if(outnet->pending == NULL) return; /* we are in testbound, no rbtree for UDP */ if(outnet->num_ip4 == 0) ie->supports_ipv4 = 0; if(outnet->num_ip6 == 0) ie->supports_ipv6 = 0; } void limit_nsec_ttl(struct dns_msg* msg) { /* Limit NSEC and NSEC3 TTL in response, RFC9077 */ size_t i; int found = 0; time_t soa_ttl = 0; /* Limit the NSEC and NSEC3 TTL values to the SOA TTL and SOA minimum * TTL. That has already been applied to the SOA record ttl. */ for(i=0; irep->rrset_count; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_SOA) { struct packed_rrset_data* soadata = (struct packed_rrset_data*)s->entry.data; found = 1; soa_ttl = soadata->ttl; break; } } if(!found) return; for(i=0; irep->rrset_count; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC || ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC3) { struct packed_rrset_data* data = (struct packed_rrset_data*)s->entry.data; /* Limit the negative TTL. */ if(data->ttl > soa_ttl) { if(verbosity >= VERB_ALGO) { char buf[256]; snprintf(buf, sizeof(buf), "limiting TTL %d of %s record to the SOA TTL of %d for", (int)data->ttl, ((ntohs(s->rk.type) == LDNS_RR_TYPE_NSEC)?"NSEC":"NSEC3"), (int)soa_ttl); log_nametypeclass(VERB_ALGO, buf, s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); } data->ttl = soa_ttl; } } } } void iter_make_minimal(struct reply_info* rep) { size_t rem = rep->ns_numrrsets + rep->ar_numrrsets; rep->ns_numrrsets = 0; rep->ar_numrrsets = 0; rep->rrset_count -= rem; } unbound-1.25.1/iterator/iterator.c0000644000175000017500000047425715203270263016557 0ustar wouterwouter/* * iterator/iterator.c - iterative resolver DNS query response module * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains a module that performs recursive iterative DNS query * processing. */ #include "config.h" #include "iterator/iterator.h" #include "iterator/iter_utils.h" #include "iterator/iter_hints.h" #include "iterator/iter_fwd.h" #include "iterator/iter_donotq.h" #include "iterator/iter_delegpt.h" #include "iterator/iter_resptype.h" #include "iterator/iter_scrub.h" #include "iterator/iter_priv.h" #include "validator/val_neg.h" #include "services/cache/dns.h" #include "services/cache/rrset.h" #include "services/cache/infra.h" #include "services/authzone.h" #include "util/module.h" #include "util/netevent.h" #include "util/net_help.h" #include "util/regional.h" #include "util/data/dname.h" #include "util/data/msgencode.h" #include "util/fptr_wlist.h" #include "util/config_file.h" #include "util/random.h" #include "sldns/rrdef.h" #include "sldns/wire2str.h" #include "sldns/str2wire.h" #include "sldns/parseutil.h" #include "sldns/sbuffer.h" /* number of packets */ int MAX_GLOBAL_QUOTA = 200; /* in msec */ int UNKNOWN_SERVER_NICENESS = 376; /* in msec */ int USEFUL_SERVER_TOP_TIMEOUT = 120000; /* Equals USEFUL_SERVER_TOP_TIMEOUT*4 */ int BLACKLIST_PENALTY = (120000*4); /** Timeout when only a single probe query per IP is allowed. */ int PROBE_MAXRTO = PROBE_MAXRTO_DEFAULT; /* in msec */ static void target_count_increase_nx(struct iter_qstate* iq, int num); int iter_init(struct module_env* env, int id) { struct iter_env* iter_env = (struct iter_env*)calloc(1, sizeof(struct iter_env)); if(!iter_env) { log_err("malloc failure"); return 0; } env->modinfo[id] = (void*)iter_env; lock_basic_init(&iter_env->queries_ratelimit_lock); lock_protect(&iter_env->queries_ratelimit_lock, &iter_env->num_queries_ratelimited, sizeof(iter_env->num_queries_ratelimited)); if(!iter_apply_cfg(iter_env, env->cfg)) { log_err("iterator: could not apply configuration settings."); return 0; } return 1; } void iter_deinit(struct module_env* env, int id) { struct iter_env* iter_env; if(!env || !env->modinfo[id]) return; iter_env = (struct iter_env*)env->modinfo[id]; lock_basic_destroy(&iter_env->queries_ratelimit_lock); free(iter_env->target_fetch_policy); priv_delete(iter_env->priv); donotq_delete(iter_env->donotq); caps_white_delete(iter_env->caps_white); free(iter_env); env->modinfo[id] = NULL; } /** new query for iterator */ static int iter_new(struct module_qstate* qstate, int id) { struct iter_qstate* iq = (struct iter_qstate*)regional_alloc( qstate->region, sizeof(struct iter_qstate)); qstate->minfo[id] = iq; if(!iq) return 0; memset(iq, 0, sizeof(*iq)); iq->state = INIT_REQUEST_STATE; iq->final_state = FINISHED_STATE; iq->an_prepend_list = NULL; iq->an_prepend_last = NULL; iq->ns_prepend_list = NULL; iq->ns_prepend_last = NULL; iq->dp = NULL; iq->depth = 0; iq->num_target_queries = 0; iq->num_current_queries = 0; iq->query_restart_count = 0; iq->referral_count = 0; iq->sent_count = 0; iq->ratelimit_ok = 0; iq->target_count = NULL; iq->dp_target_count = 0; iq->wait_priming_stub = 0; iq->refetch_glue = 0; iq->dnssec_expected = 0; iq->dnssec_lame_query = 0; iq->chase_flags = qstate->query_flags; /* Start with the (current) qname. */ iq->qchase = qstate->qinfo; outbound_list_init(&iq->outlist); iq->minimise_count = 0; iq->timeout_count = 0; if (qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; else iq->minimisation_state = DONOT_MINIMISE_STATE; memset(&iq->qinfo_out, 0, sizeof(struct query_info)); return 1; } /** * Transition to the next state. This can be used to advance a currently * processing event. It cannot be used to reactivate a forEvent. * * @param iq: iterator query state * @param nextstate The state to transition to. * @return true. This is so this can be called as the return value for the * actual process*State() methods. (Transitioning to the next state * implies further processing). */ static int next_state(struct iter_qstate* iq, enum iter_state nextstate) { /* If transitioning to a "response" state, make sure that there is a * response */ if(iter_state_is_responsestate(nextstate)) { if(iq->response == NULL) { log_err("transitioning to response state sans " "response."); } } iq->state = nextstate; return 1; } /** * Transition an event to its final state. Final states always either return * a result up the module chain, or reactivate a dependent event. Which * final state to transition to is set in the module state for the event when * it was created, and depends on the original purpose of the event. * * The response is stored in the qstate->buf buffer. * * @param iq: iterator query state * @return false. This is so this method can be used as the return value for * the processState methods. (Transitioning to the final state */ static int final_state(struct iter_qstate* iq) { return next_state(iq, iq->final_state); } /** * Callback routine to handle errors in parent query states * @param qstate: query state that failed. * @param id: module id. * @param super: super state. */ static void error_supers(struct module_qstate* qstate, int id, struct module_qstate* super) { struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id]; struct iter_qstate* super_iq = (struct iter_qstate*)super->minfo[id]; if(qstate->qinfo.qtype == LDNS_RR_TYPE_A || qstate->qinfo.qtype == LDNS_RR_TYPE_AAAA) { /* mark address as failed. */ struct delegpt_ns* dpns = NULL; super_iq->num_target_queries--; if(super_iq->dp) dpns = delegpt_find_ns(super_iq->dp, qstate->qinfo.qname, qstate->qinfo.qname_len); if(!dpns) { /* not interested */ /* this can happen, for eg. qname minimisation asked * for an NXDOMAIN to be validated, and used qtype * A for that, and the error of that, the name, is * not listed in super_iq->dp */ verbose(VERB_ALGO, "subq error, but not interested"); log_query_info(VERB_ALGO, "superq", &super->qinfo); return; } else { /* see if the failure did get (parent-lame) info */ if(!cache_fill_missing(super->env, super_iq->qchase.qclass, super->region, super_iq->dp, 0)) log_err("out of memory adding missing"); } delegpt_mark_neg(dpns, qstate->qinfo.qtype); if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) && (dpns->got6 == 2 || !ie->supports_ipv6)) { dpns->resolved = 1; /* mark as failed */ target_count_increase_nx(super_iq, 1); } } if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS) { /* prime failed to get delegation */ super_iq->dp = NULL; } /* evaluate targets again */ super_iq->state = QUERYTARGETS_STATE; /* super becomes runnable, and will process this change */ } /** * Return an error to the client * @param qstate: our query state * @param id: module id * @param rcode: error code (DNS errcode). * @return: 0 for use by caller, to make notation easy, like: * return error_response(..). */ static int error_response(struct module_qstate* qstate, int id, int rcode) { verbose(VERB_QUERY, "return error response %s", sldns_lookup_by_id(sldns_rcodes, rcode)? sldns_lookup_by_id(sldns_rcodes, rcode)->name:"??"); qstate->return_rcode = rcode; qstate->return_msg = NULL; qstate->ext_state[id] = module_finished; return 0; } /** * Return an error to the client and cache the error code in the * message cache (so per qname, qtype, qclass). * @param qstate: our query state * @param id: module id * @param rcode: error code (DNS errcode). * @return: 0 for use by caller, to make notation easy, like: * return error_response(..). */ static int error_response_cache(struct module_qstate* qstate, int id, int rcode) { struct reply_info err; struct msgreply_entry* msg; if(qstate->no_cache_store) { qstate->error_response_cache = 1; return error_response(qstate, id, rcode); } if(qstate->prefetch_leeway > NORR_TTL) { verbose(VERB_ALGO, "error response for prefetch in cache"); /* attempt to adjust the cache entry prefetch */ if(dns_cache_prefetch_adjust(qstate->env, &qstate->qinfo, NORR_TTL, qstate->query_flags)) return error_response(qstate, id, rcode); /* if that fails (not in cache), fall through to store err */ } if((msg=msg_cache_lookup(qstate->env, qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, qstate->qinfo.qclass, qstate->query_flags, 0, qstate->env->cfg->serve_expired)) != NULL) { struct reply_info* rep = (struct reply_info*)msg->entry.data; if(qstate->env->cfg->serve_expired && rep) { if(qstate->env->cfg->serve_expired_ttl_reset && *qstate->env->now + qstate->env->cfg->serve_expired_ttl > rep->serve_expired_ttl) { verbose(VERB_ALGO, "reset serve-expired-ttl for " "response in cache"); rep->serve_expired_ttl = *qstate->env->now + qstate->env->cfg->serve_expired_ttl; } verbose(VERB_ALGO, "set serve-expired-norec-ttl for " "response in cache"); rep->serve_expired_norec_ttl = NORR_TTL + *qstate->env->now; } if(rep && (FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NOERROR || FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_NXDOMAIN || FLAGS_GET_RCODE(rep->flags) == LDNS_RCODE_YXDOMAIN) && (qstate->env->cfg->serve_expired || *qstate->env->now <= rep->ttl)) { /* we have a good entry, don't overwrite */ lock_rw_unlock(&msg->entry.lock); return error_response(qstate, id, rcode); } lock_rw_unlock(&msg->entry.lock); /* nothing interesting is cached (already error response or * expired good record when we don't serve expired), so this * servfail cache entry is useful (stops waste of time on this * servfail NORR_TTL) */ } /* store in cache */ memset(&err, 0, sizeof(err)); err.flags = (uint16_t)(BIT_QR | BIT_RA); FLAGS_SET_RCODE(err.flags, rcode); err.qdcount = 1; err.ttl = NORR_TTL; err.prefetch_ttl = PREFETCH_TTL_CALC(err.ttl); err.serve_expired_ttl = NORR_TTL; /* do not waste time trying to validate this servfail */ err.security = sec_status_indeterminate; verbose(VERB_ALGO, "store error response in message cache"); iter_dns_store(qstate->env, &qstate->qinfo, &err, 0, 0, 0, NULL, qstate->query_flags, qstate->qstarttime, qstate->is_valrec); return error_response(qstate, id, rcode); } /** check if prepend item is duplicate item */ static int prepend_is_duplicate(struct ub_packed_rrset_key** sets, size_t to, struct ub_packed_rrset_key* dup) { size_t i; for(i=0; irk.type == dup->rk.type && sets[i]->rk.rrset_class == dup->rk.rrset_class && sets[i]->rk.dname_len == dup->rk.dname_len && query_dname_compare(sets[i]->rk.dname, dup->rk.dname) == 0) return 1; } return 0; } /** prepend the prepend list in the answer and authority section of dns_msg */ static int iter_prepend(struct iter_qstate* iq, struct dns_msg* msg, struct regional* region) { struct iter_prep_list* p; struct ub_packed_rrset_key** sets; size_t num_an = 0, num_ns = 0;; for(p = iq->an_prepend_list; p; p = p->next) num_an++; for(p = iq->ns_prepend_list; p; p = p->next) num_ns++; if(num_an + num_ns == 0) return 1; verbose(VERB_ALGO, "prepending %d rrsets", (int)num_an + (int)num_ns); if(num_an > RR_COUNT_MAX || num_ns > RR_COUNT_MAX || msg->rep->rrset_count > RR_COUNT_MAX) return 0; /* overflow */ sets = regional_alloc(region, (num_an+num_ns+msg->rep->rrset_count) * sizeof(struct ub_packed_rrset_key*)); if(!sets) return 0; /* ANSWER section */ num_an = 0; for(p = iq->an_prepend_list; p; p = p->next) { sets[num_an++] = p->rrset; if(ub_packed_rrset_ttl(p->rrset) < msg->rep->ttl) { msg->rep->ttl = ub_packed_rrset_ttl(p->rrset); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } } memcpy(sets+num_an, msg->rep->rrsets, msg->rep->an_numrrsets * sizeof(struct ub_packed_rrset_key*)); /* AUTH section */ num_ns = 0; for(p = iq->ns_prepend_list; p; p = p->next) { if(prepend_is_duplicate(sets+msg->rep->an_numrrsets+num_an, num_ns, p->rrset) || prepend_is_duplicate( msg->rep->rrsets+msg->rep->an_numrrsets, msg->rep->ns_numrrsets, p->rrset)) continue; sets[msg->rep->an_numrrsets + num_an + num_ns++] = p->rrset; if(ub_packed_rrset_ttl(p->rrset) < msg->rep->ttl) { msg->rep->ttl = ub_packed_rrset_ttl(p->rrset); msg->rep->prefetch_ttl = PREFETCH_TTL_CALC(msg->rep->ttl); msg->rep->serve_expired_ttl = msg->rep->ttl + SERVE_EXPIRED_TTL; } } memcpy(sets + num_an + msg->rep->an_numrrsets + num_ns, msg->rep->rrsets + msg->rep->an_numrrsets, (msg->rep->ns_numrrsets + msg->rep->ar_numrrsets) * sizeof(struct ub_packed_rrset_key*)); /* NXDOMAIN rcode can stay if we prepended DNAME/CNAMEs, because * this is what recursors should give. */ msg->rep->rrset_count += num_an + num_ns; msg->rep->an_numrrsets += num_an; msg->rep->ns_numrrsets += num_ns; msg->rep->rrsets = sets; return 1; } /** * Find rrset in ANSWER prepend list. * to avoid duplicate DNAMEs when a DNAME is traversed twice. * @param iq: iterator query state. * @param rrset: rrset to add. * @return false if not found */ static int iter_find_rrset_in_prepend_answer(struct iter_qstate* iq, struct ub_packed_rrset_key* rrset) { struct iter_prep_list* p = iq->an_prepend_list; while(p) { if(ub_rrset_compare(p->rrset, rrset) == 0 && rrsetdata_equal((struct packed_rrset_data*)p->rrset ->entry.data, (struct packed_rrset_data*)rrset ->entry.data)) return 1; p = p->next; } return 0; } /** * Add rrset to ANSWER prepend list * @param qstate: query state. * @param iq: iterator query state. * @param rrset: rrset to add. * @return false on failure (malloc). */ static int iter_add_prepend_answer(struct module_qstate* qstate, struct iter_qstate* iq, struct ub_packed_rrset_key* rrset) { struct iter_prep_list* p = (struct iter_prep_list*)regional_alloc( qstate->region, sizeof(struct iter_prep_list)); if(!p) return 0; p->rrset = rrset; p->next = NULL; /* add at end */ if(iq->an_prepend_last) iq->an_prepend_last->next = p; else iq->an_prepend_list = p; iq->an_prepend_last = p; return 1; } /** * Add rrset to AUTHORITY prepend list * @param qstate: query state. * @param iq: iterator query state. * @param rrset: rrset to add. * @return false on failure (malloc). */ static int iter_add_prepend_auth(struct module_qstate* qstate, struct iter_qstate* iq, struct ub_packed_rrset_key* rrset) { struct iter_prep_list* p = (struct iter_prep_list*)regional_alloc( qstate->region, sizeof(struct iter_prep_list)); if(!p) return 0; p->rrset = rrset; p->next = NULL; /* add at end */ if(iq->ns_prepend_last) iq->ns_prepend_last->next = p; else iq->ns_prepend_list = p; iq->ns_prepend_last = p; return 1; } /** * Given a CNAME response (defined as a response containing a CNAME or DNAME * that does not answer the request), process the response, modifying the * state as necessary. This follows the CNAME/DNAME chain and returns the * final query name. * * sets the new query name, after following the CNAME/DNAME chain. * @param qstate: query state. * @param iq: iterator query state. * @param msg: the response. * @param mname: returned target new query name. * @param mname_len: length of mname. * @return false on (malloc) error. */ static int handle_cname_response(struct module_qstate* qstate, struct iter_qstate* iq, struct dns_msg* msg, uint8_t** mname, size_t* mname_len) { size_t i; /* Start with the (current) qname. */ *mname = iq->qchase.qname; *mname_len = iq->qchase.qname_len; /* Iterate over the ANSWER rrsets in order, looking for CNAMEs and * DNAMES. */ for(i=0; irep->an_numrrsets; i++) { struct ub_packed_rrset_key* r = msg->rep->rrsets[i]; /* If there is a (relevant) DNAME, add it to the list. * We always expect there to be CNAME that was generated * by this DNAME following, so we don't process the DNAME * directly. */ if(ntohs(r->rk.type) == LDNS_RR_TYPE_DNAME && dname_strict_subdomain_c(*mname, r->rk.dname) && !iter_find_rrset_in_prepend_answer(iq, r)) { if(!iter_add_prepend_answer(qstate, iq, r)) return 0; continue; } if(ntohs(r->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(*mname, r->rk.dname) == 0 && !iter_find_rrset_in_prepend_answer(iq, r)) { /* Add this relevant CNAME rrset to the prepend list.*/ if(!iter_add_prepend_answer(qstate, iq, r)) return 0; get_cname_target(r, mname, mname_len); } /* Other rrsets in the section are ignored. */ } /* add authority rrsets to authority prepend, for wildcarded CNAMEs */ for(i=msg->rep->an_numrrsets; irep->an_numrrsets + msg->rep->ns_numrrsets; i++) { struct ub_packed_rrset_key* r = msg->rep->rrsets[i]; /* only add NSEC/NSEC3, as they may be needed for validation */ if(ntohs(r->rk.type) == LDNS_RR_TYPE_NSEC || ntohs(r->rk.type) == LDNS_RR_TYPE_NSEC3) { if(!iter_add_prepend_auth(qstate, iq, r)) return 0; } } return 1; } /** fill fail address for later recovery */ static void fill_fail_addr(struct iter_qstate* iq, struct sockaddr_storage* addr, socklen_t addrlen) { if(addrlen == 0) { iq->fail_addr_type = 0; return; } if(((struct sockaddr_in*)addr)->sin_family == AF_INET) { iq->fail_addr_type = 4; memcpy(&iq->fail_addr.in, &((struct sockaddr_in*)addr)->sin_addr, sizeof(iq->fail_addr.in)); } #ifdef AF_INET6 else if(((struct sockaddr_in*)addr)->sin_family == AF_INET6) { iq->fail_addr_type = 6; memcpy(&iq->fail_addr.in6, &((struct sockaddr_in6*)addr)->sin6_addr, sizeof(iq->fail_addr.in6)); } #endif else { iq->fail_addr_type = 0; } } /** print fail addr to string */ static void print_fail_addr(struct iter_qstate* iq, char* buf, size_t len) { if(iq->fail_addr_type == 4) { if(inet_ntop(AF_INET, &iq->fail_addr.in, buf, (socklen_t)len) == 0) (void)strlcpy(buf, "(inet_ntop error)", len); } #ifdef AF_INET6 else if(iq->fail_addr_type == 6) { if(inet_ntop(AF_INET6, &iq->fail_addr.in6, buf, (socklen_t)len) == 0) (void)strlcpy(buf, "(inet_ntop error)", len); } #endif else (void)strlcpy(buf, "", len); } /** add response specific error information for log servfail */ static void errinf_reply(struct module_qstate* qstate, struct iter_qstate* iq) { if(qstate->env->cfg->val_log_level < 2 && !qstate->env->cfg->log_servfail) return; if((qstate->reply && qstate->reply->remote_addrlen != 0) || (iq->fail_addr_type != 0)) { char from[256], frm[512]; if(qstate->reply && qstate->reply->remote_addrlen != 0) addr_to_str(&qstate->reply->remote_addr, qstate->reply->remote_addrlen, from, sizeof(from)); else print_fail_addr(iq, from, sizeof(from)); snprintf(frm, sizeof(frm), "from %s", from); errinf(qstate, frm); } if(iq->scrub_failures || iq->parse_failures) { if(iq->scrub_failures) errinf(qstate, "upstream response failed scrub"); if(iq->parse_failures) errinf(qstate, "could not parse upstream response"); } else if(iq->response == NULL && iq->timeout_count != 0) { errinf(qstate, "upstream server timeout"); } else if(iq->response == NULL) { errinf(qstate, "no server to query"); if(iq->dp) { if(iq->dp->target_list == NULL) errinf(qstate, "no addresses for nameservers"); else errinf(qstate, "nameserver addresses not usable"); if(iq->dp->nslist == NULL) errinf(qstate, "have no nameserver names"); if(iq->dp->bogus) errinf(qstate, "NS record was dnssec bogus"); } } if(iq->response && iq->response->rep) { if(FLAGS_GET_RCODE(iq->response->rep->flags) != 0) { char rcode[256], rc[32]; (void)sldns_wire2str_rcode_buf( FLAGS_GET_RCODE(iq->response->rep->flags), rc, sizeof(rc)); snprintf(rcode, sizeof(rcode), "got %s", rc); errinf(qstate, rcode); } else { /* rcode NOERROR */ if(iq->response->rep->an_numrrsets == 0) { errinf(qstate, "nodata answer"); } } } } /** see if last resort is possible - does config allow queries to parent */ static int can_have_last_resort(struct module_env* env, uint8_t* nm, size_t ATTR_UNUSED(nmlen), uint16_t qclass, int* have_dp, struct delegpt** retdp, struct regional* region) { struct delegpt* dp = NULL; int nolock = 0; /* do not process a last resort (the parent side) if a stub * or forward is configured, because we do not want to go 'above' * the configured servers */ if(!dname_is_root(nm) && (dp = hints_find(env->hints, nm, qclass, nolock)) && /* has_parent side is turned off for stub_first, where we * are allowed to go to the parent */ dp->has_parent_side_NS) { if(retdp) *retdp = delegpt_copy(dp, region); lock_rw_unlock(&env->hints->lock); if(have_dp) *have_dp = 1; return 0; } if(dp) { lock_rw_unlock(&env->hints->lock); dp = NULL; } if((dp = forwards_find(env->fwds, nm, qclass, nolock)) && /* has_parent_side is turned off for forward_first, where * we are allowed to go to the parent */ dp->has_parent_side_NS) { if(retdp) *retdp = delegpt_copy(dp, region); lock_rw_unlock(&env->fwds->lock); if(have_dp) *have_dp = 1; return 0; } /* lock_() calls are macros that could be nothing, surround in {} */ if(dp) { lock_rw_unlock(&env->fwds->lock); } return 1; } /** see if target name is caps-for-id whitelisted */ static int is_caps_whitelisted(struct iter_env* ie, struct iter_qstate* iq) { if(!ie->caps_white) return 0; /* no whitelist, or no capsforid */ return name_tree_lookup(ie->caps_white, iq->qchase.qname, iq->qchase.qname_len, dname_count_labels(iq->qchase.qname), iq->qchase.qclass) != NULL; } /** * Create target count structure for this query. This is always explicitly * created for the parent query. */ static void target_count_create(struct iter_qstate* iq) { if(!iq->target_count) { iq->target_count = (int*)calloc(TARGET_COUNT_MAX, sizeof(int)); /* if calloc fails we simply do not track this number */ if(iq->target_count) { iq->target_count[TARGET_COUNT_REF] = 1; iq->nxns_dp = (uint8_t**)calloc(1, sizeof(uint8_t*)); } } } static void target_count_increase(struct iter_qstate* iq, int num) { target_count_create(iq); if(iq->target_count) iq->target_count[TARGET_COUNT_QUERIES] += num; iq->dp_target_count++; } static void target_count_increase_nx(struct iter_qstate* iq, int num) { target_count_create(iq); if(iq->target_count) iq->target_count[TARGET_COUNT_NX] += num; } static void target_count_increase_global_quota(struct iter_qstate* iq, int num) { target_count_create(iq); if(iq->target_count) iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] += num; } /** * Generate a subrequest. * Generate a local request event. Local events are tied to this module, and * have a corresponding (first tier) event that is waiting for this event to * resolve to continue. * * @param qname The query name for this request. * @param qnamelen length of qname * @param qtype The query type for this request. * @param qclass The query class for this request. * @param qstate The event that is generating this event. * @param id: module id. * @param iq: The iterator state that is generating this event. * @param initial_state The initial response state (normally this * is QUERY_RESP_STATE, unless it is known that the request won't * need iterative processing * @param finalstate The final state for the response to this request. * @param subq_ret: if newly allocated, the subquerystate, or NULL if it does * not need initialisation. * @param v: if true, validation is done on the subquery. * @param detached: true if this qstate should not attach to the subquery * @return false on error (malloc). */ static int generate_sub_request(uint8_t* qname, size_t qnamelen, uint16_t qtype, uint16_t qclass, struct module_qstate* qstate, int id, struct iter_qstate* iq, enum iter_state initial_state, enum iter_state finalstate, struct module_qstate** subq_ret, int v, int detached) { struct module_qstate* subq = NULL; struct iter_qstate* subiq = NULL; uint16_t qflags = 0; /* OPCODE QUERY, no flags */ struct query_info qinf; int prime = (finalstate == PRIME_RESP_STATE)?1:0; int valrec = 0; qinf.qname = qname; 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. */ if(initial_state == INIT_REQUEST_STATE) qflags |= BIT_RD; /* We set the CD flag so we can send this through the "head" of * the resolution chain, which might have a validator. We are * uninterested in validating things not on the direct resolution * path. */ if(!v) { qflags |= BIT_CD; valrec = 1; } if(detached) { struct mesh_state* sub = NULL; fptr_ok(fptr_whitelist_modenv_add_sub( qstate->env->add_sub)); if(!(*qstate->env->add_sub)(qstate, &qinf, NULL, qflags, prime, valrec, &subq, &sub)){ return 0; } } else { /* attach subquery, lookup existing or make a new one */ fptr_ok(fptr_whitelist_modenv_attach_sub( qstate->env->attach_sub)); if(!(*qstate->env->attach_sub)(qstate, &qinf, NULL, qflags, prime, valrec, &subq)) { return 0; } } *subq_ret = subq; if(subq) { /* initialise the new subquery */ subq->curmod = id; subq->ext_state[id] = module_state_initial; subq->minfo[id] = regional_alloc(subq->region, sizeof(struct iter_qstate)); if(!subq->minfo[id]) { log_err("init subq: out of memory"); fptr_ok(fptr_whitelist_modenv_kill_sub( qstate->env->kill_sub)); (*qstate->env->kill_sub)(subq); return 0; } subiq = (struct iter_qstate*)subq->minfo[id]; memset(subiq, 0, sizeof(*subiq)); subiq->num_target_queries = 0; target_count_create(iq); subiq->target_count = iq->target_count; if(iq->target_count) { iq->target_count[TARGET_COUNT_REF] ++; /* extra reference */ subiq->nxns_dp = iq->nxns_dp; } subiq->dp_target_count = 0; subiq->num_current_queries = 0; subiq->depth = iq->depth+1; outbound_list_init(&subiq->outlist); subiq->state = initial_state; subiq->final_state = finalstate; subiq->qchase = subq->qinfo; subiq->chase_flags = subq->query_flags; subiq->refetch_glue = 0; if(qstate->env->cfg->qname_minimisation) subiq->minimisation_state = INIT_MINIMISE_STATE; else subiq->minimisation_state = DONOT_MINIMISE_STATE; memset(&subiq->qinfo_out, 0, sizeof(struct query_info)); } return 1; } /** * Generate and send a root priming request. * @param qstate: the qtstate that triggered the need to prime. * @param iq: iterator query state. * @param id: module id. * @param qclass: the class to prime. * @return 0 on failure */ static int prime_root(struct module_qstate* qstate, struct iter_qstate* iq, int id, uint16_t qclass) { struct delegpt* dp; struct module_qstate* subq; int nolock = 0; verbose(VERB_DETAIL, "priming . %s NS", sldns_lookup_by_id(sldns_rr_classes, (int)qclass)? sldns_lookup_by_id(sldns_rr_classes, (int)qclass)->name:"??"); dp = hints_find_root(qstate->env->hints, qclass, nolock); if(!dp) { verbose(VERB_ALGO, "Cannot prime due to lack of hints"); return 0; } /* Priming requests start at the QUERYTARGETS state, skipping * the normal INIT state logic (which would cause an infloop). */ if(!generate_sub_request((uint8_t*)"\000", 1, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { lock_rw_unlock(&qstate->env->hints->lock); verbose(VERB_ALGO, "could not prime root"); return 0; } if(subq) { struct iter_qstate* subiq = (struct iter_qstate*)subq->minfo[id]; /* Set the initial delegation point to the hint. * copy dp, it is now part of the root prime query. * dp was part of in the fixed hints structure. */ subiq->dp = delegpt_copy(dp, subq->region); lock_rw_unlock(&qstate->env->hints->lock); if(!subiq->dp) { log_err("out of memory priming root, copydp"); fptr_ok(fptr_whitelist_modenv_kill_sub( qstate->env->kill_sub)); (*qstate->env->kill_sub)(subq); return 0; } /* there should not be any target queries. */ subiq->num_target_queries = 0; subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); } else { lock_rw_unlock(&qstate->env->hints->lock); } /* this module stops, our submodule starts, and does the query. */ qstate->ext_state[id] = module_wait_subquery; return 1; } /** * Generate and process a stub priming request. This method tests for the * need to prime a stub zone, so it is safe to call for every request. * * @param qstate: the qtstate that triggered the need to prime. * @param iq: iterator query state. * @param id: module id. * @param qname: request name. * @param qclass: request class. * @return true if a priming subrequest was made, false if not. The will only * issue a priming request if it detects an unprimed stub. * Uses value of 2 to signal during stub-prime in root-prime situation * that a noprime-stub is available and resolution can continue. */ static int prime_stub(struct module_qstate* qstate, struct iter_qstate* iq, int id, uint8_t* qname, uint16_t qclass) { /* Lookup the stub hint. This will return null if the stub doesn't * need to be re-primed. */ struct iter_hints_stub* stub; struct delegpt* stub_dp; struct module_qstate* subq; int nolock = 0; if(!qname) return 0; stub = hints_lookup_stub(qstate->env->hints, qname, qclass, iq->dp, nolock); /* The stub (if there is one) does not need priming. */ if(!stub) return 0; stub_dp = stub->dp; /* if we have an auth_zone dp, and stub is equal, don't prime stub * yet, unless we want to fallback and avoid the auth_zone */ if(!iq->auth_zone_avoid && iq->dp && iq->dp->auth_dp && query_dname_compare(iq->dp->name, stub_dp->name) == 0) { lock_rw_unlock(&qstate->env->hints->lock); return 0; } /* is it a noprime stub (always use) */ if(stub->noprime) { int r = 0; if(iq->dp == NULL) r = 2; /* copy the dp out of the fixed hints structure, so that * it can be changed when servicing this query */ iq->dp = delegpt_copy(stub_dp, qstate->region); lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory priming stub"); errinf(qstate, "malloc failure, priming stub"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return 1; /* return 1 to make module stop, with error */ } log_nametypeclass(VERB_DETAIL, "use stub", iq->dp->name, LDNS_RR_TYPE_NS, qclass); return r; } /* Otherwise, we need to (re)prime the stub. */ log_nametypeclass(VERB_DETAIL, "priming stub", stub_dp->name, LDNS_RR_TYPE_NS, qclass); /* Stub priming events start at the QUERYTARGETS state to avoid the * redundant INIT state processing. */ if(!generate_sub_request(stub_dp->name, stub_dp->namelen, LDNS_RR_TYPE_NS, qclass, qstate, id, iq, QUERYTARGETS_STATE, PRIME_RESP_STATE, &subq, 0, 0)) { lock_rw_unlock(&qstate->env->hints->lock); verbose(VERB_ALGO, "could not prime stub"); errinf(qstate, "could not generate lookup for stub prime"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return 1; /* return 1 to make module stop, with error */ } if(subq) { struct iter_qstate* subiq = (struct iter_qstate*)subq->minfo[id]; /* Set the initial delegation point to the hint. */ /* make copy to avoid use of stub dp by different qs/threads */ subiq->dp = delegpt_copy(stub_dp, subq->region); lock_rw_unlock(&qstate->env->hints->lock); if(!subiq->dp) { log_err("out of memory priming stub, copydp"); fptr_ok(fptr_whitelist_modenv_kill_sub( qstate->env->kill_sub)); (*qstate->env->kill_sub)(subq); errinf(qstate, "malloc failure, in stub prime"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return 1; /* return 1 to make module stop, with error */ } /* there should not be any target queries -- although there * wouldn't be anyway, since stub hints never have * missing targets. */ subiq->num_target_queries = 0; subiq->wait_priming_stub = 1; subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); } else { lock_rw_unlock(&qstate->env->hints->lock); } /* this module stops, our submodule starts, and does the query. */ qstate->ext_state[id] = module_wait_subquery; return 1; } /** * Generate a delegation point for an auth zone (unless cached dp is better) * false on alloc failure. */ static int auth_zone_delegpt(struct module_qstate* qstate, struct iter_qstate* iq, uint8_t* delname, size_t delnamelen) { struct auth_zone* z; if(iq->auth_zone_avoid) return 1; if(!delname) { delname = iq->qchase.qname; delnamelen = iq->qchase.qname_len; } lock_rw_rdlock(&qstate->env->auth_zones->lock); z = auth_zones_find_zone(qstate->env->auth_zones, delname, delnamelen, qstate->qinfo.qclass); if(!z) { lock_rw_unlock(&qstate->env->auth_zones->lock); return 1; } lock_rw_rdlock(&z->lock); lock_rw_unlock(&qstate->env->auth_zones->lock); if(z->for_upstream) { if(iq->dp && query_dname_compare(z->name, iq->dp->name) == 0 && iq->dp->auth_dp && qstate->blacklist && z->fallback_enabled) { /* cache is blacklisted and fallback, and we * already have an auth_zone dp */ if(verbosity>=VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf); verbose(VERB_ALGO, "auth_zone %s " "fallback because cache blacklisted", buf); } lock_rw_unlock(&z->lock); iq->dp = NULL; return 1; } if(iq->dp==NULL || dname_subdomain_c(z->name, iq->dp->name)) { struct delegpt* dp; if(qstate->blacklist && z->fallback_enabled) { /* cache is blacklisted because of a DNSSEC * validation failure, and the zone allows * fallback to the internet, query there. */ if(verbosity>=VERB_ALGO) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(z->name, buf); verbose(VERB_ALGO, "auth_zone %s " "fallback because cache blacklisted", buf); } lock_rw_unlock(&z->lock); return 1; } dp = (struct delegpt*)regional_alloc_zero( qstate->region, sizeof(*dp)); if(!dp) { log_err("alloc failure"); if(z->fallback_enabled) { lock_rw_unlock(&z->lock); return 1; /* just fallback */ } lock_rw_unlock(&z->lock); errinf(qstate, "malloc failure"); return 0; } dp->name = regional_alloc_init(qstate->region, z->name, z->namelen); if(!dp->name) { log_err("alloc failure"); if(z->fallback_enabled) { lock_rw_unlock(&z->lock); return 1; /* just fallback */ } lock_rw_unlock(&z->lock); errinf(qstate, "malloc failure"); return 0; } dp->namelen = z->namelen; dp->namelabs = z->namelabs; dp->auth_dp = 1; iq->dp = dp; } } lock_rw_unlock(&z->lock); return 1; } /** * Generate A and AAAA checks for glue that is in-zone for the referral * we just got to obtain authoritative information on the addresses. * * @param qstate: the qtstate that triggered the need to prime. * @param iq: iterator query state. * @param id: module id. */ static void generate_a_aaaa_check(struct module_qstate* qstate, struct iter_qstate* iq, int id) { struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id]; struct module_qstate* subq; size_t i; struct reply_info* rep = iq->response->rep; struct ub_packed_rrset_key* s; log_assert(iq->dp); if(iq->depth == ie->max_dependency_depth) return; /* walk through additional, and check if in-zone, * only relevant A, AAAA are left after scrub anyway */ for(i=rep->an_numrrsets+rep->ns_numrrsets; irrset_count; i++) { s = rep->rrsets[i]; /* check *ALL* addresses that are transmitted in additional*/ /* is it an address ? */ if( !(ntohs(s->rk.type)==LDNS_RR_TYPE_A || ntohs(s->rk.type)==LDNS_RR_TYPE_AAAA)) { continue; } /* is this query the same as the A/AAAA check for it */ if(qstate->qinfo.qtype == ntohs(s->rk.type) && qstate->qinfo.qclass == ntohs(s->rk.rrset_class) && query_dname_compare(qstate->qinfo.qname, s->rk.dname)==0 && (qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD)) continue; /* generate subrequest for it */ log_nametypeclass(VERB_ALGO, "schedule addr fetch", s->rk.dname, ntohs(s->rk.type), ntohs(s->rk.rrset_class)); if(!generate_sub_request(s->rk.dname, s->rk.dname_len, ntohs(s->rk.type), ntohs(s->rk.rrset_class), qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate addr check"); return; } /* ignore subq - not need for more init */ } } /** * Generate a NS check request to obtain authoritative information * on an NS rrset. * * @param qstate: the qstate that triggered the need to prime. * @param iq: iterator query state. * @param id: module id. */ static void generate_ns_check(struct module_qstate* qstate, struct iter_qstate* iq, int id) { struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id]; struct module_qstate* subq; log_assert(iq->dp); if(iq->depth == ie->max_dependency_depth) return; if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, NULL, NULL, NULL)) return; /* is this query the same as the nscheck? */ if(qstate->qinfo.qtype == LDNS_RR_TYPE_NS && query_dname_compare(iq->dp->name, qstate->qinfo.qname)==0 && (qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD)){ /* spawn off A, AAAA queries for in-zone glue to check */ generate_a_aaaa_check(qstate, iq, id); return; } /* no need to get the NS record for DS, it is above the zonecut */ if(qstate->qinfo.qtype == LDNS_RR_TYPE_DS) return; log_nametypeclass(VERB_ALGO, "schedule ns fetch", iq->dp->name, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate ns check"); return; } if(subq) { struct iter_qstate* subiq = (struct iter_qstate*)subq->minfo[id]; /* make copy to avoid use of stub dp by different qs/threads */ /* refetch glue to start higher up the tree */ subiq->refetch_glue = 1; subiq->dp = delegpt_copy(iq->dp, subq->region); if(!subiq->dp) { log_err("out of memory generating ns check, copydp"); fptr_ok(fptr_whitelist_modenv_kill_sub( qstate->env->kill_sub)); (*qstate->env->kill_sub)(subq); return; } } } /** * Generate a DNSKEY prefetch query to get the DNSKEY for the DS record we * just got in a referral (where we have dnssec_expected, thus have trust * anchors above it). Note that right after calling this routine the * iterator detached subqueries (because of following the referral), and thus * the DNSKEY query becomes detached, its return stored in the cache for * later lookup by the validator. This cache lookup by the validator avoids * the roundtrip incurred by the DNSKEY query. The DNSKEY query is now * performed at about the same time the original query is sent to the domain, * thus the two answers are likely to be returned at about the same time, * saving a roundtrip from the validated lookup. * * @param qstate: the qtstate that triggered the need to prime. * @param iq: iterator query state. * @param id: module id. */ static void generate_dnskey_prefetch(struct module_qstate* qstate, struct iter_qstate* iq, int id) { struct module_qstate* subq; log_assert(iq->dp); /* is this query the same as the prefetch? */ if(qstate->qinfo.qtype == LDNS_RR_TYPE_DNSKEY && query_dname_compare(iq->dp->name, qstate->qinfo.qname)==0 && (qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD)){ return; } /* we do not generate this prefetch when the query list is full, * the query is fetched, if needed, when the validator wants it. * At that time the validator waits for it, after spawning it. * This means there is one state that uses cpu and a socket, the * spawned while this one waits, and not several at the same time, * if we had created the lookup here. And this helps to keep * the total load down, but the query still succeeds to resolve. */ if(mesh_jostle_exceeded(qstate->env->mesh)) return; /* if the DNSKEY is in the cache this lookup will stop quickly */ log_nametypeclass(VERB_ALGO, "schedule dnskey prefetch", iq->dp->name, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass); if(!generate_sub_request(iq->dp->name, iq->dp->namelen, LDNS_RR_TYPE_DNSKEY, iq->qchase.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) { /* we'll be slower, but it'll work */ verbose(VERB_ALGO, "could not generate dnskey prefetch"); return; } if(subq) { struct iter_qstate* subiq = (struct iter_qstate*)subq->minfo[id]; /* this qstate has the right delegation for the dnskey lookup*/ /* make copy to avoid use of stub dp by different qs/threads */ subiq->dp = delegpt_copy(iq->dp, subq->region); /* if !subiq->dp, it'll start from the cache, no problem */ } } /** * See if the query needs forwarding. * * @param qstate: query state. * @param iq: iterator query state. * @return true if the request is forwarded, false if not. * If returns true but, iq->dp is NULL then a malloc failure occurred. */ static int forward_request(struct module_qstate* qstate, struct iter_qstate* iq) { struct delegpt* dp; uint8_t* delname = iq->qchase.qname; size_t delnamelen = iq->qchase.qname_len; int nolock = 0; if(iq->refetch_glue && iq->dp) { delname = iq->dp->name; delnamelen = iq->dp->namelen; } /* strip one label off of DS query to lookup higher for it */ if( (iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue) && !dname_is_root(iq->qchase.qname)) dname_remove_label(&delname, &delnamelen); dp = forwards_lookup(qstate->env->fwds, delname, iq->qchase.qclass, nolock); if(!dp) return 0; /* send recursion desired to forward addr */ iq->chase_flags |= BIT_RD; iq->dp = delegpt_copy(dp, qstate->region); lock_rw_unlock(&qstate->env->fwds->lock); /* iq->dp checked by caller */ verbose(VERB_ALGO, "forwarding request"); return 1; } /** * Process the initial part of the request handling. This state roughly * corresponds to resolver algorithms steps 1 (find answer in cache) and 2 * (find the best servers to ask). * * Note that all requests start here, and query restarts revisit this state. * * This state either generates: 1) a response, from cache or error, 2) a * priming event, or 3) forwards the request to the next state (init2, * generally). * * @param qstate: query state. * @param iq: iterator query state. * @param ie: iterator shared global environment. * @param id: module id. * @return true if the event needs more request processing immediately, * false if not. */ static int processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { uint8_t dpname_storage[LDNS_MAX_DOMAINLEN+1]; uint8_t* delname, *dpname=NULL; size_t delnamelen, dpnamelen=0; struct dns_msg* msg = NULL; log_query_info(VERB_DETAIL, "resolving", &qstate->qinfo); /* check effort */ /* We enforce a maximum number of query restarts. This is primarily a * cheap way to prevent CNAME loops. */ if(iq->query_restart_count > ie->max_query_restarts) { verbose(VERB_QUERY, "request has exceeded the maximum number" " of query restarts with %d", iq->query_restart_count); errinf(qstate, "request has exceeded the maximum number " "restarts (eg. indirections)"); if(iq->qchase.qname) errinf_dname(qstate, "stop at", iq->qchase.qname); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* We enforce a maximum recursion/dependency depth -- in general, * this is unnecessary for dependency loops (although it will * catch those), but it provides a sensible limit to the amount * of work required to answer a given query. */ verbose(VERB_ALGO, "request has dependency depth of %d", iq->depth); if(iq->depth > ie->max_dependency_depth) { verbose(VERB_QUERY, "request has exceeded the maximum " "dependency depth with depth of %d", iq->depth); errinf(qstate, "request has exceeded the maximum dependency " "depth (eg. nameserver lookup recursion)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* If the request is qclass=ANY, setup to generate each class */ if(qstate->qinfo.qclass == LDNS_RR_CLASS_ANY) { iq->qchase.qclass = 0; return next_state(iq, COLLECT_CLASS_STATE); } /* * If we are restricted by a forward-zone or a stub-zone, we * can't re-fetch glue for this delegation point. * we won’t try to re-fetch glue if the iq->dp is null. */ if (iq->refetch_glue && iq->dp && !can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, NULL, NULL, NULL)) { iq->refetch_glue = 0; } /* Resolver Algorithm Step 1 -- Look for the answer in local data. */ /* This either results in a query restart (CNAME cache response), a * terminating response (ANSWER), or a cache miss (null). */ /* Check RPZ for override */ if(qstate->env->auth_zones) { /* apply rpz qname triggers, like after cname */ struct dns_msg* forged_response = rpz_callback_from_iterator_cname(qstate, iq); if(forged_response) { uint8_t* sname = 0; size_t slen = 0; int count = 0; while(forged_response && reply_find_rrset_section_an( forged_response->rep, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_CNAME, iq->qchase.qclass) && iq->qchase.qtype != LDNS_RR_TYPE_CNAME && count++ < ie->max_query_restarts) { /* another cname to follow */ if(!handle_cname_response(qstate, iq, forged_response, &sname, &slen)) { errinf(qstate, "malloc failure, CNAME info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->qchase.qname = sname; iq->qchase.qname_len = slen; forged_response = rpz_callback_from_iterator_cname(qstate, iq); } if(forged_response != NULL) { qstate->ext_state[id] = module_finished; qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = forged_response; iq->response = forged_response; next_state(iq, FINISHED_STATE); if(!iter_prepend(iq, qstate->return_msg, qstate->region)) { log_err("rpz: after cached cname, prepend rrsets: out of memory"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } qstate->return_msg->qinfo = qstate->qinfo; return 0; } /* Follow the CNAME response */ iq->dp = NULL; iq->refetch_glue = 0; iq->query_restart_count++; iq->sent_count = 0; iq->dp_target_count = 0; sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; return next_state(iq, INIT_REQUEST_STATE); } } if (iter_stub_fwd_no_cache(qstate, &iq->qchase, &dpname, &dpnamelen, dpname_storage, sizeof(dpname_storage))) { /* Asked to not query cache. */ verbose(VERB_ALGO, "no-cache set, going to the network"); qstate->no_cache_lookup = 1; qstate->no_cache_store = 1; msg = NULL; } else if(qstate->blacklist) { /* if cache, or anything else, was blacklisted then * getting older results from cache is a bad idea, no cache */ verbose(VERB_ALGO, "cache blacklisted, going to the network"); msg = NULL; } else if(!qstate->no_cache_lookup) { msg = dns_cache_lookup(qstate->env, iq->qchase.qname, iq->qchase.qname_len, iq->qchase.qtype, iq->qchase.qclass, qstate->query_flags, qstate->region, qstate->env->scratch, 0, dpname, dpnamelen); if(!msg && qstate->env->neg_cache && iter_qname_indicates_dnssec(qstate->env, &iq->qchase)) { /* lookup in negative cache; may result in * NOERROR/NODATA or NXDOMAIN answers that need validation */ msg = val_neg_getmsg(qstate->env->neg_cache, &iq->qchase, qstate->region, qstate->env->rrset_cache, qstate->env->scratch_buffer, *qstate->env->now, 1/*add SOA*/, NULL, qstate->env->cfg); } /* item taken from cache does not match our query name, thus * security needs to be re-examined later */ if(msg && query_dname_compare(qstate->qinfo.qname, iq->qchase.qname) != 0) msg->rep->security = sec_status_unchecked; } if(msg) { /* handle positive cache response */ enum response_type type = response_type_from_cache(msg, &iq->qchase); if(verbosity >= VERB_ALGO) { log_dns_msg("msg from cache lookup", &msg->qinfo, msg->rep); verbose(VERB_ALGO, "msg ttl is %d, prefetch ttl %d", (int)msg->rep->ttl, (int)msg->rep->prefetch_ttl); } if(type == RESPONSE_TYPE_CNAME) { uint8_t* sname = 0; size_t slen = 0; verbose(VERB_ALGO, "returning CNAME response from " "cache"); if(!handle_cname_response(qstate, iq, msg, &sname, &slen)) { errinf(qstate, "failed to prepend CNAME " "components, malloc failure"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->qchase.qname = sname; iq->qchase.qname_len = slen; /* This *is* a query restart, even if it is a cheap * one. */ iq->dp = NULL; iq->refetch_glue = 0; iq->query_restart_count++; iq->sent_count = 0; iq->dp_target_count = 0; sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; return next_state(iq, INIT_REQUEST_STATE); } /* if from cache, NULL, else insert 'cache IP' len=0 */ if(qstate->reply_origin) sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_SERVFAIL) errinf(qstate, "SERVFAIL in cache"); /* it is an answer, response, to final state */ verbose(VERB_ALGO, "returning answer from cache."); iq->response = msg; return final_state(iq); } /* attempt to forward the request */ if(forward_request(qstate, iq)) { if(!iq->dp) { log_err("alloc failure for forward dp"); errinf(qstate, "malloc failure for forward zone"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(!cache_fill_missing(qstate->env, iq->qchase.qclass, qstate->region, iq->dp, 0)) { errinf(qstate, "malloc failure, copy extra info into delegation point"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if((qstate->query_flags&BIT_RD)==0) { /* If the server accepts RD=0 queries and forwards * with RD=1, then if the server is listed as an NS * entry, it starts query loops. Stop that loop by * disallowing the query. The RD=0 was previously used * to check the cache with allow_snoop. For stubs, * the iterator pass would have primed the stub and * then cached information can be used for further * queries. */ verbose(VERB_ALGO, "cannot forward RD=0 query, to stop query loops"); errinf(qstate, "cannot forward RD=0 query"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->refetch_glue = 0; iq->minimisation_state = DONOT_MINIMISE_STATE; /* the request has been forwarded. * forwarded requests need to be immediately sent to the * next state, QUERYTARGETS. */ return next_state(iq, QUERYTARGETS_STATE); } /* Resolver Algorithm Step 2 -- find the "best" servers. */ /* first, adjust for DS queries. To avoid the grandparent problem, * we just look for the closest set of server to the parent of qname. * When re-fetching glue we also need to ask the parent. */ if(iq->refetch_glue) { if(!iq->dp) { log_err("internal or malloc fail: no dp for refetch"); errinf(qstate, "malloc failure, for delegation info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } delname = iq->dp->name; delnamelen = iq->dp->namelen; } else { delname = iq->qchase.qname; delnamelen = iq->qchase.qname_len; } if(iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue || (iq->qchase.qtype == LDNS_RR_TYPE_NS && qstate->prefetch_leeway && can_have_last_resort(qstate->env, delname, delnamelen, iq->qchase.qclass, NULL, NULL, NULL))) { /* remove first label from delname, root goes to hints, * but only to fetch glue, not for qtype=DS. */ /* also when prefetching an NS record, fetch it again from * its parent, just as if it expired, so that you do not * get stuck on an older nameserver that gives old NSrecords */ if(dname_is_root(delname) && (iq->refetch_glue || (iq->qchase.qtype == LDNS_RR_TYPE_NS && qstate->prefetch_leeway))) delname = NULL; /* go to root priming */ else dname_remove_label(&delname, &delnamelen); } /* delname is the name to lookup a delegation for. If NULL rootprime */ while(1) { /* Lookup the delegation in the cache. If null, then the * cache needs to be primed for the qclass. */ if(delname) iq->dp = dns_cache_find_delegation(qstate->env, delname, delnamelen, iq->qchase.qtype, iq->qchase.qclass, qstate->region, &iq->deleg_msg, *qstate->env->now+qstate->prefetch_leeway, 1, dpname, dpnamelen); else iq->dp = NULL; /* If the cache has returned nothing, then we have a * root priming situation. */ if(iq->dp == NULL) { int r; int nolock = 0; /* if under auth zone, no prime needed */ if(!auth_zone_delegpt(qstate, iq, delname, delnamelen)) return error_response(qstate, id, LDNS_RCODE_SERVFAIL); if(iq->dp) /* use auth zone dp */ return next_state(iq, INIT_REQUEST_2_STATE); /* if there is a stub, then no root prime needed */ r = prime_stub(qstate, iq, id, delname, iq->qchase.qclass); if(r == 2) break; /* got noprime-stub-zone, continue */ else if(r) return 0; /* stub prime request made */ if(forwards_lookup_root(qstate->env->fwds, iq->qchase.qclass, nolock)) { lock_rw_unlock(&qstate->env->fwds->lock); /* forward zone root, no root prime needed */ /* fill in some dp - safety belt */ iq->dp = hints_find_root(qstate->env->hints, iq->qchase.qclass, nolock); if(!iq->dp) { log_err("internal error: no hints dp"); errinf(qstate, "no hints for this class"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->dp = delegpt_copy(iq->dp, qstate->region); lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory in safety belt"); errinf(qstate, "malloc failure, in safety belt"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } return next_state(iq, INIT_REQUEST_2_STATE); } /* Note that the result of this will set a new * DelegationPoint based on the result of priming. */ if(!prime_root(qstate, iq, id, iq->qchase.qclass)) return error_response(qstate, id, LDNS_RCODE_REFUSED); /* priming creates and sends a subordinate query, with * this query as the parent. So further processing for * this event will stop until reactivated by the * results of priming. */ return 0; } if(!iq->ratelimit_ok && qstate->prefetch_leeway) iq->ratelimit_ok = 1; /* allow prefetches, this keeps otherwise valid data in the cache */ /* see if this dp not useless. * It is useless if: * o all NS items are required glue. * or the query is for NS item that is required glue. * o no addresses are provided. * o RD qflag is on. * Instead, go up one level, and try to get even further * If the root was useless, use safety belt information. * Only check cache returns, because replies for servers * could be useless but lead to loops (bumping into the * same server reply) if useless-checked. */ if(iter_dp_is_useless(&qstate->qinfo, qstate->query_flags, iq->dp, ie->supports_ipv4, ie->supports_ipv6, ie->nat64.use_nat64)) { int have_dp = 0; if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, &have_dp, &iq->dp, qstate->region)) { if(have_dp) { verbose(VERB_QUERY, "cache has stub " "or fwd but no addresses, " "fallback to config"); if(have_dp && !iq->dp) { log_err("out of memory in " "stub/fwd fallback"); errinf(qstate, "malloc failure, for fallback to config"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } break; } verbose(VERB_ALGO, "useless dp " "but cannot go up, servfail"); delegpt_log(VERB_ALGO, iq->dp); errinf(qstate, "no useful nameservers, " "and cannot go up"); errinf_dname(qstate, "for zone", iq->dp->name); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(dname_is_root(iq->dp->name)) { /* use safety belt */ int nolock = 0; verbose(VERB_QUERY, "Cache has root NS but " "no addresses. Fallback to the safety belt."); iq->dp = hints_find_root(qstate->env->hints, iq->qchase.qclass, nolock); /* note deleg_msg is from previous lookup, * but RD is on, so it is not used */ if(!iq->dp) { log_err("internal error: no hints dp"); return error_response(qstate, id, LDNS_RCODE_REFUSED); } iq->dp = delegpt_copy(iq->dp, qstate->region); lock_rw_unlock(&qstate->env->hints->lock); if(!iq->dp) { log_err("out of memory in safety belt"); errinf(qstate, "malloc failure, in safety belt, for root"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } break; } else { verbose(VERB_ALGO, "cache delegation was useless:"); delegpt_log(VERB_ALGO, iq->dp); /* go up */ delname = iq->dp->name; delnamelen = iq->dp->namelen; dname_remove_label(&delname, &delnamelen); } } else break; } verbose(VERB_ALGO, "cache delegation returns delegpt"); delegpt_log(VERB_ALGO, iq->dp); /* Otherwise, set the current delegation point and move on to the * next state. */ return next_state(iq, INIT_REQUEST_2_STATE); } /** * Process the second part of the initial request handling. This state * basically exists so that queries that generate root priming events have * the same init processing as ones that do not. Request events that reach * this state must have a valid currentDelegationPoint set. * * This part is primarily handling stub zone priming. Events that reach this * state must have a current delegation point. * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @return true if the event needs more request processing immediately, * false if not. */ static int processInitRequest2(struct module_qstate* qstate, struct iter_qstate* iq, int id) { uint8_t* delname; size_t delnamelen; log_query_info(VERB_QUERY, "resolving (init part 2): ", &qstate->qinfo); delname = iq->qchase.qname; delnamelen = iq->qchase.qname_len; if(iq->refetch_glue) { struct iter_hints_stub* stub; int nolock = 0; if(!iq->dp) { log_err("internal or malloc fail: no dp for refetch"); errinf(qstate, "malloc failure, no delegation info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* Do not send queries above stub, do not set delname to dp if * this is above stub without stub-first. */ stub = hints_lookup_stub( qstate->env->hints, iq->qchase.qname, iq->qchase.qclass, iq->dp, nolock); if(!stub || !stub->dp->has_parent_side_NS || dname_subdomain_c(iq->dp->name, stub->dp->name)) { delname = iq->dp->name; delnamelen = iq->dp->namelen; } /* lock_() calls are macros that could be nothing, surround in {} */ if(stub) { lock_rw_unlock(&qstate->env->hints->lock); } } if(iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->refetch_glue) { if(!dname_is_root(delname)) dname_remove_label(&delname, &delnamelen); iq->refetch_glue = 0; /* if CNAME causes restart, no refetch */ } /* see if we have an auth zone to answer from, improves dp from cache * (if any dp from cache) with auth zone dp, if that is lower */ if(!auth_zone_delegpt(qstate, iq, delname, delnamelen)) return error_response(qstate, id, LDNS_RCODE_SERVFAIL); /* Check to see if we need to prime a stub zone. */ if(prime_stub(qstate, iq, id, delname, iq->qchase.qclass)) { /* A priming sub request was made */ return 0; } /* most events just get forwarded to the next state. */ return next_state(iq, INIT_REQUEST_3_STATE); } /** * Process the third part of the initial request handling. This state exists * as a separate state so that queries that generate stub priming events * will get the tail end of the init process but not repeat the stub priming * check. * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @return true, advancing the event to the QUERYTARGETS_STATE. */ static int processInitRequest3(struct module_qstate* qstate, struct iter_qstate* iq, int id) { log_query_info(VERB_QUERY, "resolving (init part 3): ", &qstate->qinfo); /* if the cache reply dp equals a validation anchor or msg has DS, * then DNSSEC RRSIGs are expected in the reply */ iq->dnssec_expected = iter_indicates_dnssec(qstate->env, iq->dp, iq->deleg_msg, iq->qchase.qclass); /* If the RD flag wasn't set, then we just finish with the * cached referral as the response. */ if(!(qstate->query_flags & BIT_RD) && iq->deleg_msg) { iq->response = iq->deleg_msg; if(verbosity >= VERB_ALGO && iq->response) log_dns_msg("no RD requested, using delegation msg", &iq->response->qinfo, iq->response->rep); if(qstate->reply_origin) sock_list_insert(&qstate->reply_origin, NULL, 0, qstate->region); return final_state(iq); } /* After this point, unset the RD flag -- this query is going to * be sent to an auth. server. */ iq->chase_flags &= ~BIT_RD; /* if dnssec expected, fetch key for the trust-anchor or cached-DS */ if(iq->dnssec_expected && qstate->env->cfg->prefetch_key && !(qstate->query_flags&BIT_CD)) { generate_dnskey_prefetch(qstate, iq, id); fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); } /* Jump to the next state. */ return next_state(iq, QUERYTARGETS_STATE); } /** * Given a basic query, generate a parent-side "target" query. * These are subordinate queries for missing delegation point target addresses, * for which only the parent of the delegation provides correct IP addresses. * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @param name: target qname. * @param namelen: target qname length. * @param qtype: target qtype (either A or AAAA). * @param qclass: target qclass. * @return true on success, false on failure. */ static int generate_parentside_target_query(struct module_qstate* qstate, struct iter_qstate* iq, int id, uint8_t* name, size_t namelen, uint16_t qtype, uint16_t qclass) { struct module_qstate* subq; if(!generate_sub_request(name, namelen, qtype, qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) return 0; if(subq) { struct iter_qstate* subiq = (struct iter_qstate*)subq->minfo[id]; /* blacklist the cache - we want to fetch parent stuff */ sock_list_insert(&subq->blacklist, NULL, 0, subq->region); subiq->query_for_pside_glue = 1; if(dname_subdomain_c(name, iq->dp->name)) { subiq->dp = delegpt_copy(iq->dp, subq->region); subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); subiq->refetch_glue = 1; } else { subiq->dp = dns_cache_find_delegation(qstate->env, name, namelen, qtype, qclass, subq->region, &subiq->deleg_msg, *qstate->env->now+subq->prefetch_leeway, 1, NULL, 0); /* if no dp, then it's from root, refetch unneeded */ if(subiq->dp) { subiq->dnssec_expected = iter_indicates_dnssec( qstate->env, subiq->dp, NULL, subq->qinfo.qclass); subiq->refetch_glue = 1; } } } log_nametypeclass(VERB_QUERY, "new pside target", name, qtype, qclass); return 1; } /** * Given a basic query, generate a "target" query. These are subordinate * queries for missing delegation point target addresses. * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @param name: target qname. * @param namelen: target qname length. * @param qtype: target qtype (either A or AAAA). * @param qclass: target qclass. * @return true on success, false on failure. */ static int generate_target_query(struct module_qstate* qstate, struct iter_qstate* iq, int id, uint8_t* name, size_t namelen, uint16_t qtype, uint16_t qclass) { struct module_qstate* subq; if(!generate_sub_request(name, namelen, qtype, qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) return 0; log_nametypeclass(VERB_QUERY, "new target", name, qtype, qclass); return 1; } /** * Given an event at a certain state, generate zero or more target queries * for it's current delegation point. * * @param qstate: query state. * @param iq: iterator query state. * @param ie: iterator shared global environment. * @param id: module id. * @param maxtargets: The maximum number of targets to query for. * if it is negative, there is no maximum number of targets. * @param num: returns the number of queries generated and processed, * which may be zero if there were no missing targets. * @return 0 on success, nonzero on error. 1 means temporary failure and * 2 means the failure can be cached. */ static int query_for_targets(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id, int maxtargets, int* num) { int query_count = 0; struct delegpt_ns* ns; int missing; int toget = 0; iter_mark_cycle_targets(qstate, iq->dp); missing = (int)delegpt_count_missing_targets(iq->dp, NULL); log_assert(maxtargets != 0); /* that would not be useful */ /* Generate target requests. Basically, any missing targets * are queried for here, regardless if it is necessary to do * so to continue processing. */ if(maxtargets < 0 || maxtargets > missing) toget = missing; else toget = maxtargets; if(toget == 0) { *num = 0; return 0; } /* now that we are sure that a target query is going to be made, * check the limits. */ if(iq->depth == ie->max_dependency_depth) return 1; if(iq->depth > 0 && iq->target_count && iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) { char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d", s, iq->target_count[TARGET_COUNT_QUERIES]); return 2; } if(iq->dp_target_count > MAX_DP_TARGET_COUNT) { char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d to a single delegation point", s, iq->dp_target_count); return 2; } /* select 'toget' items from the total of 'missing' items */ log_assert(toget <= missing); /* loop over missing targets */ for(ns = iq->dp->nslist; ns; ns = ns->next) { if(ns->resolved) continue; /* randomly select this item with probability toget/missing */ if(!iter_ns_probability(qstate->env->rnd, toget, missing)) { /* do not select this one, next; select toget number * of items from a list one less in size */ missing --; continue; } if(ie->supports_ipv6 && ((ns->lame && !ns->done_pside6) || (!ns->lame && !ns->got6))) { /* Send the AAAA request. */ if(!generate_target_query(qstate, iq, id, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, iq->qchase.qclass)) { *num = query_count; if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; return 1; } query_count++; /* If the mesh query list is full, exit the loop here. * This makes the routine spawn one query at a time, * and this means there is no query state load * increase, because the spawned state uses cpu and a * socket while this state waits for that spawned * state. Next time we can look up further targets */ if(mesh_jostle_exceeded(qstate->env->mesh)) { /* If no ip4 query is possible, that makes * this ns resolved. */ if(!((ie->supports_ipv4 || ie->nat64.use_nat64) && ((ns->lame && !ns->done_pside4) || (!ns->lame && !ns->got4)))) { ns->resolved = 1; } break; } } /* Send the A request. */ if((ie->supports_ipv4 || ie->nat64.use_nat64) && ((ns->lame && !ns->done_pside4) || (!ns->lame && !ns->got4))) { if(!generate_target_query(qstate, iq, id, ns->name, ns->namelen, LDNS_RR_TYPE_A, iq->qchase.qclass)) { *num = query_count; if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; return 1; } query_count++; /* If the mesh query list is full, exit the loop. */ if(mesh_jostle_exceeded(qstate->env->mesh)) { /* With the ip6 query already checked for, * this makes the ns resolved. It is no longer * a missing target. */ ns->resolved = 1; break; } } /* mark this target as in progress. */ ns->resolved = 1; missing--; toget--; if(toget == 0) break; } *num = query_count; if(query_count > 0) qstate->ext_state[id] = module_wait_subquery; return 0; } /** * Called by processQueryTargets when it would like extra targets to query * but it seems to be out of options. At last resort some less appealing * options are explored. If there are no more options, the result is SERVFAIL * * @param qstate: query state. * @param iq: iterator query state. * @param ie: iterator shared global environment. * @param id: module id. * @return true if the event requires more request processing immediately, * false if not. */ static int processLastResort(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { struct delegpt_ns* ns; int query_count = 0; verbose(VERB_ALGO, "No more query targets, attempting last resort"); log_assert(iq->dp); if(!can_have_last_resort(qstate->env, iq->dp->name, iq->dp->namelen, iq->qchase.qclass, NULL, NULL, NULL)) { /* fail -- no more targets, no more hope of targets, no hope * of a response. */ errinf(qstate, "all the configured stub or forward servers failed,"); errinf_dname(qstate, "at zone", iq->dp->name); errinf_reply(qstate, iq); verbose(VERB_QUERY, "configured stub or forward servers failed -- returning SERVFAIL"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->dp->fallback_to_parent_side_NS = 1; if(qstate->env->cfg->harden_unverified_glue) { if(!cache_fill_missing(qstate->env, iq->qchase.qclass, qstate->region, iq->dp, PACKED_RRSET_UNVERIFIED_GLUE)) log_err("out of memory in cache_fill_missing"); if(iq->dp->usable_list) { verbose(VERB_ALGO, "try unverified glue from cache"); return next_state(iq, QUERYTARGETS_STATE); } } if(!iq->dp->has_parent_side_NS && dname_is_root(iq->dp->name)) { struct delegpt* dp; int nolock = 0; dp = hints_find_root(qstate->env->hints, iq->qchase.qclass, nolock); if(dp) { struct delegpt_addr* a; iq->chase_flags &= ~BIT_RD; /* go to authorities */ for(ns = dp->nslist; ns; ns=ns->next) { (void)delegpt_add_ns(iq->dp, qstate->region, ns->name, ns->lame, ns->tls_auth_name, ns->port); } for(a = dp->target_list; a; a=a->next_target) { (void)delegpt_add_addr(iq->dp, qstate->region, &a->addr, a->addrlen, a->bogus, a->lame, a->tls_auth_name, -1, NULL); } lock_rw_unlock(&qstate->env->hints->lock); /* copy over some configuration since we update the * delegation point in place */ iq->dp->tcp_upstream = dp->tcp_upstream; iq->dp->ssl_upstream = dp->ssl_upstream; } iq->dp->has_parent_side_NS = 1; } else if(!iq->dp->has_parent_side_NS) { if(!iter_lookup_parent_NS_from_cache(qstate->env, iq->dp, qstate->region, &qstate->qinfo) || !iq->dp->has_parent_side_NS) { /* if: malloc failure in lookup go up to try */ /* if: no parent NS in cache - go up one level */ verbose(VERB_ALGO, "try to grab parent NS"); iq->store_parent_NS = iq->dp; iq->chase_flags &= ~BIT_RD; /* go to authorities */ iq->deleg_msg = NULL; iq->refetch_glue = 1; iq->query_restart_count++; iq->sent_count = 0; iq->dp_target_count = 0; if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; return next_state(iq, INIT_REQUEST_STATE); } } /* see if that makes new names available */ if(!cache_fill_missing(qstate->env, iq->qchase.qclass, qstate->region, iq->dp, 0)) log_err("out of memory in cache_fill_missing"); if(iq->dp->usable_list) { verbose(VERB_ALGO, "try parent-side-name, w. glue from cache"); return next_state(iq, QUERYTARGETS_STATE); } /* try to fill out parent glue from cache */ if(iter_lookup_parent_glue_from_cache(qstate->env, iq->dp, qstate->region, &qstate->qinfo)) { /* got parent stuff from cache, see if we can continue */ verbose(VERB_ALGO, "try parent-side glue from cache"); return next_state(iq, QUERYTARGETS_STATE); } /* query for an extra name added by the parent-NS record */ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) { int qs = 0, ret; verbose(VERB_ALGO, "try parent-side target name"); if((ret=query_for_targets(qstate, iq, ie, id, 1, &qs))!=0) { errinf(qstate, "could not fetch nameserver"); errinf_dname(qstate, "at zone", iq->dp->name); if(ret == 1) return error_response(qstate, id, LDNS_RCODE_SERVFAIL); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += qs; target_count_increase(iq, qs); if(qs != 0) { qstate->ext_state[id] = module_wait_subquery; return 0; /* and wait for them */ } } if(iq->depth == ie->max_dependency_depth) { verbose(VERB_QUERY, "maxdepth and need more nameservers, fail"); errinf(qstate, "cannot fetch more nameservers because at max dependency depth"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->depth > 0 && iq->target_count && iq->target_count[TARGET_COUNT_QUERIES] > MAX_TARGET_COUNT) { char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "number of glue fetches %d", s, iq->target_count[TARGET_COUNT_QUERIES]); errinf(qstate, "exceeded the maximum number of glue fetches"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* mark cycle targets for parent-side lookups */ iter_mark_pside_cycle_targets(qstate, iq->dp); /* see if we can issue queries to get nameserver addresses */ /* this lookup is not randomized, but sequential. */ for(ns = iq->dp->nslist; ns; ns = ns->next) { /* if this nameserver is at a delegation point, but that * delegation point is a stub and we cannot go higher, skip*/ if( ((ie->supports_ipv6 && !ns->done_pside6) || ((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4)) && !can_have_last_resort(qstate->env, ns->name, ns->namelen, iq->qchase.qclass, NULL, NULL, NULL)) { log_nametypeclass(VERB_ALGO, "cannot pside lookup ns " "because it is also a stub/forward,", ns->name, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(ie->supports_ipv6) ns->done_pside6 = 1; if(ie->supports_ipv4 || ie->nat64.use_nat64) ns->done_pside4 = 1; continue; } /* query for parent-side A and AAAA for nameservers */ if(ie->supports_ipv6 && !ns->done_pside6) { /* Send the AAAA request. */ if(!generate_parentside_target_query(qstate, iq, id, ns->name, ns->namelen, LDNS_RR_TYPE_AAAA, iq->qchase.qclass)) { errinf_dname(qstate, "could not generate nameserver AAAA lookup for", ns->name); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } ns->done_pside6 = 1; query_count++; if(mesh_jostle_exceeded(qstate->env->mesh)) { /* Wait for the lookup; do not spawn multiple * lookups at a time. */ verbose(VERB_ALGO, "try parent-side glue lookup"); iq->num_target_queries += query_count; target_count_increase(iq, query_count); qstate->ext_state[id] = module_wait_subquery; return 0; } } if((ie->supports_ipv4 || ie->nat64.use_nat64) && !ns->done_pside4) { /* Send the A request. */ if(!generate_parentside_target_query(qstate, iq, id, ns->name, ns->namelen, LDNS_RR_TYPE_A, iq->qchase.qclass)) { errinf_dname(qstate, "could not generate nameserver A lookup for", ns->name); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } ns->done_pside4 = 1; query_count++; } if(query_count != 0) { /* suspend to await results */ verbose(VERB_ALGO, "try parent-side glue lookup"); iq->num_target_queries += query_count; target_count_increase(iq, query_count); qstate->ext_state[id] = module_wait_subquery; return 0; } } /* if this was a parent-side glue query itself, then store that * failure in cache. */ if(!qstate->no_cache_store && iq->query_for_pside_glue && !iq->pside_glue) iter_store_parentside_neg(qstate->env, &qstate->qinfo, iq->deleg_msg?iq->deleg_msg->rep: (iq->response?iq->response->rep:NULL)); errinf(qstate, "all servers for this domain failed,"); errinf_dname(qstate, "at zone", iq->dp->name); errinf_reply(qstate, iq); verbose(VERB_QUERY, "out of query targets -- returning SERVFAIL"); /* fail -- no more targets, no more hope of targets, no hope * of a response. */ return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /** * Try to find the NS record set that will resolve a qtype DS query. Due * to grandparent/grandchild reasons we did not get a proper lookup right * away. We need to create type NS queries until we get the right parent * for this lookup. We remove labels from the query to find the right point. * If we end up at the old dp name, then there is no solution. * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @return true if the event requires more immediate processing, false if * not. This is generally only true when forwarding the request to * the final state (i.e., on answer). */ static int processDSNSFind(struct module_qstate* qstate, struct iter_qstate* iq, int id) { struct module_qstate* subq = NULL; verbose(VERB_ALGO, "processDSNSFind"); if(!iq->dsns_point) { /* initialize */ iq->dsns_point = iq->qchase.qname; iq->dsns_point_len = iq->qchase.qname_len; } /* robustcheck for internal error: we are not underneath the dp */ if(!dname_subdomain_c(iq->dsns_point, iq->dp->name)) { errinf_dname(qstate, "for DS query parent-child nameserver search the query is not under the zone", iq->dp->name); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* go up one (more) step, until we hit the dp, if so, end */ dname_remove_label(&iq->dsns_point, &iq->dsns_point_len); if(query_dname_compare(iq->dsns_point, iq->dp->name) == 0) { /* there was no inbetween nameserver, use the old delegation * point again. And this time, because dsns_point is nonNULL * we are going to accept the (bad) result */ iq->state = QUERYTARGETS_STATE; return 1; } iq->state = DSNS_FIND_STATE; /* spawn NS lookup (validation not needed, this is for DS lookup) */ log_nametypeclass(VERB_ALGO, "fetch nameservers", iq->dsns_point, LDNS_RR_TYPE_NS, iq->qchase.qclass); if(!generate_sub_request(iq->dsns_point, iq->dsns_point_len, LDNS_RR_TYPE_NS, iq->qchase.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 0, 0)) { errinf_dname(qstate, "for DS query parent-child nameserver search, could not generate NS lookup for", iq->dsns_point); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } return 0; } /** * Check if we wait responses for sent queries and update the iterator's * external state. */ static void check_waiting_queries(struct iter_qstate* iq, struct module_qstate* qstate, int id) { if(iq->num_target_queries>0 && iq->num_current_queries>0) { verbose(VERB_ALGO, "waiting for %d targets to " "resolve or %d outstanding queries to " "respond", iq->num_target_queries, iq->num_current_queries); qstate->ext_state[id] = module_wait_reply; } else if(iq->num_target_queries>0) { verbose(VERB_ALGO, "waiting for %d targets to " "resolve", iq->num_target_queries); qstate->ext_state[id] = module_wait_subquery; } else { verbose(VERB_ALGO, "waiting for %d " "outstanding queries to respond", iq->num_current_queries); qstate->ext_state[id] = module_wait_reply; } } /** * This is the request event state where the request will be sent to one of * its current query targets. This state also handles issuing target lookup * queries for missing target IP addresses. Queries typically iterate on * this state, both when they are just trying different targets for a given * delegation point, and when they change delegation points. This state * roughly corresponds to RFC 1034 algorithm steps 3 and 4. * * @param qstate: query state. * @param iq: iterator query state. * @param ie: iterator shared global environment. * @param id: module id. * @return true if the event requires more request processing immediately, * false if not. This state only returns true when it is generating * a SERVFAIL response because the query has hit a dead end. */ static int processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { int tf_policy; struct delegpt_addr* target; struct outbound_entry* outq; int auth_fallback = 0; uint8_t* qout_orig = NULL; size_t qout_orig_len = 0; int sq_check_ratelimit = 1; int sq_was_ratelimited = 0; int can_do_promisc = 0; /* NOTE: a request will encounter this state for each target it * needs to send a query to. That is, at least one per referral, * more if some targets timeout or return throwaway answers. */ log_query_info(VERB_QUERY, "processQueryTargets:", &qstate->qinfo); verbose(VERB_ALGO, "processQueryTargets: targetqueries %d, " "currentqueries %d sentcount %d", iq->num_target_queries, iq->num_current_queries, iq->sent_count); /* Make sure that we haven't run away */ if(iq->referral_count > MAX_REFERRAL_COUNT) { verbose(VERB_QUERY, "request has exceeded the maximum " "number of referrrals with %d", iq->referral_count); errinf(qstate, "exceeded the maximum of referrals"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->sent_count > ie->max_sent_count) { verbose(VERB_QUERY, "request has exceeded the maximum " "number of sends with %d", iq->sent_count); errinf(qstate, "exceeded the maximum number of sends"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* Check if we reached MAX_TARGET_NX limit without a fallback activation. */ if(iq->target_count && !*iq->nxns_dp && iq->target_count[TARGET_COUNT_NX] > MAX_TARGET_NX) { struct delegpt_ns* ns; /* If we can wait for resolution, do so. */ if(iq->num_target_queries>0 || iq->num_current_queries>0) { check_waiting_queries(iq, qstate, id); return 0; } verbose(VERB_ALGO, "request has exceeded the maximum " "number of nxdomain nameserver lookups (%d) with %d", MAX_TARGET_NX, iq->target_count[TARGET_COUNT_NX]); /* Check for dp because we require one below */ if(!iq->dp) { verbose(VERB_QUERY, "Failed to get a delegation, " "giving up"); errinf(qstate, "failed to get a delegation (eg. prime " "failure)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* We reached the limit but we already have parent side * information; stop resolution */ if(iq->dp->has_parent_side_NS) { verbose(VERB_ALGO, "parent-side information is " "already present for the delegation point, no " "fallback possible"); errinf(qstate, "exceeded the maximum nameserver nxdomains"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } verbose(VERB_ALGO, "initiating parent-side fallback for " "nxdomain nameserver lookups"); /* Mark all the current NSes as resolved to allow for parent * fallback */ for(ns=iq->dp->nslist; ns; ns=ns->next) { ns->resolved = 1; } /* Note the delegation point that triggered the NXNS fallback; * no reason for shared queries to keep trying there. * This also marks the fallback activation. */ *iq->nxns_dp = malloc(iq->dp->namelen); if(!*iq->nxns_dp) { verbose(VERB_ALGO, "out of memory while initiating " "fallback"); errinf(qstate, "exceeded the maximum nameserver " "nxdomains (malloc)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } memcpy(*iq->nxns_dp, iq->dp->name, iq->dp->namelen); } else if(iq->target_count && *iq->nxns_dp) { /* Handle the NXNS fallback case. */ /* If we can wait for resolution, do so. */ if(iq->num_target_queries>0 || iq->num_current_queries>0) { check_waiting_queries(iq, qstate, id); return 0; } /* Check for dp because we require one below */ if(!iq->dp) { verbose(VERB_QUERY, "Failed to get a delegation, " "giving up"); errinf(qstate, "failed to get a delegation (eg. prime " "failure)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->target_count[TARGET_COUNT_NX] > MAX_TARGET_NX_FALLBACK) { verbose(VERB_ALGO, "request has exceeded the maximum " "number of fallback nxdomain nameserver " "lookups (%d) with %d", MAX_TARGET_NX_FALLBACK, iq->target_count[TARGET_COUNT_NX]); errinf(qstate, "exceeded the maximum nameserver nxdomains"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(!iq->dp->has_parent_side_NS) { struct delegpt_ns* ns; if(!dname_canonical_compare(*iq->nxns_dp, iq->dp->name)) { verbose(VERB_ALGO, "this delegation point " "initiated the fallback, marking the " "nslist as resolved"); for(ns=iq->dp->nslist; ns; ns=ns->next) { ns->resolved = 1; } } } } /* Make sure we have a delegation point, otherwise priming failed * or another failure occurred */ if(!iq->dp) { verbose(VERB_QUERY, "Failed to get a delegation, giving up"); errinf(qstate, "failed to get a delegation (eg. prime failure)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(!ie->supports_ipv6) delegpt_no_ipv6(iq->dp); if(!ie->supports_ipv4 && !ie->nat64.use_nat64) delegpt_no_ipv4(iq->dp); delegpt_log(VERB_ALGO, iq->dp); if(iq->num_current_queries>0) { /* already busy answering a query, this restart is because * more delegpt addrs became available, wait for existing * query. */ verbose(VERB_ALGO, "woke up, but wait for outstanding query"); qstate->ext_state[id] = module_wait_reply; return 0; } if(iq->minimisation_state == INIT_MINIMISE_STATE && !(iq->chase_flags & BIT_RD)) { /* (Re)set qinfo_out to (new) delegation point, except when * qinfo_out is already a subdomain of dp. This happens when * increasing by more than one label at once (QNAMEs with more * than MAX_MINIMISE_COUNT labels). */ if(!(iq->qinfo_out.qname_len && dname_subdomain_c(iq->qchase.qname, iq->qinfo_out.qname) && dname_subdomain_c(iq->qinfo_out.qname, iq->dp->name))) { iq->qinfo_out.qname = iq->dp->name; iq->qinfo_out.qname_len = iq->dp->namelen; iq->qinfo_out.qtype = LDNS_RR_TYPE_A; iq->qinfo_out.qclass = iq->qchase.qclass; iq->qinfo_out.local_alias = NULL; iq->minimise_count = 0; } iq->minimisation_state = MINIMISE_STATE; } if(iq->minimisation_state == MINIMISE_STATE) { int qchaselabs = dname_count_labels(iq->qchase.qname); int labdiff = qchaselabs - dname_count_labels(iq->qinfo_out.qname); qout_orig = iq->qinfo_out.qname; qout_orig_len = iq->qinfo_out.qname_len; iq->qinfo_out.qname = iq->qchase.qname; iq->qinfo_out.qname_len = iq->qchase.qname_len; iq->minimise_count++; iq->timeout_count = 0; iter_dec_attempts(iq->dp, 1, ie->outbound_msg_retry); /* Limit number of iterations for QNAMEs with more * than MAX_MINIMISE_COUNT labels. Send first MINIMISE_ONE_LAB * labels of QNAME always individually. */ if(qchaselabs > MAX_MINIMISE_COUNT && labdiff > 1 && iq->minimise_count > MINIMISE_ONE_LAB) { if(iq->minimise_count < MAX_MINIMISE_COUNT) { int multilabs = qchaselabs - 1 - MINIMISE_ONE_LAB; int extralabs = multilabs / MINIMISE_MULTIPLE_LABS; if (MAX_MINIMISE_COUNT - iq->minimise_count >= multilabs % MINIMISE_MULTIPLE_LABS) /* Default behaviour is to add 1 label * every iteration. Therefore, decrement * the extralabs by 1 */ extralabs--; if (extralabs < labdiff) labdiff -= extralabs; else labdiff = 1; } /* Last minimised iteration, send all labels with * QTYPE=NS */ else labdiff = 1; } if(labdiff > 1) { verbose(VERB_QUERY, "removing %d labels", labdiff-1); dname_remove_labels(&iq->qinfo_out.qname, &iq->qinfo_out.qname_len, labdiff-1); } if(labdiff < 1 || (labdiff < 2 && (iq->qchase.qtype == LDNS_RR_TYPE_DS || iq->qchase.qtype == LDNS_RR_TYPE_A))) /* Stop minimising this query, resolve "as usual" */ iq->minimisation_state = DONOT_MINIMISE_STATE; else if(!qstate->no_cache_lookup) { struct dns_msg* msg = dns_cache_lookup(qstate->env, iq->qinfo_out.qname, iq->qinfo_out.qname_len, iq->qinfo_out.qtype, iq->qinfo_out.qclass, qstate->query_flags, qstate->region, qstate->env->scratch, 0, iq->dp->name, iq->dp->namelen); if(msg && FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NOERROR) /* no need to send query if it is already * cached as NOERROR */ return 1; if(msg && FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN && qstate->env->need_to_validate && qstate->env->cfg->harden_below_nxdomain) { if(msg->rep->security == sec_status_secure) { iq->response = msg; return final_state(iq); } if(msg->rep->security == sec_status_unchecked) { struct module_qstate* subq = NULL; if(!generate_sub_request( iq->qinfo_out.qname, iq->qinfo_out.qname_len, iq->qinfo_out.qtype, iq->qinfo_out.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 1)) verbose(VERB_ALGO, "could not validate NXDOMAIN " "response"); } } if(msg && FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) { /* return and add a label in the next * minimisation iteration. */ return 1; } } } if(iq->minimisation_state == SKIP_MINIMISE_STATE) { if(iq->timeout_count < MAX_MINIMISE_TIMEOUT_COUNT) /* Do not increment qname, continue incrementing next * iteration */ iq->minimisation_state = MINIMISE_STATE; else if(!qstate->env->cfg->qname_minimisation_strict) /* Too many time-outs detected for this QNAME and QTYPE. * We give up, disable QNAME minimisation. */ iq->minimisation_state = DONOT_MINIMISE_STATE; } if(iq->minimisation_state == DONOT_MINIMISE_STATE) iq->qinfo_out = iq->qchase; /* now find an answer to this query */ /* see if authority zones have an answer */ /* now we know the dp, we can check the auth zone for locally hosted * contents */ if(!iq->auth_zone_avoid && qstate->blacklist) { if(auth_zones_can_fallback(qstate->env->auth_zones, iq->dp->name, iq->dp->namelen, iq->qinfo_out.qclass)) { /* if cache is blacklisted and this zone allows us * to fallback to the internet, then do so, and * fetch results from the internet servers */ iq->auth_zone_avoid = 1; } } if(iq->auth_zone_avoid) { iq->auth_zone_avoid = 0; auth_fallback = 1; } else if(auth_zones_lookup(qstate->env->auth_zones, &iq->qinfo_out, qstate->region, &iq->response, &auth_fallback, iq->dp->name, iq->dp->namelen)) { /* use this as a response to be processed by the iterator */ if(verbosity >= VERB_ALGO) { log_dns_msg("msg from auth zone", &iq->response->qinfo, iq->response->rep); } if((iq->chase_flags&BIT_RD) && !(iq->response->rep->flags&BIT_AA)) { verbose(VERB_ALGO, "forwarder, ignoring referral from auth zone"); } else { qstate->env->mesh->num_query_authzone_up++; iq->num_current_queries++; iq->chase_to_rd = 0; iq->dnssec_lame_query = 0; iq->auth_zone_response = 1; return next_state(iq, QUERY_RESP_STATE); } } iq->auth_zone_response = 0; if(auth_fallback == 0) { /* like we got servfail from the auth zone lookup, and * no internet fallback */ verbose(VERB_ALGO, "auth zone lookup failed, no fallback," " servfail"); errinf(qstate, "auth zone lookup failed, fallback is off"); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->dp->auth_dp) { /* we wanted to fallback, but had no delegpt, only the * auth zone generated delegpt, create an actual one */ iq->auth_zone_avoid = 1; return next_state(iq, INIT_REQUEST_STATE); } /* but mostly, fallback==1 (like, when no such auth zone exists) * and we continue with lookups */ tf_policy = 0; /* < not <=, because although the array is large enough for <=, the * generated query will immediately be discarded due to depth and * that servfail is cached, which is not good as opportunism goes. */ if(iq->depth < ie->max_dependency_depth && iq->num_target_queries == 0 && (!iq->target_count || iq->target_count[TARGET_COUNT_NX]==0) && iq->sent_count < TARGET_FETCH_STOP) { can_do_promisc = 1; } /* if the mesh query list is full, then do not waste cpu and sockets to * fetch promiscuous targets. They can be looked up when needed. */ if(!iq->dp->fallback_to_parent_side_NS && can_do_promisc && !mesh_jostle_exceeded(qstate->env->mesh)) { tf_policy = ie->target_fetch_policy[iq->depth]; } /* if in 0x20 fallback get as many targets as possible */ if(iq->caps_fallback) { int extra = 0, ret; size_t naddr, nres, navail; if((ret=query_for_targets(qstate, iq, ie, id, -1, &extra))!=0) { errinf(qstate, "could not fetch nameservers for 0x20 fallback"); if(ret == 1) return error_response(qstate, id, LDNS_RCODE_SERVFAIL); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += extra; target_count_increase(iq, extra); if(iq->num_target_queries > 0) { /* wait to get all targets, we want to try em */ verbose(VERB_ALGO, "wait for all targets for fallback"); qstate->ext_state[id] = module_wait_reply; /* undo qname minimise step because we'll get back here * to do it again */ if(qout_orig && iq->minimise_count > 0) { iq->minimise_count--; iq->qinfo_out.qname = qout_orig; iq->qinfo_out.qname_len = qout_orig_len; } return 0; } /* did we do enough fallback queries already? */ delegpt_count_addr(iq->dp, &naddr, &nres, &navail); /* the current caps_server is the number of fallbacks sent. * the original query is one that matched too, so we have * caps_server+1 number of matching queries now */ if(iq->caps_server+1 >= naddr*3 || iq->caps_server*2+2 >= (size_t)ie->max_sent_count) { /* *2 on sentcount check because ipv6 may fail */ /* we're done, process the response */ verbose(VERB_ALGO, "0x20 fallback had %d responses " "match for %d wanted, done.", (int)iq->caps_server+1, (int)naddr*3); iq->response = iq->caps_response; iq->caps_fallback = 0; iter_dec_attempts(iq->dp, 3, ie->outbound_msg_retry); /* space for fallback */ iq->num_current_queries++; /* RespState decrements it*/ iq->referral_count++; /* make sure we don't loop */ iq->sent_count = 0; iq->dp_target_count = 0; iq->state = QUERY_RESP_STATE; return 1; } verbose(VERB_ALGO, "0x20 fallback number %d", (int)iq->caps_server); /* if there is a policy to fetch missing targets * opportunistically, do it. we rely on the fact that once a * query (or queries) for a missing name have been issued, * they will not show up again. */ } else if(tf_policy != 0) { int extra = 0; verbose(VERB_ALGO, "attempt to get extra %d targets", tf_policy); (void)query_for_targets(qstate, iq, ie, id, tf_policy, &extra); /* errors ignored, these targets are not strictly necessary for * this result, we do not have to reply with SERVFAIL */ iq->num_target_queries += extra; target_count_increase(iq, extra); } /* Add the current set of unused targets to our queue. */ delegpt_add_unused_targets(iq->dp); if(qstate->env->auth_zones) { uint8_t* sname = NULL; size_t snamelen = 0; /* apply rpz triggers at query time; nameserver IP and dname */ struct dns_msg* forged_response_after_cname; struct dns_msg* forged_response = rpz_callback_from_iterator_module(qstate, iq); int count = 0; while(forged_response && reply_find_rrset_section_an( forged_response->rep, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_CNAME, iq->qchase.qclass) && iq->qchase.qtype != LDNS_RR_TYPE_CNAME && count++ < ie->max_query_restarts) { /* another cname to follow */ if(!handle_cname_response(qstate, iq, forged_response, &sname, &snamelen)) { errinf(qstate, "malloc failure, CNAME info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->qchase.qname = sname; iq->qchase.qname_len = snamelen; forged_response_after_cname = rpz_callback_from_iterator_cname(qstate, iq); if(forged_response_after_cname) { forged_response = forged_response_after_cname; } else { /* Follow the CNAME with a query restart */ iq->deleg_msg = NULL; iq->dp = NULL; iq->dsns_point = NULL; iq->auth_zone_response = 0; iq->refetch_glue = 0; iq->query_restart_count++; iq->sent_count = 0; iq->dp_target_count = 0; if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; return next_state(iq, INIT_REQUEST_STATE); } } if(forged_response != NULL) { qstate->ext_state[id] = module_finished; qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = forged_response; iq->response = forged_response; next_state(iq, FINISHED_STATE); if(!iter_prepend(iq, qstate->return_msg, qstate->region)) { log_err("rpz: prepend rrsets: out of memory"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } return 0; } } /* Select the next usable target, filtering out unsuitable targets. */ target = iter_server_selection(ie, qstate->env, iq->dp, iq->dp->name, iq->dp->namelen, iq->qchase.qtype, &iq->dnssec_lame_query, &iq->chase_to_rd, iq->num_target_queries, qstate->blacklist, qstate->prefetch_leeway); /* If no usable target was selected... */ if(!target) { /* Here we distinguish between three states: generate a new * target query, just wait, or quit (with a SERVFAIL). * We have the following information: number of active * target queries, number of active current queries, * the presence of missing targets at this delegation * point, and the given query target policy. */ /* Check for the wait condition. If this is true, then * an action must be taken. */ if(iq->num_target_queries==0 && iq->num_current_queries==0) { /* If there is nothing to wait for, then we need * to distinguish between generating (a) new target * query, or failing. */ if(delegpt_count_missing_targets(iq->dp, NULL) > 0) { int qs = 0, ret; verbose(VERB_ALGO, "querying for next " "missing target"); if((ret=query_for_targets(qstate, iq, ie, id, 1, &qs))!=0) { errinf(qstate, "could not fetch nameserver"); errinf_dname(qstate, "at zone", iq->dp->name); if(ret == 1) return error_response(qstate, id, LDNS_RCODE_SERVFAIL); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } if(qs == 0 && delegpt_count_missing_targets(iq->dp, NULL) == 0){ /* it looked like there were missing * targets, but they did not turn up. * Try the bad choices again (if any), * when we get back here missing==0, * so this is not a loop. */ return 1; } if(qs == 0) { /* There should be targets now, and * if there are not, it should not * wait for no targets. Stop it from * waiting forever, or looping to * here, as a safeguard. */ errinf(qstate, "could not generate nameserver lookups"); errinf_dname(qstate, "at zone", iq->dp->name); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->num_target_queries += qs; target_count_increase(iq, qs); } /* Since a target query might have been made, we * need to check again. */ if(iq->num_target_queries == 0) { /* if in capsforid fallback, instead of last * resort, we agree with the current reply * we have (if any) (our count of addrs bad)*/ if(iq->caps_fallback && iq->caps_reply) { /* we're done, process the response */ verbose(VERB_ALGO, "0x20 fallback had %d responses, " "but no more servers except " "last resort, done.", (int)iq->caps_server+1); iq->response = iq->caps_response; iq->caps_fallback = 0; iter_dec_attempts(iq->dp, 3, ie->outbound_msg_retry); /* space for fallback */ iq->num_current_queries++; /* RespState decrements it*/ iq->referral_count++; /* make sure we don't loop */ iq->sent_count = 0; iq->dp_target_count = 0; iq->state = QUERY_RESP_STATE; return 1; } return processLastResort(qstate, iq, ie, id); } } /* otherwise, we have no current targets, so submerge * until one of the target or direct queries return. */ verbose(VERB_ALGO, "no current targets"); check_waiting_queries(iq, qstate, id); /* undo qname minimise step because we'll get back here * to do it again */ if(qout_orig && iq->minimise_count > 0) { iq->minimise_count--; iq->qinfo_out.qname = qout_orig; iq->qinfo_out.qname_len = qout_orig_len; } return 0; } /* We have a target. We could have created promiscuous target * queries but we are currently under pressure (mesh_jostle_exceeded). * If we are configured to allow promiscuous target queries and haven't * gone out to the network for a target query for this delegation, then * it is possible to slip in a promiscuous one with a 1/10 chance. */ if(can_do_promisc && tf_policy == 0 && iq->depth == 0 && iq->depth < ie->max_dependency_depth && ie->target_fetch_policy[iq->depth] != 0 && iq->dp_target_count == 0 && !ub_random_max(qstate->env->rnd, 10)) { int extra = 0; verbose(VERB_ALGO, "available target exists in cache but " "attempt to get extra 1 target"); (void)query_for_targets(qstate, iq, ie, id, 1, &extra); /* errors ignored, these targets are not strictly necessary for * this result, we do not have to reply with SERVFAIL */ if(extra > 0) { iq->num_target_queries += extra; target_count_increase(iq, extra); check_waiting_queries(iq, qstate, id); /* undo qname minimise step because we'll get back here * to do it again */ if(qout_orig && iq->minimise_count > 0) { iq->minimise_count--; iq->qinfo_out.qname = qout_orig; iq->qinfo_out.qname_len = qout_orig_len; } return 0; } } target_count_increase_global_quota(iq, 1); if(iq->target_count && iq->target_count[TARGET_COUNT_GLOBAL_QUOTA] > MAX_GLOBAL_QUOTA) { char s[LDNS_MAX_DOMAINLEN]; dname_str(qstate->qinfo.qname, s); verbose(VERB_QUERY, "request %s has exceeded the maximum " "global quota on number of upstream queries %d", s, iq->target_count[TARGET_COUNT_GLOBAL_QUOTA]); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* Do not check ratelimit for forwarding queries or if we already got a * pass. */ sq_check_ratelimit = (!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok); /* We have a valid target. */ if(verbosity >= VERB_QUERY) { log_query_info(VERB_QUERY, "sending query:", &iq->qinfo_out); log_name_addr(VERB_QUERY, "sending to target:", iq->dp->name, &target->addr, target->addrlen); verbose(VERB_ALGO, "dnssec status: %s%s", iq->dnssec_expected?"expected": "not expected", iq->dnssec_lame_query?" but lame_query anyway": ""); } fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query)); outq = (*qstate->env->send_query)(&iq->qinfo_out, iq->chase_flags | (iq->chase_to_rd?BIT_RD:0), /* unset CD if to forwarder(RD set) and not dnssec retry * (blacklist nonempty) and no trust-anchors are configured * above the qname or on the first attempt when dnssec is on */ (qstate->env->cfg->disable_edns_do?0:EDNS_DO)| ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&& !qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env, &iq->qinfo_out)||target->attempts==1)?0:BIT_CD), iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted( ie, iq), sq_check_ratelimit, &target->addr, target->addrlen, iq->dp->name, iq->dp->namelen, (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream), (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream), target->tls_auth_name, qstate, &sq_was_ratelimited); if(!outq) { if(sq_was_ratelimited) { lock_basic_lock(&ie->queries_ratelimit_lock); ie->num_queries_ratelimited++; lock_basic_unlock(&ie->queries_ratelimit_lock); verbose(VERB_ALGO, "query exceeded ratelimits"); qstate->was_ratelimited = 1; errinf_dname(qstate, "exceeded ratelimit for zone", iq->dp->name); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } log_addr(VERB_QUERY, "error sending query to auth server", &target->addr, target->addrlen); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = SKIP_MINIMISE_STATE; return next_state(iq, QUERYTARGETS_STATE); } outbound_list_insert(&iq->outlist, outq); iq->num_current_queries++; iq->sent_count++; qstate->ext_state[id] = module_wait_reply; return 0; } /** find NS rrset in given list */ static struct ub_packed_rrset_key* find_NS(struct reply_info* rep, size_t from, size_t to) { size_t i; for(i=from; irrsets[i]->rk.type) == LDNS_RR_TYPE_NS) return rep->rrsets[i]; } return NULL; } /** * Process the query response. All queries end up at this state first. This * process generally consists of analyzing the response and routing the * event to the next state (either bouncing it back to a request state, or * terminating the processing for this event). * * @param qstate: query state. * @param iq: iterator query state. * @param ie: iterator shared global environment. * @param id: module id. * @return true if the event requires more immediate processing, false if * not. This is generally only true when forwarding the request to * the final state (i.e., on answer). */ static int processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { int dnsseclame = 0, origtypecname = 0, orig_empty_nodata_found; enum response_type type; iq->num_current_queries--; if(!inplace_cb_query_response_call(qstate->env, qstate, iq->response)) log_err("unable to call query_response callback"); if(iq->response == NULL) { /* Don't increment qname when QNAME minimisation is enabled */ if(qstate->env->cfg->qname_minimisation) { iq->minimisation_state = SKIP_MINIMISE_STATE; } iq->timeout_count++; iq->chase_to_rd = 0; iq->dnssec_lame_query = 0; verbose(VERB_ALGO, "query response was timeout"); return next_state(iq, QUERYTARGETS_STATE); } iq->timeout_count = 0; orig_empty_nodata_found = iq->empty_nodata_found; type = response_type_from_server( (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd), iq->response, &iq->qinfo_out, iq->dp, &iq->empty_nodata_found); iq->chase_to_rd = 0; /* remove TC flag, if this is erroneously set by TCP upstream */ iq->response->rep->flags &= ~BIT_TC; if(orig_empty_nodata_found != iq->empty_nodata_found && iq->empty_nodata_found < EMPTY_NODATA_RETRY_COUNT) { /* try to search at another server */ if(qstate->reply) { struct delegpt_addr* a = delegpt_find_addr( iq->dp, &qstate->reply->remote_addr, qstate->reply->remote_addrlen); /* make selection disprefer it */ if(a) a->lame = 1; } return next_state(iq, QUERYTARGETS_STATE); } if(type == RESPONSE_TYPE_REFERRAL && (iq->chase_flags&BIT_RD) && !iq->auth_zone_response) { /* When forwarding (RD bit is set), we handle referrals * differently. No queries should be sent elsewhere */ type = RESPONSE_TYPE_ANSWER; } if(!qstate->env->cfg->disable_dnssec_lame_check && iq->dnssec_expected && !iq->dnssec_lame_query && !(iq->chase_flags&BIT_RD) && iq->sent_count < DNSSEC_LAME_DETECT_COUNT && type != RESPONSE_TYPE_LAME && type != RESPONSE_TYPE_REC_LAME && type != RESPONSE_TYPE_THROWAWAY && type != RESPONSE_TYPE_UNTYPED) { /* a possible answer, see if it is missing DNSSEC */ /* but not when forwarding, so we dont mark fwder lame */ if(!iter_msg_has_dnssec(iq->response)) { /* Mark this address as dnsseclame in this dp, * because that will make serverselection disprefer * it, but also, once it is the only final option, * use dnssec-lame-bypass if it needs to query there.*/ if(qstate->reply) { struct delegpt_addr* a = delegpt_find_addr( iq->dp, &qstate->reply->remote_addr, qstate->reply->remote_addrlen); if(a) a->dnsseclame = 1; } /* test the answer is from the zone we expected, * otherwise, (due to parent,child on same server), we * might mark the server,zone lame inappropriately */ if(!iter_msg_from_zone(iq->response, iq->dp, type, iq->qchase.qclass)) qstate->reply = NULL; type = RESPONSE_TYPE_LAME; dnsseclame = 1; } } else iq->dnssec_lame_query = 0; /* see if referral brings us close to the target */ if(type == RESPONSE_TYPE_REFERRAL) { struct ub_packed_rrset_key* ns = find_NS( iq->response->rep, iq->response->rep->an_numrrsets, iq->response->rep->an_numrrsets + iq->response->rep->ns_numrrsets); if(!ns) ns = find_NS(iq->response->rep, 0, iq->response->rep->an_numrrsets); if(!ns || !dname_strict_subdomain_c(ns->rk.dname, iq->dp->name) || !dname_subdomain_c(iq->qchase.qname, ns->rk.dname)){ verbose(VERB_ALGO, "bad referral, throwaway"); type = RESPONSE_TYPE_THROWAWAY; } else iter_scrub_ds(iq->response, ns, iq->dp->name); } else iter_scrub_ds(iq->response, NULL, NULL); if(type == RESPONSE_TYPE_THROWAWAY && FLAGS_GET_RCODE(iq->response->rep->flags) == LDNS_RCODE_YXDOMAIN) { /* YXDOMAIN is a permanent error for DNAME expansion overflow * (RFC 6672 Section 2.2). Only accept if the response * contains a DNAME record in the answer section; otherwise * treat as invalid, to make sure the authoritative answer * make sense. */ size_t i; for(i=0; iresponse->rep->an_numrrsets; i++) { if(ntohs(iq->response->rep->rrsets[i]->rk.type) == LDNS_RR_TYPE_DNAME) { type = RESPONSE_TYPE_ANSWER; break; } } } if(type == RESPONSE_TYPE_CNAME) origtypecname = 1; if(type == RESPONSE_TYPE_CNAME && iq->response->rep->an_numrrsets >= 1 && ntohs(iq->response->rep->rrsets[0]->rk.type) == LDNS_RR_TYPE_DNAME) { uint8_t* sname = NULL; size_t snamelen = 0; get_cname_target(iq->response->rep->rrsets[0], &sname, &snamelen); if(snamelen && dname_subdomain_c(sname, iq->response->rep->rrsets[0]->rk.dname)) { /* DNAME to a subdomain loop; do not recurse */ type = RESPONSE_TYPE_ANSWER; } } if(type == RESPONSE_TYPE_CNAME && (iq->qchase.qtype == LDNS_RR_TYPE_CNAME || iq->qchase.qtype == LDNS_RR_TYPE_ANY) && iq->minimisation_state == MINIMISE_STATE && query_dname_compare(iq->qchase.qname, iq->qinfo_out.qname) == 0) { /* The minimised query for full QTYPE and hidden QTYPE can be * classified as CNAME response type, even when the original * QTYPE=CNAME. This should be treated as answer response type. */ /* For QTYPE=ANY, it is also considered the response, that * is what the classifier would say, if it saw qtype ANY, * and this same response was returned for that. The response * can already be treated as such an answer, without having * to send another query with a new qtype. */ type = RESPONSE_TYPE_ANSWER; } /* handle each of the type cases */ if(type == RESPONSE_TYPE_ANSWER) { /* ANSWER type responses terminate the query algorithm, * so they sent on their */ if(verbosity >= VERB_DETAIL) { verbose(VERB_DETAIL, "query response was %s", FLAGS_GET_RCODE(iq->response->rep->flags) ==LDNS_RCODE_NXDOMAIN?"NXDOMAIN ANSWER": (iq->response->rep->an_numrrsets?"ANSWER": "nodata ANSWER")); } /* if qtype is DS, check we have the right level of answer, * like grandchild answer but we need the middle, reject it */ if(iq->qchase.qtype == LDNS_RR_TYPE_DS && !iq->dsns_point && !(iq->chase_flags&BIT_RD) && iter_ds_toolow(iq->response, iq->dp) && iter_dp_cangodown(&iq->qchase, iq->dp)) { /* close down outstanding requests to be discarded */ outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; return processDSNSFind(qstate, iq, id); } if(iq->qchase.qtype == LDNS_RR_TYPE_DNSKEY && SERVE_EXPIRED && qstate->is_valrec && reply_find_answer_rrset(&iq->qchase, iq->response->rep) != NULL) { /* clean out the authority section, if any, so it * does not overwrite dnssec valid data in the * validation recursion lookup. */ verbose(VERB_ALGO, "make DNSKEY minimal for serve " "expired"); iter_make_minimal(iq->response->rep); } if(!qstate->no_cache_store) iter_dns_store(qstate->env, &iq->response->qinfo, iq->response->rep, iq->qchase.qtype != iq->response->qinfo.qtype, qstate->prefetch_leeway, iq->dp&&iq->dp->has_parent_side_NS, qstate->region, qstate->query_flags, qstate->qstarttime, qstate->is_valrec); /* close down outstanding requests to be discarded */ outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; if(qstate->reply) sock_list_insert(&qstate->reply_origin, &qstate->reply->remote_addr, qstate->reply->remote_addrlen, qstate->region); if(iq->minimisation_state != DONOT_MINIMISE_STATE && !(iq->chase_flags & BIT_RD)) { if(FLAGS_GET_RCODE(iq->response->rep->flags) != LDNS_RCODE_NOERROR) { if(qstate->env->cfg->qname_minimisation_strict) { if(FLAGS_GET_RCODE(iq->response->rep->flags) == LDNS_RCODE_NXDOMAIN) { iter_scrub_nxdomain(iq->response); return final_state(iq); } return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } /* Best effort qname-minimisation. * Stop minimising and send full query when * RCODE is not NOERROR. */ iq->minimisation_state = DONOT_MINIMISE_STATE; } if(FLAGS_GET_RCODE(iq->response->rep->flags) == LDNS_RCODE_NXDOMAIN && !origtypecname) { /* Stop resolving when NXDOMAIN is DNSSEC * signed. Based on assumption that nameservers * serving signed zones do not return NXDOMAIN * for empty-non-terminals. */ /* If this response is actually a CNAME type, * the nxdomain rcode may not be for the qname, * and so it is not the final response. */ if(iq->dnssec_expected) return final_state(iq); /* Make subrequest to validate intermediate * NXDOMAIN if harden-below-nxdomain is * enabled. */ if(qstate->env->cfg->harden_below_nxdomain && qstate->env->need_to_validate) { struct module_qstate* subq = NULL; log_query_info(VERB_QUERY, "schedule NXDOMAIN validation:", &iq->response->qinfo); if(!generate_sub_request( iq->response->qinfo.qname, iq->response->qinfo.qname_len, iq->response->qinfo.qtype, iq->response->qinfo.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 1)) verbose(VERB_ALGO, "could not validate NXDOMAIN " "response"); } } return next_state(iq, QUERYTARGETS_STATE); } return final_state(iq); } else if(type == RESPONSE_TYPE_REFERRAL) { struct delegpt* old_dp = NULL; /* REFERRAL type responses get a reset of the * delegation point, and back to the QUERYTARGETS_STATE. */ verbose(VERB_DETAIL, "query response was REFERRAL"); /* if hardened, only store referral if we asked for it */ if(!qstate->no_cache_store && (!qstate->env->cfg->harden_referral_path || ( qstate->qinfo.qtype == LDNS_RR_TYPE_NS && (qstate->query_flags&BIT_RD) && !(qstate->query_flags&BIT_CD) /* we know that all other NS rrsets are scrubbed * away, thus on referral only one is left. * see if that equals the query name... */ && ( /* auth section, but sometimes in answer section*/ reply_find_rrset_section_ns(iq->response->rep, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_NS, iq->qchase.qclass) || reply_find_rrset_section_an(iq->response->rep, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_NS, iq->qchase.qclass) ) ))) { /* Store the referral under the current query */ /* no prefetch-leeway, since its not the answer */ iter_dns_store(qstate->env, &iq->response->qinfo, iq->response->rep, 1, 0, 0, NULL, 0, qstate->qstarttime, qstate->is_valrec); if(iq->store_parent_NS) iter_store_parentside_NS(qstate->env, iq->response->rep); if(qstate->env->neg_cache) val_neg_addreferral(qstate->env->neg_cache, iq->response->rep, iq->dp->name); } /* store parent-side-in-zone-glue, if directly queried for */ if(!qstate->no_cache_store && iq->query_for_pside_glue && !iq->pside_glue) { iq->pside_glue = reply_find_rrset(iq->response->rep, iq->qchase.qname, iq->qchase.qname_len, iq->qchase.qtype, iq->qchase.qclass); if(iq->pside_glue) { log_rrset_key(VERB_ALGO, "found parent-side " "glue", iq->pside_glue); iter_store_parentside_rrset(qstate->env, iq->pside_glue); } } /* Reset the event state, setting the current delegation * point to the referral. */ iq->deleg_msg = iq->response; /* Keep current delegation point for label comparison */ old_dp = iq->dp; iq->dp = delegpt_from_message(iq->response, qstate->region); if (qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; if(!iq->dp) { errinf(qstate, "malloc failure, for delegation point"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(old_dp->namelabs + 1 < iq->dp->namelabs) { /* We got a grandchild delegation (more than one label * difference) than expected. Check for in-between * delegations in the cache and remove them. * They could prove problematic when they expire * and rrset_expired_above() encounters them during * delegation cache lookups. */ uint8_t* qname = iq->dp->name; size_t qnamelen = iq->dp->namelen; rrset_cache_remove_above(qstate->env->rrset_cache, &qname, &qnamelen, LDNS_RR_TYPE_NS, iq->qchase.qclass, *qstate->env->now, old_dp->name, old_dp->namelen); } if(!cache_fill_missing(qstate->env, iq->qchase.qclass, qstate->region, iq->dp, 0)) { errinf(qstate, "malloc failure, copy extra info into delegation point"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } if(iq->store_parent_NS && query_dname_compare(iq->dp->name, iq->store_parent_NS->name) == 0) iter_merge_retry_counts(iq->dp, iq->store_parent_NS, ie->outbound_msg_retry); delegpt_log(VERB_ALGO, iq->dp); /* Count this as a referral. */ iq->referral_count++; iq->sent_count = 0; iq->dp_target_count = 0; /* see if the next dp is a trust anchor, or a DS was sent * along, indicating dnssec is expected for next zone */ iq->dnssec_expected = iter_indicates_dnssec(qstate->env, iq->dp, iq->response, iq->qchase.qclass); /* if dnssec, validating then also fetch the key for the DS */ if(iq->dnssec_expected && qstate->env->cfg->prefetch_key && !(qstate->query_flags&BIT_CD)) generate_dnskey_prefetch(qstate, iq, id); /* spawn off NS and addr to auth servers for the NS we just * got in the referral. This gets authoritative answer * (answer section trust level) rrset. * right after, we detach the subs, answer goes to cache. */ if(qstate->env->cfg->harden_referral_path) generate_ns_check(qstate, iq, id); /* stop current outstanding queries. * FIXME: should the outstanding queries be waited for and * handled? Say by a subquery that inherits the outbound_entry. */ outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; iq->response = NULL; iq->fail_addr_type = 0; verbose(VERB_ALGO, "cleared outbound list for next round"); return next_state(iq, QUERYTARGETS_STATE); } else if(type == RESPONSE_TYPE_CNAME) { uint8_t* sname = NULL; size_t snamelen = 0; /* CNAME type responses get a query restart (i.e., get a * reset of the query state and go back to INIT_REQUEST_STATE). */ verbose(VERB_DETAIL, "query response was CNAME"); if(verbosity >= VERB_ALGO) log_dns_msg("cname msg", &iq->response->qinfo, iq->response->rep); /* if qtype is DS, check we have the right level of answer, * like grandchild answer but we need the middle, reject it */ if(iq->qchase.qtype == LDNS_RR_TYPE_DS && !iq->dsns_point && !(iq->chase_flags&BIT_RD) && iter_ds_toolow(iq->response, iq->dp) && iter_dp_cangodown(&iq->qchase, iq->dp)) { outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; return processDSNSFind(qstate, iq, id); } if(iq->minimisation_state == MINIMISE_STATE && query_dname_compare(iq->qchase.qname, iq->qinfo_out.qname) != 0) { verbose(VERB_ALGO, "continue query minimisation, " "downwards, after CNAME response for " "intermediate label"); /* continue query minimisation, downwards */ return next_state(iq, QUERYTARGETS_STATE); } /* Process the CNAME response. */ if(!handle_cname_response(qstate, iq, iq->response, &sname, &snamelen)) { errinf(qstate, "malloc failure, CNAME info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* cache the CNAME response under the current query */ /* NOTE : set referral=1, so that rrsets get stored but not * the partial query answer (CNAME only). */ /* prefetchleeway applied because this updates answer parts */ if(!qstate->no_cache_store) iter_dns_store(qstate->env, &iq->response->qinfo, iq->response->rep, 1, qstate->prefetch_leeway, iq->dp&&iq->dp->has_parent_side_NS, NULL, qstate->query_flags, qstate->qstarttime, qstate->is_valrec); /* set the current request's qname to the new value. */ iq->qchase.qname = sname; iq->qchase.qname_len = snamelen; if(qstate->env->auth_zones) { /* apply rpz qname triggers after cname */ struct dns_msg* forged_response = rpz_callback_from_iterator_cname(qstate, iq); int count = 0; while(forged_response && reply_find_rrset_section_an( forged_response->rep, iq->qchase.qname, iq->qchase.qname_len, LDNS_RR_TYPE_CNAME, iq->qchase.qclass) && iq->qchase.qtype != LDNS_RR_TYPE_CNAME && count++ < ie->max_query_restarts) { /* another cname to follow */ if(!handle_cname_response(qstate, iq, forged_response, &sname, &snamelen)) { errinf(qstate, "malloc failure, CNAME info"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } iq->qchase.qname = sname; iq->qchase.qname_len = snamelen; forged_response = rpz_callback_from_iterator_cname(qstate, iq); } if(forged_response != NULL) { qstate->ext_state[id] = module_finished; qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = forged_response; iq->response = forged_response; next_state(iq, FINISHED_STATE); if(!iter_prepend(iq, qstate->return_msg, qstate->region)) { log_err("rpz: after cname, prepend rrsets: out of memory"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } qstate->return_msg->qinfo = qstate->qinfo; return 0; } } /* Clear the query state, since this is a query restart. */ iq->deleg_msg = NULL; iq->dp = NULL; iq->dsns_point = NULL; iq->auth_zone_response = 0; iq->sent_count = 0; iq->dp_target_count = 0; iq->query_restart_count++; if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = INIT_MINIMISE_STATE; /* stop current outstanding queries. * FIXME: should the outstanding queries be waited for and * handled? Say by a subquery that inherits the outbound_entry. */ outbound_list_clear(&iq->outlist); iq->num_current_queries = 0; fptr_ok(fptr_whitelist_modenv_detach_subs( qstate->env->detach_subs)); (*qstate->env->detach_subs)(qstate); iq->num_target_queries = 0; if(qstate->reply) sock_list_insert(&qstate->reply_origin, &qstate->reply->remote_addr, qstate->reply->remote_addrlen, qstate->region); verbose(VERB_ALGO, "cleared outbound list for query restart"); /* go to INIT_REQUEST_STATE for new qname. */ return next_state(iq, INIT_REQUEST_STATE); } else if(type == RESPONSE_TYPE_LAME) { /* Cache the LAMEness. */ verbose(VERB_DETAIL, "query response was categorized as %sLAME", dnsseclame?"DNSSEC ":""); if(!dname_subdomain_c(iq->qchase.qname, iq->dp->name)) { log_err("mark lame: mismatch in qname and dpname"); /* throwaway this reply below */ } else if(qstate->reply) { /* need addr for lameness cache, but we may have * gotten this from cache, so test to be sure */ if(!infra_set_lame(qstate->env->infra_cache, &qstate->reply->remote_addr, qstate->reply->remote_addrlen, iq->dp->name, iq->dp->namelen, *qstate->env->now, dnsseclame, 0, iq->qchase.qtype)) log_err("mark host lame: out of memory"); } } else if(type == RESPONSE_TYPE_REC_LAME) { /* Cache the LAMEness. */ verbose(VERB_DETAIL, "query response REC_LAME: " "recursive but not authoritative server"); if(!dname_subdomain_c(iq->qchase.qname, iq->dp->name)) { log_err("mark rec_lame: mismatch in qname and dpname"); /* throwaway this reply below */ } else if(qstate->reply) { /* need addr for lameness cache, but we may have * gotten this from cache, so test to be sure */ verbose(VERB_DETAIL, "mark as REC_LAME"); if(!infra_set_lame(qstate->env->infra_cache, &qstate->reply->remote_addr, qstate->reply->remote_addrlen, iq->dp->name, iq->dp->namelen, *qstate->env->now, 0, 1, iq->qchase.qtype)) log_err("mark host lame: out of memory"); } } else if(type == RESPONSE_TYPE_THROWAWAY) { /* LAME and THROWAWAY responses are handled the same way. * In this case, the event is just sent directly back to * the QUERYTARGETS_STATE without resetting anything, * because, clearly, the next target must be tried. */ verbose(VERB_DETAIL, "query response was categorized as THROWAWAY"); } else { log_warn("A query response came back with an unknown type: %d", (int)type); } /* LAME, THROWAWAY and "unknown" all end up here. * Recycle to the QUERYTARGETS state to hopefully try a * different target. */ if (qstate->env->cfg->qname_minimisation && !qstate->env->cfg->qname_minimisation_strict) iq->minimisation_state = DONOT_MINIMISE_STATE; if(iq->auth_zone_response) { /* can we fallback? */ iq->auth_zone_response = 0; if(!auth_zones_can_fallback(qstate->env->auth_zones, iq->dp->name, iq->dp->namelen, qstate->qinfo.qclass)) { verbose(VERB_ALGO, "auth zone response bad, and no" " fallback possible, servfail"); errinf_dname(qstate, "response is bad, no fallback, " "for auth zone", iq->dp->name); return error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); } verbose(VERB_ALGO, "auth zone response was bad, " "fallback enabled"); iq->auth_zone_avoid = 1; if(iq->dp->auth_dp) { /* we are using a dp for the auth zone, with no * nameservers, get one first */ iq->dp = NULL; return next_state(iq, INIT_REQUEST_STATE); } } return next_state(iq, QUERYTARGETS_STATE); } /** * Return priming query results to interested super querystates. * * Sets the delegation point and delegation message (not nonRD queries). * This is a callback from walk_supers. * * @param qstate: priming query state that finished. * @param id: module id. * @param forq: the qstate for which priming has been done. */ static void prime_supers(struct module_qstate* qstate, int id, struct module_qstate* forq) { struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id]; struct delegpt* dp = NULL; log_assert(qstate->is_priming || foriq->wait_priming_stub); log_assert(qstate->return_rcode == LDNS_RCODE_NOERROR); /* Convert our response to a delegation point */ dp = delegpt_from_message(qstate->return_msg, forq->region); if(!dp) { /* if there is no convertible delegation point, then * the ANSWER type was (presumably) a negative answer. */ verbose(VERB_ALGO, "prime response was not a positive " "ANSWER; failing"); foriq->dp = NULL; foriq->state = QUERYTARGETS_STATE; return; } log_query_info(VERB_DETAIL, "priming successful for", &qstate->qinfo); delegpt_log(VERB_ALGO, dp); foriq->dp = dp; foriq->deleg_msg = dns_copy_msg(qstate->return_msg, forq->region); if(!foriq->deleg_msg) { log_err("copy prime response: out of memory"); foriq->dp = NULL; foriq->state = QUERYTARGETS_STATE; return; } /* root priming responses go to init stage 2, priming stub * responses to to stage 3. */ if(foriq->wait_priming_stub) { foriq->state = INIT_REQUEST_3_STATE; foriq->wait_priming_stub = 0; } else foriq->state = INIT_REQUEST_2_STATE; /* because we are finished, the parent will be reactivated */ } /** * This handles the response to a priming query. This is used to handle both * root and stub priming responses. This is basically the equivalent of the * QUERY_RESP_STATE, but will not handle CNAME responses and will treat * REFERRALs as ANSWERS. It will also update and reactivate the originating * event. * * @param qstate: query state. * @param id: module id. * @return true if the event needs more immediate processing, false if not. * This state always returns false. */ static int processPrimeResponse(struct module_qstate* qstate, int id) { struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id]; enum response_type type; iq->response->rep->flags &= ~(BIT_RD|BIT_RA); /* ignore rec-lame */ type = response_type_from_server( (int)((iq->chase_flags&BIT_RD) || iq->chase_to_rd), iq->response, &iq->qchase, iq->dp, NULL); if(type == RESPONSE_TYPE_ANSWER) { qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = iq->response; } else { errinf(qstate, "prime response did not get an answer"); errinf_dname(qstate, "for", qstate->qinfo.qname); qstate->return_rcode = LDNS_RCODE_SERVFAIL; qstate->return_msg = NULL; } /* validate the root or stub after priming (if enabled). * This is the same query as the prime query, but with validation. * Now that we are primed, the additional queries that validation * may need can be resolved. */ if(qstate->env->cfg->harden_referral_path) { struct module_qstate* subq = NULL; log_nametypeclass(VERB_ALGO, "schedule prime validation", qstate->qinfo.qname, qstate->qinfo.qtype, qstate->qinfo.qclass); if(!generate_sub_request(qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, qstate->qinfo.qclass, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, 1, 0)) { verbose(VERB_ALGO, "could not generate prime check"); } generate_a_aaaa_check(qstate, iq, id); } /* This event is finished. */ qstate->ext_state[id] = module_finished; return 0; } /** * Do final processing on responses to target queries. Events reach this * state after the iterative resolution algorithm terminates. This state is * responsible for reactivating the original event, and housekeeping related * to received target responses (caching, updating the current delegation * point, etc). * Callback from walk_supers for every super state that is interested in * the results from this query. * * @param qstate: query state. * @param id: module id. * @param forq: super query state. */ static void processTargetResponse(struct module_qstate* qstate, int id, struct module_qstate* forq) { struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id]; struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id]; struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id]; struct ub_packed_rrset_key* rrset; struct delegpt_ns* dpns; log_assert(qstate->return_rcode == LDNS_RCODE_NOERROR); foriq->state = QUERYTARGETS_STATE; log_query_info(VERB_ALGO, "processTargetResponse", &qstate->qinfo); log_query_info(VERB_ALGO, "processTargetResponse super", &forq->qinfo); /* Tell the originating event that this target query has finished * (regardless if it succeeded or not). */ foriq->num_target_queries--; /* check to see if parent event is still interested (in orig name). */ if(!foriq->dp) { verbose(VERB_ALGO, "subq: parent not interested, was reset"); return; /* not interested anymore */ } dpns = delegpt_find_ns(foriq->dp, qstate->qinfo.qname, qstate->qinfo.qname_len); if(!dpns) { /* If not interested, just stop processing this event */ verbose(VERB_ALGO, "subq: parent not interested anymore"); /* could be because parent was jostled out of the cache, and a new identical query arrived, that does not want it*/ return; } /* if iq->query_for_pside_glue then add the pside_glue (marked lame) */ if(iq->pside_glue) { /* if the pside_glue is NULL, then it could not be found, * the done_pside is already set when created and a cache * entry created in processFinished so nothing to do here */ log_rrset_key(VERB_ALGO, "add parentside glue to dp", iq->pside_glue); if(!delegpt_add_rrset(foriq->dp, forq->region, iq->pside_glue, 1, NULL)) log_err("out of memory adding pside glue"); } /* This response is relevant to the current query, so we * add (attempt to add, anyway) this target(s) and reactivate * the original event. * NOTE: we could only look for the AnswerRRset if the * response type was ANSWER. */ rrset = reply_find_answer_rrset(&iq->qchase, qstate->return_msg->rep); if(rrset) { int additions = 0; /* if CNAMEs have been followed - add new NS to delegpt. */ /* BTW. RFC 1918 says NS should not have got CNAMEs. Robust. */ if(!delegpt_find_ns(foriq->dp, rrset->rk.dname, rrset->rk.dname_len)) { /* if dpns->lame then set newcname ns lame too */ if(!delegpt_add_ns(foriq->dp, forq->region, rrset->rk.dname, dpns->lame, dpns->tls_auth_name, dpns->port)) log_err("out of memory adding cnamed-ns"); } /* if dpns->lame then set the address(es) lame too */ if(!delegpt_add_rrset(foriq->dp, forq->region, rrset, dpns->lame, &additions)) log_err("out of memory adding targets"); if(!additions) { /* no new addresses, increase the nxns counter, like * this could be a list of wildcards with no new * addresses */ target_count_increase_nx(foriq, 1); } verbose(VERB_ALGO, "added target response"); delegpt_log(VERB_ALGO, foriq->dp); } else { verbose(VERB_ALGO, "iterator TargetResponse failed"); delegpt_mark_neg(dpns, qstate->qinfo.qtype); if((dpns->got4 == 2 || (!ie->supports_ipv4 && !ie->nat64.use_nat64)) && (dpns->got6 == 2 || !ie->supports_ipv6)) { dpns->resolved = 1; /* fail the target */ /* do not count cached answers */ if(qstate->reply_origin && qstate->reply_origin->len != 0) { target_count_increase_nx(foriq, 1); } } } } /** * Process response for DS NS Find queries, that attempt to find the delegation * point where we ask the DS query from. * * @param qstate: query state. * @param id: module id. * @param forq: super query state. */ static void processDSNSResponse(struct module_qstate* qstate, int id, struct module_qstate* forq) { struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id]; /* if the finished (iq->response) query has no NS set: continue * up to look for the right dp; nothing to change, do DPNSstate */ if(qstate->return_rcode != LDNS_RCODE_NOERROR) return; /* seek further */ /* find the NS RRset (without allowing CNAMEs) */ if(!reply_find_rrset(qstate->return_msg->rep, qstate->qinfo.qname, qstate->qinfo.qname_len, LDNS_RR_TYPE_NS, qstate->qinfo.qclass)){ return; /* seek further */ } /* else, store as DP and continue at querytargets */ foriq->state = QUERYTARGETS_STATE; foriq->dp = delegpt_from_message(qstate->return_msg, forq->region); if(!foriq->dp) { log_err("out of memory in dsns dp alloc"); errinf(qstate, "malloc failure, in DS search"); return; /* dp==NULL in QUERYTARGETS makes SERVFAIL */ } /* success, go query the querytargets in the new dp (and go down) */ } /** * Process response for qclass=ANY queries for a particular class. * Append to result or error-exit. * * @param qstate: query state. * @param id: module id. * @param forq: super query state. */ static void processClassResponse(struct module_qstate* qstate, int id, struct module_qstate* forq) { struct iter_qstate* foriq = (struct iter_qstate*)forq->minfo[id]; struct dns_msg* from = qstate->return_msg; log_query_info(VERB_ALGO, "processClassResponse", &qstate->qinfo); log_query_info(VERB_ALGO, "processClassResponse super", &forq->qinfo); if(qstate->return_rcode != LDNS_RCODE_NOERROR) { /* cause servfail for qclass ANY query */ foriq->response = NULL; foriq->state = FINISHED_STATE; return; } /* append result */ if(!foriq->response) { /* allocate the response: copy RCODE, sec_state */ foriq->response = dns_copy_msg(from, forq->region); if(!foriq->response) { log_err("malloc failed for qclass ANY response"); foriq->state = FINISHED_STATE; return; } foriq->response->qinfo.qclass = forq->qinfo.qclass; /* qclass ANY does not receive the AA flag on replies */ foriq->response->rep->authoritative = 0; } else { struct dns_msg* to = foriq->response; /* add _from_ this response _to_ existing collection */ /* if there are records, copy RCODE */ /* lower sec_state if this message is lower */ if(from->rep->rrset_count != 0) { size_t n = from->rep->rrset_count+to->rep->rrset_count; struct ub_packed_rrset_key** dest, **d; /* copy appropriate rcode */ to->rep->flags = from->rep->flags; /* copy rrsets */ if(from->rep->rrset_count > RR_COUNT_MAX || to->rep->rrset_count > RR_COUNT_MAX) { log_err("malloc failed (too many rrsets) in collect ANY"); foriq->state = FINISHED_STATE; return; /* integer overflow protection */ } dest = regional_alloc(forq->region, sizeof(dest[0])*n); if(!dest) { log_err("malloc failed in collect ANY"); foriq->state = FINISHED_STATE; return; } d = dest; /* copy AN */ memcpy(dest, to->rep->rrsets, to->rep->an_numrrsets * sizeof(dest[0])); dest += to->rep->an_numrrsets; memcpy(dest, from->rep->rrsets, from->rep->an_numrrsets * sizeof(dest[0])); dest += from->rep->an_numrrsets; /* copy NS */ memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets, to->rep->ns_numrrsets * sizeof(dest[0])); dest += to->rep->ns_numrrsets; memcpy(dest, from->rep->rrsets+from->rep->an_numrrsets, from->rep->ns_numrrsets * sizeof(dest[0])); dest += from->rep->ns_numrrsets; /* copy AR */ memcpy(dest, to->rep->rrsets+to->rep->an_numrrsets+ to->rep->ns_numrrsets, to->rep->ar_numrrsets * sizeof(dest[0])); dest += to->rep->ar_numrrsets; memcpy(dest, from->rep->rrsets+from->rep->an_numrrsets+ from->rep->ns_numrrsets, from->rep->ar_numrrsets * sizeof(dest[0])); /* update counts */ to->rep->rrsets = d; to->rep->an_numrrsets += from->rep->an_numrrsets; to->rep->ns_numrrsets += from->rep->ns_numrrsets; to->rep->ar_numrrsets += from->rep->ar_numrrsets; to->rep->rrset_count = n; } if(from->rep->security < to->rep->security) /* lowest sec */ to->rep->security = from->rep->security; if(from->rep->qdcount != 0) /* insert qd if appropriate */ to->rep->qdcount = from->rep->qdcount; if(from->rep->ttl < to->rep->ttl) /* use smallest TTL */ to->rep->ttl = from->rep->ttl; if(from->rep->prefetch_ttl < to->rep->prefetch_ttl) to->rep->prefetch_ttl = from->rep->prefetch_ttl; if(from->rep->serve_expired_ttl < to->rep->serve_expired_ttl) to->rep->serve_expired_ttl = from->rep->serve_expired_ttl; if(from->rep->serve_expired_norec_ttl < to->rep->serve_expired_norec_ttl) to->rep->serve_expired_norec_ttl = from->rep->serve_expired_norec_ttl; } /* are we done? */ foriq->num_current_queries --; if(foriq->num_current_queries == 0) foriq->state = FINISHED_STATE; } /** * Collect class ANY responses and make them into one response. This * state is started and it creates queries for all classes (that have * root hints). The answers are then collected. * * @param qstate: query state. * @param id: module id. * @return true if the event needs more immediate processing, false if not. */ static int processCollectClass(struct module_qstate* qstate, int id) { struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id]; struct module_qstate* subq; /* If qchase.qclass == 0 then send out queries for all classes. * Otherwise, do nothing (wait for all answers to arrive and the * processClassResponse to put them together, and that moves us * towards the Finished state when done. */ if(iq->qchase.qclass == 0) { uint16_t c = 0; iq->qchase.qclass = LDNS_RR_CLASS_ANY; while(iter_get_next_root(qstate->env->hints, qstate->env->fwds, &c)) { /* generate query for this class */ log_nametypeclass(VERB_ALGO, "spawn collect query", qstate->qinfo.qname, qstate->qinfo.qtype, c); if(!generate_sub_request(qstate->qinfo.qname, qstate->qinfo.qname_len, qstate->qinfo.qtype, c, qstate, id, iq, INIT_REQUEST_STATE, FINISHED_STATE, &subq, (int)!(qstate->query_flags&BIT_CD), 0)) { errinf(qstate, "could not generate class ANY" " lookup query"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* ignore subq, no special init required */ iq->num_current_queries ++; if(c == 0xffff) break; else c++; } /* if no roots are configured at all, return */ if(iq->num_current_queries == 0) { verbose(VERB_ALGO, "No root hints or fwds, giving up " "on qclass ANY"); return error_response_cache(qstate, id, LDNS_RCODE_REFUSED); } /* return false, wait for queries to return */ } /* if woke up here because of an answer, wait for more answers */ return 0; } /** * This handles the final state for first-tier responses (i.e., responses to * externally generated queries). * * @param qstate: query state. * @param iq: iterator query state. * @param id: module id. * @return true if the event needs more processing, false if not. Since this * is the final state for an event, it always returns false. */ static int processFinished(struct module_qstate* qstate, struct iter_qstate* iq, int id) { log_query_info(VERB_QUERY, "finishing processing for", &qstate->qinfo); /* store negative cache element for parent side glue. */ if(!qstate->no_cache_store && iq->query_for_pside_glue && !iq->pside_glue) iter_store_parentside_neg(qstate->env, &qstate->qinfo, iq->deleg_msg?iq->deleg_msg->rep: (iq->response?iq->response->rep:NULL)); if(!iq->response) { verbose(VERB_ALGO, "No response is set, servfail"); errinf(qstate, "(no response found at query finish)"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* Make sure that the RA flag is set (since the presence of * this module means that recursion is available) */ iq->response->rep->flags |= BIT_RA; /* Clear the AA flag */ /* FIXME: does this action go here or in some other module? */ iq->response->rep->flags &= ~BIT_AA; /* make sure QR flag is on */ iq->response->rep->flags |= BIT_QR; /* explicitly set the EDE string to NULL */ iq->response->rep->reason_bogus_str = NULL; if((qstate->env->cfg->val_log_level >= 2 || qstate->env->cfg->log_servfail) && qstate->errinf && !qstate->env->cfg->val_log_squelch) { char* err_str = errinf_to_str_misc(qstate); if(err_str) { verbose(VERB_ALGO, "iterator EDE: %s", err_str); iq->response->rep->reason_bogus_str = err_str; } } /* we have finished processing this query */ qstate->ext_state[id] = module_finished; /* TODO: we are using a private TTL, trim the response. */ /* if (mPrivateTTL > 0){IterUtils.setPrivateTTL(resp, mPrivateTTL); } */ /* prepend any items we have accumulated */ if(iq->an_prepend_list || iq->ns_prepend_list) { if(!iter_prepend(iq, iq->response, qstate->region)) { log_err("prepend rrsets: out of memory"); return error_response(qstate, id, LDNS_RCODE_SERVFAIL); } /* reset the query name back */ iq->response->qinfo = qstate->qinfo; /* the security state depends on the combination */ iq->response->rep->security = sec_status_unchecked; /* store message with the finished prepended items, * but only if we did recursion. The nonrecursion referral * from cache does not need to be stored in the msg cache. */ if(!qstate->no_cache_store && (qstate->query_flags&BIT_RD)) { iter_dns_store(qstate->env, &qstate->qinfo, iq->response->rep, 0, qstate->prefetch_leeway, iq->dp&&iq->dp->has_parent_side_NS, qstate->region, qstate->query_flags, qstate->qstarttime, qstate->is_valrec); } } qstate->return_rcode = LDNS_RCODE_NOERROR; qstate->return_msg = iq->response; return 0; } /* * Return priming query results to interested super querystates. * * Sets the delegation point and delegation message (not nonRD queries). * This is a callback from walk_supers. * * @param qstate: query state that finished. * @param id: module id. * @param super: the qstate to inform. */ void iter_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super) { if(!qstate->is_priming && super->qinfo.qclass == LDNS_RR_CLASS_ANY) processClassResponse(qstate, id, super); else if(super->qinfo.qtype == LDNS_RR_TYPE_DS && ((struct iter_qstate*) super->minfo[id])->state == DSNS_FIND_STATE) processDSNSResponse(qstate, id, super); else if(qstate->return_rcode != LDNS_RCODE_NOERROR) error_supers(qstate, id, super); else if(qstate->is_priming) prime_supers(qstate, id, super); else processTargetResponse(qstate, id, super); } /** * Handle iterator state. * Handle events. This is the real processing loop for events, responsible * for moving events through the various states. If a processing method * returns true, then it will be advanced to the next state. If false, then * processing will stop. * * @param qstate: query state. * @param ie: iterator shared global environment. * @param iq: iterator query state. * @param id: module id. */ static void iter_handle(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { int cont = 1; while(cont) { verbose(VERB_ALGO, "iter_handle processing q with state %s", iter_state_to_string(iq->state)); switch(iq->state) { case INIT_REQUEST_STATE: cont = processInitRequest(qstate, iq, ie, id); break; case INIT_REQUEST_2_STATE: cont = processInitRequest2(qstate, iq, id); break; case INIT_REQUEST_3_STATE: cont = processInitRequest3(qstate, iq, id); break; case QUERYTARGETS_STATE: cont = processQueryTargets(qstate, iq, ie, id); break; case QUERY_RESP_STATE: cont = processQueryResponse(qstate, iq, ie, id); break; case PRIME_RESP_STATE: cont = processPrimeResponse(qstate, id); break; case COLLECT_CLASS_STATE: cont = processCollectClass(qstate, id); break; case DSNS_FIND_STATE: cont = processDSNSFind(qstate, iq, id); break; case FINISHED_STATE: cont = processFinished(qstate, iq, id); break; default: log_warn("iterator: invalid state: %d", iq->state); cont = 0; break; } } } /** * This is the primary entry point for processing request events. Note that * this method should only be used by external modules. * @param qstate: query state. * @param ie: iterator shared global environment. * @param iq: iterator query state. * @param id: module id. */ static void process_request(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id) { /* external requests start in the INIT state, and finish using the * FINISHED state. */ iq->state = INIT_REQUEST_STATE; iq->final_state = FINISHED_STATE; verbose(VERB_ALGO, "process_request: new external request event"); iter_handle(qstate, iq, ie, id); } /** process authoritative server reply */ static void process_response(struct module_qstate* qstate, struct iter_qstate* iq, struct iter_env* ie, int id, struct outbound_entry* outbound, enum module_ev event) { struct msg_parse* prs; struct edns_data edns; sldns_buffer* pkt; verbose(VERB_ALGO, "process_response: new external response event"); iq->response = NULL; iq->state = QUERY_RESP_STATE; if(event == module_event_noreply || event == module_event_error) { if(event == module_event_noreply && iq->timeout_count >= 3 && qstate->env->cfg->use_caps_bits_for_id && !iq->caps_fallback && !is_caps_whitelisted(ie, iq)) { /* start fallback */ iq->caps_fallback = 1; iq->caps_server = 0; iq->caps_reply = NULL; iq->caps_response = NULL; iq->caps_minimisation_state = DONOT_MINIMISE_STATE; iq->state = QUERYTARGETS_STATE; iq->num_current_queries--; /* need fresh attempts for the 0x20 fallback, if * that was the cause for the failure */ iter_dec_attempts(iq->dp, 3, ie->outbound_msg_retry); verbose(VERB_DETAIL, "Capsforid: timeouts, starting fallback"); goto handle_it; } goto handle_it; } if( (event != module_event_reply && event != module_event_capsfail) || !qstate->reply) { log_err("Bad event combined with response"); outbound_list_remove(&iq->outlist, outbound); errinf(qstate, "module iterator received wrong internal event with a response message"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return; } /* parse message */ fill_fail_addr(iq, &qstate->reply->remote_addr, qstate->reply->remote_addrlen); prs = (struct msg_parse*)regional_alloc(qstate->env->scratch, sizeof(struct msg_parse)); if(!prs) { log_err("out of memory on incoming message"); /* like packet got dropped */ goto handle_it; } memset(prs, 0, sizeof(*prs)); memset(&edns, 0, sizeof(edns)); pkt = qstate->reply->c->buffer; sldns_buffer_set_position(pkt, 0); if(parse_packet(pkt, prs, qstate->env->scratch) != LDNS_RCODE_NOERROR) { verbose(VERB_ALGO, "parse error on reply packet"); iq->parse_failures++; goto handle_it; } /* edns is not examined, but removed from message to help cache */ if(parse_extract_edns_from_response_msg(prs, &edns, qstate->env->scratch) != LDNS_RCODE_NOERROR) { iq->parse_failures++; goto handle_it; } /* Copy the edns options we may got from the back end */ qstate->edns_opts_back_in = NULL; if(edns.opt_list_in) { qstate->edns_opts_back_in = edns_opt_copy_region(edns.opt_list_in, qstate->region); if(!qstate->edns_opts_back_in) { log_err("out of memory on incoming message"); /* like packet got dropped */ goto handle_it; } } if(!inplace_cb_edns_back_parsed_call(qstate->env, qstate)) { log_err("unable to call edns_back_parsed callback"); goto handle_it; } /* remove CD-bit, we asked for in case we handle validation ourself */ prs->flags &= ~BIT_CD; /* normalize and sanitize: easy to delete items from linked lists */ if(!scrub_message(pkt, prs, &iq->qinfo_out, iq->dp->name, qstate->env->scratch, qstate->env, qstate, ie)) { /* if 0x20 enabled, start fallback, but we have no message */ if(event == module_event_capsfail && !iq->caps_fallback) { iq->caps_fallback = 1; iq->caps_server = 0; iq->caps_reply = NULL; iq->caps_response = NULL; iq->caps_minimisation_state = DONOT_MINIMISE_STATE; iq->state = QUERYTARGETS_STATE; iq->num_current_queries--; verbose(VERB_DETAIL, "Capsforid: scrub failed, starting fallback with no response"); } iq->scrub_failures++; goto handle_it; } /* allocate response dns_msg in region */ iq->response = dns_alloc_msg(pkt, prs, qstate->region); if(!iq->response) goto handle_it; log_query_info(VERB_DETAIL, "response for", &qstate->qinfo); log_name_addr(VERB_DETAIL, "reply from", iq->dp->name, &qstate->reply->remote_addr, qstate->reply->remote_addrlen); if(verbosity >= VERB_ALGO) log_dns_msg("incoming scrubbed packet:", &iq->response->qinfo, iq->response->rep); if(qstate->env->cfg->aggressive_nsec) { limit_nsec_ttl(iq->response); } if(event == module_event_capsfail || iq->caps_fallback) { if(qstate->env->cfg->qname_minimisation && iq->minimisation_state != DONOT_MINIMISE_STATE) { /* Skip QNAME minimisation for next query, since that * one has to match the current query. */ iq->minimisation_state = SKIP_MINIMISE_STATE; } /* for fallback we care about main answer, not additionals */ /* removing that makes comparison more likely to succeed */ caps_strip_reply(iq->response->rep); if(iq->caps_fallback && iq->caps_minimisation_state != iq->minimisation_state) { /* QNAME minimisation state has changed, restart caps * fallback. */ iq->caps_fallback = 0; } if(!iq->caps_fallback) { /* start fallback */ iq->caps_fallback = 1; iq->caps_server = 0; iq->caps_reply = iq->response->rep; iq->caps_response = iq->response; iq->caps_minimisation_state = iq->minimisation_state; iq->state = QUERYTARGETS_STATE; iq->num_current_queries--; verbose(VERB_DETAIL, "Capsforid: starting fallback"); goto handle_it; } else { /* check if reply is the same, otherwise, fail */ if(!iq->caps_reply) { iq->caps_reply = iq->response->rep; iq->caps_response = iq->response; iq->caps_server = -1; /*become zero at ++, so that we start the full set of trials */ } else if(caps_failed_rcode(iq->caps_reply) && !caps_failed_rcode(iq->response->rep)) { /* prefer to upgrade to non-SERVFAIL */ iq->caps_reply = iq->response->rep; iq->caps_response = iq->response; } else if(!caps_failed_rcode(iq->caps_reply) && caps_failed_rcode(iq->response->rep)) { /* if we have non-SERVFAIL as answer then * we can ignore SERVFAILs for the equality * comparison */ /* no instructions here, skip other else */ } else if(caps_failed_rcode(iq->caps_reply) && caps_failed_rcode(iq->response->rep)) { /* failure is same as other failure in fallbk*/ /* no instructions here, skip other else */ } else if(!reply_equal(iq->response->rep, iq->caps_reply, qstate->env->scratch)) { verbose(VERB_DETAIL, "Capsforid fallback: " "getting different replies, failed"); outbound_list_remove(&iq->outlist, outbound); errinf(qstate, "0x20 failed, then got different replies in fallback"); (void)error_response_cache(qstate, id, LDNS_RCODE_SERVFAIL); return; } /* continue the fallback procedure at next server */ iq->caps_server++; iq->state = QUERYTARGETS_STATE; iq->num_current_queries--; verbose(VERB_DETAIL, "Capsforid: reply is equal. " "go to next fallback"); goto handle_it; } } iq->caps_fallback = 0; /* if we were in fallback, 0x20 is OK now */ handle_it: outbound_list_remove(&iq->outlist, outbound); iter_handle(qstate, iq, ie, id); } void iter_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound) { struct iter_env* ie = (struct iter_env*)qstate->env->modinfo[id]; struct iter_qstate* iq = (struct iter_qstate*)qstate->minfo[id]; verbose(VERB_QUERY, "iterator[module %d] operate: extstate:%s event:%s", id, strextstate(qstate->ext_state[id]), strmodulevent(event)); if(iq) log_query_info(VERB_QUERY, "iterator operate: query", &qstate->qinfo); if(iq && qstate->qinfo.qname != iq->qchase.qname) log_query_info(VERB_QUERY, "iterator operate: chased to", &iq->qchase); /* perform iterator state machine */ if((event == module_event_new || event == module_event_pass) && iq == NULL) { if(!iter_new(qstate, id)) { errinf(qstate, "malloc failure, new iterator module allocation"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return; } iq = (struct iter_qstate*)qstate->minfo[id]; process_request(qstate, iq, ie, id); return; } if(iq && event == module_event_pass) { iter_handle(qstate, iq, ie, id); return; } if(iq && outbound) { process_response(qstate, iq, ie, id, outbound, event); return; } if(event == module_event_error) { verbose(VERB_ALGO, "got called with event error, giving up"); errinf(qstate, "iterator module got the error event"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); return; } log_err("bad event for iterator"); errinf(qstate, "iterator module received wrong event"); (void)error_response(qstate, id, LDNS_RCODE_SERVFAIL); } void iter_clear(struct module_qstate* qstate, int id) { struct iter_qstate* iq; if(!qstate) return; iq = (struct iter_qstate*)qstate->minfo[id]; if(iq) { outbound_list_clear(&iq->outlist); if(iq->target_count && --iq->target_count[TARGET_COUNT_REF] == 0) { free(iq->target_count); if(*iq->nxns_dp) free(*iq->nxns_dp); free(iq->nxns_dp); } iq->num_current_queries = 0; } qstate->minfo[id] = NULL; } size_t iter_get_mem(struct module_env* env, int id) { struct iter_env* ie = (struct iter_env*)env->modinfo[id]; if(!ie) return 0; return sizeof(*ie) + sizeof(int)*((size_t)ie->max_dependency_depth+1) + donotq_get_mem(ie->donotq) + priv_get_mem(ie->priv); } /** * The iterator function block */ static struct module_func_block iter_block = { "iterator", NULL, NULL, &iter_init, &iter_deinit, &iter_operate, &iter_inform_super, &iter_clear, &iter_get_mem }; struct module_func_block* iter_get_funcblock(void) { return &iter_block; } const char* iter_state_to_string(enum iter_state state) { switch (state) { case INIT_REQUEST_STATE : return "INIT REQUEST STATE"; case INIT_REQUEST_2_STATE : return "INIT REQUEST STATE (stage 2)"; case INIT_REQUEST_3_STATE: return "INIT REQUEST STATE (stage 3)"; case QUERYTARGETS_STATE : return "QUERY TARGETS STATE"; case PRIME_RESP_STATE : return "PRIME RESPONSE STATE"; case COLLECT_CLASS_STATE : return "COLLECT CLASS STATE"; case DSNS_FIND_STATE : return "DSNS FIND STATE"; case QUERY_RESP_STATE : return "QUERY RESPONSE STATE"; case FINISHED_STATE : return "FINISHED RESPONSE STATE"; default : return "UNKNOWN ITER STATE"; } } int iter_state_is_responsestate(enum iter_state s) { switch(s) { case INIT_REQUEST_STATE : case INIT_REQUEST_2_STATE : case INIT_REQUEST_3_STATE : case QUERYTARGETS_STATE : case COLLECT_CLASS_STATE : return 0; default: break; } return 1; } unbound-1.25.1/iterator/iter_scrub.h0000644000175000017500000000564715203270263017065 0ustar wouterwouter/* * iterator/iter_scrub.h - scrubbing, normalization, sanitization of DNS msgs. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has routine(s) for cleaning up incoming DNS messages from * possible useless or malicious junk in it. */ #ifndef ITERATOR_ITER_SCRUB_H #define ITERATOR_ITER_SCRUB_H struct sldns_buffer; struct msg_parse; struct query_info; struct regional; struct module_env; struct iter_env; struct module_qstate; /** * Cleanup the passed dns message. * @param pkt: the packet itself, for resolving name compression pointers. * the packet buffer is unaltered. * @param msg: the parsed packet, this structure is cleaned up. * @param qinfo: the query info that was sent to the server. Checked. * @param zonename: the name of the last delegation point. * Used to determine out of bailiwick information. * @param regional: where to allocate (new) parts of the message. * @param env: module environment with config settings and cache. * @param qstate: for setting errinf for EDE error messages. * @param ie: iterator module environment data. * @return: false if the message is total waste. true if scrubbed with success. */ int scrub_message(struct sldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct regional* regional, struct module_env* env, struct module_qstate* qstate, struct iter_env* ie); #endif /* ITERATOR_ITER_SCRUB_H */ unbound-1.25.1/iterator/iter_resptype.c0000644000175000017500000002632015203270263017604 0ustar wouterwouter/* * iterator/iter_resptype.c - response type information and classification. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file defines the response type. DNS Responses can be classified as * one of the response types. */ #include "config.h" #include "iterator/iter_resptype.h" #include "iterator/iter_delegpt.h" #include "iterator/iterator.h" #include "services/cache/dns.h" #include "util/net_help.h" #include "util/data/dname.h" #include "sldns/rrdef.h" #include "sldns/pkthdr.h" enum response_type response_type_from_cache(struct dns_msg* msg, struct query_info* request) { /* If the message is NXDOMAIN, then it is an ANSWER. */ if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) return RESPONSE_TYPE_ANSWER; if(request->qtype == LDNS_RR_TYPE_ANY) return RESPONSE_TYPE_ANSWER; /* First we look at the answer section. This can tell us if this is * CNAME or positive ANSWER. */ if(msg->rep->an_numrrsets > 0) { /* Now look at the answer section first. 3 states: * o our answer is there directly, * o our answer is there after a cname, * o or there is just a cname. */ uint8_t* mname = request->qname; size_t mname_len = request->qname_len; size_t i; for(i=0; irep->an_numrrsets; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; /* If we have encountered an answer (before or * after a CNAME), then we are done! Note that * if qtype == CNAME then this will be noted as * an ANSWER before it gets treated as a CNAME, * as it should */ if(ntohs(s->rk.type) == request->qtype && ntohs(s->rk.rrset_class) == request->qclass && query_dname_compare(mname, s->rk.dname) == 0) { return RESPONSE_TYPE_ANSWER; } /* If we have encountered a CNAME, make sure that * it is relevant. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(mname, s->rk.dname) == 0) { get_cname_target(s, &mname, &mname_len); } } /* if we encountered a CNAME (or a bunch of CNAMEs), and * still got to here, then it is a CNAME response. (i.e., * the CNAME chain didn't terminate in an answer rrset.) */ if(mname != request->qname) { return RESPONSE_TYPE_CNAME; } } /* At this point, since we don't need to detect REFERRAL or LAME * messages, it can only be an ANSWER. */ return RESPONSE_TYPE_ANSWER; } enum response_type response_type_from_server(int rdset, struct dns_msg* msg, struct query_info* request, struct delegpt* dp, int* empty_nodata_found) { uint8_t* origzone = (uint8_t*)"\000"; /* the default */ struct ub_packed_rrset_key* s; size_t i; if(!msg || !request) return RESPONSE_TYPE_THROWAWAY; /* If the TC flag is set, the response is incomplete. Too large to * fit even in TCP or so. Discard it, it cannot be retrieved here. */ if((msg->rep->flags & BIT_TC)) return RESPONSE_TYPE_THROWAWAY; /* If the message is NXDOMAIN, then it answers the question. */ if(FLAGS_GET_RCODE(msg->rep->flags) == LDNS_RCODE_NXDOMAIN) { /* make sure its not recursive when we don't want it to */ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset) return RESPONSE_TYPE_REC_LAME; /* it could be a CNAME with NXDOMAIN rcode */ for(i=0; irep->an_numrrsets; i++) { s = msg->rep->rrsets[i]; if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(request->qname, s->rk.dname) == 0) { return RESPONSE_TYPE_CNAME; } } return RESPONSE_TYPE_ANSWER; } /* Other response codes mean (so far) to throw the response away as * meaningless and move on to the next nameserver. */ if(FLAGS_GET_RCODE(msg->rep->flags) != LDNS_RCODE_NOERROR) return RESPONSE_TYPE_THROWAWAY; /* Note: TC bit has already been handled */ if(dp) { origzone = dp->name; } /* First we look at the answer section. This can tell us if this is a * CNAME or ANSWER or (provisional) ANSWER. */ if(msg->rep->an_numrrsets > 0) { uint8_t* mname = request->qname; size_t mname_len = request->qname_len; /* Now look at the answer section first. 3 states: our * answer is there directly, our answer is there after * a cname, or there is just a cname. */ for(i=0; irep->an_numrrsets; i++) { s = msg->rep->rrsets[i]; /* if the answer section has NS rrset, and qtype ANY * and the delegation is lower, and no CNAMEs followed, * this is a referral where the NS went to AN section */ if((request->qtype == LDNS_RR_TYPE_ANY || request->qtype == LDNS_RR_TYPE_NS) && ntohs(s->rk.type) == LDNS_RR_TYPE_NS && ntohs(s->rk.rrset_class) == request->qclass && dname_strict_subdomain_c(s->rk.dname, origzone)) { if((msg->rep->flags&BIT_AA)) return RESPONSE_TYPE_ANSWER; return RESPONSE_TYPE_REFERRAL; } /* If we have encountered an answer (before or * after a CNAME), then we are done! Note that * if qtype == CNAME then this will be noted as an * ANSWER before it gets treated as a CNAME, as * it should. */ if(ntohs(s->rk.type) == request->qtype && ntohs(s->rk.rrset_class) == request->qclass && query_dname_compare(mname, s->rk.dname) == 0) { if((msg->rep->flags&BIT_AA)) return RESPONSE_TYPE_ANSWER; /* If the AA bit isn't on, and we've seen * the answer, we only provisionally say * 'ANSWER' -- it very well could be a * REFERRAL. */ break; } /* If we have encountered a CNAME, make sure that * it is relevant. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_CNAME && query_dname_compare(mname, s->rk.dname) == 0) { get_cname_target(s, &mname, &mname_len); } } /* not a referral, and qtype any, thus an answer */ if(request->qtype == LDNS_RR_TYPE_ANY) return RESPONSE_TYPE_ANSWER; /* if we encountered a CNAME (or a bunch of CNAMEs), and * still got to here, then it is a CNAME response. * (This is regardless of the AA bit at this point) */ if(mname != request->qname) { return RESPONSE_TYPE_CNAME; } } /* Looking at the authority section, we just look and see if * there is a SOA record, that means a NOERROR/NODATA */ for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets + msg->rep->ns_numrrsets); i++) { s = msg->rep->rrsets[i]; /* The normal way of detecting NOERROR/NODATA. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_SOA && dname_subdomain_c(request->qname, s->rk.dname)) { /* we do our own recursion, thank you */ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset) return RESPONSE_TYPE_REC_LAME; return RESPONSE_TYPE_ANSWER; } } /* Looking at the authority section, we just look and see if * there is a delegation NS set, turning it into a delegation. * Otherwise, we will have to conclude ANSWER (either it is * NOERROR/NODATA, or an non-authoritative answer). */ for(i = msg->rep->an_numrrsets; i < (msg->rep->an_numrrsets + msg->rep->ns_numrrsets); i++) { s = msg->rep->rrsets[i]; /* Detect REFERRAL/LAME/ANSWER based on the relationship * of the NS set to the originating zone name. */ if(ntohs(s->rk.type) == LDNS_RR_TYPE_NS) { /* If we are getting an NS set for the zone we * thought we were contacting, then it is an answer.*/ if(query_dname_compare(s->rk.dname, origzone) == 0) { /* see if mistakenly a recursive server was * deployed and is responding nonAA */ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset) return RESPONSE_TYPE_REC_LAME; /* Or if a lame server is deployed, * which gives ns==zone delegation from cache * without AA bit as well, with nodata nosoa*/ /* real answer must be +AA and SOA RFC(2308), * so this is wrong, and we SERVFAIL it if * this is the only possible reply, if it * is misdeployed the THROWAWAY makes us pick * the next server from the selection */ if(msg->rep->an_numrrsets==0 && !(msg->rep->flags&BIT_AA) && !rdset) return RESPONSE_TYPE_THROWAWAY; return RESPONSE_TYPE_ANSWER; } /* If we are getting a referral upwards (or to * the same zone), then the server is 'lame'. */ if(dname_subdomain_c(origzone, s->rk.dname)) { if(rdset) /* forward or reclame not LAME */ return RESPONSE_TYPE_THROWAWAY; return RESPONSE_TYPE_LAME; } /* If the NS set is below the delegation point we * are on, and it is non-authoritative, then it is * a referral, otherwise it is an answer. */ if(dname_subdomain_c(s->rk.dname, origzone)) { /* NOTE: I no longer remember in what case * we would like this to be an answer. * NODATA should have a SOA or nothing, * not an NS rrset. * True, referrals should not have the AA * bit set, but... */ /* if((msg->rep->flags&BIT_AA)) return RESPONSE_TYPE_ANSWER; */ return RESPONSE_TYPE_REFERRAL; } /* Otherwise, the NS set is irrelevant. */ } } /* If we've gotten this far, this is NOERROR/NODATA (which could * be an entirely empty message) */ /* For entirely empty messages, try again, at first, then accept * it it happens more. A regular noerror/nodata response has a soa * negative ttl value in the authority section. This makes it try * again at another authority. And decides between storing a 5 second * empty message or a 5 second servfail response. */ if(msg->rep->an_numrrsets == 0 && msg->rep->ns_numrrsets == 0 && msg->rep->ar_numrrsets == 0) { if(empty_nodata_found) { /* detect as throwaway at first, but accept later. */ (*empty_nodata_found)++; if(*empty_nodata_found < EMPTY_NODATA_RETRY_COUNT) return RESPONSE_TYPE_THROWAWAY; return RESPONSE_TYPE_ANSWER; } return RESPONSE_TYPE_ANSWER; } /* check if recursive answer; saying it has empty cache */ if( (msg->rep->flags&BIT_RA) && !(msg->rep->flags&BIT_AA) && !rdset) return RESPONSE_TYPE_REC_LAME; return RESPONSE_TYPE_ANSWER; } unbound-1.25.1/iterator/iter_delegpt.c0000644000175000017500000004771415203270263017367 0ustar wouterwouter/* * iterator/iter_delegpt.c - delegation point with NS and address information. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file implements the Delegation Point. It contains a list of name servers * and their addresses if known. */ #include "config.h" #include "iterator/iter_delegpt.h" #include "services/cache/dns.h" #include "util/regional.h" #include "util/data/dname.h" #include "util/data/packed_rrset.h" #include "util/data/msgreply.h" #include "util/net_help.h" #include "sldns/rrdef.h" #include "sldns/sbuffer.h" struct delegpt* delegpt_create(struct regional* region) { struct delegpt* dp=(struct delegpt*)regional_alloc( region, sizeof(*dp)); if(!dp) return NULL; memset(dp, 0, sizeof(*dp)); return dp; } struct delegpt* delegpt_copy(struct delegpt* dp, struct regional* region) { struct delegpt* copy = delegpt_create(region); struct delegpt_ns* ns; struct delegpt_addr* a; if(!copy) return NULL; if(!delegpt_set_name(copy, region, dp->name)) return NULL; copy->bogus = dp->bogus; copy->has_parent_side_NS = dp->has_parent_side_NS; copy->ssl_upstream = dp->ssl_upstream; copy->tcp_upstream = dp->tcp_upstream; for(ns = dp->nslist; ns; ns = ns->next) { if(!delegpt_add_ns(copy, region, ns->name, ns->lame, ns->tls_auth_name, ns->port)) return NULL; copy->nslist->cache_lookup_count = ns->cache_lookup_count; copy->nslist->resolved = ns->resolved; copy->nslist->got4 = ns->got4; copy->nslist->got6 = ns->got6; copy->nslist->done_pside4 = ns->done_pside4; copy->nslist->done_pside6 = ns->done_pside6; } for(a = dp->target_list; a; a = a->next_target) { if(!delegpt_add_addr(copy, region, &a->addr, a->addrlen, a->bogus, a->lame, a->tls_auth_name, -1, NULL)) return NULL; } return copy; } int delegpt_set_name(struct delegpt* dp, struct regional* region, uint8_t* name) { log_assert(!dp->dp_type_mlc); dp->namelabs = dname_count_size_labels(name, &dp->namelen); dp->name = regional_alloc_init(region, name, dp->namelen); return dp->name != 0; } int delegpt_add_ns(struct delegpt* dp, struct regional* region, uint8_t* name, uint8_t lame, char* tls_auth_name, int port) { struct delegpt_ns* ns; size_t len; (void)dname_count_size_labels(name, &len); log_assert(!dp->dp_type_mlc); /* slow check for duplicates to avoid counting failures when * adding the same server as a dependency twice */ if(delegpt_find_ns(dp, name, len)) return 1; ns = (struct delegpt_ns*)regional_alloc(region, sizeof(struct delegpt_ns)); if(!ns) return 0; ns->next = dp->nslist; ns->namelen = len; dp->nslist = ns; ns->name = regional_alloc_init(region, name, ns->namelen); ns->cache_lookup_count = 0; ns->resolved = 0; ns->got4 = 0; ns->got6 = 0; ns->lame = lame; ns->done_pside4 = 0; ns->done_pside6 = 0; ns->port = port; if(tls_auth_name) { ns->tls_auth_name = regional_strdup(region, tls_auth_name); if(!ns->tls_auth_name) return 0; } else { ns->tls_auth_name = NULL; } return ns->name != 0; } struct delegpt_ns* delegpt_find_ns(struct delegpt* dp, uint8_t* name, size_t namelen) { struct delegpt_ns* p = dp->nslist; while(p) { if(namelen == p->namelen && query_dname_compare(name, p->name) == 0) { return p; } p = p->next; } return NULL; } struct delegpt_addr* delegpt_find_addr(struct delegpt* dp, struct sockaddr_storage* addr, socklen_t addrlen) { struct delegpt_addr* p = dp->target_list; while(p) { if(sockaddr_cmp_addr(addr, addrlen, &p->addr, p->addrlen)==0 && ((struct sockaddr_in*)addr)->sin_port == ((struct sockaddr_in*)&p->addr)->sin_port) { return p; } p = p->next_target; } return NULL; } int delegpt_add_target(struct delegpt* dp, struct regional* region, uint8_t* name, size_t namelen, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, int* additions) { struct delegpt_ns* ns = delegpt_find_ns(dp, name, namelen); log_assert(!dp->dp_type_mlc); if(!ns) { /* ignore it */ return 1; } if(!lame) { if(addr_is_ip6(addr, addrlen)) ns->got6 = 1; else ns->got4 = 1; if(ns->got4 && ns->got6) ns->resolved = 1; } else { if(addr_is_ip6(addr, addrlen)) ns->done_pside6 = 1; else ns->done_pside4 = 1; } log_assert(ns->port>0); return delegpt_add_addr(dp, region, addr, addrlen, bogus, lame, ns->tls_auth_name, ns->port, additions); } int delegpt_add_addr(struct delegpt* dp, struct regional* region, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, char* tls_auth_name, int port, int* additions) { struct delegpt_addr* a; log_assert(!dp->dp_type_mlc); if(port != -1) { log_assert(port>0); sockaddr_store_port(addr, addrlen, port); } /* check for duplicates */ if((a = delegpt_find_addr(dp, addr, addrlen))) { if(bogus) a->bogus = bogus; if(!lame) a->lame = 0; return 1; } if(additions) *additions = 1; a = (struct delegpt_addr*)regional_alloc(region, sizeof(struct delegpt_addr)); if(!a) return 0; a->next_target = dp->target_list; dp->target_list = a; a->next_result = 0; a->next_usable = dp->usable_list; dp->usable_list = a; memcpy(&a->addr, addr, addrlen); a->addrlen = addrlen; a->attempts = 0; a->bogus = bogus; a->lame = lame; a->dnsseclame = 0; if(tls_auth_name) { a->tls_auth_name = regional_strdup(region, tls_auth_name); if(!a->tls_auth_name) return 0; } else { a->tls_auth_name = NULL; } return 1; } void delegpt_count_ns(struct delegpt* dp, size_t* numns, size_t* missing) { struct delegpt_ns* ns; *numns = 0; *missing = 0; for(ns = dp->nslist; ns; ns = ns->next) { (*numns)++; if(!ns->resolved) (*missing)++; } } void delegpt_count_addr(struct delegpt* dp, size_t* numaddr, size_t* numres, size_t* numavail) { struct delegpt_addr* a; *numaddr = 0; *numres = 0; *numavail = 0; for(a = dp->target_list; a; a = a->next_target) { (*numaddr)++; } for(a = dp->result_list; a; a = a->next_result) { (*numres)++; } for(a = dp->usable_list; a; a = a->next_usable) { (*numavail)++; } } void delegpt_log(enum verbosity_value v, struct delegpt* dp) { char buf[LDNS_MAX_DOMAINLEN]; struct delegpt_ns* ns; struct delegpt_addr* a; size_t missing=0, numns=0, numaddr=0, numres=0, numavail=0; if(verbosity < v) return; dname_str(dp->name, buf); if(dp->nslist == NULL && dp->target_list == NULL) { log_info("DelegationPoint<%s>: empty", buf); return; } delegpt_count_ns(dp, &numns, &missing); delegpt_count_addr(dp, &numaddr, &numres, &numavail); log_info("DelegationPoint<%s>: %u names (%u missing), " "%u addrs (%u result, %u avail)%s", buf, (unsigned)numns, (unsigned)missing, (unsigned)numaddr, (unsigned)numres, (unsigned)numavail, (dp->has_parent_side_NS?" parentNS":" cacheNS")); if(verbosity >= VERB_ALGO) { for(ns = dp->nslist; ns; ns = ns->next) { dname_str(ns->name, buf); log_info(" %s %s%s%s%s%s%s%s", buf, (ns->resolved?"*":""), (ns->got4?" A":""), (ns->got6?" AAAA":""), (dp->bogus?" BOGUS":""), (ns->lame?" PARENTSIDE":""), (ns->done_pside4?" PSIDE_A":""), (ns->done_pside6?" PSIDE_AAAA":"")); } for(a = dp->target_list; a; a = a->next_target) { char s[128]; const char* str = " "; if(a->bogus && a->lame) str = " BOGUS ADDR_LAME "; else if(a->bogus) str = " BOGUS "; else if(a->lame) str = " ADDR_LAME "; if(a->tls_auth_name) snprintf(s, sizeof(s), "%s[%s]", str, a->tls_auth_name); else snprintf(s, sizeof(s), "%s", str); log_addr(VERB_ALGO, s, &a->addr, a->addrlen); } } } int delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find) { struct delegpt_addr* a = dp->result_list; while(a) { if(a == find) return 1; a = a->next_result; } return 0; } void delegpt_usable_list_remove_addr(struct delegpt* dp, struct delegpt_addr* del) { struct delegpt_addr* usa = dp->usable_list, *prev = NULL; while(usa) { if(usa == del) { /* snip off the usable list */ if(prev) prev->next_usable = usa->next_usable; else dp->usable_list = usa->next_usable; return; } prev = usa; usa = usa->next_usable; } } void delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a) { if(delegpt_addr_on_result_list(dp, a)) return; delegpt_usable_list_remove_addr(dp, a); a->next_result = dp->result_list; dp->result_list = a; } void delegpt_add_unused_targets(struct delegpt* dp) { struct delegpt_addr* usa = dp->usable_list; dp->usable_list = NULL; while(usa) { usa->next_result = dp->result_list; dp->result_list = usa; usa = usa->next_usable; } } size_t delegpt_count_targets(struct delegpt* dp) { struct delegpt_addr* a; size_t n = 0; for(a = dp->target_list; a; a = a->next_target) n++; return n; } size_t delegpt_count_missing_targets(struct delegpt* dp, int* alllame) { struct delegpt_ns* ns; size_t n = 0, nlame = 0; for(ns = dp->nslist; ns; ns = ns->next) { if(ns->resolved) continue; n++; if(ns->lame) nlame++; } if(alllame && n == nlame) *alllame = 1; return n; } /** find NS rrset in given list */ static struct ub_packed_rrset_key* find_NS(struct reply_info* rep, size_t from, size_t to) { size_t i; for(i=from; irrsets[i]->rk.type) == LDNS_RR_TYPE_NS) return rep->rrsets[i]; } return NULL; } struct delegpt* delegpt_from_message(struct dns_msg* msg, struct regional* region) { struct ub_packed_rrset_key* ns_rrset = NULL; struct delegpt* dp; size_t i; /* look for NS records in the authority section... */ ns_rrset = find_NS(msg->rep, msg->rep->an_numrrsets, msg->rep->an_numrrsets+msg->rep->ns_numrrsets); /* In some cases (even legitimate, perfectly legal cases), the * NS set for the "referral" might be in the answer section. */ if(!ns_rrset) ns_rrset = find_NS(msg->rep, 0, msg->rep->an_numrrsets); /* If there was no NS rrset in the authority section, then this * wasn't a referral message. (It might not actually be a * referral message anyway) */ if(!ns_rrset) return NULL; /* If we found any, then Yay! we have a delegation point. */ dp = delegpt_create(region); if(!dp) return NULL; dp->has_parent_side_NS = 1; /* created from message */ if(!delegpt_set_name(dp, region, ns_rrset->rk.dname)) return NULL; if(!delegpt_rrset_add_ns(dp, region, ns_rrset, 0)) return NULL; /* add glue, A and AAAA in answer and additional section */ for(i=0; irep->rrset_count; i++) { struct ub_packed_rrset_key* s = msg->rep->rrsets[i]; /* skip auth section. FIXME really needed?*/ if(msg->rep->an_numrrsets <= i && i < (msg->rep->an_numrrsets+msg->rep->ns_numrrsets)) continue; if(ntohs(s->rk.type) == LDNS_RR_TYPE_A) { if(!delegpt_add_rrset_A(dp, region, s, 0, NULL)) return NULL; } else if(ntohs(s->rk.type) == LDNS_RR_TYPE_AAAA) { if(!delegpt_add_rrset_AAAA(dp, region, s, 0, NULL)) return NULL; } } return dp; } int delegpt_rrset_add_ns(struct delegpt* dp, struct regional* region, struct ub_packed_rrset_key* ns_rrset, uint8_t lame) { struct packed_rrset_data* nsdata = (struct packed_rrset_data*) ns_rrset->entry.data; size_t i; log_assert(!dp->dp_type_mlc); if(nsdata->security == sec_status_bogus) dp->bogus = 1; for(i=0; icount; i++) { if(nsdata->rr_len[i] < 2+1) continue; /* len + root label */ if(dname_valid(nsdata->rr_data[i]+2, nsdata->rr_len[i]-2) != (size_t)sldns_read_uint16(nsdata->rr_data[i])) continue; /* bad format */ /* add rdata of NS (= wirefmt dname), skip rdatalen bytes */ if(!delegpt_add_ns(dp, region, nsdata->rr_data[i]+2, lame, NULL, UNBOUND_DNS_PORT)) return 0; } return 1; } int delegpt_add_rrset_A(struct delegpt* dp, struct regional* region, struct ub_packed_rrset_key* ak, uint8_t lame, int* additions) { struct packed_rrset_data* d=(struct packed_rrset_data*)ak->entry.data; size_t i; struct sockaddr_in sa; socklen_t len = (socklen_t)sizeof(sa); log_assert(!dp->dp_type_mlc); memset(&sa, 0, len); sa.sin_family = AF_INET; for(i=0; icount; i++) { if(d->rr_len[i] != 2 + INET_SIZE) continue; memmove(&sa.sin_addr, d->rr_data[i]+2, INET_SIZE); if(!delegpt_add_target(dp, region, ak->rk.dname, ak->rk.dname_len, (struct sockaddr_storage*)&sa, len, (d->security==sec_status_bogus), lame, additions)) return 0; } return 1; } int delegpt_add_rrset_AAAA(struct delegpt* dp, struct regional* region, struct ub_packed_rrset_key* ak, uint8_t lame, int* additions) { struct packed_rrset_data* d=(struct packed_rrset_data*)ak->entry.data; size_t i; struct sockaddr_in6 sa; socklen_t len = (socklen_t)sizeof(sa); log_assert(!dp->dp_type_mlc); memset(&sa, 0, len); sa.sin6_family = AF_INET6; for(i=0; icount; i++) { if(d->rr_len[i] != 2 + INET6_SIZE) /* rdatalen + len of IP6 */ continue; memmove(&sa.sin6_addr, d->rr_data[i]+2, INET6_SIZE); if(!delegpt_add_target(dp, region, ak->rk.dname, ak->rk.dname_len, (struct sockaddr_storage*)&sa, len, (d->security==sec_status_bogus), lame, additions)) return 0; } return 1; } int delegpt_add_rrset(struct delegpt* dp, struct regional* region, struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions) { if(!rrset) return 1; if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_NS) return delegpt_rrset_add_ns(dp, region, rrset, lame); else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_A) return delegpt_add_rrset_A(dp, region, rrset, lame, additions); else if(ntohs(rrset->rk.type) == LDNS_RR_TYPE_AAAA) return delegpt_add_rrset_AAAA(dp, region, rrset, lame, additions); log_warn("Unknown rrset type added to delegpt"); return 1; } void delegpt_mark_neg(struct delegpt_ns* ns, uint16_t qtype) { if(ns) { if(qtype == LDNS_RR_TYPE_A) ns->got4 = 2; else if(qtype == LDNS_RR_TYPE_AAAA) ns->got6 = 2; if(ns->got4 && ns->got6) ns->resolved = 1; } } void delegpt_add_neg_msg(struct delegpt* dp, struct msgreply_entry* msg) { struct reply_info* rep = (struct reply_info*)msg->entry.data; if(!rep) return; /* if error or no answers */ if(FLAGS_GET_RCODE(rep->flags) != 0 || rep->an_numrrsets == 0) { struct delegpt_ns* ns = delegpt_find_ns(dp, msg->key.qname, msg->key.qname_len); delegpt_mark_neg(ns, msg->key.qtype); } } void delegpt_no_ipv6(struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { /* no ipv6, so only ipv4 is enough to resolve a nameserver */ if(ns->got4) ns->resolved = 1; } } void delegpt_no_ipv4(struct delegpt* dp) { struct delegpt_ns* ns; for(ns = dp->nslist; ns; ns = ns->next) { /* no ipv4, so only ipv6 is enough to resolve a nameserver */ if(ns->got6) ns->resolved = 1; } } struct delegpt* delegpt_create_mlc(uint8_t* name) { struct delegpt* dp=(struct delegpt*)calloc(1, sizeof(*dp)); if(!dp) return NULL; dp->dp_type_mlc = 1; if(name) { dp->namelabs = dname_count_size_labels(name, &dp->namelen); dp->name = memdup(name, dp->namelen); if(!dp->name) { free(dp); return NULL; } } return dp; } void delegpt_free_mlc(struct delegpt* dp) { struct delegpt_ns* n, *nn; struct delegpt_addr* a, *na; if(!dp) return; log_assert(dp->dp_type_mlc); n = dp->nslist; while(n) { nn = n->next; free(n->name); free(n->tls_auth_name); free(n); n = nn; } a = dp->target_list; while(a) { na = a->next_target; free(a->tls_auth_name); free(a); a = na; } free(dp->name); free(dp); } int delegpt_set_name_mlc(struct delegpt* dp, uint8_t* name) { log_assert(dp->dp_type_mlc); dp->namelabs = dname_count_size_labels(name, &dp->namelen); dp->name = memdup(name, dp->namelen); return (dp->name != NULL); } int delegpt_add_ns_mlc(struct delegpt* dp, uint8_t* name, uint8_t lame, char* tls_auth_name, int port) { struct delegpt_ns* ns; size_t len; (void)dname_count_size_labels(name, &len); log_assert(dp->dp_type_mlc); /* slow check for duplicates to avoid counting failures when * adding the same server as a dependency twice */ if(delegpt_find_ns(dp, name, len)) return 1; ns = (struct delegpt_ns*)malloc(sizeof(struct delegpt_ns)); if(!ns) return 0; ns->namelen = len; ns->name = memdup(name, ns->namelen); if(!ns->name) { free(ns); return 0; } ns->next = dp->nslist; dp->nslist = ns; ns->cache_lookup_count = 0; ns->resolved = 0; ns->got4 = 0; ns->got6 = 0; ns->lame = (uint8_t)lame; ns->done_pside4 = 0; ns->done_pside6 = 0; ns->port = port; if(tls_auth_name) { ns->tls_auth_name = strdup(tls_auth_name); if(!ns->tls_auth_name) { free(ns->name); free(ns); return 0; } } else { ns->tls_auth_name = NULL; } return 1; } int delegpt_add_addr_mlc(struct delegpt* dp, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, char* tls_auth_name, int port) { struct delegpt_addr* a; log_assert(dp->dp_type_mlc); if(port != -1) { log_assert(port>0); sockaddr_store_port(addr, addrlen, port); } /* check for duplicates */ if((a = delegpt_find_addr(dp, addr, addrlen))) { if(bogus) a->bogus = bogus; if(!lame) a->lame = 0; return 1; } a = (struct delegpt_addr*)malloc(sizeof(struct delegpt_addr)); if(!a) return 0; a->next_target = dp->target_list; dp->target_list = a; a->next_result = 0; a->next_usable = dp->usable_list; dp->usable_list = a; memcpy(&a->addr, addr, addrlen); a->addrlen = addrlen; a->attempts = 0; a->bogus = bogus; a->lame = lame; a->dnsseclame = 0; if(tls_auth_name) { a->tls_auth_name = strdup(tls_auth_name); if(!a->tls_auth_name) { free(a); return 0; } } else { a->tls_auth_name = NULL; } return 1; } int delegpt_add_target_mlc(struct delegpt* dp, uint8_t* name, size_t namelen, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame) { struct delegpt_ns* ns = delegpt_find_ns(dp, name, namelen); log_assert(dp->dp_type_mlc); if(!ns) { /* ignore it */ return 1; } if(!lame) { if(addr_is_ip6(addr, addrlen)) ns->got6 = 1; else ns->got4 = 1; if(ns->got4 && ns->got6) ns->resolved = 1; } else { if(addr_is_ip6(addr, addrlen)) ns->done_pside6 = 1; else ns->done_pside4 = 1; } log_assert(ns->port>0); return delegpt_add_addr_mlc(dp, addr, addrlen, bogus, lame, ns->tls_auth_name, ns->port); } size_t delegpt_get_mem(struct delegpt* dp) { struct delegpt_ns* ns; size_t s; if(!dp) return 0; s = sizeof(*dp) + dp->namelen + delegpt_count_targets(dp)*sizeof(struct delegpt_addr); for(ns=dp->nslist; ns; ns=ns->next) s += sizeof(*ns)+ns->namelen; return s; } unbound-1.25.1/iterator/iter_fwd.h0000644000175000017500000002127015203270263016515 0ustar wouterwouter/* * iterator/iter_fwd.h - iterative resolver module forward zones. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of forward zones, and read those from config. */ #ifndef ITERATOR_ITER_FWD_H #define ITERATOR_ITER_FWD_H #include "util/rbtree.h" #include "util/locks.h" struct config_file; struct delegpt; /** * Iterator forward zones structure */ struct iter_forwards { /** lock on the forwards tree. * When grabbing both this lock and the anchors.lock, this lock * is grabbed first. When grabbing both this lock and the hints.lock * this lock is grabbed first. */ lock_rw_type lock; /** * Zones are stored in this tree. Sort order is specially chosen. * first sorted on qclass. Then on dname in nsec-like order, so that * a lookup on class, name will return an exact match or the closest * match which gives the ancestor needed. * contents of type iter_forward_zone. */ rbtree_type* tree; }; /** * Iterator forward servers for a particular zone. */ struct iter_forward_zone { /** redblacktree node, key is this structure: class and name */ rbnode_type node; /** name */ uint8_t* name; /** length of name */ size_t namelen; /** number of labels in name */ int namelabs; /** delegation point with forward server information for this zone. * If NULL then this forward entry is used to indicate that a * stub-zone with the same name exists, and should be used. * This delegation point is malloced. */ struct delegpt* dp; /** pointer to parent in tree (or NULL if none) */ struct iter_forward_zone* parent; /** class. host order. */ uint16_t dclass; }; /** * Create forwards * @return new forwards or NULL on error. */ struct iter_forwards* forwards_create(void); /** * Delete forwards. * @param fwd: to delete. */ void forwards_delete(struct iter_forwards* fwd); /** * Process forwards config. * @param fwd: where to store. * @param cfg: config options. * @return 0 on error. */ int forwards_apply_cfg(struct iter_forwards* fwd, struct config_file* cfg); /** * Find forward zone exactly by name * The return value is contents of the forwards structure. * Caller should lock and unlock a readlock on the forwards structure if nolock * is set. * Otherwise caller should unlock the readlock on the forwards structure if a * value was returned. * @param fwd: forward storage. * @param qname: The qname of the query. * @param qclass: The qclass of the query. * @param nolock: Skip locking, locking is handled by the caller. * @return: A delegation point or null. */ struct delegpt* forwards_find(struct iter_forwards* fwd, uint8_t* qname, uint16_t qclass, int nolock); /** * Find forward zone information * For this qname/qclass find forward zone information, returns delegation * point with server names and addresses, or NULL if no forwarding is needed. * The return value is contents of the forwards structure. * Caller should lock and unlock a readlock on the forwards structure if nolock * is set. * Otherwise caller should unlock the readlock on the forwards structure if a * value was returned. * * @param fwd: forward storage. * @param qname: The qname of the query. * @param qclass: The qclass of the query. * @param nolock: Skip locking, locking is handled by the caller. * @return: A delegation point if the query has to be forwarded to that list, * otherwise null. */ struct delegpt* forwards_lookup(struct iter_forwards* fwd, uint8_t* qname, uint16_t qclass, int nolock); /** * Same as forwards_lookup, but for the root only * @param fwd: forward storage. * @param qclass: The qclass of the query. * @param nolock: Skip locking, locking is handled by the caller. * @return: A delegation point if root forward exists, otherwise null. */ struct delegpt* forwards_lookup_root(struct iter_forwards* fwd, uint16_t qclass, int nolock); /** * Find next root item in forwards lookup tree. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a readlock on the forwards structure. * @param fwd: the forward storage * @param qclass: class to look at next, or higher. * @param nolock: Skip locking, locking is handled by the caller. * @return false if none found, or if true stored in qclass. */ int forwards_next_root(struct iter_forwards* fwd, uint16_t* qclass, int nolock); /** * Get memory in use by forward storage * Locks and unlocks the structure. * @param fwd: forward storage. * @return bytes in use */ size_t forwards_get_mem(struct iter_forwards* fwd); /** compare two fwd entries */ int fwd_cmp(const void* k1, const void* k2); /** * Add zone to forward structure. For external use since it recalcs * the tree parents. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the forwards structure. * @param fwd: the forward data structure * @param c: class of zone * @param dp: delegation point with name and target nameservers for new * forward zone. malloced. * @param nolock: Skip locking, locking is handled by the caller. * @return false on failure (out of memory); */ int forwards_add_zone(struct iter_forwards* fwd, uint16_t c, struct delegpt* dp, int nolock); /** * Remove zone from forward structure. For external use since it * recalcs the tree parents. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the forwards structure. * @param fwd: the forward data structure * @param c: class of zone * @param nm: name of zone (in uncompressed wireformat). * @param nolock: Skip locking, locking is handled by the caller. */ void forwards_delete_zone(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock); /** * Add stub hole (empty entry in forward table, that makes resolution skip * a forward-zone because the stub zone should override the forward zone). * Does not add one if not necessary. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the forwards structure. * @param fwd: the forward data structure * @param c: class of zone * @param nm: name of zone (in uncompressed wireformat). * @param nolock: Skip locking, locking is handled by the caller. * @return false on failure (out of memory); */ int forwards_add_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock); /** * Remove stub hole, if one exists. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the forwards structure. * @param fwd: the forward data structure * @param c: class of zone * @param nm: name of zone (in uncompressed wireformat). * @param nolock: Skip locking, locking is handled by the caller. */ void forwards_delete_stub_hole(struct iter_forwards* fwd, uint16_t c, uint8_t* nm, int nolock); /** * Swap internal tree with preallocated entries. Caller should manage * the locks. * @param fwd: the forward data structure. * @param data: the data structure used to take elements from. This contains * the old elements on return. */ void forwards_swap_tree(struct iter_forwards* fwd, struct iter_forwards* data); #endif /* ITERATOR_ITER_FWD_H */ unbound-1.25.1/iterator/iter_hints.h0000644000175000017500000001720615203270263017066 0ustar wouterwouter/* * iterator/iter_hints.h - iterative resolver module stub and root hints. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of stub and root hints, and read those from config. */ #ifndef ITERATOR_ITER_HINTS_H #define ITERATOR_ITER_HINTS_H #include "util/storage/dnstree.h" #include "util/locks.h" struct iter_env; struct config_file; struct delegpt; /** * Iterator hints structure */ struct iter_hints { /** lock on the forwards tree. * When grabbing both this lock and the anchors.lock, this lock * is grabbed first. */ lock_rw_type lock; /** * Hints are stored in this tree. Sort order is specially chosen. * first sorted on qclass. Then on dname in nsec-like order, so that * a lookup on class, name will return an exact match or the closest * match which gives the ancestor needed. * contents of type iter_hints_stub. The class IN root is in here. * uses name_tree_node from dnstree.h. */ rbtree_type tree; }; /** * Iterator hints for a particular stub. */ struct iter_hints_stub { /** tree sorted by name, class */ struct name_tree_node node; /** delegation point with hint information for this stub. malloced. */ struct delegpt* dp; /** does the stub need to forego priming (like on other ports) */ uint8_t noprime; }; /** * Create hints * @return new hints or NULL on error. */ struct iter_hints* hints_create(void); /** * Delete hints. * @param hints: to delete. */ void hints_delete(struct iter_hints* hints); /** * Process hints config. Sets default values for root hints if no config. * @param hints: where to store. * @param cfg: config options. * @return 0 on error. */ int hints_apply_cfg(struct iter_hints* hints, struct config_file* cfg); /** * Find hints for the given class. * The return value is contents of the hints structure. * Caller should lock and unlock a readlock on the hints structure if nolock * is set. * Otherwise caller should unlock the readlock on the hints structure if a * value was returned. * @param hints: hint storage. * @param qname: the qname that generated the delegation point. * @param qclass: class for which root hints are requested. host order. * @param nolock: Skip locking, locking is handled by the caller. * @return: NULL if no hints, or a ptr to stored hints. */ struct delegpt* hints_find(struct iter_hints* hints, uint8_t* qname, uint16_t qclass, int nolock); /** * Same as hints_lookup, but for the root only. * @param hints: hint storage. * @param qclass: class for which root hints are requested. host order. * @param nolock: Skip locking, locking is handled by the caller. * @return: NULL if no hints, or a ptr to stored hints. */ struct delegpt* hints_find_root(struct iter_hints* hints, uint16_t qclass, int nolock); /** * Find next root hints (to cycle through all root hints). * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a readlock on the hints structure. * @param hints: hint storage * @param qclass: class for which root hints are sought. * 0 means give the first available root hints class. * x means, give class x or a higher class if any. * returns the found class in this variable. * @param nolock: Skip locking, locking is handled by the caller. * @return true if a root hint class is found. * false if not root hint class is found (qclass may have been changed). */ int hints_next_root(struct iter_hints* hints, uint16_t* qclass, int nolock); /** * Given a qname/qclass combination, and the delegation point from the cache * for this qname/qclass, determine if this combination indicates that a * stub hint exists and must be primed. * The return value is contents of the hints structure. * Caller should lock and unlock a readlock on the hints structure if nolock * is set. * Otherwise caller should unlock the readlock on the hints structure if a * value was returned. * * @param hints: hint storage. * @param qname: The qname that generated the delegation point. * @param qclass: The qclass that generated the delegation point. * @param dp: The cache generated delegation point. * @param nolock: Skip locking, locking is handled by the caller. * @return: A priming delegation point if there is a stub hint that must * be primed, otherwise null. */ struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints, uint8_t* qname, uint16_t qclass, struct delegpt* dp, int nolock); /** * Get memory in use by hints * Locks and unlocks the structure. * @param hints: hint storage. * @return bytes in use */ size_t hints_get_mem(struct iter_hints* hints); /** * Add stub to hints structure. For external use since it recalcs * the tree parents. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the hints structure. * @param hints: the hints data structure * @param c: class of zone * @param dp: delegation point with name and target nameservers for new * hints stub. malloced. * @param noprime: set noprime option to true or false on new hint stub. * @param nolock: Skip locking, locking is handled by the caller. * @return false on failure (out of memory); */ int hints_add_stub(struct iter_hints* hints, uint16_t c, struct delegpt* dp, int noprime, int nolock); /** * Remove stub from hints structure. For external use since it * recalcs the tree parents. * Handles its own locking unless nolock is set. In that case the caller * should lock and unlock a writelock on the hints structure. * @param hints: the hints data structure * @param c: class of stub zone * @param nm: name of stub zone (in uncompressed wireformat). * @param nolock: Skip locking, locking is handled by the caller. */ void hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm, int nolock); /** * Swap internal tree with preallocated entries. Caller should manage * the locks. * @param hints: the hints data structure. * @param data: the data structure used to take elements from. This contains * the old elements on return. */ void hints_swap_tree(struct iter_hints* hints, struct iter_hints* data); #endif /* ITERATOR_ITER_HINTS_H */ unbound-1.25.1/iterator/iter_scrub.c0000644000175000017500000011427415203270263017055 0ustar wouterwouter/* * iterator/iter_scrub.c - scrubbing, normalization, sanitization of DNS msgs. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file has routine(s) for cleaning up incoming DNS messages from * possible useless or malicious junk in it. */ #include "config.h" #include "iterator/iter_scrub.h" #include "iterator/iterator.h" #include "iterator/iter_priv.h" #include "services/cache/rrset.h" #include "util/log.h" #include "util/net_help.h" #include "util/regional.h" #include "util/config_file.h" #include "util/module.h" #include "util/data/msgparse.h" #include "util/data/dname.h" #include "util/data/msgreply.h" #include "util/alloc.h" #include "sldns/sbuffer.h" /** RRset flag used during scrubbing. The RRset is OK. */ #define RRSET_SCRUB_OK 0x80 /** remove rrset, update loop variables */ static void remove_rrset(const char* str, sldns_buffer* pkt, struct msg_parse* msg, struct rrset_parse* prev, struct rrset_parse** rrset) { if(verbosity >= VERB_QUERY && str && (*rrset)->dname_len <= LDNS_MAX_DOMAINLEN) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, (*rrset)->dname); log_nametypeclass(VERB_QUERY, str, buf, (*rrset)->type, ntohs((*rrset)->rrset_class)); } if(prev) prev->rrset_all_next = (*rrset)->rrset_all_next; else msg->rrset_first = (*rrset)->rrset_all_next; if(msg->rrset_last == *rrset) msg->rrset_last = prev; msg->rrset_count --; switch((*rrset)->section) { case LDNS_SECTION_ANSWER: msg->an_rrsets--; break; case LDNS_SECTION_AUTHORITY: msg->ns_rrsets--; break; case LDNS_SECTION_ADDITIONAL: msg->ar_rrsets--; break; default: log_assert(0); } msgparse_bucket_remove(msg, *rrset); *rrset = (*rrset)->rrset_all_next; } /** return true if rr type has additional names in it */ static int has_additional(uint16_t t) { switch(t) { case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_NS: case LDNS_RR_TYPE_MX: case LDNS_RR_TYPE_KX: case LDNS_RR_TYPE_SRV: return 1; case LDNS_RR_TYPE_NAPTR: /* TODO: NAPTR not supported, glue stripped off */ return 0; } return 0; } /** get additional name from rrset RR, return false if no name present */ static int get_additional_name(struct rrset_parse* rrset, struct rr_parse* rr, uint8_t** nm, size_t* nmlen, sldns_buffer* pkt) { size_t offset = 0; size_t len, oldpos; switch(rrset->type) { case LDNS_RR_TYPE_MB: case LDNS_RR_TYPE_MD: case LDNS_RR_TYPE_MF: case LDNS_RR_TYPE_NS: offset = 0; break; case LDNS_RR_TYPE_MX: case LDNS_RR_TYPE_KX: offset = 2; break; case LDNS_RR_TYPE_SRV: offset = 6; break; case LDNS_RR_TYPE_NAPTR: /* TODO: NAPTR not supported, glue stripped off */ return 0; default: return 0; } len = sldns_read_uint16(rr->ttl_data+sizeof(uint32_t)); if(len < offset+1) return 0; /* rdata field too small */ *nm = rr->ttl_data+sizeof(uint32_t)+sizeof(uint16_t)+offset; oldpos = sldns_buffer_position(pkt); sldns_buffer_set_position(pkt, (size_t)(*nm - sldns_buffer_begin(pkt))); *nmlen = pkt_dname_len(pkt); sldns_buffer_set_position(pkt, oldpos); if(*nmlen == 0) return 0; return 1; } /** Place mark on rrsets in additional section they are OK */ static void mark_additional_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct rrset_parse* rrset) { /* Mark A and AAAA for NS as appropriate additional section info. */ uint8_t* nm = NULL; size_t nmlen = 0; struct rr_parse* rr; if(!has_additional(rrset->type)) return; for(rr = rrset->rr_first; rr; rr = rr->next) { if(get_additional_name(rrset, rr, &nm, &nmlen, pkt)) { /* mark A */ hashvalue_type h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_A, rrset->rrset_class, 0); struct rrset_parse* r = msgparse_hashtable_lookup( msg, pkt, h, 0, nm, nmlen, LDNS_RR_TYPE_A, rrset->rrset_class); if(r && r->section == LDNS_SECTION_ADDITIONAL) { r->flags |= RRSET_SCRUB_OK; } /* mark AAAA */ h = pkt_hash_rrset(pkt, nm, LDNS_RR_TYPE_AAAA, rrset->rrset_class, 0); r = msgparse_hashtable_lookup(msg, pkt, h, 0, nm, nmlen, LDNS_RR_TYPE_AAAA, rrset->rrset_class); if(r && r->section == LDNS_SECTION_ADDITIONAL) { r->flags |= RRSET_SCRUB_OK; } } } } /** Get target name of a CNAME */ static int parse_get_cname_target(struct rrset_parse* rrset, uint8_t** sname, size_t* snamelen, sldns_buffer* pkt) { size_t oldpos, dlen; if(rrset->rr_count != 1) { struct rr_parse* sig; verbose(VERB_ALGO, "Found CNAME rrset with " "size > 1: %u", (unsigned)rrset->rr_count); /* use the first CNAME! */ rrset->rr_count = 1; rrset->size = rrset->rr_first->size; for(sig=rrset->rrsig_first; sig; sig=sig->next) rrset->size += sig->size; rrset->rr_last = rrset->rr_first; rrset->rr_first->next = NULL; } if(rrset->rr_first->size < sizeof(uint16_t)+1) return 0; /* CNAME rdata too small */ *sname = rrset->rr_first->ttl_data + sizeof(uint32_t) + sizeof(uint16_t); /* skip ttl, rdatalen */ *snamelen = rrset->rr_first->size - sizeof(uint16_t); if(rrset->rr_first->outside_packet) { if(!dname_valid(*sname, *snamelen)) return 0; return 1; } oldpos = sldns_buffer_position(pkt); sldns_buffer_set_position(pkt, (size_t)(*sname - sldns_buffer_begin(pkt))); dlen = pkt_dname_len(pkt); sldns_buffer_set_position(pkt, oldpos); if(dlen == 0) return 0; /* parse fail on the rdata name */ *snamelen = dlen; return 1; } /** Synthesize CNAME from DNAME, false if too long */ static int synth_cname(uint8_t* qname, size_t qnamelen, struct rrset_parse* dname_rrset, uint8_t* alias, size_t* aliaslen, sldns_buffer* pkt) { /* we already know that sname is a strict subdomain of DNAME owner */ uint8_t* dtarg = NULL; size_t dtarglen; if(!parse_get_cname_target(dname_rrset, &dtarg, &dtarglen, pkt)) return 0; if(qnamelen <= dname_rrset->dname_len) return 0; if(qnamelen == 0) return 0; log_assert(qnamelen > dname_rrset->dname_len); /* DNAME from com. to net. with qname example.com. -> example.net. */ /* so: \3com\0 to \3net\0 and qname \7example\3com\0 */ *aliaslen = qnamelen + dtarglen - dname_rrset->dname_len; if(*aliaslen > LDNS_MAX_DOMAINLEN) return 0; /* should have been RCODE YXDOMAIN */ /* decompress dnames into buffer, we know it fits */ dname_pkt_copy(pkt, alias, qname); dname_pkt_copy(pkt, alias+(qnamelen-dname_rrset->dname_len), dtarg); return 1; } /** synthesize a CNAME rrset */ static struct rrset_parse* synth_cname_rrset(uint8_t** sname, size_t* snamelen, uint8_t* alias, size_t aliaslen, struct regional* region, struct msg_parse* msg, struct rrset_parse* rrset, struct rrset_parse* prev, struct rrset_parse* nx, sldns_buffer* pkt) { struct rrset_parse* cn = (struct rrset_parse*)regional_alloc(region, sizeof(struct rrset_parse)); if(!cn) return NULL; memset(cn, 0, sizeof(*cn)); cn->rr_first = (struct rr_parse*)regional_alloc(region, sizeof(struct rr_parse)); if(!cn->rr_first) return NULL; cn->rr_last = cn->rr_first; /* CNAME from sname to alias */ cn->dname = (uint8_t*)regional_alloc(region, *snamelen); if(!cn->dname) return NULL; dname_pkt_copy(pkt, cn->dname, *sname); cn->dname_len = *snamelen; cn->type = LDNS_RR_TYPE_CNAME; cn->section = rrset->section; cn->rrset_class = rrset->rrset_class; cn->rr_count = 1; cn->size = sizeof(uint16_t) + aliaslen; cn->hash=pkt_hash_rrset(pkt, cn->dname, cn->type, cn->rrset_class, 0); /* allocate TTL + rdatalen + uncompressed dname */ memset(cn->rr_first, 0, sizeof(struct rr_parse)); cn->rr_first->outside_packet = 1; cn->rr_first->ttl_data = (uint8_t*)regional_alloc(region, sizeof(uint32_t)+sizeof(uint16_t)+aliaslen); if(!cn->rr_first->ttl_data) return NULL; memmove(cn->rr_first->ttl_data, rrset->rr_first->ttl_data, sizeof(uint32_t)); /* RFC6672: synth CNAME TTL == DNAME TTL */ /* Apply cache TTL policy so DNAME and synthesized CNAME stay equal * and respect cache-min-ttl/cache-max-ttl (same as rdata_copy path). */ if(!SERVE_ORIGINAL_TTL) { uint32_t ttl = sldns_read_uint32(cn->rr_first->ttl_data); time_t ttl_t = (time_t)ttl; if(ttl_t < MIN_TTL) ttl_t = MIN_TTL; if(ttl_t > MAX_TTL) ttl_t = MAX_TTL; ttl = (uint32_t)ttl_t; sldns_write_uint32(cn->rr_first->ttl_data, ttl); sldns_write_uint32(rrset->rr_first->ttl_data, ttl); } sldns_write_uint16(cn->rr_first->ttl_data+4, aliaslen); memmove(cn->rr_first->ttl_data+6, alias, aliaslen); cn->rr_first->size = sizeof(uint16_t)+aliaslen; /* link it in */ cn->rrset_all_next = nx; if(prev) prev->rrset_all_next = cn; else msg->rrset_first = cn; if(nx == NULL) msg->rrset_last = cn; msg->rrset_count ++; msg->an_rrsets++; /* it is not inserted in the msg hashtable. */ *sname = cn->rr_first->ttl_data + sizeof(uint32_t)+sizeof(uint16_t); *snamelen = aliaslen; return cn; } /** check if DNAME applies to a name */ static int pkt_strict_sub(sldns_buffer* pkt, uint8_t* sname, uint8_t* dr) { uint8_t buf1[LDNS_MAX_DOMAINLEN+1]; uint8_t buf2[LDNS_MAX_DOMAINLEN+1]; /* decompress names */ dname_pkt_copy(pkt, buf1, sname); dname_pkt_copy(pkt, buf2, dr); return dname_strict_subdomain_c(buf1, buf2); } /** check subdomain with decompression */ static int pkt_sub(sldns_buffer* pkt, uint8_t* comprname, uint8_t* zone) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, comprname); return dname_subdomain_c(buf, zone); } /** check subdomain with decompression, compressed is parent */ static int sub_of_pkt(sldns_buffer* pkt, uint8_t* zone, uint8_t* comprname) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, comprname); return dname_subdomain_c(zone, buf); } /** Check if there are SOA records in the authority section (negative) */ static int soa_in_auth(struct msg_parse* msg) { struct rrset_parse* rrset; for(rrset = msg->rrset_first; rrset; rrset = rrset->rrset_all_next) if(rrset->type == LDNS_RR_TYPE_SOA && rrset->section == LDNS_SECTION_AUTHORITY) return 1; return 0; } /** Check if type is allowed in the authority section */ static int type_allowed_in_authority_section(uint16_t tp) { if(tp == LDNS_RR_TYPE_SOA || tp == LDNS_RR_TYPE_NS || tp == LDNS_RR_TYPE_DS || tp == LDNS_RR_TYPE_NSEC || tp == LDNS_RR_TYPE_NSEC3) return 1; return 0; } /** Check if type is allowed in the additional section */ static int type_allowed_in_additional_section(uint16_t tp) { if(tp == LDNS_RR_TYPE_A || tp == LDNS_RR_TYPE_AAAA) return 1; return 0; } /** Shorten RRset */ static void shorten_rrset(sldns_buffer* pkt, struct rrset_parse* rrset, int count) { /* The too large NS RRset is shortened. This is so that too large * content does not overwhelm the cache. It may make the rrset * bogus if it was signed, and then the domain is not resolved any * more, that is okay, the NS RRset was too large. During a referral * it can be shortened and then the first part of the list could * be used to resolve. The scrub continues to disallow glue for the * removed nameserver RRs and removes that too. Because the glue * is not marked as okay, since the RRs have been removed here. */ int i; struct rr_parse* rr = rrset->rr_first, *prev = NULL; if(!rr) return; for(i=0; inext; if(!rr) return; /* The RRset is already short. */ } if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, rrset->dname); log_nametypeclass(VERB_QUERY, "normalize: shorten RRset:", buf, rrset->type, ntohs(rrset->rrset_class)); } /* remove further rrs */ rrset->rr_last = prev; rrset->rr_count = count; while(rr) { rrset->size -= rr->size; rr = rr->next; } if(rrset->rr_last) rrset->rr_last->next = NULL; else rrset->rr_first = NULL; } /** Shorten RRSIGs list */ static void shorten_rrsig(sldns_buffer* pkt, struct rrset_parse* rrset, int count) { /* The too large list of RRSIGs on the RRset is shortened. * This is so that too large content does not overwhelm the cache. * The validator does not validate more than a max number of * RRSIGs as well. */ int i; struct rr_parse* rr = rrset->rrsig_first, *prev = NULL; if(!rr) return; for(i=0; inext; if(!rr) return; /* The RRSIG list is already short. */ } if(verbosity >= VERB_QUERY && rrset->dname_len <= LDNS_MAX_DOMAINLEN) { uint8_t buf[LDNS_MAX_DOMAINLEN+1]; dname_pkt_copy(pkt, buf, rrset->dname); log_nametypeclass(VERB_QUERY, "normalize: shorten RRSIGs:", buf, rrset->type, ntohs(rrset->rrset_class)); } /* remove further rrsigs */ rrset->rrsig_last = prev; rrset->rrsig_count = count; while(rr) { rrset->size -= rr->size; rr = rr->next; } if(rrset->rrsig_last) rrset->rrsig_last->next = NULL; else rrset->rrsig_first = NULL; } /** * This routine normalizes a response. This includes removing "irrelevant" * records from the answer and additional sections and (re)synthesizing * CNAMEs from DNAMEs, if present. * * @param pkt: packet. * @param msg: msg to normalize. * @param qinfo: original query. * @param region: where to allocate synthesized CNAMEs. * @param env: module env with config options. * @param zonename: name of server zone. * @return 0 on error. */ static int scrub_normalize(sldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, struct regional* region, struct module_env* env, uint8_t* zonename) { uint8_t* sname = qinfo->qname; size_t snamelen = qinfo->qname_len; struct rrset_parse* rrset, *prev, *nsset=NULL; int cname_length = 0; /* number of CNAMEs, or DNAMEs */ if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NXDOMAIN && FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_YXDOMAIN) return 1; /* For the ANSWER section, remove all "irrelevant" records and add * synthesized CNAMEs from DNAMEs * This will strip out-of-order CNAMEs as well. */ /* walk through the parse packet rrset list, keep track of previous * for insert and delete ease, and examine every RRset */ prev = NULL; rrset = msg->rrset_first; while(rrset && rrset->section == LDNS_SECTION_ANSWER) { if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig) shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig); if(cname_length > env->cfg->iter_scrub_cname) { /* Too many CNAMEs, or DNAMEs, from the authority * server, scrub down the length to something * shorter. This deletes everything after the limit * is reached. The iterator is going to look up * the content one by one anyway. */ remove_rrset("normalize: removing because too many cnames:", pkt, msg, prev, &rrset); continue; } if(rrset->type == LDNS_RR_TYPE_DNAME && pkt_strict_sub(pkt, sname, rrset->dname) && pkt_sub(pkt, rrset->dname, zonename)) { /* check if next rrset is correct CNAME. else, * synthesize a CNAME */ struct rrset_parse* nx = rrset->rrset_all_next; uint8_t alias[LDNS_MAX_DOMAINLEN+1]; size_t aliaslen = 0; if(rrset->rr_count != 1) { verbose(VERB_ALGO, "Found DNAME rrset with " "size > 1: %u", (unsigned)rrset->rr_count); return 0; } if(!synth_cname(sname, snamelen, rrset, alias, &aliaslen, pkt)) { verbose(VERB_ALGO, "synthesized CNAME " "too long"); if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) { prev = rrset; rrset = rrset->rrset_all_next; continue; } return 0; } cname_length++; if(nx && nx->type == LDNS_RR_TYPE_CNAME && dname_pkt_compare(pkt, sname, nx->dname) == 0) { /* check next cname */ uint8_t* t = NULL; size_t tlen = 0; if(!parse_get_cname_target(nx, &t, &tlen, pkt)) return 0; if(dname_pkt_compare(pkt, alias, t) == 0) { /* it's OK and better capitalized */ prev = rrset; rrset = nx; continue; } /* synth ourselves */ } /* synth a CNAME rrset */ prev = synth_cname_rrset(&sname, &snamelen, alias, aliaslen, region, msg, rrset, rrset, nx, pkt); if(!prev) { log_err("out of memory synthesizing CNAME"); return 0; } rrset = nx; continue; } /* The only records in the ANSWER section not allowed to */ if(dname_pkt_compare(pkt, sname, rrset->dname) != 0) { remove_rrset("normalize: removing irrelevant RRset:", pkt, msg, prev, &rrset); continue; } /* Follow the CNAME chain. */ if(rrset->type == LDNS_RR_TYPE_CNAME) { struct rrset_parse* nx = rrset->rrset_all_next; uint8_t* oldsname = sname; cname_length++; /* see if the next one is a DNAME, if so, swap them */ if(nx && nx->section == LDNS_SECTION_ANSWER && nx->type == LDNS_RR_TYPE_DNAME && nx->rr_count == 1 && pkt_strict_sub(pkt, sname, nx->dname) && pkt_sub(pkt, nx->dname, zonename)) { /* there is a DNAME after this CNAME, it * is in the ANSWER section, and the DNAME * applies to the name we cover */ /* check if the alias of the DNAME equals * this CNAME */ uint8_t alias[LDNS_MAX_DOMAINLEN+1]; size_t aliaslen = 0; uint8_t* t = NULL; size_t tlen = 0; if(synth_cname(sname, snamelen, nx, alias, &aliaslen, pkt) && parse_get_cname_target(rrset, &t, &tlen, pkt) && dname_pkt_compare(pkt, alias, t) == 0) { /* the synthesized CNAME equals the * current CNAME. This CNAME is the * one that the DNAME creates, and this * CNAME is better capitalised */ verbose(VERB_ALGO, "normalize: re-order of DNAME and its CNAME"); if(prev) prev->rrset_all_next = nx; else msg->rrset_first = nx; if(nx->rrset_all_next == NULL) msg->rrset_last = rrset; rrset->rrset_all_next = nx->rrset_all_next; nx->rrset_all_next = rrset; /* prev = nx; unused, enable if there * is other rrset removal code after * this */ } } /* move to next name in CNAME chain */ if(!parse_get_cname_target(rrset, &sname, &snamelen, pkt)) return 0; prev = rrset; rrset = rrset->rrset_all_next; /* in CNAME ANY response, can have data after CNAME */ if(qinfo->qtype == LDNS_RR_TYPE_ANY) { while(rrset && rrset->section == LDNS_SECTION_ANSWER && dname_pkt_compare(pkt, oldsname, rrset->dname) == 0) { if(rrset->type == LDNS_RR_TYPE_NS && rrset->rr_count > env->cfg->iter_scrub_ns) { shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } prev = rrset; rrset = rrset->rrset_all_next; } } continue; } /* Otherwise, make sure that the RRset matches the qtype. */ if(qinfo->qtype != LDNS_RR_TYPE_ANY && qinfo->qtype != rrset->type) { remove_rrset("normalize: removing irrelevant RRset:", pkt, msg, prev, &rrset); continue; } if(rrset->type == LDNS_RR_TYPE_NS && rrset->rr_count > env->cfg->iter_scrub_ns) { shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } /* Mark the additional names from relevant rrset as OK. */ /* only for RRsets that match the query name, other ones * will be removed by sanitize, so no additional for them */ if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) mark_additional_rrset(pkt, msg, rrset); prev = rrset; rrset = rrset->rrset_all_next; } /* Mark additional names from AUTHORITY */ while(rrset && rrset->section == LDNS_SECTION_AUTHORITY) { /* protect internals of recursor by making sure to del these */ if(rrset->type==LDNS_RR_TYPE_DNAME || rrset->type==LDNS_RR_TYPE_CNAME || rrset->type==LDNS_RR_TYPE_A || rrset->type==LDNS_RR_TYPE_AAAA) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } /* Allowed list of types in the authority section */ if(env->cfg->harden_unknown_additional && !type_allowed_in_authority_section(rrset->type)) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig) shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig); /* only one NS set allowed in authority section */ if(rrset->type==LDNS_RR_TYPE_NS) { /* NS set must be pertinent to the query */ if(!sub_of_pkt(pkt, qinfo->qname, rrset->dname)) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } /* we don't want NS sets for NXDOMAIN answers, * because they could contain poisonous contents, * from. eg. fragmentation attacks, inserted after * long RRSIGs in the packet get to the packet * border and such */ /* also for NODATA answers */ if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN || (FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && soa_in_auth(msg) && msg->an_rrsets == 0)) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } /* If the NS set is a promiscuous NS set, scrub that * to remove potential for poisonous contents that * affects other names in the same zone. Remove * promiscuous NS sets in positive answers, that * thus have records in the answer section. Nodata * and nxdomain promiscuous NS sets have been removed * already. Since the NS rrset is scrubbed, its * address records are also not marked to be allowed * and are removed later. */ if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && msg->an_rrsets != 0 && env->cfg->iter_scrub_promiscuous) { remove_rrset("normalize: removing promiscuous " "RRset:", pkt, msg, prev, &rrset); continue; } /* Also delete promiscuous NS for other RCODEs */ if(FLAGS_GET_RCODE(msg->flags) != LDNS_RCODE_NOERROR && env->cfg->iter_scrub_promiscuous) { remove_rrset("normalize: removing promiscuous " "RRset:", pkt, msg, prev, &rrset); continue; } /* Also delete promiscuous NS for NOERROR with nodata * for authoritative answers, not for delegations. * NOERROR with an_rrsets!=0 already handled. * Also NOERROR and soa_in_auth already handled. * NOERROR with an_rrsets==0, and not a referral. * referral is (NS not the zonename, noSOA). */ if(FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && msg->an_rrsets == 0 && !(dname_pkt_compare(pkt, rrset->dname, zonename) != 0 && !soa_in_auth(msg)) && env->cfg->iter_scrub_promiscuous) { remove_rrset("normalize: removing promiscuous " "RRset:", pkt, msg, prev, &rrset); continue; } if(nsset == NULL) { nsset = rrset; } else { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } if(rrset->rr_count > env->cfg->iter_scrub_ns) { /* If this is not a referral, and the NS RRset * is signed, then remove it entirely, so * that when it becomes bogus it does not * make the message that is otherwise fine * into a bogus message. */ if(!(msg->an_rrsets == 0 && FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && !soa_in_auth(msg) && !(msg->flags & BIT_AA)) && rrset->rrsig_count != 0) { remove_rrset("normalize: removing too large NS " "RRset:", pkt, msg, prev, &rrset); continue; } else { shorten_rrset(pkt, rrset, env->cfg->iter_scrub_ns); } } } /* if this is type DS and we query for type DS we just got * a referral answer for our type DS query, fix packet */ if(rrset->type==LDNS_RR_TYPE_DS && qinfo->qtype == LDNS_RR_TYPE_DS && dname_pkt_compare(pkt, qinfo->qname, rrset->dname) == 0) { rrset->section = LDNS_SECTION_ANSWER; msg->ancount = rrset->rr_count + rrset->rrsig_count; msg->nscount = 0; msg->arcount = 0; msg->an_rrsets = 1; msg->ns_rrsets = 0; msg->ar_rrsets = 0; msg->rrset_count = 1; msg->rrset_first = rrset; msg->rrset_last = rrset; rrset->rrset_all_next = NULL; return 1; } /* Only mark glue as allowed for type NS in the authority * section. Other RR types do not get glue for them, it * is allowed from the answer section, but not authority * so that a message can not have address records cached * as a side effect to the query. */ if(rrset->type==LDNS_RR_TYPE_NS) mark_additional_rrset(pkt, msg, rrset); prev = rrset; rrset = rrset->rrset_all_next; } /* For each record in the additional section, remove it if it is an * address record and not in the collection of additional names * found in ANSWER and AUTHORITY. */ /* These records have not been marked OK previously */ while(rrset && rrset->section == LDNS_SECTION_ADDITIONAL) { if(rrset->type==LDNS_RR_TYPE_A || rrset->type==LDNS_RR_TYPE_AAAA) { if((rrset->flags & RRSET_SCRUB_OK)) { /* remove flag to clean up flags variable */ rrset->flags &= ~RRSET_SCRUB_OK; } else { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } } /* protect internals of recursor by making sure to del these */ if(rrset->type==LDNS_RR_TYPE_DNAME || rrset->type==LDNS_RR_TYPE_CNAME || rrset->type==LDNS_RR_TYPE_NS) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } /* Allowed list of types in the additional section */ if(env->cfg->harden_unknown_additional && !type_allowed_in_additional_section(rrset->type)) { remove_rrset("normalize: removing irrelevant " "RRset:", pkt, msg, prev, &rrset); continue; } if((int)rrset->rrsig_count > env->cfg->iter_scrub_rrsig) shorten_rrsig(pkt, rrset, env->cfg->iter_scrub_rrsig); prev = rrset; rrset = rrset->rrset_all_next; } return 1; } /** * Store potential poison in the cache (only if hardening disabled). * The rrset is stored in the cache but removed from the message. * So that it will be used for infrastructure purposes, but not be * returned to the client. * @param pkt: packet * @param msg: message parsed * @param env: environment with cache * @param rrset: to store. */ static void store_rrset(sldns_buffer* pkt, struct msg_parse* msg, struct module_env* env, struct rrset_parse* rrset) { struct ub_packed_rrset_key* k; struct packed_rrset_data* d; struct rrset_ref ref; time_t now = *env->now; k = alloc_special_obtain(env->alloc); if(!k) return; k->entry.data = NULL; if(!parse_copy_decompress_rrset(pkt, msg, rrset, NULL, k)) { alloc_special_release(env->alloc, k); return; } d = (struct packed_rrset_data*)k->entry.data; packed_rrset_ttl_add(d, now); ref.key = k; ref.id = k->id; /*ignore ret: it was in the cache, ref updated */ (void)rrset_cache_update(env->rrset_cache, &ref, env->alloc, now); } /** * Check if right hand name in NSEC is within zone * @param pkt: the packet buffer for decompression. * @param rrset: the NSEC rrset * @param zonename: the zone name. * @return true if BAD. */ static int sanitize_nsec_is_overreach(sldns_buffer* pkt, struct rrset_parse* rrset, uint8_t* zonename) { struct rr_parse* rr; uint8_t* rhs; size_t len; log_assert(rrset->type == LDNS_RR_TYPE_NSEC); for(rr = rrset->rr_first; rr; rr = rr->next) { size_t pos = sldns_buffer_position(pkt); size_t rhspos; rhs = rr->ttl_data+4+2; len = sldns_read_uint16(rr->ttl_data+4); rhspos = rhs-sldns_buffer_begin(pkt); sldns_buffer_set_position(pkt, rhspos); if(pkt_dname_len(pkt) == 0) { /* malformed */ sldns_buffer_set_position(pkt, pos); return 1; } if(sldns_buffer_position(pkt)-rhspos > len) { /* outside of rdata boundaries */ sldns_buffer_set_position(pkt, pos); return 1; } sldns_buffer_set_position(pkt, pos); if(!pkt_sub(pkt, rhs, zonename)) { /* overreaching */ return 1; } } /* all NSEC RRs OK */ return 0; } /** Remove individual RRs, if the length is wrong. Returns true if the RRset * has been removed. */ static int scrub_sanitize_rr_length(sldns_buffer* pkt, struct msg_parse* msg, struct rrset_parse* prev, struct rrset_parse** rrset, int* added_ede, struct module_qstate* qstate) { struct rr_parse* rr, *rr_prev = NULL; for(rr = (*rrset)->rr_first; rr; rr = rr->next) { /* Sanity check for length of records * An A record should be 6 bytes only * (2 bytes for length and 4 for IPv4 addr)*/ if((*rrset)->type == LDNS_RR_TYPE_A && rr->size != 6 ) { if(!*added_ede) { *added_ede = 1; errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.", LDNS_EDE_OTHER); } if(msgparse_rrset_remove_rr("sanitize: removing type A RR of inappropriate length:", pkt, *rrset, rr_prev, rr, NULL, 0)) { remove_rrset("sanitize: removing type A RRset of inappropriate length:", pkt, msg, prev, rrset); return 1; } continue; } /* Sanity check for length of records * An AAAA record should be 18 bytes only * (2 bytes for length and 16 for IPv6 addr)*/ if((*rrset)->type == LDNS_RR_TYPE_AAAA && rr->size != 18 ) { if(!*added_ede) { *added_ede = 1; errinf_ede(qstate, "sanitize: records of inappropriate length have been removed.", LDNS_EDE_OTHER); } if(msgparse_rrset_remove_rr("sanitize: removing type AAAA RR of inappropriate length:", pkt, *rrset, rr_prev, rr, NULL, 0)) { remove_rrset("sanitize: removing type AAAA RRset of inappropriate length:", pkt, msg, prev, rrset); return 1; } continue; } rr_prev = rr; } return 0; } /** * Given a response event, remove suspect RRsets from the response. * "Suspect" rrsets are potentially poison. Note that this routine expects * the response to be in a "normalized" state -- that is, all "irrelevant" * RRsets have already been removed, CNAMEs are in order, etc. * * @param pkt: packet. * @param msg: msg to normalize. * @param qinfo: the question originally asked. * @param zonename: name of server zone. * @param env: module environment with config and cache. * @param ie: iterator environment with private address data. * @param qstate: for setting errinf for EDE error messages. * @return 0 on error. */ static int scrub_sanitize(sldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct module_env* env, struct iter_env* ie, struct module_qstate* qstate) { int del_addi = 0; /* if additional-holding rrsets are deleted, we do not trust the normalized additional-A-AAAA any more */ uint8_t* ns_rrset_dname = NULL; int added_rrlen_ede = 0; struct rrset_parse* rrset, *prev; prev = NULL; rrset = msg->rrset_first; /* the first DNAME is allowed to stay. It needs checking before * it can be used from the cache. After normalization, an initial * DNAME will have a correctly synthesized CNAME after it. */ if(rrset && rrset->type == LDNS_RR_TYPE_DNAME && rrset->section == LDNS_SECTION_ANSWER && pkt_strict_sub(pkt, qinfo->qname, rrset->dname) && pkt_sub(pkt, rrset->dname, zonename)) { prev = rrset; /* DNAME allowed to stay in answer section */ rrset = rrset->rrset_all_next; } /* remove all records from the answer section that are * not the same domain name as the query domain name. * The answer section should contain rrsets with the same name * as the question. For DNAMEs a CNAME has been synthesized. * Wildcards have the query name in answer section. * ANY queries get query name in answer section. * Remainders of CNAME chains are cut off and resolved by iterator. */ while(rrset && rrset->section == LDNS_SECTION_ANSWER) { if(dname_pkt_compare(pkt, qinfo->qname, rrset->dname) != 0) { if(has_additional(rrset->type)) del_addi = 1; remove_rrset("sanitize: removing extraneous answer " "RRset:", pkt, msg, prev, &rrset); continue; } prev = rrset; rrset = rrset->rrset_all_next; } /* At this point, we brutally remove ALL rrsets that aren't * children of the originating zone. The idea here is that, * as far as we know, the server that we contacted is ONLY * authoritative for the originating zone. It, of course, MAY * be authoritative for any other zones, and of course, MAY * NOT be authoritative for some subdomains of the originating * zone. */ prev = NULL; rrset = msg->rrset_first; while(rrset) { /* Sanity check for length of records */ if(rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA) { if(scrub_sanitize_rr_length(pkt, msg, prev, &rrset, &added_rrlen_ede, qstate)) continue; } /* remove private addresses */ if(rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA || rrset->type == LDNS_RR_TYPE_SVCB || rrset->type == LDNS_RR_TYPE_HTTPS) { /* do not set servfail since this leads to too * many drops of other people using rfc1918 space */ /* also do not remove entire rrset, unless all records * in it are bad */ if(priv_rrset_bad(ie->priv, pkt, rrset)) { remove_rrset(NULL, pkt, msg, prev, &rrset); continue; } } /* skip DNAME records -- they will always be followed by a * synthesized CNAME, which will be relevant. * FIXME: should this do something differently with DNAME * rrsets NOT in Section.ANSWER? */ /* But since DNAME records are also subdomains of the zone, * same check can be used */ if(!pkt_sub(pkt, rrset->dname, zonename)) { if(msg->an_rrsets == 0 && rrset->type == LDNS_RR_TYPE_NS && rrset->section == LDNS_SECTION_AUTHORITY && FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR && !soa_in_auth(msg) && sub_of_pkt(pkt, zonename, rrset->dname)) { /* noerror, nodata and this NS rrset is above * the zone. This is LAME! * Leave in the NS for lame classification. */ /* remove everything from the additional * (we dont want its glue that was approved * during the normalize action) */ del_addi = 1; } else if(!env->cfg->harden_glue && ( rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA)) { /* store in cache! Since it is relevant * (from normalize) it will be picked up * from the cache to be used later */ store_rrset(pkt, msg, env, rrset); remove_rrset("sanitize: storing potential " "poison RRset:", pkt, msg, prev, &rrset); continue; } else { if(has_additional(rrset->type)) del_addi = 1; remove_rrset("sanitize: removing potential " "poison RRset:", pkt, msg, prev, &rrset); continue; } } if(rrset->type == LDNS_RR_TYPE_NS && (rrset->section == LDNS_SECTION_AUTHORITY || rrset->section == LDNS_SECTION_ANSWER)) { /* If the type is NS, and we're in the * answer or authority section, then * store the dname so we can check * against the glue records * further down */ ns_rrset_dname = rrset->dname; } if(del_addi && rrset->section == LDNS_SECTION_ADDITIONAL) { remove_rrset("sanitize: removing potential " "poison reference RRset:", pkt, msg, prev, &rrset); continue; } /* check if right hand side of NSEC is within zone */ if(rrset->type == LDNS_RR_TYPE_NSEC && sanitize_nsec_is_overreach(pkt, rrset, zonename)) { remove_rrset("sanitize: removing overreaching NSEC " "RRset:", pkt, msg, prev, &rrset); continue; } if(env->cfg->harden_unverified_glue && ns_rrset_dname && rrset->section == LDNS_SECTION_ADDITIONAL && (rrset->type == LDNS_RR_TYPE_A || rrset->type == LDNS_RR_TYPE_AAAA) && !pkt_strict_sub(pkt, rrset->dname, ns_rrset_dname)) { /* We're in the additional section, looking * at an A/AAAA rrset, have a previous * delegation point and we notice that * the glue records are NOT for strict * subdomains of the delegation. So set a * flag, recompute the hash for the rrset * and write the A/AAAA record to cache. * It'll be retrieved if we can't separately * resolve the glue */ rrset->flags = PACKED_RRSET_UNVERIFIED_GLUE; rrset->hash = pkt_hash_rrset(pkt, rrset->dname, rrset->type, rrset->rrset_class, rrset->flags); store_rrset(pkt, msg, env, rrset); remove_rrset("sanitize: storing potential " "unverified glue reference RRset:", pkt, msg, prev, &rrset); continue; } prev = rrset; rrset = rrset->rrset_all_next; } return 1; } int scrub_message(sldns_buffer* pkt, struct msg_parse* msg, struct query_info* qinfo, uint8_t* zonename, struct regional* region, struct module_env* env, struct module_qstate* qstate, struct iter_env* ie) { /* basic sanity checks */ log_nametypeclass(VERB_ALGO, "scrub for", zonename, LDNS_RR_TYPE_NS, qinfo->qclass); if(msg->qdcount > 1) return 0; if( !(msg->flags&BIT_QR) ) return 0; msg->flags &= ~(BIT_AD|BIT_Z); /* force off bit AD and Z */ /* make sure that a query is echoed back when NOERROR or NXDOMAIN */ /* this is not required for basic operation but is a forgery * resistance (security) feature */ if((FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NOERROR || FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_NXDOMAIN || FLAGS_GET_RCODE(msg->flags) == LDNS_RCODE_YXDOMAIN) && msg->qdcount == 0) return 0; /* if a query is echoed back, make sure it is correct. Otherwise, * this may be not a reply to our query. */ if(msg->qdcount == 1) { if(dname_pkt_compare(pkt, msg->qname, qinfo->qname) != 0) return 0; if(msg->qtype != qinfo->qtype || msg->qclass != qinfo->qclass) return 0; } /* normalize the response, this cleans up the additional. */ if(!scrub_normalize(pkt, msg, qinfo, region, env, zonename)) return 0; /* delete all out-of-zone information */ if(!scrub_sanitize(pkt, msg, qinfo, zonename, env, ie, qstate)) return 0; return 1; } unbound-1.25.1/iterator/iter_donotq.h0000644000175000017500000000636415203270263017250 0ustar wouterwouter/* * iterator/iter_donotq.h - iterative resolver donotqueryaddresses storage. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of the donotquery addresses and lookup fast. */ #ifndef ITERATOR_ITER_DONOTQ_H #define ITERATOR_ITER_DONOTQ_H #include "util/storage/dnstree.h" struct iter_env; struct config_file; struct regional; /** * Iterator donotqueryaddresses structure */ struct iter_donotq { /** regional for allocation */ struct regional* region; /** * Tree of the address spans that are blocked. * contents of type addr_tree_node. Each node is an address span * that must not be used to send queries to. */ rbtree_type tree; }; /** * Create donotqueryaddresses structure * @return new structure or NULL on error. */ struct iter_donotq* donotq_create(void); /** * Delete donotqueryaddresses structure. * @param donotq: to delete. */ void donotq_delete(struct iter_donotq* donotq); /** * Process donotqueryaddresses config. * @param donotq: where to store. * @param cfg: config options. * @return 0 on error. */ int donotq_apply_cfg(struct iter_donotq* donotq, struct config_file* cfg); /** * See if an address is blocked. * @param donotq: structure for address storage. * @param addr: address to check * @param addrlen: length of addr. * @return: true if the address must not be queried. false if unlisted. */ int donotq_lookup(struct iter_donotq* donotq, struct sockaddr_storage* addr, socklen_t addrlen); /** * Get memory used by donotqueryaddresses structure. * @param donotq: structure for address storage. * @return bytes in use. */ size_t donotq_get_mem(struct iter_donotq* donotq); #endif /* ITERATOR_ITER_DONOTQ_H */ unbound-1.25.1/iterator/iter_priv.c0000644000175000017500000003170515203270263016714 0ustar wouterwouter/* * iterator/iter_priv.c - iterative resolver private address and domain store * * Copyright (c) 2008, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of the private addresses and lookup fast. */ #include "config.h" #include "iterator/iter_priv.h" #include "util/regional.h" #include "util/log.h" #include "util/config_file.h" #include "util/data/dname.h" #include "util/data/msgparse.h" #include "util/net_help.h" #include "util/storage/dnstree.h" #include "sldns/str2wire.h" #include "sldns/sbuffer.h" struct iter_priv* priv_create(void) { struct iter_priv* priv = (struct iter_priv*)calloc(1, sizeof(*priv)); if(!priv) return NULL; priv->region = regional_create(); if(!priv->region) { priv_delete(priv); return NULL; } addr_tree_init(&priv->a); name_tree_init(&priv->n); return priv; } void priv_delete(struct iter_priv* priv) { if(!priv) return; regional_destroy(priv->region); free(priv); } /** Read private-addr declarations from config */ static int read_addrs(struct iter_priv* priv, struct config_file* cfg) { /* parse addresses, report errors, insert into tree */ struct config_strlist* p; struct addr_tree_node* n; struct sockaddr_storage addr; int net; socklen_t addrlen; for(p = cfg->private_address; p; p = p->next) { log_assert(p->str); if(!netblockstrtoaddr(p->str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) { log_err("cannot parse private-address: %s", p->str); return 0; } n = (struct addr_tree_node*)regional_alloc(priv->region, sizeof(*n)); if(!n) { log_err("out of memory"); return 0; } if(!addr_tree_insert(&priv->a, n, &addr, addrlen, net)) { verbose(VERB_QUERY, "ignoring duplicate " "private-address: %s", p->str); } } return 1; } /** Read private-domain declarations from config */ static int read_names(struct iter_priv* priv, struct config_file* cfg) { /* parse names, report errors, insert into tree */ struct config_strlist* p; struct name_tree_node* n; uint8_t* nm, *nmr; size_t nm_len; int nm_labs; for(p = cfg->private_domain; p; p = p->next) { log_assert(p->str); nm = sldns_str2wire_dname(p->str, &nm_len); if(!nm) { log_err("cannot parse private-domain: %s", p->str); return 0; } nm_labs = dname_count_size_labels(nm, &nm_len); nmr = (uint8_t*)regional_alloc_init(priv->region, nm, nm_len); free(nm); if(!nmr) { log_err("out of memory"); return 0; } n = (struct name_tree_node*)regional_alloc(priv->region, sizeof(*n)); if(!n) { log_err("out of memory"); return 0; } if(!name_tree_insert(&priv->n, n, nmr, nm_len, nm_labs, LDNS_RR_CLASS_IN)) { verbose(VERB_QUERY, "ignoring duplicate " "private-domain: %s", p->str); } } return 1; } int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg) { /* empty the current contents */ regional_free_all(priv->region); addr_tree_init(&priv->a); name_tree_init(&priv->n); /* read new contents */ if(!read_addrs(priv, cfg)) return 0; if(!read_names(priv, cfg)) return 0; /* prepare for lookups */ addr_tree_init_parents(&priv->a); name_tree_init_parents(&priv->n); return 1; } /** * See if an address is blocked. * @param priv: structure for address storage. * @param addr: address to check * @param addrlen: length of addr. * @return: true if the address must not be queried. false if unlisted. */ static int priv_lookup_addr(struct iter_priv* priv, struct sockaddr_storage* addr, socklen_t addrlen) { return addr_tree_lookup(&priv->a, addr, addrlen) != NULL; } /** * See if a name is whitelisted. * @param priv: structure for address storage. * @param pkt: the packet (for compression ptrs). * @param name: name to check. * @param name_len: uncompressed length of the name to check. * @param dclass: class to check. * @return: true if the name is OK. false if unlisted. */ static int priv_lookup_name(struct iter_priv* priv, sldns_buffer* pkt, uint8_t* name, size_t name_len, uint16_t dclass) { size_t len; uint8_t decomp[256]; int labs; if(name_len >= sizeof(decomp)) return 0; dname_pkt_copy(pkt, decomp, name); labs = dname_count_size_labels(decomp, &len); log_assert(name_len == len); return name_tree_lookup(&priv->n, decomp, len, labs, dclass) != NULL; } size_t priv_get_mem(struct iter_priv* priv) { if(!priv) return 0; return sizeof(*priv) + regional_get_mem(priv->region); } /** * Check if svcparam ipv4hint contains a private address. * @param priv: private address lookup struct. * @param d: the data bytes. * @param data_len: number of data bytes in the svcparam. * @param addr: address to return the private address to log in to. * It has space for IPv4 and IPv6 addresses. * @param addrlen: length of the addr. Returns the correct size for the addr. * @return true if the rdata contains a private address. */ static int svcb_ipv4hint_contains_priv_addr(struct iter_priv* priv, uint8_t* d, uint16_t data_len, struct sockaddr_storage* addr, socklen_t* addrlen) { struct sockaddr_in sa; *addrlen = (socklen_t)sizeof(struct sockaddr_in); memset(&sa, 0, sizeof(struct sockaddr_in)); sa.sin_family = AF_INET; sa.sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); while(data_len >= LDNS_IP4ADDRLEN) { memmove(&sa.sin_addr, d, LDNS_IP4ADDRLEN); memmove(addr, &sa, *addrlen); if(priv_lookup_addr(priv, addr, *addrlen)) return 1; d += LDNS_IP4ADDRLEN; data_len -= LDNS_IP4ADDRLEN; } /* if data_len != 0 here, then the svcparam is malformed. */ return 0; } /** * Check if svcparam ipv6hint contains a private address. * @param priv: private address lookup struct. * @param d: the data bytes. * @param data_len: number of data bytes in the svcparam. * @param addr: address to return the private address to log in to. * It has space for IPv4 and IPv6 addresses. * @param addrlen: length of the addr. Returns the correct size for the addr. * @return true if the rdata contains a private address. */ static int svcb_ipv6hint_contains_priv_addr(struct iter_priv* priv, uint8_t* d, uint16_t data_len, struct sockaddr_storage* addr, socklen_t* addrlen) { struct sockaddr_in6 sa; *addrlen = (socklen_t)sizeof(struct sockaddr_in6); memset(&sa, 0, sizeof(struct sockaddr_in6)); sa.sin6_family = AF_INET6; sa.sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); while(data_len >= LDNS_IP6ADDRLEN) { memmove(&sa.sin6_addr, d, LDNS_IP6ADDRLEN); memmove(addr, &sa, *addrlen); if(priv_lookup_addr(priv, addr, *addrlen)) return 1; d += LDNS_IP6ADDRLEN; data_len -= LDNS_IP6ADDRLEN; } /* if data_len != 0 here, then the svcparam is malformed. */ return 0; } /** * Check if type SVCB and HTTPS rdata contains a private address. * @param priv: private address lookup struct. * @param pkt: the packet. * @param rr: the rr with rdata to check. * @param addr: address to return the private address to log in to. * @param addrlen: length of the addr. Initially the total size, on * return the correct size for the addr. * @return true if the rdata contains a private address. */ static int svcb_rr_contains_priv_addr(struct iter_priv* priv, sldns_buffer* pkt, struct rr_parse* rr, struct sockaddr_storage* addr, socklen_t* addrlen) { uint8_t* d = rr->ttl_data; uint16_t svcparamkey, data_len, rdatalen; size_t oldpos, dname_len, dname_start, dname_compr_len; d += 4; /* skip TTL */ rdatalen = sldns_read_uint16(d); /* read rdata length */ d += 2; if(rdatalen < 2 /* priority */ + 1 /* 1 length target */) return 0; /* malformed, too short */ d += 2; /* skip priority */ rdatalen -= 2; oldpos = sldns_buffer_position(pkt); sldns_buffer_set_position(pkt, (size_t)(d - sldns_buffer_begin(pkt))); dname_start = sldns_buffer_position(pkt); dname_len = pkt_dname_len(pkt); dname_compr_len = sldns_buffer_position(pkt) - dname_start; sldns_buffer_set_position(pkt, oldpos); if(dname_len == 0) return 0; /* dname malformed */ if(dname_compr_len > rdatalen) return 0; /* malformed */ d += dname_compr_len; /* skip target */ rdatalen -= dname_compr_len; while(rdatalen >= 4) { svcparamkey = sldns_read_uint16(d); data_len = sldns_read_uint16(d+2); d += 4; rdatalen -= 4; /* verify that we have data_len data */ if(data_len > rdatalen) { /* It is malformed, but if there are addresses * in there it can be rejected. */ data_len = rdatalen; } if(!data_len) continue; /* no data for the svcparamkey */ if(svcparamkey == SVCB_KEY_IPV4HINT) { if(svcb_ipv4hint_contains_priv_addr(priv, d, data_len, addr, addrlen)) return 1; } else if(svcparamkey == SVCB_KEY_IPV6HINT) { if(svcb_ipv6hint_contains_priv_addr(priv, d, data_len, addr, addrlen)) return 1; } d += data_len; rdatalen -= data_len; } /* If rdatalen != 0 here, then the svcb rdata is malformed. */ return 0; } /** * Check if the SVCB and HTTPS rrset is bad. * @param priv: private address lookup struct. * @param pkt: the packet. * @param rrset: the rrset to check. * @return 1 if the entire rrset has to be removed. 0 if not. * It removes RRs if they have private addresses, and log that. */ static int priv_svcb_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt, struct rrset_parse* rrset) { struct rr_parse* rr, *prev = NULL; struct sockaddr_storage addr; socklen_t addrlen = (socklen_t)sizeof(addr); for(rr = rrset->rr_first; rr; rr = rr->next) { if(svcb_rr_contains_priv_addr(priv, pkt, rr, &addr, &addrlen)) { if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, addrlen)) return 1; continue; } prev = rr; } return 0; } int priv_rrset_bad(struct iter_priv* priv, sldns_buffer* pkt, struct rrset_parse* rrset) { if(priv->a.count == 0) return 0; /* there are no blocked addresses */ /* see if it is a private name, that is allowed to have any */ if(priv_lookup_name(priv, pkt, rrset->dname, rrset->dname_len, ntohs(rrset->rrset_class))) { return 0; } else { /* so its a public name, check the address */ socklen_t len; struct rr_parse* rr, *prev = NULL; if(rrset->type == LDNS_RR_TYPE_A) { struct sockaddr_storage addr; struct sockaddr_in sa; len = (socklen_t)sizeof(sa); memset(&sa, 0, len); sa.sin_family = AF_INET; sa.sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); for(rr = rrset->rr_first; rr; rr = rr->next) { if(sldns_read_uint16(rr->ttl_data+4) != INET_SIZE) { prev = rr; continue; } memmove(&sa.sin_addr, rr->ttl_data+4+2, INET_SIZE); memmove(&addr, &sa, len); if(priv_lookup_addr(priv, &addr, len)) { if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len)) return 1; continue; } prev = rr; } } else if(rrset->type == LDNS_RR_TYPE_AAAA) { struct sockaddr_storage addr; struct sockaddr_in6 sa; len = (socklen_t)sizeof(sa); memset(&sa, 0, len); sa.sin6_family = AF_INET6; sa.sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); for(rr = rrset->rr_first; rr; rr = rr->next) { if(sldns_read_uint16(rr->ttl_data+4) != INET6_SIZE) { prev = rr; continue; } memmove(&sa.sin6_addr, rr->ttl_data+4+2, INET6_SIZE); memmove(&addr, &sa, len); if(priv_lookup_addr(priv, &addr, len)) { if(msgparse_rrset_remove_rr("sanitize: removing public name with private address", pkt, rrset, prev, rr, &addr, len)) return 1; continue; } prev = rr; } } else if(rrset->type == LDNS_RR_TYPE_SVCB || rrset->type == LDNS_RR_TYPE_HTTPS) { if(priv_svcb_rrset_bad(priv, pkt, rrset)) return 1; } } return 0; } unbound-1.25.1/iterator/iter_hints.c0000644000175000017500000004342415203270263017062 0ustar wouterwouter/* * iterator/iter_hints.c - iterative resolver module stub and root hints. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of stub and root hints, and read those from config. */ #include "config.h" #include "iterator/iter_hints.h" #include "iterator/iter_delegpt.h" #include "util/log.h" #include "util/config_file.h" #include "util/net_help.h" #include "util/data/dname.h" #include "sldns/rrdef.h" #include "sldns/str2wire.h" #include "sldns/wire2str.h" struct iter_hints* hints_create(void) { struct iter_hints* hints = (struct iter_hints*)calloc(1, sizeof(struct iter_hints)); if(!hints) return NULL; lock_rw_init(&hints->lock); lock_protect(&hints->lock, &hints->tree, sizeof(hints->tree)); return hints; } static void hints_stub_free(struct iter_hints_stub* s) { if(!s) return; delegpt_free_mlc(s->dp); free(s); } static void delhintnode(rbnode_type* n, void* ATTR_UNUSED(arg)) { struct iter_hints_stub* node = (struct iter_hints_stub*)n; hints_stub_free(node); } static void hints_del_tree(struct iter_hints* hints) { traverse_postorder(&hints->tree, &delhintnode, NULL); } void hints_delete(struct iter_hints* hints) { if(!hints) return; lock_rw_destroy(&hints->lock); hints_del_tree(hints); free(hints); } /** add hint to delegation hints */ static int ah(struct delegpt* dp, const char* sv, const char* ip) { struct sockaddr_storage addr; socklen_t addrlen; size_t dname_len; uint8_t* dname = sldns_str2wire_dname(sv, &dname_len); if(!dname) { log_err("could not parse %s", sv); return 0; } if(!delegpt_add_ns_mlc(dp, dname, 0, NULL, UNBOUND_DNS_PORT) || !extstrtoaddr(ip, &addr, &addrlen, UNBOUND_DNS_PORT) || !delegpt_add_target_mlc(dp, dname, dname_len, &addr, addrlen, 0, 0)) { free(dname); return 0; } free(dname); return 1; } /** obtain compiletime provided root hints */ static struct delegpt* compile_time_root_prime(int do_ip4, int do_ip6) { /* from: ; This file is made available by InterNIC ; under anonymous FTP as ; file /domain/named.cache ; on server FTP.INTERNIC.NET ; -OR- RS.INTERNIC.NET ; ; related version of root zone: changes-on-20120103 */ struct delegpt* dp = delegpt_create_mlc((uint8_t*)"\000"); if(!dp) return NULL; dp->has_parent_side_NS = 1; if(do_ip4) { if(!ah(dp, "A.ROOT-SERVERS.NET.", "198.41.0.4")) goto failed; if(!ah(dp, "B.ROOT-SERVERS.NET.", "170.247.170.2")) goto failed; if(!ah(dp, "C.ROOT-SERVERS.NET.", "192.33.4.12")) goto failed; if(!ah(dp, "D.ROOT-SERVERS.NET.", "199.7.91.13")) goto failed; if(!ah(dp, "E.ROOT-SERVERS.NET.", "192.203.230.10")) goto failed; if(!ah(dp, "F.ROOT-SERVERS.NET.", "192.5.5.241")) goto failed; if(!ah(dp, "G.ROOT-SERVERS.NET.", "192.112.36.4")) goto failed; if(!ah(dp, "H.ROOT-SERVERS.NET.", "198.97.190.53")) goto failed; if(!ah(dp, "I.ROOT-SERVERS.NET.", "192.36.148.17")) goto failed; if(!ah(dp, "J.ROOT-SERVERS.NET.", "192.58.128.30")) goto failed; if(!ah(dp, "K.ROOT-SERVERS.NET.", "193.0.14.129")) goto failed; if(!ah(dp, "L.ROOT-SERVERS.NET.", "199.7.83.42")) goto failed; if(!ah(dp, "M.ROOT-SERVERS.NET.", "202.12.27.33")) goto failed; } if(do_ip6) { if(!ah(dp, "A.ROOT-SERVERS.NET.", "2001:503:ba3e::2:30")) goto failed; if(!ah(dp, "B.ROOT-SERVERS.NET.", "2801:1b8:10::b")) goto failed; if(!ah(dp, "C.ROOT-SERVERS.NET.", "2001:500:2::c")) goto failed; if(!ah(dp, "D.ROOT-SERVERS.NET.", "2001:500:2d::d")) goto failed; if(!ah(dp, "E.ROOT-SERVERS.NET.", "2001:500:a8::e")) goto failed; if(!ah(dp, "F.ROOT-SERVERS.NET.", "2001:500:2f::f")) goto failed; if(!ah(dp, "G.ROOT-SERVERS.NET.", "2001:500:12::d0d")) goto failed; if(!ah(dp, "H.ROOT-SERVERS.NET.", "2001:500:1::53")) goto failed; if(!ah(dp, "I.ROOT-SERVERS.NET.", "2001:7fe::53")) goto failed; if(!ah(dp, "J.ROOT-SERVERS.NET.", "2001:503:c27::2:30")) goto failed; if(!ah(dp, "K.ROOT-SERVERS.NET.", "2001:7fd::1")) goto failed; if(!ah(dp, "L.ROOT-SERVERS.NET.", "2001:500:9f::42")) goto failed; if(!ah(dp, "M.ROOT-SERVERS.NET.", "2001:dc3::35")) goto failed; } return dp; failed: delegpt_free_mlc(dp); return 0; } /** insert new hint info into hint structure */ static int hints_insert(struct iter_hints* hints, uint16_t c, struct delegpt* dp, int noprime) { struct iter_hints_stub* node = (struct iter_hints_stub*)malloc( sizeof(struct iter_hints_stub)); if(!node) { delegpt_free_mlc(dp); return 0; } node->dp = dp; node->noprime = (uint8_t)noprime; if(!name_tree_insert(&hints->tree, &node->node, dp->name, dp->namelen, dp->namelabs, c)) { char buf[LDNS_MAX_DOMAINLEN]; dname_str(dp->name, buf); log_err("second hints for zone %s ignored.", buf); delegpt_free_mlc(dp); free(node); } return 1; } /** set stub name */ static struct delegpt* read_stubs_name(struct config_stub* s) { struct delegpt* dp; size_t dname_len; uint8_t* dname; if(!s->name) { log_err("stub zone without a name"); return NULL; } dname = sldns_str2wire_dname(s->name, &dname_len); if(!dname) { log_err("cannot parse stub zone name %s", s->name); return NULL; } if(!(dp=delegpt_create_mlc(dname))) { free(dname); log_err("out of memory"); return NULL; } free(dname); return dp; } /** set stub host names */ static int read_stubs_host(struct config_stub* s, struct delegpt* dp) { struct config_strlist* p; uint8_t* dname; char* tls_auth_name; int port; for(p = s->hosts; p; p = p->next) { log_assert(p->str); dname = authextstrtodname(p->str, &port, &tls_auth_name); if(!dname) { log_err("cannot parse stub %s nameserver name: '%s'", s->name, p->str); return 0; } if(dname_subdomain_c(dname, dp->name)) { log_warn("stub-host '%s' may have a circular " "dependency on stub-zone '%s'", p->str, s->name); } #if ! defined(HAVE_SSL_SET1_HOST) && ! defined(HAVE_X509_VERIFY_PARAM_SET1_HOST) if(tls_auth_name) log_err("no name verification functionality in " "ssl library, ignored name for %s", p->str); #endif if(!delegpt_add_ns_mlc(dp, dname, 0, tls_auth_name, port)) { free(dname); log_err("out of memory"); return 0; } free(dname); } return 1; } /** set stub server addresses */ static int read_stubs_addr(struct config_stub* s, struct delegpt* dp) { struct config_strlist* p; struct sockaddr_storage addr; socklen_t addrlen; char* auth_name; for(p = s->addrs; p; p = p->next) { log_assert(p->str); if(!authextstrtoaddr(p->str, &addr, &addrlen, &auth_name)) { log_err("cannot parse stub %s ip address: '%s'", s->name, p->str); return 0; } #if ! defined(HAVE_SSL_SET1_HOST) && ! defined(HAVE_X509_VERIFY_PARAM_SET1_HOST) if(auth_name) log_err("no name verification functionality in " "ssl library, ignored name for %s", p->str); #endif if(!delegpt_add_addr_mlc(dp, &addr, addrlen, 0, 0, auth_name, -1)) { log_err("out of memory"); return 0; } } return 1; } /** read stubs config */ static int read_stubs(struct iter_hints* hints, struct config_file* cfg) { struct config_stub* s; struct delegpt* dp; for(s = cfg->stubs; s; s = s->next) { if(!(dp=read_stubs_name(s))) return 0; if(!read_stubs_host(s, dp) || !read_stubs_addr(s, dp)) { delegpt_free_mlc(dp); return 0; } /* the flag is turned off for 'stub-first' so that the * last resort will ask for parent-side NS record and thus * fallback to the internet name servers on a failure */ dp->has_parent_side_NS = (uint8_t)!s->isfirst; /* Do not cache if set. */ dp->no_cache = s->no_cache; /* ssl_upstream */ dp->ssl_upstream = (uint8_t)s->ssl_upstream; /* tcp_upstream */ dp->tcp_upstream = (uint8_t)s->tcp_upstream; delegpt_log(VERB_QUERY, dp); if(!hints_insert(hints, LDNS_RR_CLASS_IN, dp, !s->isprime)) return 0; } return 1; } /** read root hints from file */ static int read_root_hints(struct iter_hints* hints, char* fname) { struct sldns_file_parse_state pstate; struct delegpt* dp; uint8_t rr[LDNS_RR_BUF_SIZE]; size_t rr_len, dname_len; int status; uint16_t c = LDNS_RR_CLASS_IN; FILE* f = fopen(fname, "r"); if(!f) { log_err("could not read root hints %s: %s", fname, strerror(errno)); return 0; } dp = delegpt_create_mlc(NULL); if(!dp) { log_err("out of memory reading root hints"); fclose(f); return 0; } verbose(VERB_QUERY, "Reading root hints from %s", fname); memset(&pstate, 0, sizeof(pstate)); pstate.lineno = 1; dp->has_parent_side_NS = 1; while(!feof(f)) { rr_len = sizeof(rr); dname_len = 0; status = sldns_fp2wire_rr_buf(f, rr, &rr_len, &dname_len, &pstate); if(status != 0) { log_err("reading root hints %s %d:%d: %s", fname, pstate.lineno, LDNS_WIREPARSE_OFFSET(status), sldns_get_errorstr_parse(status)); goto stop_read; } if(rr_len == 0) continue; /* EMPTY line, TTL or ORIGIN */ if(sldns_wirerr_get_type(rr, rr_len, dname_len) == LDNS_RR_TYPE_NS) { if(!delegpt_add_ns_mlc(dp, sldns_wirerr_get_rdata(rr, rr_len, dname_len), 0, NULL, UNBOUND_DNS_PORT)) { log_err("out of memory reading root hints"); goto stop_read; } c = sldns_wirerr_get_class(rr, rr_len, dname_len); if(!dp->name) { if(!delegpt_set_name_mlc(dp, rr)) { log_err("out of memory."); goto stop_read; } } } else if(sldns_wirerr_get_type(rr, rr_len, dname_len) == LDNS_RR_TYPE_A && sldns_wirerr_get_rdatalen(rr, rr_len, dname_len) == INET_SIZE) { struct sockaddr_in sa; socklen_t len = (socklen_t)sizeof(sa); memset(&sa, 0, len); sa.sin_family = AF_INET; sa.sin_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa.sin_addr, sldns_wirerr_get_rdata(rr, rr_len, dname_len), INET_SIZE); if(!delegpt_add_target_mlc(dp, rr, dname_len, (struct sockaddr_storage*)&sa, len, 0, 0)) { log_err("out of memory reading root hints"); goto stop_read; } } else if(sldns_wirerr_get_type(rr, rr_len, dname_len) == LDNS_RR_TYPE_AAAA && sldns_wirerr_get_rdatalen(rr, rr_len, dname_len) == INET6_SIZE) { struct sockaddr_in6 sa; socklen_t len = (socklen_t)sizeof(sa); memset(&sa, 0, len); sa.sin6_family = AF_INET6; sa.sin6_port = (in_port_t)htons(UNBOUND_DNS_PORT); memmove(&sa.sin6_addr, sldns_wirerr_get_rdata(rr, rr_len, dname_len), INET6_SIZE); if(!delegpt_add_target_mlc(dp, rr, dname_len, (struct sockaddr_storage*)&sa, len, 0, 0)) { log_err("out of memory reading root hints"); goto stop_read; } } else { char buf[17]; sldns_wire2str_type_buf(sldns_wirerr_get_type(rr, rr_len, dname_len), buf, sizeof(buf)); log_warn("root hints %s:%d skipping type %s", fname, pstate.lineno, buf); } } fclose(f); if(!dp->name) { log_warn("root hints %s: no NS content", fname); delegpt_free_mlc(dp); return 1; } delegpt_log(VERB_QUERY, dp); if(!hints_insert(hints, c, dp, 0)) { return 0; } return 1; stop_read: delegpt_free_mlc(dp); fclose(f); return 0; } /** read root hints list */ static int read_root_hints_list(struct iter_hints* hints, struct config_file* cfg) { struct config_strlist* p; for(p = cfg->root_hints; p; p = p->next) { log_assert(p->str); if(p->str && p->str[0]) { char* f = p->str; if(cfg->chrootdir && cfg->chrootdir[0] && strncmp(p->str, cfg->chrootdir, strlen(cfg->chrootdir)) == 0) f += strlen(cfg->chrootdir); if(!read_root_hints(hints, f)) return 0; } } return 1; } int hints_apply_cfg(struct iter_hints* hints, struct config_file* cfg) { int nolock = 1; lock_rw_wrlock(&hints->lock); hints_del_tree(hints); name_tree_init(&hints->tree); /* read root hints */ if(!read_root_hints_list(hints, cfg)) { lock_rw_unlock(&hints->lock); return 0; } /* read stub hints */ if(!read_stubs(hints, cfg)) { lock_rw_unlock(&hints->lock); return 0; } /* use fallback compiletime root hints */ if(!hints_find_root(hints, LDNS_RR_CLASS_IN, nolock)) { struct delegpt* dp = compile_time_root_prime(cfg->do_ip4, cfg->do_ip6); verbose(VERB_ALGO, "no config, using builtin root hints."); if(!dp) { lock_rw_unlock(&hints->lock); return 0; } if(!hints_insert(hints, LDNS_RR_CLASS_IN, dp, 0)) { lock_rw_unlock(&hints->lock); return 0; } } name_tree_init_parents(&hints->tree); lock_rw_unlock(&hints->lock); return 1; } struct delegpt* hints_find(struct iter_hints* hints, uint8_t* qname, uint16_t qclass, int nolock) { struct iter_hints_stub *stub; size_t len; int has_dp; int labs = dname_count_size_labels(qname, &len); /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&hints->lock); } stub = (struct iter_hints_stub*)name_tree_find(&hints->tree, qname, len, labs, qclass); has_dp = stub && stub->dp; if(!has_dp && !nolock) { lock_rw_unlock(&hints->lock); } return has_dp?stub->dp:NULL; } struct delegpt* hints_find_root(struct iter_hints* hints, uint16_t qclass, int nolock) { uint8_t rootlab = 0; return hints_find(hints, &rootlab, qclass, nolock); } struct iter_hints_stub* hints_lookup_stub(struct iter_hints* hints, uint8_t* qname, uint16_t qclass, struct delegpt* cache_dp, int nolock) { size_t len; int labs; struct iter_hints_stub *r; /* first lookup the stub */ labs = dname_count_size_labels(qname, &len); /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&hints->lock); } r = (struct iter_hints_stub*)name_tree_lookup(&hints->tree, qname, len, labs, qclass); if(!r) { if(!nolock) { lock_rw_unlock(&hints->lock); } return NULL; } /* If there is no cache (root prime situation) */ if(cache_dp == NULL) { if(r->dp->namelabs != 1) return r; /* no cache dp, use any non-root stub */ if(!nolock) { lock_rw_unlock(&hints->lock); } return NULL; } /* * If the stub is same as the delegation we got * And has noprime set, we need to 'prime' to use this stub instead. */ if(r->noprime && query_dname_compare(cache_dp->name, r->dp->name)==0) return r; /* use this stub instead of cached dp */ /* * If our cached delegation point is above the hint, we need to prime. */ if(dname_strict_subdomain(r->dp->name, r->dp->namelabs, cache_dp->name, cache_dp->namelabs)) return r; /* need to prime this stub */ if(!nolock) { lock_rw_unlock(&hints->lock); } return NULL; } int hints_next_root(struct iter_hints* hints, uint16_t* qclass, int nolock) { int ret; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_rdlock(&hints->lock); } ret = name_tree_next_root(&hints->tree, qclass); if(!nolock) { lock_rw_unlock(&hints->lock); } return ret; } size_t hints_get_mem(struct iter_hints* hints) { size_t s; struct iter_hints_stub* p; if(!hints) return 0; lock_rw_rdlock(&hints->lock); s = sizeof(*hints); RBTREE_FOR(p, struct iter_hints_stub*, &hints->tree) { s += sizeof(*p) + delegpt_get_mem(p->dp); } lock_rw_unlock(&hints->lock); return s; } int hints_add_stub(struct iter_hints* hints, uint16_t c, struct delegpt* dp, int noprime, int nolock) { struct iter_hints_stub *z; /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&hints->lock); } if((z=(struct iter_hints_stub*)name_tree_find(&hints->tree, dp->name, dp->namelen, dp->namelabs, c)) != NULL) { (void)rbtree_delete(&hints->tree, &z->node); hints_stub_free(z); } if(!hints_insert(hints, c, dp, noprime)) { if(!nolock) { lock_rw_unlock(&hints->lock); } return 0; } name_tree_init_parents(&hints->tree); if(!nolock) { lock_rw_unlock(&hints->lock); } return 1; } void hints_delete_stub(struct iter_hints* hints, uint16_t c, uint8_t* nm, int nolock) { struct iter_hints_stub *z; size_t len; int labs = dname_count_size_labels(nm, &len); /* lock_() calls are macros that could be nothing, surround in {} */ if(!nolock) { lock_rw_wrlock(&hints->lock); } if(!(z=(struct iter_hints_stub*)name_tree_find(&hints->tree, nm, len, labs, c))) { if(!nolock) { lock_rw_unlock(&hints->lock); } return; /* nothing to do */ } (void)rbtree_delete(&hints->tree, &z->node); hints_stub_free(z); name_tree_init_parents(&hints->tree); if(!nolock) { lock_rw_unlock(&hints->lock); } } void hints_swap_tree(struct iter_hints* hints, struct iter_hints* data) { rbnode_type* oldroot = hints->tree.root; size_t oldcount = hints->tree.count; hints->tree.root = data->tree.root; hints->tree.count = data->tree.count; data->tree.root = oldroot; data->tree.count = oldcount; } unbound-1.25.1/iterator/iterator.h0000644000175000017500000004170315203270263016546 0ustar wouterwouter/* * iterator/iterator.h - iterative resolver DNS query response module * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains a module that performs recursive iterative DNS query * processing. */ #ifndef ITERATOR_ITERATOR_H #define ITERATOR_ITERATOR_H #include "services/outbound_list.h" #include "util/data/msgreply.h" #include "util/module.h" struct delegpt; struct iter_donotq; struct iter_prep_list; struct iter_priv; struct rbtree_type; /** max number of targets spawned for a query and its subqueries */ #define MAX_TARGET_COUNT 64 /** max number of upstream queries for a query and its subqueries, it is * never reset. */ extern int MAX_GLOBAL_QUOTA; /** max number of target lookups per qstate, per delegation point */ #define MAX_DP_TARGET_COUNT 16 /** max number of nxdomains allowed for target lookups for a query and * its subqueries */ #define MAX_TARGET_NX 5 /** max number of nxdomains allowed for target lookups for a query and * its subqueries when fallback has kicked in */ #define MAX_TARGET_NX_FALLBACK (MAX_TARGET_NX*2) /** max number of referrals. Makes sure resolver does not run away */ #define MAX_REFERRAL_COUNT 130 /** max number of queries for which to perform dnsseclameness detection, * (rrsigs missing detection) after that, just pick up that response */ #define DNSSEC_LAME_DETECT_COUNT 4 /** * max number of QNAME minimisation iterations. Limits number of queries for * QNAMEs with a lot of labels. */ #define MAX_MINIMISE_COUNT 10 /* max number of time-outs for minimised query. Prevents resolving failures * when the QNAME minimisation QTYPE is blocked. */ #define MAX_MINIMISE_TIMEOUT_COUNT 3 /** * number of labels from QNAME that are always send individually when using * QNAME minimisation, even when the number of labels of the QNAME is bigger * than MAX_MINIMISE_COUNT */ #define MINIMISE_ONE_LAB 4 #define MINIMISE_MULTIPLE_LABS (MAX_MINIMISE_COUNT - MINIMISE_ONE_LAB) /** at what query-sent-count to stop target fetch policy */ #define TARGET_FETCH_STOP 3 /** how nice is a server without further information, in msec * Equals rtt initial timeout value. */ extern int UNKNOWN_SERVER_NICENESS; /** maximum timeout before a host is deemed unsuitable, in msec. * After host_ttl this will be timed out and the host will be tried again. * Equals RTT_MAX_TIMEOUT, and thus when RTT_MAX_TIMEOUT is overwritten by * config infra_cache_max_rtt, it will be overwritten as well. */ extern int USEFUL_SERVER_TOP_TIMEOUT; /** penalty to validation failed blacklisted IPs * Equals USEFUL_SERVER_TOP_TIMEOUT*4, and thus when RTT_MAX_TIMEOUT is * overwritten by config infra_cache_max_rtt, it will be overwritten as well. */ extern int BLACKLIST_PENALTY; /** RTT band, within this amount from the best, servers are chosen randomly. * Chosen so that the UNKNOWN_SERVER_NICENESS falls within the band of a * fast server, this causes server exploration as a side benefit. msec. */ #define RTT_BAND 400 /** Number of retries for empty nodata packets before it is accepted. */ #define EMPTY_NODATA_RETRY_COUNT 2 /** * Iterator global state for nat64. */ struct iter_nat64 { /** A flag to locally apply NAT64 to make IPv4 addrs into IPv6 */ int use_nat64; /** NAT64 prefix address, cf. dns64_env->prefix_addr */ struct sockaddr_storage nat64_prefix_addr; /** sizeof(sockaddr_in6) */ socklen_t nat64_prefix_addrlen; /** CIDR mask length of NAT64 prefix */ int nat64_prefix_net; }; /** * Global state for the iterator. */ struct iter_env { /** A flag to indicate whether or not we have an IPv6 route */ int supports_ipv6; /** A flag to indicate whether or not we have an IPv4 route */ int supports_ipv4; /** State for nat64 */ struct iter_nat64 nat64; /** A set of inetaddrs that should never be queried. */ struct iter_donotq* donotq; /** private address space and private domains */ struct iter_priv* priv; /** whitelist for capsforid names */ struct rbtree_type* caps_white; /** The maximum dependency depth that this resolver will pursue. */ int max_dependency_depth; /** * The target fetch policy for each dependency level. This is * described as a simple number (per dependency level): * negative numbers (usually just -1) mean fetch-all, * 0 means only fetch on demand, and * positive numbers mean to fetch at most that many targets. * array of max_dependency_depth+1 size. */ int* target_fetch_policy; /** lock on ratelimit counter */ lock_basic_type queries_ratelimit_lock; /** number of queries that have been ratelimited */ size_t num_queries_ratelimited; /** number of retries on outgoing queries */ int outbound_msg_retry; /** number of queries_sent */ int max_sent_count; /** max number of query restarts to limit length of CNAME chain */ int max_query_restarts; }; /** * QNAME minimisation state */ enum minimisation_state { /** * (Re)start minimisation. Outgoing QNAME should be set to dp->name. * State entered on new query or after following referral or CNAME. */ INIT_MINIMISE_STATE = 0, /** * QNAME minimisation ongoing. Increase QNAME on every iteration. */ MINIMISE_STATE, /** * Don't increment QNAME this iteration */ SKIP_MINIMISE_STATE, /** * Send out full QNAME + original QTYPE */ DONOT_MINIMISE_STATE, }; /** * State of the iterator for a query. */ enum iter_state { /** * Externally generated queries start at this state. Query restarts are * reset to this state. */ INIT_REQUEST_STATE = 0, /** * Root priming events reactivate here, most other events pass * through this naturally as the 2nd part of the INIT_REQUEST_STATE. */ INIT_REQUEST_2_STATE, /** * Stub priming events reactivate here, most other events pass * through this naturally as the 3rd part of the INIT_REQUEST_STATE. */ INIT_REQUEST_3_STATE, /** * Each time a delegation point changes for a given query or a * query times out and/or wakes up, this state is (re)visited. * This state is responsible for iterating through a list of * nameserver targets. */ QUERYTARGETS_STATE, /** * Responses to queries start at this state. This state handles * the decision tree associated with handling responses. */ QUERY_RESP_STATE, /** Responses to priming queries finish at this state. */ PRIME_RESP_STATE, /** Collecting query class information, for qclass=ANY, when * it spawns off queries for every class, it returns here. */ COLLECT_CLASS_STATE, /** Find NS record to resolve DS record from, walking to the right * NS spot until we find it */ DSNS_FIND_STATE, /** Responses that are to be returned upstream end at this state. * As well as responses to target queries. */ FINISHED_STATE }; /** * Shared counters for queries. */ enum target_count_variables { /** Reference count for the shared iter_qstate->target_count. */ TARGET_COUNT_REF = 0, /** Number of target queries spawned for the query and subqueries. */ TARGET_COUNT_QUERIES, /** Number of nxdomain responses encountered. */ TARGET_COUNT_NX, /** Global quota on number of queries to upstream servers per * client request, that is never reset. */ TARGET_COUNT_GLOBAL_QUOTA, /** This should stay last here, it is used for the allocation */ TARGET_COUNT_MAX, }; /** * Per query state for the iterator module. */ struct iter_qstate { /** * State of the iterator module. * This is the state that event is in or should sent to -- all * requests should start with the INIT_REQUEST_STATE. All * responses should start with QUERY_RESP_STATE. Subsequent * processing of the event will change this state. */ enum iter_state state; /** * Final state for the iterator module. * This is the state that responses should be routed to once the * response is final. For externally initiated queries, this * will be FINISHED_STATE, locally initiated queries will have * different final states. */ enum iter_state final_state; /** * The depth of this query, this means the depth of recursion. * This address is needed for another query, which is an address * needed for another query, etc. Original client query has depth 0. */ int depth; /** * The response */ struct dns_msg* response; /** * This is a list of RRsets that must be prepended to the * ANSWER section of a response before being sent upstream. */ struct iter_prep_list* an_prepend_list; /** Last element of the prepend list */ struct iter_prep_list* an_prepend_last; /** * This is the list of RRsets that must be prepended to the * AUTHORITY section of the response before being sent upstream. */ struct iter_prep_list* ns_prepend_list; /** Last element of the authority prepend list */ struct iter_prep_list* ns_prepend_last; /** query name used for chasing the results. Initially the same as * the state qinfo, but after CNAMEs this will be different. * The query info used to elicit the results needed. */ struct query_info qchase; /** query flags to use when chasing the answer (i.e. RD flag) */ uint16_t chase_flags; /** true if we set RD bit because of last resort recursion lame query*/ int chase_to_rd; /** * This is the current delegation point for an in-progress query. This * object retains state as to which delegation targets need to be * (sub)queried for vs which ones have already been visited. */ struct delegpt* dp; /** state for 0x20 fallback when capsfail happens, 0 not a fallback */ int caps_fallback; /** state for capsfail: current server number to try */ size_t caps_server; /** state for capsfail: stored query for comparisons. Can be NULL if * no response had been seen prior to starting the fallback. */ struct reply_info* caps_reply; struct dns_msg* caps_response; /** Current delegation message - returned for non-RD queries */ struct dns_msg* deleg_msg; /** number of outstanding target sub queries */ int num_target_queries; /** outstanding direct queries */ int num_current_queries; /** the number of times this query has been restarted. */ int query_restart_count; /** the number of times this query has followed a referral. */ int referral_count; /** number of queries fired off */ int sent_count; /** malloced-array shared with this query and its subqueries. It keeps * track of the defined enum target_count_variables counters. */ int* target_count; /** number of target lookups per delegation point. Reset to 0 after * receiving referral answer. Not shared with subqueries. */ int dp_target_count; /** Delegation point that triggered the NXNS fallback; shared with * this query and its subqueries, count-referenced by the reference * counter in target_count. * This also marks the fallback activation. */ uint8_t** nxns_dp; /** if true, already tested for ratelimiting and passed the test */ int ratelimit_ok; /** * The query must store NS records from referrals as parentside RRs * Enabled once it hits resolution problems, to throttle retries. * If enabled it is the pointer to the old delegation point with * the old retry counts for bad-nameserver-addresses. */ struct delegpt* store_parent_NS; /** * The query is for parent-side glue(A or AAAA) for a nameserver. * If the item is seen as glue in a referral, and pside_glue is NULL, * then it is stored in pside_glue for later. * If it was never seen, at the end, then a negative caching element * must be created. * The (data or negative) RR cache element then throttles retries. */ int query_for_pside_glue; /** the parent-side-glue element (NULL if none, its first match) */ struct ub_packed_rrset_key* pside_glue; /** If nonNULL we are walking upwards from DS query to find NS */ uint8_t* dsns_point; /** length of the dname in dsns_point */ size_t dsns_point_len; /** * expected dnssec information for this iteration step. * If dnssec rrsigs are expected and not given, the server is marked * lame (dnssec-lame). */ int dnssec_expected; /** * We are expecting dnssec information, but we also know the server * is DNSSEC lame. The response need not be marked dnssec-lame again. */ int dnssec_lame_query; /** * This is flag that, if true, means that this event is * waiting for a stub priming query. */ int wait_priming_stub; /** * This is a flag that, if true, means that this query is * for (re)fetching glue from a zone. Since the address should * have been glue, query again to the servers that should have * been returning it as glue. * The delegation point must be set to the one that should *not* * be used when creating the state. A higher one will be attempted. */ int refetch_glue; /** * This flag detects that a completely empty nodata was received, * already so that it is accepted later. */ int empty_nodata_found; /** list of pending queries to authoritative servers. */ struct outbound_list outlist; /** QNAME minimisation state, RFC9156 */ enum minimisation_state minimisation_state; /** State for capsfail: QNAME minimisation state for comparisons. */ enum minimisation_state caps_minimisation_state; /** * The query info that is sent upstream. Will be a subset of qchase * when qname minimisation is enabled. */ struct query_info qinfo_out; /** * Count number of QNAME minimisation iterations. Used to limit number of * outgoing queries when QNAME minimisation is enabled. */ int minimise_count; /** * Count number of time-outs. Used to prevent resolving failures when * the QNAME minimisation QTYPE is blocked. Used to determine if * capsforid fallback should be started.*/ int timeout_count; /** True if the current response is from auth_zone */ int auth_zone_response; /** True if the auth_zones should not be consulted for the query */ int auth_zone_avoid; /** true if there have been scrubbing failures of reply packets */ int scrub_failures; /** true if there have been parse failures of reply packets */ int parse_failures; /** a failure printout address for last received answer */ union { struct in_addr in; #ifdef AF_INET6 struct in6_addr in6; #endif } fail_addr; /** which fail_addr, 0 is nothing, 4 or 6 */ int fail_addr_type; }; /** * List of prepend items */ struct iter_prep_list { /** next in list */ struct iter_prep_list* next; /** rrset */ struct ub_packed_rrset_key* rrset; }; /** * Get the iterator function block. * @return: function block with function pointers to iterator methods. */ struct module_func_block* iter_get_funcblock(void); /** * Get iterator state as a string * @param state: to convert * @return constant string that is printable. */ const char* iter_state_to_string(enum iter_state state); /** * See if iterator state is a response state * @param s: to inspect * @return true if response state. */ int iter_state_is_responsestate(enum iter_state s); /** iterator init */ int iter_init(struct module_env* env, int id); /** iterator deinit */ void iter_deinit(struct module_env* env, int id); /** iterator operate on a query */ void iter_operate(struct module_qstate* qstate, enum module_ev event, int id, struct outbound_entry* outbound); /** * Return priming query results to interested super querystates. * * Sets the delegation point and delegation message (not nonRD queries). * This is a callback from walk_supers. * * @param qstate: query state that finished. * @param id: module id. * @param super: the qstate to inform. */ void iter_inform_super(struct module_qstate* qstate, int id, struct module_qstate* super); /** iterator cleanup query state */ void iter_clear(struct module_qstate* qstate, int id); /** iterator alloc size routine */ size_t iter_get_mem(struct module_env* env, int id); #endif /* ITERATOR_ITERATOR_H */ unbound-1.25.1/iterator/iter_priv.h0000644000175000017500000000673215203270263016723 0ustar wouterwouter/* * iterator/iter_priv.h - iterative resolver private address and domain store * * Copyright (c) 2008, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Keep track of the private addresses and lookup fast. */ #ifndef ITERATOR_ITER_PRIV_H #define ITERATOR_ITER_PRIV_H #include "util/rbtree.h" struct sldns_buffer; struct iter_env; struct config_file; struct regional; struct rrset_parse; /** * Iterator priv structure */ struct iter_priv { /** regional for allocation */ struct regional* region; /** * Tree of the address spans that are blocked. * contents of type addr_tree_node. * No further data need, only presence or absence. */ rbtree_type a; /** * Tree of the domains spans that are allowed to contain * the blocked address spans. * contents of type name_tree_node. * No further data need, only presence or absence. */ rbtree_type n; }; /** * Create priv structure * @return new structure or NULL on error. */ struct iter_priv* priv_create(void); /** * Delete priv structure. * @param priv: to delete. */ void priv_delete(struct iter_priv* priv); /** * Process priv config. * @param priv: where to store. * @param cfg: config options. * @return 0 on error. */ int priv_apply_cfg(struct iter_priv* priv, struct config_file* cfg); /** * See if rrset is bad. * Will remove individual RRs that are bad (if possible) to * sanitize the RRset without removing it completely. * @param priv: structure for private address storage. * @param pkt: packet to decompress rrset name in. * @param rrset: the rrset to examine, A or AAAA. * @return true if the rrset is bad and should be removed. */ int priv_rrset_bad(struct iter_priv* priv, struct sldns_buffer* pkt, struct rrset_parse* rrset); /** * Get memory used by priv structure. * @param priv: structure for address storage. * @return bytes in use. */ size_t priv_get_mem(struct iter_priv* priv); #endif /* ITERATOR_ITER_PRIV_H */ unbound-1.25.1/iterator/iter_donotq.c0000644000175000017500000001032515203270263017233 0ustar wouterwouter/* * iterator/iter_donotq.c - iterative resolver donotqueryaddresses storage. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * The donotqueryaddresses are stored and looked up. These addresses * (like 127.0.0.1) must not be used to send queries to, and can be * discarded immediately from the server selection. */ #include "config.h" #include "iterator/iter_donotq.h" #include "util/regional.h" #include "util/log.h" #include "util/config_file.h" #include "util/net_help.h" struct iter_donotq* donotq_create(void) { struct iter_donotq* dq = (struct iter_donotq*)calloc(1, sizeof(struct iter_donotq)); if(!dq) return NULL; dq->region = regional_create(); if(!dq->region) { donotq_delete(dq); return NULL; } return dq; } void donotq_delete(struct iter_donotq* dq) { if(!dq) return; regional_destroy(dq->region); free(dq); } /** insert new address into donotq structure */ static int donotq_insert(struct iter_donotq* dq, struct sockaddr_storage* addr, socklen_t addrlen, int net) { struct addr_tree_node* node = (struct addr_tree_node*)regional_alloc( dq->region, sizeof(*node)); if(!node) return 0; if(!addr_tree_insert(&dq->tree, node, addr, addrlen, net)) { verbose(VERB_QUERY, "duplicate donotquery address ignored."); } return 1; } /** apply donotq string */ static int donotq_str_cfg(struct iter_donotq* dq, const char* str) { struct sockaddr_storage addr; int net; socklen_t addrlen; verbose(VERB_ALGO, "donotq: %s", str); if(!netblockstrtoaddr(str, UNBOUND_DNS_PORT, &addr, &addrlen, &net)) { log_err("cannot parse donotquery netblock: %s", str); return 0; } if(!donotq_insert(dq, &addr, addrlen, net)) { log_err("out of memory"); return 0; } return 1; } /** read donotq config */ static int read_donotq(struct iter_donotq* dq, struct config_file* cfg) { struct config_strlist* p; for(p = cfg->donotqueryaddrs; p; p = p->next) { log_assert(p->str); if(!donotq_str_cfg(dq, p->str)) return 0; } return 1; } int donotq_apply_cfg(struct iter_donotq* dq, struct config_file* cfg) { regional_free_all(dq->region); addr_tree_init(&dq->tree); if(!read_donotq(dq, cfg)) return 0; if(cfg->donotquery_localhost) { if(!donotq_str_cfg(dq, "127.0.0.0/8")) return 0; if(cfg->do_ip6) { if(!donotq_str_cfg(dq, "::1")) return 0; } } addr_tree_init_parents(&dq->tree); return 1; } int donotq_lookup(struct iter_donotq* donotq, struct sockaddr_storage* addr, socklen_t addrlen) { return addr_tree_lookup(&donotq->tree, addr, addrlen) != NULL; } size_t donotq_get_mem(struct iter_donotq* donotq) { if(!donotq) return 0; return sizeof(*donotq) + regional_get_mem(donotq->region); } unbound-1.25.1/iterator/iter_utils.h0000644000175000017500000004535015203270263017102 0ustar wouterwouter/* * iterator/iter_utils.h - iterative resolver module utility functions. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file contains functions to assist the iterator module. * Configuration options. Forward zones. */ #ifndef ITERATOR_ITER_UTILS_H #define ITERATOR_ITER_UTILS_H #include "iterator/iter_resptype.h" struct sldns_buffer; struct iter_env; struct iter_hints; struct iter_forwards; struct config_file; struct module_env; struct delegpt_addr; struct delegpt; struct regional; struct msg_parse; struct ub_randstate; struct query_info; struct reply_info; struct module_qstate; struct sock_list; struct ub_packed_rrset_key; struct module_stack; struct outside_network; struct iter_nat64; /* max number of lookups in the cache for target nameserver names. * This stops, for large delegations, N*N lookups in the cache. */ #define ITERATOR_NAME_CACHELOOKUP_MAX 3 /* max number of lookups in the cache for parentside glue for nameserver names * This stops, for larger delegations, N*N lookups in the cache. * It is a little larger than the nonpside max, so it allows a couple extra * lookups of parent side glue. */ #define ITERATOR_NAME_CACHELOOKUP_MAX_PSIDE 5 /** * Process config options and set iterator module state. * Sets default values if no config is found. * @param iter_env: iterator module state. * @param cfg: config options. * @return 0 on error. */ int iter_apply_cfg(struct iter_env* iter_env, struct config_file* cfg); /** * Select a valid, nice target to send query to. * Sorting and removing unsuitable targets is combined. * Adds records to the infra cache if not already there. * * @param iter_env: iterator module global state, with ip6 enabled and * do-not-query-addresses. * @param env: environment with infra cache (lameness, rtt info). * @param dp: delegation point with result list. * @param name: zone name (for lameness check). * @param namelen: length of name. * @param qtype: query type that we want to send. * @param dnssec_lame: set to 1, if a known dnssec-lame server is selected * these are not preferred, but are used as a last resort. * @param chase_to_rd: set to 1 if a known recursion lame server is selected * these are not preferred, but are used as a last resort. * @param open_target: number of currently outstanding target queries. * If we wait for these, perhaps more server addresses become available. * @param blacklist: the IP blacklist to use. * @param prefetch: if not 0, prefetch is in use for this query. * This means the query can have different timing, because prefetch is * not waited upon by the downstream client, and thus a good time to * perform exploration of other targets. * @return best target or NULL if no target. * if not null, that target is removed from the result list in the dp. */ struct delegpt_addr* iter_server_selection(struct iter_env* iter_env, struct module_env* env, struct delegpt* dp, uint8_t* name, size_t namelen, uint16_t qtype, int* dnssec_lame, int* chase_to_rd, int open_target, struct sock_list* blacklist, time_t prefetch); /** * Allocate dns_msg from parsed msg, in regional. * @param pkt: packet. * @param msg: parsed message (cleaned and ready for regional allocation). * @param regional: regional to use for allocation. * @return newly allocated dns_msg, or NULL on memory error. */ struct dns_msg* dns_alloc_msg(struct sldns_buffer* pkt, struct msg_parse* msg, struct regional* regional); /** * Copy a dns_msg to this regional. * @param from: dns message, also in regional. * @param regional: regional to use for allocation. * @return newly allocated dns_msg, or NULL on memory error. */ struct dns_msg* dns_copy_msg(struct dns_msg* from, struct regional* regional); /** * Allocate a dns_msg with malloc/alloc structure and store in dns cache. * @param env: environment, with alloc structure and dns cache. * @param qinf: query info, the query for which answer is stored. * @param rep: reply in dns_msg from dns_alloc_msg for example. * @param is_referral: If true, then the given message to be stored is a * referral. The cache implementation may use this as a hint. * @param leeway: prefetch TTL leeway to expire old rrsets quicker. * @param pside: true if dp is parentside, thus message is 'fresh' and NS * can be prefetch-updates. * @param region: to copy modified (cache is better) rrs back to. * @param flags: with BIT_CD for dns64 AAAA translated queries. * @param qstarttime: time of query start. * @param is_valrec: if the query is validation recursion and does not get * return void, because we are not interested in alloc errors, * the iterator and validator can operate on the results in their * scratch space (the qstate.region) and are not dependent on the cache. * It is useful to log the alloc failure (for the server operator), * but the query resolution can continue without cache storage. */ void iter_dns_store(struct module_env* env, struct query_info* qinf, struct reply_info* rep, int is_referral, time_t leeway, int pside, struct regional* region, uint16_t flags, time_t qstarttime, int is_valrec); /** * Select randomly with n/m probability. * For shuffle NS records for address fetching. * @param rnd: random table * @param n: probability. * @param m: divisor for probability. * @return true with n/m probability. */ int iter_ns_probability(struct ub_randstate* rnd, int n, int m); /** * Mark targets that result in a dependency cycle as done, so they * will not get selected as targets. * @param qstate: query state. * @param dp: delegpt to mark ns in. */ void iter_mark_cycle_targets(struct module_qstate* qstate, struct delegpt* dp); /** * Mark targets that result in a dependency cycle as done, so they * will not get selected as targets. For the parent-side lookups. * @param qstate: query state. * @param dp: delegpt to mark ns in. */ void iter_mark_pside_cycle_targets(struct module_qstate* qstate, struct delegpt* dp); /** * See if delegation is useful or offers immediately no targets for * further recursion. * @param qinfo: query name and type * @param qflags: query flags with RD flag * @param dp: delegpt to check. * @param supports_ipv4: if we support ipv4 for lookups to the target. * if not, then the IPv4 addresses are useless. * @param supports_ipv6: if we support ipv6 for lookups to the target. * if not, then the IPv6 addresses are useless. * @param use_nat64: if we support NAT64 for lookups to the target. * if yes, IPv4 addresses are useful even if we don't support IPv4. * @return true if dp is useless. */ int iter_dp_is_useless(struct query_info* qinfo, uint16_t qflags, struct delegpt* dp, int supports_ipv4, int supports_ipv6, int use_nat64); /** * See if qname has DNSSEC needs. This is true if there is a trust anchor above * it. Whether there is an insecure delegation to the data is unknown. * @param env: environment with anchors. * @param qinfo: query name and class. * @return true if trust anchor above qname, false if no anchor or insecure * point above qname. */ int iter_qname_indicates_dnssec(struct module_env* env, struct query_info *qinfo); /** * See if delegation is expected to have DNSSEC information (RRSIGs) in * its answers, or not. Inspects delegation point (name), trust anchors, * and delegation message (DS RRset) to determine this. * @param env: module env with trust anchors. * @param dp: delegation point. * @param msg: delegation message, with DS if a secure referral. * @param dclass: class of query. * @return 1 if dnssec is expected, 0 if not or insecure point above qname. */ int iter_indicates_dnssec(struct module_env* env, struct delegpt* dp, struct dns_msg* msg, uint16_t dclass); /** * See if a message contains DNSSEC. * This is examined by looking for RRSIGs. With DNSSEC a valid answer, * nxdomain, nodata, referral or cname reply has RRSIGs in answer or auth * sections, sigs on answer data, SOA, DS, or NSEC/NSEC3 records. * @param msg: message to examine. * @return true if DNSSEC information was found. */ int iter_msg_has_dnssec(struct dns_msg* msg); /** * See if a message is known to be from a certain zone. * This looks for SOA or NS rrsets, for answers. * For referrals, when one label is delegated, the zone is detected. * Does not look at signatures. * @param msg: the message to inspect. * @param dp: delegation point with zone name to look for. * @param type: type of message. * @param dclass: class of query. * @return true if message is certain to be from zone in dp->name. * false if not sure (empty msg), or not from the zone. */ int iter_msg_from_zone(struct dns_msg* msg, struct delegpt* dp, enum response_type type, uint16_t dclass); /** * Check if two replies are equal * For fallback procedures * @param p: reply one. The reply has rrset data pointers in region. * Does not check rrset-IDs * @param q: reply two * @param region: scratch buffer. * @return if one and two are equal. */ int reply_equal(struct reply_info* p, struct reply_info* q, struct regional* region); /** * Remove unused bits from the reply if possible. * So that caps-for-id (0x20) fallback is more likely to be successful. * This removes like, the additional section, and NS record in the authority * section if those records are gratuitous (not for a referral). * @param rep: the reply to strip stuff out of. */ void caps_strip_reply(struct reply_info* rep); /** * see if reply has a 'useful' rcode for capsforid comparison, so * not SERVFAIL or REFUSED, and thus NOERROR or NXDOMAIN. * @param rep: reply to check. * @return true if the rcode is a bad type of message. */ int caps_failed_rcode(struct reply_info* rep); /** * Store parent-side rrset in separate rrset cache entries for later * last-resort * lookups in case the child-side versions of this information * fails. * @param env: environment with cache, time, ... * @param rrset: the rrset to store (copied). * Failure to store is logged, but otherwise ignored. */ void iter_store_parentside_rrset(struct module_env* env, struct ub_packed_rrset_key* rrset); /** * Store parent-side NS records from a referral message * @param env: environment with cache, time, ... * @param rep: response with NS rrset. * Failure to store is logged, but otherwise ignored. */ void iter_store_parentside_NS(struct module_env* env, struct reply_info* rep); /** * Store parent-side negative element, the parentside rrset does not exist, * creates an rrset with empty rdata in the rrset cache with PARENTSIDE flag. * @param env: environment with cache, time, ... * @param qinfo: the identity of the rrset that is missing. * @param rep: delegation response or answer response, to glean TTL from. * (malloc) failure is logged but otherwise ignored. */ void iter_store_parentside_neg(struct module_env* env, struct query_info* qinfo, struct reply_info* rep); /** * Add parent NS record if that exists in the cache. This is both new * information and acts like a timeout throttle on retries. * @param env: query env with rrset cache and time. * @param dp: delegation point to store result in. Also this dp is used to * see which NS name is needed. * @param region: region to alloc result in. * @param qinfo: pertinent information, the qclass. * @return false on malloc failure. * if true, the routine worked and if such cached information * existed dp->has_parent_side_NS is set true. */ int iter_lookup_parent_NS_from_cache(struct module_env* env, struct delegpt* dp, struct regional* region, struct query_info* qinfo); /** * Add parent-side glue if that exists in the cache. This is both new * information and acts like a timeout throttle on retries to fetch them. * @param env: query env with rrset cache and time. * @param dp: delegation point to store result in. Also this dp is used to * see which NS name is needed. * @param region: region to alloc result in. * @param qinfo: pertinent information, the qclass. * @return: true, it worked, no malloc failures, and new addresses (lame) * have been added, giving extra options as query targets. */ int iter_lookup_parent_glue_from_cache(struct module_env* env, struct delegpt* dp, struct regional* region, struct query_info* qinfo); /** * Lookup next root-hint or root-forward entry. * @param hints: the hints. * @param fwd: the forwards. * @param c: the class to start searching at. 0 means find first one. * @return false if no classes found, true if found and returned in c. */ int iter_get_next_root(struct iter_hints* hints, struct iter_forwards* fwd, uint16_t* c); /** * Remove DS records that are inappropriate before they are cached. * @param msg: the response to scrub. * @param ns: RRSET that is the NS record for the referral. * if NULL, then all DS records are removed from the authority section. * @param z: zone name that the response is from. */ void iter_scrub_ds(struct dns_msg* msg, struct ub_packed_rrset_key* ns, uint8_t* z); /** * Prepare an NXDOMAIN message to be used for a subdomain answer by removing all * RRs from the ANSWER section. * @param msg: the response to scrub. */ void iter_scrub_nxdomain(struct dns_msg* msg); /** * Remove query attempts from all available ips. For 0x20. * @param dp: delegpt. * @param d: decrease. * @param outbound_msg_retry: number of retries of outgoing queries */ void iter_dec_attempts(struct delegpt* dp, int d, int outbound_msg_retry); /** * Add retry counts from older delegpt to newer delegpt. * Does not waste time on timeout'd (or other failing) addresses. * @param dp: new delegationpoint. * @param old: old delegationpoint. * @param outbound_msg_retry: number of retries of outgoing queries */ void iter_merge_retry_counts(struct delegpt* dp, struct delegpt* old, int outbound_msg_retry); /** * See if a DS response (type ANSWER) is too low: a nodata answer with * a SOA record in the authority section at-or-below the qchase.qname. * Also returns true if we are not sure (i.e. empty message, CNAME nosig). * @param msg: the response. * @param dp: the dp name is used to check if the RRSIG gives a clue that * it was originated from the correct nameserver. * @return true if too low. */ int iter_ds_toolow(struct dns_msg* msg, struct delegpt* dp); /** * See if delegpt can go down a step to the qname or not * @param qinfo: the query name looked up. * @param dp: checked if the name can go lower to the qname * @return true if can go down, false if that would not be possible. * the current response seems to be the one and only, best possible, response. */ int iter_dp_cangodown(struct query_info* qinfo, struct delegpt* dp); /** * Lookup if no_cache is set in stub or fwd. * @param qstate: query state with env with hints and fwds. * @param qinf: query name to lookup for. * @param retdpname: returns NULL or the deepest enclosing name of fwd or stub. * This is the name under which the closest lookup is going to happen. * Used for NXDOMAIN checks, above that it is an nxdomain from a * different server and zone. You can pass NULL to not get it. * @param retdpnamelen: returns the length of the dpname. * @param dpname_storage: this is where the dpname buf is stored, if any. * So that caller can manage the buffer. * @param dpname_storage_len: size of dpname_storage buffer. * @return true if no_cache is set in stub or fwd. */ int iter_stub_fwd_no_cache(struct module_qstate *qstate, struct query_info *qinf, uint8_t** retdpname, size_t* retdpnamelen, uint8_t* dpname_storage, size_t dpname_storage_len); /** * Set support for IP4 and IP6 depending on outgoing interfaces * in the outside network. If none, no support, so no use to lookup * the AAAA and then attempt to use it if there is no outgoing-interface * for it. * @param mods: modstack to find iterator module in. * @param env: module env, find iterator module (if one) in there. * @param outnet: outside network structure. */ void iterator_set_ip46_support(struct module_stack* mods, struct module_env* env, struct outside_network* outnet); /** * Read config string that represents the target fetch policy. * @param target_fetch_policy: alloced on return. * @param max_dependency_depth: set on return. * @param str: the config string * @return false on failure. */ int read_fetch_policy(int** target_fetch_policy, int* max_dependency_depth, const char* str); /** * Create caps exempt data structure. * @return NULL on failure. */ struct rbtree_type* caps_white_create(void); /** * Delete caps exempt data structure. * @param caps_white: caps exempt tree. */ void caps_white_delete(struct rbtree_type* caps_white); /** * Apply config caps whitelist items to name tree * @param ntree: caps exempt tree. * @param cfg: config with options. */ int caps_white_apply_cfg(struct rbtree_type* ntree, struct config_file* cfg); /** * Apply config for nat64 * @param nat64: the nat64 state. * @param cfg: config with options. * @return false on failure. */ int nat64_apply_cfg(struct iter_nat64* nat64, struct config_file* cfg); /** * Limit NSEC and NSEC3 TTL in response, RFC9077 * @param msg: dns message, the SOA record ttl is used to restrict ttls * of NSEC and NSEC3 RRsets. If no SOA record, nothing happens. */ void limit_nsec_ttl(struct dns_msg* msg); /** * Make the response minimal. Removed authority and additional section, * that works when there is an answer in the answer section. * @param rep: reply to modify. */ void iter_make_minimal(struct reply_info* rep); #endif /* ITERATOR_ITER_UTILS_H */ unbound-1.25.1/iterator/iter_delegpt.h0000644000175000017500000004252615203270263017370 0ustar wouterwouter/* * iterator/iter_delegpt.h - delegation point with NS and address information. * * Copyright (c) 2007, NLnet Labs. All rights reserved. * * This software is open source. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the NLNET LABS nor the names of its contributors may * be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * \file * * This file implements the Delegation Point. It contains a list of name servers * and their addresses if known. */ #ifndef ITERATOR_ITER_DELEGPT_H #define ITERATOR_ITER_DELEGPT_H #include "util/log.h" struct regional; struct delegpt_ns; struct delegpt_addr; struct dns_msg; struct ub_packed_rrset_key; struct msgreply_entry; /** * Delegation Point. * For a domain name, the NS rrset, and the A and AAAA records for those. */ struct delegpt { /** the domain name of the delegation point. */ uint8_t* name; /** length of the delegation point name */ size_t namelen; /** number of labels in delegation point */ int namelabs; /** the nameservers, names from the NS RRset rdata. */ struct delegpt_ns* nslist; /** the target addresses for delegation */ struct delegpt_addr* target_list; /** the list of usable targets; subset of target_list * the items in this list are not part of the result list. */ struct delegpt_addr* usable_list; /** the list of returned targets; subset of target_list */ struct delegpt_addr* result_list; /** if true, the NS RRset was bogus. All info is bad. */ int bogus; /** if true, the parent-side NS record has been applied: * its names have been added and their addresses can follow later. * Also true if the delegationpoint was created from a delegation * message and thus contains the parent-side-info already. */ uint8_t has_parent_side_NS; /** if true, the delegation point has reached last resort processing * and the parent side information has been possibly added to the * delegation point. * For now this signals that further target lookups will ignore * the configured target-fetch-policy and only resolve on * demand to try and avoid triggering limits at this stage (.i.e, it * is very likely that the A/AAAA queries for the newly added name * servers will not yield new IP addresses and trigger NXNS * countermeasures. */ uint8_t fallback_to_parent_side_NS; /** for assertions on type of delegpt */ uint8_t dp_type_mlc; /** use SSL for upstream query */ uint8_t ssl_upstream; /** use TCP for upstream query */ uint8_t tcp_upstream; /** delegpt from authoritative zone that is locally hosted */ uint8_t auth_dp; /*** no cache */ int no_cache; }; /** * Nameservers for a delegation point. */ struct delegpt_ns { /** next in list */ struct delegpt_ns* next; /** name of nameserver */ uint8_t* name; /** length of name */ size_t namelen; /** number of cache lookups for the name */ int cache_lookup_count; /** * If the name has been resolved. false if not queried for yet. * true if the A, AAAA queries have been generated. * marked true if those queries fail. * and marked true if got4 and got6 are both true. */ int resolved; /** if the ipv4 address is in the delegpt, 0=not, 1=yes 2=negative, * negative means it was done, but no content. */ uint8_t got4; /** if the ipv6 address is in the delegpt, 0=not, 1=yes 2=negative */ uint8_t got6; /** * If the name is parent-side only and thus dispreferred. * Its addresses become dispreferred as well */ uint8_t lame; /** if the parent-side ipv4 address has been looked up (last resort). * Also enabled if a parent-side cache entry exists, or a parent-side * negative-cache entry exists. */ uint8_t done_pside4; /** if the parent-side ipv6 address has been looked up (last resort). * Also enabled if a parent-side cache entry exists, or a parent-side * negative-cache entry exists. */ uint8_t done_pside6; /** the TLS authentication name, (if not NULL) to use. */ char* tls_auth_name; /** the port to use; it should mostly be the default 53 but configured * upstreams can provide nondefault ports. */ int port; }; /** * Address of target nameserver in delegation point. */ struct delegpt_addr { /** next delegation point in results */ struct delegpt_addr* next_result; /** next delegation point in usable list */ struct delegpt_addr* next_usable; /** next delegation point in all targets list */ struct delegpt_addr* next_target; /** delegation point address */ struct sockaddr_storage addr; /** length of addr */ socklen_t addrlen; /** number of attempts for this addr */ int attempts; /** rtt stored here in the selection algorithm */ int sel_rtt; /** if true, the A or AAAA RR was bogus, so this address is bad. * Also check the dp->bogus to see if everything is bogus. */ uint8_t bogus; /** if true, this address is dispreferred: it is a lame IP address */ uint8_t lame; /** if the address is dnsseclame, but this cannot be cached, this * option is useful to mark the address dnsseclame. * This value is not copied in addr-copy and dp-copy. */ uint8_t dnsseclame; /** the TLS authentication name, (if not NULL) to use. */ char* tls_auth_name; }; /** * Create new delegation point. * @param regional: where to allocate it. * @return new delegation point or NULL on error. */ struct delegpt* delegpt_create(struct regional* regional); /** * Create a copy of a delegation point. * @param dp: delegation point to copy. * @param regional: where to allocate it. * @return new delegation point or NULL on error. */ struct delegpt* delegpt_copy(struct delegpt* dp, struct regional* regional); /** * Set name of delegation point. * @param dp: delegation point. * @param regional: where to allocate the name copy. * @param name: name to use. * @return false on error. */ int delegpt_set_name(struct delegpt* dp, struct regional* regional, uint8_t* name); /** * Add a name to the delegation point. * @param dp: delegation point. * @param regional: where to allocate the info. * @param name: domain name in wire format. * @param lame: name is lame, disprefer it. * @param tls_auth_name: TLS authentication name (or NULL). * @param port: port to use for resolved addresses. * @return false on error. */ int delegpt_add_ns(struct delegpt* dp, struct regional* regional, uint8_t* name, uint8_t lame, char* tls_auth_name, int port); /** * Add NS rrset; calls add_ns repeatedly. * @param dp: delegation point. * @param regional: where to allocate the info. * @param ns_rrset: NS rrset. * @param lame: rrset is lame, disprefer it. * @return 0 on alloc error. */ int delegpt_rrset_add_ns(struct delegpt* dp, struct regional* regional, struct ub_packed_rrset_key* ns_rrset, uint8_t lame); /** * Add target address to the delegation point. * @param dp: delegation point. * @param regional: where to allocate the info. * @param name: name for which target was found (must be in nslist). * This name is marked resolved. * @param namelen: length of name. * @param addr: the address. * @param addrlen: the length of addr. * @param bogus: security status for the address, pass true if bogus. * @param lame: address is lame. * @param additions: will be set to 1 if a new address is added * @return false on error. */ int delegpt_add_target(struct delegpt* dp, struct regional* regional, uint8_t* name, size_t namelen, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, int* additions); /** * Add A RRset to delegpt. * @param dp: delegation point. * @param regional: where to allocate the info. * @param rrset: RRset A to add. * @param lame: rrset is lame, disprefer it. * @param additions: will be set to 1 if a new address is added * @return 0 on alloc error. */ int delegpt_add_rrset_A(struct delegpt* dp, struct regional* regional, struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions); /** * Add AAAA RRset to delegpt. * @param dp: delegation point. * @param regional: where to allocate the info. * @param rrset: RRset AAAA to add. * @param lame: rrset is lame, disprefer it. * @param additions: will be set to 1 if a new address is added * @return 0 on alloc error. */ int delegpt_add_rrset_AAAA(struct delegpt* dp, struct regional* regional, struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions); /** * Add any RRset to delegpt. * Does not check for duplicates added. * @param dp: delegation point. * @param regional: where to allocate the info. * @param rrset: RRset to add, NS, A, AAAA. * @param lame: rrset is lame, disprefer it. * @param additions: will be set to 1 if a new address is added * @return 0 on alloc error. */ int delegpt_add_rrset(struct delegpt* dp, struct regional* regional, struct ub_packed_rrset_key* rrset, uint8_t lame, int* additions); /** * Add address to the delegation point. No servername is associated or checked. * @param dp: delegation point. * @param regional: where to allocate the info. * @param addr: the address. * @param addrlen: the length of addr. * @param bogus: if address is bogus. * @param lame: if address is lame. * @param tls_auth_name: TLS authentication name (or NULL). * @param port: the port to use; if -1 the port is taken from addr. * @param additions: will be set to 1 if a new address is added * @return false on error. */ int delegpt_add_addr(struct delegpt* dp, struct regional* regional, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, char* tls_auth_name, int port, int* additions); /** * Find NS record in name list of delegation point. * @param dp: delegation point. * @param name: name of nameserver to look for, uncompressed wireformat. * @param namelen: length of name. * @return the ns structure or NULL if not found. */ struct delegpt_ns* delegpt_find_ns(struct delegpt* dp, uint8_t* name, size_t namelen); /** * Find address record in total list of delegation point. * @param dp: delegation point. * @param addr: address * @param addrlen: length of addr * @return the addr structure or NULL if not found. */ struct delegpt_addr* delegpt_find_addr(struct delegpt* dp, struct sockaddr_storage* addr, socklen_t addrlen); /** * Print the delegation point to the log. For debugging. * @param v: verbosity value that is needed to emit to log. * @param dp: delegation point. */ void delegpt_log(enum verbosity_value v, struct delegpt* dp); /** count NS and number missing for logging */ void delegpt_count_ns(struct delegpt* dp, size_t* numns, size_t* missing); /** count addresses, and number in result and available lists, for logging */ void delegpt_count_addr(struct delegpt* dp, size_t* numaddr, size_t* numres, size_t* numavail); /** * Add all usable targets to the result list. * @param dp: delegation point. */ void delegpt_add_unused_targets(struct delegpt* dp); /** * Count number of missing targets. These are ns names with no resolved flag. * @param dp: delegation point. * @param alllame: if set, check if all the missing targets are lame. * @return number of missing targets (or 0). */ size_t delegpt_count_missing_targets(struct delegpt* dp, int* alllame); /** count total number of targets in dp */ size_t delegpt_count_targets(struct delegpt* dp); /** * Create new delegation point from a dns message * * Note that this method does not actually test to see if the message is an * actual referral. It really is just checking to see if it can construct a * delegation point, so the message could be of some other type (some ANSWER * messages, some CNAME messages, generally.) Note that the resulting * DelegationPoint will contain targets for all "relevant" glue (i.e., * address records whose ownernames match the target of one of the NS * records), so if policy dictates that some glue should be discarded beyond * that, discard it before calling this method. Note that this method will * find "glue" in either the ADDITIONAL section or the ANSWER section. * * @param msg: the dns message, referral. * @param regional: where to allocate delegation point. * @return new delegation point or NULL on alloc error, or if the * message was not appropriate. */ struct delegpt* delegpt_from_message(struct dns_msg* msg, struct regional* regional); /** * Mark negative return in delegation point for specific nameserver. * sets the got4 or got6 to negative, updates the ns->resolved. * @param ns: the nameserver in the delegpt. * @param qtype: A or AAAA (host order). */ void delegpt_mark_neg(struct delegpt_ns* ns, uint16_t qtype); /** * Add negative message to delegation point. * @param dp: delegation point. * @param msg: the message added, marks off A or AAAA from an NS entry. */ void delegpt_add_neg_msg(struct delegpt* dp, struct msgreply_entry* msg); /** * Register the fact that there is no ipv6 and thus AAAAs are not going * to be queried for or be useful. * @param dp: the delegation point. Updated to reflect no ipv6. */ void delegpt_no_ipv6(struct delegpt* dp); /** * Register the fact that there is no ipv4 and thus As are not going * to be queried for or be useful. * @param dp: the delegation point. Updated to reflect no ipv4. */ void delegpt_no_ipv4(struct delegpt* dp); /** * create malloced delegation point, with the given name * @param name: uncompressed wireformat of delegpt name. * @return NULL on alloc failure */ struct delegpt* delegpt_create_mlc(uint8_t* name); /** * free malloced delegation point. * @param dp: must have been created with delegpt_create_mlc, free'd. */ void delegpt_free_mlc(struct delegpt* dp); /** * Set name of delegation point. * @param dp: delegation point. malloced. * @param name: name to use. * @return false on error. */ int delegpt_set_name_mlc(struct delegpt* dp, uint8_t* name); /** * add a name to malloced delegation point. * @param dp: must have been created with delegpt_create_mlc. * @param name: the name to add. * @param lame: the name is lame, disprefer. * @param tls_auth_name: TLS authentication name (or NULL). * @param port: port to use for resolved addresses. * @return false on error. */ int delegpt_add_ns_mlc(struct delegpt* dp, uint8_t* name, uint8_t lame, char* tls_auth_name, int port); /** * add an address to a malloced delegation point. * @param dp: must have been created with delegpt_create_mlc. * @param addr: the address. * @param addrlen: the length of addr. * @param bogus: if address is bogus. * @param lame: if address is lame. * @param tls_auth_name: TLS authentication name (or NULL). * @param port: the port to use; if -1 the port is taken from addr. * @return false on error. */ int delegpt_add_addr_mlc(struct delegpt* dp, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame, char* tls_auth_name, int port); /** * Add target address to the delegation point. * @param dp: must have been created with delegpt_create_mlc. * @param name: name for which target was found (must be in nslist). * This name is marked resolved. * @param namelen: length of name. * @param addr: the address. * @param addrlen: the length of addr. * @param bogus: security status for the address, pass true if bogus. * @param lame: address is lame. * @return false on error. */ int delegpt_add_target_mlc(struct delegpt* dp, uint8_t* name, size_t namelen, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t bogus, uint8_t lame); /** get memory in use by dp */ size_t delegpt_get_mem(struct delegpt* dp); /** * See if the addr is on the result list. * @param dp: delegation point. * @param find: the pointer is searched for on the result list. * @return 1 if found, 0 if not found. */ int delegpt_addr_on_result_list(struct delegpt* dp, struct delegpt_addr* find); /** * Remove the addr from the usable list. * @param dp: the delegation point. * @param del: the addr to remove from the list, the pointer is searched for. */ void delegpt_usable_list_remove_addr(struct delegpt* dp, struct delegpt_addr* del); /** * Add the delegpt_addr back to the result list, if it is not already on * the result list. Also removes it from the usable list. * @param dp: delegation point. * @param a: addr to add, nothing happens if it is already on the result list. * It is removed from the usable list. */ void delegpt_add_to_result_list(struct delegpt* dp, struct delegpt_addr* a); #endif /* ITERATOR_ITER_DELEGPT_H */ unbound-1.25.1/doc/0000755000175000017500000000000015203270272013453 5ustar wouterwouterunbound-1.25.1/doc/unbound.conf.rst0000644000175000017500000061267715203270263016626 0ustar wouterwouter.. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES unbound.conf(5) =============== Synopsis -------- **unbound.conf** Description ----------- **unbound.conf** is used to configure :doc:`unbound(8)`. The utility :doc:`unbound-checkconf(8)` can be used to check ``unbound.conf`` prior to usage. File Format ----------- Whitespace is used to separate keywords. Whitespace indentation is insignificant, but is still recommended for visual clarity. Comments start with ``#`` and last to the end of line. Empty lines are ignored, as is whitespace at the beginning of a line. Attribute keywords end with a colon (``:``) and they are either options or section clauses (group options together). The configuration file is logically divided into **sections** where each section is introduced by a :ref:`section clause`. Example ------- An example minimal config file is shown below; most settings are the defaults. Copy this to ``@ub_conf_file@`` and start the server with: .. code-block:: text $ unbound -c @ub_conf_file@ Stop the server with: .. code-block:: text $ kill `cat @UNBOUND_PIDFILE@` The source distribution contains an extensive :file:`example.conf` file with all the options. .. code-block:: text # unbound.conf(5) config file for unbound(8). server: directory: "@UNBOUND_RUN_DIR@" username: unbound # make sure unbound can access entropy from inside the chroot. # e.g. on linux the use these commands (on BSD, devfs(8) is used): # mount --bind -n /dev/urandom @UNBOUND_RUN_DIR@/dev/urandom # and mount --bind -n /dev/log @UNBOUND_RUN_DIR@/dev/log chroot: "@UNBOUND_CHROOT_DIR@" # logfile: "@UNBOUND_RUN_DIR@/unbound.log" #uncomment to use logfile. pidfile: "@UNBOUND_PIDFILE@" # verbosity: 1 # uncomment and increase to get more logging. # listen on all interfaces, answer queries from the local subnet. interface: 0.0.0.0 interface: ::0 access-control: 10.0.0.0/8 allow access-control: 2001:db8::/64 allow .. _unbound.conf.clauses: Section Clauses --------------- The recognized section clauses are: :ref:`server:` Most of the configuration is found in this section. :ref:`remote-control:` Configuration for the facility used by :doc:`unbound-control(8)`. :ref:`stub-zone:` Configuration for a zone that redirects to specific authoritative name servers, e.g. for zones not generally available on the greater Internet. :ref:`forward-zone:` Configuration for a zone that forwards to specific DNS resolvers. :ref:`auth-zone:` Configuration for local authoritative zones. :ref:`view:` Overriding a small subset of configuration for incoming requests. Requests are mapped to views with :ref:`access-control-view` and :ref:`interface-view`. :ref:`python:` Configuration for the optional ``python`` script module. :ref:`dynlib:` Configuration for the optional ``dynlib`` module that loads dynamic libraries into Unbound. :ref:`dnscrypt:` Configuration for the optional DNSCrypt feature. :ref:`cachedb:` Configuration for the optional ``cachedb`` module that can interface with second level caches, currently Redis or Redis-complatible databases. :ref:`dnstap:` Configuration of the optional dnstap logging feature; a flexible, structured binary log format for DNS software. :ref:`rpz:` Configuration for Response Policy Zones that allows for DNS filtering. Requires the ``respip`` module. Section clauses can be repeated throughout the file (or included files) to logically group options in one visually cohesive group. This may be particularly useful for the ``server:`` clause with its myriad of options. .. _unbound.conf.include: Including Files --------------- Files can be included using the ``include:`` directive. It can appear anywhere, it accepts a single file name as argument. Processing continues as if the text from the included file was copied into the config file at that point. If also using :ref:`chroot`, using full path names for the included files works, relative pathnames for the included names work if the directory where the daemon is started equals its chroot/working directory or is specified before the include statement with :ref:`directory: dir`. Wildcards can be used to include multiple files, see *glob(7)*. .. _unbound.conf.include-toplevel: For a more structural include option, the ``include-toplevel:`` directive can be used. This closes whatever section clause is currently active (if any) and forces the use of section clauses in the included files and right after this directive. .. _unbound.conf.server: Server Options -------------- These options are part of the ``server:`` section. @@UAHL@unbound.conf@verbosity@@: ** The verbosity level. Level 0 No verbosity, only errors. Level 1 Gives operational information. Level 2 Gives detailed operational information including short information per query. Level 3 Gives query level information, output per query. Level 4 Gives algorithm level information. Level 5 Logs client identification for cache misses. The verbosity can also be increased from the command line and during run time via remote control. See :doc:`unbound(8)` and :doc:`unbound-control(8)` respectively. Default: 1 @@UAHL@unbound.conf@statistics-interval@@: ** The number of seconds between printing statistics to the log for every thread. Disable with value ``0`` or ``""``. The histogram statistics are only printed if replies were sent during the statistics interval, requestlist statistics are printed for every interval (but can be 0). This is because the median calculation requires data to be present. Default: 0 (disabled) @@UAHL@unbound.conf@statistics-cumulative@@: ** If enabled, statistics are cumulative since starting Unbound, without clearing the statistics counters after logging the statistics. Default: no @@UAHL@unbound.conf@extended-statistics@@: ** If enabled, extended statistics are printed from :doc:`unbound-control(8)`. The counters are listed in :doc:`unbound-control(8)`. Keeping track of more statistics takes time. Default: no @@UAHL@unbound.conf@statistics-inhibit-zero@@: ** If enabled, selected extended statistics with a value of 0 are inhibited from printing with :doc:`unbound-control(8)`. These are query types, query classes, query opcodes, answer rcodes (except NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMPL, REFUSED) and PRZ actions. Default: yes @@UAHL@unbound.conf@num-threads@@: ** The number of threads to create to serve clients. Use 1 for no threading. Default: 1 @@UAHL@unbound.conf@port@@: ** The port number on which the server responds to queries. Default: 53 @@UAHL@unbound.conf@interface@@: ** Interface to use to connect to the network. This interface is listened to for queries from clients, and answers to clients are given from it. Can be given multiple times to work on several interfaces. If none are given the default is to listen on localhost. If an interface name is used instead of an IP address, the list of IP addresses on that interface are used. The interfaces are not changed on a reload (``kill -HUP``) but only on restart. A port number can be specified with @port (without spaces between interface and port number), if not specified the default port (from :ref:`port`) is used. @@UAHL@unbound.conf@ip-address@@: ** Same as :ref:`interface` (for ease of compatibility with :external+nsd:doc:`manpages/nsd.conf`). @@UAHL@unbound.conf@interface-automatic@@: ** Listen on all addresses on all (current and future) interfaces, detect the source interface on UDP queries and copy them to replies. This is a lot like :ref:`ip-transparent`, but this option services all interfaces whilst with :ref:`ip-transparent` you can select which (future) interfaces Unbound provides service on. This feature is experimental, and needs support in your OS for particular socket options. Default: no @@UAHL@unbound.conf@interface-automatic-ports@@: *""* List the port numbers that :ref:`interface-automatic` listens on. If empty, the default port is listened on. The port numbers are separated by spaces in the string. This can be used to have interface automatic to deal with the interface, and listen on the normal port number, by including it in the list, and also HTTPS or DNS-over-TLS port numbers by putting them in the list as well. Default: "" @@UAHL@unbound.conf@outgoing-interface@@: ** Interface to use to connect to the network. This interface is used to send queries to authoritative servers and receive their replies. Can be given multiple times to work on several interfaces. If none are given the default (all) is used. You can specify the same interfaces in :ref:`interface` and :ref:`outgoing-interface` lines, the interfaces are then used for both purposes. Outgoing queries are sent via a random outgoing interface to counter spoofing. If an IPv6 netblock is specified instead of an individual IPv6 address, outgoing UDP queries will use a randomised source address taken from the netblock to counter spoofing. Requires the IPv6 netblock to be routed to the host running Unbound, and requires OS support for unprivileged non-local binds (currently only supported on Linux). Several netblocks may be specified with multiple :ref:`outgoing-interface` options, but do not specify both an individual IPv6 address and an IPv6 netblock, or the randomisation will be compromised. Consider combining with :ref:`prefer-ip6: yes` to increase the likelihood of IPv6 nameservers being selected for queries. On Linux you need these two commands to be able to use the freebind socket option to receive traffic for the ip6 netblock: .. code-block:: text ip -6 addr add mynetblock/64 dev lo && \ ip -6 route add local mynetblock/64 dev lo @@UAHL@unbound.conf@outgoing-range@@: ** Number of ports to open. This number of file descriptors can be opened per thread. Must be at least 1. Default depends on compile options. Larger numbers need extra resources from the operating system. For performance a very large value is best, use libevent to make this possible. Should be higher (preferably double) than the value of :ref:`num-queries-per-thread` to account for cases where the request list is full and avoid file descriptor starvation. Default: 4096 (libevent) / 960 (minievent) / 48 (windows) @@UAHL@unbound.conf@outgoing-port-permit@@: ** Permit Unbound to open this port or range of ports for use to send queries. A larger number of permitted outgoing ports increases resilience against spoofing attempts. Make sure these ports are not needed by other daemons. By default only ports above 1024 that have not been assigned by IANA are used. Give a port number or a range of the form "low-high", without spaces. The :ref:`outgoing-port-permit` and :ref:`outgoing-port-avoid` statements are processed in the line order of the config file, adding the permitted ports and subtracting the avoided ports from the set of allowed ports. The processing starts with the non IANA allocated ports above 1024 in the set of allowed ports. @@UAHL@unbound.conf@outgoing-port-avoid@@: ** Do not permit Unbound to open this port or range of ports for use to send queries. Use this to make sure Unbound does not grab a port that another daemon needs. The port is avoided on all outgoing interfaces, both IPv4 and IPv6. By default only ports above 1024 that have not been assigned by IANA are used. Give a port number or a range of the form "low-high", without spaces. @@UAHL@unbound.conf@outgoing-num-tcp@@: ** Number of outgoing TCP buffers to allocate per thread. If set to 0, or if :ref:`do-tcp: no` is set, no TCP queries to authoritative servers are done. For larger installations increasing this value is a good idea. Default: 10 @@UAHL@unbound.conf@incoming-num-tcp@@: ** Number of incoming TCP buffers to allocate per thread. If set to 0, or if :ref:`do-tcp: no` is set, no TCP queries from clients are accepted. For larger installations increasing this value is a good idea. Default: 10 @@UAHL@unbound.conf@edns-buffer-size@@: ** Number of bytes size to advertise as the EDNS reassembly buffer size. This is the value put into datagrams over UDP towards peers. The actual buffer size is determined by :ref:`msg-buffer-size` (both for TCP and UDP). Do not set higher than that value. Setting to 512 bypasses even the most stringent path MTU problems, but is seen as extreme, since the amount of TCP fallback generated is excessive (probably also for this resolver, consider tuning :ref:`outgoing-num-tcp`). Default: 1232 (`DNS Flag Day 2020 recommendation `__) @@UAHL@unbound.conf@max-udp-size@@: ** Maximum UDP response size (not applied to TCP response). 65536 disables the UDP response size maximum, and uses the choice from the client, always. Suggested values are 512 to 4096. Default: 1232 (same as :ref:`edns-buffer-size`) @@UAHL@unbound.conf@stream-wait-size@@: ** Number of bytes size maximum to use for waiting stream buffers. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). As TCP and TLS streams queue up multiple results, the amount of memory used for these buffers does not exceed this number, otherwise the responses are dropped. This manages the total memory usage of the server (under heavy use), the number of requests that can be queued up per connection is also limited, with further requests waiting in TCP buffers. Default: 4m @@UAHL@unbound.conf@msg-buffer-size@@: ** Number of bytes size of the message buffers. Default is 65552 bytes, enough for 64 Kb packets, the maximum DNS message size. No message larger than this can be sent or received. Can be reduced to use less memory, but some requests for DNS data, such as for huge resource records, will result in a SERVFAIL reply to the client. Default: 65552 @@UAHL@unbound.conf@msg-cache-size@@: ** Number of bytes size of the message cache. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 4m @@UAHL@unbound.conf@msg-cache-slabs@@: ** Number of slabs in the message cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@num-queries-per-thread@@: ** The number of queries that every thread will service simultaneously. If more queries arrive that need servicing, and no queries can be jostled out (see :ref:`jostle-timeout`), then the queries are dropped. This forces the client to resend after a timeout; allowing the server time to work on the existing queries. Default depends on compile options. Default: 2048 (libevent) / 512 (minievent) / 24 (windows) @@UAHL@unbound.conf@jostle-timeout@@: ** Timeout used when the server is very busy. Set to a value that usually results in one roundtrip to the authority servers. If too many queries arrive, then 50% of the queries are allowed to run to completion, and the other 50% are replaced with the new incoming query if they have already spent more than their allowed time. This protects against denial of service by slow queries or high query rates. The effect is that the qps for long-lasting queries is about: .. code-block:: text (num-queries-per-thread / 2) / (average time for such long queries) qps The qps for short queries can be about: .. code-block:: text (num-queries-per-thread / 2) / (jostle-timeout in whole seconds) qps per thread about (2048/2)*5 = 5120 qps by default. Default: 200 @@UAHL@unbound.conf@delay-close@@: ** Extra delay for timeouted UDP ports before they are closed, in msec. This prevents very delayed answer packets from the upstream (recursive) servers from bouncing against closed ports and setting off all sort of close-port counters, with eg. 1500 msec. When timeouts happen you need extra sockets, it checks the ID and remote IP of packets, and unwanted packets are added to the unwanted packet counter. Default: 0 (disabled) @@UAHL@unbound.conf@udp-connect@@: ** Perform *connect(2)* for UDP sockets that mitigates ICMP side channel leakage. Default: yes @@UAHL@unbound.conf@unknown-server-time-limit@@: ** The wait time in msec for waiting for an unknown server to reply. Increase this if you are behind a slow satellite link, to eg. 1128. That would then avoid re-querying every initial query because it times out. Default: 376 @@UAHL@unbound.conf@discard-timeout@@: ** The wait time in msec where recursion requests are dropped. This is to stop a large number of replies from accumulating. They receive no reply, the work item continues to recurse. For UDP the replies are dropped, for stream connections the reply is not dropped if the stream connection is still open ready to receive answers. It is nice to be a bit larger than :ref:`serve-expired-client-timeout` if that is enabled. A value of ``1900`` msec is suggested. The value ``0`` disables it. Default: 1900 @@UAHL@unbound.conf@wait-limit@@: ** The number of replies that can wait for recursion, for an IP address. This makes a ratelimit per IP address of waiting replies for recursion. It stops very large amounts of queries waiting to be returned to one destination. The value ``0`` disables all wait limits. Default: 1000 @@UAHL@unbound.conf@wait-limit-cookie@@: ** The number of replies that can wait for recursion, for an IP address that sent the query with a valid DNS Cookie. Since the cookie already validates the client address, this option allows to override a configured :ref:`wait-limit` value usually with a higher one for cookie validated queries. The value ``0`` disables wait limits for cookie validated queries. Default: 10000 @@UAHL@unbound.conf@wait-limit-netblock@@: ** ** The wait limit for the netblock. If not given the :ref:`wait-limit` value is used. The most specific netblock is used to determine the limit. Useful for overriding the default for a specific, group or individual, server. The value ``-1`` disables wait limits for the netblock. By default the loopback has a wait limit netblock of ``-1``, it is not limited, because it is separated from the rest of network for spoofed packets. The loopback addresses ``127.0.0.0/8`` and ``::1/128`` are default at ``-1``. Default: (none) @@UAHL@unbound.conf@wait-limit-cookie-netblock@@: ** ** The wait limit for the netblock, when the query has a DNS Cookie. If not given, the :ref:`wait-limit-cookie` value is used. The value ``-1`` disables wait limits for the netblock. The loopback addresses ``127.0.0.0/8`` and ``::1/128`` are default at ``-1``. Default: (none) @@UAHL@unbound.conf@so-rcvbuf@@: ** If not 0, then set the SO_RCVBUF socket option to get more buffer space on UDP port 53 incoming queries. So that short spikes on busy servers do not drop packets (see counter in ``netstat -su``). Otherwise, the number of bytes to ask for, try "4m" on a busy server. The OS caps it at a maximum, on linux Unbound needs root permission to bypass the limit, or the admin can use ``sysctl net.core.rmem_max``. On BSD change ``kern.ipc.maxsockbuf`` in ``/etc/sysctl.conf``. On OpenBSD change header and recompile kernel. On Solaris ``ndd -set /dev/udp udp_max_buf 8388608``. Default: 0 (use system value) @@UAHL@unbound.conf@so-sndbuf@@: ** If not 0, then set the SO_SNDBUF socket option to get more buffer space on UDP port 53 outgoing queries. This for very busy servers handles spikes in answer traffic, otherwise: .. code-block:: text send: resource temporarily unavailable can get logged, the buffer overrun is also visible by ``netstat -su``. If set to 0 it uses the system value. Specify the number of bytes to ask for, try "8m" on a very busy server. It needs some space to be able to deal with packets that wait for local address resolution, from like ARP and NDP discovery, before they are sent out, hence it is elevated above the system default by default. The OS caps it at a maximum, on linux Unbound needs root permission to bypass the limit, or the admin can use ``sysctl net.core.wmem_max``. On BSD, Solaris changes are similar to :ref:`so-rcvbuf`. Default: 4m @@UAHL@unbound.conf@so-reuseport@@: ** If yes, then open dedicated listening sockets for incoming queries for each thread and try to set the SO_REUSEPORT socket option on each socket. May distribute incoming queries to threads more evenly. On Linux it is supported in kernels >= 3.9. On other systems, FreeBSD, OSX it may also work. You can enable it (on any platform and kernel), it then attempts to open the port and passes the option if it was available at compile time, if that works it is used, if it fails, it continues silently (unless verbosity 3) without the option. At extreme load it could be better to turn it off to distribute the queries evenly, reported for Linux systems (4.4.x). Default: yes @@UAHL@unbound.conf@ip-transparent@@: ** If yes, then use IP_TRANSPARENT socket option on sockets where Unbound is listening for incoming traffic. Allows you to bind to non-local interfaces. For example for non-existent IP addresses that are going to exist later on, with host failover configuration. This is a lot like :ref:`interface-automatic`, but that one services all interfaces and with this option you can select which (future) interfaces Unbound provides service on. This option needs Unbound to be started with root permissions on some systems. The option uses IP_BINDANY on FreeBSD systems and SO_BINDANY on OpenBSD systems. Default: no @@UAHL@unbound.conf@ip-freebind@@: ** If yes, then use IP_FREEBIND socket option on sockets where Unbound is listening to incoming traffic. Allows you to bind to IP addresses that are nonlocal or do not exist, like when the network interface or IP address is down. Exists only on Linux, where the similar :ref:`ip-transparent` option is also available. Default: no @@UAHL@unbound.conf@ip-dscp@@: ** The value of the Differentiated Services Codepoint (DSCP) in the differentiated services field (DS) of the outgoing IP packet headers. The field replaces the outdated IPv4 Type-Of-Service field and the IPv6 traffic class field. @@UAHL@unbound.conf@rrset-cache-size@@: ** Number of bytes size of the RRset cache. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 4m @@UAHL@unbound.conf@rrset-cache-slabs@@: ** Number of slabs in the RRset cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@cache-max-ttl@@: ** Time to live maximum for RRsets and messages in the cache. When the TTL expires, the cache item has expired. Can be set lower to force the resolver to query for data often, and not trust (very large) TTL values. Downstream clients also see the lower TTL. Default: 86400 (1 day) @@UAHL@unbound.conf@cache-min-ttl@@: ** Time to live minimum for RRsets and messages in the cache. If the minimum kicks in, the data is cached for longer than the domain owner intended, and thus less queries are made to look up the data. Zero makes sure the data in the cache is as the domain owner intended, higher values, especially more than an hour or so, can lead to trouble as the data in the cache does not match up with the actual data any more. Default: 0 (disabled) @@UAHL@unbound.conf@cache-max-negative-ttl@@: ** Time to live maximum for negative responses, these have a SOA in the authority section that is limited in time. This applies to NXDOMAIN and NODATA answers. Default: 3600 @@UAHL@unbound.conf@cache-min-negative-ttl@@: ** Time to live minimum for negative responses, these have a SOA in the authority section that is limited in time. If this is disabled and :ref:`cache-min-ttl` is configured, it will take effect instead. In that case you can set this to ``1`` to honor the upstream TTL. This applies to NXDOMAIN and NODATA answers. Default: 0 (disabled) @@UAHL@unbound.conf@infra-host-ttl@@: ** Time to live for entries in the host cache. The host cache contains roundtrip timing, lameness and EDNS support information. Default: 900 @@UAHL@unbound.conf@infra-cache-slabs@@: ** Number of slabs in the infrastructure cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@infra-cache-numhosts@@: ** Number of hosts for which information is cached. Default: 10000 @@UAHL@unbound.conf@infra-cache-min-rtt@@: ** Lower limit for dynamic retransmit timeout calculation in infrastructure cache. Increase this value if using forwarders needing more time to do recursive name resolution. Default: 50 @@UAHL@unbound.conf@infra-cache-max-rtt@@: ** Upper limit for dynamic retransmit timeout calculation in infrastructure cache. Default: 120000 (2 minutes) @@UAHL@unbound.conf@infra-keep-probing@@: ** If enabled the server keeps probing hosts that are down, in the one probe at a time regime. Hosts that are down, eg. they did not respond during the one probe at a time period, are marked as down and it may take :ref:`infra-host-ttl` time to get probed again. Default: no @@UAHL@unbound.conf@define-tag@@: *""* Define the tags that can be used with :ref:`local-zone` and :ref:`access-control`. Enclose the list between quotes (``""``) and put spaces between tags. @@UAHL@unbound.conf@do-ip4@@: ** Enable or disable whether IPv4 queries are answered or issued. Default: yes @@UAHL@unbound.conf@do-ip6@@: ** Enable or disable whether IPv6 queries are answered or issued. If disabled, queries are not answered on IPv6, and queries are not sent on IPv6 to the internet nameservers. With this option you can disable the IPv6 transport for sending DNS traffic, it does not impact the contents of the DNS traffic, which may have IPv4 (A) and IPv6 (AAAA) addresses in it. Default: yes @@UAHL@unbound.conf@prefer-ip4@@: ** If enabled, prefer IPv4 transport for sending DNS queries to internet nameservers. Useful if the IPv6 netblock the server has, the entire /64 of that is not owned by one operator and the reputation of the netblock /64 is an issue, using IPv4 then uses the IPv4 filters that the upstream servers have. Default: no @@UAHL@unbound.conf@prefer-ip6@@: ** If enabled, prefer IPv6 transport for sending DNS queries to internet nameservers. Default: no @@UAHL@unbound.conf@do-udp@@: ** Enable or disable whether UDP queries are answered or issued. Default: yes @@UAHL@unbound.conf@do-tcp@@: ** Enable or disable whether TCP queries are answered or issued. Default: yes @@UAHL@unbound.conf@tcp-mss@@: ** Maximum segment size (MSS) of TCP socket on which the server responds to queries. Value lower than common MSS on Ethernet (1220 for example) will address path MTU problem. Note that not all platform supports socket option to set MSS (TCP_MAXSEG). Default is system default MSS determined by interface MTU and negotiation between server and client. @@UAHL@unbound.conf@outgoing-tcp-mss@@: ** Maximum segment size (MSS) of TCP socket for outgoing queries (from Unbound to other servers). Value lower than common MSS on Ethernet (1220 for example) will address path MTU problem. Note that not all platform supports socket option to set MSS (TCP_MAXSEG). Default is system default MSS determined by interface MTU and negotiation between Unbound and other servers. @@UAHL@unbound.conf@tcp-idle-timeout@@: ** The period Unbound will wait for a query on a TCP connection. If this timeout expires Unbound closes the connection. When the number of free incoming TCP buffers falls below 50% of the total number configured, the option value used is progressively reduced, first to 1% of the configured value, then to 0.2% of the configured value if the number of free buffers falls below 35% of the total number configured, and finally to 0 if the number of free buffers falls below 20% of the total number configured. A minimum timeout of 200 milliseconds is observed regardless of the option value used. It will be overridden by :ref:`edns-tcp-keepalive-timeout` if :ref:`edns-tcp-keepalive` is enabled. Default: 30000 (30 seconds) @@UAHL@unbound.conf@tcp-reuse-timeout@@: ** The period Unbound will keep TCP persistent connections open to authority servers. Default: 60000 (60 seconds) @@UAHL@unbound.conf@max-reuse-tcp-queries@@: ** The maximum number of queries that can be sent on a persistent TCP connection. Default: 200 @@UAHL@unbound.conf@tcp-auth-query-timeout@@: ** Timeout in milliseconds for TCP queries to auth servers. Default: 3000 (3 seconds) @@UAHL@unbound.conf@edns-tcp-keepalive@@: ** Enable or disable EDNS TCP Keepalive. Default: no @@UAHL@unbound.conf@edns-tcp-keepalive-timeout@@: ** Overrides :ref:`tcp-idle-timeout` when :ref:`edns-tcp-keepalive` is enabled. If the client supports the EDNS TCP Keepalive option, If the client supports the EDNS TCP Keepalive option, Unbound sends the timeout value to the client to encourage it to close the connection before the server times out. Default: 120000 (2 minutes) @@UAHL@unbound.conf@sock-queue-timeout@@: ** UDP queries that have waited in the socket buffer for a long time can be dropped. The time is set in seconds, 3 could be a good value to ignore old queries that likely the client does not need a reply for any more. This could happen if the host has not been able to service the queries for a while, i.e. Unbound is not running, and then is enabled again. It uses timestamp socket options. The socket option is available on the Linux and FreeBSD platforms. Default: 0 (disabled) @@UAHL@unbound.conf@tcp-upstream@@: ** Enable or disable whether the upstream queries use TCP only for transport. Useful in tunneling scenarios. If set to no you can specify TCP transport only for selected forward or stub zones using :ref:`forward-tcp-upstream` or :ref:`stub-tcp-upstream` respectively. Default: no @@UAHL@unbound.conf@udp-upstream-without-downstream@@: ** Enable UDP upstream even if :ref:`do-udp: no` is set. Useful for TLS service providers, that want no UDP downstream but use UDP to fetch data upstream. Default: no (no changes) @@UAHL@unbound.conf@tls-upstream@@: ** Enabled or disable whether the upstream queries use TLS only for transport. Useful in tunneling scenarios. The TLS contains plain DNS in TCP wireformat. The other server must support this (see :ref:`tls-service-key`). If you enable this, also configure a :ref:`tls-cert-bundle` or use :ref:`tls-win-cert` or :ref:`tls-system-cert` to load CA certs, otherwise the connections cannot be authenticated. This option enables TLS for all of them, but if you do not set this you can configure TLS specifically for some forward zones with :ref:`forward-tls-upstream`. And also with :ref:`stub-tls-upstream`. If the :ref:`tls-upstream` option is enabled, it is for all the forwards and stubs, where the :ref:`forward-tls-upstream` and :ref:`stub-tls-upstream` options are ignored, as if they had been set to yes. Default: no @@UAHL@unbound.conf@ssl-upstream@@: ** Alternate syntax for :ref:`tls-upstream`. If both are present in the config file the last is used. @@UAHL@unbound.conf@tls-service-key@@: ** If enabled, the server provides DNS-over-TLS or DNS-over-HTTPS service on the TCP ports marked implicitly or explicitly for these services with :ref:`tls-port` or :ref:`https-port`. The file must contain the private key for the TLS session, the public certificate is in the :ref:`tls-service-pem` file and it must also be specified if :ref:`tls-service-key` is specified. If the key is stored with root permissions or outside of chroot, then a change or enabling or disabling requires a restart (a reload is not enough). But if the key file (and tls-service-pem file) are accessible, then they are read in on reload, and fast_reload. The server checks the modification time of the file (and the filename) to see if the file has changed for reload. The ports enabled implicitly or explicitly via :ref:`tls-port` and :ref:`https-port` do not provide normal DNS TCP service. .. note:: Unbound needs to be compiled with libnghttp2 in order to provide DNS-over-HTTPS. Default: "" (disabled) @@UAHL@unbound.conf@ssl-service-key@@: ** Alternate syntax for :ref:`tls-service-key`. @@UAHL@unbound.conf@tls-service-pem@@: ** The public key certificate pem file for the tls service. Default: "" (disabled) @@UAHL@unbound.conf@ssl-service-pem@@: ** Alternate syntax for :ref:`tls-service-pem`. @@UAHL@unbound.conf@tls-port@@: ** The port number on which to provide TCP TLS service. Only interfaces configured with that port number as @number get the TLS service. Default: 853 @@UAHL@unbound.conf@ssl-port@@: ** Alternate syntax for :ref:`tls-port`. @@UAHL@unbound.conf@tls-cert-bundle@@: ** If null or ``""``, no file is used. Set it to the certificate bundle file, for example :file:`/etc/pki/tls/certs/ca-bundle.crt`. These certificates are used for authenticating connections made to outside peers. For example :ref:`auth-zone urls`, and also DNS-over-TLS connections. It is read at start up before permission drop and chroot. Default: "" (disabled) @@UAHL@unbound.conf@ssl-cert-bundle@@: ** Alternate syntax for :ref:`tls-cert-bundle`. @@UAHL@unbound.conf@tls-win-cert@@: ** Add the system certificates to the cert bundle certificates for authentication. If no cert bundle, it uses only these certificates. On windows this option uses the certificates from the cert store. Use the :ref:`tls-cert-bundle` option on other systems. On other systems, this option enables the system certificates. Default: no @@UAHL@unbound.conf@tls-system-cert@@: ** This the same as the :ref:`tls-win-cert` option, under a different name. Because it is not windows specific. @@UAHL@unbound.conf@tls-additional-port@@: ** List port numbers as :ref:`tls-additional-port`, and when interfaces are defined, eg. with the @port suffix, as this port number, they provide DNS-over-TLS service. Can list multiple, each on a new statement. @@UAHL@unbound.conf@tls-session-ticket-keys@@: ** If not ``""``, lists files with 80 bytes of random contents that are used to perform TLS session resumption for clients using the Unbound server. These files contain the secret key for the TLS session tickets. First key use to encrypt and decrypt TLS session tickets. Other keys use to decrypt only. With this you can roll over to new keys, by generating a new first file and allowing decrypt of the old file by listing it after the first file for some time, after the wait clients are not using the old key any more and the old key can be removed. One way to create the file is: .. code-block:: text dd if=/dev/random bs=1 count=80 of=ticket.dat The first 16 bytes should be different from the old one if you create a second key, that is the name used to identify the key. Then there is 32 bytes random data for an AES key and then 32 bytes random data for the HMAC key. Default: "" @@UAHL@unbound.conf@tls-ciphers@@: ** Set the list of ciphers to allow when serving TLS. Use ``""`` for default ciphers. Default: "" @@UAHL@unbound.conf@tls-ciphersuites@@: ** Set the list of ciphersuites to allow when serving TLS. This is for newer TLS 1.3 connections. Use ``""`` for default ciphersuites. Default: "" @@UAHL@unbound.conf@tls-use-sni@@: ** Enable or disable sending the SNI extension on TLS connections. .. note:: Changing the value requires a restart. Default: yes @@UAHL@unbound.conf@tls-protocols@@: *""* Specify the allowed TLS protocol versions to use, in no particular order. Possible values are ``TLSv1.2`` and ``TLSv1.3``. Enclose list of protocols in quotes (``""``) and put spaces between them. .. note:: Changing the value requires a restart. Default: "TLSv1.2 TLSv1.3" @@UAHL@unbound.conf@pad-responses@@: ** If enabled, TLS serviced queries that contained an EDNS Padding option will cause responses padded to the closest multiple of the size specified in :ref:`pad-responses-block-size`. Default: yes @@UAHL@unbound.conf@pad-responses-block-size@@: ** The block size with which to pad responses serviced over TLS. Only responses to padded queries will be padded. Default: 468 @@UAHL@unbound.conf@pad-queries@@: ** If enabled, all queries sent over TLS upstreams will be padded to the closest multiple of the size specified in :ref:`pad-queries-block-size`. Default: yes @@UAHL@unbound.conf@pad-queries-block-size@@: ** The block size with which to pad queries sent over TLS upstreams. Default: 128 @@UAHL@unbound.conf@https-port@@: ** The port number on which to provide DNS-over-HTTPS service. Only interfaces configured with that port number as @number get the HTTPS service. Default: 443 @@UAHL@unbound.conf@http-endpoint@@: ** The HTTP endpoint to provide DNS-over-HTTPS service on. Default: /dns-query @@UAHL@unbound.conf@http-max-streams@@: ** Number used in the SETTINGS_MAX_CONCURRENT_STREAMS parameter in the HTTP/2 SETTINGS frame for DNS-over-HTTPS connections. Default: 100 @@UAHL@unbound.conf@http-query-buffer-size@@: ** Maximum number of bytes used for all HTTP/2 query buffers combined. These buffers contain (partial) DNS queries waiting for request stream completion. An RST_STREAM frame will be send to streams exceeding this limit. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 4m @@UAHL@unbound.conf@http-response-buffer-size@@: ** Maximum number of bytes used for all HTTP/2 response buffers combined. These buffers contain DNS responses waiting to be written back to the clients. An RST_STREAM frame will be send to streams exceeding this limit. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 4m @@UAHL@unbound.conf@http-nodelay@@: ** Set TCP_NODELAY socket option on sockets used to provide DNS-over-HTTPS service. Ignored if the option is not available. Default: yes @@UAHL@unbound.conf@http-notls-downstream@@: ** Disable use of TLS for the downstream DNS-over-HTTP connections. Useful for local back end servers. Default: no @@UAHL@unbound.conf@proxy-protocol-port@@: ** List port numbers as :ref:`proxy-protocol-port`, and when interfaces are defined, eg. with the @port suffix, as this port number, they support and expect PROXYv2. In this case the proxy address will only be used for the network communication and initial ACL (check if the proxy itself is denied/refused by configuration). The proxied address (if any) will then be used as the true client address and will be used where applicable for logging, ACL, DNSTAP, RPZ and IP ratelimiting. PROXYv2 is supported for UDP and TCP/TLS listening interfaces. There is no support for PROXYv2 on a DoH, DoQ or DNSCrypt listening interface. Can list multiple, each on a new statement. @@UAHL@unbound.conf@quic-port@@: ** The port number on which to provide DNS-over-QUIC service. Only interfaces configured with that port number as @number get the QUIC service. The interface uses QUIC for the UDP traffic on that port number. If it is set to 0, the server does not init QUIC code, and QUIC is disabled. This is similar to if QUIC is not in use, but then explicitly. Default: 853 @@UAHL@unbound.conf@quic-size@@: ** Maximum number of bytes for all QUIC buffers and data combined. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). New connections receive connection refused when the limit is exceeded. New streams are reset when the limit is exceeded. Default: 8m @@UAHL@unbound.conf@use-systemd@@: ** Enable or disable systemd socket activation. Default: no @@UAHL@unbound.conf@do-daemonize@@: ** Enable or disable whether the Unbound server forks into the background as a daemon. Set the value to no when Unbound runs as systemd service. Default: yes @@UAHL@unbound.conf@tcp-connection-limit@@: * * Allow up to limit simultaneous TCP connections from the given netblock. When at the limit, further connections are accepted but closed immediately. This option is experimental at this time. Default: (disabled) @@UAHL@unbound.conf@access-control@@: * * Specify treatment of incoming queries from their originating IP address. Queries can be allowed to have access to this server that gives DNS answers, or refused, with other actions possible. The IP address range can be specified as a netblock, it is possible to give the statement several times in order to specify the treatment of different netblocks. The netblock is given as an IPv4 or IPv6 address with /size appended for a classless network block. The most specific netblock match is used, if none match :ref:`refuse` is used. The order of the access-control statements therefore does not matter. The action can be :ref:`deny`, :ref:`refuse`, :ref:`allow`, :ref:`allow_setrd`, :ref:`allow_snoop`, :ref:`allow_cookie`, :ref:`deny_non_local` or :ref:`refuse_non_local`. @@UAHL@unbound.conf.access-control.action@deny@@ Stops queries from hosts from that netblock. @@UAHL@unbound.conf.access-control.action@refuse@@ Stops queries too, but sends a DNS rcode REFUSED error message back. @@UAHL@unbound.conf.access-control.action@allow@@ Gives access to clients from that netblock. It gives only access for recursion clients (which is what almost all clients need). Non-recursive queries are refused. The :ref:`allow` action does allow non-recursive queries to access the local-data that is configured. The reason is that this does not involve the Unbound server recursive lookup algorithm, and static data is served in the reply. This supports normal operations where non-recursive queries are made for the authoritative data. For non-recursive queries any replies from the dynamic cache are refused. @@UAHL@unbound.conf.access-control.action@allow_setrd@@ Ignores the recursion desired (RD) bit and treats all requests as if the recursion desired bit is set. Note that this behavior violates :rfc:`1034` which states that a name server should never perform recursive service unless asked via the RD bit since this interferes with trouble shooting of name servers and their databases. This prohibited behavior may be useful if another DNS server must forward requests for specific zones to a resolver DNS server, but only supports stub domains and sends queries to the resolver DNS server with the RD bit cleared. @@UAHL@unbound.conf.access-control.action@allow_snoop@@ Gives non-recursive access too. This gives both recursive and non recursive access. The name *allow_snoop* refers to cache snooping, a technique to use non-recursive queries to examine the cache contents (for malicious acts). However, non-recursive queries can also be a valuable debugging tool (when you want to examine the cache contents). In that case use :ref:`allow_snoop` for your administration host. @@UAHL@unbound.conf.access-control.action@allow_cookie@@ Allows access only to UDP queries that contain a valid DNS Cookie as specified in RFC 7873 and RFC 9018, when the :ref:`answer-cookie` option is enabled. UDP queries containing only a DNS Client Cookie and no Server Cookie, or an invalid DNS Cookie, will receive a BADCOOKIE response including a newly generated DNS Cookie, allowing clients to retry with that DNS Cookie. The *allow_cookie* action will also accept requests over stateful transports, regardless of the presence of an DNS Cookie and regardless of the :ref:`answer-cookie` setting. UDP queries without a DNS Cookie receive REFUSED responses with the TC flag set, that may trigger fall back to TCP for those clients. @@UAHL@unbound.conf.access-control.action@deny_non_local@@ The :ref:`deny_non_local` action is for hosts that are only allowed to query for the authoritative :ref:`local-data`, they are not allowed full recursion but only the static data. Messages that are disallowed are dropped. @@UAHL@unbound.conf.access-control.action@refuse_non_local@@ The :ref:`refuse_non_local` action is for hosts that are only allowed to query for the authoritative :ref:`local-data`, they are not allowed full recursion but only the static data. Messages that are disallowed receive error code REFUSED. By default only localhost (the 127.0.0.0/8 IP netblock, not the loopback interface) is implicitly *allowed*, the rest is refused. The default is *refused*, because that is protocol-friendly. The DNS protocol is not designed to handle dropped packets due to policy, and dropping may result in (possibly excessive) retried queries. @@UAHL@unbound.conf@access-control-tag@@: * ""* Assign tags to :ref:`access-control` elements. Clients using this access control element use localzones that are tagged with one of these tags. Tags must be defined in :ref:`define-tag`. Enclose list of tags in quotes (``""``) and put spaces between tags. If :ref:`access-control-tag` is configured for a netblock that does not have an :ref:`access-control`, an access-control element with action :ref:`allow` is configured for this netblock. @@UAHL@unbound.conf@access-control-tag-action@@: * * Set action for particular tag for given access control element. If you have multiple tag values, the tag used to lookup the action is the first tag match between :ref:`access-control-tag` and :ref:`local-zone-tag` where "first" comes from the order of the :ref:`define-tag` values. @@UAHL@unbound.conf@access-control-tag-data@@: * ""* Set redirect data for particular tag for given access control element. @@UAHL@unbound.conf@access-control-view@@: * * Set view for given access control element. @@UAHL@unbound.conf@interface-action@@: * * Similar to :ref:`access-control` but for interfaces. The action is the same as the ones defined under :ref:`access-control`. Default action for interfaces is :ref:`refuse`. By default only localhost (the 127.0.0.0/8 IP netblock, not the loopback interface) is implicitly allowed through the default :ref:`access-control` behavior. This also means that any attempt to use the **interface-\*:** options for the loopback interface will not work as they will be overridden by the implicit default "access-control: 127.0.0.0/8 allow" option. .. note:: The interface needs to be already specified with :ref:`interface` and that any **access-control\*:** option overrides all **interface-\*:** options for targeted clients. @@UAHL@unbound.conf@interface-tag@@: * <"list of tags">* Similar to :ref:`access-control-tag` but for interfaces. .. note:: The interface needs to be already specified with :ref:`interface` and that any **access-control\*:** option overrides all **interface-\*:** options for targeted clients. @@UAHL@unbound.conf@interface-tag-action@@: * * Similar to :ref:`access-control-tag-action` but for interfaces. .. note:: The interface needs to be already specified with :ref:`interface` and that any **access-control\*:** option overrides all **interface-\*:** options for targeted clients. @@UAHL@unbound.conf@interface-tag-data@@: * <"resource record string">* Similar to :ref:`access-control-tag-data` but for interfaces. .. note:: The interface needs to be already specified with :ref:`interface` and that any **access-control\*:** option overrides all **interface-\*:** options for targeted clients. @@UAHL@unbound.conf@interface-view@@: * * Similar to :ref:`access-control-view` but for interfaces. .. note:: The interface needs to be already specified with :ref:`interface` and that any **access-control\*:** option overrides all **interface-\*:** options for targeted clients. @@UAHL@unbound.conf@chroot@@: ** If :ref:`chroot` is enabled, you should pass the configfile (from the commandline) as a full path from the original root. After the chroot has been performed the now defunct portion of the config file path is removed to be able to reread the config after a reload. All other file paths (working dir, logfile, roothints, and key files) can be specified in several ways: as an absolute path relative to the new root, as a relative path to the working directory, or as an absolute path relative to the original root. In the last case the path is adjusted to remove the unused portion. The pidfile can be either a relative path to the working directory, or an absolute path relative to the original root. It is written just prior to chroot and dropping permissions. This allows the pidfile to be :file:`/var/run/unbound.pid` and the chroot to be :file:`/var/unbound`, for example. Note that Unbound is not able to remove the pidfile after termination when it is located outside of the chroot directory. Additionally, Unbound may need to access :file:`/dev/urandom` (for entropy) from inside the chroot. If given, a *chroot(2)* is done to the given directory. If you give ``""`` no *chroot(2)* is performed. Default: @UNBOUND_CHROOT_DIR@ @@UAHL@unbound.conf@username@@: ** If given, after binding the port the user privileges are dropped. If you give username: ``""`` no user change is performed. If this user is not capable of binding the port, reloads (by signal HUP) will still retain the opened ports. If you change the port number in the config file, and that new port number requires privileges, then a reload will fail; a restart is needed. Default: @UNBOUND_USERNAME@ @@UAHL@unbound.conf@directory@@: ** Sets the working directory for the program. On Windows the string "%EXECUTABLE%" tries to change to the directory that :command:`unbound.exe` resides in. If you give a :ref:`server: directory: \` before :ref:`include` file statements then those includes can be relative to the working directory. Default: @UNBOUND_RUN_DIR@ @@UAHL@unbound.conf@logfile@@: ** If ``""`` is given, logging goes to stderr, or nowhere once daemonized. The logfile is appended to, in the following format: .. code-block:: text [seconds since 1970] unbound[pid:tid]: type: message. If this option is given, the :ref:`use-syslog` option is internally set to ``no``. The logfile is reopened (for append) when the config file is reread, on SIGHUP. Default: "" (disabled) @@UAHL@unbound.conf@use-syslog@@: ** Sets Unbound to send log messages to the syslogd, using *syslog(3)*. The log facility LOG_DAEMON is used, with identity "unbound". The logfile setting is overridden when :ref:`use-syslog: yes` is set. Default: yes @@UAHL@unbound.conf@log-identity@@: ** If ``""`` is given, then the name of the executable, usually "unbound" is used to report to the log. Enter a string to override it with that, which is useful on systems that run more than one instance of Unbound, with different configurations, so that the logs can be easily distinguished against. Default: "" @@UAHL@unbound.conf@log-time-ascii@@: ** Sets logfile lines to use a timestamp in UTC ASCII. No effect if using syslog, in that case syslog formats the timestamp printed into the log files. Default: no (prints the seconds since 1970 in brackets) @@UAHL@unbound.conf@log-time-iso@@: ** Log time in ISO8601 format, if :ref:`log-time-ascii: yes` is also set. Default: no @@UAHL@unbound.conf@log-queries@@: ** Prints one line per query to the log, with the log timestamp and IP address, name, type and class. Note that it takes time to print these lines which makes the server (significantly) slower. Odd (nonprintable) characters in names are printed as ``'?'``. Default: no @@UAHL@unbound.conf@log-replies@@: ** Prints one line per reply to the log, with the log timestamp and IP address, name, type, class, return code, time to resolve, from cache and response size. Note that it takes time to print these lines which makes the server (significantly) slower. Odd (nonprintable) characters in names are printed as ``'?'``. Default: no @@UAHL@unbound.conf@log-tag-queryreply@@: ** Prints the word 'query' and 'reply' with :ref:`log-queries` and :ref:`log-replies`. This makes filtering logs easier. Default: no (backwards compatible) @@UAHL@unbound.conf@log-destaddr@@: ** Prints the destination address, port and type in the :ref:`log-replies` output. This disambiguates what type of traffic, eg. UDP or TCP, and to what local port the traffic was sent to. Default: no @@UAHL@unbound.conf@log-local-actions@@: ** Print log lines to inform about local zone actions. These lines are like the :ref:`local-zone type inform` print outs, but they are also printed for the other types of local zones. Default: no @@UAHL@unbound.conf@log-servfail@@: ** Print log lines that say why queries return SERVFAIL to clients. This is separate from the verbosity debug logs, much smaller, and printed at the error level, not the info level of debug info from verbosity. Default: no @@UAHL@unbound.conf@log-thread-id@@: ** (Only on Linux and only when threads are available) Logs the system-wide Linux thread ID instead of Unbound's internal thread counter. Can be useful when debugging with system tools. Default: no @@UAHL@unbound.conf@pidfile@@: ** The process id is written to the file. Default is :file:`"@UNBOUND_PIDFILE@"`. So, .. code-block:: text kill -HUP `cat @UNBOUND_PIDFILE@` triggers a reload, .. code-block:: text kill -TERM `cat @UNBOUND_PIDFILE@` gracefully terminates. Default: @UNBOUND_PIDFILE@ @@UAHL@unbound.conf@root-hints@@: ** Read the root hints from this file. Default is nothing, using builtin hints for the IN class. The file has the format of zone files, with root nameserver names and addresses only. The default may become outdated, when servers change, and then it is possible to use a root hints file with specific servers. Default: "" @@UAHL@unbound.conf@hide-identity@@: ** If enabled 'id.server' and 'hostname.bind' queries are REFUSED. Default: no @@UAHL@unbound.conf@identity@@: ** Set the identity to report. If set to ``""``, then the hostname of the server is returned. Default: "" @@UAHL@unbound.conf@hide-version@@: ** If enabled 'version.server' and 'version.bind' queries are REFUSED. Default: no @@UAHL@unbound.conf@version@@: ** Set the version to report. If set to ``""``, then the package version is returned. Default: "" @@UAHL@unbound.conf@hide-http-user-agent@@: ** If enabled the HTTP header User-Agent is not set. Use with caution as some webserver configurations may reject HTTP requests lacking this header. If needed, it is better to explicitly set the :ref:`http-user-agent` below. Default: no @@UAHL@unbound.conf@http-user-agent@@: ** Set the HTTP User-Agent header for outgoing HTTP requests. If set to ``""``, then the package name and version are used. Default: "" @@UAHL@unbound.conf@nsid@@: ** Add the specified nsid to the EDNS section of the answer when queried with an NSID EDNS enabled packet. As a sequence of hex characters or with 'ascii\_' prefix and then an ASCII string. Default: (disabled) @@UAHL@unbound.conf@hide-trustanchor@@: ** If enabled 'trustanchor.unbound' queries are REFUSED. Default: no @@UAHL@unbound.conf@target-fetch-policy@@: *<"list of numbers">* Set the target fetch policy used by Unbound to determine if it should fetch nameserver target addresses opportunistically. The policy is described per dependency depth. The number of values determines the maximum dependency depth that Unbound will pursue in answering a query. A value of -1 means to fetch all targets opportunistically for that dependency depth. A value of 0 means to fetch on demand only. A positive value fetches that many targets opportunistically. Enclose the list between quotes (``""``) and put spaces between numbers. Setting all zeroes, "0 0 0 0 0" gives behaviour closer to that of BIND 9, while setting "-1 -1 -1 -1 -1" gives behaviour rumoured to be closer to that of BIND 8. Default: "3 2 1 0 0" @@UAHL@unbound.conf@harden-short-bufsize@@: ** Very small EDNS buffer sizes from queries are ignored. Default: yes (per :rfc:`6891`) @@UAHL@unbound.conf@harden-large-queries@@: ** Very large queries are ignored. Default is no, since it is legal protocol wise to send these, and could be necessary for operation if TSIG or EDNS payload is very large. Default: no @@UAHL@unbound.conf@harden-glue@@: ** Will trust glue only if it is within the servers authority. Default: yes @@UAHL@unbound.conf@harden-unverified-glue@@: ** Will trust only in-zone glue. Will try to resolve all out of zone (*unverified*) glue. Will fallback to the original glue if unable to resolve. Default: no @@UAHL@unbound.conf@harden-dnssec-stripped@@: ** Require DNSSEC data for trust-anchored zones, if such data is absent, the zone becomes bogus. If turned off, and no DNSSEC data is received (or the DNSKEY data fails to validate), then the zone is made insecure, this behaves like there is no trust anchor. You could turn this off if you are sometimes behind an intrusive firewall (of some sort) that removes DNSSEC data from packets, or a zone changes from signed to unsigned to badly signed often. If turned off you run the risk of a downgrade attack that disables security for a zone. Default: yes @@UAHL@unbound.conf@harden-below-nxdomain@@: ** From :rfc:`8020` (with title "NXDOMAIN: There Really Is Nothing Underneath"), returns NXDOMAIN to queries for a name below another name that is already known to be NXDOMAIN. DNSSEC mandates NOERROR for empty nonterminals, hence this is possible. Very old software might return NXDOMAIN for empty nonterminals (that usually happen for reverse IP address lookups), and thus may be incompatible with this. To try to avoid this only DNSSEC-secure NXDOMAINs are used, because the old software does not have DNSSEC. .. note:: The NXDOMAIN must be secure, this means NSEC3 with optout is insufficient. Default: yes @@UAHL@unbound.conf@harden-referral-path@@: ** Harden the referral path by performing additional queries for infrastructure data. Validates the replies if trust anchors are configured and the zones are signed. This enforces DNSSEC validation on nameserver NS sets and the nameserver addresses that are encountered on the referral path to the answer. Default is off, because it burdens the authority servers, and it is not RFC standard, and could lead to performance problems because of the extra query load that is generated. Experimental option. If you enable it consider adding more numbers after the :ref:`target-fetch-policy` to increase the max depth that is checked to. Default: no @@UAHL@unbound.conf@harden-algo-downgrade@@: ** Harden against algorithm downgrade when multiple algorithms are advertised in the DS record. This works by first choosing only the strongest DS digest type as per :rfc:`4509` (Unbound treats the highest algorithm as the strongest) and then expecting signatures from all the advertised signing algorithms from the chosen DS(es) to be present. If no, allows any one supported algorithm to validate the zone, even if other advertised algorithms are broken. :rfc:`6840` mandates that zone signers must produce zones signed with all advertised algorithms, but sometimes they do not. :rfc:`6840` also clarifies that this requirement is not for validators and validators should accept any single valid path. It should thus be explicitly noted that this option violates :rfc:`6840` for DNSSEC validation and should only be used to perform a signature completeness test to support troubleshooting. .. warning:: Using this option may break DNSSEC resolution with non :rfc:`6840` conforming signers and/or in multi-signer configurations that don't send all the advertised signatures. Default: no @@UAHL@unbound.conf@harden-unknown-additional@@: ** Harden against unknown records in the authority section and additional section. If no, such records are copied from the upstream and presented to the client together with the answer. If yes, it could hamper future protocol developments that want to add records. Default: no @@UAHL@unbound.conf@use-caps-for-id@@: ** Use 0x20-encoded random bits in the query to foil spoof attempts. This perturbs the lowercase and uppercase of query names sent to authority servers and checks if the reply still has the correct casing. This feature is an experimental implementation of draft dns-0x20. Default: no @@UAHL@unbound.conf@caps-exempt@@: ** Exempt the domain so that it does not receive caps-for-id perturbed queries. For domains that do not support 0x20 and also fail with fallback because they keep sending different answers, like some load balancers. Can be given multiple times, for different domains. @@UAHL@unbound.conf@caps-whitelist@@: ** Alternate syntax for :ref:`caps-exempt`. @@UAHL@unbound.conf@qname-minimisation@@: ** Send minimum amount of information to upstream servers to enhance privacy. Only send minimum required labels of the QNAME and set QTYPE to A when possible. Best effort approach; full QNAME and original QTYPE will be sent when upstream replies with a RCODE other than NOERROR, except when receiving NXDOMAIN from a DNSSEC signed zone. Default: yes @@UAHL@unbound.conf@qname-minimisation-strict@@: ** QNAME minimisation in strict mode. Do not fall-back to sending full QNAME to potentially broken nameservers. A lot of domains will not be resolvable when this option in enabled. Only use if you know what you are doing. This option only has effect when :ref:`qname-minimisation` is enabled. Default: no @@UAHL@unbound.conf@aggressive-nsec@@: ** Aggressive NSEC uses the DNSSEC NSEC chain to synthesize NXDOMAIN and other denials, using information from previous NXDOMAINs answers. It helps to reduce the query rate towards targets that get a very high nonexistent name lookup rate. Default: yes @@UAHL@unbound.conf@private-address@@: ** Give IPv4 of IPv6 addresses or classless subnets. These are addresses on your private network, and are not allowed to be returned for public internet names. Any occurrence of such addresses are removed from DNS answers. Additionally, the DNSSEC validator may mark the answers bogus. This protects against so-called DNS Rebinding, where a user browser is turned into a network proxy, allowing remote access through the browser to other parts of your private network. The option removes resource records of types A, AAAA, SVCB and HTTPS that match the filter. Inside the SVCB and HTTPS records, the svcparams of type ipv4hint and ipv6hint are checked for matches. Some names can be allowed to contain your private addresses, by default all the :ref:`local-data` that you configured is allowed to, and you can specify additional names using :ref:`private-domain`. No private addresses are enabled by default. We consider to enable this for the :rfc:`1918` private IP address space by default in later releases. That would enable private addresses for ``10.0.0.0/8``, ``172.16.0.0/12``, ``192.168.0.0/16``, ``169.254.0.0/16``, ``fd00::/8`` and ``fe80::/10``, since the RFC standards say these addresses should not be visible on the public internet. Turning on ``127.0.0.0/8`` would hinder many spamblocklists as they use that. Adding ``::ffff:0:0/96`` stops IPv4-mapped IPv6 addresses from bypassing the filter. @@UAHL@unbound.conf@private-domain@@: ** Allow this domain, and all its subdomains to contain private addresses. Give multiple times to allow multiple domain names to contain private addresses. Default: (none) @@UAHL@unbound.conf@unwanted-reply-threshold@@: ** If set, a total number of unwanted replies is kept track of in every thread. When it reaches the threshold, a defensive action is taken and a warning is printed to the log. The defensive action is to clear the rrset and message caches, hopefully flushing away any poison. A value of 10 million is suggested. Default: 0 (disabled) @@UAHL@unbound.conf@do-not-query-address@@: ** Do not query the given IP address. Can be IPv4 or IPv6. Append /num to indicate a classless delegation netblock, for example like ``10.2.3.4/24`` or ``2001::11/64``. Default: (none) @@UAHL@unbound.conf@do-not-query-localhost@@: ** If yes, localhost is added to the :ref:`do-not-query-address` entries, both IPv6 ``::1`` and IPv4 ``127.0.0.1/8``. If no, then localhost can be used to send queries to. Default: yes @@UAHL@unbound.conf@prefetch@@: ** If yes, cache hits on message cache elements that are on their last 10 percent of their TTL value trigger a prefetch to keep the cache up to date. Turning it on gives about 10 percent more traffic and load on the machine, but popular items do not expire from the cache. Default: no @@UAHL@unbound.conf@prefetch-key@@: ** If yes, fetch the DNSKEYs earlier in the validation process, when a DS record is encountered. This lowers the latency of requests. It does use a little more CPU. Also if the cache is set to 0, it is no use. Default: no @@UAHL@unbound.conf@deny-any@@: ** If yes, deny queries of type ANY with an empty response. If disabled, Unbound responds with a short list of resource records if some can be found in the cache and makes the upstream type ANY query if there are none. Default: no @@UAHL@unbound.conf@rrset-roundrobin@@: ** If yes, Unbound rotates RRSet order in response (the random number is taken from the query ID, for speed and thread safety). Default: yes @@UAHL@unbound.conf@minimal-responses@@: ** If yes, Unbound does not insert authority/additional sections into response messages when those sections are not required. This reduces response size significantly, and may avoid TCP fallback for some responses which may cause a slight speedup. The default is yes, even though the DNS protocol RFCs mandate these sections, and the additional content could save roundtrips for clients that use the additional content. However these sections are hardly used by clients. Enabling prefetch can benefit clients that need the additional content by trying to keep that content fresh in the cache. Default: yes @@UAHL@unbound.conf@disable-dnssec-lame-check@@: ** If yes, disables the DNSSEC lameness check in the iterator. This check sees if RRSIGs are present in the answer, when DNSSEC is expected, and retries another authority if RRSIGs are unexpectedly missing. The validator will insist in RRSIGs for DNSSEC signed domains regardless of this setting, if a trust anchor is loaded. Default: no @@UAHL@unbound.conf@module-config@@: *""* Module configuration, a list of module names separated by spaces, surround the string with quotes (``""``). The modules can be ``respip``, ``validator``, or ``iterator`` (and possibly more, see below). .. note:: The ordering of the modules is significant, the order decides the order of processing. Setting this to just "iterator" will result in a non-validating server. Setting this to "validator iterator" will turn on DNSSEC validation. .. note:: You must also set trust-anchors for validation to be useful. Adding ``respip`` to the front will cause RPZ processing to be done on all queries. Most modules that need to be listed here have to be listed at the beginning of the line. The ``subnetcache`` module has to be listed just before the iterator. The ``python`` module can be listed in different places, it then processes the output of the module it is just before. The ``dynlib`` module can be listed pretty much anywhere, it is only a very thin wrapper that allows dynamic libraries to run in its place. Default: "validator iterator" @@UAHL@unbound.conf@trust-anchor-file@@: ** File with trusted keys for validation. Both DS and DNSKEY entries can appear in the file. The format of the file is the standard DNS Zone file format. Default: "" (no trust anchor file) @@UAHL@unbound.conf@auto-trust-anchor-file@@: ** File with trust anchor for one zone, which is tracked with :rfc:`5011` probes. The probes are run several times per month, thus the machine must be online frequently. The initial file can be one with contents as described in :ref:`trust-anchor-file`. The file is written to when the anchor is updated, so the Unbound user must have write permission. Write permission to the file, but also to the directory it is in (to create a temporary file, which is necessary to deal with filesystem full events), it must also be inside the :ref:`chroot` (if that is used). Default: "" (no auto trust anchor file) @@UAHL@unbound.conf@trust-anchor@@: *""* A DS or DNSKEY RR for a key to use for validation. Multiple entries can be given to specify multiple trusted keys, in addition to the :ref:`trust-anchor-file`. The resource record is entered in the same format as *dig(1)* or *drill(1)* prints them, the same format as in the zone file. Has to be on a single line, with ``""`` around it. A TTL can be specified for ease of cut and paste, but is ignored. A class can be specified, but class IN is default. Default: (none) @@UAHL@unbound.conf@trusted-keys-file@@: ** File with trusted keys for validation. Specify more than one file with several entries, one file per entry. Like :ref:`trust-anchor-file` but has a different file format. Format is BIND-9 style format, the ``trusted-keys { name flag proto algo "key"; };`` clauses are read. It is possible to use wildcards with this statement, the wildcard is expanded on start and on reload. Default: "" (no trusted keys file) @@UAHL@unbound.conf@trust-anchor-signaling@@: ** Send :rfc:`8145` key tag query after trust anchor priming. Default: yes @@UAHL@unbound.conf@root-key-sentinel@@: ** Root key trust anchor sentinel. Default: yes @@UAHL@unbound.conf@domain-insecure@@: ** Sets ** to be insecure, DNSSEC chain of trust is ignored towards the **. So a trust anchor above the domain name can not make the domain secure with a DS record, such a DS record is then ignored. Can be given multiple times to specify multiple domains that are treated as if unsigned. If you set trust anchors for the domain they override this setting (and the domain is secured). This can be useful if you want to make sure a trust anchor for external lookups does not affect an (unsigned) internal domain. A DS record externally can create validation failures for that internal domain. Default: (none) @@UAHL@unbound.conf@val-override-date@@: ** .. warning:: Debugging feature! If enabled by giving a RRSIG style date, that date is used for verifying RRSIG inception and expiration dates, instead of the current date. Do not set this unless you are debugging signature inception and expiration. The value -1 ignores the date altogether, useful for some special applications. Default: 0 (disabled) @@UAHL@unbound.conf@val-sig-skew-min@@: ** Minimum number of seconds of clock skew to apply to validated signatures. A value of 10% of the signature lifetime (expiration - inception) is used, capped by this setting. Default is 3600 (1 hour) which allows for daylight savings differences. Lower this value for more strict checking of short lived signatures. Default: 3600 (1 hour) @@UAHL@unbound.conf@val-sig-skew-max@@: ** Maximum number of seconds of clock skew to apply to validated signatures. A value of 10% of the signature lifetime (expiration - inception) is used, capped by this setting. Default is 86400 (24 hours) which allows for timezone setting problems in stable domains. Setting both min and max very low disables the clock skew allowances. Setting both min and max very high makes the validator check the signature timestamps less strictly. Default: 86400 (24 hours) @@UAHL@unbound.conf@val-max-restart@@: ** The maximum number the validator should restart validation with another authority in case of failed validation. Default: 5 @@UAHL@unbound.conf@val-bogus-ttl@@: ** The time to live for bogus data. This is data that has failed validation; due to invalid signatures or other checks. The TTL from that data cannot be trusted, and this value is used instead. The time interval prevents repeated revalidation of bogus data. Default: 60 @@UAHL@unbound.conf@val-clean-additional@@: ** Instruct the validator to remove data from the additional section of secure messages that are not signed properly. Messages that are insecure, bogus, indeterminate or unchecked are not affected. Use this setting to protect the users that rely on this validator for authentication from potentially bad data in the additional section. Default: yes @@UAHL@unbound.conf@val-log-level@@: ** Have the validator print validation failures to the log. Regardless of the verbosity setting. At 1, for every user query that fails a line is printed to the logs. This way you can monitor what happens with validation. Use a diagnosis tool, such as dig or drill, to find out why validation is failing for these queries. At 2, not only the query that failed is printed but also the reason why Unbound thought it was wrong and which server sent the faulty data. Default: 0 (disabled) @@UAHL@unbound.conf@val-permissive-mode@@: ** Instruct the validator to mark bogus messages as indeterminate. The security checks are performed, but if the result is bogus (failed security), the reply is not withheld from the client with SERVFAIL as usual. The client receives the bogus data. For messages that are found to be secure the AD bit is set in replies. Also logging is performed as for full validation. Default: no @@UAHL@unbound.conf@ignore-cd-flag@@: ** Instruct Unbound to ignore the CD flag from clients and refuse to return bogus answers to them. Thus, the CD (Checking Disabled) flag does not disable checking any more. This is useful if legacy (w2008) servers that set the CD flag but cannot validate DNSSEC themselves are the clients, and then Unbound provides them with DNSSEC protection. Default: no @@UAHL@unbound.conf@disable-edns-do@@: ** Disable the EDNS DO flag in upstream requests. It breaks DNSSEC validation for Unbound's clients. This results in the upstream name servers to not include DNSSEC records in their replies and could be helpful for devices that cannot handle DNSSEC information. When the option is enabled, clients that set the DO flag receive no EDNS record in the response to indicate the lack of support to them. If this option is enabled but Unbound is already configured for DNSSEC validation (i.e., the validator module is enabled; default) this option is implicitly turned off with a warning as to not break DNSSEC validation in Unbound. Default: no @@UAHL@unbound.conf@serve-expired@@: ** If enabled, Unbound attempts to serve old responses from cache with a TTL of :ref:`serve-expired-reply-ttl` in the response. By default the expired answer will be used after a resolution attempt errored out or is taking more than :ref:`serve-expired-client-timeout` to resolve. Default: no @@UAHL@unbound.conf@serve-expired-ttl@@: ** Limit serving of expired responses to configured seconds after expiration. ``0`` disables the limit. This option only applies when :ref:`serve-expired` is enabled. A suggested value per RFC 8767 is between 86400 (1 day) and 259200 (3 days). The default is 86400. Default: 86400 @@UAHL@unbound.conf@serve-expired-ttl-reset@@: ** Set the TTL of expired records to the :ref:`serve-expired-ttl` value after a failed attempt to retrieve the record from upstream. This makes sure that the expired records will be served as long as there are queries for it. Default: no @@UAHL@unbound.conf@serve-expired-reply-ttl@@: ** TTL value to use when replying with expired data. If :ref:`serve-expired-client-timeout` is also used then it is RECOMMENDED to use 30 as the value (:rfc:`8767`). This value is capped by the original TTL of the record. This means that records with higher original TTL than this value will use this value for expired replies. Records with lower original TTL than this value will use their original TTL for expired replies. Default: 30 @@UAHL@unbound.conf@serve-expired-client-timeout@@: ** Time in milliseconds before replying to the client with expired data. This essentially enables the serve-stale behavior as specified in :rfc:`8767` that first tries to resolve before immediately responding with expired data. Setting this to ``0`` will disable this behavior and instead serve the expired record immediately from the cache before attempting to refresh it via resolution. Default: 1800 @@UAHL@unbound.conf@serve-original-ttl@@: ** If enabled, Unbound will always return the original TTL as received from the upstream name server rather than the decrementing TTL as stored in the cache. This feature may be useful if Unbound serves as a front-end to a hidden authoritative name server. Enabling this feature does not impact cache expiry, it only changes the TTL Unbound embeds in responses to queries. .. note:: Enabling this feature implicitly disables enforcement of the configured minimum and maximum TTL, as it is assumed users who enable this feature do not want Unbound to change the TTL obtained from an upstream server. .. note:: The values set using :ref:`cache-min-ttl` and :ref:`cache-max-ttl` are ignored. Default: no @@UAHL@unbound.conf@val-nsec3-keysize-iterations@@: <"list of values"> List of keysize and iteration count values, separated by spaces, surrounded by quotes. This determines the maximum allowed NSEC3 iteration count before a message is simply marked insecure instead of performing the many hashing iterations. The list must be in ascending order and have at least one entry. If you set it to "1024 65535" there is no restriction to NSEC3 iteration values. .. note:: This table must be kept short; a very long list could cause slower operation. Default: "1024 150 2048 150 4096 150" @@UAHL@unbound.conf@zonemd-permissive-mode@@: ** If enabled the ZONEMD verification failures are only logged and do not cause the zone to be blocked and only return servfail. Useful for testing out if it works, or if the operator only wants to be notified of a problem without disrupting service. Default: no @@UAHL@unbound.conf@add-holddown@@: ** Instruct the :ref:`auto-trust-anchor-file` probe mechanism for :rfc:`5011` autotrust updates to add new trust anchors only after they have been visible for this time. Default: 2592000 (30 days as per the RFC) @@UAHL@unbound.conf@del-holddown@@: ** Instruct the :ref:`auto-trust-anchor-file` probe mechanism for :rfc:`5011` autotrust updates to remove revoked trust anchors after they have been kept in the revoked list for this long. Default: 2592000 (30 days as per the RFC) @@UAHL@unbound.conf@keep-missing@@: ** Instruct the :ref:`auto-trust-anchor-file` probe mechanism for :rfc:`5011` autotrust updates to remove missing trust anchors after they have been unseen for this long. This cleans up the state file if the target zone does not perform trust anchor revocation, so this makes the auto probe mechanism work with zones that perform regular (non-5011) rollovers. The value 0 does not remove missing anchors, as per the RFC. Default: 31622400 (366 days) @@UAHL@unbound.conf@permit-small-holddown@@: ** Debug option that allows the autotrust 5011 rollover timers to assume very small values. Default: no @@UAHL@unbound.conf@key-cache-size@@: ** Number of bytes size of the key cache. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 4m @@UAHL@unbound.conf@key-cache-slabs@@: ** Number of slabs in the key cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@neg-cache-size@@: ** Number of bytes size of the aggressive negative cache. A plain number is in bytes, append 'k', 'm' or 'g' for kilobytes, megabytes or gigabytes (1024*1024 bytes in a megabyte). Default: 1m @@UAHL@unbound.conf@unblock-lan-zones@@: ** If enabled, then for private address space, the reverse lookups are no longer filtered. This allows Unbound when running as dns service on a host where it provides service for that host, to put out all of the queries for the 'lan' upstream. When enabled, only localhost, ``127.0.0.1`` reverse and ``::1`` reverse zones are configured with default local zones. Disable the option when Unbound is running as a (DHCP-) DNS network resolver for a group of machines, where such lookups should be filtered (RFC compliance), this also stops potential data leakage about the local network to the upstream DNS servers. Default: no @@UAHL@unbound.conf@insecure-lan-zones@@: ** If enabled, then reverse lookups in private address space are not validated. This is usually required whenever :ref:`unblock-lan-zones` is used. Default: no @@UAHL@unbound.conf@local-zone@@: * * Configure a local zone. The type determines the answer to give if there is no match from :ref:`local-data`. The types are :ref:`deny`, :ref:`refuse`, :ref:`static`, :ref:`transparent`, :ref:`redirect`, :ref:`nodefault`, :ref:`typetransparent`, :ref:`inform`, :ref:`inform_deny`, :ref:`inform_redirect`, :ref:`always_transparent`, :ref:`block_a`, :ref:`always_refuse`, :ref:`always_nxdomain`, :ref:`always_null`, :ref:`noview`, and are explained below. After that the default settings are listed. Use :ref:`local-data` to enter data into the local zone. Answers for local zones are authoritative DNS answers. By default the zones are class IN. If you need more complicated authoritative data, with referrals, wildcards, CNAME/DNAME support, or DNSSEC authoritative service, setup a :ref:`stub-zone` for it as detailed in the stub zone section below. A :ref:`stub-zone` can be used to have unbound send queries to another server, an authoritative server, to fetch the information. With a :ref:`forward-zone`, unbound sends queries to a server that is a recursive server to fetch the information. With an :ref:`auth-zone` a zone can be loaded from file and used, it can be used like a local zone for users downstream, or the :ref:`auth-zone` information can be used to fetch information from when resolving like it is an upstream server. The :ref:`forward-zone` and :ref:`auth-zone` options are described in their sections below. If you want to perform filtering of the information that the users can fetch, the :ref:`local-zone` and :ref:`local-data` statements allow for this, but also the :ref:`rpz` functionality can be used, described in the RPZ section. @@UAHL@unbound.conf.local-zone.type@deny@@ Do not send an answer, drop the query. If there is a match from local data, the query is answered. @@UAHL@unbound.conf.local-zone.type@refuse@@ Send an error message reply, with rcode REFUSED. If there is a match from local data, the query is answered. @@UAHL@unbound.conf.local-zone.type@static@@ If there is a match from local data, the query is answered. Otherwise, the query is answered with NODATA or NXDOMAIN. For a negative answer a SOA is included in the answer if present as :ref:`local-data` for the zone apex domain. @@UAHL@unbound.conf.local-zone.type@transparent@@ If there is a match from :ref:`local-data`, the query is answered. Otherwise if the query has a different name, the query is resolved normally. If the query is for a name given in :ref:`local-data` but no such type of data is given in localdata, then a NOERROR NODATA answer is returned. If no :ref:`local-zone` is given :ref:`local-data` causes a transparent zone to be created by default. @@UAHL@unbound.conf.local-zone.type@typetransparent@@ If there is a match from local data, the query is answered. If the query is for a different name, or for the same name but for a different type, the query is resolved normally. So, similar to :ref:`transparent` but types that are not listed in local data are resolved normally, so if an A record is in the local data that does not cause a NODATA reply for AAAA queries. @@UAHL@unbound.conf.local-zone.type@redirect@@ The query is answered from the local data for the zone name. There may be no local data beneath the zone name. This answers queries for the zone, and all subdomains of the zone with the local data for the zone. It can be used to redirect a domain to return a different address record to the end user, with: .. code-block:: text local-zone: "example.com." redirect local-data: "example.com. A 127.0.0.1" queries for ``www.example.com`` and ``www.foo.example.com`` are redirected, so that users with web browsers cannot access sites with suffix example.com. A ``CNAME`` record can also be provided via local-data: .. code-block:: text local-zone: "example.com." redirect local-data: "example.com. CNAME www.example.org." In that case, the ``CNAME`` is resolved and the answer includes resolved target records as well. The ``CNAME`` record has to be with the zone name of the local-zone, and there can be one CNAME, not more. The ``CNAME`` record has to be at the zone apex of the ``redirect`` zone, then it is used for redirection. The resolution proceeds with upstream DNS resolution, and that does not include the lookup in local zones. So the record is not able to point in local zones, but it can point to upstream DNS answers. ``CNAME`` resolution is supported only in type ``redirect`` local-zone, and in type ``inform_redirect`` local-zone. As different from ``CNAME`` records that are used elsewhere, in the ``redirect`` type local-zone, it is supported that in the target of the record a wildcard label gets expanded to the query name, with for example: ``example.com. CNAME *.foo.net.`` gets expanded to ``www.example.com. CNAME www.example.com.foo.net.``. @@UAHL@unbound.conf.local-zone.type@inform@@ The query is answered normally, same as :ref:`transparent`. The client IP address (@portnumber) is printed to the logfile. The log message is: .. code-block:: text timestamp, unbound-pid, info: zonename inform IP@port queryname type class. This option can be used for normal resolution, but machines looking up infected names are logged, eg. to run antivirus on them. @@UAHL@unbound.conf.local-zone.type@inform_deny@@ The query is dropped, like :ref:`deny`, and logged, like :ref:`inform`. Ie. find infected machines without answering the queries. @@UAHL@unbound.conf.local-zone.type@inform_redirect@@ The query is redirected, like :ref:`redirect`, and logged, like :ref:`inform`. Ie. answer queries with fixed data and also log the machines that ask. @@UAHL@unbound.conf.local-zone.type@always_transparent@@ Like :ref:`transparent`, but ignores local data and resolves normally. @@UAHL@unbound.conf.local-zone.type@block_a@@ Like :ref:`transparent`, but ignores local data and resolves normally all query types excluding A. For A queries it unconditionally returns NODATA. Useful in cases when there is a need to explicitly force all apps to use IPv6 protocol and avoid any queries to IPv4. @@UAHL@unbound.conf.local-zone.type@always_refuse@@ Like :ref:`refuse`, but ignores local data and refuses the query. This type also blocks queries of type DS for the zone name. That can break the DNSSEC chain of trust, but it is refused anyway. The block for type DS assists in more completely blocking the zone. @@UAHL@unbound.conf.local-zone.type@always_nxdomain@@ Like :ref:`static`, but ignores local data and returns NXDOMAIN for the query. @@UAHL@unbound.conf.local-zone.type@always_nodata@@ Like :ref:`static`, but ignores local data and returns NODATA for the query. @@UAHL@unbound.conf.local-zone.type@always_deny@@ Like :ref:`deny`, but ignores local data and drops the query. @@UAHL@unbound.conf.local-zone.type@always_null@@ Always returns ``0.0.0.0`` or ``::0`` for every name in the zone. Like :ref:`redirect` with zero data for A and AAAA. Ignores local data in the zone. Used for some block lists. @@UAHL@unbound.conf.local-zone.type@noview@@ Breaks out of that view and moves towards the global local zones for answer to the query. If the :ref:`view-first` is no, it'll resolve normally. If :ref:`view-first` is enabled, it'll break perform that step and check the global answers. For when the view has view specific overrides but some zone has to be answered from global local zone contents. @@UAHL@unbound.conf.local-zone.type@nodefault@@ Used to turn off default contents for AS112 zones. The other types also turn off default contents for the zone. The :ref:`nodefault` option has no other effect than turning off default contents for the given zone. Use :ref:`nodefault` if you use exactly that zone, if you want to use a subzone, use :ref:`transparent`. The default zones are localhost, reverse ``127.0.0.1`` and ``::1``, the ``home.arpa``, ``resolver.arpa``, ``service.arpa``, ``onion``, ``test``, ``invalid`` and the AS112 zones. The AS112 zones are reverse DNS zones for private use and reserved IP addresses for which the servers on the internet cannot provide correct answers. They are configured by default to give NXDOMAIN (no reverse information) answers. The defaults can be turned off by specifying your own :ref:`local-zone` of that name, or using the :ref:`nodefault` type. Below is a list of the default zone contents. @@UAHL@unbound.conf.local-zone.defaults@localhost@@ The IPv4 and IPv6 localhost information is given. NS and SOA records are provided for completeness and to satisfy some DNS update tools. Default content: .. code-block:: text local-zone: "localhost." redirect local-data: "localhost. 10800 IN NS localhost." local-data: "localhost. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" local-data: "localhost. 10800 IN A 127.0.0.1" local-data: "localhost. 10800 IN AAAA ::1" @@UAHL@unbound.conf.local-zone.defaults@reverse IPv4 loopback@@ Default content: .. code-block:: text local-zone: "127.in-addr.arpa." static local-data: "127.in-addr.arpa. 10800 IN NS localhost." local-data: "127.in-addr.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" local-data: "1.0.0.127.in-addr.arpa. 10800 IN PTR localhost." @@UAHL@unbound.conf.local-zone.defaults@reverse IPv6 loopback@@ Default content: .. code-block:: text local-zone: "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." static local-data: "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 NS localhost." local-data: "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 SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" local-data: "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." @@UAHL@unbound.conf.local-zone.defaults@home.arpa@@ (:rfc:`8375`) Default content: .. code-block:: text local-zone: "home.arpa." static local-data: "home.arpa. 10800 IN NS localhost." local-data: "home.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@resolver.arpa@@ (:rfc:`9462`) Default content: .. code-block:: text local-zone: "resolver.arpa." static local-data: "resolver.arpa. 10800 IN NS localhost." local-data: "resolver.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@service.arpa@@ (draft-ietf-dnssd-srp-25) Default content: .. code-block:: text local-zone: "service.arpa." static local-data: "service.arpa. 10800 IN NS localhost." local-data: "service.arpa. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@onion@@ (:rfc:`7686`) Default content: .. code-block:: text local-zone: "onion." static local-data: "onion. 10800 IN NS localhost." local-data: "onion. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@test@@ (:rfc:`6761`) Default content: .. code-block:: text local-zone: "test." static local-data: "test. 10800 IN NS localhost." local-data: "test. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@invalid@@ (:rfc:`6761`) Default content: .. code-block:: text local-zone: "invalid." static local-data: "invalid. 10800 IN NS localhost." local-data: "invalid. 10800 IN SOA localhost. nobody.invalid. 1 3600 1200 604800 10800" @@UAHL@unbound.conf.local-zone.defaults@reverse local use zones@@ (:rfc:`1918`) Reverse data for zones ``10.in-addr.arpa``, ``16.172.in-addr.arpa`` to ``31.172.in-addr.arpa``, ``168.192.in-addr.arpa``. The :ref:`local-zone` is set static and as :ref:`local-data` SOA and NS records are provided. @@UAHL@unbound.conf.local-zone.defaults@special-use IPv4 Addresses@@ (:rfc:`3330`) Reverse data for zones ``0.in-addr.arpa`` (this), ``254.169.in-addr.arpa`` (link-local), ``2.0.192.in-addr.arpa`` (TEST NET 1), ``100.51.198.in-addr.arpa`` (TEST NET 2), ``113.0.203.in-addr.arpa`` (TEST NET 3), ``255.255.255.255.in-addr.arpa`` (broadcast). And from ``64.100.in-addr.arpa`` to ``127.100.in-addr.arpa`` (Shared Address Space). @@UAHL@unbound.conf.local-zone.defaults@reverse IPv6 unspecified@@ (:rfc:`4291`) Reverse data for zone ``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.0.ip6.arpa.`` @@UAHL@unbound.conf.local-zone.defaults@reverse IPv6 Locally Assigned Local Addresses@@ (:rfc:`4193`) Reverse data for zone ``D.F.ip6.arpa``. @@UAHL@unbound.conf.local-zone.defaults@reverse IPv6 Link Local Addresses@@ (:rfc:`4291`) Reverse data for zones ``8.E.F.ip6.arpa`` to ``B.E.F.ip6.arpa``. @@UAHL@unbound.conf.local-zone.defaults@reverse IPv6 Example Prefix@@ Reverse data for zone ``8.B.D.0.1.0.0.2.ip6.arpa``. This zone is used for tutorials and examples. You can remove the block on this zone with: .. code-block:: text local-zone: 8.B.D.0.1.0.0.2.ip6.arpa. nodefault You can also selectively unblock a part of the zone by making that part transparent with a :ref:`local-zone` statement. This also works with the other default zones. @@UAHL@unbound.conf@local-data@@: *""* Configure local data, which is served in reply to queries for it. The query has to match exactly unless you configure the :ref:`local-zone` as redirect. If not matched exactly, the :ref:`local-zone` type determines further processing. If :ref:`local-data` is configured that is not a subdomain of a :ref:`local-zone`, a :ref:`transparent local-zone` is configured. For record types such as TXT, use single quotes, as in: .. code-block:: text local-data: 'example. TXT "text"' .. note:: If you need more complicated authoritative data, with referrals, wildcards, CNAME/DNAME support, or DNSSEC authoritative service, setup a :ref:`stub-zone` for it as detailed in the stub zone section below. @@UAHL@unbound.conf@local-data-ptr@@: *"IPaddr name"* Configure local data shorthand for a PTR record with the reversed IPv4 or IPv6 address and the host name. For example ``"192.0.2.4 www.example.com"``. TTL can be inserted like this: ``"2001:db8::4 7200 www.example.com"`` @@UAHL@unbound.conf@local-zone-tag@@: * <"list of tags">* Assign tags to local zones. Tagged localzones will only be applied when the used :ref:`access-control` element has a matching tag. Tags must be defined in :ref:`define-tag`. Enclose list of tags in quotes (``""``) and put spaces between tags. When there are multiple tags it checks if the intersection of the list of tags for the query and :ref:`local-zone-tag` is non-empty. @@UAHL@unbound.conf@local-zone-override@@: * * Override the local zone type for queries from addresses matching netblock. Use this localzone type, regardless the type configured for the local zone (both tagged and untagged) and regardless the type configured using :ref:`access-control-tag-action`. @@UAHL@unbound.conf@response-ip@@: * * This requires use of the ``respip`` module. If the IP address in an AAAA or A RR in the answer section of a response matches the specified IP netblock, the specified action will apply. ** has generally the same semantics as that for :ref:`access-control-tag-action`, but there are some exceptions. Actions for :ref:`response-ip` are different from those for :ref:`local-zone` in that in case of the former there is no point of such conditions as "the query matches it but there is no local data". Because of this difference, the semantics of :ref:`response-ip` actions are modified or simplified as follows: The *static*, *refuse*, *transparent*, *typetransparent*, and *nodefault* actions are invalid for *response-ip*. Using any of these will cause the configuration to be rejected as faulty. The *deny* action is non-conditional, i.e. it always results in dropping the corresponding query. The resolution result before applying the *deny* action is still cached and can be used for other queries. @@UAHL@unbound.conf@response-ip-data@@: * <"resource record string">* This requires use of the ``respip`` module. This specifies the action data for :ref:`response-ip` with action being to redirect as specified by *<"resource record string">*. *<"Resource record string">* is similar to that of :ref:`access-control-tag-data`, but it must be of either AAAA, A or CNAME types. If the ** is an IPv6/IPv4 prefix, the record must be AAAA/A respectively, unless it is a CNAME (which can be used for both versions of IP netblocks). If it is CNAME there must not be more than one :ref:`response-ip-data` for the same **. Also, CNAME and other types of records must not coexist for the same **, following the normal rules for CNAME records. The textual domain name for the CNAME does not have to be explicitly terminated with a dot (``"."``); the root name is assumed to be the origin for the name. @@UAHL@unbound.conf@response-ip-tag@@: * <"list of tags">* This requires use of the ``respip`` module. Assign tags to response **. If the IP address in an AAAA or A RR in the answer section of a response matches the specified **, the specified tags are assigned to the IP address. Then, if an :ref:`access-control-tag` is defined for the client and it includes one of the tags for the response IP, the corresponding :ref:`access-control-tag-action` will apply. Tag matching rule is the same as that for :ref:`access-control-tag` and :ref:`local-zone`. Unlike :ref:`local-zone-tag`, :ref:`response-ip-tag` can be defined for an ** even if no :ref:`response-ip` is defined for that netblock. If multiple :ref:`response-ip-tag` options are specified for the same ** in different statements, all but the first will be ignored. However, this will not be flagged as a configuration error, but the result is probably not what was intended. Actions specified in an :ref:`access-control-tag-action` that has a matching tag with :ref:`response-ip-tag` can be those that are "invalid" for :ref:`response-ip` listed above, since :ref:`access-control-tag-action` can be shared with local zones. For these actions, if they behave differently depending on whether local data exists or not in case of local zones, the behavior for :ref:`response-ip-data` will generally result in NOERROR/NODATA instead of NXDOMAIN, since the :ref:`response-ip` data are inherently type specific, and non-existence of data does not indicate anything about the existence or non-existence of the qname itself. For example, if the matching tag action is static but there is no data for the corresponding :ref:`response-ip` configuration, then the result will be NOERROR/NODATA. The only case where NXDOMAIN is returned is when an :ref:`always_nxdomain` action applies. @@UAHL@unbound.conf@ratelimit@@: ** Enable ratelimiting of queries sent to nameserver for performing recursion. 0 disables the feature. This option is experimental at this time. The ratelimit is in queries per second that are allowed. More queries are turned away with an error (SERVFAIL). Cached responses are not ratelimited by this setting. This stops recursive floods, eg. random query names, but not spoofed reflection floods. The zone of the query is determined by examining the nameservers for it, the zone name is used to keep track of the rate. For example, 1000 may be a suitable value to stop the server from being overloaded with random names, and keeps unbound from sending traffic to the nameservers for those zones. .. note:: Configured forwarders are excluded from ratelimiting. Default: 0 @@UAHL@unbound.conf@ratelimit-size@@: ** Give the size of the data structure in which the current ongoing rates are kept track in. In bytes or use m(mega), k(kilo), g(giga). The ratelimit structure is small, so this data structure likely does not need to be large. Default: 4m @@UAHL@unbound.conf@ratelimit-slabs@@: ** Number of slabs in the ratelimit tracking data structure. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@ratelimit-factor@@: ** Set the amount of queries to rate limit when the limit is exceeded. If set to 0, all queries are dropped for domains where the limit is exceeded. If set to another value, 1 in that number is allowed through to complete. Default is 10, allowing 1/10 traffic to flow normally. This can make ordinary queries complete (if repeatedly queried for), and enter the cache, whilst also mitigating the traffic flow by the factor given. Default: 10 @@UAHL@unbound.conf@ratelimit-backoff@@: ** If enabled, the ratelimit is treated as a hard failure instead of the default maximum allowed constant rate. When the limit is reached, traffic is ratelimited and demand continues to be kept track of for a 2 second rate window. No traffic is allowed, except for :ref:`ratelimit-factor`, until demand decreases below the configured ratelimit for a 2 second rate window. Useful to set :ref:`ratelimit` to a suspicious rate to aggressively limit unusually high traffic. Default: no @@UAHL@unbound.conf@ratelimit-for-domain@@: * * Override the global :ref:`ratelimit` for an exact match domain name with the listed number. You can give this for any number of names. For example, for a top-level-domain you may want to have a higher limit than other names. A value of 0 will disable ratelimiting for that domain. @@UAHL@unbound.conf@ratelimit-below-domain@@: * * Override the global :ref:`ratelimit` for a domain name that ends in this name. You can give this multiple times, it then describes different settings in different parts of the namespace. The closest matching suffix is used to determine the qps limit. The rate for the exact matching domain name is not changed, use :ref:`ratelimit-for-domain` to set that, you might want to use different settings for a top-level-domain and subdomains. A value of 0 will disable ratelimiting for domain names that end in this name. @@UAHL@unbound.conf@ip-ratelimit@@: ** Enable global ratelimiting of queries accepted per ip address. This option is experimental at this time. The ratelimit is in queries per second that are allowed. More queries are completely dropped and will not receive a reply, SERVFAIL or otherwise. IP ratelimiting happens before looking in the cache. This may be useful for mitigating amplification attacks. Clients with a valid DNS Cookie will bypass the ratelimit. If a ratelimit for such clients is still needed, :ref:`ip-ratelimit-cookie` can be used instead. Default: 0 (disabled) @@UAHL@unbound.conf@ip-ratelimit-cookie@@: ** Enable global ratelimiting of queries accepted per IP address with a valid DNS Cookie. This option is experimental at this time. The ratelimit is in queries per second that are allowed. More queries are completely dropped and will not receive a reply, SERVFAIL or otherwise. IP ratelimiting happens before looking in the cache. This option could be useful in combination with :ref:`allow_cookie`, in an attempt to mitigate other amplification attacks than UDP reflections (e.g., attacks targeting Unbound itself) which are already handled with DNS Cookies. If used, the value is suggested to be higher than :ref:`ip-ratelimit` e.g., tenfold. Default: 0 (disabled) @@UAHL@unbound.conf@ip-ratelimit-size@@: ** Give the size of the data structure in which the current ongoing rates are kept track in. In bytes or use m(mega), k(kilo), g(giga). The IP ratelimit structure is small, so this data structure likely does not need to be large. Default: 4m @@UAHL@unbound.conf@ip-ratelimit-slabs@@: ** Number of slabs in the ip ratelimit tracking data structure. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf@ip-ratelimit-factor@@: ** Set the amount of queries to rate limit when the limit is exceeded. If set to 0, all queries are dropped for addresses where the limit is exceeded. If set to another value, 1 in that number is allowed through to complete. Default is 10, allowing 1/10 traffic to flow normally. This can make ordinary queries complete (if repeatedly queried for), and enter the cache, whilst also mitigating the traffic flow by the factor given. Default: 10 @@UAHL@unbound.conf@ip-ratelimit-backoff@@: ** If enabled, the rate limit is treated as a hard failure instead of the default maximum allowed constant rate. When the limit is reached, traffic is ratelimited and demand continues to be kept track of for a 2 second rate window. No traffic is allowed, except for :ref:`ip-ratelimit-factor`, until demand decreases below the configured ratelimit for a 2 second rate window. Useful to set :ref:`ip-ratelimit` to a suspicious rate to aggressively limit unusually high traffic. Default: no @@UAHL@unbound.conf@outbound-msg-retry@@: ** The number of retries, per upstream nameserver in a delegation, that Unbound will attempt in case a throwaway response is received. No response (timeout) contributes to the retry counter. If a forward/stub zone is used, this is the number of retries per nameserver in the zone. Default: 5 @@UAHL@unbound.conf@max-sent-count@@: ** Hard limit on the number of outgoing queries Unbound will make while resolving a name, making sure large NS sets do not loop. Results in SERVFAIL when reached. It resets on query restarts (e.g., CNAME) and referrals. Default: 32 @@UAHL@unbound.conf@max-query-restarts@@: ** Hard limit on the number of times Unbound is allowed to restart a query upon encountering a CNAME record. Results in SERVFAIL when reached. This applies to chained CNAME records but not sporadic CNAME records that could be encountered in the lifetime of the query's resolution effort. When a CNAME chain concludes, the counter keeping track of this limit is reset. Changing this value needs caution as it can allow long CNAME chains to be accepted, where Unbound needs to verify (resolve) each link individually. Default: 11 @@UAHL@unbound.conf@iter-scrub-ns@@: ** Limit on the number of NS records allowed in an rrset of type NS, from the iterator scrubber. This protects the internals of the resolver from overly large NS sets. Default: 20 @@UAHL@unbound.conf@iter-scrub-cname@@: ** Limit on the number of CNAME, DNAME records in an answer, from the iterator scrubber. This protects the internals of the resolver from overly long indirection chains. Clips off the remainder of the reply packet at that point. Default: 11 @@UAHL@unbound.conf@iter-scrub-rrsig@@: ** Limit on the number of RRSIGs allowed for an RRset, from the iterator scrubber. This protects against an overly large number of RRSIGs. Clips off the remainder of the RRSIG list at that point. Default: 8 @@UAHL@unbound.conf@max-global-quota@@: ** Limit on the number of upstream queries sent out for an incoming query and its subqueries from recursion. It is not reset during the resolution. When it is exceeded the query is failed and the lookup process stops. Default: 200 @@UAHL@unbound.conf@iter-scrub-promiscuous@@: ** Should the iterator scrubber remove promiscuous NS from positive answers. This protects against poisonous contents, that could affect names in the same zone as a spoofed packet. Default: yes @@UAHL@unbound.conf@fast-server-permil@@: ** Specify how many times out of 1000 to pick from the set of fastest servers. 0 turns the feature off. A value of 900 would pick from the fastest servers 90 percent of the time, and would perform normal exploration of random servers for the remaining time. When :ref:`prefetch` is enabled (or :ref:`serve-expired`), such prefetches are not sped up, because there is no one waiting for it, and it presents a good moment to perform server exploration. The :ref:`fast-server-num` option can be used to specify the size of the fastest servers set. Default: 0 @@UAHL@unbound.conf@fast-server-num@@: ** Set the number of servers that should be used for fast server selection. Only use the fastest specified number of servers with the :ref:`fast-server-permil` option, that turns this on or off. Default: 3 @@UAHL@unbound.conf@answer-cookie@@: ** If enabled, Unbound will answer to requests containing DNS Cookies as specified in RFC 7873 and RFC 9018. Default: no @@UAHL@unbound.conf@cookie-secret@@: *"<128 bit hex string>"* Server's secret for DNS Cookie generation. Useful to explicitly set for servers in an anycast deployment that need to share the secret in order to verify each other's Server Cookies. An example hex string would be "000102030405060708090a0b0c0d0e0f". .. note:: This option is ignored if a :ref:`cookie-secret-file` is present. In that case the secrets from that file are used in DNS Cookie calculations. Default: 128 bits random secret generated at startup time @@UAHL@unbound.conf@cookie-secret-file@@: ** File from which the secrets are read used in DNS Cookie calculations. When this file exists, the secrets in this file are used and the secret specified by the :ref:`cookie-secret` option is ignored. Enable it by setting a filename, like "/usr/local/etc/unbound_cookiesecrets.txt". The content of this file must be manipulated with the :ref:`add_cookie_secret`, :ref:`drop_cookie_secret` and :ref:`activate_cookie_secret` commands to the :doc:`unbound-control(8)` tool. Please see that manpage on how to perform a safe cookie secret rollover. Default: "" (disabled) @@UAHL@unbound.conf@edns-client-string@@: * * Include an EDNS0 option containing configured ASCII string in queries with destination address matching the configured **. This configuration option can be used multiple times. The most specific match will be used. @@UAHL@unbound.conf@edns-client-string-opcode@@: ** EDNS0 option code for the :ref:`edns-client-string` option, from 0 to 65535. A value from the 'Reserved for Local/Experimental' range (65001-65534) should be used. Default: 65001 @@UAHL@unbound.conf@ede@@: ** If enabled, Unbound will respond with Extended DNS Error codes (:rfc:`8914`). These EDEs provide additional information with a response mainly for, but not limited to, DNS and DNSSEC errors. When the :ref:`val-log-level` option is also set to ``2``, responses with Extended DNS Errors concerning DNSSEC failures will also contain a descriptive text message about the reason for the failure. Default: no @@UAHL@unbound.conf@ede-serve-expired@@: ** If enabled, Unbound will attach an Extended DNS Error (:rfc:`8914`) *Code 3 - Stale Answer* as EDNS0 option to the expired response. .. note:: :ref:`ede: yes` needs to be set as well for this to work. Default: no @@UAHL@unbound.conf@dns-error-reporting@@: ** If enabled, Unbound will send DNS Error Reports (:rfc:`9567`). The name servers need to express support by attaching the Report-Channel EDNS0 option on their replies specifying the reporting agent for the zone. Any errors encountered during resolution that would result in Unbound generating an Extended DNS Error (:rfc:`8914`) will be reported to the zone's reporting agent. The :ref:`ede` option does not need to be enabled for this to work. It is advised that the :ref:`qname-minimisation` option is also enabled to increase privacy on the outgoing reports. Default: no .. _unbound.conf.remote: Remote Control Options ---------------------- These options are part of the ``remote-control:`` section and are the declarations for the remote control facility. If this is enabled, the :doc:`unbound-control(8)` utility can be used to send commands to the running Unbound server. The server uses these options to setup TLS security for the connection. The :doc:`unbound-control(8)` utility also reads this ``remote-control:`` section for options. To setup the correct self-signed certificates use the ``unbound-control-setup(8)`` utility. @@UAHL@unbound.conf.remote@control-enable@@: ** The option is used to enable remote control. If turned off, the server does not listen for control commands. Default: no @@UAHL@unbound.conf.remote@control-interface@@: ** Give IPv4 or IPv6 addresses or local socket path to listen on for control commands. If an interface name is used instead of an IP address, the list of IP addresses on that interface are used. By default localhost (``127.0.0.1`` and ``::1``) is listened to. Use ``0.0.0.0`` and ``::0`` to listen to all interfaces. If you change this and permissions have been dropped, you must restart the server for the change to take effect. If you set it to an absolute path, a unix domain socket is used. This socket does not use the certificates and keys, so those files need not be present. To restrict access, Unbound sets permissions on the file to the user and group that is configured, the access bits are set to allow the group members to access the control socket file. Put users that need to access the socket in the that group. To restrict access further, create a directory to put the control socket in and restrict access to that directory. @@UAHL@unbound.conf.remote@control-port@@: ** The port number to listen on for IPv4 or IPv6 control interfaces. .. note:: If you change this and permissions have been dropped, you must restart the server for the change to take effect. Default: 8953 @@UAHL@unbound.conf.remote@control-use-cert@@: ** For localhost :ref:`control-interface` you can disable the use of TLS by setting this option to "no". For local sockets, TLS is disabled and the value of this option is ignored. Default: yes @@UAHL@unbound.conf.remote@server-key-file@@: ** Path to the server private key. This file is generated by the :doc:`unbound-control-setup(8)` utility. This file is used by the Unbound server, but not by :doc:`unbound-control(8)`. Default: unbound_server.key @@UAHL@unbound.conf.remote@server-cert-file@@: ** Path to the server self signed certificate. This file is generated by the :doc:`unbound-control-setup(8)` utility. This file is used by the Unbound server, and also by :doc:`unbound-control(8)`. Default: unbound_server.pem @@UAHL@unbound.conf.remote@control-key-file@@: ** Path to the control client private key. This file is generated by the :doc:`unbound-control-setup(8)` utility. This file is used by :doc:`unbound-control(8)`. Default: unbound_control.key @@UAHL@unbound.conf.remote@control-cert-file@@: ** Path to the control client certificate. This certificate has to be signed with the server certificate. This file is generated by the :doc:`unbound-control-setup(8)` utility. This file is used by :doc:`unbound-control(8)`. Default: unbound_control.pem .. _unbound.conf.stub: Stub Zone Options ----------------- These options are part of the ``stub-zone:`` section. There may be multiple ``stub-zone:`` sections. Each with a :ref:`name` and zero or more hostnames or IP addresses. For the stub zone this list of nameservers is used. Class IN is assumed. The servers should be authority servers, not recursors; Unbound performs the recursive processing itself for stub zones. The stub zone can be used to configure authoritative data to be used by the resolver that cannot be accessed using the public internet servers. This is useful for company-local data or private zones. Setup an authoritative server on a different host (or different port). Enter a config entry for Unbound with: .. code-block:: text stub-addr: The Unbound resolver can then access the data, without referring to the public internet for it. This setup allows DNSSEC signed zones to be served by that authoritative server, in which case a trusted key entry with the public key can be put in config, so that Unbound can validate the data and set the AD bit on replies for the private zone (authoritative servers do not set the AD bit). This setup makes Unbound capable of answering queries for the private zone, and can even set the AD bit ('authentic'), but the AA ('authoritative') bit is not set on these replies. Consider adding :ref:`server` statements for :ref:`domain-insecure` and for :ref:`local-zone: \ nodefault` for the zone if it is a locally served zone. The :ref:`domain-insecure` option stops DNSSEC from invalidating the zone. The :ref:`local-zone: nodefault` (or :ref:`transparent`) option makes the (reverse-) zone bypass Unbound's filtering of :rfc:`1918` zones. @@UAHL@unbound.conf.stub@name@@: ** Name of the stub zone. This is the full domain name of the zone. @@UAHL@unbound.conf.stub@stub-host@@: ** Name of stub zone nameserver. Is itself resolved before it is used. .. caution:: If the domain (or a subdomain) from this zone is used as the host, it will unavoidably introduce a circular dependency on retrieving the IP addresses of the name server. In that case, it is suggested to use :ref:`stub-addr` instead. Alternatively, :ref:`stub-first: yes` can also work around the circular dependency by trying resolution outside of this zone. However this has the caveat that it would allow escaping this zone when any resolution attempt fails within this zone. To use a non-default port for DNS communication append ``'@'`` with the port number. If TLS is enabled, then you can append a ``'#'`` and a name, then it'll check the TLS authentication certificates with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If only ``'#'`` is used the default port is the configured :ref:`tls-port`. @@UAHL@unbound.conf.stub@stub-addr@@: ** IP address of stub zone nameserver. Can be IPv4 or IPv6. To use a non-default port for DNS communication append ``'@'`` with the port number. If TLS is enabled, then you can append a ``'#'`` and a name, then it'll check the tls authentication certificates with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If only ``'#'`` is used the default port is the configured :ref:`tls-port`. @@UAHL@unbound.conf.stub@stub-prime@@: ** If enabled it performs NS set priming, which is similar to root hints, where it starts using the list of nameservers currently published by the zone. Thus, if the hint list is slightly outdated, the resolver picks up a correct list online. Default: no @@UAHL@unbound.conf.stub@stub-first@@: ** If enabled, a query is attempted without this stub section if it fails. The data could not be retrieved and would have caused SERVFAIL because the servers are unreachable, instead it is tried without this stub section. This can lead to using less specific configured forward/stub/auth zones if any, or end up to otherwise normal recursive resolution for that particular query. Default: no @@UAHL@unbound.conf.stub@stub-tls-upstream@@: ** Enabled or disable whether the queries to this stub use TLS for transport. Default: no @@UAHL@unbound.conf.stub@stub-ssl-upstream@@: ** Alternate syntax for :ref:`stub-tls-upstream`. @@UAHL@unbound.conf.stub@stub-tcp-upstream@@: ** If it is set to "yes" then upstream queries use TCP only for transport regardless of global flag :ref:`tcp-upstream`. Default: no @@UAHL@unbound.conf.stub@stub-no-cache@@: ** If enabled, data inside the stub is not cached. This is useful when you want immediate changes to be visible. Default: no .. _unbound.conf.forward: Forward Zone Options -------------------- These options are part of the ``forward-zone:`` section. There may be multiple ``forward-zone:`` sections. Each with a :ref:`name` and zero or more hostnames or IP addresses. For the forward zone this list of nameservers is used to forward the queries to. The servers listed as :ref:`forward-host` and :ref:`forward-addr` have to handle further recursion for the query. Thus, those servers are not authority servers, but are (just like Unbound is) recursive servers too; Unbound does not perform recursion itself for the forward zone, it lets the remote server do it. Class IN is assumed. CNAMEs are chased by Unbound itself, asking the remote server for every name in the indirection chain, to protect the local cache from illegal indirect referenced items. A :ref:`forward-zone` entry with name ``"."`` and a :ref:`forward-addr` target will forward all queries to that other server (unless it can answer from the cache). @@UAHL@unbound.conf.forward@name@@: ** Name of the forward zone. This is the full domain name of the zone. @@UAHL@unbound.conf.forward@forward-host@@: ** Name of server to forward to. Is itself resolved before it is used. .. caution:: If the domain (or a subdomain) from this zone is used as the host, it will unavoidably introduce a circular dependency on retrieving the IP addresses of the name server. In that case, it is suggested to use :ref:`forward-addr` instead. Alternatively, :ref:`forward-first: yes` can also work around the circular dependency by trying resolution outside of this zone. However this has the caveat that it would allow escaping this zone when any resolution attempt fails within this zone. To use a non-default port for DNS communication append ``'@'`` with the port number. If TLS is enabled, then you can append a ``'#'`` and a name, then it'll check the TLS authentication certificates with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If only ``'#'`` is used the default port is the configured :ref:`tls-port`. @@UAHL@unbound.conf.forward@forward-addr@@: ** IP address of server to forward to. Can be IPv4 or IPv6. To use a non-default port for DNS communication append ``'@'`` with the port number. If TLS is enabled, then you can append a ``'#'`` and a name, then it'll check the tls authentication certificates with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If only ``'#'`` is used the default port is the configured :ref:`tls-port`. At high verbosity it logs the TLS certificate, with TLS enabled. If you leave out the ``'#'`` and auth name from the :ref:`forward-addr`, any name is accepted. The cert must also match a CA from the :ref:`tls-cert-bundle`. @@UAHL@unbound.conf.forward@forward-first@@: ** If a forwarded query is met with a SERVFAIL error and this option is enabled Unbound will fall back to less specific resolution. This can lead to using less specific configured forward/stub/auth zones if any, or end up to otherwise normal recursive resolution for that particular query. Default: no @@UAHL@unbound.conf.forward@forward-tls-upstream@@: ** Enabled or disable whether the queries to this forwarder use TLS for transport. If you enable this, also configure a :ref:`tls-cert-bundle` or use :ref:`tls-win-cert` to load CA certs, otherwise the connections cannot be authenticated. Default: no @@UAHL@unbound.conf.forward@forward-ssl-upstream@@: ** Alternate syntax for :ref:`forward-tls-upstream`. @@UAHL@unbound.conf.forward@forward-tcp-upstream@@: ** If it is set to "yes" then upstream queries use TCP only for transport regardless of global flag :ref:`tcp-upstream`. Default: no @@UAHL@unbound.conf.forward@forward-no-cache@@: ** If enabled, data inside the forward is not cached. This is useful when you want immediate changes to be visible. Default: no .. _unbound.conf.auth: Authority Zone Options ---------------------- These options are part of the ``auth-zone:`` section. Authority zones are configured with ``auth-zone:``, and each one must have a :ref:`name`. There can be multiple ones, by listing multiple ``auth-zone`` section clauses, each with a different name, pertaining to that part of the namespace. The authority zone with the name closest to the name looked up is used. Authority zones can be processed on two distinct, non-exclusive, configurable stages. With :ref:`for-downstream: yes` (default), authority zones are processed after **local-zones** and before cache. When used in this manner, Unbound responds like an authority server with no further processing other than returning an answer from the zone contents. A notable example, in this case, is CNAME records which are returned verbatim to downstream clients without further resolution. With :ref:`for-upstream: yes` (default), authority zones are processed after the cache lookup, just before going to the network to fetch information for recursion. When used in this manner they provide a local copy of an authority server that speeds up lookups for that data during resolving. If both options are enabled (default), client queries for an authority zone are answered authoritatively from Unbound, while internal queries that require data from the authority zone consult the local zone data instead of going to the network. An interesting configuration is :ref:`for-downstream: no`, :ref:`for-upstream: yes` that allows for hyperlocal behavior where both client and internal queries consult the local zone data while resolving. In this case, the aforementioned CNAME example will result in a thoroughly resolved answer. Authority zones can be read from a zonefile. And can be kept updated via AXFR and IXFR. After update the zonefile is rewritten. The update mechanism uses the SOA timer values and performs SOA UDP queries to detect zone changes. If the update fetch fails, the timers in the SOA record are used to time another fetch attempt. Until the SOA expiry timer is reached. Then the zone is expired. When a zone is expired, queries are SERVFAIL, and any new serial number is accepted from the primary (even if older), and if fallback is enabled, the fallback activates to fetch from the upstream instead of the SERVFAIL. @@UAHL@unbound.conf.auth@name@@: ** Name of the authority zone. @@UAHL@unbound.conf.auth@primary@@: ** Where to download a copy of the zone from, with AXFR and IXFR. Multiple primaries can be specified. They are all tried if one fails. To use a non-default port for DNS communication append ``'@'`` with the port number. You can append a ``'#'`` and a name, then AXFR over TLS can be used and the TLS authentication certificates will be checked with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If you point it at another Unbound instance, it would not work because that does not support AXFR/IXFR for the zone, but if you used :ref:`url` to download the zonefile as a text file from a webserver that would work. .. caution:: If you specify the hostname, you cannot use the domain from the zonefile, because it may not have that when retrieving that data, instead use a plain IP address to avoid a circular dependency on retrieving that IP address. @@UAHL@unbound.conf.auth@master@@: ** Alternate syntax for :ref:`primary`. @@UAHL@unbound.conf.auth@url@@: ** Where to download a zonefile for the zone. With HTTP or HTTPS. An example for the url is: .. code-block:: text http://www.example.com/example.org.zone Multiple url statements can be given, they are tried in turn. If only urls are given the SOA refresh timer is used to wait for making new downloads. If also primaries are listed, the primaries are first probed with UDP SOA queries to see if the SOA serial number has changed, reducing the number of downloads. If none of the urls work, the primaries are tried with IXFR and AXFR. For HTTPS, the :ref:`tls-cert-bundle` and the hostname from the url are used to authenticate the connection. If you specify a hostname in the URL, you cannot use the domain from the zonefile, because it may not have that when retrieving that data, instead use a plain IP address to avoid a circular dependency on retrieving that IP address. Avoid dependencies on name lookups by using a notation like ``"http://192.0.2.1/unbound-primaries/example.com.zone"``, with an explicit IP address. @@UAHL@unbound.conf.auth@allow-notify@@: ** With :ref:`allow-notify` you can specify additional sources of notifies. When notified, the server attempts to first probe and then zone transfer. If the notify is from a primary, it first attempts that primary. Otherwise other primaries are attempted. If there are no primaries, but only urls, the file is downloaded when notified. .. note:: The primaries from :ref:`primary` and :ref:`url` statements are allowed notify by default. @@UAHL@unbound.conf.auth@fallback-enabled@@: ** If enabled, Unbound falls back to querying the internet as a resolver for this zone when lookups fail. For example for DNSSEC validation failures. Default: no @@UAHL@unbound.conf.auth@for-downstream@@: ** If enabled, Unbound serves authority responses to downstream clients for this zone. This option makes Unbound behave, for the queries with names in this zone, like one of the authority servers for that zone. Turn it off if you want Unbound to provide recursion for the zone but have a local copy of zone data. If :ref:`for-downstream: no` and :ref:`for-upstream: yes` are set, then Unbound will DNSSEC validate the contents of the zone before serving the zone contents to clients and store validation results in the cache. Default: yes @@UAHL@unbound.conf.auth@for-upstream@@: ** If enabled, Unbound fetches data from this data collection for answering recursion queries. Instead of sending queries over the internet to the authority servers for this zone, it'll fetch the data directly from the zone data. Turn it on when you want Unbound to provide recursion for downstream clients, and use the zone data as a local copy to speed up lookups. Default: yes @@UAHL@unbound.conf.auth@zonemd-check@@: ** Enable this option to check ZONEMD records in the zone. The ZONEMD record is a checksum over the zone data. This includes glue in the zone and data from the zone file, and excludes comments from the zone file. When there is a DNSSEC chain of trust, DNSSEC signatures are checked too. Default: no @@UAHL@unbound.conf.auth@zonemd-reject-absence@@: ** Enable this option to reject the absence of the ZONEMD record. Without it, when ZONEMD is not there it is not checked. It is useful to enable for a non-DNSSEC signed zone where the operator wants to require the verification of a ZONEMD, hence a missing ZONEMD is a failure. The action upon failure is controlled by the :ref:`zonemd-permissive-mode` option, for log only or also block the zone. Without the option, absence of a ZONEMD is only a failure when the zone is DNSSEC signed, and we have a trust anchor, and the DNSSEC verification of the absence of the ZONEMD fails. With the option enabled, the absence of a ZONEMD is always a failure, also for nonDNSSEC signed zones. Default: no @@UAHL@unbound.conf.auth@zonefile@@: ** The filename where the zone is stored. If not given then no zonefile is used. If the file does not exist or is empty, Unbound will attempt to fetch zone data (eg. from the primary servers). .. _unbound.conf.view: View Options ------------ These options are part of the ``view:`` section. There may be multiple ``view:`` sections. Each with a :ref:`name` and zero or more :ref:`local-zone` and :ref:`local-data` options. Views can also contain :ref:`view-first`, :ref:`response-ip`, :ref:`response-ip-data` and :ref:`local-data-ptr` options. View can be mapped to requests by specifying the view name in an :ref:`access-control-view` option. Options from matching views will override global options. Global options will be used if no matching view is found, or when the matching view does not have the option specified. @@UAHL@unbound.conf.view@name@@: ** Name of the view. Must be unique. This name is used in the :ref:`access-control-view` option. @@UAHL@unbound.conf.view@local-zone@@: * * View specific local zone elements. Has the same types and behaviour as the global :ref:`local-zone` elements. When there is at least one *local-zone:* specified and :ref:`view-first: no` is set, the default local-zones will be added to this view. Defaults can be disabled using the nodefault type. When :ref:`view-first: yes` is set or when a view does not have a :ref:`local-zone`, the global :ref:`local-zone` will be used including it's default zones. @@UAHL@unbound.conf.view@local-data@@: *""* View specific local data elements. Has the same behaviour as the global :ref:`local-data` elements. @@UAHL@unbound.conf.view@local-data-ptr@@: *"IPaddr name"* View specific local-data-ptr elements. Has the same behaviour as the global :ref:`local-data-ptr` elements. @@UAHL@unbound.conf.view@response-ip@@: * * This requires use of the ``respip`` module. Similar to :ref:`response-ip` but only applies to this view. @@UAHL@unbound.conf.view@response-ip-data@@: * <"resource record string">* This requires use of the ``respip`` module. Similar to :ref:`response-ip-data` but only applies to this view. @@UAHL@unbound.conf.view@view-first@@: ** If enabled, it attempts to use the global :ref:`local-zone` and :ref:`local-data` if there is no match in the view specific options. Default: no .. _unbound.conf.python: Python Module Options --------------------- These options are part of the ``python:`` section. The ``python:`` section gives the settings for the *python(1)* script module. This module acts like the iterator and validator modules do, on queries and answers. To enable the script module it has to be compiled into the daemon, and the word ``python`` has to be put in the :ref:`module-config` option (usually first, or between the validator and iterator). Multiple instances of the python module are supported by adding the word ``python`` more than once. If the :ref:`chroot` option is enabled, you should make sure Python's library directory structure is bind mounted in the new root environment, see *mount(8)*. Also the :ref:`python-script` path should be specified as an absolute path relative to the new root, or as a relative path to the working directory. @@UAHL@unbound.conf.python@python-script@@: ** The script file to load. Repeat this option for every python module instance added to the :ref:`module-config` option. .. _unbound.conf.dynlib: Dynamic Library Module Options ------------------------------ These options are part of the ``dynlib:`` section. The ``dynlib:`` section gives the settings for the ``dynlib`` module. This module is only a very small wrapper that allows dynamic modules to be loaded on runtime instead of being compiled into the application. To enable the dynlib module it has to be compiled into the daemon, and the word ``dynlib`` has to be put in the :ref:`module-config` option. Multiple instances of dynamic libraries are supported by adding the word ``dynlib`` more than once. The :ref:`dynlib-file` path should be specified as an absolute path relative to the new path set by :ref:`chroot`, or as a relative path to the working directory. @@UAHL@unbound.conf.dynlib@dynlib-file@@: ** The dynamic library file to load. Repeat this option for every dynlib module instance added to the :ref:`module-config` option. DNS64 Module Options -------------------- These options are part of the ``server:`` section. The ``dns64`` module must be configured in the :ref:`module-config` directive, e.g.: .. code-block:: text module-config: "dns64 validator iterator" and be compiled into the daemon to be enabled. .. note:: If combining the ``respip`` and ``dns64`` modules, the ``respip`` module needs to appear before the ``dns64`` module in the :ref:`module-config` configuration option so that response IP and/or RPZ feeds can properly filter responses regardless of DNS64 synthesis. @@UAHL@unbound.conf.dns64@dns64-prefix@@: ** This sets the DNS64 prefix to use to synthesize AAAA records with. It must be /96 or shorter. Default: 64:ff9b::/96 @@UAHL@unbound.conf.dns64@dns64-synthall@@: ** .. warning:: Debugging feature! If enabled, synthesize all AAAA records despite the presence of actual AAAA records. Default: no @@UAHL@unbound.conf.dns64@dns64-ignore-aaaa@@: ** List domain for which the AAAA records are ignored and the A record is used by DNS64 processing instead. Can be entered multiple times, list a new domain for which it applies, one per line. Applies also to names underneath the name given. NAT64 Options ------------- These options are part of the ``server:`` section. NAT64 operation allows using a NAT64 prefix for outbound requests to IPv4-only servers. @@UAHL@unbound.conf.nat64@do-nat64@@: ** Use NAT64 to reach IPv4-only servers. Consider also enabling :ref:`prefer-ip6` to prefer native IPv6 connections to nameservers. Default: no @@UAHL@unbound.conf.nat64@nat64-prefix@@: ** Use a specific NAT64 prefix to reach IPv4-only servers. The prefix length must be one of /32, /40, /48, /56, /64 or /96. The NAT64 prefix is allowed by the :ref:`do-not-query-address` option, so that there is a clear outcome of addresses in both; the NAT64 prefix is allowed. The IPv4 address could be filtered by the :ref:`do-not-query-address` option, if needed. Allowing the NAT64 prefix is useful when using do-not-query-address for a cluster of machines that is IPv6-only and uses NAT64, but does not have internet access. Default: 64:ff9b::/96 (same as :ref:`dns64-prefix`) .. _unbound.conf.dnscrypt: DNSCrypt Options ---------------- These options are part of the ``dnscrypt:`` section. The ``dnscrypt:`` section gives the settings of the dnscrypt channel. While those options are available, they are only meaningful if Unbound was compiled with ``--enable-dnscrypt``. Currently certificate and secret/public keys cannot be generated by Unbound. You can use dnscrypt-wrapper to generate those: https://github.com/cofyc/dnscrypt-wrapper/blob/master/README.md#usage @@UAHL@unbound.conf.dnscrypt@dnscrypt-enable@@: ** Whether or not the dnscrypt config should be enabled. You may define configuration but not activate it. Default: no @@UAHL@unbound.conf.dnscrypt@dnscrypt-port@@: ** On which port should dnscrypt should be activated. .. note:: There should be a matching interface option defined in the :ref:`server:` section for this port. @@UAHL@unbound.conf.dnscrypt@dnscrypt-provider@@: ** The provider name to use to distribute certificates. This is of the form: .. code-block:: text 2.dnscrypt-cert.example.com. .. important:: The name *MUST* end with a dot. @@UAHL@unbound.conf.dnscrypt@dnscrypt-secret-key@@: ** Path to the time limited secret key file. This option may be specified multiple times. @@UAHL@unbound.conf.dnscrypt@dnscrypt-provider-cert@@: ** Path to the certificate related to the :ref:`dnscrypt-secret-key`. This option may be specified multiple times. @@UAHL@unbound.conf.dnscrypt@dnscrypt-provider-cert-rotated@@: ** Path to a certificate that we should be able to serve existing connection from but do not want to advertise over :ref:`dnscrypt-provider` 's TXT record certs distribution. A typical use case is when rotating certificates, existing clients may still use the client magic from the old cert in their queries until they fetch and update the new cert. Likewise, it would allow one to prime the new cert/key without distributing the new cert yet, this can be useful when using a network of servers using anycast and on which the configuration may not get updated at the exact same time. By priming the cert, the servers can handle both old and new certs traffic while distributing only one. This option may be specified multiple times. @@UAHL@unbound.conf.dnscrypt@dnscrypt-shared-secret-cache-size@@: ** Give the size of the data structure in which the shared secret keys are kept in. In bytes or use m(mega), k(kilo), g(giga). The shared secret cache is used when a same client is making multiple queries using the same public key. It saves a substantial amount of CPU. Default: 4m @@UAHL@unbound.conf.dnscrypt@dnscrypt-shared-secret-cache-slabs@@: ** Number of slabs in the dnscrypt shared secrets cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) @@UAHL@unbound.conf.dnscrypt@dnscrypt-nonce-cache-size@@: ** Give the size of the data structure in which the client nonces are kept in. In bytes or use m(mega), k(kilo), g(giga). The nonce cache is used to prevent dnscrypt message replaying. Client nonce should be unique for any pair of client pk/server sk. Default: 4m @@UAHL@unbound.conf.dnscrypt@dnscrypt-nonce-cache-slabs@@: ** Number of slabs in the dnscrypt nonce cache. Slabs reduce lock contention by threads. Must be set to a power of 2. Setting (close) to the number of cpus is a fairly good setting. If left unconfigured, it will be configured automatically to be a power of 2 close to the number of configured threads in multi-threaded environments. Default: (unconfigured) EDNS Client Subnet Module Options --------------------------------- These options are part of the ``server:`` section. The ECS module must be configured in the :ref:`module-config` directive, e.g.: .. code-block:: text module-config: "subnetcache validator iterator" and be compiled into the daemon to be enabled. If the destination address is allowed in the configuration Unbound will add the EDNS0 option to the query containing the relevant part of the client's address. When an answer contains the ECS option the response and the option are placed in a specialized cache. If the authority indicated no support, the response is stored in the regular cache. Additionally, when a client includes the option in its queries, Unbound will forward the option when sending the query to addresses that are explicitly allowed in the configuration using :ref:`send-client-subnet`. The option will always be forwarded, regardless the allowed addresses, when :ref:`client-subnet-always-forward: yes` is set. In this case the lookup in the regular cache is skipped. The maximum size of the ECS cache is controlled by :ref:`msg-cache-size` in the configuration file. On top of that, for each query only 100 different subnets are allowed to be stored for each address family. Exceeding that number, older entries will be purged from cache. Note that due to the nature of how EDNS Client Subnet works, by segregating the client IP space in order to try and have tailored responses for prefixes of unknown sizes, resolution and cache response performance are impacted as a result. Usage of the subnetcache module should only be enabled in installations that require such functionality where the resolver and the clients belong to different networks. An example of that is an open resolver installation. This module does not interact with the :ref:`serve-expired\*` and :ref:`prefetch` options. @@UAHL@unbound.conf.ecs@send-client-subnet@@: ** Send client source address to this authority. Append /num to indicate a classless delegation netblock, for example like ``10.2.3.4/24`` or ``2001::11/64``. Can be given multiple times. Authorities not listed will not receive edns-subnet information, unless domain in query is specified in :ref:`client-subnet-zone`. @@UAHL@unbound.conf.ecs@client-subnet-zone@@: ** Send client source address in queries for this domain and its subdomains. Can be given multiple times. Zones not listed will not receive edns-subnet information, unless hosted by authority specified in :ref:`send-client-subnet`. @@UAHL@unbound.conf.ecs@client-subnet-always-forward@@: ** Specify whether the ECS address check (configured using :ref:`send-client-subnet`) is applied for all queries, even if the triggering query contains an ECS record, or only for queries for which the ECS record is generated using the querier address (and therefore did not contain ECS data in the client query). If enabled, the address check is skipped when the client query contains an ECS record. And the lookup in the regular cache is skipped. Default: no @@UAHL@unbound.conf.ecs@max-client-subnet-ipv6@@: ** Specifies the maximum prefix length of the client source address we are willing to expose to third parties for IPv6. Default: 56 @@UAHL@unbound.conf.ecs@max-client-subnet-ipv4@@: ** Specifies the maximum prefix length of the client source address we are willing to expose to third parties for IPv4. Default: 24 @@UAHL@unbound.conf.ecs@min-client-subnet-ipv6@@: ** Specifies the minimum prefix length of the IPv6 source mask we are willing to accept in queries. Shorter source masks result in REFUSED answers. Source mask of 0 is always accepted. Default: 0 @@UAHL@unbound.conf.ecs@min-client-subnet-ipv4@@: ** Specifies the minimum prefix length of the IPv4 source mask we are willing to accept in queries. Shorter source masks result in REFUSED answers. Source mask of 0 is always accepted. Default: 0 @@UAHL@unbound.conf.ecs@max-ecs-tree-size-ipv4@@: ** Specifies the maximum number of subnets ECS answers kept in the ECS radix tree. This number applies for each qname/qclass/qtype tuple. Default: 100 @@UAHL@unbound.conf.ecs@max-ecs-tree-size-ipv6@@: ** Specifies the maximum number of subnets ECS answers kept in the ECS radix tree. This number applies for each qname/qclass/qtype tuple. Default: 100 Opportunistic IPsec Support Module Options ------------------------------------------ These options are part of the ``server:`` section. The IPsec module must be configured in the :ref:`module-config` directive, e.g.: .. code-block:: text module-config: "ipsecmod validator iterator" and be compiled into Unbound by using ``--enable-ipsecmod`` to be enabled. When Unbound receives an A/AAAA query that is not in the cache and finds a valid answer, it will withhold returning the answer and instead will generate an IPSECKEY subquery for the same domain name. If an answer was found, Unbound will call an external hook passing the following arguments: QNAME Domain name of the A/AAAA and IPSECKEY query. In string format. IPSECKEY TTL TTL of the IPSECKEY RRset. A/AAAA String of space separated IP addresses present in the A/AAAA RRset. The IP addresses are in string format. IPSECKEY String of space separated IPSECKEY RDATA present in the IPSECKEY RRset. The IPSECKEY RDATA are in DNS presentation format. The A/AAAA answer is then cached and returned to the client. If the external hook was called the TTL changes to ensure it doesn't surpass :ref:`ipsecmod-max-ttl`. The same procedure is also followed when :ref:`prefetch: yes` is set, but the A/AAAA answer is given to the client before the hook is called. :ref:`ipsecmod-max-ttl` ensures that the A/AAAA answer given from cache is still relevant for opportunistic IPsec. @@UAHL@unbound.conf@ipsecmod-enabled@@: ** Specifies whether the IPsec module is enabled or not. The IPsec module still needs to be defined in the :ref:`module-config` directive. This option facilitates turning on/off the module without restarting/reloading Unbound. Default: yes @@UAHL@unbound.conf@ipsecmod-hook@@: ** Specifies the external hook that Unbound will call with *system(3)*. The file can be specified as an absolute/relative path. The file needs the proper permissions to be able to be executed by the same user that runs Unbound. It must be present when the IPsec module is defined in the :ref:`module-config` directive. @@UAHL@unbound.conf@ipsecmod-strict@@: ** If enabled Unbound requires the external hook to return a success value of 0. Failing to do so Unbound will reply with SERVFAIL. The A/AAAA answer will also not be cached. Default: no @@UAHL@unbound.conf@ipsecmod-max-ttl@@: ** Time to live maximum for A/AAAA cached records after calling the external hook. Default: 3600 @@UAHL@unbound.conf@ipsecmod-ignore-bogus@@: ** Specifies the behaviour of Unbound when the IPSECKEY answer is bogus. If set to yes, the hook will be called and the A/AAAA answer will be returned to the client. If set to no, the hook will not be called and the answer to the A/AAAA query will be SERVFAIL. Mainly used for testing. Default: no @@UAHL@unbound.conf@ipsecmod-allow@@: ** Allow the IPsec module functionality for the domain so that the module logic will be executed. Can be given multiple times, for different domains. If the option is not specified, all domains are treated as being allowed (default). @@UAHL@unbound.conf@ipsecmod-whitelist@@: ** Alternate syntax for :ref:`ipsecmod-allow`. .. _unbound.conf.cachedb: Cache DB Module Options ----------------------- These options are part of the ``cachedb:`` section. The Cache DB module must be configured in the :ref:`module-config` directive, e.g.: .. code-block:: text module-config: "validator cachedb iterator" and be compiled into the daemon with ``--enable-cachedb``. If this module is enabled and configured, the specified backend database works as a second level cache; when Unbound cannot find an answer to a query in its built-in in-memory cache, it consults the specified backend. If it finds a valid answer in the backend, Unbound uses it to respond to the query without performing iterative DNS resolution. If Unbound cannot even find an answer in the backend, it resolves the query as usual, and stores the answer in the backend. This module interacts with the *serve-expired-\** options and will reply with expired data if Unbound is configured for that. If Unbound was built with ``--with-libhiredis`` on a system that has installed the hiredis C client library of Redis, then the ``redis`` backend can be used. This backend communicates with the specified Redis server over a TCP connection to store and retrieve cache data. It can be used as a persistent and/or shared cache backend. .. note:: Unbound never removes data stored in the Redis server, even if some data have expired in terms of DNS TTL or the Redis server has cached too much data; if necessary the Redis server must be configured to limit the cache size, preferably with some kind of least-recently-used eviction policy. Additionally, the :ref:`redis-expire-records` option can be used in order to set the relative DNS TTL of the message as timeout to the Redis records; keep in mind that some additional memory is used per key and that the expire information is stored as absolute Unix timestamps in Redis (computer time must be stable). This backend uses synchronous communication with the Redis server based on the assumption that the communication is stable and sufficiently fast. The thread waiting for a response from the Redis server cannot handle other DNS queries. Although the backend has the ability to reconnect to the server when the connection is closed unexpectedly and there is a configurable timeout in case the server is overly slow or hangs up, these cases are assumed to be very rare. If connection close or timeout happens too often, Unbound will be effectively unusable with this backend. It's the administrator's responsibility to make the assumption hold. The ``cachedb:`` section gives custom settings of the cache DB module. @@UAHL@unbound.conf.cachedb@backend@@: ** Specify the backend database name. The default database is the in-memory backend named ``testframe``, which, as the name suggests, is not of any practical use. Depending on the build-time configuration, ``redis`` backend may also be used as described above. Default: testframe @@UAHL@unbound.conf.cachedb@secret-seed@@: *""* Specify a seed to calculate a hash value from query information. This value will be used as the key of the corresponding answer for the backend database and can be customized if the hash should not be predictable operationally. If the backend database is shared by multiple Unbound instances, all instances must use the same secret seed. Default: "default" @@UAHL@unbound.conf.cachedb@cachedb-no-store@@: ** If the backend should be read from, but not written to. This makes this instance not store dns messages in the backend. But if data is available it is retrieved. Default: no @@UAHL@unbound.conf.cachedb@cachedb-check-when-serve-expired@@: ** If enabled, the cachedb is checked before an expired response is returned. When :ref:`serve-expired` is enabled, without :ref:`serve-expired-client-timeout` , it then does not immediately respond with an expired response from cache, but instead first checks the cachedb for valid contents, and if so returns it. If the cachedb also has no valid contents, the serve expired response is sent. If also :ref:`serve-expired-client-timeout` is enabled, the expired response is delayed until the timeout expires. Unless the lookup succeeds within the timeout. Default: yes The following ``cachedb:`` options are specific to the ``redis`` backend. @@UAHL@unbound.conf.cachedb@redis-server-host@@: ** The IP (either v6 or v4) address or domain name of the Redis server. In general an IP address should be specified as otherwise Unbound will have to resolve the name of the server every time it establishes a connection to the server. Default: 127.0.0.1 @@UAHL@unbound.conf.cachedb@redis-server-port@@: ** The TCP port number of the Redis server. Default: 6379 @@UAHL@unbound.conf.cachedb@redis-server-path@@: ** The unix socket path to connect to the Redis server. Unix sockets may have better throughput than the IP address option. Default: "" (disabled) @@UAHL@unbound.conf.cachedb@redis-server-password@@: *""* The Redis AUTH password to use for the Redis server. Only relevant if Redis is configured for client password authorisation. Default: "" (disabled) @@UAHL@unbound.conf.cachedb@redis-timeout@@: ** The period until when Unbound waits for a response from the Redis server. If this timeout expires Unbound closes the connection, treats it as if the Redis server does not have the requested data, and will try to re-establish a new connection later. Default: 100 @@UAHL@unbound.conf.cachedb@redis-command-timeout@@: ** The timeout to use for Redis commands, in milliseconds. If ``0``, it uses the :ref:`redis-timeout` value. Default: 0 @@UAHL@unbound.conf.cachedb@redis-connect-timeout@@: ** The timeout to use for Redis connection set up, in milliseconds. If ``0``, it uses the :ref:`redis-timeout` value. Default: 0 @@UAHL@unbound.conf.cachedb@redis-expire-records@@: ** If Redis record expiration is enabled. If yes, Unbound sets timeout for Redis records so that Redis can evict keys that have expired automatically. If Unbound is configured with :ref:`serve-expired` and :ref:`serve-expired-ttl: 0`, this option is internally reverted to "no". .. note:: Redis "SET ... EX" support is required for this option (Redis >= 2.6.12). Default: no @@UAHL@unbound.conf.cachedb@redis-logical-db@@: ** The logical database in Redis to use. These are databases in the same Redis instance sharing the same configuration and persisted in the same RDB/AOF file. If unsure about using this option, Redis documentation (https://redis.io/commands/select/) suggests not to use a single Redis instance for multiple unrelated applications. The default database in Redis is 0 while other logical databases need to be explicitly SELECT'ed upon connecting. Default: 0 @@UAHL@unbound.conf.cachedb@redis-replica-server-host@@: ** The IP (either v6 or v4) address or domain name of the Redis server. In general an IP address should be specified as otherwise Unbound will have to resolve the name of the server every time it establishes a connection to the server. This server is treated as a read-only replica server (https://redis.io/docs/management/replication/#read-only-replica). If specified, all Redis read commands will go to this replica server, while the write commands will go to the :ref:`redis-server-host`. Default: "" (disabled). @@UAHL@unbound.conf.cachedb@redis-replica-server-port@@: ** The TCP port number of the Redis replica server. Default: 6379 @@UAHL@unbound.conf.cachedb@redis-replica-server-path@@: ** The unix socket path to connect to the Redis replica server. Unix sockets may have better throughput than the IP address option. Default: "" (disabled) @@UAHL@unbound.conf.cachedb@redis-replica-server-password@@: *""* The Redis AUTH password to use for the Redis server. Only relevant if Redis is configured for client password authorisation. Default: "" (disabled) @@UAHL@unbound.conf.cachedb@redis-replica-timeout@@: ** The period until when Unbound waits for a response from the Redis replica server. If this timeout expires Unbound closes the connection, treats it as if the Redis server does not have the requested data, and will try to re-establish a new connection later. Default: 100 @@UAHL@unbound.conf.cachedb@redis-replica-command-timeout@@: ** The timeout to use for Redis replica commands, in milliseconds. If ``0``, it uses the :ref:`redis-replica-timeout` value. Default: 0 @@UAHL@unbound.conf.cachedb@redis-replica-connect-timeout@@: ** The timeout to use for Redis replica connection set up, in milliseconds. If ``0``, it uses the :ref:`redis-replica-timeout` value. Default: 0 @@UAHL@unbound.conf.cachedb@redis-replica-logical-db@@: ** Same as :ref:`redis-logical-db` but for the Redis replica server. Default: 0 .. _unbound.conf.dnstap: DNSTAP Options -------------- These options are part of the ``dnstap:`` section. DNSTAP is a flexible, structured binary log format for DNS software. When compiled in by using ``--enable-dnstap``, it can be enabled in the ``dnstap:`` section. This starts an extra thread (when compiled with threading) that writes the log information to the destination. If Unbound is compiled without threading it does not spawn a thread, but connects per-process to the destination. @@UAHL@unbound.conf.dnstap@dnstap-enable@@: ** If dnstap is enabled. If yes, it connects to the DNSTAP server and if any of the *dnstap-log-..-messages:* options is enabled it sends logs for those messages to the server. Default: no @@UAHL@unbound.conf.dnstap@dnstap-bidirectional@@: ** Use frame streams in bidirectional mode to transfer DNSTAP messages. Default: yes @@UAHL@unbound.conf.dnstap@dnstap-socket-path@@: ** Sets the unix socket file name for connecting to the server that is listening on that socket. Default: @DNSTAP_SOCKET_PATH@ @@UAHL@unbound.conf.dnstap@dnstap-ip@@: ** If ``""``, the unix socket is used, if set with an IP address (IPv4 or IPv6) that address is used to connect to the server. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-tls@@: ** Set this to use TLS to connect to the server specified in :ref:`dnstap-ip`. If set to no, TCP is used to connect to the server. Default: yes @@UAHL@unbound.conf.dnstap@dnstap-tls-server-name@@: ** The TLS server name to authenticate the server with. Used when :ref:`dnstap-tls: yes` is set. If ``""`` it is ignored. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-tls-cert-bundle@@: ** The pem file with certs to verify the TLS server certificate. If ``""`` the server default cert bundle is used, or the windows cert bundle on windows. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-tls-client-key-file@@: ** The client key file for TLS client authentication. If ``""`` client authentication is not used. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-tls-client-cert-file@@: ** The client cert file for TLS client authentication. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-send-identity@@: ** If enabled, the server identity is included in the log messages. Default: no @@UAHL@unbound.conf.dnstap@dnstap-send-version@@: ** If enabled, the server version if included in the log messages. Default: no @@UAHL@unbound.conf.dnstap@dnstap-identity@@: ** The identity to send with messages, if ``""`` the hostname is used. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-version@@: ** The version to send with messages, if ``""`` the package version is used. Default: "" @@UAHL@unbound.conf.dnstap@dnstap-sample-rate@@: ** The sample rate for log of messages, it logs only 1/N messages. With 0 it is disabled. This is useful in a high volume environment, where log functionality would otherwise not be reliable. For example 10 would spend only 1/10th time on logging, and 100 would only spend a hundredth of the time on logging. Default: 0 (disabled) @@UAHL@unbound.conf.dnstap@dnstap-log-resolver-query-messages@@: ** Enable to log resolver query messages. These are messages from Unbound to upstream servers. Default: no @@UAHL@unbound.conf.dnstap@dnstap-log-resolver-response-messages@@: ** Enable to log resolver response messages. These are replies from upstream servers to Unbound. Default: no @@UAHL@unbound.conf.dnstap@dnstap-log-client-query-messages@@: ** Enable to log client query messages. These are client queries to Unbound. Default: no @@UAHL@unbound.conf.dnstap@dnstap-log-client-response-messages@@: ** Enable to log client response messages. These are responses from Unbound to clients. Default: no @@UAHL@unbound.conf.dnstap@dnstap-log-forwarder-query-messages@@: ** Enable to log forwarder query messages. Default: no @@UAHL@unbound.conf.dnstap@dnstap-log-forwarder-response-messages@@: ** Enable to log forwarder response messages. Default: no .. _unbound.conf.rpz: Response Policy Zone Options ---------------------------- These options are part of the ``rpz:`` section. Response Policy Zones are configured with ``rpz:`` section clauses, and each one must have a :ref:`name` option. There can be multiple ones, by listing multiple ``rpz:`` section clauses, each with a different name. RPZ sections are applied in order of configuration and any match from an earlier RPZ zone will terminate the RPZ lookup. Note that a PASSTHRU action is still considered a match. The respip module needs to be added to the :ref:`module-config`, e.g.: .. code-block:: text module-config: "respip validator iterator" .. note:: If combining the ``respip`` and ``dns64`` modules, the ``respip`` module needs to appear before the ``dns64`` module in the :ref:`module-config` configuration option so that response IP and/or RPZ feeds can properly filter responses regardless of DNS64 synthesis. QNAME, Response IP Address, nsdname, nsip and clientip triggers are supported. Supported actions are: NXDOMAIN, NODATA, PASSTHRU, DROP, Local Data, tcp-only and drop. RPZ QNAME triggers are applied after any :ref:`local-zone` and before any :ref:`auth-zone`. The RPZ zone is a regular DNS zone formatted with a SOA start record as usual. The items in the zone are entries, that specify what to act on (the trigger) and what to do (the action). The trigger to act on is recorded in the name, the action to do is recorded as the resource record. The names all end in the zone name, so you could type the trigger names without a trailing dot in the zonefile. An example RPZ record, that answers ``example.com`` with ``NXDOMAIN``: .. code-block:: text example.com CNAME . The triggers are encoded in the name on the left .. code-block:: text name query name netblock.rpz-client-ip client IP address netblock.rpz-ip response IP address in the answer name.rpz-nsdname nameserver name netblock.rpz-nsip nameserver IP address The netblock is written as ``.``. For IPv6 use ``'zz'`` for ``'::'``. Specify individual addresses with scope length of 32 or 128. For example, ``24.10.100.51.198.rpz-ip`` is ``198.51.100.10/24`` and ``32.10.zz.db8.2001.rpz-ip`` is ``2001:db8:0:0:0:0:0:10/32``. The actions are specified with the record on the right .. code-block:: text CNAME . nxdomain reply CNAME *. nodata reply CNAME rpz-passthru. do nothing, allow to continue CNAME rpz-drop. the query is dropped CNAME rpz-tcp-only. answer over TCP A 192.0.2.1 answer with this IP address Other records like AAAA, TXT and other CNAMEs (not rpz-..) can also be used to answer queries with that content. @@UAHL@unbound.conf.rpz@name@@: ** Name of the authority zone. @@UAHL@unbound.conf.rpz@primary@@: ** Where to download a copy of the zone from, with AXFR and IXFR. Multiple primaries can be specified. They are all tried if one fails. To use a non-default port for DNS communication append ``'@'`` with the port number. You can append a ``'#'`` and a name, then AXFR over TLS can be used and the TLS authentication certificates will be checked with that name. If you combine the ``'@'`` and ``'#'``, the ``'@'`` comes first. If you point it at another Unbound instance, it would not work because that does not support AXFR/IXFR for the zone, but if you used :ref:`url` to download the zonefile as a text file from a webserver that would work. If you specify the hostname, you cannot use the domain from the zonefile, because it may not have that when retrieving that data, instead use a plain IP address to avoid a circular dependency on retrieving that IP address. @@UAHL@unbound.conf.rpz@master@@: ** Alternate syntax for :ref:`primary`. @@UAHL@unbound.conf.rpz@url@@: ** Where to download a zonefile for the zone. With HTTP or HTTPS. An example for the url is: .. code-block:: text http://www.example.com/example.org.zone Multiple url statements can be given, they are tried in turn. If only urls are given the SOA refresh timer is used to wait for making new downloads. If also primaries are listed, the primaries are first probed with UDP SOA queries to see if the SOA serial number has changed, reducing the number of downloads. If none of the URLs work, the primaries are tried with IXFR and AXFR. For HTTPS, the :ref:`tls-cert-bundle` and the hostname from the url are used to authenticate the connection. @@UAHL@unbound.conf.rpz@allow-notify@@: ** With :ref:`allow-notify` you can specify additional sources of notifies. When notified, the server attempts to first probe and then zone transfer. If the notify is from a primary, it first attempts that primary. Otherwise other primaries are attempted. If there are no primaries, but only urls, the file is downloaded when notified. .. note:: The primaries from :ref:`primary` and :ref:`url` statements are allowed notify by default. @@UAHL@unbound.conf.rpz@zonefile@@: ** The filename where the zone is stored. If not given then no zonefile is used. If the file does not exist or is empty, Unbound will attempt to fetch zone data (eg. from the primary servers). @@UAHL@unbound.conf.rpz@rpz-action-override@@: ** Always use this RPZ action for matching triggers from this zone. Possible actions are: *nxdomain*, *nodata*, *passthru*, *drop*, *disabled* and *cname*. @@UAHL@unbound.conf.rpz@rpz-cname-override@@: ** The CNAME target domain to use if the cname action is configured for :ref:`rpz-action-override`. @@UAHL@unbound.conf.rpz@rpz-log@@: ** Log all applied RPZ actions for this RPZ zone. Default: no @@UAHL@unbound.conf.rpz@rpz-log-name@@: ** Specify a string to be part of the log line, for easy referencing. @@UAHL@unbound.conf.rpz@rpz-signal-nxdomain-ra@@: ** Signal when a query is blocked by the RPZ with NXDOMAIN with an unset RA flag. This allows certain clients, like dnsmasq, to infer that the domain is externally blocked. Default: no @@UAHL@unbound.conf.rpz@for-downstream@@: ** If enabled the zone is authoritatively answered for and queries for the RPZ zone information are answered to downstream clients. This is useful for monitoring scripts, that can then access the SOA information to check if the RPZ information is up to date. Default: no @@UAHL@unbound.conf.rpz@tags@@: *""* Limit the policies from this RPZ section to clients with a matching tag. Tags need to be defined in :ref:`define-tag` and can be assigned to client addresses using :ref:`access-control-tag` or :ref:`interface-tag`. Enclose list of tags in quotes (``""``) and put spaces between tags. If no tags are specified the policies from this section will be applied for all clients. Memory Control Example ---------------------- In the example config settings below memory usage is reduced. Some service levels are lower, notable very large data and a high TCP load are no longer supported. Very large data and high TCP loads are exceptional for the DNS. DNSSEC validation is enabled, just add trust anchors. If you do not have to worry about programs using more than 3 Mb of memory, the below example is not for you. Use the defaults to receive full service, which on BSD-32bit tops out at 30-40 Mb after heavy usage. .. code-block:: text # example settings that reduce memory usage server: num-threads: 1 outgoing-num-tcp: 1 # this limits TCP service, uses less buffers. incoming-num-tcp: 1 outgoing-range: 60 # uses less memory, but less performance. msg-buffer-size: 8192 # note this limits service, 'no huge stuff'. msg-cache-size: 100k msg-cache-slabs: 1 rrset-cache-size: 100k rrset-cache-slabs: 1 infra-cache-numhosts: 200 infra-cache-slabs: 1 key-cache-size: 100k key-cache-slabs: 1 neg-cache-size: 10k num-queries-per-thread: 30 target-fetch-policy: "2 1 0 0 0 0" harden-large-queries: "yes" harden-short-bufsize: "yes" Files ----- @UNBOUND_RUN_DIR@ default Unbound working directory. @UNBOUND_CHROOT_DIR@ default *chroot(2)* location. @ub_conf_file@ Unbound configuration file. @UNBOUND_PIDFILE@ default Unbound pidfile with process ID of the running daemon. unbound.log Unbound log file. Default is to log to *syslog(3)*. See Also -------- :doc:`unbound(8)`, :doc:`unbound-checkonf(8)`. unbound-1.25.1/doc/unbound.rst0000644000175000017500000000606515203270263015666 0ustar wouterwouter.. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. WHEN EDITING MAKE SURE EACH SENTENCE STARTS ON A NEW LINE .. IT HELPS RENDERERS TO DO THE RIGHT THING WRT SPACE .. IT HELPS PEOPLE DIFFING THE CHANGES .. program:: unbound unbound(8) ========== Synopsis -------- **unbound** [``-hdpVv``] [``-c ``] Description ----------- ``unbound`` is a caching DNS resolver. It uses a built in list of authoritative nameservers for the root zone (``.``), the so called root hints. On receiving a DNS query it will ask the root nameservers for an answer and will in almost all cases receive a delegation to a top level domain (TLD) authoritative nameserver. It will then ask that nameserver for an answer. It will recursively continue until an answer is found or no answer is available (NXDOMAIN). For performance and efficiency reasons that answer is cached for a certain time (the answer's time-to-live or TTL). A second query for the same name will then be answered from the cache. Unbound can also do DNSSEC validation. To use a locally running Unbound for resolving put: .. code-block:: text nameserver 127.0.0.1 into *resolv.conf(5)*. If authoritative DNS is needed as well using :external+nsd:doc:`manpages/nsd`, careful setup is required because authoritative nameservers and resolvers are using the same port number (53). The available options are: .. option:: -h Show the version number and commandline option help, and exit. .. option:: -c Set the config file with settings for unbound to read instead of reading the file at the default location, :file:`@ub_conf_file@`. The syntax is described in :doc:`unbound.conf(5)`. .. option:: -d Debug flag: do not fork into the background, but stay attached to the console. This flag will also delay writing to the log file until the thread-spawn time, so that most config and setup errors appear on stderr. If given twice or more, logging does not switch to the log file or to syslog, but the log messages are printed to stderr all the time. .. option:: -p Don't use a pidfile. This argument should only be used by supervision systems which can ensure that only one instance of Unbound will run concurrently. .. option:: -v Increase verbosity. If given multiple times, more information is logged. This is in addition to the verbosity (if any) from the config file. .. option:: -V Show the version number and build options, and exit. See Also -------- :doc:`unbound.conf(5)`, :doc:`unbound-checkconf(8)`, :external+nsd:doc:`manpages/nsd`. unbound-1.25.1/doc/requirements.txt0000644000175000017500000003557615203270263016757 0ustar wouterwouterRequirements for Recursive Caching Resolver (a.k.a. Treeshrew, Unbound-C) By W.C.A. Wijngaards, NLnet Labs, October 2006. Contents 1. Introduction 2. History 3. Goals 4. Non-Goals 1. Introduction --------------- This is the requirements document for a DNS name server and aims to document the goals and non-goals of the project. The DNS (the Domain Name System) is a global, replicated database that uses a hierarchical structure for queries. Data in the DNS is stored in Resource Record sets (RR sets), and has a time to live (TTL). During this time the data can be cached. It is thus useful to cache data to speed up future lookups. A server that looks up data in the DNS for clients and caches previous answers to speed up processing is called a caching, recursive nameserver. This project aims to develop such a nameserver in modular components, so that also DNSSEC (secure DNS) validation and stub-resolvers (that do not run as a server, but a linked into an application) are easily possible. The main components are the Validator that validates the security fingerprints on data sets, the Iterator that sends queries to the hierarchical DNS servers that own the data and the Cache that stores data from previous queries. The networking and query management code then interface with the modules to perform the necessary processing. In Section 2 the origins of the Unbound project are documented. Section 3 lists the goals, while Section 4 lists the explicit non-goals of the project. Section 5 discusses choices made during development. 2. History ---------- The unbound resolver project started by Bill Manning, David Blacka, and Matt Larson (from the University of California and from Verisign), that created a Java based prototype resolver called Unbound. The basic design decisions of clean modules was executed. The Java prototype worked very well, with contributions from Geoff Sisson and Roy Arends from Nominet. Around 2006 the idea came to create a full-fledged C implementation ready for deployed use. NLnet Labs volunteered to write this implementation. 3. Goals -------- o A validating recursive DNS resolver. o Code diversity in the DNS resolver monoculture. o Drop-in replacement for BIND apart from config. o DNSSEC support. o Fully RFC compliant. o High performance * even with validation. o Used as * stub resolver. * full caching name server. * resolver library. o Elegant design of validator, resolver, cache modules. * provide the ability to pick and choose modules. o Robust. o In C, open source: The BSD license. o Highly portable, targets include modern Unix systems, such as *BSD, solaris, linux, and maybe also the windows platform. o Smallest as possible component that does the job. o Stub-zones can be configured (local data or AS112 zones). 4. Non-Goals ------------ o An authoritative name server. o Too many Features. 5. Choices ---------- o rfc2181 discourages duplicates RRs in RRsets. unbound does not create duplicates, but when presented with duplicates on the wire from the authoritative servers, does not perform duplicate removal. It does do some rrsig duplicate removal, in the msgparser, for dnssec qtype rrsig and any, because of special rrsig processing in the msgparser. o The harden-glue feature, when yes all out of zone glue is deleted, when no out of zone glue is used for further resolving, is more complicated than that, see below. Main points: * rfc2182 trust handling is used. * data is let through only in very specific cases * spoofability remains possible. Not all glue is let through (despite the name of the option). Only glue which is present in a delegation, of type A and AAAA, where the name is present in the NS record in the authority section is let through. The glue that is let through is stored in the cache (marked as 'from the additional section'). And will then be used for sending queries to. It will not be present in the reply to the client (if RD is off). A direct query for that name will attempt to get a msg into the message cache. Since A and AAAA queries are not synthesized by the unbound cache, this query will be (eventually) sent to the authoritative server and its answer will be put in the cache, marked as 'from the answer section' and thus remove the 'from the additional section' data, and this record is returned to the client. The message has a TTL smaller or equal to the TTL of the answer RR. If the cache memory is low; the answer RR may be dropped, and a glue RR may be inserted, within the message TTL time, and thus return the spoofed glue to a client. When the message expires, it is refetched and the cached RR is updated with the correct content. The server can be spoofed by getting it to visit a especially prepared domain. This domain then inserts an address for another authoritative server into the cache, when visiting that other domain, this address may then be used to send queries to. And fake answers may be returned. If the other domain is signed by DNSSEC, the fakes will be detected. In summary, the harden glue feature presents a security risk if disabled. Disabling the feature leads to possible better performance as more glue is present for the recursive service to use. The feature is implemented so as to minimise the security risk, while trying to keep this performance gain. o The method by which dnssec-lameness is detected is not secure. DNSSEC lame is when a server has the zone in question, but lacks dnssec data, such as signatures. The method to detect dnssec lameness looks at nonvalidated data from the parent of a zone. This can be used, by spoofing the parent, to create a false sense of dnssec-lameness in the child, or a false sense or dnssec-non-lameness in the child. The first results in the server marked lame, and not used for 900 seconds, and the second will result in a validator failure (SERVFAIL again), when the query is validated later on. Concluding, a spoof of the parent delegation can be used for many cases of denial of service. I.e. a completely different NS set could be returned, or the information withheld. All of these alterations can be caught by the validator if the parent is signed, and result in 900 seconds bogus. The dnssec-lameness detection is used to detect operator failures, before the validator will properly verify the messages. Also for zones for which no chain of trust exists, but a DS is given by the parent, dnssec-lameness detection enables. This delivers dnssec to our clients when possible (for client validators). The following issue needs to be resolved: a server that serves both a parent and child zone, where parent is signed, but child is not. The server must not be marked lame for the parent zone, because the child answer is not signed. Instead of a false positive, we want false negatives; failure to detect dnssec-lameness is less of a problem than marking honest servers lame. dnssec-lameness is a config error and deserves the trouble. So, only messages that identify the zone are used to mark the zone lame. The zone is identified by SOA or NS RRsets in the answer/auth. That includes almost all negative responses and also A, AAAA qtypes. That would be most responses from servers. For referrals, delegations that add a single label can be checked to be from their zone, this covers most delegation-centric zones. So possibly, for complicated setups, with multiple (parent-child) zones on a server, dnssec-lameness detection does not work - no dnssec-lameness is detected. Instead the zone that is dnssec-lame becomes bogus. o authority features. This is a recursive server, and authority features are out of scope. However, some authority features are expected in a recursor. Things like localhost, reverse lookup for 127.0.0.1, or blocking AS112 traffic. Also redirection of domain names with fixed data is needed by service providers. Limited support is added specifically to address this. Adding full authority support, requires much more code, and more complex maintenance. The limited support allows adding some static data (for localhost and so), and to respond with a fixed rcode (NXDOMAIN) for domains (such as AS112). 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). o If a client makes a query without RD bit, in the case of a returned message from cache which is: answer section: empty auth section: NS record present, no SOA record, no DS record, maybe NSEC or NSEC3 records present. additional: A records or other relevant records. A SOA record would indicate that this was a NODATA answer. A DS records would indicate a referral. Absence of NS record would indicate a NODATA answer as well. Then the receiver does not know whether this was a referral with attempt at no-DS proof) or a nodata answer with attempt at no-data proof. It could be determined by attempting to prove either condition; and looking if only one is valid, but both proofs could be valid, or neither could be valid, which creates doubt. This case is validated by unbound as a 'referral' which ascertains that RRSIGs are OK (and not omitted), but does not check NSEC/NSEC3. o Case preservation Unbound preserves the casing received from authority servers as best as possible. It compresses without case, so case can get lost there. The casing from the query name is used in preference to the casing of the authority server. This is the same as BIND. RFC4343 allows either behaviour. o Denial of service protection If many queries are made, and they are made to names for which the authority servers do not respond, then the requestlist for unbound fills up fast. This results in denial of service for new queries. To combat this the first 50% of the requestlist can run to completion. The last 50% of the requestlist get (200 msec) at least and are replaced by newer queries when older (LIFO). When a new query comes in, and a place in the first 50% is available, this is preferred. Otherwise, it can replace older queries out of the last 50%. Thus, even long queries get a 50% chance to be resolved. And many 'short' one or two round-trip resolves can be done in the last 50% of the list. The timeout can be configured. o EDNS fallback. Is done according to the EDNS RFC (and update draft-00). Unbound assumes EDNS 0 support for the first query. Then it can detect support (if the servers replies) or non-support (on a NOTIMPL or FORMERR). Some middleboxes drop EDNS 0 queries, mainly when forwarding, not when routing packets. To detect this, when timeouts keep happening, as the timeout approached 5-10 seconds, and EDNS status has not been detected yet, a single probe query is sent. This probe has a sub-second timeout, and if the server responds (quickly) without EDNS, this is cached for 15 min. This works very well when detecting an address that you use much - like a forwarder address - which is where the middleboxes need to be detected. Otherwise, it results in a 5 second wait time before EDNS timeout is detected, which is slow but it works at least. It minimizes the chances of a dropped query making a (DNSSEC) EDNS server falsely EDNS-nonsupporting, and thus DNSSEC-bogus, works well with middleboxes, and can detect the occasional authority that drops EDNS. For some boxes it is necessary to probe for every failing query, a reassurance that the DNS server does EDNS does not mean that path can take large DNS answers. o 0x20 backoff. The draft describes to back off to the next server, and go through all servers several times. Unbound goes on get the full list of nameserver addresses, and then makes 3 * number of addresses queries. They are sent to a random server, but no one address more than 4 times. It succeeds if one has 0x20 intact, or else all are equal. Otherwise, servfail is returned to the client. o NXDOMAIN and SOA serial numbers. Unbound keeps TTL values for message formats, and thus rcodes, such as NXDOMAIN. Also it keeps the latest rrsets in the rrset cache. So it will faithfully negative cache for the exact TTL as originally specified for an NXDOMAIN message, but send a newer SOA record if this has been found in the mean time. In point, this could lead to a negative cached NXDOMAIN reply with a SOA RR where the serial number indicates a zone version where this domain is not any longer NXDOMAIN. These situations become consistent once the original TTL expires. If the domain is DNSSEC signed, by the way, then NSEC records are updated more carefully. If one of the NSEC records in an NXDOMAIN is updated from another query, the NXDOMAIN is dropped from the cache, and queried for again, so that its proof can be checked again. o SOA records in negative cached answers for DS queries. The current unbound code uses a negative cache for queries for type DS. This speeds up building chains of trust, and uses NSEC and NSEC3 (optout) information to speed up lookups. When used internally, the bare NSEC(3) information is sufficient, probably picked up from a referral. When answering to clients, a SOA record is needed for the correct message format, a SOA record is picked from the cache (and may not actually match the serial number of the SOA for which the NSEC and NSEC3 records were obtained) if available otherwise network queries are performed to get the data. o Parent and child with different nameserver information. A misconfiguration that sometimes happens is where the parent and child have different NS, glue information. The child is authoritative, and unbound will not trust information from the parent nameservers as the final answer. To help lookups, unbound will however use the parent-side version of the glue as a last resort lookup. This resolves lookups for those misconfigured domains where the servers reported by the parent are the only ones working, and servers reported by the child do not. o Failure of validation and probing. Retries on a validation failure are now 5x to a different nameserver IP (if possible), and then it gives up, for one name, type, class entry in the message cache. If a DNSKEY or DS fails in the chain of trust in the key cache additionally, after the probing, a bad key entry is created that makes the entire zone bogus for 900 seconds. This is a fixed value at this time and is conservative in sending probes. It makes the compound effect of many resolvers less and easier to handle, but penalizes individual resolvers by having less probes and a longer time before fixes are picked up. unbound-1.25.1/doc/unbound-checkconf.8.in0000644000175000017500000000425315203270272017550 0ustar wouterwouter.\" Man page generated from reStructuredText. . . .nr rst2man-indent-level 0 . .de1 rstReportMargin \\$1 \\n[an-margin] level \\n[rst2man-indent-level] level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] - \\n[rst2man-indent0] \\n[rst2man-indent1] \\n[rst2man-indent2] .. .de1 INDENT .\" .rstReportMargin pre: . RS \\$1 . nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin] . nr rst2man-indent-level +1 .\" .rstReportMargin post: .. .de UNINDENT . RE .\" indent \\n[an-margin] .\" old: \\n[rst2man-indent\\n[rst2man-indent-level]] .nr rst2man-indent-level -1 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. .TH "UNBOUND-CHECKCONF" "8" "May 20, 2026" "1.25.1" "Unbound" .SH NAME unbound-checkconf \- Check Unbound 1.25.1 configuration file for errors. .SH SYNOPSIS .sp \fBunbound\-checkconf\fP [\fB\-hf\fP] [\fB\-o option\fP] [cfgfile] .SH DESCRIPTION .sp \fBunbound\-checkconf\fP checks the configuration file for the \fI\%unbound(8)\fP DNS resolver for syntax and other errors. The config file syntax is described in \fI\%unbound.conf(5)\fP\&. .sp The available options are: .INDENT 0.0 .TP .B \-h Show the version and commandline option help. .UNINDENT .INDENT 0.0 .TP .B \-f Print full pathname, with chroot applied to it. Use with the \fI\%\-o\fP option. .UNINDENT .INDENT 0.0 .TP .B \-q Make the operation quiet, suppress output on success. .UNINDENT .INDENT 0.0 .TP .B \-o