• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PowerDNS / pdns / 12595591960

03 Jan 2025 09:27AM UTC coverage: 62.774% (+2.5%) from 60.245%
12595591960

Pull #15008

github

web-flow
Merge c2a2749d3 into 788f396a7
Pull Request #15008: Do not follow CNAME records for ANY or CNAME queries

30393 of 78644 branches covered (38.65%)

Branch coverage included in aggregate %.

105822 of 138350 relevant lines covered (76.49%)

4613078.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.33
/pdns/ixfr.cc
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
 */
22
#include "ixfr.hh"
23
#include "sstuff.hh"
24
#include "dns_random.hh"
25
#include "dnsrecords.hh"
26
#include "dnssecinfra.hh"
27
#include "tsigverifier.hh"
28

29
vector<pair<vector<DNSRecord>, vector<DNSRecord> > > processIXFRRecords(const ComboAddress& primary, const DNSName& zone,
30
                                                                        const vector<DNSRecord>& records, const std::shared_ptr<const SOARecordContent>& primarySOA)
31
{
57✔
32
  vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
57✔
33

34
  if (records.size() == 0 || primarySOA == nullptr) {
57✔
35
    return ret;
6✔
36
  }
6✔
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(); ) {
89!
43
    vector<DNSRecord> remove, add;
89✔
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) {
89✔
48
      // this is an actual AXFR!
49
      return {{remove, records}};
8✔
50
    }
8✔
51

52
    auto sr = getRR<SOARecordContent>(records[pos]);
81✔
53
    if (!sr) {
81!
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()+"'");
×
55
    }
×
56

57
    // cerr<<"Serial is "<<sr->d_st.serial<<", final serial is "<<primarySOA->d_st.serial<<endl;
58

59
    // the serial of this SOA record is the serial of the
60
    // zone before the removals and updates of this sequence
61
    if (sr->d_st.serial == primarySOA->d_st.serial) {
81✔
62
      if (records.size() == 2) {
31✔
63
        // if the entire update is two SOAs records with the same
64
        // serial, this is actually an empty AXFR!
65
        return {{remove, records}};
4✔
66
      }
4✔
67

68
      // if it's the final SOA, there is nothing for us to see
69
      break;
27✔
70
    }
31✔
71

72
    remove.push_back(records[pos]); // this adds the SOA
50✔
73

74
    // process removals
75
    for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos) {
96✔
76
      remove.push_back(records[pos]);
46✔
77
    }
46✔
78

79
    if (pos >= records.size()) {
50✔
80
      throw std::runtime_error("No SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
3✔
81
    }
3✔
82

83
    sr = getRR<SOARecordContent>(records[pos]);
47✔
84
    if (!sr) {
47!
85
      throw std::runtime_error("Invalid SOA record to finish the removals part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
×
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;
47✔
92
    add.push_back(records[pos]); // this adds the new SOA
47✔
93

94
    // process additions
95
    for(pos++; pos < records.size() && records[pos].d_type != QType::SOA; ++pos)  {
119✔
96
      add.push_back(records[pos]);
72✔
97
    }
72✔
98

99
    if (pos >= records.size()) {
47✔
100
      throw std::runtime_error("No SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
3✔
101
    }
3✔
102

103
    sr = getRR<SOARecordContent>(records[pos]);
44✔
104
    if (!sr) {
44!
105
      throw std::runtime_error("Invalid SOA record to finish the additions part of the IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
×
106
    }
×
107

108
    if (sr->d_st.serial != newSerial) {
44✔
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());
3✔
110
    }
3✔
111

112
    if (newSerial == primarySOA->d_st.serial) {
41✔
113
      // this was the last sequence
114
      if (pos != (records.size() - 1)) {
30✔
115
        throw std::runtime_error("Trailing records after the last IXFR sequence of zone '" + zone.toLogString() + "' from " + primary.toStringWithPort());
3✔
116
      }
3✔
117
    }
30✔
118

119
    ret.emplace_back(remove, add);
38✔
120
  }
38✔
121

122
  return ret;
27✔
123
}
51✔
124

125
// Returns pairs of "remove & add" vectors. If you get an empty remove, it means you got an AXFR!
126
 // NOLINTNEXTLINE(readability-function-cognitive-complexity): https://github.com/PowerDNS/pdns/issues/12791
127
vector<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)
130
{
62✔
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)
133
  //
134
  vector<pair<vector<DNSRecord>, vector<DNSRecord> > >  ret;
62✔
135
  vector<uint8_t> packet;
62✔
136
  DNSPacketWriter pw(packet, zone, QType::IXFR);
62✔
137
  pw.getHeader()->qr=0;
62✔
138
  pw.getHeader()->rd=0;
62✔
139
  pw.getHeader()->id=dns_random_uint16();
62✔
140
  pw.startRecord(zone, QType::SOA, 0, QClass::IN, DNSResourceRecord::AUTHORITY);
62✔
141
  oursr.getContent()->toPacket(pw);
62✔
142

143
  pw.commit();
62✔
144
  TSIGRecordContent trc;
62✔
145
  TSIGTCPVerifier tsigVerifier(tt, primary, trc);
62✔
146
  if(!tt.algo.empty()) {
62!
147
    TSIGHashEnum the;
×
148
    getTSIGHashEnum(tt.algo, the);
×
149
    try {
×
150
      trc.d_algoName = getTSIGAlgoName(the);
×
151
    } catch(PDNSException& pe) {
×
152
      throw std::runtime_error("TSIG algorithm '"+tt.algo.toLogString()+"' is unknown.");
×
153
    }
×
154
    trc.d_time = time((time_t*)nullptr);
×
155
    trc.d_fudge = 300;
×
156
    trc.d_origID=ntohs(pw.getHeader()->id);
×
157
    trc.d_eRcode=0;
×
158
    addTSIG(pw, trc, tt.name, tt.secret, "", false);
×
159
  }
×
160
  uint16_t len=htons(packet.size());
62✔
161
  string msg((const char*)&len, 2);
62✔
162
  msg.append((const char*)&packet[0], packet.size());
62✔
163

164
  Socket s(primary.sin4.sin_family, SOCK_STREAM);
62✔
165
  if (laddr != nullptr) {
62!
166
    s.bind(*laddr);
62✔
167
  }
62✔
168
  s.setNonBlocking();
62✔
169

170
  const time_t xfrStart = time(nullptr);
62✔
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 {
95✔
175
    time_t elapsed = 0;
95✔
176
    if (totalTimeout) {
95!
177
      elapsed = time(nullptr) - xfrStart;
95✔
178
      if (elapsed >= xfrTimeout) {
95!
179
        throw std::runtime_error("Reached the maximum elapsed time in an IXFR delta for zone '" + zone.toLogString() + "' from primary " + primary.toStringWithPort());
×
180
      }
×
181
    }
95✔
182
    return elapsed;
95✔
183
  };
95✔
184

185
  s.connect(primary, xfrTimeout);
62✔
186

187
  time_t elapsed = timeoutChecker();
62✔
188
  // coverity[store_truncates_time_t]
189
  s.writenWithTimeout(msg.data(), msg.size(), xfrTimeout - elapsed);
62✔
190

191
  // CURRENT PRIMARY SOA
192
  // REPEAT:
193
  //   SOA WHERE THIS DELTA STARTS
194
  //   RECORDS TO REMOVE
195
  //   SOA WHERE THIS DELTA GOES
196
  //   RECORDS TO ADD
197
  // CURRENT PRIMARY SOA
198
  std::shared_ptr<const SOARecordContent> primarySOA = nullptr;
62✔
199
  vector<DNSRecord> records;
62✔
200
  size_t receivedBytes = 0;
62✔
201
  std::string reply;
62✔
202

203
  enum transferStyle { Unknown, AXFR, IXFR } style = Unknown;
62✔
204
  const unsigned int expectedSOAForAXFR = 2;
62✔
205
  const unsigned int expectedSOAForIXFR = 3;
62✔
206
  unsigned int primarySOACount = 0;
62✔
207

208
  std::string state;
62✔
209
  for (;;) {
62✔
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) {
54✔
212
      state = "AXFRdone";
6✔
213
      break;
6✔
214
    }
6✔
215
    if (style == IXFR && primarySOACount == expectedSOAForIXFR) {
48✔
216
      state = "IXFRdone";
15✔
217
      break;
15✔
218
    }
15✔
219

220
    elapsed = timeoutChecker();
33✔
221
    try {
33✔
222
      const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
33✔
223
      const struct timeval idleTime = remainingTime;
33✔
224
      readn2WithTimeout(s.getHandle(), &len, sizeof(len), idleTime, remainingTime, false);
33✔
225
    }
33✔
226
    catch (const runtime_error& ex) {
33✔
227
      state = ex.what();
2✔
228
      break;
2✔
229
    }
2✔
230

231
    len = ntohs(len);
31✔
232
    if (len == 0) {
31!
233
      state = "zeroLen";
×
234
      break;
×
235
    }
×
236
    // Currently no more break statements after this
237

238
    if (maxReceivedBytes > 0 && (maxReceivedBytes - receivedBytes) < (size_t) len) {
31!
239
      throw std::runtime_error("Reached the maximum number of received bytes in an IXFR delta for zone '"+zone.toLogString()+"' from primary "+primary.toStringWithPort());
×
240
    }
×
241

242
    reply.resize(len);
31✔
243

244
    elapsed = timeoutChecker();
31✔
245
    const struct timeval remainingTime = { .tv_sec = xfrTimeout - elapsed, .tv_usec = 0 };
31✔
246
    const struct timeval idleTime = remainingTime;
31✔
247
    readn2WithTimeout(s.getHandle(), reply.data(), len, idleTime, remainingTime, false);
31✔
248
    receivedBytes += len;
31✔
249

250
    MOADNSParser mdp(false, reply);
31✔
251
    if (mdp.d_header.rcode) {
31!
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));
×
253
    }
×
254

255
    if (!tt.algo.empty()) { // TSIG verify message
31!
256
      tsigVerifier.check(reply, mdp);
×
257
    }
×
258

259
    for (auto& r: mdp.d_answers) {
143✔
260
      if(!primarySOA) {
143✔
261
        // we have not seen the first SOA record yet
262
        if (r.d_type != QType::SOA) {
30!
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.d_type).toString()+")");
×
264
        }
×
265

266
        auto soaRecord = getRR<SOARecordContent>(r);
30✔
267
        if (!soaRecord) {
30!
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()+"'");
×
269
        }
×
270

271
        if(soaRecord->d_st.serial == getRR<SOARecordContent>(oursr)->d_st.serial) {
30✔
272
          // we are up to date
273
          return ret;
7✔
274
        }
7✔
275
        if(soaRecord->d_st.serial < getRR<SOARecordContent>(oursr)->d_st.serial) {
23!
276
          // we have a higher SOA than the auth? Should not happen, but what can we do?
277
          throw std::runtime_error("Our serial is higher than remote one for zone '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + "': ours " + std::to_string(getRR<SOARecordContent>(oursr)->d_st.serial) + " theirs " + std::to_string(soaRecord->d_st.serial));
×
278
        }
×
279
        primarySOA = std::move(soaRecord);
23✔
280
        ++primarySOACount;
23✔
281
      } else if (r.d_type == QType::SOA) {
113✔
282
        auto soaRecord = getRR<SOARecordContent>(r);
57✔
283
        if (!soaRecord) {
57!
284
          throw std::runtime_error("Error getting the content of SOA record of IXFR answer for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort()+"'");
×
285
        }
×
286

287
        // we hit a marker SOA record
288
        if (primarySOA->d_st.serial == soaRecord->d_st.serial) {
57✔
289
          ++primarySOACount;
37✔
290
        }
37✔
291
      }
57✔
292
      // When we see the 2nd record, we can decide what the style is
293
      if (records.size() == 1 && style == Unknown) {
136!
294
        if (r.d_type != QType::SOA || primarySOACount == expectedSOAForAXFR) {
23✔
295
          // 1. Non-empty AXFR style has a non-SOA record following the first SOA
296
          // 2. Empty zone AXFR style: start SOA is immediately followed by end marker SOA
297
          style = AXFR;
7✔
298
        }
7✔
299
        else {
16✔
300
          // IXFR has a 2nd SOA (with different serial) following the first
301
          style = IXFR;
16✔
302
        }
16✔
303
      }
23✔
304

305
      if(r.d_place != DNSResourceRecord::ANSWER) {
136!
306
        if (r.d_type == QType::TSIG) {
×
307
          continue;
×
308
        }
×
309

310
        if (r.d_type == QType::OPT) {
×
311
          continue;
×
312
        }
×
313

314
        throw std::runtime_error("Unexpected record (" +QType(r.d_type).toString()+") in non-answer section ("+std::to_string(r.d_place)+") in IXFR response for zone '"+zone.toLogString()+"' from primary '"+primary.toStringWithPort());
×
315
      }
×
316

317
      r.d_name.makeUsRelative(zone);
136✔
318
      records.push_back(r);
136✔
319
    }
136✔
320
  }
31✔
321

322
  switch (style) {
55✔
323
  case IXFR:
16✔
324
    if (primarySOACount != expectedSOAForIXFR) {
16✔
325
      throw std::runtime_error("Incomplete IXFR transfer (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
1✔
326
    }
1✔
327
    break;
15✔
328
  case AXFR:
15✔
329
    if (primarySOACount != expectedSOAForAXFR){
7✔
330
      throw std::runtime_error("Incomplete AXFR style transfer (primarySOACount=" + std::to_string(primarySOACount) + ")  for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
1✔
331
    }
1✔
332
    break;
6✔
333
  case Unknown:
6!
334
    throw std::runtime_error("Incomplete XFR (primarySOACount=" + std::to_string(primarySOACount) + ") for '" + zone.toLogString() + "' from primary '" + primary.toStringWithPort() + " state=" + state);
×
335
    break;
×
336
  }
55✔
337

338
  return processIXFRRecords(primary, zone, records, primarySOA);
21✔
339
}
55✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc