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
.empty() || syncMode
== "sync")
668 throw std::runtime_error("Unknown sync mode " + syncMode
+ " requested for LMDB backend");
670 uint64_t mapSize
= 0;
672 mapSize
= std::stoll(getArg("map-size"));
674 catch (const std::exception
& e
) {
675 throw std::runtime_error(std::string("Unable to parse the 'map-size' LMDB value: ") + e
.what());
678 LMDBLS::s_flag_deleted
= mustDo("flag-deleted");
679 d_handle_dups
= false;
681 if (mustDo("lightning-stream")) {
683 d_handle_dups
= true;
684 LMDBLS::s_flag_deleted
= true;
686 if (atoi(getArg("shards").c_str()) != 1) {
687 throw std::runtime_error(std::string("running with Lightning Stream support requires shards=1"));
694 std::lock_guard
<std::mutex
> l(s_lmdbStartupLock
);
696 auto filename
= getArg("filename");
698 auto currentSchemaVersionAndShards
= getSchemaVersionAndShards(filename
);
699 uint32_t currentSchemaVersion
= currentSchemaVersionAndShards
.first
;
700 // std::cerr<<"current schema version: "<<currentSchemaVersion<<", shards="<<currentSchemaVersionAndShards.second<<std::endl;
702 if (getArgAsNum("schema-version") != SCHEMAVERSION
) {
703 throw std::runtime_error("This version of the lmdbbackend only supports schema version 5. Configuration demands a lower version. Not starting up.");
706 if (currentSchemaVersion
> 0 && currentSchemaVersion
< 3) {
707 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 if (currentSchemaVersion
== 0) {
711 // no database is present yet, we can just create them
712 currentSchemaVersion
= 5;
715 if (currentSchemaVersion
== 3 || currentSchemaVersion
== 4) {
716 if (!upgradeToSchemav5(filename
)) {
717 throw std::runtime_error("Failed to perform LMDB schema version upgrade from v4 to v5");
719 currentSchemaVersion
= 5;
722 if (currentSchemaVersion
!= 5) {
723 throw std::runtime_error("Somehow, we are not at schema version 5. Giving up");
726 d_tdomains
= std::make_shared
<tdomains_t
>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR
| d_asyncFlag
, 0600, mapSize
), "domains_v5");
727 d_tmeta
= std::make_shared
<tmeta_t
>(d_tdomains
->getEnv(), "metadata_v5");
728 d_tkdb
= std::make_shared
<tkdb_t
>(d_tdomains
->getEnv(), "keydata_v5");
729 d_ttsig
= std::make_shared
<ttsig_t
>(d_tdomains
->getEnv(), "tsig_v5");
731 auto pdnsdbi
= d_tdomains
->getEnv()->openDB("pdns", MDB_CREATE
);
735 auto txn
= d_tdomains
->getEnv()->getRWTransaction();
738 if (!txn
->get(pdnsdbi
, "shards", shards
)) {
739 s_shards
= shards
.get
<uint32_t>();
741 if (mustDo("lightning-stream") && s_shards
!= 1) {
742 throw std::runtime_error(std::string("running with Lightning Stream support enabled requires a database with exactly 1 shard"));
745 if (s_shards
!= atoi(getArg("shards").c_str())) {
746 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
;
750 s_shards
= atoi(getArg("shards").c_str());
751 txn
->put(pdnsdbi
, "shards", s_shards
);
755 if (txn
->get(pdnsdbi
, "uuid", gotuuid
)) {
756 const auto uuid
= getUniqueID();
757 const string
uuids(uuid
.begin(), uuid
.end());
758 txn
->put(pdnsdbi
, "uuid", uuids
);
761 MDBOutVal _schemaversion
;
762 if (txn
->get(pdnsdbi
, "schemaversion", _schemaversion
)) {
763 // our DB is entirely new, so we need to write the schemaversion
764 txn
->put(pdnsdbi
, "schemaversion", currentSchemaVersion
);
773 d_tdomains
= std::make_shared
<tdomains_t
>(getMDBEnv(getArg("filename").c_str(), MDB_NOSUBDIR
| d_asyncFlag
, 0600, mapSize
), "domains_v5");
774 d_tmeta
= std::make_shared
<tmeta_t
>(d_tdomains
->getEnv(), "metadata_v5");
775 d_tkdb
= std::make_shared
<tkdb_t
>(d_tdomains
->getEnv(), "keydata_v5");
776 d_ttsig
= std::make_shared
<ttsig_t
>(d_tdomains
->getEnv(), "tsig_v5");
778 d_trecords
.resize(s_shards
);
779 d_dolog
= ::arg().mustDo("query-logging");
784 namespace serialization
787 template <class Archive
>
788 void save(Archive
& ar
, const DNSName
& g
, const unsigned int /* version */)
794 ar
& g
.toDNSStringLC();
798 template <class Archive
>
799 void load(Archive
& ar
, DNSName
& g
, const unsigned int /* version */)
807 g
= DNSName(tmp
.c_str(), tmp
.size(), 0, false);
811 template <class Archive
>
812 void save(Archive
& ar
, const QType
& g
, const unsigned int /* version */)
817 template <class Archive
>
818 void load(Archive
& ar
, QType
& g
, const unsigned int /* version */)
825 template <class Archive
>
826 void save(Archive
& ar
, const DomainInfo
& g
, const unsigned int /* version */)
833 ar
& g
.notified_serial
;
839 template <class Archive
>
840 void load(Archive
& ar
, DomainInfo
& g
, const unsigned int version
)
847 ar
& g
.notified_serial
;
859 template <class Archive
>
860 void serialize(Archive
& ar
, LMDBBackend::DomainMeta
& g
, const unsigned int /* version */)
862 ar
& g
.domain
& g
.key
& g
.value
;
865 template <class Archive
>
866 void save(Archive
& ar
, const LMDBBackend::KeyDataDB
& g
, const unsigned int /* version */)
868 ar
& g
.domain
& g
.content
& g
.flags
& g
.active
& g
.published
;
871 template <class Archive
>
872 void load(Archive
& ar
, LMDBBackend::KeyDataDB
& g
, const unsigned int version
)
874 ar
& g
.domain
& g
.content
& g
.flags
& g
.active
;
883 template <class Archive
>
884 void serialize(Archive
& ar
, TSIGKey
& g
, const unsigned int /* version */)
887 ar
& g
.algorithm
; // this is the ordername
891 } // namespace serialization
894 BOOST_SERIALIZATION_SPLIT_FREE(DNSName
);
895 BOOST_SERIALIZATION_SPLIT_FREE(QType
);
896 BOOST_SERIALIZATION_SPLIT_FREE(LMDBBackend::KeyDataDB
);
897 BOOST_SERIALIZATION_SPLIT_FREE(DomainInfo
);
898 BOOST_IS_BITWISE_SERIALIZABLE(ComboAddress
);
901 std::string
serToString(const LMDBBackend::LMDBResourceRecord
& lrr
)
904 uint16_t len
= lrr
.content
.length();
905 ret
.reserve(2 + len
+ 7);
907 ret
.assign((const char*)&len
, 2);
909 ret
.append((const char*)&lrr
.ttl
, 4);
910 ret
.append(1, (char)lrr
.auth
);
911 ret
.append(1, (char)lrr
.disabled
);
912 ret
.append(1, (char)lrr
.ordername
);
917 std::string
serToString(const vector
<LMDBBackend::LMDBResourceRecord
>& lrrs
)
920 for (const auto& lrr
: lrrs
) {
921 ret
+= serToString(lrr
);
926 static inline size_t serOneRRFromString(const string_view
& str
, LMDBBackend::LMDBResourceRecord
& lrr
)
929 memcpy(&len
, &str
[0], 2);
930 lrr
.content
.assign(&str
[2], len
); // len bytes
931 memcpy(&lrr
.ttl
, &str
[2] + len
, 4);
932 lrr
.auth
= str
[2 + len
+ 4];
933 lrr
.disabled
= str
[2 + len
+ 4 + 1];
934 lrr
.ordername
= str
[2 + len
+ 4 + 2];
935 lrr
.wildcardname
.clear();
941 void serFromString(const string_view
& str
, LMDBBackend::LMDBResourceRecord
& lrr
)
943 serOneRRFromString(str
, lrr
);
947 void serFromString(const string_view
& str
, vector
<LMDBBackend::LMDBResourceRecord
>& lrrs
)
950 while (str_copy
.size() >= 9) { // minimum length for a record is 10
951 LMDBBackend::LMDBResourceRecord lrr
;
952 auto rrLength
= serOneRRFromString(str_copy
, lrr
);
953 lrrs
.emplace_back(lrr
);
954 str_copy
.remove_prefix(rrLength
);
958 static std::string
serializeContent(uint16_t qtype
, const DNSName
& domain
, const std::string
& content
)
960 auto drc
= DNSRecordContent::make(qtype
, QClass::IN
, content
);
961 return drc
->serialize(domain
, false);
964 static std::shared_ptr
<DNSRecordContent
> deserializeContentZR(uint16_t qtype
, const DNSName
& qname
, const std::string
& content
)
966 if (qtype
== QType::A
&& content
.size() == 4) {
967 return std::make_shared
<ARecordContent
>(*((uint32_t*)content
.c_str()));
969 return DNSRecordContent::deserialize(qname
, qtype
, content
);
972 /* design. If you ask a question without a zone id, we lookup the best
973 zone id for you, and answer from that. This is different than other backends, but I can't see why it would not work.
975 The index we use is "zoneid,canonical relative name". This index is also used
978 Note - domain_id, name and type are ONLY present on the index!
981 #if BOOST_VERSION >= 106100
982 #define StringView string_view
984 #define StringView string
987 void LMDBBackend::deleteDomainRecords(RecordsRWTransaction
& txn
, uint32_t domain_id
, uint16_t qtype
)
989 compoundOrdername co
;
990 string match
= co(domain_id
);
992 auto cursor
= txn
.txn
->getCursor(txn
.db
->dbi
);
994 // cout<<"Match: "<<makeHexDump(match);
995 if (!cursor
.lower_bound(match
, key
, val
)) {
996 while (key
.getNoStripHeader
<StringView
>().rfind(match
, 0) == 0) {
997 if (qtype
== QType::ANY
|| co
.getQType(key
.getNoStripHeader
<StringView
>()) == qtype
)
999 if (cursor
.next(key
, val
))
1005 /* Here's the complicated story. Other backends have just one transaction, which is either
1008 You can't call feedRecord without a transaction started with startTransaction.
1010 However, other functions can be called after startTransaction() or without startTransaction()
1011 (like updateDNSSECOrderNameAndAuth)
1017 bool LMDBBackend::startTransaction(const DNSName
& domain
, int domain_id
)
1019 // cout <<"startTransaction("<<domain<<", "<<domain_id<<")"<<endl;
1020 int real_id
= domain_id
;
1022 auto rotxn
= d_tdomains
->getROTransaction();
1024 real_id
= rotxn
.get
<0>(domain
, di
);
1025 // cout<<"real_id = "<<real_id << endl;
1030 throw DBException("Attempt to start a transaction while one was open already");
1032 d_rwtxn
= getRecordsRWTransaction(real_id
);
1034 d_transactiondomain
= domain
;
1035 d_transactiondomainid
= real_id
;
1036 if (domain_id
>= 0) {
1037 deleteDomainRecords(*d_rwtxn
, domain_id
);
1043 bool LMDBBackend::commitTransaction()
1045 // cout<<"Commit transaction" <<endl;
1047 throw DBException("Attempt to commit a transaction while there isn't one open");
1050 d_rwtxn
->txn
->commit();
1055 bool LMDBBackend::abortTransaction()
1057 // cout<<"Abort transaction"<<endl;
1059 throw DBException("Attempt to abort a transaction while there isn't one open");
1062 d_rwtxn
->txn
->abort();
1068 // d_rwtxn must be set here
1069 bool LMDBBackend::feedRecord(const DNSResourceRecord
& r
, const DNSName
& ordername
, bool ordernameIsNSEC3
)
1071 LMDBResourceRecord
lrr(r
);
1072 lrr
.qname
.makeUsRelative(d_transactiondomain
);
1073 lrr
.content
= serializeContent(lrr
.qtype
.getCode(), r
.qname
, lrr
.content
);
1075 compoundOrdername co
;
1076 string matchName
= co(lrr
.domain_id
, lrr
.qname
, lrr
.qtype
.getCode());
1080 if (!d_rwtxn
->txn
->get(d_rwtxn
->db
->dbi
, matchName
, _rrs
)) {
1081 rrs
= _rrs
.get
<string
>();
1084 rrs
+= serToString(lrr
);
1086 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, matchName
, rrs
);
1088 if (ordernameIsNSEC3
&& !ordername
.empty()) {
1090 if (d_rwtxn
->txn
->get(d_rwtxn
->db
->dbi
, co(lrr
.domain_id
, lrr
.qname
, QType::NSEC3
), val
)) {
1092 lrr
.content
= lrr
.qname
.toDNSStringLC();
1094 string ser
= serToString(lrr
);
1095 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(lrr
.domain_id
, ordername
, QType::NSEC3
), ser
);
1098 lrr
.content
= ordername
.toDNSString();
1099 ser
= serToString(lrr
);
1100 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(lrr
.domain_id
, lrr
.qname
, QType::NSEC3
), ser
);
1106 bool LMDBBackend::feedEnts(int domain_id
, map
<DNSName
, bool>& nonterm
)
1108 LMDBResourceRecord lrr
;
1110 compoundOrdername co
;
1111 for (const auto& nt
: nonterm
) {
1112 lrr
.qname
= nt
.first
.makeRelative(d_transactiondomain
);
1113 lrr
.auth
= nt
.second
;
1114 lrr
.ordername
= true;
1116 std::string ser
= serToString(lrr
);
1117 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, lrr
.qname
, QType::ENT
), ser
);
1122 bool LMDBBackend::feedEnts3(int domain_id
, const DNSName
& domain
, map
<DNSName
, bool>& nonterm
, const NSEC3PARAMRecordContent
& ns3prc
, bool narrow
)
1126 LMDBResourceRecord lrr
;
1127 compoundOrdername co
;
1128 for (const auto& nt
: nonterm
) {
1129 lrr
.qname
= nt
.first
.makeRelative(domain
);
1131 lrr
.auth
= nt
.second
;
1132 lrr
.ordername
= nt
.second
;
1133 ser
= serToString(lrr
);
1134 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, lrr
.qname
, QType::ENT
), ser
);
1136 if (!narrow
&& lrr
.auth
) {
1137 lrr
.content
= lrr
.qname
.toDNSString();
1139 lrr
.ordername
= false;
1140 ser
= serToString(lrr
);
1142 ordername
= DNSName(toBase32Hex(hashQNameWithSalt(ns3prc
, nt
.first
)));
1143 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, ordername
, QType::NSEC3
), ser
);
1146 lrr
.content
= ordername
.toDNSString();
1147 ser
= serToString(lrr
);
1148 d_rwtxn
->txn
->put(d_rwtxn
->db
->dbi
, co(domain_id
, lrr
.qname
, QType::NSEC3
), ser
);
1154 // might be called within a transaction, might also be called alone
1155 bool LMDBBackend::replaceRRSet(uint32_t domain_id
, const DNSName
& qname
, const QType
& qt
, const vector
<DNSResourceRecord
>& rrset
)
1157 // zonk qname/qtype within domain_id (go through qname, check domain_id && qtype)
1158 shared_ptr
<RecordsRWTransaction
> txn
;
1159 bool needCommit
= false;
1160 if (d_rwtxn
&& d_transactiondomainid
== domain_id
) {
1162 // cout<<"Reusing open transaction"<<endl;
1165 // cout<<"Making a new RW txn for replace rrset"<<endl;
1166 txn
= getRecordsRWTransaction(domain_id
);
1171 if (!d_tdomains
->getROTransaction().get(domain_id
, di
)) {
1175 compoundOrdername co
;
1176 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
1178 string match
= co(domain_id
, qname
.makeRelative(di
.zone
), qt
.getCode());
1179 if (!cursor
.find(match
, key
, val
)) {
1183 if (!rrset
.empty()) {
1184 vector
<LMDBResourceRecord
> adjustedRRSet
;
1185 for (const auto& rr
: rrset
) {
1186 LMDBResourceRecord
lrr(rr
);
1187 lrr
.content
= serializeContent(lrr
.qtype
.getCode(), lrr
.qname
, lrr
.content
);
1188 lrr
.qname
.makeUsRelative(di
.zone
);
1190 adjustedRRSet
.emplace_back(lrr
);
1192 txn
->txn
->put(txn
->db
->dbi
, match
, serToString(adjustedRRSet
));
1201 bool LMDBBackend::replaceComments([[maybe_unused
]] const uint32_t domain_id
, [[maybe_unused
]] const DNSName
& qname
, [[maybe_unused
]] const QType
& qt
, const vector
<Comment
>& comments
)
1203 // if the vector is empty, good, that's what we do here (LMDB does not store comments)
1204 // if it's not, report failure
1205 return comments
.empty();
1208 // tempting to templatize these two functions but the pain is not worth it
1209 std::shared_ptr
<LMDBBackend::RecordsRWTransaction
> LMDBBackend::getRecordsRWTransaction(uint32_t id
)
1211 auto& shard
= d_trecords
[id
% s_shards
];
1213 shard
.env
= getMDBEnv((getArg("filename") + "-" + std::to_string(id
% s_shards
)).c_str(),
1214 MDB_NOSUBDIR
| d_asyncFlag
, 0600);
1215 shard
.dbi
= shard
.env
->openDB("records_v5", MDB_CREATE
);
1217 auto ret
= std::make_shared
<RecordsRWTransaction
>(shard
.env
->getRWTransaction());
1218 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1223 std::shared_ptr
<LMDBBackend::RecordsROTransaction
> LMDBBackend::getRecordsROTransaction(uint32_t id
, const std::shared_ptr
<LMDBBackend::RecordsRWTransaction
>& rwtxn
)
1225 auto& shard
= d_trecords
[id
% s_shards
];
1228 throw DBException("attempting to start nested transaction without open parent env");
1230 shard
.env
= getMDBEnv((getArg("filename") + "-" + std::to_string(id
% s_shards
)).c_str(),
1231 MDB_NOSUBDIR
| d_asyncFlag
, 0600);
1232 shard
.dbi
= shard
.env
->openDB("records_v5", MDB_CREATE
);
1236 auto ret
= std::make_shared
<RecordsROTransaction
>(rwtxn
->txn
->getROTransaction());
1237 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1241 auto ret
= std::make_shared
<RecordsROTransaction
>(shard
.env
->getROTransaction());
1242 ret
->db
= std::make_shared
<RecordsDB
>(shard
);
1248 // FIXME reinstate soon
1249 bool LMDBBackend::upgradeToSchemav3()
1251 g_log
<< Logger::Warning
<< "Upgrading LMDB schema" << endl
;
1253 for (auto i
= 0; i
< s_shards
; i
++) {
1254 string filename
= getArg("filename") + "-" + std::to_string(i
);
1255 if (rename(filename
.c_str(), (filename
+ "-old").c_str()) < 0) {
1256 if (errno
== ENOENT
) {
1257 // apparently this shard doesn't exist yet, moving on
1260 unixDie("Rename failed during LMDB upgrade");
1263 LMDBBackend::RecordsDB oldShard
, newShard
;
1265 oldShard
.env
= getMDBEnv((filename
+ "-old").c_str(),
1266 MDB_NOSUBDIR
| d_asyncFlag
, 0600);
1267 oldShard
.dbi
= oldShard
.env
->openDB("records", MDB_CREATE
| MDB_DUPSORT
);
1268 auto txn
= oldShard
.env
->getROTransaction();
1269 auto cursor
= txn
->getROCursor(oldShard
.dbi
);
1271 newShard
.env
= getMDBEnv((filename
).c_str(),
1272 MDB_NOSUBDIR
| d_asyncFlag
, 0600);
1273 newShard
.dbi
= newShard
.env
->openDB("records", MDB_CREATE
);
1274 auto newTxn
= newShard
.env
->getRWTransaction();
1277 if (cursor
.first(key
, val
) != 0) {
1283 string_view currentKey
;
1286 auto newKey
= key
.getNoStripHeader
<string_view
>();
1287 if (currentKey
.compare(newKey
) != 0) {
1288 if (value
.size() > 0) {
1289 newTxn
->put(newShard
.dbi
, currentKey
, value
);
1291 currentKey
= newKey
;
1294 value
+= val
.get
<string
>();
1295 if (cursor
.next(key
, val
) != 0) {
1296 if (value
.size() > 0) {
1297 newTxn
->put(newShard
.dbi
, currentKey
, value
);
1312 bool LMDBBackend::deleteDomain(const DNSName
& domain
)
1315 throw DBException(std::string(__PRETTY_FUNCTION__
) + " called without a transaction");
1318 int transactionDomainId
= d_transactiondomainid
;
1319 DNSName transactionDomain
= d_transactiondomain
;
1325 if (!d_handle_dups
) {
1327 auto txn
= d_tdomains
->getROTransaction();
1330 idvec
.push_back(txn
.get
<0>(domain
, di
));
1333 // this transaction used to be RO.
1334 // it is now RW to narrow a race window between PowerDNS and Lightning Stream
1335 // FIXME: turn the entire delete, including this ID scan, into one RW transaction
1336 // when doing that, first do a short RO check to see if we actually have anything to delete
1337 auto txn
= d_tdomains
->getRWTransaction();
1339 txn
.get_multi
<0>(domain
, idvec
);
1342 for (auto id
: idvec
) {
1344 startTransaction(domain
, id
);
1346 { // Remove metadata
1347 auto txn
= d_tmeta
->getRWTransaction();
1350 txn
.get_multi
<0>(domain
, ids
);
1352 for (auto& _id
: ids
) {
1359 { // Remove cryptokeys
1360 auto txn
= d_tkdb
->getRWTransaction();
1362 txn
.get_multi
<0>(domain
, ids
);
1364 for (auto _id
: ids
) {
1372 commitTransaction();
1375 auto txn
= d_tdomains
->getRWTransaction();
1380 startTransaction(transactionDomain
, transactionDomainId
);
1385 bool LMDBBackend::list(const DNSName
& target
, int /* id */, bool include_disabled
)
1387 d_includedisabled
= include_disabled
;
1391 auto dtxn
= d_tdomains
->getROTransaction();
1392 if ((di
.id
= dtxn
.get
<0>(target
, di
))) {
1393 // cerr << "Found domain " << target << " on domain_id " << di.id << ", list requested " << id << endl;
1396 // cerr << "Did not find " << target << endl;
1401 d_rotxn
= getRecordsROTransaction(di
.id
, d_rwtxn
);
1402 d_getcursor
= std::make_shared
<MDBROCursor
>(d_rotxn
->txn
->getCursor(d_rotxn
->db
->dbi
));
1404 compoundOrdername co
;
1405 d_matchkey
= co(di
.id
);
1408 auto a
= d_getcursor
->lower_bound(d_matchkey
, key
, val
);
1409 auto b0
= key
.getNoStripHeader
<StringView
>();
1410 auto b
= b0
.rfind(d_matchkey
, 0);
1412 d_getcursor
.reset();
1415 d_lookupdomain
= target
;
1417 // Make sure we start with fresh data
1418 d_currentrrset
.clear();
1419 d_currentrrsetpos
= 0;
1424 void LMDBBackend::lookup(const QType
& type
, const DNSName
& qdomain
, int zoneId
, DNSPacket
* /* p */)
1427 g_log
<< Logger::Warning
<< "Got lookup for " << qdomain
<< "|" << type
.toString() << " in zone " << zoneId
<< endl
;
1431 d_includedisabled
= false;
1433 DNSName
hunt(qdomain
);
1436 auto rotxn
= d_tdomains
->getROTransaction();
1439 zoneId
= rotxn
.get
<0>(hunt
, di
);
1440 } while (!zoneId
&& type
!= QType::SOA
&& hunt
.chopOff());
1442 // cout << "Did not find zone for "<< qdomain<<endl;
1443 d_getcursor
.reset();
1448 if (!d_tdomains
->getROTransaction().get(zoneId
, di
)) {
1449 // cout<<"Could not find a zone with id "<<zoneId<<endl;
1450 d_getcursor
.reset();
1456 DNSName relqname
= qdomain
.makeRelative(hunt
);
1457 if (relqname
.empty()) {
1460 // cout<<"get will look for "<<relqname<< " in zone "<<hunt<<" with id "<<zoneId<<" and type "<<type.toString()<<endl;
1461 d_rotxn
= getRecordsROTransaction(zoneId
, d_rwtxn
);
1463 compoundOrdername co
;
1464 d_getcursor
= std::make_shared
<MDBROCursor
>(d_rotxn
->txn
->getCursor(d_rotxn
->db
->dbi
));
1466 if (type
.getCode() == QType::ANY
) {
1467 d_matchkey
= co(zoneId
, relqname
);
1470 d_matchkey
= co(zoneId
, relqname
, type
.getCode());
1473 if (d_getcursor
->lower_bound(d_matchkey
, key
, val
) || key
.getNoStripHeader
<StringView
>().rfind(d_matchkey
, 0) != 0) {
1474 d_getcursor
.reset();
1476 g_log
<< Logger::Warning
<< "Query " << ((long)(void*)this) << ": " << d_dtime
.udiffNoReset() << " us to execute (found nothing)" << endl
;
1482 g_log
<< Logger::Warning
<< "Query " << ((long)(void*)this) << ": " << d_dtime
.udiffNoReset() << " us to execute" << endl
;
1485 d_lookupdomain
= hunt
;
1487 // Make sure we start with fresh data
1488 d_currentrrset
.clear();
1489 d_currentrrsetpos
= 0;
1492 bool LMDBBackend::get(DNSZoneRecord
& zr
)
1495 // std::cerr<<"d_getcursor="<<d_getcursor<<std::endl;
1503 if (d_currentrrset
.empty()) {
1504 d_getcursor
->current(d_currentKey
, d_currentVal
);
1506 key
= d_currentKey
.getNoStripHeader
<string_view
>();
1507 zr
.dr
.d_type
= compoundOrdername::getQType(key
).getCode();
1509 if (zr
.dr
.d_type
== QType::NSEC3
) {
1510 // Hit a magic NSEC3 skipping
1511 if (d_getcursor
->next(d_currentKey
, d_currentVal
) || d_currentKey
.getNoStripHeader
<StringView
>().rfind(d_matchkey
, 0) != 0) {
1512 // cerr<<"resetting d_getcursor 1"<<endl;
1513 d_getcursor
.reset();
1518 serFromString(d_currentVal
.get
<string_view
>(), d_currentrrset
);
1519 d_currentrrsetpos
= 0;
1522 key
= d_currentKey
.getNoStripHeader
<string_view
>();
1525 const auto& lrr
= d_currentrrset
.at(d_currentrrsetpos
++);
1527 zr
.disabled
= lrr
.disabled
;
1528 if (!zr
.disabled
|| d_includedisabled
) {
1529 zr
.dr
.d_name
= compoundOrdername::getQName(key
) + d_lookupdomain
;
1530 zr
.domain_id
= compoundOrdername::getDomainID(key
);
1531 zr
.dr
.d_type
= compoundOrdername::getQType(key
).getCode();
1532 zr
.dr
.d_ttl
= lrr
.ttl
;
1533 zr
.dr
.setContent(deserializeContentZR(zr
.dr
.d_type
, zr
.dr
.d_name
, lrr
.content
));
1537 if (d_currentrrsetpos
>= d_currentrrset
.size()) {
1538 d_currentrrset
.clear(); // will invalidate lrr
1539 if (d_getcursor
->next(d_currentKey
, d_currentVal
) || d_currentKey
.getNoStripHeader
<StringView
>().rfind(d_matchkey
, 0) != 0) {
1540 // cerr<<"resetting d_getcursor 2"<<endl;
1541 d_getcursor
.reset();
1545 if (zr
.disabled
&& !d_includedisabled
) {
1549 catch (const std::exception
& e
) {
1550 throw PDNSException(e
.what());
1559 bool LMDBBackend::get(DNSResourceRecord
& rr
)
1566 rr
.qname
= zr
.dr
.d_name
;
1567 rr
.ttl
= zr
.dr
.d_ttl
;
1568 rr
.qtype
= zr
.dr
.d_type
;
1569 rr
.content
= zr
.dr
.getContent()->getZoneRepresentation(true);
1570 rr
.domain_id
= zr
.domain_id
;
1572 rr
.disabled
= zr
.disabled
;
1577 bool LMDBBackend::getSerial(DomainInfo
& di
)
1579 auto txn
= getRecordsROTransaction(di
.id
);
1580 compoundOrdername co
;
1582 if (!txn
->txn
->get(txn
->db
->dbi
, co(di
.id
, g_rootdnsname
, QType::SOA
), val
)) {
1583 LMDBResourceRecord lrr
;
1584 serFromString(val
.get
<string_view
>(), lrr
);
1585 if (lrr
.content
.size() >= 5 * sizeof(uint32_t)) {
1587 // a SOA has five 32 bit fields, the first of which is the serial
1588 // there are two variable length names before the serial, so we calculate from the back
1589 memcpy(&serial
, &lrr
.content
[lrr
.content
.size() - (5 * sizeof(uint32_t))], sizeof(serial
));
1590 di
.serial
= ntohl(serial
);
1592 return !lrr
.disabled
;
1597 bool LMDBBackend::getDomainInfo(const DNSName
& domain
, DomainInfo
& di
, bool getserial
)
1600 auto txn
= d_tdomains
->getROTransaction();
1601 // auto range = txn.prefix_range<0>(domain);
1603 // bool found = false;
1605 // for (auto& iter = range.first ; iter != range.second; ++iter) {
1607 // di.id = iter.getID();
1608 // di.backend = this;
1614 if (!(di
.id
= txn
.get
<0>(domain
, di
))) {
1628 int LMDBBackend::genChangeDomain(const DNSName
& domain
, const std::function
<void(DomainInfo
&)>& func
)
1630 auto txn
= d_tdomains
->getRWTransaction();
1634 auto id
= txn
.get
<0>(domain
, di
);
1642 int LMDBBackend::genChangeDomain(uint32_t id
, const std::function
<void(DomainInfo
&)>& func
)
1646 auto txn
= d_tdomains
->getRWTransaction();
1648 if (!txn
.get(id
, di
))
1659 bool LMDBBackend::setKind(const DNSName
& domain
, const DomainInfo::DomainKind kind
)
1661 return genChangeDomain(domain
, [kind
](DomainInfo
& di
) {
1666 bool LMDBBackend::setAccount(const DNSName
& domain
, const std::string
& account
)
1668 return genChangeDomain(domain
, [account
](DomainInfo
& di
) {
1669 di
.account
= account
;
1673 bool LMDBBackend::setPrimaries(const DNSName
& domain
, const vector
<ComboAddress
>& primaries
)
1675 return genChangeDomain(domain
, [&primaries
](DomainInfo
& di
) {
1676 di
.primaries
= primaries
;
1680 bool LMDBBackend::createDomain(const DNSName
& domain
, const DomainInfo::DomainKind kind
, const vector
<ComboAddress
>& primaries
, const string
& account
)
1685 auto txn
= d_tdomains
->getRWTransaction();
1686 if (txn
.get
<0>(domain
, di
)) {
1687 throw DBException("Domain '" + domain
.toLogString() + "' exists already");
1692 di
.primaries
= primaries
;
1693 di
.account
= account
;
1695 txn
.put(di
, 0, d_random_ids
);
1702 void LMDBBackend::getAllDomainsFiltered(vector
<DomainInfo
>* domains
, const std::function
<bool(DomainInfo
&)>& allow
)
1704 auto txn
= d_tdomains
->getROTransaction();
1705 if (d_handle_dups
) {
1706 map
<DNSName
, DomainInfo
> zonemap
;
1709 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
1710 DomainInfo di
= *iter
;
1711 di
.id
= iter
.getID();
1714 if (!zonemap
.emplace(di
.zone
, di
).second
) {
1715 dups
.insert(di
.zone
);
1719 for (const auto& zone
: dups
) {
1722 // this get grabs the oldest item if there are duplicates
1723 di
.id
= txn
.get
<0>(zone
, di
);
1726 // .get actually found nothing for us
1731 zonemap
[di
.zone
] = di
;
1734 for (auto& [k
, v
] : zonemap
) {
1736 domains
->push_back(std::move(v
));
1741 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
1742 DomainInfo di
= *iter
;
1743 di
.id
= iter
.getID();
1747 domains
->push_back(di
);
1753 void LMDBBackend::getAllDomains(vector
<DomainInfo
>* domains
, bool /* doSerial */, bool include_disabled
)
1757 getAllDomainsFiltered(domains
, [this, include_disabled
](DomainInfo
& di
) {
1758 if (!getSerial(di
) && !include_disabled
) {
1766 void LMDBBackend::getUnfreshSecondaryInfos(vector
<DomainInfo
>* domains
)
1769 time_t now
= time(0);
1770 LMDBResourceRecord lrr
;
1773 getAllDomainsFiltered(domains
, [this, &lrr
, &st
, &now
, &serial
](DomainInfo
& di
) {
1774 if (!di
.isSecondaryType()) {
1778 auto txn2
= getRecordsROTransaction(di
.id
);
1779 compoundOrdername co
;
1781 if (!txn2
->txn
->get(txn2
->db
->dbi
, co(di
.id
, g_rootdnsname
, QType::SOA
), val
)) {
1782 serFromString(val
.get
<string_view
>(), lrr
);
1783 memcpy(&st
, &lrr
.content
[lrr
.content
.size() - sizeof(soatimes
)], sizeof(soatimes
));
1784 if ((time_t)(di
.last_check
+ ntohl(st
.refresh
)) > now
) { // still fresh
1787 serial
= ntohl(st
.serial
);
1797 void LMDBBackend::setStale(uint32_t domain_id
)
1799 genChangeDomain(domain_id
, [](DomainInfo
& di
) {
1804 void LMDBBackend::setFresh(uint32_t domain_id
)
1806 genChangeDomain(domain_id
, [](DomainInfo
& di
) {
1807 di
.last_check
= time(nullptr);
1811 void LMDBBackend::getUpdatedPrimaries(vector
<DomainInfo
>& updatedDomains
, std::unordered_set
<DNSName
>& catalogs
, CatalogHashMap
& catalogHashes
)
1815 getAllDomainsFiltered(&(updatedDomains
), [this, &catalogs
, &catalogHashes
, &ci
](DomainInfo
& di
) {
1816 if (!di
.isPrimaryType()) {
1820 if (di
.kind
== DomainInfo::Producer
) {
1821 catalogs
.insert(di
.zone
);
1822 catalogHashes
[di
.zone
].process("\0");
1823 return false; // Producer fresness check is performed elsewhere
1826 if (!di
.catalog
.empty()) {
1827 ci
.fromJson(di
.options
, CatalogInfo::CatalogType::Producer
);
1828 ci
.updateHash(catalogHashes
, di
);
1831 if (getSerial(di
) && di
.serial
!= di
.notified_serial
) {
1840 void LMDBBackend::setNotified(uint32_t domain_id
, uint32_t serial
)
1842 genChangeDomain(domain_id
, [serial
](DomainInfo
& di
) {
1843 di
.notified_serial
= serial
;
1847 class getCatalogMembersReturnFalseException
: std::runtime_error
1850 getCatalogMembersReturnFalseException() :
1851 std::runtime_error("getCatalogMembers should return false") {}
1854 bool LMDBBackend::getCatalogMembers(const DNSName
& catalog
, vector
<CatalogInfo
>& members
, CatalogInfo::CatalogType type
)
1856 vector
<DomainInfo
> scratch
;
1859 getAllDomainsFiltered(&scratch
, [&catalog
, &members
, &type
](DomainInfo
& di
) {
1860 if ((type
== CatalogInfo::CatalogType::Producer
&& di
.kind
!= DomainInfo::Primary
) || (type
== CatalogInfo::CatalogType::Consumer
&& di
.kind
!= DomainInfo::Secondary
) || di
.catalog
!= catalog
) {
1866 ci
.d_zone
= di
.zone
;
1867 ci
.d_primaries
= di
.primaries
;
1869 ci
.fromJson(di
.options
, type
);
1871 catch (const std::runtime_error
& e
) {
1872 g_log
<< Logger::Warning
<< __PRETTY_FUNCTION__
<< " options '" << di
.options
<< "' for zone '" << di
.zone
<< "' is no valid JSON: " << e
.what() << endl
;
1874 throw getCatalogMembersReturnFalseException();
1876 members
.emplace_back(ci
);
1881 catch (const getCatalogMembersReturnFalseException
& e
) {
1887 bool LMDBBackend::setOptions(const DNSName
& domain
, const std::string
& options
)
1889 return genChangeDomain(domain
, [options
](DomainInfo
& di
) {
1890 di
.options
= options
;
1894 bool LMDBBackend::setCatalog(const DNSName
& domain
, const DNSName
& catalog
)
1896 return genChangeDomain(domain
, [catalog
](DomainInfo
& di
) {
1897 di
.catalog
= catalog
;
1901 bool LMDBBackend::getAllDomainMetadata(const DNSName
& name
, std::map
<std::string
, std::vector
<std::string
>>& meta
)
1904 auto txn
= d_tmeta
->getROTransaction();
1906 txn
.get_multi
<0>(name
, ids
);
1909 // cerr<<"getAllDomainMetadata start"<<endl;
1910 for (auto id
: ids
) {
1911 if (txn
.get(id
, dm
)) {
1912 meta
[dm
.key
].push_back(dm
.value
);
1918 bool LMDBBackend::setDomainMetadata(const DNSName
& name
, const std::string
& kind
, const std::vector
<std::string
>& meta
)
1920 auto txn
= d_tmeta
->getRWTransaction();
1923 txn
.get_multi
<0>(name
, ids
);
1926 for (auto id
: ids
) {
1927 if (txn
.get(id
, dmeta
)) {
1928 if (dmeta
.key
== kind
) {
1929 // cerr<<"delete"<<endl;
1935 for (const auto& m
: meta
) {
1936 DomainMeta dm
{name
, kind
, m
};
1937 txn
.put(dm
, 0, d_random_ids
);
1943 bool LMDBBackend::getDomainKeys(const DNSName
& name
, std::vector
<KeyData
>& keys
)
1945 auto txn
= d_tkdb
->getROTransaction();
1947 txn
.get_multi
<0>(name
, ids
);
1951 for (auto id
: ids
) {
1952 if (txn
.get(id
, key
)) {
1953 KeyData kd
{key
.content
, id
, key
.flags
, key
.active
, key
.published
};
1961 bool LMDBBackend::removeDomainKey(const DNSName
& name
, unsigned int id
)
1963 auto txn
= d_tkdb
->getRWTransaction();
1965 if (txn
.get(id
, kdb
)) {
1966 if (kdb
.domain
== name
) {
1972 // cout << "??? wanted to remove domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
1976 bool LMDBBackend::addDomainKey(const DNSName
& name
, const KeyData
& key
, int64_t& id
)
1978 auto txn
= d_tkdb
->getRWTransaction();
1979 KeyDataDB kdb
{name
, key
.content
, key
.flags
, key
.active
, key
.published
};
1980 id
= txn
.put(kdb
, 0, d_random_ids
);
1986 bool LMDBBackend::activateDomainKey(const DNSName
& name
, unsigned int id
)
1988 auto txn
= d_tkdb
->getRWTransaction();
1990 if (txn
.get(id
, kdb
)) {
1991 if (kdb
.domain
== name
) {
1992 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
1993 kdbarg
.active
= true;
2000 // cout << "??? wanted to activate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2004 bool LMDBBackend::deactivateDomainKey(const DNSName
& name
, unsigned int id
)
2006 auto txn
= d_tkdb
->getRWTransaction();
2008 if (txn
.get(id
, kdb
)) {
2009 if (kdb
.domain
== name
) {
2010 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2011 kdbarg
.active
= false;
2017 // cout << "??? wanted to deactivate domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2021 bool LMDBBackend::publishDomainKey(const DNSName
& name
, unsigned int id
)
2023 auto txn
= d_tkdb
->getRWTransaction();
2025 if (txn
.get(id
, kdb
)) {
2026 if (kdb
.domain
== name
) {
2027 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2028 kdbarg
.published
= true;
2035 // cout << "??? wanted to hide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2039 bool LMDBBackend::unpublishDomainKey(const DNSName
& name
, unsigned int id
)
2041 auto txn
= d_tkdb
->getRWTransaction();
2043 if (txn
.get(id
, kdb
)) {
2044 if (kdb
.domain
== name
) {
2045 txn
.modify(id
, [](KeyDataDB
& kdbarg
) {
2046 kdbarg
.published
= false;
2052 // cout << "??? wanted to unhide domain key for domain "<<name<<" with id "<<id<<", could not find it"<<endl;
2056 bool LMDBBackend::getBeforeAndAfterNamesAbsolute(uint32_t id
, const DNSName
& qname
, DNSName
& unhashed
, DNSName
& before
, DNSName
& after
)
2058 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<qname << " " << unhashed<<endl;
2061 if (!d_tdomains
->getROTransaction().get(id
, di
)) {
2062 // domain does not exist, tough luck
2065 // cout <<"Zone: "<<di.zone<<endl;
2067 compoundOrdername co
;
2068 auto txn
= getRecordsROTransaction(id
);
2070 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
2073 LMDBResourceRecord lrr
;
2075 string matchkey
= co(id
, qname
, QType::NSEC3
);
2076 if (cursor
.lower_bound(matchkey
, key
, val
)) {
2077 // this is beyond the end of the database
2078 // cout << "Beyond end of database!" << endl;
2079 cursor
.last(key
, val
);
2082 if (co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2083 //cout<<"Last record also not part of this zone!"<<endl;
2084 // this implies something is wrong in the database, nothing we can do
2088 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2089 serFromString(val
.get
<StringView
>(), lrr
);
2090 if (!lrr
.ttl
) // the kind of NSEC3 we need
2093 if (cursor
.prev(key
, val
)) {
2094 // hit beginning of database, again means something is wrong with it
2098 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2099 unhashed
= DNSName(lrr
.content
.c_str(), lrr
.content
.size(), 0, false) + di
.zone
;
2101 // now to find after .. at the beginning of the zone
2102 if (cursor
.lower_bound(co(id
), key
, val
)) {
2103 // cout<<"hit end of zone find when we shouldn't"<<endl;
2107 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2108 serFromString(val
.get
<StringView
>(), lrr
);
2113 if (cursor
.next(key
, val
) || co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2114 // cout<<"hit end of zone or database when we shouldn't"<<endl;
2118 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2119 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2123 // cout<<"Ended up at "<<co.getQName(key.get<StringView>()) <<endl;
2125 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2126 if (before
== qname
) {
2127 // cout << "Ended up on exact right node" << endl;
2128 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2129 // unhashed should be correct now, maybe check?
2130 if (cursor
.next(key
, val
)) {
2131 // xxx should find first hash now
2133 if (cursor
.lower_bound(co(id
), key
, val
)) {
2134 // cout<<"hit end of zone find when we shouldn't for id "<<id<< __LINE__<<endl;
2138 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2139 serFromString(val
.get
<StringView
>(), lrr
);
2144 if (cursor
.next(key
, val
) || co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2145 // cout<<"hit end of zone or database when we shouldn't" << __LINE__<<endl;
2149 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2150 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2155 // cout <<"Going backwards to find 'before'"<<endl;
2158 if (co
.getQName(key
.getNoStripHeader
<StringView
>()).canonCompare(qname
) && co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2159 // cout<<"Potentially stopping traverse at "<< co.getQName(key.get<StringView>()) <<", " << (co.getQName(key.get<StringView>()).canonCompare(qname))<<endl;
2160 // cout<<"qname = "<<qname<<endl;
2161 // cout<<"here = "<<co.getQName(key.get<StringView>())<<endl;
2162 serFromString(val
.get
<StringView
>(), lrr
);
2167 if (cursor
.prev(key
, val
) || co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2168 // cout <<"XXX Hit *beginning* of zone or database"<<endl;
2169 // this can happen, must deal with it
2170 // should now find the last hash of the zone
2172 if (cursor
.lower_bound(co(id
+ 1), key
, val
)) {
2173 // cout << "Could not find the next higher zone, going to the end of the database then"<<endl;
2174 cursor
.last(key
, val
);
2177 cursor
.prev(key
, val
);
2180 if (co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2181 //cout<<"Last record also not part of this zone!"<<endl;
2182 // this implies something is wrong in the database, nothing we can do
2186 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2187 serFromString(val
.get
<StringView
>(), lrr
);
2188 if (!lrr
.ttl
) // the kind of NSEC3 we need
2191 if (cursor
.prev(key
, val
)) {
2192 // hit beginning of database, again means something is wrong with it
2196 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2197 unhashed
= DNSName(lrr
.content
.c_str(), lrr
.content
.size(), 0, false) + di
.zone
;
2198 // cout <<"Should still find 'after'!"<<endl;
2199 // for 'after', we need to find the first hash of this zone
2201 if (cursor
.lower_bound(co(id
), key
, val
)) {
2202 // cout<<"hit end of zone find when we shouldn't"<<endl;
2203 // means database is wrong, nothing we can do
2207 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2208 serFromString(val
.get
<StringView
>(), lrr
);
2213 if (cursor
.next(key
, val
)) {
2214 // means database is wrong, nothing we can do
2215 // cout<<"hit end of zone when we shouldn't 2"<<endl;
2219 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2221 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2226 before
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2227 unhashed
= DNSName(lrr
.content
.c_str(), lrr
.content
.size(), 0, false) + di
.zone
;
2228 // cout<<"Went backwards, found "<<before<<endl;
2229 // return us to starting point
2231 cursor
.next(key
, val
);
2233 // cout<<"Now going forward"<<endl;
2234 for (int count
= 0;; ++count
) {
2235 if ((count
&& cursor
.next(key
, val
)) || co
.getDomainID(key
.getNoStripHeader
<StringView
>()) != id
) {
2236 // cout <<"Hit end of database or zone, finding first hash then in zone "<<id<<endl;
2237 if (cursor
.lower_bound(co(id
), key
, val
)) {
2238 // cout<<"hit end of zone find when we shouldn't"<<endl;
2239 // means database is wrong, nothing we can do
2243 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2244 serFromString(val
.get
<StringView
>(), lrr
);
2249 if (cursor
.next(key
, val
)) {
2250 // means database is wrong, nothing we can do
2251 // cout<<"hit end of zone when we shouldn't 2"<<endl;
2254 // cout << "Next.. "<<endl;
2256 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2258 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2262 // cout<<"After "<<co.getQName(key.get<StringView>()) <<endl;
2263 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) == QType::NSEC3
) {
2264 serFromString(val
.get
<StringView
>(), lrr
);
2270 after
= co
.getQName(key
.getNoStripHeader
<StringView
>());
2271 // cout<<"returning: before="<<before<<", after="<<after<<", unhashed: "<<unhashed<<endl;
2275 bool LMDBBackend::getBeforeAndAfterNames(uint32_t id
, const DNSName
& zonenameU
, const DNSName
& qname
, DNSName
& before
, DNSName
& after
)
2277 DNSName zonename
= zonenameU
.makeLowerCase();
2278 // cout << __PRETTY_FUNCTION__<< ": "<<id <<", "<<zonename << ", '"<<qname<<"'"<<endl;
2280 auto txn
= getRecordsROTransaction(id
);
2281 compoundOrdername co
;
2282 DNSName qname2
= qname
.makeRelative(zonename
);
2283 string matchkey
= co(id
, qname2
);
2284 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
2286 // cout<<"Lower_bound for "<<qname2<<endl;
2287 if (cursor
.lower_bound(matchkey
, key
, val
)) {
2288 // cout << "Hit end of database, bummer"<<endl;
2289 cursor
.last(key
, val
);
2290 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) == id
) {
2291 before
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2295 // cout << "We were at end of database, but this zone is not there?!"<<endl;
2298 // cout<<"Cursor is at "<<co.getQName(key.get<string_view>()) <<", in zone id "<<co.getDomainID(key.get<string_view>())<< endl;
2300 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
2301 // cout << "Had an exact match!"<<endl;
2302 before
= qname2
+ zonename
;
2305 rc
= cursor
.next(key
, val
);
2309 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) == id
&& key
.getNoStripHeader
<StringView
>().rfind(matchkey
, 0) == 0)
2311 LMDBResourceRecord lrr
;
2312 serFromString(val
.get
<StringView
>(), lrr
);
2313 if (co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() && (lrr
.auth
|| co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() == QType::NS
))
2316 if (rc
|| co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2317 // cout << "We hit the end of the zone or database. 'after' is apex" << endl;
2321 after
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2325 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2326 // cout << "Ended up in next zone, 'after' is zonename" <<endl;
2328 // cout << "Now hunting for previous" << endl;
2331 rc
= cursor
.prev(key
, val
);
2333 // cout<<"Reversed into zone, but got not found from lmdb" <<endl;
2337 if (co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2338 // cout<<"Reversed into zone, but found wrong zone id " << co.getDomainID(key.getNoStripHeader<string_view>()) << " != "<<id<<endl;
2339 // "this can't happen"
2342 LMDBResourceRecord lrr
;
2343 serFromString(val
.get
<StringView
>(), lrr
);
2344 if (co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() && (lrr
.auth
|| co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() == QType::NS
))
2348 before
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2349 // cout<<"Found: "<< before<<endl;
2353 // cout <<"We ended up after "<<qname<<", on "<<co.getQName(key.getNoStripHeader<string_view>())<<endl;
2357 LMDBResourceRecord lrr
;
2358 serFromString(val
.get
<StringView
>(), lrr
);
2359 if (co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() && (lrr
.auth
|| co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() == QType::NS
)) {
2360 after
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2361 // cout <<"Found auth ("<<lrr.auth<<") or an NS record "<<after<<", type: "<<co.getQType(key.getNoStripHeader<string_view>()).toString()<<", ttl = "<<lrr.ttl<<endl;
2362 // cout << makeHexDump(val.get<string>()) << endl;
2365 // 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;
2366 int rc
= cursor
.next(key
, val
);
2369 if (rc
|| co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2370 // cout << " oops, hit end of database or zone. This means after is apex" <<endl;
2375 // go back to where we were
2377 cursor
.prev(key
, val
);
2380 int rc
= cursor
.prev(key
, val
);
2381 if (rc
|| co
.getDomainID(key
.getNoStripHeader
<string_view
>()) != id
) {
2382 // XX I don't think this case can happen
2383 // cout << "We hit the beginning of the zone or database.. now what" << endl;
2386 before
= co
.getQName(key
.getNoStripHeader
<string_view
>()) + zonename
;
2387 LMDBResourceRecord lrr
;
2388 serFromString(val
.get
<string_view
>(), lrr
);
2389 // cout<<"And before to "<<before<<", auth = "<<rr.auth<<endl;
2390 if (co
.getQType(key
.getNoStripHeader
<string_view
>()).getCode() && (lrr
.auth
|| co
.getQType(key
.getNoStripHeader
<string_view
>()) == QType::NS
))
2392 // cout << "Oops, that was wrong, go back one more"<<endl;
2398 bool LMDBBackend::updateDNSSECOrderNameAndAuth(uint32_t domain_id
, const DNSName
& qname
, const DNSName
& ordername
, bool auth
, const uint16_t qtype
)
2400 // cout << __PRETTY_FUNCTION__<< ": "<< domain_id <<", '"<<qname <<"', '"<<ordername<<"', "<<auth<< ", " << qtype << endl;
2401 shared_ptr
<RecordsRWTransaction
> txn
;
2402 bool needCommit
= false;
2403 if (d_rwtxn
&& d_transactiondomainid
== domain_id
) {
2405 // cout<<"Reusing open transaction"<<endl;
2408 // cout<<"Making a new RW txn for " << __PRETTY_FUNCTION__ <<endl;
2409 txn
= getRecordsRWTransaction(domain_id
);
2414 if (!d_tdomains
->getROTransaction().get(domain_id
, di
)) {
2415 // cout<<"Could not find domain_id "<<domain_id <<endl;
2419 DNSName rel
= qname
.makeRelative(di
.zone
);
2421 compoundOrdername co
;
2422 string matchkey
= co(domain_id
, rel
);
2424 auto cursor
= txn
->txn
->getCursor(txn
->db
->dbi
);
2426 if (cursor
.lower_bound(matchkey
, key
, val
)) {
2427 // cout << "Could not find anything"<<endl;
2431 bool hasOrderName
= !ordername
.empty();
2432 bool needNSEC3
= hasOrderName
;
2434 for (; key
.getNoStripHeader
<StringView
>().rfind(matchkey
, 0) == 0;) {
2435 vector
<LMDBResourceRecord
> lrrs
;
2437 if (co
.getQType(key
.getNoStripHeader
<StringView
>()) != QType::NSEC3
) {
2438 serFromString(val
.get
<StringView
>(), lrrs
);
2439 bool changed
= false;
2440 vector
<LMDBResourceRecord
> newRRs
;
2441 for (auto& lrr
: lrrs
) {
2442 lrr
.qtype
= co
.getQType(key
.getNoStripHeader
<StringView
>());
2443 if (!needNSEC3
&& qtype
!= QType::ANY
) {
2444 needNSEC3
= (lrr
.ordername
&& QType(qtype
) != lrr
.qtype
);
2447 if ((qtype
== QType::ANY
|| QType(qtype
) == lrr
.qtype
) && (lrr
.ordername
!= hasOrderName
|| lrr
.auth
!= auth
)) {
2449 lrr
.ordername
= hasOrderName
;
2452 newRRs
.push_back(std::move(lrr
));
2455 cursor
.put(key
, serToString(newRRs
));
2459 if (cursor
.next(key
, val
))
2464 LMDBResourceRecord lrr
;
2465 matchkey
= co(domain_id
, rel
, QType::NSEC3
);
2466 // cerr<<"here qname="<<qname<<" ordername="<<ordername<<" qtype="<<qtype<<" matchkey="<<makeHexDump(matchkey)<<endl;
2468 if (!(txngetrc
= txn
->txn
->get(txn
->db
->dbi
, matchkey
, val
))) {
2469 serFromString(val
.get
<string_view
>(), lrr
);
2472 if (hasOrderName
&& lrr
.content
!= ordername
.toDNSStringLC()) {
2480 txn
->txn
->del(txn
->db
->dbi
, co(domain_id
, DNSName(lrr
.content
.c_str(), lrr
.content
.size(), 0, false), QType::NSEC3
));
2481 txn
->txn
->del(txn
->db
->dbi
, matchkey
);
2488 if (hasOrderName
&& del
) {
2489 matchkey
= co(domain_id
, rel
, QType::NSEC3
);
2493 lrr
.content
= rel
.toDNSStringLC();
2495 string str
= serToString(lrr
);
2496 txn
->txn
->put(txn
->db
->dbi
, co(domain_id
, ordername
, QType::NSEC3
), str
);
2498 lrr
.content
= ordername
.toDNSStringLC();
2499 str
= serToString(lrr
);
2500 txn
->txn
->put(txn
->db
->dbi
, matchkey
, str
); // 2
2508 bool LMDBBackend::updateEmptyNonTerminals(uint32_t domain_id
, set
<DNSName
>& insert
, set
<DNSName
>& erase
, bool remove
)
2510 // cout << __PRETTY_FUNCTION__<< ": "<< domain_id << ", insert.size() "<<insert.size()<<", "<<erase.size()<<", " <<remove<<endl;
2512 bool needCommit
= false;
2513 shared_ptr
<RecordsRWTransaction
> txn
;
2514 if (d_rwtxn
&& d_transactiondomainid
== domain_id
) {
2516 // cout<<"Reusing open transaction"<<endl;
2519 // cout<<"Making a new RW txn for delete domain"<<endl;
2520 txn
= getRecordsRWTransaction(domain_id
);
2524 // if remove is set, all ENTs should be removed & nothing else should be done
2526 deleteDomainRecords(*txn
, domain_id
, 0);
2530 auto rotxn
= d_tdomains
->getROTransaction();
2531 if (!rotxn
.get(domain_id
, di
)) {
2532 // cout <<"No such domain with id "<<domain_id<<endl;
2535 compoundOrdername co
;
2536 for (const auto& n
: insert
) {
2537 LMDBResourceRecord lrr
;
2538 lrr
.qname
= n
.makeRelative(di
.zone
);
2542 std::string ser
= serToString(lrr
);
2544 txn
->txn
->put(txn
->db
->dbi
, co(domain_id
, lrr
.qname
, 0), ser
);
2546 // cout <<" +"<<n<<endl;
2548 for (auto n
: erase
) {
2549 // cout <<" -"<<n<<endl;
2550 n
.makeUsRelative(di
.zone
);
2551 txn
->txn
->del(txn
->db
->dbi
, co(domain_id
, n
, 0));
2560 bool LMDBBackend::getTSIGKey(const DNSName
& name
, DNSName
& algorithm
, string
& content
)
2562 auto txn
= d_ttsig
->getROTransaction();
2564 txn
.get_multi
<0>(name
, ids
);
2567 for (auto id
: ids
) {
2568 if (txn
.get(id
, key
)) {
2569 if (algorithm
.empty() || algorithm
== DNSName(key
.algorithm
)) {
2570 algorithm
= DNSName(key
.algorithm
);
2579 // this deletes an old key if it has the same algorithm
2580 bool LMDBBackend::setTSIGKey(const DNSName
& name
, const DNSName
& algorithm
, const string
& content
)
2582 auto txn
= d_ttsig
->getRWTransaction();
2585 txn
.get_multi
<0>(name
, ids
);
2588 for (auto id
: ids
) {
2589 if (txn
.get(id
, key
)) {
2590 if (key
.algorithm
== algorithm
) {
2598 tk
.algorithm
= algorithm
;
2601 txn
.put(tk
, 0, d_random_ids
);
2606 bool LMDBBackend::deleteTSIGKey(const DNSName
& name
)
2608 auto txn
= d_ttsig
->getRWTransaction();
2611 txn
.get_multi
<0>(name
, ids
);
2615 for (auto id
: ids
) {
2616 if (txn
.get(id
, key
)) {
2623 bool LMDBBackend::getTSIGKeys(std::vector
<struct TSIGKey
>& keys
)
2625 auto txn
= d_ttsig
->getROTransaction();
2628 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
2629 keys
.push_back(*iter
);
2634 string
LMDBBackend::directBackendCmd(const string
& query
)
2636 ostringstream ret
, usage
;
2638 usage
<< "info show some information about the database" << endl
;
2639 usage
<< "index check domains check zone<>ID indexes" << endl
;
2640 usage
<< "index refresh domains <ID> refresh index for zone with this ID" << endl
;
2641 usage
<< "index refresh-all domains refresh index for all zones with disconnected indexes" << endl
;
2642 vector
<string
> argv
;
2643 stringtok(argv
, query
);
2649 string
& cmd
= argv
[0];
2651 if (cmd
== "help") {
2655 if (cmd
== "info") {
2656 ret
<< "shards: " << s_shards
<< endl
;
2657 ret
<< "schemaversion: " << SCHEMAVERSION
<< endl
;
2662 if (cmd
== "index") {
2663 if (argv
.size() < 2) {
2664 return "need an index subcommand\n";
2667 string
& subcmd
= argv
[1];
2669 if (subcmd
== "check" || subcmd
== "refresh-all") {
2670 bool refresh
= false;
2672 if (subcmd
== "refresh-all") {
2676 if (argv
.size() < 3) {
2677 return "need an index name\n";
2680 if (argv
[2] != "domains") {
2681 return "can only check the domains index\n";
2684 vector
<uint32_t> refreshQueue
;
2687 auto txn
= d_tdomains
->getROTransaction();
2689 for (auto iter
= txn
.begin(); iter
!= txn
.end(); ++iter
) {
2690 DomainInfo di
= *iter
;
2692 auto id
= iter
.getID();
2695 txn
.get_multi
<0>(di
.zone
, ids
);
2697 if (ids
.size() != 1) {
2698 ret
<< "ID->zone index has " << id
<< "->" << di
.zone
<< ", ";
2701 ret
<< "zone->ID index has no entry for " << di
.zone
<< endl
;
2703 refreshQueue
.push_back(id
);
2706 ret
<< " suggested remedy: index refresh domains " << id
<< endl
;
2711 ret
<< "zone->ID index has multiple entries for " << di
.zone
<< ": ";
2712 for (auto id_
: ids
) {
2722 for (const auto& id
: refreshQueue
) {
2723 if (genChangeDomain(id
, [](DomainInfo
& /* di */) {})) {
2724 ret
<< "refreshed " << id
<< endl
;
2727 ret
<< "failed to refresh " << id
<< endl
;
2733 if (subcmd
== "refresh") {
2734 // index refresh domains 12345
2735 if (argv
.size() < 4) {
2736 return "usage: index refresh domains <ID>\n";
2739 if (argv
[2] != "domains") {
2740 return "can only refresh in the domains index\n";
2746 id
= pdns::checked_stoi
<uint32_t>(argv
[3]);
2748 catch (const std::out_of_range
& e
) {
2749 return "ID out of range\n";
2752 if (genChangeDomain(id
, [](DomainInfo
& /* di */) {})) {
2753 ret
<< "refreshed" << endl
;
2756 ret
<< "failed" << endl
;
2762 return "unknown lmdbbackend command\n";
2765 class LMDBFactory
: public BackendFactory
2769 BackendFactory("lmdb") {}
2770 void declareArguments(const string
& suffix
= "") override
2772 declare(suffix
, "filename", "Filename for lmdb", "./pdns.lmdb");
2773 declare(suffix
, "sync-mode", "Synchronisation mode: nosync, nometasync, sync", "sync");
2774 // there just is no room for more on 32 bit
2775 declare(suffix
, "shards", "Records database will be split into this number of shards", (sizeof(void*) == 4) ? "2" : "64");
2776 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
));
2777 declare(suffix
, "random-ids", "Numeric IDs inside the database are generated randomly instead of sequentially", "no");
2778 declare(suffix
, "map-size", "LMDB map size in megabytes", (sizeof(void*) == 4) ? "100" : "16000");
2779 declare(suffix
, "flag-deleted", "Flag entries on deletion instead of deleting them", "no");
2780 declare(suffix
, "lightning-stream", "Run in Lightning Stream compatible mode", "no");
2782 DNSBackend
* make(const string
& suffix
= "") override
2784 return new LMDBBackend(suffix
);
2795 BackendMakers().report(new LMDBFactory
);
2796 g_log
<< Logger::Info
<< "[lmdbbackend] This is the lmdb backend version " VERSION
2797 #ifndef REPRODUCIBLE
2798 << " (" __DATE__
" " __TIME__
")"
2800 << " reporting" << endl
;
2804 static LMDBLoader randomLoader
;