From: Pieter Lexis Date: Wed, 21 Feb 2018 15:36:00 +0000 (+0100) Subject: ixfrdist: Keep latest AXFR and `--keep` IXFR's in memory X-Git-Tag: dnsdist-1.3.0~87^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e2cddb03bbc053887e076d8bd5c23cf9d34e4fdd;p=thirdparty%2Fpdns.git ixfrdist: Keep latest AXFR and `--keep` IXFR's in memory This prevents us going to the disk for every incoming query. --- diff --git a/pdns/ixfrdist.cc b/pdns/ixfrdist.cc index f2f6151129..b048e9db89 100644 --- a/pdns/ixfrdist.cc +++ b/pdns/ixfrdist.cc @@ -58,8 +58,8 @@ FDMultiplexer* g_fdm; // The domains we support set g_domains; -// Map domains to SOA Records and have a mutex to update it. -std::map> g_soas; +// Map domains and their data +std::map g_soas; std::mutex g_soas_mutex; using namespace boost::multi_index; @@ -154,6 +154,26 @@ void cleanUpDomain(const DNSName& domain) { } } +shared_ptr getSOAFromRecords(const records_t& records) { + for (const auto& dnsrecord : records) { + if (dnsrecord.d_type == QType::SOA) { + auto soa = getRR(dnsrecord); + if (soa == nullptr) { + throw PDNSException("Unable to determine SOARecordContent from old records"); + } + return soa; + } + } + throw PDNSException("No SOA in supplied records"); +} + +void makeIXFRDiff(const records_t& from, const records_t& to, ixfrdiff_t& diff) { + set_difference(from.cbegin(), from.cend(), to.cbegin(), to.cend(), back_inserter(diff.removals), from.value_comp()); + set_difference(to.cbegin(), to.cend(), from.cbegin(), from.cend(), back_inserter(diff.additions), from.value_comp()); + diff.oldSOA = getSOAFromRecords(from); + diff.newSOA = getSOAFromRecords(to); +} + void updateThread() { std::map lastCheck; @@ -162,16 +182,27 @@ void updateThread() { lastCheck[domain] = 0; string dir = g_workdir + "/" + domain.toString(); try { + if (g_verbose) { + cerr<<"[INFO] Trying to initially load domain "< soa; { - loadSOAFromDisk(domain, g_workdir + "/" + domain.toString() + "/" + std::to_string(serial), soa); - std::lock_guard guard(g_soas_mutex); + string fname = g_workdir + "/" + domain.toString() + "/" + std::to_string(serial); + loadSOAFromDisk(domain, fname, soa); + records_t records; if (soa != nullptr) { - g_soas[domain] = soa; + loadZoneFromDisk(records, fname, domain); } + std::lock_guard guard(g_soas_mutex); + g_soas[domain] = ixfrinfo_t(); + g_soas[domain].latestAXFR = records; + g_soas[domain].soa = soa; } if (soa != nullptr) { + if (g_verbose) { + cerr<<"[INFO] Loaded zone "<d_st.serial< guard(g_soas_mutex); if (g_soas.find(domain) != g_soas.end()) { - current_soa = g_soas[domain]; + current_soa = g_soas[domain].soa; } } if ((current_soa != nullptr && now - lastCheck[domain] < current_soa->d_st.refresh) || // Only check if we have waited `refresh` seconds @@ -283,7 +314,17 @@ void updateThread() { } { std::lock_guard guard(g_soas_mutex); - g_soas[domain] = soa; + ixfrdiff_t diff; + if (!g_soas[domain].latestAXFR.empty()) { + makeIXFRDiff(g_soas[domain].latestAXFR, records, diff); + g_soas[domain].ixfrDiffs.push_back(diff); + } + // Clean up the diffs + while (g_soas[domain].ixfrDiffs.size() > g_keep) { + g_soas[domain].ixfrDiffs.erase(g_soas[domain].ixfrDiffs.begin()); + } + g_soas[domain].latestAXFR = records; + g_soas[domain].soa = soa; } } catch (PDNSException &e) { cerr<<"[WARNING] Could not retrieve AXFR for '"<& packet) { pw.startRecord(mdp.d_qname, QType::SOA); { std::lock_guard guard(g_soas_mutex); - g_soas[mdp.d_qname]->toPacket(pw); + g_soas[mdp.d_qname].soa->toPacket(pw); } pw.commit(); @@ -378,22 +419,13 @@ vector getSOAPacket(const MOADNSParser& mdp, const shared_ptr>& packets) { - string dir = g_workdir + "/" + mdp.d_qname.toString(); - auto serial = getSerialsFromDir(dir); - string fname = dir + "/" + std::to_string(serial); - // Use the SOA from the file, the one in g_soas _may_ have changed shared_ptr soa; - loadSOAFromDisk(mdp.d_qname, fname, soa); - if (soa == nullptr) { - // :( - cerr<<"[WARNING] Could not retrieve SOA record from "< guard(g_soas_mutex); + soa = g_soas[mdp.d_qname].soa; + records = g_soas[mdp.d_qname].latestAXFR; } // Initial SOA @@ -441,16 +473,16 @@ void makeXFRPacketsFromDNSRecords(const MOADNSParser& mdp, const vector& clientSOA, vector>& packets) { - string dir = g_workdir + "/" + mdp.d_qname.toString(); // Get the new SOA only once, so it will not change under our noses from the // updateThread. - uint32_t newSerial; + vector toSend; + uint32_t ourLatestSerial; { std::lock_guard guard(g_soas_mutex); - newSerial = g_soas[mdp.d_qname]->d_st.serial; + ourLatestSerial = g_soas[mdp.d_qname].soa->d_st.serial; } - if (rfc1982LessThan(newSerial, clientSOA->d_st.serial)){ + if (rfc1982LessThan(ourLatestSerial, clientSOA->d_st.serial) || ourLatestSerial == clientSOA->d_st.serial){ /* RFC 1995 Section 2 * If an IXFR query with the same or newer version number than that of * the server is received, it is replied to with a single SOA record of @@ -464,77 +496,45 @@ bool makeIXFRPackets(const MOADNSParser& mdp, const shared_ptr return ret; } - // Let's see if we have the old zone - string oldZoneFname = dir + "/" + std::to_string(clientSOA->d_st.serial); - string newZoneFname = dir + "/" + std::to_string(newSerial); - records_t oldRecords, newRecords; - shared_ptr newSOA; { - // Make sure the update thread does not clean this in front of our feet + // as we use push_back in the updater, we know the vector is sorted as oldest first + bool shouldAdd = false; + // Get all relevant IXFR differences std::lock_guard guard(g_soas_mutex); - - // Check if we can actually make an IXFR - struct stat s; - if (stat(oldZoneFname.c_str(), &s) == -1) { - if (errno == ENOENT) { - if (g_verbose) { - cerr<<"[INFO] IXFR for "<d_st.serial<<", sending out AXFR"<d_st.serial == clientSOA->d_st.serial) { + toSend.push_back(diff); + // Add all consecutive diffs + shouldAdd = true; } - cerr<<"[WARNING] Could not determine existence of "<d_st.serial<<" for zone "< diff; - set_difference(oldRecords.cbegin(), oldRecords.cend(), newRecords.cbegin(), newRecords.cend(), back_inserter(diff), oldRecords.value_comp()); - makeXFRPacketsFromDNSRecords(mdp, diff, packets); - - // Added records - packets.push_back(getSOAPacket(mdp, newSOA)); - - diff.clear(); - - set_difference(newRecords.cbegin(), newRecords.cend(), oldRecords.cbegin(), oldRecords.cend(), back_inserter(diff), oldRecords.value_comp()); - makeXFRPacketsFromDNSRecords(mdp, diff, packets); - - // Final SOA - packets.push_back(getSOAPacket(mdp, newSOA)); + for (const auto& diff : toSend) { + /* An IXFR packet's ANSWER section looks as follows: + * SOA new_serial + * SOA old_serial + * ... removed records ... + * SOA new_serial + * ... added records ... + * SOA new_serial + */ + packets.push_back(getSOAPacket(mdp, diff.newSOA)); + packets.push_back(getSOAPacket(mdp, diff.oldSOA)); + makeXFRPacketsFromDNSRecords(mdp, diff.removals, packets); + packets.push_back(getSOAPacket(mdp, diff.newSOA)); + makeXFRPacketsFromDNSRecords(mdp, diff.additions, packets); + packets.push_back(getSOAPacket(mdp, diff.newSOA)); + } return true; } diff --git a/pdns/ixfrutils.hh b/pdns/ixfrutils.hh index f787f6dde4..f7e0c5a848 100644 --- a/pdns/ixfrutils.hh +++ b/pdns/ixfrutils.hh @@ -55,3 +55,16 @@ uint32_t getSerialFromRecords(const records_t& records, DNSRecord& soaret); void writeZoneToDisk(const records_t& records, const DNSName& zone, const std::string& directory); void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zone); void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr& soa); + +struct ixfrdiff_t { + shared_ptr oldSOA; + shared_ptr newSOA; + vector removals; + vector additions; +}; + +struct ixfrinfo_t { + shared_ptr soa; // The SOA of the latestAXFR + records_t latestAXFR; // The most recent AXFR + vector ixfrDiffs; +};