From d56c144398601ba38583aba839a3f821eded2f07 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Mon, 26 Nov 2018 14:42:25 +0100 Subject: [PATCH] ixfrdist: Preserve the correct TTL for SOA records --- pdns/ixfrdist.cc | 42 +++++++++++++++++--------- pdns/ixfrutils.cc | 3 +- pdns/ixfrutils.hh | 2 +- regression-tests.ixfrdist/test_IXFR.py | 13 ++++++++ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/pdns/ixfrdist.cc b/pdns/ixfrdist.cc index 783880195e..b3065704cb 100644 --- a/pdns/ixfrdist.cc +++ b/pdns/ixfrdist.cc @@ -127,12 +127,15 @@ struct ixfrdiff_t { shared_ptr newSOA; vector removals; vector additions; + uint32_t oldSOATTL; + uint32_t newSOATTL; }; struct ixfrinfo_t { shared_ptr soa; // The SOA of the latest AXFR records_t latestAXFR; // The most recent AXFR vector> ixfrDiffs; + uint32_t soaTTL; }; // Why a struct? This way we can add more options to a domain in the future @@ -224,29 +227,32 @@ static void cleanUpDomain(const DNSName& domain, const uint16_t& keep, const str } } -static shared_ptr getSOAFromRecords(const records_t& records) { +static void getSOAFromRecords(const records_t& records, shared_ptr& soa, uint32_t& soaTTL) { for (const auto& dnsrecord : records) { if (dnsrecord.d_type == QType::SOA) { - auto soa = getRR(dnsrecord); + soa = getRR(dnsrecord); if (soa == nullptr) { throw PDNSException("Unable to determine SOARecordContent from old records"); } - return soa; + soaTTL = dnsrecord.d_ttl; + return; } } throw PDNSException("No SOA in supplied records"); } -static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr& diff, const shared_ptr& fromSOA = nullptr, const shared_ptr& toSOA = nullptr) { +static void makeIXFRDiff(const records_t& from, const records_t& to, std::shared_ptr& diff, const shared_ptr& fromSOA = nullptr, uint32_t fromSOATTL=0, const shared_ptr& toSOA = nullptr, uint32_t toSOATTL = 0) { 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 = fromSOA; + diff->oldSOATTL = fromSOATTL; if (fromSOA == nullptr) { - diff->oldSOA = getSOAFromRecords(from); + getSOAFromRecords(from, diff->oldSOA, diff->oldSOATTL); } diff->newSOA = toSOA; + diff->newSOATTL = toSOATTL; if (toSOA == nullptr) { - diff->newSOA = getSOAFromRecords(to); + getSOAFromRecords(to, diff->newSOA, diff->newSOATTL); } } @@ -278,9 +284,10 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a g_log< soa; + uint32_t soaTTL; { string fname = workdir + "/" + domain.toString() + "/" + std::to_string(serial); - loadSOAFromDisk(domain, fname, soa); + loadSOAFromDisk(domain, fname, soa, soaTTL); records_t records; if (soa != nullptr) { loadZoneFromDisk(records, fname, domain); @@ -288,6 +295,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a auto zoneInfo = std::make_shared(); zoneInfo->latestAXFR = std::move(records); zoneInfo->soa = soa; + zoneInfo->soaTTL = soaTTL; updateCurrentZoneInfo(domain, zoneInfo); } if (soa != nullptr) { @@ -301,7 +309,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a // Attempt to create it, if _that_ fails, there is no hope if (mkdir(dir.c_str(), 0777) == -1 && errno != EEXIST) { g_log< soa; + uint32_t soaTTL = 0; records_t records; try { AXFRRetriever axfr(master, domain, tt, &local); @@ -385,6 +394,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a nrecords++; if (dr.d_type == QType::SOA) { soa = getRR(dr); + soaTTL = dr.d_ttl; } } axfr_now = time(nullptr); @@ -421,7 +431,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a auto diff = std::make_shared(); zoneInfo->ixfrDiffs = oldZoneInfo->ixfrDiffs; g_log<latestAXFR, records, diff, oldZoneInfo->soa, soa); + makeIXFRDiff(oldZoneInfo->latestAXFR, records, diff, oldZoneInfo->soa, oldZoneInfo->soaTTL, soa, soaTTL); g_log<removals.size()<<" removals and "<additions.size()<<" additions"<ixfrDiffs.push_back(std::move(diff)); } @@ -434,6 +444,7 @@ void updateThread(const string& workdir, const uint16_t& keep, const uint16_t& a g_log<latestAXFR.size() : 0)<<" entries, "<latestAXFR = std::move(records); zoneInfo->soa = soa; + zoneInfo->soaTTL = soaTTL; updateCurrentZoneInfo(domain, zoneInfo); } catch (PDNSException &e) { g_stats.incrementAXFRFailures(domain); @@ -509,7 +520,7 @@ static bool makeSOAPacket(const MOADNSParser& mdp, vector& packet) { pw.getHeader()->rd = mdp.d_header.rd; pw.getHeader()->qr = 1; - pw.startRecord(mdp.d_qname, QType::SOA); + pw.startRecord(mdp.d_qname, QType::SOA, zoneInfo->soaTTL); zoneInfo->soa->toPacket(pw); pw.commit(); @@ -530,7 +541,7 @@ static bool makeRefusedPacket(const MOADNSParser& mdp, vector& packet) return true; } -static vector getSOAPacket(const MOADNSParser& mdp, const shared_ptr& soa) { +static vector getSOAPacket(const MOADNSParser& mdp, const shared_ptr& soa, uint32_t soaTTL) { vector packet; DNSPacketWriter pw(packet, mdp.d_qname, mdp.d_qtype); pw.getHeader()->id = mdp.d_header.id; @@ -538,7 +549,7 @@ static vector getSOAPacket(const MOADNSParser& mdp, const shared_ptrqr = 1; // Add the first SOA - pw.startRecord(mdp.d_qname, QType::SOA); + pw.startRecord(mdp.d_qname, QType::SOA, soaTTL); soa->toPacket(pw); pw.commit(); return packet; @@ -626,10 +637,11 @@ static bool handleAXFR(int fd, const MOADNSParser& mdp) { } shared_ptr soa = zoneInfo->soa; + uint32_t soaTTL = zoneInfo->soaTTL; const records_t& records = zoneInfo->latestAXFR; // Initial SOA - const auto soaPacket = getSOAPacket(mdp, soa); + const auto soaPacket = getSOAPacket(mdp, soa, soaTTL); if (!sendPacketOverTCP(fd, soaPacket)) { return false; } @@ -712,8 +724,8 @@ static bool handleIXFR(int fd, const ComboAddress& destination, const MOADNSPars * SOA new_serial */ - const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA); - const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA); + const auto newSOAPacket = getSOAPacket(mdp, diff->newSOA, diff->newSOATTL); + const auto oldSOAPacket = getSOAPacket(mdp, diff->oldSOA, diff->oldSOATTL); if (!sendPacketOverTCP(fd, newSOAPacket)) { return false; diff --git a/pdns/ixfrutils.cc b/pdns/ixfrutils.cc index c7757042ba..df4e8500dc 100644 --- a/pdns/ixfrutils.cc +++ b/pdns/ixfrutils.cc @@ -166,7 +166,7 @@ void loadZoneFromDisk(records_t& records, const string& fname, const DNSName& zo * Load the zone `zone` from `fname` and put the first found SOA into `soa` * Does NOT check for nullptr */ -void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr& soa) +void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr& soa, uint32_t& soaTTL) { ZoneParserTNG zpt(fname, zone); DNSResourceRecord rr; @@ -174,6 +174,7 @@ void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr(DNSRecord(rr)); + soaTTL = rr.ttl; return; } } diff --git a/pdns/ixfrutils.hh b/pdns/ixfrutils.hh index 78bdc051be..ea66bfa90d 100644 --- a/pdns/ixfrutils.hh +++ b/pdns/ixfrutils.hh @@ -54,4 +54,4 @@ uint32_t getSerialsFromDir(const std::string& dir); 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); +void loadSOAFromDisk(const DNSName& zone, const string& fname, shared_ptr& soa, uint32_t& soaTTL); diff --git a/regression-tests.ixfrdist/test_IXFR.py b/regression-tests.ixfrdist/test_IXFR.py index bc97f1bdca..986a741385 100644 --- a/regression-tests.ixfrdist/test_IXFR.py +++ b/regression-tests.ixfrdist/test_IXFR.py @@ -106,6 +106,14 @@ class IXFRDistBasicTest(IXFRDistTest): # answers[1].sort(key=lambda rrset: (rrset.name, rrset.rdtype)) self.assertEqual(answers, expected) + # check the TTLs + answerPos = 0 + for expectedAnswer in expected: + pos = 0 + for rec in expectedAnswer: + self.assertEquals(rec.ttl, answers[answerPos][pos].ttl) + pos = pos + 1 + answerPos = answerPos + 1 def test_a_XFR(self): self.waitUntilCorrectSerialIsLoaded(1) @@ -124,6 +132,11 @@ class IXFRDistBasicTest(IXFRDistTest): response = self.sendUDPQuery(query) self.assertEquals(expected, response) + # check the TTLs + pos = 0 + for rec in expected.answer: + self.assertEquals(rec.ttl, response.answer[pos].ttl) + pos = pos + 1 def test_b_UDP_SOA_not_loaded(self): query = dns.message.make_query('example2.', 'SOA') -- 2.39.2