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