resolve.c 12.6 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 14 15 16
/*  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
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

17
#include <stdio.h>
Marek Vavruša's avatar
Marek Vavruša committed
18
#include <sys/fcntl.h>
19

Marek Vavruša's avatar
Marek Vavruša committed
20
#include <libknot/internal/mempool.h>
21
#include <libknot/rrtype/rdname.h>
22
#include <libknot/descriptor.h>
Marek Vavruša's avatar
Marek Vavruša committed
23
#include <libknot/internal/net.h>
Marek Vavruša's avatar
Marek Vavruša committed
24

25
#include "lib/layer.h"
26
#include "lib/rplan.h"
27 28
#include "lib/resolve.h"

Marek Vavruša's avatar
Marek Vavruša committed
29
#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), "resl",  fmt)
30

31
/** Invalidate current NS/addr pair. */
32
static int invalidate_ns(struct kr_rplan *rplan, struct kr_query *qry)
Marek Vavruša's avatar
Marek Vavruša committed
33
{
34 35 36 37 38
	uint8_t *addr = kr_nsrep_inaddr(qry->ns.addr);
	size_t addr_len = kr_nsrep_inaddr_len(qry->ns.addr);
	knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
	knot_rdata_init(rdata, addr_len, addr, 0);
	return kr_zonecut_del(&qry->zone_cut, qry->ns.name, rdata);
39
}
40

Marek Vavruša's avatar
Marek Vavruša committed
41
static int ns_resolve_addr(struct kr_query *qry, struct kr_request *param)
42
{
Marek Vavruša's avatar
Marek Vavruša committed
43 44 45 46
	struct kr_rplan *rplan = &param->rplan;
	if (kr_rplan_satisfies(qry, qry->ns.name, KNOT_CLASS_IN, KNOT_RRTYPE_A) ||
	    kr_rplan_satisfies(qry, qry->ns.name, KNOT_CLASS_IN, KNOT_RRTYPE_AAAA) ||
	    qry->flags & QUERY_AWAIT_ADDR) {
47
		DEBUG_MSG("=> dependency loop, bailing out\n");
Marek Vavruša's avatar
Marek Vavruša committed
48 49
		kr_rplan_pop(rplan, qry);
		return KNOT_STATE_PRODUCE;
50 51
	}

Marek Vavruša's avatar
Marek Vavruša committed
52 53 54 55
	(void) kr_rplan_push(rplan, qry, qry->ns.name, KNOT_CLASS_IN, KNOT_RRTYPE_AAAA);
	(void) kr_rplan_push(rplan, qry, qry->ns.name, KNOT_CLASS_IN, KNOT_RRTYPE_A);
	qry->flags |= QUERY_AWAIT_ADDR;
	return KNOT_STATE_PRODUCE;
56 57
}

Marek Vavruša's avatar
Marek Vavruša committed
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
static void prepare_layers(struct kr_request *param)
{
	struct kr_context *ctx = param->ctx;
	for (size_t i = 0; i < ctx->modules->len; ++i) {
		struct kr_module *mod = &ctx->modules->at[i];
		if (mod->layer) {
			knot_overlay_add(&param->overlay, mod->layer(), param);
		}
	}
}

static int connected(struct sockaddr *addr, int proto, struct timeval *timeout)
{
	unsigned flags = (proto == SOCK_STREAM) ? O_NONBLOCK : 0;
	int fd = net_connected_socket(proto, (struct sockaddr_storage *)addr, NULL, flags);
	if (fd < 0) {
		return kr_error(ECONNREFUSED);
	}

	/* Workaround for timeout, as we have no control over
	 * connect() time limit in blocking mode. */
	if (proto == SOCK_STREAM) {
		fd_set set;
		FD_ZERO(&set);
		FD_SET(fd, &set);
		int ret = select(fd + 1, NULL, &set, NULL, timeout);
		if (ret == 0) {
			close(fd);
			return kr_error(ETIMEDOUT);
		}
		if (ret < 0) {
			close(fd);
			return kr_error(ECONNREFUSED);
		}
		fcntl(fd, F_SETFL, 0);
	}

	return fd;
}

static int sendrecv(struct sockaddr *addr, int proto, const knot_pkt_t *query, knot_pkt_t *resp)
99
{
100
	struct timeval timeout = { KR_CONN_RTT_MAX / 1000, 0 };
Marek Vavruša's avatar
Marek Vavruša committed
101
	auto_close int fd = connected(addr, proto, &timeout);
102
	resp->size = 0;
Marek Vavruša's avatar
Marek Vavruša committed
103 104 105
	if (fd < 0) {
		return fd;
	}
Marek Vavruša's avatar
Marek Vavruša committed
106

Marek Vavruša's avatar
Marek Vavruša committed
107 108 109 110 111 112 113 114 115 116
	/* Send packet */
	int ret = 0;
	if (proto == SOCK_STREAM) {
		ret = tcp_send_msg(fd, query->wire, query->size, &timeout);
	} else {
		ret = udp_send_msg(fd, query->wire, query->size, NULL);
	}
	if (ret != query->size) {
		return kr_error(EIO);
	}
117

Marek Vavruša's avatar
Marek Vavruša committed
118 119 120
	/* Receive it */
	if (proto == SOCK_STREAM) {
		ret = tcp_recv_msg(fd, resp->wire, resp->max_size, &timeout);
121
	} else {
Marek Vavruša's avatar
Marek Vavruša committed
122 123 124 125
		ret = udp_recv_msg(fd, resp->wire, resp->max_size, &timeout);
	}
	if (ret <= 0) {
		return kr_error(ETIMEDOUT);
126
	}
127

Marek Vavruša's avatar
Marek Vavruša committed
128 129
	/* Parse and return */
	resp->size = ret;
130
	return knot_pkt_parse(resp, 0);
Marek Vavruša's avatar
Marek Vavruša committed
131 132
}

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
static int edns_put(knot_pkt_t *pkt)
{
	/* Reclaim reserved size. */
	int ret = knot_pkt_reclaim(pkt, knot_edns_wire_size(pkt->opt_rr));
	if (ret != 0) {
		return ret;
	}
	/* Write to packet. */
	assert(pkt->current == KNOT_ADDITIONAL);
	return knot_pkt_put(pkt, KNOT_COMPR_HINT_NONE, pkt->opt_rr, KNOT_PF_FREE);
}

static int edns_create(knot_pkt_t *pkt, knot_pkt_t *template)
{
	/* Create empty OPT RR */
	pkt->opt_rr = mm_alloc(&pkt->mm, sizeof(*pkt->opt_rr));
	if (!pkt->opt_rr) {
		return kr_error(ENOMEM);
	}
	int ret = knot_edns_init(pkt->opt_rr, KR_EDNS_PAYLOAD, 0, KR_EDNS_VERSION, &pkt->mm);
	if (ret != 0) {
		return ret;
	}
	/* Set DO bit if set (DNSSEC requested). */
	if (knot_pkt_has_dnssec(template)) {
		knot_edns_set_do(pkt->opt_rr);
	}
	return knot_pkt_reserve(pkt, knot_edns_wire_size(pkt->opt_rr));
}

static int answer_prepare(knot_pkt_t *answer, knot_pkt_t *query)
{
	if (!knot_wire_get_rd(query->wire)) {
		return kr_error(ENOSYS); /* Only recursive service */
	}
	if (knot_pkt_init_response(answer, query) != 0) {
		return kr_error(ENOMEM); /* Failed to initialize answer */
	}
	/* Handle EDNS in the query */
	if (knot_pkt_has_edns(query)) {
		int ret = edns_create(answer, query);
		if (ret != 0){
			return ret;
		}
	}
	return kr_ok();
}

static int answer_finalize(knot_pkt_t *answer)
{
	knot_pkt_begin(answer, KNOT_ADDITIONAL);
	if (answer->opt_rr) {
		return edns_put(answer);

	}
	return kr_ok();
}

static int query_finalize(struct kr_request *request, knot_pkt_t *pkt)
{
	int ret = 0;
	struct kr_query *qry = kr_rplan_current(&request->rplan);
	knot_pkt_begin(pkt, KNOT_ADDITIONAL);
	if (!(qry->flags & QUERY_SAFEMODE)) {
		ret = edns_create(pkt, request->answer);
		if (ret == 0) {
			ret = edns_put(pkt);
		}
	}
	return ret;
}

Marek Vavruša's avatar
Marek Vavruša committed
205 206 207 208 209 210 211 212 213 214
int kr_resolve(struct kr_context* ctx, knot_pkt_t *answer,
               const knot_dname_t *qname, uint16_t qclass, uint16_t qtype)
{
	if (ctx == NULL || answer == NULL || qname == NULL) {
		return kr_error(EINVAL);
	}

	/* Create memory pool */
	mm_ctx_t pool;
	mm_ctx_mempool(&pool, MM_DEFAULT_BLKSIZE);
215
	knot_pkt_t *query = knot_pkt_new(NULL, KNOT_EDNS_MAX_UDP_PAYLOAD, &pool);
Marek Vavruša's avatar
Marek Vavruša committed
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
	knot_pkt_t *resp = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, &pool);
	if (!query || !resp) {
		mp_delete(pool.ctx);
		return kr_error(ENOMEM);
	}

	/* Initialize context. */
	struct kr_request request;
	request.pool = pool;
	kr_resolve_begin(&request, ctx, answer);
#ifndef NDEBUG
	struct kr_rplan *rplan = &request.rplan; /* for DEBUG_MSG */
#endif
	/* Resolve query, iteratively */
	int proto = 0;
	struct sockaddr *addr = NULL;
	unsigned iter_count = 0;
	int state = kr_resolve_query(&request, qname, qclass, qtype);
	while (state == KNOT_STATE_PRODUCE) {
		/* Hardlimit on iterative queries */
236
		if (++iter_count > KR_ITER_LIMIT) {
237
			DEBUG_MSG("iteration limit %d reached\n", KR_ITER_LIMIT);
Marek Vavruša's avatar
Marek Vavruša committed
238 239
			state = KNOT_STATE_FAIL;
			break;
240
		}
Marek Vavruša's avatar
Marek Vavruša committed
241 242 243 244 245 246 247 248 249 250
		/* Produce next query or finish */
		state = kr_resolve_produce(&request, &addr, &proto, query);
		while (state == KNOT_STATE_CONSUME) {
			/* Get answer from nameserver and consume it */
			int ret = sendrecv(addr, proto, query, resp);
			if (ret != 0) {
				DEBUG_MSG("sendrecv: %s\n", kr_strerror(ret));
			}
			state = kr_resolve_consume(&request, resp);
			knot_pkt_clear(resp);
251
		}
Marek Vavruša's avatar
Marek Vavruša committed
252
		knot_pkt_clear(query);
253 254
	}

Marek Vavruša's avatar
Marek Vavruša committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268
	/* Cleanup */
	kr_resolve_finish(&request, state);
	mp_delete(pool.ctx);
	return state == KNOT_STATE_DONE ? 0 : kr_error(EIO);
}

int kr_resolve_begin(struct kr_request *request, struct kr_context *ctx, knot_pkt_t *answer)
{
	/* Initialize request */
	kr_rplan_init(&request->rplan, ctx, &request->pool);
	knot_overlay_init(&request->overlay, &request->pool);
	request->ctx = ctx;
	request->answer = answer;
	prepare_layers(request);
269

Marek Vavruša's avatar
Marek Vavruša committed
270 271
	/* Expect first query */
	return KNOT_STATE_CONSUME;
272 273
}

Marek Vavruša's avatar
Marek Vavruša committed
274
int kr_resolve_query(struct kr_request *request, const knot_dname_t *qname, uint16_t qclass, uint16_t qtype)
275
{
Marek Vavruša's avatar
Marek Vavruša committed
276 277 278 279
	struct kr_rplan *rplan = &request->rplan;
	struct kr_query *qry = kr_rplan_push(rplan, NULL, qname, qclass, qtype);
	if (!qry) {
		return KNOT_STATE_FAIL;
280
	}
Marek Vavruša's avatar
Marek Vavruša committed
281

282
	/* Initialize answer packet */
Marek Vavruša's avatar
Marek Vavruša committed
283 284 285 286 287 288 289 290
	knot_pkt_t *answer = request->answer;
	knot_wire_set_qr(answer->wire);
	knot_wire_clear_aa(answer->wire);
	knot_wire_set_ra(answer->wire);
	knot_wire_set_rcode(answer->wire, KNOT_RCODE_NOERROR);

	/* Expect answer */
	return KNOT_STATE_PRODUCE;
291 292
}

Marek Vavruša's avatar
Marek Vavruša committed
293
int kr_resolve_consume(struct kr_request *request, knot_pkt_t *packet)
294
{
Marek Vavruša's avatar
Marek Vavruša committed
295 296
	struct kr_rplan *rplan = &request->rplan;
	struct kr_query *qry = kr_rplan_current(rplan);
297

Marek Vavruša's avatar
Marek Vavruša committed
298
	/* Empty resolution plan, push packet as the new query */
299
	if (packet && kr_rplan_empty(rplan)) {
300
		if (answer_prepare(request->answer, packet) != 0) {
301 302
			return KNOT_STATE_FAIL;
		}
303
		/* Start query resolution */
Marek Vavruša's avatar
Marek Vavruša committed
304 305 306 307 308 309 310 311
		const knot_dname_t *qname = knot_pkt_qname(packet);
		uint16_t qclass = knot_pkt_qclass(packet);
		uint16_t qtype = knot_pkt_qtype(packet);
		return kr_resolve_query(request, qname, qclass, qtype);
	}

	/* Different processing for network error */
	int state = KNOT_STATE_FAIL;
312
	if (!packet || packet->size == 0) {
Marek Vavruša's avatar
Marek Vavruša committed
313 314 315 316 317 318
		/* Network error, retry over TCP. */
		if (!(qry->flags & QUERY_TCP)) {
			/** @todo This should just penalize UDP and elect next best. */
			DEBUG_MSG("=> ns unreachable, retrying over TCP\n");
			qry->flags |= QUERY_TCP;
			return KNOT_STATE_CONSUME; /* Try again */
319
		}
Marek Vavruša's avatar
Marek Vavruša committed
320 321
	} else {
		state = knot_overlay_consume(&request->overlay, packet);
322 323
	}

Marek Vavruša's avatar
Marek Vavruša committed
324 325 326 327 328
	/* Resolution failed, invalidate current NS and reset to UDP. */
	if (state == KNOT_STATE_FAIL) {
		DEBUG_MSG("=> resolution failed, invalidating\n");
		if (invalidate_ns(rplan, qry) == 0) {
			qry->flags &= ~QUERY_TCP;
329 330 331
		}
	}

Marek Vavruša's avatar
Marek Vavruša committed
332 333 334 335 336 337 338
	/* Pop query if resolved. */
	if (qry->flags & QUERY_RESOLVED) {
		kr_rplan_pop(rplan, qry);
	}

	knot_overlay_reset(&request->overlay);
	return kr_rplan_empty(&request->rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE;
339 340
}

341
int kr_resolve_produce(struct kr_request *request, struct sockaddr **dst, int *type, knot_pkt_t *packet)
342
{
Marek Vavruša's avatar
Marek Vavruša committed
343 344 345 346 347 348
	struct kr_rplan *rplan = &request->rplan;
	struct kr_query *qry = kr_rplan_current(rplan);
	
	/* No query left for resolution */
	if (kr_rplan_empty(rplan)) {
		return KNOT_STATE_FAIL;
349 350
	}

Marek Vavruša's avatar
Marek Vavruša committed
351 352 353 354 355 356 357 358 359
#ifndef NDEBUG
	char name_str[KNOT_DNAME_MAXLEN], type_str[16];
	knot_dname_to_str(name_str, qry->sname, sizeof(name_str));
	knot_rrtype_to_string(qry->stype, type_str, sizeof(type_str));
	DEBUG_MSG("query '%s %s'\n", type_str, name_str);
#endif

	/* Resolve current query and produce dependent or finish */
	int state = knot_overlay_produce(&request->overlay, packet);
360 361 362 363 364
	if (state != KNOT_STATE_FAIL && knot_wire_get_qr(packet->wire)) {
		/* Produced an answer, consume it. */
		request->overlay.state = KNOT_STATE_CONSUME;
		state = knot_overlay_consume(&request->overlay, packet);
	}
Marek Vavruša's avatar
Marek Vavruša committed
365 366 367
	switch(state) {
	case KNOT_STATE_FAIL: return state; break;
	case KNOT_STATE_CONSUME: break;
368
	case KNOT_STATE_DONE:
Marek Vavruša's avatar
Marek Vavruša committed
369 370 371 372 373 374 375 376 377 378 379 380 381
	default: /* Current query is done */
		knot_overlay_reset(&request->overlay);
		kr_rplan_pop(rplan, qry);
		return kr_rplan_empty(rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE;
	}

	/* Elect best nameserver candidate */
	kr_nsrep_elect(&qry->ns, &qry->zone_cut.nsset);
	if (qry->ns.score < KR_NS_VALID) {
		DEBUG_MSG("=> no valid NS left\n");
		knot_overlay_reset(&request->overlay);
		kr_rplan_pop(rplan, qry);
		return KNOT_STATE_PRODUCE;
382
	} else {
Marek Vavruša's avatar
Marek Vavruša committed
383 384 385 386 387
		if (qry->ns.addr.ip.sa_family == AF_UNSPEC) {
			DEBUG_MSG("=> ns missing A/AAAA, fetching\n");
			knot_overlay_reset(&request->overlay);
			return ns_resolve_addr(qry, request);
		}
388
	}
389

Marek Vavruša's avatar
Marek Vavruša committed
390 391 392 393 394 395 396 397 398
#ifndef NDEBUG
	char qname_str[KNOT_DNAME_MAXLEN], zonecut_str[KNOT_DNAME_MAXLEN], ns_str[SOCKADDR_STRLEN];
	knot_dname_to_str(qname_str, knot_pkt_qname(packet), sizeof(qname_str));
	struct sockaddr *addr = &qry->ns.addr.ip;
	inet_ntop(addr->sa_family, kr_nsrep_inaddr(qry->ns.addr), ns_str, sizeof(ns_str));
	knot_dname_to_str(zonecut_str, qry->zone_cut.name, sizeof(zonecut_str));
	DEBUG_MSG("=> querying: '%s' zone cut: '%s' m12n: '%s'\n", ns_str, zonecut_str, qname_str);
#endif

399 400 401 402 403
	/* Prepare additional query */
	int ret = query_finalize(request, packet);
	if (ret != 0) {
		return KNOT_STATE_FAIL;
	}
Marek Vavruša's avatar
Marek Vavruša committed
404
	*dst = &qry->ns.addr.ip;
405
	*type = (qry->flags & QUERY_TCP) ? SOCK_STREAM : SOCK_DGRAM;
Marek Vavruša's avatar
Marek Vavruša committed
406 407 408 409 410
	return state;
}

int kr_resolve_finish(struct kr_request *request, int state)
{
411
#ifndef NDEBUG
Marek Vavruša's avatar
Marek Vavruša committed
412 413
	struct kr_rplan *rplan = &request->rplan;
	DEBUG_MSG("finished: %d, mempool: %zu B\n", state, (size_t) mp_total_size(request->pool.ctx));
414
#endif
415 416 417 418
	/* Finalize answer */
	if (answer_finalize(request->answer) != 0) {
		state = KNOT_STATE_FAIL;
	}
419
	/* Error during procesing, internal failure */
420
	knot_overlay_finish(&request->overlay);
421
	if (state != KNOT_STATE_DONE) {
Marek Vavruša's avatar
Marek Vavruša committed
422 423 424 425
		knot_pkt_t *answer = request->answer;
		if (knot_wire_get_rcode(answer->wire) == KNOT_RCODE_NOERROR) {
			knot_wire_set_rcode(answer->wire, KNOT_RCODE_SERVFAIL);
		}
426
	}
Marek Vavruša's avatar
Marek Vavruša committed
427
	/* Clean up. */
Marek Vavruša's avatar
Marek Vavruša committed
428
	knot_overlay_deinit(&request->overlay);
429
	request->overlay.state = KNOT_STATE_NOOP;
Marek Vavruša's avatar
Marek Vavruša committed
430 431
	kr_rplan_deinit(&request->rplan);
	return KNOT_STATE_DONE;
432
}