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

PowerDNS / pdns / 14497954401

16 Apr 2025 04:40PM UTC coverage: 63.538% (+0.05%) from 63.485%
14497954401

Pull #15441

github

web-flow
Merge 8bfeeeb15 into f7c7e299c
Pull Request #15441: [evil] ZoneName, step 2

41812 of 100540 branches covered (41.59%)

Branch coverage included in aggregate %.

357 of 407 new or added lines in 38 files covered. (87.71%)

38 existing lines in 10 files now uncovered.

129019 of 168323 relevant lines covered (76.65%)

3923687.88 hits per line

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

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

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

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

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

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

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

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

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

205
  TDI_t* state = &(*domainInfo)[d_suffix];
1✔
206

207
  getAllDomains_locked(domains, getSerial);
1✔
208

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

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

225
  TDI_t* state = &(*domainInfo)[d_suffix];
×
226

227
  vector<DomainInfo> allDomains;
×
228
  getAllDomains_locked(&allDomains, getSerial);
×
229

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

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

256
  return d_cdbReader->searchSuffix(key);
×
257
}
×
258

259
void TinyDNSBackend::lookup(const QType& qtype, const DNSName& qdomain, int /* zoneId */, DNSPacket* pkt_p)
260
{
232✔
261
  d_isAxfr = false;
232✔
262
  d_isGetDomains = false;
232✔
263
  string queryDomain = toLowerCanonic(qdomain.toString());
232✔
264

265
  string key = simpleCompress(queryDomain);
232✔
266

267
  DLOG(g_log << Logger::Debug << backendname << "[lookup] query for qtype [" << qtype.toString() << "] qdomain [" << qdomain << "]" << endl);
232✔
268
  DLOG(g_log << Logger::Debug << "[lookup] key [" << makeHexDump(key) << "]" << endl);
232✔
269

270
  d_isWildcardQuery = false;
232✔
271
  if (key[0] == '\001' && key[1] == '\052') {
232✔
272
    d_isWildcardQuery = true;
49✔
273
    key.erase(0, 2);
49✔
274
  }
49✔
275

276
  d_qtype = qtype;
232✔
277

278
  try {
232✔
279
    d_cdbReader = std::make_unique<CDB>(getArg("dbfile"));
232✔
280
  }
232✔
281
  catch (const std::exception& e) {
232✔
282
    g_log << Logger::Error << e.what() << endl;
×
283
    throw PDNSException(e.what());
×
284
  }
×
285

286
  d_cdbReader->searchKey(key);
232✔
287
  d_dnspacket = pkt_p;
232✔
288
}
232✔
289

290
bool TinyDNSBackend::get(DNSResourceRecord& rr)
291
{
579✔
292
  pair<string, string> record;
579✔
293

294
  while (d_cdbReader->readNext(record)) {
20,937✔
295
    string val = record.second;
20,704✔
296
    string key = record.first;
20,704✔
297

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

304
    if (!d_isAxfr) {
20,704✔
305
      // If we have a wildcard query, but the record we got is not a wildcard, we skip.
306
      if (d_isWildcardQuery && val[2] != '\052' && val[2] != '\053') {
370!
307
        continue;
28✔
308
      }
28✔
309

310
      // If it is NOT a wildcard query, but we do find a wildcard record, we skip it.
311
      if (!d_isWildcardQuery && (val[2] == '\052' || val[2] == '\053')) {
342!
312
        continue;
13✔
313
      }
13✔
314
    }
342✔
315

316
    PacketReader pr(val, 0);
20,663✔
317
    rr.qtype = QType(pr.get16BitInt());
20,663✔
318

319
    if (d_isGetDomains && rr.qtype != QType::SOA) {
20,663✔
320
      continue;
20,317✔
321
    }
20,317✔
322

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

333
        if (d_locations) {
×
334
          bool foundLocation = false;
×
335
          vector<string> locations = getLocations();
×
336
          while (locations.size() > 0) {
×
337
            string locId = locations.back();
×
338
            locations.pop_back();
×
339

340
            if (recloc[0] == locId[0] && recloc[1] == locId[1]) {
×
341
              foundLocation = true;
×
342
              break;
×
343
            }
×
344
          }
×
345
          if (!foundLocation) {
×
346
            continue;
×
347
          }
×
348
        }
×
349
      }
×
350

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

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

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

409
  d_cdbReader = nullptr;
233✔
410
  return false;
233✔
411
}
579✔
412

413
// boilerplate
414
class TinyDNSFactory : public BackendFactory
415
{
416
public:
417
  TinyDNSFactory() :
418
    BackendFactory("tinydns") {}
3,912✔
419

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

429
  DNSBackend* make(const string& suffix = "") override
430
  {
5✔
431
    return new TinyDNSBackend(suffix);
5✔
432
  }
5✔
433
};
434

435
// boilerplate
436
class TinyDNSLoader
437
{
438
public:
439
  TinyDNSLoader()
440
  {
3,912✔
441
    BackendMakers().report(std::make_unique<TinyDNSFactory>());
3,912✔
442
    g_log << Logger::Info << "[tinydnsbackend] This is the tinydns backend version " VERSION
3,912✔
443
#ifndef REPRODUCIBLE
3,912✔
444
          << " (" __DATE__ " " __TIME__ ")"
3,912✔
445
#endif
3,912✔
446
          << " reporting" << endl;
3,912✔
447
  }
3,912✔
448
};
449

450
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