]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/lmdbbackend/lmdbbackend.cc
Merge pull request #13649 from chbruyand/auth-delay-notifications
[thirdparty/pdns.git] / 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 "ext/lmdb-safe/lmdb-safe.hh"
24 #include <lmdb.h>
25 #include <stdexcept>
26 #include <utility>
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 #include "pdns/utility.hh"
31 #include "pdns/dnsbackend.hh"
32 #include "pdns/dns.hh"
33 #include "pdns/dnspacket.hh"
34 #include "pdns/base32.hh"
35 #include "pdns/dnssecinfra.hh"
36 #include "pdns/pdnsexception.hh"
37 #include "pdns/logger.hh"
38 #include "pdns/version.hh"
39 #include "pdns/arguments.hh"
40 #include "pdns/lock.hh"
41 #include "pdns/uuid-utils.hh"
42 #include <boost/archive/binary_oarchive.hpp>
43 #include <boost/archive/binary_iarchive.hpp>
44 #include <boost/serialization/vector.hpp>
45 #include <boost/serialization/string.hpp>
46 #include <boost/serialization/utility.hpp>
47 #include <boost/uuid/uuid_serialize.hpp>
48
49 #include <boost/iostreams/device/back_inserter.hpp>
50
51 #ifdef HAVE_SYSTEMD
52 #include <systemd/sd-daemon.h>
53 #endif
54
55 #include <stdio.h>
56 #include <unistd.h>
57
58 #include "lmdbbackend.hh"
59
60 #define SCHEMAVERSION 5
61
62 // List the class version here. Default is 0
63 BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB, 1)
64 BOOST_CLASS_VERSION(DomainInfo, 1)
65
66 static bool s_first = true;
67 static int s_shards = 0;
68 static std::mutex s_lmdbStartupLock;
69
70 std::pair<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string& filename)
71 {
72 // cerr << "getting schema version for path " << filename << endl;
73
74 uint32_t schemaversion;
75
76 int rc;
77 MDB_env* env = nullptr;
78
79 if ((rc = mdb_env_create(&env)) != 0) {
80 throw std::runtime_error("mdb_env_create failed");
81 }
82
83 if ((rc = mdb_env_set_mapsize(env, 0)) != 0) {
84 throw std::runtime_error("mdb_env_set_mapsize failed");
85 }
86
87 if ((rc = mdb_env_set_maxdbs(env, 20)) != 0) { // we need 17: 1 {"pdns"} + 4 {"domains", "keydata", "tsig", "metadata"} * 2 {v4, v5} * 2 {main, index in _0}
88 mdb_env_close(env);
89 throw std::runtime_error("mdb_env_set_maxdbs failed");
90 }
91
92 if ((rc = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR | MDB_RDONLY, 0600)) != 0) {
93 if (rc == ENOENT) {
94 // we don't have a database yet! report schema 0, with 0 shards
95 return {0u, 0u};
96 }
97 mdb_env_close(env);
98 throw std::runtime_error("mdb_env_open failed");
99 }
100
101 MDB_txn* txn = nullptr;
102
103 if ((rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn)) != 0) {
104 mdb_env_close(env);
105 throw std::runtime_error("mdb_txn_begin failed");
106 }
107
108 MDB_dbi dbi;
109
110 if ((rc = mdb_dbi_open(txn, "pdns", 0, &dbi)) != 0) {
111 if (rc == MDB_NOTFOUND) {
112 // this means nothing has been inited yet
113 // we pretend this means 5
114 mdb_txn_abort(txn);
115 mdb_env_close(env);
116 return {5u, 0u};
117 }
118 mdb_txn_abort(txn);
119 mdb_env_close(env);
120 throw std::runtime_error("mdb_dbi_open failed");
121 }
122
123 MDB_val key, data;
124
125 key.mv_data = (char*)"schemaversion";
126 key.mv_size = strlen((char*)key.mv_data);
127
128 if ((rc = mdb_get(txn, dbi, &key, &data)) != 0) {
129 if (rc == MDB_NOTFOUND) {
130 // this means nothing has been inited yet
131 // we pretend this means 5
132 mdb_txn_abort(txn);
133 mdb_env_close(env);
134 return {5u, 0u};
135 }
136
137 throw std::runtime_error("mdb_get pdns.schemaversion failed");
138 }
139
140 if (data.mv_size == 4) {
141 // schemaversion is < 5 and is stored in 32 bits, in host order
142
143 memcpy(&schemaversion, data.mv_data, data.mv_size);
144 }
145 else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(schemaversion)) {
146 // schemaversion presumably is 5, stored in 32 bits, network order, after the LS header
147
148 // FIXME: get actual header size (including extension blocks) instead of just reading from the back
149 // FIXME: add a test for reading schemaversion and shards (and actual data, later) when there are variably sized headers
150 memcpy(&schemaversion, (char*)data.mv_data + data.mv_size - sizeof(schemaversion), sizeof(schemaversion));
151 schemaversion = ntohl(schemaversion);
152 }
153 else {
154 throw std::runtime_error("pdns.schemaversion had unexpected size");
155 }
156
157 uint32_t shards;
158
159 key.mv_data = (char*)"shards";
160 key.mv_size = strlen((char*)key.mv_data);
161
162 if ((rc = mdb_get(txn, dbi, &key, &data)) != 0) {
163 if (rc == MDB_NOTFOUND) {
164 cerr << "schemaversion was set, but shards was not. Dazed and confused, trying to exit." << endl;
165 mdb_txn_abort(txn);
166 mdb_env_close(env);
167 exit(1);
168 }
169
170 throw std::runtime_error("mdb_get pdns.shards failed");
171 }
172
173 if (data.mv_size == 4) {
174 // 'shards' is stored in 32 bits, in host order
175
176 memcpy(&shards, data.mv_data, data.mv_size);
177 }
178 else if (data.mv_size >= LMDBLS::LS_MIN_HEADER_SIZE + sizeof(shards)) {
179 // FIXME: get actual header size (including extension blocks) instead of just reading from the back
180 memcpy(&shards, (char*)data.mv_data + data.mv_size - sizeof(shards), sizeof(shards));
181 shards = ntohl(shards);
182 }
183 else {
184 throw std::runtime_error("pdns.shards had unexpected size");
185 }
186
187 mdb_txn_abort(txn);
188 mdb_env_close(env);
189
190 return {schemaversion, shards};
191 }
192
193 namespace
194 {
195 // copy sdbi to tdbi, prepending an empty LS header (24 bytes of '\0') to all values
196 void copyDBIAndAddLSHeader(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
197 {
198 // FIXME: clear out target dbi first
199
200 std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
201 int rc;
202
203 MDB_cursor* cur;
204
205 if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
206 throw std::runtime_error("mdb_cursur_open failed");
207 }
208
209 MDB_val key, data;
210
211 rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
212
213 while (rc == 0) {
214 std::string skey(reinterpret_cast<const char*>(key.mv_data), key.mv_size);
215 std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
216
217 std::string stdata = header + sdata;
218
219 // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
220
221 MDB_val tkey;
222 MDB_val tdata;
223
224 tkey.mv_data = const_cast<char*>(skey.c_str());
225 tkey.mv_size = skey.size();
226 tdata.mv_data = const_cast<char*>(stdata.c_str());
227 tdata.mv_size = stdata.size();
228
229 if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
230 throw std::runtime_error("mdb_put failed");
231 }
232
233 rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
234 }
235 if (rc != MDB_NOTFOUND) {
236 cerr << "rc=" << rc << endl;
237 throw std::runtime_error("error while iterating dbi");
238 }
239 }
240
241 // migrated a typed DBI:
242 // 1. change keys (uint32_t) from host to network order
243 // 2. prepend empty LS header to values
244 void copyTypedDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
245 {
246 // FIXME: clear out target dbi first
247
248 std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
249 int rc;
250
251 MDB_cursor* cur;
252
253 if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
254 throw std::runtime_error("mdb_cursur_open failed");
255 }
256
257 MDB_val key, data;
258
259 rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
260
261 while (rc == 0) {
262 // std::string skey((char*) key.mv_data, key.mv_size);
263 std::string sdata(reinterpret_cast<const char*>(data.mv_data), data.mv_size);
264
265 std::string stdata = header + sdata;
266
267 uint32_t id;
268
269 if (key.mv_size != sizeof(uint32_t)) {
270 throw std::runtime_error("got non-uint32_t key in TypedDBI");
271 }
272
273 memcpy(&id, key.mv_data, sizeof(uint32_t));
274
275 id = htonl(id);
276
277 // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
278
279 MDB_val tkey;
280 MDB_val tdata;
281
282 tkey.mv_data = reinterpret_cast<char*>(&id);
283 tkey.mv_size = sizeof(uint32_t);
284 tdata.mv_data = const_cast<char*>(stdata.c_str());
285 tdata.mv_size = stdata.size();
286
287 if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
288 throw std::runtime_error("mdb_put failed");
289 }
290
291 rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
292 }
293 if (rc != MDB_NOTFOUND) {
294 cerr << "rc=" << rc << endl;
295 throw std::runtime_error("error while iterating dbi");
296 }
297 }
298
299 // migrating an index DBI:
300 // newkey = oldkey.len(), oldkey, htonl(oldvalue)
301 // newvalue = empty lsheader
302 void copyIndexDBI(MDB_txn* txn, MDB_dbi sdbi, MDB_dbi tdbi)
303 {
304 // FIXME: clear out target dbi first
305
306 std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
307 int rc;
308
309 MDB_cursor* cur;
310
311 if ((rc = mdb_cursor_open(txn, sdbi, &cur)) != 0) {
312 throw std::runtime_error("mdb_cursur_open failed");
313 }
314
315 MDB_val key, data;
316
317 rc = mdb_cursor_get(cur, &key, &data, MDB_FIRST);
318
319 while (rc == 0) {
320 std::string lenprefix(sizeof(uint16_t), '\0');
321 std::string skey((char*)key.mv_data, key.mv_size);
322
323 uint32_t id;
324
325 if (data.mv_size != sizeof(uint32_t)) {
326 throw std::runtime_error("got non-uint32_t ID value in IndexDBI");
327 }
328
329 memcpy((void*)&id, data.mv_data, sizeof(uint32_t));
330 id = htonl(id);
331
332 uint16_t len = htons(skey.size());
333 memcpy((void*)lenprefix.data(), &len, sizeof(len));
334 std::string stkey = lenprefix + skey + std::string((char*)&id, sizeof(uint32_t));
335
336 MDB_val tkey;
337 MDB_val tdata;
338
339 tkey.mv_data = (char*)stkey.c_str();
340 tkey.mv_size = stkey.size();
341 tdata.mv_data = (char*)header.c_str();
342 tdata.mv_size = header.size();
343
344 if ((rc = mdb_put(txn, tdbi, &tkey, &tdata, 0)) != 0) {
345 throw std::runtime_error("mdb_put failed");
346 }
347
348 rc = mdb_cursor_get(cur, &key, &data, MDB_NEXT);
349 }
350 if (rc != MDB_NOTFOUND) {
351 throw std::runtime_error("error while iterating dbi");
352 }
353 }
354
355 }
356
357 bool LMDBBackend::upgradeToSchemav5(std::string& filename)
358 {
359 int rc;
360
361 auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
362 uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
363 uint32_t shards = currentSchemaVersionAndShards.second;
364
365 if (currentSchemaVersion != 3 && currentSchemaVersion != 4) {
366 throw std::runtime_error("upgrade to v5 requested but current schema is not v3 or v4, stopping");
367 }
368
369 MDB_env* env = nullptr;
370
371 if ((rc = mdb_env_create(&env)) != 0) {
372 throw std::runtime_error("mdb_env_create failed");
373 }
374
375 if ((rc = mdb_env_set_maxdbs(env, 20)) != 0) {
376 mdb_env_close(env);
377 throw std::runtime_error("mdb_env_set_maxdbs failed");
378 }
379
380 if ((rc = mdb_env_open(env, filename.c_str(), MDB_NOSUBDIR, 0600)) != 0) {
381 mdb_env_close(env);
382 throw std::runtime_error("mdb_env_open failed");
383 }
384
385 MDB_txn* txn = nullptr;
386
387 if ((rc = mdb_txn_begin(env, NULL, 0, &txn)) != 0) {
388 mdb_env_close(env);
389 throw std::runtime_error("mdb_txn_begin failed");
390 }
391
392 #ifdef HAVE_SYSTEMD
393 /* A schema migration may take a long time. Extend the startup service timeout to 1 day,
394 * but only if this is beyond the original maximum time of TimeoutStartSec=.
395 */
396 sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
397 #endif
398
399 std::cerr << "migrating shards" << std::endl;
400 for (uint32_t i = 0; i < shards; i++) {
401 string shardfile = filename + "-" + std::to_string(i);
402 if (access(shardfile.c_str(), F_OK) < 0) {
403 if (errno == ENOENT) {
404 // apparently this shard doesn't exist yet, moving on
405 std::cerr << "shard " << shardfile << " not found, continuing" << std::endl;
406 continue;
407 }
408 }
409
410 std::cerr << "migrating shard " << shardfile << std::endl;
411 MDB_env* shenv = nullptr;
412
413 if ((rc = mdb_env_create(&shenv)) != 0) {
414 throw std::runtime_error("mdb_env_create failed");
415 }
416
417 if ((rc = mdb_env_set_maxdbs(shenv, 8)) != 0) {
418 mdb_env_close(env);
419 throw std::runtime_error("mdb_env_set_maxdbs failed");
420 }
421
422 if ((rc = mdb_env_open(shenv, shardfile.c_str(), MDB_NOSUBDIR, 0600)) != 0) {
423 mdb_env_close(env);
424 throw std::runtime_error("mdb_env_open failed");
425 }
426
427 MDB_txn* shtxn = nullptr;
428
429 if ((rc = mdb_txn_begin(shenv, NULL, 0, &shtxn)) != 0) {
430 mdb_env_close(env);
431 throw std::runtime_error("mdb_txn_begin failed");
432 }
433
434 MDB_dbi shdbi;
435
436 if ((rc = mdb_dbi_open(shtxn, "records", 0, &shdbi)) != 0) {
437 if (rc == MDB_NOTFOUND) {
438 mdb_txn_abort(shtxn);
439 mdb_env_close(shenv);
440 continue;
441 }
442 mdb_txn_abort(shtxn);
443 mdb_env_close(shenv);
444 throw std::runtime_error("mdb_dbi_open shard records failed");
445 }
446
447 MDB_dbi shdbi2;
448
449 if ((rc = mdb_dbi_open(shtxn, "records_v5", MDB_CREATE, &shdbi2)) != 0) {
450 mdb_dbi_close(shenv, shdbi);
451 mdb_txn_abort(shtxn);
452 mdb_env_close(shenv);
453 throw std::runtime_error("mdb_dbi_open shard records_v5 failed");
454 }
455
456 try {
457 copyDBIAndAddLSHeader(shtxn, shdbi, shdbi2);
458 }
459 catch (std::exception& e) {
460 mdb_dbi_close(shenv, shdbi2);
461 mdb_dbi_close(shenv, shdbi);
462 mdb_txn_abort(shtxn);
463 mdb_env_close(shenv);
464 throw std::runtime_error("copyDBIAndAddLSHeader failed");
465 }
466
467 cerr << "shard mbd_drop=" << mdb_drop(shtxn, shdbi, 1) << endl;
468 mdb_txn_commit(shtxn);
469 mdb_dbi_close(shenv, shdbi2);
470 mdb_env_close(shenv);
471 }
472
473 std::array<MDB_dbi, 4> fromtypeddbi;
474 std::array<MDB_dbi, 4> totypeddbi;
475
476 int index = 0;
477
478 for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
479 std::cerr << "migrating " << dbname << std::endl;
480 std::string tdbname = dbname + "_v5";
481
482 if ((rc = mdb_dbi_open(txn, dbname.c_str(), 0, &fromtypeddbi[index])) != 0) {
483 mdb_txn_abort(txn);
484 mdb_env_close(env);
485 throw std::runtime_error("mdb_dbi_open typeddbi failed");
486 }
487
488 if ((rc = mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &totypeddbi[index])) != 0) {
489 mdb_dbi_close(env, fromtypeddbi[index]);
490 mdb_txn_abort(txn);
491 mdb_env_close(env);
492 throw std::runtime_error("mdb_dbi_open typeddbi target failed");
493 }
494
495 try {
496 copyTypedDBI(txn, fromtypeddbi[index], totypeddbi[index]);
497 }
498 catch (std::exception& e) {
499 mdb_dbi_close(env, totypeddbi[index]);
500 mdb_dbi_close(env, fromtypeddbi[index]);
501 mdb_txn_abort(txn);
502 mdb_env_close(env);
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;
509
510 index++;
511 }
512
513 std::array<MDB_dbi, 4> fromindexdbi;
514 std::array<MDB_dbi, 4> toindexdbi;
515
516 index = 0;
517
518 for (const std::string dbname : {"domains", "keydata", "tsig", "metadata"}) {
519 std::string fdbname = dbname + "_0";
520 std::cerr << "migrating " << dbname << std::endl;
521 std::string tdbname = dbname + "_v5_0";
522
523 if ((rc = mdb_dbi_open(txn, fdbname.c_str(), 0, &fromindexdbi[index])) != 0) {
524 mdb_txn_abort(txn);
525 mdb_env_close(env);
526 throw std::runtime_error("mdb_dbi_open indexdbi failed");
527 }
528
529 if ((rc = mdb_dbi_open(txn, tdbname.c_str(), MDB_CREATE, &toindexdbi[index])) != 0) {
530 mdb_dbi_close(env, fromindexdbi[index]);
531 mdb_txn_abort(txn);
532 mdb_env_close(env);
533 throw std::runtime_error("mdb_dbi_open indexdbi target failed");
534 }
535
536 try {
537 copyIndexDBI(txn, fromindexdbi[index], toindexdbi[index]);
538 }
539 catch (std::exception& e) {
540 mdb_dbi_close(env, toindexdbi[index]);
541 mdb_dbi_close(env, fromindexdbi[index]);
542 mdb_txn_abort(txn);
543 mdb_env_close(env);
544 throw std::runtime_error("copyIndexDBI failed");
545 }
546
547 // mdb_dbi_close(env, dbi2);
548 // mdb_dbi_close(env, dbi);
549 std::cerr << "migrated " << dbname << std::endl;
550
551 index++;
552 }
553
554 MDB_dbi dbi;
555
556 // finally, migrate the pdns db
557 if ((rc = mdb_dbi_open(txn, "pdns", 0, &dbi)) != 0) {
558 mdb_txn_abort(txn);
559 mdb_env_close(env);
560 throw std::runtime_error("mdb_dbi_open pdns failed");
561 }
562
563 MDB_val key, data;
564
565 std::string header(LMDBLS::LS_MIN_HEADER_SIZE, '\0');
566
567 for (const std::string keyname : {"schemaversion", "shards"}) {
568 cerr << "migrating pdns." << keyname << endl;
569
570 key.mv_data = (char*)keyname.c_str();
571 key.mv_size = keyname.size();
572
573 if ((rc = mdb_get(txn, dbi, &key, &data))) {
574 throw std::runtime_error("mdb_get pdns.shards failed");
575 }
576
577 uint32_t value;
578
579 if (data.mv_size != sizeof(uint32_t)) {
580 throw std::runtime_error("got non-uint32_t key");
581 }
582
583 memcpy((void*)&value, data.mv_data, sizeof(uint32_t));
584
585 value = htonl(value);
586 if (keyname == "schemaversion") {
587 value = htonl(5);
588 }
589
590 std::string sdata((char*)data.mv_data, data.mv_size);
591
592 std::string stdata = header + std::string((char*)&value, sizeof(uint32_t));
593 ;
594
595 MDB_val tdata;
596
597 tdata.mv_data = (char*)stdata.c_str();
598 tdata.mv_size = stdata.size();
599
600 if ((rc = mdb_put(txn, dbi, &key, &tdata, 0)) != 0) {
601 throw std::runtime_error("mdb_put failed");
602 }
603 }
604
605 for (const std::string keyname : {"uuid"}) {
606 cerr << "migrating pdns." << keyname << endl;
607
608 key.mv_data = (char*)keyname.c_str();
609 key.mv_size = keyname.size();
610
611 if ((rc = mdb_get(txn, dbi, &key, &data))) {
612 throw std::runtime_error("mdb_get pdns.shards failed");
613 }
614
615 std::string sdata((char*)data.mv_data, data.mv_size);
616
617 std::string stdata = header + sdata;
618
619 MDB_val tdata;
620
621 tdata.mv_data = (char*)stdata.c_str();
622 tdata.mv_size = stdata.size();
623
624 if ((rc = mdb_put(txn, dbi, &key, &tdata, 0)) != 0) {
625 throw std::runtime_error("mdb_put failed");
626 }
627 }
628
629 for (int i = 0; i < 4; i++) {
630 mdb_drop(txn, fromtypeddbi[i], 1);
631 mdb_drop(txn, fromindexdbi[i], 1);
632 }
633
634 cerr << "txn commit=" << mdb_txn_commit(txn) << endl;
635
636 for (int i = 0; i < 4; i++) {
637 mdb_dbi_close(env, totypeddbi[i]);
638 mdb_dbi_close(env, toindexdbi[i]);
639 }
640 mdb_env_close(env);
641
642 // throw std::runtime_error("migration done");
643 cerr << "migration done" << endl;
644 // exit(1);
645 return true;
646 }
647
648 LMDBBackend::LMDBBackend(const std::string& suffix)
649 {
650 // overlapping domain ids in combination with relative names are a recipe for disaster
651 if (!suffix.empty()) {
652 throw std::runtime_error("LMDB backend does not support multiple instances");
653 }
654
655 setArgPrefix("lmdb" + suffix);
656
657 string syncMode = toLower(getArg("sync-mode"));
658
659 d_random_ids = mustDo("random-ids");
660
661 if (syncMode == "nosync")
662 d_asyncFlag = MDB_NOSYNC;
663 else if (syncMode == "nometasync")
664 d_asyncFlag = MDB_NOMETASYNC;
665 else if (syncMode == "mapasync")
666 d_asyncFlag = MDB_MAPASYNC;
667 else if (syncMode.empty() || syncMode == "sync")
668 d_asyncFlag = 0;
669 else
670 throw std::runtime_error("Unknown sync mode " + syncMode + " requested for LMDB backend");
671
672 uint64_t mapSize = 0;
673 try {
674 mapSize = std::stoll(getArg("map-size"));
675 }
676 catch (const std::exception& e) {
677 throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e.what());
678 }
679
680 LMDBLS::s_flag_deleted = mustDo("flag-deleted");
681 d_handle_dups = false;
682
683 if (mustDo("lightning-stream")) {
684 d_random_ids = true;
685 d_handle_dups = true;
686 LMDBLS::s_flag_deleted = true;
687
688 if (atoi(getArg("shards").c_str()) != 1) {
689 throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
690 }
691 }
692
693 bool opened = false;
694
695 if (s_first) {
696 std::lock_guard<std::mutex> l(s_lmdbStartupLock);
697 if (s_first) {
698 auto filename = getArg("filename");
699
700 auto currentSchemaVersionAndShards = getSchemaVersionAndShards(filename);
701 uint32_t currentSchemaVersion = currentSchemaVersionAndShards.first;
702 // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
703
704 if (getArgAsNum("schema-version") != SCHEMAVERSION) {
705 throw std::runtime_error("This version of the lmdbbackend only supports schema version 5. Configuration demands a lower version. Not starting up.");
706 }
707
708 if (currentSchemaVersion > 0 && currentSchemaVersion < 3) {
709 throw std::runtime_error("this version of the lmdbbackend can only upgrade from schema v3/v4 to v5. Upgrading from older schemas is not yet supported.");
710 }
711
712 if (currentSchemaVersion == 0) {
713 // no database is present yet, we can just create them
714 currentSchemaVersion = 5;
715 }
716
717 if (currentSchemaVersion == 3 || currentSchemaVersion == 4) {
718 if (!upgradeToSchemav5(filename)) {
719 throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
720 }
721 currentSchemaVersion = 5;
722 }
723
724 if (currentSchemaVersion != 5) {
725 throw std::runtime_error("Somehow, we are not at schema version 5. Giving up");
726 }
727
728 d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
729 d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
730 d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
731 d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
732
733 auto pdnsdbi = d_tdomains->getEnv()->openDB("pdns", MDB_CREATE);
734
735 opened = true;
736
737 auto txn = d_tdomains->getEnv()->getRWTransaction();
738
739 MDBOutVal shards;
740 if (!txn->get(pdnsdbi, "shards", shards)) {
741 s_shards = shards.get<uint32_t>();
742
743 if (mustDo("lightning-stream") && s_shards != 1) {
744 throw std::runtime_error(std::string("running with Lightning Stream support enabled requires a database with exactly 1 shard"));
745 }
746
747 if (s_shards != atoi(getArg("shards").c_str())) {
748 g_log << Logger::Warning << "Note: configured number of lmdb shards (" << atoi(getArg("shards").c_str()) << ") is different from on-disk (" << s_shards << "). Using on-disk shard number" << endl;
749 }
750 }
751 else {
752 s_shards = atoi(getArg("shards").c_str());
753 txn->put(pdnsdbi, "shards", s_shards);
754 }
755
756 MDBOutVal gotuuid;
757 if (txn->get(pdnsdbi, "uuid", gotuuid)) {
758 const auto uuid = getUniqueID();
759 const string uuids(uuid.begin(), uuid.end());
760 txn->put(pdnsdbi, "uuid", uuids);
761 }
762
763 MDBOutVal _schemaversion;
764 if (txn->get(pdnsdbi, "schemaversion", _schemaversion)) {
765 // our DB is entirely new, so we need to write the schemaversion
766 txn->put(pdnsdbi, "schemaversion", currentSchemaVersion);
767 }
768 txn->commit();
769
770 s_first = false;
771 }
772 }
773
774 if (!opened) {
775 d_tdomains = std::make_shared<tdomains_t>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR | d_asyncFlag, 0600, mapSize), "domains_v5");
776 d_tmeta = std::make_shared<tmeta_t>(d_tdomains->getEnv(), "metadata_v5");
777 d_tkdb = std::make_shared<tkdb_t>(d_tdomains->getEnv(), "keydata_v5");
778 d_ttsig = std::make_shared<ttsig_t>(d_tdomains->getEnv(), "tsig_v5");
779 }
780 d_trecords.resize(s_shards);
781 d_dolog = ::arg().mustDo("query-logging");
782 }
783
784 namespace boost
785 {
786 namespace serialization
787 {
788
789 template <class Archive>
790 void save(Archive& ar, const DNSName& g, const unsigned int /* version */)
791 {
792 if (g.empty()) {
793 ar& std::string();
794 }
795 else {
796 ar& g.toDNSStringLC();
797 }
798 }
799
800 template <class Archive>
801 void load(Archive& ar, DNSName& g, const unsigned int /* version */)
802 {
803 string tmp;
804 ar& tmp;
805 if (tmp.empty()) {
806 g = DNSName();
807 }
808 else {
809 g = DNSName(tmp.c_str(), tmp.size(), 0, false);
810 }
811 }
812
813 template <class Archive>
814 void save(Archive& ar, const QType& g, const unsigned int /* version */)
815 {
816 ar& g.getCode();
817 }
818
819 template <class Archive>
820 void load(Archive& ar, QType& g, const unsigned int /* version */)
821 {
822 uint16_t tmp;
823 ar& tmp;
824 g = QType(tmp);
825 }
826
827 template <class Archive>
828 void save(Archive& ar, const DomainInfo& g, const unsigned int /* version */)
829 {
830 ar& g.zone;
831 ar& g.last_check;
832 ar& g.account;
833 ar& g.primaries;
834 ar& g.id;
835 ar& g.notified_serial;
836 ar& g.kind;
837 ar& g.options;
838 ar& g.catalog;
839 }
840
841 template <class Archive>
842 void load(Archive& ar, DomainInfo& g, const unsigned int version)
843 {
844 ar& g.zone;
845 ar& g.last_check;
846 ar& g.account;
847 ar& g.primaries;
848 ar& g.id;
849 ar& g.notified_serial;
850 ar& g.kind;
851 if (version >= 1) {
852 ar& g.options;
853 ar& g.catalog;
854 }
855 else {
856 g.options.clear();
857 g.catalog.clear();
858 }
859 }
860
861 template <class Archive>
862 void serialize(Archive& ar, LMDBBackend::DomainMeta& g, const unsigned int /* version */)
863 {
864 ar& g.domain& g.key& g.value;
865 }
866
867 template <class Archive>
868 void save(Archive& ar, const LMDBBackend::KeyDataDB& g, const unsigned int /* version */)
869 {
870 ar& g.domain& g.content& g.flags& g.active& g.published;
871 }
872
873 template <class Archive>
874 void load(Archive& ar, LMDBBackend::KeyDataDB& g, const unsigned int version)
875 {
876 ar& g.domain& g.content& g.flags& g.active;
877 if (version >= 1) {
878 ar& g.published;
879 }
880 else {
881 g.published = true;
882 }
883 }
884
885 template <class Archive>
886 void serialize(Archive& ar, TSIGKey& g, const unsigned int /* version */)
887 {
888 ar& g.name;
889 ar& g.algorithm; // this is the ordername
890 ar& g.key;
891 }
892
893 } // namespace serialization
894 } // namespace boost
895
896 BOOST_SERIALIZATION_SPLIT_FREE(DNSName);
897 BOOST_SERIALIZATION_SPLIT_FREE(QType);
898 BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB);
899 BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo);
900 BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress);
901
902 template <>
903 std::string serToString(const LMDBBackend::LMDBResourceRecord& lrr)
904 {
905 std::string ret;
906 uint16_t len = lrr.content.length();
907 ret.reserve(2 + len + 7);
908
909 ret.assign((const char*)&len, 2);
910 ret += lrr.content;
911 ret.append((const char*)&lrr.ttl, 4);
912 ret.append(1, (char)lrr.auth);
913 ret.append(1, (char)lrr.disabled);
914 ret.append(1, (char)lrr.ordername);
915 return ret;
916 }
917
918 template <>
919 std::string serToString(const vector<LMDBBackend::LMDBResourceRecord>& lrrs)
920 {
921 std::string ret;
922 for (const auto& lrr : lrrs) {
923 ret += serToString(lrr);
924 }
925 return ret;
926 }
927
928 static inline size_t serOneRRFromString(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
929 {
930 uint16_t len;
931 memcpy(&len, &str[0], 2);
932 lrr.content.assign(&str[2], len); // len bytes
933 memcpy(&lrr.ttl, &str[2] + len, 4);
934 lrr.auth = str[2 + len + 4];
935 lrr.disabled = str[2 + len + 4 + 1];
936 lrr.ordername = str[2 + len + 4 + 2];
937 lrr.wildcardname.clear();
938
939 return 2 + len + 7;
940 }
941
942 template <>
943 void serFromString(const string_view& str, LMDBBackend::LMDBResourceRecord& lrr)
944 {
945 serOneRRFromString(str, lrr);
946 }
947
948 template <>
949 void serFromString(const string_view& str, vector<LMDBBackend::LMDBResourceRecord>& lrrs)
950 {
951 auto str_copy = str;
952 while (str_copy.size() >= 9) { // minimum length for a record is 10
953 LMDBBackend::LMDBResourceRecord lrr;
954 auto rrLength = serOneRRFromString(str_copy, lrr);
955 lrrs.emplace_back(lrr);
956 str_copy.remove_prefix(rrLength);
957 }
958 }
959
960 static std::string serializeContent(uint16_t qtype, const DNSName& domain, const std::string& content)
961 {
962 auto drc = DNSRecordContent::make(qtype, QClass::IN, content);
963 return drc->serialize(domain, false);
964 }
965
966 static std::shared_ptr<DNSRecordContent> deserializeContentZR(uint16_t qtype, const DNSName& qname, const std::string& content)
967 {
968 if (qtype == QType::A && content.size() == 4) {
969 return std::make_shared<ARecordContent>(*((uint32_t*)content.c_str()));
970 }
971 return DNSRecordContent::deserialize(qname, qtype, content);
972 }
973
974 /* design. If you ask a question without a zone id, we lookup the best
975 zone id for you, and answer from that. This is different than other backends, but I can't see why it would not work.
976
977 The index we use is "zoneid,canonical relative name". This index is also used
978 for AXFR.
979
980 Note - domain_id, name and type are ONLY present on the index!
981 */
982
983 #if BOOST_VERSION >= 106100
984 #define StringView string_view
985 #else
986 #define StringView string
987 #endif
988
989 void LMDBBackend::deleteDomainRecords(RecordsRWTransaction& txn, uint32_t domain_id, uint16_t qtype)
990 {
991 compoundOrdername co;
992 string match = co(domain_id);
993
994 auto cursor = txn.txn->getCursor(txn.db->dbi);
995 MDBOutVal key, val;
996 // cout<<"Match: "<<makeHexDump(match);
997 if (!cursor.lower_bound(match, key, val)) {
998 while (key.getNoStripHeader<StringView>().rfind(match, 0) == 0) {
999 if (qtype == QType::ANY || co.getQType(key.getNoStripHeader<StringView>()) == qtype)
1000 cursor.del();
1001 if (cursor.next(key, val))
1002 break;
1003 }
1004 }
1005 }
1006
1007 /* Here's the complicated story. Other backends have just one transaction, which is either
1008 on or not.
1009
1010 You can't call feedRecord without a transaction started with startTransaction.
1011
1012 However, other functions can be called after startTransaction() or without startTransaction()
1013 (like updateDNSSECOrderNameAndAuth)
1014
1015
1016
1017 */
1018
1019 bool LMDBBackend::startTransaction(const DNSName& domain, int domain_id)
1020 {
1021 // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1022 int real_id = domain_id;
1023 if (real_id < 0) {
1024 auto rotxn = d_tdomains->getROTransaction();
1025 DomainInfo di;
1026 real_id = rotxn.get<0>(domain, di);
1027 // cout<<"real_id = "<<real_id << endl;
1028 if (!real_id)
1029 return false;
1030 }
1031 if (d_rwtxn) {
1032 throw DBException("Attempt to start a transaction while one was open already");
1033 }
1034 d_rwtxn = getRecordsRWTransaction(real_id);
1035
1036 d_transactiondomain = domain;
1037 d_transactiondomainid = real_id;
1038 if (domain_id >= 0) {
1039 deleteDomainRecords(*d_rwtxn, domain_id);
1040 }
1041
1042 return true;
1043 }
1044
1045 bool LMDBBackend::commitTransaction()
1046 {
1047 // cout<<"Commit transaction" <<endl;
1048 if (!d_rwtxn) {
1049 throw DBException("Attempt to commit a transaction while there isn't one open");
1050 }
1051
1052 d_rwtxn->txn->commit();
1053 d_rwtxn.reset();
1054 return true;
1055 }
1056
1057 bool LMDBBackend::abortTransaction()
1058 {
1059 // cout<<"Abort transaction"<<endl;
1060 if (!d_rwtxn) {
1061 throw DBException("Attempt to abort a transaction while there isn't one open");
1062 }
1063
1064 d_rwtxn->txn->abort();
1065 d_rwtxn.reset();
1066
1067 return true;
1068 }
1069
1070 // d_rwtxn must be set here
1071 bool LMDBBackend::feedRecord(const DNSResourceRecord& r, const DNSName& ordername, bool ordernameIsNSEC3)
1072 {
1073 LMDBResourceRecord lrr(r);
1074 lrr.qname.makeUsRelative(d_transactiondomain);
1075 lrr.content = serializeContent(lrr.qtype.getCode(), r.qname, lrr.content);
1076
1077 compoundOrdername co;
1078 string matchName = co(lrr.domain_id, lrr.qname, lrr.qtype.getCode());
1079
1080 string rrs;
1081 MDBOutVal _rrs;
1082 if (!d_rwtxn->txn->get(d_rwtxn->db->dbi, matchName, _rrs)) {
1083 rrs = _rrs.get<string>();
1084 }
1085
1086 rrs += serToString(lrr);
1087
1088 d_rwtxn->txn->put(d_rwtxn->db->dbi, matchName, rrs);
1089
1090 if (ordernameIsNSEC3 && !ordername.empty()) {
1091 MDBOutVal val;
1092 if (d_rwtxn->txn->get(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), val)) {
1093 lrr.ttl = 0;
1094 lrr.content = lrr.qname.toDNSStringLC();
1095 lrr.auth = 0;
1096 string ser = serToString(lrr);
1097 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, ordername, QType::NSEC3), ser);
1098
1099 lrr.ttl = 1;
1100 lrr.content = ordername.toDNSString();
1101 ser = serToString(lrr);
1102 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(lrr.domain_id, lrr.qname, QType::NSEC3), ser);
1103 }
1104 }
1105 return true;
1106 }
1107
1108 bool LMDBBackend::feedEnts(int domain_id, map<DNSName, bool>& nonterm)
1109 {
1110 LMDBResourceRecord lrr;
1111 lrr.ttl = 0;
1112 compoundOrdername co;
1113 for (const auto& nt : nonterm) {
1114 lrr.qname = nt.first.makeRelative(d_transactiondomain);
1115 lrr.auth = nt.second;
1116 lrr.ordername = true;
1117
1118 std::string ser = serToString(lrr);
1119 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
1120 }
1121 return true;
1122 }
1123
1124 bool LMDBBackend::feedEnts3(int domain_id, const DNSName& domain, map<DNSName, bool>& nonterm, const NSEC3PARAMRecordContent& ns3prc, bool narrow)
1125 {
1126 string ser;
1127 DNSName ordername;
1128 LMDBResourceRecord lrr;
1129 compoundOrdername co;
1130 for (const auto& nt : nonterm) {
1131 lrr.qname = nt.first.makeRelative(domain);
1132 lrr.ttl = 0;
1133 lrr.auth = nt.second;
1134 lrr.ordername = nt.second;
1135 ser = serToString(lrr);
1136 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::ENT), ser);
1137
1138 if (!narrow && lrr.auth) {
1139 lrr.content = lrr.qname.toDNSString();
1140 lrr.auth = false;
1141 lrr.ordername = false;
1142 ser = serToString(lrr);
1143
1144 ordername = DNSName(toBase32Hex(hashQNameWithSalt(ns3prc, nt.first)));
1145 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, ordername, QType::NSEC3), ser);
1146
1147 lrr.ttl = 1;
1148 lrr.content = ordername.toDNSString();
1149 ser = serToString(lrr);
1150 d_rwtxn->txn->put(d_rwtxn->db->dbi, co(domain_id, lrr.qname, QType::NSEC3), ser);
1151 }
1152 }
1153 return true;
1154 }
1155
1156 // might be called within a transaction, might also be called alone
1157 bool LMDBBackend::replaceRRSet(uint32_t domain_id, const DNSName& qname, const QType& qt, const vector<DNSResourceRecord>& rrset)
1158 {
1159 // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1160 shared_ptr<RecordsRWTransaction> txn;
1161 bool needCommit = false;
1162 if (d_rwtxn && d_transactiondomainid == domain_id) {
1163 txn = d_rwtxn;
1164 // cout<<"Reusing open transaction"<<endl;
1165 }
1166 else {
1167 // cout<<"Making a new RW txn for replace rrset"<<endl;
1168 txn = getRecordsRWTransaction(domain_id);
1169 needCommit = true;
1170 }
1171
1172 DomainInfo di;
1173 if (!d_tdomains->getROTransaction().get(domain_id, di)) {
1174 return false;
1175 }
1176
1177 compoundOrdername co;
1178 auto cursor = txn->txn->getCursor(txn->db->dbi);
1179 MDBOutVal key, val;
1180 string match = co(domain_id, qname.makeRelative(di.zone), qt.getCode());
1181 if (!cursor.find(match, key, val)) {
1182 cursor.del();
1183 }
1184
1185 if (!rrset.empty()) {
1186 vector<LMDBResourceRecord> adjustedRRSet;
1187 for (const auto& rr : rrset) {
1188 LMDBResourceRecord lrr(rr);
1189 lrr.content = serializeContent(lrr.qtype.getCode(), lrr.qname, lrr.content);
1190 lrr.qname.makeUsRelative(di.zone);
1191
1192 adjustedRRSet.emplace_back(lrr);
1193 }
1194 txn->txn->put(txn->db->dbi, match, serToString(adjustedRRSet));
1195 }
1196
1197 if (needCommit)
1198 txn->txn->commit();
1199
1200 return true;
1201 }
1202
1203 bool LMDBBackend::replaceComments([[maybe_unused]] const uint32_t domain_id, [[maybe_unused]] const DNSName& qname, [[maybe_unused]] const QType& qt, const vector<Comment>& comments)
1204 {
1205 // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1206 // if it's not, report failure
1207 return comments.empty();
1208 }
1209
1210 // tempting to templatize these two functions but the pain is not worth it
1211 std::shared_ptr<LMDBBackend::RecordsRWTransaction> LMDBBackend::getRecordsRWTransaction(uint32_t id)
1212 {
1213 auto& shard = d_trecords[id % s_shards];
1214 if (!shard.env) {
1215 shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
1216 MDB_NOSUBDIR | d_asyncFlag, 0600);
1217 shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
1218 }
1219 auto ret = std::make_shared<RecordsRWTransaction>(shard.env->getRWTransaction());
1220 ret->db = std::make_shared<RecordsDB>(shard);
1221
1222 return ret;
1223 }
1224
1225 std::shared_ptr<LMDBBackend::RecordsROTransaction> LMDBBackend::getRecordsROTransaction(uint32_t id, const std::shared_ptr<LMDBBackend::RecordsRWTransaction>& rwtxn)
1226 {
1227 auto& shard = d_trecords[id % s_shards];
1228 if (!shard.env) {
1229 if (rwtxn) {
1230 throw DBException("attempting to start nested transaction without open parent env");
1231 }
1232 shard.env = getMDBEnv((getArg("filename") + "-" + std::to_string(id % s_shards)).c_str(),
1233 MDB_NOSUBDIR | d_asyncFlag, 0600);
1234 shard.dbi = shard.env->openDB("records_v5", MDB_CREATE);
1235 }
1236
1237 if (rwtxn) {
1238 auto ret = std::make_shared<RecordsROTransaction>(rwtxn->txn->getROTransaction());
1239 ret->db = std::make_shared<RecordsDB>(shard);
1240 return ret;
1241 }
1242 else {
1243 auto ret = std::make_shared<RecordsROTransaction>(shard.env->getROTransaction());
1244 ret->db = std::make_shared<RecordsDB>(shard);
1245 return ret;
1246 }
1247 }
1248
1249 #if 0
1250 // FIXME reinstate soon
1251 bool LMDBBackend::upgradeToSchemav3()
1252 {
1253 g_log << Logger::Warning << "Upgrading LMDB schema" << endl;
1254
1255 for (auto i = 0; i < s_shards; i++) {
1256 string filename = getArg("filename") + "-" + std::to_string(i);
1257 if (rename(filename.c_str(), (filename + "-old").c_str()) < 0) {
1258 if (errno == ENOENT) {
1259 // apparently this shard doesn't exist yet, moving on
1260 continue;
1261 }
1262 unixDie("Rename failed during LMDB upgrade");
1263 }
1264
1265 LMDBBackend::RecordsDB oldShard, newShard;
1266
1267 oldShard.env = getMDBEnv((filename + "-old").c_str(),
1268 MDB_NOSUBDIR | d_asyncFlag, 0600);
1269 oldShard.dbi = oldShard.env->openDB("records", MDB_CREATE | MDB_DUPSORT);
1270 auto txn = oldShard.env->getROTransaction();
1271 auto cursor = txn->getROCursor(oldShard.dbi);
1272
1273 newShard.env = getMDBEnv((filename).c_str(),
1274 MDB_NOSUBDIR | d_asyncFlag, 0600);
1275 newShard.dbi = newShard.env->openDB("records", MDB_CREATE);
1276 auto newTxn = newShard.env->getRWTransaction();
1277
1278 MDBOutVal key, val;
1279 if (cursor.first(key, val) != 0) {
1280 cursor.close();
1281 txn->abort();
1282 newTxn->abort();
1283 continue;
1284 }
1285 string_view currentKey;
1286 string value;
1287 for (;;) {
1288 auto newKey = key.getNoStripHeader<string_view>();
1289 if (currentKey.compare(newKey) != 0) {
1290 if (value.size() > 0) {
1291 newTxn->put(newShard.dbi, currentKey, value);
1292 }
1293 currentKey = newKey;
1294 value = "";
1295 }
1296 value += val.get<string>();
1297 if (cursor.next(key, val) != 0) {
1298 if (value.size() > 0) {
1299 newTxn->put(newShard.dbi, currentKey, value);
1300 }
1301 break;
1302 }
1303 }
1304
1305 cursor.close();
1306 txn->commit();
1307 newTxn->commit();
1308 }
1309
1310 return true;
1311 }
1312 #endif
1313
1314 bool LMDBBackend::deleteDomain(const DNSName& domain)
1315 {
1316 if (!d_rwtxn) {
1317 throw DBException(std::string(__PRETTY_FUNCTION__) + " called without a transaction");
1318 }
1319
1320 int transactionDomainId = d_transactiondomainid;
1321 DNSName transactionDomain = d_transactiondomain;
1322
1323 abortTransaction();
1324
1325 LMDBIDvec idvec;
1326
1327 if (!d_handle_dups) {
1328 // get domain id
1329 auto txn = d_tdomains->getROTransaction();
1330
1331 DomainInfo di;
1332 idvec.push_back(txn.get<0>(domain, di));
1333 }
1334 else {
1335 // this transaction used to be RO.
1336 // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1337 // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1338 // when doing that, first do a short RO check to see if we actually have anything to delete
1339 auto txn = d_tdomains->getRWTransaction();
1340
1341 txn.get_multi<0>(domain, idvec);
1342 }
1343
1344 for (auto id : idvec) {
1345
1346 startTransaction(domain, id);
1347
1348 { // Remove metadata
1349 auto txn = d_tmeta->getRWTransaction();
1350 LMDBIDvec ids;
1351
1352 txn.get_multi<0>(domain, ids);
1353
1354 for (auto& _id : ids) {
1355 txn.del(_id);
1356 }
1357
1358 txn.commit();
1359 }
1360
1361 { // Remove cryptokeys
1362 auto txn = d_tkdb->getRWTransaction();
1363 LMDBIDvec ids;
1364 txn.get_multi<0>(domain, ids);
1365
1366 for (auto _id : ids) {
1367 txn.del(_id);
1368 }
1369
1370 txn.commit();
1371 }
1372
1373 // Remove records
1374 commitTransaction();
1375
1376 // Remove zone
1377 auto txn = d_tdomains->getRWTransaction();
1378 txn.del(id);
1379 txn.commit();
1380 }
1381
1382 startTransaction(transactionDomain, transactionDomainId);
1383
1384 return true;
1385 }
1386
1387 bool LMDBBackend::list(const DNSName& target, int /* id */, bool include_disabled)
1388 {
1389 d_includedisabled = include_disabled;
1390
1391 DomainInfo di;
1392 {
1393 auto dtxn = d_tdomains->getROTransaction();
1394 if ((di.id = dtxn.get<0>(target, di))) {
1395 // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
1396 }
1397 else {
1398 // cerr << "Did not find " << target << endl;
1399 return false;
1400 }
1401 }
1402
1403 d_rotxn = getRecordsROTransaction(di.id, d_rwtxn);
1404 d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
1405
1406 compoundOrdername co;
1407 d_matchkey = co(di.id);
1408
1409 MDBOutVal key, val;
1410 auto a = d_getcursor->lower_bound(d_matchkey, key, val);
1411 auto b0 = key.getNoStripHeader<StringView>();
1412 auto b = b0.rfind(d_matchkey, 0);
1413 if (a || b != 0) {
1414 d_getcursor.reset();
1415 }
1416
1417 d_lookupdomain = target;
1418
1419 // Make sure we start with fresh data
1420 d_currentrrset.clear();
1421 d_currentrrsetpos = 0;
1422
1423 return true;
1424 }
1425
1426 void LMDBBackend::lookup(const QType& type, const DNSName& qdomain, int zoneId, DNSPacket* /* p */)
1427 {
1428 if (d_dolog) {
1429 g_log << Logger::Warning << "Got lookup for " << qdomain << "|" << type.toString() << " in zone " << zoneId << endl;
1430 d_dtime.set();
1431 }
1432
1433 d_includedisabled = false;
1434
1435 DNSName hunt(qdomain);
1436 DomainInfo di;
1437 if (zoneId < 0) {
1438 auto rotxn = d_tdomains->getROTransaction();
1439
1440 do {
1441 zoneId = rotxn.get<0>(hunt, di);
1442 } while (!zoneId && type != QType::SOA && hunt.chopOff());
1443 if (zoneId <= 0) {
1444 // cout << "Did not find zone for "<< qdomain<<endl;
1445 d_getcursor.reset();
1446 return;
1447 }
1448 }
1449 else {
1450 if (!d_tdomains->getROTransaction().get(zoneId, di)) {
1451 // cout<<"Could not find a zone with id "<<zoneId<<endl;
1452 d_getcursor.reset();
1453 return;
1454 }
1455 hunt = di.zone;
1456 }
1457
1458 DNSName relqname = qdomain.makeRelative(hunt);
1459 if (relqname.empty()) {
1460 return;
1461 }
1462 // cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<" and type "<<type.toString()<<endl;
1463 d_rotxn = getRecordsROTransaction(zoneId, d_rwtxn);
1464
1465 compoundOrdername co;
1466 d_getcursor = std::make_shared<MDBROCursor>(d_rotxn->txn->getCursor(d_rotxn->db->dbi));
1467 MDBOutVal key, val;
1468 if (type.getCode() == QType::ANY) {
1469 d_matchkey = co(zoneId, relqname);
1470 }
1471 else {
1472 d_matchkey = co(zoneId, relqname, type.getCode());
1473 }
1474
1475 if (d_getcursor->lower_bound(d_matchkey, key, val) || key.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
1476 d_getcursor.reset();
1477 if (d_dolog) {
1478 g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute (found nothing)" << endl;
1479 }
1480 return;
1481 }
1482
1483 if (d_dolog) {
1484 g_log << Logger::Warning << "Query " << ((long)(void*)this) << ": " << d_dtime.udiffNoReset() << " us to execute" << endl;
1485 }
1486
1487 d_lookupdomain = hunt;
1488
1489 // Make sure we start with fresh data
1490 d_currentrrset.clear();
1491 d_currentrrsetpos = 0;
1492 }
1493
1494 bool LMDBBackend::get(DNSZoneRecord& zr)
1495 {
1496 for (;;) {
1497 // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1498 if (!d_getcursor) {
1499 d_rotxn.reset();
1500 return false;
1501 }
1502
1503 string_view key;
1504
1505 if (d_currentrrset.empty()) {
1506 d_getcursor->current(d_currentKey, d_currentVal);
1507
1508 key = d_currentKey.getNoStripHeader<string_view>();
1509 zr.dr.d_type = compoundOrdername::getQType(key).getCode();
1510
1511 if (zr.dr.d_type == QType::NSEC3) {
1512 // Hit a magic NSEC3 skipping
1513 if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
1514 // cerr<<"resetting d_getcursor 1"<<endl;
1515 d_getcursor.reset();
1516 }
1517 continue;
1518 }
1519
1520 serFromString(d_currentVal.get<string_view>(), d_currentrrset);
1521 d_currentrrsetpos = 0;
1522 }
1523 else {
1524 key = d_currentKey.getNoStripHeader<string_view>();
1525 }
1526 try {
1527 const auto& lrr = d_currentrrset.at(d_currentrrsetpos++);
1528
1529 zr.disabled = lrr.disabled;
1530 if (!zr.disabled || d_includedisabled) {
1531 zr.dr.d_name = compoundOrdername::getQName(key) + d_lookupdomain;
1532 zr.domain_id = compoundOrdername::getDomainID(key);
1533 zr.dr.d_type = compoundOrdername::getQType(key).getCode();
1534 zr.dr.d_ttl = lrr.ttl;
1535 zr.dr.setContent(deserializeContentZR(zr.dr.d_type, zr.dr.d_name, lrr.content));
1536 zr.auth = lrr.auth;
1537 }
1538
1539 if (d_currentrrsetpos >= d_currentrrset.size()) {
1540 d_currentrrset.clear(); // will invalidate lrr
1541 if (d_getcursor->next(d_currentKey, d_currentVal) || d_currentKey.getNoStripHeader<StringView>().rfind(d_matchkey, 0) != 0) {
1542 // cerr<<"resetting d_getcursor 2"<<endl;
1543 d_getcursor.reset();
1544 }
1545 }
1546
1547 if (zr.disabled && !d_includedisabled) {
1548 continue;
1549 }
1550 }
1551 catch (const std::exception& e) {
1552 throw PDNSException(e.what());
1553 }
1554
1555 break;
1556 }
1557
1558 return true;
1559 }
1560
1561 bool LMDBBackend::get(DNSResourceRecord& rr)
1562 {
1563 DNSZoneRecord zr;
1564 if (!get(zr)) {
1565 return false;
1566 }
1567
1568 rr.qname = zr.dr.d_name;
1569 rr.ttl = zr.dr.d_ttl;
1570 rr.qtype = zr.dr.d_type;
1571 rr.content = zr.dr.getContent()->getZoneRepresentation(true);
1572 rr.domain_id = zr.domain_id;
1573 rr.auth = zr.auth;
1574 rr.disabled = zr.disabled;
1575
1576 return true;
1577 }
1578
1579 bool LMDBBackend::getSerial(DomainInfo& di)
1580 {
1581 auto txn = getRecordsROTransaction(di.id);
1582 compoundOrdername co;
1583 MDBOutVal val;
1584 if (!txn->txn->get(txn->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
1585 LMDBResourceRecord lrr;
1586 serFromString(val.get<string_view>(), lrr);
1587 if (lrr.content.size() >= 5 * sizeof(uint32_t)) {
1588 uint32_t serial;
1589 // a SOA has five 32 bit fields, the first of which is the serial
1590 // there are two variable length names before the serial, so we calculate from the back
1591 memcpy(&serial, &lrr.content[lrr.content.size() - (5 * sizeof(uint32_t))], sizeof(serial));
1592 di.serial = ntohl(serial);
1593 }
1594 return !lrr.disabled;
1595 }
1596 return false;
1597 }
1598
1599 bool LMDBBackend::getDomainInfo(const DNSName& domain, DomainInfo& di, bool getserial)
1600 {
1601 {
1602 auto txn = d_tdomains->getROTransaction();
1603 // auto range = txn.prefix_range<0>(domain);
1604
1605 // bool found = false;
1606
1607 // for (auto& iter = range.first ; iter != range.second; ++iter) {
1608 // found = true;
1609 // di.id = iter.getID();
1610 // di.backend = this;
1611 // }
1612
1613 // if (!found) {
1614 // return false;
1615 // }
1616 if (!(di.id = txn.get<0>(domain, di))) {
1617 return false;
1618 }
1619
1620 di.backend = this;
1621 }
1622
1623 if (getserial) {
1624 getSerial(di);
1625 }
1626
1627 return true;
1628 }
1629
1630 int LMDBBackend::genChangeDomain(const DNSName& domain, const std::function<void(DomainInfo&)>& func)
1631 {
1632 auto txn = d_tdomains->getRWTransaction();
1633
1634 DomainInfo di;
1635
1636 auto id = txn.get<0>(domain, di);
1637 func(di);
1638 txn.put(di, id);
1639
1640 txn.commit();
1641 return true;
1642 }
1643
1644 int LMDBBackend::genChangeDomain(uint32_t id, const std::function<void(DomainInfo&)>& func)
1645 {
1646 DomainInfo di;
1647
1648 auto txn = d_tdomains->getRWTransaction();
1649
1650 if (!txn.get(id, di))
1651 return false;
1652
1653 func(di);
1654
1655 txn.put(di, id);
1656
1657 txn.commit();
1658 return true;
1659 }
1660
1661 bool LMDBBackend::setKind(const DNSName& domain, const DomainInfo::DomainKind kind)
1662 {
1663 return genChangeDomain(domain, [kind](DomainInfo& di) {
1664 di.kind = kind;
1665 });
1666 }
1667
1668 bool LMDBBackend::setAccount(const DNSName& domain, const std::string& account)
1669 {
1670 return genChangeDomain(domain, [account](DomainInfo& di) {
1671 di.account = account;
1672 });
1673 }
1674
1675 bool LMDBBackend::setPrimaries(const DNSName& domain, const vector<ComboAddress>& primaries)
1676 {
1677 return genChangeDomain(domain, [&primaries](DomainInfo& di) {
1678 di.primaries = primaries;
1679 });
1680 }
1681
1682 bool LMDBBackend::createDomain(const DNSName& domain, const DomainInfo::DomainKind kind, const vector<ComboAddress>& primaries, const string& account)
1683 {
1684 DomainInfo di;
1685
1686 {
1687 auto txn = d_tdomains->getRWTransaction();
1688 if (txn.get<0>(domain, di)) {
1689 throw DBException("Domain '" + domain.toLogString() + "' exists already");
1690 }
1691
1692 di.zone = domain;
1693 di.kind = kind;
1694 di.primaries = primaries;
1695 di.account = account;
1696
1697 txn.put(di, 0, d_random_ids);
1698 txn.commit();
1699 }
1700
1701 return true;
1702 }
1703
1704 void LMDBBackend::getAllDomainsFiltered(vector<DomainInfo>* domains, const std::function<bool(DomainInfo&)>& allow)
1705 {
1706 auto txn = d_tdomains->getROTransaction();
1707 if (d_handle_dups) {
1708 map<DNSName, DomainInfo> zonemap;
1709 set<DNSName> dups;
1710
1711 for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
1712 DomainInfo di = *iter;
1713 di.id = iter.getID();
1714 di.backend = this;
1715
1716 if (!zonemap.emplace(di.zone, di).second) {
1717 dups.insert(di.zone);
1718 }
1719 }
1720
1721 for (const auto& zone : dups) {
1722 DomainInfo di;
1723
1724 // this get grabs the oldest item if there are duplicates
1725 di.id = txn.get<0>(zone, di);
1726
1727 if (di.id == 0) {
1728 // .get actually found nothing for us
1729 continue;
1730 }
1731
1732 di.backend = this;
1733 zonemap[di.zone] = di;
1734 }
1735
1736 for (auto& [k, v] : zonemap) {
1737 if (allow(v)) {
1738 domains->push_back(std::move(v));
1739 }
1740 }
1741 }
1742 else {
1743 for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
1744 DomainInfo di = *iter;
1745 di.id = iter.getID();
1746 di.backend = this;
1747
1748 if (allow(di)) {
1749 domains->push_back(di);
1750 }
1751 }
1752 }
1753 }
1754
1755 void LMDBBackend::getAllDomains(vector<DomainInfo>* domains, bool /* doSerial */, bool include_disabled)
1756 {
1757 domains->clear();
1758
1759 getAllDomainsFiltered(domains, [this, include_disabled](DomainInfo& di) {
1760 if (!getSerial(di) && !include_disabled) {
1761 return false;
1762 }
1763
1764 return true;
1765 });
1766 }
1767
1768 void LMDBBackend::getUnfreshSecondaryInfos(vector<DomainInfo>* domains)
1769 {
1770 uint32_t serial;
1771 time_t now = time(0);
1772 LMDBResourceRecord lrr;
1773 soatimes st;
1774
1775 getAllDomainsFiltered(domains, [this, &lrr, &st, &now, &serial](DomainInfo& di) {
1776 if (!di.isSecondaryType()) {
1777 return false;
1778 }
1779
1780 auto txn2 = getRecordsROTransaction(di.id);
1781 compoundOrdername co;
1782 MDBOutVal val;
1783 if (!txn2->txn->get(txn2->db->dbi, co(di.id, g_rootdnsname, QType::SOA), val)) {
1784 serFromString(val.get<string_view>(), lrr);
1785 memcpy(&st, &lrr.content[lrr.content.size() - sizeof(soatimes)], sizeof(soatimes));
1786 if ((time_t)(di.last_check + ntohl(st.refresh)) > now) { // still fresh
1787 return false;
1788 }
1789 serial = ntohl(st.serial);
1790 }
1791 else {
1792 serial = 0;
1793 }
1794
1795 return true;
1796 });
1797 }
1798
1799 void LMDBBackend::setStale(uint32_t domain_id)
1800 {
1801 genChangeDomain(domain_id, [](DomainInfo& di) {
1802 di.last_check = 0;
1803 });
1804 }
1805
1806 void LMDBBackend::setFresh(uint32_t domain_id)
1807 {
1808 genChangeDomain(domain_id, [](DomainInfo& di) {
1809 di.last_check = time(nullptr);
1810 });
1811 }
1812
1813 void LMDBBackend::getUpdatedPrimaries(vector<DomainInfo>& updatedDomains, std::unordered_set<DNSName>& catalogs, CatalogHashMap& catalogHashes)
1814 {
1815 CatalogInfo ci;
1816
1817 getAllDomainsFiltered(&(updatedDomains), [this, &catalogs, &catalogHashes, &ci](DomainInfo& di) {
1818 if (!di.isPrimaryType()) {
1819 return false;
1820 }
1821
1822 if (di.kind == DomainInfo::Producer) {
1823 catalogs.insert(di.zone);
1824 catalogHashes[di.zone].process("\0");
1825 return false; // Producer fresness check is performed elsewhere
1826 }
1827
1828 if (!di.catalog.empty()) {
1829 ci.fromJson(di.options, CatalogInfo::CatalogType::Producer);
1830 ci.updateHash(catalogHashes, di);
1831 }
1832
1833 if (getSerial(di) && di.serial != di.notified_serial) {
1834 di.backend = this;
1835 return true;
1836 }
1837
1838 return false;
1839 });
1840 }
1841
1842 void LMDBBackend::setNotified(uint32_t domain_id, uint32_t serial)
1843 {
1844 genChangeDomain(domain_id, [serial](DomainInfo& di) {
1845 di.notified_serial = serial;
1846 });
1847 }
1848
1849 class getCatalogMembersReturnFalseException : std::runtime_error
1850 {
1851 public:
1852 getCatalogMembersReturnFalseException() :
1853 std::runtime_error("getCatalogMembers should return false") {}
1854 };
1855
1856 bool LMDBBackend::getCatalogMembers(const DNSName& catalog, vector<CatalogInfo>& members, CatalogInfo::CatalogType type)
1857 {
1858 vector<DomainInfo> scratch;
1859
1860 try {
1861 getAllDomainsFiltered(&scratch, [&catalog, &members, &type](DomainInfo& di) {
1862 if ((type == CatalogInfo::CatalogType::Producer && di.kind != DomainInfo::Primary) || (type == CatalogInfo::CatalogType::Consumer && di.kind != DomainInfo::Secondary) || di.catalog != catalog) {
1863 return false;
1864 }
1865
1866 CatalogInfo ci;
1867 ci.d_id = di.id;
1868 ci.d_zone = di.zone;
1869 ci.d_primaries = di.primaries;
1870 try {
1871 ci.fromJson(di.options, type);
1872 }
1873 catch (const std::runtime_error& e) {
1874 g_log << Logger::Warning << __PRETTY_FUNCTION__ << " options '" << di.options << "' for zone '" << di.zone << "' is no valid JSON: " << e.what() << endl;
1875 members.clear();
1876 throw getCatalogMembersReturnFalseException();
1877 }
1878 members.emplace_back(ci);
1879
1880 return false;
1881 });
1882 }
1883 catch (const getCatalogMembersReturnFalseException& e) {
1884 return false;
1885 }
1886 return true;
1887 }
1888
1889 bool LMDBBackend::setOptions(const DNSName& domain, const std::string& options)
1890 {
1891 return genChangeDomain(domain, [options](DomainInfo& di) {
1892 di.options = options;
1893 });
1894 }
1895
1896 bool LMDBBackend::setCatalog(const DNSName& domain, const DNSName& catalog)
1897 {
1898 return genChangeDomain(domain, [catalog](DomainInfo& di) {
1899 di.catalog = catalog;
1900 });
1901 }
1902
1903 bool LMDBBackend::getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string>>& meta)
1904 {
1905 meta.clear();
1906 auto txn = d_tmeta->getROTransaction();
1907 LMDBIDvec ids;
1908 txn.get_multi<0>(name, ids);
1909
1910 DomainMeta dm;
1911 // cerr<<"getAllDomainMetadata start"<<endl;
1912 for (auto id : ids) {
1913 if (txn.get(id, dm)) {
1914 meta[dm.key].push_back(dm.value);
1915 }
1916 }
1917 return true;
1918 }
1919
1920 bool LMDBBackend::setDomainMetadata(const DNSName& name, const std::string& kind, const std::vector<std::string>& meta)
1921 {
1922 auto txn = d_tmeta->getRWTransaction();
1923
1924 LMDBIDvec ids;
1925 txn.get_multi<0>(name, ids);
1926
1927 DomainMeta dmeta;
1928 for (auto id : ids) {
1929 if (txn.get(id, dmeta)) {
1930 if (dmeta.key == kind) {
1931 // cerr<<"delete"<<endl;
1932 txn.del(id);
1933 }
1934 }
1935 }
1936
1937 for (const auto& m : meta) {
1938 DomainMeta dm{name, kind, m};
1939 txn.put(dm, 0, d_random_ids);
1940 }
1941 txn.commit();
1942 return true;
1943 }
1944
1945 bool LMDBBackend::getDomainKeys(const DNSName& name, std::vector<KeyData>& keys)
1946 {
1947 auto txn = d_tkdb->getROTransaction();
1948 LMDBIDvec ids;
1949 txn.get_multi<0>(name, ids);
1950
1951 KeyDataDB key;
1952
1953 for (auto id : ids) {
1954 if (txn.get(id, key)) {
1955 KeyData kd{key.content, id, key.flags, key.active, key.published};
1956 keys.push_back(kd);
1957 }
1958 }
1959
1960 return true;
1961 }
1962
1963 bool LMDBBackend::removeDomainKey(const DNSName& name, unsigned int id)
1964 {
1965 auto txn = d_tkdb->getRWTransaction();
1966 KeyDataDB kdb;
1967 if (txn.get(id, kdb)) {
1968 if (kdb.domain == name) {
1969 txn.del(id);
1970 txn.commit();
1971 return true;
1972 }
1973 }
1974 // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
1975 return true;
1976 }
1977
1978 bool LMDBBackend::addDomainKey(const DNSName& name, const KeyData& key, int64_t& id)
1979 {
1980 auto txn = d_tkdb->getRWTransaction();
1981 KeyDataDB kdb{name, key.content, key.flags, key.active, key.published};
1982 id = txn.put(kdb, 0, d_random_ids);
1983 txn.commit();
1984
1985 return true;
1986 }
1987
1988 bool LMDBBackend::activateDomainKey(const DNSName& name, unsigned int id)
1989 {
1990 auto txn = d_tkdb->getRWTransaction();
1991 KeyDataDB kdb;
1992 if (txn.get(id, kdb)) {
1993 if (kdb.domain == name) {
1994 txn.modify(id, [](KeyDataDB& kdbarg) {
1995 kdbarg.active = true;
1996 });
1997 txn.commit();
1998 return true;
1999 }
2000 }
2001
2002 // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2003 return true;
2004 }
2005
2006 bool LMDBBackend::deactivateDomainKey(const DNSName& name, unsigned int id)
2007 {
2008 auto txn = d_tkdb->getRWTransaction();
2009 KeyDataDB kdb;
2010 if (txn.get(id, kdb)) {
2011 if (kdb.domain == name) {
2012 txn.modify(id, [](KeyDataDB& kdbarg) {
2013 kdbarg.active = false;
2014 });
2015 txn.commit();
2016 return true;
2017 }
2018 }
2019 // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2020 return true;
2021 }
2022
2023 bool LMDBBackend::publishDomainKey(const DNSName& name, unsigned int id)
2024 {
2025 auto txn = d_tkdb->getRWTransaction();
2026 KeyDataDB kdb;
2027 if (txn.get(id, kdb)) {
2028 if (kdb.domain == name) {
2029 txn.modify(id, [](KeyDataDB& kdbarg) {
2030 kdbarg.published = true;
2031 });
2032 txn.commit();
2033 return true;
2034 }
2035 }
2036
2037 // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2038 return true;
2039 }
2040
2041 bool LMDBBackend::unpublishDomainKey(const DNSName& name, unsigned int id)
2042 {
2043 auto txn = d_tkdb->getRWTransaction();
2044 KeyDataDB kdb;
2045 if (txn.get(id, kdb)) {
2046 if (kdb.domain == name) {
2047 txn.modify(id, [](KeyDataDB& kdbarg) {
2048 kdbarg.published = false;
2049 });
2050 txn.commit();
2051 return true;
2052 }
2053 }
2054 // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2055 return true;
2056 }
2057
2058 bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after)
2059 {
2060 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2061
2062 DomainInfo di;
2063 if (!d_tdomains->getROTransaction().get(id, di)) {
2064 // domain does not exist, tough luck
2065 return false;
2066 }
2067 // cout <<"Zone: "<<di.zone<<endl;
2068
2069 compoundOrdername co;
2070 auto txn = getRecordsROTransaction(id);
2071
2072 auto cursor = txn->txn->getCursor(txn->db->dbi);
2073 MDBOutVal key, val;
2074
2075 LMDBResourceRecord lrr;
2076
2077 string matchkey = co(id, qname, QType::NSEC3);
2078 if (cursor.lower_bound(matchkey, key, val)) {
2079 // this is beyond the end of the database
2080 // cout << "Beyond end of database!" << endl;
2081 cursor.last(key, val);
2082
2083 for (;;) {
2084 if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2085 //cout<<"Last record also not part of this zone!"<<endl;
2086 // this implies something is wrong in the database, nothing we can do
2087 return false;
2088 }
2089
2090 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2091 serFromString(val.get<StringView>(), lrr);
2092 if (!lrr.ttl) // the kind of NSEC3 we need
2093 break;
2094 }
2095 if (cursor.prev(key, val)) {
2096 // hit beginning of database, again means something is wrong with it
2097 return false;
2098 }
2099 }
2100 before = co.getQName(key.getNoStripHeader<StringView>());
2101 unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
2102
2103 // now to find after .. at the beginning of the zone
2104 if (cursor.lower_bound(co(id), key, val)) {
2105 // cout<<"hit end of zone find when we shouldn't"<<endl;
2106 return false;
2107 }
2108 for (;;) {
2109 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2110 serFromString(val.get<StringView>(), lrr);
2111 if (!lrr.ttl)
2112 break;
2113 }
2114
2115 if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2116 // cout<<"hit end of zone or database when we shouldn't"<<endl;
2117 return false;
2118 }
2119 }
2120 after = co.getQName(key.getNoStripHeader<StringView>());
2121 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2122 return true;
2123 }
2124
2125 // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
2126
2127 before = co.getQName(key.getNoStripHeader<StringView>());
2128 if (before == qname) {
2129 // cout << "Ended up on exact right node" << endl;
2130 before = co.getQName(key.getNoStripHeader<StringView>());
2131 // unhashed should be correct now, maybe check?
2132 if (cursor.next(key, val)) {
2133 // xxx should find first hash now
2134
2135 if (cursor.lower_bound(co(id), key, val)) {
2136 // cout<<"hit end of zone find when we shouldn't for id "<<id<< __LINE__<<endl;
2137 return false;
2138 }
2139 for (;;) {
2140 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2141 serFromString(val.get<StringView>(), lrr);
2142 if (!lrr.ttl)
2143 break;
2144 }
2145
2146 if (cursor.next(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2147 // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
2148 return false;
2149 }
2150 }
2151 after = co.getQName(key.getNoStripHeader<StringView>());
2152 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2153 return true;
2154 }
2155 }
2156 else {
2157 // cout <<"Going backwards to find 'before'"<<endl;
2158 int count = 0;
2159 for (;;) {
2160 if (co.getQName(key.getNoStripHeader<StringView>()).canonCompare(qname) && co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2161 // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
2162 // cout<<"qname = "<<qname<<endl;
2163 // cout<<"here = "<<co.getQName(key.get<StringView>())<<endl;
2164 serFromString(val.get<StringView>(), lrr);
2165 if (!lrr.ttl)
2166 break;
2167 }
2168
2169 if (cursor.prev(key, val) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2170 // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2171 // this can happen, must deal with it
2172 // should now find the last hash of the zone
2173
2174 if (cursor.lower_bound(co(id + 1), key, val)) {
2175 // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2176 cursor.last(key, val);
2177 }
2178 else
2179 cursor.prev(key, val);
2180
2181 for (;;) {
2182 if (co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2183 //cout<<"Last record also not part of this zone!"<<endl;
2184 // this implies something is wrong in the database, nothing we can do
2185 return false;
2186 }
2187
2188 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2189 serFromString(val.get<StringView>(), lrr);
2190 if (!lrr.ttl) // the kind of NSEC3 we need
2191 break;
2192 }
2193 if (cursor.prev(key, val)) {
2194 // hit beginning of database, again means something is wrong with it
2195 return false;
2196 }
2197 }
2198 before = co.getQName(key.getNoStripHeader<StringView>());
2199 unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
2200 // cout <<"Should still find 'after'!"<<endl;
2201 // for 'after', we need to find the first hash of this zone
2202
2203 if (cursor.lower_bound(co(id), key, val)) {
2204 // cout<<"hit end of zone find when we shouldn't"<<endl;
2205 // means database is wrong, nothing we can do
2206 return false;
2207 }
2208 for (;;) {
2209 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2210 serFromString(val.get<StringView>(), lrr);
2211 if (!lrr.ttl)
2212 break;
2213 }
2214
2215 if (cursor.next(key, val)) {
2216 // means database is wrong, nothing we can do
2217 // cout<<"hit end of zone when we shouldn't 2"<<endl;
2218 return false;
2219 }
2220 }
2221 after = co.getQName(key.getNoStripHeader<StringView>());
2222
2223 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2224 return true;
2225 }
2226 ++count;
2227 }
2228 before = co.getQName(key.getNoStripHeader<StringView>());
2229 unhashed = DNSName(lrr.content.c_str(), lrr.content.size(), 0, false) + di.zone;
2230 // cout<<"Went backwards, found "<<before<<endl;
2231 // return us to starting point
2232 while (count--)
2233 cursor.next(key, val);
2234 }
2235 // cout<<"Now going forward"<<endl;
2236 for (int count = 0;; ++count) {
2237 if ((count && cursor.next(key, val)) || co.getDomainID(key.getNoStripHeader<StringView>()) != id) {
2238 // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2239 if (cursor.lower_bound(co(id), key, val)) {
2240 // cout<<"hit end of zone find when we shouldn't"<<endl;
2241 // means database is wrong, nothing we can do
2242 return false;
2243 }
2244 for (;;) {
2245 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2246 serFromString(val.get<StringView>(), lrr);
2247 if (!lrr.ttl)
2248 break;
2249 }
2250
2251 if (cursor.next(key, val)) {
2252 // means database is wrong, nothing we can do
2253 // cout<<"hit end of zone when we shouldn't 2"<<endl;
2254 return false;
2255 }
2256 // cout << "Next.. "<<endl;
2257 }
2258 after = co.getQName(key.getNoStripHeader<StringView>());
2259
2260 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2261 return true;
2262 }
2263
2264 // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2265 if (co.getQType(key.getNoStripHeader<StringView>()) == QType::NSEC3) {
2266 serFromString(val.get<StringView>(), lrr);
2267 if (!lrr.ttl) {
2268 break;
2269 }
2270 }
2271 }
2272 after = co.getQName(key.getNoStripHeader<StringView>());
2273 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2274 return true;
2275 }
2276
2277 bool LMDBBackend::getBeforeAndAfterNames(uint32_t id, const DNSName& zonenameU, const DNSName& qname, DNSName& before, DNSName& after)
2278 {
2279 DNSName zonename = zonenameU.makeLowerCase();
2280 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2281
2282 auto txn = getRecordsROTransaction(id);
2283 compoundOrdername co;
2284 DNSName qname2 = qname.makeRelative(zonename);
2285 string matchkey = co(id, qname2);
2286 auto cursor = txn->txn->getCursor(txn->db->dbi);
2287 MDBOutVal key, val;
2288 // cout<<"Lower_bound for "<<qname2<<endl;
2289 if (cursor.lower_bound(matchkey, key, val)) {
2290 // cout << "Hit end of database, bummer"<<endl;
2291 cursor.last(key, val);
2292 if (co.getDomainID(key.getNoStripHeader<string_view>()) == id) {
2293 before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
2294 after = zonename;
2295 }
2296 // else
2297 // cout << "We were at end of database, but this zone is not there?!"<<endl;
2298 return true;
2299 }
2300 // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<co.getDomainID(key.get<string_view>())<< endl;
2301
2302 if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && co.getDomainID(key.getNoStripHeader<string_view>()) == id && co.getQName(key.getNoStripHeader<string_view>()) == qname2) { // don't match ENTs
2303 // cout << "Had an exact match!"<<endl;
2304 before = qname2 + zonename;
2305 int rc;
2306 for (;;) {
2307 rc = cursor.next(key, val);
2308 if (rc)
2309 break;
2310
2311 if (co.getDomainID(key.getNoStripHeader<string_view>()) == id && key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0)
2312 continue;
2313 LMDBResourceRecord lrr;
2314 serFromString(val.get<StringView>(), lrr);
2315 if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
2316 break;
2317 }
2318 if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
2319 // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2320 after = zonename;
2321 return false;
2322 }
2323 after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
2324 return true;
2325 }
2326
2327 if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
2328 // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2329 after = zonename;
2330 // cout << "Now hunting for previous" << endl;
2331 int rc;
2332 for (;;) {
2333 rc = cursor.prev(key, val);
2334 if (rc) {
2335 // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2336 return false;
2337 }
2338
2339 if (co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
2340 // cout<<"Reversed into zone, but found wrong zone id " << co.getDomainID(key.getNoStripHeader<string_view>()) << " != "<<id<<endl;
2341 // "this can't happen"
2342 return false;
2343 }
2344 LMDBResourceRecord lrr;
2345 serFromString(val.get<StringView>(), lrr);
2346 if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS))
2347 break;
2348 }
2349
2350 before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
2351 // cout<<"Found: "<< before<<endl;
2352 return true;
2353 }
2354
2355 // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
2356
2357 int skips = 0;
2358 for (;;) {
2359 LMDBResourceRecord lrr;
2360 serFromString(val.get<StringView>(), lrr);
2361 if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()).getCode() == QType::NS)) {
2362 after = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
2363 // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2364 // cout << makeHexDump(val.get<string>()) << endl;
2365 break;
2366 }
2367 // 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;
2368 int rc = cursor.next(key, val);
2369 if (!rc)
2370 ++skips;
2371 if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
2372 // cout << " oops, hit end of database or zone. This means after is apex" <<endl;
2373 after = zonename;
2374 break;
2375 }
2376 }
2377 // go back to where we were
2378 while (skips--)
2379 cursor.prev(key, val);
2380
2381 for (;;) {
2382 int rc = cursor.prev(key, val);
2383 if (rc || co.getDomainID(key.getNoStripHeader<string_view>()) != id) {
2384 // XX I don't think this case can happen
2385 // cout << "We hit the beginning of the zone or database.. now what" << endl;
2386 return false;
2387 }
2388 before = co.getQName(key.getNoStripHeader<string_view>()) + zonename;
2389 LMDBResourceRecord lrr;
2390 serFromString(val.get<string_view>(), lrr);
2391 // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2392 if (co.getQType(key.getNoStripHeader<string_view>()).getCode() && (lrr.auth || co.getQType(key.getNoStripHeader<string_view>()) == QType::NS))
2393 break;
2394 // cout << "Oops, that was wrong, go back one more"<<endl;
2395 }
2396
2397 return true;
2398 }
2399
2400 bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id, const DNSName& qname, const DNSName& ordername, bool auth, const uint16_t qtype)
2401 {
2402 // cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2403 shared_ptr<RecordsRWTransaction> txn;
2404 bool needCommit = false;
2405 if (d_rwtxn && d_transactiondomainid == domain_id) {
2406 txn = d_rwtxn;
2407 // cout<<"Reusing open transaction"<<endl;
2408 }
2409 else {
2410 // cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2411 txn = getRecordsRWTransaction(domain_id);
2412 needCommit = true;
2413 }
2414
2415 DomainInfo di;
2416 if (!d_tdomains->getROTransaction().get(domain_id, di)) {
2417 // cout<<"Could not find domain_id "<<domain_id <<endl;
2418 return false;
2419 }
2420
2421 DNSName rel = qname.makeRelative(di.zone);
2422
2423 compoundOrdername co;
2424 string matchkey = co(domain_id, rel);
2425
2426 auto cursor = txn->txn->getCursor(txn->db->dbi);
2427 MDBOutVal key, val;
2428 if (cursor.lower_bound(matchkey, key, val)) {
2429 // cout << "Could not find anything"<<endl;
2430 return false;
2431 }
2432
2433 bool hasOrderName = !ordername.empty();
2434 bool needNSEC3 = hasOrderName;
2435
2436 for (; key.getNoStripHeader<StringView>().rfind(matchkey, 0) == 0;) {
2437 vector<LMDBResourceRecord> lrrs;
2438
2439 if (co.getQType(key.getNoStripHeader<StringView>()) != QType::NSEC3) {
2440 serFromString(val.get<StringView>(), lrrs);
2441 bool changed = false;
2442 vector<LMDBResourceRecord> newRRs;
2443 for (auto& lrr : lrrs) {
2444 lrr.qtype = co.getQType(key.getNoStripHeader<StringView>());
2445 if (!needNSEC3 && qtype != QType::ANY) {
2446 needNSEC3 = (lrr.ordername && QType(qtype) != lrr.qtype);
2447 }
2448
2449 if ((qtype == QType::ANY || QType(qtype) == lrr.qtype) && (lrr.ordername != hasOrderName || lrr.auth != auth)) {
2450 lrr.auth = auth;
2451 lrr.ordername = hasOrderName;
2452 changed = true;
2453 }
2454 newRRs.push_back(std::move(lrr));
2455 }
2456 if (changed) {
2457 cursor.put(key, serToString(newRRs));
2458 }
2459 }
2460
2461 if (cursor.next(key, val))
2462 break;
2463 }
2464
2465 bool del = false;
2466 LMDBResourceRecord lrr;
2467 matchkey = co(domain_id, rel, QType::NSEC3);
2468 // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2469 int txngetrc;
2470 if (!(txngetrc = txn->txn->get(txn->db->dbi, matchkey, val))) {
2471 serFromString(val.get<string_view>(), lrr);
2472
2473 if (needNSEC3) {
2474 if (hasOrderName && lrr.content != ordername.toDNSStringLC()) {
2475 del = true;
2476 }
2477 }
2478 else {
2479 del = true;
2480 }
2481 if (del) {
2482 txn->txn->del(txn->db->dbi, co(domain_id, DNSName(lrr.content.c_str(), lrr.content.size(), 0, false), QType::NSEC3));
2483 txn->txn->del(txn->db->dbi, matchkey);
2484 }
2485 }
2486 else {
2487 del = true;
2488 }
2489
2490 if (hasOrderName && del) {
2491 matchkey = co(domain_id, rel, QType::NSEC3);
2492
2493 lrr.ttl = 0;
2494 lrr.auth = 0;
2495 lrr.content = rel.toDNSStringLC();
2496
2497 string str = serToString(lrr);
2498 txn->txn->put(txn->db->dbi, co(domain_id, ordername, QType::NSEC3), str);
2499 lrr.ttl = 1;
2500 lrr.content = ordername.toDNSStringLC();
2501 str = serToString(lrr);
2502 txn->txn->put(txn->db->dbi, matchkey, str); // 2
2503 }
2504
2505 if (needCommit)
2506 txn->txn->commit();
2507 return false;
2508 }
2509
2510 bool LMDBBackend::updateEmptyNonTerminals(uint32_t domain_id, set<DNSName>& insert, set<DNSName>& erase, bool remove)
2511 {
2512 // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2513
2514 bool needCommit = false;
2515 shared_ptr<RecordsRWTransaction> txn;
2516 if (d_rwtxn && d_transactiondomainid == domain_id) {
2517 txn = d_rwtxn;
2518 // cout<<"Reusing open transaction"<<endl;
2519 }
2520 else {
2521 // cout<<"Making a new RW txn for delete domain"<<endl;
2522 txn = getRecordsRWTransaction(domain_id);
2523 needCommit = true;
2524 }
2525
2526 // if remove is set, all ENTs should be removed & nothing else should be done
2527 if (remove) {
2528 deleteDomainRecords(*txn, domain_id, 0);
2529 }
2530 else {
2531 DomainInfo di;
2532 auto rotxn = d_tdomains->getROTransaction();
2533 if (!rotxn.get(domain_id, di)) {
2534 // cout <<"No such domain with id "<<domain_id<<endl;
2535 return false;
2536 }
2537 compoundOrdername co;
2538 for (const auto& n : insert) {
2539 LMDBResourceRecord lrr;
2540 lrr.qname = n.makeRelative(di.zone);
2541 lrr.ttl = 0;
2542 lrr.auth = true;
2543
2544 std::string ser = serToString(lrr);
2545
2546 txn->txn->put(txn->db->dbi, co(domain_id, lrr.qname, 0), ser);
2547
2548 // cout <<" +"<<n<<endl;
2549 }
2550 for (auto n : erase) {
2551 // cout <<" -"<<n<<endl;
2552 n.makeUsRelative(di.zone);
2553 txn->txn->del(txn->db->dbi, co(domain_id, n, 0));
2554 }
2555 }
2556 if (needCommit)
2557 txn->txn->commit();
2558 return false;
2559 }
2560
2561 /* TSIG */
2562 bool LMDBBackend::getTSIGKey(const DNSName& name, DNSName& algorithm, string& content)
2563 {
2564 auto txn = d_ttsig->getROTransaction();
2565 LMDBIDvec ids;
2566 txn.get_multi<0>(name, ids);
2567
2568 TSIGKey key;
2569 for (auto id : ids) {
2570 if (txn.get(id, key)) {
2571 if (algorithm.empty() || algorithm == DNSName(key.algorithm)) {
2572 algorithm = DNSName(key.algorithm);
2573 content = key.key;
2574 }
2575 }
2576 }
2577
2578 return true;
2579 }
2580
2581 // this deletes an old key if it has the same algorithm
2582 bool LMDBBackend::setTSIGKey(const DNSName& name, const DNSName& algorithm, const string& content)
2583 {
2584 auto txn = d_ttsig->getRWTransaction();
2585
2586 LMDBIDvec ids;
2587 txn.get_multi<0>(name, ids);
2588
2589 TSIGKey key;
2590 for (auto id : ids) {
2591 if (txn.get(id, key)) {
2592 if (key.algorithm == algorithm) {
2593 txn.del(id);
2594 }
2595 }
2596 }
2597
2598 TSIGKey tk;
2599 tk.name = name;
2600 tk.algorithm = algorithm;
2601 tk.key = content;
2602
2603 txn.put(tk, 0, d_random_ids);
2604 txn.commit();
2605
2606 return true;
2607 }
2608 bool LMDBBackend::deleteTSIGKey(const DNSName& name)
2609 {
2610 auto txn = d_ttsig->getRWTransaction();
2611
2612 LMDBIDvec ids;
2613 txn.get_multi<0>(name, ids);
2614
2615 TSIGKey key;
2616
2617 for (auto id : ids) {
2618 if (txn.get(id, key)) {
2619 txn.del(id);
2620 }
2621 }
2622 txn.commit();
2623 return true;
2624 }
2625 bool LMDBBackend::getTSIGKeys(std::vector<struct TSIGKey>& keys)
2626 {
2627 auto txn = d_ttsig->getROTransaction();
2628
2629 keys.clear();
2630 for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2631 keys.push_back(*iter);
2632 }
2633 return true;
2634 }
2635
2636 string LMDBBackend::directBackendCmd(const string& query)
2637 {
2638 ostringstream ret, usage;
2639
2640 usage << "info show some information about the database" << endl;
2641 usage << "index check domains check zone<>ID indexes" << endl;
2642 usage << "index refresh domains <ID> refresh index for zone with this ID" << endl;
2643 usage << "index refresh-all domains refresh index for all zones with disconnected indexes" << endl;
2644 vector<string> argv;
2645 stringtok(argv, query);
2646
2647 if (argv.empty()) {
2648 return usage.str();
2649 }
2650
2651 string& cmd = argv[0];
2652
2653 if (cmd == "help") {
2654 return usage.str();
2655 }
2656
2657 if (cmd == "info") {
2658 ret << "shards: " << s_shards << endl;
2659 ret << "schemaversion: " << SCHEMAVERSION << endl;
2660
2661 return ret.str();
2662 }
2663
2664 if (cmd == "index") {
2665 if (argv.size() < 2) {
2666 return "need an index subcommand\n";
2667 }
2668
2669 string& subcmd = argv[1];
2670
2671 if (subcmd == "check" || subcmd == "refresh-all") {
2672 bool refresh = false;
2673
2674 if (subcmd == "refresh-all") {
2675 refresh = true;
2676 }
2677
2678 if (argv.size() < 3) {
2679 return "need an index name\n";
2680 }
2681
2682 if (argv[2] != "domains") {
2683 return "can only check the domains index\n";
2684 }
2685
2686 vector<uint32_t> refreshQueue;
2687
2688 {
2689 auto txn = d_tdomains->getROTransaction();
2690
2691 for (auto iter = txn.begin(); iter != txn.end(); ++iter) {
2692 DomainInfo di = *iter;
2693
2694 auto id = iter.getID();
2695
2696 LMDBIDvec ids;
2697 txn.get_multi<0>(di.zone, ids);
2698
2699 if (ids.size() != 1) {
2700 ret << "ID->zone index has " << id << "->" << di.zone << ", ";
2701
2702 if (ids.empty()) {
2703 ret << "zone->ID index has no entry for " << di.zone << endl;
2704 if (refresh) {
2705 refreshQueue.push_back(id);
2706 }
2707 else {
2708 ret << " suggested remedy: index refresh domains " << id << endl;
2709 }
2710 }
2711 else {
2712 // ids.size() > 1
2713 ret << "zone->ID index has multiple entries for " << di.zone << ": ";
2714 for (auto id_ : ids) {
2715 ret << id_ << " ";
2716 }
2717 ret << endl;
2718 }
2719 }
2720 }
2721 }
2722
2723 if (refresh) {
2724 for (const auto& id : refreshQueue) {
2725 if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
2726 ret << "refreshed " << id << endl;
2727 }
2728 else {
2729 ret << "failed to refresh " << id << endl;
2730 }
2731 }
2732 }
2733 return ret.str();
2734 }
2735 if (subcmd == "refresh") {
2736 // index refresh domains 12345
2737 if (argv.size() < 4) {
2738 return "usage: index refresh domains <ID>\n";
2739 }
2740
2741 if (argv[2] != "domains") {
2742 return "can only refresh in the domains index\n";
2743 }
2744
2745 uint32_t id = 0;
2746
2747 try {
2748 id = pdns::checked_stoi<uint32_t>(argv[3]);
2749 }
2750 catch (const std::out_of_range& e) {
2751 return "ID out of range\n";
2752 }
2753
2754 if (genChangeDomain(id, [](DomainInfo& /* di */) {})) {
2755 ret << "refreshed" << endl;
2756 }
2757 else {
2758 ret << "failed" << endl;
2759 }
2760 return ret.str();
2761 }
2762 }
2763
2764 return "unknown lmdbbackend command\n";
2765 }
2766
2767 class LMDBFactory : public BackendFactory
2768 {
2769 public:
2770 LMDBFactory() :
2771 BackendFactory("lmdb") {}
2772 void declareArguments(const string& suffix = "") override
2773 {
2774 declare(suffix, "filename", "Filename for lmdb", "./pdns.lmdb");
2775 declare(suffix, "sync-mode", "Synchronisation mode: nosync, nometasync, mapasync, sync", "mapasync");
2776 // there just is no room for more on 32 bit
2777 declare(suffix, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
2778 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));
2779 declare(suffix, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
2780 declare(suffix, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
2781 declare(suffix, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
2782 declare(suffix, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
2783 }
2784 DNSBackend* make(const string& suffix = "") override
2785 {
2786 return new LMDBBackend(suffix);
2787 }
2788 };
2789
2790 /* THIRD PART */
2791
2792 class LMDBLoader
2793 {
2794 public:
2795 LMDBLoader()
2796 {
2797 BackendMakers().report(new LMDBFactory);
2798 g_log << Logger::Info << "[lmdbbackend] This is the lmdb backend version " VERSION
2799 #ifndef REPRODUCIBLE
2800 << " (" __DATE__ " " __TIME__ ")"
2801 #endif
2802 << " reporting" << endl;
2803 }
2804 };
2805
2806 static LMDBLoader randomLoader;