#include "aggressive_nsec.hh"
#include "cachecleaner.hh"
#include "recursor_cache.hh"
+#include "logger.hh"
#include "validate.hh"
std::unique_ptr<AggressiveNSECCache> g_aggressiveNSECCache{nullptr};
return false;
}
-#if 1
+#if 0
cerr<<"We have:"<<endl;
for (const auto& ent : zoneEntry->d_entries) {
cerr<<"- "<<ent.d_owner<<" -> "<<ent.d_next<<endl;
bool wrapped = false;
if (it == idx.begin() && it->d_owner != name) {
- //cerr<<"the lower bound is already the first entry, let's if the end is a wrap"<<endl;
it = idx.end();
// we know the map is not empty
it--;
}
else {
it--;
- // cerr<<"looping with "<<it->d_owner<<endl;
}
}
if (end) {
- //cerr<<"nothing left"<<endl;
return false;
}
- //cerr<<"considering "<<it->d_owner<<" "<<it->d_next<<endl;
-
if (it->d_ttd <= now) {
- //cerr<<"not using it"<<endl;
moveCacheItemToFront<ZoneEntry::SequencedTag>(zoneEntry->d_entries, it);
return false;
}
return false;
}
-static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std::vector<std::shared_ptr<RRSIGRecordContent>> signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret)
+static void addToRRSet(const time_t now, std::vector<DNSRecord>& recordSet, std::vector<std::shared_ptr<RRSIGRecordContent>> signatures, const DNSName& owner, bool doDNSSEC, std::vector<DNSRecord>& ret, DNSResourceRecord::Place place=DNSResourceRecord::AUTHORITY)
{
uint32_t ttl = 0;
}
record.d_ttl -= now;
+ record.d_name = owner;
ttl = record.d_ttl;
- record.d_place = DNSResourceRecord::AUTHORITY;
+ record.d_place = place;
ret.push_back(std::move(record));
}
dr.d_name = owner;
dr.d_ttl = ttl;
dr.d_content = std::move(signature);
- dr.d_place = DNSResourceRecord::AUTHORITY;
+ dr.d_place = place;
dr.d_class = QClass::IN;
ret.push_back(std::move(dr));
}
}
}
+#define LOG(x) if (g_dnssecLOG) { g_log <<Logger::Warning << x; }
+
+bool AggressiveNSECCache::synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName)
+{
+ vState cachedState;
+
+ std::vector<DNSRecord> wcSet;
+ std::vector<std::shared_ptr<RRSIGRecordContent>> wcSignatures;
+
+ if (g_recCache->get(now, wildcardName, type, true, &wcSet, ComboAddress("127.0.0.1"), false, boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
+ LOG("Unfortunately we don't have a valid entry for "<<wildcardName<<", so we cannot synthesize from that wildcard"<<endl);
+ return false;
+ }
+
+ addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+ /* no need for closest encloser proof, the wildcard is there */
+ addRecordToRRSet(now, nextCloser.d_owner, QType::NSEC3, nextCloser.d_ttd - now, nextCloser.d_record, nextCloser.d_signatures, doDNSSEC, ret);
+ /* and of course we won't deny the wildcard either */
+
+ LOG("Synthesized valid answer from NSEC3s and wildcard!"<<endl);
+ return true;
+}
+
+bool AggressiveNSECCache::synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName)
+{
+ vState cachedState;
+
+ std::vector<DNSRecord> wcSet;
+ std::vector<std::shared_ptr<RRSIGRecordContent>> wcSignatures;
+
+ if (g_recCache->get(now, wildcardName, type, true, &wcSet, ComboAddress("127.0.0.1"), false, boost::none, doDNSSEC ? &wcSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
+ LOG("Unfortunately we don't have a valid entry for "<<wildcardName<<", so we cannot synthesize from that wildcard"<<endl);
+ return false;
+ }
+
+ addToRRSet(now, wcSet, wcSignatures, name, doDNSSEC, ret, DNSResourceRecord::ANSWER);
+ addRecordToRRSet(now, nsec.d_owner, QType::NSEC3, nsec.d_ttd - now, nsec.d_record, nsec.d_signatures, doDNSSEC, ret);
+
+ LOG("Synthesized valid answer from NSECs and wildcard!"<<endl);
+ return true;
+}
+
bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr<AggressiveNSECCache::ZoneEntry>& zoneEntry, std::vector<DNSRecord>& soaSet, std::vector<std::shared_ptr<RRSIGRecordContent>>& soaSignatures, const DNSName& name, const QType& type, std::vector<DNSRecord>& ret, int& res, bool doDNSSEC)
{
const auto& salt = zoneEntry->d_salt;
auto nameHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, name))) + zone;
- cerr<<"looking for nsec3 "<<nameHash<<endl;
ZoneEntry::CacheEntry exactNSEC3;
if (getNSEC3(now, zoneEntry, nameHash, exactNSEC3)) {
- cerr<<"found direct match "<<nameHash<<endl;
+ LOG("Found a direct NSEC3 match for "<<nameHash);
auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(exactNSEC3.d_record);
if (!nsec3) {
+ LOG(" but the content is not valid"<<endl);
return false;
}
if (!isTypeDenied(nsec3, type)) {
+ LOG(" but the requested type ("<<type.getName()<<") does exist"<<endl);
return false;
}
that (original) owner name other than DS RRs, and all RRs below that
owner name regardless of type.
*/
+ LOG(" but this is an ancetor delegation NSEC3"<<endl);
return false;
}
- cerr<<"Direct match, done!"<<endl;
+ LOG(": done!"<<endl);
res = RCode::NoError;
addToRRSet(now, soaSet, soaSignatures, zoneEntry->d_zone, doDNSSEC, ret);
addRecordToRRSet(now, exactNSEC3.d_owner, QType::NSEC3, exactNSEC3.d_ttd - now, exactNSEC3.d_record, exactNSEC3.d_signatures, doDNSSEC, ret);
return true;
}
- cerr<<"no direct match, looking for closest encloser"<<endl;
+ LOG("No direct NSEC3 match found for "<<nameHash<<", looking for closest encloser"<<endl);
DNSName closestEncloser(name);
bool found = false;
ZoneEntry::CacheEntry closestNSEC3;
while (!found && closestEncloser.chopOff()) {
auto closestHash = DNSName(toBase32Hex(hashQNameWithSalt(salt, iterations, closestEncloser))) + zone;
- cerr<<"looking for nsec3 "<<closestHash<<endl;
if (getNSEC3(now, zoneEntry, closestHash, closestNSEC3)) {
- cerr<<"found next closest encloser at "<<closestEncloser<<endl;
+ LOG("Found closest encloser at "<<closestEncloser<<" ("<<closestHash<<")"<<endl);
found = true;
break;
}
}
if (!found) {
- cerr<<"nothing found in aggressive cache either"<<endl;
+ LOG("Nothing found for the closest encloser in NSEC3 aggressive cache either"<<endl);
return false;
}
DNSName nextCloser(closestEncloser);
nextCloser.prependRawLabel(name.getRawLabel(labelIdx - 1));
auto nextCloserHash = toBase32Hex(hashQNameWithSalt(salt, iterations, nextCloser));
- cerr<<"looking for a NSEC3 covering the next closer "<<nextCloser<<": "<<nextCloserHash<<endl;
+ LOG("Looking for a NSEC3 covering the next closer "<<nextCloser<<" ("<<nextCloserHash<<")"<<endl);
ZoneEntry::CacheEntry nextCloserEntry;
if (!getNSECBefore(now, zoneEntry, DNSName(nextCloserHash) + zone, nextCloserEntry)) {
- cerr<<"nothing found for the next closer in aggressive cache"<<endl;
+ LOG("Nothing found for the next encloser in NSEC3 aggressive cache"<<endl);
return false;
}
if (!isCoveredByNSEC3Hash(DNSName(nextCloserHash) + zone, nextCloserEntry.d_owner, nextCloserEntry.d_next)) {
- cerr<<"no covering record found for the next closer in aggressive cache"<<endl;
+ LOG("No covering record found for the next encloser in NSEC3 aggressive cache"<<endl);
return false;
}
DNSName wildcard(g_wildcarddnsname + closestEncloser);
auto wcHash = toBase32Hex(hashQNameWithSalt(salt, iterations, wildcard));
- cerr<<"looking for a NSEC3 covering the wildcard "<<wildcard<<": "<<wcHash<<endl;
+ LOG("Looking for a NSEC3 covering the wildcard "<<wildcard<<" ("<<wcHash<<")"<<endl);
ZoneEntry::CacheEntry wcEntry;
if (!getNSECBefore(now, zoneEntry, DNSName(wcHash) + zone, wcEntry)) {
- cerr<<"nothing found for the wildcard in aggressive cache"<<endl;
+ LOG("Nothing found for the wildcard in NSEC3 aggressive cache"<<endl);
return false;
}
if ((DNSName(wcHash) + zone) == wcEntry.d_owner) {
+ LOG("Found an exact match for the wildcard");
+
auto nsec3 = std::dynamic_pointer_cast<NSEC3RecordContent>(wcEntry.d_record);
if (!nsec3) {
+ LOG(" but the content is not valid"<<endl);
return false;
}
if (!isTypeDenied(nsec3, type)) {
- return false;
+ LOG(" but the requested type ("<<type.getName()<<") does exist"<<endl);
+ return synthesizeFromNSEC3Wildcard(now, name, type, ret, res, doDNSSEC, nextCloserEntry, wildcard);
}
res = RCode::NoError;
+ LOG(endl);
}
else {
if (!isCoveredByNSEC3Hash(DNSName(wcHash) + zone, wcEntry.d_owner, wcEntry.d_next)) {
- cerr<<"no covering record found for the wildcard in aggressive cache"<<endl;
+ LOG("No covering record found for the wildcard in aggressive cache"<<endl);
return false;
}
res = RCode::NXDomain;
addRecordToRRSet(now, nextCloserEntry.d_owner, QType::NSEC3, nextCloserEntry.d_ttd - now, nextCloserEntry.d_record, nextCloserEntry.d_signatures, doDNSSEC, ret);
addRecordToRRSet(now, wcEntry.d_owner, QType::NSEC3, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
- cerr<<"Done!"<<endl;
+ LOG("Found valid NSEC3s covering the requested name and type!"<<endl);
return true;
}
{
auto zoneEntry = getBestZone(name);
if (!zoneEntry) {
- cerr<<"zone info not found"<<endl;
return false;
}
std::vector<DNSRecord> soaSet;
std::vector<std::shared_ptr<RRSIGRecordContent>> soaSignatures;
if (g_recCache->get(now, zoneEntry->d_zone, QType::SOA, true, &soaSet, who, false, routingTag, doDNSSEC ? &soaSignatures : nullptr, nullptr, nullptr, &cachedState) <= 0 || cachedState != vState::Secure) {
- cerr<<"could not find SOA"<<endl;
+ LOG("No valid SOA found for "<<zoneEntry->d_zone<<", which is the best match for "<<name<<endl);
return false;
}
if (zoneEntry->d_nsec3) {
- cerr<<"nsec 3"<<endl;
return getNSEC3Denial(now, zoneEntry, soaSet, soaSignatures, name, type, ret, res, doDNSSEC);
}
bool covered = false;
bool needWildcard = false;
- cerr<<"looking for nsec before "<<name<<endl;
+ LOG("Looking for a NSEC before "<<name);
if (!getNSECBefore(now, zoneEntry, name, entry)) {
- cerr<<"nothing found in aggressive cache either"<<endl;
+ LOG(": nothing found in the aggressive cache"<<endl);
return false;
}
return false;
}
- cerr<<"nsecFound "<<entry.d_owner<<endl;
+ LOG(": found a possible NSEC at "<<entry.d_owner<<" ");
auto denial = matchesNSEC(name, type.getCode(), entry.d_owner, content, entry.d_signatures);
if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
- cerr<<"no dice"<<endl;
+ LOG(" but it does no cover us"<<endl);
return false;
}
else if (denial == dState::NXQTYPE) {
covered = true;
- cerr<<"nx qtype"<<endl;
+ LOG(" and it proves that the type does not exist"<<endl);
res = RCode::NoError;
}
else if (denial == dState::NXDOMAIN) {
+ LOG(" and it proves that the name does not exist"<<endl);
const DNSName commonLabels = entry.d_owner.getCommonLabels(entry.d_next);
DNSName wc(name);
auto labelsCount = wc.countLabels();
}
wc = g_wildcarddnsname + wc;
- cerr<<"looking for nsec before "<<wc<<endl;
+ LOG("Now looking for a NSEC before the wildcard "<<wc);
if (!getNSECBefore(now, zoneEntry, wc, wcEntry)) {
- cerr<<"nothing found in aggressive cache for Wildcard"<<endl;
+ LOG(": nothing found in the aggressive cache"<<endl);
return false;
}
- cerr<<"wc nsec found "<<wcEntry.d_owner<<endl;
- if (wcEntry.d_owner == entry.d_owner) {
+ LOG(": found a possible NSEC at "<<wcEntry.d_owner<<" ");
+ auto nsecContent = std::dynamic_pointer_cast<NSECRecordContent>(wcEntry.d_record);
+
+ denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, nsecContent, wcEntry.d_signatures);
+ if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
+ LOG(" but it does no cover us"<<endl);
+
+ if (wcEntry.d_owner == wc) {
+ return synthesizeFromNSECWildcard(now, name, type, ret, res, doDNSSEC, entry, wc);
+ }
+
+ return false;
+ }
+ else if (denial == dState::NXQTYPE) {
+ LOG(" and it proves that there is a matching wildcard, but the type does not exist"<<endl);
+ covered = true;
+ res = RCode::NoError;
+ }
+ else if (denial == dState::NXDOMAIN) {
+ LOG(" and it proves that there is no matching wildcard"<<endl);
covered = true;
res = RCode::NXDomain;
}
- else {
- auto nsecContent = std::dynamic_pointer_cast<NSECRecordContent>(wcEntry.d_record);
- denial = matchesNSEC(wc, type.getCode(), wcEntry.d_owner, nsecContent, wcEntry.d_signatures);
- if (denial == dState::NODENIAL || denial == dState::INCONCLUSIVE) {
- /* too complicated for now */
- /* we would need:
- - to store wildcard entries in the non-expanded form in the record cache, in addition to their expanded form ;
- - do a lookup to retrieve them ;
- - expand them and the NSEC
- */
- return false;
- }
- else if (denial == dState::NXQTYPE) {
- covered = true;
- res = RCode::NoError;
- }
- else if (denial == dState::NXDOMAIN) {
- covered = true;
- res = RCode::NXDomain;
- }
- if (wcEntry.d_owner != wc) {
- needWildcard = true;
- }
+ if (wcEntry.d_owner != wc && wcEntry.d_owner != entry.d_owner) {
+ needWildcard = true;
}
}
addRecordToRRSet(now, wcEntry.d_owner, QType::NSEC, wcEntry.d_ttd - now, wcEntry.d_record, wcEntry.d_signatures, doDNSSEC, ret);
}
+ LOG("Found valid NSECs covering the requested name and type!"<<endl);
return true;
}
BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc)
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask b.powerdns.com., get a NXD, then check that the aggressive
+ NSEC cache will use the NSEC (a -> h) to prove that g.powerdns.com. does not exist
+ either */
+ const DNSName target1("b.powerdns.com.");
+ const DNSName target2("g.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target1, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC, name does not exist (the generic version will generate an exact NSEC for the target, which we don't want) */
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no record for this name */
+ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no wildcard either */
+ addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ return LWResult::Result::Success;
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == target1) {
+ setLWResult(res, RCode::NXDomain, true, false, true);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no record for this name */
+ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("h.powerdns.com."), {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no wildcard either */
+ addNSECRecordToLW(DNSName(").powerdns.com."), DNSName("a.powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
+ return LWResult::Result::Success;
+ }
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NXDomain);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive
+ NSEC cache will use the NSEC to prove that the AAAA does not exist either */
+ const DNSName target("a.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == target && type == QType::A) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no record for this name */
+ /* exact match */
+ addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no need for wildcard in that case */
+ return LWResult::Result::Success;
+ }
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nodata_wildcard)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match),
+ then check that the aggressive NSEC cache will use the NSEC to prove that the AAAA does not exist either */
+ const DNSName target("a.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* the name does not exist, a wildcard applies but does not have this type */
+ addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == target && type == QType::A) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* the name does not exist, a wildcard applies but does not have this type */
+ addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec_wildcard_synthesis)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard,
+ then check that the aggressive NSEC cache will use the wildcard to synthesize an answer
+ for b.powerdns.com */
+ const DNSName target("a.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC, name does not exist but there is a wildcard (the generic version will generate an exact NSEC for the target, which we don't want) */
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* the name does not exist, a wildcard applies and have the requested type but no DS */
+ addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ /* the name does not exist, a wildcard applies and has the requested type */
+ addNSECRecordToLW(DNSName("*.powerdns.com."), DNSName("z.powerdns.com."), {QType::A, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(ret.at(0).d_name, target);
+ BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("b.powerdns.com."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com."));
+ BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain)
{
std::unique_ptr<SyncRes> sr;
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
primeHints();
- /* we are lucky enough that our hashes will cover g.powerdns.com. as well */
+ /* we are lucky enough that our hashes will cover g.powerdns.com. as well,
+ so we first ask b.powerdns.com., get a NXD, then check that the aggressive
+ NSEC cache will use the NSEC3 to prove that g.powerdns.com. does not exist
+ either */
const DNSName target1("b.powerdns.com.");
const DNSName target2("g.powerdns.com.");
testkeysset_t keys;
else if (ip == ComboAddress("192.0.2.1:53")) {
if (domain == target1) {
setLWResult(res, RCode::NXDomain, true, false, true);
- /* no data */
addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* no record for this name */
/* first the closest encloser */
- addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* then the next closer */
- addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG, QType::NSEC}, 600, res->d_records);
+ addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* no wildcard */
- addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
+ addNSEC3NarrowRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), {QType::AAAA, QType::RRSIG}, 600, res->d_records);
addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300);
return LWResult::Result::Success;
}
BOOST_CHECK_EQUAL(queriesCount, 7U);
}
-#if 0
BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata)
{
std::unique_ptr<SyncRes> sr;
setDNSSECValidation(sr, DNSSECMode::ValidateAll);
primeHints();
- const DNSName target1("a.powerdns.com.");
- const DNSName target2("b.powerdns.com.");
+ /* we first ask a.powerdns.com. | A, get a NODATA, then check that the aggressive
+ NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */
+ const DNSName target("a.powerdns.com.");
testkeysset_t keys;
auto luaconfsCopy = g_luaconfs.getCopy();
size_t queriesCount = 0;
- sr->setAsyncCallback([target1, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
queriesCount++;
if (type == QType::DS || type == QType::DNSKEY) {
}
else if (domain == DNSName("com.")) {
/* no cut */
- return genericDSAndDNSKEYHandler(res, domain, domain, type, keys, false);
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
}
else {
/* cut */
return LWResult::Result::Success;
}
else if (ip == ComboAddress("192.0.2.1:53")) {
- if (domain == target1) {
- setLWResult(res, 0, true, false, true);
+ if (domain == target && type == QType::A) {
+ setLWResult(res, RCode::NoError, true, false, true);
/* no data */
- addRecordToLW(res, DNSName("com."), QType::SOA, "com. com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
- addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* no record for this name */
+ /* exact match */
+ addNSEC3UnhashedRecordToLW(DNSName("a.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* no need for next closer or wildcard in that case */
+ return LWResult::Result::Success;
+ }
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata_wildcard)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask a.powerdns.com. | A, get a NODATA (no exact match but there is a wildcard match),
+ then check that the aggressive NSEC cache will use the NSEC3 to prove that the AAAA does not exist either */
+ const DNSName target("a.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* then the next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* a wildcard applies but does not have this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ if (domain == target && type == QType::A) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* first the closest encloser */
- addNSEC3UnhashedRecordToLW(DNSName("com."), DNSName("com."), "whatever", {QType::A, QType::TXT, QType::RRSIG, QType::NSEC}, 600, res->d_records);
- addRRSIG(keys, res->d_records, DNSName("com."), 300);
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
/* then the next closer */
- addNSEC3NarrowRecordToLW(domain, DNSName("com."), {QType::RRSIG, QType::NSEC}, 600, res->d_records);
- addRRSIG(keys, res->d_records, DNSName("com."), 300);
- /* a wildcard matches but has no record for this type */
- addNSEC3UnhashedRecordToLW(DNSName("*.com."), DNSName("com."), "whatever", {QType::AAAA, QType::NSEC, QType::RRSIG}, 600, res->d_records);
- addRRSIG(keys, res->d_records, DNSName("com"), 300, false, boost::none, DNSName("*.com"));
+ addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* a wildcard applies but does not have this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
return LWResult::Result::Success;
}
}
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 8U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
- /* again, to test the cache */
ret.clear();
- res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
BOOST_REQUIRE_EQUAL(ret.size(), 8U);
- BOOST_CHECK_EQUAL(queriesCount, 6U);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+}
+
+BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_wildcard_synthesis)
+{
+ std::unique_ptr<SyncRes> sr;
+ initSR(sr, true);
+ g_aggressiveNSECCache = make_unique<AggressiveNSECCache>();
+
+ setDNSSECValidation(sr, DNSSECMode::ValidateAll);
+
+ primeHints();
+ /* we first ask a.powerdns.com. | A, get an answer synthesized from the wildcard,
+ then check that the aggressive NSEC cache will use the wildcard to synthesize an answer
+ for b.powerdns.com */
+ const DNSName target("a.powerdns.com.");
+ testkeysset_t keys;
+
+ auto luaconfsCopy = g_luaconfs.getCopy();
+ luaconfsCopy.dsAnchors.clear();
+ generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys, luaconfsCopy.dsAnchors);
+ generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::DIGEST_SHA256, keys);
+
+ g_luaconfs.setState(luaconfsCopy);
+
+ size_t queriesCount = 0;
+
+ sr->setAsyncCallback([target, &queriesCount, keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, LWResult* res, bool* chained) {
+ queriesCount++;
+
+ if (type == QType::DS || type == QType::DNSKEY) {
+ if (domain != DNSName("powerdns.com.") && domain.isPartOf(DNSName("powerdns.com."))) {
+ /* no cut, NSEC3, name does not exist but there is a wildcard (the generic version will generate an exact NSEC3 for the target, which we don't want) */
+ setLWResult(res, RCode::NoError, true, false, true);
+ /* no data */
+ addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "powerdns.com. powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* first the closest encloser */
+ addNSEC3UnhashedRecordToLW(DNSName("powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::TXT, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* then the next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* a wildcard applies but does not have this type */
+ addNSEC3UnhashedRecordToLW(DNSName("*.powerdns.com."), DNSName("powerdns.com."), "whatever", {QType::A, QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com"));
+ return LWResult::Result::Success;
+ }
+ else if (domain == DNSName("com.")) {
+ /* no cut */
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys, false);
+ }
+ else if (domain == DNSName("powerdns.com.")) {
+ return genericDSAndDNSKEYHandler(res, domain, DNSName("."), type, keys);
+ }
+ else {
+ /* cut */
+ return genericDSAndDNSKEYHandler(res, domain, domain, type, keys);
+ }
+ }
+ else {
+ if (isRootServer(ip)) {
+ setLWResult(res, 0, false, false, true);
+ addRecordToLW(res, "powerdns.com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600);
+ addDS(DNSName("powerdns.com."), 300, res->d_records, keys);
+ addRRSIG(keys, res->d_records, DNSName("."), 300);
+ addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);
+ return LWResult::Result::Success;
+ }
+ else if (ip == ComboAddress("192.0.2.1:53")) {
+ setLWResult(res, RCode::NoError, true, false, true);
+ addRecordToLW(res, domain, QType::A, "192.0.2.1");
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300, false, boost::none, DNSName("*.powerdns.com"));
+ /* no need for the closest encloser since we have a positive answer expanded from a wildcard */
+ /* the next closer */
+ addNSEC3UnhashedRecordToLW(DNSName("+.powerdns.com."), DNSName("powerdns.com."), "v", {QType::RRSIG}, 600, res->d_records);
+ addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300);
+ /* and of course we don't deny the wildcard itself */
+ return LWResult::Result::Success;
+ }
+ }
+
+ return LWResult::Result::Timeout;
+ });
+
+ vector<DNSRecord> ret;
+ int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(ret.at(0).d_name, target);
+ BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
+
+ ret.clear();
+ res = sr->beginResolve(DNSName("b.powerdns.com."), QType(QType::A), QClass::IN, ret);
+ BOOST_CHECK_EQUAL(res, RCode::NoError);
+ BOOST_CHECK_EQUAL(sr->getValidationState(), vState::Secure);
+ BOOST_REQUIRE_EQUAL(ret.size(), 4U);
+ BOOST_CHECK_EQUAL(ret.at(0).d_name, DNSName("b.powerdns.com."));
+ BOOST_CHECK_EQUAL(ret.at(0).d_type, QType::A);
+ BOOST_CHECK_EQUAL(queriesCount, 7U);
}
-#endif
BOOST_AUTO_TEST_SUITE_END()