zonecut.c 11.6 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 "lib/defines.h"
26
#include "lib/layer.h"
27
#include "lib/resolve.h"
28
#include "lib/generic/pack.h"
29

30
/* Root hint descriptor. */
31 32
struct hint_info {
	const knot_dname_t *name;
33
	size_t len;
34
	const uint8_t *addr;
35 36 37
};

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

39 40 41 42 43 44 45 46 47 48
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;
}

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

55 56
	cut->name = knot_dname_copy(name, pool);
	cut->pool = pool;
57
	cut->key  = NULL;
58
	cut->trust_anchor = NULL;
59
	cut->parent = NULL;
60 61 62 63 64 65 66 67 68 69 70 71 72 73
	cut->nsset = map_make();
	cut->nsset.malloc = (map_alloc_f) mm_alloc;
	cut->nsset.free = (map_free_f) mm_free;
	cut->nsset.baton = pool;
	return kr_ok();
}

static int free_addr_set(const char *k, void *v, void *baton)
{
	pack_t *pack = v;
	pack_clear_mm(*pack, mm_free, baton);
	mm_free(baton, pack);
	return kr_ok();
}
74

75 76
void kr_zonecut_deinit(struct kr_zonecut *cut)
{
77
	if (!cut) {
78 79 80 81 82
		return;
	}
	mm_free(cut->pool, cut->name);
	map_walk(&cut->nsset, free_addr_set, cut->pool);
	map_clear(&cut->nsset);
83
	knot_rrset_free(&cut->key, cut->pool);
84
	knot_rrset_free(&cut->trust_anchor, cut->pool);
85
	cut->name = NULL;
86
}
87

88 89
void kr_zonecut_set(struct kr_zonecut *cut, const knot_dname_t *name)
{
90
	if (!cut || !name) {
91 92
		return;
	}
93 94 95
	knot_rrset_t *key, *ta;
	key = cut->key; cut->key = NULL;
	ta = cut->trust_anchor; cut->trust_anchor = NULL;
96 97
	kr_zonecut_deinit(cut);
	kr_zonecut_init(cut, name, cut->pool);
98 99
	cut->key = key;
	cut->trust_anchor = ta;
100 101
}

102 103 104 105 106 107 108 109 110
static int copy_addr_set(const char *k, void *v, void *baton)
{
	pack_t *addr_set = v;
	struct kr_zonecut *dst = baton;
	/* Clone addr_set pack */
	pack_t *new_set = mm_alloc(dst->pool, sizeof(*new_set));
	if (!new_set) {
		return kr_error(ENOMEM);
	}
111 112 113 114 115 116 117 118 119 120 121
	pack_init(*new_set);
	/* Clone data only if needed */
	if (addr_set->len > 0) {
		new_set->at = mm_alloc(dst->pool, addr_set->len);
		if (!new_set->at) {
			mm_free(dst->pool, new_set);
			return kr_error(ENOMEM);
		}
		memcpy(new_set->at, addr_set->at, addr_set->len);
		new_set->len = addr_set->len;
		new_set->cap = addr_set->len;
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	}
	/* Reinsert */
	if (map_set(&dst->nsset, k, new_set) != 0) {
		pack_clear_mm(*new_set, mm_free, dst->pool);
		mm_free(dst->pool, new_set);
		return kr_error(ENOMEM);
	}
	return kr_ok();
}

int kr_zonecut_copy(struct kr_zonecut *dst, const struct kr_zonecut *src)
{
	if (!dst || !src) {
		return kr_error(EINVAL);
	}
	/* We're not touching src nsset, I promise */
	return map_walk((map_t *)&src->nsset, copy_addr_set, dst);
}

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

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

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

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

166
	return kr_ok();
167 168
}

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

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

208
	/* Find the address list. */
209
	int ret = kr_ok();
210
	pack_t *pack = kr_zonecut_find(cut, ns);
211 212
	if (pack == NULL) {
		return kr_error(ENOENT);
213
	}
214
	/* Remove address from the pack. */
215 216 217 218
	if (rdata) {
		ret = pack_obj_del(pack, knot_rdata_data(rdata), knot_rdata_rdlen(rdata));
	}
	/* No servers left, remove NS from the set. */
219
	if (pack->len == 0) {
220 221
		free_addr_set((const char *)ns, pack, cut->pool);
		return map_del(&cut->nsset, (const char *)ns);
222
	}
223 224 225

	return ret;
}
226

227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
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. */
	pack_t *pack = kr_zonecut_find(cut, ns);
	if (pack == NULL) {
		return kr_error(ENOENT);
	}
	free_addr_set((const char *)ns, pack, cut->pool);
	return map_del(&cut->nsset, (const char *)ns);
}

242 243
pack_t *kr_zonecut_find(struct kr_zonecut *cut, const knot_dname_t *ns)
{
244
	if (!cut || !ns) {
245 246 247 248 249 250 251 252
		return NULL;
	}

	const char *key = (const char *)ns;
	map_t *nsset = &cut->nsset;
	return map_get(nsset, key);
}

253
int kr_zonecut_set_sbelt(struct kr_context *ctx, struct kr_zonecut *cut)
254
{
255
	if (!ctx || !cut) {
256 257 258 259
		return kr_error(EINVAL);
	}

	update_cut_name(cut, U8(""));
260 261
	map_walk(&cut->nsset, free_addr_set, cut->pool);
	map_clear(&cut->nsset);
262 263

	/* Copy root hints from resolution context. */
264
	int ret = 0;
265
	if (ctx->root_hints.nsset.root) {
266
		ret = kr_zonecut_copy(cut, &ctx->root_hints);
267
	}
268
	return ret;
269 270
}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
271
/** Fetch address for zone cut.  Any rank is accepted (i.e. glue as well). */
272
static void fetch_addr(struct kr_zonecut *cut, struct kr_cache *cache, const knot_dname_t *ns, uint16_t rrtype, uint32_t timestamp)
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
273
// LATER(optim.): excessive data copying
274
{
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
275 276 277 278 279 280
	struct kr_cache_p peek = {};
	if (kr_cache_peek_exact(cache, ns, rrtype, &peek) != 0) {
		return;
	}
	int32_t new_ttl = kr_cache_ttl(&peek, timestamp);
	if (new_ttl < 0) {
281 282 283
		return;
	}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
284 285
	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
286 287 288
	if (kr_cache_materialize(&cached_rr.rrs, &peek, new_ttl, cut->pool) < 0) {
		return;
	}
289
	knot_rdata_t *rd = cached_rr.rrs.data;
290
	for (uint16_t i = 0; i < cached_rr.rrs.rr_count; ++i) {
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
291
		(void) kr_zonecut_add(cut, ns, rd);
292
		rd = kr_rdataset_next(rd);
293 294 295
	}
}

296
/** Fetch best NS for zone cut. */
297 298
static int fetch_ns(struct kr_context *ctx, struct kr_zonecut *cut,
		    const knot_dname_t *name, uint32_t timestamp,
Vladimír Čunát's avatar
Vladimír Čunát committed
299
		    uint8_t * restrict rank)
300
{
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
301 302
	struct kr_cache_p peek = {};
	int ret = kr_cache_peek_exact(&ctx->cache, name, KNOT_RRTYPE_NS, &peek);
303 304 305
	if (ret != 0) {
		return ret;
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
306 307 308 309
	int32_t new_ttl = kr_cache_ttl(&peek, timestamp);
	if (new_ttl < 0) {
		return kr_error(ESTALE);
	}
310 311 312 313
	/* 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.
	 */
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
314 315 316
	/* Materialize the rdataset temporarily, for simplicity. */
	knot_rdataset_t ns_rds = {};
	ret = kr_cache_materialize(&ns_rds, &peek, new_ttl, cut->pool);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
317
	if (ret < 0) {
318 319
		return ret;
	}
320

321 322
	/* 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
323 324 325
	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);
326
		/* Fetch NS reputation and decide whether to prefetch A/AAAA records. */
327 328
		unsigned *cached = lru_get_try(ctx->cache_rep,
				(const char *)ns_name, knot_dname_size(ns_name));
329
		unsigned reputation = (cached) ? *cached : 0;
330
		if (!(reputation & KR_NS_NOIP4) && !(ctx->options.NO_IPV4)) {
331
			fetch_addr(cut, &ctx->cache, ns_name, KNOT_RRTYPE_A, timestamp);
332
		}
333
		if (!(reputation & KR_NS_NOIP6) && !(ctx->options.NO_IPV6)) {
334
			fetch_addr(cut,  &ctx->cache, ns_name, KNOT_RRTYPE_AAAA, timestamp);
335
		}
336 337
	}

Vladimír Čunát's avatar
.  
Vladimír Čunát committed
338
	knot_rdataset_clear(&ns_rds, cut->pool);
339
	return kr_ok();
340 341
}

342
/**
343
 * Fetch secure RRSet of given type.
344
 */
345 346
static int fetch_secure_rrset(knot_rrset_t **rr, struct kr_cache *cache,
	const knot_dname_t *owner, uint16_t type, knot_mm_t *pool, uint32_t timestamp)
347
{
348 349 350
	if (!rr) {
		return kr_error(ENOENT);
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
351 352 353
	/* peek, check rank and TTL */
	struct kr_cache_p peek = {};
	int ret = kr_cache_peek_exact(cache, owner, type, &peek);
354 355 356
	if (ret != 0) {
		return ret;
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
357
	if (!kr_rank_test(peek.rank, KR_RANK_SECURE)) {
358 359
		return kr_error(ENOENT);
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
360 361 362 363 364
	int32_t new_ttl = kr_cache_ttl(&peek, timestamp);
	if (new_ttl < 0) {
		return kr_error(ESTALE);
	}
	/* materialize a new RRset */
365
	knot_rrset_free(rr, pool);
366
	*rr = mm_alloc(pool, sizeof(knot_rrset_t));
367
	if (*rr == NULL) {
368 369
		return kr_error(ENOMEM);
	}
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
370 371 372 373 374 375 376 377
	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
378
	if (ret < 0) {
379
		knot_rrset_free(rr, pool);
380 381 382 383 384 385
		return ret;
	}

	return kr_ok();
}

386
int kr_zonecut_find_cached(struct kr_context *ctx, struct kr_zonecut *cut, const knot_dname_t *name,
387
                           uint32_t timestamp, bool * restrict secured)
388
{
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
389
	kr_log_verbose("[     ][ *c ] kr_zonecut_find_cached\n");
390
	if (!ctx || !cut || !name) {
391
		return kr_error(EINVAL);
392
	}
393 394
	/* Copy name as it may overlap with cut name that is to be replaced. */
	knot_dname_t *qname = knot_dname_copy(name, cut->pool);
395
	if (!qname) {
396 397
		return kr_error(ENOMEM);
	}
398
	/* Start at QNAME parent. */
399
	const knot_dname_t *label = qname;
400
	while (true) {
401
		/* Fetch NS first and see if it's insecure. */
402
		uint8_t rank = 0;
403
		const bool is_root = (label[0] == '\0');
Vladimír Čunát's avatar
Vladimír Čunát committed
404
		if (fetch_ns(ctx, cut, label, timestamp, &rank) == 0) {
405
			/* Flag as insecure if cached as this */
Vladimír Čunát's avatar
Vladimír Čunát committed
406
			if (kr_rank_test(rank, KR_RANK_INSECURE)) {
407
				*secured = false;
408
			}
409
			/* Fetch DS and DNSKEY if caller wants secure zone cut */
410
			if (*secured || is_root) {
411
				fetch_secure_rrset(&cut->trust_anchor, &ctx->cache, label,
412
					    KNOT_RRTYPE_DS, cut->pool, timestamp);
413
				fetch_secure_rrset(&cut->key, &ctx->cache, label,
414
					    KNOT_RRTYPE_DNSKEY, cut->pool, timestamp);
415
			}
416 417
			update_cut_name(cut, label);
			mm_free(cut->pool, qname);
418
			kr_cache_sync(&ctx->cache);
Vladimír Čunát's avatar
.  
Vladimír Čunát committed
419 420 421
			WITH_VERBOSE {
				kr_dname_print(label, "[     ][ *c ] and found cut: ", "\n");
			}
422
			return kr_ok();
423
		}
424 425 426 427
		/* Subtract label from QNAME. */
		if (!is_root) {
			label = knot_wire_next_label(label, NULL);
		} else {
428 429
			break;
		}
430
	}
431
	kr_cache_sync(&ctx->cache);
432
	mm_free(cut->pool, qname);
433
	return kr_error(ENOENT);
434
}