From: Fred Morcos Date: Tue, 31 May 2022 11:37:30 +0000 (+0200) Subject: Move domain-map related helper functions for testability and add tests X-Git-Tag: auth-4.8.0-alpha0~63^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=23855ca0b2e39fb0008cd158e08580773d5260ac;p=thirdparty%2Fpdns.git Move domain-map related helper functions for testability and add tests This also adds tests for loading entries from an etc-hosts-like file. --- diff --git a/pdns/recursordist/Makefile.am b/pdns/recursordist/Makefile.am index 743a17b991..df74be989a 100644 --- a/pdns/recursordist/Makefile.am +++ b/pdns/recursordist/Makefile.am @@ -175,6 +175,7 @@ pdns_recursor_SOURCES = \ rec_channel_rec.cc \ recpacketcache.cc recpacketcache.hh \ recursor_cache.cc recursor_cache.hh \ + reczones-helpers.cc reczones-helpers.hh \ reczones.cc \ remote_logger.cc remote_logger.hh \ resolve-context.hh \ @@ -290,6 +291,7 @@ testrunner_SOURCES = \ rec-zonetocache.cc rec-zonetocache.hh \ recpacketcache.cc recpacketcache.hh \ recursor_cache.cc recursor_cache.hh \ + reczones-helpers.cc reczones-helpers.hh \ resolver.hh resolver.cc \ responsestats.cc \ root-dnssec.hh \ @@ -329,6 +331,7 @@ testrunner_SOURCES = \ test-rec-zonetocache.cc \ test-recpacketcache_cc.cc \ test-recursorcache_cc.cc \ + test-reczones-helpers.cc \ test-rpzloader_cc.cc \ test-secpoll_cc.cc \ test-signers.cc \ diff --git a/pdns/recursordist/reczones-helpers.cc b/pdns/recursordist/reczones-helpers.cc new file mode 120000 index 0000000000..b77778de18 --- /dev/null +++ b/pdns/recursordist/reczones-helpers.cc @@ -0,0 +1 @@ +../reczones-helpers.cc \ No newline at end of file diff --git a/pdns/recursordist/reczones-helpers.hh b/pdns/recursordist/reczones-helpers.hh new file mode 120000 index 0000000000..6851251dd7 --- /dev/null +++ b/pdns/recursordist/reczones-helpers.hh @@ -0,0 +1 @@ +../reczones-helpers.hh \ No newline at end of file diff --git a/pdns/recursordist/test-reczones-helpers.cc b/pdns/recursordist/test-reczones-helpers.cc new file mode 100644 index 0000000000..9d142581dc --- /dev/null +++ b/pdns/recursordist/test-reczones-helpers.cc @@ -0,0 +1,163 @@ +#define BOOST_TEST_DYN_LINK +#include + +#include + +#include "test-syncres_cc.hh" +#include "reczones-helpers.hh" + +BOOST_AUTO_TEST_SUITE(reczones_helpers) + +static const std::array hostLines = { + "192.168.0.1 foo bar\n", + "192.168.0.1 dupfoo\n", + "192.168.0.2 baz\n", + "1.1.1.1 fancy\n", + "2.2.2.2 more.fancy\n", +}; + +struct Fixture +{ + static std::shared_ptr makeLocalhostRootDRC() + { + return DNSRecordContent::mastermake(QType::SOA, QClass::IN, "localhost. root 1 604800 86400 2419200 604800"); + } + + static std::shared_ptr makeLocalhostDRC() + { + return DNSRecordContent::mastermake(QType::NS, QClass::IN, "localhost."); + } + + static std::shared_ptr makePtrDRC(const std::string& name) + { + return DNSRecordContent::mastermake(QType::PTR, QClass::IN, name); + } + + void addDomainMapFixtureEntry(const std::string& name, const SyncRes::AuthDomain::records_t& records) + { + domainMapFixture[DNSName{name}] = SyncRes::AuthDomain{ + .d_records = records, + .d_servers = {}, + .d_name = DNSName{name}, + .d_rdForward = false, + }; + } + + void addDomainMapFixtureEntry(const std::string& name, const QType type, const std::string& address) + { + domainMapFixture[DNSName{name}] = SyncRes::AuthDomain{ + .d_records = { + DNSRecord(name, DNSRecordContent::mastermake(type, QClass::IN, address), type), + DNSRecord(name, makeLocalhostDRC(), QType::NS), + DNSRecord(name, makeLocalhostRootDRC(), QType::SOA), + }, + .d_servers = {}, + .d_name = DNSName{name}, + .d_rdForward = false, + }; + } + + Fixture() + { + addDomainMapFixtureEntry("foo", QType::A, "192.168.0.1"); + addDomainMapFixtureEntry("bar", QType::A, "192.168.0.1"); + addDomainMapFixtureEntry("dupfoo", QType::A, "192.168.0.1"); + addDomainMapFixtureEntry( + "1.0.168.192.in-addr.arpa", + { + DNSRecord("1.0.168.192.in-addr.arpa", makeLocalhostDRC(), QType::NS), + DNSRecord("1.0.168.192.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA), + DNSRecord("1.0.168.192.in-addr.arpa", makePtrDRC("foo."), QType::PTR), + DNSRecord("1.0.168.192.in-addr.arpa", makePtrDRC("bar."), QType::PTR), + }); + addDomainMapFixtureEntry("baz", QType::A, "192.168.0.2"); + addDomainMapFixtureEntry( + "2.0.168.192.in-addr.arpa", + { + DNSRecord("2.0.168.192.in-addr.arpa", makeLocalhostDRC(), QType::NS), + DNSRecord("2.0.168.192.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA), + DNSRecord("2.0.168.192.in-addr.arpa", makePtrDRC("baz."), QType::PTR), + }); + addDomainMapFixtureEntry("fancy", QType::A, "1.1.1.1"); + addDomainMapFixtureEntry( + "1.1.1.1.in-addr.arpa", + { + DNSRecord("1.1.1.1.in-addr.arpa", makeLocalhostDRC(), QType::NS), + DNSRecord("1.1.1.1.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA), + DNSRecord("1.1.1.1.in-addr.arpa", makePtrDRC("fancy."), QType::PTR), + }); + addDomainMapFixtureEntry("more.fancy", QType::A, "2.2.2.2"); + addDomainMapFixtureEntry( + "2.2.2.2.in-addr.arpa", + { + DNSRecord("2.2.2.2.in-addr.arpa", makeLocalhostDRC(), QType::NS), + DNSRecord("2.2.2.2.in-addr.arpa", makeLocalhostRootDRC(), QType::SOA), + DNSRecord("2.2.2.2.in-addr.arpa", makePtrDRC("more.fancy."), QType::PTR), + }); + } + + using DomainMapEntry = std::pair; + + static std::vector sortDomainMap(const SyncRes::domainmap_t& domainMap) + { + std::vector sorted{}; + sorted.reserve(domainMap.size()); + for (const auto& pair : domainMap) { + sorted.emplace_back(pair.first, pair.second); + } + std::stable_sort(std::begin(sorted), std::end(sorted), [](const DomainMapEntry& a, const DomainMapEntry& b) { + return a.first < b.first && a.second.d_name < b.second.d_name; + }); + return sorted; + } + + static std::string printDomainMap(const std::vector& domainMap) + { + std::stringstream s{}; + for (const auto& entry : domainMap) { + s << "Entry `" << entry.first << "` {" << std::endl; + s << entry.second.print(" "); + s << "}" << std::endl; + } + return s.str(); + } + + std::vector getDomainMapFixture() const + { + return sortDomainMap(domainMapFixture); + } + +private: + SyncRes::domainmap_t domainMapFixture{}; +}; + +BOOST_FIXTURE_TEST_CASE(test_loading_etc_hosts, Fixture) +{ + auto log = g_slog->withName("config"); + + auto domainMap = std::make_shared(); + std::vector parts{}; + for (auto line : hostLines) { + BOOST_REQUIRE(parseEtcHostsLine(parts, line)); + addForwardAndReverseLookupEntries(domainMap, "", parts, log); + } + + auto actual = sortDomainMap(*domainMap); + BOOST_TEST_MESSAGE("Actual:"); + BOOST_TEST_MESSAGE(printDomainMap(actual)); + + auto expected = getDomainMapFixture(); + BOOST_TEST_MESSAGE("Expected:"); + BOOST_TEST_MESSAGE(printDomainMap(expected)); + + BOOST_CHECK_EQUAL(actual.size(), expected.size()); + for (std::vector::size_type i = 0; i < actual.size(); i++) { + BOOST_CHECK(actual[i].first == expected[i].first); + BOOST_CHECK(actual[i].second == expected[i].second); + } + + // BOOST_CHECK_EQUAL(actual, expected); + // BOOST_CHECK(actualSorted == expectedSorted); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/pdns/reczones-helpers.cc b/pdns/reczones-helpers.cc new file mode 100644 index 0000000000..a96c4213e8 --- /dev/null +++ b/pdns/reczones-helpers.cc @@ -0,0 +1,157 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "syncres.hh" +#include "reczones-helpers.hh" + +static void makeNameToIPZone(const std::shared_ptr& newMap, + const DNSName& hostname, + const string& ip, + Logr::log_t log) +{ + SyncRes::AuthDomain ad; + ad.d_rdForward = false; + + DNSRecord dr; + dr.d_name = hostname; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_ttl = 86400; + dr.d_type = QType::SOA; + dr.d_class = 1; + dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"); + + ad.d_records.insert(dr); + + dr.d_type = QType::NS; + dr.d_content = std::make_shared("localhost."); + + ad.d_records.insert(dr); + + dr.d_type = QType::A; + dr.d_content = DNSRecordContent::mastermake(QType::A, 1, ip); + ad.d_records.insert(dr); + + if (newMap->count(dr.d_name) != 0) { + SLOG(g_log << Logger::Warning << "Hosts file will not overwrite zone '" << dr.d_name << "' already loaded" << endl, + log->info(Logr::Warning, "Hosts file will not overwrite already loaded zone", "zone", Logging::Loggable(dr.d_name))); + } + else { + SLOG(g_log << Logger::Warning << "Inserting forward zone '" << dr.d_name << "' based on hosts file" << endl, + log->info(Logr::Notice, "Inserting forward zone based on hosts file", "zone", Logging::Loggable(dr.d_name))); + ad.d_name = dr.d_name; + (*newMap)[ad.d_name] = ad; + } +} + +//! parts[0] must be an IP address, the rest must be host names +void makeIPToNamesZone(const std::shared_ptr& newMap, + const vector& parts, + Logr::log_t log) +{ + string address = parts[0]; + vector ipParts; + stringtok(ipParts, address, "."); + + SyncRes::AuthDomain ad; + ad.d_rdForward = false; + + DNSRecord dr; + for (auto part = ipParts.rbegin(); part != ipParts.rend(); ++part) { + dr.d_name.appendRawLabel(*part); + } + dr.d_name.appendRawLabel("in-addr"); + dr.d_name.appendRawLabel("arpa"); + dr.d_class = 1; + dr.d_place = DNSResourceRecord::ANSWER; + dr.d_ttl = 86400; + dr.d_type = QType::SOA; + dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"); + + ad.d_records.insert(dr); + + dr.d_type = QType::NS; + dr.d_content = std::make_shared(DNSName("localhost.")); + + ad.d_records.insert(dr); + dr.d_type = QType::PTR; + + if (ipParts.size() == 4) { // otherwise this is a partial zone + for (unsigned int n = 1; n < parts.size(); ++n) { + dr.d_content = DNSRecordContent::mastermake(QType::PTR, 1, DNSName(parts[n]).toString()); // XXX FIXME DNSNAME PAIN CAN THIS BE RIGHT? + ad.d_records.insert(dr); + } + } + + if (newMap->count(dr.d_name) != 0) { + SLOG(g_log << Logger::Warning << "Will not overwrite zone '" << dr.d_name << "' already loaded" << endl, + log->info(Logr::Warning, "Will not overwrite already loaded zone", "zone", Logging::Loggable(dr.d_name))); + } + else { + if (ipParts.size() == 4) { + SLOG(g_log << Logger::Warning << "Inserting reverse zone '" << dr.d_name << "' based on hosts file" << endl, + log->info(Logr::Notice, "Inserting reverse zone based on hosts file", "zone", Logging::Loggable(dr.d_name))); + } + ad.d_name = dr.d_name; + (*newMap)[ad.d_name] = ad; + } +} + +void addForwardAndReverseLookupEntries(const std::shared_ptr& newMap, + const std::string& searchSuffix, + const std::vector& parts, + Logr::log_t log) +{ + for (unsigned int n = 1; n < parts.size(); ++n) { + if (searchSuffix.empty() || parts[n].find('.') != string::npos) { + makeNameToIPZone(newMap, DNSName(parts[n]), parts[0], log); + } + else { + DNSName canonic = toCanonic(DNSName(searchSuffix), parts[n]); /// XXXX DNSName pain + if (canonic != DNSName(parts[n])) { // XXX further DNSName pain + makeNameToIPZone(newMap, canonic, parts[0], log); + } + } + } + makeIPToNamesZone(newMap, parts, log); +} + +bool parseEtcHostsLine(std::vector& parts, std::string& line) +{ + const string::size_type pos = line.find('#'); + if (pos != string::npos) { + line.resize(pos); + } + boost::trim(line); + if (line.empty()) { + return false; + } + parts.clear(); + stringtok(parts, line, "\t\r\n "); + if (parts[0].find(':') != string::npos) { + return false; + } + return parts.size() >= 2; +} diff --git a/pdns/reczones-helpers.hh b/pdns/reczones-helpers.hh new file mode 100644 index 0000000000..f6900e7cc9 --- /dev/null +++ b/pdns/reczones-helpers.hh @@ -0,0 +1,45 @@ +/* + * This file is part of PowerDNS or dnsdist. + * Copyright -- PowerDNS.COM B.V. and its contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * In addition, for the avoidance of any doubt, permission is granted to + * link this program with OpenSSL and to (re)distribute the binaries + * produced as the result of such linking. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#include "syncres.hh" +#include "logger.hh" + +void makeIPToNamesZone(const std::shared_ptr& newMap, + const vector& parts, + Logr::log_t log); + +//! A return value `false` means that the line cannot be parsed (e.g. unsupported IPv6). +bool parseEtcHostsLine(std::vector& parts, std::string& line); + +void addForwardAndReverseLookupEntries(const std::shared_ptr& newMap, + const std::string& searchSuffix, + const std::vector& parts, + Logr::log_t log); diff --git a/pdns/reczones.cc b/pdns/reczones.cc index d81678ffc0..141c308b74 100644 --- a/pdns/reczones.cc +++ b/pdns/reczones.cc @@ -24,6 +24,7 @@ #include "config.h" #endif +#include "reczones-helpers.hh" #include "syncres.hh" #include "arguments.hh" #include "zoneparser-tng.hh" @@ -199,93 +200,6 @@ void primeRootNSZones(DNSSECMode mode, unsigned int depth) } } -static void makeNameToIPZone(const std::shared_ptr& newMap, const DNSName& hostname, const string& ip, Logr::log_t log) -{ - SyncRes::AuthDomain ad; - ad.d_rdForward = false; - - DNSRecord dr; - dr.d_name = hostname; - dr.d_place = DNSResourceRecord::ANSWER; - dr.d_ttl = 86400; - dr.d_type = QType::SOA; - dr.d_class = 1; - dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"); - - ad.d_records.insert(dr); - - dr.d_type = QType::NS; - dr.d_content = std::make_shared("localhost."); - - ad.d_records.insert(dr); - - dr.d_type = QType::A; - dr.d_content = DNSRecordContent::mastermake(QType::A, 1, ip); - ad.d_records.insert(dr); - - if (newMap->count(dr.d_name) != 0) { - SLOG(g_log << Logger::Warning << "Hosts file will not overwrite zone '" << dr.d_name << "' already loaded" << endl, - log->info(Logr::Warning, "Hosts file will not overwrite already loaded zone", "zone", Logging::Loggable(dr.d_name))); - } - else { - SLOG(g_log << Logger::Warning << "Inserting forward zone '" << dr.d_name << "' based on hosts file" << endl, - log->info(Logr::Notice, "Inserting forward zone based on hosts file", "zone", Logging::Loggable(dr.d_name))); - ad.d_name = dr.d_name; - (*newMap)[ad.d_name] = ad; - } -} - -//! parts[0] must be an IP address, the rest must be host names -static void makeIPToNamesZone(const std::shared_ptr& newMap, const vector& parts, Logr::log_t log) -{ - string address = parts[0]; - vector ipParts; - stringtok(ipParts, address, "."); - - SyncRes::AuthDomain ad; - ad.d_rdForward = false; - - DNSRecord dr; - for (auto part = ipParts.rbegin(); part != ipParts.rend(); ++part) { - dr.d_name.appendRawLabel(*part); - } - dr.d_name.appendRawLabel("in-addr"); - dr.d_name.appendRawLabel("arpa"); - dr.d_class = 1; - dr.d_place = DNSResourceRecord::ANSWER; - dr.d_ttl = 86400; - dr.d_type = QType::SOA; - dr.d_content = DNSRecordContent::mastermake(QType::SOA, 1, "localhost. root 1 604800 86400 2419200 604800"); - - ad.d_records.insert(dr); - - dr.d_type = QType::NS; - dr.d_content = std::make_shared(DNSName("localhost.")); - - ad.d_records.insert(dr); - dr.d_type = QType::PTR; - - if (ipParts.size() == 4) { // otherwise this is a partial zone - for (unsigned int n = 1; n < parts.size(); ++n) { - dr.d_content = DNSRecordContent::mastermake(QType::PTR, 1, DNSName(parts[n]).toString()); // XXX FIXME DNSNAME PAIN CAN THIS BE RIGHT? - ad.d_records.insert(dr); - } - } - - if (newMap->count(dr.d_name) != 0) { - SLOG(g_log << Logger::Warning << "Will not overwrite zone '" << dr.d_name << "' already loaded" << endl, - log->info(Logr::Warning, "Will not overwrite already loaded zone", "zone", Logging::Loggable(dr.d_name))); - } - else { - if (ipParts.size() == 4) { - SLOG(g_log << Logger::Warning << "Inserting reverse zone '" << dr.d_name << "' based on hosts file" << endl, - log->info(Logr::Notice, "Inserting reverse zone based on hosts file", "zone", Logging::Loggable(dr.d_name))); - } - ad.d_name = dr.d_name; - (*newMap)[ad.d_name] = ad; - } -} - static void convertServersForAD(const std::string& zone, const std::string& input, SyncRes::AuthDomain& ad, const char* sepa, Logr::log_t log, bool verbose = true) { vector servers; @@ -560,34 +474,13 @@ std::tuple, std::shared_ptr> log->error(Logr::Warning, "Could not open file for reading", "file", Logging::Loggable(fname))); } else { - string searchSuffix = ::arg()["export-etc-hosts-search-suffix"]; - string::size_type pos = 0; while (getline(ifs, line)) { - pos = line.find('#'); - if (pos != string::npos) { - line.resize(pos); - } - boost::trim(line); - if (line.empty()) { + if (!parseEtcHostsLine(parts, line)) { continue; } - parts.clear(); - stringtok(parts, line, "\t\r\n "); - if (parts[0].find(':') != string::npos) - continue; - for (unsigned int n = 1; n < parts.size(); ++n) { - if (searchSuffix.empty() || parts[n].find('.') != string::npos) { - makeNameToIPZone(newMap, DNSName(parts[n]), parts[0], log); - } - else { - DNSName canonic = toCanonic(DNSName(searchSuffix), parts[n]); /// XXXX DNSName pain - if (canonic != DNSName(parts[n])) { // XXX further DNSName pain - makeNameToIPZone(newMap, canonic, parts[0], log); - } - } - } - makeIPToNamesZone(newMap, parts, log); + string searchSuffix = ::arg()["export-etc-hosts-search-suffix"]; + addForwardAndReverseLookupEntries(newMap, searchSuffix, parts, log); } } }