1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     You should have received a copy of the GNU General Public License
14     along with this program.  If not, see <https://www.gnu.org/licenses/>.
15  */
16 
17 #include "knot/zone/timers.h"
18 
19 #include "contrib/wire_ctx.h"
20 #include "knot/zone/zonedb.h"
21 
22 /*
23  * # Timer database
24  *
25  * Timer database stores timestaps of events which need to be retained
26  * across server restarts. The key in the database is the zone name in
27  * wire format. The value contains serialized timers.
28  *
29  * # Serialization format
30  *
31  * The value is a sequence of timers. Each timer consists of the timer
32  * identifier (1 byte, unsigned integer) and timer value (8 bytes, unsigned
33  * integer, network order).
34  *
35  * For example, the following byte sequence:
36  *
37  *     81 00 00 00 00 57 e3 e8 0a 82 00 00 00 00 57 e3 e9 a1
38  *
39  * Encodes the following timers:
40  *
41  *     last_flush = 1474553866
42  *     last_refresh = 1474554273
43  */
44 
45 /*!
46  * \brief Timer database fields identifiers.
47  *
48  * Valid ID starts with '1' in MSB to avoid conflicts with "old timers".
49  */
50 enum timer_id {
51 	TIMER_INVALID = 0,
52 	TIMER_SOA_EXPIRE = 0x80,
53 	TIMER_LAST_FLUSH,
54 	TIMER_LAST_REFRESH,
55 	TIMER_NEXT_REFRESH,
56 	TIMER_LAST_RESALT,
57 	TIMER_NEXT_DS_CHECK,
58 	TIMER_NEXT_DS_PUSH,
59 	TIMER_CATALOG_MEMBER,
60 	TIMER_LAST_NOTIFIED,
61 	TIMER_LAST_REFR_OK,
62 };
63 
64 #define TIMER_SIZE (sizeof(uint8_t) + sizeof(uint64_t))
65 
66 /*!
67  * \brief Deserialize timers from a binary buffer.
68  *
69  * \note Unknown timers are ignored.
70  */
deserialize_timers(zone_timers_t * timers_ptr,const uint8_t * data,size_t size)71 static int deserialize_timers(zone_timers_t *timers_ptr,
72                               const uint8_t *data, size_t size)
73 {
74 	if (!timers_ptr || !data) {
75 		return KNOT_EINVAL;
76 	}
77 
78 	zone_timers_t timers = { 0 };
79 
80 	wire_ctx_t wire = wire_ctx_init_const(data, size);
81 	while (wire_ctx_available(&wire) >= TIMER_SIZE) {
82 		uint8_t id = wire_ctx_read_u8(&wire);
83 		uint64_t value = wire_ctx_read_u64(&wire);
84 		switch (id) {
85 		case TIMER_SOA_EXPIRE:     timers.soa_expire = value; break;
86 		case TIMER_LAST_FLUSH:     timers.last_flush = value; break;
87 		case TIMER_LAST_REFRESH:   timers.last_refresh = value; break;
88 		case TIMER_NEXT_REFRESH:   timers.next_refresh = value; break;
89 		case TIMER_LAST_REFR_OK:   timers.last_refresh_ok = value; break;
90 		case TIMER_LAST_NOTIFIED:  timers.last_notified_serial = value; break;
91 		case TIMER_LAST_RESALT:    timers.last_resalt = value; break;
92 		case TIMER_NEXT_DS_CHECK:  timers.next_ds_check = value; break;
93 		case TIMER_NEXT_DS_PUSH:   timers.next_ds_push = value; break;
94 		case TIMER_CATALOG_MEMBER: timers.catalog_member = value; break;
95 		default:                   break; // ignore
96 		}
97 	}
98 
99 	if (wire_ctx_available(&wire) != 0) {
100 		return KNOT_EMALF;
101 	}
102 
103 	assert(wire.error == KNOT_EOK);
104 
105 	*timers_ptr = timers;
106 	return KNOT_EOK;
107 }
108 
txn_write_timers(knot_lmdb_txn_t * txn,const knot_dname_t * zone,const zone_timers_t * timers)109 static void txn_write_timers(knot_lmdb_txn_t *txn, const knot_dname_t *zone,
110                              const zone_timers_t *timers)
111 {
112 	MDB_val k = { knot_dname_size(zone), (void *)zone };
113 	MDB_val v = knot_lmdb_make_key("BLBLBLBLBLBLBLBLBLBL",
114 		TIMER_SOA_EXPIRE,    (uint64_t)timers->soa_expire,
115 		TIMER_LAST_FLUSH,    (uint64_t)timers->last_flush,
116 		TIMER_LAST_REFRESH,  (uint64_t)timers->last_refresh,
117 		TIMER_NEXT_REFRESH,  (uint64_t)timers->next_refresh,
118 		TIMER_LAST_REFR_OK,  (uint64_t)timers->last_refresh_ok,
119 		TIMER_LAST_NOTIFIED, timers->last_notified_serial,
120 		TIMER_LAST_RESALT,   (uint64_t)timers->last_resalt,
121 		TIMER_NEXT_DS_CHECK, (uint64_t)timers->next_ds_check,
122 		TIMER_NEXT_DS_PUSH,  (uint64_t)timers->next_ds_push,
123 		TIMER_CATALOG_MEMBER,(uint64_t)timers->catalog_member);
124 	knot_lmdb_insert(txn, &k, &v);
125 	free(v.mv_data);
126 }
127 
128 
zone_timers_open(const char * path,knot_db_t ** db,size_t mapsize)129 int zone_timers_open(const char *path, knot_db_t **db, size_t mapsize)
130 {
131 	if (path == NULL || db == NULL) {
132 		return KNOT_EINVAL;
133 	}
134 
135 	struct knot_db_lmdb_opts opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
136 	opts.mapsize = mapsize;
137 	opts.path = path;
138 
139 	return knot_db_lmdb_api()->init(db, NULL, &opts);
140 }
141 
zone_timers_close(knot_db_t * db)142 void zone_timers_close(knot_db_t *db)
143 {
144 	if (db == NULL) {
145 		return;
146 	}
147 
148 	knot_db_lmdb_api()->deinit(db);
149 }
150 
zone_timers_read(knot_lmdb_db_t * db,const knot_dname_t * zone,zone_timers_t * timers)151 int zone_timers_read(knot_lmdb_db_t *db, const knot_dname_t *zone,
152                      zone_timers_t *timers)
153 {
154 	if (!knot_lmdb_exists(db)) {
155 		return KNOT_ENOENT;
156 	}
157 	int ret = knot_lmdb_open(db);
158 	if (ret != KNOT_EOK) {
159 		return ret;
160 	}
161 	knot_lmdb_txn_t txn = { 0 };
162 	knot_lmdb_begin(db, &txn, false);
163 	MDB_val k = { knot_dname_size(zone), (void *)zone };
164 	if (knot_lmdb_find(&txn, &k, KNOT_LMDB_EXACT | KNOT_LMDB_FORCE)) {
165 		deserialize_timers(timers, txn.cur_val.mv_data, txn.cur_val.mv_size);
166 	}
167 	knot_lmdb_abort(&txn);
168 	return txn.ret;
169 }
170 
zone_timers_write(knot_lmdb_db_t * db,const knot_dname_t * zone,const zone_timers_t * timers)171 int zone_timers_write(knot_lmdb_db_t *db, const knot_dname_t *zone,
172                       const zone_timers_t *timers)
173 {
174 	knot_lmdb_txn_t txn = { 0 };
175 	knot_lmdb_begin(db, &txn, true);
176 	txn_write_timers(&txn, zone, timers);
177 	knot_lmdb_commit(&txn);
178 	return txn.ret;
179 }
180 
txn_zone_write(zone_t * z,knot_lmdb_txn_t * txn)181 static void txn_zone_write(zone_t *z, knot_lmdb_txn_t *txn)
182 {
183 	txn_write_timers(txn, z->name, &z->timers);
184 }
185 
zone_timers_write_all(knot_lmdb_db_t * db,knot_zonedb_t * zonedb)186 int zone_timers_write_all(knot_lmdb_db_t *db, knot_zonedb_t *zonedb)
187 {
188 	int ret = knot_lmdb_open(db);
189 	if (ret != KNOT_EOK) {
190 		return ret;
191 	}
192 	knot_lmdb_txn_t txn = { 0 };
193 	knot_lmdb_begin(db, &txn, true);
194 	knot_zonedb_foreach(zonedb, txn_zone_write, &txn);
195 	knot_lmdb_commit(&txn);
196 	return txn.ret;
197 }
198 
zone_timers_sweep(knot_lmdb_db_t * db,sweep_cb keep_zone,void * cb_data)199 int zone_timers_sweep(knot_lmdb_db_t *db, sweep_cb keep_zone, void *cb_data)
200 {
201 	if (!knot_lmdb_exists(db)) {
202 		return KNOT_EOK;
203 	}
204 	int ret = knot_lmdb_open(db);
205 	if (ret != KNOT_EOK) {
206 		return ret;
207 	}
208 	knot_lmdb_txn_t txn = { 0 };
209 	knot_lmdb_begin(db, &txn, true);
210 	knot_lmdb_forwhole(&txn) {
211 		if (!keep_zone((const knot_dname_t *)txn.cur_key.mv_data, cb_data)) {
212 			knot_lmdb_del_cur(&txn);
213 		}
214 	}
215 	knot_lmdb_commit(&txn);
216 	return txn.ret;
217 }
218 
zone_timers_serial_notified(const zone_timers_t * timers,uint32_t serial)219 bool zone_timers_serial_notified(const zone_timers_t *timers, uint32_t serial)
220 {
221 	return (timers->last_notified_serial & LAST_NOTIFIED_SERIAL_VALID) &&
222 	       ((uint32_t)timers->last_notified_serial == serial);
223 }
224