diff options
Diffstat (limited to 'src/knot/zone/timers.c')
-rw-r--r-- | src/knot/zone/timers.c | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/src/knot/zone/timers.c b/src/knot/zone/timers.c new file mode 100644 index 0000000..0ff8aed --- /dev/null +++ b/src/knot/zone/timers.c @@ -0,0 +1,288 @@ +/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "knot/zone/timers.h" + +#include "contrib/wire_ctx.h" +#include "libknot/db/db.h" +#include "libknot/db/db_lmdb.h" + +/* + * # Timer database + * + * Timer database stores timestaps of events which need to be retained + * across server restarts. The key in the database is the zone name in + * wire format. The value contains serialized timers. + * + * # Serialization format + * + * The value is a sequence of timers. Each timer consists of the timer + * identifier (1 byte, unsigned integer) and timer value (8 bytes, unsigned + * integer, network order). + * + * For example, the following byte sequence: + * + * 81 00 00 00 00 57 e3 e8 0a 82 00 00 00 00 57 e3 e9 a1 + * + * Encodes the following timers: + * + * last_flush = 1474553866 + * last_refresh = 1474554273 + */ + +/** + * \brief Timer database fields identifiers. + * + * Valid ID starts with '1' in MSB to avoid conflicts with "old timers". + */ +enum timer_id { + TIMER_INVALID = 0, + TIMER_SOA_EXPIRE = 0x80, + TIMER_LAST_FLUSH, + TIMER_LAST_REFRESH, + TIMER_NEXT_REFRESH, + TIMER_LAST_RESALT, + TIMER_NEXT_PARENT_DS_Q +}; + +#define TIMER_COUNT 6 +#define TIMER_SIZE (sizeof(uint8_t) + sizeof(uint64_t)) +#define SERIALIZED_SIZE (TIMER_COUNT * TIMER_SIZE) + +/*! + * \brief Serialize timers into a binary buffer. + */ +static int serialize_timers(const zone_timers_t *timers, uint8_t *data, size_t size) +{ + if (!timers || !data || size != SERIALIZED_SIZE) { + return KNOT_EINVAL; + } + + wire_ctx_t wire = wire_ctx_init(data, size); + + wire_ctx_write_u8(&wire, TIMER_SOA_EXPIRE); + wire_ctx_write_u64(&wire, timers->soa_expire); + wire_ctx_write_u8(&wire, TIMER_LAST_FLUSH); + wire_ctx_write_u64(&wire, timers->last_flush); + wire_ctx_write_u8(&wire, TIMER_LAST_REFRESH); + wire_ctx_write_u64(&wire, timers->last_refresh); + wire_ctx_write_u8(&wire, TIMER_NEXT_REFRESH); + wire_ctx_write_u64(&wire, timers->next_refresh); + wire_ctx_write_u8(&wire, TIMER_LAST_RESALT); + wire_ctx_write_u64(&wire, timers->last_resalt); + wire_ctx_write_u8(&wire, TIMER_NEXT_PARENT_DS_Q); + wire_ctx_write_u64(&wire, timers->next_parent_ds_q); + + assert(wire.error == KNOT_EOK); + assert(wire_ctx_available(&wire) == 0); + + return KNOT_EOK; +} + +/*! + * \brief Deserialize timers from a binary buffer. + * + * \note Unknown timers are ignored. + */ +static int deserialize_timers(zone_timers_t *timers_ptr, + const uint8_t *data, size_t size) +{ + if (!timers_ptr || !data) { + return KNOT_EINVAL; + } + + zone_timers_t timers = { 0 }; + + wire_ctx_t wire = wire_ctx_init_const(data, size); + while (wire_ctx_available(&wire) >= TIMER_SIZE) { + uint8_t id = wire_ctx_read_u8(&wire); + uint64_t value = wire_ctx_read_u64(&wire); + switch (id) { + case TIMER_SOA_EXPIRE: timers.soa_expire = value; break; + case TIMER_LAST_FLUSH: timers.last_flush = value; break; + case TIMER_LAST_REFRESH: timers.last_refresh = value; break; + case TIMER_NEXT_REFRESH: timers.next_refresh = value; break; + case TIMER_LAST_RESALT: timers.last_resalt = value; break; + case TIMER_NEXT_PARENT_DS_Q: timers.next_parent_ds_q = value; break; + default: break; // ignore + } + } + + if (wire_ctx_available(&wire) != 0) { + return KNOT_EMALF; + } + + assert(wire.error == KNOT_EOK); + + *timers_ptr = timers; + return KNOT_EOK; +} + +static int txn_write_timers(knot_db_txn_t *txn, const knot_dname_t *zone, + const zone_timers_t *timers) +{ + uint8_t data[SERIALIZED_SIZE] = { 0 }; + int ret = serialize_timers(timers, data, sizeof(data)); + if (ret != KNOT_EOK) { + return ret; + } + + knot_db_val_t key = { (uint8_t *)zone, knot_dname_size(zone) }; + knot_db_val_t val = { data, sizeof(data) }; + + return knot_db_lmdb_api()->insert(txn, &key, &val, 0); +} + +static int txn_read_timers(knot_db_txn_t *txn, const knot_dname_t *zone, + zone_timers_t *timers) +{ + knot_db_val_t key = { (uint8_t *)zone, knot_dname_size(zone) }; + knot_db_val_t val = { 0 }; + int ret = knot_db_lmdb_api()->find(txn, &key, &val, 0); + if (ret != KNOT_EOK) { + return ret; + } + + return deserialize_timers(timers, val.data, val.len); +} + +int zone_timers_open(const char *path, knot_db_t **db, size_t mapsize) +{ + if (path == NULL || db == NULL) { + return KNOT_EINVAL; + } + + struct knot_db_lmdb_opts opts = KNOT_DB_LMDB_OPTS_INITIALIZER; + opts.mapsize = mapsize; + opts.path = path; + + return knot_db_lmdb_api()->init(db, NULL, &opts); +} + +void zone_timers_close(knot_db_t *db) +{ + if (db == NULL) { + return; + } + + knot_db_lmdb_api()->deinit(db); +} + +int zone_timers_read(knot_db_t *db, const knot_dname_t *zone, + zone_timers_t *timers) +{ + if (!db || !zone || !timers) { + return KNOT_EINVAL; + } + + const knot_db_api_t *db_api = knot_db_lmdb_api(); + assert(db_api); + + knot_db_txn_t txn = { 0 }; + int ret = db_api->txn_begin(db, &txn, KNOT_DB_RDONLY); + if (ret != KNOT_EOK) { + return ret; + } + + ret = txn_read_timers(&txn, zone, timers); + db_api->txn_abort(&txn); + + return ret; +} + +int zone_timers_write_begin(knot_db_t *db, knot_db_txn_t *txn) +{ + memset(txn, 0, sizeof(*txn)); + return knot_db_lmdb_api()->txn_begin(db, txn, KNOT_DB_SORTED); +} + +int zone_timers_write_end(knot_db_txn_t *txn) +{ + return knot_db_lmdb_api()->txn_commit(txn); +} + +int zone_timers_write(knot_db_t *db, const knot_dname_t *zone, + const zone_timers_t *timers, knot_db_txn_t *txn) +{ + if (!zone || !timers || (!db && !txn)) { + return KNOT_EINVAL; + } + + const knot_db_api_t *db_api = knot_db_lmdb_api(); + assert(db_api); + + knot_db_txn_t static_txn, *mytxn = txn; + if (txn == NULL) { + mytxn = &static_txn; + int ret = db_api->txn_begin(db, mytxn, KNOT_DB_SORTED); + if (ret != KNOT_EOK) { + return ret; + } + } + + int ret = txn_write_timers(mytxn, zone, timers); + if (ret != KNOT_EOK) { + db_api->txn_abort(mytxn); + return ret; + } + + if (txn == NULL) { + db_api->txn_commit(mytxn); + } + + return KNOT_EOK; +} + +int zone_timers_sweep(knot_db_t *db, sweep_cb keep_zone, void *cb_data) +{ + if (!db || !keep_zone) { + return KNOT_EINVAL; + } + + const knot_db_api_t *db_api = knot_db_lmdb_api(); + assert(db_api); + + knot_db_txn_t txn = { 0 }; + int ret = db_api->txn_begin(db, &txn, KNOT_DB_SORTED); + if (ret != KNOT_EOK) { + return ret; + } + + knot_db_iter_t *it = NULL; + for (it = db_api->iter_begin(&txn, 0); it != NULL; it = db_api->iter_next(it)) { + knot_db_val_t key = { 0 }; + ret = db_api->iter_key(it, &key); + if (ret != KNOT_EOK) { + break; + } + + const knot_dname_t *zone = (const knot_dname_t *)key.data; + if (!keep_zone(zone, cb_data)) { + ret = db_api->del(&txn, &key); + if (ret != KNOT_EOK) { + break; + } + } + } + db_api->iter_finish(it); + + if (ret != KNOT_EOK) { + db_api->txn_abort(&txn); + return ret; + } + + return db_api->txn_commit(&txn); +} |