zonecut.c 12.4 KB
Newer Older
1
/*  Copyright (C) 2014-2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
Marek Vavruša's avatar
Marek Vavruša committed
2 3 4 5 6 7 8 9 10 11 12 13

    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 19
#include <libknot/descriptor.h>
#include <libknot/rrtype/rdname.h>
#include <libknot/packet/wire.h>
20 21
#include <libknot/descriptor.h>
#include <libknot/rrtype/aaaa.h>
22

23
#include "lib/zonecut.h"
24
#include "lib/rplan.h"
25
#include "contrib/cleanup.h"
26
#include "lib/defines.h"
27
#include "lib/layer.h"
28
#include "lib/resolve.h"
29
#include "lib/generic/pack.h"
30

31 32
#define VERBOSE_MSG(qry, fmt...) QRVERBOSE(qry, "zcut", fmt)

33
/* Root hint descriptor. */
34 35
struct hint_info {
	const knot_dname_t *name;
36
	size_t len;
37
	const uint8_t *addr;
38 39 40
};

#define U8(x) (const uint8_t *)(x)
41

42 43 44 45 46 47 48 49 50 51
static void update_cut_name(struct kr_zonecut *cut, const knot_dname_t *name)
{
	if (knot_dname_is_equal(name, cut->name)) {
		return;
	}
	knot_dname_t *next_name = knot_dname_copy(name, cut->pool);
	mm_free(cut->pool, cut->name);
	cut->name = next_name;
}

52
int kr_zonecut_init(struct kr_zonecut *cut, const knot_dname_t *name, knot_mm_t *pool)
53
{
54
	if (!cut || !name) {
55
		return kr_error(EINVAL);
56
	}
57

58 59
	cut->name = knot_dname_copy(name, pool);
	cut->pool = pool;
60
	cut->key  = NULL;
61
	cut->trust_anchor = NULL;
62
	cut->parent = NULL;
63 64
	cut->nsset = trie_create(pool);
	return cut->name && cut->nsset ? kr_ok() : kr_error(ENOMEM);
65 66
}

67 68 69 70 71 72 73 74 75 76 77 78 79
/** Completely free a pack_t. */
static inline void free_addr_set(pack_t *pack, knot_mm_t *pool)
{
	if (unlikely(!pack)) {
		/* promised we don't store NULL packs */
		assert(false);
		return;
	}
	pack_clear_mm(*pack, mm_free, pool);
	mm_free(pool, pack);
}
/** Trivial wrapper for use in trie_apply, due to ugly casting. */
static int free_addr_set_cb(trie_val_t *v, void *pool)
80
{
81
	free_addr_set(*v, pool);
82 83
	return kr_ok();
}
84

85 86
void kr_zonecut_deinit(struct kr_zonecut *cut)
{
87
	if (!cut) {
88 89 90
		return;
	}
	mm_free(cut->pool, cut->name);
91 92 93 94 95
	if (cut->nsset) {
		trie_apply(cut->nsset, free_addr_set_cb, cut->pool);
		trie_free(cut->nsset);
		cut->nsset = NULL;
	}
96
	knot_rrset_free(&cut->key, cut->pool);
97
	knot_rrset_free(&cut->trust_anchor, cut->pool);
98
	cut->name = NULL;
99
}
100

101 102
void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name)
{
103
	if (!cut || !name) {
104 105
		return;
	}
106 107 108
	knot_rrset_t *key, *ta;
	key = cut->key; cut->key = NULL;
	ta = cut->trust_anchor; cut->trust_anchor = NULL;
109 110
	kr_zonecut_deinit(cut);
	kr_zonecut_init(cut, name, cut->pool);
111 112
	cut->key = key;
	cut->trust_anchor = ta;
113 114
}

115 116 117 118 119
int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src)
{
	if (!dst || !src) {
		return kr_error(EINVAL);
	}
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
	if (!dst->nsset) {
		dst->nsset = trie_create(dst->pool);
	}
	/* Copy the contents, one by one. */
	int ret = kr_ok();
	trie_it_t *it;
	for (it = trie_it_begin(src->nsset); !trie_it_finished(it); trie_it_next(it)) {
		size_t klen;
		const char * const k = trie_it_key(it, &klen);
		pack_t **new_pack = (pack_t **)trie_get_ins(dst->nsset, k, klen);
		if (!new_pack) {
			ret = kr_error(ENOMEM);
			break;
		}
		const pack_t *old_pack = *trie_it_val(it);
		ret = pack_clone(new_pack, old_pack, dst->pool);
		if (ret) break;
	}
	trie_it_free(it);
	return ret;
140 141
}

142
int kr_zonecut_copy_trust(struct kr_zonecut *dst, const struct kr_zonecut *src)
143
{
144 145
	knot_rrset_t *key_copy = NULL;
	knot_rrset_t *ta_copy = NULL;
146 147

	if (src->key) {
148 149 150
		key_copy = knot_rrset_copy(src->key, dst->pool);
		if (!key_copy) {
			return kr_error(ENOMEM);
151 152 153 154
		}
	}

	if (src->trust_anchor) {
155 156 157 158
		ta_copy = knot_rrset_copy(src->trust_anchor, dst->pool);
		if (!ta_copy) {
			knot_rrset_free(&key_copy, dst->pool);
			return kr_error(ENOMEM);
159 160 161
		}
	}

162 163 164 165
	knot_rrset_free(&dst->key, dst->pool);
	dst->key = key_copy;
	knot_rrset_free(&dst->trust_anchor, dst->pool);
	dst->trust_anchor = ta_copy;
166

167
	return kr_ok();
168 169
}

170
int kr_zonecut_add(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata)
171
{
172
	if (!cut || !ns || !cut->nsset) {
173
		return kr_error(EINVAL);
174
	}
175 176 177 178 179 180 181
	/* Get a pack_t for the ns. */
	pack_t **pack = (pack_t **)trie_get_ins(cut->nsset, (const char *)ns, knot_dname_size(ns));
	if (!pack) return kr_error(ENOMEM);
	if (*pack == NULL) {
		*pack = mm_alloc(cut->pool, sizeof(pack_t));
		if (*pack == NULL) return kr_error(ENOMEM);
		pack_init(**pack);
182 183 184 185 186
	}
	/* Insert data (if has any) */
	if (rdata == NULL) {
		return kr_ok();
	}
187
	/* Check for duplicates */
188
	uint16_t rdlen = knot_rdata_rdlen(rdata);
189
	uint8_t *raw_addr = knot_rdata_data(rdata);
190
	if (pack_obj_find(*pack, raw_addr, rdlen)) {
191 192 193
		return kr_ok();
	}
	/* Push new address */
194
	int ret = pack_reserve_mm(**pack, 1, rdlen, kr_memreserve, cut->pool);
195 196 197
	if (ret != 0) {
		return kr_error(ENOMEM);
	}
198
	return pack_obj_push(*pack, raw_addr, rdlen);
199 200
}

201
int kr_zonecut_del(struct kr_zonecut *cut, const knot_dname_t *ns, const knot_rdata_t *rdata)
202
{
203
	if (!cut || !ns) {
204 205
		return kr_error(EINVAL);
	}
206

207
	/* Find the address list. */
208
	int ret = kr_ok();
209
	pack_t *pack = kr_zonecut_find(cut, ns);
210 211
	if (pack == NULL) {
		return kr_error(ENOENT);
212
	}
213
	/* Remove address from the pack. */
214 215 216 217
	if (rdata) {
		ret = pack_obj_del(pack, knot_rdata_data(rdata), knot_rdata_rdlen(rdata));
	}
	/* No servers left, remove NS from the set. */
218
	if (pack->len == 0) {
219 220 221 222
		free_addr_set(pack, cut->pool);
		ret = trie_del(cut->nsset, (const char *)ns, knot_dname_size(ns), NULL);
		assert(ret == 0); /* only KNOT_ENOENT and that *can't* happen */
		return (ret == 0) ? kr_ok() : kr_error(ret);
223
	}
224 225 226

	return ret;
}
227

228 229 230 231 232 233 234
int kr_zonecut_del_all(struct kr_zonecut *cut, const knot_dname_t *ns)
{
	if (!cut || !ns) {
		return kr_error(EINVAL);
	}

	/* Find the address list; then free and remove it. */
235 236 237 238 239
	pack_t *pack;
	int ret = trie_del(cut->nsset, (const char *)ns, knot_dname_size(ns),
			   (trie_val_t *)&pack);
	if (ret) { /* deletion failed */
		assert(ret == KNOT_ENOENT);
240 241
		return kr_error(ENOENT);
	}
242 243
	free_addr_set(pack, cut->pool);
	return kr_ok();
244 245
}

246 247
pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns)
{
248
	if (!cut || !ns) {
249 250
		return NULL;
	}
251 252 253
	trie_val_t *val = trie_get_try(cut->nsset, (const char *)ns, knot_dname_size(ns));
	/* we get pointer to the pack_t pointer */
	return val ? (pack_t *)*val : NULL;
254 255
}

256
static int has_address(trie_val_t *v, void *baton_)
257
{
258 259 260
	const pack_t *pack = *v;
	const bool found = pack != NULL && pack->len != 0;
	return found;
261 262
}

263
bool kr_zonecut_is_empty(struct kr_zonecut *cut)
264
{
265 266 267
	if (!cut || !cut->nsset) {
		assert(false);
		return true;
268
	}
269
	return !trie_apply(cut->nsset, has_address, NULL);
270 271
}

272
int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
273
{
274
	if (!ctx || !cut || !ctx->root_hints.nsset) {
275 276 277
		return kr_error(EINVAL);
	}

278 279
	trie_apply(cut->nsset, free_addr_set_cb, cut->pool);
	trie_clear(cut->nsset);
280

281
	update_cut_name(cut, U8(""));
282
	/* Copy root hints from resolution context. */
283
	return kr_zonecut_copy(cut, &ctx->root_hints);
284 285
}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
286
/** Fetch address for zone cut.  Any rank is accepted (i.e. glue as well). */
287 288 289
static void fetch_addr(struct kr_zonecut *cut, struct kr_cache *cache,
			const knot_dname_t *ns, uint16_t rrtype,
			const struct kr_query *qry)
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
290
// LATER(optim.): excessive data copying
291
{
292
	struct kr_cache_p peek;
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
293 294 295
	if (kr_cache_peek_exact(cache, ns, rrtype, &peek) != 0) {
		return;
	}
296
	int32_t new_ttl = kr_cache_ttl(&peek, qry, ns, rrtype);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
297
	if (new_ttl < 0) {
298 299 300
		return;
	}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
301 302
	knot_rrset_t cached_rr;
	knot_rrset_init(&cached_rr, /*const-cast*/(knot_dname_t *)ns, rrtype, KNOT_CLASS_IN);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
303 304 305
	if (kr_cache_materialize(&cached_rr.rrs, &peek, new_ttl, cut->pool) < 0) {
		return;
	}
306
	knot_rdata_t *rd = cached_rr.rrs.data;
307
	for (uint16_t i = 0; i < cached_rr.rrs.rr_count; ++i) {
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
308
		(void) kr_zonecut_add(cut, ns, rd);
309
		rd = kr_rdataset_next(rd);
310 311 312
	}
}

313
/** Fetch best NS for zone cut. */
314
static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut,
315
		    const knot_dname_t *name, const struct kr_query *qry,
Vladimír Čunát's avatar
Vladimír Čunát committed
316
		    uint8_t * restrict rank)
317
{
318
	struct kr_cache_p peek;
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
319
	int ret = kr_cache_peek_exact(&ctx->cache, name, KNOT_RRTYPE_NS, &peek);
320 321 322
	if (ret != 0) {
		return ret;
	}
323 324 325 326
	/* Note: we accept *any* rank from the cache.  We assume that nothing
	 * completely untrustworthy could get into the cache, e.g out-of-bailiwick
	 * records that weren't validated.
	 */
327 328 329 330 331 332
	*rank = peek.rank;

	int32_t new_ttl = kr_cache_ttl(&peek, qry, name, KNOT_RRTYPE_NS);
	if (new_ttl < 0) {
		return kr_error(ESTALE);
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
333
	/* Materialize the rdataset temporarily, for simplicity. */
334
	knot_rdataset_t ns_rds = { 0, NULL };
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
335
	ret = kr_cache_materialize(&ns_rds, &peek, new_ttl, cut->pool);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
336
	if (ret < 0) {
337 338
		return ret;
	}
339

340 341
	/* Insert name servers for this zone cut, addresses will be looked up
	 * on-demand (either from cache or iteratively) */
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
342 343 344
	for (unsigned i = 0; i < ns_rds.rr_count; ++i) {
		const knot_dname_t *ns_name = knot_ns_name(&ns_rds, i);
		(void) kr_zonecut_add(cut, ns_name, NULL);
345
		/* Fetch NS reputation and decide whether to prefetch A/AAAA records. */
346 347
		unsigned *cached = lru_get_try(ctx->cache_rep,
				(const char *)ns_name, knot_dname_size(ns_name));
348
		unsigned reputation = (cached) ? *cached : 0;
349
		if (!(reputation & KR_NS_NOIP4) && !(qry->flags.NO_IPV4)) {
350
			fetch_addr(cut, &ctx->cache, ns_name, KNOT_RRTYPE_A, qry);
351
		}
352
		if (!(reputation & KR_NS_NOIP6) && !(qry->flags.NO_IPV6)) {
353
			fetch_addr(cut,  &ctx->cache, ns_name, KNOT_RRTYPE_AAAA, qry);
354
		}
355 356
	}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
357
	knot_rdataset_clear(&ns_rds, cut->pool);
358
	return kr_ok();
359 360
}

361
/**
362
 * Fetch secure RRSet of given type.
363
 */
364
static int fetch_secure_rrset(knot_rrset_t **rr, struct kr_cache *cache,
365 366
	const knot_dname_t *owner, uint16_t type, knot_mm_t *pool,
	const struct kr_query *qry)
367
{
368
	if (!rr) {
369 370
		assert(!EINVAL);
		return kr_error(EINVAL);
371
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
372
	/* peek, check rank and TTL */
373
	struct kr_cache_p peek;
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
374
	int ret = kr_cache_peek_exact(cache, owner, type, &peek);
375 376 377
	if (ret != 0) {
		return ret;
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
378
	if (!kr_rank_test(peek.rank, KR_RANK_SECURE)) {
379 380
		return kr_error(ENOENT);
	}
381
	int32_t new_ttl = kr_cache_ttl(&peek, qry, owner, type);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
382 383 384 385
	if (new_ttl < 0) {
		return kr_error(ESTALE);
	}
	/* materialize a new RRset */
386
	knot_rrset_free(rr, pool);
387
	*rr = mm_alloc(pool, sizeof(knot_rrset_t));
388
	if (*rr == NULL) {
389 390
		return kr_error(ENOMEM);
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
391 392 393 394 395 396 397 398
	owner = knot_dname_copy(/*const-cast*/(knot_dname_t *)owner, pool);
	if (!owner) {
		mm_free(pool, *rr);
		*rr = NULL;
		return kr_error(ENOMEM);
	}
	knot_rrset_init(*rr, /*const-cast*/(knot_dname_t *)owner, type, KNOT_CLASS_IN);
	ret = kr_cache_materialize(&(*rr)->rrs, &peek, new_ttl, pool);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
399
	if (ret < 0) {
400
		knot_rrset_free(rr, pool);
401 402 403 404 405 406
		return ret;
	}

	return kr_ok();
}

407 408 409
int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut,
			   const knot_dname_t *name, const struct kr_query *qry,
			   bool * restrict secured)
410
{
411
	//VERBOSE_MSG(qry, "_find_cached\n");
412
	if (!ctx || !cut || !name) {
413
		return kr_error(EINVAL);
414
	}
415 416
	/* Copy name as it may overlap with cut name that is to be replaced. */
	knot_dname_t *qname = knot_dname_copy(name, cut->pool);
417
	if (!qname) {
418 419
		return kr_error(ENOMEM);
	}
420
	/* Start at QNAME parent. */
421
	const knot_dname_t *label = qname;
422
	while (true) {
423
		/* Fetch NS first and see if it's insecure. */
424
		uint8_t rank = 0;
425
		const bool is_root = (label[0] == '\0');
426
		if (fetch_ns(ctx, cut, label, qry, &rank) == 0) {
427
			/* Flag as insecure if cached as this */
Vladimír Čunát's avatar
Vladimír Čunát committed
428
			if (kr_rank_test(rank, KR_RANK_INSECURE)) {
429
				*secured = false;
430
			}
431
			/* Fetch DS and DNSKEY if caller wants secure zone cut */
432
			int ret_ds = 1, ret_dnskey = 1;
433
			if (*secured || is_root) {
434 435 436 437
				ret_ds = fetch_secure_rrset(&cut->trust_anchor, &ctx->cache,
						label, KNOT_RRTYPE_DS, cut->pool, qry);
				ret_dnskey = fetch_secure_rrset(&cut->key, &ctx->cache,
						label, KNOT_RRTYPE_DNSKEY, cut->pool, qry);
438
			}
439 440
			update_cut_name(cut, label);
			mm_free(cut->pool, qname);
441
			kr_cache_sync(&ctx->cache);
442 443
			WITH_VERBOSE(qry) {
				auto_free char *label_str = kr_dname_text(label);
444
				VERBOSE_MSG(qry,
Marek Vavruša's avatar
Marek Vavruša committed
445 446
					"found cut: %s (rank 0%.2o return codes: DS %d, DNSKEY %d)\n",
					label_str, rank, ret_ds, ret_dnskey);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
447
			}
448
			return kr_ok();
449
		}
450 451 452 453
		/* Subtract label from QNAME. */
		if (!is_root) {
			label = knot_wire_next_label(label, NULL);
		} else {
454 455
			break;
		}
456
	}
457
	kr_cache_sync(&ctx->cache);
458
	mm_free(cut->pool, qname);
459
	return kr_error(ENOENT);
460
}