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

PowerDNS / pdns / 26633102090

29 May 2026 10:51AM UTC coverage: 71.072% (+4.1%) from 66.93%
26633102090

Pull #17481

github

web-flow
Merge 297bae12d into 82597ead2
Pull Request #17481: bump meson to 1.11.1

46324 of 81426 branches covered (56.89%)

Branch coverage included in aggregate %.

132737 of 170516 relevant lines covered (77.84%)

6394113.4 hits per line

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

71.91
/modules/lmdbbackend/lmdbbackend.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

23
#include "lmdbbackend.hh"
24

25
#include "config.h"
26
#include "ext/lmdb-safe/lmdb-safe.hh"
27
#include "pdns/arguments.hh"
28
#include "pdns/base32.hh"
29
#include "pdns/dns.hh"
30
#include "pdns/dnsbackend.hh"
31
#include "pdns/dnsname.hh"
32
#include "pdns/dnspacket.hh"
33
#include "pdns/dnssecinfra.hh"
34
#include "pdns/logger.hh"
35
#include "pdns/misc.hh"
36
#include "pdns/pdnsexception.hh"
37
#include "pdns/sha.hh"
38
#include "pdns/uuid-utils.hh"
39
#include <boost/archive/binary_iarchive.hpp>
40
#include <boost/archive/binary_oarchive.hpp>
41
#include <boost/iostreams/device/back_inserter.hpp>
42
#include <boost/serialization/string.hpp>
43
#include <boost/serialization/utility.hpp>
44
#include <boost/serialization/vector.hpp>
45
#include <boost/uuid/uuid_serialize.hpp>
46
#include <protozero/pbf_reader.hpp>
47
#include <protozero/pbf_writer.hpp>
48
#include <cstdio>
49
#include <cstring>
50
#include <lmdb.h>
51
#include <memory>
52
#include <stdexcept>
53
#include <unistd.h>
54
#include <utility>
55

56
#ifdef HAVE_SYSTEMD
57
#include <systemd/sd-daemon.h>
58
#endif
59

60
constexpr unsigned int SCHEMAVERSION{6};
61

62
// List the class version here. Default is 0
63
BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB, 1)
64
BOOST_CLASS_VERSION(ZoneName, 1)
65
BOOST_CLASS_VERSION(DomainInfo, 2)
66

67
static bool s_first = true;
68
static uint32_t s_shards = 0;
69
static std::mutex s_lmdbStartupLock;
70

71
std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string& filename)
72
{
2,564✔
73
  // cerr << "getting schema version for path " << filename << endl;
74

75
  uint32_t schemaversion = 0;
2,564✔
76

77
  MDB_env* tmpEnv = nullptr;
2,564✔
78

79
  if (int retCode = mdb_env_create(&tmpEnv); retCode != 0) {
2,564!
80
    throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
81
  }
×
82

83
  std::unique_ptr<MDB_env, decltype(&mdb_env_close)> env{tmpEnv, mdb_env_close};
2,564✔
84

85
  if (int retCode = mdb_env_set_mapsize(tmpEnv, 0); retCode != 0) {
2,564!
86
    throw std::runtime_error("mdb_env_set_mapsize failed: " + MDBError(retCode));
×
87
  }
×
88

89
  if (int retCode = mdb_env_set_maxdbs(tmpEnv, 20); retCode != 0) { // we need 17: 1 {"pdns"} + 4 {"domains", "keydata", "tsig", "metadata"} * 2 {v4, v5} * 2 {main, index in _0}
2,564!
90
    throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
91
  }
×
92

93
  {
2,564✔
94
    int retCode = mdb_env_open(tmpEnv, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600);
2,564✔
95
    if (retCode != 0) {
2,564✔
96
      if (retCode == ENOENT) {
60!
97
        // we don't have a database yet! report schema 0, with 0 shards
98
        return {0U, 0U};
60✔
99
      }
60✔
100
      throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
101
    }
60✔
102
  }
2,564✔
103

104
  MDB_txn* txn = nullptr;
2,504✔
105

106
  if (int retCode = mdb_txn_begin(tmpEnv, nullptr, MDB_RDONLY, &txn); retCode != 0) {
2,504!
107
    throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
108
  }
×
109

110
  MDB_dbi dbi;
2,504✔
111

112
  {
2,504✔
113
    int retCode = MDBDbi::mdb_dbi_open(txn, "pdns", 0, &dbi);
2,504✔
114
    if (retCode != 0) {
2,504!
115
      if (retCode == MDB_NOTFOUND) {
×
116
        // this means nothing has been inited yet
117
        // we pretend this means the latest schema
118
        mdb_txn_abort(txn);
×
119
        return {SCHEMAVERSION, 0U};
×
120
      }
×
121
      mdb_txn_abort(txn);
×
122
      throw std::runtime_error("mdb_dbi_open failed: " + MDBError(retCode));
×
123
    }
×
124
  }
2,504✔
125

126
  MDB_val key, data;
2,504✔
127

128
  key.mv_data = (char*)"schemaversion";
2,504✔
129
  key.mv_size = strlen((char*)key.mv_data);
2,504✔
130

131
  {
2,504✔
132
    int retCode = mdb_get(txn, dbi, &key, &data);
2,504✔
133
    if (retCode != 0) {
2,504!
134
      if (retCode == MDB_NOTFOUND) {
×
135
        // this means nothing has been inited yet
136
        // we pretend this means the latest schema
137
        mdb_txn_abort(txn);
×
138
        return {SCHEMAVERSION, 0U};
×
139
      }
×
140

141
      throw std::runtime_error("mdb_get pdns.schemaversion failed: " + MDBError(retCode));
×
142
    }
×
143
  }
2,504✔
144

145
  if (data.mv_size == 4) {
2,504✔
146
    // schemaversion is < 5 and is stored in 32 bits, in host order
147

148
    memcpy(&schemaversion, data.mv_data, data.mv_size);
4✔
149
  }
4✔
150
  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(schemaversion)) {
2,500!
151
    // schemaversion is >= 5, stored in 32 bits, network order, after the LS header
152

153
    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
154
    // FIXME: add a test for reading schemaversion and shards (and actual data, later) when there are variably sized headers
155
    memcpy(&schemaversion, (char*)data.mv_data + data.mv_size - sizeof(schemaversion), sizeof(schemaversion));
2,500✔
156
    schemaversion = ntohl(schemaversion);
2,500✔
157
  }
2,500✔
158
  else {
×
159
    throw std::runtime_error("pdns.schemaversion had unexpected size");
×
160
  }
×
161

162
  uint32_t shards = 0;
2,504✔
163

164
  key.mv_data = (char*)"shards";
2,504✔
165
  key.mv_size = strlen((char*)key.mv_data);
2,504✔
166

167
  {
2,504✔
168
    int retCode = mdb_get(txn, dbi, &key, &data);
2,504✔
169
    if (retCode != 0) {
2,504!
170
      if (retCode == MDB_NOTFOUND) {
×
171
        cerr << "schemaversion was set, but shards was not. Dazed and confused, trying to exit." << endl;
×
172
        mdb_txn_abort(txn);
×
173
        // NOLINTNEXTLINE(concurrency-mt-unsafe)
174
        exit(1);
×
175
      }
×
176

177
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
178
    }
×
179
  }
2,504✔
180

181
  if (data.mv_size == 4) {
2,504✔
182
    // 'shards' is stored in 32 bits, in host order
183

184
    memcpy(&shards, data.mv_data, data.mv_size);
4✔
185
  }
4✔
186
  else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(shards)) {
2,500!
187
    // FIXME: get actual header size (including extension blocks) instead of just reading from the back
188
    memcpy(&shards, (char*)data.mv_data + data.mv_size - sizeof(shards), sizeof(shards));
2,500✔
189
    shards = ntohl(shards);
2,500✔
190
  }
2,500✔
191
  else {
×
192
    throw std::runtime_error("pdns.shards had unexpected size");
×
193
  }
×
194

195
  mdb_txn_abort(txn);
2,504✔
196

197
  return {schemaversion, shards};
2,504✔
198
}
2,504✔
199

200
namespace
201
{
202
// copy sdbi to tdbi, prepending an empty LS header (24 bytes of '\0') to all values
203
void copyDBIAndAddLSHeader(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
204
{
4✔
205
  // FIXME: clear out target dbi first
206

207
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
4✔
208
  int rc;
4✔
209

210
  MDB_cursor* cur;
4✔
211

212
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
4!
213
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
214
  }
×
215

216
  MDB_val key, data;
4✔
217

218
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
4✔
219

220
  while (rc == 0) {
40,448✔
221
    std::string skey(reinterpret_cast<const char*>(key.mv_data), key.mv_size);
40,444✔
222
    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
40,444✔
223

224
    std::string stdata = header + sdata;
40,444✔
225

226
    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
227

228
    MDB_val tkey;
40,444✔
229
    MDB_val tdata;
40,444✔
230

231
    tkey.mv_data = const_cast<char*>(skey.c_str());
40,444✔
232
    tkey.mv_size = skey.size();
40,444✔
233
    tdata.mv_data = const_cast<char*>(stdata.c_str());
40,444✔
234
    tdata.mv_size = stdata.size();
40,444✔
235

236
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
40,444!
237
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
238
    }
×
239

240
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
40,444✔
241
  }
40,444✔
242
  if (rc != MDB_NOTFOUND) {
4!
243
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
244
  }
×
245
}
4✔
246

247
// migrated a typed DBI:
248
// 1. change keys (uint32_t) from host to network order
249
// 2. prepend empty LS header to values
250
void copyTypedDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
251
{
8✔
252
  // FIXME: clear out target dbi first
253

254
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
8✔
255
  int rc;
8✔
256

257
  MDB_cursor* cur;
8✔
258

259
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
8!
260
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
261
  }
×
262

263
  MDB_val key, data;
8✔
264

265
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
266

267
  while (rc == 0) {
40✔
268
    // std::string skey((char*) key.mv_data, key.mv_size);
269
    std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
32✔
270

271
    std::string stdata = header + sdata;
32✔
272

273
    uint32_t id;
32✔
274

275
    if (key.mv_size != sizeof(uint32_t)) {
32!
276
      throw std::runtime_error("got non-uint32_t key in TypedDBI");
×
277
    }
×
278

279
    memcpy(&id, key.mv_data, sizeof(uint32_t));
32✔
280

281
    id = htonl(id);
32✔
282

283
    // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
284

285
    MDB_val tkey;
32✔
286
    MDB_val tdata;
32✔
287

288
    tkey.mv_data = reinterpret_cast<char*>(&id);
32✔
289
    tkey.mv_size = sizeof(uint32_t);
32✔
290
    tdata.mv_data = const_cast<char*>(stdata.c_str());
32✔
291
    tdata.mv_size = stdata.size();
32✔
292

293
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
32!
294
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
295
    }
×
296

297
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
32✔
298
  }
32✔
299
  if (rc != MDB_NOTFOUND) {
8!
300
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
301
  }
×
302
}
8✔
303

304
// migrating an index DBI:
305
// newkey = oldkey.len(), oldkey, htonl(oldvalue)
306
// newvalue = empty lsheader
307
void copyIndexDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
308
{
8✔
309
  // FIXME: clear out target dbi first
310

311
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
8✔
312
  int rc;
8✔
313

314
  MDB_cursor* cur;
8✔
315

316
  if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
8!
317
    throw std::runtime_error("mdb_cursor_open failed: " + MDBError(rc));
×
318
  }
×
319

320
  MDB_val key, data;
8✔
321

322
  rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
8✔
323

324
  while (rc == 0) {
40✔
325
    std::string lenprefix(sizeof(uint16_t), '\0');
32✔
326
    std::string skey((char*)key.mv_data, key.mv_size);
32✔
327

328
    uint32_t id;
32✔
329

330
    if (data.mv_size != sizeof(uint32_t)) {
32!
331
      throw std::runtime_error("got non-uint32_t ID value in IndexDBI");
×
332
    }
×
333

334
    memcpy((void*)&id, data.mv_data, sizeof(uint32_t));
32✔
335
    id = htonl(id);
32✔
336

337
    uint16_t len = htons(skey.size());
32✔
338
    memcpy((void*)lenprefix.data(), &len, sizeof(len));
32✔
339
    std::string stkey = lenprefix + skey + std::string((char*)&id, sizeof(uint32_t));
32✔
340

341
    MDB_val tkey;
32✔
342
    MDB_val tdata;
32✔
343

344
    tkey.mv_data = (char*)stkey.c_str();
32✔
345
    tkey.mv_size = stkey.size();
32✔
346
    tdata.mv_data = (char*)header.c_str();
32✔
347
    tdata.mv_size = header.size();
32✔
348

349
    if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
32!
350
      throw std::runtime_error("mdb_put failed: " + MDBError(rc));
×
351
    }
×
352

353
    rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
32✔
354
  }
32✔
355
  if (rc != MDB_NOTFOUND) {
8!
356
    throw std::runtime_error("error while iterating dbi: " + MDBError(rc));
×
357
  }
×
358
}
8✔
359

360
}
361

362
bool LMDBBackend::upgradeToSchemav5(std::string& filename)
363
{
2✔
364
  auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
2✔
365
  uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
2✔
366
  uint32_t shards = currentSchemaVersionAndShards.second;
2✔
367

368
  if (currentSchemaVersion != 3 && currentSchemaVersion != 4) {
2!
369
    throw std::runtime_error("upgrade to v5 requested but current schema is not v3 or v4, stopping");
×
370
  }
×
371

372
  MDB_env* env = nullptr;
2✔
373

374
  if (int retCode = mdb_env_create(&env); retCode != 0) {
2!
375
    throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
376
  }
×
377

378
  std::unique_ptr<MDB_env, decltype(&mdb_env_close)> envGuard{env, mdb_env_close};
2✔
379

380
  if (int retCode = mdb_env_set_maxdbs(env, 20); retCode != 0) {
2!
381
    throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
382
  }
×
383

384
  if (int retCode = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR, 0600); retCode != 0) {
2!
385
    throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
386
  }
×
387

388
  MDB_txn* txn = nullptr;
2✔
389

390
  if (int retCode = mdb_txn_begin(env, nullptr, 0, &txn); retCode != 0) {
2!
391
    throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
392
  }
×
393

394
#ifdef HAVE_SYSTEMD
2✔
395
  /* A schema migration may take a long time. Extend the startup service timeout to 1 day,
396
   * but only if this is beyond the original maximum time of TimeoutStartSec=.
397
   */
398
  sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
2✔
399
#endif
2✔
400

401
  std::cerr << "migrating shards" << std::endl;
2✔
402
  for (uint32_t i = 0; i < shards; i++) {
6✔
403
    string shardfile = filename + "-" + std::to_string(i);
4✔
404
    if (access(shardfile.c_str(), F_OK) < 0) {
4!
405
      if (errno == ENOENT) {
×
406
        // apparently this shard doesn't exist yet, moving on
407
        std::cerr << "shard " << shardfile << " not found, continuing" << std::endl;
×
408
        continue;
×
409
      }
×
410
    }
×
411

412
    std::cerr << "migrating shard " << shardfile << std::endl;
4✔
413
    MDB_env* shenv = nullptr;
4✔
414

415
    if (int retCode = mdb_env_create(&shenv); retCode != 0) {
4!
416
      throw std::runtime_error("mdb_env_create failed: " + MDBError(retCode));
×
417
    }
×
418

419
    std::unique_ptr<MDB_env, decltype(&mdb_env_close)> shenvGuard{shenv, mdb_env_close};
4✔
420

421
    if (int retCode = mdb_env_set_maxdbs(shenv, 8); retCode != 0) {
4!
422
      throw std::runtime_error("mdb_env_set_maxdbs failed: " + MDBError(retCode));
×
423
    }
×
424

425
    if (int retCode = mdb_env_open(shenv, shardfile.c_str(), MDB_NOSUBDIR, 0600); retCode != 0) {
4!
426
      throw std::runtime_error("mdb_env_open failed: " + MDBError(retCode));
×
427
    }
×
428

429
    MDB_txn* shtxn = nullptr;
4✔
430

431
    if (int retCode = mdb_txn_begin(shenv, nullptr, 0, &shtxn); retCode != 0) {
4!
432
      throw std::runtime_error("mdb_txn_begin failed: " + MDBError(retCode));
×
433
    }
×
434

435
    MDB_dbi shdbi = 0;
4✔
436

437
    const auto dbiOpenRc = MDBDbi::mdb_dbi_open(shtxn, "records", 0, &shdbi);
4✔
438
    if (dbiOpenRc != 0) {
4!
439
      if (dbiOpenRc == MDB_NOTFOUND) {
×
440
        mdb_txn_abort(shtxn);
×
441
        continue;
×
442
      }
×
443
      mdb_txn_abort(shtxn);
×
444
      throw std::runtime_error("mdb_dbi_open shard records failed: " + MDBError(dbiOpenRc));
×
445
    }
×
446

447
    MDB_dbi shdbi2 = 0;
4✔
448

449
    if (int retCode = MDBDbi::mdb_dbi_open(shtxn, "records_v5", MDB_CREATE, &shdbi2); retCode != 0) {
4!
450
      mdb_dbi_close(shenv, shdbi);
×
451
      mdb_txn_abort(shtxn);
×
452
      throw std::runtime_error("mdb_dbi_open shard records_v5 failed: " + MDBError(retCode));
×
453
    }
×
454

455
    try {
4✔
456
      copyDBIAndAddLSHeader(shtxn, shdbi, shdbi2);
4✔
457
    }
4✔
458
    catch (std::exception& e) {
4✔
459
      mdb_dbi_close(shenv, shdbi2);
×
460
      mdb_dbi_close(shenv, shdbi);
×
461
      mdb_txn_abort(shtxn);
×
462
      throw std::runtime_error("copyDBIAndAddLSHeader failed");
×
463
    }
×
464

465
    cerr << "shard mbd_drop=" << mdb_drop(shtxn, shdbi, 1) << endl;
4✔
466
    mdb_txn_commit(shtxn);
4✔
467
    mdb_dbi_close(shenv, shdbi2);
4✔
468
  }
4✔
469

470
  std::array<MDB_dbi, 4> fromtypeddbi{};
2✔
471
  std::array<MDB_dbi, 4> totypeddbi{};
2✔
472

473
  int index = 0;
2✔
474

475
  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
8✔
476
    std::cerr << "migrating " << dbname << std::endl;
8✔
477
    std::string tdbname = dbname + "_v5";
8✔
478

479
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
480
    if (int retCode = MDBDbi::mdb_dbi_open(txn, dbname.c_str(), 0, &fromtypeddbi[index]); retCode != 0) {
8!
481
      mdb_txn_abort(txn);
×
482
      throw std::runtime_error("MDBDbi::mdb_dbi_open typeddbi failed: " + MDBError(retCode));
×
483
    }
×
484

485
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
486
    if (int retCode = MDBDbi::mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &totypeddbi[index]); retCode != 0) {
8!
487
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
488
      mdb_dbi_close(env, fromtypeddbi[index]);
×
489
      mdb_txn_abort(txn);
×
490
      throw std::runtime_error("mdb_dbi_open typeddbi target failed: " + MDBError(retCode));
×
491
    }
×
492

493
    try {
8✔
494
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
495
      copyTypedDBI(txn, fromtypeddbi[index], totypeddbi[index]);
8✔
496
    }
8✔
497
    catch (std::exception& e) {
8✔
498
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
499
      mdb_dbi_close(env, totypeddbi[index]);
×
500
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
501
      mdb_dbi_close(env, fromtypeddbi[index]);
×
502
      mdb_txn_abort(txn);
×
503
      throw std::runtime_error("copyTypedDBI failed");
×
504
    }
×
505

506
    // mdb_dbi_close(env, dbi2);
507
    // mdb_dbi_close(env, dbi);
508
    std::cerr << "migrated " << dbname << std::endl;
8✔
509

510
    index++;
8✔
511
  }
8✔
512

513
  std::array<MDB_dbi, 4> fromindexdbi{};
2✔
514
  std::array<MDB_dbi, 4> toindexdbi{};
2✔
515

516
  index = 0;
2✔
517

518
  for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
8✔
519
    std::string fdbname = dbname + "_0";
8✔
520
    std::cerr << "migrating " << dbname << std::endl;
8✔
521
    std::string tdbname = dbname + "_v5_0";
8✔
522

523
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
524
    if (int retCode = MDBDbi::mdb_dbi_open(txn, fdbname.c_str(), 0, &fromindexdbi[index]); retCode != 0) {
8!
525
      mdb_txn_abort(txn);
×
526
      throw std::runtime_error("mdb_dbi_open indexdbi failed: " + MDBError(retCode));
×
527
    }
×
528

529
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
530
    if (int retCode = MDBDbi::mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &toindexdbi[index]); retCode != 0) {
8!
531
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
532
      mdb_dbi_close(env, fromindexdbi[index]);
×
533
      mdb_txn_abort(txn);
×
534
      throw std::runtime_error("mdb_dbi_open indexdbi target failed: " + MDBError(retCode));
×
535
    }
×
536

537
    try {
8✔
538
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
539
      copyIndexDBI(txn, fromindexdbi[index], toindexdbi[index]);
8✔
540
    }
8✔
541
    catch (std::exception& e) {
8✔
542
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
543
      mdb_dbi_close(env, toindexdbi[index]);
×
544
      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
545
      mdb_dbi_close(env, fromindexdbi[index]);
×
546
      mdb_txn_abort(txn);
×
547
      throw std::runtime_error("copyIndexDBI failed");
×
548
    }
×
549

550
    // mdb_dbi_close(env, dbi2);
551
    // mdb_dbi_close(env, dbi);
552
    std::cerr << "migrated " << dbname << std::endl;
8✔
553

554
    index++;
8✔
555
  }
8✔
556

557
  MDB_dbi dbi = 0;
2✔
558

559
  // finally, migrate the pdns db
560
  if (int retCode = MDBDbi::mdb_dbi_open(txn, "pdns", 0, &dbi); retCode != 0) {
2!
561
    mdb_txn_abort(txn);
×
562
    throw std::runtime_error("mdb_dbi_open pdns failed: " + MDBError(retCode));
×
563
  }
×
564

565
  MDB_val key;
2✔
566
  MDB_val data;
2✔
567

568
  std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
2✔
569

570
  for (const std::string keyname : {"schemaversion", "shards"}) {
4✔
571
    cerr << "migrating pdns." << keyname << endl;
4✔
572

573
    key.mv_data = (char*)keyname.c_str();
4✔
574
    key.mv_size = keyname.size();
4✔
575

576
    if (int retCode = mdb_get(txn, dbi, &key, &data); retCode != 0) {
4!
577
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
578
    }
×
579

580
    if (data.mv_size != sizeof(uint32_t)) {
4!
581
      throw std::runtime_error("got non-uint32_t key");
×
582
    }
×
583

584
    uint32_t value = 0;
4✔
585
    memcpy((void*)&value, data.mv_data, sizeof(uint32_t));
4✔
586

587
    value = htonl(value);
4✔
588
    if (keyname == "schemaversion") {
4✔
589
      value = htonl(5);
2✔
590
    }
2✔
591

592
    std::string sdata(static_cast<char*>(data.mv_data), data.mv_size);
4✔
593
    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
594
    std::string stdata = header + std::string((char*)&value, sizeof(uint32_t));
4✔
595

596
    MDB_val tdata;
4✔
597

598
    tdata.mv_data = (char*)stdata.c_str();
4✔
599
    tdata.mv_size = stdata.size();
4✔
600

601
    if (int retCode = mdb_put(txn, dbi, &key, &tdata, 0); retCode != 0) {
4!
602
      throw std::runtime_error("mdb_put failed: " + MDBError(retCode));
×
603
    }
×
604
  }
4✔
605

606
  for (const std::string keyname : {"uuid"}) {
2✔
607
    cerr << "migrating pdns." << keyname << endl;
2✔
608

609
    key.mv_data = (char*)keyname.c_str();
2✔
610
    key.mv_size = keyname.size();
2✔
611

612
    if (int retCode = mdb_get(txn, dbi, &key, &data); retCode != 0) {
2!
613
      throw std::runtime_error("mdb_get pdns.shards failed: " + MDBError(retCode));
×
614
    }
×
615

616
    std::string sdata((char*)data.mv_data, data.mv_size);
2✔
617

618
    std::string stdata = header + sdata;
2✔
619

620
    MDB_val tdata;
2✔
621

622
    tdata.mv_data = (char*)stdata.c_str();
2✔
623
    tdata.mv_size = stdata.size();
2✔
624

625
    if (int retCode = mdb_put(txn, dbi, &key, &tdata, 0); retCode != 0) {
2!
626
      throw std::runtime_error("mdb_put failed: " + MDBError(retCode));
×
627
    }
×
628
  }
2✔
629

630
  for (int i = 0; i < 4; i++) {
10✔
631
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
632
    mdb_drop(txn, fromtypeddbi[i], 1);
8✔
633
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
634
    mdb_drop(txn, fromindexdbi[i], 1);
8✔
635
  }
8✔
636

637
  cerr << "txn commit=" << mdb_txn_commit(txn) << endl;
2✔
638

639
  for (int i = 0; i < 4; i++) {
10✔
640
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
641
    mdb_dbi_close(env, totypeddbi[i]);
8✔
642
    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)
643
    mdb_dbi_close(env, toindexdbi[i]);
8✔
644
  }
8✔
645

646
  cerr << "migration done" << endl;
2✔
647
  return true;
2✔
648
}
2✔
649

650
bool LMDBBackend::upgradeToSchemav6(std::string& /* filename */)
651
{
3✔
652
  // a v6 reader can read v5 databases just fine
653
  // so this function currently does nothing
654
  // - except rely on the caller to write '6' to pdns.schemaversion,
655
  // as a v5 reader will be unable to handle domain objects once we've touched them
656
  return true;
3✔
657
}
3✔
658

659
// Serial number cache
660

661
// Retrieve the transient domain info for the given domain, if any
662
bool LMDBBackend::TransientDomainInfoCache::get(domainid_t domainid, TransientDomainInfo& data) const
663
{
×
664
  if (auto iter = d_data.find(domainid); iter != d_data.end()) {
×
665
    data = iter->second;
×
666
    return true;
×
667
  }
×
668
  return false;
×
669
}
×
670

671
// Remove the transient domain info for the given domain
672
void LMDBBackend::TransientDomainInfoCache::remove(domainid_t domainid)
673
{
31✔
674
  if (auto iter = d_data.find(domainid); iter != d_data.end()) {
31!
675
    d_data.erase(iter);
×
676
  }
×
677
}
31✔
678

679
// Create or update the transient domain info for the given domain
680
void LMDBBackend::TransientDomainInfoCache::update(domainid_t domainid, const TransientDomainInfo& data)
681
{
×
682
  d_data.insert_or_assign(domainid, data);
×
683
}
×
684

685
// Return the contents of the first element and remove it
686
bool LMDBBackend::TransientDomainInfoCache::pop(domainid_t& domainid, TransientDomainInfo& data)
687
{
×
688
  auto iter = d_data.begin();
×
689
  if (iter == d_data.end()) {
×
690
    return false;
×
691
  }
×
692
  domainid = iter->first;
×
693
  data = iter->second;
×
694
  (void)d_data.erase(iter);
×
695
  return true;
×
696
}
×
697

698
SharedLockGuarded<LMDBBackend::TransientDomainInfoCache> LMDBBackend::s_transient_domain_info;
699

700
LMDBBackend::LMDBBackend(const std::string& suffix)
701
{
4,579✔
702
  // overlapping domain ids in combination with relative names are a recipe for disaster
703
  if (!suffix.empty()) {
4,579!
704
    throw std::runtime_error("LMDB backend does not support multiple instances");
×
705
  }
×
706

707
  if (g_slogStructured) {
4,579!
708
    d_slog = g_slog->withName("lmdb" + suffix);
×
709
  }
×
710

711
  d_views = ::arg().mustDo("views"); // This is a global setting
4,579✔
712

713
  setArgPrefix("lmdb" + suffix);
4,579✔
714

715
  string syncMode = toLower(getArg("sync-mode"));
4,579✔
716

717
  if (syncMode == "nosync")
4,579!
718
    d_asyncFlag = MDB_NOSYNC;
×
719
  else if (syncMode == "nometasync")
4,579!
720
    d_asyncFlag = MDB_NOMETASYNC;
×
721
  else if (syncMode.empty() || syncMode == "sync")
4,579!
722
    d_asyncFlag = 0;
4,579✔
723
  else
×
724
    throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
×
725

726
  d_mapsize_main = d_mapsize_shards = 0;
4,579✔
727
  try {
4,579✔
728
    d_mapsize_main = std::stoll(getArg("map-size"));
4,579✔
729
  }
4,579✔
730
  catch (const std::exception& e) {
4,579✔
731
    throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
×
732
  }
×
733
  try {
4,579✔
734
    d_mapsize_shards = std::stoll(getArg("shards-map-size"));
4,579✔
735
  }
4,579✔
736
  catch (const std::exception& e) {
4,579✔
737
    throw std::runtime_error(std::string("Unable to parse the 'shards-map-size' LMDB value: ") + e.what());
×
738
  }
×
739
  if (d_mapsize_shards == 0) {
4,579✔
740
    // Old configuration with only one settings for main and shards.
741
    d_mapsize_shards = d_mapsize_main;
4,578✔
742
  }
4,578✔
743

744
  d_write_notification_update = mustDo("write-notification-update");
4,579✔
745
  d_split_domains_table = mustDo("split-domains-table");
4,579✔
746

747
  if (mustDo("lightning-stream")) {
4,579!
748
    d_random_ids = true;
×
749
    d_handle_dups = true;
×
750
    LMDBLS::s_flag_deleted = true;
×
751

752
    if (atoi(getArg("shards").c_str()) != 1) {
×
753
      throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
×
754
    }
×
755
  }
×
756
  else {
4,579✔
757
    d_random_ids = mustDo("random-ids");
4,579✔
758
    d_handle_dups = false;
4,579✔
759
    LMDBLS::s_flag_deleted = mustDo("flag-deleted");
4,579✔
760
  }
4,579✔
761

762
  bool opened = false;
4,579✔
763

764
  if (s_first) {
4,579✔
765
    auto lock = std::scoped_lock(s_lmdbStartupLock);
2,562✔
766
    if (s_first) {
2,562!
767
      auto filename = getArg("filename");
2,562✔
768

769
      auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
2,562✔
770
      uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
2,562✔
771
      // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
772

773
      if (getArgAsNum("schema-version") != SCHEMAVERSION) {
2,562!
774
        throw std::runtime_error("This version of the lmdbbackend only supports schema version 6. Configuration demands a lower version. Not starting up.");
×
775
      }
×
776

777
      if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
2,562!
778
        throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3 and up. Upgrading from older schemas is not supported.");
×
779
      }
×
780

781
      if (currentSchemaVersion == 0) {
2,562✔
782
        // no database is present yet, we can just create them
783
        currentSchemaVersion = 6;
60✔
784
      }
60✔
785

786
      if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
2,562✔
787
        if (!upgradeToSchemav5(filename)) {
2!
788
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
×
789
        }
×
790
        currentSchemaVersion = 5;
2✔
791
      }
2✔
792

793
      if (currentSchemaVersion == 5) {
2,562✔
794
        if (!upgradeToSchemav6(filename)) {
3!
795
          throw std::runtime_error("Failed to perform LMDB schema version upgrade from v5 to v6");
×
796
        }
×
797
        currentSchemaVersion = 6;
3✔
798
      }
3✔
799

800
      if (currentSchemaVersion != 6) {
2,562!
801
        throw std::runtime_error("Somehow, we are not at schema version 6. Giving up");
×
802
      }
×
803

804
      openAllTheDatabases();
2,562✔
805
      opened = true;
2,562✔
806
      auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
2,562✔
807

808
      auto txn = d_tdomains->getEnv()->getRWTransaction();
2,562✔
809

810
      const auto configShardsTemp = atoi(getArg("shards").c_str());
2,562✔
811
      if (configShardsTemp < 0) {
2,562!
812
        throw std::runtime_error("a negative shards value is not supported");
×
813
      }
×
814
      if (configShardsTemp == 0) {
2,562!
815
        throw std::runtime_error("a shards value of 0 is not supported");
×
816
      }
×
817
      const auto configShards = static_cast<uint32_t>(configShardsTemp);
2,562✔
818

819
      MDBOutVal shards{};
2,562✔
820
      if (txn->get(pdnsdbi, "shards", shards) == 0) {
2,562✔
821
        s_shards = shards.get<uint32_t>();
2,502✔
822

823
        if (mustDo("lightning-stream") && s_shards != 1) {
2,502!
824
          throw std::runtime_error("running with Lightning Stream support enabled requires a database with exactly 1 shard");
×
825
        }
×
826

827
        if (s_shards != configShards) {
2,502!
828
          SLOG(g_log << Logger::Warning
×
829
                     << "Note: configured number of lmdb shards ("
×
830
                     << configShards
×
831
                     << ") is different from on-disk ("
×
832
                     << s_shards
×
833
                     << "). Using on-disk shard number"
×
834
                     << endl,
×
835
               d_slog->info(Logr::Warning, "Note: configured number of lmdb shards differs from on-disk; using the on-disk value", "configured", Logging::Loggable(configShards), "on-disk", Logging::Loggable(s_shards)));
×
836
        }
×
837
      }
2,502✔
838
      else {
60✔
839
        s_shards = configShards;
60✔
840
        txn->put(pdnsdbi, "shards", s_shards);
60✔
841
      }
60✔
842

843
      MDBOutVal gotuuid{};
2,562✔
844
      if (txn->get(pdnsdbi, "uuid", gotuuid) != 0) {
2,562✔
845
        const auto uuid = getUniqueID();
60✔
846
        const string uuids(uuid.begin(), uuid.end());
60✔
847
        txn->put(pdnsdbi, "uuid", uuids);
60✔
848
      }
60✔
849

850
      MDBOutVal _schemaversion{};
2,562✔
851
      if (txn->get(pdnsdbi, "schemaversion", _schemaversion) != 0 || _schemaversion.get<uint32_t>() != currentSchemaVersion) {
2,562✔
852
        txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
63✔
853
      }
63✔
854
      txn->commit();
2,562✔
855

856
      s_first = false;
2,562✔
857
    }
2,562✔
858
  }
2,562✔
859

860
  if (!opened) {
4,579✔
861
    openAllTheDatabases();
2,017✔
862
  }
2,017✔
863
  d_trecords.resize(s_shards);
4,579✔
864
  d_dolog = ::arg().mustDo("query-logging");
4,579✔
865
}
4,579✔
866

867
LMDBBackend::~LMDBBackend()
868
{
4,455✔
869
  // LMDB internals require that, if we have multiple transactions active,
870
  // we destroy them in the reverse order of their creation, thus we can't
871
  // let the default destructor take care of d_rotxn and d_rwtxn.
872
  if (d_txnorder) {
4,455✔
873
    // RO transaction more recent than RW transaction
874
    d_rotxn.reset();
1,325✔
875
    d_rwtxn.reset();
1,325✔
876
  }
1,325✔
877
  else {
3,130✔
878
    // RW transaction more recent than RO transaction
879
    d_rwtxn.reset();
3,130✔
880
    d_rotxn.reset();
3,130✔
881
  }
3,130✔
882
}
4,455✔
883

884
void LMDBBackend::openAllTheDatabases()
885
{
4,579✔
886
  auto filename = getArg("filename");
4,579✔
887
  d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(filename.c_str(), MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize_main), "domains_v5");
4,579✔
888
  d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
4,579✔
889
  d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
4,579✔
890
  d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
4,579✔
891
  d_tnetworks = d_tdomains->getEnv()->openDB("networks_v6", MDB_CREATE);
4,579✔
892
  d_tviews = d_tdomains->getEnv()->openDB("views_v6", MDB_CREATE);
4,579✔
893
  if (d_split_domains_table) {
4,579!
894
    d_tdomains_extra = std::make_shared<tdomain_extra_t>(d_tdomains->getEnv(), "domains_extra_v6");
×
895
  }
×
896
}
4,579✔
897

898
unsigned int LMDBBackend::getCapabilities()
899
{
23,036✔
900
  unsigned int caps = CAP_DNSSEC | CAP_DIRECT | CAP_LIST | CAP_CREATE | CAP_SEARCH | CAP_COMMENTS;
23,036✔
901
  if (d_views) {
23,036✔
902
    caps |= CAP_VIEWS;
21,559✔
903
  }
21,559✔
904
  return caps;
23,036✔
905
}
23,036✔
906

907
namespace boost
908
{
909
namespace serialization
910
{
911

912
  template <class Archive>
913
  void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
914
  {
3,793✔
915
    if (g.empty()) {
3,793✔
916
      ar& std::string();
1,001✔
917
    }
1,001✔
918
    else {
2,792✔
919
      ar & g.toDNSStringLC();
2,792✔
920
    }
2,792✔
921
  }
3,793✔
922

923
  template <class Archive>
924
  void load(Archive& ar, DNSName& g, const unsigned int /* version */)
925
  {
358,084✔
926
    string tmp;
358,084✔
927
    ar & tmp;
358,084✔
928
    if (tmp.empty()) {
358,084✔
929
      g = DNSName();
132,078✔
930
    }
132,078✔
931
    else {
226,006✔
932
      g = DNSName(tmp.c_str(), tmp.size(), 0, false);
226,006✔
933
    }
226,006✔
934
  }
358,084✔
935

936
  template <class Archive>
937
  void save(Archive& arc, const ZoneName& zone, const unsigned int /* version */)
938
  {
3,739✔
939
    arc & zone.operator const DNSName&();
3,739✔
940
    arc & zone.getVariant();
3,739✔
941
  }
3,739✔
942

943
  template <class Archive>
944
  void load(Archive& arc, ZoneName& zone, const unsigned int version)
945
  {
357,792✔
946
    if (version == 0) { // for schemas up to 5, ZoneName serialized as DNSName
357,792!
947
      std::string tmp{};
×
948
      arc & tmp;
×
949
      if (tmp.empty()) {
×
950
        zone = ZoneName();
×
951
      }
×
952
      else {
×
953
        zone = ZoneName(DNSName(tmp.c_str(), tmp.size(), 0, false));
×
954
      }
×
955
      return;
×
956
    }
×
957
    DNSName tmp;
357,792✔
958
    std::string variant{};
357,792✔
959
    arc & tmp;
357,792✔
960
    arc & variant;
357,792✔
961
    zone = ZoneName(tmp, variant);
357,792✔
962
  }
357,792✔
963

964
  template <class Archive>
965
  void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
966
  {
1,320✔
967
    ar & g.zone;
1,320✔
968
    ar & g.last_check;
1,320✔
969
    ar & g.account;
1,320✔
970
    ar & g.primaries;
1,320✔
971
    ar& static_cast<uint32_t>(g.id);
1,320✔
972
    ar & g.notified_serial;
1,320✔
973
    ar & g.kind;
1,320✔
974
    ar & g.options;
1,320✔
975
    ar & g.catalog;
1,320✔
976
  }
1,320✔
977

978
  template <class Archive>
979
  void load(Archive& ar, DomainInfo& g, const unsigned int version)
980
  {
170,652✔
981
    if (version >= 2) {
170,652✔
982
      ar & g.zone;
170,604✔
983
    }
170,604✔
984
    else {
48✔
985
      DNSName tmp;
48✔
986
      ar & tmp;
48✔
987
      new (&g.zone) ZoneName(tmp);
48✔
988
    }
48✔
989
    ar & g.last_check;
170,652✔
990
    ar & g.account;
170,652✔
991
    ar & g.primaries;
170,652✔
992
    uint32_t domainId{0};
170,652✔
993
    ar & domainId;
170,652✔
994
    g.id = static_cast<domainid_t>(domainId);
170,652✔
995
    ar & g.notified_serial;
170,652✔
996
    ar & g.kind;
170,652✔
997
    switch (version) {
170,652✔
998
    case 0:
47✔
999
      // These fields did not exist.
1000
      g.options.clear();
47✔
1001
      g.catalog.clear();
47✔
1002
      break;
47✔
1003
    case 1:
1✔
1004
      // These fields did exist, but catalog as DNSName only.
1005
      ar & g.options;
1✔
1006
      {
1✔
1007
        DNSName tmp;
1✔
1008
        ar & tmp;
1✔
1009
        g.catalog = ZoneName(tmp);
1✔
1010
      }
1✔
1011
      break;
1✔
1012
    default:
170,606✔
1013
      // These fields exist, with catalog as ZoneName.
1014
      ar & g.options;
170,606✔
1015
      ar & g.catalog;
170,606✔
1016
      break;
170,606✔
1017
    }
170,652✔
1018
  }
170,652✔
1019

1020
  template <class Archive>
1021
  void serialize(Archive& ar, LMDBBackend::TransientDomainInfo& g, const unsigned int /* version */)
1022
  {
×
1023
    ar & g.last_check & g.notified_serial;
×
1024
  }
×
1025

1026
  template <class Archive>
1027
  void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
1028
  {
16,583✔
1029
    ar & g.domain & g.key & g.value;
16,583✔
1030
  }
16,583✔
1031

1032
  template <class Archive>
1033
  void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
1034
  {
243✔
1035
    ar & g.domain & g.content & g.flags & g.active & g.published;
243✔
1036
  }
243✔
1037

1038
  template <class Archive>
1039
  void load(Archive& ar, LMDBBackend::KeyDataDB& g, const unsigned int version)
1040
  {
858✔
1041
    ar & g.domain & g.content & g.flags & g.active;
858✔
1042
    if (version >= 1) {
858!
1043
      ar & g.published;
858✔
1044
    }
858✔
1045
    else {
×
1046
      g.published = true;
×
1047
    }
×
1048
  }
858✔
1049

1050
  template <class Archive>
1051
  void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
1052
  {
147✔
1053
    ar & g.name;
147✔
1054
    ar & g.algorithm; // this is the ordername
147✔
1055
    ar & g.key;
147✔
1056
  }
147✔
1057

1058
} // namespace serialization
1059
} // namespace boost
1060

1061
BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
1062
BOOST_SERIALIZATION_SPLIT_FREE(ZoneName);
1063
BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
1064
BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
1065
BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
1066

1067
// Resource records are serialized in the following format:
1068
// - length of record content (16 bits)
1069
// - record content (variable size)
1070
// - ttl (32 bits)
1071
// - auth flag (8 bits)
1072
// - disabled flag (8 bits)
1073
// - hasOrderName flag (8 bits)
1074

1075
// The following constants try and make the logic in the following few routines
1076
// less obscure and more future-proof.
1077
constexpr size_t serialize_prefix_size = sizeof(uint16_t); // 2
1078
constexpr size_t serialize_trailing_size = sizeof(uint32_t) + 3; // 7
1079
constexpr size_t serialize_minimum_size = serialize_prefix_size + serialize_trailing_size;
1080
constexpr size_t serialize_offset_ttl = 0;
1081
constexpr size_t serialize_offset_auth = serialize_offset_ttl + sizeof(uint32_t);
1082
constexpr size_t serialize_offset_disabled = serialize_offset_auth + sizeof(char);
1083
constexpr size_t serialize_offset_ordername = serialize_offset_disabled + sizeof(char);
1084

1085
template <>
1086
void serializeToBuffer(std::string& buffer, const LMDBBackend::LMDBResourceRecord& value)
1087
{
779,001✔
1088
  if (value.content.length() > std::numeric_limits<uint16_t>::max()) {
779,001!
1089
    throw PDNSException("DNS record is too large (" + std::to_string(value.content.length()) + "), unable to serialize in LMDB");
×
1090
  }
×
1091

1092
  // Data size of the resource record.
1093
  uint16_t len = value.content.length();
779,001✔
1094

1095
  // Reserve space to store the size of the resource record + the content of the resource
1096
  // record + a few other things.
1097
  buffer.reserve(buffer.size() + serialize_prefix_size + len + serialize_trailing_size);
779,001✔
1098

1099
  // Store the size of the resource record (in host order).
1100
  // NOLINTNEXTLINE.
1101
  buffer.append((const char*)&len, sizeof(len));
779,001✔
1102

1103
  // Store the contents of the resource record.
1104
  buffer.append(value.content);
779,001✔
1105

1106
  // The few other things.
1107
  // NOLINTNEXTLINE.
1108
  buffer.append((const char*)&value.ttl, sizeof(value.ttl));
779,001✔
1109
  buffer.append(1, (char)value.auth);
779,001✔
1110
  buffer.append(1, (char)value.disabled);
779,001✔
1111
  buffer.append(1, (char)value.hasOrderName);
779,001✔
1112
}
779,001✔
1113

1114
template <>
1115
void serializeToBuffer(std::string& buffer, const vector<LMDBBackend::LMDBResourceRecord>& value)
1116
{
84,666✔
1117
  for (const auto& lrr : value) {
104,857✔
1118
    serializeToBuffer(buffer, lrr);
104,857✔
1119
  }
104,857✔
1120
}
84,666✔
1121

1122
// Deserialize a single resource record, and return its length.
1123
// Returns zero if the given string_view is not large enough to hold a valid
1124
// record.
1125
static inline size_t deserializeRRFromBuffer(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
1126
{
896,026✔
1127
  const auto* data = str.data();
896,026✔
1128
  uint16_t len;
896,026✔
1129
  if (str.size() < serialize_prefix_size) {
896,026!
1130
    return 0;
×
1131
  }
×
1132
  memcpy(&len, data, sizeof(len));
896,026✔
1133
  if (str.size() < serialize_prefix_size + len + serialize_trailing_size) {
896,026!
1134
    return 0;
×
1135
  }
×
1136
  // NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic): due to the above size check, this is safe
1137
  data += sizeof(len);
896,026✔
1138
  lrr.content.assign(data, len); // len bytes
896,026✔
1139
  data += len;
896,026✔
1140
  memcpy(&lrr.ttl, data, sizeof(uint32_t));
896,026✔
1141
  data += sizeof(uint32_t);
896,026✔
1142
  lrr.auth = *data++ != 0;
896,026✔
1143
  lrr.disabled = *data++ != 0;
896,026✔
1144
  lrr.hasOrderName = *data++ != 0;
896,026✔
1145
  // NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic)
1146
  lrr.wildcardname.clear();
896,026✔
1147

1148
  return data - str.data();
896,026✔
1149
}
896,026✔
1150

1151
// Deserialize a single resource record.
1152
// Returns true if successful, false if truncated or invalid data found.
1153
static bool deserializeFromBuffer(const string_view& buffer, LMDBBackend::LMDBResourceRecord& value)
1154
{
16,135✔
1155
  return deserializeRRFromBuffer(buffer, value) != 0;
16,135✔
1156
}
16,135✔
1157

1158
// Deserialize a list of resource records.
1159
// Stops as soon as all resource records in the buffer have been processed, or
1160
// a non-deserializable (truncated) record is found.
1161
// May return an empty vector.
1162
static void deserializeMultipleFromBuffer(const string_view& buffer, vector<LMDBBackend::LMDBResourceRecord>& value)
1163
{
713,211✔
1164
  auto str_copy = buffer;
713,211✔
1165
  while (str_copy.size() >= serialize_minimum_size) {
1,593,102✔
1166
    LMDBBackend::LMDBResourceRecord lrr;
879,891✔
1167
    auto rrLength = deserializeRRFromBuffer(str_copy, lrr);
879,891✔
1168
    if (rrLength == 0) {
879,891!
1169
      break;
×
1170
    }
×
1171
    value.emplace_back(lrr);
879,891✔
1172
    str_copy.remove_prefix(rrLength);
879,891✔
1173
  }
879,891✔
1174
}
713,211✔
1175

1176
static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
1177
{
431,216✔
1178
  auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
431,216✔
1179
  return drc->serialize(domain, false);
431,216✔
1180
}
431,216✔
1181

1182
// perhaps this can also create the compound order name for us
1183
std::pair<std::string, std::string> LMDBBackend::serializeComment(const Comment& comment)
1184
{
8✔
1185
  compoundOrdername co;
8✔
1186
  auto qname = comment.qname.makeRelative(d_transactiondomain);
8✔
1187
  string key = co(comment.domain_id, qname, comment.qtype);
8✔
1188

1189
  // we serialized domain_id, qname, qtype
1190
  // that leaves us with content, modified_at, account
1191

1192
  string val;
8✔
1193

1194
  protozero::pbf_writer val_writer{val};
8✔
1195
  val_writer.add_sfixed64(1, comment.modified_at);
8✔
1196
  val_writer.add_string(2, comment.account);
8✔
1197
  val_writer.add_string(3, comment.content);
8✔
1198

1199
  // because of Lightning Stream, we don't want to put all comments for an RRSET in a single LMDB value
1200
  // because if we did, then after adding one comment on node A and adding one comment on B, the set from one will overwrite
1201
  // the set from the other, deleting one of the comments.
1202
  // instead, we use one LMDB key/value per comment. This requires a unique key. Our unique key is the hash of our value.
1203
  // This makes sure that identical/duplicate comments do not actually duplicate, and that different comments do not
1204
  // overwrite each other, because their hashes are different.
1205
  auto hash = pdns::sha256sum(val);
8✔
1206

1207
  key.append(hash);
8✔
1208

1209
  return {key, val};
8✔
1210
}
8✔
1211

1212
static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
1213
{
673,179✔
1214
  if (qtype == QType::A && content.size() == 4) {
673,179!
1215
    return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
539,232✔
1216
  }
539,232✔
1217
  return DNSRecordContent::deserialize(qname, qtype, content, QClass::IN, true);
133,947✔
1218
}
673,179✔
1219

1220
// For the few places where we are only interested in the hasOrderName field,
1221
// this cheap routine is faster than doing:
1222
// {
1223
//   LMDBResourceRecord lrr;
1224
//   deserializeFromBuffer(buffer, lrr);
1225
//   return lrr.hasOrderName;
1226
// }
1227
static bool peekAtHasOrderName(const string_view& buffer)
1228
{
1,788✔
1229
  if (buffer.length() < sizeof(uint16_t)) {
1,788!
1230
    return false;
×
1231
  }
×
1232
  uint16_t len{0};
1,788✔
1233
  memcpy(&len, buffer.data(), sizeof(uint16_t));
1,788✔
1234
  auto offset = serialize_prefix_size + len + serialize_offset_ordername;
1,788✔
1235
  if (buffer.length() < offset + 1) {
1,788!
1236
    return false;
×
1237
  }
×
1238
  bool hasOrderName = buffer[offset] != 0;
1,788✔
1239
  return hasOrderName;
1,788✔
1240
}
1,788✔
1241

1242
// Similar to the above, but for the auth field.
1243
static bool peekAtAuth(const string_view& buffer)
1244
{
7,876✔
1245
  if (buffer.length() < sizeof(uint16_t)) {
7,876!
1246
    return false;
×
1247
  }
×
1248
  uint16_t len{0};
7,876✔
1249
  memcpy(&len, buffer.data(), sizeof(uint16_t));
7,876✔
1250
  auto offset = serialize_prefix_size + len + serialize_offset_auth;
7,876✔
1251
  if (buffer.length() < offset + 1) {
7,876!
1252
    return false;
×
1253
  }
×
1254
  bool auth = buffer[offset] != 0;
7,876✔
1255
  return auth;
7,876✔
1256
}
7,876✔
1257

1258
// Similar to the above, but for the ttl.
1259
static uint32_t peekAtTtl(const string_view& buffer)
1260
{
29,554✔
1261
  if (buffer.length() < sizeof(uint16_t)) {
29,554!
1262
    return 0;
×
1263
  }
×
1264
  uint16_t len{0};
29,554✔
1265
  memcpy(&len, buffer.data(), sizeof(uint16_t));
29,554✔
1266
  auto offset = serialize_prefix_size + len + serialize_offset_ttl;
29,554✔
1267
  if (buffer.length() < offset + sizeof(uint32_t)) {
29,554!
1268
    return 0;
×
1269
  }
×
1270
  uint32_t ttl{0};
29,554✔
1271
  memcpy(&ttl, buffer.data() + offset, sizeof(uint32_t));
29,554✔
1272
  return ttl;
29,554✔
1273
}
29,554✔
1274

1275
/* A note on the design.
1276

1277
   If you ask a question without a zone id (this can be the case for lookup(),
1278
   and of course also for startTransaction if you don't want to delete the
1279
   domain contents), we lookup the best zone id for you, and answer from that.
1280

1281
   The index we use is "zoneid,canonical relative name". This index is also used
1282
   for AXFR.
1283

1284
   Note - domain_id, name and type are ONLY present on the index!
1285
*/
1286

1287
#if BOOST_VERSION >= 106100
1288
#define StringView string_view
1289
#else
1290
#define StringView string
1291
#endif
1292

1293
void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, const std::string& match, QType qtype)
1294
{
1,385✔
1295
  auto cursor = txn.txn->getCursor(txn.db->rdbi);
1,385✔
1296
  MDBOutVal key{};
1,385✔
1297
  MDBOutVal val{};
1,385✔
1298

1299
  if (cursor.prefix(match, key, val) == 0) {
1,385✔
1300
    do {
245,112✔
1301
      if (qtype == QType::ANY || compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == qtype) {
245,112✔
1302
        cursor.del(key);
1,555✔
1303
      }
1,555✔
1304
    } while (cursor.next(key, val) == 0);
245,112✔
1305
  }
458✔
1306
}
1,385✔
1307

1308
bool LMDBBackend::findDomain(const ZoneName& domain, DomainInfo& info) const
1309
{
8,933✔
1310
  auto rotxn = d_tdomains->getROTransaction();
8,933✔
1311
  auto domain_id = rotxn.get<0>(domain, info);
8,933✔
1312
  if (domain_id == 0) {
8,933✔
1313
    return false;
1,115✔
1314
  }
1,115✔
1315
  info.id = static_cast<domainid_t>(domain_id);
7,818✔
1316
  return true;
7,818✔
1317
}
8,933✔
1318

1319
bool LMDBBackend::findDomain(domainid_t domainid, DomainInfo& info) const
1320
{
159,643✔
1321
  auto rotxn = d_tdomains->getROTransaction();
159,643✔
1322
  if (!rotxn.get(domainid, info)) {
159,643✔
1323
    return false;
8✔
1324
  }
8✔
1325
  info.id = domainid;
159,635✔
1326
  return true;
159,635✔
1327
}
159,643✔
1328

1329
void LMDBBackend::consolidateDomainInfo(DomainInfo& info) const
1330
{
8,445✔
1331
  TransientDomainInfo tdi;
8,445✔
1332
  bool valid{false};
8,445✔
1333

1334
  // Get data from the cache if we don't keep the database up to date.
1335
  if (!d_write_notification_update) {
8,445!
1336
    auto container = s_transient_domain_info.read_lock();
×
1337
    if (container->get(info.id, tdi)) {
×
1338
      valid = true;
×
1339
    }
×
1340
  }
×
1341

1342
  // If the DomainInfo table is split, get the TransientDomainInfo part
1343
  // from the extra table.
1344
  if (!valid && d_split_domains_table) {
8,445!
1345
    auto rotxn = d_tdomains_extra->getROTransaction();
×
1346
    if (rotxn.get(info.id, tdi)) {
×
1347
      valid = true;
×
1348
    }
×
1349
  }
×
1350

1351
  if (valid) {
8,445!
1352
    info.notified_serial = tdi.notified_serial;
×
1353
    info.last_check = tdi.last_check;
×
1354
  }
×
1355
}
8,445✔
1356

1357
void LMDBBackend::writeTransientDomainInfo(const DomainInfo& info)
1358
{
1,468✔
1359
  // If the DomainInfo table is split, write the TransientDomainInfo part
1360
  // to the extra table.
1361
  if (d_split_domains_table) {
1,468!
1362
    TransientDomainInfo tdi;
×
1363
    tdi.notified_serial = info.notified_serial;
×
1364
    tdi.last_check = info.last_check;
×
1365
    auto txn = d_tdomains_extra->getRWTransaction();
×
1366
    txn.put(tdi, info.id);
×
1367
    txn.commit();
×
1368
  }
×
1369
}
1,468✔
1370

1371
void LMDBBackend::writeDomainInfo(const DomainInfo& info)
1372
{
775✔
1373
  // Update the in-memory cache if we don't keep the database up to date.
1374
  if (!d_write_notification_update) {
775!
1375
    TransientDomainInfo tdi;
×
1376
    auto container = s_transient_domain_info.write_lock();
×
1377
    if (container->get(info.id, tdi)) {
×
1378
      // Only remove the in-memory value if it has not been modified since the
1379
      // DomainInfo data was set up.
1380
      if (tdi.notified_serial == info.notified_serial && tdi.last_check == info.last_check) {
×
1381
        container->remove(info.id);
×
1382
      }
×
1383
    }
×
1384
    return;
×
1385
  }
×
1386

1387
  auto txn = d_tdomains->getRWTransaction();
775✔
1388
  txn.put(info, info.id);
775✔
1389
  txn.commit();
775✔
1390
  writeTransientDomainInfo(info);
775✔
1391
}
775✔
1392

1393
/* Here's the complicated story. Other backends have just one transaction, which is either
1394
   on or not.
1395

1396
   You can't call feedRecord without a transaction started with startTransaction.
1397

1398
   However, other functions can be called after startTransaction() or without startTransaction()
1399
     (like updateDNSSECOrderNameAndAuth)
1400

1401

1402

1403
*/
1404

1405
bool LMDBBackend::startTransaction(const ZoneName& domain, domainid_t domain_id)
1406
{
2,676✔
1407
  // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1408
  domainid_t real_id = domain_id;
2,676✔
1409
  if (real_id == UnknownDomainID) {
2,676✔
1410
    DomainInfo info;
1,721✔
1411
    if (!findDomain(domain, info)) {
1,721!
1412
      return false;
×
1413
    }
×
1414
    real_id = info.id;
1,721✔
1415
  }
1,721✔
1416
  if (d_rwtxn) {
2,676!
1417
    throw DBException("Attempt to start a transaction while one was open already");
×
1418
  }
×
1419
  d_rwtxn = getRecordsRWTransaction(real_id);
2,676✔
1420
  d_txnorder = false;
2,676✔
1421

1422
  d_transactiondomain = domain;
2,676✔
1423
  d_transactiondomainid = real_id;
2,676✔
1424
  if (domain_id != UnknownDomainID) {
2,676✔
1425
    compoundOrdername order;
955✔
1426
    string match = order(domain_id);
955✔
1427
    LMDBBackend::deleteDomainRecords(*d_rwtxn, match);
955✔
1428
  }
955✔
1429

1430
  return true;
2,676✔
1431
}
2,676✔
1432

1433
bool LMDBBackend::commitTransaction()
1434
{
2,406✔
1435
  // cout<<"Commit transaction" <<endl;
1436
  if (!d_rwtxn) {
2,406!
1437
    throw DBException("Attempt to commit a transaction while there isn't one open");
×
1438
  }
×
1439

1440
  d_rwtxn->txn->commit();
2,406✔
1441
  d_rwtxn.reset();
2,406✔
1442
  return true;
2,406✔
1443
}
2,406✔
1444

1445
bool LMDBBackend::abortTransaction()
1446
{
247✔
1447
  // cout<<"Abort transaction"<<endl;
1448
  if (!d_rwtxn) {
247!
1449
    throw DBException("Attempt to abort a transaction while there isn't one open");
×
1450
  }
×
1451

1452
  d_rwtxn->txn->abort();
247✔
1453
  d_rwtxn.reset();
247✔
1454

1455
  return true;
247✔
1456
}
247✔
1457

1458
// Remove the NSEC3 records pair found at the given `qname', if any.
1459
void LMDBBackend::deleteNSEC3RecordPair(const std::shared_ptr<RecordsRWTransaction>& txn, domainid_t domain_id, const DNSName& qname)
1460
{
531✔
1461
  compoundOrdername co; // NOLINT(readability-identifier-length)
531✔
1462
  MDBOutVal val{};
531✔
1463

1464
  auto key = co(domain_id, qname, QType::NSEC3);
531✔
1465
  if (txn->txn->get(txn->db->rdbi, key, val) == 0) {
531✔
1466
    LMDBResourceRecord lrr;
256✔
1467
    if (deserializeFromBuffer(val.get<string_view>(), lrr)) {
256!
1468
      DNSName ordername(lrr.content.c_str(), lrr.content.size(), 0, false);
256✔
1469
      txn->txn->del(txn->db->rdbi, co(domain_id, ordername, QType::NSEC3));
256✔
1470
    }
256✔
1471
    txn->txn->del(txn->db->rdbi, key);
256✔
1472
  }
256✔
1473
}
531✔
1474

1475
// Write a pair of NSEC3 records referencing each other, between `qname' and
1476
// `ordername'.
1477
void LMDBBackend::writeNSEC3RecordPair(const std::shared_ptr<RecordsRWTransaction>& txn, domainid_t domain_id, const DNSName& qname, const DNSName& ordername)
1478
{
123,150✔
1479
  // We can only write one NSEC3 record par qname; do not attempt to write
1480
  // records pointing to ourselves, as only the last record of the pair would
1481
  // end up in the database.
1482
  if (ordername == qname) {
123,150!
1483
    return;
×
1484
  }
×
1485

1486
  compoundOrdername co; // NOLINT(readability-identifier-length)
123,150✔
1487

1488
  // Check for an existing NSEC3 record. If one exists, either it points to the
1489
  // same ordername and we have nothing to do, or the ordername has changed and
1490
  // we need to remove the about-to-become-dangling back chain record.
1491
  MDBOutVal val{};
123,150✔
1492
  if (txn->txn->get(txn->db->rdbi, co(domain_id, qname, QType::NSEC3), val) == 0) {
123,150✔
1493
    LMDBResourceRecord lrr;
1,294✔
1494
    if (deserializeFromBuffer(val.get<string_view>(), lrr)) {
1,294!
1495
      DNSName prevordername(lrr.content.c_str(), lrr.content.size(), 0, false);
1,294✔
1496
      if (prevordername == ordername) {
1,294!
1497
        return; // nothing to do! (assuming the other record also exists)
1,294✔
1498
      }
1,294✔
1499
      txn->txn->del(txn->db->rdbi, co(domain_id, prevordername, QType::NSEC3));
×
1500
    }
×
1501
  }
1,294✔
1502

1503
  LMDBResourceRecord lrr;
121,856✔
1504
  lrr.auth = false;
121,856✔
1505

1506
  // Write ordername -> qname back chain record with ttl set to 0
1507
  lrr.ttl = 0;
121,856✔
1508
  lrr.content = qname.toDNSStringLC();
121,856✔
1509
  std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
121,856✔
1510
  serializeToBuffer(ser, lrr);
121,856✔
1511
  txn->txn->put_header_in_place(txn->db->rdbi, co(domain_id, ordername, QType::NSEC3), ser);
121,856✔
1512

1513
  // Write qname -> ordername forward chain record with ttl set to 1
1514
  lrr.ttl = 1;
121,856✔
1515
  lrr.content = ordername.toDNSString();
121,856✔
1516
  ser = MDBRWTransactionImpl::stringWithEmptyHeader();
121,856✔
1517
  serializeToBuffer(ser, lrr);
121,856✔
1518
  txn->txn->put_header_in_place(txn->db->rdbi, co(domain_id, qname, QType::NSEC3), ser);
121,856✔
1519
}
121,856✔
1520

1521
// Check if the only records found for this particular name are a single NSEC3
1522
// record. (in which case there is no actual data for that qname and that
1523
// record needs to be deleted)
1524
bool LMDBBackend::hasOrphanedNSEC3Record(MDBRWCursor& cursor, domainid_t domain_id, const DNSName& qname)
1525
{
150✔
1526
  compoundOrdername co; // NOLINT(readability-identifier-length)
150✔
1527
  bool seenNSEC3{false};
150✔
1528
  bool seenOther{false};
150✔
1529
  MDBOutVal key{};
150✔
1530
  MDBOutVal val{};
150✔
1531

1532
  if (cursor.prefix(co(domain_id, qname), key, val) == 0) {
150✔
1533
    do {
220✔
1534
      if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
220✔
1535
        seenNSEC3 = true;
146✔
1536
      }
146✔
1537
      else {
74✔
1538
        seenOther = true;
74✔
1539
      }
74✔
1540
      if (seenNSEC3 && seenOther) {
220✔
1541
        break;
34✔
1542
      }
34✔
1543
    } while (cursor.next(key, val) == 0);
220✔
1544
  }
146✔
1545
  return seenNSEC3 && !seenOther;
150✔
1546
}
150✔
1547

1548
// d_rwtxn must be set here
1549
bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1550
{
429,710✔
1551
  LMDBResourceRecord lrr(r);
429,710✔
1552
  lrr.qname.makeUsRelative(d_transactiondomain);
429,710✔
1553
  lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
429,710✔
1554
  // Note that this is safe, as ordernameIsNSEC3 will NOT be set if NSEC3
1555
  // but narrow.
1556
  lrr.hasOrderName = ordernameIsNSEC3 && !ordername.empty();
429,710!
1557

1558
  compoundOrdername co;
429,710✔
1559
  string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
429,710✔
1560

1561
  string rrs = MDBRWTransactionImpl::stringWithEmptyHeader();
429,710✔
1562
  MDBOutVal _rrs;
429,710✔
1563
  if (!d_rwtxn->txn->get(d_rwtxn->db->rdbi, matchName, _rrs)) {
429,710✔
1564
    rrs.append(_rrs.get<string>());
23,532✔
1565
  }
23,532✔
1566
  serializeToBuffer(rrs, lrr);
429,710✔
1567
  d_rwtxn->txn->put_header_in_place(d_rwtxn->db->rdbi, matchName, rrs);
429,710✔
1568

1569
  if (lrr.hasOrderName) {
429,710✔
1570
    writeNSEC3RecordPair(d_rwtxn, lrr.domain_id, lrr.qname, ordername);
40,791✔
1571
  }
40,791✔
1572
  return true;
429,710✔
1573
}
429,710✔
1574

1575
// d_rwtxn must be set here
1576
bool LMDBBackend::feedComment(const Comment& comment)
1577
{
8✔
1578
  auto [key, val] = serializeComment(comment);
8✔
1579

1580
  d_rwtxn->txn->put(d_rwtxn->db->cdbi, key, val);
8✔
1581

1582
  return true;
8✔
1583
}
8✔
1584

1585
bool LMDBBackend::feedEnts(domainid_t domain_id, map<DNSName, bool>& nonterm)
1586
{
26✔
1587
  LMDBResourceRecord lrr;
26✔
1588
  lrr.ttl = 0;
26✔
1589
  compoundOrdername co;
26✔
1590
  for (const auto& nt : nonterm) {
102✔
1591
    lrr.qname = nt.first.makeRelative(d_transactiondomain);
102✔
1592
    lrr.auth = nt.second;
102✔
1593
    lrr.hasOrderName = false;
102✔
1594

1595
    std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
102✔
1596
    serializeToBuffer(ser, lrr);
102✔
1597
    d_rwtxn->txn->put_header_in_place(d_rwtxn->db->rdbi, co(domain_id, lrr.qname, QType::ENT), ser);
102✔
1598
  }
102✔
1599
  return true;
26✔
1600
}
26✔
1601

1602
bool LMDBBackend::feedEnts3(domainid_t domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
1603
{
18✔
1604
  DNSName ordername;
18✔
1605
  LMDBResourceRecord lrr;
18✔
1606
  compoundOrdername co;
18✔
1607
  for (const auto& nt : nonterm) {
94✔
1608
    lrr.qname = nt.first.makeRelative(domain);
94✔
1609
    lrr.ttl = 0;
94✔
1610
    lrr.auth = nt.second;
94✔
1611
    lrr.hasOrderName = lrr.auth && !narrow;
94!
1612
    std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
94✔
1613
    serializeToBuffer(ser, lrr);
94✔
1614
    d_rwtxn->txn->put_header_in_place(d_rwtxn->db->rdbi, co(domain_id, lrr.qname, QType::ENT), ser);
94✔
1615

1616
    if (lrr.hasOrderName) {
94✔
1617
      ordername = DNSName(toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)));
82✔
1618
      writeNSEC3RecordPair(d_rwtxn, domain_id, lrr.qname, ordername);
82✔
1619
    }
82✔
1620
  }
94✔
1621
  return true;
18✔
1622
}
18✔
1623

1624
// might be called within a transaction, might also be called alone
1625
// NOLINTNEXTLINE(readability-identifier-length)
1626
bool LMDBBackend::replaceRRSet(domainid_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
1627
{
1,907✔
1628
  // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1629
  shared_ptr<RecordsRWTransaction> txn;
1,907✔
1630
  bool needCommit = false;
1,907✔
1631
  if (d_rwtxn && d_transactiondomainid == domain_id) {
1,907!
1632
    txn = d_rwtxn;
1,907✔
1633
    //    cout<<"Reusing open transaction"<<endl;
1634
  }
1,907✔
1635
  else {
×
1636
    //    cout<<"Making a new RW txn for replace rrset"<<endl;
1637
    txn = getRecordsRWTransaction(domain_id);
×
1638
    needCommit = true;
×
1639
  }
×
1640

1641
  DomainInfo info;
1,907✔
1642
  if (!findDomain(domain_id, info)) {
1,907!
1643
    return false;
×
1644
  }
×
1645

1646
  DNSName relative = qname.makeRelative(info.zone);
1,907✔
1647
  compoundOrdername co;
1,907✔
1648
  string match;
1,907✔
1649
  if (qt.getCode() == QType::ANY) {
1,907✔
1650
    // Check for an existing NSEC3 record. If one exists, we need to also
1651
    // remove the back chain record.
1652
    deleteNSEC3RecordPair(txn, domain_id, relative);
60✔
1653
    match = co(domain_id, relative);
60✔
1654
    deleteDomainRecords(*txn, match);
60✔
1655
    // Update key if insertions are to follow
1656
    if (!rrset.empty()) {
60!
1657
      match = co(domain_id, relative, rrset.front().qtype.getCode());
×
1658
    }
×
1659
  }
60✔
1660
  else {
1,847✔
1661
    if (qt.getCode() == QType::NSEC3) {
1,847!
1662
      deleteNSEC3RecordPair(txn, domain_id, relative);
×
1663
      // Compute key if insertions are to follow
1664
      if (!rrset.empty()) {
×
1665
        match = co(domain_id, relative, qt.getCode());
×
1666
      }
×
1667
    }
×
1668
    else {
1,847✔
1669
      auto cursor = txn->txn->getCursor(txn->db->rdbi);
1,847✔
1670
      MDBOutVal key{};
1,847✔
1671
      MDBOutVal val{};
1,847✔
1672
      bool hadOrderName{false};
1,847✔
1673
      match = co(domain_id, relative, qt.getCode());
1,847✔
1674
      // There should be at most one exact match here.
1675
      if (cursor.find(match, key, val) == 0) {
1,847✔
1676
        hadOrderName = peekAtHasOrderName(val.get<string_view>());
1,628✔
1677
        cursor.del(key);
1,628✔
1678
      }
1,628✔
1679
      // If we are not going to add any new records, check if there are any
1680
      // remaining records for this qname, ignoring NSEC3 chain records. If
1681
      // there aren't any, yet there is an NSEC3 record, delete the NSEC3 chain
1682
      // pair as well.
1683
      if (rrset.empty()) {
1,847✔
1684
        if (hadOrderName && hasOrphanedNSEC3Record(cursor, domain_id, relative)) {
430✔
1685
          deleteNSEC3RecordPair(txn, domain_id, relative);
112✔
1686
        }
112✔
1687
      }
430✔
1688
    }
1,847✔
1689
  }
1,847✔
1690

1691
  if (!rrset.empty()) {
1,907✔
1692
    vector<LMDBResourceRecord> adjustedRRSet;
1,417✔
1693
    adjustedRRSet.reserve(rrset.size());
1,417✔
1694
    for (const auto& rr : rrset) {
1,505✔
1695
      LMDBResourceRecord lrr(rr);
1,505✔
1696
      lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
1,505✔
1697
      lrr.qname.makeUsRelative(info.zone);
1,505✔
1698

1699
      adjustedRRSet.emplace_back(lrr);
1,505✔
1700
    }
1,505✔
1701
    std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
1,417✔
1702
    serializeToBuffer(ser, adjustedRRSet);
1,417✔
1703
    txn->txn->put_header_in_place(txn->db->rdbi, match, ser);
1,417✔
1704
  }
1,417✔
1705

1706
  if (needCommit)
1,907!
1707
    txn->txn->commit();
×
1708

1709
  return true;
1,907✔
1710
}
1,907✔
1711

1712
bool LMDBBackend::replaceComments(const domainid_t domain_id, const DNSName& qname, const QType& qtype, const vector<Comment>& comments)
1713
{
5✔
1714
  // delete all existing comments for the RRset
1715
  // this could be smarter and not del+replace unchanged comments
1716
  auto cursor = d_rwtxn->txn->getCursor(d_rwtxn->db->cdbi);
5✔
1717
  MDBOutVal key{};
5✔
1718
  MDBOutVal val{};
5✔
1719

1720
  compoundOrdername co;
5✔
1721

1722
  auto relqname = qname.makeRelative(d_transactiondomain);
5✔
1723

1724
  string match = co(domain_id, relqname, qtype);
5✔
1725

1726
  if (cursor.prefix(match, key, val) == 0) {
5✔
1727
    do {
1✔
1728
      cursor.del(key);
1✔
1729
    } while (cursor.next(key, val) == 0);
1!
1730
  }
1✔
1731

1732
  for (const auto& comment : comments) {
5✔
1733
    feedComment(comment);
5✔
1734
  }
5✔
1735

1736
  return true;
5✔
1737
}
5✔
1738

1739
// FIXME: this is not very efficient
1740
static DNSName keyUnconv(std::string& instr)
1741
{
182✔
1742
  // instr is now com0example0
1743
  vector<string> labels;
182✔
1744
  boost::split(labels, instr, [](char chr) { return chr == '\0'; });
2,549✔
1745

1746
  // we get a spurious empty label at the end, drop it
1747
  labels.resize(labels.size() - 1);
182✔
1748

1749
  if (labels.size() == 1 && labels[0].empty()) {
182!
1750
    // this is the root
1751
    return g_rootdnsname;
5✔
1752
  }
5✔
1753

1754
  DNSName tmp;
177✔
1755

1756
  while (!labels.empty()) {
566✔
1757
    tmp.appendRawLabel(labels.back());
389✔
1758
    labels.pop_back();
389✔
1759
  }
389✔
1760
  return tmp;
177✔
1761
}
182✔
1762

1763
static std::string makeBadDataExceptionMessage(const std::string& where, std::exception& exc, MDBOutVal& key, MDBOutVal& val)
1764
{
×
1765
  ostringstream msg;
×
1766
  msg << "during " << where << ", got exception (" << exc.what() << "), ";
×
1767
  msg << "key: " << makeHexDump(key.getNoStripHeader<string>()) << ", ";
×
1768
  msg << "value: " << makeHexDump(val.get<string>());
×
1769

1770
  return msg.str();
×
1771
}
×
1772

1773
void LMDBBackend::viewList(vector<string>& result)
1774
{
43✔
1775
  auto txn = d_tdomains->getEnv()->getROTransaction();
43✔
1776

1777
  auto cursor = txn->getROCursor(d_tviews);
43✔
1778

1779
  MDBOutVal key{}; // <view, dnsname>
43✔
1780
  MDBOutVal val{}; // <variant>
43✔
1781

1782
  auto ret = cursor.first(key, val);
43✔
1783

1784
  if (ret == MDB_NOTFOUND) {
43✔
1785
    return;
11✔
1786
  }
11✔
1787

1788
  do {
82✔
1789
    string view;
82✔
1790
    string zone;
82✔
1791
    try {
82✔
1792
      std::tie(view, zone) = splitField(key.getNoStripHeader<string>(), '\x0');
82✔
1793
      auto variant = val.get<string>();
82✔
1794
      result.push_back(view);
82✔
1795
    }
82✔
1796
    catch (std::exception& e) {
82✔
1797
      throw PDNSException(makeBadDataExceptionMessage("viewList", e, key, val));
×
1798
    }
×
1799

1800
    string inkey{view + string(1, (char)1)};
82✔
1801
    MDBInVal bound{inkey};
82✔
1802
    ret = cursor.lower_bound(bound, key, val); // this should use some lower bound thing to skip to the next view, also avoiding duplicates in `result`
82✔
1803
  } while (ret != MDB_NOTFOUND);
82✔
1804
}
32✔
1805

1806
void LMDBBackend::viewListZones(const string& inview, vector<ZoneName>& result)
1807
{
84✔
1808
  result.clear();
84✔
1809

1810
  auto txn = d_tdomains->getEnv()->getROTransaction();
84✔
1811

1812
  auto cursor = txn->getROCursor(d_tviews);
84✔
1813

1814
  string inkey{inview + string(1, (char)0)};
84✔
1815
  MDBInVal prefix{inkey};
84✔
1816
  MDBOutVal key{}; // <view, dnsname>
84✔
1817
  MDBOutVal val{}; // <variant>
84✔
1818

1819
  auto ret = cursor.prefix(prefix, key, val);
84✔
1820

1821
  if (ret == MDB_NOTFOUND) {
84✔
1822
    return;
2✔
1823
  }
2✔
1824

1825
  do {
182✔
1826
    try {
182✔
1827
      auto [view, _zone] = splitField(key.getNoStripHeader<string>(), '\x0');
182✔
1828
      auto variant = val.get<string>();
182✔
1829
      auto zone = keyUnconv(_zone);
182✔
1830
      result.emplace_back(ZoneName(zone, variant));
182✔
1831
    }
182✔
1832
    catch (std::exception& e) {
182✔
1833
      throw PDNSException(makeBadDataExceptionMessage("viewListZones", e, key, val));
×
1834
    }
×
1835

1836
    ret = cursor.next(key, val);
182✔
1837
  } while (ret != MDB_NOTFOUND);
182✔
1838
}
82✔
1839

1840
// TODO: make this add-or-del to reduce code duplication?
1841
bool LMDBBackend::viewAddZone(const string& view, const ZoneName& zone)
1842
{
201✔
1843
  auto txn = d_tdomains->getEnv()->getRWTransaction();
201✔
1844

1845
  string key = view + string(1, (char)0) + keyConv(zone.operator const DNSName&());
201✔
1846
  std::string val = MDBRWTransactionImpl::stringWithEmptyHeader();
201✔
1847
  val.append(zone.getVariant()); // variant goes here
201✔
1848

1849
  txn->put_header_in_place(d_tviews, key, val);
201✔
1850
  txn->commit();
201✔
1851

1852
  return true;
201✔
1853
}
201✔
1854

1855
bool LMDBBackend::viewDelZone(const string& view, const ZoneName& zone)
1856
{
12✔
1857
  auto txn = d_tdomains->getEnv()->getRWTransaction();
12✔
1858

1859
  string key = view + string(1, (char)0) + keyConv(zone.operator const DNSName&());
12✔
1860
  // string val = "foo"; // variant goes here
1861

1862
  txn->del(d_tviews, key);
12✔
1863
  txn->commit();
12✔
1864

1865
  return true;
12✔
1866
}
12✔
1867

1868
bool LMDBBackend::networkSet(const Netmask& net, std::string& view)
1869
{
103✔
1870
  auto txn = d_tdomains->getEnv()->getRWTransaction();
103✔
1871

1872
  if (view.empty()) {
103✔
1873
    txn->del(d_tnetworks, net.toByteString());
1✔
1874
  }
1✔
1875
  else {
102✔
1876
    txn->put(d_tnetworks, net.toByteString(), view);
102✔
1877
  }
102✔
1878
  txn->commit();
103✔
1879

1880
  return true;
103✔
1881
}
103✔
1882

1883
bool LMDBBackend::networkList(vector<pair<Netmask, string>>& networks)
1884
{
33✔
1885
  networks.clear();
33✔
1886

1887
  auto txn = d_tdomains->getEnv()->getROTransaction();
33✔
1888

1889
  auto cursor = txn->getROCursor(d_tnetworks);
33✔
1890

1891
  MDBOutVal netval{};
33✔
1892
  MDBOutVal viewval{};
33✔
1893

1894
  auto ret = cursor.first(netval, viewval);
33✔
1895

1896
  if (ret == MDB_NOTFOUND) {
33✔
1897
    return true;
11✔
1898
  }
11✔
1899

1900
  do {
52✔
1901
    try {
52✔
1902
      auto net = Netmask(netval.getNoStripHeader<string>(), Netmask::byteString);
52✔
1903
      auto view = viewval.get<string>();
52✔
1904
      networks.emplace_back(std::make_pair(net, view));
52✔
1905
    }
52✔
1906
    catch (std::exception& e) {
52✔
1907
      throw PDNSException(makeBadDataExceptionMessage("networkList", e, netval, viewval));
×
1908
    }
×
1909

1910
    ret = cursor.next(netval, viewval);
52✔
1911
  } while (ret != MDB_NOTFOUND);
52✔
1912

1913
  return true;
22✔
1914
}
22✔
1915

1916
// tempting to templatize these two functions but the pain is not worth it
1917
// NOLINTNEXTLINE(readability-identifier-length)
1918
std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(domainid_t id)
1919
{
2,676✔
1920
  auto& shard = d_trecords[id % s_shards];
2,676✔
1921
  if (!shard.env) {
2,676✔
1922
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
129✔
1923
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize_shards);
129✔
1924
    shard.rdbi = shard.env->openDB("records_v5", MDB_CREATE);
129✔
1925
    shard.cdbi = shard.env->openDB("comments_v7", MDB_CREATE);
129✔
1926
  }
129✔
1927
  auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
2,676✔
1928
  ret->db = std::make_shared<RecordsDB>(shard);
2,676✔
1929

1930
  return ret;
2,676✔
1931
}
2,676✔
1932

1933
// NOLINTNEXTLINE(readability-identifier-length)
1934
std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(domainid_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1935
{
41,745✔
1936
  auto& shard = d_trecords[id % s_shards];
41,745✔
1937
  if (!shard.env) {
41,745✔
1938
    if (rwtxn) {
5,198!
1939
      throw DBException("attempting to start nested transaction without open parent env");
×
1940
    }
×
1941
    shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
5,198✔
1942
                          MDB_NOSUBDIR | MDB_NORDAHEAD | d_asyncFlag, 0600, d_mapsize_shards);
5,198✔
1943
    shard.rdbi = shard.env->openDB("records_v5", MDB_CREATE);
5,198✔
1944
    shard.cdbi = shard.env->openDB("comments_v7", MDB_CREATE);
5,198✔
1945
  }
5,198✔
1946

1947
  if (rwtxn) {
41,745✔
1948
    auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
8,886✔
1949
    ret->db = std::make_shared<RecordsDB>(shard);
8,886✔
1950
    return ret;
8,886✔
1951
  }
8,886✔
1952
  else {
32,859✔
1953
    auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
32,859✔
1954
    ret->db = std::make_shared<RecordsDB>(shard);
32,859✔
1955
    return ret;
32,859✔
1956
  }
32,859✔
1957
}
41,745✔
1958

1959
bool LMDBBackend::deleteDomain(const ZoneName& domain)
1960
{
31✔
1961
  if (!d_rwtxn) {
31!
1962
    throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
×
1963
  }
×
1964

1965
  int transactionDomainId = d_transactiondomainid;
31✔
1966
  ZoneName transactionDomain = d_transactiondomain;
31✔
1967

1968
  abortTransaction();
31✔
1969

1970
  LmdbIdVec idvec;
31✔
1971

1972
  if (!d_handle_dups) {
31!
1973
    // get domain id
1974
    DomainInfo info;
31✔
1975
    if (findDomain(domain, info)) {
31!
1976
      idvec.push_back(info.id);
31✔
1977
    }
31✔
1978
  }
31✔
1979
  else {
×
1980
    // this transaction used to be RO.
1981
    // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1982
    // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1983
    // when doing that, first do a short RO check to see if we actually have anything to delete
1984
    auto txn = d_tdomains->getRWTransaction();
×
1985

1986
    txn.get_multi<0>(domain, idvec);
×
1987
  }
×
1988

1989
  for (auto id : idvec) {
31✔
1990

1991
    startTransaction(domain, id);
31✔
1992

1993
    { // Remove metadata
31✔
1994
      auto txn = d_tmeta->getRWTransaction();
31✔
1995
      LmdbIdVec ids;
31✔
1996

1997
      txn.get_multi<0>(domain, ids);
31✔
1998

1999
      for (auto& _id : ids) {
35✔
2000
        txn.del(_id);
35✔
2001
      }
35✔
2002

2003
      txn.commit();
31✔
2004
    }
31✔
2005

2006
    { // Remove cryptokeys
31✔
2007
      auto txn = d_tkdb->getRWTransaction();
31✔
2008
      LmdbIdVec ids;
31✔
2009
      txn.get_multi<0>(domain, ids);
31✔
2010

2011
      for (auto _id : ids) {
31!
2012
        txn.del(_id);
×
2013
      }
×
2014

2015
      txn.commit();
31✔
2016
    }
31✔
2017

2018
    // Remove records
2019
    commitTransaction();
31✔
2020

2021
    // Remove zone
2022
    {
31✔
2023
      auto container = s_transient_domain_info.write_lock();
31✔
2024
      container->remove(static_cast<domainid_t>(id));
31✔
2025
    }
31✔
2026
    auto txn = d_tdomains->getRWTransaction();
31✔
2027
    txn.del(id);
31✔
2028
    txn.commit();
31✔
2029
  }
31✔
2030

2031
  startTransaction(transactionDomain, transactionDomainId);
31✔
2032

2033
  return true;
31✔
2034
}
31✔
2035

2036
bool LMDBBackend::listComments(domainid_t domain_id)
2037
{
501✔
2038
  DomainInfo info;
501✔
2039
  if (!findDomain(domain_id, info)) {
501!
2040
    throw DBException("Domain with id '" + std::to_string(domain_id) + "' not found");
×
2041
  }
×
2042

2043
  d_lookupstate.domain = info.zone;
501✔
2044
  d_lookupstate.submatch.clear();
501✔
2045
  d_lookupstate.comments = true;
501✔
2046

2047
  compoundOrdername order;
501✔
2048
  std::string match = order(domain_id);
501✔
2049

2050
  lookupStart(domain_id, match, false);
501✔
2051
  return true;
501✔
2052
}
501✔
2053

2054
bool LMDBBackend::list(const ZoneName& target, domainid_t domain_id, bool include_disabled)
2055
{
1,676✔
2056
  d_lookupstate.domain = target;
1,676✔
2057
  d_lookupstate.submatch.clear();
1,676✔
2058
  d_lookupstate.includedisabled = include_disabled;
1,676✔
2059
  d_lookupstate.comments = false;
1,676✔
2060

2061
  compoundOrdername order;
1,676✔
2062
  std::string match = order(domain_id);
1,676✔
2063

2064
  lookupStart(domain_id, match, false);
1,676✔
2065
  return true;
1,676✔
2066
}
1,676✔
2067

2068
bool LMDBBackend::listSubZone(const ZoneName& target, domainid_t domain_id)
2069
{
1,192✔
2070
  // 1. from domain_id get base domain name
2071
  DomainInfo info;
1,192✔
2072
  if (!findDomain(domain_id, info)) {
1,192!
2073
    return false;
×
2074
  }
×
2075

2076
  // 2. make target relative to it
2077
  DNSName relqname = target.operator const DNSName&().makeRelative(info.zone);
1,192✔
2078
  if (relqname.empty()) {
1,192!
2079
    return false;
×
2080
  }
×
2081

2082
  // 3. enumerate complete domain, but tell get() to ignore entries which are
2083
  //    not subsets of target
2084
  d_lookupstate.domain = std::move(info.zone);
1,192✔
2085
  d_lookupstate.submatch = std::move(relqname);
1,192✔
2086
  d_lookupstate.includedisabled = true;
1,192✔
2087
  d_lookupstate.comments = false;
1,192✔
2088

2089
  compoundOrdername order;
1,192✔
2090
  std::string match = order(domain_id);
1,192✔
2091

2092
  lookupStart(domain_id, match, false);
1,192✔
2093
  return true;
1,192✔
2094
}
1,192✔
2095

2096
void LMDBBackend::lookupInternal(const QType& type, const DNSName& qdomain, domainid_t zoneId, DNSPacket* /* p */, bool include_disabled)
2097
{
16,173✔
2098
  if (d_dolog) {
16,173!
2099
    SLOG(g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl,
×
2100
         d_slog->info(Logr::Warning, "lookup", "domain", Logging::Loggable(qdomain), "type", Logging::Loggable(type), "zone id", Logging::Loggable(zoneId)));
×
2101
    d_dtime.set();
×
2102
  }
×
2103

2104
  DomainInfo info;
16,173✔
2105
  if (zoneId == UnknownDomainID) { // may be the case if coming from lookup()
16,173✔
2106
    ZoneName hunt(qdomain);
940✔
2107
    do {
940✔
2108
      if (findDomain(hunt, info)) {
940!
2109
        break;
940✔
2110
      }
940✔
2111
    } while (type != QType::SOA && hunt.chopOff());
940!
2112
    if (info.id == 0) {
940!
2113
      //      cout << "Did not find zone for "<< qdomain<<endl;
2114
      d_lookupstate.reset();
×
2115
      return;
×
2116
    }
×
2117
  }
940✔
2118
  else {
15,233✔
2119
    if (!findDomain(zoneId, info)) {
15,233✔
2120
      // cout<<"Could not find a zone with id "<<zoneId<<endl;
2121
      d_lookupstate.reset();
8✔
2122
      return;
8✔
2123
    }
8✔
2124
  }
15,233✔
2125

2126
  DNSName relqname = qdomain.makeRelative(info.zone);
16,165✔
2127
  if (relqname.empty()) {
16,165!
2128
    return;
×
2129
  }
×
2130
  // cout<<"get will look for "<<relqname<< " in zone "<<info.zone<<" with id "<<info.id<<" and type "<<type.toString()<<endl;
2131

2132
  d_lookupstate.domain = std::move(info.zone);
16,165✔
2133
  d_lookupstate.submatch.clear();
16,165✔
2134
  d_lookupstate.includedisabled = include_disabled;
16,165✔
2135
  d_lookupstate.comments = false;
16,165✔
2136

2137
  compoundOrdername order;
16,165✔
2138
  std::string match;
16,165✔
2139
  if (type.getCode() == QType::ANY) {
16,165✔
2140
    match = order(info.id, relqname);
10,133✔
2141
  }
10,133✔
2142
  else {
6,032✔
2143
    match = order(info.id, relqname, type.getCode());
6,032✔
2144
  }
6,032✔
2145

2146
  lookupStart(info.id, match, d_dolog);
16,165✔
2147
}
16,165✔
2148

2149
void LMDBBackend::lookupStart(domainid_t domain_id, const std::string& match, bool dolog)
2150
{
19,533✔
2151
  d_rotxn = getRecordsROTransaction(domain_id, d_rwtxn);
19,533✔
2152
  d_txnorder = true;
19,533✔
2153
  if (d_lookupstate.comments) {
19,533✔
2154
    d_lookupstate.cursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->cdbi));
501✔
2155
  }
501✔
2156
  else {
19,032✔
2157
    d_lookupstate.cursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->rdbi));
19,032✔
2158
  }
19,032✔
2159

2160
  // Make sure we start with fresh data
2161
  d_lookupstate.rrset.clear();
19,533✔
2162
  d_lookupstate.rrsetpos = 0;
19,533✔
2163

2164
  if (d_lookupstate.cursor->prefix(match, d_lookupstate.key, d_lookupstate.val) != 0) {
19,533✔
2165
    d_lookupstate.reset(); // will cause get() to fail
6,230✔
2166
    if (dolog) {
6,230!
2167
      SLOG(g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl,
×
2168
           d_slog->info(Logr::Warning, "query returned no results", "microseconds", Logging::Loggable(d_dtime.udiffNoReset())));
×
2169
    }
×
2170
    return;
6,230✔
2171
  }
6,230✔
2172

2173
  if (dolog) {
13,303!
2174
    SLOG(g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl,
×
2175
         d_slog->info(Logr::Warning, "query returned results", "microseconds", Logging::Loggable(d_dtime.udiffNoReset())));
×
2176
  }
×
2177
}
13,303✔
2178

2179
bool LMDBBackend::getInternal(DNSName& basename, std::string_view& key)
2180
{
692,538✔
2181
  // FIXME: bit of duplication from below here, but much simpler
2182
  if (d_lookupstate.comments) {
692,538✔
2183
    if (!d_lookupstate.cursor) {
522✔
2184
      d_rotxn.reset();
501✔
2185
      return false;
501✔
2186
    }
501✔
2187

2188
    key = d_lookupstate.key.getNoStripHeader<string_view>();
21✔
2189
    // remove hash from the key so compoundOrdername::get* works
2190
    key = key.substr(0, key.size() - 256 / 8);
21✔
2191
    if (key.size() == 0) {
21!
2192
      // removing the hash-sized suffix left us with nothing
2193
      throw DBException("got invalid serialized comment: key too short");
×
2194
    }
×
2195

2196
    basename = compoundOrdername::getQName(key);
21✔
2197

2198
    const auto& val = d_lookupstate.val.get<string>();
21✔
2199

2200
    d_lookupstate.comment.domain_id = compoundOrdername::getDomainID(key);
21✔
2201
    d_lookupstate.comment.qname = basename + d_lookupstate.domain.operator const DNSName&();
21✔
2202
    d_lookupstate.comment.qtype = compoundOrdername::getQType(key);
21✔
2203
    try {
21✔
2204
      protozero::pbf_reader message{val};
21✔
2205
      message.next(1);
21✔
2206
      d_lookupstate.comment.modified_at = message.get_sfixed64();
21✔
2207
      message.next(2);
21✔
2208
      d_lookupstate.comment.account = message.get_string();
21✔
2209
      message.next(3);
21✔
2210
      d_lookupstate.comment.content = message.get_string();
21✔
2211
    }
21✔
2212
    catch (protozero::exception& e) {
21✔
2213
      throw DBException(std::string("got invalid serialized comment: ") + e.what());
×
2214
    }
×
2215

2216
    if (d_lookupstate.cursor && d_lookupstate.cursor->next(d_lookupstate.key, d_lookupstate.val) != 0) {
21!
2217
      d_lookupstate.reset(); // this invalidates cursor and makes us return false on the next round
13✔
2218
    }
13✔
2219

2220
    return true;
21✔
2221
  }
21✔
2222

2223
  for (;;) {
829,128✔
2224
    if (!d_lookupstate.rrset.empty()) {
829,128✔
2225
      if (++d_lookupstate.rrsetpos >= d_lookupstate.rrset.size()) {
697,796✔
2226
        d_lookupstate.rrset.clear(); // will invalidate lrr
583,268✔
2227
        if (d_lookupstate.cursor && d_lookupstate.cursor->next(d_lookupstate.key, d_lookupstate.val) != 0) {
583,268!
2228
          // cerr<<"resetting d_lookupstate.cursor 2"<<endl;
2229
          d_lookupstate.reset();
9,756✔
2230
        }
9,756✔
2231
      }
583,268✔
2232
    }
697,796✔
2233

2234
    // std::cerr<<"d_lookupstate.cursor="<<d_lookupstate.cursor<<std::endl;
2235
    if (!d_lookupstate.cursor) {
829,128✔
2236
      d_rotxn.reset();
18,837✔
2237
      return false;
18,837✔
2238
    }
18,837✔
2239

2240
    if (d_lookupstate.rrset.empty()) {
810,291✔
2241
      d_lookupstate.cursor->current(d_lookupstate.key, d_lookupstate.val);
695,763✔
2242

2243
      key = d_lookupstate.key.getNoStripHeader<string_view>();
695,763✔
2244
      QType qtype = compoundOrdername::getQType(key).getCode();
695,763✔
2245

2246
      if (qtype == QType::NSEC3) {
695,763✔
2247
        // Hit a special NSEC3 record, skip it
2248
        if (d_lookupstate.cursor->next(d_lookupstate.key, d_lookupstate.val) != 0) {
112,291✔
2249
          // cerr<<"resetting d_lookupstate.cursor 1"<<endl;
2250
          d_lookupstate.reset();
3,331✔
2251
        }
3,331✔
2252
        continue;
112,291✔
2253
      }
112,291✔
2254

2255
      deserializeMultipleFromBuffer(d_lookupstate.val.get<string_view>(), d_lookupstate.rrset);
583,472✔
2256
      if (!d_lookupstate.rrset.empty()) {
583,472!
2257
        d_lookupstate.rrsettime = static_cast<time_t>(LMDBLS::LSgetTimestamp(d_lookupstate.val.getNoStripHeader<string_view>()) / (1000UL * 1000UL * 1000UL));
583,472✔
2258
        d_lookupstate.rrsetpos = 0;
583,472✔
2259
      }
583,472✔
2260
    }
583,472✔
2261
    else {
114,528✔
2262
      key = d_lookupstate.key.getNoStripHeader<string_view>();
114,528✔
2263
    }
114,528✔
2264
    try {
698,000✔
2265
      const auto& lrr = d_lookupstate.rrset.at(d_lookupstate.rrsetpos);
698,000✔
2266
      bool validRecord = d_lookupstate.includedisabled || !lrr.disabled;
698,000✔
2267

2268
      if (validRecord) {
698,000✔
2269
        basename = compoundOrdername::getQName(key);
697,994✔
2270
        if (!d_lookupstate.submatch.empty()) {
697,994✔
2271
          validRecord = basename.isPartOf(d_lookupstate.submatch);
33,904✔
2272
        }
33,904✔
2273
      }
697,994✔
2274

2275
      if (!validRecord) {
698,000✔
2276
        continue;
24,821✔
2277
      }
24,821✔
2278
    }
698,000✔
2279
    catch (const std::exception& e) {
698,000✔
2280
      throw PDNSException(e.what());
×
2281
    }
×
2282

2283
    break;
673,179✔
2284
  }
698,000✔
2285

2286
  return true;
673,179✔
2287
}
692,016✔
2288

2289
bool LMDBBackend::getComment(Comment& comment) // NOLINT(readability-identifier-length)
2290
{
522✔
2291
  DNSName basename;
522✔
2292
  std::string_view key;
522✔
2293

2294
  if (!getInternal(basename, key)) {
522✔
2295
    return false;
501✔
2296
  }
501✔
2297
  comment = d_lookupstate.comment;
21✔
2298

2299
  return true;
21✔
2300
}
522✔
2301

2302
bool LMDBBackend::get(DNSZoneRecord& zr) // NOLINT(readability-identifier-length)
2303
{
679,990✔
2304
  DNSName basename;
679,990✔
2305
  std::string_view key;
679,990✔
2306

2307
  if (!getInternal(basename, key)) {
679,990✔
2308
    return false;
18,465✔
2309
  }
18,465✔
2310
  const auto& lrr = d_lookupstate.rrset.at(d_lookupstate.rrsetpos);
661,525✔
2311
  try {
661,525✔
2312
    zr.dr.d_name = basename + d_lookupstate.domain.operator const DNSName&();
661,525✔
2313
    zr.domain_id = compoundOrdername::getDomainID(key);
661,525✔
2314
    zr.dr.d_type = compoundOrdername::getQType(key).getCode();
661,525✔
2315
    zr.dr.d_ttl = lrr.ttl;
661,525✔
2316
    zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
661,525✔
2317
    zr.auth = lrr.auth;
661,525✔
2318
    zr.disabled = lrr.disabled;
661,525✔
2319
  }
661,525✔
2320
  catch (const std::exception& e) {
661,525✔
2321
    lookupEnd();
×
2322
    throw PDNSException(e.what());
×
2323
  }
×
2324

2325
  return true;
661,525✔
2326
}
661,525✔
2327

2328
bool LMDBBackend::get(DNSResourceRecord& rr)
2329
{
572,907✔
2330
  DNSZoneRecord zr;
572,907✔
2331
  if (!get(zr)) {
572,907✔
2332
    return false;
10,376✔
2333
  }
10,376✔
2334

2335
  rr.qname = zr.dr.d_name;
562,531✔
2336
  rr.ttl = zr.dr.d_ttl;
562,531✔
2337
  rr.qtype = zr.dr.d_type;
562,531✔
2338
  rr.content = zr.dr.getContent()->getZoneRepresentation(true);
562,531✔
2339
  rr.domain_id = zr.domain_id;
562,531✔
2340
  rr.auth = zr.auth;
562,531✔
2341
  rr.disabled = zr.disabled;
562,531✔
2342
  rr.last_modified = d_lookupstate.rrsettime;
562,531✔
2343

2344
  return true;
562,531✔
2345
}
572,907✔
2346

2347
void LMDBBackend::lookupEnd()
2348
{
204✔
2349
  d_lookupstate.reset();
204✔
2350
  d_rotxn.reset();
204✔
2351
}
204✔
2352

2353
bool LMDBBackend::getSerial(DomainInfo& di)
2354
{
4,474✔
2355
  auto txn = getRecordsROTransaction(di.id);
4,474✔
2356
  compoundOrdername co;
4,474✔
2357
  MDBOutVal val;
4,474✔
2358
  if (!txn->txn->get(txn->db->rdbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
4,474✔
2359
    LMDBResourceRecord lrr;
3,745✔
2360
    if (deserializeFromBuffer(val.get<string_view>(), lrr)) {
3,745!
2361
      if (lrr.content.size() >= sizeof(soatimes)) {
3,745!
2362
        soatimes st;
3,745✔
2363
        // A SOA has five 32 bit fields, the first of which is the serial;
2364
        // there are two variable length names before the serial, so we calculate from the back.
2365
        memcpy(&st.serial, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(st.serial));
3,745✔
2366
        di.serial = ntohl(st.serial);
3,745✔
2367
      }
3,745✔
2368
      return !lrr.disabled;
3,745✔
2369
    }
3,745✔
2370
  }
3,745✔
2371
  return false;
729✔
2372
}
4,474✔
2373

2374
bool LMDBBackend::getDomainInfo(const ZoneName& domain, DomainInfo& info, bool getserial)
2375
{
4,921✔
2376
  // If caller asks about a zone with variant, but views are not enabled,
2377
  // punt.
2378
  if (domain.hasVariant() && !d_views) {
4,921!
2379
    return false;
×
2380
  }
×
2381

2382
  if (!findDomain(domain, info)) {
4,921✔
2383
    return false;
570✔
2384
  }
570✔
2385
  info.backend = this;
4,351✔
2386
  consolidateDomainInfo(info);
4,351✔
2387

2388
  if (getserial) {
4,351✔
2389
    getSerial(info);
3,248✔
2390
  }
3,248✔
2391

2392
  return true;
4,351✔
2393
}
4,921✔
2394

2395
bool LMDBBackend::genChangeDomain(const ZoneName& domain, const std::function<void(DomainInfo&)>& func)
2396
{
775✔
2397
  DomainInfo info;
775✔
2398
  if (!findDomain(domain, info)) {
775!
2399
    return false;
×
2400
  }
×
2401
  consolidateDomainInfo(info);
775✔
2402
  func(info);
775✔
2403
  writeDomainInfo(info);
775✔
2404
  return true;
775✔
2405
}
775✔
2406

2407
// NOLINTNEXTLINE(readability-identifier-length)
2408
bool LMDBBackend::genChangeDomain(domainid_t id, const std::function<void(DomainInfo&)>& func)
2409
{
×
2410
  DomainInfo info;
×
2411
  if (!findDomain(id, info)) {
×
2412
    return false;
×
2413
  }
×
2414
  consolidateDomainInfo(info);
×
2415
  func(info);
×
2416
  writeDomainInfo(info);
×
2417
  return true;
×
2418
}
×
2419

2420
// Similar to the above, but callback will only change the TransientDomainInfo
2421
// fields.
2422
bool LMDBBackend::genChangeTransientDomain(domainid_t id, const std::function<void(DomainInfo&)>& func) // NOLINTNEXT(readability-identifier-length)
2423
{
148✔
2424
  DomainInfo info;
148✔
2425
  if (!findDomain(id, info)) {
148!
2426
    return false;
×
2427
  }
×
2428
  consolidateDomainInfo(info);
148✔
2429
  func(info);
148✔
2430
  if (!d_write_notification_update) {
148!
2431
    // This won't write anything but update the in-memory cache
2432
    writeDomainInfo(info);
×
2433
  }
×
2434
  else {
148✔
2435
    // No need to write the complete DomainInfo in this case
2436
    writeTransientDomainInfo(info);
148✔
2437
  }
148✔
2438
  return true;
148✔
2439
}
148✔
2440

2441
bool LMDBBackend::setKind(const ZoneName& domain, const DomainInfo::DomainKind kind)
2442
{
331✔
2443
  return genChangeDomain(domain, [kind](DomainInfo& di) {
331✔
2444
    di.kind = kind;
331✔
2445
  });
331✔
2446
}
331✔
2447

2448
bool LMDBBackend::setAccount(const ZoneName& domain, const std::string& account)
2449
{
1✔
2450
  return genChangeDomain(domain, [account](DomainInfo& di) {
1✔
2451
    di.account = account;
1✔
2452
  });
1✔
2453
}
1✔
2454

2455
bool LMDBBackend::setPrimaries(const ZoneName& domain, const vector<ComboAddress>& primaries)
2456
{
76✔
2457
  return genChangeDomain(domain, [&primaries](DomainInfo& di) {
76✔
2458
    di.primaries = primaries;
76✔
2459
  });
76✔
2460
}
76✔
2461

2462
bool LMDBBackend::createDomain(const ZoneName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
2463
{
545✔
2464
  DomainInfo info;
545✔
2465

2466
  if (findDomain(domain, info)) {
545!
2467
    throw DBException("Domain '" + domain.toLogString() + "' exists already");
×
2468
  }
×
2469
  {
545✔
2470
    auto txn = d_tdomains->getRWTransaction();
545✔
2471

2472
    info.zone = domain;
545✔
2473
    info.kind = kind;
545✔
2474
    info.primaries = primaries;
545✔
2475
    info.account = account;
545✔
2476

2477
    txn.put(info, 0, d_random_ids, domain.hash());
545✔
2478
    txn.commit();
545✔
2479
    writeTransientDomainInfo(info);
545✔
2480
  }
545✔
2481

2482
  return true;
545✔
2483
}
545✔
2484

2485
void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
2486
{
170✔
2487
  auto txn = d_tdomains->getROTransaction();
170✔
2488
  if (d_handle_dups) {
170!
2489
    map<ZoneName, DomainInfo> zonemap;
×
2490
    set<ZoneName> dups;
×
2491

2492
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
2493
      DomainInfo di = *iter;
×
2494
      di.id = iter.getID();
×
2495
      di.backend = this;
×
2496

2497
      if (!zonemap.emplace(di.zone, di).second) {
×
2498
        dups.insert(di.zone);
×
2499
      }
×
2500
    }
×
2501

2502
    for (const auto& zone : dups) {
×
2503
      DomainInfo info;
×
2504
      // this get grabs the oldest item if there are duplicates
2505
      if (!findDomain(zone, info)) {
×
2506
        continue;
×
2507
      }
×
2508
      info.backend = this;
×
2509
      zonemap[info.zone] = info;
×
2510
    }
×
2511

2512
    for (auto& [k, v] : zonemap) {
×
2513
      consolidateDomainInfo(v);
×
2514
      if (allow(v)) {
×
2515
        domains->push_back(std::move(v));
×
2516
      }
×
2517
    }
×
2518
  }
×
2519
  else {
170✔
2520
    for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
3,340✔
2521
      DomainInfo di = *iter;
3,170✔
2522
      di.id = iter.getID();
3,170✔
2523
      di.backend = this;
3,170✔
2524

2525
      consolidateDomainInfo(di);
3,170✔
2526
      if (allow(di)) {
3,170✔
2527
        domains->push_back(std::move(di));
2,690✔
2528
      }
2,690✔
2529
    }
3,170✔
2530
  }
170✔
2531
}
170✔
2532

2533
void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool doSerial, bool include_disabled)
2534
{
135✔
2535
  getAllDomainsFiltered(domains, [this, doSerial, include_disabled](DomainInfo& info) {
2,604✔
2536
    // We need to read the SOA record in order to know if the domain is
2537
    // disabled. If we don't care about serials and want all domains to be
2538
    // returned, skip the SOA record retrieval.
2539
    if (doSerial || !include_disabled) {
2,604✔
2540
      if (!getSerial(info) && !include_disabled) {
1,226!
2541
        return false;
×
2542
      }
×
2543
    }
1,226✔
2544

2545
    // Skip domains with variants if views are disabled.
2546
    if (info.zone.hasVariant() && !d_views) {
2,604!
2547
      return false;
×
2548
    }
×
2549

2550
    return true;
2,604✔
2551
  });
2,604✔
2552
}
135✔
2553

2554
void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
2555
{
11✔
2556
  time_t now = time(nullptr);
11✔
2557

2558
  getAllDomainsFiltered(domains, [this, now](DomainInfo& di) {
86✔
2559
    if (!di.isSecondaryType()) {
86!
2560
      return false;
×
2561
    }
×
2562

2563
    auto txn2 = getRecordsROTransaction(di.id);
86✔
2564
    compoundOrdername co;
86✔
2565
    MDBOutVal val;
86✔
2566
    if (!txn2->txn->get(txn2->db->rdbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
86✔
2567
      LMDBResourceRecord lrr;
54✔
2568
      if (deserializeFromBuffer(val.get<string_view>(), lrr)) {
54!
2569
        if (lrr.content.size() >= sizeof(soatimes)) {
54!
2570
          soatimes st;
54✔
2571
          // There are two variable length names before the SOA numbers, so we calculate from the back.
2572
          memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
54✔
2573
          if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
54!
2574
            return false;
×
2575
          }
×
2576
        }
54✔
2577
      }
54✔
2578
    }
54✔
2579
    return true;
86✔
2580
  });
86✔
2581
}
11✔
2582

2583
void LMDBBackend::setStale(domainid_t domain_id)
2584
{
2✔
2585
  setLastCheckTime(domain_id, 0);
2✔
2586
}
2✔
2587

2588
void LMDBBackend::setFresh(domainid_t domain_id)
2589
{
146✔
2590
  setLastCheckTime(domain_id, time(nullptr));
146✔
2591
}
146✔
2592

2593
void LMDBBackend::setLastCheckTime(domainid_t domain_id, time_t last_check)
2594
{
148✔
2595
  if (!d_write_notification_update) {
148!
2596
    DomainInfo info;
×
2597
    if (findDomain(domain_id, info)) {
×
2598
      auto container = s_transient_domain_info.write_lock();
×
2599
      TransientDomainInfo tdi;
×
2600
      if (!container->get(info.id, tdi)) {
×
2601
        // No data yet, initialize from DomainInfo
2602
        tdi.notified_serial = info.notified_serial;
×
2603
      }
×
2604
      tdi.last_check = last_check;
×
2605
      container->update(info.id, tdi);
×
2606
    }
×
2607
    return;
×
2608
  }
×
2609

2610
  genChangeTransientDomain(domain_id, [last_check](DomainInfo& info) {
148✔
2611
    info.last_check = last_check;
148✔
2612
  });
148✔
2613
}
148✔
2614

2615
void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
2616
{
×
2617
  getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes](DomainInfo& di) {
×
2618
    if (!di.isPrimaryType()) {
×
2619
      return false;
×
2620
    }
×
2621

2622
    if (di.kind == DomainInfo::Producer) {
×
2623
      catalogs.insert(di.zone.operator const DNSName&());
×
2624
      catalogHashes[di.zone].process("");
×
2625
      return false; // Producer freshness check is performed elsewhere
×
2626
    }
×
2627

2628
    if (!di.catalog.empty()) {
×
2629
      CatalogInfo::updateCatalogHash(catalogHashes, di);
×
2630
    }
×
2631

2632
    if (getSerial(di) && di.serial != di.notified_serial) {
×
2633
      di.backend = this;
×
2634
      di.catalog.clear();
×
2635
      di.options.clear();
×
2636
      return true;
×
2637
    }
×
2638

2639
    return false;
×
2640
  });
×
2641
}
×
2642

2643
void LMDBBackend::setNotified(domainid_t domain_id, uint32_t serial)
2644
{
1✔
2645
  if (!d_write_notification_update) {
1!
2646
    DomainInfo info;
×
2647
    if (findDomain(domain_id, info)) {
×
2648
      auto container = s_transient_domain_info.write_lock();
×
2649
      TransientDomainInfo tdi;
×
2650
      if (!container->get(info.id, tdi)) {
×
2651
        // No data yet, initialize from DomainInfo
2652
        tdi.last_check = info.last_check;
×
2653
      }
×
2654
      tdi.notified_serial = serial;
×
2655
      container->update(info.id, tdi);
×
2656
    }
×
2657
    return;
×
2658
  }
×
2659

2660
  genChangeTransientDomain(domain_id, [serial](DomainInfo& info) {
1✔
2661
    info.notified_serial = serial;
1✔
2662
  });
1✔
2663
}
1✔
2664

2665
class getCatalogMembersReturnFalseException : std::runtime_error
2666
{
2667
public:
2668
  getCatalogMembersReturnFalseException() :
2669
    std::runtime_error("getCatalogMembers should return false") {}
×
2670
};
2671

2672
bool LMDBBackend::getCatalogMembers(const ZoneName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
2673
{
24✔
2674
  vector<DomainInfo> scratch;
24✔
2675

2676
  try {
24✔
2677
    getAllDomainsFiltered(&scratch, [this, &catalog, &members, &type](DomainInfo& di) {
480✔
2678
      if ((type == CatalogInfo::CatalogType::Producer && !di.isPrimaryType()) || (type == CatalogInfo::CatalogType::Consumer && !di.isSecondaryType()) || di.catalog != catalog) {
480!
2679
        return false;
272✔
2680
      }
272✔
2681

2682
      if (di.isCatalogType() && di.zone == di.catalog) {
208!
2683
        SLOG(g_log << Logger::Warning << __PRETTY_FUNCTION__ << " catalog '" << di.zone << "' cannot be a member of itself" << endl,
×
2684
             d_slog->info(Logr::Warning, "catalog cannot be a member of itself", "catalog", Logging::Loggable(di.zone)));
×
2685
        members.clear();
×
2686
        throw getCatalogMembersReturnFalseException();
×
2687
      }
×
2688

2689
      CatalogInfo ci;
208✔
2690
      ci.d_id = di.id;
208✔
2691
      ci.d_zone = di.zone;
208✔
2692
      ci.d_primaries = di.primaries;
208✔
2693
      try {
208✔
2694
        ci.fromJson(di.options, type);
208✔
2695
        if (di.isCatalogType()) {
208✔
2696
          ci.addGroup(g_memberCatalogGroup);
12✔
2697
        }
12✔
2698
      }
208✔
2699
      catch (const std::runtime_error& e) {
208✔
2700
        SLOG(g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl,
×
2701
             d_slog->error(Logr::Warning, e.what(), "zone options are not in valid JSON format", "zone", Logging::Loggable(di.zone), "options", Logging::Loggable(di.options)));
×
2702
        members.clear();
×
2703
        throw getCatalogMembersReturnFalseException();
×
2704
      }
×
2705
      members.emplace_back(ci);
208✔
2706

2707
      return false;
208✔
2708
    });
208✔
2709
  }
24✔
2710
  catch (const getCatalogMembersReturnFalseException& e) {
24✔
2711
    return false;
×
2712
  }
×
2713
  return true;
24✔
2714
}
24✔
2715

2716
bool LMDBBackend::setOptions(const ZoneName& domain, const std::string& options)
2717
{
100✔
2718
  return genChangeDomain(domain, [options](DomainInfo& di) {
100✔
2719
    di.options = options;
100✔
2720
  });
100✔
2721
}
100✔
2722

2723
bool LMDBBackend::setCatalog(const ZoneName& domain, const ZoneName& catalog)
2724
{
267✔
2725
  return genChangeDomain(domain, [catalog](DomainInfo& di) {
267✔
2726
    di.catalog = catalog;
267✔
2727
  });
267✔
2728
}
267✔
2729

2730
bool LMDBBackend::getAllDomainMetadata(const ZoneName& name, std::map<std::string, std::vector<std::string>>& meta)
2731
{
9,747✔
2732
  meta.clear();
9,747✔
2733
  auto txn = d_tmeta->getROTransaction();
9,747✔
2734
  LmdbIdVec ids;
9,747✔
2735
  txn.get_multi<0>(name, ids);
9,747✔
2736

2737
  DomainMeta dm;
9,747✔
2738
  // cerr<<"getAllDomainMetadata start"<<endl;
2739
  for (auto id : ids) {
14,783✔
2740
    if (txn.get(id, dm)) {
14,783!
2741
      meta[dm.key].push_back(dm.value);
14,783✔
2742
    }
14,783✔
2743
  }
14,783✔
2744
  return true;
9,747✔
2745
}
9,747✔
2746

2747
bool LMDBBackend::setDomainMetadata(const ZoneName& name, const std::string& kind, const std::vector<std::string>& meta)
2748
{
1,035✔
2749
  auto txn = d_tmeta->getRWTransaction();
1,035✔
2750

2751
  LmdbIdVec ids;
1,035✔
2752
  txn.get_multi<0>(name, ids);
1,035✔
2753

2754
  DomainMeta dmeta;
1,035✔
2755
  for (auto id : ids) {
1,037✔
2756
    if (txn.get(id, dmeta)) {
840!
2757
      if (dmeta.key == kind) {
840✔
2758
        // cerr<<"delete"<<endl;
2759
        txn.del(id);
69✔
2760
      }
69✔
2761
    }
840✔
2762
  }
840✔
2763

2764
  for (const auto& m : meta) {
1,035✔
2765
    DomainMeta dm{name, kind, m};
856✔
2766
    txn.put(dm, 0, d_random_ids, burtleCI(kind, name.hash()));
856✔
2767
  }
856✔
2768
  txn.commit();
1,035✔
2769
  return true;
1,035✔
2770
}
1,035✔
2771

2772
bool LMDBBackend::getDomainKeys(const ZoneName& name, std::vector<KeyData>& keys)
2773
{
1,327✔
2774
  auto txn = d_tkdb->getROTransaction();
1,327✔
2775
  LmdbIdVec ids;
1,327✔
2776
  txn.get_multi<0>(name, ids);
1,327✔
2777

2778
  KeyDataDB key;
1,327✔
2779

2780
  for (auto id : ids) {
1,327✔
2781
    if (txn.get(id, key)) {
740!
2782
      KeyData kd{key.content, id, key.flags, key.active, key.published};
740✔
2783
      keys.emplace_back(std::move(kd));
740✔
2784
    }
740✔
2785
  }
740✔
2786

2787
  return true;
1,327✔
2788
}
1,327✔
2789

2790
bool LMDBBackend::removeDomainKey(const ZoneName& name, unsigned int keyId)
2791
{
29✔
2792
  auto txn = d_tkdb->getRWTransaction();
29✔
2793
  KeyDataDB kdb;
29✔
2794
  if (txn.get(keyId, kdb)) {
29✔
2795
    if (kdb.domain == name) {
20!
2796
      txn.del(keyId);
20✔
2797
      txn.commit();
20✔
2798
      return true;
20✔
2799
    }
20✔
2800
  }
20✔
2801
  // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2802
  return true;
9✔
2803
}
29✔
2804

2805
bool LMDBBackend::addDomainKey(const ZoneName& name, const KeyData& key, int64_t& keyId)
2806
{
217✔
2807
  auto txn = d_tkdb->getRWTransaction();
217✔
2808
  KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
217✔
2809

2810
  // all this just to get the tag - while most of our callers (except b2b-migrate) already have a dpk
2811
  DNSKEYRecordContent dkrc;
217✔
2812
  auto keyEngine = shared_ptr<DNSCryptoKeyEngine>(DNSCryptoKeyEngine::makeFromISCString(d_slog, dkrc, key.content));
217✔
2813
  DNSSECPrivateKey dpk;
217✔
2814
  dpk.setKey(keyEngine, key.flags);
217✔
2815
  auto tag = dpk.getDNSKEY().getTag();
217✔
2816

2817
  keyId = txn.put(kdb, 0, d_random_ids, name.hash(tag));
217✔
2818
  txn.commit();
217✔
2819

2820
  return true;
217✔
2821
}
217✔
2822

2823
bool LMDBBackend::activateDomainKey(const ZoneName& name, unsigned int keyId)
2824
{
6✔
2825
  auto txn = d_tkdb->getRWTransaction();
6✔
2826
  KeyDataDB kdb;
6✔
2827
  if (txn.get(keyId, kdb)) {
6!
2828
    if (kdb.domain == name) {
6!
2829
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2830
        kdbarg.active = true;
6✔
2831
      });
6✔
2832
      txn.commit();
6✔
2833
      return true;
6✔
2834
    }
6✔
2835
  }
6✔
2836

2837
  // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2838
  return true;
×
2839
}
6✔
2840

2841
bool LMDBBackend::deactivateDomainKey(const ZoneName& name, unsigned int keyId)
2842
{
4✔
2843
  auto txn = d_tkdb->getRWTransaction();
4✔
2844
  KeyDataDB kdb;
4✔
2845
  if (txn.get(keyId, kdb)) {
4!
2846
    if (kdb.domain == name) {
4!
2847
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
4✔
2848
        kdbarg.active = false;
4✔
2849
      });
4✔
2850
      txn.commit();
4✔
2851
      return true;
4✔
2852
    }
4✔
2853
  }
4✔
2854
  // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2855
  return true;
×
2856
}
4✔
2857

2858
bool LMDBBackend::publishDomainKey(const ZoneName& name, unsigned int keyId)
2859
{
6✔
2860
  auto txn = d_tkdb->getRWTransaction();
6✔
2861
  KeyDataDB kdb;
6✔
2862
  if (txn.get(keyId, kdb)) {
6!
2863
    if (kdb.domain == name) {
6!
2864
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
6✔
2865
        kdbarg.published = true;
6✔
2866
      });
6✔
2867
      txn.commit();
6✔
2868
      return true;
6✔
2869
    }
6✔
2870
  }
6✔
2871

2872
  // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2873
  return true;
×
2874
}
6✔
2875

2876
bool LMDBBackend::unpublishDomainKey(const ZoneName& name, unsigned int keyId)
2877
{
10✔
2878
  auto txn = d_tkdb->getRWTransaction();
10✔
2879
  KeyDataDB kdb;
10✔
2880
  if (txn.get(keyId, kdb)) {
10!
2881
    if (kdb.domain == name) {
10!
2882
      txn.modify(keyId, [](KeyDataDB& kdbarg) {
10✔
2883
        kdbarg.published = false;
10✔
2884
      });
10✔
2885
      txn.commit();
10✔
2886
      return true;
10✔
2887
    }
10✔
2888
  }
10✔
2889
  // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<keyId<<", could not find it"<<endl;
2890
  return true;
×
2891
}
10✔
2892

2893
// Return true if the key points to an NSEC3 back chain record (ttl == 0).
2894
bool LMDBBackend::isNSEC3BackRecord(const MDBOutVal& key, const MDBOutVal& val)
2895
{
48,830✔
2896
  if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
48,830✔
2897
    if (peekAtTtl(val.get<StringView>()) == 0) {
29,554✔
2898
      return true;
18,598✔
2899
    }
18,598✔
2900
  }
29,554✔
2901
  return false;
30,232✔
2902
}
48,830✔
2903

2904
// Search for the next NSEC3 back record and return its qname as `after'.
2905
// Returns true if found, false if not (either end of database records, or
2906
// different domain).
2907
// NOLINTNEXTLINE(readability-identifier-length)
2908
bool LMDBBackend::getAfterForward(MDBROCursor& cursor, MDBOutVal& key, MDBOutVal& val, domainid_t id, DNSName& after)
2909
{
13,670✔
2910
  while (!isNSEC3BackRecord(key, val)) {
37,952✔
2911
    if (cursor.next(key, val) != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<StringView>()) != id) {
25,566✔
2912
      // cout<<"hit end of zone or database when we shouldn't"<<endl;
2913
      return false;
1,284✔
2914
    }
1,284✔
2915
  }
25,566✔
2916
  after = compoundOrdername::getQName(key.getNoStripHeader<StringView>());
12,386✔
2917
  // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2918
  return true;
12,386✔
2919
}
13,670✔
2920

2921
// Reset the cursor position and fall through getAfterForward.
2922
// NOLINTNEXTLINE(readability-identifier-length)
2923
bool LMDBBackend::getAfterForwardFromStart(MDBROCursor& cursor, MDBOutVal& key, MDBOutVal& val, domainid_t id, DNSName& after)
2924
{
1,954✔
2925
  compoundOrdername co; // NOLINT(readability-identifier-length)
1,954✔
2926

2927
  if (cursor.lower_bound(co(id), key, val) != 0) {
1,954!
2928
    // cout<<"hit end of zone find when we shouldn't"<<endl;
2929
    return false;
×
2930
  }
×
2931
  return getAfterForward(cursor, key, val, id, after);
1,954✔
2932
}
1,954✔
2933

2934
// NOLINTNEXTLINE(readability-identifier-length)
2935
bool LMDBBackend::getBeforeAndAfterNamesAbsolute(domainid_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2936
{
12,386✔
2937
  //  cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2938

2939
  DomainInfo info;
12,386✔
2940
  if (!findDomain(id, info)) {
12,386!
2941
    // domain does not exist, tough luck
2942
    return false;
×
2943
  }
×
2944
  // cout <<"Zone: "<<info.zone<<endl;
2945

2946
  compoundOrdername co;
12,386✔
2947
  auto txn = getRecordsROTransaction(id);
12,386✔
2948

2949
  auto cursor = txn->txn->getCursor(txn->db->rdbi);
12,386✔
2950
  MDBOutVal key, val;
12,386✔
2951

2952
  string matchkey = co(id, qname, QType::NSEC3);
12,386✔
2953
  if (cursor.lower_bound(matchkey, key, val)) {
12,386✔
2954
    // this is beyond the end of the database
2955
    // cout << "Beyond end of database!" << endl;
2956
    cursor.last(key, val);
182✔
2957

2958
    for (;;) {
752✔
2959
      if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
752!
2960
        // cout<<"Last record also not part of this zone!"<<endl;
2961
        //  this implies something is wrong in the database, nothing we can do
2962
        return false;
×
2963
      }
×
2964

2965
      if (isNSEC3BackRecord(key, val)) {
752✔
2966
        break; // the kind of NSEC3 we need
182✔
2967
      }
182✔
2968
      if (cursor.prev(key, val)) {
570!
2969
        // hit beginning of database, again means something is wrong with it
2970
        return false;
×
2971
      }
×
2972
    }
570✔
2973
    before = co.getQName(key.getNoStripHeader<StringView>());
182✔
2974
    {
182✔
2975
      LMDBResourceRecord lrr;
182✔
2976
      if (deserializeFromBuffer(val.get<StringView>(), lrr)) {
182!
2977
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
182✔
2978
      }
182✔
2979
    }
182✔
2980
    // now to find after .. at the beginning of the zone
2981
    return getAfterForwardFromStart(cursor, key, val, id, after);
182✔
2982
  }
182✔
2983

2984
  // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
2985

2986
  before = co.getQName(key.getNoStripHeader<StringView>());
12,204✔
2987
  if (before == qname) {
12,204✔
2988
    // cout << "Ended up on exact right node" << endl;
2989
    // unhashed should be correct now, maybe check?
2990
    if (cursor.next(key, val)) {
6,174✔
2991
      // xxx should find first hash now
2992

2993
      return getAfterForwardFromStart(cursor, key, val, id, after);
190✔
2994
    }
190✔
2995
  }
6,174✔
2996
  else {
6,030✔
2997
    // cout <<"Going backwards to find 'before'"<<endl;
2998
    int count = 0;
6,030✔
2999
    for (;;) {
14,666✔
3000
      if (compoundOrdername::getQName(key.getNoStripHeader<StringView>()).canonCompare(qname)) {
14,666✔
3001
        if (isNSEC3BackRecord(key, val)) {
8,636✔
3002
          break;
5,732✔
3003
        }
5,732✔
3004
      }
8,636✔
3005

3006
      if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
8,934✔
3007
        // cout <<"XXX Hit *beginning* of zone or database"<<endl;
3008
        // this can happen, must deal with it
3009
        // should now find the last hash of the zone
3010

3011
        if (cursor.lower_bound(co(id + 1), key, val)) {
298!
3012
          // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
3013
          cursor.last(key, val);
298✔
3014
        }
298✔
3015
        else
×
3016
          cursor.prev(key, val);
×
3017

3018
        for (;;) {
1,490✔
3019
          if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
1,490!
3020
            // cout<<"Last record also not part of this zone!"<<endl;
3021
            //  this implies something is wrong in the database, nothing we can do
3022
            return false;
×
3023
          }
×
3024

3025
          if (isNSEC3BackRecord(key, val)) {
1,490✔
3026
            break;
298✔
3027
          }
298✔
3028
          if (cursor.prev(key, val)) {
1,192!
3029
            // hit beginning of database, again means something is wrong with it
3030
            return false;
×
3031
          }
×
3032
        }
1,192✔
3033
        before = co.getQName(key.getNoStripHeader<StringView>());
298✔
3034
        {
298✔
3035
          LMDBResourceRecord lrr;
298✔
3036
          if (deserializeFromBuffer(val.get<StringView>(), lrr)) {
298!
3037
            unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
298✔
3038
          }
298✔
3039
        }
298✔
3040
        // cout <<"Should still find 'after'!"<<endl;
3041
        // for 'after', we need to find the first hash of this zone
3042

3043
        return getAfterForwardFromStart(cursor, key, val, id, after);
298✔
3044
      }
298✔
3045
      ++count;
8,636✔
3046
    }
8,636✔
3047
    before = co.getQName(key.getNoStripHeader<StringView>());
5,732✔
3048
    {
5,732✔
3049
      LMDBResourceRecord lrr;
5,732✔
3050
      if (deserializeFromBuffer(val.get<StringView>(), lrr)) {
5,732!
3051
        unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + info.zone.operator const DNSName&();
5,732✔
3052
      }
5,732✔
3053
    }
5,732✔
3054
    // cout<<"Went backwards, found "<<before<<endl;
3055
    // return us to starting point
3056
    while (count-- != 0) {
13,238✔
3057
      cursor.next(key, val);
7,506✔
3058
    }
7,506✔
3059
  }
5,732✔
3060
  //  cout<<"Now going forward"<<endl;
3061
  if (getAfterForward(cursor, key, val, id, after)) {
11,716✔
3062
    return true;
10,432✔
3063
  }
10,432✔
3064
  // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
3065
  // Reset cursor position and retry
3066
  return getAfterForwardFromStart(cursor, key, val, id, after);
1,284✔
3067
}
11,716✔
3068

3069
// Return whether the given entry is an authoritative record, ignoring empty
3070
// non terminal records.
3071
bool LMDBBackend::isValidAuthRecord(const MDBOutVal& key, const MDBOutVal& val)
3072
{
10,292✔
3073
  QType qtype = compoundOrdername::getQType(key.getNoStripHeader<string_view>()).getCode();
10,292✔
3074
  switch (qtype) {
10,292✔
3075
  case QType::ENT:
2,113✔
3076
    return false;
2,113✔
3077
  case QType::NS:
303✔
3078
    return true;
303✔
3079
  default:
7,876✔
3080
    return peekAtAuth(val.get<string_view>());
7,876✔
3081
  }
10,292✔
3082
}
10,292✔
3083

3084
bool LMDBBackend::getBeforeAndAfterNames(domainid_t domainId, const ZoneName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
3085
{
5,268✔
3086
  ZoneName zonename = zonenameU.makeLowerCase();
5,268✔
3087
  //  cout << __PRETTY_FUNCTION__<< ": "<<domainId <<", "<<zonename << ", '"<<qname<<"'"<<endl;
3088

3089
  compoundOrdername co;
5,268✔
3090
  auto txn = getRecordsROTransaction(domainId);
5,268✔
3091

3092
  auto cursor = txn->txn->getCursor(txn->db->rdbi);
5,268✔
3093
  MDBOutVal key, val;
5,268✔
3094

3095
  DNSName qname2 = qname.makeRelative(zonename);
5,268✔
3096
  string matchkey = co(domainId, qname2);
5,268✔
3097
  // cout<<"Lower_bound for "<<qname2<<endl;
3098
  if (cursor.lower_bound(matchkey, key, val)) {
5,268✔
3099
    // cout << "Hit end of database, bummer"<<endl;
3100
    cursor.last(key, val);
359✔
3101
    if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId) {
359!
3102
      before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
359✔
3103
      after = zonename.operator const DNSName&();
359✔
3104
    }
359✔
3105
    // else
3106
    // cout << "We were at end of database, but this zone is not there?!"<<endl;
3107
    return true;
359✔
3108
  }
359✔
3109
  // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<compoundOrdername::getDomainID(key.get<string_view>())<< endl;
3110

3111
  if (compoundOrdername::getQType(key.getNoStripHeader<string_view>()).getCode() != 0 && compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && compoundOrdername::getQName(key.getNoStripHeader<string_view>()) == qname2) { // don't match ENTs
4,909✔
3112
    // cout << "Had an exact match!"<<endl;
3113
    before = qname; // i.e. qname2 + zonename.operator const DNSName&();
1,915✔
3114
    int rc;
1,915✔
3115
    for (;;) {
5,173✔
3116
      rc = cursor.next(key, val);
5,173✔
3117
      if (rc)
5,173✔
3118
        break;
39✔
3119

3120
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) == domainId && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0) {
5,134!
3121
        continue;
2,521✔
3122
      }
2,521✔
3123
      if (isValidAuthRecord(key, val)) {
2,613✔
3124
        break;
1,876✔
3125
      }
1,876✔
3126
    }
2,613✔
3127
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
1,915!
3128
      // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
3129
      after = zonename.operator const DNSName&();
39✔
3130
      return false;
39✔
3131
    }
39✔
3132
    after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
1,876✔
3133
    return true;
1,876✔
3134
  }
1,915✔
3135

3136
  if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
2,994✔
3137
    // cout << "Ended up in next zone, 'after' is zonename" <<endl;
3138
    after = zonename.operator const DNSName&();
18✔
3139
    // cout << "Now hunting for previous" << endl;
3140
    int rc;
18✔
3141
    for (;;) {
18✔
3142
      rc = cursor.prev(key, val);
18✔
3143
      if (rc) {
18!
3144
        // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
3145
        return false;
×
3146
      }
×
3147

3148
      if (compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
18!
3149
        // cout<<"Reversed into zone, but found wrong zone id " << compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) << " != "<<domainId<<endl;
3150
        // "this can't happen"
3151
        return false;
×
3152
      }
×
3153
      if (isValidAuthRecord(key, val)) {
18!
3154
        break;
18✔
3155
      }
18✔
3156
    }
18✔
3157

3158
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
18✔
3159
    // cout<<"Found: "<< before<<endl;
3160
    return true;
18✔
3161
  }
18✔
3162

3163
  // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
3164

3165
  int skips = 0;
2,976✔
3166
  for (;;) {
3,909✔
3167
    if (isValidAuthRecord(key, val)) {
3,909✔
3168
      after = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
2,976✔
3169
      // Note: change isValidAuthRecord to also return the LMDBResourceRecord if
3170
      // uncommenting these debug messages...
3171
      // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
3172
      // cout << makeHexDump(val.get<string>()) << endl;
3173
      break;
2,976✔
3174
    }
2,976✔
3175
    // cout <<"  oops, " << co.getQName(key.getNoStripHeader<string_view>()) << " was not auth "<<lrr.auth<< " type=" << lrr.qtype.toString()<<" or NS, so need to skip ahead a bit more" << endl;
3176
    int rc = cursor.next(key, val);
933✔
3177
    if (!rc)
933!
3178
      ++skips;
933✔
3179
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
933!
3180
      // cout << "  oops, hit end of database or zone. This means after is apex" <<endl;
3181
      after = zonename.operator const DNSName&();
×
3182
      break;
×
3183
    }
×
3184
  }
933✔
3185
  // go back to where we were
3186
  while (skips--)
3,909✔
3187
    cursor.prev(key, val);
933✔
3188

3189
  for (;;) {
3,752✔
3190
    int rc = cursor.prev(key, val);
3,752✔
3191
    if (rc != 0 || compoundOrdername::getDomainID(key.getNoStripHeader<string_view>()) != domainId) {
3,752!
3192
      // XX I don't think this case can happen
3193
      // cout << "We hit the beginning of the zone or database.. now what" << endl;
3194
      return false;
×
3195
    }
×
3196
    before = compoundOrdername::getQName(key.getNoStripHeader<string_view>()) + zonename.operator const DNSName&();
3,752✔
3197
    // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
3198
    if (isValidAuthRecord(key, val)) {
3,752✔
3199
      break;
2,975✔
3200
    }
2,975✔
3201
    // cout << "Oops, that was wrong, go back one more"<<endl;
3202
  }
3,752✔
3203

3204
  return true;
2,976✔
3205
}
2,976✔
3206

3207
bool LMDBBackend::updateDNSSECOrderNameAndAuth(domainid_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype, bool isNsec3)
3208
{
126,544✔
3209
  //  cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
3210
  shared_ptr<RecordsRWTransaction> txn;
126,544✔
3211
  bool needCommit = false;
126,544✔
3212
  if (d_rwtxn && d_transactiondomainid == domain_id) {
126,544!
3213
    txn = d_rwtxn;
126,544✔
3214
    //    cout<<"Reusing open transaction"<<endl;
3215
  }
126,544✔
3216
  else {
×
3217
    //    cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
3218
    txn = getRecordsRWTransaction(domain_id);
×
3219
    needCommit = true;
×
3220
  }
×
3221

3222
  DomainInfo info;
126,544✔
3223
  if (!findDomain(domain_id, info)) {
126,544!
3224
    //    cout<<"Could not find domain_id "<<domain_id <<endl;
3225
    return false;
×
3226
  }
×
3227

3228
  DNSName rel = qname.makeRelative(info.zone);
126,544✔
3229

3230
  compoundOrdername co;
126,544✔
3231
  string matchkey = co(domain_id, rel);
126,544✔
3232

3233
  auto cursor = txn->txn->getCursor(txn->db->rdbi);
126,544✔
3234
  MDBOutVal key, val;
126,544✔
3235
  if (cursor.prefix(matchkey, key, val) != 0) {
126,544!
3236
    // cout << "Could not find anything"<<endl;
3237
    return false;
×
3238
  }
×
3239

3240
  bool hadOrderName{false};
126,544✔
3241
  bool hasOrderName = !ordername.empty() && isNsec3;
126,544✔
3242
  bool keepNSEC3 = hasOrderName;
126,544✔
3243

3244
  do {
130,907✔
3245
    if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
130,907✔
3246
      hadOrderName = true;
1,168✔
3247
      continue;
1,168✔
3248
    }
1,168✔
3249

3250
    vector<LMDBResourceRecord> lrrs;
129,739✔
3251
    deserializeMultipleFromBuffer(val.get<StringView>(), lrrs);
129,739✔
3252
    bool changed = false;
129,739✔
3253
    vector<LMDBResourceRecord> newRRs;
129,739✔
3254
    newRRs.reserve(lrrs.size());
129,739✔
3255
    for (auto& lrr : lrrs) {
181,696✔
3256
      hadOrderName |= lrr.hasOrderName;
181,696✔
3257
      lrr.qtype = compoundOrdername::getQType(key.getNoStripHeader<StringView>());
181,696✔
3258
      bool isDifferentQType = qtype != QType::ANY && QType(qtype) != lrr.qtype;
181,696✔
3259
      // If there is at least one entry for that qname, with a different qtype
3260
      // than the one we are working for, known to be associated to an NSEC3
3261
      // record, then we should NOT delete it.
3262
      if (!keepNSEC3) {
181,696✔
3263
        keepNSEC3 = lrr.hasOrderName && isDifferentQType;
77,387✔
3264
      }
77,387✔
3265

3266
      if (!isDifferentQType && (lrr.hasOrderName != hasOrderName || lrr.auth != auth)) {
181,696✔
3267
        lrr.auth = auth;
83,920✔
3268
        lrr.hasOrderName = hasOrderName;
83,920✔
3269
        changed = true;
83,920✔
3270
      }
83,920✔
3271
      newRRs.push_back(std::move(lrr));
181,696✔
3272
    }
181,696✔
3273
    if (changed) {
129,739✔
3274
      std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
83,249✔
3275
      serializeToBuffer(ser, newRRs);
83,249✔
3276
      cursor.put_header_in_place(key, ser);
83,249✔
3277
    }
83,249✔
3278
  } while (cursor.next(key, val) == 0);
130,907✔
3279

3280
  if (!keepNSEC3) {
126,544✔
3281
    // NSEC3 link to be removed: need to remove an existing pair, if any
3282
    if (hadOrderName) {
44,057✔
3283
      deleteNSEC3RecordPair(txn, domain_id, rel);
295✔
3284
    }
295✔
3285
  }
44,057✔
3286
  else if (hasOrderName) {
82,487✔
3287
    // NSEC3 link to be added or updated
3288
    writeNSEC3RecordPair(txn, domain_id, rel, ordername);
82,277✔
3289
  }
82,277✔
3290

3291
  if (needCommit)
126,544!
3292
    txn->txn->commit();
×
3293
  return false;
126,544✔
3294
}
126,544✔
3295

3296
bool LMDBBackend::updateEmptyNonTerminals(domainid_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
3297
{
1,731✔
3298
  // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
3299

3300
  bool needCommit = false;
1,731✔
3301
  shared_ptr<RecordsRWTransaction> txn;
1,731✔
3302
  if (d_rwtxn && d_transactiondomainid == domain_id) {
1,731!
3303
    txn = d_rwtxn;
1,731✔
3304
    //    cout<<"Reusing open transaction"<<endl;
3305
  }
1,731✔
3306
  else {
×
3307
    //    cout<<"Making a new RW txn for delete domain"<<endl;
3308
    txn = getRecordsRWTransaction(domain_id);
×
3309
    needCommit = true;
×
3310
  }
×
3311

3312
  DomainInfo info;
1,731✔
3313
  if (!findDomain(domain_id, info)) {
1,731!
3314
    // cout <<"No such domain with id "<<domain_id<<endl;
3315
    return false;
×
3316
  }
×
3317

3318
  // if remove is set, all ENTs should be removed
3319
  compoundOrdername order;
1,731✔
3320
  if (remove) {
1,731!
3321
    string match = order(domain_id);
×
3322
    // We can not simply blindly delete all ENT records the way
3323
    // deleteDomainRecords() would do, as we also need to remove
3324
    // NSEC3 records for these ENT, if any.
3325
    {
×
3326
      auto cursor = txn->txn->getCursor(txn->db->rdbi);
×
3327
      MDBOutVal key{};
×
3328
      MDBOutVal val{};
×
3329
      std::vector<DNSName> names;
×
3330

3331
      while (cursor.prefix(match, key, val) == 0) {
×
3332
        do {
×
3333
          if (compoundOrdername::getQType(key.getNoStripHeader<StringView>()) == QType::ENT) {
×
3334
            // We need to remember the name of the records we're deleting, so
3335
            // as to remove the matching NSEC3 records, if any.
3336
            // (we can't invoke deleteNSEC3RecordPair here as doing this
3337
            // could make our cursor invalid)
3338
            if (peekAtHasOrderName(val.get<string_view>())) {
×
3339
              DNSName qname = compoundOrdername::getQName(key.getNoStripHeader<StringView>());
×
3340
              names.emplace_back(qname);
×
3341
            }
×
3342
            cursor.del(key);
×
3343
            // Do not risk accumulating too many names. Better iterate
3344
            // multiple times, there won't be any ENT left eventually.
3345
            if (names.size() >= 100) {
×
3346
              break;
×
3347
            }
×
3348
          }
×
3349
        } while (cursor.next(key, val) == 0);
×
3350
        for (const auto& qname : names) {
×
3351
          deleteNSEC3RecordPair(txn, domain_id, qname);
×
3352
        }
×
3353
        names.clear();
×
3354
      }
×
3355
    }
×
3356
  }
×
3357
  else {
1,731✔
3358
    for (auto name : erase) {
1,731✔
3359
      // cout <<" -"<<name<<endl;
3360
      name.makeUsRelative(info.zone);
1,666✔
3361
      std::string match = order(domain_id, name, QType::ENT);
1,666✔
3362
      MDBOutVal val{};
1,666✔
3363
      if (txn->txn->get(txn->db->rdbi, match, val) == 0) {
1,666✔
3364
        bool hadOrderName = peekAtHasOrderName(val.get<string_view>());
160✔
3365
        txn->txn->del(txn->db->rdbi, match);
160✔
3366
        if (hadOrderName) {
160✔
3367
          deleteNSEC3RecordPair(txn, domain_id, name);
64✔
3368
        }
64✔
3369
      }
160✔
3370
    }
1,666✔
3371
  }
1,731✔
3372
  for (const auto& name : insert) {
1,742✔
3373
    LMDBResourceRecord lrr;
526✔
3374
    lrr.qname = name.makeRelative(info.zone);
526✔
3375
    lrr.ttl = 0;
526✔
3376
    lrr.auth = true;
526✔
3377
    std::string ser = MDBRWTransactionImpl::stringWithEmptyHeader();
526✔
3378
    serializeToBuffer(ser, lrr);
526✔
3379
    txn->txn->put_header_in_place(txn->db->rdbi, order(domain_id, lrr.qname, QType::ENT), ser);
526✔
3380
    // cout <<" +"<<name<<endl;
3381
  }
526✔
3382
  if (needCommit) {
1,731!
3383
    txn->txn->commit();
×
3384
  }
×
3385
  return false;
1,731✔
3386
}
1,731✔
3387

3388
/* TSIG */
3389
bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
3390
{
109✔
3391
  auto txn = d_ttsig->getROTransaction();
109✔
3392
  LmdbIdVec ids;
109✔
3393
  txn.get_multi<0>(name, ids);
109✔
3394

3395
  TSIGKey key;
109✔
3396
  for (auto id : ids) {
109✔
3397
    if (txn.get(id, key)) {
93!
3398
      if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
93!
3399
        algorithm = DNSName(key.algorithm);
93✔
3400
        content = key.key;
93✔
3401
      }
93✔
3402
    }
93✔
3403
  }
93✔
3404

3405
  return true;
109✔
3406
}
109✔
3407

3408
// this deletes an old key if it has the same algorithm
3409
bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
3410
{
27✔
3411
  auto txn = d_ttsig->getRWTransaction();
27✔
3412

3413
  LmdbIdVec ids;
27✔
3414
  txn.get_multi<0>(name, ids);
27✔
3415

3416
  TSIGKey key;
27✔
3417
  for (auto id : ids) {
27✔
3418
    if (txn.get(id, key)) {
2!
3419
      if (key.algorithm == algorithm) {
2✔
3420
        txn.del(id);
1✔
3421
      }
1✔
3422
    }
2✔
3423
  }
2✔
3424

3425
  TSIGKey tk;
27✔
3426
  tk.name = name;
27✔
3427
  tk.algorithm = algorithm;
27✔
3428
  tk.key = content;
27✔
3429

3430
  txn.put(tk, 0, d_random_ids, name.hash());
27✔
3431
  txn.commit();
27✔
3432

3433
  return true;
27✔
3434
}
27✔
3435
bool LMDBBackend::deleteTSIGKey(const DNSName& name)
3436
{
2✔
3437
  auto txn = d_ttsig->getRWTransaction();
2✔
3438

3439
  LmdbIdVec ids;
2✔
3440
  txn.get_multi<0>(name, ids);
2✔
3441

3442
  TSIGKey key;
2✔
3443

3444
  for (auto id : ids) {
2✔
3445
    if (txn.get(id, key)) {
2!
3446
      txn.del(id);
2✔
3447
    }
2✔
3448
  }
2✔
3449
  txn.commit();
2✔
3450
  return true;
2✔
3451
}
2✔
3452
bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
3453
{
1✔
3454
  auto txn = d_ttsig->getROTransaction();
1✔
3455

3456
  keys.clear();
1✔
3457
  // In a perfect world, we would simply iterate over txn and add every
3458
  // item to the returned vector:
3459
  //   for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
3460
  //     keys.push_back(*iter);
3461
  //   }
3462
  // But databases converted from older (< 5) schemas _may_ have multiple
3463
  // entries for the same TSIG key name and algorithm, something which is not
3464
  // allowed in the v5 database schema. These extra entries will not be found
3465
  // by get_multi<> during regular operations, and would only appear in the
3466
  // results of this method.
3467
  // In order to prevent this, we first only gather the list of key names, and
3468
  // in a second step, query for them using a similar logic as getTSIGKey().
3469
  // Unfortunately, there does not seem to be a way to know if the database had
3470
  // been created using the v5 schema (not converted), in which case we could
3471
  // use the above, simpler logic.
3472
  std::unordered_set<DNSName> keynames;
1✔
3473
  for (const auto& iter : txn) {
10✔
3474
    keynames.insert(iter.name);
10✔
3475
  }
10✔
3476
  for (const auto& iter : keynames) {
9✔
3477
    LmdbIdVec ids;
9✔
3478
    txn.get_multi<0>(iter, ids);
9✔
3479
    for (auto key_id : ids) {
10✔
3480
      TSIGKey key;
10✔
3481
      if (txn.get(key_id, key)) {
10!
3482
        keys.push_back(std::move(key));
10✔
3483
      }
10✔
3484
    }
10✔
3485
  }
9✔
3486
  return true;
1✔
3487
}
1✔
3488

3489
string LMDBBackend::directBackendCmd(const string& query)
3490
{
372✔
3491
  ostringstream ret, usage;
372✔
3492

3493
  usage << "info                               show some information about the database" << endl;
372✔
3494
  usage << "index check domains                check zone<>ID indexes" << endl;
372✔
3495
  usage << "index refresh domains <ID>         refresh index for zone with this ID" << endl;
372✔
3496
  usage << "index refresh-all domains          refresh index for all zones with disconnected indexes" << endl;
372✔
3497
  vector<string> argv;
372✔
3498
  stringtok(argv, query);
372✔
3499

3500
  if (argv.empty()) {
372!
3501
    return usage.str();
×
3502
  }
×
3503

3504
  string& cmd = argv[0];
372✔
3505

3506
  if (cmd == "help") {
372!
3507
    return usage.str();
×
3508
  }
×
3509

3510
  if (cmd == "info") {
372!
3511
    ret << "shards: " << s_shards << endl;
×
3512
    ret << "schemaversion: " << SCHEMAVERSION << endl;
×
3513

3514
    return ret.str();
×
3515
  }
×
3516

3517
  if (cmd == "index") {
372!
3518
    if (argv.size() < 2) {
×
3519
      return "need an index subcommand\n";
×
3520
    }
×
3521

3522
    string& subcmd = argv[1];
×
3523

3524
    if (subcmd == "check" || subcmd == "refresh-all") {
×
3525
      bool refresh = false;
×
3526

3527
      if (subcmd == "refresh-all") {
×
3528
        refresh = true;
×
3529
      }
×
3530

3531
      if (argv.size() < 3) {
×
3532
        return "need an index name\n";
×
3533
      }
×
3534

3535
      if (argv[2] != "domains") {
×
3536
        return "can only check the domains index\n";
×
3537
      }
×
3538

3539
      vector<uint32_t> refreshQueue;
×
3540

3541
      {
×
3542
        auto txn = d_tdomains->getROTransaction();
×
3543

3544
        for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
×
3545
          DomainInfo di = *iter;
×
3546

3547
          auto id = iter.getID();
×
3548

3549
          LmdbIdVec ids;
×
3550
          txn.get_multi<0>(di.zone, ids);
×
3551

3552
          if (ids.size() != 1) {
×
3553
            ret << "ID->zone index has " << id << "->" << di.zone << ", ";
×
3554

3555
            if (ids.empty()) {
×
3556
              ret << "zone->ID index has no entry for " << di.zone << endl;
×
3557
              if (refresh) {
×
3558
                refreshQueue.push_back(id);
×
3559
              }
×
3560
              else {
×
3561
                ret << "  suggested remedy: index refresh domains " << id << endl;
×
3562
              }
×
3563
            }
×
3564
            else {
×
3565
              // ids.size() > 1
3566
              ret << "zone->ID index has multiple entries for " << di.zone << ": ";
×
3567
              for (auto id_ : ids) {
×
3568
                ret << id_ << " ";
×
3569
              }
×
3570
              ret << endl;
×
3571
            }
×
3572
          }
×
3573
        }
×
3574
      }
×
3575

3576
      if (refresh) {
×
3577
        for (const auto& id : refreshQueue) {
×
3578
          if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3579
            ret << "refreshed " << id << endl;
×
3580
          }
×
3581
          else {
×
3582
            ret << "failed to refresh " << id << endl;
×
3583
          }
×
3584
        }
×
3585
      }
×
3586
      return ret.str();
×
3587
    }
×
3588
    if (subcmd == "refresh") {
×
3589
      // index refresh domains 12345
3590
      if (argv.size() < 4) {
×
3591
        return "usage: index refresh domains <ID>\n";
×
3592
      }
×
3593

3594
      if (argv[2] != "domains") {
×
3595
        return "can only refresh in the domains index\n";
×
3596
      }
×
3597

3598
      domainid_t id = 0; // NOLINT(readability-identifier-length)
×
3599

3600
      try {
×
3601
        pdns::checked_stoi_into(id, argv[3]);
×
3602
      }
×
3603
      catch (const std::logic_error&) {
×
3604
        return "ill-formed ID\n";
×
3605
      }
×
3606

3607
      if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
×
3608
        ret << "refreshed" << endl;
×
3609
      }
×
3610
      else {
×
3611
        ret << "failed" << endl;
×
3612
      }
×
3613
      return ret.str();
×
3614
    }
×
3615
  }
×
3616

3617
  if (cmd == "list") {
372!
3618
    return directBackendCmd_list(argv);
372✔
3619
  }
372✔
3620

3621
  return "unknown lmdbbackend command\n";
×
3622
}
372✔
3623

3624
string LMDBBackend::directBackendCmd_list(std::vector<string>& argv)
3625
{
372✔
3626
  ostringstream ret;
372✔
3627

3628
  if (argv.size() < 2) {
372!
3629
    ret << "need a domain name" << endl;
×
3630
    return ret.str();
×
3631
  }
×
3632
  ZoneName zone(argv[1]);
372✔
3633

3634
  DomainInfo info;
372✔
3635
  if (!getDomainInfo(zone, info, false)) {
372!
3636
    ret << "zone " << zone << " not found" << endl;
×
3637
    return ret.str();
×
3638
  }
×
3639
  list(zone, info.id, true);
372✔
3640
  {
372✔
3641
    DNSName basename;
372✔
3642
    std::string_view key;
372✔
3643
    while (getInternal(basename, key)) {
12,026✔
3644
      const auto& lrr = d_lookupstate.rrset.at(d_lookupstate.rrsetpos);
11,654✔
3645
      DNSName qname = basename + d_lookupstate.domain.operator const DNSName&();
11,654✔
3646
      QType qtype = compoundOrdername::getQType(key);
11,654✔
3647
      DNSRecord record;
11,654✔
3648
      record.setContent(deserializeContentZR(qtype, qname, lrr.content));
11,654✔
3649
      std::string content = record.getContent()->getZoneRepresentation(true);
11,654✔
3650
      // Mimic the `prio' field in SQL
3651
      int prio{0};
11,654✔
3652
      if (qtype == QType::MX || qtype == QType::SRV) {
11,654!
3653
        if (auto pos = content.find_first_not_of("0123456789"); pos != std::string::npos) {
744!
3654
          pdns::checked_stoi_into(prio, content.substr(0, pos));
744✔
3655
          content.erase(0, pos);
744✔
3656
          boost::trim_left(content);
744✔
3657
        }
744✔
3658
      }
744✔
3659
      ret << qname << "\t" << qtype.toString() << "\t" << prio << "\t" << content << "\t" << lrr.ttl;
11,654✔
3660
      if (lrr.hasOrderName) {
11,654✔
3661
        ret << "\t'";
4,574✔
3662
        // The get() logic skips the NSEC3 records containing the information
3663
        // we need, and there is no way to nest lookups. But the NSEC3
3664
        // record has a unique key we can compute, so we can fetch it
3665
        // without disturbing the current get() cursor.
3666
        compoundOrdername order;
4,574✔
3667
        MDBOutVal val{};
4,574✔
3668
        if (d_rotxn->txn->get(d_rotxn->db->rdbi, order(info.id, basename, QType::NSEC3), val) == 0) {
4,574!
3669
          LMDBResourceRecord nsec3rr;
4,574✔
3670
          if (deserializeFromBuffer(val.get<string_view>(), nsec3rr)) {
4,574!
3671
            DNSName ordername(nsec3rr.content.c_str(), nsec3rr.content.size(), 0, false);
4,574✔
3672
            ret << ordername;
4,574✔
3673
          }
4,574✔
3674
          else {
×
3675
            ret << "TRUNCATED";
×
3676
          }
×
3677
        }
4,574✔
3678
        ret << "'\t";
4,574✔
3679
        ret << static_cast<int>(lrr.auth);
4,574✔
3680
      }
4,574✔
3681
      ret << std::endl;
11,654✔
3682
    }
11,654✔
3683
  }
372✔
3684
  return ret.str();
372✔
3685
}
372✔
3686

3687
bool LMDBBackend::hasCreatedLocalFiles() const
3688
{
1,756✔
3689
  // Since the lmdb file creation counter is global, if multiple LMDB backends
3690
  // are used, they may end up all reporting having created files even if
3691
  // not all of them did.
3692
  // But since this information is for the sake of pdnsutil, this is not
3693
  // really a problem.
3694
  // However, there is a false positive if we make new databases (dbis) inside
3695
  // existing LMDB files on disk. This is not easy to avoid.
3696
  return MDBDbi::d_creationCount != 0;
1,756✔
3697
}
1,756✔
3698

3699
// Hook for rectifyZone operation.
3700
// Before the operation starts, we forcibly remove all NSEC3 records from the
3701
// domain, since logic flaws in older versions may have left us with dangling
3702
// records. The appropriate records will be regenerated with
3703
// updateDNSSECOrderNameAndAuth() calls anyway.
3704
void LMDBBackend::rectifyZoneHook(domainid_t domain_id, bool before) const
3705
{
740✔
3706
  if (!before) {
740✔
3707
    return;
370✔
3708
  }
370✔
3709

3710
  if (!d_rwtxn) {
370!
3711
    throw DBException("rectifyZoneHook invoked outside of a transaction");
×
3712
  }
×
3713

3714
  compoundOrdername order;
370✔
3715
  LMDBBackend::deleteDomainRecords(*d_rwtxn, order(domain_id), QType::NSEC3);
370✔
3716
}
370✔
3717

3718
void LMDBBackend::flush()
3719
{
×
3720
  if (d_write_notification_update) {
×
3721
    return; // no data needs to be synchronized
×
3722
  }
×
3723

3724
  // We flush in chunks of 10 domains, in order not to keep the serial number
3725
  // cache locked for too long.
3726
  while (true) {
×
3727
    unsigned int done = 0;
×
3728
    auto container = s_transient_domain_info.write_lock();
×
3729
    for (; done < 10; ++done) {
×
3730
      domainid_t domid{};
×
3731
      TransientDomainInfo tdi;
×
3732
      if (!container->pop(domid, tdi)) {
×
3733
        break;
×
3734
      }
×
3735
      DomainInfo info;
×
3736
      if (findDomain(domid, info)) {
×
3737
        info.notified_serial = tdi.notified_serial;
×
3738
        info.last_check = tdi.last_check;
×
3739
        // If the DomainInfo table is split, only update the extra table.
3740
        if (d_split_domains_table) {
×
3741
          writeTransientDomainInfo(info);
×
3742
        }
×
3743
        else {
×
3744
          auto txn = d_tdomains->getRWTransaction();
×
3745
          txn.put(info, info.id);
×
3746
          txn.commit();
×
3747
        }
×
3748
      }
×
3749
      else {
×
3750
        // Domain has been removed. This should not happen because deletion
3751
        // is supposed to take care of removing the entry here too.
3752
        // Is it worth logging something here?
3753
      }
×
3754
    }
×
3755
    if (done == 0) {
×
3756
      break; // no more work to do!
×
3757
    }
×
3758
  }
×
3759
}
×
3760

3761
class LMDBFactory : public BackendFactory
3762
{
3763
public:
3764
  LMDBFactory() :
3765
    BackendFactory("lmdb") {}
6,011✔
3766
  void declareArguments(const string& suffix = "") override
3767
  {
2,562✔
3768
    declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
2,562✔
3769
    declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
2,562✔
3770
    // there just is no room for more on 32 bit
3771
    declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
2,562✔
3772
    declare(suffix, "schema-version", "Maximum allowed schema version to run on this DB. If a lower version is found, auto update is performed", std::to_string(SCHEMAVERSION));
2,562✔
3773
    declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
2,562✔
3774
    declare(suffix, "map-size", "main LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
2,562✔
3775
    declare(suffix, "shards-map-size", "shard LMDB map size in megabytes, zero to use the same size as main", "0");
2,562✔
3776
    declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
2,562✔
3777
    declare(suffix, "write-notification-update", "Update domain table upon notification", "yes");
2,562✔
3778
    declare(suffix, "split-domains-table", "Use a split domain table to reduce I/O load after XFR notifications", "no");
2,562✔
3779
    declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
2,562✔
3780
  }
2,562✔
3781
  DNSBackend* make(const string& suffix = "") override
3782
  {
4,579✔
3783
    return new LMDBBackend(suffix);
4,579✔
3784
  }
4,579✔
3785
};
3786

3787
/* THIRD PART */
3788

3789
class LMDBLoader
3790
{
3791
public:
3792
  LMDBLoader()
3793
  {
6,011✔
3794
    BackendMakers().report(std::make_unique<LMDBFactory>());
6,011✔
3795
    // If this module is not loaded dynamically at runtime, this code runs
3796
    // as part of a global constructor, before the structured logger has a
3797
    // chance to be set up, so fallback to simple logging.
3798
    g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
6,011✔
3799
#ifndef REPRODUCIBLE
6,011✔
3800
          << " (" __DATE__ " " __TIME__ ")"
6,011✔
3801
#endif
6,011✔
3802
          << " reporting" << endl;
6,011✔
3803
  }
6,011✔
3804
};
3805

3806
static LMDBLoader randomLoader;
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