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

PowerDNS / pdns / 13112842211

03 Feb 2025 11:51AM UTC coverage: 64.731% (+0.02%) from 64.716%
13112842211

push

github

web-flow
Merge pull request #15103 from miodvallat/minus_one_is_the_new_black

Grow tinybackend capabilities a bit

38348 of 90328 branches covered (42.45%)

Branch coverage included in aggregate %.

25 of 60 new or added lines in 1 file covered. (41.67%)

44 existing lines in 10 files now uncovered.

127918 of 166528 relevant lines covered (76.81%)

4578067.98 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!
NEW
96
    return itByZone;
×
NEW
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
{
×
NEW
109
  bool alwaysNotify{false};
×
110
  auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
×
NEW
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.
NEW
114
    alwaysNotify = mustDo("notify-on-startup");
×
115
    TDI_t tmp;
×
116
    domainInfo->emplace(d_suffix, tmp);
×
117
  }
×
118

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

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

NEW
124
  for (auto& domain : allDomains) {
×
NEW
125
    auto iter = updateState(domain, state);
×
126
    // Keep domain id in sync with our current state.
NEW
127
    domain.id = iter->id;
×
NEW
128
    if (alwaysNotify || iter->notified_serial < domain.serial) {
×
NEW
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
  }
×
NEW
140
  TDI_t* state = &(*domainInfo)[d_suffix];
×
NEW
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
  }
×
NEW
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 = 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 DNSName& domain, DomainInfo& di, bool getSerial)
NEW
218
{
×
NEW
219
  auto domainInfo = s_domainInfo.lock(); //TODO: We could actually lock less if we do it per suffix.
×
NEW
220
  if (domainInfo->count(d_suffix) == 0) {
×
NEW
221
    TDI_t tmp;
×
NEW
222
    domainInfo->emplace(d_suffix, tmp);
×
NEW
223
  }
×
224

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

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

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

243
bool TinyDNSBackend::list(const DNSName& target, int /* domain_id */, bool /* include_disabled */)
244
{
×
245
  d_isAxfr = true;
×
246
  d_isGetDomains = false;
×
247
  string key = target.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
{
229✔
261
  d_isAxfr = false;
229✔
262
  d_isGetDomains = false;
229✔
263
  string queryDomain = toLowerCanonic(qdomain.toString());
229✔
264

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

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

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

276
  d_qtype = qtype;
229✔
277

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

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

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

294
  while (d_cdbReader->readNext(record)) {
20,924✔
295
    string val = record.second;
20,694✔
296
    string key = record.first;
20,694✔
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,694!
301
      continue;
×
302
    }
×
303

304
    if (!d_isAxfr) {
20,694✔
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') {
365!
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')) {
337!
312
        continue;
13✔
313
      }
13✔
314
    }
337✔
315

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

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

323
    if (d_isAxfr || d_qtype.getCode() == QType::ANY || rr.qtype == d_qtype) {
341!
324
      char locwild = pr.get8BitInt();
341✔
325
      if (locwild != '\075' && (locwild == '\076' || locwild == '\053')) {
341!
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.
341!
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);
341✔
357
      rr.domain_id = -1;
341✔
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;
341✔
361

362
      rr.ttl = pr.get32BitInt();
341✔
363
      uint64_t timestamp = pr.get32BitInt();
341✔
364
      timestamp <<= 32;
341✔
365
      timestamp += pr.get32BitInt();
341✔
366
      if (timestamp) {
341!
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 {
341✔
383
        DNSRecord dr;
341✔
384
        dr.d_class = 1;
341✔
385
        dr.d_type = rr.qtype.getCode();
341✔
386
        dr.d_clen = val.size() - pr.getPosition();
341✔
387

388
        auto drc = DNSRecordContent::make(dr, pr);
341✔
389
        rr.content = drc->getZoneRepresentation();
341✔
390
        DLOG(cerr << "CONTENT: " << rr.content << endl);
341✔
391
      }
341✔
392
      catch (...) {
341✔
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;
341✔
405
    }
341✔
406
  } // end of while
341✔
407
  DLOG(g_log << Logger::Debug << backendname << "No more records to return." << endl);
230✔
408

409
  d_cdbReader = nullptr;
230✔
410
  return false;
230✔
411
}
571✔
412

413
// boilerplate
414
class TinyDNSFactory : public BackendFactory
415
{
416
public:
417
  TinyDNSFactory() :
418
    BackendFactory("tinydns") {}
3,484✔
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,484✔
441
    BackendMakers().report(std::make_unique<TinyDNSFactory>());
3,484✔
442
    g_log << Logger::Info << "[tinydnsbackend] This is the tinydns backend version " VERSION
3,484✔
443
#ifndef REPRODUCIBLE
3,484✔
444
          << " (" __DATE__ " " __TIME__ ")"
3,484✔
445
#endif
3,484✔
446
          << " reporting" << endl;
3,484✔
447
  }
3,484✔
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

© 2025 Coveralls, Inc