From: Remi Gacogne Date: Mon, 4 Jan 2021 17:43:12 +0000 (+0100) Subject: rec: Synthesize wildcard answers from the aggressive NSEC cache X-Git-Tag: dnsdist-1.6.0-alpha2~12^2~26 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5a5c53b86696eee29e1362a42c283f3bbd3ccb28;p=thirdparty%2Fpdns.git rec: Synthesize wildcard answers from the aggressive NSEC cache --- diff --git a/pdns/recursordist/aggressive_nsec.cc b/pdns/recursordist/aggressive_nsec.cc index 325daadd78..0f57213714 100644 --- a/pdns/recursordist/aggressive_nsec.cc +++ b/pdns/recursordist/aggressive_nsec.cc @@ -23,6 +23,7 @@ #include "aggressive_nsec.hh" #include "cachecleaner.hh" #include "recursor_cache.hh" +#include "logger.hh" #include "validate.hh" std::unique_ptr g_aggressiveNSECCache{nullptr}; @@ -136,7 +137,7 @@ bool AggressiveNSECCache::getNSECBefore(time_t now, std::shared_ptrd_entries) { cerr<<"- "< "<d_owner != name) { - //cerr<<"the lower bound is already the first entry, let's if the end is a wrap"<d_owner<d_owner<<" "<d_next<d_ttd <= now) { - //cerr<<"not using it"<(zoneEntry->d_entries, it); return false; } @@ -218,7 +213,7 @@ bool AggressiveNSECCache::getNSEC3(time_t now, std::shared_ptr& recordSet, std::vector> signatures, const DNSName& owner, bool doDNSSEC, std::vector& ret) +static void addToRRSet(const time_t now, std::vector& recordSet, std::vector> signatures, const DNSName& owner, bool doDNSSEC, std::vector& ret, DNSResourceRecord::Place place=DNSResourceRecord::AUTHORITY) { uint32_t ttl = 0; @@ -228,8 +223,9 @@ static void addToRRSet(const time_t now, std::vector& recordSet, std: } 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)); } @@ -240,7 +236,7 @@ static void addToRRSet(const time_t now, std::vector& recordSet, std: 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)); } @@ -272,6 +268,48 @@ static void addRecordToRRSet(time_t now, const DNSName& owner, const QType& type } } +#define LOG(x) if (g_dnssecLOG) { g_log <& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName) +{ + vState cachedState; + + std::vector wcSet; + std::vector> 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 "<& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName) +{ + vState cachedState; + + std::vector wcSet; + std::vector> 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 "<& zoneEntry, std::vector& soaSet, std::vector>& soaSignatures, const DNSName& name, const QType& type, std::vector& ret, int& res, bool doDNSSEC) { const auto& salt = zoneEntry->d_salt; @@ -280,16 +318,17 @@ bool AggressiveNSECCache::getNSEC3Denial(time_t now, std::shared_ptr(exactNSEC3.d_record); if (!nsec3) { + LOG(" but the content is not valid"<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"<(wcEntry.d_record); if (!nsec3) { + LOG(" but the content is not valid"< soaSet; std::vector> 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"<d_zone<<", which is the best match for "<d_nsec3) { - cerr<<"nsec 3"<(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"<(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; } } @@ -504,5 +544,6 @@ bool AggressiveNSECCache::getDenial(time_t now, const DNSName& name, const QType 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!"<& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry); bool getNSEC3(time_t now, std::shared_ptr& zoneEntry, const DNSName& name, ZoneEntry::CacheEntry& entry); bool getNSEC3Denial(time_t now, std::shared_ptr& zoneEntry, std::vector& soaSet, std::vector>& soaSignatures, const DNSName& name, const QType& type, std::vector& ret, int& res, bool doDNSSEC); + bool synthesizeFromNSEC3Wildcard(time_t now, const DNSName& name, const QType& type, std::vector& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nextCloser, const DNSName& wildcardName); + bool synthesizeFromNSECWildcard(time_t now, const DNSName& name, const QType& type, std::vector& ret, int& res, bool doDNSSEC, ZoneEntry::CacheEntry& nsec, const DNSName& wildcardName); SuffixMatchTree> d_zones; ReadWriteLock d_lock; diff --git a/pdns/recursordist/test-aggressive_nsec_cc.cc b/pdns/recursordist/test-aggressive_nsec_cc.cc index da73338926..a355b7e7a4 100644 --- a/pdns/recursordist/test-aggressive_nsec_cc.cc +++ b/pdns/recursordist/test-aggressive_nsec_cc.cc @@ -6,6 +6,374 @@ BOOST_AUTO_TEST_SUITE(aggressive_nsec_cc) +BOOST_AUTO_TEST_CASE(test_aggressive_nsec_nxdomain) +{ + std::unique_ptr sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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 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 sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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 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 sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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 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 sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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 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 sr; @@ -15,7 +383,10 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain) 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; @@ -61,18 +432,17 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain) 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; } @@ -97,7 +467,6 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nxdomain) BOOST_CHECK_EQUAL(queriesCount, 7U); } -#if 0 BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) { std::unique_ptr sr; @@ -107,8 +476,9 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) 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(); @@ -120,7 +490,7 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) 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& srcmask, boost::optional 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& srcmask, boost::optional context, LWResult* res, bool* chained) { queriesCount++; if (type == QType::DS || type == QType::DNSKEY) { @@ -130,7 +500,10 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) } 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 */ @@ -147,21 +520,119 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) 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 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 sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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; } } @@ -175,16 +646,115 @@ BOOST_AUTO_TEST_CASE(test_aggressive_nsec3_nodata) 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 sr; + initSR(sr, true); + g_aggressiveNSECCache = make_unique(); + + 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& srcmask, boost::optional 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 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() diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index d2c1f2954e..1c0c259895 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -287,7 +287,7 @@ void computeRRSIG(const DNSSECPrivateKey& dpk, const DNSName& signer, const DNSN const auto& rc = dpk.getKey(); rrc.d_type = signQType; - rrc.d_labels = signQName.countLabels() - signQName.isWildcard(); + rrc.d_labels = signQName.countLabels() - (signQName.isWildcard() ? 1 : 0); rrc.d_originalttl = signTTL; rrc.d_siginception = inception ? *inception : (*now - 10); rrc.d_sigexpire = *now + sigValidity; diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 4052e1f5a3..d7d43c6ef1 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -1945,7 +1945,6 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w /* let's check if we have a NSEC covering that record */ if (g_aggressiveNSECCache) { - cerr<<"calling get denial"<getDenial(d_now.tv_sec, qname, qtype, ret, res, d_cacheRemote, d_routingTag, d_doDNSSEC)) { state = vState::Secure; return true; @@ -3352,12 +3351,29 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr if (doCache) { g_recCache->replace(d_now.tv_sec, i->first.name, i->first.type, i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, auth, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, d_routingTag, recordState, remoteIP); + + if (needWildcardProof && recordState == vState::Secure && i->first.place == DNSResourceRecord::ANSWER && g_aggressiveNSECCache && i->first.name == qname && !i->second.signatures.empty() && !d_routingTag && !ednsmask) { + /* we have an answer synthesized from a wildcard and aggressive NSEC is enabled, we need to store the + wildcard in its non-expanded form in the cache to be able to synthesize wildcard answers later */ + const auto& rrsig = i->second.signatures.at(0); + if (isWildcardExpanded(labelCount, rrsig) && !isWildcardExpandedOntoItself(i->first.name, labelCount, rrsig)) { + DNSName realOwner = getNSECOwnerName(i->first.name, i->second.signatures); + std::vector content; + content.reserve(i->second.records.size()); + for (const auto& record : i->second.records) { + DNSRecord nonExpandedRecord(record); + nonExpandedRecord.d_name = realOwner; + content.push_back(std::move(nonExpandedRecord)); + } + + g_recCache->replace(d_now.tv_sec, realOwner, QType(i->first.type), content, i->second.signatures, /* no additional records in that case */ {}, i->first.type == QType::DS ? true : isAA, auth, boost::none, boost::none, recordState, remoteIP); + } + } } } if ((i->first.type == QType::NSEC || i->first.type == QType::NSEC3) && recordState == vState::Secure && !seenAuth.empty() && g_aggressiveNSECCache) { - cerr<<"Good candidate for aggressive NSEC caching"<insertNSEC(seenAuth, i->first.name, i->second.records.at(0), i->second.signatures, i->first.type == QType::NSEC3); }