From: Remi Gacogne Date: Wed, 28 Jun 2017 16:26:33 +0000 (+0200) Subject: rec: Fix IXFR skipping the additions part of the last sequence X-Git-Tag: rec-4.0.6~1^2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=c2086f265b4095cf01f41721ee8beae39ede072c;p=thirdparty%2Fpdns.git rec: Fix IXFR skipping the additions part of the last sequence Under certain conditions, we could have skipped the additions part of the last `IXFR` sequence, because we stopped processing records after seeing a `SOA` record with the new serial. However, as stated in rfc1995's "Response format" section: "the first RR of the added RRs is the newer SOA RR" (cherry picked from commit d67ae3b477c9cf9d2a98f0edad9977dc34a2c8bf) --- diff --git a/pdns/ixfr.cc b/pdns/ixfr.cc index a325eaec5f..df4d5ff1f3 100644 --- a/pdns/ixfr.cc +++ b/pdns/ixfr.cc @@ -26,6 +26,96 @@ #include "dnssecinfra.hh" #include "tsigverifier.hh" +vector, vector > > processIXFRRecords(const ComboAddress& master, const DNSName& zone, + const vector& records, const std::shared_ptr masterSOA) +{ + vector, vector > > ret; + + if (records.size() == 0 || masterSOA == nullptr) { + return ret; + } + + // we start at 1 to skip the first SOA record + // we don't increase pos because the final SOA + // of the previous sequence is also the first SOA + // of this one + for(unsigned int pos = 1; pos < records.size(); ) { + vector remove, add; + + // cerr<<"Looking at record in position "<(records[pos]); + if (!sr) { + throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"'"); + } + + // cerr<<"Serial is "<d_st.serial<<", final serial is "<d_st.serial<d_st.serial == masterSOA->d_st.serial) { + // if it's the final SOA, there is nothing for us to see + break; + } + + remove.push_back(records[pos]); // this adds the SOA + + // process removals + for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { + remove.push_back(records[pos]); + } + + if (pos >= records.size()) { + throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + + sr = getRR(records[pos]); + if (!sr) { + throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + + // this is the serial of the zone after the removals + // and updates, but that might not be the final serial + // because there might be several sequences + uint32_t newSerial = sr->d_st.serial; + add.push_back(records[pos]); // this adds the new SOA + + // process additions + for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { + add.push_back(records[pos]); + } + + if (pos >= records.size()) { + throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + + sr = getRR(records[pos]); + if (!sr) { + throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + + if (sr->d_st.serial != newSerial) { + throw std::runtime_error("Invalid serial (" + std::to_string(sr->d_st.serial) + ", expecting " + std::to_string(newSerial) + ") in the SOA record finishing the additions part of the IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + + if (newSerial == masterSOA->d_st.serial) { + // this was the last sequence + if (pos != (records.size() - 1)) { + throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toString() + "' from " + master.toStringWithPort()); + } + } + + ret.push_back(make_pair(remove,add)); + } + + return ret; +} + // Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR! vector, vector > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& oursr, const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes) @@ -75,24 +165,26 @@ vector, vector > > getIXFRDeltas(const ComboAd // SOA WHERE THIS DELTA GOES // RECORDS TO ADD // CURRENT MASTER SOA - shared_ptr masterSOA; + std::shared_ptr masterSOA = nullptr; vector records; size_t receivedBytes = 0; for(;;) { - if(s.read((char*)&len, 2)!=2) + if(s.read((char*)&len, sizeof(len)) != sizeof(len)) break; + len=ntohs(len); // cout<<"Got chunk of "< 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) - throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()); + throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toString()+"' from master "+master.toStringWithPort()); char reply[len]; readn2(s.getHandle(), reply, len); receivedBytes += len; + MOADNSParser mdp(false, string(reply, len)); if(mdp.d_header.rcode) throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode)); @@ -104,49 +196,41 @@ vector, vector > > getIXFRDeltas(const ComboAd } for(auto& r: mdp.d_answers) { - if(r.first.d_type == QType::TSIG) - continue; // cout<getZoneRepresentation()<(r.first); - if(sr) { - if(!masterSOA) { - if(sr->d_st.serial == std::dynamic_pointer_cast(oursr.d_content)->d_st.serial) { // we are up to date - goto done; - } - masterSOA=sr; - } - else if(sr->d_st.serial == masterSOA->d_st.serial) - goto done; - } + if (!sr) { + throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()+"'"); + } + + if(sr->d_st.serial == std::dynamic_pointer_cast(oursr.d_content)->d_st.serial) { + // we are up to date + return ret; + } + masterSOA = sr; } - } - } - // cout<<"Got "<(records[pos]); - vector remove, add; - if(!sr) { // this is an actual AXFR! - return {{remove, records}}; - } - if(sr->d_st.serial == masterSOA->d_st.serial) - break; - - remove.push_back(records[pos]); // this adds the SOA - for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { - remove.push_back(records[pos]); - } - sr = getRR(records[pos]); + if(r.first.d_place != DNSResourceRecord::ANSWER) { + if(r.first.d_type == QType::TSIG) + continue; - add.push_back(records[pos]); // this adds the new SOA - for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) { - add.push_back(records[pos]); + if(r.first.d_type == QType::OPT) + continue; + + throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).getName()+") in non-answer section ("+std::to_string(r.first.d_place)+")in IXFR response for zone '"+zone.toString()+"' from master '"+master.toStringWithPort()); + } + + r.first.d_name.makeUsRelative(zone); + records.push_back(r.first); } - ret.push_back(make_pair(remove,add)); } - return ret; + + // cout<<"Got "<, vector > > getIXFRDeltas(const ComboAddress& master, const DNSName& zone, const DNSRecord& sr, const TSIGTriplet& tt=TSIGTriplet(), const ComboAddress* laddr=0, size_t maxReceivedBytes=0); + +vector, vector > > processIXFRRecords(const ComboAddress& master, const DNSName& zone, + const vector& records, const std::shared_ptr masterSOA);