From: Remi Gacogne Date: Thu, 29 Jun 2017 13:29:40 +0000 (+0200) Subject: rec: Add IXFR unit tests X-Git-Tag: rec-4.0.6~1^2 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c8f3468f102a4ab17ea1b5a9f408ce2bad3ddeab;p=pdns rec: Add IXFR unit tests (cherry picked from commit e503653f7d4c7e28b594336b37bcf602c7f5119a) --- diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 9f1cead79..fc73177b8 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -1134,6 +1134,7 @@ testrunner_SOURCES = \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ iputils.cc \ + ixfr.cc ixfr.hh \ logger.cc \ misc.cc \ nameserver.cc \ @@ -1149,6 +1150,7 @@ testrunner_SOURCES = \ test-base32_cc.cc \ test-base64_cc.cc \ test-bindparser_cc.cc \ + test-common.hh \ test-delaypipe_hh.cc \ test-distributor_hh.cc \ test-dns_random_hh.cc \ @@ -1156,6 +1158,7 @@ testrunner_SOURCES = \ test-dnsparser_hh.cc \ test-dnsrecords_cc.cc \ test-iputils_hh.cc \ + test-ixfr_cc.cc \ test-md5_hh.cc \ test-misc_hh.cc \ test-nameserver_cc.cc \ @@ -1167,6 +1170,7 @@ testrunner_SOURCES = \ test-statbag_cc.cc \ test-zoneparser_tng_cc.cc \ testrunner.cc \ + tsigverifier.cc tsigverifier.hh \ ueberbackend.cc \ unix_utility.cc \ zoneparser-tng.cc zoneparser-tng.hh diff --git a/pdns/dnsrecords.hh b/pdns/dnsrecords.hh index 53284eccc..2bf11d5f9 100644 --- a/pdns/dnsrecords.hh +++ b/pdns/dnsrecords.hh @@ -198,6 +198,7 @@ class CNAMERecordContent : public DNSRecordContent { public: includeboilerplate(CNAME) + CNAMERecordContent(const DNSName& content) : d_content(content){} DNSName getTarget() const { return d_content; } private: DNSName d_content; diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index c80444056..d1c6b306e 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -185,6 +185,7 @@ testrunner_SOURCES = \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ iputils.cc iputils.hh \ + ixfr.cc ixfr.hh \ logger.cc logger.hh \ misc.cc misc.hh \ namespaces.hh \ @@ -202,16 +203,19 @@ testrunner_SOURCES = \ test-arguments_cc.cc \ test-base32_cc.cc \ test-base64_cc.cc \ + test-common.hh \ test-dns_random_hh.cc \ test-dnsname_cc.cc \ test-dnsparser_hh.cc \ test-dnsrecords_cc.cc \ test-iputils_hh.cc \ + test-ixfr_cc.cc \ test-misc_hh.cc \ test-nmtree.cc \ test-rcpgenerator_cc.cc \ test-recpacketcache_cc.cc \ testrunner.cc \ + tsigverifier.cc tsigverifier.hh \ unix_utility.cc \ zoneparser-tng.cc zoneparser-tng.hh diff --git a/pdns/recursordist/test-common.hh b/pdns/recursordist/test-common.hh new file mode 120000 index 000000000..c03711b8c --- /dev/null +++ b/pdns/recursordist/test-common.hh @@ -0,0 +1 @@ +../test-common.hh \ No newline at end of file diff --git a/pdns/recursordist/test-ixfr_cc.cc b/pdns/recursordist/test-ixfr_cc.cc new file mode 120000 index 000000000..d033c3917 --- /dev/null +++ b/pdns/recursordist/test-ixfr_cc.cc @@ -0,0 +1 @@ +../test-ixfr_cc.cc \ No newline at end of file diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc new file mode 100644 index 000000000..0e1e5051b --- /dev/null +++ b/pdns/recursordist/test-syncres_cc.cc @@ -0,0 +1,6601 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN +#include + +#include "arguments.hh" +#include "dnssecinfra.hh" +#include "dnsseckeeper.hh" +#include "lua-recursor4.hh" +#include "namespaces.hh" +#include "rec-lua-conf.hh" +#include "root-dnssec.hh" +#include "syncres.hh" +#include "test-common.hh" +#include "utility.hh" +#include "validate-recursor.hh" + +RecursorStats g_stats; +GlobalStateHolder g_luaconfs; +thread_local std::unique_ptr t_RC{nullptr}; +unsigned int g_numThreads = 1; + +/* Fake some required functions we didn't want the trouble to + link with */ +ArgvMap &arg() +{ + static ArgvMap theArg; + return theArg; +} + +int getMTaskerTID() +{ + return 0; +} + +bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector& res, int& ret) +{ + return false; +} + +int asyncresolve(const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) +{ + return 0; +} + +/* primeHints() is only here for now because it + was way too much trouble to link with the real one. + We should fix this, empty functions are one thing, but this is + bad. +*/ + +#include "root-addresses.hh" + +void primeHints(void) +{ + vector nsset; + if(!t_RC) + t_RC = std::unique_ptr(new MemRecursorCache()); + + DNSRecord arr, aaaarr, nsrr; + nsrr.d_name=g_rootdnsname; + arr.d_type=QType::A; + aaaarr.d_type=QType::AAAA; + nsrr.d_type=QType::NS; + arr.d_ttl=aaaarr.d_ttl=nsrr.d_ttl=time(nullptr)+3600000; + + for(char c='a';c<='m';++c) { + static char templ[40]; + strncpy(templ,"a.root-servers.net.", sizeof(templ) - 1); + templ[sizeof(templ)-1] = '\0'; + *templ=c; + aaaarr.d_name=arr.d_name=DNSName(templ); + nsrr.d_content=std::make_shared(DNSName(templ)); + arr.d_content=std::make_shared(ComboAddress(rootIps4[c-'a'])); + vector aset; + aset.push_back(arr); + t_RC->replace(time(0), DNSName(templ), QType(QType::A), aset, vector>(), vector>(), true); // auth, nuke it all + if (rootIps6[c-'a'] != NULL) { + aaaarr.d_content=std::make_shared(ComboAddress(rootIps6[c-'a'])); + + vector aaaaset; + aaaaset.push_back(aaaarr); + t_RC->replace(time(0), DNSName(templ), QType(QType::AAAA), aaaaset, vector>(), vector>(), true); + } + + nsset.push_back(nsrr); + } + t_RC->replace(time(0), g_rootdnsname, QType(QType::NS), nsset, vector>(), vector>(), false); // and stuff in the cache +} + +LuaConfigItems::LuaConfigItems() +{ + for (const auto &dsRecord : rootDSs) { + auto ds=unique_ptr(dynamic_cast(DSRecordContent::make(dsRecord))); + dsAnchors[g_rootdnsname].insert(*ds); + } +} + +/* Some helpers functions */ + +static void init(bool debug=false) +{ + if (debug) { + L.setName("test"); + L.setLoglevel((Logger::Urgency)(6)); // info and up + L.disableSyslog(true); + L.toConsole(Logger::Info); + } + + seedRandom("/dev/urandom"); + reportAllTypes(); + + t_RC = std::unique_ptr(new MemRecursorCache()); + + SyncRes::s_maxqperq = 50; + SyncRes::s_maxtotusec = 1000*7000; + SyncRes::s_maxdepth = 40; + SyncRes::s_maxnegttl = 3600; + SyncRes::s_maxcachettl = 86400; + SyncRes::s_packetcachettl = 3600; + SyncRes::s_packetcacheservfailttl = 60; + SyncRes::s_serverdownmaxfails = 64; + SyncRes::s_serverdownthrottletime = 60; + SyncRes::s_doIPv6 = true; + SyncRes::s_ecsipv4limit = 24; + SyncRes::s_ecsipv6limit = 56; + SyncRes::s_rootNXTrust = true; + SyncRes::s_minimumTTL = 0; + SyncRes::s_serverID = "PowerDNS Unit Tests Server ID"; + SyncRes::clearEDNSSubnets(); + SyncRes::clearEDNSDomains(); + SyncRes::clearDelegationOnly(); + SyncRes::clearDontQuery(); + + SyncRes::clearNSSpeeds(); + BOOST_CHECK_EQUAL(SyncRes::getNSSpeedsSize(), 0); + SyncRes::clearEDNSStatuses(); + BOOST_CHECK_EQUAL(SyncRes::getEDNSStatusesSize(), 0); + SyncRes::clearThrottle(); + BOOST_CHECK_EQUAL(SyncRes::getThrottledServersSize(), 0); + SyncRes::clearFailedServers(); + BOOST_CHECK_EQUAL(SyncRes::getFailedServersSize(), 0); + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dfe.clear(); + luaconfsCopy.dsAnchors.clear(); + for (const auto &dsRecord : rootDSs) { + auto ds=unique_ptr(dynamic_cast(DSRecordContent::make(dsRecord))); + luaconfsCopy.dsAnchors[g_rootdnsname].insert(*ds); + } + luaconfsCopy.negAnchors.clear(); + g_luaconfs.setState(luaconfsCopy); + + g_dnssecmode = DNSSECMode::Off; + g_dnssecLOG = debug; + + ::arg().set("version-string", "string reported on version.pdns or version.bind")="PowerDNS Unit Tests"; +} + +static void initSR(std::unique_ptr& sr, bool dnssec=false, bool debug=false, time_t fakeNow=0) +{ + struct timeval now; + if (fakeNow > 0) { + now.tv_sec = fakeNow; + now.tv_usec = 0; + } + else { + Utility::gettimeofday(&now, 0); + } + + init(debug); + + sr = std::unique_ptr(new SyncRes(now)); + sr->setDoEDNS0(true); + sr->setDoDNSSEC(dnssec); + sr->setLogMode(debug == false ? SyncRes::LogNone : SyncRes::Log); + + SyncRes::setDomainMap(std::make_shared()); + SyncRes::clearNegCache(); +} + +static void setLWResult(LWResult* res, int rcode, bool aa=false, bool tc=false, bool edns=false) +{ + res->d_rcode = rcode; + res->d_aabit = aa; + res->d_tcbit = tc; + res->d_haveEDNS = edns; +} + +static void addRecordToLW(LWResult* res, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60) +{ + addRecordToList(res->d_records, name, type, content, place, ttl); +} + +static void addRecordToLW(LWResult* res, const std::string& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=60) +{ + addRecordToLW(res, DNSName(name), type, content, place, ttl); +} + +static bool isRootServer(const ComboAddress& ip) +{ + if (ip.isIPv4()) { + for (size_t idx = 0; idx < rootIps4Count; idx++) { + if (ip.toString() == rootIps4[idx]) { + return true; + } + } + } + else { + for (size_t idx = 0; idx < rootIps6Count; idx++) { + if (ip.toString() == rootIps6[idx]) { + return true; + } + } + } + + return false; +} + +static void computeRRSIG(const DNSSECPrivateKey& dpk, const DNSName& signer, const DNSName& signQName, uint16_t signQType, uint32_t signTTL, uint32_t sigValidity, RRSIGRecordContent& rrc, vector >& toSign, boost::optional algo=boost::none) +{ + time_t now = time(nullptr); + DNSKEYRecordContent drc = dpk.getDNSKEY(); + const std::shared_ptr rc = dpk.getKey(); + + rrc.d_type = signQType; + rrc.d_labels = signQName.countLabels() - signQName.isWildcard(); + rrc.d_originalttl = signTTL; + rrc.d_siginception = now - 10; + rrc.d_sigexpire = now + sigValidity; + rrc.d_signer = signer; + rrc.d_tag = 0; + rrc.d_tag = drc.getTag(); + rrc.d_algorithm = algo ? *algo : drc.d_algorithm; + + std::string msg = getMessageForRRSET(signQName, rrc, toSign); + + rrc.d_signature = rc->sign(msg); +} + +typedef std::unordered_map > testkeysset_t; + +static void addRRSIG(const testkeysset_t& keys, std::vector& records, const DNSName& signer, uint32_t sigValidity, bool broken=false, boost::optional algo=boost::none, boost::optional wildcard=boost::none) +{ + if (records.empty()) { + return; + } + + const auto it = keys.find(signer); + if (it == keys.cend()) { + throw std::runtime_error("No DNSKEY found for " + signer.toString() + ", unable to compute the requested RRSIG"); + } + + size_t recordsCount = records.size(); + const DNSName& name = records[recordsCount-1].d_name; + const uint16_t type = records[recordsCount-1].d_type; + + std::vector > recordcontents; + for (const auto record : records) { + if (record.d_name == name && record.d_type == type) { + recordcontents.push_back(record.d_content); + } + } + + RRSIGRecordContent rrc; + computeRRSIG(it->second.first, signer, wildcard ? *wildcard : records[recordsCount-1].d_name, records[recordsCount-1].d_type, records[recordsCount-1].d_ttl, sigValidity, rrc, recordcontents, algo); + if (broken) { + rrc.d_signature[0] ^= 42; + } + + DNSRecord rec; + rec.d_place = records[recordsCount-1].d_place; + rec.d_name = records[recordsCount-1].d_name; + rec.d_type = QType::RRSIG; + rec.d_ttl = sigValidity; + + rec.d_content = std::make_shared(rrc); + records.push_back(rec); +} + +static void addDNSKEY(const testkeysset_t& keys, const DNSName& signer, uint32_t ttl, std::vector& records) +{ + const auto it = keys.find(signer); + if (it == keys.cend()) { + throw std::runtime_error("No DNSKEY found for " + signer.toString()); + } + + DNSRecord rec; + rec.d_place = DNSResourceRecord::ANSWER; + rec.d_name = signer; + rec.d_type = QType::DNSKEY; + rec.d_ttl = ttl; + + rec.d_content = std::make_shared(it->second.first.getDNSKEY()); + records.push_back(rec); +} + +static void addDS(const DNSName& domain, uint32_t ttl, std::vector& records, const testkeysset_t& keys, DNSResourceRecord::Place place=DNSResourceRecord::AUTHORITY) +{ + const auto it = keys.find(domain); + if (it == keys.cend()) { + return; + } + + DNSRecord rec; + rec.d_name = domain; + rec.d_type = QType::DS; + rec.d_place = place; + rec.d_ttl = ttl; + rec.d_content = std::make_shared(it->second.second); + + records.push_back(rec); +} + +static void addNSECRecordToLW(const DNSName& domain, const DNSName& next, const std::set& types, uint32_t ttl, std::vector& records) +{ + NSECRecordContent nrc; + nrc.d_next = next; + nrc.d_set = types; + + DNSRecord rec; + rec.d_name = domain; + rec.d_ttl = ttl; + rec.d_type = QType::NSEC; + rec.d_content = std::make_shared(nrc); + rec.d_place = DNSResourceRecord::AUTHORITY; + + records.push_back(rec); +} + +static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys) +{ + auto dcke = std::shared_ptr(DNSCryptoKeyEngine::make(algo)); + dcke->create((algo <= 10) ? 2048 : dcke->getBits()); + DNSSECPrivateKey dpk; + dpk.d_flags = 256; + dpk.setKey(dcke); + DSRecordContent ds = makeDSFromDNSKey(name, dpk.getDNSKEY(), digest); + keys[name] = std::pair(dpk,ds); +} + +static void generateKeyMaterial(const DNSName& name, unsigned int algo, uint8_t digest, testkeysset_t& keys, map& dsAnchors) +{ + generateKeyMaterial(name, algo, digest, keys); + dsAnchors[name].insert(keys[name].second); +} + +/* Real tests */ + +BOOST_AUTO_TEST_SUITE(syncres_cc) + +BOOST_AUTO_TEST_CASE(test_root_primed) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("a.root-servers.net."); + + /* we are primed, we should be able to resolve A a.root-servers.net. without any query */ + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + + ret.clear(); + res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Indeterminate); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::AAAA); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_root_primed_ns) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + const DNSName target("."); + + /* we are primed, but we should not be able to NS . without any query + because the . NS entry is not stored as authoritative */ + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, g_rootdnsname, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 13); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_root_not_primed) { + std::unique_ptr sr; + initSR(sr); + + size_t queriesCount = 0; + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == g_rootdnsname && type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, g_rootdnsname, QType::NS, "a.root-servers.net.", DNSResourceRecord::ANSWER, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } + + return 0; + }); + + /* we are not primed yet, so SyncRes will have to call primeHints() + then call getRootNS(), for which at least one of the root servers needs to answer */ + vector ret; + int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_root_not_primed_and_no_response) { + std::unique_ptr sr; + initSR(sr); + std::set downServers; + + /* we are not primed yet, so SyncRes will have to call primeHints() + then call getRootNS(), for which at least one of the root servers needs to answer. + None will, so it should ServFail. + */ + sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + downServers.insert(ip); + return 0; + }); + + vector ret; + int res = sr->beginResolve(DNSName("."), QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK(downServers.size() > 0); + /* we explicitly refuse to mark the root servers down */ + for (const auto& server : downServers) { + BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 0); + } +} + +BOOST_AUTO_TEST_CASE(test_edns_formerr_fallback) { + std::unique_ptr sr; + initSR(sr); + + ComboAddress noEDNSServer; + size_t queriesWithEDNS = 0; + size_t queriesWithoutEDNS = 0; + + sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS, &noEDNSServer](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + if (EDNS0Level != 0) { + queriesWithEDNS++; + noEDNSServer = ip; + + setLWResult(res, RCode::FormErr); + return 1; + } + + queriesWithoutEDNS++; + + if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.1"); + return 1; + } + + return 0; + }); + + primeHints(); + + /* fake that the root NS doesn't handle EDNS, check that we fallback */ + vector ret; + int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesWithEDNS, 1); + BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1); + BOOST_CHECK_EQUAL(SyncRes::getEDNSStatusesSize(), 1); + BOOST_CHECK_EQUAL(SyncRes::getEDNSStatus(noEDNSServer), SyncRes::EDNSStatus::NOEDNS); +} + +BOOST_AUTO_TEST_CASE(test_edns_notimp_fallback) { + std::unique_ptr sr; + initSR(sr); + + size_t queriesWithEDNS = 0; + size_t queriesWithoutEDNS = 0; + + sr->setAsyncCallback([&queriesWithEDNS, &queriesWithoutEDNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + if (EDNS0Level != 0) { + queriesWithEDNS++; + setLWResult(res, RCode::NotImp); + return 1; + } + + queriesWithoutEDNS++; + + if (domain == DNSName("powerdns.com") && type == QType::A && !doTCP) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.1"); + return 1; + } + + return 0; + }); + + primeHints(); + + /* fake that the NS doesn't handle EDNS, check that we fallback */ + vector ret; + int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesWithEDNS, 1); + BOOST_CHECK_EQUAL(queriesWithoutEDNS, 1); +} + +BOOST_AUTO_TEST_CASE(test_tc_fallback_to_tcp) { + std::unique_ptr sr; + initSR(sr); + + sr->setAsyncCallback([](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + if (!doTCP) { + setLWResult(res, 0, false, true, false); + return 1; + } + if (domain == DNSName("powerdns.com") && type == QType::A && doTCP) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.1"); + return 1; + } + + return 0; + }); + + primeHints(); + + /* fake that the NS truncates every request over UDP, we should fallback to TCP */ + vector ret; + int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); +} + +BOOST_AUTO_TEST_CASE(test_tc_over_tcp) { + std::unique_ptr sr; + initSR(sr); + + size_t tcpQueriesCount = 0; + + sr->setAsyncCallback([&tcpQueriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + if (!doTCP) { + setLWResult(res, 0, true, true, false); + return 1; + } + + /* first TCP query is answered with a TC response */ + tcpQueriesCount++; + if (tcpQueriesCount == 1) { + setLWResult(res, 0, true, true, false); + } + else { + setLWResult(res, 0, true, false, false); + } + + addRecordToLW(res, domain, QType::A, "192.0.2.1"); + return 1; + }); + + primeHints(); + + vector ret; + int res = sr->beginResolve(DNSName("powerdns.com."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(tcpQueriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_all_nss_down) { + std::unique_ptr sr; + initSR(sr); + std::set downServers; + + primeHints(); + + sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800); + return 1; + } + else { + downServers.insert(ip); + return 0; + } + }); + + DNSName target("powerdns.com."); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(downServers.size(), 4); + + for (const auto& server : downServers) { + BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 1); + BOOST_CHECK(SyncRes::isThrottled(time(nullptr), server, target, QType::A)); + } +} + +BOOST_AUTO_TEST_CASE(test_all_nss_network_error) { + std::unique_ptr sr; + initSR(sr); + std::set downServers; + + primeHints(); + + sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800); + return 1; + } + else { + downServers.insert(ip); + return -1; + } + }); + + /* exact same test than the previous one, except instead of a time out we fake a network error */ + DNSName target("powerdns.com."); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(downServers.size(), 4); + + for (const auto& server : downServers) { + BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 1); + BOOST_CHECK(SyncRes::isThrottled(time(nullptr), server, target, QType::A)); +; + } +} + +BOOST_AUTO_TEST_CASE(test_os_limit_errors) { + std::unique_ptr sr; + initSR(sr); + std::set downServers; + + primeHints(); + + sr->setAsyncCallback([&downServers](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800); + return 1; + } + else { + if (downServers.size() < 3) { + /* only the last one will answer */ + downServers.insert(ip); + return -2; + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, "powerdns.com.", QType::A, "192.0.2.42"); + return 1; + } + } + }); + + DNSName target("powerdns.com."); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(downServers.size(), 3); + + /* Error is reported as "OS limit error" (-2) so the servers should _NOT_ be marked down */ + for (const auto& server : downServers) { + BOOST_CHECK_EQUAL(SyncRes::getServerFailsCount(server), 0); + BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), server, target, QType::A)); + } +} + +BOOST_AUTO_TEST_CASE(test_glued_referral) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + /* this will cause issue with qname minimization if we ever implement it */ + if (domain != target) { + return 0; + } + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 172800); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 172800); + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, target, QType::A, "192.0.2.4"); + return 1; + } + else { + return 0; + } + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_glueless_referral) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + + if (domain.isPartOf(DNSName("com."))) { + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + } else if (domain.isPartOf(DNSName("org."))) { + addRecordToLW(res, "org.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + } + else { + setLWResult(res, RCode::NXDomain, false, false, true); + return 1; + } + + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53") || ip == ComboAddress("[2001:DB8::1]:53")) { + if (domain == target) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + return 1; + } + else if (domain == DNSName("pdns-public-ns1.powerdns.org.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::A, "192.0.2.2"); + addRecordToLW(res, "pdns-public-ns1.powerdns.org.", QType::AAAA, "2001:DB8::2"); + return 1; + } + else if (domain == DNSName("pdns-public-ns2.powerdns.org.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::A, "192.0.2.3"); + addRecordToLW(res, "pdns-public-ns2.powerdns.org.", QType::AAAA, "2001:DB8::3"); + return 1; + } + + setLWResult(res, RCode::NXDomain, false, false, true); + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53") || ip == ComboAddress("192.0.2.3:53") || ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("[2001:DB8::3]:53")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, target, QType::A, "192.0.2.4"); + return 1; + } + else { + return 0; + } + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_edns_submask_by_domain) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSDomain(target); + + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("192.0.2.128/32"); + sr->setIncomingECSFound(true); + sr->setIncomingECS(incomingECS); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24"); + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); +} + +BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSSubnet(Netmask("192.0.2.1/32")); + + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("2001:DB8::FF/128"); + sr->setIncomingECSFound(true); + sr->setIncomingECS(incomingECS); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + BOOST_REQUIRE(!srcmask); + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "2001:db8::/56"); + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_following_cname) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("cname.powerdns.com."); + const DNSName cnameTarget("cname-target.powerdns.com"); + + sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + if (domain == target) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString()); + return 1; + } + else if (domain == cnameTarget) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + } + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget); +} + +BOOST_AUTO_TEST_CASE(test_included_poisonous_cname) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + /* In this test we directly get the NS server for cname.powerdns.com., + and we don't know whether it's also authoritative for + cname-target.powerdns.com or powerdns.com, so we shouldn't accept + the additional A record for cname-target.powerdns.com. */ + const DNSName target("cname.powerdns.com."); + const DNSName cnameTarget("cname-target.powerdns.com"); + + sr->setAsyncCallback([target, cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + if (domain == target) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString()); + addRecordToLW(res, cnameTarget, QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL); + return 1; + } else if (domain == cnameTarget) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, cnameTarget, QType::A, "192.0.2.3"); + return 1; + } + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_REQUIRE(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(ret[0].d_name, target); + BOOST_CHECK_EQUAL(getRR(ret[0])->getTarget(), cnameTarget); + BOOST_REQUIRE(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[1].d_name, cnameTarget); + BOOST_CHECK(getRR(ret[1])->getCA() == ComboAddress("192.0.2.3")); +} + +BOOST_AUTO_TEST_CASE(test_cname_loop) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t count = 0; + const DNSName target("cname.powerdns.com."); + + sr->setAsyncCallback([target,&count](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + count++; + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + if (domain == target) { + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::CNAME, domain.toString()); + return 1; + } + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_GT(ret.size(), 0); + BOOST_CHECK_EQUAL(count, 2); +} + +BOOST_AUTO_TEST_CASE(test_cname_depth) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t depth = 0; + const DNSName target("cname.powerdns.com."); + + sr->setAsyncCallback([target,&depth](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::CNAME, std::to_string(depth) + "-cname.powerdns.com"); + depth++; + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), depth); + /* we have an arbitrary limit at 10 when following a CNAME chain */ + BOOST_CHECK_EQUAL(depth, 10 + 2); +} + +BOOST_AUTO_TEST_CASE(test_time_limit) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queries = 0; + const DNSName target("cname.powerdns.com."); + + sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queries++; + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + /* Pretend that this query took 2000 ms */ + res->d_usec = 2000; + + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + /* Set the maximum time to 1 ms */ + SyncRes::s_maxtotusec = 1000; + + try { + vector ret; + sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK(false); + } + catch(const ImmediateServFailException& e) { + } + BOOST_CHECK_EQUAL(queries, 1); +} + +BOOST_AUTO_TEST_CASE(test_referral_depth) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queries = 0; + const DNSName target("www.powerdns.com."); + + sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queries++; + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + + if (domain == DNSName("www.powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + } + else if (domain == DNSName("ns.powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + } + else if (domain == DNSName("ns1.powerdns.org.")) { + addRecordToLW(res, domain, QType::NS, "ns2.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + } + else if (domain == DNSName("ns2.powerdns.org.")) { + addRecordToLW(res, domain, QType::NS, "ns3.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + } + else if (domain == DNSName("ns3.powerdns.org.")) { + addRecordToLW(res, domain, QType::NS, "ns4.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + } + else if (domain == DNSName("ns4.powerdns.org.")) { + addRecordToLW(res, domain, QType::NS, "ns5.powerdns.org.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, domain, QType::A, "192.0.2.1", DNSResourceRecord::AUTHORITY, 172800); + } + + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + /* Set the maximum depth low */ + SyncRes::s_maxdepth = 10; + + try { + vector ret; + sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK(false); + } + catch(const ImmediateServFailException& e) { + } +} + +BOOST_AUTO_TEST_CASE(test_cname_qperq) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queries = 0; + const DNSName target("cname.powerdns.com."); + + sr->setAsyncCallback([target,&queries](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queries++; + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::CNAME, std::to_string(queries) + "-cname.powerdns.com"); + return 1; + } + + return 0; + }); + + /* Set the maximum number of questions very low */ + SyncRes::s_maxqperq = 5; + + try { + vector ret; + sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK(false); + } + catch(const ImmediateServFailException& e) { + BOOST_CHECK_EQUAL(queries, SyncRes::s_maxqperq); + } +} + +BOOST_AUTO_TEST_CASE(test_throttled_server) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("throttled.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + size_t queriesToNS = 0; + + sr->setAsyncCallback([target,ns,&queriesToNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + queriesToNS++; + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + + return 1; + } + + return 0; + }); + + /* mark ns as down */ + SyncRes::doThrottle(time(nullptr), ns, SyncRes::s_serverdownthrottletime, 10000); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + /* we should not have sent any queries to ns */ + BOOST_CHECK_EQUAL(queriesToNS, 0); +} + +BOOST_AUTO_TEST_CASE(test_throttled_server_count) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const ComboAddress ns("192.0.2.1:53"); + + const size_t blocks = 10; + /* mark ns as down for 'blocks' queries */ + SyncRes::doThrottle(time(nullptr), ns, SyncRes::s_serverdownthrottletime, blocks); + + for (size_t idx = 0; idx < blocks; idx++) { + BOOST_CHECK(SyncRes::isThrottled(time(nullptr), ns)); + } + + /* we have been throttled 'blocks' times, we should not be throttled anymore */ + BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), ns)); +} + +BOOST_AUTO_TEST_CASE(test_throttled_server_time) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const ComboAddress ns("192.0.2.1:53"); + + const size_t seconds = 1; + /* mark ns as down for 'seconds' seconds */ + SyncRes::doThrottle(time(nullptr), ns, seconds, 10000); + + BOOST_CHECK(SyncRes::isThrottled(time(nullptr), ns)); + + sleep(seconds + 1); + + /* we should not be throttled anymore */ + BOOST_CHECK(!SyncRes::isThrottled(time(nullptr), ns)); +} + +BOOST_AUTO_TEST_CASE(test_dont_query_server) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("throttled.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + size_t queriesToNS = 0; + + sr->setAsyncCallback([target,ns,&queriesToNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + queriesToNS++; + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + + return 1; + } + + return 0; + }); + + /* prevent querying this NS */ + SyncRes::addDontQuery(Netmask(ns)); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + /* we should not have sent any queries to ns */ + BOOST_CHECK_EQUAL(queriesToNS, 0); +} + +BOOST_AUTO_TEST_CASE(test_root_nx_trust) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target1("powerdns.com."); + const DNSName target2("notpowerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + size_t queriesCount = 0; + + sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (isRootServer(ip)) { + + if (domain == target1) { + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + } + + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 1); + /* one for target1 and one for the entire TLD */ + BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2); + + ret.clear(); + res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 1); + /* one for target1 and one for the entire TLD */ + BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 2); + + /* we should have sent only one query */ + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_root_nx_trust_specific) { + std::unique_ptr sr; + init(); + initSR(sr, true, false); + + primeHints(); + + const DNSName target1("powerdns.com."); + const DNSName target2("notpowerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + size_t queriesCount = 0; + + /* This time the root denies target1 with a "com." SOA instead of a "." one. + We should add target1 to the negcache, but not "com.". */ + + sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (isRootServer(ip)) { + + if (domain == target1) { + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, "com.", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + } + + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 1); + + /* even with root-nx-trust on and a NX answer from the root, + we should not have cached the entire TLD this time. */ + BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1); + + ret.clear(); + res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_REQUIRE(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target2); + BOOST_CHECK(getRR(ret[0])->getCA() == ComboAddress("192.0.2.2")); + + BOOST_CHECK_EQUAL(SyncRes::t_sstorage.negcache.size(), 1); + + BOOST_CHECK_EQUAL(queriesCount, 3); +} + +BOOST_AUTO_TEST_CASE(test_root_nx_dont_trust) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target1("powerdns.com."); + const DNSName target2("notpowerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + size_t queriesCount = 0; + + sr->setAsyncCallback([target1, target2, ns, &queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (isRootServer(ip)) { + + if (domain == target1) { + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + } + + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + + return 1; + } + + return 0; + }); + + SyncRes::s_rootNXTrust = false; + + vector ret; + int res = sr->beginResolve(target1, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 1); + /* one for target1 */ + BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1); + + ret.clear(); + res = sr->beginResolve(target2, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + /* one for target1 */ + BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 1); + + /* we should have sent three queries */ + BOOST_CHECK_EQUAL(queriesCount, 3); +} + +BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("www.powerdns.com."); + const DNSName cnameTarget("cname.powerdns.com."); + + SyncRes::addEDNSDomain(DNSName("powerdns.com.")); + + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("192.0.2.128/32"); + sr->setIncomingECSFound(true); + sr->setIncomingECS(incomingECS); + + sr->setAsyncCallback([target,cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24"); + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == target) { + /* Type 2 NXDOMAIN (rfc2308 section-2.1) */ + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, domain, QType::CNAME, cnameTarget.toString()); + addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + else if (domain == cnameTarget) { + /* we shouldn't get there since the Type NXDOMAIN should have been enough, + but we might if we still chase the CNAME. */ + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, "powerdns.com", QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 2); + /* no negative cache entry because the response was variable */ + BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 0); +} + +BOOST_AUTO_TEST_CASE(test_ns_speed) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + std::map nsCounts; + + sr->setAsyncCallback([target,&nsCounts](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::AAAA, "2001:DB8::1", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "pdns-public-ns2.powerdns.com.", QType::AAAA, "2001:DB8::2", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::A, "192.0.2.3", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "pdns-public-ns3.powerdns.com.", QType::AAAA, "2001:DB8::3", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else { + nsCounts[ip]++; + + if (ip == ComboAddress("[2001:DB8::2]:53") || ip == ComboAddress("192.0.2.2:53")) { + BOOST_CHECK_LT(nsCounts.size(), 3); + + /* let's time out on pdns-public-ns2.powerdns.com. */ + return 0; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + BOOST_CHECK_EQUAL(nsCounts.size(), 3); + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.254"); + return 1; + } + + return 0; + } + + return 0; + }); + + struct timeval now; + gettimeofday(&now, 0); + + /* make pdns-public-ns2.powerdns.com. the fastest NS, with its IPv6 address faster than the IPV4 one, + then pdns-public-ns1.powerdns.com. on IPv4 */ + SyncRes::submitNSSpeed(DNSName("pdns-public-ns1.powerdns.com."), ComboAddress("192.0.2.1:53"), 100, &now); + SyncRes::submitNSSpeed(DNSName("pdns-public-ns1.powerdns.com."), ComboAddress("[2001:DB8::1]:53"), 10000, &now); + SyncRes::submitNSSpeed(DNSName("pdns-public-ns2.powerdns.com."), ComboAddress("192.0.2.2:53"), 10, &now); + SyncRes::submitNSSpeed(DNSName("pdns-public-ns2.powerdns.com."), ComboAddress("[2001:DB8::2]:53"), 1, &now); + SyncRes::submitNSSpeed(DNSName("pdns-public-ns3.powerdns.com."), ComboAddress("192.0.2.3:53"), 10000, &now); + SyncRes::submitNSSpeed(DNSName("pdns-public-ns3.powerdns.com."), ComboAddress("[2001:DB8::3]:53"), 10000, &now); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(nsCounts.size(), 3); + BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.1:53")], 1); + BOOST_CHECK_EQUAL(nsCounts[ComboAddress("192.0.2.2:53")], 1); + BOOST_CHECK_EQUAL(nsCounts[ComboAddress("[2001:DB8::2]:53")], 1); +} + +BOOST_AUTO_TEST_CASE(test_flawed_nsset) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.254"); + return 1; + } + + return 0; + }); + + /* we populate the cache with a flawed NSset, i.e. there is a NS entry but no corresponding glue */ + time_t now = time(nullptr); + std::vector records; + std::vector > sigs; + addRecordToList(records, target, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, now + 3600); + + t_RC->replace(now, target, QType(QType::NS), records, sigs, vector>(), true, boost::optional()); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_completely_flawed_nsset) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + size_t queriesCount = 0; + + sr->setAsyncCallback([&queriesCount,target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (isRootServer(ip) && domain == target) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns2.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns3.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + return 1; + } else if (domain == DNSName("pdns-public-ns2.powerdns.com.") || domain == DNSName("pdns-public-ns3.powerdns.com.")){ + setLWResult(res, 0, true, false, true); + addRecordToLW(res, ".", QType::SOA, "a.root-servers.net. nstld.verisign-grs.com. 2017032800 1800 900 604800 86400", DNSResourceRecord::AUTHORITY, 86400); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); + /* one query to get NSs, then A and AAAA for each NS */ + BOOST_CHECK_EQUAL(queriesCount, 5); +} + +BOOST_AUTO_TEST_CASE(test_cache_hit) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + return 0; + }); + + /* we populate the cache with eveything we need */ + time_t now = time(nullptr); + std::vector records; + std::vector > sigs; + + addRecordToList(records, target, QType::A, "192.0.2.1", DNSResourceRecord::ANSWER, now + 3600); + t_RC->replace(now, target , QType(QType::A), records, sigs, vector>(), true, boost::optional()); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_no_rd) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + size_t queriesCount = 0; + + sr->setCacheOnly(); + + sr->setAsyncCallback([target,&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl) { + std::unique_ptr sr; + const time_t now = time(nullptr); + initSR(sr); + + primeHints(); + + const DNSName target("cachettl.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + + sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 7200); + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10); + + return 1; + } + + return 0; + }); + + SyncRes::s_minimumTTL = 60; + SyncRes::s_maxcachettl = 3600; + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(ret[0].d_ttl, SyncRes::s_minimumTTL); + + const ComboAddress who; + vector cached; + BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0); + BOOST_REQUIRE_EQUAL(cached.size(), 1); + BOOST_REQUIRE_GT(cached[0].d_ttl, now); + BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumTTL); + + cached.clear(); + BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::NS), false, &cached, who), 0); + BOOST_REQUIRE_EQUAL(cached.size(), 1); + BOOST_REQUIRE_GT(cached[0].d_ttl, now); + BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl); +} + +BOOST_AUTO_TEST_CASE(test_cache_expired_ttl) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800); + + addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + /* we populate the cache with entries that expired 60s ago*/ + time_t now = time(nullptr); + std::vector records; + std::vector > sigs; + addRecordToList(records, target, QType::A, "192.0.2.42", DNSResourceRecord::ANSWER, now - 60); + + t_RC->replace(now - 3600, target, QType(QType::A), records, sigs, vector>(), true, boost::optional()); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_REQUIRE(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[0])->getCA().toStringWithPort(), ComboAddress("192.0.2.2").toStringWithPort()); +} + +BOOST_AUTO_TEST_CASE(test_delegation_only) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + /* Thanks, Verisign */ + SyncRes::addDelegationOnly(DNSName("com.")); + SyncRes::addDelegationOnly(DNSName("net.")); + + const DNSName target("nx-powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_unauth_any) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::ANY), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::ServFail); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_no_data) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, 0, true, false, true); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_skip_opt_any) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + addRecordToLW(res, domain, QType::ANY, "0 0"); + addRecordToLW(res, domain, QType::OPT, ""); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_nodata_nsec_nodnssec) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + /* the NSEC and RRSIG contents are complete garbage, please ignore them */ + addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 2 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_nodata_nsec_dnssec) { + std::unique_ptr sr; + initSR(sr, true); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + /* the NSEC and RRSIG contents are complete garbage, please ignore them */ + addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 2 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 4); +} + +BOOST_AUTO_TEST_CASE(test_nx_nsec_nodnssec) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + /* the NSEC and RRSIG contents are complete garbage, please ignore them */ + addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 2 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_nx_nsec_dnssec) { + std::unique_ptr sr; + initSR(sr, true); + + primeHints(); + + const DNSName target("powerdns.com."); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + /* the NSEC and RRSIG contents are complete garbage, please ignore them */ + addRecordToLW(res, domain, QType::NSEC, "deadbeef", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "NSEC 5 2 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + addRecordToLW(res, domain, QType::RRSIG, "SOA 5 3 600 2100010100000000 2100010100000000 24567 dummy data", DNSResourceRecord::AUTHORITY); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(ret.size(), 4); +} + +BOOST_AUTO_TEST_CASE(test_qclass_none) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + /* apart from special names and QClass::ANY, anything else than QClass::IN should be rejected right away */ + size_t queriesCount = 0; + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + return 0; + }); + + const DNSName target("powerdns.com."); + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::NONE, ret); + BOOST_CHECK_EQUAL(res, -1); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_xfr) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + /* {A,I}XFR should be rejected right away */ + size_t queriesCount = 0; + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + cerr<<"asyncresolve called to ask "< ret; + int res = sr->beginResolve(target, QType(QType::AXFR), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, -1); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(queriesCount, 0); + + res = sr->beginResolve(target, QType(QType::IXFR), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, -1); + BOOST_CHECK_EQUAL(ret.size(), 0); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_special_names) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + /* special names should be handled internally */ + + size_t queriesCount = 0; + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + return 0; + }); + + vector ret; + int res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::PTR), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::PTR); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("1.0.0.127.in-addr.arpa."), QType(QType::ANY), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::PTR); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::PTR), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::PTR); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa."), QType(QType::ANY), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::PTR); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("localhost."), QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[0])->getCA().toString(), "127.0.0.1"); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("localhost."), QType(QType::AAAA), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::AAAA); + BOOST_CHECK_EQUAL(getRR(ret[0])->getCA().toString(), "::1"); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("localhost."), QType(QType::ANY), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + for (const auto& rec : ret) { + BOOST_REQUIRE((rec.d_type == QType::A) || rec.d_type == QType::AAAA); + if (rec.d_type == QType::A) { + BOOST_CHECK_EQUAL(getRR(rec)->getCA().toString(), "127.0.0.1"); + } + else { + BOOST_CHECK_EQUAL(getRR(rec)->getCA().toString(), "::1"); + } + } + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("version.bind."), QType(QType::TXT), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests\""); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("version.bind."), QType(QType::ANY), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests\""); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("version.pdns."), QType(QType::TXT), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests\""); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("version.pdns."), QType(QType::ANY), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests\""); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("id.server."), QType(QType::TXT), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\""); + BOOST_CHECK_EQUAL(queriesCount, 0); + + ret.clear(); + res = sr->beginResolve(DNSName("id.server."), QType(QType::ANY), QClass::CHAOS, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::TXT); + BOOST_CHECK_EQUAL(getRR(ret[0])->d_text, "\"PowerDNS Unit Tests Server ID\""); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_nameserver_ipv4_rpz) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("rpz.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + + sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, false, true, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + DNSFilterEngine::Policy pol; + pol.d_kind = DNSFilterEngine::PolicyKind::Drop; + std::shared_ptr zone = std::make_shared(); + zone->setName("Unit test policy 0"); + zone->addNSIPTrigger(Netmask(ns, 32), pol); + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dfe.addZone(zone); + g_luaconfs.setState(luaconfsCopy); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, -2); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_nameserver_ipv6_rpz) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("rpz.powerdns.com."); + const ComboAddress ns("[2001:DB8::42]:53"); + + sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::AAAA, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + DNSFilterEngine::Policy pol; + pol.d_kind = DNSFilterEngine::PolicyKind::Drop; + std::shared_ptr zone = std::make_shared(); + zone->setName("Unit test policy 0"); + zone->addNSIPTrigger(Netmask(ns, 128), pol); + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dfe.addZone(zone); + g_luaconfs.setState(luaconfsCopy); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, -2); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("rpz.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const DNSName nsName("ns1.powerdns.com."); + + sr->setAsyncCallback([target,ns,nsName](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + DNSFilterEngine::Policy pol; + pol.d_kind = DNSFilterEngine::PolicyKind::Drop; + std::shared_ptr zone = std::make_shared(); + zone->setName("Unit test policy 0"); + zone->addNSTrigger(nsName, pol); + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dfe.addZone(zone); + g_luaconfs.setState(luaconfsCopy); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, -2); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_nameserver_name_rpz_disabled) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("rpz.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const DNSName nsName("ns1.powerdns.com."); + + sr->setAsyncCallback([target,ns,nsName](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, nsName.toString(), DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, nsName, QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + DNSFilterEngine::Policy pol; + pol.d_kind = DNSFilterEngine::PolicyKind::Drop; + std::shared_ptr zone = std::make_shared(); + zone->setName("Unit test policy 0"); + zone->addNSIPTrigger(Netmask(ns, 128), pol); + zone->addNSTrigger(nsName, pol); + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dfe.addZone(zone); + g_luaconfs.setState(luaconfsCopy); + + /* RPZ is disabled for this query, we should not be blocked */ + sr->setWantsRPZ(false); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_forward_zone_nord) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const ComboAddress forwardedNS("192.0.2.42:53"); + + SyncRes::AuthDomain ad; + ad.d_rdForward = false; + ad.d_servers.push_back(forwardedNS); + (*SyncRes::t_sstorage.domainmap)[target] = ad; + + sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (ip == forwardedNS) { + BOOST_CHECK_EQUAL(sendRDQuery, false); + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + /* simulate a no-RD query */ + sr->setCacheOnly(); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_forward_zone_rd) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const ComboAddress forwardedNS("192.0.2.42:53"); + + SyncRes::AuthDomain ad; + ad.d_rdForward = false; + ad.d_servers.push_back(forwardedNS); + (*SyncRes::t_sstorage.domainmap)[target] = ad; + + sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (ip == forwardedNS) { + BOOST_CHECK_EQUAL(sendRDQuery, false); + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_nord) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const ComboAddress forwardedNS("192.0.2.42:53"); + + SyncRes::AuthDomain ad; + ad.d_rdForward = true; + ad.d_servers.push_back(forwardedNS); + (*SyncRes::t_sstorage.domainmap)[target] = ad; + + sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (ip == forwardedNS) { + BOOST_CHECK_EQUAL(sendRDQuery, false); + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + /* simulate a no-RD query */ + sr->setCacheOnly(); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_forward_zone_recurse_rd) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + const ComboAddress forwardedNS("192.0.2.42:53"); + + SyncRes::AuthDomain ad; + ad.d_rdForward = true; + ad.d_servers.push_back(forwardedNS); + (*SyncRes::t_sstorage.domainmap)[target] = ad; + + sr->setAsyncCallback([forwardedNS](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (ip == forwardedNS) { + BOOST_CHECK_EQUAL(sendRDQuery, true); + + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_delegation_oob) { + std::unique_ptr sr; + init(); + initSR(sr, true, false); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("test.xx."); + const ComboAddress targetAddr("127.0.0.1"); + const DNSName ns("localhost."); + const ComboAddress nsAddr("127.0.0.1"); + const DNSName authZone("test.xx"); + + SyncRes::AuthDomain ad; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::NS; + dr.d_ttl = 1800; + dr.d_content = std::make_shared("localhost."); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::A; + dr.d_ttl = 1800; + dr.d_content = std::make_shared(nsAddr); + ad.d_records.insert(dr); + + (*SyncRes::t_sstorage.domainmap)[authZone] = ad; + + sr->setAsyncCallback([&queriesCount,nsAddr,target,targetAddr](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, 0); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 0); + BOOST_CHECK(sr->wasOutOfBand()); + + /* a second time, to check that the OOB flag is set when the query cache is used */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, 0); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 0); + BOOST_CHECK(sr->wasOutOfBand()); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("powerdns.com."); + const ComboAddress addr("192.0.2.5"); + + SyncRes::AuthDomain ad; + ad.d_name = target; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(addr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[target] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[0])->getCA().toString(), addr.toString()); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_cname_lead_to_oob) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("powerdns.com."); + const DNSName authZone("internal.powerdns.com."); + const ComboAddress addr("192.0.2.5"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(addr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount,target,authZone](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (domain == target) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, target, QType::CNAME, authZone.toString(), DNSResourceRecord::ANSWER, 3600); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(getRR(ret[0])->getTarget().toString(), authZone.toString()); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[1])->getCA().toString(), addr.toString()); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_oob_lead_to_outgoing_queryb) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("powerdns.com."); + const DNSName externalCNAME("www.open-xchange.com."); + const ComboAddress addr("192.0.2.5"); + + SyncRes::AuthDomain ad; + ad.d_name = target; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::CNAME; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(externalCNAME); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[target] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount,externalCNAME,addr](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (domain == externalCNAME) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, externalCNAME, QType::A, addr.toString(), DNSResourceRecord::ANSWER, 3600); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::CNAME); + BOOST_CHECK_EQUAL(getRR(ret[0])->getTarget().toString(), externalCNAME.toString()); + BOOST_CHECK(ret[1].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[1])->getCA().toString(), addr.toString()); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_nodata) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("nodata.powerdns.com."); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(ComboAddress("192.0.2.1")); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::SOA); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_nx) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("nx.powerdns.com."); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = DNSName("powerdns.com."); + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::SOA); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_delegation) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("www.test.powerdns.com."); + const ComboAddress targetAddr("192.0.2.2"); + const DNSName ns("ns1.test.powerdns.com."); + const ComboAddress nsAddr("192.0.2.1"); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = DNSName("test.powerdns.com."); + dr.d_type = QType::NS; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(ns); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = ns; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(nsAddr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount,target,targetAddr,nsAddr](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_delegation_point) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("test.powerdns.com."); + const ComboAddress targetAddr("192.0.2.2"); + const DNSName ns("ns1.test.powerdns.com."); + const ComboAddress nsAddr("192.0.2.1"); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = DNSName("test.powerdns.com."); + dr.d_type = QType::NS; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(ns); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = ns; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(nsAddr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount,nsAddr,target,targetAddr](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + if (ip == ComboAddress(nsAddr.toString(), 53) && domain == target) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("test.powerdns.com."); + const ComboAddress targetAddr("192.0.2.2"); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = DNSName("*.powerdns.com."); + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(targetAddr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_wildcard_nodata) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("test.powerdns.com."); + const ComboAddress targetAddr("192.0.2.2"); + const DNSName authZone("powerdns.com"); + + SyncRes::AuthDomain ad; + ad.d_name = authZone; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = authZone; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = DNSName("*.powerdns.com."); + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(targetAddr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[authZone] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::AAAA), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::SOA); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_auth_zone_cache_only) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + size_t queriesCount = 0; + const DNSName target("powerdns.com."); + const ComboAddress addr("192.0.2.5"); + + SyncRes::AuthDomain ad; + ad.d_name = target; + DNSRecord dr; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::SOA; + dr.d_ttl = 3600; + dr.d_content = std::make_shared("pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600"); + ad.d_records.insert(dr); + + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_name = target; + dr.d_type = QType::A; + dr.d_ttl = 3600; + dr.d_content = std::make_shared(addr); + ad.d_records.insert(dr); + + auto map = std::make_shared(); + (*map)[target] = ad; + SyncRes::setDomainMap(map); + + sr->setAsyncCallback([&queriesCount](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + queriesCount++; + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + return 1; + }); + + /* simulate a no-RD query */ + sr->setCacheOnly(); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(getRR(ret[0])->getCA().toString(), addr.toString()); + BOOST_CHECK_EQUAL(queriesCount, 0); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_rrsig) { + init(); + + auto dcke = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dcke->create(dcke->getBits()); + // cerr<convertToISC()< > recordcontents; + recordcontents.push_back(getRecordContent(QType::A, "192.0.2.1")); + + DNSName qname("powerdns.com."); + + RRSIGRecordContent rrc; + computeRRSIG(dpk, qname, qname, QType::A, 600, 300, rrc, recordcontents); + + skeyset_t keyset; + keyset.insert(std::make_shared(dpk.getDNSKEY())); + + std::vector > sigs; + sigs.push_back(std::make_shared(rrc)); + + BOOST_CHECK(validateWithKeySet(time(nullptr), qname, recordcontents, sigs, keyset)); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_csk) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_root_validation_ksk_zsk) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t zskeys; + testkeysset_t kskeys; + + /* Generate key material for "." */ + auto dckeZ = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dckeZ->create(dckeZ->getBits()); + DNSSECPrivateKey ksk; + ksk.d_flags = 257; + ksk.setKey(dckeZ); + DSRecordContent kskds = makeDSFromDNSKey(target, ksk.getDNSKEY(), DNSSECKeeper::SHA256); + + auto dckeK = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dckeK->create(dckeK->getBits()); + DNSSECPrivateKey zsk; + zsk.d_flags = 256; + zsk.setKey(dckeK); + DSRecordContent zskds = makeDSFromDNSKey(target, zsk.getDNSKEY(), DNSSECKeeper::SHA256); + + kskeys[target] = std::pair(ksk, kskds); + zskeys[target] = std::pair(zsk, zskds); + + /* Set the root DS */ + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + luaconfsCopy.dsAnchors[g_rootdnsname].insert(kskds); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,zskeys,kskeys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(zskeys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(kskeys, domain, 300, res->d_records); + addDNSKEY(zskeys, domain, 300, res->d_records); + addRRSIG(kskeys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_dnskey) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + /* No DNSKEY */ + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_dnskey_doesnt_match_ds) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t dskeys; + testkeysset_t keys; + + /* Generate key material for "." */ + auto dckeDS = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dckeDS->create(dckeDS->getBits()); + DNSSECPrivateKey dskey; + dskey.d_flags = 257; + dskey.setKey(dckeDS); + DSRecordContent drc = makeDSFromDNSKey(target, dskey.getDNSKEY(), DNSSECKeeper::SHA256); + + auto dcke = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dcke->create(dcke->getBits()); + DNSSECPrivateKey dpk; + dpk.d_flags = 256; + dpk.setKey(dcke); + DSRecordContent uselessdrc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::SHA256); + + dskeys[target] = std::pair(dskey, drc); + keys[target] = std::pair(dpk, uselessdrc); + + /* Set the root DS */ + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_rrsig_signed_with_unknown_dnskey) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + testkeysset_t rrsigkeys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + g_luaconfs.setState(luaconfsCopy); + + auto dckeRRSIG = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dckeRRSIG->create(dckeRRSIG->getBits()); + DNSSECPrivateKey rrsigkey; + rrsigkey.d_flags = 257; + rrsigkey.setKey(dckeRRSIG); + DSRecordContent rrsigds = makeDSFromDNSKey(target, rrsigkey.getDNSKEY(), DNSSECKeeper::SHA256); + + rrsigkeys[target] = std::pair(rrsigkey, rrsigds); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,keys,rrsigkeys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(rrsigkeys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(rrsigkeys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_no_rrsig) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + /* No RRSIG */ + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 0 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 13); + /* no RRSIG so no query for DNSKEYs */ + BOOST_CHECK_EQUAL(queriesCount, 1); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 13); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_algorithm) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + /* Generate key material for "." */ + auto dcke = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dcke->create(dcke->getBits()); + DNSSECPrivateKey dpk; + dpk.d_flags = 256; + dpk.setKey(dcke); + /* Fake algorithm number (private) */ + dpk.d_algorithm = 253; + + DSRecordContent drc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::SHA256); + keys[target] = std::pair(dpk, drc); + /* Fake algorithm number (private) */ + drc.d_algorithm = 253; + + /* Set the root DS */ + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + /* no supported DS so no query for DNSKEYs */ + BOOST_CHECK_EQUAL(queriesCount, 1); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_unknown_ds_digest) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + /* Generate key material for "." */ + auto dcke = std::shared_ptr(DNSCryptoKeyEngine::make(DNSSECKeeper::ECDSA256)); + dcke->create(dcke->getBits()); + DNSSECPrivateKey dpk; + dpk.d_flags = 256; + dpk.setKey(dcke); + DSRecordContent drc = makeDSFromDNSKey(target, dpk.getDNSKEY(), DNSSECKeeper::SHA256); + /* Fake digest number (reserved) */ + drc.d_digesttype = 0; + + keys[target] = std::pair(dpk, drc); + + /* Set the root DS */ + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + luaconfsCopy.dsAnchors[g_rootdnsname].insert(drc); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + /* no supported DS so no query for DNSKEYs */ + BOOST_CHECK_EQUAL(queriesCount, 1); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_sig) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::SHA384, keys, luaconfsCopy.dsAnchors); + + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300, true); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_bad_algo) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::SHA384, keys, luaconfsCopy.dsAnchors); + + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + /* FORCE WRONG ALGO */ + addRRSIG(keys, res->d_records, domain, 300, false, DNSSECKeeper::RSASHA256); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_various_algos) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::RSASHA512, DNSSECKeeper::SHA384, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA384, DNSSECKeeper::SHA384, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + DNSName auth = domain; + if (domain == target) { + auth = DNSName("powerdns.com."); + } + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, auth, 300, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(auth, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, auth, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + else { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_a_then_ns) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + DNSName auth = domain; + if (domain == target) { + auth = DNSName("powerdns.com."); + } + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, auth, 300, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(auth, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, auth, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + else { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* this time we ask for the NS that should be in the cache, to check + the validation status */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); + +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_a_then_ns) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + DNSName auth = domain; + if (domain == target) { + auth = DNSName("powerdns.com."); + } + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, auth, 300, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + /* no DS */ + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* this time we ask for the NS that should be in the cache, to check + the validation status */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 7); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_with_nta) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + /* Add a NTA for "powerdns.com" */ + luaconfsCopy.negAnchors[target] = "NTA for PowerDNS.com"; + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + DNSName auth = domain; + if (domain == target) { + auth = DNSName("powerdns.com."); + } + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, auth, 300, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(auth, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, auth, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + else { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + addRRSIG(keys, res->d_records, auth, 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + /* Should be insecure because of the NTA */ + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + /* Should be insecure because of the NTA */ + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 7); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_with_nta) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + /* Add a NTA for "powerdns.com" */ + luaconfsCopy.negAnchors[target] = "NTA for PowerDNS.com"; + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS || type == QType::DNSKEY) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, RCode::NoError, true, false, true); + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + } + return 1; + } + } + + return 0; + }); + + /* There is TA for root but no DS/DNSKEY/RRSIG, should be Bogus, but.. */ + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + /* Should be insecure because of the NTA */ + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + /* and a such, no query for the DNSKEYs */ + BOOST_CHECK_EQUAL(queriesCount, 6); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 6); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(domain, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, domain, 300); + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS, QType::DNSKEY }, 600, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 8); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nxdomain_nsec) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("nx.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + DNSName auth = domain; + if (domain == target) { + auth = DNSName("powerdns.com."); + } + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, auth, 300, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, auth, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(auth, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + setLWResult(res, 0, true, false, true); + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(DNSName("nx.powerdns.com."), DNSName("nz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + } + else { + setLWResult(res, RCode::NXDomain, true, false, true); + addRecordToLW(res, DNSName("powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, auth, 300); + addNSECRecordToLW(DNSName("nw.powerdns.com."), DNSName("ny.powerdns.com."), { QType::RRSIG, QType::NSEC }, 600, res->d_records); + addRRSIG(keys, res->d_records, auth, 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 9); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NXDomain); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 9); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_nsec_wildcard) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("www.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + } + else { + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300, false, boost::none, DNSName("*.powerdns.com")); + addNSECRecordToLW(DNSName("a.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 9); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 9); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_secure) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("www.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + size_t dsQueriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,&dsQueriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + DNSName auth(domain); + auth.chopOff(); + dsQueriesCount++; + + setLWResult(res, 0, true, false, true); + addDS(domain, 300, res->d_records, keys, DNSResourceRecord::ANSWER); + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + /* No DS on referral, and no denial of the DS either */ + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + /* No DS on referral, and no denial of the DS either */ + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.powerdns.com."), DNSName("wwz.powerdns.com."), { QType::A, QType::NSEC, QType::RRSIG }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + } + else { + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 11); + BOOST_CHECK_EQUAL(dsQueriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 11); + BOOST_CHECK_EQUAL(dsQueriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_no_ds_on_referral_insecure) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("www.powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + size_t dsQueriesCount = 0; + + sr->setAsyncCallback([target,&queriesCount,&dsQueriesCount,keys](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + DNSName auth(domain); + auth.chopOff(); + dsQueriesCount++; + + setLWResult(res, 0, true, false, true); + if (domain == DNSName("com.")) { + addDS(domain, 300, res->d_records, keys, DNSResourceRecord::ANSWER); + } + else { + addRecordToLW(res, "com.", QType::SOA, "a.gtld-servers.com. hostmastercom. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addNSECRecordToLW(domain, DNSName("powerdnt.com."), { QType::NS }, 600, res->d_records); + } + addRRSIG(keys, res->d_records, auth, 300); + return 1; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + /* No DS on referral, and no denial of the DS either */ + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "powerdns.com.", QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + /* No DS on referral, and no denial of the DS either */ + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + } + else { + addRecordToLW(res, domain, QType::A, "192.0.2.42"); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 10); + BOOST_CHECK_EQUAL(dsQueriesCount, 2); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 10); + BOOST_CHECK_EQUAL(dsQueriesCount, 2); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_unsigned_nsec) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(domain, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, domain, 300); + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS, QType::DNSKEY }, 600, res->d_records); + /* NO RRSIG for the NSEC record! */ + } + return 1; + } + } + + return 0; + }); + + /* NSEC record without the corresponding RRSIG in a secure zone, should be Bogus! */ + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_CHECK_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 8); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_validation_bogus_no_nsec) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(domain, 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, domain, 300); + + /* NO NSEC record! */ + } + return 1; + } + } + + return 0; + }); + + /* no NSEC record in a secure zone, should be Bogus! */ + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_CHECK_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 8); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == target) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + /* no DS */ + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + } + else { + addRecordToLW(res, domain, QType::A, targetAddr.toString()); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + /* 4 NS: com at ., com at com, powerdns.com at com, powerdns.com at powerdns.com + 4 DNSKEY: ., com (not for powerdns.com because DS denial in referral) + 1 query for A */ + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 7); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_skipped_cut) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("www.sub.powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == DNSName("sub.powerdns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + return 1; + } + else if (domain == DNSName("www.sub.powerdns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, DNSName("sub.powerdns.com."), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("www.sub.powerdns.com.")) { + addRecordToLW(res, DNSName("sub.powerdns.com"), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + else if (domain == DNSName("sub.powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + addNSECRecordToLW(domain, DNSName("tub.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("powerdns.com"), 300); + } + else if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("powerdns.com."), 300); + } + } else { + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 11); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 11); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_ta_skipped_cut) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("www.sub.powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + /* No key material for .com */ + /* But TA for sub.powerdns.com. */ + generateKeyMaterial(DNSName("sub.powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + luaconfsCopy.dsAnchors[DNSName("sub.powerdns.com.")].insert(keys[DNSName("sub.powerdns.com.")].second); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == DNSName("www.sub.powerdns.com")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, DNSName("sub.powerdns.com"), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.sub.powerdns.com"), DNSName("vww.sub.powerdns.com."), { QType::A }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com"), 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + return 1; + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("sub.powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + /* no DS */ + addNSECRecordToLW(DNSName("com."), DNSName("dom."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, DNSName("com."), QType::NS, "a.gtld-servers.com."); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + else if (domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, DNSName("powerdns.com."), QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + if (domain == DNSName("www.sub.powerdns.com.")) { + addRecordToLW(res, DNSName("sub.powerdns.com"), QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com"), 300); + addNSECRecordToLW(DNSName("www.sub.powerdns.com"), DNSName("vww.sub.powerdns.com."), { QType::A }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com"), 300); + } + else if (domain == DNSName("sub.powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300); + } + else if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + } + } + else if (domain == DNSName("www.sub.powerdns.com.")) { + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + addRRSIG(keys, res->d_records, DNSName("sub.powerdns.com."), 300); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 9); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 9); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_nodata) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == target) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + /* no DS */ + addNSECRecordToLW(domain, DNSName("z.powerdns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + /* 4 NS (com from root, com from com, powerdns.com from com, + powerdns.com from powerdns.com) + 2 DNSKEY (. and com., none for powerdns.com because no DS) + 1 query for A + */ + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(queriesCount, 7); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_insecure_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const DNSName targetCName("power-dns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == target) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + setLWResult(res, 0, false, false, true); + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + if (domain == DNSName("powerdns.com.")) { + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + } + else if (domain == targetCName) { + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + } + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + if (domain == DNSName("powerdns.com.")) { + addRRSIG(keys, res->d_records, domain, 300); + } + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + if (domain == DNSName("powerdns.com.")) { + addRRSIG(keys, res->d_records, domain, 300); + } + } + else { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + } + } + + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_to_secure_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("power-dns.com."); + const DNSName targetCName("powerdns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else if (domain == DNSName("powerdns.com.") || domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + if (domain == targetCName) { + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + } + else if (domain == target) { + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + } + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + if (domain == DNSName("powerdns.com.")) { + addRRSIG(keys, res->d_records, domain, 300); + } + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + if (domain == DNSName("powerdns.com.")) { + addRRSIG(keys, res->d_records, domain, 300); + } + } + else { + if (domain == target) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_secure_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("power-dns.com."); + const DNSName targetCName("powerdns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("power-dns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else if (domain == DNSName("powerdns.com.") || domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName(domain), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + if (domain == target) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + /* No RRSIG, leading to bogus */ + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_bogus_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("power-dns.com."); + const DNSName targetCName("powerdns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("power-dns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else if (domain == DNSName("powerdns.com.") || domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName(domain), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + if (domain == target) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + /* No RRSIG, leading to bogus */ + } + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 3); + BOOST_CHECK_EQUAL(queriesCount, 11); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_secure_to_secure_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("power-dns.com."); + const DNSName targetCName("powerdns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("power-dns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + return 0; + } + else if (type == QType::DNSKEY) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else if (domain == DNSName("powerdns.com.") || domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName(domain), 300, res->d_records, keys); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRRSIG(keys, res->d_records, domain, 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, domain, 300); + } + else { + if (domain == target) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + addRRSIG(keys, res->d_records, domain, 300); + } + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 12); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 4); + BOOST_CHECK_EQUAL(queriesCount, 12); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_bogus_to_insecure_cname) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const DNSName targetCName("power-dns.com."); + const ComboAddress targetCNameAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + generateKeyMaterial(DNSName("com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("powerdns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + generateKeyMaterial(DNSName("power-dns.com."), DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetCName,targetCNameAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DS) { + if (domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + return 1; + } + } + else if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("com.") || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, "pdns-public-ns1.powerdns.com. pieter\\.lexis.powerdns.com. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addDS(DNSName("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 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + addRRSIG(keys, res->d_records, DNSName("com."), 300); + } + else if (domain == DNSName("powerdns.com.") || domain == DNSName("power-dns.com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + if (domain == DNSName("powerdns.com.")) { + addDS(DNSName("powerdns.com."), 300, res->d_records, keys); + } + else if (domain == targetCName) { + addNSECRecordToLW(domain, DNSName("z.power-dns.com."), { QType::NS }, 600, res->d_records); + } + addRRSIG(keys, res->d_records, DNSName("com."), 300); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else { + if (domain == DNSName("powerdns.com.")) { + addRecordToLW(res, domain, QType::CNAME, targetCName.toString()); + /* No RRSIG -> Bogus */ + } + else if (domain == targetCName) { + addRecordToLW(res, domain, QType::A, targetCNameAddr.toString()); + } + } + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* no RRSIG to show */ + BOOST_CHECK_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 10); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_CHECK_EQUAL(ret.size(), 2); + BOOST_CHECK_EQUAL(queriesCount, 10); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + /* No key material for .com */ + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + luaconfsCopy.dsAnchors[target].insert(keys[target].second); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else if (domain == DNSName("com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(DNSName("com."), DNSName("com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (target == domain) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + } + else { + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + } + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + /* should be insecure but we have a TA for powerdns.com. */ + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + /* We got a RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::A); + /* - NS com. (at . and com.) + - NS powerdns.com (com. and powerdns.com.) + - DNSKEY (. and powerdns.com.) + - A powerdns.com + */ + BOOST_CHECK_EQUAL(queriesCount, 7); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Secure); + BOOST_REQUIRE_EQUAL(ret.size(), 2); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 7); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_insecure_ta_norrsig) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("powerdns.com."); + const ComboAddress targetAddr("192.0.2.42"); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + /* No key material for .com */ + generateKeyMaterial(target, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys); + luaconfsCopy.dsAnchors[target].insert(keys[target].second); + g_luaconfs.setState(luaconfsCopy); + + size_t queriesCount = 0; + + sr->setAsyncCallback([target,targetAddr,&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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (type == QType::DNSKEY) { + if (domain == g_rootdnsname || domain == DNSName("powerdns.com.")) { + setLWResult(res, 0, true, false, true); + addDNSKEY(keys, domain, 300, res->d_records); + addRRSIG(keys, res->d_records, domain, 300); + return 1; + } + else if (domain == DNSName("com.")) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::SOA, ". yop. 2017032301 10800 3600 604800 3600", DNSResourceRecord::AUTHORITY, 3600); + return 1; + } + } + else { + if (target.isPartOf(domain) && isRootServer(ip)) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, "com.", QType::NS, "a.gtld-servers.com.", DNSResourceRecord::AUTHORITY, 3600); + addNSECRecordToLW(DNSName("com."), DNSName("com."), { QType::NS }, 600, res->d_records); + addRRSIG(keys, res->d_records, DNSName("."), 300); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } + else if (ip == ComboAddress("192.0.2.1:53")) { + if (target == domain) { + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 3600); + addRecordToLW(res, "ns1.powerdns.com.", QType::A, "192.0.2.2", DNSResourceRecord::ADDITIONAL, 3600); + } + else if (domain == DNSName("com.")) { + setLWResult(res, 0, true, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.com."); + addRecordToLW(res, "a.gtld-servers.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + } + return 1; + } + else if (domain == target && ip == ComboAddress("192.0.2.2:53")) { + setLWResult(res, 0, true, false, true); + if (type == QType::NS) { + addRecordToLW(res, domain, QType::NS, "ns1.powerdns.com."); + } + else { + addRecordToLW(res, domain, QType::A, targetAddr.toString(), DNSResourceRecord::ANSWER, 3600); + } + /* No RRSIG in a now (thanks to TA) Secure zone -> Bogus*/ + return 1; + } + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + /* should be insecure but we have a TA for powerdns.com., but no RRSIG so Bogus */ + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + /* No RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + /* - NS com. (at . and com.) + - NS powerdns.com (com. and powerdns.com.) + - DNSKEY (.) + - A powerdns.com (no DNSKEY because no RRSIG) + */ + BOOST_CHECK_EQUAL(queriesCount, 6); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Bogus); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(queriesCount, 6); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_nta) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + generateKeyMaterial(g_rootdnsname, DNSSECKeeper::ECDSA256, DNSSECKeeper::SHA256, keys, luaconfsCopy.dsAnchors); + /* Add a NTA for "." */ + luaconfsCopy.negAnchors[g_rootdnsname] = "NTA for Root"; + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRRSIG(keys, res->d_records, domain, 300); + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } else if (domain == target && type == QType::DNSKEY) { + + setLWResult(res, 0, true, false, true); + + /* No DNSKEY */ + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + /* 13 NS + 1 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 1); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 14); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +BOOST_AUTO_TEST_CASE(test_dnssec_no_ta) { + std::unique_ptr sr; + initSR(sr, true); + + g_dnssecmode = DNSSECMode::ValidateAll; + + primeHints(); + const DNSName target("."); + testkeysset_t keys; + + /* Remove the root DS */ + auto luaconfsCopy = g_luaconfs.getCopy(); + luaconfsCopy.dsAnchors.clear(); + 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, std::shared_ptr outgoingLogger, LWResult* res) { + queriesCount++; + + if (domain == target && type == QType::NS) { + + setLWResult(res, 0, true, false, true); + char addr[] = "a.root-servers.net."; + for (char idx = 'a'; idx <= 'm'; idx++) { + addr[0] = idx; + addRecordToLW(res, domain, QType::NS, std::string(addr), DNSResourceRecord::ANSWER, 3600); + } + + addRecordToLW(res, "a.root-servers.net.", QType::A, "198.41.0.4", DNSResourceRecord::ADDITIONAL, 3600); + addRecordToLW(res, "a.root-servers.net.", QType::AAAA, "2001:503:ba3e::2:30", DNSResourceRecord::ADDITIONAL, 3600); + + return 1; + } + + return 0; + }); + + vector ret; + int res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + /* 13 NS + 0 RRSIG */ + BOOST_REQUIRE_EQUAL(ret.size(), 13); + BOOST_CHECK_EQUAL(queriesCount, 1); + + /* again, to test the cache */ + ret.clear(); + res = sr->beginResolve(target, QType(QType::NS), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_CHECK_EQUAL(sr->getValidationState(), Insecure); + BOOST_REQUIRE_EQUAL(ret.size(), 13); + BOOST_CHECK_EQUAL(queriesCount, 1); +} + +/* +// cerr<<"asyncresolve called to ask "< getRecordContent(uint16_t type, const std::string& content) +{ + std::shared_ptr result = nullptr; + + if (type == QType::NS) { + result = std::make_shared(DNSName(content)); + } + else if (type == QType::A) { + result = std::make_shared(ComboAddress(content)); + } + else if (type == QType::AAAA) { + result = std::make_shared(ComboAddress(content)); + } + else if (type == QType::CNAME) { + result = std::make_shared(DNSName(content)); + } + else if (type == QType::OPT) { + result = std::make_shared(); + } + else { + result = std::shared_ptr(DNSRecordContent::mastermake(type, QClass::IN, content)); + } + + return result; +} + +static inline void addRecordToList(std::vector& records, const DNSName& name, uint16_t type, const std::string& content, DNSResourceRecord::Place place=DNSResourceRecord::ANSWER, uint32_t ttl=3600) +{ + DNSRecord rec; + rec.d_place = place; + rec.d_name = name; + rec.d_type = type; + rec.d_ttl = ttl; + + rec.d_content = getRecordContent(type, content); + + records.push_back(rec); +} diff --git a/pdns/test-ixfr_cc.cc b/pdns/test-ixfr_cc.cc new file mode 100644 index 000000000..e4bbd49c6 --- /dev/null +++ b/pdns/test-ixfr_cc.cc @@ -0,0 +1,303 @@ +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_NO_MAIN + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include "test-common.hh" +#include "ixfr.hh" + +BOOST_AUTO_TEST_SUITE(ixfr_cc) + +BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_axfr) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::NS, "NS.JAIN.AD.JP."); + addRecordToList(records, DNSName("NS.JAIN.AD.JP."), QType::A, "133.69.136.1"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + BOOST_CHECK_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(ret.at(0).first.size(), 0); + BOOST_REQUIRE_EQUAL(ret.at(0).second.size(), records.size()); + for (size_t idx = 0; idx < records.size(); idx++) { + BOOST_CHECK(ret.at(0).second.at(idx) == records.at(idx)); + } +} + +BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_incremental) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.4"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.4"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + // two sequences + BOOST_CHECK_EQUAL(ret.size(), 2); + // the first one has one removal, two additions (plus the corresponding SOA removal/addition) + BOOST_CHECK_EQUAL(ret.at(0).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(0).second.size(), 2 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(0).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(0).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(1).d_type, QType(QType::A).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(2).d_type, QType(QType::A).getCode()); + + // the second one has one removal, one addition + BOOST_CHECK_EQUAL(ret.at(1).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(1).second.size(), 1 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(1).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(1).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).second.at(1).d_type, QType(QType::A).getCode()); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_rfc1995_condensed_incremental) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + // one sequence + BOOST_CHECK_EQUAL(ret.size(), 1); + // it has one removal, two additions (plus the corresponding SOA removal/addition) + BOOST_CHECK_EQUAL(ret.at(0).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(0).second.size(), 2 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(0).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(0).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(1).d_type, QType(QType::A).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(2).d_type, QType(QType::A).getCode()); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_no_additions_in_first_sequence) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + // two sequences + BOOST_CHECK_EQUAL(ret.size(), 2); + // the first one has one removal, no additions (plus the corresponding SOA removal/addition) + BOOST_CHECK_EQUAL(ret.at(0).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(0).second.size(), 0 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(0).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(0).second.at(0).d_type, QType(QType::SOA).getCode()); + + // the second one has one removal, one addition + BOOST_CHECK_EQUAL(ret.at(1).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(1).second.size(), 1 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(1).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(1).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).second.at(1).d_type, QType(QType::A).getCode()); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_no_removals_in_first_sequence) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.4"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.4"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + // two sequences + BOOST_CHECK_EQUAL(ret.size(), 2); + // the first one has no removal, two additions (plus the corresponding SOA removal/addition) + BOOST_CHECK_EQUAL(ret.at(0).first.size(), 0 + 1); + BOOST_CHECK_EQUAL(ret.at(0).second.size(), 2 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(0).first.at(0).d_type, QType(QType::SOA).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(0).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(1).d_type, QType(QType::A).getCode()); + BOOST_CHECK_EQUAL(ret.at(0).second.at(1).d_type, QType(QType::A).getCode()); + + // the second one has one removal, one addition + BOOST_CHECK_EQUAL(ret.at(1).first.size(), 1 + 1); + BOOST_CHECK_EQUAL(ret.at(1).second.size(), 1 + 1); + + // check removals + BOOST_CHECK_EQUAL(ret.at(1).first.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).first.at(1).d_type, QType(QType::A).getCode()); + + // check additions + BOOST_CHECK_EQUAL(ret.at(1).second.at(0).d_type, QType(QType::SOA).getCode()); + BOOST_CHECK_EQUAL(ret.at(1).second.at(1).d_type, QType(QType::A).getCode()); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_same_serial) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_records) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + + auto ret = processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_master_soa) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); +; + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + auto ret = processIXFRRecords(master, zone, records, nullptr); + BOOST_CHECK_EQUAL(ret.size(), 0); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_trailing_soa) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + + BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_invalid_no_soa_after_removals) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + + BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_mistmatching_serial_before_and_after_additions) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 2 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + + BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)), std::runtime_error); +} + +BOOST_AUTO_TEST_CASE(test_ixfr_trailing_record_after_end) { + const ComboAddress master("[2001:DB8::1]:53"); + const DNSName zone("JAIN.AD.JP."); + + auto masterSOA = std::shared_ptr(DNSRecordContent::mastermake(QType::SOA, QClass::IN, "NS.JAIN.AD.JP. mohta.jain.ad.jp. 3 600 600 3600000 604800")); + vector records; + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 1 600 600 3600000 604800"); + addRecordToList(records, DNSName("NEZU.JAIN.AD.JP."), QType::A, "133.69.136.5"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "192.41.197.2"); + addRecordToList(records, DNSName("JAIN.AD.JP."), QType::SOA, "ns.jain.ad.jp. mohta.jain.ad.jp. 3 600 600 3600000 604800"); + addRecordToList(records, DNSName("JAIN-BB.JAIN.AD.JP."), QType::A, "133.69.136.3"); + + BOOST_CHECK_THROW(processIXFRRecords(master, zone, records, std::dynamic_pointer_cast(masterSOA)), std::runtime_error); +} + +BOOST_AUTO_TEST_SUITE_END();