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

PowerDNS / pdns / 20618548088

31 Dec 2025 12:00PM UTC coverage: 72.648% (-0.7%) from 73.336%
20618548088

Pull #16693

github

web-flow
Merge 3f7d9a75b into 65de281db
Pull Request #16693: auth: plumbing for structured logging

39009 of 65430 branches covered (59.62%)

Branch coverage included in aggregate %.

807 of 2400 new or added lines in 58 files covered. (33.63%)

200 existing lines in 39 files now uncovered.

129187 of 166092 relevant lines covered (77.78%)

5266744.49 hits per line

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

41.7
/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 "pdns/logging.hh"
29
#include <utility>
30

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

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

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

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

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

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

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

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

77
  return ret;
×
78
}
×
79

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

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

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

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

121
  TDI_t* state = &(*domainInfo)[d_suffix];
×
122

123
  vector<DomainInfo> allDomains;
×
124
  getAllDomains_locked(&allDomains, true);
×
125

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

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

158
void TinyDNSBackend::getAllDomains_locked(vector<DomainInfo>* domains, bool getSerial)
159
{
1✔
160
  d_isAxfr = true;
1✔
161
  d_isGetDomains = true;
1✔
162
  d_dnspacket = NULL;
1✔
163

164
  try {
1✔
165
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
1✔
166
    d_currentDomain = UnknownDomainID;
1✔
167
  }
1✔
168
  catch (const std::exception& e) {
1✔
NEW
169
    SLOG(g_log << Logger::Error << e.what() << endl,
×
NEW
170
         d_slog->error(Logr::Error, e.what(), "unable to initialize database reader"));
×
171
    throw PDNSException(e.what());
×
172
  }
×
173

174
  d_cdbReader->searchAll();
1✔
175
  DNSResourceRecord rr;
1✔
176
  std::unordered_set<DNSName> dupcheck;
1✔
177

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

187
      if (getSerial) {
17!
188
        SOAData sd;
×
189
        try {
×
190
          fillSOAData(rr.content, sd);
×
191
          di.serial = sd.serial;
×
192
        }
×
193
        catch (...) {
×
194
          di.serial = 0;
×
195
        }
×
196
      }
×
197

198
      di.notified_serial = di.serial;
17✔
199
      domains->push_back(di);
17✔
200
    }
17✔
201
  }
17✔
202
}
1✔
203

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

212
  TDI_t* state = &(*domainInfo)[d_suffix];
1✔
213

214
  getAllDomains_locked(domains, getSerial);
1✔
215

216
  for (auto& domain : *domains) {
17✔
217
    auto iter = updateState(domain, state);
17✔
218
    // Keep domain id in sync with our current state.
219
    domain.id = iter->id;
17✔
220
  }
17✔
221
}
1✔
222

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

232
  TDI_t* state = &(*domainInfo)[d_suffix];
×
233

234
  vector<DomainInfo> allDomains;
×
235
  getAllDomains_locked(&allDomains, getSerial);
×
236

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

250
bool TinyDNSBackend::list(const ZoneName& target, domainid_t domain_id, bool /* include_disabled */)
251
{
×
252
  d_isAxfr = true;
×
253
  d_isGetDomains = false;
×
254
  string key = target.operator const DNSName&().toDNSStringLC();
×
255
  try {
×
256
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
×
257
    d_currentDomain = domain_id;
×
258
  }
×
259
  catch (const std::exception& e) {
×
NEW
260
    SLOG(g_log << Logger::Error << e.what() << endl,
×
NEW
261
         d_slog->error(Logr::Error, e.what(), "unable to initialize database reader"));
×
262
    throw PDNSException(e.what());
×
263
  }
×
264

265
  return d_cdbReader->searchSuffix(key);
×
266
}
×
267

268
void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, domainid_t zoneId, DNSPacket* pkt_p)
269
{
218✔
270
  d_isAxfr = false;
218✔
271
  d_isGetDomains = false;
218✔
272
  string queryDomain = toLowerCanonic(qdomain.toString());
218✔
273

274
  string key = simpleCompress(queryDomain);
218✔
275

276
  DLOG(SLOG(g_log << Logger::Debug << backendname << "[lookup] query for qtype [" << qtype.toString() << "] qdomain [" << qdomain << "]" << endl,
218✔
277
            d_slog->info(Logr::Debug, "lookup query", "domain", Logging::Loggable(qdomain), "type", Logging::Loggable(qtype))));
218✔
278
  DLOG(SLOG(g_log << Logger::Debug << "[lookup] key [" << makeHexDump(key) << "]" << endl,
218✔
279
            d_slog->info(Logr::Debug, "lookup query", "key", Logging::Loggable(makeHexDump(key)))));
218✔
280

281
  d_isWildcardQuery = false;
218✔
282
  if (key[0] == '\001' && key[1] == '\052') {
218✔
283
    d_isWildcardQuery = true;
49✔
284
    key.erase(0, 2);
49✔
285
  }
49✔
286

287
  d_qtype = qtype;
218✔
288

289
  try {
218✔
290
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
218✔
291
    d_currentDomain = zoneId;
218✔
292
  }
218✔
293
  catch (const std::exception& e) {
218✔
NEW
294
    SLOG(g_log << Logger::Error << e.what() << endl,
×
NEW
295
         d_slog->error(Logr::Error, e.what(), "unable to initialize database reader"));
×
296
    throw PDNSException(e.what());
×
297
  }
×
298

299
  d_cdbReader->searchKey(key);
218✔
300
  d_dnspacket = pkt_p;
218✔
301
}
218✔
302

303
bool TinyDNSBackend::get(DNSResourceRecord& rr)
304
{
495✔
305
  pair<string, string> record;
495✔
306

307
  while (d_cdbReader->readNext(record)) {
20,853✔
308
    string val = record.second;
20,634✔
309
    string key = record.first;
20,634✔
310

311
#if 0
312
    DLOG(SLOG(g_log<<Logger::Debug<<"[GET] Key: "<<makeHexDump(key)<<endl,
313
              d_slog->info(Logr::Debug, "get", "key", Logging::Loggable(makeHexDump(key)))));
314
    DLOG(SLOG(g_log<<Logger::Debug<<"[GET] Val: "<<makeHexDump(val)<<endl,
315
              d_slog->info(Logr::Debug, "get", "val", Logging::Loggable(makeHexDump(val)))));
316
#endif
317
    if (key[0] == '\000' && key[1] == '\045') { // skip locations
20,634!
318
      continue;
×
319
    }
×
320

321
    if (!d_isAxfr) {
20,634✔
322
      // If we have a wildcard query, but the record we got is not a wildcard, we skip.
323
      if (d_isWildcardQuery && val[2] != '\052' && val[2] != '\053') {
298!
324
        continue;
28✔
325
      }
28✔
326

327
      // If it is NOT a wildcard query, but we do find a wildcard record, we skip it.
328
      if (!d_isWildcardQuery && (val[2] == '\052' || val[2] == '\053')) {
270!
329
        continue;
11✔
330
      }
11✔
331
    }
270✔
332

333
    PacketReader pr(val, 0);
20,595✔
334
    rr.qtype = QType(pr.get16BitInt());
20,595✔
335

336
    if (d_isGetDomains && rr.qtype != QType::SOA) {
20,595✔
337
      continue;
20,319✔
338
    }
20,319✔
339

340
    if (d_isAxfr || d_qtype.getCode() == QType::ANY || rr.qtype == d_qtype) {
276!
341
      char locwild = pr.get8BitInt();
276✔
342
      if (locwild != '\075' && (locwild == '\076' || locwild == '\053')) {
276!
343
        if (d_isAxfr && d_locations) { // We skip records with a location in AXFR, unless we disable locations.
×
344
          continue;
×
345
        }
×
346
        char recloc[2];
×
347
        recloc[0] = pr.get8BitInt();
×
348
        recloc[1] = pr.get8BitInt();
×
349

350
        if (d_locations) {
×
351
          bool foundLocation = false;
×
352
          vector<string> locations = getLocations();
×
353
          while (locations.size() > 0) {
×
354
            string locId = locations.back();
×
355
            locations.pop_back();
×
356

357
            if (recloc[0] == locId[0] && recloc[1] == locId[1]) {
×
358
              foundLocation = true;
×
359
              break;
×
360
            }
×
361
          }
×
362
          if (!foundLocation) {
×
363
            continue;
×
364
          }
×
365
        }
×
366
      }
×
367

368
      if (d_isAxfr && (val[2] == '\052' || val[2] == '\053')) { // Keys are not stored with wildcard character, with AXFR we need to add that.
276!
369
        key.insert(0, 1, '\052');
×
370
        key.insert(0, 1, '\001');
×
371
      }
×
372
      // rr.qname.clear();
373
      rr.qname = DNSName(key.c_str(), key.size(), 0, false);
276✔
374
      rr.domain_id = d_currentDomain;
276✔
375
      // 11:13.21 <@ahu> IT IS ALWAYS AUTH --- well not really because we are just a backend :-)
376
      // We could actually do NSEC3-NARROW DNSSEC according to Habbie, if we do, we need to change something here.
377
      rr.auth = true;
276✔
378

379
      rr.ttl = pr.get32BitInt();
276✔
380
      uint64_t timestamp = pr.get32BitInt();
276✔
381
      timestamp <<= 32;
276✔
382
      timestamp += pr.get32BitInt();
276✔
383
      if (timestamp) {
276!
384
        uint64_t now = d_taiepoch + time(NULL);
×
385
        if (rr.ttl == 0) {
×
386
          if (timestamp < now) {
×
387
            continue;
×
388
          }
×
389
          rr.ttl = timestamp - now;
×
390
          if (rr.ttl <= 2)
×
391
            rr.ttl = 2;
×
392
          if (rr.ttl >= 3600)
×
393
            rr.ttl = 3600;
×
394
        }
×
395
        else if (now <= timestamp) {
×
396
          continue;
×
397
        }
×
398
      }
×
399
      try {
276✔
400
        DNSRecord dr;
276✔
401
        dr.d_class = 1;
276✔
402
        dr.d_type = rr.qtype.getCode();
276✔
403
        dr.d_clen = val.size() - pr.getPosition();
276✔
404

405
        auto drc = DNSRecordContent::make(dr, pr);
276✔
406
        rr.content = drc->getZoneRepresentation();
276✔
407
        DLOG(cerr << "CONTENT: " << rr.content << endl);
276✔
408
      }
276✔
409
      catch (...) {
276✔
NEW
410
        SLOG(g_log << Logger::Error << backendname << "Failed to parse record content for " << rr.qname << " with type " << rr.qtype.toString() << ((d_ignorebogus || d_isGetDomains) ? " (ignoring)" : " (erroring out)") << endl,
×
NEW
411
             d_slog->info(Logr::Error, "failed to parse record contents " + std::string((d_ignorebogus || d_isGetDomains) ? "(ignoring)" : "(erroring out)"), "name", Logging::Loggable(rr.qname), "type", Logging::Loggable(rr.qtype)));
×
412
        if (d_ignorebogus || d_isGetDomains) {
×
413
          continue;
×
414
        }
×
415
        else {
×
416
          throw;
×
417
        }
×
418
      }
×
419
#if 0
420
      DLOG(SLOG(g_log<<Logger::Debug<<backendname<<"Returning ["<<rr.content<<"] for ["<<rr.qname<<"] of RecordType ["<<rr.qtype.toString()<<"]"<<endl,
421
                d_slog->info(Logr::Debug, "returning record", "name", Logging::Loggable(rr.qname), "type", Logging::Loggable(rr.qtype), "contents", Logging::Loggable(rr.content))));
422
#endif
423
      return true;
276✔
424
    }
276✔
425
  } // end of while
276✔
426
  DLOG(SLOG(g_log << Logger::Debug << backendname << "No more records to return." << endl,
219✔
427
            d_slog->info(Logr::Debug, "no more records to return")));
219✔
428

429
  d_cdbReader = nullptr;
219✔
430
  d_currentDomain = UnknownDomainID;
219✔
431
  return false;
219✔
432
}
495✔
433

434
// boilerplate
435
class TinyDNSFactory : public BackendFactory
436
{
437
public:
438
  TinyDNSFactory() :
439
    BackendFactory("tinydns") {}
5,904✔
440

441
  void declareArguments(const string& suffix = "") override
442
  {
1✔
443
    declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.", "no");
1✔
444
    declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
1✔
445
    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✔
446
    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✔
447
    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✔
448
  }
1✔
449

450
  DNSBackend* make(const string& suffix = "") override
451
  {
5✔
452
    return new TinyDNSBackend(suffix);
5✔
453
  }
5✔
454
};
455

456
// boilerplate
457
class TinyDNSLoader
458
{
459
public:
460
  TinyDNSLoader()
461
  {
5,904✔
462
    BackendMakers().report(std::make_unique<TinyDNSFactory>());
5,904✔
463
    // If this module is not loaded dynamically at runtime, this code runs
464
    // as part of a global constructor, before the structured logger has a
465
    // chance to be set up, so fallback to simple logging in this case.
466
    if (!g_slogStructured || !g_slog) {
5,904!
467
      g_log << Logger::Info << "[tinydnsbackend] This is the tinydns backend version " VERSION
5,904✔
468
#ifndef REPRODUCIBLE
5,904✔
469
            << " (" __DATE__ " " __TIME__ ")"
5,904✔
470
#endif
5,904✔
471
            << " reporting" << endl;
5,904✔
472
    }
5,904✔
NEW
473
    else {
×
NEW
474
      g_slog->withName("tinydnsbackend")->info(Logr::Info, "tinydns backend starting", "version", Logging::Loggable(VERSION)
×
NEW
475
#ifndef REPRODUCIBLE
×
NEW
476
                                                                                                    ,
×
NEW
477
                                               "build date", Logging::Loggable(__DATE__ " " __TIME__)
×
NEW
478
#endif
×
NEW
479
      );
×
NEW
480
    }
×
481
  }
5,904✔
482
};
483

484
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