1 /*  Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
2 
3     This program is free software: you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation, either version 3 of the License, or
6     (at your option) any later version.
7 
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12 
13     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/>.
15  */
16 
17 #include "contrib/ctype.h"
18 #include "contrib/macros.h"
19 #include "contrib/net.h"
20 #include "contrib/sockaddr.h"
21 #include "contrib/wire_ctx.h"
22 #include "knot/include/module.h"
23 
24 #define MOD_NET		"\x07""network"
25 #define MOD_ORIGIN	"\x06""origin"
26 #define MOD_PREFIX	"\x06""prefix"
27 #define MOD_TTL		"\x03""ttl"
28 #define MOD_TYPE	"\x04""type"
29 #define MOD_SHORT	"\x0d""reverse-short"
30 
31 /*! \brief Supported answer synthesis template types. */
32 enum synth_template_type {
33 	SYNTH_NULL    = 0,
34 	SYNTH_FORWARD = 1,
35 	SYNTH_REVERSE = 2
36 };
37 
38 static const knot_lookup_t synthetic_types[] = {
39 	{ SYNTH_FORWARD, "forward" },
40 	{ SYNTH_REVERSE, "reverse" },
41 	{ 0, NULL }
42 };
43 
check_prefix(knotd_conf_check_args_t * args)44 int check_prefix(knotd_conf_check_args_t *args)
45 {
46 	if (strchr((const char *)args->data, '.') != NULL) {
47 		args->err_str = "dot '.' is not allowed";
48 		return KNOT_EINVAL;
49 	}
50 
51 	return KNOT_EOK;
52 }
53 
54 const yp_item_t synth_record_conf[] = {
55 	{ MOD_TYPE,   YP_TOPT,   YP_VOPT = { synthetic_types, SYNTH_NULL } },
56 	{ MOD_PREFIX, YP_TSTR,   YP_VSTR = { "" }, YP_FNONE, { check_prefix } },
57 	{ MOD_ORIGIN, YP_TDNAME, YP_VNONE },
58 	{ MOD_TTL,    YP_TINT,   YP_VINT = { 0, UINT32_MAX, 3600, YP_STIME } },
59 	{ MOD_NET,    YP_TNET,   YP_VNONE, YP_FMULTI },
60 	{ MOD_SHORT,  YP_TBOOL,  YP_VBOOL = { true } },
61 	{ NULL }
62 };
63 
synth_record_conf_check(knotd_conf_check_args_t * args)64 int synth_record_conf_check(knotd_conf_check_args_t *args)
65 {
66 	// Check type.
67 	knotd_conf_t type = knotd_conf_check_item(args, MOD_TYPE);
68 	if (type.count == 0) {
69 		args->err_str = "no synthesis type specified";
70 		return KNOT_EINVAL;
71 	}
72 
73 	// Check origin.
74 	knotd_conf_t origin = knotd_conf_check_item(args, MOD_ORIGIN);
75 	if (origin.count == 0 && type.single.option == SYNTH_REVERSE) {
76 		args->err_str = "no origin specified";
77 		return KNOT_EINVAL;
78 	}
79 	if (origin.count != 0 && type.single.option == SYNTH_FORWARD) {
80 		args->err_str = "origin not allowed with forward type";
81 		return KNOT_EINVAL;
82 	}
83 
84 	// Check network subnet.
85 	knotd_conf_t net = knotd_conf_check_item(args, MOD_NET);
86 	if (net.count == 0) {
87 		args->err_str = "no network subnet specified";
88 		return KNOT_EINVAL;
89 	}
90 	knotd_conf_free(&net);
91 
92 	// Check reverse-short parameter is only for reverse synthrecord.
93 	knotd_conf_t reverse_short = knotd_conf_check_item(args, MOD_SHORT);
94 	if (reverse_short.count != 0 && type.single.option == SYNTH_FORWARD) {
95 		args->err_str = "reverse-short not allowed with forward type";
96 		return KNOT_EINVAL;
97 	}
98 
99 	return KNOT_EOK;
100 }
101 
102 #define ARPA_ZONE_LABELS	2
103 #define IPV4_ADDR_LABELS	4
104 #define IPV6_ADDR_LABELS	32
105 #define IPV4_ARPA_DNAME		(uint8_t *)"\x07""in-addr""\x04""arpa"
106 #define IPV6_ARPA_DNAME		(uint8_t *)"\x03""ip6""\x04""arpa"
107 #define IPV4_ARPA_LEN		14
108 #define IPV6_ARPA_LEN		10
109 
110 /*!
111  * \brief Synthetic response template.
112  */
113 typedef struct {
114 	struct sockaddr_storage addr;
115 	struct sockaddr_storage addr_max;
116 	int addr_mask;
117 } synth_templ_addr_t;
118 
119 typedef struct {
120 	enum synth_template_type type;
121 	char *prefix;
122 	size_t prefix_len;
123 	char *zone;
124 	size_t zone_len;
125 	uint32_t ttl;
126 	size_t addr_count;
127 	synth_templ_addr_t *addr;
128 	bool reverse_short;
129 } synth_template_t;
130 
131 typedef union {
132 	uint32_t b32;
133 	uint8_t b4[4];
134 } addr_block_t;
135 
136 /*! \brief Write one IPV4 address block without redundant leading zeros. */
block_write(addr_block_t * block,char * addr_str)137 static unsigned block_write(addr_block_t *block, char *addr_str)
138 {
139 	unsigned len = 0;
140 
141 	if (block->b4[0] != '0') {
142 		addr_str[len++] = block->b4[0];
143 	}
144 	if (len > 0 || block->b4[1] != '0') {
145 		addr_str[len++] = block->b4[1];
146 	}
147 	if (len > 0 || block->b4[2] != '0') {
148 		addr_str[len++] = block->b4[2];
149 	}
150 	addr_str[len++] = block->b4[3];
151 
152 	return len;
153 }
154 
155 /*! \brief Substitute all occurrences of given character. */
str_subst(char * str,size_t len,char from,char to)156 static void str_subst(char *str, size_t len, char from, char to)
157 {
158 	for (int i = 0; i < len; ++i) {
159 		if (str[i] == from) {
160 			str[i] = to;
161 		}
162 	}
163 }
164 
165 /*! \brief Separator character for address family. */
str_separator(int addr_family)166 static char str_separator(int addr_family)
167 {
168 	return (addr_family == AF_INET6) ? ':' : '.';
169 }
170 
171 /*! \brief Return true if query type is satisfied with provided address family. */
query_satisfied_by_family(uint16_t qtype,int family)172 static bool query_satisfied_by_family(uint16_t qtype, int family)
173 {
174 	switch (qtype) {
175 	case KNOT_RRTYPE_A:    return family == AF_INET;
176 	case KNOT_RRTYPE_AAAA: return family == AF_INET6;
177 	case KNOT_RRTYPE_ANY:  return true;
178 	default:               return false;
179 	}
180 }
181 
182 /*! \brief Parse address from reverse query QNAME and return address family. */
reverse_addr_parse(knotd_qdata_t * qdata,const synth_template_t * tpl,char * addr_str,int * addr_family,bool * parent)183 static int reverse_addr_parse(knotd_qdata_t *qdata, const synth_template_t *tpl,
184                               char *addr_str, int *addr_family, bool *parent)
185 {
186 	/* QNAME required format is [address].[subnet/zone]
187 	 * f.e.  [1.0...0].[h.g.f.e.0.0.0.0.d.c.b.a.ip6.arpa] represents
188 	 *       [abcd:0:efgh::1] */
189 	const knot_dname_t *label = qdata->name; // uncompressed name
190 
191 	static const char ipv4_zero[] = "0.0.0.0";
192 
193 	bool can_ipv4 = true;
194 	bool can_ipv6 = true;
195 	unsigned labels = 0;
196 
197 	uint8_t buf4[16], *buf4_end = buf4 + sizeof(buf4), *buf4_pos = buf4_end;
198 	uint8_t buf6[32], *buf6_end = buf6 + sizeof(buf6), *buf6_pos = buf6_end;
199 
200 	for ( ; labels < IPV6_ADDR_LABELS; labels++) {
201 		if (unlikely(*label == 0)) {
202 			return KNOT_EINVAL;
203 		}
204 		if (label[1] == 'i') {
205 			break;
206 		}
207 		if (labels < IPV4_ADDR_LABELS) {
208 			switch (*label) {
209 			case 1:
210 				assert(buf4 + 1 < buf4_pos && buf6 < buf6_pos);
211 				*--buf6_pos = label[1];
212 				*--buf4_pos = label[1];
213 				*--buf4_pos = '.';
214 				break;
215 			case 2:
216 			case 3:
217 				assert(buf4 + *label < buf4_pos);
218 				can_ipv6 = false;
219 				buf4_pos -= *label;
220 				memcpy(buf4_pos, label + 1, *label);
221 				*--buf4_pos = '.';
222 				break;
223 			default:
224 				return KNOT_EINVAL;
225 			}
226 		} else {
227 			can_ipv4 = false;
228 			if (!can_ipv6 || *label != 1) {
229 				return KNOT_EINVAL;
230 			}
231 			assert(buf6 < buf6_pos);
232 			*--buf6_pos = label[1];
233 
234 		}
235 		label += *label + sizeof(*label);
236 	}
237 
238 	if (can_ipv4 && knot_dname_is_equal(label, IPV4_ARPA_DNAME)) {
239 		*addr_family = AF_INET;
240 		*parent = (labels < IPV4_ADDR_LABELS);
241 		int buf4_overweight = (buf4_end - buf4_pos) - (2 * labels);
242 		assert(buf4_overweight >= 0);
243 		memcpy(addr_str + buf4_overweight, ipv4_zero, sizeof(ipv4_zero));
244 		if (labels > 0) {
245 			buf4_pos++; // skip leading '.'
246 			memcpy(addr_str, buf4_pos, buf4_end - buf4_pos);
247 		}
248 		return KNOT_EOK;
249 	} else if (can_ipv6 && knot_dname_is_equal(label, IPV6_ARPA_DNAME)) {
250 		*addr_family = AF_INET6;
251 		*parent = (labels < IPV6_ADDR_LABELS);
252 
253 		addr_block_t blocks[8] = { { 0 } };
254 		int compr_start = -1, compr_end = -1;
255 
256 		unsigned buf6_len = buf6_end - buf6_pos;
257 		memcpy(blocks, buf6_pos, buf6_len);
258 		memset(((uint8_t *)blocks) + buf6_len, 0x30, sizeof(blocks) - buf6_len);
259 
260 		for (int i = 0; i < 8; i++) {
261 			addr_block_t *block = &blocks[i];
262 
263 			/* The Unicode string MUST NOT contain "--" in the third and fourth
264 			   character positions and MUST NOT start or end with a "-".
265 			   So we will not compress first, second, and last address blocks
266 			   for simplicity. And we will not compress a single block.
267 
268 			   i:             0 1 2 3 4 5 6 7
269 			   label block:   H:G:F:E:D:C:B:A
270 			   address block: A B C D E F G H
271 			   compressibles:     0 0 0 0 0
272 			                      0 0 0 0
273 			                      0 0 0
274 			                      0 0
275 			 */
276 			// Check for trailing zero dual-blocks.
277 			if (tpl->reverse_short && i > 1 && i < 6 &&
278 			    block[0].b32 == 0x30303030UL && block[1].b32 == 0x30303030UL) {
279 				if (compr_start == -1) {
280 					compr_start = i;
281 				}
282 			} else {
283 				if (compr_start != -1 && compr_end == -1) {
284 					compr_end = i;
285 				}
286 			}
287 		}
288 
289 		// Write address blocks.
290 		unsigned addr_len = 0;
291 		for (int i = 0; i < 8; i++) {
292 			if (compr_start == -1 || i < compr_start || i > compr_end) {
293 				// Write regular address block.
294 				if (tpl->reverse_short) {
295 					addr_len += block_write(&blocks[i], addr_str + addr_len);
296 				} else {
297 					assert(sizeof(blocks[i]) == 4);
298 					memcpy(addr_str + addr_len, &blocks[i], 4);
299 					addr_len += 4;
300 				}
301 				// Write separator
302 				if (i < 7) {
303 					addr_str[addr_len++] = ':';
304 				}
305 			} else if (compr_start != -1 && compr_end == i) {
306 				// Write compression double colon.
307 				addr_str[addr_len++] = ':';
308 			}
309 		}
310 		addr_str[addr_len] = '\0';
311 
312 		return KNOT_EOK;
313 	}
314 
315 	return KNOT_EINVAL;
316 }
317 
forward_addr_parse(knotd_qdata_t * qdata,const synth_template_t * tpl,char * addr_str,int * addr_family)318 static int forward_addr_parse(knotd_qdata_t *qdata, const synth_template_t *tpl,
319                               char *addr_str, int *addr_family)
320 {
321 	const knot_dname_t *label = qdata->name;
322 
323 	// Check for prefix mismatch.
324 	if (label[0] <= tpl->prefix_len ||
325 	    memcmp(label + 1, tpl->prefix, tpl->prefix_len) != 0) {
326 		return KNOT_EINVAL;
327 	}
328 
329 	// Copy address part.
330 	unsigned addr_len = label[0] - tpl->prefix_len;
331 	memcpy(addr_str, label + 1 + tpl->prefix_len, addr_len);
332 	addr_str[addr_len] = '\0';
333 
334 	// Determine address family.
335 	unsigned hyphen_cnt = 0;
336 	const char *ch = addr_str;
337 	while (hyphen_cnt < 4 && ch < addr_str + addr_len) {
338 		if (*ch == '-') {
339 			hyphen_cnt++;
340 			if (*++ch == '-') { // Check for shortened IPv6 notation.
341 				hyphen_cnt = 4;
342 				break;
343 			}
344 		}
345 		ch++;
346 	}
347 	// Valid IPv4 address looks like A-B-C-D.
348 	*addr_family = (hyphen_cnt == 3) ? AF_INET : AF_INET6;
349 
350 	// Restore correct address format.
351 	const char sep = str_separator(*addr_family);
352 	str_subst(addr_str, addr_len, '-', sep);
353 
354 	return KNOT_EOK;
355 }
356 
addr_parse(knotd_qdata_t * qdata,const synth_template_t * tpl,char * addr_str,int * addr_family,bool * parent)357 static int addr_parse(knotd_qdata_t *qdata, const synth_template_t *tpl, char *addr_str,
358                       int *addr_family, bool *parent)
359 {
360 	switch (tpl->type) {
361 	case SYNTH_REVERSE: return reverse_addr_parse(qdata, tpl, addr_str, addr_family, parent);
362 	case SYNTH_FORWARD: return forward_addr_parse(qdata, tpl, addr_str, addr_family);
363 	default:            return KNOT_EINVAL;
364 	}
365 }
366 
synth_ptrname(uint8_t * out,const char * addr_str,const synth_template_t * tpl,int addr_family)367 static knot_dname_t *synth_ptrname(uint8_t *out, const char *addr_str,
368                                    const synth_template_t *tpl, int addr_family)
369 {
370 	knot_dname_txt_storage_t ptrname;
371 	int addr_len = strlen(addr_str);
372 	const char sep = str_separator(addr_family);
373 
374 	// PTR right-hand value is [prefix][address][zone]
375 	wire_ctx_t ctx = wire_ctx_init((uint8_t *)ptrname, sizeof(ptrname));
376 	wire_ctx_write(&ctx, tpl->prefix, tpl->prefix_len);
377 	wire_ctx_write(&ctx, addr_str, addr_len);
378 	wire_ctx_write_u8(&ctx, '.');
379 	wire_ctx_write(&ctx, tpl->zone, tpl->zone_len);
380 	wire_ctx_write_u8(&ctx, '\0');
381 	if (ctx.error != KNOT_EOK) {
382 		return NULL;
383 	}
384 
385 	// Substitute address separator by '-'.
386 	str_subst(ptrname + tpl->prefix_len, addr_len, sep, '-');
387 
388 	// Convert to domain name.
389 	return knot_dname_from_str(out, ptrname, KNOT_DNAME_MAXLEN);
390 }
391 
reverse_rr(char * addr_str,const synth_template_t * tpl,knot_pkt_t * pkt,knot_rrset_t * rr,int addr_family)392 static int reverse_rr(char *addr_str, const synth_template_t *tpl, knot_pkt_t *pkt,
393                       knot_rrset_t *rr, int addr_family)
394 {
395 	// Synthesize PTR record data.
396 	knot_dname_storage_t ptrname;
397 	if (synth_ptrname(ptrname, addr_str, tpl, addr_family) == NULL) {
398 		return KNOT_EINVAL;
399 	}
400 
401 	rr->type = KNOT_RRTYPE_PTR;
402 	knot_rrset_add_rdata(rr, ptrname, knot_dname_size(ptrname), &pkt->mm);
403 
404 	return KNOT_EOK;
405 }
406 
forward_rr(char * addr_str,const synth_template_t * tpl,knot_pkt_t * pkt,knot_rrset_t * rr,int addr_family)407 static int forward_rr(char *addr_str, const synth_template_t *tpl, knot_pkt_t *pkt,
408                       knot_rrset_t *rr, int addr_family)
409 {
410 	struct sockaddr_storage query_addr;
411 	sockaddr_set(&query_addr, addr_family, addr_str, 0);
412 
413 	// Specify address type and data.
414 	if (addr_family == AF_INET6) {
415 		rr->type = KNOT_RRTYPE_AAAA;
416 		const struct sockaddr_in6* ip = (const struct sockaddr_in6*)&query_addr;
417 		knot_rrset_add_rdata(rr, (const uint8_t *)&ip->sin6_addr,
418 		                     sizeof(struct in6_addr), &pkt->mm);
419 	} else if (addr_family == AF_INET) {
420 		rr->type = KNOT_RRTYPE_A;
421 		const struct sockaddr_in* ip = (const struct sockaddr_in*)&query_addr;
422 		knot_rrset_add_rdata(rr, (const uint8_t *)&ip->sin_addr,
423 		                     sizeof(struct in_addr), &pkt->mm);
424 	} else {
425 		return KNOT_EINVAL;
426 	}
427 
428 	return KNOT_EOK;
429 }
430 
synth_rr(char * addr_str,const synth_template_t * tpl,knot_pkt_t * pkt,knotd_qdata_t * qdata,int addr_family)431 static knot_rrset_t *synth_rr(char *addr_str, const synth_template_t *tpl, knot_pkt_t *pkt,
432                               knotd_qdata_t *qdata, int addr_family)
433 {
434 	knot_rrset_t *rr = knot_rrset_new(qdata->name, 0, KNOT_CLASS_IN, tpl->ttl,
435 	                                  &pkt->mm);
436 	if (rr == NULL) {
437 		return NULL;
438 	}
439 
440 	// Fill in the specific data.
441 	int ret = KNOT_ERROR;
442 	switch (tpl->type) {
443 	case SYNTH_REVERSE: ret = reverse_rr(addr_str, tpl, pkt, rr, addr_family); break;
444 	case SYNTH_FORWARD: ret = forward_rr(addr_str, tpl, pkt, rr, addr_family); break;
445 	default: break;
446 	}
447 
448 	if (ret != KNOT_EOK) {
449 		knot_rrset_free(rr, &pkt->mm);
450 		return NULL;
451 	}
452 
453 	return rr;
454 }
455 
456 /*! \brief Check if query fits the template requirements. */
template_match(knotd_in_state_t state,const synth_template_t * tpl,knot_pkt_t * pkt,knotd_qdata_t * qdata)457 static knotd_in_state_t template_match(knotd_in_state_t state, const synth_template_t *tpl,
458                                        knot_pkt_t *pkt, knotd_qdata_t *qdata)
459 {
460 	int provided_af = AF_UNSPEC;
461 	struct sockaddr_storage query_addr;
462 	char addr_str[SOCKADDR_STRLEN];
463 	assert(SOCKADDR_STRLEN > KNOT_DNAME_MAXLABELLEN);
464 	bool parent = false; // querying empty-non-terminal being (possibly indirect) parent of synthesized name
465 
466 	// Parse address from query name.
467 	if (addr_parse(qdata, tpl, addr_str, &provided_af, &parent) != KNOT_EOK ||
468 	    sockaddr_set(&query_addr, provided_af, addr_str, 0) != KNOT_EOK) {
469 		return state;
470 	}
471 
472 	// Try all available addresses.
473 	int i;
474 	for (i = 0; i < tpl->addr_count; i++) {
475 		if (tpl->addr[i].addr_max.ss_family == AF_UNSPEC) {
476 			if (sockaddr_net_match(&query_addr, &tpl->addr[i].addr,
477 			                       tpl->addr[i].addr_mask)) {
478 				break;
479 			}
480 		} else {
481 			if (sockaddr_range_match(&query_addr, &tpl->addr[i].addr,
482 			                         &tpl->addr[i].addr_max)) {
483 				break;
484 			}
485 		}
486 	}
487 	if (i >= tpl->addr_count) {
488 		return state;
489 	}
490 
491 	// Check if the request is for an available query type.
492 	uint16_t qtype = knot_pkt_qtype(qdata->query);
493 	switch (tpl->type) {
494 	case SYNTH_FORWARD:
495 		assert(!parent);
496 		if (!query_satisfied_by_family(qtype, provided_af)) {
497 			qdata->rcode = KNOT_RCODE_NOERROR;
498 			return KNOTD_IN_STATE_NODATA;
499 		}
500 		break;
501 	case SYNTH_REVERSE:
502 		if (parent || (qtype != KNOT_RRTYPE_PTR && qtype != KNOT_RRTYPE_ANY)) {
503 			qdata->rcode = KNOT_RCODE_NOERROR;
504 			return KNOTD_IN_STATE_NODATA;
505 		}
506 		break;
507 	default:
508 		return state;
509 	}
510 
511 	// Synthesize record from template.
512 	knot_rrset_t *rr = synth_rr(addr_str, tpl, pkt, qdata, provided_af);
513 	if (rr == NULL) {
514 		qdata->rcode = KNOT_RCODE_SERVFAIL;
515 		return KNOTD_IN_STATE_ERROR;
516 	}
517 
518 	// Insert synthetic response into packet.
519 	if (knot_pkt_put(pkt, 0, rr, KNOT_PF_FREE) != KNOT_EOK) {
520 		return KNOTD_IN_STATE_ERROR;
521 	}
522 
523 	// Authoritative response.
524 	knot_wire_set_aa(pkt->wire);
525 
526 	return KNOTD_IN_STATE_HIT;
527 }
528 
solve_synth_record(knotd_in_state_t state,knot_pkt_t * pkt,knotd_qdata_t * qdata,knotd_mod_t * mod)529 static knotd_in_state_t solve_synth_record(knotd_in_state_t state, knot_pkt_t *pkt,
530                                            knotd_qdata_t *qdata, knotd_mod_t *mod)
531 {
532 	assert(pkt && qdata && mod);
533 
534 	// Applicable when search in zone fails.
535 	if (state != KNOTD_IN_STATE_MISS) {
536 		return state;
537 	}
538 
539 	// Check if template fits.
540 	return template_match(state, knotd_mod_ctx(mod), pkt, qdata);
541 }
542 
synth_record_load(knotd_mod_t * mod)543 int synth_record_load(knotd_mod_t *mod)
544 {
545 	// Create synthesis template.
546 	synth_template_t *tpl = calloc(1, sizeof(*tpl));
547 	if (tpl == NULL) {
548 		return KNOT_ENOMEM;
549 	}
550 
551 	// Set type.
552 	knotd_conf_t conf = knotd_conf_mod(mod, MOD_TYPE);
553 	tpl->type = conf.single.option;
554 
555 	/* Set prefix. */
556 	conf = knotd_conf_mod(mod, MOD_PREFIX);
557 	tpl->prefix = strdup(conf.single.string);
558 	tpl->prefix_len = strlen(tpl->prefix);
559 
560 	// Set origin if generating reverse record.
561 	if (tpl->type == SYNTH_REVERSE) {
562 		conf = knotd_conf_mod(mod, MOD_ORIGIN);
563 		tpl->zone = knot_dname_to_str_alloc(conf.single.dname);
564 		if (tpl->zone == NULL) {
565 			free(tpl->prefix);
566 			free(tpl);
567 			return KNOT_ENOMEM;
568 		}
569 		tpl->zone_len = strlen(tpl->zone);
570 	}
571 
572 	// Set ttl.
573 	conf = knotd_conf_mod(mod, MOD_TTL);
574 	tpl->ttl = conf.single.integer;
575 
576 	// Set address.
577 	conf = knotd_conf_mod(mod, MOD_NET);
578 	tpl->addr_count = conf.count;
579 	tpl->addr = calloc(conf.count, sizeof(*tpl->addr));
580 	if (tpl->addr == NULL) {
581 		knotd_conf_free(&conf);
582 		free(tpl->zone);
583 		free(tpl->prefix);
584 		free(tpl);
585 		return KNOT_ENOMEM;
586 	}
587 	for (size_t i = 0; i < conf.count; i++) {
588 		tpl->addr[i].addr = conf.multi[i].addr;
589 		tpl->addr[i].addr_max = conf.multi[i].addr_max;
590 		tpl->addr[i].addr_mask = conf.multi[i].addr_mask;
591 	}
592 	knotd_conf_free(&conf);
593 
594 	// Set address shortening.
595 	if (tpl->type == SYNTH_REVERSE) {
596 		conf = knotd_conf_mod(mod, MOD_SHORT);
597 		tpl->reverse_short = conf.single.boolean;
598 	}
599 
600 	knotd_mod_ctx_set(mod, tpl);
601 
602 	return knotd_mod_in_hook(mod, KNOTD_STAGE_ANSWER, solve_synth_record);
603 }
604 
synth_record_unload(knotd_mod_t * mod)605 void synth_record_unload(knotd_mod_t *mod)
606 {
607 	synth_template_t *tpl = knotd_mod_ctx(mod);
608 
609 	free(tpl->addr);
610 	free(tpl->zone);
611 	free(tpl->prefix);
612 	free(tpl);
613 }
614 
615 KNOTD_MOD_API(synthrecord, KNOTD_MOD_FLAG_SCOPE_ZONE,
616               synth_record_load, synth_record_unload, synth_record_conf,
617               synth_record_conf_check);
618