#include "lua-recursor4.hh"
#include "rec-lua-conf.hh"
#include "syncres.hh"
+#include "dnsseckeeper.hh"
#include "validate-recursor.hh"
thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
if (d_queryValidationState != Indeterminate) {
g_stats.dnssecValidations++;
}
- if (d_DNSSECValidationRequested) {
+ if (shouldValidate()) {
increaseDNSSECStateCounter(d_queryValidationState);
}
return result;
}
-bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res) const
+bool SyncRes::doOOBResolve(const AuthDomain& domain, const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, int& res)
{
+ d_authzonequeries++;
+
res = domain.getRecords(qname, qtype.getCode(), ret);
return true;
}
for(const auto& i : t_sstorage.nsSpeeds)
{
count++;
- fprintf(fp, "%s -> ", i.first.toString().c_str());
+
+ // an <empty> can appear hear in case of authoritative (hosted) zones
+ fprintf(fp, "%s -> ", i.first.toLogString().c_str());
for(const auto& j : i.second.d_collection)
{
// typedef vector<pair<ComboAddress, DecayingEwma> > collection_t;
}
}
- if(!d_skipCNAMECheck && doCNAMECacheCheck(qname,qtype,ret,depth,res,state)) // will reroute us if needed
+ DNSName authname(qname);
+ bool wasForwardedOrAuthZone = false;
+ bool wasAuthZone = false;
+ domainmap_t::const_iterator iter = getBestAuthZone(&authname);
+ if(iter != t_sstorage.domainmap->end()) {
+ wasForwardedOrAuthZone = true;
+ const vector<ComboAddress>& servers = iter->second.d_servers;
+ if(servers.empty()) {
+ wasAuthZone = true;
+ }
+ }
+
+ if(!d_skipCNAMECheck && doCNAMECacheCheck(qname, qtype, ret, depth, res, state, wasAuthZone)) { // will reroute us if needed
+ d_wasOutOfBand = wasAuthZone;
return res;
+ }
- if(doCacheCheck(qname,qtype,ret,depth,res,state)) // we done
+ if(doCacheCheck(qname, authname, wasForwardedOrAuthZone, wasAuthZone, qtype, ret, depth, res, state)) {
+ // we done
+ d_wasOutOfBand = wasAuthZone;
return res;
+ }
}
if(d_cacheonly)
}
#endif
+struct speedOrderCA
+{
+ speedOrderCA(std::map<ComboAddress,double>& speeds): d_speeds(speeds) {}
+ bool operator()(const ComboAddress& a, const ComboAddress& b) const
+ {
+ return d_speeds[a] < d_speeds[b];
+ }
+ std::map<ComboAddress, double>& d_speeds;
+};
+
/** This function explicitly goes out for A or AAAA addresses
*/
-vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere)
+vector<ComboAddress> SyncRes::getAddrs(const DNSName &qname, unsigned int depth, set<GetBestNSAnswer>& beenthere, bool cacheOnly)
{
typedef vector<DNSRecord> res_t;
res_t res;
ret_t ret;
QType type;
+ bool oldCacheOnly = d_cacheonly;
bool oldRequireAuthData = d_requireAuthData;
+ bool oldValidationRequested = d_DNSSECValidationRequested;
d_requireAuthData = false;
+ d_DNSSECValidationRequested = false;
+ d_cacheonly = cacheOnly;
for(int j=1; j<2+s_doIPv6; j++)
{
if(!doResolve(qname, type, res,depth+1, beenthere, newState) && !res.empty()) { // this consults cache, OR goes out
for(res_t::const_iterator i=res.begin(); i!= res.end(); ++i) {
if(i->d_type == QType::A || i->d_type == QType::AAAA) {
- if(auto rec = std::dynamic_pointer_cast<ARecordContent>(i->d_content))
+ if(auto rec = getRR<ARecordContent>(*i))
ret.push_back(rec->getCA(53));
- else if(auto aaaarec = std::dynamic_pointer_cast<AAAARecordContent>(i->d_content))
+ else if(auto aaaarec = getRR<AAAARecordContent>(*i))
ret.push_back(aaaarec->getCA(53));
done=true;
}
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)) {
+ if (auto drc = getRR<AAAARecordContent>(*k)) {
ComboAddress ca=drc->getCA(53);
ret.push_back(ca);
}
}
d_requireAuthData = oldRequireAuthData;
+ d_DNSSECValidationRequested = oldValidationRequested;
+ d_cacheonly = oldCacheOnly;
- if(ret.size() > 1) {
- random_shuffle(ret.begin(), ret.end(), dns_random);
+ /* we need to remove from the nsSpeeds collection the existing IPs
+ for this nameserver that are no longer in the set, even if there
+ is only one or none at all in the current set.
+ */
+ map<ComboAddress, double> speeds;
+ auto& collection = t_sstorage.nsSpeeds[qname].d_collection;
+ for(const auto& val: ret) {
+ speeds[val] = collection[val].get(&d_now);
+ }
- // move 'best' address for this nameserver name up front
- nsspeeds_t::iterator best = t_sstorage.nsSpeeds.find(qname);
+ t_sstorage.nsSpeeds[qname].purge(speeds);
- if(best != t_sstorage.nsSpeeds.end())
- for(ret_t::iterator i=ret.begin(); i != ret.end(); ++i) {
- if(*i==best->second.d_best) { // got the fastest one
- if(i!=ret.begin()) {
- *i=*ret.begin();
- *ret.begin()=best->second.d_best;
- }
- break;
+ if(ret.size() > 1) {
+ random_shuffle(ret.begin(), ret.end(), dns_random);
+ speedOrderCA so(speeds);
+ stable_sort(ret.begin(), ret.end(), so);
+
+ if(doLog()) {
+ string prefix=d_prefix;
+ prefix.append(depth, ' ');
+ LOG(prefix<<"Nameserver "<<qname<<" IPs: ");
+ bool first = true;
+ for(const auto& addr : ret) {
+ if (first) {
+ first = false;
}
+ else {
+ LOG(", ");
+ }
+ LOG((addr.toString())<<"(" << (boost::format("%0.2f") % (speeds[addr]/1000.0)).str() <<"ms)");
}
+ LOG(endl);
+ }
}
return ret;
GetBestNSAnswer answer;
answer.qname=qname;
answer.qtype=qtype.getCode();
- for(const auto& dr : bestns)
- answer.bestns.insert(make_pair(dr.d_name, getRR<NSRecordContent>(dr)->getNS()));
+ for(const auto& dr : bestns) {
+ if (auto nsContent = getRR<NSRecordContent>(dr)) {
+ answer.bestns.insert(make_pair(dr.d_name, nsContent->getNS()));
+ }
+ }
if(beenthere.count(answer)) {
brokeloop=true;
for(auto k=bestns.cbegin() ; k != bestns.cend(); ++k) {
// The actual resolver code will not even look at the ComboAddress or bool
- nsset.insert({std::dynamic_pointer_cast<NSRecordContent>(k->d_content)->getNS(), {{}, false}});
- if(k==bestns.cbegin())
- subdomain=k->d_name;
+ const auto nsContent = getRR<NSRecordContent>(*k);
+ if (nsContent) {
+ nsset.insert({nsContent->getNS(), {{}, false}});
+ if(k==bestns.cbegin())
+ subdomain=k->d_name;
+ }
}
return subdomain;
}
-bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state)
+bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>& ret, unsigned int depth, int &res, vState& state, bool wasAuthZone)
{
string prefix;
if(doLog()) {
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_class != QClass::IN) {
+ continue;
+ }
+
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
- if (d_DNSSECValidationRequested && wasAuth && state == Indeterminate && d_requireAuthData) {
+ if (!wasAuthZone && shouldValidate() && 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);
+ DNSName subdomain(qname);
+ /* if we are retrieving a DS, we only care about the state of the parent zone */
+ if(qtype == QType::DS)
+ subdomain.chopOff();
+
+ computeZoneCuts(subdomain, g_rootdnsname, depth);
- vState recordState = getValidationStatus(qname);
+ vState recordState = getValidationStatus(qname, false);
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);
}
for(const auto& rec : authorityRecs) {
- DNSRecord dr(*rec);
- dr.d_ttl=j->d_ttl - d_now.tv_sec;
- ret.push_back(dr);
+ DNSRecord authDR(*rec);
+ authDR.d_ttl=j->d_ttl - d_now.tv_sec;
+ ret.push_back(authDR);
}
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(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);
+ const auto cnameContent = getRR<CNAMERecordContent>(*j);
+ if (cnameContent) {
+ res=doResolve(cnameContent->getTarget(), qtype, ret, depth+1, beenthere, cnameState);
+ 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
res=0;
return false;
}
+namespace {
+struct CacheEntry
+{
+ vector<DNSRecord> records;
+ vector<shared_ptr<RRSIGRecordContent>> signatures;
+ uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
+};
+struct CacheKey
+{
+ DNSName name;
+ uint16_t type;
+ DNSResourceRecord::Place place;
+ bool operator<(const CacheKey& rhs) const {
+ return tie(name, type) < tie(rhs.name, rhs.type);
+ }
+};
+typedef map<CacheKey, CacheEntry> tcache_t;
+}
+
+static void reapRecordsFromNegCacheEntryForValidation(tcache_t& tcache, const vector<DNSRecord>& records)
+{
+ for (const auto& rec : records) {
+ if (rec.d_type == QType::RRSIG) {
+ auto rrsig = getRR<RRSIGRecordContent>(rec);
+ if (rrsig) {
+ tcache[{rec.d_name, rrsig->d_type, rec.d_place}].signatures.push_back(rrsig);
+ }
+ } else {
+ tcache[{rec.d_name,rec.d_type,rec.d_place}].records.push_back(rec);
+ }
+ }
+}
+
/*!
* Convience function to push the records from records into ret with a new TTL
*
}
}
+void SyncRes::computeNegCacheValidationStatus(NegCache::NegCacheEntry& ne, const DNSName& qname, const QType& qtype, const int res, vState& state, unsigned int depth)
+{
+ DNSName subdomain(qname);
+ /* if we are retrieving a DS, we only care about the state of the parent zone */
+ if(qtype == QType::DS)
+ subdomain.chopOff();
+
+ computeZoneCuts(subdomain, g_rootdnsname, depth);
+
+ tcache_t tcache;
+ reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.records);
+ reapRecordsFromNegCacheEntryForValidation(tcache, ne.authoritySOA.signatures);
+ reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.records);
+ reapRecordsFromNegCacheEntryForValidation(tcache, ne.DNSSECRecords.signatures);
+
+ for (const auto& entry : tcache) {
+ // this happens when we did store signatures, but passed on the records themselves
+ if (entry.second.records.empty()) {
+ continue;
+ }
+
+ const DNSName& owner = entry.first.name;
+
+ vState recordState = getValidationStatus(owner, false);
+ if (state == Indeterminate) {
+ state = recordState;
+ }
+
+ if (recordState == Secure) {
+ recordState = SyncRes::validateRecordsWithSigs(depth, qname, qtype, owner, entry.second.records, entry.second.signatures);
+ }
-bool SyncRes::doCacheCheck(const DNSName &qname, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
+ if (recordState != Indeterminate && recordState != state) {
+ updateValidationState(state, recordState);
+ if (state != Secure) {
+ break;
+ }
+ }
+ }
+
+ if (state == Secure) {
+ dState expectedState = res == RCode::NXDomain ? NXDOMAIN : NXQTYPE;
+ dState denialState = getDenialValidationState(ne, state, expectedState, false);
+ updateDenialValidationState(ne, state, denialState, expectedState, qtype == QType::DS);
+ }
+ if (state != Indeterminate) {
+ /* validation succeeded, let's update the cache entry so we don't have to validate again */
+ t_sstorage.negcache.updateValidationStatus(ne.d_name, ne.d_qtype, state);
+ }
+}
+
+bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool wasForwardedOrAuthZone, bool wasAuthZone, const QType &qtype, vector<DNSRecord>&ret, unsigned int depth, int &res, vState& state)
{
bool giveNegative=false;
QType sqt(qtype);
uint32_t sttl=0;
// cout<<"Lookup for '"<<qname<<"|"<<qtype.getName()<<"' -> "<<getLastLabel(qname)<<endl;
-
- DNSName authname(qname);
vState cachedState;
- bool wasForwardedOrAuth = false;
- bool wasAuth = false;
- domainmap_t::const_iterator iter=getBestAuthZone(&authname);
- if(iter != t_sstorage.domainmap->end()) {
- wasForwardedOrAuth = true;
- const vector<ComboAddress>& servers = iter->second.d_servers;
- if(servers.empty()) {
- wasAuth = true;
- }
- }
NegCache::NegCacheEntry ne;
if(s_rootNXTrust &&
t_sstorage.negcache.getRootNXTrust(qname, d_now, ne) &&
ne.d_auth.isRoot() &&
- !(wasForwardedOrAuth && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
+ !(wasForwardedOrAuthZone && !authname.isRoot())) { // when forwarding, the root may only neg-cache if it was forwarded to.
sttl = ne.d_ttd - d_now.tv_sec;
LOG(prefix<<qname<<": Entire name '"<<qname<<"', is negatively cached via '"<<ne.d_auth<<"' & '"<<ne.d_name<<"' for another "<<sttl<<" seconds"<<endl);
res = RCode::NXDomain;
cachedState = ne.d_validationState;
}
else if (t_sstorage.negcache.get(qname, qtype, d_now, ne) &&
- !(wasForwardedOrAuth && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
+ !(wasForwardedOrAuthZone && ne.d_auth != authname)) { // Only the authname nameserver can neg cache entries
/* If we are looking for a DS, discard NXD if auth == qname
and ask for a specific denial instead */
}
if (giveNegative) {
+
+ state = cachedState;
+
+ if (!wasAuthZone && shouldValidate() && state == Indeterminate) {
+ LOG(prefix<<qname<<": got Indeterminate state for records retrieved from the negative cache, validating.."<<endl);
+ computeNegCacheValidationStatus(ne, qname, qtype, res, state, depth);
+ }
+
// Transplant SOA to the returned packet
addTTLModifiedRecords(ne.authoritySOA.records, sttl, ret);
if(d_doDNSSEC) {
addTTLModifiedRecords(ne.DNSSECRecords.signatures, sttl, ret);
}
- LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
- state = cachedState;
+ LOG(prefix<<qname<<": updating validation state with negative cache content for "<<qname<<" to "<<vStates[state]<<endl);
return true;
}
LOG(prefix<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
- if (d_DNSSECValidationRequested && sqt != QType::DNSKEY && wasCachedAuth && cachedState == Indeterminate && d_requireAuthData) {
+ if (!wasAuthZone && shouldValidate() && 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);
+ DNSName subdomain(sqname);
+ /* if we are retrieving a DS, we only care about the state of the parent zone */
+ if(qtype == QType::DS)
+ subdomain.chopOff();
- vState recordState = getValidationStatus(qname);
+ computeZoneCuts(subdomain, g_rootdnsname, depth);
+
+ vState recordState = getValidationStatus(qname, false);
if (recordState == Secure) {
LOG(prefix<<sqname<<": got Indeterminate state from the cache, validating.."<<endl);
cachedState = SyncRes::validateRecordsWithSigs(depth, sqname, sqt, sqname, cset, signatures);
+ }
+ else {
+ cachedState = recordState;
+ }
- 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);
- }
+ 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_class != QClass::IN) {
+ continue;
+ }
+
if(j->d_ttl>(unsigned int) d_now.tv_sec) {
DNSRecord dr=*j;
ttl = (dr.d_ttl-=d_now.tv_sec);
if(found && !expired) {
if (!giveNegative)
res=0;
- d_wasOutOfBand = wasAuth;
LOG(prefix<<qname<<": updating validation state with cache content for "<<qname<<" to "<<vStates[cachedState]<<endl);
state = cachedState;
return true;
{
vector<DNSName> rnameservers;
rnameservers.reserve(tnameservers.size());
- for(const auto& tns:tnameservers) {
+ for(const auto& tns: tnameservers) {
rnameservers.push_back(tns.first);
+ if(tns.first.empty()) // this was an authoritative OOB zone, don't pollute the nsSpeeds with that
+ return rnameservers;
}
map<DNSName, double> speeds;
return rnameservers;
}
-static bool magicAddrMatch(const QType& query, const QType& answer)
-{
- if(query.getCode() != QType::ADDR)
- return false;
- return answer.getCode() == QType::A || answer.getCode() == QType::AAAA;
-}
-
static uint32_t getRRSIGTTL(const time_t now, const std::shared_ptr<RRSIGRecordContent>& rrsig)
{
uint32_t res = 0;
return false;
}
-vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet)
+vector<ComboAddress> SyncRes::retrieveAddressesForNS(const std::string& prefix, const DNSName& qname, vector<DNSName >::const_iterator& tns, const unsigned int depth, set<GetBestNSAnswer>& beenthere, const vector<DNSName >& rnameservers, NsSet& nameservers, bool& sendRDQuery, bool& pierceDontQuery, bool& flawedNSSet, bool cacheOnly)
{
vector<ComboAddress> result;
if(!tns->empty()) {
LOG(prefix<<qname<<": Trying to resolve NS '"<<*tns<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
- result = getAddrs(*tns, depth+2, beenthere);
+ result = getAddrs(*tns, depth+2, beenthere, cacheOnly);
pierceDontQuery=false;
}
else {
auto luaLocal = g_luaconfs.getLocal();
if (luaLocal->dsAnchors.empty()) {
+ LOG(d_prefix<<": No trust anchors configured, everything is Insecure"<<endl);
/* We have no TA, everything is insecure */
return Insecure;
}
std::string reason;
if (haveNegativeTrustAnchor(luaLocal->negAnchors, zone, reason)) {
- LOG(d_prefix<<": got NTA for "<<zone<<endl);
+ LOG(d_prefix<<": got NTA for '"<<zone<<"'"<<endl);
return NTA;
}
if (getTrustAnchor(luaLocal->dsAnchors, zone, ds)) {
- LOG(d_prefix<<": got TA for "<<zone<<endl);
+ LOG(d_prefix<<": got TA for '"<<zone<<"'"<<endl);
return TA;
}
+ else {
+ LOG(d_prefix<<": no TA found for '"<<zone<<"' among "<< luaLocal->dsAnchors.size()<<endl);
+ }
if (zone.isRoot()) {
/* No TA for the root */
}
bool oldSkipCNAME = d_skipCNAMECheck;
- bool oldRequireAuthData = d_requireAuthData;
d_skipCNAMECheck = true;
- d_requireAuthData = false;
std::set<GetBestNSAnswer> beenthere;
std::vector<DNSRecord> dsrecords;
vState state = Indeterminate;
int rcode = doResolve(zone, QType(QType::DS), dsrecords, depth + 1, beenthere, state);
d_skipCNAMECheck = oldSkipCNAME;
- d_requireAuthData = oldRequireAuthData;
if (rcode == RCode::NoError || (rcode == RCode::NXDomain && !bogusOnNXD)) {
+ uint8_t bestDigestType = 0;
+
if (state == Secure) {
bool gotCNAME = false;
for (const auto& record : dsrecords) {
if (record.d_type == QType::DS) {
const auto dscontent = getRR<DSRecordContent>(record);
if (dscontent && isSupportedDS(*dscontent)) {
+ // Make GOST a lower prio than SHA256
+ if (dscontent->d_digesttype == DNSSECKeeper::GOST && bestDigestType == DNSSECKeeper::SHA256) {
+ continue;
+ }
+ if (dscontent->d_digesttype > bestDigestType || (bestDigestType == DNSSECKeeper::GOST && dscontent->d_digesttype == DNSSECKeeper::SHA256)) {
+ bestDigestType = dscontent->d_digesttype;
+ }
ds.insert(*dscontent);
}
}
}
}
+ /* RFC 4509 section 3: "Validator implementations SHOULD ignore DS RRs containing SHA-1
+ * digests if DS RRs with SHA-256 digests are present in the DS RRset."
+ * As SHA348 is specified as well, the spirit of the this line is "use the best algorithm".
+ */
+ for (auto dsrec = ds.begin(); dsrec != ds.end(); ) {
+ if (dsrec->d_digesttype != bestDigestType) {
+ dsrec = ds.erase(dsrec);
+ }
+ else {
+ ++dsrec;
+ }
+ }
+
if (rcode == RCode::NoError && ds.empty()) {
if (foundCut) {
if (gotCNAME || denialProvesNoDelegation(zone, dsrecords)) {
bool SyncRes::haveExactValidationStatus(const DNSName& domain)
{
- if (!d_DNSSECValidationRequested) {
+ if (!shouldValidate()) {
return false;
}
const auto& it = d_cutStates.find(domain);
{
vState result = Indeterminate;
- if (!d_DNSSECValidationRequested) {
+ if (!shouldValidate()) {
return result;
}
DNSName name(subdomain);
LOG(d_prefix<<": setting cut state for "<<end<<" to "<<vStates[cutState]<<endl);
d_cutStates[end] = cutState;
- if (!d_DNSSECValidationRequested) {
+ if (!shouldValidate()) {
return;
}
std::vector<string> labelsToAdd = begin.makeRelative(end).getRawLabels();
bool oldSkipCNAME = d_skipCNAMECheck;
- bool oldRequireAuthData = d_requireAuthData;
d_skipCNAMECheck = true;
- d_requireAuthData = false;
while(qname != begin) {
if (labelsToAdd.empty())
just look for (N)TA
*/
if (cutState == Insecure || cutState == Bogus) {
- dsmap_t ds;
- vState newState = getDSRecords(qname, ds, true, depth);
+ dsmap_t cutDS;
+ vState newState = getDSRecords(qname, cutDS, true, depth);
if (newState == Indeterminate) {
continue;
}
}
else {
/* remove the temporary cut */
- LOG(d_prefix<<qname<<": removing cut state for "<<qname<<", was "<<vStates[d_cutStates[qname]]<<endl);
+ LOG(d_prefix<<qname<<": removing cut state for "<<qname<<endl);
d_cutStates.erase(qname);
}
}
d_skipCNAMECheck = oldSkipCNAME;
- d_requireAuthData = oldRequireAuthData;
LOG(d_prefix<<": list of cuts from "<<begin<<" to "<<end<<endl);
for (const auto& cut : d_cutStates) {
if (!signatures.empty()) {
DNSName signer = getSigner(signatures);
- if (!signer.empty() && signer.isPartOf(zone)) {
+ if (!signer.empty() && zone.isPartOf(signer)) {
vState state = getDSRecords(signer, ds, false, depth);
if (state != Secure) {
return Bogus;
}
-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)
+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, unsigned int& wildcardLabelsCount)
{
- struct CacheEntry
- {
- vector<DNSRecord> records;
- vector<shared_ptr<RRSIGRecordContent>> signatures;
- uint32_t signaturesTTL{std::numeric_limits<uint32_t>::max()};
- };
- struct CacheKey
- {
- DNSName name;
- uint16_t type;
- DNSResourceRecord::Place place;
- bool operator<(const CacheKey& rhs) const {
- return tie(name, type) < tie(rhs.name, rhs.type);
- }
- };
- typedef map<CacheKey, CacheEntry> tcache_t;
tcache_t tcache;
string prefix;
}
std::vector<std::shared_ptr<DNSRecord>> authorityRecs;
+ const unsigned int labelCount = qname.countLabels();
bool isCNAMEAnswer = false;
for(const auto& rec : lwr.d_records) {
+ if (rec.d_class != QClass::IN) {
+ continue;
+ }
+
if(!isCNAMEAnswer && rec.d_place == DNSResourceRecord::ANSWER && rec.d_type == QType::CNAME && (!(qtype==QType(QType::CNAME))) && rec.d_name == qname) {
isCNAMEAnswer = true;
}
+ /* if we have a positive answer synthetized from a wildcard,
+ we need to store the corresponding NSEC/NSEC3 records proving
+ that the exact name did not exist in the negative cache */
if(needWildcardProof) {
if (nsecTypes.count(rec.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
+ synthetized 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);
+ LOG(prefix<<qname<<": RRSIG indicates the name was synthetized from a wildcard, we need a wildcard proof"<<endl);
needWildcardProof = true;
+ wildcardLabelsCount = rrsig->d_labels;
}
// cerr<<"Got an RRSIG for "<<DNSRecordContent::NumberToType(rrsig->d_type)<<" with name '"<<rec.d_name<<"'"<<endl;
LOG(prefix<<qname<<": OPT answer '"<<rec.d_name<<"' from '"<<auth<<"' nameservers" <<endl);
continue;
}
- LOG(prefix<<qname<<": accept answer '"<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"|"<<rec.d_content->getZoneRepresentation()<<"' from '"<<auth<<"' nameservers? "<<(int)rec.d_place<<" ");
+ LOG(prefix<<qname<<": accept answer '"<<rec.d_name<<"|"<<DNSRecordContent::NumberToType(rec.d_type)<<"|"<<rec.d_content->getZoneRepresentation()<<"' from '"<<auth<<"' nameservers? ttl="<<rec.d_ttl<<", place="<<(int)rec.d_place<<" ");
if(rec.d_type == QType::ANY) {
- LOG("NO! - we don't accept 'ANY' data"<<endl);
+ LOG("NO! - we don't accept 'ANY'-typed data"<<endl);
+ continue;
+ }
+
+ if(rec.d_class != QClass::IN) {
+ LOG("NO! - we don't accept records for any other class than 'IN'"<<endl);
continue;
}
if(i->second.records.empty()) // this happens when we did store signatures, but passed on the records themselves
continue;
- bool isAA = lwr.d_aabit;
- if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME)) {
+ /* Even if the AA bit is set, additional data cannot be considered
+ as authoritative. This is especially important during validation
+ because keeping records in the additional section is allowed even
+ if the corresponding RRSIGs are not included, without setting the TC
+ bit, as stated in rfc4035's section 3.1.1. Including RRSIG RRs in a Response:
+ "When placing a signed RRset in the Additional section, the name
+ server MUST also place its RRSIG RRs in the Additional section.
+ If space does not permit inclusion of both the RRset and its
+ associated RRSIG RRs, the name server MAY retain the RRset while
+ dropping the RRSIG RRs. If this happens, the name server MUST NOT
+ set the TC bit solely because these RRSIG RRs didn't fit."
+ */
+ bool isAA = lwr.d_aabit && i->first.place != DNSResourceRecord::ADDITIONAL;
+ if (isAA && isCNAMEAnswer && (i->first.place != DNSResourceRecord::ANSWER || i->first.type != QType::CNAME || i->first.name != qname)) {
/*
rfc2181 states:
Note that the answer section of an authoritative answer normally
vState recordState = getValidationStatus(i->first.name, false);
LOG(d_prefix<<": got initial zone status "<<vStates[recordState]<<" for record "<<i->first.name<<endl);
- if (d_DNSSECValidationRequested && recordState == Secure) {
+ if (shouldValidate() && recordState == Secure) {
vState initialState = recordState;
if (isAA) {
}
}
else {
+ recordState = Indeterminate;
+
/* 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);
}
}
- if (initialState == Secure && state != recordState) {
+ if (initialState == Secure && state != recordState && isAA) {
updateValidationState(state, recordState);
}
}
else {
- if (d_DNSSECValidationRequested) {
+ if (shouldValidate()) {
LOG(d_prefix<<"Skipping validation because the current state is "<<vStates[recordState]<<endl);
}
}
- denial of existence proofs for negative responses are stored in the negative cache
*/
if (i->first.type != QType::NSEC3) {
- 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);
+ t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
}
if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
return RCode::NoError;
}
-void SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState expectedState, bool allowOptOut, bool referralToUnsigned)
+void SyncRes::updateDenialValidationState(NegCache::NegCacheEntry& ne, vState& state, const dState denialState, const dState expectedState, bool allowOptOut)
{
- ne.d_validationState = state;
-
- if (state == Secure) {
- cspmap_t csp = harvestCSPFromNE(ne);
- dState res = getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == NXQTYPE);
- if (res != expectedState) {
- if (res == OPTOUT && allowOptOut) {
- LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<endl);
- ne.d_validationState = Secure;
- return;
- }
- else if (res == INSECURE) {
- LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", returning Insecure"<<endl);
- ne.d_validationState = Insecure;
- }
- else {
- LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", returning Bogus, res="<<res<<", expectedState="<<expectedState<<endl);
- ne.d_validationState = Bogus;
- }
- updateValidationState(state, ne.d_validationState);
+ if (denialState == expectedState) {
+ ne.d_validationState = Secure;
+ }
+ else {
+ if (denialState == OPTOUT && allowOptOut) {
+ LOG(d_prefix<<"OPT-out denial found for "<<ne.d_name<<endl);
+ ne.d_validationState = Secure;
+ return;
+ }
+ else if (denialState == INSECURE) {
+ LOG(d_prefix<<"Insecure denial found for "<<ne.d_name<<", returning Insecure"<<endl);
+ ne.d_validationState = Insecure;
}
+ else {
+ LOG(d_prefix<<"Invalid denial found for "<<ne.d_name<<", returning Bogus, res="<<denialState<<", expectedState="<<expectedState<<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 needWildcardProof)
+dState SyncRes::getDenialValidationState(NegCache::NegCacheEntry& ne, const vState state, const dState expectedState, bool referralToUnsigned)
+{
+ cspmap_t csp = harvestCSPFromNE(ne);
+ return getDenial(csp, ne.d_name, ne.d_qtype.getCode(), referralToUnsigned, expectedState == NXQTYPE);
+}
+
+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, const bool needWildcardProof, const unsigned int wildcardLabelsCount)
{
bool done = false;
NegCache::NegCacheEntry ne;
uint32_t lowestTTL = rec.d_ttl;
- ne.d_name = qname;
+ /* if we get an NXDomain answer with a CNAME, the name
+ does exist but the target does not */
+ ne.d_name = newtarget.empty() ? qname : newtarget;
ne.d_qtype = QType(0); // this encodes 'whole record'
ne.d_auth = rec.d_name;
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
ne.d_ttd = d_now.tv_sec + lowestTTL;
- getDenialValidationState(ne, state, NXDOMAIN, false, false);
+ if (state == Secure) {
+ dState denialState = getDenialValidationState(ne, state, NXDOMAIN, false);
+ updateDenialValidationState(ne, state, denialState, NXDOMAIN, false);
+ }
+ else {
+ ne.d_validationState = state;
+ }
- if(!wasVariable()) {
+ /* if we get an NXDomain answer with a CNAME, let's not cache the
+ target, even the server was authoritative for it,
+ and do an additional query for the CNAME target.
+ We have a regression test making sure we do exactly that.
+ */
+ if(!wasVariable() && newtarget.empty()) {
t_sstorage.negcache.add(ne);
if(s_rootNXTrust && ne.d_auth.isRoot() && auth.isRoot()) {
ne.d_name = ne.d_name.getLastLabel();
newtarget=content->getTarget();
}
}
+ /* if we have a positive answer synthetized from a wildcard, we need to
+ return the corresponding NSEC/NSEC3 records from the AUTHORITY section
+ proving that the exact name did not exist */
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_type==qtype.getCode() || (lwr.d_aabit && (qtype==QType(QType::ANY) || magicAddrMatch(qtype, QType(rec.d_type)) ) ) || sendRDQuery
+ rec.d_type==qtype.getCode() || ((lwr.d_aabit || sendRDQuery) && qtype == QType(QType::ANY))
)
)
{
done=true;
ret.push_back(rec);
+
+ if (state == Secure && needWildcardProof) {
+ /* We have a positive answer synthetized from a wildcard, we need to check that we have
+ proof that the exact name doesn't exist so the wildcard can be used,
+ as described in section 5.3.4 of RFC 4035 and 5.3 of FRC 7129.
+ */
+ NegCache::NegCacheEntry ne;
+
+ uint32_t lowestTTL = rec.d_ttl;
+ ne.d_name = qname;
+ ne.d_qtype = QType(0); // this encodes 'whole record'
+ harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
+
+ cspmap_t csp = harvestCSPFromNE(ne);
+ dState res = getDenial(csp, qname, ne.d_qtype.getCode(), false, false, false, wildcardLabelsCount);
+ if (res != NXDOMAIN) {
+ vState st = Bogus;
+ if (res == INSECURE) {
+ /* Some part could not be validated, for example a NSEC3 record with a too large number of iterations,
+ this is not enough to warrant a Bogus, but go Insecure. */
+ st = Insecure;
+ LOG(d_prefix<<"Unable to validate denial in wildcard expanded positive response found for "<<qname<<", returning Insecure, res="<<res<<endl);
+ }
+ else {
+ LOG(d_prefix<<"Invalid denial in wildcard expanded positive response found for "<<qname<<", returning Bogus, res="<<res<<endl);
+ }
+
+ updateValidationState(state, st);
+ /* we already stored the record with a different validation status, let's fix it */
+ t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, lwr.d_aabit, st);
+ }
+ }
}
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)
uint32_t lowestTTL = rec.d_ttl;
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
- cspmap_t csp = harvestCSPFromNE(ne);
- dState denialState = getDenial(csp, newauth, QType::DS, true, true);
+ dState denialState = getDenialValidationState(ne, state, NXQTYPE, true);
+
if (denialState == NXQTYPE || denialState == OPTOUT || denialState == INSECURE) {
ne.d_ttd = lowestTTL + d_now.tv_sec;
ne.d_validationState = Secure;
LOG(prefix<<qname<<": got negative indication of DS record for '"<<newauth<<"'"<<endl);
- auto cut = d_cutStates.find(newauth);
- if (cut != d_cutStates.end()) {
- if (cut->second == Indeterminate) {
- cut->second = Insecure;
- }
- }
- else {
- LOG(prefix<<qname<<": setting cut state for "<<newauth<<" to "<<vStates[Insecure]<<endl);
- d_cutStates[newauth] = Insecure;
- }
-
if(!wasVariable()) {
t_sstorage.negcache.add(ne);
}
harvestNXRecords(lwr.d_records, ne, d_now.tv_sec, &lowestTTL);
ne.d_ttd = d_now.tv_sec + lowestTTL;
- getDenialValidationState(ne, state, NXQTYPE, qtype == QType::DS, false);
+ if (state == Secure) {
+ dState denialState = getDenialValidationState(ne, state, NXQTYPE, false);
+ updateDenialValidationState(ne, state, denialState, NXQTYPE, qtype == QType::DS);
+ } else {
+ ne.d_validationState = state;
+ }
if(!wasVariable()) {
if(qtype.getCode()) { // prevents us from blacking out a whole domain
bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, const QType& qtype, LWResult& lwr, boost::optional<Netmask>& ednsmask, const DNSName& auth, bool const sendRDQuery, const DNSName& nsName, const ComboAddress& remoteIP, bool doTCP, bool* truncated)
{
- int resolveret;
+ int resolveret = RCode::NoError;
s_outqueries++;
d_outqueries++;
}
bool needWildcardProof = false;
- *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof);
+ unsigned int wildcardLabelsCount;
+ *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof, wildcardLabelsCount);
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, needWildcardProof);
+ bool done = processRecords(prefix, qname, qtype, auth, lwr, sendRDQuery, ret, nsset, newtarget, newauth, realreferral, negindic, state, needWildcardProof, wildcardLabelsCount);
if(done){
LOG(prefix<<qname<<": status=got results, this level of recursion done"<<endl);
return -1;
}
- // this line needs to identify the 'self-resolving' behaviour, but we get it wrong now
- if(qname == *tns && qtype.getCode()==QType::A && rnameservers.size() > (size_t)(1+1*s_doIPv6)) {
- LOG(prefix<<qname<<": Not using NS to resolve itself! ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
- continue;
+ bool cacheOnly = false;
+ // this line needs to identify the 'self-resolving' behaviour
+ if(qname == *tns && (qtype.getCode() == QType::A || qtype.getCode() == QType::AAAA)) {
+ /* we might have a glue entry in cache so let's try this NS
+ but only if we have enough in the cache to know how to reach it */
+ LOG(prefix<<qname<<": Using NS to resolve itself, but only using what we have in cache ("<<(1+tns-rnameservers.cbegin())<<"/"<<rnameservers.size()<<")"<<endl);
+ cacheOnly = true;
}
typedef vector<ComboAddress> remoteIPs_t;
if(tns->empty() && !wasForwarded) {
LOG(prefix<<qname<<": Domain is out-of-band"<<endl);
- state = Insecure;
+ /* setting state to indeterminate since validation is disabled for local auth zone,
+ and Insecure would be misleading. */
+ state = Indeterminate;
d_wasOutOfBand = doOOBResolve(qname, qtype, lwr.d_records, depth, lwr.d_rcode);
lwr.d_tcbit=false;
lwr.d_aabit=true;
}
else {
/* if tns is empty, retrieveAddressesForNS() knows we have hardcoded servers (i.e. "forwards") */
- remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet);
+ remoteIPs = retrieveAddressesForNS(prefix, qname, tns, depth, beenthere, rnameservers, nameservers, sendRDQuery, pierceDontQuery, flawedNSSet, cacheOnly);
if(remoteIPs.empty()) {
LOG(prefix<<qname<<": Failed to get IP for NS "<<*tns<<", trying next if available"<<endl);