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

PowerDNS / pdns / 16673722362

01 Aug 2025 11:19AM UTC coverage: 65.826% (+12.0%) from 53.823%
16673722362

Pull #15955

github

web-flow
Merge 245ce2bbf into 631f2ad0f
Pull Request #15955: rec: test rpzPrimary instead of rpzMaster

42043 of 92440 branches covered (45.48%)

Branch coverage included in aggregate %.

127950 of 165806 relevant lines covered (77.17%)

6122257.21 hits per line

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

44.32
/modules/tinydnsbackend/tinydnsbackend.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
#ifdef HAVE_CONFIG_H
23
#include "config.h"
24
#endif
25
#include "tinydnsbackend.hh"
26
#include "pdns/misc.hh"
27
#include "pdns/dnsrecords.hh"
28
#include <utility>
29

30
static string backendname = "[TinyDNSBackend] ";
31
domainid_t TinyDNSBackend::s_lastId;
32
LockGuarded<TinyDNSBackend::TDI_suffix_t> TinyDNSBackend::s_domainInfo;
33

34
vector<string> TinyDNSBackend::getLocations()
35
{
×
36
  vector<string> ret;
×
37

38
  if (!d_dnspacket) {
×
39
    return ret;
×
40
  }
×
41

42
  //TODO: We do not have IPv6 support.
43
  Netmask remote = d_dnspacket->getRealRemote();
×
44
  if (remote.getBits() != 32) {
×
45
    return ret;
×
46
  }
×
47

48
  unsigned long addr = remote.getNetwork().sin4.sin_addr.s_addr;
×
49

50
  char key[6];
×
51
  key[0] = '\000';
×
52
  key[1] = '\045';
×
53
  key[2] = (addr) & 0xff;
×
54
  key[3] = (addr >> 8) & 0xff;
×
55
  key[4] = (addr >> 16) & 0xff;
×
56
  key[5] = (addr >> 24) & 0xff;
×
57

58
  for (int i = 4; i >= 0; i--) {
×
59
    string searchkey(key, i + 2);
×
60
    try {
×
61
      auto reader = std::make_unique<CDB>(getArg("dbfile"));
×
62
      ret = reader->findall(searchkey);
×
63
    }
×
64
    catch (const std::exception& e) {
×
65
      g_log << Logger::Error << e.what() << endl;
×
66
      throw PDNSException(e.what());
×
67
    }
×
68

69
    //Biggest item wins, so when we find something, we can jump out.
70
    if (ret.size() > 0) {
×
71
      break;
×
72
    }
×
73
  }
×
74

75
  return ret;
×
76
}
×
77

78
TinyDNSBackend::TinyDNSBackend(const string& suffix)
79
{
5✔
80
  setArgPrefix("tinydns" + suffix);
5✔
81
  d_suffix = suffix;
5✔
82
  d_locations = mustDo("locations");
5✔
83
  d_ignorebogus = mustDo("ignore-bogus-records");
5✔
84
  d_taiepoch = 4611686018427387904ULL + getArgAsNum("tai-adjust");
5✔
85
  d_dnspacket = NULL;
5✔
86
  d_cdbReader = NULL;
5✔
87
  d_isAxfr = false;
5✔
88
  d_isWildcardQuery = false;
5✔
89
}
5✔
90

91
TinyDNSBackend::TDI_t::iterator TinyDNSBackend::updateState(DomainInfo& domain, TDI_t* state)
92
{
17✔
93
  TDIByZone_t& zone_index = state->get<tag_zone>();
17✔
94
  TDIByZone_t::iterator itByZone = zone_index.find(domain.zone);
17✔
95
  if (itByZone != zone_index.end()) {
17!
96
    return itByZone;
×
97
  }
×
98

99
  TinyDomainInfo tmp;
17✔
100
  s_lastId++;
17✔
101
  tmp.zone = domain.zone;
17✔
102
  tmp.id = s_lastId;
17✔
103
  tmp.notified_serial = domain.serial;
17✔
104
  return state->insert(tmp).first;
17✔
105
}
17✔
106

107
void TinyDNSBackend::getUpdatedPrimaries(vector<DomainInfo>& retDomains, std::unordered_set<DNSName>& /* catalogs */, CatalogHashMap& /* catalogHashes */)
108
{
×
109
  bool alwaysNotify{false};
×
110
  auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
×
111
  if (domainInfo->count(d_suffix) == 0) {
×
112
    // If we don't have any state yet, this is startup, check whether we need
113
    // to always notify.
114
    alwaysNotify = mustDo("notify-on-startup");
×
115
    TDI_t tmp;
×
116
    domainInfo->emplace(d_suffix, tmp);
×
117
  }
×
118

119
  TDI_t* state = &(*domainInfo)[d_suffix];
×
120

121
  vector<DomainInfo> allDomains;
×
122
  getAllDomains_locked(&allDomains, true);
×
123

124
  for (auto& domain : allDomains) {
×
125
    auto iter = updateState(domain, state);
×
126
    // Keep domain id in sync with our current state.
127
    domain.id = iter->id;
×
128
    if (alwaysNotify || iter->notified_serial < domain.serial) {
×
129
      retDomains.push_back(domain);
×
130
    }
×
131
  }
×
132
}
×
133

134
// NOLINTNEXTLINE(readability-identifier-length)
135
void TinyDNSBackend::setNotified(domainid_t id, uint32_t serial)
136
{
×
137
  auto domainInfo = s_domainInfo.lock();
×
138
  if (!domainInfo->count(d_suffix)) {
×
139
    throw PDNSException("Can't get list of domains to set the serial.");
×
140
  }
×
141
  TDI_t* state = &(*domainInfo)[d_suffix];
×
142
  TDIById_t& domain_index = state->get<tag_domainid>();
×
143
  TDIById_t::iterator itById = domain_index.find(id);
×
144
  if (itById == domain_index.end()) {
×
145
    g_log << Logger::Error << backendname << "Received updated serial(" << serial << "), but domain ID (" << id << ") is not known in this backend." << endl;
×
146
  }
×
147
  else {
×
148
    DLOG(g_log << Logger::Debug << backendname << "Setting serial for " << itById->zone << " to " << serial << endl);
×
149
    domain_index.modify(itById, TDI_SerialModifier(serial));
×
150
  }
×
151
  (*domainInfo)[d_suffix] = *state;
×
152
}
×
153

154
void TinyDNSBackend::getAllDomains_locked(vector<DomainInfo>* domains, bool getSerial)
155
{
1✔
156
  d_isAxfr = true;
1✔
157
  d_isGetDomains = true;
1✔
158
  d_dnspacket = NULL;
1✔
159

160
  try {
1✔
161
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
1✔
162
    d_currentDomain = UnknownDomainID;
1✔
163
  }
1✔
164
  catch (const std::exception& e) {
1✔
165
    g_log << Logger::Error << e.what() << endl;
×
166
    throw PDNSException(e.what());
×
167
  }
×
168

169
  d_cdbReader->searchAll();
1✔
170
  DNSResourceRecord rr;
1✔
171
  std::unordered_set<DNSName> dupcheck;
1✔
172

173
  while (get(rr)) {
18✔
174
    if (rr.qtype.getCode() == QType::SOA && dupcheck.insert(rr.qname).second) {
17!
175
      DomainInfo di;
17✔
176
      di.id = d_currentDomain; // Will be overridden by caller
17✔
177
      di.backend = this;
17✔
178
      di.zone = ZoneName(rr.qname);
17✔
179
      di.kind = DomainInfo::Primary;
17✔
180
      di.last_check = time(0);
17✔
181

182
      if (getSerial) {
17!
183
        SOAData sd;
×
184
        try {
×
185
          fillSOAData(rr.content, sd);
×
186
          di.serial = sd.serial;
×
187
        }
×
188
        catch (...) {
×
189
          di.serial = 0;
×
190
        }
×
191
      }
×
192

193
      di.notified_serial = di.serial;
17✔
194
      domains->push_back(di);
17✔
195
    }
17✔
196
  }
17✔
197
}
1✔
198

199
void TinyDNSBackend::getAllDomains(vector<DomainInfo>* domains, bool getSerial, bool /* include_disabled */)
200
{
1✔
201
  auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
1✔
202
  if (domainInfo->count(d_suffix) == 0) {
1!
203
    TDI_t tmp;
1✔
204
    domainInfo->emplace(d_suffix, tmp);
1✔
205
  }
1✔
206

207
  TDI_t* state = &(*domainInfo)[d_suffix];
1✔
208

209
  getAllDomains_locked(domains, getSerial);
1✔
210

211
  for (auto& domain : *domains) {
17✔
212
    auto iter = updateState(domain, state);
17✔
213
    // Keep domain id in sync with our current state.
214
    domain.id = iter->id;
17✔
215
  }
17✔
216
}
1✔
217

218
//NOLINTNEXTLINE(readability-identifier-length)
219
bool TinyDNSBackend::getDomainInfo(const ZoneName& domain, DomainInfo& di, bool getSerial)
220
{
×
221
  auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
×
222
  if (domainInfo->count(d_suffix) == 0) {
×
223
    TDI_t tmp;
×
224
    domainInfo->emplace(d_suffix, tmp);
×
225
  }
×
226

227
  TDI_t* state = &(*domainInfo)[d_suffix];
×
228

229
  vector<DomainInfo> allDomains;
×
230
  getAllDomains_locked(&allDomains, getSerial);
×
231

232
  bool found{false};
×
233
  for (auto& oneDomain : allDomains) {
×
234
    auto iter = updateState(oneDomain, state);
×
235
    if (oneDomain.zone == domain) {
×
236
      // Keep domain id in sync with our current state.
237
      oneDomain.id = iter->id;
×
238
      di = oneDomain;
×
239
      found = true;
×
240
    }
×
241
  }
×
242
  return found;
×
243
}
×
244

245
bool TinyDNSBackend::list(const ZoneName& target, domainid_t domain_id, bool /* include_disabled */)
246
{
×
247
  d_isAxfr = true;
×
248
  d_isGetDomains = false;
×
249
  string key = target.operator const DNSName&().toDNSStringLC();
×
250
  try {
×
251
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
×
252
    d_currentDomain = domain_id;
×
253
  }
×
254
  catch (const std::exception& e) {
×
255
    g_log << Logger::Error << e.what() << endl;
×
256
    throw PDNSException(e.what());
×
257
  }
×
258

259
  return d_cdbReader->searchSuffix(key);
×
260
}
×
261

262
void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
263
{
218✔
264
  d_isAxfr = false;
218✔
265
  d_isGetDomains = false;
218✔
266
  string queryDomain = toLowerCanonic(qdomain.toString());
218✔
267

268
  string key = simpleCompress(queryDomain);
218✔
269

270
  DLOG(g_log << Logger::Debug << backendname << "[lookup] query for qtype [" << qtype.toString() << "] qdomain [" << qdomain << "]" << endl);
218✔
271
  DLOG(g_log << Logger::Debug << "[lookup] key [" << makeHexDump(key) << "]" << endl);
218✔
272

273
  d_isWildcardQuery = false;
218✔
274
  if (key[0] == '\001' && key[1] == '\052') {
218✔
275
    d_isWildcardQuery = true;
49✔
276
    key.erase(0, 2);
49✔
277
  }
49✔
278

279
  d_qtype = qtype;
218✔
280

281
  try {
218✔
282
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
218✔
283
    d_currentDomain = zoneId;
218✔
284
  }
218✔
285
  catch (const std::exception& e) {
218✔
286
    g_log << Logger::Error << e.what() << endl;
×
287
    throw PDNSException(e.what());
×
288
  }
×
289

290
  d_cdbReader->searchKey(key);
218✔
291
  d_dnspacket = pkt_p;
218✔
292
}
218✔
293

294
bool TinyDNSBackend::get(DNSResourceRecord& rr)
295
{
493✔
296
  pair<string, string> record;
493✔
297

298
  while (d_cdbReader->readNext(record)) {
20,849✔
299
    string val = record.second;
20,630✔
300
    string key = record.first;
20,630✔
301

302
    //DLOG(g_log<<Logger::Debug<<"[GET] Key: "<<makeHexDump(key)<<endl);
303
    //DLOG(g_log<<Logger::Debug<<"[GET] Val: "<<makeHexDump(val)<<endl);
304
    if (key[0] == '\000' && key[1] == '\045') { // skip locations
20,630!
305
      continue;
×
306
    }
×
307

308
    if (!d_isAxfr) {
20,630✔
309
      // If we have a wildcard query, but the record we got is not a wildcard, we skip.
310
      if (d_isWildcardQuery && val[2] != '\052' && val[2] != '\053') {
296!
311
        continue;
28✔
312
      }
28✔
313

314
      // If it is NOT a wildcard query, but we do find a wildcard record, we skip it.
315
      if (!d_isWildcardQuery && (val[2] == '\052' || val[2] == '\053')) {
268!
316
        continue;
11✔
317
      }
11✔
318
    }
268✔
319

320
    PacketReader pr(val, 0);
20,591✔
321
    rr.qtype = QType(pr.get16BitInt());
20,591✔
322

323
    if (d_isGetDomains && rr.qtype != QType::SOA) {
20,591✔
324
      continue;
20,317✔
325
    }
20,317✔
326

327
    if (d_isAxfr || d_qtype.getCode() == QType::ANY || rr.qtype == d_qtype) {
274!
328
      char locwild = pr.get8BitInt();
274✔
329
      if (locwild != '\075' && (locwild == '\076' || locwild == '\053')) {
274!
330
        if (d_isAxfr && d_locations) { // We skip records with a location in AXFR, unless we disable locations.
×
331
          continue;
×
332
        }
×
333
        char recloc[2];
×
334
        recloc[0] = pr.get8BitInt();
×
335
        recloc[1] = pr.get8BitInt();
×
336

337
        if (d_locations) {
×
338
          bool foundLocation = false;
×
339
          vector<string> locations = getLocations();
×
340
          while (locations.size() > 0) {
×
341
            string locId = locations.back();
×
342
            locations.pop_back();
×
343

344
            if (recloc[0] == locId[0] && recloc[1] == locId[1]) {
×
345
              foundLocation = true;
×
346
              break;
×
347
            }
×
348
          }
×
349
          if (!foundLocation) {
×
350
            continue;
×
351
          }
×
352
        }
×
353
      }
×
354

355
      if (d_isAxfr && (val[2] == '\052' || val[2] == '\053')) { // Keys are not stored with wildcard character, with AXFR we need to add that.
274!
356
        key.insert(0, 1, '\052');
×
357
        key.insert(0, 1, '\001');
×
358
      }
×
359
      // rr.qname.clear();
360
      rr.qname = DNSName(key.c_str(), key.size(), 0, false);
274✔
361
      rr.domain_id = d_currentDomain;
274✔
362
      // 11:13.21 <@ahu> IT IS ALWAYS AUTH --- well not really because we are just a backend :-)
363
      // We could actually do NSEC3-NARROW DNSSEC according to Habbie, if we do, we need to change something here.
364
      rr.auth = true;
274✔
365

366
      rr.ttl = pr.get32BitInt();
274✔
367
      uint64_t timestamp = pr.get32BitInt();
274✔
368
      timestamp <<= 32;
274✔
369
      timestamp += pr.get32BitInt();
274✔
370
      if (timestamp) {
274!
371
        uint64_t now = d_taiepoch + time(NULL);
×
372
        if (rr.ttl == 0) {
×
373
          if (timestamp < now) {
×
374
            continue;
×
375
          }
×
376
          rr.ttl = timestamp - now;
×
377
          if (rr.ttl <= 2)
×
378
            rr.ttl = 2;
×
379
          if (rr.ttl >= 3600)
×
380
            rr.ttl = 3600;
×
381
        }
×
382
        else if (now <= timestamp) {
×
383
          continue;
×
384
        }
×
385
      }
×
386
      try {
274✔
387
        DNSRecord dr;
274✔
388
        dr.d_class = 1;
274✔
389
        dr.d_type = rr.qtype.getCode();
274✔
390
        dr.d_clen = val.size() - pr.getPosition();
274✔
391

392
        auto drc = DNSRecordContent::make(dr, pr);
274✔
393
        rr.content = drc->getZoneRepresentation();
274✔
394
        DLOG(cerr << "CONTENT: " << rr.content << endl);
274✔
395
      }
274✔
396
      catch (...) {
274✔
397
        g_log << Logger::Error << backendname << "Failed to parse record content for " << rr.qname << " with type " << rr.qtype.toString();
×
398
        if (d_ignorebogus || d_isGetDomains) {
×
399
          g_log << ". Ignoring!" << endl;
×
400
          continue;
×
401
        }
×
402
        else {
×
403
          g_log << ". Erroring out!" << endl;
×
404
          throw;
×
405
        }
×
406
      }
×
407
      //      DLOG(g_log<<Logger::Debug<<backendname<<"Returning ["<<rr.content<<"] for ["<<rr.qname<<"] of RecordType ["<<rr.qtype.toString()<<"]"<<endl;);
408
      return true;
274✔
409
    }
274✔
410
  } // end of while
274✔
411
  DLOG(g_log << Logger::Debug << backendname << "No more records to return." << endl);
219✔
412

413
  d_cdbReader = nullptr;
219✔
414
  d_currentDomain = UnknownDomainID;
219✔
415
  return false;
219✔
416
}
493✔
417

418
// boilerplate
419
class TinyDNSFactory : public BackendFactory
420
{
421
public:
422
  TinyDNSFactory() :
423
    BackendFactory("tinydns") {}
5,861✔
424

425
  void declareArguments(const string& suffix = "") override
426
  {
1✔
427
    declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.", "no");
1✔
428
    declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
1✔
429
    declare(suffix, "tai-adjust", "This adjusts the TAI value if timestamps are used. These seconds will be added to the start point (1970) and will allow you to adjust for leap seconds. The default is 11.", "11");
1✔
430
    declare(suffix, "locations", "Enable or Disable location support in the backend. Changing the value to 'no' will make the backend ignore the locations. This then returns all records!", "yes");
1✔
431
    declare(suffix, "ignore-bogus-records", "The data.cdb file might have some incorrect record data, this causes PowerDNS to fail, where tinydns would send out truncated data. This option makes powerdns ignore that data!", "no");
1✔
432
  }
1✔
433

434
  DNSBackend* make(const string& suffix = "") override
435
  {
5✔
436
    return new TinyDNSBackend(suffix);
5✔
437
  }
5✔
438
};
439

440
// boilerplate
441
class TinyDNSLoader
442
{
443
public:
444
  TinyDNSLoader()
445
  {
5,861✔
446
    BackendMakers().report(std::make_unique<TinyDNSFactory>());
5,861✔
447
    g_log << Logger::Info << "[tinydnsbackend] This is the tinydns backend version " VERSION
5,861✔
448
#ifndef REPRODUCIBLE
5,861✔
449
          << " (" __DATE__ " " __TIME__ ")"
5,861✔
450
#endif
5,861✔
451
          << " reporting" << endl;
5,861✔
452
  }
5,861✔
453
};
454

455
static TinyDNSLoader tinydnsloader;
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

© 2026 Coveralls, Inc