summaryrefslogtreecommitdiffstats
path: root/src/knot/nameserver/axfr.c
blob: dac4a43052850b36d9c4fefdbf8e0f31db6307e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/*  Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
 */

#include <urcu.h>

#include "contrib/mempattern.h"
#include "contrib/sockaddr.h"
#include "knot/nameserver/axfr.h"
#include "knot/nameserver/internet.h"
#include "knot/nameserver/log.h"
#include "knot/nameserver/xfr.h"
#include "libknot/libknot.h"

#define ZONE_NAME(qdata) knot_pkt_qname((qdata)->query)
#define REMOTE(qdata) (struct sockaddr *)knotd_qdata_remote_addr(qdata)

#define AXFROUT_LOG(priority, qdata, fmt...) \
	ns_log(priority, ZONE_NAME(qdata), LOG_OPERATION_AXFR, \
	       LOG_DIRECTION_OUT, REMOTE(qdata), false, fmt)

/* AXFR context. @note aliasing the generic xfr_proc */
struct axfr_proc {
	struct xfr_proc proc;
	trie_it_t *i;
	zone_tree_it_t it;
	unsigned cur_rrset;
};

static int axfr_put_rrsets(knot_pkt_t *pkt, zone_node_t *node,
                           struct axfr_proc *state)
{
	assert(node != NULL);

	/* Append all RRs. */
	for (unsigned i = state->cur_rrset; i < node->rrset_count; ++i) {
		knot_rrset_t rrset = node_rrset_at(node, i);
		if (rrset.type == KNOT_RRTYPE_SOA) {
			continue;
		}

		int ret = knot_pkt_put(pkt, 0, &rrset, KNOT_PF_NOTRUNC | KNOT_PF_ORIGTTL);
		if (ret != KNOT_EOK) {
			/* If something failed, remember the current RR for later. */
			state->cur_rrset = i;
			return ret;
		}
		if (pkt->size > KNOT_WIRE_PTR_MAX) {
			// optimization: once the XFR DNS message is > 16 KiB, compression
			// is limited. Better wrap to next message.
			state->cur_rrset = i + 1;
			return KNOT_ESPACE;
		}
	}

	state->cur_rrset = 0;

	return KNOT_EOK;
}

static int axfr_process_node_tree(knot_pkt_t *pkt, const void *item,
                                  struct xfr_proc *state)
{
	assert(item != NULL);

	struct axfr_proc *axfr = (struct axfr_proc*)state;

	int ret = zone_tree_it_begin((zone_tree_t *)item, &axfr->it); // does nothing if already iterating

	/* Put responses. */
	while (ret == KNOT_EOK && !zone_tree_it_finished(&axfr->it)) {
		zone_node_t *node = zone_tree_it_val(&axfr->it);
		ret = axfr_put_rrsets(pkt, node, axfr);
		if (ret == KNOT_EOK) {
			zone_tree_it_next(&axfr->it);
		}
	}

	/* Finished all nodes. */
	if (ret == KNOT_EOK) {
		zone_tree_it_free(&axfr->it);
	}
	return ret;
}

static void axfr_query_cleanup(knotd_qdata_t *qdata)
{
	struct axfr_proc *axfr = (struct axfr_proc *)qdata->extra->ext;

	zone_tree_it_free(&axfr->it);
	ptrlist_free(&axfr->proc.nodes, qdata->mm);
	mm_free(qdata->mm, axfr);

	/* Allow zone changes (finished). */
	rcu_read_unlock();
}

static int axfr_query_check(knotd_qdata_t *qdata)
{
	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
	NS_NEED_AUTH(qdata, ACL_ACTION_TRANSFER);
	NS_NEED_ZONE_CONTENTS(qdata);

	return KNOT_STATE_DONE;
}

static int axfr_query_init(knotd_qdata_t *qdata)
{
	assert(qdata);

	/* Check AXFR query validity. */
	if (axfr_query_check(qdata) == KNOT_STATE_FAIL) {
		if (qdata->rcode == KNOT_RCODE_FORMERR) {
			return KNOT_EMALF;
		} else {
			return KNOT_EDENIED;
		}
	}

	if (zone_get_flag(qdata->extra->zone, ZONE_XFR_FROZEN, false)) {
		qdata->rcode = KNOT_RCODE_REFUSED;
		qdata->rcode_ede = KNOT_EDNS_EDE_NOT_READY;
		return KNOT_EAGAIN;
	}

	/* Create transfer processing context. */
	knot_mm_t *mm = qdata->mm;
	struct axfr_proc *axfr = mm_alloc(mm, sizeof(struct axfr_proc));
	if (axfr == NULL) {
		return KNOT_ENOMEM;
	}
	memset(axfr, 0, sizeof(struct axfr_proc));
	init_list(&axfr->proc.nodes);

	/* Put data to process. */
	xfr_stats_begin(&axfr->proc.stats);
	const zone_contents_t *contents = qdata->extra->contents;
	/* Must be non-NULL for the first message. */
	assert(contents);
	ptrlist_add(&axfr->proc.nodes, contents->nodes, mm);
	/* Put NSEC3 data if exists. */
	if (!zone_tree_is_empty(contents->nsec3_nodes)) {
		ptrlist_add(&axfr->proc.nodes, contents->nsec3_nodes, mm);
	}

	/* Set up cleanup callback. */
	qdata->extra->ext = axfr;
	qdata->extra->ext_cleanup = &axfr_query_cleanup;

	/* No zone changes during multipacket answer (unlocked in axfr_answer_cleanup) */
	rcu_read_lock();

	return KNOT_EOK;
}

int axfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
{
	if (pkt == NULL || qdata == NULL) {
		return KNOT_STATE_FAIL;
	}

	/* AXFR over UDP isn't allowed, respond with NOTIMPL. */
	if (qdata->params->proto == KNOTD_QUERY_PROTO_UDP) {
		qdata->rcode = KNOT_RCODE_NOTIMPL;
		return KNOT_STATE_FAIL;
	}

	/* Initialize on first call. */
	struct axfr_proc *axfr = qdata->extra->ext;
	if (axfr == NULL) {
		int ret = axfr_query_init(qdata);
		axfr = qdata->extra->ext;
		switch (ret) {
		case KNOT_EOK:      /* OK */
			AXFROUT_LOG(LOG_INFO, qdata, "started, serial %u",
			            zone_contents_serial(qdata->extra->contents));
			break;
		case KNOT_EDENIED:  /* Not authorized, already logged. */
			return KNOT_STATE_FAIL;
		case KNOT_EMALF:    /* Malformed query. */
			AXFROUT_LOG(LOG_DEBUG, qdata, "malformed query");
			return KNOT_STATE_FAIL;
		case KNOT_EAGAIN:   /* Outgoing AXFR temporarily disabled. */
			AXFROUT_LOG(LOG_INFO, qdata, "outgoing AXFR frozen");
			return KNOT_STATE_FAIL;
		default:
			AXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)",
			            knot_strerror(ret));
			return KNOT_STATE_FAIL;
		}
	}

	/* Reserve space for TSIG. */
	int ret = knot_pkt_reserve(pkt, knot_tsig_wire_size(&qdata->sign.tsig_key));
	if (ret != KNOT_EOK) {
		return KNOT_STATE_FAIL;
	}

	/* Answer current packet (or continue). */
	ret = xfr_process_list(pkt, &axfr_process_node_tree, qdata);
	switch (ret) {
	case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */
		return KNOT_STATE_PRODUCE; /* Check for more. */
	case KNOT_EOK:    /* Last response. */
		xfr_stats_end(&axfr->proc.stats);
		xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_AXFR, LOG_DIRECTION_OUT,
		                 REMOTE(qdata), false, &axfr->proc.stats);
		return KNOT_STATE_DONE;
	default:          /* Generic error. */
		AXFROUT_LOG(LOG_ERR, qdata, "failed (%s)", knot_strerror(ret));
		return KNOT_STATE_FAIL;
	}
}