From: Otto Moerbeek Date: Mon, 3 Apr 2023 10:18:02 +0000 (+0200) Subject: Add test, with some reorganization as reczones is not linked into testrunner. X-Git-Tag: rec-4.9.0-alpha1~12^2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=refs%2Fpull%2F12655%2Fhead;p=thirdparty%2Fpdns.git Add test, with some reorganization as reczones is not linked into testrunner. So move a few functions to reczones-helpers.cc --- diff --git a/pdns/recursordist/reczones-helpers.cc b/pdns/recursordist/reczones-helpers.cc index 41a36dc7f0..721a8d2e7d 100644 --- a/pdns/recursordist/reczones-helpers.cc +++ b/pdns/recursordist/reczones-helpers.cc @@ -24,8 +24,150 @@ #include "config.h" #endif +#include "arguments.hh" #include "syncres.hh" #include "reczones-helpers.hh" +#include "root-addresses.hh" +#include "zoneparser-tng.hh" + +static void putIntoCache(time_t now, QType qtype, vState state, const ComboAddress& from, const set& seen, const std::multimap& allRecords) +{ + for (const auto& name : seen) { + auto records = allRecords.equal_range(name); + vector aset; + for (auto elem = records.first; elem != records.second; ++elem) { + aset.emplace_back(elem->second); + } + // Put non-default root hints into cache as authoritative. As argued below in + // putDefaultHintsIntoCache, this is actually wrong, but people might depend on it by having + // root-hints that refer to servers that aren't actually capable or willing to serve root data. + g_recCache->replace(now, name, qtype, aset, {}, {}, true, g_rootdnsname, boost::none, boost::none, state, from); + } +} + +static void parseHintFile(time_t now, const std::string& hintfile, set& seenA, set& seenAAAA, set& seenNS, std::multimap& aRecords, std::multimap& aaaaRecords, vector& nsvec) +{ + ZoneParserTNG zpt(hintfile); + zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps")); + zpt.setMaxIncludes(::arg().asNum("max-include-depth")); + DNSResourceRecord rrecord; + + while (zpt.get(rrecord)) { + rrecord.ttl += now; + switch (rrecord.qtype) { + case QType::A: + seenA.insert(rrecord.qname); + aRecords.emplace(rrecord.qname, DNSRecord(rrecord)); + break; + case QType::AAAA: + seenAAAA.insert(rrecord.qname); + aaaaRecords.emplace(rrecord.qname, DNSRecord(rrecord)); + break; + case QType::NS: + seenNS.emplace(rrecord.content); + rrecord.content = toLower(rrecord.content); + nsvec.emplace_back(rrecord); + break; + } + } +} + +static bool determineReachable(const set& names, const set& nameservers) +{ + bool reachable = false; + for (auto const& record : names) { + if (nameservers.count(record) != 0) { + reachable = true; + break; + } + } + return reachable; +} + +bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector& nsvec) +{ + const ComboAddress from("255.255.255.255"); + set seenNS; + set seenA; + set seenAAAA; + + std::multimap aRecords; + std::multimap aaaaRecords; + + parseHintFile(now, hintfile, seenA, seenAAAA, seenNS, aRecords, aaaaRecords, nsvec); + + putIntoCache(now, QType::A, vState::Insecure, from, seenA, aRecords); + putIntoCache(now, QType::AAAA, vState::Insecure, from, seenAAAA, aaaaRecords); + + bool reachableA = determineReachable(seenA, seenNS); + bool reachableAAAA = determineReachable(seenAAAA, seenNS); + + auto log = g_slog->withName("config"); + if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) { + SLOG(g_log << Logger::Error << "Running IPv4 only but no IPv4 root hints" << endl, + log->info(Logr::Error, "Running IPv4 only but no IPv4 root hints")); + return false; + } + if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) { + SLOG(g_log << Logger::Error << "Running IPv6 only but no IPv6 root hints" << endl, + log->info(Logr::Error, "Running IPv6 only but no IPv6 root hints")); + return false; + } + if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) { + SLOG(g_log << Logger::Error << "No valid root hints" << endl, + log->info(Logr::Error, "No valid root hints")); + return false; + } + return true; +} + +void putDefaultHintsIntoCache(time_t now, std::vector& nsvec) +{ + const ComboAddress from("255.255.255.255"); + + DNSRecord arr; + DNSRecord aaaarr; + DNSRecord 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 = now + 3600000; + + string templ = "a.root-servers.net."; + + static_assert(rootIps4.size() == rootIps6.size()); + + for (size_t letter = 0; letter < rootIps4.size(); ++letter) { + templ.at(0) = static_cast(letter + 'a'); + aaaarr.d_name = arr.d_name = DNSName(templ); + nsrr.setContent(std::make_shared(DNSName(templ))); + nsvec.push_back(nsrr); + + if (!rootIps4.at(letter).empty()) { + arr.setContent(std::make_shared(ComboAddress(rootIps4.at(letter)))); + /* + * Originally the hint records were inserted with the auth flag set, with the consequence that + * data from AUTHORITY and ADDITIONAL sections (as seen in a . NS response) were not used. This + * (together with the long ttl) caused outdated hint to be kept in cache. So insert as non-auth, + * and the extra sections in the . NS refreshing cause the cached records to be updated with + * up-to-date information received from a real root server. + * + * Note that if a user query is done for one of the root-server.net names, it will be inserted + * into the cache with the auth bit set. Further NS refreshes will not update that entry. If all + * root names are queried at the same time by a user, all root-server.net names will be marked + * auth and will expire at the same time. A re-prime is then triggered, as before, when the + * records were inserted with the auth bit set and the TTD comes. + */ + g_recCache->replace(now, DNSName(templ), QType::A, {arr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from); + } + if (!rootIps6.at(letter).empty()) { + aaaarr.setContent(std::make_shared(ComboAddress(rootIps6.at(letter)))); + g_recCache->replace(now, DNSName(templ), QType::AAAA, {aaaarr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from); + } + } +} template static SyncRes::AuthDomain makeSOAAndNSNodes(DNSRecord& dr, T content) diff --git a/pdns/recursordist/reczones-helpers.hh b/pdns/recursordist/reczones-helpers.hh index e68fed2ec0..dafeee4daf 100644 --- a/pdns/recursordist/reczones-helpers.hh +++ b/pdns/recursordist/reczones-helpers.hh @@ -32,6 +32,9 @@ #include "syncres.hh" #include "logger.hh" +bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector& nsvec); +void putDefaultHintsIntoCache(time_t now, std::vector& nsvec); + void makeIPToNamesZone(const std::shared_ptr& newMap, const vector& parts, Logr::log_t log); diff --git a/pdns/recursordist/reczones.cc b/pdns/recursordist/reczones.cc index 81e6774bd9..ba658b19c2 100644 --- a/pdns/recursordist/reczones.cc +++ b/pdns/recursordist/reczones.cc @@ -25,155 +25,15 @@ #endif #include "reczones-helpers.hh" -#include "syncres.hh" #include "arguments.hh" -#include "zoneparser-tng.hh" -#include "logger.hh" #include "dnsrecords.hh" -#include "root-addresses.hh" +#include "logger.hh" +#include "syncres.hh" +#include "zoneparser-tng.hh" extern int g_argc; extern char** g_argv; -static void putIntoCache(time_t now, QType qtype, vState state, const ComboAddress& from, const set& seen, const std::multimap& allRecords) -{ - for (const auto& name : seen) { - auto records = allRecords.equal_range(name); - vector aset; - for (auto elem = records.first; elem != records.second; ++elem) { - aset.emplace_back(elem->second); - } - // Put non-default root hints into cache as authoritative. As argued below in - // putDefaultHintsIntoCache, this is actually wrong, but people might depend on it by having - // root-hints that refer to servers that aren't actually capable or willing to serve root data. - g_recCache->replace(now, name, qtype, aset, {}, {}, true, g_rootdnsname, boost::none, boost::none, state, from); - } -} - -static void parseHintFile(time_t now, const std::string& hintfile, set& seenA, set& seenAAAA, set& seenNS, std::multimap& aRecords, std::multimap& aaaaRecords, vector& nsvec) -{ - ZoneParserTNG zpt(hintfile); - zpt.setMaxGenerateSteps(::arg().asNum("max-generate-steps")); - zpt.setMaxIncludes(::arg().asNum("max-include-depth")); - DNSResourceRecord rrecord; - - while (zpt.get(rrecord)) { - rrecord.ttl += now; - switch (rrecord.qtype) { - case QType::A: - seenA.insert(rrecord.qname); - aRecords.emplace(rrecord.qname, DNSRecord(rrecord)); - break; - case QType::AAAA: - seenAAAA.insert(rrecord.qname); - aaaaRecords.emplace(rrecord.qname, DNSRecord(rrecord)); - break; - case QType::NS: - seenNS.emplace(rrecord.content); - rrecord.content = toLower(rrecord.content); - nsvec.emplace_back(rrecord); - break; - } - } -} - -static bool determineReachable(const set& names, const set& nameservers) -{ - bool reachable = false; - for (auto const& record : names) { - if (nameservers.count(record) != 0) { - reachable = true; - break; - } - } - return reachable; -} - -static bool readHintsIntoCache(time_t now, const std::string& hintfile, std::vector& nsvec) -{ - const ComboAddress from("255.255.255.255"); - set seenNS; - set seenA; - set seenAAAA; - - std::multimap aRecords; - std::multimap aaaaRecords; - - parseHintFile(now, hintfile, seenA, seenAAAA, seenNS, aRecords, aaaaRecords, nsvec); - - putIntoCache(now, QType::A, vState::Insecure, from, seenA, aRecords); - putIntoCache(now, QType::AAAA, vState::Insecure, from, seenAAAA, aaaaRecords); - - bool reachableA = determineReachable(seenA, seenNS); - bool reachableAAAA = determineReachable(seenAAAA, seenNS); - - auto log = g_slog->withName("config"); - if (SyncRes::s_doIPv4 && !SyncRes::s_doIPv6 && !reachableA) { - SLOG(g_log << Logger::Error << "Running IPv4 only but no IPv4 root hints" << endl, - log->info(Logr::Error, "Running IPv4 only but no IPv4 root hints")); - return false; - } - if (!SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableAAAA) { - SLOG(g_log << Logger::Error << "Running IPv6 only but no IPv6 root hints" << endl, - log->info(Logr::Error, "Running IPv6 only but no IPv6 root hints")); - return false; - } - if (SyncRes::s_doIPv4 && SyncRes::s_doIPv6 && !reachableA && !reachableAAAA) { - SLOG(g_log << Logger::Error << "No valid root hints" << endl, - log->info(Logr::Error, "No valid root hints")); - return false; - } - return true; -} - -static void putDefaultHintsIntoCache(time_t now, std::vector& nsvec) -{ - const ComboAddress from("255.255.255.255"); - - DNSRecord arr; - DNSRecord aaaarr; - DNSRecord 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 = now + 3600000; - - string templ = "a.root-servers.net."; - - static_assert(rootIps4.size() == rootIps6.size()); - - for (size_t letter = 0; letter < rootIps4.size(); ++letter) { - templ.at(0) = static_cast(letter + 'a'); - aaaarr.d_name = arr.d_name = DNSName(templ); - nsrr.setContent(std::make_shared(DNSName(templ))); - nsvec.push_back(nsrr); - - if (!rootIps4.at(letter).empty()) { - arr.setContent(std::make_shared(ComboAddress(rootIps4.at(letter)))); - /* - * Originally the hint records were inserted with the auth flag set, with the consequence that - * data from AUTHORITY and ADDITIONAL sections (as seen in a . NS response) were not used. This - * (together with the long ttl) caused outdated hint to be kept in cache. So insert as non-auth, - * and the extra sections in the . NS refreshing cause the cached records to be updated with - * up-to-date information received from a real root server. - * - * Note that if a user query is done for one of the root-server.net names, it will be inserted - * into the cache with the auth bit set. Further NS refreshes will not update that entry. If all - * root names are queried at the same time by a user, all root-server.net names will be marked - * auth and will expire at the same time. A re-prime is then triggered, as before, when the - * records were inserted with the auth bit set and the TTD comes. - */ - g_recCache->replace(now, DNSName(templ), QType::A, {arr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from); - } - if (!rootIps6.at(letter).empty()) { - aaaarr.setContent(std::make_shared(ComboAddress(rootIps6.at(letter)))); - g_recCache->replace(now, DNSName(templ), QType::AAAA, {aaaarr}, {}, {}, false, g_rootdnsname, boost::none, boost::none, vState::Insecure, from); - } - } -} - bool primeHints(time_t now) { const string hintfile = ::arg()["hint-file"]; diff --git a/pdns/recursordist/test-reczones-helpers.cc b/pdns/recursordist/test-reczones-helpers.cc index 4a7aa30e78..921c7d1f11 100644 --- a/pdns/recursordist/test-reczones-helpers.cc +++ b/pdns/recursordist/test-reczones-helpers.cc @@ -258,4 +258,48 @@ BOOST_FIXTURE_TEST_CASE(test_loading_etc_hosts, Fixture) BOOST_TEST_MESSAGE("-----------------------------------------------------"); } +const std::string hints = ". 3600 IN NS ns.\n" + ". 3600 IN NS ns1.\n" + "ns. 3600 IN A 192.168.178.16\n" + "ns. 3600 IN A 192.168.178.17\n" + "ns. 3600 IN A 192.168.178.18\n" + "ns. 3600 IN AAAA 1::2\n" + "ns. 3600 IN AAAA 1::3\n" + "ns1. 3600 IN A 192.168.178.18\n"; + +BOOST_AUTO_TEST_CASE(test_UserHints) +{ + + g_recCache = make_unique(); + + ::arg().set("max-generate-steps") = "0"; + ::arg().set("max-include-depth") = "0"; + char temp[] = "/tmp/hintsXXXXXXXXXX"; + int fd = mkstemp(temp); + BOOST_REQUIRE(fd > 0); + FILE* fp = fdopen(fd, "w"); + BOOST_REQUIRE(fp != nullptr); + size_t written = fwrite(hints.data(), 1, hints.length(), fp); + BOOST_REQUIRE(written == hints.length()); + BOOST_REQUIRE(fclose(fp) == 0); + + time_t now = time(nullptr); + std::vector nsvec; + + auto ok = readHintsIntoCache(now, std::string(temp), nsvec); + BOOST_CHECK(ok); + BOOST_CHECK_EQUAL(nsvec.size(), 2U); + + const MemRecursorCache::Flags flags = 0; + + BOOST_CHECK(g_recCache->get(now, DNSName("ns"), QType::A, flags, &nsvec, ComboAddress()) > 0); + BOOST_CHECK_EQUAL(nsvec.size(), 3U); + + BOOST_CHECK(g_recCache->get(now, DNSName("ns"), QType::AAAA, flags, &nsvec, ComboAddress()) > 0); + BOOST_CHECK_EQUAL(nsvec.size(), 2U); + + BOOST_CHECK(g_recCache->get(now, DNSName("ns1"), QType::A, flags, &nsvec, ComboAddress()) > 0); + BOOST_CHECK_EQUAL(nsvec.size(), 1U); +} + BOOST_AUTO_TEST_SUITE_END()