hints.c 9.25 KB
Newer Older
Marek Vavruša's avatar
Marek Vavruša committed
1
/*  Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2

Marek Vavruša's avatar
Marek Vavruša committed
3 4 5 6
    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.
7

Marek Vavruša's avatar
Marek Vavruša committed
8 9 10 11
    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.
12

Marek Vavruša's avatar
Marek Vavruša committed
13 14 15
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
16

17 18 19 20 21 22 23
/**
 * @file hints.h
 * @brief Constructed zone cut from the hosts-like file, see @zonecut.h
 *
 * The module provides an override for queried address records.
 */

24 25 26
#include <libknot/packet/pkt.h>
#include <libknot/descriptor.h>
#include <libknot/rrtype/aaaa.h>
27 28
#include <ccan/json/json.h>
#include <ucw/mempool.h>
29

30
#include "daemon/engine.h"
31
#include "lib/zonecut.h"
32 33
#include "lib/module.h"
#include "lib/layer.h"
34

35
/* Defaults */
36
#define DEFAULT_FILE "/etc/hosts"
37
#define DEBUG_MSG(qry, fmt...) QRDEBUG(qry, "hint",  fmt)
38

39
static int begin(knot_layer_t *ctx, void *module_param)
40
{
41
	ctx->data = module_param;
42 43 44
	return ctx->state;
}

45
static int answer_query(knot_pkt_t *pkt, pack_t *addr_set, struct kr_query *qry)
46
{
47 48 49 50 51 52 53
	uint16_t rrtype = qry->stype;
	uint16_t rrclass = qry->sclass;
	if (rrtype != KNOT_RRTYPE_A && rrtype != KNOT_RRTYPE_AAAA) {
		return kr_error(ENOENT);
	}

	knot_dname_t *qname = knot_dname_copy(qry->sname, &pkt->mm);
54
	knot_rrset_t rr;
55
	knot_rrset_init(&rr, qname, rrtype, rrclass);
56 57 58 59 60
	int family_len = sizeof(struct in_addr);
	if (rr.type == KNOT_RRTYPE_AAAA) {
		family_len = sizeof(struct in6_addr);
	}

61
	/* Append address records from hints */
62 63 64 65 66
	uint8_t *addr = pack_head(*addr_set);
	while (addr != pack_tail(*addr_set)) {
		size_t len = pack_obj_len(addr);
		void *addr_val = pack_obj_val(addr);
		if (len == family_len) {
67
			knot_rrset_add_rdata(&rr, addr_val, len, 0, &pkt->mm);
68 69 70 71
		}
		addr = pack_obj_next(addr);
	}

72 73 74 75 76 77 78 79 80
	int ret = kr_error(ENOENT);
	if (!knot_rrset_empty(&rr)) {
		/* Update packet question */
		if (!knot_dname_is_equal(knot_pkt_qname(pkt), qname)) {
			KR_PKT_RECYCLE(pkt);
			knot_pkt_put_question(pkt, qname, rrclass, rrtype);
		}
		/* Append to packet */
		ret = knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &rr, KNOT_PF_FREE);
81
	}
82
	/* Clear RR if failed */
83 84
	if (ret != 0) {
		knot_rrset_clear(&rr, &pkt->mm);
85
	}
86
	return ret;
87 88
}

89
static int query(knot_layer_t *ctx, knot_pkt_t *pkt)
90
{
91 92
	struct kr_request *param = ctx->data;
	struct kr_query *qry = kr_rplan_current(&param->rplan);
93 94 95
	if (!qry || ctx->state & (KNOT_STATE_DONE|KNOT_STATE_FAIL)) {
		return ctx->state;
	}
96

97
	/* Find a matching name */
98 99 100
	struct kr_module *module = ctx->api->data;
	struct kr_zonecut *hint_map = module->data;
	pack_t *pack = kr_zonecut_find(hint_map, qry->sname);
101
	if (!pack || pack->len == 0) {
102 103 104
		return ctx->state;
	}

105
	/* Write to packet */
106
	int ret = answer_query(pkt, pack, qry);
107 108 109 110
	if (ret != 0) {
		return ctx->state;
	}
	DEBUG_MSG(qry, "<= answered from hints\n");
111 112
	qry->flags |= QUERY_CACHED|QUERY_NO_MINIMIZE;
	pkt->parsed = pkt->size;
113 114
	knot_wire_set_qr(pkt->wire);
	return KNOT_STATE_DONE;
115
}
116

117 118 119 120 121
static int parse_addr_str(struct sockaddr_storage *sa, const char *addr)
{
	int family = strchr(addr, ':') ? AF_INET6 : AF_INET;
	return sockaddr_set(sa, family, addr, 0);
}
122

123 124 125 126 127 128
static int add_pair(struct kr_zonecut *hints, const char *name, const char *addr)
{
	/* Build key */
	knot_dname_t key[KNOT_DNAME_MAXLEN];
	if (!knot_dname_from_str(key, name, sizeof(key))) {
		return kr_error(EINVAL);
129 130
	}

131 132 133 134 135 136 137 138 139 140 141 142 143
	/* Parse address string */
	struct sockaddr_storage ss;
	if (parse_addr_str(&ss, addr) != 0) {
		return kr_error(EINVAL);
	}

	/* Build rdata */
	size_t addr_len = 0;
	uint8_t *raw_addr = sockaddr_raw(&ss, &addr_len);
	knot_rdata_t rdata[knot_rdata_array_size(addr_len)];
	knot_rdata_init(rdata, addr_len, raw_addr, 0);

	return kr_zonecut_add(hints, key, rdata);
144
}
145

146
static int load_map(struct kr_zonecut *hints, FILE *fp)
147 148
{
	size_t line_len = 0;
149
	size_t count = 0;
150 151 152 153 154 155 156 157 158 159
	auto_free char *line = NULL;

	while(getline(&line, &line_len, fp) > 0) {
		char *saveptr = NULL;
		char *tok = strtok_r(line, " \t\r", &saveptr);
		if (tok == NULL || strchr(tok, '#') || strlen(tok) == 0) {
			continue;
		}
		char *name_tok = strtok_r(NULL, " \t\n", &saveptr);
		while (name_tok != NULL) {
160 161
			if (add_pair(hints, name_tok, tok) == 0) {
				count += 1;
162 163 164 165 166
			}
			name_tok = strtok_r(NULL, " \t\n", &saveptr);
		}
	}

167
	DEBUG_MSG(NULL, "loaded %zu hints\n", count);
168 169 170
	return kr_ok();
}

171 172 173 174
static int load(struct kr_module *module, const char *path)
{
	auto_fclose FILE *fp = fopen(path, "r");
	if (fp == NULL) {
175
		DEBUG_MSG(NULL, "reading '%s' failed: %s\n", path, strerror(errno));
176 177
		return kr_error(errno);
	} else {
178
		DEBUG_MSG(NULL, "reading '%s'\n", path);
179 180 181
	}

	/* Create pool and copy itself */
182 183 184 185
	mm_ctx_t _pool = {
		.ctx = mp_new(4096),
		.alloc = (mm_alloc_t) mp_alloc
	};
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
	mm_ctx_t *pool = mm_alloc(&_pool, sizeof(*pool));
	if (!pool) {
		return kr_error(ENOMEM);
	}
	memcpy(pool, &_pool, sizeof(*pool));

	/* Load file to map */
	struct kr_zonecut *hints = mm_alloc(pool, sizeof(*hints));
	kr_zonecut_init(hints, (const uint8_t *)(""), pool);
	module->data = hints;
	return load_map(hints, fp);
}

static void unload(struct kr_module *module)
{
	struct kr_zonecut *hints = module->data;
	if (hints) {
203
		kr_zonecut_deinit(hints);
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
		mp_delete(hints->pool->ctx);
		module->data = NULL;
	}
}

/**
 * Set name => address hint.
 *
 * Input:  { name, address }
 * Output: { result: bool }
 *
 */
static char* hint_set(void *env, struct kr_module *module, const char *args)
{
	struct kr_zonecut *hints = module->data;
	auto_free char *args_copy = strdup(args);

	int ret = -1;
	char *addr = strchr(args_copy, ' ');
	if (addr) {
		*addr = '\0';
		ret = add_pair(hints, args_copy, addr + 1);
	}

	char *result = NULL;
229
	asprintf(&result, "{ \"result\": %s", ret == 0 ? "true" : "false");
230 231 232
	return result;
}

233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
/** @internal Pack address list into JSON array. */
static JsonNode *pack_addrs(pack_t *pack)
{
	char buf[SOCKADDR_STRLEN];
	JsonNode *root = json_mkarray();
	uint8_t *addr = pack_head(*pack);
	while (addr != pack_tail(*pack)) {
		size_t len = pack_obj_len(addr);
		int family = len == sizeof(struct in_addr) ? AF_INET : AF_INET6;
		if (!inet_ntop(family, pack_obj_val(addr), buf, sizeof(buf))) {
			break;
		}
		json_append_element(root, json_mkstring(buf));
		addr = pack_obj_next(addr);
	}
	return root;
}

251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
/**
 * Retrieve address hint for given name.
 *
 * Input:  name
 * Output: { address1, address2, ... }
 */
static char* hint_get(void *env, struct kr_module *module, const char *args)
{
	struct kr_zonecut *hints = module->data;
	knot_dname_t key[KNOT_DNAME_MAXLEN];
	pack_t *pack = NULL;
	if (knot_dname_from_str(key, args, sizeof(key))) {
		pack = kr_zonecut_find(hints, key);
	}
	if (!pack || pack->len == 0) {
		return NULL;
	}

269 270 271 272 273
	char *result = NULL;
	JsonNode *root = pack_addrs(pack);
	if (root) {
		result = json_encode(root);
		json_delete(root);
274
	}
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
	return result;
}

/** Retrieve hint list. */
static int pack_hint(const char *k, void *v, void *baton)
{
	char nsname_str[KNOT_DNAME_MAXLEN] = {'\0'};
	knot_dname_to_str(nsname_str, (const uint8_t *)k, sizeof(nsname_str));
	JsonNode *root_node = baton;
	JsonNode *addr_list = pack_addrs((pack_t *)v);
	if (!addr_list) {
		return kr_error(ENOMEM);
	}
	json_append_member(root_node, nsname_str, addr_list);
	return kr_ok();
}
291

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
/**
 * Get/set root hints set.
 *
 * Input:  { name: [addr_list], ... }
 * Output: current list
 *
 */
static char* hint_root(void *env, struct kr_module *module, const char *args)
{
	struct engine *engine = env;
	struct kr_context *ctx = &engine->resolver;
	/* Replace root hints if parameter is set */
	if (args && strlen(args) > 0) {
		JsonNode *node = NULL;
		JsonNode *root_node = json_decode(args);
		kr_zonecut_set(&ctx->root_hints, (const uint8_t *)"");
		json_foreach(node, root_node) {
			switch(node->tag) {
			case JSON_STRING: add_pair(&ctx->root_hints, node->key, node->string_); break;
			default: continue;
			}
		}
314
		json_delete(root_node);
315 316 317 318 319 320 321 322
	}
	/* Return current root hints */
	char *result = NULL;
	JsonNode *root_node = json_mkobject();
	if (map_walk(&ctx->root_hints.nsset, pack_hint, root_node) == 0) {
		result = json_encode(root_node);
	}
	json_delete(root_node);
323 324 325
	return result;
}

326 327 328
/*
 * Module implementation.
 */
329

330
const knot_layer_api_t *hints_layer(struct kr_module *module)
331
{
332
	static knot_layer_api_t _layer = {
333
		.begin = &begin,
334
		.produce = &query,
335
	};
336 337
	/* Store module reference */
	_layer.data = module;
338
	return &_layer;
339 340
}

341
int hints_init(struct kr_module *module)
342
{
343 344
	module->data = NULL;
	return 0;
345
}
346

347
int hints_config(struct kr_module *module, const char *conf)
348
{
349 350 351
	unload(module);
	if (!conf || strlen(conf) < 1) {
		conf = DEFAULT_FILE;
352
	}
353 354
	return load(module, conf);
}
355

356 357 358
int hints_deinit(struct kr_module *module)
{
	unload(module);
359
	return kr_ok();
360 361
}

362 363 364 365 366
struct kr_prop *hints_props(void)
{
	static struct kr_prop prop_list[] = {
	    { &hint_set,    "set", "Set {name, address} hint.", },
	    { &hint_get,    "get", "Retrieve hint for given name.", },
367
	    { &hint_root,   "root", "Replace root hints set (empty value to return current list).", },
368 369 370 371 372
	    { NULL, NULL, NULL }
	};
	return prop_list;
}

Marek Vavruša's avatar
Marek Vavruša committed
373
KR_MODULE_EXPORT(hints);