synthrecord.c 12.4 KB
Newer Older
1
/*  Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

    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 18
#include "contrib/net.h"
#include "contrib/sockaddr.h"
19
#include "knot/include/module.h"
20

21
/* Module configuration scheme. */
22 23
#define MOD_NET		"\x07""network"
#define MOD_ORIGIN	"\x06""origin"
24 25 26
#define MOD_PREFIX	"\x06""prefix"
#define MOD_TTL		"\x03""ttl"
#define MOD_TYPE	"\x04""type"
27 28 29

/*! \brief Supported answer synthesis template types. */
enum synth_template_type {
30 31 32
	SYNTH_NULL    = 0,
	SYNTH_FORWARD = 1,
	SYNTH_REVERSE = 2
33 34
};

35
static const knot_lookup_t synthetic_types[] = {
36 37 38 39 40
	{ SYNTH_FORWARD, "forward" },
	{ SYNTH_REVERSE, "reverse" },
	{ 0, NULL }
};

41
int check_prefix(knotd_conf_check_args_t *args)
42
{
43 44
	if (strchr((const char *)args->data, '.') != NULL) {
		args->err_str = "dot '.' is not allowed";
45 46 47 48 49 50
		return KNOT_EINVAL;
	}

	return KNOT_EOK;
}

51
const yp_item_t synth_record_conf[] = {
52
	{ MOD_TYPE,   YP_TOPT,   YP_VOPT = { synthetic_types, SYNTH_NULL } },
53
	{ MOD_PREFIX, YP_TSTR,   YP_VSTR = { "" }, YP_FNONE, { check_prefix } },
54
	{ MOD_ORIGIN, YP_TDNAME, YP_VNONE },
55
	{ MOD_TTL,    YP_TINT,   YP_VINT = { 0, UINT32_MAX, 3600, YP_STIME } },
56
	{ MOD_NET,    YP_TNET,   YP_VNONE },
57 58 59
	{ NULL }
};

60
int synth_record_conf_check(knotd_conf_check_args_t *args)
61
{
62
	// Check type.
63 64
	knotd_conf_t type = knotd_conf_check_item(args, MOD_TYPE);
	if (type.count == 0) {
65
		args->err_str = "no synthesis type specified";
66 67 68 69
		return KNOT_EINVAL;
	}

	// Check origin.
70 71
	knotd_conf_t origin = knotd_conf_check_item(args, MOD_ORIGIN);
	if (origin.count == 0 && type.single.option == SYNTH_REVERSE) {
72
		args->err_str = "no origin specified";
73 74
		return KNOT_EINVAL;
	}
75
	if (origin.count != 0 && type.single.option == SYNTH_FORWARD) {
76
		args->err_str = "origin not allowed with forward type";
77 78 79
		return KNOT_EINVAL;
	}

80
	// Check network subnet.
81 82
	knotd_conf_t net = knotd_conf_check_item(args, MOD_NET);
	if (net.count == 0) {
83
		args->err_str = "no network subnet specified";
84 85 86 87 88 89
		return KNOT_EINVAL;
	}

	return KNOT_EOK;
}

90 91 92
/* Defines. */
#define ARPA_ZONE_LABELS 2

93 94 95 96 97
/*!
 * \brief Synthetic response template.
 */
typedef struct synth_template {
	enum synth_template_type type;
98 99
	char *prefix;
	char *zone;
100
	uint32_t ttl;
101
	struct sockaddr_storage addr;
102
	struct sockaddr_storage addr_max;
103
	int addr_mask;
104 105
} synth_template_t;

106
/*! \brief Substitute all occurrences of given character. */
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
static void str_subst(char *str, size_t len, char from, char to)
{
	for (int i = 0; i < len; ++i) {
		if (str[i] == from) {
			str[i] = to;
		}
	}
}

/*! \brief Separator character for address family. */
static char str_separator(int addr_family)
{
	if (addr_family == AF_INET6) {
		return ':';
	}
	return '.';
}

125 126
/*! \brief Return true if query type is satisfied with provided address family. */
static bool query_satisfied_by_family(uint16_t qtype, int family)
127
{
128
	switch (qtype) {
129 130 131 132
	case KNOT_RRTYPE_A:    return family == AF_INET;
	case KNOT_RRTYPE_AAAA: return family == AF_INET6;
	case KNOT_RRTYPE_ANY:  return true;
	default:               return false;
133 134 135
	}
}

136
/*! \brief Parse address from reverse query QNAME and return address family. */
137
static int reverse_addr_parse(knotd_qdata_t *qdata, synth_template_t *tpl, char *addr_str)
138 139 140 141
{
	/* QNAME required format is [address].[subnet/zone]
	 * f.e.  [1.0...0].[h.g.f.e.0.0.0.0.d.c.b.a.ip6.arpa] represents
	 *       [abcd:0:efgh::1] */
142
	const knot_dname_t* label = qdata->name;
143 144 145 146 147
	const uint8_t *query_wire = qdata->query->wire;

	/* Push labels on stack for reverse walkthrough. */
	const uint8_t* label_stack[KNOT_DNAME_MAXLABELS];
	const uint8_t** sp = label_stack;
148
	int label_count = knot_dname_labels(label, query_wire);
149 150 151 152 153 154 155
	while(label_count > ARPA_ZONE_LABELS) {
		*sp++ = label;
		label = knot_wire_next_label(label, query_wire);
		--label_count;
	}

	/* Write formatted address string. */
156
	char sep = str_separator(tpl->addr.ss_family);
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
	int sep_frequency = 1;
	if (sep == ':') {
		sep_frequency = 4; /* Separator per 4 hexdigits. */
	}

	char *dst = addr_str;
	label_count = 0;
	while(sp != label_stack) {
		label = *--sp;
		/* Write separator for each Nth label. */
		if (label_count == sep_frequency) {
			*dst = sep;
			dst += 1;
			label_count = 0;
		}
		/* Write label */
		memcpy(dst, label + 1, label[0]);
		dst += label[0];
		label_count += 1;
	}

178
	return KNOT_EOK;
179 180
}

181
static int forward_addr_parse(knotd_qdata_t *qdata, synth_template_t *tpl, char *addr_str)
182 183
{
	/* Find prefix label count (additive to prefix length). */
184
	const knot_dname_t *addr_label = qdata->name;
185 186 187 188 189 190

	/* Mismatch if label shorter/equal than prefix. */
	int prefix_len = strlen(tpl->prefix);
	if (addr_label == NULL || addr_label[0] <= prefix_len) {
		return KNOT_EINVAL;
	}
191

192 193
	int addr_len = *addr_label - prefix_len;
	memcpy(addr_str, addr_label + 1 + prefix_len, addr_len);
194

195
	/* Restore correct address format. */
196
	char sep = str_separator(tpl->addr.ss_family);
197
	str_subst(addr_str, addr_len, '-', sep);
198 199

	return KNOT_EOK;
200 201
}

202
static int addr_parse(knotd_qdata_t *qdata, synth_template_t *tpl, char *addr_str)
203
{
204
	/* Check if we have at least 1 label below zone. */
205
	int zone_labels = knot_dname_labels(knotd_qdata_zone_name(qdata), NULL);
206 207
	int query_labels = knot_dname_labels(qdata->name, qdata->query->wire);
	if (query_labels < zone_labels + 1) {
208 209 210
		return KNOT_EINVAL;
	}

211
	switch (tpl->type) {
212
	case SYNTH_REVERSE: return reverse_addr_parse(qdata, tpl, addr_str);
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
	case SYNTH_FORWARD: return forward_addr_parse(qdata, tpl, addr_str);
	default:            return KNOT_EINVAL;
	}
}

static knot_dname_t *synth_ptrname(const char *addr_str, synth_template_t *tpl)
{
	/* PTR right-hand value is [prefix][address][zone] */
	char ptrname[KNOT_DNAME_MAXLEN] = {'\0'};
	int prefix_len = strlen(tpl->prefix);
	int addr_len = strlen(addr_str);
	int zone_len = strlen(tpl->zone);

	/* Check required space (zone requires extra leading dot). */
	if (prefix_len + addr_len + zone_len + 1 >= KNOT_DNAME_MAXLEN) {
		return NULL;
	}

	/* Write prefix string. */
	memcpy(ptrname, tpl->prefix, prefix_len);
	int written = prefix_len;

	/* Write address with substituted separator to '-'. */
236
	char sep = str_separator(tpl->addr.ss_family);
237 238 239 240 241 242 243 244 245 246
	memcpy(ptrname + written, addr_str, addr_len);
	str_subst(ptrname + written, addr_len, sep, '-');
	written += addr_len;

	/* Write zone name. */
	ptrname[written] = '.';
	written += 1;
	memcpy(ptrname + written, tpl->zone, zone_len);

	/* Convert to domain name. */
247
	return knot_dname_from_str_alloc(ptrname);
248 249
}

250
static int reverse_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, knot_rrset_t *rr)
251 252 253 254
{
	/* Synthetize PTR record data. */
	knot_dname_t *ptrname = synth_ptrname(addr_str, tpl);
	if (ptrname == NULL) {
255
		return KNOT_ENOMEM;
256
	}
257 258

	rr->type = KNOT_RRTYPE_PTR;
259
	knot_rrset_add_rdata(rr, ptrname, knot_dname_size(ptrname), tpl->ttl, &pkt->mm);
260
	knot_dname_free(&ptrname, NULL);
261

262
	return KNOT_EOK;
263 264
}

265
static int forward_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt, knot_rrset_t *rr)
266 267
{
	struct sockaddr_storage query_addr = {'\0'};
268
	sockaddr_set(&query_addr, tpl->addr.ss_family, addr_str, 0);
269

270
	/* Specify address type and data. */
271
	if (tpl->addr.ss_family == AF_INET6) {
272
		rr->type = KNOT_RRTYPE_AAAA;
273
		const struct sockaddr_in6* ip = (const struct sockaddr_in6*)&query_addr;
274 275
		knot_rrset_add_rdata(rr, (const uint8_t *)&ip->sin6_addr,
		                     sizeof(struct in6_addr), tpl->ttl, &pkt->mm);
276
	} else if (tpl->addr.ss_family == AF_INET) {
277
		rr->type = KNOT_RRTYPE_A;
278
		const struct sockaddr_in* ip = (const struct sockaddr_in*)&query_addr;
279 280
		knot_rrset_add_rdata(rr, (const uint8_t *)&ip->sin_addr,
		                     sizeof(struct in_addr), tpl->ttl, &pkt->mm);
281 282
	} else {
		return KNOT_EINVAL;
283 284
	}

285
	return KNOT_EOK;
286 287
}

288 289
static knot_rrset_t *synth_rr(char *addr_str, synth_template_t *tpl, knot_pkt_t *pkt,
                              knotd_qdata_t *qdata)
290
{
291
	knot_rrset_t *rr = knot_rrset_new(qdata->name, 0, KNOT_CLASS_IN, &pkt->mm);
292 293 294 295 296 297
	if (rr == NULL) {
		return NULL;
	}

	/* Fill in the specific data. */
	int ret = KNOT_ERROR;
298
	switch (tpl->type) {
299 300 301 302 303 304
	case SYNTH_REVERSE: ret = reverse_rr(addr_str, tpl, pkt, rr); break;
	case SYNTH_FORWARD: ret = forward_rr(addr_str, tpl, pkt, rr); break;
	default: break;
	}

	if (ret != KNOT_EOK) {
305
		knot_rrset_free(&rr, &pkt->mm);
306
		return NULL;
307
	}
308 309

	return rr;
310 311 312
}

/*! \brief Check if query fits the template requirements. */
313 314
static knotd_in_state_t template_match(knotd_in_state_t state, synth_template_t *tpl,
                                       knot_pkt_t *pkt, knotd_qdata_t *qdata)
315 316 317
{
	/* Parse address from query name. */
	char addr_str[SOCKADDR_STRLEN] = { '\0' };
318 319
	int ret = addr_parse(qdata, tpl, addr_str);
	if (ret != KNOT_EOK) {
320 321 322 323
		return state; /* Can't identify addr in QNAME, not applicable. */
	}

	/* Match against template netblock. */
324
	struct sockaddr_storage query_addr = { '\0' };
325
	int provided_af = tpl->addr.ss_family;
326
	ret = sockaddr_set(&query_addr, provided_af, addr_str, 0);
327 328
	if (ret != KNOT_EOK) {
		return state;
329
	}
330
	if (tpl->addr_max.ss_family == AF_UNSPEC) {
331 332
		if (!sockaddr_net_match((struct sockaddr *)&query_addr,
		                        (struct sockaddr *)&tpl->addr,
333
		                        tpl->addr_mask)) {
334 335 336
			return state; /* Out of our netblock, not applicable. */
		}
	} else {
337 338 339
		if (!sockaddr_range_match((struct sockaddr *)&query_addr,
		                          (struct sockaddr *)&tpl->addr,
		                          (struct sockaddr *)&tpl->addr_max)) {
340 341
			return state; /* Out of our netblock, not applicable. */
		}
342 343
	}

344
	/* Check if the request is for an available query type. */
345
	uint16_t qtype = knot_pkt_qtype(qdata->query);
346
	switch (tpl->type) {
347 348
	case SYNTH_FORWARD:
		if (!query_satisfied_by_family(qtype, provided_af)) {
349
			qdata->rcode = KNOT_RCODE_NOERROR;
350
			return KNOTD_IN_STATE_NODATA;
351
		}
352 353 354 355
		break;
	case SYNTH_REVERSE:
		if (qtype != KNOT_RRTYPE_PTR && qtype != KNOT_RRTYPE_ANY) {
			qdata->rcode = KNOT_RCODE_NOERROR;
356
			return KNOTD_IN_STATE_NODATA;
357 358 359 360
		}
		break;
	default:
		break;
361 362
	}

363 364 365 366
	/* Synthetise record from template. */
	knot_rrset_t *rr = synth_rr(addr_str, tpl, pkt, qdata);
	if (rr == NULL) {
		qdata->rcode = KNOT_RCODE_SERVFAIL;
367
		return KNOTD_IN_STATE_ERROR;
368 369
	}

370 371
	/* Insert synthetic response into packet. */
	if (knot_pkt_put(pkt, 0, rr, KNOT_PF_FREE) != KNOT_EOK) {
372
		return KNOTD_IN_STATE_ERROR;
373 374
	}

375 376 377
	/* Authoritative response. */
	knot_wire_set_aa(pkt->wire);

378
	return KNOTD_IN_STATE_HIT;
379 380
}

381 382
static knotd_in_state_t solve_synth_record(knotd_in_state_t state, knot_pkt_t *pkt,
                                           knotd_qdata_t *qdata, knotd_mod_t *mod)
383
{
384
	assert(pkt && qdata && mod);
385 386

	/* Applicable when search in zone fails. */
387
	if (state != KNOTD_IN_STATE_MISS) {
388 389 390 391
		return state;
	}

	/* Check if template fits. */
392
	return template_match(state, knotd_mod_ctx(mod), pkt, qdata);
393 394
}

395
int synth_record_load(knotd_mod_t *mod)
396 397
{
	/* Create synthesis template. */
398
	synth_template_t *tpl = calloc(1, sizeof(*tpl));
399 400 401 402
	if (tpl == NULL) {
		return KNOT_ENOMEM;
	}

403
	/* Set type. */
404 405
	knotd_conf_t conf = knotd_conf_mod(mod, MOD_TYPE);
	tpl->type = conf.single.option;
406

407
	/* Set prefix. */
408 409
	conf = knotd_conf_mod(mod, MOD_PREFIX);
	tpl->prefix = strdup(conf.single.string);
410

411
	/* Set origin if generating reverse record. */
412
	if (tpl->type == SYNTH_REVERSE) {
413 414
		conf = knotd_conf_mod(mod, MOD_ORIGIN);
		tpl->zone = knot_dname_to_str_alloc(conf.single.dname);
415 416
		if (tpl->zone == NULL) {
			free(tpl->prefix);
417
			free(tpl);
418 419
			return KNOT_ENOMEM;
		}
420 421
	}

422
	/* Set ttl. */
423 424
	conf = knotd_conf_mod(mod, MOD_TTL);
	tpl->ttl = conf.single.integer;
425 426

	/* Set address. */
427 428 429 430
	conf = knotd_conf_mod(mod, MOD_NET);
	tpl->addr = conf.single.addr;
	tpl->addr_max = conf.single.addr_max;
	tpl->addr_mask = conf.single.addr_mask;
431

432
	knotd_mod_ctx_set(mod, tpl);
433

434
	return knotd_mod_in_hook(mod, KNOTD_STAGE_ANSWER, solve_synth_record);
435 436
}

437
void synth_record_unload(knotd_mod_t *mod)
438
{
439
	synth_template_t *tpl = knotd_mod_ctx(mod);
440 441 442

	free(tpl->zone);
	free(tpl->prefix);
443
	free(tpl);
444
}
445 446 447 448

KNOTD_MOD_API(synthrecord, KNOTD_MOD_FLAG_SCOPE_ZONE,
              synth_record_load, synth_record_unload, synth_record_conf,
              synth_record_conf_check);