cache.c 11.5 KB
Newer Older
Marek Vavruša's avatar
Marek Vavruša committed
1 2 3 4 5 6 7 8 9 10 11 12 13
/*  Copyright (C) 2014 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
14
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
Marek Vavruša's avatar
Marek Vavruša committed
15 16
 */

17 18
#include <assert.h>
#include <time.h>
19 20 21
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
22

23
#include <libknot/errcode.h>
24
#include <libknot/descriptor.h>
25 26
#include <libknot/dname.h>
#include <libknot/rrtype/rrsig.h>
27

28
#include "contrib/cleanup.h"
29
#include "lib/cache.h"
30
#include "lib/cdb_lmdb.h"
31
#include "lib/defines.h"
32
#include "lib/utils.h"
33

34
/* Cache version */
35
#define KEY_VERSION "V\x02"
36
/* Key size */
37
#define KEY_HSIZE (sizeof(uint8_t) + sizeof(uint16_t))
38
#define KEY_SIZE (KEY_HSIZE + KNOT_DNAME_MAXLEN)
39

40 41 42
/* Shorthand for operations on cache backend */
#define cache_isvalid(cache) ((cache) && (cache)->api && (cache)->db)
#define cache_op(cache, op, ...) (cache)->api->op((cache)->db, ## __VA_ARGS__)
43 44

/** @internal Removes all records from cache. */
45
static inline int cache_purge(struct kr_cache *cache)
46
{
47 48
	cache->stats.delete += 1;
	return cache_op(cache, clear);
49 50
}

51 52
/** @internal Open cache db transaction and check internal data version. */
static int assert_right_version(struct kr_cache *cache)
53 54 55
{
	/* Check cache ABI version */
	knot_db_val_t key = { KEY_VERSION, 2 };
56
	knot_db_val_t val = { KEY_VERSION, 2 };
57
	int ret = cache_op(cache, read, &key, &val, 1);
58 59 60
	if (ret == 0) {
		ret = kr_error(EEXIST);
	} else {
61
		/* Version doesn't match. Recreate cache and write version key. */
62
		ret = cache_op(cache, count);
63 64
		if (ret != 0) { /* Non-empty cache, purge it. */
			kr_log_info("[cache] purging cache\n");
65
			ret = cache_purge(cache);
66 67 68
		}
		/* Either purged or empty. */
		if (ret == 0) {
69 70 71 72
			/* Key/Val is invalidated by cache purge, recreate it */
			key.data = KEY_VERSION;
			key.len = 2;
			val = key;
73
			ret = cache_op(cache, write, &key, &val, 1);
74 75
		}
	}
76
	kr_cache_sync(cache);
77 78 79
	return ret;
}

80
int kr_cache_open(struct kr_cache *cache, const struct kr_cdb_api *api, struct kr_cdb_opts *opts, knot_mm_t *mm)
81
{
82 83 84
	if (!cache) {
		return kr_error(EINVAL);
	}
85
	/* Open cache */
86 87 88 89 90
	if (!api) {
		api = kr_cdb_lmdb();
	}
	cache->api = api;
	int ret = cache->api->open(&cache->db, opts, mm);
91
	if (ret != 0) {
92
		return ret;
93
	}
94
	memset(&cache->stats, 0, sizeof(cache->stats));
95
	/* Check cache ABI version */
96 97
	(void) assert_right_version(cache);
	return 0;
98 99
}

100
void kr_cache_close(struct kr_cache *cache)
101
{
102 103
	if (cache_isvalid(cache)) {
		cache_op(cache, close);
104
		cache->db = NULL;
105
	}
106 107
}

108
void kr_cache_sync(struct kr_cache *cache)
109
{
110 111
	if (cache_isvalid(cache) && cache->api->sync) {
		cache_op(cache, sync);
112
	}
113 114
}

115 116 117 118 119
/**
 * @internal Composed key as { u8 tag, u8[1-255] name, u16 type }
 * The name is lowercased and label order is reverted for easy prefix search.
 * e.g. '\x03nic\x02cz\x00' is saved as '\0x00cz\x00nic\x00'
 */
120
static size_t cache_key(uint8_t *buf, uint8_t tag, const knot_dname_t *name, uint16_t rrtype)
121
{
122 123 124
	/* Convert to lookup format */
	int ret = knot_dname_lf(buf, name, NULL);
	if (ret != 0) {
125 126
		return 0;
	}
127 128 129 130 131
	/* Write tag + type */
	uint8_t name_len = buf[0];
	buf[0] = tag;
	memcpy(buf + sizeof(uint8_t) + name_len, &rrtype, sizeof(uint16_t));
	return name_len + KEY_HSIZE;
132 133
}

134
static struct kr_cache_entry *lookup(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, uint16_t type)
135
{
136
	if (!name || !cache) {
137 138
		return NULL;
	}
139

140 141 142
	uint8_t keybuf[KEY_SIZE];
	size_t key_len = cache_key(keybuf, tag, name, type);

143
	/* Look up and return value */
144 145
	knot_db_val_t key = { keybuf, key_len };
	knot_db_val_t val = { NULL, 0 };
146 147
	int ret = cache_op(cache, read, &key, &val, 1);
	if (ret != 0) {
148
		return NULL;
149 150
	}

151
	return (struct kr_cache_entry *)val.data;
152 153
}

154
static int check_lifetime(struct kr_cache_entry *found, uint32_t *timestamp)
155
{
156 157
	/* No time constraint */
	if (!timestamp) {
158 159
		return kr_ok();
	} else if (*timestamp <= found->timestamp) {
160 161
		/* John Connor record cached in the future. */
		*timestamp = 0;
162
		return kr_ok();
163 164
	} else {
		/* Check if the record is still valid. */
165
		uint32_t drift = *timestamp - found->timestamp;
166
		if (drift <= found->ttl) {
167
			*timestamp = drift;
168
			return kr_ok();
169 170
		}
	}
171
	return kr_error(ESTALE);
172
}
173

174
int kr_cache_peek(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, uint16_t type,
175 176
                  struct kr_cache_entry **entry, uint32_t *timestamp)
{
177
	if (!cache_isvalid(cache) || !name || !entry) {
178 179 180
		return kr_error(EINVAL);
	}

181
	struct kr_cache_entry *found = lookup(cache, tag, name, type);
182
	if (!found) {
183
		cache->stats.miss += 1;
184 185 186 187 188 189 190
		return kr_error(ENOENT);
	}

	/* Check entry lifetime */
	*entry = found;
	int ret = check_lifetime(found, timestamp);
	if (ret == 0) {
191
		cache->stats.hit += 1;
192
	} else {
193
		cache->stats.miss += 1;
194 195 196 197
	}
	return ret;
}

198
static void entry_write(struct kr_cache_entry *dst, struct kr_cache_entry *header, knot_db_val_t data)
199 200
{
	memcpy(dst, header, sizeof(*header));
201 202
	if (data.data)
		memcpy(dst->data, data.data, data.len);
203
}
204

205
int kr_cache_insert(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, uint16_t type,
206
                    struct kr_cache_entry *header, knot_db_val_t data)
207
{
208
	if (!cache_isvalid(cache) || !name || !header) {
209 210
		return kr_error(EINVAL);
	}
211

212
	/* Prepare key/value for insertion. */
213 214
	uint8_t keybuf[KEY_SIZE];
	size_t key_len = cache_key(keybuf, tag, name, type);
215 216 217
	if (key_len == 0) {
		return kr_error(EILSEQ);
	}
218
	assert(data.len != 0);
219 220
	knot_db_val_t key = { keybuf, key_len };
	knot_db_val_t entry = { NULL, sizeof(*header) + data.len };
221 222

	/* LMDB can do late write and avoid copy */
223 224 225 226
	int ret = 0;
	cache->stats.insert += 1;
	if (cache->api == kr_cdb_lmdb()) {
		ret = cache_op(cache, write, &key, &entry, 1);
227 228
		if (ret != 0) {
			return ret;
229
		}
230
		entry_write(entry.data, header, data);
231
		ret = cache_op(cache, sync); /* Make sure the entry is comitted. */
232 233
	} else {
		/* Other backends must prepare contiguous data first */
234 235
		auto_free char *buffer = malloc(entry.len);
		entry.data = buffer;
236
		entry_write(entry.data, header, data);
237
		ret = cache_op(cache, write, &key, &entry, 1);
238 239
	}

240
	return ret;
241
}
242

243
int kr_cache_remove(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, uint16_t type)
244
{
245
	if (!cache_isvalid(cache) || !name ) {
246 247 248 249 250
		return kr_error(EINVAL);
	}

	uint8_t keybuf[KEY_SIZE];
	size_t key_len = cache_key(keybuf, tag, name, type);
251 252 253
	if (key_len == 0) {
		return kr_error(EILSEQ);
	}
254
	knot_db_val_t key = { keybuf, key_len };
255 256
	cache->stats.delete += 1;
	return cache_op(cache, remove, &key, 1);
257 258
}

259
int kr_cache_clear(struct kr_cache *cache)
260
{
261
	if (!cache_isvalid(cache)) {
262 263
		return kr_error(EINVAL);
	}
264
	int ret = cache_purge(cache);
265
	if (ret == 0) {
266
		ret = assert_right_version(cache);
267 268
	}
	return ret;
269 270
}

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
int kr_cache_match(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, knot_db_val_t *val, int maxcount)
{
	if (!cache_isvalid(cache) || !name ) {
		return kr_error(EINVAL);
	}
	if (!cache->api->match) {
		return kr_error(ENOSYS);
	}

	uint8_t keybuf[KEY_SIZE];
	size_t key_len = cache_key(keybuf, tag, name, 0);
	if (key_len == 0) {
		return kr_error(EILSEQ);
	}

	/* Trim type from the search key */ 
	knot_db_val_t key = { keybuf, key_len - 2 };
	return cache_op(cache, match, &key, val, maxcount);
}

int kr_cache_peek_rr(struct kr_cache *cache, knot_rrset_t *rr, uint8_t *rank, uint8_t *flags, uint32_t *timestamp)
292
{
293
	if (!cache_isvalid(cache) || !rr || !timestamp) {
294 295 296 297
		return kr_error(EINVAL);
	}

	/* Check if the RRSet is in the cache. */
298
	struct kr_cache_entry *entry = NULL;
299
	int ret = kr_cache_peek(cache, KR_CACHE_RR, rr->owner, rr->type, &entry, timestamp);
300 301
	if (ret != 0) {
		return ret;
302
	}
303 304 305
	if (rank) {
		*rank = entry->rank;
	}
306 307 308
	if (flags) {
		*flags = entry->flags;
	}
309 310 311
	rr->rrs.rr_count = entry->count;
	rr->rrs.data = entry->data;
	return kr_ok();
312 313
}

314
int kr_cache_peek_rank(struct kr_cache *cache, uint8_t tag, const knot_dname_t *name, uint16_t type, uint32_t timestamp)
315
{
316
	if (!cache_isvalid(cache) || !name) {
317 318
		return kr_error(EINVAL);
	}
319
	struct kr_cache_entry *found = lookup(cache, tag, name, type);
320 321 322 323 324 325 326 327 328
	if (!found) {
		return kr_error(ENOENT);
	}
	if (check_lifetime(found, &timestamp) != 0) {
		return kr_error(ESTALE);
	}
	return found->rank;
}

329
int kr_cache_materialize(knot_rrset_t *dst, const knot_rrset_t *src, uint32_t drift, knot_mm_t *mm)
330
{
331
	if (!dst || !src || dst == src) {
332 333
		return kr_error(EINVAL);
	}
334

335 336 337 338 339
	/* Make RRSet copy */
	knot_rrset_init(dst, NULL, src->type, src->rclass);
	dst->owner = knot_dname_copy(src->owner, mm);
	if (!dst->owner) {
		return kr_error(ENOMEM);
340
	}
341

342 343
	/* Copy valid records */
	knot_rdata_t *rd = src->rrs.data;
344
	for (uint16_t i = 0; i < src->rrs.rr_count; ++i) {
345
		if (knot_rdata_ttl(rd) >= drift) {
346 347 348
			if (knot_rdataset_add(&dst->rrs, rd, mm) != 0) {
				knot_rrset_clear(dst, mm);
				return kr_error(ENOMEM);
349 350
			}
		}
351 352 353 354 355 356 357
		rd = kr_rdataset_next(rd);
	}
	/* Fixup TTL by time passed */
	rd = dst->rrs.data;
	for (uint16_t i = 0; i < dst->rrs.rr_count; ++i) {
		knot_rdata_set_ttl(rd, knot_rdata_ttl(rd) - drift);
		rd = kr_rdataset_next(rd);
358
	}
359

360
	return kr_ok();
361 362
}

363
int kr_cache_insert_rr(struct kr_cache *cache, const knot_rrset_t *rr, uint8_t rank, uint8_t flags, uint32_t timestamp)
364
{
365
	if (!cache_isvalid(cache) || !rr) {
366
		return kr_error(EINVAL);
367 368
	}

369
	/* Ignore empty records */
370
	if (knot_rrset_empty(rr)) {
371
		return kr_ok();
372 373
	}

374 375 376 377
	/* Prepare header to write */
	struct kr_cache_entry header = {
		.timestamp = timestamp,
		.ttl = 0,
378
		.rank = rank,
379
		.flags = flags,
380 381
		.count = rr->rrs.rr_count
	};
382
	knot_rdata_t *rd = rr->rrs.data;
383 384 385 386
	for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
		if (knot_rdata_ttl(rd) > header.ttl) {
			header.ttl = knot_rdata_ttl(rd);
		}
387
		rd = kr_rdataset_next(rd);
388 389
	}

390
	knot_db_val_t data = { rr->rrs.data, knot_rdataset_size(&rr->rrs) };
391
	return kr_cache_insert(cache, KR_CACHE_RR, rr->owner, rr->type, &header, data);
392
}
393

394
int kr_cache_peek_rrsig(struct kr_cache *cache, knot_rrset_t *rr, uint8_t *rank, uint8_t *flags, uint32_t *timestamp)
395
{
396
	if (!cache_isvalid(cache) || !rr || !timestamp) {
397 398 399 400
		return kr_error(EINVAL);
	}

	/* Check if the RRSet is in the cache. */
401
	struct kr_cache_entry *entry = NULL;
402
	int ret = kr_cache_peek(cache, KR_CACHE_SIG, rr->owner, rr->type, &entry, timestamp);
403 404
	if (ret != 0) {
		return ret;
405
	}
406
	assert(entry);
407 408 409
	if (rank) {
		*rank = entry->rank;
	}
410 411 412
	if (flags) {
		*flags = entry->flags;
	}
413 414 415 416
	rr->type = KNOT_RRTYPE_RRSIG;
	rr->rrs.rr_count = entry->count;
	rr->rrs.data = entry->data;
	return kr_ok();
417 418
}

419
int kr_cache_insert_rrsig(struct kr_cache *cache, const knot_rrset_t *rr, uint8_t rank, uint8_t flags, uint32_t timestamp)
420
{
421
	if (!cache_isvalid(cache) || !rr) {
422 423 424 425 426 427 428 429 430 431 432 433
		return kr_error(EINVAL);
	}

	/* Ignore empty records */
	if (knot_rrset_empty(rr)) {
		return kr_ok();
	}

	/* Prepare header to write */
	struct kr_cache_entry header = {
		.timestamp = timestamp,
		.ttl = 0,
434
		.rank = rank,
435
		.flags = flags,
436 437 438 439 440 441 442 443 444
		.count = rr->rrs.rr_count
	};
	for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
		knot_rdata_t *rd = knot_rdataset_at(&rr->rrs, i);
		if (knot_rdata_ttl(rd) > header.ttl) {
			header.ttl = knot_rdata_ttl(rd);
		}
	}

445
	uint16_t covered = knot_rrsig_type_covered(&rr->rrs, 0);
446
	knot_db_val_t data = { rr->rrs.data, knot_rdataset_size(&rr->rrs) };
447
	return kr_cache_insert(cache, KR_CACHE_SIG, rr->owner, covered, &header, data);
448
}