2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
23 #include "ext/lmdb-safe/lmdb-safe.hh"
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>
49 #include <boost/iostreams/device/back_inserter.hpp>
52 #include <systemd/sd-daemon.h>
58 #include "lmdbbackend.hh"
60 #define SCHEMAVERSION 5
62 // List the class version here. Default is 0
63 BOOST_CLASS_VERSION(LMDBBackend::KeyDataDB
, 1)
64 BOOST_CLASS_VERSION(DomainInfo
, 1)
66 static bool s_first
= true;
67 static int s_shards
= 0;
68 static std::mutex s_lmdbStartupLock
;
70 std::pair
<uint32_t, uint32_t> LMDBBackend::getSchemaVersionAndShards(std::string
& filename
)
72 // cerr << "getting schema version for path " << filename << endl;
74 uint32_t schemaversion
;
77 MDB_env
* env
= nullptr;
79 if ((rc
= mdb_env_create(&env
)) != 0) {
80 throw std::runtime_error("mdb_env_create failed");
83 if ((rc
= mdb_env_set_mapsize(env
, 0)) != 0) {
84 throw std::runtime_error("mdb_env_set_mapsize failed");
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}
89 throw std::runtime_error("mdb_env_set_maxdbs failed");
92 if ((rc
= mdb_env_open(env
, filename
.c_str(), MDB_NOSUBDIR
| MDB_RDONLY
, 0600)) != 0) {
94 // we don't have a database yet! report schema 0, with 0 shards
98 throw std::runtime_error("mdb_env_open failed");
101 MDB_txn
* txn
= nullptr;
103 if ((rc
= mdb_txn_begin(env
, NULL
, MDB_RDONLY
, &txn
)) != 0) {
105 throw std::runtime_error("mdb_txn_begin failed");
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
120 throw std::runtime_error("mdb_dbi_open failed");
125 key
.mv_data
= (char*)"schemaversion";
126 key
.mv_size
= strlen((char*)key
.mv_data
);
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
137 throw std::runtime_error("mdb_get pdns.schemaversion failed");
140 if (data
.mv_size
== 4) {
141 // schemaversion is < 5 and is stored in 32 bits, in host order
143 memcpy(&schemaversion
, data
.mv_data
, data
.mv_size
);
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
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
);
154 throw std::runtime_error("pdns.schemaversion had unexpected size");
159 key
.mv_data
= (char*)"shards";
160 key
.mv_size
= strlen((char*)key
.mv_data
);
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
;
170 throw std::runtime_error("mdb_get pdns.shards failed");
173 if (data
.mv_size
== 4) {
174 // 'shards' is stored in 32 bits, in host order
176 memcpy(&shards
, data
.mv_data
, data
.mv_size
);
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
);
184 throw std::runtime_error("pdns.shards had unexpected size");
190 return {schemaversion
, shards
};
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
)
198 // FIXME: clear out target dbi first
200 std::string
header(LMDBLS::LS_MIN_HEADER_SIZE
, '\0');
205 if ((rc
= mdb_cursor_open(txn
, sdbi
, &cur
)) != 0) {
206 throw std::runtime_error("mdb_cursur_open failed");
211 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_FIRST
);
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
);
217 std::string stdata
= header
+ sdata
;
219 // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
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();
229 if ((rc
= mdb_put(txn
, tdbi
, &tkey
, &tdata
, 0)) != 0) {
230 throw std::runtime_error("mdb_put failed");
233 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_NEXT
);
235 if (rc
!= MDB_NOTFOUND
) {
236 cerr
<< "rc=" << rc
<< endl
;
237 throw std::runtime_error("error while iterating dbi");
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
)
246 // FIXME: clear out target dbi first
248 std::string
header(LMDBLS::LS_MIN_HEADER_SIZE
, '\0');
253 if ((rc
= mdb_cursor_open(txn
, sdbi
, &cur
)) != 0) {
254 throw std::runtime_error("mdb_cursur_open failed");
259 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_FIRST
);
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
);
265 std::string stdata
= header
+ sdata
;
269 if (key
.mv_size
!= sizeof(uint32_t)) {
270 throw std::runtime_error("got non-uint32_t key in TypedDBI");
273 memcpy(&id
, key
.mv_data
, sizeof(uint32_t));
277 // cerr<<"got key="<<makeHexDump(skey)<<", data="<<makeHexDump(sdata)<<", sdata="<<makeHexDump(stdata)<<endl;
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();
287 if ((rc
= mdb_put(txn
, tdbi
, &tkey
, &tdata
, 0)) != 0) {
288 throw std::runtime_error("mdb_put failed");
291 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_NEXT
);
293 if (rc
!= MDB_NOTFOUND
) {
294 cerr
<< "rc=" << rc
<< endl
;
295 throw std::runtime_error("error while iterating dbi");
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
)
304 // FIXME: clear out target dbi first
306 std::string
header(LMDBLS::LS_MIN_HEADER_SIZE
, '\0');
311 if ((rc
= mdb_cursor_open(txn
, sdbi
, &cur
)) != 0) {
312 throw std::runtime_error("mdb_cursur_open failed");
317 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_FIRST
);
320 std::string
lenprefix(sizeof(uint16_t), '\0');
321 std::string
skey((char*)key
.mv_data
, key
.mv_size
);
325 if (data
.mv_size
!= sizeof(uint32_t)) {
326 throw std::runtime_error("got non-uint32_t ID value in IndexDBI");
329 memcpy((void*)&id
, data
.mv_data
, sizeof(uint32_t));
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));
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();
344 if ((rc
= mdb_put(txn
, tdbi
, &tkey
, &tdata
, 0)) != 0) {
345 throw std::runtime_error("mdb_put failed");
348 rc
= mdb_cursor_get(cur
, &key
, &data
, MDB_NEXT
);
350 if (rc
!= MDB_NOTFOUND
) {
351 throw std::runtime_error("error while iterating dbi");
357 bool LMDBBackend::upgradeToSchemav5(std::string
& filename
)
361 auto currentSchemaVersionAndShards
= getSchemaVersionAndShards(filename
);
362 uint32_t currentSchemaVersion
= currentSchemaVersionAndShards
.first
;
363 uint32_t shards
= currentSchemaVersionAndShards
.second
;
365 if (currentSchemaVersion
!= 3 && currentSchemaVersion
!= 4) {
366 throw std::runtime_error("upgrade to v5 requested but current schema is not v3 or v4, stopping");
369 MDB_env
* env
= nullptr;
371 if ((rc
= mdb_env_create(&env
)) != 0) {
372 throw std::runtime_error("mdb_env_create failed");
375 if ((rc
= mdb_env_set_maxdbs(env
, 20)) != 0) {
377 throw std::runtime_error("mdb_env_set_maxdbs failed");
380 if ((rc
= mdb_env_open(env
, filename
.c_str(), MDB_NOSUBDIR
, 0600)) != 0) {
382 throw std::runtime_error("mdb_env_open failed");
385 MDB_txn
* txn
= nullptr;
387 if ((rc
= mdb_txn_begin(env
, NULL
, 0, &txn
)) != 0) {
389 throw std::runtime_error("mdb_txn_begin failed");
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=.
396 sd_notify(0, "EXTEND_TIMEOUT_USEC=86400000000");
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
;
410 std::cerr
<< "migrating shard " << shardfile
<< std::endl
;
411 MDB_env
* shenv
= nullptr;
413 if ((rc
= mdb_env_create(&shenv
)) != 0) {
414 throw std::runtime_error("mdb_env_create failed");
417 if ((rc
= mdb_env_set_maxdbs(shenv
, 8)) != 0) {
419 throw std::runtime_error("mdb_env_set_maxdbs failed");
422 if ((rc
= mdb_env_open(shenv
, shardfile
.c_str(), MDB_NOSUBDIR
, 0600)) != 0) {
424 throw std::runtime_error("mdb_env_open failed");
427 MDB_txn
* shtxn
= nullptr;
429 if ((rc
= mdb_txn_begin(shenv
, NULL
, 0, &shtxn
)) != 0) {
431 throw std::runtime_error("mdb_txn_begin failed");
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
);
442 mdb_txn_abort(shtxn
);
443 mdb_env_close(shenv
);
444 throw std::runtime_error("mdb_dbi_open shard records failed");
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");
457 copyDBIAndAddLSHeader(shtxn
, shdbi
, shdbi2
);
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");
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
);
473 std::array
<MDB_dbi
, 4> fromtypeddbi
;
474 std::array
<MDB_dbi
, 4> totypeddbi
;
478 for (const std::string dbname
: {"domains", "keydata", "tsig", "metadata"}) {
479 std::cerr
<< "migrating " << dbname
<< std::endl
;
480 std::string tdbname
= dbname
+ "_v5";
482 if ((rc
= mdb_dbi_open(txn
, dbname
.c_str(), 0, &fromtypeddbi
[index
])) != 0) {
485 throw std::runtime_error("mdb_dbi_open typeddbi failed");
488 if ((rc
= mdb_dbi_open(txn
, tdbname
.c_str(), MDB_CREATE
, &totypeddbi
[index
])) != 0) {
489 mdb_dbi_close(env
, fromtypeddbi
[index
]);
492 throw std::runtime_error("mdb_dbi_open typeddbi target failed");
496 copyTypedDBI(txn
, fromtypeddbi
[index
], totypeddbi
[index
]);
498 catch (std::exception
& e
) {
499 mdb_dbi_close(env
, totypeddbi
[index
]);
500 mdb_dbi_close(env
, fromtypeddbi
[index
]);
503 throw std::runtime_error("copyTypedDBI failed");
506 // mdb_dbi_close(env, dbi2);
507 // mdb_dbi_close(env, dbi);
508 std::cerr
<< "migrated " << dbname
<< std::endl
;
513 std::array
<MDB_dbi
, 4> fromindexdbi
;
514 std::array
<MDB_dbi
, 4> toindexdbi
;
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";
523 if ((rc
= mdb_dbi_open(txn
, fdbname
.c_str(), 0, &fromindexdbi
[index
])) != 0) {
526 throw std::runtime_error("mdb_dbi_open indexdbi failed");
529 if ((rc
= mdb_dbi_open(txn
, tdbname
.c_str(), MDB_CREATE
, &toindexdbi
[index
])) != 0) {
530 mdb_dbi_close(env
, fromindexdbi
[index
]);
533 throw std::runtime_error("mdb_dbi_open indexdbi target failed");
537 copyIndexDBI(txn
, fromindexdbi
[index
], toindexdbi
[index
]);
539 catch (std::exception
& e
) {
540 mdb_dbi_close(env
, toindexdbi
[index
]);
541 mdb_dbi_close(env
, fromindexdbi
[index
]);
544 throw std::runtime_error("copyIndexDBI failed");
547 // mdb_dbi_close(env, dbi2);
548 // mdb_dbi_close(env, dbi);
549 std::cerr
<< "migrated " << dbname
<< std::endl
;
556 // finally, migrate the pdns db
557 if ((rc
= mdb_dbi_open(txn
, "pdns", 0, &dbi
)) != 0) {
560 throw std::runtime_error("mdb_dbi_open pdns failed");
565 std::string
header(LMDBLS::LS_MIN_HEADER_SIZE
, '\0');
567 for (const std::string keyname
: {"schemaversion", "shards"}) {
568 cerr
<< "migrating pdns." << keyname
<< endl
;
570 key
.mv_data
= (char*)keyname
.c_str();
571 key
.mv_size
= keyname
.size();
573 if ((rc
= mdb_get(txn
, dbi
, &key
, &data
))) {
574 throw std::runtime_error("mdb_get pdns.shards failed");
579 if (data
.mv_size
!= sizeof(uint32_t)) {
580 throw std::runtime_error("got non-uint32_t key");
583 memcpy((void*)&value
, data
.mv_data
, sizeof(uint32_t));
585 value
= htonl(value
);
586 if (keyname
== "schemaversion") {
590 std::string
sdata((char*)data
.mv_data
, data
.mv_size
);
592 std::string stdata
= header
+ std::string((char*)&value
, sizeof(uint32_t));
597 tdata
.mv_data
= (char*)stdata
.c_str();
598 tdata
.mv_size
= stdata
.size();
600 if ((rc
= mdb_put(txn
, dbi
, &key
, &tdata
, 0)) != 0) {
601 throw std::runtime_error("mdb_put failed");
605 for (const std::string keyname
: {"uuid"}) {
606 cerr
<< "migrating pdns." << keyname
<< endl
;
608 key
.mv_data
= (char*)keyname
.c_str();
609 key
.mv_size
= keyname
.size();
611 if ((rc
= mdb_get(txn
, dbi
, &key
, &data
))) {
612 throw std::runtime_error("mdb_get pdns.shards failed");
615 std::string
sdata((char*)data
.mv_data
, data
.mv_size
);
617 std::string stdata
= header
+ sdata
;
621 tdata
.mv_data
= (char*)stdata
.c_str();
622 tdata
.mv_size
= stdata
.size();
624 if ((rc
= mdb_put(txn
, dbi
, &key
, &tdata
, 0)) != 0) {
625 throw std::runtime_error("mdb_put failed");
629 for (int i
= 0; i
< 4; i
++) {
630 mdb_drop(txn
, fromtypeddbi
[i
], 1);
631 mdb_drop(txn
, fromindexdbi
[i
], 1);
634 cerr
<< "txn commit=" << mdb_txn_commit(txn
) << endl
;
636 for (int i
= 0; i
< 4; i
++) {
637 mdb_dbi_close(env
, totypeddbi
[i
]);
638 mdb_dbi_close(env
, toindexdbi
[i
]);
642 // throw std::runtime_error("migration done");
643 cerr
<< "migration done" << endl
;
648 LMDBBackend::LMDBBackend(const std::string
& suffix
)
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");
655 setArgPrefix("lmdb" + suffix
);
657 string syncMode
= toLower(getArg("sync-mode"));
659 d_random_ids
= mustDo("random-ids");
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")
670 throw std::runtime_error("Unknown sync mode " + syncMode
+ " requested for LMDB backend");
672 uint64_t mapSize
= 0;
674 mapSize
= std::stoll(getArg("map-size"));
676 catch (const std::exception
& e
) {
677 throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e
.what());
680 LMDBLS::s_flag_deleted
= mustDo("flag-deleted");
681 d_handle_dups
= false;
683 if (mustDo("lightning-stream")) {
685 d_handle_dups
= true;
686 LMDBLS::s_flag_deleted
= true;
688 if (atoi(getArg("shards").c_str()) != 1) {
689 throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
696 std::lock_guard
<std::mutex
> l(s_lmdbStartupLock
);
698 auto filename
= getArg("filename");
700 auto currentSchemaVersionAndShards
= getSchemaVersionAndShards(filename
);
701 uint32_t currentSchemaVersion
= currentSchemaVersionAndShards
.first
;
702 // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
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.");
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.");
712 if (currentSchemaVersion
== 0) {
713 // no database is present yet, we can just create them
714 currentSchemaVersion
= 5;
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");
721 currentSchemaVersion
= 5;
724 if (currentSchemaVersion
!= 5) {
725 throw std::runtime_error("Somehow, we are not at schema version 5. Giving up");
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");
733 auto pdnsdbi
= d_tdomains
->getEnv()->openDB("pdns", MDB_CREATE
);
737 auto txn
= d_tdomains
->getEnv()->getRWTransaction();
740 if (!txn
->get(pdnsdbi
, "shards", shards
)) {
741 s_shards
= shards
.get
<uint32_t>();
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"));
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
;
752 s_shards
= atoi(getArg("shards").c_str());
753 txn
->put(pdnsdbi
, "shards", s_shards
);
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
);
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
);
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");
780 d_trecords
.resize(s_shards
);
781 d_dolog
= ::arg().mustDo("query-logging");
786 namespace serialization
789 template <class Archive
>
790 void save(Archive
& ar
, const DNSName
& g
, const unsigned int /* version */)
796 ar
& g
.toDNSStringLC();
800 template <class Archive
>
801 void load(Archive
& ar
, DNSName
& g
, const unsigned int /* version */)
809 g
= DNSName(tmp
.c_str(), tmp
.size(), 0, false);
813 template <class Archive
>
814 void save(Archive
& ar
, const QType
& g
, const unsigned int /* version */)
819 template <class Archive
>
820 void load(Archive
& ar
, QType
& g
, const unsigned int /* version */)
827 template <class Archive
>
828 void save(Archive
& ar
, const DomainInfo
& g
, const unsigned int /* version */)
835 ar
& g
.notified_serial
;
841 template <class Archive
>
842 void load(Archive
& ar
, DomainInfo
& g
, const unsigned int version
)
849 ar
& g
.notified_serial
;
861 template <class Archive
>
862 void serialize(Archive
& ar
, LMDBBackend::DomainMeta
& g
, const unsigned int /* version */)
864 ar
& g
.domain
& g
.key
& g
.value
;
867 template <class Archive
>
868 void save(Archive
& ar
, const LMDBBackend::KeyDataDB
& g
, const unsigned int /* version */)
870 ar
& g
.domain
& g
.content
& g
.flags
& g
.active
& g
.published
;
873 template <class Archive
>
874 void load(Archive
& ar
, LMDBBackend::KeyDataDB
& g
, const unsigned int version
)
876 ar
& g
.domain
& g
.content
& g
.flags
& g
.active
;
885 template <class Archive
>
886 void serialize(Archive
& ar
, TSIGKey
& g
, const unsigned int /* version */)
889 ar
& g
.algorithm
; // this is the ordername
893 } // namespace serialization
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
);
903 std::string
serToString(const LMDBBackend::LMDBResourceRecord
& lrr
)
906 uint16_t len
= lrr
.content
.length();
907 ret
.reserve(2 + len
+ 7);
909 ret
.assign((const char*)&len
, 2);
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
);
919 std::string
serToString(const vector
<LMDBBackend::LMDBResourceRecord
>& lrrs
)
922 for (const auto& lrr
: lrrs
) {
923 ret
+= serToString(lrr
);
928 static inline size_t serOneRRFromString(const string_view
& str
, LMDBBackend::LMDBResourceRecord
& lrr
)
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();
943 void serFromString(const string_view
& str
, LMDBBackend::LMDBResourceRecord
& lrr
)
945 serOneRRFromString(str
, lrr
);
949 void serFromString(const string_view
& str
, vector
<LMDBBackend::LMDBResourceRecord
>& lrrs
)
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
);
960 static std::string
serializeContent(uint16_t qtype
, const DNSName
& domain
, const std::string
& content
)
962 auto drc
= DNSRecordContent::make(qtype
, QClass::IN
, content
);
963 return drc
->serialize(domain
, false);
966 static std::shared_ptr
<DNSRecordContent
> deserializeContentZR(uint16_t qtype
, const DNSName
& qname
, const std::string
& content
)
968 if (qtype
== QType::A
&& content
.size() == 4) {
969 return std::make_shared
<ARecordContent
>(*((uint32_t*)content
.c_str()));
971 return DNSRecordContent::deserialize(qname
, qtype
, content
);
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.
977 The index we use is "zoneid,canonical relative name". This index is also used
980 Note - domain_id, name and type are ONLY present on the index!
983 #if BOOST_VERSION >= 106100
984 #define StringView string_view
986 #define StringView string
989 void LMDBBackend::deleteDomainRecords(RecordsRWTransaction
& txn
, uint32_t domain_id
, uint16_t qtype
)
991 compoundOrdername co
;
992 string match
= co(domain_id
);
994 auto cursor
= txn
.txn
->getCursor(txn
.db
->dbi
);
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
)
1001 if (cursor
.next(key
, val
))
1007 /* Here's the complicated story. Other backends have just one transaction, which is either
1010 You can't call feedRecord without a transaction started with startTransaction.
1012 However, other functions can be called after startTransaction() or without startTransaction()
1013 (like updateDNSSECOrderNameAndAuth)
1019 bool LMDBBackend::startTransaction(const DNSName
& domain
, int domain_id
)
1021 // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1022 int real_id
= domain_id
;
1024 auto rotxn
= d_tdomains
->getROTransaction();
1026 real_id
= rotxn
.get
<0>(domain
, di
);
1027 // cout<<"real_id = "<<real_id << endl;
1032 throw DBException("Attempt to start a transaction while one was open already");
1034 d_rwtxn
= getRecordsRWTransaction(real_id
);
1036 d_transactiondomain
= domain
;
1037 d_transactiondomainid
= real_id
;
1038 if (domain_id
>= 0) {
1039 deleteDomainRecords(*d_rwtxn
, domain_id
);
1045 bool LMDBBackend::commitTransaction()
1047 // cout<<"Commit transaction" <<endl;
1049 throw DBException("Attempt to commit a transaction while there isn't one open");
1052 d_rwtxn
->txn
->commit();
1057 bool LMDBBackend::abortTransaction()
1059 // cout<<"Abort transaction"<<endl;
1061 throw DBException("Attempt to abort a transaction while there isn't one open");
1064 d_rwtxn
->txn
->abort();
1070 // d_rwtxn must be set here
1071 bool LMDBBackend::feedRecord(const DNSResourceRecord
& r
, const DNSName
& ordername
, bool ordernameIsNSEC3
)
1073 LMDBResourceRecord
lrr(r
);
1074 lrr
.qname
.makeUsRelative(d_transactiondomain
);
1075 lrr
.content
= serializeContent(lrr
.qtype
.getCode(), r
.qname
, lrr
.content
);
1077 compoundOrdername co
;
1078 string matchName
= co(lrr
.domain_id
, lrr
.qname
, lrr
.qtype
.getCode());
1082 if (!d_rwtxn
->txn
->get(d_rwtxn
->db
->dbi
, matchName
, _rrs
)) {
1083 rrs
= _rrs
.get
<string
>();
1086 rrs
+= serToString(lrr
);
1088 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, matchName
, rrs
);
1090 if (ordernameIsNSEC3
&& !ordername
.empty()) {
1092 if (d_rwtxn
->txn
->get(d_rwtxn
->db
->dbi
, co(lrr
.domain_id
, lrr
.qname
, QType::NSEC3
), val
)) {
1094 lrr
.content
= lrr
.qname
.toDNSStringLC();
1096 string ser
= serToString(lrr
);
1097 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(lrr
.domain_id
, ordername
, QType::NSEC3
), ser
);
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
);
1108 bool LMDBBackend::feedEnts(int domain_id
, map
<DNSName
, bool>& nonterm
)
1110 LMDBResourceRecord lrr
;
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;
1118 std::string ser
= serToString(lrr
);
1119 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, lrr
.qname
, QType::ENT
), ser
);
1124 bool LMDBBackend::feedEnts3(int domain_id
, const DNSName
& domain
, map
<DNSName
, bool>& nonterm
, const NSEC3PARAMRecordContent
& ns3prc
, bool narrow
)
1128 LMDBResourceRecord lrr
;
1129 compoundOrdername co
;
1130 for (const auto& nt
: nonterm
) {
1131 lrr
.qname
= nt
.first
.makeRelative(domain
);
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
);
1138 if (!narrow
&& lrr
.auth
) {
1139 lrr
.content
= lrr
.qname
.toDNSString();
1141 lrr
.ordername
= false;
1142 ser
= serToString(lrr
);
1144 ordername
= DNSName(toBase32Hex(hashQNameWithSalt(ns3prc
, nt
.first
)));
1145 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, ordername
, QType::NSEC3
), ser
);
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
);
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
)
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
) {
1164 // cout<<"Reusing open transaction"<<endl;
1167 // cout<<"Making a new RW txn for replace rrset"<<endl;
1168 txn
= getRecordsRWTransaction(domain_id
);
1173 if (!d_tdomains
->getROTransaction().get(domain_id
, di
)) {
1177 compoundOrdername co
;
1178 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
1180 string match
= co(domain_id
, qname
.makeRelative(di
.zone
), qt
.getCode());
1181 if (!cursor
.find(match
, key
, val
)) {
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
);
1192 adjustedRRSet
.emplace_back(lrr
);
1194 txn
->txn
->put(txn
->db
->dbi
, match
, serToString(adjustedRRSet
));
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
)
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();
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
)
1213 auto& shard
= d_trecords
[id
% s_shards
];
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
);
1219 auto ret
= std::make_shared
<RecordsRWTransaction
>(shard
.env
->getRWTransaction());
1220 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1225 std::shared_ptr
<LMDBBackend::RecordsROTransaction
> LMDBBackend::getRecordsROTransaction(uint32_t id
, const std::shared_ptr
<LMDBBackend::RecordsRWTransaction
>& rwtxn
)
1227 auto& shard
= d_trecords
[id
% s_shards
];
1230 throw DBException("attempting to start nested transaction without open parent env");
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
);
1238 auto ret
= std::make_shared
<RecordsROTransaction
>(rwtxn
->txn
->getROTransaction());
1239 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1243 auto ret
= std::make_shared
<RecordsROTransaction
>(shard
.env
->getROTransaction());
1244 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1250 // FIXME reinstate soon
1251 bool LMDBBackend::upgradeToSchemav3()
1253 g_log
<< Logger::Warning
<< "Upgrading LMDB schema" << endl
;
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
1262 unixDie("Rename failed during LMDB upgrade");
1265 LMDBBackend::RecordsDB oldShard
, newShard
;
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
);
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();
1279 if (cursor
.first(key
, val
) != 0) {
1285 string_view currentKey
;
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
);
1293 currentKey
= newKey
;
1296 value
+= val
.get
<string
>();
1297 if (cursor
.next(key
, val
) != 0) {
1298 if (value
.size() > 0) {
1299 newTxn
->put(newShard
.dbi
, currentKey
, value
);
1314 bool LMDBBackend::deleteDomain(const DNSName
& domain
)
1317 throw DBException(std::string(__PRETTY_FUNCTION__
) + " called without a transaction");
1320 int transactionDomainId
= d_transactiondomainid
;
1321 DNSName transactionDomain
= d_transactiondomain
;
1327 if (!d_handle_dups
) {
1329 auto txn
= d_tdomains
->getROTransaction();
1332 idvec
.push_back(txn
.get
<0>(domain
, di
));
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();
1341 txn
.get_multi
<0>(domain
, idvec
);
1344 for (auto id
: idvec
) {
1346 startTransaction(domain
, id
);
1348 { // Remove metadata
1349 auto txn
= d_tmeta
->getRWTransaction();
1352 txn
.get_multi
<0>(domain
, ids
);
1354 for (auto& _id
: ids
) {
1361 { // Remove cryptokeys
1362 auto txn
= d_tkdb
->getRWTransaction();
1364 txn
.get_multi
<0>(domain
, ids
);
1366 for (auto _id
: ids
) {
1374 commitTransaction();
1377 auto txn
= d_tdomains
->getRWTransaction();
1382 startTransaction(transactionDomain
, transactionDomainId
);
1387 bool LMDBBackend::list(const DNSName
& target
, int /* id */, bool include_disabled
)
1389 d_includedisabled
= include_disabled
;
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;
1398 // cerr << "Did not find " << target << endl;
1403 d_rotxn
= getRecordsROTransaction(di
.id
, d_rwtxn
);
1404 d_getcursor
= std::make_shared
<MDBROCursor
>(d_rotxn
->txn
->getCursor(d_rotxn
->db
->dbi
));
1406 compoundOrdername co
;
1407 d_matchkey
= co(di
.id
);
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);
1414 d_getcursor
.reset();
1417 d_lookupdomain
= target
;
1419 // Make sure we start with fresh data
1420 d_currentrrset
.clear();
1421 d_currentrrsetpos
= 0;
1426 void LMDBBackend::lookup(const QType
& type
, const DNSName
& qdomain
, int zoneId
, DNSPacket
* /* p */)
1429 g_log
<< Logger::Warning
<< "Got lookup for " << qdomain
<< "|" << type
.toString() << " in zone " << zoneId
<< endl
;
1433 d_includedisabled
= false;
1435 DNSName
hunt(qdomain
);
1438 auto rotxn
= d_tdomains
->getROTransaction();
1441 zoneId
= rotxn
.get
<0>(hunt
, di
);
1442 } while (!zoneId
&& type
!= QType::SOA
&& hunt
.chopOff());
1444 // cout << "Did not find zone for "<< qdomain<<endl;
1445 d_getcursor
.reset();
1450 if (!d_tdomains
->getROTransaction().get(zoneId
, di
)) {
1451 // cout<<"Could not find a zone with id "<<zoneId<<endl;
1452 d_getcursor
.reset();
1458 DNSName relqname
= qdomain
.makeRelative(hunt
);
1459 if (relqname
.empty()) {
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
);
1465 compoundOrdername co
;
1466 d_getcursor
= std::make_shared
<MDBROCursor
>(d_rotxn
->txn
->getCursor(d_rotxn
->db
->dbi
));
1468 if (type
.getCode() == QType::ANY
) {
1469 d_matchkey
= co(zoneId
, relqname
);
1472 d_matchkey
= co(zoneId
, relqname
, type
.getCode());
1475 if (d_getcursor
->lower_bound(d_matchkey
, key
, val
) || key
.getNoStripHeader
<StringView
>().rfind(d_matchkey
, 0) != 0) {
1476 d_getcursor
.reset();
1478 g_log
<< Logger::Warning
<< "Query " << ((long)(void*)this) << ": " << d_dtime
.udiffNoReset() << " us to execute (found nothing)" << endl
;
1484 g_log
<< Logger::Warning
<< "Query " << ((long)(void*)this) << ": " << d_dtime
.udiffNoReset() << " us to execute" << endl
;
1487 d_lookupdomain
= hunt
;
1489 // Make sure we start with fresh data
1490 d_currentrrset
.clear();
1491 d_currentrrsetpos
= 0;
1494 bool LMDBBackend::get(DNSZoneRecord
& zr
)
1497 // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1505 if (d_currentrrset
.empty()) {
1506 d_getcursor
->current(d_currentKey
, d_currentVal
);
1508 key
= d_currentKey
.getNoStripHeader
<string_view
>();
1509 zr
.dr
.d_type
= compoundOrdername::getQType(key
).getCode();
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();
1520 serFromString(d_currentVal
.get
<string_view
>(), d_currentrrset
);
1521 d_currentrrsetpos
= 0;
1524 key
= d_currentKey
.getNoStripHeader
<string_view
>();
1527 const auto& lrr
= d_currentrrset
.at(d_currentrrsetpos
++);
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
));
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();
1547 if (zr
.disabled
&& !d_includedisabled
) {
1551 catch (const std::exception
& e
) {
1552 throw PDNSException(e
.what());
1561 bool LMDBBackend::get(DNSResourceRecord
& rr
)
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
;
1574 rr
.disabled
= zr
.disabled
;
1579 bool LMDBBackend::getSerial(DomainInfo
& di
)
1581 auto txn
= getRecordsROTransaction(di
.id
);
1582 compoundOrdername co
;
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)) {
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
);
1594 return !lrr
.disabled
;
1599 bool LMDBBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
& di
, bool getserial
)
1602 auto txn
= d_tdomains
->getROTransaction();
1603 // auto range = txn.prefix_range<0>(domain);
1605 // bool found = false;
1607 // for (auto& iter = range.first ; iter != range.second; ++iter) {
1609 // di.id = iter.getID();
1610 // di.backend = this;
1616 if (!(di
.id
= txn
.get
<0>(domain
, di
))) {
1630 int LMDBBackend::genChangeDomain(const DNSName
& domain
, const std::function
<void(DomainInfo
&)>& func
)
1632 auto txn
= d_tdomains
->getRWTransaction();
1636 auto id
= txn
.get
<0>(domain
, di
);
1644 int LMDBBackend::genChangeDomain(uint32_t id
, const std::function
<void(DomainInfo
&)>& func
)
1648 auto txn
= d_tdomains
->getRWTransaction();
1650 if (!txn
.get(id
, di
))
1661 bool LMDBBackend::setKind(const DNSName
& domain
, const DomainInfo::DomainKind kind
)
1663 return genChangeDomain(domain
, [kind
](DomainInfo
& di
) {
1668 bool LMDBBackend::setAccount(const DNSName
& domain
, const std::string
& account
)
1670 return genChangeDomain(domain
, [account
](DomainInfo
& di
) {
1671 di
.account
= account
;
1675 bool LMDBBackend::setPrimaries(const DNSName
& domain
, const vector
<ComboAddress
>& primaries
)
1677 return genChangeDomain(domain
, [&primaries
](DomainInfo
& di
) {
1678 di
.primaries
= primaries
;
1682 bool LMDBBackend::createDomain(const DNSName
& domain
, const DomainInfo::DomainKind kind
, const vector
<ComboAddress
>& primaries
, const string
& account
)
1687 auto txn
= d_tdomains
->getRWTransaction();
1688 if (txn
.get
<0>(domain
, di
)) {
1689 throw DBException("Domain '" + domain
.toLogString() + "' exists already");
1694 di
.primaries
= primaries
;
1695 di
.account
= account
;
1697 txn
.put(di
, 0, d_random_ids
);
1704 void LMDBBackend::getAllDomainsFiltered(vector
<DomainInfo
>* domains
, const std::function
<bool(DomainInfo
&)>& allow
)
1706 auto txn
= d_tdomains
->getROTransaction();
1707 if (d_handle_dups
) {
1708 map
<DNSName
, DomainInfo
> zonemap
;
1711 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
1712 DomainInfo di
= *iter
;
1713 di
.id
= iter
.getID();
1716 if (!zonemap
.emplace(di
.zone
, di
).second
) {
1717 dups
.insert(di
.zone
);
1721 for (const auto& zone
: dups
) {
1724 // this get grabs the oldest item if there are duplicates
1725 di
.id
= txn
.get
<0>(zone
, di
);
1728 // .get actually found nothing for us
1733 zonemap
[di
.zone
] = di
;
1736 for (auto& [k
, v
] : zonemap
) {
1738 domains
->push_back(std::move(v
));
1743 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
1744 DomainInfo di
= *iter
;
1745 di
.id
= iter
.getID();
1749 domains
->push_back(di
);
1755 void LMDBBackend::getAllDomains(vector
<DomainInfo
>* domains
, bool /* doSerial */, bool include_disabled
)
1759 getAllDomainsFiltered(domains
, [this, include_disabled
](DomainInfo
& di
) {
1760 if (!getSerial(di
) && !include_disabled
) {
1768 void LMDBBackend::getUnfreshSecondaryInfos(vector
<DomainInfo
>* domains
)
1771 time_t now
= time(0);
1772 LMDBResourceRecord lrr
;
1775 getAllDomainsFiltered(domains
, [this, &lrr
, &st
, &now
, &serial
](DomainInfo
& di
) {
1776 if (!di
.isSecondaryType()) {
1780 auto txn2
= getRecordsROTransaction(di
.id
);
1781 compoundOrdername co
;
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
1789 serial
= ntohl(st
.serial
);
1799 void LMDBBackend::setStale(uint32_t domain_id
)
1801 genChangeDomain(domain_id
, [](DomainInfo
& di
) {
1806 void LMDBBackend::setFresh(uint32_t domain_id
)
1808 genChangeDomain(domain_id
, [](DomainInfo
& di
) {
1809 di
.last_check
= time(nullptr);
1813 void LMDBBackend::getUpdatedPrimaries(vector
<DomainInfo
>& updatedDomains
, std::unordered_set
<DNSName
>& catalogs
, CatalogHashMap
& catalogHashes
)
1817 getAllDomainsFiltered(&(updatedDomains
), [this, &catalogs
, &catalogHashes
, &ci
](DomainInfo
& di
) {
1818 if (!di
.isPrimaryType()) {
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
1828 if (!di
.catalog
.empty()) {
1829 ci
.fromJson(di
.options
, CatalogInfo::CatalogType::Producer
);
1830 ci
.updateHash(catalogHashes
, di
);
1833 if (getSerial(di
) && di
.serial
!= di
.notified_serial
) {
1842 void LMDBBackend::setNotified(uint32_t domain_id
, uint32_t serial
)
1844 genChangeDomain(domain_id
, [serial
](DomainInfo
& di
) {
1845 di
.notified_serial
= serial
;
1849 class getCatalogMembersReturnFalseException
: std::runtime_error
1852 getCatalogMembersReturnFalseException() :
1853 std::runtime_error("getCatalogMembers should return false") {}
1856 bool LMDBBackend::getCatalogMembers(const DNSName
& catalog
, vector
<CatalogInfo
>& members
, CatalogInfo::CatalogType type
)
1858 vector
<DomainInfo
> scratch
;
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
) {
1868 ci
.d_zone
= di
.zone
;
1869 ci
.d_primaries
= di
.primaries
;
1871 ci
.fromJson(di
.options
, type
);
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
;
1876 throw getCatalogMembersReturnFalseException();
1878 members
.emplace_back(ci
);
1883 catch (const getCatalogMembersReturnFalseException
& e
) {
1889 bool LMDBBackend::setOptions(const DNSName
& domain
, const std::string
& options
)
1891 return genChangeDomain(domain
, [options
](DomainInfo
& di
) {
1892 di
.options
= options
;
1896 bool LMDBBackend::setCatalog(const DNSName
& domain
, const DNSName
& catalog
)
1898 return genChangeDomain(domain
, [catalog
](DomainInfo
& di
) {
1899 di
.catalog
= catalog
;
1903 bool LMDBBackend::getAllDomainMetadata(const DNSName
& name
, std::map
<std::string
, std::vector
<std::string
>>& meta
)
1906 auto txn
= d_tmeta
->getROTransaction();
1908 txn
.get_multi
<0>(name
, ids
);
1911 // cerr<<"getAllDomainMetadata start"<<endl;
1912 for (auto id
: ids
) {
1913 if (txn
.get(id
, dm
)) {
1914 meta
[dm
.key
].push_back(dm
.value
);
1920 bool LMDBBackend::setDomainMetadata(const DNSName
& name
, const std::string
& kind
, const std::vector
<std::string
>& meta
)
1922 auto txn
= d_tmeta
->getRWTransaction();
1925 txn
.get_multi
<0>(name
, ids
);
1928 for (auto id
: ids
) {
1929 if (txn
.get(id
, dmeta
)) {
1930 if (dmeta
.key
== kind
) {
1931 // cerr<<"delete"<<endl;
1937 for (const auto& m
: meta
) {
1938 DomainMeta dm
{name
, kind
, m
};
1939 txn
.put(dm
, 0, d_random_ids
);
1945 bool LMDBBackend::getDomainKeys(const DNSName
& name
, std::vector
<KeyData
>& keys
)
1947 auto txn
= d_tkdb
->getROTransaction();
1949 txn
.get_multi
<0>(name
, ids
);
1953 for (auto id
: ids
) {
1954 if (txn
.get(id
, key
)) {
1955 KeyData kd
{key
.content
, id
, key
.flags
, key
.active
, key
.published
};
1963 bool LMDBBackend::removeDomainKey(const DNSName
& name
, unsigned int id
)
1965 auto txn
= d_tkdb
->getRWTransaction();
1967 if (txn
.get(id
, kdb
)) {
1968 if (kdb
.domain
== name
) {
1974 // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
1978 bool LMDBBackend::addDomainKey(const DNSName
& name
, const KeyData
& key
, int64_t& id
)
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
);
1988 bool LMDBBackend::activateDomainKey(const DNSName
& name
, unsigned int id
)
1990 auto txn
= d_tkdb
->getRWTransaction();
1992 if (txn
.get(id
, kdb
)) {
1993 if (kdb
.domain
== name
) {
1994 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
1995 kdbarg
.active
= true;
2002 // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2006 bool LMDBBackend::deactivateDomainKey(const DNSName
& name
, unsigned int id
)
2008 auto txn
= d_tkdb
->getRWTransaction();
2010 if (txn
.get(id
, kdb
)) {
2011 if (kdb
.domain
== name
) {
2012 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2013 kdbarg
.active
= false;
2019 // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2023 bool LMDBBackend::publishDomainKey(const DNSName
& name
, unsigned int id
)
2025 auto txn
= d_tkdb
->getRWTransaction();
2027 if (txn
.get(id
, kdb
)) {
2028 if (kdb
.domain
== name
) {
2029 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2030 kdbarg
.published
= true;
2037 // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2041 bool LMDBBackend::unpublishDomainKey(const DNSName
& name
, unsigned int id
)
2043 auto txn
= d_tkdb
->getRWTransaction();
2045 if (txn
.get(id
, kdb
)) {
2046 if (kdb
.domain
== name
) {
2047 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2048 kdbarg
.published
= false;
2054 // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2058 bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id
, const DNSName
& qname
, DNSName
& unhashed
, DNSName
& before
, DNSName
& after
)
2060 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2063 if (!d_tdomains
->getROTransaction().get(id
, di
)) {
2064 // domain does not exist, tough luck
2067 // cout <<"Zone: "<<di.zone<<endl;
2069 compoundOrdername co
;
2070 auto txn
= getRecordsROTransaction(id
);
2072 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
2075 LMDBResourceRecord lrr
;
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
);
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
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
2095 if (cursor
.prev(key
, val
)) {
2096 // hit beginning of database, again means something is wrong with it
2100 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2101 unhashed
= DNSName(lrr
.content
.c_str(), lrr
.content
.size(), 0, false) + di
.zone
;
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;
2109 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2110 serFromString(val
.get
<StringView
>(), lrr
);
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;
2120 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2121 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2125 // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
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
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;
2140 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2141 serFromString(val
.get
<StringView
>(), lrr
);
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;
2151 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2152 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2157 // cout <<"Going backwards to find 'before'"<<endl;
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
);
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
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
);
2179 cursor
.prev(key
, val
);
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
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
2193 if (cursor
.prev(key
, val
)) {
2194 // hit beginning of database, again means something is wrong with it
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
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
2209 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2210 serFromString(val
.get
<StringView
>(), lrr
);
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;
2221 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2223 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
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
2233 cursor
.next(key
, val
);
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
2245 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2246 serFromString(val
.get
<StringView
>(), lrr
);
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;
2256 // cout << "Next.. "<<endl;
2258 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2260 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2264 // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2265 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2266 serFromString(val
.get
<StringView
>(), lrr
);
2272 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2273 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2277 bool LMDBBackend::getBeforeAndAfterNames(uint32_t id
, const DNSName
& zonenameU
, const DNSName
& qname
, DNSName
& before
, DNSName
& after
)
2279 DNSName zonename
= zonenameU
.makeLowerCase();
2280 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<zonename << ", '"<<qname<<"'"<<endl;
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
);
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
;
2297 // cout << "We were at end of database, but this zone is not there?!"<<endl;
2300 // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<co.getDomainID(key.get<string_view>())<< endl;
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
;
2307 rc
= cursor
.next(key
, val
);
2311 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) == id
&& key
.getNoStripHeader
<StringView
>().rfind(matchkey
, 0) == 0)
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
))
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;
2323 after
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2327 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2328 // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2330 // cout << "Now hunting for previous" << endl;
2333 rc
= cursor
.prev(key
, val
);
2335 // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
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"
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
))
2350 before
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2351 // cout<<"Found: "<< before<<endl;
2355 // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
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;
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
);
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;
2377 // go back to where we were
2379 cursor
.prev(key
, val
);
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;
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
))
2394 // cout << "Oops, that was wrong, go back one more"<<endl;
2400 bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id
, const DNSName
& qname
, const DNSName
& ordername
, bool auth
, const uint16_t qtype
)
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
) {
2407 // cout<<"Reusing open transaction"<<endl;
2410 // cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2411 txn
= getRecordsRWTransaction(domain_id
);
2416 if (!d_tdomains
->getROTransaction().get(domain_id
, di
)) {
2417 // cout<<"Could not find domain_id "<<domain_id <<endl;
2421 DNSName rel
= qname
.makeRelative(di
.zone
);
2423 compoundOrdername co
;
2424 string matchkey
= co(domain_id
, rel
);
2426 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
2428 if (cursor
.lower_bound(matchkey
, key
, val
)) {
2429 // cout << "Could not find anything"<<endl;
2433 bool hasOrderName
= !ordername
.empty();
2434 bool needNSEC3
= hasOrderName
;
2436 for (; key
.getNoStripHeader
<StringView
>().rfind(matchkey
, 0) == 0;) {
2437 vector
<LMDBResourceRecord
> lrrs
;
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
);
2449 if ((qtype
== QType::ANY
|| QType(qtype
) == lrr
.qtype
) && (lrr
.ordername
!= hasOrderName
|| lrr
.auth
!= auth
)) {
2451 lrr
.ordername
= hasOrderName
;
2454 newRRs
.push_back(std::move(lrr
));
2457 cursor
.put(key
, serToString(newRRs
));
2461 if (cursor
.next(key
, val
))
2466 LMDBResourceRecord lrr
;
2467 matchkey
= co(domain_id
, rel
, QType::NSEC3
);
2468 // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2470 if (!(txngetrc
= txn
->txn
->get(txn
->db
->dbi
, matchkey
, val
))) {
2471 serFromString(val
.get
<string_view
>(), lrr
);
2474 if (hasOrderName
&& lrr
.content
!= ordername
.toDNSStringLC()) {
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
);
2490 if (hasOrderName
&& del
) {
2491 matchkey
= co(domain_id
, rel
, QType::NSEC3
);
2495 lrr
.content
= rel
.toDNSStringLC();
2497 string str
= serToString(lrr
);
2498 txn
->txn
->put(txn
->db
->dbi
, co(domain_id
, ordername
, QType::NSEC3
), str
);
2500 lrr
.content
= ordername
.toDNSStringLC();
2501 str
= serToString(lrr
);
2502 txn
->txn
->put(txn
->db
->dbi
, matchkey
, str
); // 2
2510 bool LMDBBackend::updateEmptyNonTerminals(uint32_t domain_id
, set
<DNSName
>& insert
, set
<DNSName
>& erase
, bool remove
)
2512 // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2514 bool needCommit
= false;
2515 shared_ptr
<RecordsRWTransaction
> txn
;
2516 if (d_rwtxn
&& d_transactiondomainid
== domain_id
) {
2518 // cout<<"Reusing open transaction"<<endl;
2521 // cout<<"Making a new RW txn for delete domain"<<endl;
2522 txn
= getRecordsRWTransaction(domain_id
);
2526 // if remove is set, all ENTs should be removed & nothing else should be done
2528 deleteDomainRecords(*txn
, domain_id
, 0);
2532 auto rotxn
= d_tdomains
->getROTransaction();
2533 if (!rotxn
.get(domain_id
, di
)) {
2534 // cout <<"No such domain with id "<<domain_id<<endl;
2537 compoundOrdername co
;
2538 for (const auto& n
: insert
) {
2539 LMDBResourceRecord lrr
;
2540 lrr
.qname
= n
.makeRelative(di
.zone
);
2544 std::string ser
= serToString(lrr
);
2546 txn
->txn
->put(txn
->db
->dbi
, co(domain_id
, lrr
.qname
, 0), ser
);
2548 // cout <<" +"<<n<<endl;
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));
2562 bool LMDBBackend::getTSIGKey(const DNSName
& name
, DNSName
& algorithm
, string
& content
)
2564 auto txn
= d_ttsig
->getROTransaction();
2566 txn
.get_multi
<0>(name
, ids
);
2569 for (auto id
: ids
) {
2570 if (txn
.get(id
, key
)) {
2571 if (algorithm
.empty() || algorithm
== DNSName(key
.algorithm
)) {
2572 algorithm
= DNSName(key
.algorithm
);
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
)
2584 auto txn
= d_ttsig
->getRWTransaction();
2587 txn
.get_multi
<0>(name
, ids
);
2590 for (auto id
: ids
) {
2591 if (txn
.get(id
, key
)) {
2592 if (key
.algorithm
== algorithm
) {
2600 tk
.algorithm
= algorithm
;
2603 txn
.put(tk
, 0, d_random_ids
);
2608 bool LMDBBackend::deleteTSIGKey(const DNSName
& name
)
2610 auto txn
= d_ttsig
->getRWTransaction();
2613 txn
.get_multi
<0>(name
, ids
);
2617 for (auto id
: ids
) {
2618 if (txn
.get(id
, key
)) {
2625 bool LMDBBackend::getTSIGKeys(std::vector
<struct TSIGKey
>& keys
)
2627 auto txn
= d_ttsig
->getROTransaction();
2630 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
2631 keys
.push_back(*iter
);
2636 string
LMDBBackend::directBackendCmd(const string
& query
)
2638 ostringstream ret
, usage
;
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
);
2651 string
& cmd
= argv
[0];
2653 if (cmd
== "help") {
2657 if (cmd
== "info") {
2658 ret
<< "shards: " << s_shards
<< endl
;
2659 ret
<< "schemaversion: " << SCHEMAVERSION
<< endl
;
2664 if (cmd
== "index") {
2665 if (argv
.size() < 2) {
2666 return "need an index subcommand\n";
2669 string
& subcmd
= argv
[1];
2671 if (subcmd
== "check" || subcmd
== "refresh-all") {
2672 bool refresh
= false;
2674 if (subcmd
== "refresh-all") {
2678 if (argv
.size() < 3) {
2679 return "need an index name\n";
2682 if (argv
[2] != "domains") {
2683 return "can only check the domains index\n";
2686 vector
<uint32_t> refreshQueue
;
2689 auto txn
= d_tdomains
->getROTransaction();
2691 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
2692 DomainInfo di
= *iter
;
2694 auto id
= iter
.getID();
2697 txn
.get_multi
<0>(di
.zone
, ids
);
2699 if (ids
.size() != 1) {
2700 ret
<< "ID->zone index has " << id
<< "->" << di
.zone
<< ", ";
2703 ret
<< "zone->ID index has no entry for " << di
.zone
<< endl
;
2705 refreshQueue
.push_back(id
);
2708 ret
<< " suggested remedy: index refresh domains " << id
<< endl
;
2713 ret
<< "zone->ID index has multiple entries for " << di
.zone
<< ": ";
2714 for (auto id_
: ids
) {
2724 for (const auto& id
: refreshQueue
) {
2725 if (genChangeDomain(id
, [](DomainInfo
& /* di */) {})) {
2726 ret
<< "refreshed " << id
<< endl
;
2729 ret
<< "failed to refresh " << id
<< endl
;
2735 if (subcmd
== "refresh") {
2736 // index refresh domains 12345
2737 if (argv
.size() < 4) {
2738 return "usage: index refresh domains <ID>\n";
2741 if (argv
[2] != "domains") {
2742 return "can only refresh in the domains index\n";
2748 id
= pdns::checked_stoi
<uint32_t>(argv
[3]);
2750 catch (const std::out_of_range
& e
) {
2751 return "ID out of range\n";
2754 if (genChangeDomain(id
, [](DomainInfo
& /* di */) {})) {
2755 ret
<< "refreshed" << endl
;
2758 ret
<< "failed" << endl
;
2764 return "unknown lmdbbackend command\n";
2767 class LMDBFactory
: public BackendFactory
2771 BackendFactory("lmdb") {}
2772 void declareArguments(const string
& suffix
= "") override
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");
2784 DNSBackend
* make(const string
& suffix
= "") override
2786 return new LMDBBackend(suffix
);
2797 BackendMakers().report(new LMDBFactory
);
2798 g_log
<< Logger::Info
<< "[lmdbbackend] This is the lmdb backend version " VERSION
2799 #ifndef REPRODUCIBLE
2800 << " (" __DATE__
" " __TIME__
")"
2802 << " reporting" << endl
;
2806 static LMDBLoader randomLoader
;