std::atomic<uint64_t> SyncRes::s_dontqueries;
std::atomic<uint64_t> SyncRes::s_nodelegated;
std::atomic<uint64_t> SyncRes::s_unreachables;
+std::atomic<uint64_t> SyncRes::s_ecsqueries;
+std::atomic<uint64_t> SyncRes::s_ecsresponses;
uint8_t SyncRes::s_ecsipv4limit;
uint8_t SyncRes::s_ecsipv6limit;
bool SyncRes::s_doIPv6;
d_wasOutOfBand=false;
if (doSpecialNamesResolve(qname, qtype, qclass, ret)) {
- d_queryValidationState = Secure;
+ d_queryValidationState = Insecure;
return 0;
}
set<GetBestNSAnswer> beenthere;
int res=doResolve(qname, qtype, ret, 0, beenthere, state);
d_queryValidationState = state;
+
+ if (d_queryValidationState != Indeterminate) {
+ g_stats.dnssecValidations++;
+ }
+ if (d_DNSSECValidationRequested) {
+ increaseDNSSECStateCounter(d_queryValidationState);
+ }
+
return res;
}
NsSet nsset;
bool flawedNSSet=false;
+ computeZoneCuts(qname, g_rootdnsname, depth);
+
// the two retries allow getBestNSNamesFromCache&co to reprime the root
// hints, in case they ever go missing
for(int tries=0;tries<2 && nsset.empty();++tries) {
subdomain=getBestNSNamesFromCache(subdomain, qtype, nsset, &flawedNSSet, depth, beenthere); // pass beenthere to both occasions
}
- state = getValidationStatus(subdomain, depth);
- LOG("Initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
+ state = getValidationStatus(subdomain);
+
+ LOG(prefix<<qname<<": initial validation status for "<<qname<<" inherited from "<<subdomain<<" is "<<vStates[state]<<endl);
if(!(res=doResolveAt(nsset, subdomain, flawedNSSet, qname, qtype, ret, depth, beenthere, state)))
return 0;
if(done) {
if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
vector<DNSRecord> cset;
- if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_requestor) > 0) {
+ if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
for(auto k=cset.cbegin();k!=cset.cend();++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
if (auto drc = std::dynamic_pointer_cast<AAAARecordContent>(k->d_content)) {
vector<DNSRecord> ns;
*flawedNSSet = false;
- if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_requestor, nullptr) > 0) {
+ if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) {
for(auto k=ns.cbegin();k!=ns.cend(); ++k) {
if(k->d_ttl > (unsigned int)d_now.tv_sec ) {
vector<DNSRecord> aset;
const DNSRecord& dr=*k;
auto nrr = getRR<NSRecordContent>(dr);
if(nrr && (!nrr->getNS().isPartOf(subdomain) || t_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A),
- false, doLog() ? &aset : nullptr, d_requestor) > 5)) {
+ false, doLog() ? &aset : 0, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 5)) {
bestns.push_back(dr);
LOG(prefix<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
LOG(prefix<<qname<<": Looking for CNAME cache hit of '"<<qname<<"|CNAME"<<"'"<<endl);
vector<DNSRecord> cset;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
- if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_requestor, &signatures, &d_wasVariable, &state) > 0) {
+ vector<std::shared_ptr<DNSRecord>> authorityRecs;
+ bool wasAuth;
+ if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) {
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
+
+ if (d_DNSSECValidationRequested && wasAuth && state == Indeterminate && d_requireAuthData) {
+ /* This means we couldn't figure out the state when this entry was cached,
+ most likely because we hadn't computed the zone cuts yet. */
+ /* make sure they are computed before validating */
+ computeZoneCuts(qname, g_rootdnsname, depth);
+
+ vState recordState = getValidationStatus(qname);
+ if (recordState == Secure) {
+ LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, validating.."<<endl);
+ state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures);
+ if (state != Indeterminate) {
+ LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
+ t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, state);
+ }
+ }
+ }
+
LOG(prefix<<qname<<": Found cache CNAME hit for '"<< qname << "|CNAME" <<"' to '"<<j->d_content->getZoneRepresentation()<<"', validation state is "<<vStates[state]<<endl);
+
DNSRecord dr=*j;
dr.d_ttl-=d_now.tv_sec;
ret.push_back(dr);
ret.push_back(sigdr);
}
+ for(const auto& rec : authorityRecs) {
+ DNSRecord dr(*rec);
+ dr.d_ttl=j->d_ttl - d_now.tv_sec;
+ ret.push_back(dr);
+ }
+
if(qtype != QType::CNAME) { // perhaps they really wanted a CNAME!
set<GetBestNSAnswer>beenthere;
vState cnameState = Indeterminate;
res=doResolve(std::dynamic_pointer_cast<CNAMERecordContent>(j->d_content)->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
- LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+ LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
updateValidationState(state, cnameState);
}
else
if(d_doDNSSEC)
addTTLModifiedRecords(ne.authoritySOA.signatures, sttl, ret);
- LOG("Updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+ LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
state = cachedState;
return true;
}
vector<DNSRecord> cset;
bool found=false, expired=false;
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
+ vector<std::shared_ptr<DNSRecord>> authorityRecs;
uint32_t ttl=0;
- if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_requestor, d_doDNSSEC ? &signatures : nullptr, &d_wasVariable, &cachedState) > 0) {
+ bool wasCachedAuth;
+ if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) {
+
LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
+
+ if (d_DNSSECValidationRequested && sqt != QType::DNSKEY && wasCachedAuth && cachedState == Indeterminate && d_requireAuthData) {
+
+ /* This means we couldn't figure out the state when this entry was cached,
+ most likely because we hadn't computed the zone cuts yet. */
+ /* make sure they are computed before validating */
+ computeZoneCuts(sqname, g_rootdnsname, depth);
+
+ vState recordState = getValidationStatus(qname);
+ if (recordState == Secure) {
+ LOG(prefix<<sqname<<": got Indeterminate state from the cache, validating.."<<endl);
+ cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+
+ if (cachedState != Indeterminate) {
+ LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
+ t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, cachedState);
+ }
+ }
+ }
+
for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) {
LOG(j->d_content->getZoneRepresentation());
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
dr.d_class=QClass::IN;
ret.push_back(dr);
}
-
+
+ for(const auto& rec : authorityRecs) {
+ DNSRecord dr(*rec);
+ dr.d_ttl=ttl;
+ ret.push_back(dr);
+ }
+
LOG(endl);
if(found && !expired) {
if (!giveNegative)
res=0;
d_wasOutOfBand = wasAuth;
- LOG("Updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
+ LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
state = cachedState;
return true;
}
return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
}
+static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
+
/* Fills the authoritySOA and DNSSECRecords fields from ne with those found in the records
*
* \param records The records to parse for the authority SOA and NSEC(3) records
* \param ne The NegCacheEntry to be filled out (will not be cleared, only appended to
*/
static void harvestNXRecords(const vector<DNSRecord>& records, NegCache::NegCacheEntry& ne) {
- static const set<uint16_t> nsecTypes = {QType::NSEC, QType::NSEC3};
for(const auto& rec : records) {
if(rec.d_place != DNSResourceRecord::AUTHORITY)
// RFC 4035 section 3.1.3. indicates that NSEC records MUST be placed in
for(const auto& record : records)
lowestTTD = min(lowestTTD, record.d_ttl);
+ /* even if it was not requested for that request (Process, and neither AD nor DO set),
+ it might be requested at a later time so we need to be careful with the TTL. */
if (validationEnabled() && !signatures.empty()) {
/* if we are validating, we don't want to cache records after their signatures
expires. */
std::string reason;
if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
- LOG("Got NTA for "<<zone<<endl);
+ LOG(d_prefix<<": got NTA for "<<zone<<endl);
return NTA;
}
if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
- LOG("Got TA for "<<zone<<endl);
+ LOG(d_prefix<<": got TA for "<<zone<<endl);
return TA;
}
std::vector<DNSRecord> dsrecords;
vState state = Indeterminate;
- int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth, beenthere, state);
+ int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth + 1, beenthere, state);
d_skipCNAMECheck = oldSkipCNAME;
d_requireAuthData = oldRequireAuthData;
- if (rcode == RCode::NoError) {
+ if (rcode == RCode::NoError || rcode == RCode::NXDomain) {
if (state == Secure) {
for (const auto& record : dsrecords) {
if (record.d_type == QType::DS) {
return state;
}
- LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+ LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
return Bogus;
}
-vState SyncRes::getValidationStatus(const DNSName& subdomain, unsigned int depth)
+bool SyncRes::haveExactValidationStatus(const DNSName& domain)
{
- if (!validationEnabled()) {
- return Indeterminate;
+ if (!d_DNSSECValidationRequested) {
+ return false;
}
+ const auto& it = d_cutStates.find(domain);
+ if (it != d_cutStates.cend()) {
+ return true;
+ }
+ return false;
+}
- dsmap_t ds;
- vState result = getTA(subdomain, ds);
- if (result != Indeterminate) {
- if (result == TA) {
- result = Secure;
- }
- else if (result == NTA) {
- result = Insecure;
- }
-
- if (result == Secure && countSupportedDS(ds) == 0) {
- ds.clear();
- result = Insecure;
- }
+vState SyncRes::getValidationStatus(const DNSName& subdomain)
+{
+ vState result = Indeterminate;
+ if (!d_DNSSECValidationRequested) {
return result;
}
-
- vector<DNSRecord> cset;
- vector<shared_ptr<RRSIGRecordContent> > signatures;
- if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::DS), false, &cset, d_requestor, &signatures, nullptr, &result) > 0) {
- if (result != Indeterminate) {
- return result;
+ DNSName name(subdomain);
+ do {
+ const auto& it = d_cutStates.find(name);
+ if (it != d_cutStates.cend()) {
+ LOG(d_prefix<<": got status "<<vStates[it->second]<<" for name "<<subdomain<<" (from "<<name<<")"<<endl);
+ return it->second;
}
}
+ while (name.chopOff());
- /* we are out of luck */
- DNSName higher(subdomain);
- if (!higher.chopOff()) {
- /* No (N)TA for ".", something is very wrong */
- return Bogus;
- }
-
- result = getValidationStatus(higher, depth);
+ return result;
+}
- if (result != Secure) {
- /* we know we don't have any (N)TA, so we are done */
- return result;
+void SyncRes::computeZoneCuts(const DNSName& begin, const DNSName& end, unsigned int depth)
+{
+ if(!begin.isPartOf(end)) {
+ LOG(d_prefix<<" "<<end.toLogString()<<" is not part of "<<begin.toString()<<endl);
+ throw PDNSException(end.toLogString() + " is not part of " + begin.toString());
}
- /* get subdomain DS */
- result = getDSRecords(subdomain, ds, false, depth);
+ if (d_cutStates.count(begin) != 0) {
+ return;
+ }
- if (result == TA) {
- result = Secure;
+ dsmap_t ds;
+ vState cutState = getDSRecords(end, ds, false, depth);
+ if (cutState == TA) {
+ cutState = Secure;
}
- else if (result != Secure) {
- if (result == NTA) {
- result = Insecure;
- }
- return result;
+ else if (cutState == NTA) {
+ cutState = Insecure;
}
+ LOG(d_prefix<<": setting cut state for "<<end<<" to "<<vStates[cutState]<<endl);
+ d_cutStates[end] = cutState;
- if (ds.empty()) {
- return Insecure;
+ if (!d_DNSSECValidationRequested) {
+ return;
}
- return Secure;
-}
+ DNSName qname(end);
+ std::vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
-void SyncRes::updateValidationStatusAfterReferral(const DNSName& oldauth, const DNSName& newauth, vState& state, unsigned int depth)
-{
- if (!validationEnabled()) {
- return;
- }
+ bool oldSkipCNAME = d_skipCNAMECheck;
+ bool oldRequireAuthData = d_requireAuthData;
+ d_skipCNAMECheck = true;
+ d_requireAuthData = false;
- dsmap_t ds;
- vState newState = getDSRecords(newauth, ds, state == Insecure || state == Bogus, depth);
+ while(qname != begin) {
+ bool foundCut = false;
+ if (labelsToAdd.empty())
+ break;
- if (newState == Indeterminate) {
- /* no (N)TA */
- return;
- }
+ qname.prependRawLabel(labelsToAdd.back());
+ labelsToAdd.pop_back();
+ LOG(d_prefix<<": - Looking for a cut at "<<qname<<endl);
- if (newState == Secure) {
- if (ds.empty()) {
- updateValidationState(state, Insecure);
+ const auto cutIt = d_cutStates.find(qname);
+ if (cutIt != d_cutStates.cend()) {
+ if (cutIt->second != Indeterminate) {
+ LOG(d_prefix<<": - Cut already known at "<<qname<<endl);
+ continue;
+ }
}
- else {
- updateValidationState(state, Secure);
+
+ std::set<GetBestNSAnswer> beenthere;
+ std::vector<DNSRecord> nsrecords;
+
+ vState state = Indeterminate;
+ /* temporarily mark as Indeterminate, so that we won't enter an endless loop
+ trying to determine that zone cut again. */
+ d_cutStates[qname] = state;
+ int rcode = doResolve(qname, QType(QType::NS), nsrecords, depth + 1, beenthere, state);
+
+ if (rcode == RCode::NoError && !nsrecords.empty()) {
+ for (const auto& record : nsrecords) {
+ if(record.d_type != QType::NS || record.d_name != qname)
+ continue;
+ foundCut = true;
+ break;
+ }
+ if (foundCut) {
+ LOG(d_prefix<<": - Found cut at "<<qname<<endl);
+ /* if we get a Bogus state while retrieving the NS,
+ the cut state is Bogus (we'll look for a (N)TA below though). */
+ if (state == Bogus) {
+ cutState = Bogus;
+ }
+ dsmap_t ds;
+ vState newState = getDSRecords(qname, ds, cutState == Insecure || cutState == Bogus, depth);
+ if (newState != Indeterminate) {
+ cutState = newState;
+ }
+ LOG(d_prefix<<": New state for "<<qname<<" is "<<vStates[cutState]<<endl);
+ if (cutState == TA) {
+ cutState = Secure;
+ }
+ else if (cutState == NTA) {
+ cutState = Insecure;
+ }
+ d_cutStates[qname] = cutState;
+ }
+ }
+ if (!foundCut) {
+ /* remove the temporary cut */
+ LOG(d_prefix<<qname<<": removing cut state for "<<qname<<", was "<<vStates[d_cutStates[qname]]<<endl);
+ d_cutStates.erase(qname);
}
}
- else {
- updateValidationState(state, newState);
+
+ d_skipCNAMECheck = oldSkipCNAME;
+ d_requireAuthData = oldRequireAuthData;
+
+ LOG(d_prefix<<": list of cuts from "<<begin<<" to "<<end<<endl);
+ for (const auto& cut : d_cutStates) {
+ if (cut.first.isRoot() || (begin.isPartOf(cut.first) && cut.first.isPartOf(end))) {
+ LOG(" - "<<cut.first<<": "<<vStates[cut.second]<<endl);
+ }
}
}
}
}
- LOG("Trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
+ LOG(d_prefix<<": trying to validate "<<std::to_string(tentativeKeys.size())<<" DNSKEYs with "<<std::to_string(ds.size())<<" DS"<<endl);
skeyset_t validatedKeys;
validateDNSKeysAgainstDS(d_now.tv_sec, zone, ds, tentativeKeys, toSign, signatures, validatedKeys);
- LOG("We now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
+ LOG(d_prefix<<": we now have "<<std::to_string(validatedKeys.size())<<" DNSKEYs"<<endl);
+ /* if we found at least one valid RRSIG covering the set,
+ all tentative keys are validated keys. Otherwise it means
+ we haven't found at least one DNSKEY and a matching RRSIG
+ covering this set, this looks Bogus. */
if (validatedKeys.size() != tentativeKeys.size()) {
- LOG("Returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
+ LOG(d_prefix<<": returning Bogus state from "<<__func__<<"("<<zone<<")"<<endl);
return Bogus;
}
{
std::vector<DNSRecord> records;
std::set<GetBestNSAnswer> beenthere;
- LOG("Retrieving DNSKeys for "<<signer<<endl);
+ LOG(d_prefix<<"Retrieving DNSKeys for "<<signer<<endl);
vState state = Indeterminate;
+ /* following CNAME might lead to us to the wrong DNSKEY */
+ bool oldSkipCNAME = d_skipCNAMECheck;
+ d_skipCNAMECheck = true;
int rcode = doResolve(signer, QType(QType::DNSKEY), records, depth + 1, beenthere, state);
+ d_skipCNAMECheck = oldSkipCNAME;
if (rcode == RCode::NoError) {
if (state == Secure) {
}
}
}
- LOG("Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
+ LOG(d_prefix<<"Retrieved "<<keys.size()<<" DNSKeys for "<<signer<<", state is "<<vStates[state]<<endl);
return state;
}
- LOG("Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
+ LOG(d_prefix<<"Returning Bogus state from "<<__func__<<"("<<signer<<")"<<endl);
return Bogus;
}
-vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
+vState SyncRes::validateRecordsWithSigs(unsigned int depth, const DNSName& qname, const QType& qtype, const DNSName& name, const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures)
{
skeyset_t keys;
if (!signatures.empty()) {
const DNSName signer = getSigner(signatures);
if (!signer.empty() && name.isPartOf(signer)) {
+ if (qtype == QType::DNSKEY && signer == qname) {
+ /* we are already retrieving those keys, sorry */
+ return Indeterminate;
+ }
vState state = getDNSKeys(signer, keys, depth);
if (state != Secure) {
return state;
}
}
} else {
- LOG("Bogus!"<<endl);
+ LOG(d_prefix<<"Bogus!"<<endl);
return Bogus;
}
recordcontents.push_back(record.d_content);
}
- LOG("Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
+ LOG(d_prefix<<"Going to validate "<<recordcontents.size()<< " record contents with "<<signatures.size()<<" sigs and "<<keys.size()<<" keys for "<<name<<endl);
if (validateWithKeySet(d_now.tv_sec, name, recordcontents, signatures, keys, false)) {
- LOG("Secure!"<<endl);
+ LOG(d_prefix<<"Secure!"<<endl);
return Secure;
}
- LOG("Bogus!"<<endl);
+ LOG(d_prefix<<"Bogus!"<<endl);
return Bogus;
}
-RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state)
+RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr, const DNSName& qname, const QType& qtype, const DNSName& auth, bool wasForwarded, const boost::optional<Netmask> ednsmask, vState& state, bool& needWildcardProof)
{
struct CacheEntry
{
prefix.append(depth, ' ');
}
+ std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
+ bool isCNAMEAnswer = false;
for(const auto& rec : lwr.d_records) {
+ if(!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
+ isCNAMEAnswer = true;
+ }
+
+ if(needWildcardProof) {
+ if (nsecTypes.count(rec.d_type)) {
+ authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
+ }
+ else if (rec.d_type == QType::RRSIG) {
+ auto rrsig = getRR<RRSIGRecordContent>(rec);
+ if (rrsig && nsecTypes.count(rrsig->d_type)) {
+ authorityRecs.push_back(std::make_shared<DNSRecord>(rec));
+ }
+ }
+ }
if(rec.d_type == QType::RRSIG) {
auto rrsig = getRR<RRSIGRecordContent>(rec);
if (rrsig) {
+ unsigned int labelCount = rec.d_name.countLabels();
+ /* As illustrated in rfc4035's Appendix B.6, the RRSIG label
+ count can be lower than the name's label count if it was
+ synthesized from the wildcard. Note that the difference might
+ be > 1. */
+ if (rec.d_name == qname && rrsig->d_labels < labelCount) {
+ LOG(prefix<<qname<<": RRSIG indicates the name was expanded from a wildcard, we need a wildcard proof"<<endl);
+ needWildcardProof = true;
+ }
+
// cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL = std::min(tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signaturesTTL, rec.d_ttl);
rec.d_ttl=min(s_maxcachettl, rec.d_ttl);
DNSRecord dr(rec);
- dr.d_place=DNSResourceRecord::ANSWER;
-
dr.d_ttl += d_now.tv_sec;
+ dr.d_place=DNSResourceRecord::ANSWER;
tcache[{rec.d_name,rec.d_type,rec.d_place}].records.push_back(dr);
}
}
}
for(tcache_t::iterator i = tcache.begin(); i != tcache.end(); ++i) {
- vState recordState = state;
if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
continue;
- if (validationEnabled() && state == Secure) {
- if (lwr.d_aabit) {
+ bool isAA = lwr.d_aabit;
+ if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME)) {
+ /*
+ rfc2181 states:
+ Note that the answer section of an authoritative answer normally
+ contains only authoritative data. However when the name sought is an
+ alias (see section 10.1.1) only the record describing that alias is
+ necessarily authoritative. Clients should assume that other records
+ may have come from the server's cache. Where authoritative answers
+ are required, the client should query again, using the canonical name
+ associated with the alias.
+ */
+ isAA = false;
+ }
+
+ vState recordState = getValidationStatus(auth);
+ LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<endl);
+
+ if (d_DNSSECValidationRequested && recordState == Secure) {
+ if (isAA) {
if (i->first.place != DNSResourceRecord::ADDITIONAL) {
/* the additional entries can be insecure,
like glue:
"Glue address RRsets associated with delegations MUST NOT be signed"
*/
if (i->first.type == QType::DNSKEY && i->first.place == DNSResourceRecord::ANSWER) {
+ LOG(d_prefix<<"Validating DNSKEY for "<<i->first.name<<endl);
recordState = validateDNSKeys(i->first.name, i->second.records, i->second.signatures, depth);
}
else {
- recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+ LOG(d_prefix<<"Validating non-additional record for "<<i->first.name<<endl);
+ recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, i->second.records, i->second.signatures);
+ /* we might have missed a cut (zone cut within the same auth servers), causing the NS query for an Insecure zone to seem Bogus during zone cut determination */
+ if (qtype == QType::NS && i->second.signatures.empty() && recordState == Bogus && haveExactValidationStatus(i->first.name) && getValidationStatus(i->first.name) == Indeterminate) {
+ recordState = Indeterminate;
+ }
}
}
}
else {
- /* for non authoritative answer, we only care about the DS record */
- if (i->first.type == QType::DS && i->first.place == DNSResourceRecord::AUTHORITY) {
- recordState = validateRecordsWithSigs(depth, i->first.name, i->second.records, i->second.signatures);
+ /* in a non authoritative answer, we only care about the DS record (or lack of) */
+ if ((i->first.type == QType::DS || i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && i->first.place == DNSResourceRecord::AUTHORITY) {
+ LOG(d_prefix<<"Validating DS record for "<<i->first.name<<endl);
+ recordState = validateRecordsWithSigs(depth, qname, qtype, i->first.name, i->second.records, i->second.signatures);
}
}
+
updateValidationState(state, recordState);
}
else {
- if (validationEnabled()) {
- LOG("Skipping validation because the current state is "<<vStates[state]<<endl);
+ if (d_DNSSECValidationRequested) {
+ LOG(d_prefix<<"Skipping validation because the current state is "<<vStates[recordState]<<endl);
}
}
- t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, lwr.d_aabit, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
+ t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
d_wasVariable=true;
dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode());
if (res != expectedState) {
if (res == OPTOUT && allowOptOut) {
- LOG("OPT-out denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+ LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
ne.d_validationState = Secure;
updateValidationState(state, Insecure);
return;
}
else if (res == INSECURE) {
- LOG("Insecure denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
+ LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", retuning Insecure"<<endl);
ne.d_validationState = Insecure;
}
+ if (res == NXDOMAIN && expectedState == NXQTYPE) {
+ /* might happen for empty non-terminal, have fun */
+ return;
+ }
else {
- LOG("Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
+ LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", retuning Bogus"<<endl);
ne.d_validationState = Bogus;
}
updateValidationState(state, ne.d_validationState);
}
}
-bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state)
+bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, const QType& qtype, const DNSName& auth, LWResult& lwr, const bool sendRDQuery, vector<DNSRecord>& ret, set<DNSName>& nsset, DNSName& newtarget, DNSName& newauth, bool& realreferral, bool& negindic, vState& state, bool needWildcardProof)
{
bool done = false;
rec.d_ttl = min(rec.d_ttl, s_maxnegttl);
if(newtarget.empty()) // only add a SOA if we're not going anywhere after this
ret.push_back(rec);
- if(!wasVariable()) {
- NegCache::NegCacheEntry ne;
- ne.d_ttd = d_now.tv_sec + rec.d_ttl;
- ne.d_name = qname;
- ne.d_qtype = QType(0); // this encodes 'whole record'
- ne.d_auth = rec.d_name;
- harvestNXRecords(lwr.d_records, ne);
- getDenialValidationState(ne, state, NXDOMAIN, false);
+ NegCache::NegCacheEntry ne;
+
+ ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = QType(0); // this encodes 'whole record'
+ ne.d_auth = rec.d_name;
+ harvestNXRecords(lwr.d_records, ne);
+ getDenialValidationState(ne, state, NXDOMAIN, false);
+
+ if(!wasVariable()) {
t_sstorage.negcache.add(ne);
if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot()) {
ne.d_name = ne.d_name.getLastLabel();
newtarget=content->getTarget();
}
}
- else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER){
+ else if((rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::ANSWER) {
if(rec.d_type != QType::RRSIG || rec.d_name == qname)
ret.push_back(rec); // enjoy your DNSSEC
}
+ else if(needWildcardProof && (rec.d_type==QType::RRSIG || rec.d_type==QType::NSEC || rec.d_type==QType::NSEC3) && rec.d_place==DNSResourceRecord::AUTHORITY) {
+ ret.push_back(rec); // enjoy your DNSSEC
+ }
// for ANY answers we *must* have an authoritative answer, unless we are forwarding recursively
else if(rec.d_place==DNSResourceRecord::ANSWER && rec.d_name == qname &&
(
rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
updateValidationState(state, Insecure);
+ auto cut = d_cutStates.find(newauth);
+ if (cut != d_cutStates.end()) {
+ if (cut->second == Indeterminate) {
+ cut->second = state;
+ }
+ }
+ else {
+ LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[state]<<endl);
+ d_cutStates[newauth] = state;
+ }
if(!wasVariable()) {
t_sstorage.negcache.add(ne);
}
else {
rec.d_ttl = min(s_maxnegttl, rec.d_ttl);
ret.push_back(rec);
+
+ NegCache::NegCacheEntry ne;
+ ne.d_auth = rec.d_name;
+ ne.d_ttd = d_now.tv_sec + rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = qtype;
+ harvestNXRecords(lwr.d_records, ne);
+ getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS);
+
if(!wasVariable()) {
- NegCache::NegCacheEntry ne;
- ne.d_auth = rec.d_name;
- ne.d_ttd = d_now.tv_sec + rec.d_ttl;
- ne.d_name = qname;
- ne.d_qtype = qtype;
- harvestNXRecords(lwr.d_records, ne);
- getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS);
if(qtype.getCode()) { // prevents us from blacking out a whole domain
t_sstorage.negcache.add(ne);
}
ednsmask=getEDNSSubnetMask(d_requestor, qname, remoteIP);
if(ednsmask) {
LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
+ s_ecsqueries++;
}
resolveret = asyncresolveWrapper(remoteIP, d_doDNSSEC, qname, qtype.getCode(),
doTCP, sendRDQuery, &d_now, ednsmask, &lwr); // <- we go out on the wire!
if(ednsmask) {
+ s_ecsresponses++;
LOG(prefix<<qname<<": Received EDNS Client Subnet Mask "<<ednsmask->toString()<<" on response"<<endl);
}
}
}
}
- *rcode = updateCacheFromRecords(depth, lwr, qname, auth, wasForwarded, ednsmask, state);
+ bool needWildcardProof = false;
+ *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof);
if (*rcode != RCode::NoError) {
return true;
}
DNSName newauth;
DNSName newtarget;
- bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state);
+ bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof);
if(done){
LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
set<GetBestNSAnswer> beenthere2;
vState cnameState = Indeterminate;
*rcode = doResolve(newtarget, qtype, ret, depth + 1, beenthere2, cnameState);
- LOG("Updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
+ LOG(prefix<<qname<<": updating validation state for response to "<<qname<<" from "<<vStates[state]<<" with the state from the CNAME quest: "<<vStates[cnameState]<<endl);
updateValidationState(state, cnameState);
-
return true;
}
if(nsset.empty() && !lwr.d_rcode && (negindic || lwr.d_aabit || sendRDQuery)) {
LOG(prefix<<qname<<": status=noerror, other types may exist, but we are done "<<(negindic ? "(have negative SOA) " : "")<<(lwr.d_aabit ? "(have aa bit) " : "")<<endl);
+ if(state == Secure && lwr.d_aabit && !negindic) {
+ updateValidationState(state, Bogus);
+ }
+
if(d_doDNSSEC)
addNXNSECS(ret, lwr.d_records);
}
LOG("looping to them"<<endl);
*gotNewServers = true;
- updateValidationStatusAfterReferral(auth, newauth, state, depth);
auth=newauth;
return false;
/* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
return result;
}
- trunc = d_incomingECS->source.getMaskedNetwork();
+ trunc = d_incomingECSNetwork;
bits = d_incomingECS->source.getBits();
}
else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
sr.setDoEDNS0(true);
sr.setUpdatingRootNS();
sr.setDoDNSSEC(g_dnssecmode != DNSSECMode::Off);
+ sr.setDNSSECValidationRequested(g_dnssecmode != DNSSECMode::Off && g_dnssecmode != DNSSECMode::ProcessNoValidate);
sr.setAsyncCallback(asyncCallback);
vector<DNSRecord> ret;