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

PowerDNS / pdns / 18102394575

29 Sep 2025 03:34PM UTC coverage: 65.352% (-0.7%) from 66.079%
18102394575

Pull #15550

github

web-flow
Merge 1403bf643 into 58b4e6e1e
Pull Request #15550: Support new SVCB parameters: ohttp, dohpath, tls-supported-groups

42050 of 93184 branches covered (45.13%)

Branch coverage included in aggregate %.

330 of 333 new or added lines in 8 files covered. (99.1%)

1491 existing lines in 46 files now uncovered.

128093 of 167166 relevant lines covered (76.63%)

5144842.61 hits per line

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

2.05
/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)
UNCOV
79
{
×
UNCOV
80
  setArgPrefix("tinydns" + suffix);
×
UNCOV
81
  d_suffix = suffix;
×
UNCOV
82
  d_locations = mustDo("locations");
×
UNCOV
83
  d_ignorebogus = mustDo("ignore-bogus-records");
×
UNCOV
84
  d_taiepoch = 4611686018427387904ULL + getArgAsNum("tai-adjust");
×
UNCOV
85
  d_dnspacket = NULL;
×
UNCOV
86
  d_cdbReader = NULL;
×
UNCOV
87
  d_isAxfr = false;
×
UNCOV
88
  d_isWildcardQuery = false;
×
UNCOV
89
}
×
90

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

UNCOV
99
  TinyDomainInfo tmp;
×
UNCOV
100
  s_lastId++;
×
UNCOV
101
  tmp.zone = domain.zone;
×
UNCOV
102
  tmp.id = s_lastId;
×
UNCOV
103
  tmp.notified_serial = domain.serial;
×
UNCOV
104
  return state->insert(tmp).first;
×
UNCOV
105
}
×
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)
UNCOV
155
{
×
UNCOV
156
  d_isAxfr = true;
×
UNCOV
157
  d_isGetDomains = true;
×
UNCOV
158
  d_dnspacket = NULL;
×
159

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

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

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

UNCOV
182
      if (getSerial) {
×
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

UNCOV
193
      di.notified_serial = di.serial;
×
UNCOV
194
      domains->push_back(di);
×
UNCOV
195
    }
×
UNCOV
196
  }
×
UNCOV
197
}
×
198

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

UNCOV
207
  TDI_t* state = &(*domainInfo)[d_suffix];
×
208

UNCOV
209
  getAllDomains_locked(domains, getSerial);
×
210

UNCOV
211
  for (auto& domain : *domains) {
×
UNCOV
212
    auto iter = updateState(domain, state);
×
213
    // Keep domain id in sync with our current state.
UNCOV
214
    domain.id = iter->id;
×
UNCOV
215
  }
×
UNCOV
216
}
×
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)
UNCOV
263
{
×
UNCOV
264
  d_isAxfr = false;
×
UNCOV
265
  d_isGetDomains = false;
×
UNCOV
266
  string queryDomain = toLowerCanonic(qdomain.toString());
×
267

UNCOV
268
  string key = simpleCompress(queryDomain);
×
269

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

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

UNCOV
279
  d_qtype = qtype;
×
280

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

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

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

UNCOV
298
  while (d_cdbReader->readNext(record)) {
×
UNCOV
299
    string val = record.second;
×
UNCOV
300
    string key = record.first;
×
301

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

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

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

UNCOV
320
    PacketReader pr(val, 0);
×
UNCOV
321
    rr.qtype = QType(pr.get16BitInt());
×
322

UNCOV
323
    if (d_isGetDomains && rr.qtype != QType::SOA) {
×
UNCOV
324
      continue;
×
UNCOV
325
    }
×
326

UNCOV
327
    if (d_isAxfr || d_qtype.getCode() == QType::ANY || rr.qtype == d_qtype) {
×
UNCOV
328
      char locwild = pr.get8BitInt();
×
UNCOV
329
      if (locwild != '\075' && (locwild == '\076' || locwild == '\053')) {
×
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

UNCOV
355
      if (d_isAxfr && (val[2] == '\052' || val[2] == '\053')) { // Keys are not stored with wildcard character, with AXFR we need to add that.
×
356
        key.insert(0, 1, '\052');
×
357
        key.insert(0, 1, '\001');
×
358
      }
×
359
      // rr.qname.clear();
UNCOV
360
      rr.qname = DNSName(key.c_str(), key.size(), 0, false);
×
UNCOV
361
      rr.domain_id = d_currentDomain;
×
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.
UNCOV
364
      rr.auth = true;
×
365

UNCOV
366
      rr.ttl = pr.get32BitInt();
×
UNCOV
367
      uint64_t timestamp = pr.get32BitInt();
×
UNCOV
368
      timestamp <<= 32;
×
UNCOV
369
      timestamp += pr.get32BitInt();
×
UNCOV
370
      if (timestamp) {
×
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
      }
×
UNCOV
386
      try {
×
UNCOV
387
        DNSRecord dr;
×
UNCOV
388
        dr.d_class = 1;
×
UNCOV
389
        dr.d_type = rr.qtype.getCode();
×
UNCOV
390
        dr.d_clen = val.size() - pr.getPosition();
×
391

UNCOV
392
        auto drc = DNSRecordContent::make(dr, pr);
×
UNCOV
393
        rr.content = drc->getZoneRepresentation();
×
UNCOV
394
        DLOG(cerr << "CONTENT: " << rr.content << endl);
×
UNCOV
395
      }
×
UNCOV
396
      catch (...) {
×
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;);
UNCOV
408
      return true;
×
UNCOV
409
    }
×
UNCOV
410
  } // end of while
×
UNCOV
411
  DLOG(g_log << Logger::Debug << backendname << "No more records to return." << endl);
×
412

UNCOV
413
  d_cdbReader = nullptr;
×
UNCOV
414
  d_currentDomain = UnknownDomainID;
×
UNCOV
415
  return false;
×
UNCOV
416
}
×
417

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

425
  void declareArguments(const string& suffix = "") override
UNCOV
426
  {
×
UNCOV
427
    declare(suffix, "notify-on-startup", "Tell the TinyDNSBackend to notify all the secondary nameservers on startup. Default is no.", "no");
×
UNCOV
428
    declare(suffix, "dbfile", "Location of the cdb data file", "data.cdb");
×
UNCOV
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");
×
UNCOV
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");
×
UNCOV
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");
×
UNCOV
432
  }
×
433

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

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