From: Remi Gacogne Date: Tue, 18 Jan 2022 15:29:14 +0000 (+0100) Subject: dnsdist: Add functions to look into the content of the cache X-Git-Tag: rec-4.9.0-alpha0~13^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fec4382e7bb4ad31b8bf2dda766856fb5f6a9b78;p=thirdparty%2Fpdns.git dnsdist: Add functions to look into the content of the cache --- diff --git a/pdns/dnsdist-cache.cc b/pdns/dnsdist-cache.cc index 7222a29162..b884df5002 100644 --- a/pdns/dnsdist-cache.cc +++ b/pdns/dnsdist-cache.cc @@ -489,3 +489,110 @@ void DNSDistPacketCache::setSkippedOptions(const std::unordered_set& o { d_optionsToSkip = optionsToSkip; } + +std::set DNSDistPacketCache::getDomainsContainingRecords(const ComboAddress& addr) +{ + std::set domains; + + for (auto& shard : d_shards) { + auto map = shard.d_map.read_lock(); + + for (const auto& entry : *map) { + const CacheValue& value = entry.second; + + try { + dnsheader dh; + if (value.len >= sizeof(dnsheader)) { + memcpy(&dh, value.value.data(), sizeof(dnsheader)); + } + if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { + continue; + } + + bool found = false; + bool valid = visitDNSPacket(value.value, [addr, &found](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl, uint16_t rdatalength, const char* rdata) { + + if (qtype == QType::A && qclass == QClass::IN && addr.isIPv4() && rdatalength == 4 && rdata != nullptr) { + ComboAddress parsed; + parsed.sin4.sin_family = AF_INET; + memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); + if (parsed == addr) { + found = true; + return true; + } + } + else if (qtype == QType::AAAA && qclass == QClass::IN && addr.isIPv6() && rdatalength == 16 && rdata != nullptr) { + ComboAddress parsed; + parsed.sin6.sin6_family = AF_INET6; + memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); + if (parsed == addr) { + found = true; + return true; + } + } + + return false; + }); + + if (valid && found) { + domains.insert(value.qname); + } + } + catch (...) { + continue; + } + } + } + + return domains; +} + +std::set DNSDistPacketCache::getRecordsForDomain(const DNSName& domain) +{ + std::set addresses; + + for (auto& shard : d_shards) { + auto map = shard.d_map.read_lock(); + + for (const auto& entry : *map) { + const CacheValue& value = entry.second; + + try { + if (value.qname != domain) { + continue; + } + + dnsheader dh; + if (value.len >= sizeof(dnsheader)) { + memcpy(&dh, value.value.data(), sizeof(dnsheader)); + } + if (dh.rcode != RCode::NoError || (dh.ancount == 0 && dh.nscount == 0 && dh.arcount == 0)) { + continue; + } + + visitDNSPacket(value.value, [&addresses](uint8_t section, uint16_t qclass, uint16_t qtype, uint32_t ttl, uint16_t rdatalength, const char* rdata) { + + if (qtype == QType::A && qclass == QClass::IN && rdatalength == 4 && rdata != nullptr) { + ComboAddress parsed; + parsed.sin4.sin_family = AF_INET; + memcpy(&parsed.sin4.sin_addr.s_addr, rdata, rdatalength); + addresses.insert(parsed); + } + else if (qtype == QType::AAAA && qclass == QClass::IN && rdatalength == 16 && rdata != nullptr) { + ComboAddress parsed; + parsed.sin6.sin6_family = AF_INET6; + memcpy(&parsed.sin6.sin6_addr.s6_addr, rdata, rdatalength); + addresses.insert(parsed); + } + + return false; + }); + } + catch (...) { + continue; + } + } + } + + return addresses; +} diff --git a/pdns/dnsdist-cache.hh b/pdns/dnsdist-cache.hh index 23b321375c..2ef6b7a872 100644 --- a/pdns/dnsdist-cache.hh +++ b/pdns/dnsdist-cache.hh @@ -56,6 +56,12 @@ public: uint64_t getCleanupCount() const { return d_cleanupCount; } uint64_t getEntriesCount(); uint64_t dump(int fd); + + /* get the list of domains (qnames) that contains the given address in an A or AAAA record */ + std::set getDomainsContainingRecords(const ComboAddress& addr); + /* get the list of IP addresses contained in A or AAAA for a given domains (qname) */ + std::set getRecordsForDomain(const DNSName& domain); + void setSkippedOptions(const std::unordered_set& optionsToSkip); bool isECSParsingEnabled() const { return d_parseECS; } diff --git a/pdns/dnsparser.cc b/pdns/dnsparser.cc index 2e21461208..20ba535e07 100644 --- a/pdns/dnsparser.cc +++ b/pdns/dnsparser.cc @@ -1129,3 +1129,54 @@ bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payl return false; } + +bool visitDNSPacket(const std::string_view& packet, const std::function& visitor) +{ + if (packet.size() < sizeof(dnsheader)) { + return false; + } + + try + { + dnsheader dh; + memcpy(&dh, reinterpret_cast(packet.data()), sizeof(dh)); + uint64_t numrecords = ntohs(dh.ancount) + ntohs(dh.nscount) + ntohs(dh.arcount); + PacketReader reader(packet); + + uint64_t n; + for (n = 0; n < ntohs(dh.qdcount) ; ++n) { + (void) reader.getName(); + /* type and class */ + reader.skip(4); + } + + for (n = 0; n < numrecords; ++n) { + (void) reader.getName(); + + uint8_t section = n < ntohs(dh.ancount) ? 1 : (n < (ntohs(dh.ancount) + ntohs(dh.nscount)) ? 2 : 3); + uint16_t dnstype = reader.get16BitInt(); + uint16_t dnsclass = reader.get16BitInt(); + + if (dnstype == QType::OPT) { + // not getting near that one with a stick + break; + } + + uint32_t dnsttl = reader.get32BitInt(); + uint16_t contentLength = reader.get16BitInt(); + uint16_t pos = reader.getPosition(); + + bool done = visitor(section, dnsclass, dnstype, dnsttl, contentLength, &packet.at(pos)); + if (done) { + return true; + } + + reader.skip(contentLength); + } + } + catch (...) { + return false; + } + + return true; +} diff --git a/pdns/dnsparser.hh b/pdns/dnsparser.hh index 3636677720..9d8e14a54a 100644 --- a/pdns/dnsparser.hh +++ b/pdns/dnsparser.hh @@ -470,6 +470,9 @@ uint32_t getDNSPacketMinTTL(const char* packet, size_t length, bool* seenAuthSOA uint32_t getDNSPacketLength(const char* packet, size_t length); uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type); bool getEDNSUDPPayloadSizeAndZ(const char* packet, size_t length, uint16_t* payloadSize, uint16_t* z); +/* call the visitor for every records in the answer, authority and additional sections, passing the section, class, type, ttl, rdatalength and rdata + to the visitor. Stops whenever the visitor returns false or at the end of the packet */ +bool visitDNSPacket(const std::string_view& packet, const std::function& visitor); template std::shared_ptr getRR(const DNSRecord& dr) diff --git a/pdns/test-dnsdistpacketcache_cc.cc b/pdns/test-dnsdistpacketcache_cc.cc index 4be01c2fcf..02050ff875 100644 --- a/pdns/test-dnsdistpacketcache_cc.cc +++ b/pdns/test-dnsdistpacketcache_cc.cc @@ -124,7 +124,6 @@ BOOST_AUTO_TEST_CASE(test_PacketCacheSimple) { } } - BOOST_AUTO_TEST_CASE(test_PacketCacheSharded) { const size_t maxEntries = 150000; const size_t numberOfShards = 10; @@ -722,4 +721,247 @@ BOOST_AUTO_TEST_CASE(test_PCDNSSECCollision) { } +BOOST_AUTO_TEST_CASE(test_PacketCacheInspection) { + const size_t maxEntries = 100; + DNSDistPacketCache PC(maxEntries, 86400, 1); + BOOST_CHECK_EQUAL(PC.getSize(), 0U); + struct timespec queryTime; + gettime(&queryTime); // does not have to be accurate ("realTime") in tests + + ComboAddress remote; + bool dnssecOK = false; + + uint32_t key = 0; + + /* insert powerdns.com A 192.0.2.1, 192.0.2.2 */ + { + DNSName qname("powerdns.com"); + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, qname, QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + PacketBuffer response; + GenericDNSPacketWriter pwR(response, qname, QType::A, QClass::IN, 0); + pwR.getHeader()->rd = 1; + pwR.getHeader()->ra = 1; + pwR.getHeader()->qr = 1; + pwR.getHeader()->id = pwQ.getHeader()->id; + { + ComboAddress addr("192.0.2.1"); + pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); + pwR.xfrCAWithoutPort(4, addr); + pwR.commit(); + } + { + ComboAddress addr("192.0.2.2"); + pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); + pwR.xfrCAWithoutPort(4, addr); + pwR.commit(); + } + + PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none); + BOOST_CHECK_EQUAL(PC.getSize(), key); + } + + /* insert powerdns1.com A 192.0.2.3, 192.0.2.4, AAAA 2001:db8::3, 2001:db8::4 */ + { + DNSName qname("powerdns1.com"); + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, qname, QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + PacketBuffer response; + GenericDNSPacketWriter pwR(response, qname, QType::A, QClass::IN, 0); + pwR.getHeader()->rd = 1; + pwR.getHeader()->ra = 1; + pwR.getHeader()->qr = 1; + pwR.getHeader()->id = pwQ.getHeader()->id; + { + ComboAddress addr("192.0.2.3"); + pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); + pwR.xfrCAWithoutPort(4, addr); + pwR.commit(); + } + { + ComboAddress addr("192.0.2.4"); + pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER); + pwR.xfrCAWithoutPort(4, addr); + pwR.commit(); + } + { + ComboAddress addr("2001:db8::3"); + pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrCAWithoutPort(6, addr); + pwR.commit(); + } + { + ComboAddress addr("2001:db8::4"); + pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrCAWithoutPort(6, addr); + pwR.commit(); + } + + PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none); + BOOST_CHECK_EQUAL(PC.getSize(), key); + } + + /* insert powerdns2.com NODATA */ + { + DNSName qname("powerdns2.com"); + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, qname, QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + PacketBuffer response; + GenericDNSPacketWriter pwR(response, qname, QType::A, QClass::IN, 0); + pwR.getHeader()->rd = 1; + pwR.getHeader()->ra = 1; + pwR.getHeader()->qr = 1; + pwR.getHeader()->id = pwQ.getHeader()->id; + pwR.commit(); + pwR.startRecord(qname, QType::SOA, 86400, QClass::IN, DNSResourceRecord::AUTHORITY); + pwR.commit(); + pwR.addOpt(4096, 0, 0); + pwR.commit(); + + PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none); + BOOST_CHECK_EQUAL(PC.getSize(), key); + } + + /* insert powerdns3.com AAAA 2001:db8::4, 2001:db8::5 */ + { + DNSName qname("powerdns3.com"); + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, qname, QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + PacketBuffer response; + GenericDNSPacketWriter pwR(response, qname, QType::A, QClass::IN, 0); + pwR.getHeader()->rd = 1; + pwR.getHeader()->ra = 1; + pwR.getHeader()->qr = 1; + pwR.getHeader()->id = pwQ.getHeader()->id; + { + ComboAddress addr("2001:db8::4"); + pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrCAWithoutPort(6, addr); + pwR.commit(); + } + { + ComboAddress addr("2001:db8::5"); + pwR.startRecord(qname, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrCAWithoutPort(6, addr); + pwR.commit(); + } + + PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none); + BOOST_CHECK_EQUAL(PC.getSize(), key); + } + + /* insert powerdns4.com A 192.0.2.1 */ + { + DNSName qname("powerdns4.com"); + PacketBuffer query; + GenericDNSPacketWriter pwQ(query, qname, QType::A, QClass::IN, 0); + pwQ.getHeader()->rd = 1; + + PacketBuffer response; + GenericDNSPacketWriter pwR(response, qname, QType::A, QClass::IN, 0); + pwR.getHeader()->rd = 1; + pwR.getHeader()->ra = 1; + pwR.getHeader()->qr = 1; + pwR.getHeader()->id = pwQ.getHeader()->id; + { + ComboAddress addr("192.0.2.1"); + pwR.startRecord(qname, QType::A, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL); + pwR.xfrCAWithoutPort(4, addr); + pwR.commit(); + } + + PC.insert(key++, boost::none, *getFlagsFromDNSHeader(pwQ.getHeader()), dnssecOK, qname, QType::A, QClass::IN, response, receivedOverUDP, 0, boost::none); + BOOST_CHECK_EQUAL(PC.getSize(), key); + } + + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.1")); + BOOST_CHECK_EQUAL(domains.size(), 2U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns4.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.2")); + BOOST_CHECK_EQUAL(domains.size(), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.3")); + BOOST_CHECK_EQUAL(domains.size(), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.4")); + BOOST_CHECK_EQUAL(domains.size(), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("192.0.2.5")); + BOOST_CHECK_EQUAL(domains.size(), 0U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::3")); + BOOST_CHECK_EQUAL(domains.size(), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::4")); + BOOST_CHECK_EQUAL(domains.size(), 2U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns1.com")), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U); + } + { + auto domains = PC.getDomainsContainingRecords(ComboAddress("2001:db8::5")); + BOOST_CHECK_EQUAL(domains.size(), 1U); + BOOST_CHECK_EQUAL(domains.count(DNSName("powerdns3.com")), 1U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns.com")); + BOOST_CHECK_EQUAL(records.size(), 2U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.2")), 1U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns1.com")); + BOOST_CHECK_EQUAL(records.size(), 4U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.3")), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.4")), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::3")), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns2.com")); + BOOST_CHECK_EQUAL(records.size(), 0U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns3.com")); + BOOST_CHECK_EQUAL(records.size(), 2U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("2001:db8::4")), 1U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns4.com")); + BOOST_CHECK_EQUAL(records.size(), 1U); + BOOST_CHECK_EQUAL(records.count(ComboAddress("192.0.2.1")), 1U); + } + + { + auto records = PC.getRecordsForDomain(DNSName("powerdns5.com")); + BOOST_CHECK_EQUAL(records.size(), 0U); + } +} + BOOST_AUTO_TEST_SUITE_END()