diff --git a/meson.build b/meson.build index c201c5cc0883..c99de112b0fa 100644 --- a/meson.build +++ b/meson.build @@ -533,8 +533,6 @@ common_sources += files( src_dir / 'dnspacket.hh', src_dir / 'dnsparser.cc', src_dir / 'dnsparser.hh', - src_dir / 'dnsproxy.cc', - src_dir / 'dnsproxy.hh', src_dir / 'dnsrecords.cc', src_dir / 'dnsrecords.hh', src_dir / 'dnssecinfra.cc', diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 2522839a0f3c..40d817098e6c 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -223,7 +223,6 @@ pdns_server_SOURCES = \ dnsname.cc dnsname.hh \ dnspacket.cc dnspacket.hh \ dnsparser.cc \ - dnsproxy.cc dnsproxy.hh \ dnsrecords.cc dnsrecords.hh \ dnssecinfra.cc dnssecinfra.hh \ dnsseckeeper.hh \ @@ -593,7 +592,7 @@ sdig_LDADD += $(LIBSSL_LIBS) endif if LIBSODIUM -sdig_CPPFLAGS +=$(LIBSODIUM_CFLAGS) +sdig_CPPFLAGS += $(LIBSODIUM_CFLAGS) sdig_LDADD += $(LIBSODIUM_LIBS) endif @@ -654,28 +653,49 @@ stubquery_SOURCES = \ arguments.cc arguments.hh \ base32.cc \ base64.cc \ + dns.cc dns.hh \ dnslabeltext.cc \ dnsname.cc \ + dnspacket.cc \ dnsparser.cc \ dnsrecords.cc \ + dnssecinfra.cc dnssecinfra.hh \ dnswriter.cc \ + ednscookies.cc ednscookies.hh \ ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ + gss_context.cc gss_context.hh \ iputils.cc \ logger.cc \ misc.cc \ nsecrecords.cc \ qtype.cc \ rcpgenerator.cc \ + shuffle.cc shuffle.hh \ sillyrecords.cc \ statbag.cc \ stubquery.cc \ stubresolver.cc stubresolver.hh \ svc-records.cc svc-records.hh \ + tsigverifier.cc tsigverifier.hh \ unix_utility.cc +stubquery_CPPFLAGS = $(AM_CPPFLAGS) stubquery_LDADD = $(LIBCRYPTO_LIBS) stubquery_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS) +if GSS_TSIG +stubquery_LDADD += $(GSS_LIBS) +endif + +if PKCS11 +stubquery_SOURCES += pkcs11signers.cc pkcs11signers.hh +stubquery_LDADD += $(P11KIT1_LIBS) +endif + +if LIBSODIUM +stubquery_CPPFLAGS += $(LIBSODIUM_CFLAGS) +stubquery_LDADD += $(LIBSODIUM_LIBS) +endif saxfr_SOURCES = \ base32.cc \ @@ -798,14 +818,16 @@ ixplore_SOURCES = \ axfr-retriever.cc \ base32.cc \ base64.cc base64.hh \ - dns.cc \ + dns.cc dns.hh \ dns_random.hh \ dnslabeltext.cc \ dnsname.cc dnsname.hh \ + dnspacket.cc dnspacket.hh \ dnsparser.cc dnsparser.hh \ dnsrecords.cc \ - dnssecinfra.cc \ + dnssecinfra.cc dnssecinfra.hh \ dnswriter.cc dnswriter.hh \ + ednscookies.cc ednscookies.hh \ ednsoptions.cc ednsoptions.hh \ ednssubnet.cc ednssubnet.hh \ gss_context.cc gss_context.hh \ @@ -820,6 +842,7 @@ ixplore_SOURCES = \ query-local-address.hh query-local-address.cc \ rcpgenerator.cc rcpgenerator.hh \ resolver.cc \ + shuffle.cc shuffle.hh \ sillyrecords.cc \ sstuff.hh \ statbag.cc \ @@ -827,6 +850,7 @@ ixplore_SOURCES = \ tsigverifier.cc tsigverifier.hh \ unix_utility.cc zoneparser-tng.cc +ixplore_CPPFLAGS = $(AM_CPPFLAGS) ixplore_LDADD = $(LIBCRYPTO_LIBS) ixplore_LDFLAGS = $(AM_LDFLAGS) $(LIBCRYPTO_LDFLAGS) if GSS_TSIG @@ -838,6 +862,11 @@ ixplore_SOURCES += pkcs11signers.cc pkcs11signers.hh ixplore_LDADD += $(P11KIT1_LIBS) endif +if LIBSODIUM +ixplore_CPPFLAGS += $(LIBSODIUM_CFLAGS) +ixplore_LDADD += $(LIBSODIUM_LIBS) +endif + dnstcpbench_SOURCES = \ base32.cc \ base64.cc base64.hh \ diff --git a/pdns/auth-main.cc b/pdns/auth-main.cc index 72e05c9e6e7f..6a83ca98a5ab 100644 --- a/pdns/auth-main.cc +++ b/pdns/auth-main.cc @@ -79,7 +79,6 @@ #include "dynlistener.hh" #include "dynhandler.hh" #include "communicator.hh" -#include "dnsproxy.hh" #include "utility.hh" #include "dnsrecords.hh" #include "version.hh" @@ -123,7 +122,6 @@ StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads AuthQueryCache QC; AuthZoneCache g_zoneCache; -std::unique_ptr DP{nullptr}; static std::unique_ptr s_dynListener{nullptr}; CommunicatorClass Communicator; static double avg_latency{0.0}, receive_latency{0.0}, cache_latency{0.0}, backend_latency{0.0}, send_latency{0.0}; @@ -786,11 +784,6 @@ static void mainthread() AuthWebServer webserver; Utility::dropUserPrivs(newuid); - if (::arg().mustDo("resolver")) { - DP = std::make_unique(::arg()["resolver"], ::arg()["dnsproxy-udp-port-range"]); - DP->go(); - } - try { doSecPoll(true); } diff --git a/pdns/auth-main.hh b/pdns/auth-main.hh index bc3d90c0d77d..aea0dd3a3950 100644 --- a/pdns/auth-main.hh +++ b/pdns/auth-main.hh @@ -28,7 +28,6 @@ #include "communicator.hh" #include "distributor.hh" #include "dnspacket.hh" -#include "dnsproxy.hh" #include "dynlistener.hh" #include "nameserver.hh" #include "statbag.hh" @@ -40,7 +39,6 @@ extern ArgvMap theArg; extern StatBag S; //!< Statistics are gathered across PDNS via the StatBag class S extern AuthPacketCache PC; //!< This is the main PacketCache, shared across all threads extern AuthQueryCache QC; -extern std::unique_ptr DP; extern CommunicatorClass Communicator; void carbonDumpThread(); // Implemented in auth-carbon.cc. Avoids having an auth-carbon.hh declaring exactly one function. extern bool g_anyToTcp; diff --git a/pdns/dnsproxy.cc b/pdns/dnsproxy.cc deleted file mode 100644 index 8c14559c92ec..000000000000 --- a/pdns/dnsproxy.cc +++ /dev/null @@ -1,362 +0,0 @@ -/* - * 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 -#include - -#include "packetcache.hh" -#include "utility.hh" -#include "dnsproxy.hh" -#include "pdnsexception.hh" -#include "dns.hh" -#include "logger.hh" -#include "statbag.hh" -#include "dns_random.hh" -#include "stubresolver.hh" -#include "arguments.hh" -#include "threadname.hh" -#include "ednsoptions.hh" -#include "ednssubnet.hh" - -#include - -extern StatBag S; - -DNSProxy::DNSProxy(const string& remote, const string& udpPortRange) : - d_xor(dns_random_uint16()) -{ - d_resanswers = S.getPointer("recursing-answers"); - d_resquestions = S.getPointer("recursing-questions"); - d_udpanswers = S.getPointer("udp-answers"); - - vector addresses; - stringtok(addresses, remote, " ,\t"); - d_remote = ComboAddress(addresses[0], 53); - - vector parts; - stringtok(parts, udpPortRange, " "); - if (parts.size() != 2) { - throw PDNSException("DNS Proxy UDP port range must contain exactly one lower and one upper bound"); - } - unsigned long portRangeLow = std::stoul(parts.at(0)); - unsigned long portRangeHigh = std::stoul(parts.at(1)); - if (portRangeLow < 1 || portRangeHigh > 65535) { - throw PDNSException("DNS Proxy UDP port range values out of valid port bounds (1 to 65535)"); - } - if (portRangeLow >= portRangeHigh) { - throw PDNSException("DNS Proxy UDP port range upper bound " + std::to_string(portRangeHigh) + " must be higher than lower bound (" + std::to_string(portRangeLow) + ")"); - } - - if ((d_sock = socket(d_remote.sin4.sin_family, SOCK_DGRAM, 0)) < 0) { - throw PDNSException(string("socket: ") + stringerror()); - } - - ComboAddress local; - if (d_remote.sin4.sin_family == AF_INET) { - local = ComboAddress("0.0.0.0"); - } - else { - local = ComboAddress("::"); - } - - unsigned int attempts = 0; - for (; attempts < 10; attempts++) { - local.sin4.sin_port = htons(portRangeLow + dns_random(portRangeHigh - portRangeLow)); - - if (::bind(d_sock, (struct sockaddr*)&local, local.getSocklen()) >= 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) - break; - } - } - if (attempts == 10) { - closesocket(d_sock); - d_sock = -1; - throw PDNSException(string("binding dnsproxy socket: ") + stringerror()); - } - - if (connect(d_sock, (sockaddr*)&d_remote, d_remote.getSocklen()) < 0) { // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) - throw PDNSException("Unable to UDP connect to remote nameserver " + d_remote.toStringWithPort() + ": " + stringerror()); - } - - g_log << Logger::Error << "DNS Proxy launched, local port " << ntohs(local.sin4.sin_port) << ", remote " << d_remote.toStringWithPort() << endl; -} - -void DNSProxy::go() -{ - std::thread proxythread([this]() { mainloop(); }); - proxythread.detach(); -} - -//! look up qname 'target' with reply->qtype, plonk it in the answer section of 'reply' with name 'aname' -bool DNSProxy::completePacket(std::unique_ptr& reply, const DNSName& target, const DNSName& aname, const uint8_t scopeMask) -{ - string ECSOptionStr; - - if (reply->hasEDNSSubnet()) { - DLOG(g_log << "dnsproxy::completePacket: Parsed edns source: " << reply->d_eso.source.toString() << ", scope: " << reply->d_eso.scope.toString() << ", family = " << reply->d_eso.scope.getNetwork().sin4.sin_family << endl); - ECSOptionStr = makeEDNSSubnetOptsString(reply->d_eso); - DLOG(g_log << "from dnsproxy::completePacket: Creating ECS option string " << makeHexDump(ECSOptionStr) << endl); - } - - if (reply->d_tcp) { - vector ips; - int ret1 = 0; - int ret2 = 0; - // rip out edns info here, pass it to the stubDoResolve - if (reply->qtype == QType::A || reply->qtype == QType::ANY) { - ret1 = stubDoResolve(target, QType::A, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr); - } - if (reply->qtype == QType::AAAA || reply->qtype == QType::ANY) { - ret2 = stubDoResolve(target, QType::AAAA, ips, reply->hasEDNSSubnet() ? &reply->d_eso : nullptr); - } - - if (ret1 != RCode::NoError || ret2 != RCode::NoError) { - g_log << Logger::Error << "Error resolving for " << aname << " ALIAS " << target << " over UDP, original query came in over TCP"; - if (ret1 != RCode::NoError) { - g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1); - } - if (ret2 != RCode::NoError) { - g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2); - } - g_log << Logger::Error << ", returning SERVFAIL" << endl; - reply->clearRecords(); - reply->setRcode(RCode::ServFail); - } - else { - for (auto& ip : ips) { // NOLINT(readability-identifier-length) - ip.dr.d_name = aname; - reply->addRecord(std::move(ip)); - } - } - - uint16_t len = htons(reply->getString().length()); - string buffer((const char*)&len, 2); - buffer.append(reply->getString()); - writen2WithTimeout(reply->getSocket(), buffer.c_str(), buffer.length(), timeval{::arg().asNum("tcp-idle-timeout"), 0}); - - return true; - } - - uint16_t id; - uint16_t qtype = reply->qtype.getCode(); - { - auto conntrack = d_conntrack.lock(); - id = getID_locked(*conntrack); - - ConntrackEntry ce; - ce.id = reply->d.id; - ce.remote = reply->d_remote; - ce.outsock = reply->getSocket(); - ce.created = time(nullptr); - ce.qtype = reply->qtype.getCode(); - ce.qname = target; - ce.anyLocal = reply->d_anyLocal; - ce.complete = std::move(reply); - ce.aname = aname; - ce.anameScopeMask = scopeMask; - (*conntrack)[id] = std::move(ce); - } - - vector packet; - DNSPacketWriter pw(packet, target, qtype); - pw.getHeader()->rd = true; - pw.getHeader()->id = id ^ d_xor; - // Add EDNS Subnet if the client sent one - issue #5469 - if (!ECSOptionStr.empty()) { - DLOG(g_log << "from dnsproxy::completePacket: adding ECS option string to packet options " << makeHexDump(ECSOptionStr) << endl); - DNSPacketWriter::optvect_t opts; - opts.emplace_back(EDNSOptionCode::ECS, ECSOptionStr); - pw.addOpt(512, 0, 0, opts); - pw.commit(); - } - - if (send(d_sock, packet.data(), packet.size(), 0) < 0) { // zoom - g_log << Logger::Error << "Unable to send a packet to our recursing backend: " << stringerror() << endl; - } - - return true; -} - -/** This finds us an unused or stale ID. Does not actually clean the contents */ -int DNSProxy::getID_locked(map_t& conntrack) -{ - map_t::iterator iter; - for (int n = 0;; ++n) { // NOLINT(readability-identifier-length) - iter = conntrack.find(n); - if (iter == conntrack.end()) { - return n; - } - if (iter->second.created < time(nullptr) - 60) { - if (iter->second.created != 0) { - g_log << Logger::Warning << "Recursive query for remote " << iter->second.remote.toStringWithPort() << " with internal id " << n << " was not answered by backend within timeout, reusing id" << endl; - iter->second.complete.reset(); - S.inc("recursion-unanswered"); - } - return n; - } - } -} - -void DNSProxy::mainloop() -{ - setThreadName("pdns/dnsproxy"); - try { - char buffer[1500]; - ssize_t len; - - struct msghdr msgh; - struct iovec iov; - cmsgbuf_aligned cbuf; - ComboAddress fromaddr; - - for (;;) { - socklen_t fromaddrSize = sizeof(fromaddr); - len = recvfrom(d_sock, &buffer[0], sizeof(buffer), 0, (struct sockaddr*)&fromaddr, &fromaddrSize); // answer from our backend NOLINT(cppcoreguidelines-pro-type-cstyle-cast) - if (len < (ssize_t)sizeof(dnsheader)) { - if (len < 0) { - g_log << Logger::Error << "Error receiving packet from recursor backend: " << stringerror() << endl; - } - else if (len == 0) { - g_log << Logger::Error << "Error receiving packet from recursor backend, EOF" << endl; - } - else { - g_log << Logger::Error << "Short packet from recursor backend, " << len << " bytes" << endl; - } - - continue; - } - if (fromaddr != d_remote) { - g_log << Logger::Error << "Got answer from unexpected host " << fromaddr.toStringWithPort() << " instead of our recursor backend " << d_remote.toStringWithPort() << endl; - continue; - } - (*d_resanswers)++; - (*d_udpanswers)++; - dnsheader dHead{}; - memcpy(&dHead, &buffer[0], sizeof(dHead)); - { - auto conntrack = d_conntrack.lock(); - if (BYTE_ORDER == BIG_ENDIAN) { - // this is needed because spoof ID down below does not respect the native byteorder - dHead.id = (256 * (uint16_t)buffer[1]) + (uint16_t)buffer[0]; - } - - auto iter = conntrack->find(dHead.id ^ d_xor); - if (iter == conntrack->end()) { - g_log << Logger::Error << "Discarding untracked packet from recursor backend with id " << (dHead.id ^ d_xor) << ". Conntrack table size=" << conntrack->size() << endl; - continue; - } - if (iter->second.created == 0) { - g_log << Logger::Error << "Received packet from recursor backend with id " << (dHead.id ^ d_xor) << " which is a duplicate" << endl; - continue; - } - - dHead.id = iter->second.id; - memcpy(&buffer[0], &dHead, sizeof(dHead)); // commit spoofed id - - DNSPacket packet(false); - packet.parse(&buffer[0], (size_t)len); - - if (packet.qtype.getCode() != iter->second.qtype || packet.qdomain != iter->second.qname) { - g_log << Logger::Error << "Discarding packet from recursor backend with id " << (dHead.id ^ d_xor) << ", qname or qtype mismatch (" << packet.qtype.getCode() << " v " << iter->second.qtype << ", " << packet.qdomain << " v " << iter->second.qname << ")" << endl; - continue; - } - - /* Set up iov and msgh structures. */ - memset(&msgh, 0, sizeof(struct msghdr)); - string reply; // needs to be alive at time of sendmsg! - MOADNSParser mdp(false, packet.getString()); - // update the EDNS options with info from the resolver - issue #5469 - // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope - iter->second.complete->d_eso.scope = packet.d_eso.scope; - DLOG(g_log << "from dnsproxy::mainLoop: updated EDNS options from resolver EDNS source: " << iter->second.complete->d_eso.source.toString() << " EDNS scope: " << iter->second.complete->d_eso.scope.toString() << endl); - - if (mdp.d_header.rcode == RCode::NoError) { - for (const auto& answer : mdp.d_answers) { - if (answer.d_place == DNSResourceRecord::ANSWER || (answer.d_place == DNSResourceRecord::AUTHORITY && answer.d_type == QType::SOA)) { - - if (answer.d_type == iter->second.qtype || (iter->second.qtype == QType::ANY && (answer.d_type == QType::A || answer.d_type == QType::AAAA))) { - DNSZoneRecord dzr; - dzr.dr.d_name = iter->second.aname; - dzr.dr.d_type = answer.d_type; - dzr.dr.d_ttl = answer.d_ttl; - dzr.dr.d_place = answer.d_place; - dzr.dr.setContent(answer.getContent()); - iter->second.complete->addRecord(std::move(dzr)); - } - } - } - - iter->second.complete->setRcode(mdp.d_header.rcode); - } - else { - g_log << Logger::Error << "Error resolving for " << iter->second.aname << " ALIAS " << iter->second.qname << " over UDP, " << QType(iter->second.qtype).toString() << "-record query returned " << RCode::to_s(mdp.d_header.rcode) << ", returning SERVFAIL" << endl; - iter->second.complete->clearRecords(); - iter->second.complete->setRcode(RCode::ServFail); - } - reply = iter->second.complete->getString(); - iov.iov_base = (void*)reply.c_str(); - iov.iov_len = reply.length(); - iter->second.complete.reset(); - msgh.msg_iov = &iov; - msgh.msg_iovlen = 1; - msgh.msg_name = (struct sockaddr*)&iter->second.remote; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) - msgh.msg_namelen = iter->second.remote.getSocklen(); - msgh.msg_control = nullptr; - - if (iter->second.anyLocal) { - addCMsgSrcAddr(&msgh, &cbuf, iter->second.anyLocal.get_ptr(), 0); - } - if (sendmsg(iter->second.outsock, &msgh, 0) < 0) { - int err = errno; - g_log << Logger::Warning << "dnsproxy.cc: Error sending reply with sendmsg (socket=" << iter->second.outsock << "): " << stringerror(err) << endl; - } - iter->second.created = 0; - } - } - } - catch (PDNSException& ae) { - g_log << Logger::Error << "Fatal error in DNS proxy: " << ae.reason << endl; - } - catch (std::exception& e) { - g_log << Logger::Error << "Communicator thread died because of STL error: " << e.what() << endl; - } - catch (...) { - g_log << Logger::Error << "Caught unknown exception." << endl; - } - g_log << Logger::Error << "Exiting because DNS proxy failed" << endl; - _exit(1); -} - -DNSProxy::~DNSProxy() -{ - if (d_sock > -1) { - try { - closesocket(d_sock); - } - catch (const PDNSException& e) { - } - } - - d_sock = -1; -} diff --git a/pdns/dnsproxy.hh b/pdns/dnsproxy.hh deleted file mode 100644 index b5ec5de13642..000000000000 --- a/pdns/dnsproxy.hh +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include "dnspacket.hh" -#include "lock.hh" -#include "iputils.hh" - -#include "namespaces.hh" - -/** - -how will this work. - -This is a thread that just throws packets around. Should handle ~1000 packets/second. - -Consists of a thread receiving packets back from the backend and retransmitting them to the original client. - -Furthermore, it provides a member function that reports the packet to the connection tracker and actually sends it out. - -The sending happens from a source port that is determined by the constructor, but IS random. Furthermore, the ID is XOR-ed with a random value -to make sure outside parties can't spoof us. - -To fix: how to remove the stale entries that will surely accumulate -*/ - -class DNSProxy -{ -public: - DNSProxy(const string& remote, const string& udpPortRange); //!< creates socket - ~DNSProxy(); //& reply, const DNSName& target, const DNSName& aname, uint8_t scopeMask); - - void mainloop(); //!< this is the main loop that receives reply packets and sends them out again -private: - struct ConntrackEntry - { - time_t created; - boost::optional anyLocal; - DNSName qname; - std::unique_ptr complete; - DNSName aname; - uint8_t anameScopeMask; - ComboAddress remote; - uint16_t id; - uint16_t qtype; - int outsock; - }; - - using map_t = map; - - // Data - ComboAddress d_remote; - AtomicCounter* d_resanswers; - AtomicCounter* d_udpanswers; - AtomicCounter* d_resquestions; - LockGuarded d_conntrack; - int d_sock; - const uint16_t d_xor; - - static int getID_locked(map_t&); -}; diff --git a/pdns/packethandler.cc b/pdns/packethandler.cc index d06e4724e127..83595b39d404 100644 --- a/pdns/packethandler.cc +++ b/pdns/packethandler.cc @@ -43,11 +43,11 @@ #include "statbag.hh" #include "resolver.hh" #include "communicator.hh" -#include "dnsproxy.hh" #include "version.hh" #include "auth-main.hh" #include "trusted-notification-proxy.hh" #include "gss_context.hh" +#include "stubresolver.hh" #if 0 #undef DLOG @@ -1347,8 +1347,6 @@ std::unique_ptr PacketHandler::doQuestion(DNSPacket& p) vector rrset; bool weDone=false, weRedirected=false, weHaveUnauth=false, doSigs=false; - DNSName haveAlias; - uint8_t aliasScopeMask; std::unique_ptr r{nullptr}; bool noCache=false; @@ -1594,8 +1592,6 @@ std::unique_ptr PacketHandler::doQuestion(DNSPacket& p) // see what we get.. B.lookup(QType(QType::ANY), target, d_sd.domain_id, &p); rrset.clear(); - haveAlias.clear(); - aliasScopeMask = 0; weDone = weRedirected = weHaveUnauth = false; while(B.get(rr)) { @@ -1615,7 +1611,7 @@ std::unique_ptr PacketHandler::doQuestion(DNSPacket& p) for (const auto& r_it : recvec) { rr.dr.d_type = rec->d_type; // might be CNAME rr.dr.setContent(r_it); - rr.scopeMask = p.getRealRemote().getBits(); // this makes sure answer is a specific as your question + rr.scopeMask = p.getRealRemote().getBits(); // this makes sure answer is as specific as your question rrset.push_back(rr); } if(rec->d_type == QType::CNAME && p.qtype.getCode() != QType::CNAME) @@ -1635,6 +1631,63 @@ std::unique_ptr PacketHandler::doQuestion(DNSPacket& p) } } #endif + if (rr.dr.d_type == QType::ALIAS && (p.qtype.getCode() == QType::A || p.qtype.getCode() == QType::AAAA || p.qtype.getCode() == QType::ANY) && !d_dk.isPresigned(d_sd.qname)) { + if (!d_doExpandALIAS) { + g_log<(rr.dr)->getContent(); + if (aliasTarget.empty()) { + continue; + } + + vector ips; + EDNSSubnetOpts eso; + if (r->hasEDNSSubnet()) { + eso.scope = r->d_eso.scope; + eso.source = r->d_eso.source; + } + int ret1 = RCode::NoError; + int ret2 = RCode::NoError; + if (r->qtype == QType::A || r->qtype == QType::ANY) { + ret1 = stubDoResolve(aliasTarget, QType::A, ips, r->hasEDNSSubnet() ? &eso : nullptr); + } + if (r->qtype == QType::AAAA || r->qtype == QType::ANY) { + ret2 = stubDoResolve(aliasTarget, QType::AAAA, ips, r->hasEDNSSubnet() ? &eso : nullptr); + } + + if (ret1 != RCode::NoError || ret2 != RCode::NoError) { + g_log << Logger::Error << "Error resolving for " << target << " ALIAS " << aliasTarget << " over UDP"; + if (ret1 != RCode::NoError) { + g_log << Logger::Error << ", A-record query returned " << RCode::to_s(ret1); + } + if (ret2 != RCode::NoError) { + g_log << Logger::Error << ", AAAA-record query returned " << RCode::to_s(ret2); + } + g_log << Logger::Error << ", returning SERVFAIL" << endl; + + while (B.get(rr)) { + // don't leave DB handle in bad state + } + + r = p.replyPacket(); + r->setRcode(RCode::ServFail); + return r; + } + + for (auto& ip : ips) { // NOLINT(readability-identifier-length) + ip.dr.d_name = target; + if (r->hasEDNSSubnet()) { + // update the EDNS options with info from the resolver - issue #5469 + // note that this relies on the ECS string encoder to use the source network, and only take the prefix length from scope + ip.scopeMask = eso.scope.getBits(); + } + rrset.push_back(ip); + } + + weDone = true; + } + //cerr<<"got content: ["< PacketHandler::doQuestion(DNSPacket& p) if(rr.dr.d_type == QType::CNAME && p.qtype.getCode() != QType::CNAME) weRedirected=true; - if (DP && rr.dr.d_type == QType::ALIAS && (p.qtype.getCode() == QType::A || p.qtype.getCode() == QType::AAAA || p.qtype.getCode() == QType::ANY) && !d_dk.isPresigned(d_sd.qname)) { - if (!d_doExpandALIAS) { - g_log<(rr.dr)->getContent(); - aliasScopeMask=rr.scopeMask; - } - // Filter out all SOA's and add them in later if(rr.dr.d_type == QType::SOA) continue; @@ -1674,20 +1718,13 @@ std::unique_ptr PacketHandler::doQuestion(DNSPacket& p) } - DLOG(g_log<<"After first ANY query for '"<completePacket(r, haveAlias, target, aliasScopeMask); - return nullptr; - } - - // referral for DS query if(p.qtype.getCode() == QType::DS) { DLOG(g_log<<"Qtype is DS"<& ret, const EDNSSubnetOpts* d_eso) +int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, EDNSSubnetOpts* d_eso) { // ensure resolver gets always configured if (!s_stubResolvConfigured) { @@ -176,6 +177,12 @@ int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& r continue; } + if (d_eso != nullptr) { + DNSPacket replyPacket(false); + replyPacket.parse(reply.c_str(), reply.size()); + d_eso->scope = replyPacket.d_eso.scope; + } + for (const auto& answer : mdp.d_answers) { if (answer.d_place == 1 && answer.d_type == qtype) { DNSZoneRecord zrr; @@ -190,7 +197,7 @@ int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& r return RCode::ServFail; } -int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, const EDNSSubnetOpts* d_eso) +int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, EDNSSubnetOpts* d_eso) { vector ret2; int res = stubDoResolve(qname, qtype, ret2, d_eso); diff --git a/pdns/stubresolver.hh b/pdns/stubresolver.hh index 061a3f368697..88f79f4cdfa9 100644 --- a/pdns/stubresolver.hh +++ b/pdns/stubresolver.hh @@ -26,5 +26,5 @@ void stubParseResolveConf(); bool resolversDefined(); -int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, const EDNSSubnetOpts* d_eso = nullptr); -int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, const EDNSSubnetOpts* d_eso = nullptr); +int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, EDNSSubnetOpts* d_eso = nullptr); +int stubDoResolve(const DNSName& qname, uint16_t qtype, vector& ret, EDNSSubnetOpts* d_eso = nullptr); diff --git a/regression-tests.auth-py/authtests.py b/regression-tests.auth-py/authtests.py index 5eb6534df8fe..89e2daa51363 100644 --- a/regression-tests.auth-py/authtests.py +++ b/regression-tests.auth-py/authtests.py @@ -137,6 +137,68 @@ def generateAuthConfig(cls, confdir): except subprocess.CalledProcessError as e: raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output)) + @classmethod + def listKeys(cls, confdir: str, zonename: str): + zone = '.' if zonename == 'ROOT' else zonename + pdnsutilCmd = [os.environ['PDNSUTIL'], + '--config-dir=%s' % confdir, + 'list-keys', + zone] + + print(' '.join(pdnsutilCmd)) + try: + lines = subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output)) + + keys = [] + found_header = False + for line in lines.splitlines(): + if not found_header: + if line.startswith(b"----"): + found_header = True + continue + line = line.decode().split() + if not (len(line) >= 8 and line[1] in ("CSK", "KSK", "ZSK")): + continue + print(line) + keys.append( + { + "zone": line[0], + "type": line[1], + "act": line[2], + "pub": line[3], + "size": int(line[4]), + "algorithm": line[5], + "id": int(line[6]), + "location": line[7], + "keytag": int(line[8]), + }) + return keys + + @classmethod + def exportZoneDnsKey(cls, confdir: str, zonename: str, keyid: int): + zone = '.' if zonename == 'ROOT' else zonename + pdnsutilCmd = [os.environ['PDNSUTIL'], + '--config-dir=%s' % confdir, + 'export-zone-dnskey', + zone, + str(keyid)] + + print(' '.join(pdnsutilCmd)) + try: + lines = subprocess.check_output(pdnsutilCmd, stderr=subprocess.STDOUT) + except subprocess.CalledProcessError as e: + raise AssertionError('%s failed (%d): %s' % (pdnsutilCmd, e.returncode, e.output)) + + for line in lines.splitlines(): + line = line.strip().decode().split(maxsplit=3) + print(line) + if line[2] == "DNSKEY": + _, rdclass, rdtype, key = line + return dns.rrset.from_text(dns.name.from_text(zone), 3600, rdclass, rdtype, key) + return None + @classmethod def secureZone(cls, confdir, zonename, key=None): zone = '.' if zonename == 'ROOT' else zonename @@ -168,6 +230,7 @@ def secureZone(cls, confdir, zonename, key=None): def generateAllAuthConfig(cls, confdir): cls.generateAuthConfig(confdir) cls.generateAuthNamedConf(confdir, cls._zones.keys()) + cls._zone_dnskeys = {} for zonename, zonecontent in cls._zones.items(): cls.generateAuthZone(confdir, @@ -175,6 +238,10 @@ def generateAllAuthConfig(cls, confdir): zonecontent) if cls._zone_keys.get(zonename, None): cls.secureZone(confdir, zonename, cls._zone_keys.get(zonename)) + zone_keys = cls.listKeys(confdir, zonename) + cls._zone_dnskeys[dns.name.from_text(zonename)] = cls.exportZoneDnsKey( + confdir, zonename, zone_keys[-1]["id"] + ) @classmethod def waitForTCPSocket(cls, ipaddress, port): @@ -590,3 +657,16 @@ def assertAuthorityHasSOA(self, msg): if not found: raise AssertionError("No SOA record found in the authority section:\n%s" % msg.to_text()) + + def assertSigned(self, msg: dns.message.Message, name: str | dns.name.Name, rdatatype: str | int): + if not isinstance(msg, dns.message.Message): + raise TypeError("msg is not a dns.message.Message but a %s" % type(msg)) + + name = dns.name.from_text(name) + + if not isinstance(rdatatype, int): + rdatatype = dns.rdatatype.from_text(rdatatype) + + rrset = msg.find_rrset(msg.answer, name, dns.rdataclass.IN, rdatatype) + rrsig = msg.find_rrset(msg.answer, name, dns.rdataclass.IN, dns.rdatatype.RRSIG, covers=rdatatype) + dns.dnssec.validate(rrset, rrsig, self._zone_dnskeys) diff --git a/regression-tests.auth-py/requirements.txt b/regression-tests.auth-py/requirements.txt index f1236ed89f9b..aaa56746ff34 100644 --- a/regression-tests.auth-py/requirements.txt +++ b/regression-tests.auth-py/requirements.txt @@ -1,3 +1,4 @@ +cryptography dnspython==2.1.0 pytest Twisted>0.15.0 diff --git a/regression-tests.auth-py/test_ALIAS.py b/regression-tests.auth-py/test_ALIAS.py index 8cd7f4f3deb7..8bb8088e7865 100644 --- a/regression-tests.auth-py/test_ALIAS.py +++ b/regression-tests.auth-py/test_ALIAS.py @@ -62,32 +62,37 @@ def startResponders(cls): cls._ALIASResponder.start() def testNoError(self): - expected_a = [dns.rrset.from_text('noerror.example.org.', + name = 'noerror.example.org.' + expected_a = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1')] - expected_aaaa = [dns.rrset.from_text('noerror.example.org.', + expected_aaaa = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:DB8::1')] - query = dns.message.make_query('noerror.example.org', 'A') + query = dns.message.make_query(name, 'A', want_dnssec=True) res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_a) self.assertEqual(len(res.options), 0) # this checks that we don't invent ECS on non-ECS queries + self.assertSigned(res, name, dns.rdatatype.A) - query = dns.message.make_query('noerror.example.org', 'AAAA') + query = dns.message.make_query(name, 'AAAA', want_dnssec=True) res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_aaaa) + self.assertSigned(res, name, dns.rdatatype.AAAA) - query = dns.message.make_query('noerror.example.org', 'ANY') + query = dns.message.make_query(name, 'ANY', want_dnssec=True) res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_a) self.assertAnyRRsetInAnswer(res, expected_aaaa) + self.assertSigned(res, name, dns.rdatatype.A) + self.assertSigned(res, name, dns.rdatatype.AAAA) # NODATA - query = dns.message.make_query('noerror.example.org', 'MX') + query = dns.message.make_query(name, 'MX') res = self.sendUDPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertEqual(len(res.answer), 0) @@ -121,31 +126,36 @@ def testServFail(self): self.assertRcodeEqual(res, dns.rcode.SERVFAIL) def testNoErrorTCP(self): - expected_a = [dns.rrset.from_text('noerror.example.org.', + name = 'noerror.example.org.' + expected_a = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1')] - expected_aaaa = [dns.rrset.from_text('noerror.example.org.', + expected_aaaa = [dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:DB8::1')] - query = dns.message.make_query('noerror.example.org', 'A') + query = dns.message.make_query(name, 'A', want_dnssec=True) res = self.sendTCPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_a) + self.assertSigned(res, name, dns.rdatatype.A) - query = dns.message.make_query('noerror.example.org', 'AAAA') + query = dns.message.make_query(name, 'AAAA', want_dnssec=True) res = self.sendTCPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_aaaa) + self.assertSigned(res, name, dns.rdatatype.AAAA) - query = dns.message.make_query('noerror.example.org', 'ANY') + query = dns.message.make_query(name, 'ANY', want_dnssec=True) res = self.sendTCPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertAnyRRsetInAnswer(res, expected_a) self.assertAnyRRsetInAnswer(res, expected_aaaa) + self.assertSigned(res, name, dns.rdatatype.A) + self.assertSigned(res, name, dns.rdatatype.AAAA) # NODATA - query = dns.message.make_query('noerror.example.org', 'MX') + query = dns.message.make_query(name, 'MX', want_dnssec=True) res = self.sendTCPQuery(query) self.assertRcodeEqual(res, dns.rcode.NOERROR) self.assertEqual(len(res.answer), 0) diff --git a/regression-tests.auth-py/test_GSSTSIG.py b/regression-tests.auth-py/test_GSSTSIG.py index 2297f46c2a98..ec30f2089c3f 100644 --- a/regression-tests.auth-py/test_GSSTSIG.py +++ b/regression-tests.auth-py/test_GSSTSIG.py @@ -33,6 +33,9 @@ class GSSTSIGBase(AuthTest): 'KRB5_KTNAME' : './kerberos-client/kt.keytab' } + # zones will be created by our own setUpClass() code + _zones = {} + @classmethod def setUpClass(cls): super(GSSTSIGBase, cls).setUpClass() diff --git a/regression-tests.auth-py/test_XFRIncomplete.py b/regression-tests.auth-py/test_XFRIncomplete.py index 3db145e293f1..effbf19f3653 100644 --- a/regression-tests.auth-py/test_XFRIncomplete.py +++ b/regression-tests.auth-py/test_XFRIncomplete.py @@ -154,6 +154,8 @@ class XFRIncompleteAuthTest(AuthTest): #axfr-fetch-timeout=20 """ + _zones = {} # zone setup happens in setUpClass + @classmethod def setUpClass(cls): super(XFRIncompleteAuthTest, cls).setUpClass()