]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ixfr.cc
Merge pull request #14032 from rgacogne/ddist-192-changelog-secpoll
[thirdparty/pdns.git] / pdns / ixfr.cc
CommitLineData
12471842
PL
1/*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
39ec5d29 22#include "ixfr.hh"
23#include "sstuff.hh"
24#include "dns_random.hh"
25#include "dnsrecords.hh"
189f9579 26#include "dnssecinfra.hh"
60a1c204 27#include "tsigverifier.hh"
7eafc52f 28
3d324e00 29vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
d06dcda4 30 const vector<DNSRecord>& records, const std::shared_ptr<const SOARecordContent>& primarySOA)
d67ae3b4
RG
31{
32 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret;
33
3d324e00 34 if (records.size() == 0 || primarySOA == nullptr) {
d67ae3b4
RG
35 return ret;
36 }
37
38 // we start at 1 to skip the first SOA record
39 // we don't increase pos because the final SOA
40 // of the previous sequence is also the first SOA
41 // of this one
42 for(unsigned int pos = 1; pos < records.size(); ) {
43 vector<DNSRecord> remove, add;
44
45 // cerr<<"Looking at record in position "<<pos<<" of type "<<QType(records[pos].d_type).getName()<<endl;
46
47 if (records[pos].d_type != QType::SOA) {
48 // this is an actual AXFR!
49 return {{remove, records}};
50 }
51
52 auto sr = getRR<SOARecordContent>(records[pos]);
53 if (!sr) {
3d324e00 54 throw std::runtime_error("Error getting the content of the first SOA record of this IXFR sequence for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
d67ae3b4
RG
55 }
56
3d324e00 57 // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<primarySOA->d_st.serial<<endl;
d67ae3b4
RG
58
59 // the serial of this SOA record is the serial of the
60 // zone before the removals and updates of this sequence
3d324e00 61 if (sr->d_st.serial == primarySOA->d_st.serial) {
98b33176
RG
62 if (records.size() == 2) {
63 // if the entire update is two SOAs records with the same
64 // serial, this is actually an empty AXFR!
65 return {{remove, records}};
66 }
67
d67ae3b4
RG
68 // if it's the final SOA, there is nothing for us to see
69 break;
70 }
71
72 remove.push_back(records[pos]); // this adds the SOA
73
74 // process removals
75 for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) {
76 remove.push_back(records[pos]);
77 }
78
79 if (pos >= records.size()) {
3d324e00 80 throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
81 }
82
83 sr = getRR<SOARecordContent>(records[pos]);
84 if (!sr) {
3d324e00 85 throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
86 }
87
88 // this is the serial of the zone after the removals
89 // and updates, but that might not be the final serial
90 // because there might be several sequences
91 uint32_t newSerial = sr->d_st.serial;
92 add.push_back(records[pos]); // this adds the new SOA
93
94 // process additions
95 for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) {
96 add.push_back(records[pos]);
97 }
98
99 if (pos >= records.size()) {
3d324e00 100 throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
101 }
102
103 sr = getRR<SOARecordContent>(records[pos]);
104 if (!sr) {
3d324e00 105 throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
106 }
107
108 if (sr->d_st.serial != newSerial) {
3d324e00 109 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.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
110 }
111
3d324e00 112 if (newSerial == primarySOA->d_st.serial) {
d67ae3b4
RG
113 // this was the last sequence
114 if (pos != (records.size() - 1)) {
3d324e00 115 throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
d67ae3b4
RG
116 }
117 }
118
e32a8d46 119 ret.emplace_back(remove, add);
d67ae3b4
RG
120 }
121
122 return ret;
123}
124
3e7dcee6 125// Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
8faf5a90 126 // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
fee334ae
OM
127vector<pair<vector<DNSRecord>, vector<DNSRecord>>> getIXFRDeltas(const ComboAddress& primary, const DNSName& zone, const DNSRecord& oursr,
128 uint16_t xfrTimeout, bool totalTimeout,
129 const TSIGTriplet& tt, const ComboAddress* laddr, size_t maxReceivedBytes)
39ec5d29 130{
240460d7
OM
131 // Auth documents xfrTimeout to be a max idle time (sets totalTimeout=false)
132 // Rec documents it to be a total XFR time (sets totalTimeout=true)
fee334ae 133 //
39ec5d29 134 vector<pair<vector<DNSRecord>, vector<DNSRecord> > > ret;
135 vector<uint8_t> packet;
136 DNSPacketWriter pw(packet, zone, QType::IXFR);
137 pw.getHeader()->qr=0;
138 pw.getHeader()->rd=0;
a410b176 139 pw.getHeader()->id=dns_random_uint16();
189f9579 140 pw.startRecord(zone, QType::SOA, 0, QClass::IN, DNSResourceRecord::AUTHORITY);
d06dcda4 141 oursr.getContent()->toPacket(pw);
189f9579 142
39ec5d29 143 pw.commit();
60a1c204 144 TSIGRecordContent trc;
3d324e00 145 TSIGTCPVerifier tsigVerifier(tt, primary, trc);
98c9ec39 146 if(!tt.algo.empty()) {
bd051ad6
PL
147 TSIGHashEnum the;
148 getTSIGHashEnum(tt.algo, the);
bd051ad6
PL
149 try {
150 trc.d_algoName = getTSIGAlgoName(the);
151 } catch(PDNSException& pe) {
86f1af1c 152 throw std::runtime_error("TSIG algorithm '"+tt.algo.toLogString()+"' is unknown.");
bd051ad6 153 }
4646277d 154 trc.d_time = time((time_t*)nullptr);
189f9579 155 trc.d_fudge = 300;
156 trc.d_origID=ntohs(pw.getHeader()->id);
157 trc.d_eRcode=0;
ea3816cf 158 addTSIG(pw, trc, tt.name, tt.secret, "", false);
189f9579 159 }
39ec5d29 160 uint16_t len=htons(packet.size());
161 string msg((const char*)&len, 2);
162 msg.append((const char*)&packet[0], packet.size());
163
3d324e00 164 Socket s(primary.sin4.sin_family, SOCK_STREAM);
fee334ae 165 if (laddr != nullptr) {
3e7dcee6 166 s.bind(*laddr);
fee334ae
OM
167 }
168 s.setNonBlocking();
169
170 const time_t xfrStart = time(nullptr);
171
172 // Helper function: if we have a total timeout, check it and set elapsed to the total time taken sofar,
173 // otherwise set elapsed to 0, making the total time limit ineffective
174 const auto timeoutChecker = [=] () -> time_t {
175 time_t elapsed = 0;
176 if (totalTimeout) {
177 elapsed = time(nullptr) - xfrStart;
178 if (elapsed >= xfrTimeout) {
179 throw std::runtime_error("Reached the maximum elapsed time in an IXFR delta for zone '" + zone.toLogString() + "' from primary " + primary.toStringWithPort());
180 }
181 }
182 return elapsed;
183 };
184
185 s.connect(primary, xfrTimeout);
186
187 time_t elapsed = timeoutChecker();
8f67f0c2 188 // coverity[store_truncates_time_t]
fee334ae 189 s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
39ec5d29 190
3d324e00 191 // CURRENT PRIMARY SOA
39ec5d29 192 // REPEAT:
193 // SOA WHERE THIS DELTA STARTS
194 // RECORDS TO REMOVE
195 // SOA WHERE THIS DELTA GOES
196 // RECORDS TO ADD
fee334ae 197 // CURRENT PRIMARY SOA
d06dcda4 198 std::shared_ptr<const SOARecordContent> primarySOA = nullptr;
39ec5d29 199 vector<DNSRecord> records;
db8f9152 200 size_t receivedBytes = 0;
32fc3b03 201 std::string reply;
60a1c204 202
8bed4b38
OM
203 enum transferStyle { Unknown, AXFR, IXFR } style = Unknown;
204 const unsigned int expectedSOAForAXFR = 2;
205 const unsigned int expectedSOAForIXFR = 3;
206 unsigned int primarySOACount = 0;
207
8faf5a90 208 std::string state;
fee334ae 209 for (;;) {
8bed4b38
OM
210 // IXFR or AXFR style end reached? We don't want to process trailing data after the closing SOA
211 if (style == AXFR && primarySOACount == expectedSOAForAXFR) {
8faf5a90 212 state = "AXFRdone";
8bed4b38
OM
213 break;
214 }
8faf5a90
PD
215 if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
216 state = "IXFRdone";
397ed71d 217 break;
8bed4b38 218 }
397ed71d 219
fee334ae 220 elapsed = timeoutChecker();
8faf5a90
PD
221 try {
222 const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
223 const struct timeval idleTime = remainingTime;
224 readn2WithTimeout(s.getHandle(), &len, sizeof(len), idleTime, remainingTime, false);
225 }
226 catch (const runtime_error& ex) {
227 state = ex.what();
39ec5d29 228 break;
fee334ae 229 }
d67ae3b4 230
fee334ae
OM
231 len = ntohs(len);
232 if (len == 0) {
8faf5a90 233 state = "zeroLen";
39ec5d29 234 break;
fee334ae 235 }
8faf5a90 236 // Currently no more break statements after this
db8f9152 237
fee334ae 238 if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) {
3d324e00 239 throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
fee334ae 240 }
db8f9152 241
32fc3b03 242 reply.resize(len);
fee334ae
OM
243
244 elapsed = timeoutChecker();
8faf5a90 245 const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
fee334ae 246 const struct timeval idleTime = remainingTime;
8faf5a90 247 readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
db8f9152 248 receivedBytes += len;
d67ae3b4 249
32fc3b03 250 MOADNSParser mdp(false, reply);
fee334ae 251 if (mdp.d_header.rcode) {
3d324e00 252 throw std::runtime_error("Got an error trying to IXFR zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"': "+RCode::to_s(mdp.d_header.rcode));
fee334ae 253 }
189f9579 254
fee334ae 255 if (!tt.algo.empty()) { // TSIG verify message
32fc3b03 256 tsigVerifier.check(reply, mdp);
60a1c204
RG
257 }
258
fee334ae 259 for (auto& r: mdp.d_answers) {
3d324e00 260 if(!primarySOA) {
d67ae3b4
RG
261 // we have not seen the first SOA record yet
262 if (r.first.d_type != QType::SOA) {
d5fcd583 263 throw std::runtime_error("The first record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"' is not a SOA ("+QType(r.first.d_type).toString()+")");
d67ae3b4
RG
264 }
265
397ed71d
LX
266 auto sr = getRR<SOARecordContent>(r.first);
267 if (!sr) {
3d324e00 268 throw std::runtime_error("Error getting the content of the first SOA record of the IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
d67ae3b4
RG
269 }
270
d06dcda4 271 if(sr->d_st.serial == getRR<SOARecordContent>(oursr)->d_st.serial) {
d67ae3b4
RG
272 // we are up to date
273 return ret;
274 }
4c5a50dc 275 primarySOA = std::move(sr);
8bed4b38 276 ++primarySOACount;
4f5cde49
LX
277 } else if (r.first.d_type == QType::SOA) {
278 auto sr = getRR<SOARecordContent>(r.first);
279 if (!sr) {
3d324e00 280 throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
4f5cde49
LX
281 }
282
8bed4b38 283 // we hit a marker SOA record
3d324e00 284 if (primarySOA->d_st.serial == sr->d_st.serial) {
8bed4b38
OM
285 ++primarySOACount;
286 }
287 }
288 // When we see the 2nd record, we can decide what the style is
289 if (records.size() == 1 && style == Unknown) {
290 if (r.first.d_type != QType::SOA) {
291 // Non-empty AXFR style has a non-SOA record following the first SOA
292 style = AXFR;
293 }
294 else if (primarySOACount == expectedSOAForAXFR) {
295 // Empty zone AXFR style: start SOA is immediately followed by end marker SOA
296 style = AXFR;
297 }
298 else {
299 // IXFR has a 2nd SOA (with different serial) following the first
300 style = IXFR;
397ed71d 301 }
39ec5d29 302 }
7eafc52f 303
d67ae3b4
RG
304 if(r.first.d_place != DNSResourceRecord::ANSWER) {
305 if(r.first.d_type == QType::TSIG)
306 continue;
39ec5d29 307
d67ae3b4
RG
308 if(r.first.d_type == QType::OPT)
309 continue;
310
8fb5bba0 311 throw std::runtime_error("Unexpected record (" +QType(r.first.d_type).toString()+") in non-answer section ("+std::to_string(r.first.d_place)+") in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
d67ae3b4
RG
312 }
313
314 r.first.d_name.makeUsRelative(zone);
315 records.push_back(r.first);
39ec5d29 316 }
39ec5d29 317 }
d67ae3b4 318
8bed4b38
OM
319 switch (style) {
320 case IXFR:
321 if (primarySOACount != expectedSOAForIXFR) {
8faf5a90 322 throw std::runtime_error("Incomplete IXFR transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
8bed4b38
OM
323 }
324 break;
325 case AXFR:
326 if (primarySOACount != expectedSOAForAXFR){
8faf5a90 327 throw std::runtime_error("Incomplete AXFR style transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
8bed4b38
OM
328 }
329 break;
330 case Unknown:
8faf5a90 331 throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
8bed4b38
OM
332 break;
333 }
d67ae3b4 334
3d324e00 335 return processIXFRRecords(primary, zone, records, primarySOA);
39ec5d29 336}