ixfr.c 9.93 KB
Newer Older
Daniel Salzman's avatar
Daniel Salzman committed
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 <urcu.h>

19 20
#include "contrib/mempattern.h"
#include "contrib/sockaddr.h"
21
#include "knot/nameserver/axfr.h"
22
#include "knot/nameserver/internet.h"
23
#include "knot/nameserver/ixfr.h"
Jan Včelák's avatar
Jan Včelák committed
24 25
#include "knot/nameserver/log.h"
#include "knot/nameserver/xfr.h"
26
#include "knot/zone/serial.h"
27
#include "libknot/libknot.h"
28

Jan Včelák's avatar
Jan Včelák committed
29
#define ZONE_NAME(qdata) knot_pkt_qname((qdata)->query)
30
#define REMOTE(qdata) (struct sockaddr *)(qdata)->params->remote
Jan Včelák's avatar
Jan Včelák committed
31 32 33 34

#define IXFROUT_LOG(priority, qdata, fmt...) \
	ns_log(priority, ZONE_NAME(qdata), LOG_OPERATION_IXFR, \
	       LOG_DIRECTION_OUT, REMOTE(qdata), fmt)
35 36 37

/*! \brief Helper macro for putting RRs into packet. */
#define IXFR_SAFE_PUT(pkt, rr) \
38
	int ret = knot_pkt_put((pkt), 0, (rr), KNOT_PF_NOTRUNC); \
39 40 41 42
	if (ret != KNOT_EOK) { \
		return ret; \
	}

43 44 45
/*! \brief Puts current RR into packet, stores state for retries. */
static int ixfr_put_chg_part(knot_pkt_t *pkt, struct ixfr_proc *ixfr,
                             changeset_iter_t *itt)
46
{
47 48
	assert(pkt);
	assert(ixfr);
49
	assert(itt);
50

51 52 53
	if (knot_rrset_empty(&ixfr->cur_rr)) {
		ixfr->cur_rr = changeset_iter_next(itt);
	}
Daniel Salzman's avatar
Daniel Salzman committed
54 55

	while (!knot_rrset_empty(&ixfr->cur_rr)) {
56 57
		IXFR_SAFE_PUT(pkt, &ixfr->cur_rr);
		ixfr->cur_rr = changeset_iter_next(itt);
58
	}
59

60
	return KNOT_EOK;
61 62
}

63 64 65 66 67 68
/*! \brief Tests if iteration has started. */
static bool iter_empty(struct ixfr_proc *ixfr)
{
	return EMPTY_LIST(ixfr->cur.iters) && knot_rrset_empty(&ixfr->cur_rr);
}

69 70 71 72 73 74 75
/*!
 * \brief Process single changeset.
 * \note Keep in mind that this function must be able to resume processing,
 *       for example if it fills a packet and returns ESPACE, it is called again
 *       with next empty answer and it must resume the processing exactly where
 *       it's left off.
 */
76 77
static int ixfr_process_changeset(knot_pkt_t *pkt, const void *item,
                                  struct xfr_proc *xfer)
78
{
79
	int ret = KNOT_EOK;
80 81
	struct ixfr_proc *ixfr = (struct ixfr_proc *)xfer;
	changeset_t *chgset = (changeset_t *)item;
82 83

	/* Put former SOA. */
84
	if (ixfr->state == IXFR_SOA_DEL) {
85
		IXFR_SAFE_PUT(pkt, chgset->soa_from);
86
		ixfr->state = IXFR_DEL;
87 88 89
	}

	/* Put REMOVE RRSets. */
90
	if (ixfr->state == IXFR_DEL) {
91
		if (iter_empty(ixfr)) {
92
			ret = changeset_iter_rem(&ixfr->cur, chgset);
93 94 95
			if (ret != KNOT_EOK) {
				return ret;
			}
96
		}
97
		ret = ixfr_put_chg_part(pkt, ixfr, &ixfr->cur);
98 99 100
		if (ret != KNOT_EOK) {
			return ret;
		}
101
		changeset_iter_clear(&ixfr->cur);
102
		ixfr->state = IXFR_SOA_ADD;
103 104 105
	}

	/* Put next SOA. */
106
	if (ixfr->state == IXFR_SOA_ADD) {
107
		IXFR_SAFE_PUT(pkt, chgset->soa_to);
108
		ixfr->state = IXFR_ADD;
109 110
	}

111
	/* Put Add RRSets. */
112
	if (ixfr->state == IXFR_ADD) {
113
		if (iter_empty(ixfr)) {
114
			ret = changeset_iter_add(&ixfr->cur, chgset);
115 116 117
			if (ret != KNOT_EOK) {
				return ret;
			}
118
		}
119
		ret = ixfr_put_chg_part(pkt, ixfr, &ixfr->cur);
120 121 122
		if (ret != KNOT_EOK) {
			return ret;
		}
123
		changeset_iter_clear(&ixfr->cur);
124
		ixfr->state = IXFR_SOA_DEL;
125
	}
126

127
	/* Finished change set. */
128 129
	const uint32_t serial_from = knot_soa_serial(&chgset->soa_from->rrs);
	const uint32_t serial_to = knot_soa_serial(&chgset->soa_to->rrs);
Jan Včelák's avatar
Jan Včelák committed
130
	IXFROUT_LOG(LOG_DEBUG, ixfr->qdata, "serial %u -> %u", serial_from, serial_to);
131 132 133 134 135 136

	return ret;
}

#undef IXFR_SAFE_PUT

137
static int ixfr_load_chsets(list_t *chgsets, zone_t *zone,
138
                            const knot_rrset_t *their_soa)
139 140 141 142 143
{
	assert(chgsets);
	assert(zone);

	/* Compare serials. */
144
	uint32_t serial_to = zone_contents_serial(zone->contents);
145
	uint32_t serial_from = knot_soa_serial(&their_soa->rrs);
146
	if (serial_compare(serial_to, serial_from) & SERIAL_MASK_LEQ) { /* We have older/same age zone. */
147 148 149
		return KNOT_EUPTODATE;
	}

150
	int ret = zone_changes_load(conf(), zone, chgsets, serial_from);
151
	if (ret != KNOT_EOK) {
152
		changesets_free(chgsets);
153 154 155 156 157
	}

	return ret;
}

158
static int ixfr_query_check(knotd_qdata_t *qdata)
159
{
160
	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
161 162
	NS_NEED_AUTH(qdata, qdata->extra->zone->name, ACL_ACTION_TRANSFER);
	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
163

164
	/* Need SOA authority record. */
165
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
166
	const knot_rrset_t *their_soa = knot_pkt_rr(authority, 0);
167
	if (authority->count < 1 || their_soa->type != KNOT_RRTYPE_SOA) {
168
		qdata->rcode = KNOT_RCODE_FORMERR;
169
		return KNOT_STATE_FAIL;
170 171 172
	}
	/* SOA needs to match QNAME. */
	NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
173

174
	return KNOT_STATE_DONE;
175
}
176

177
static void ixfr_answer_cleanup(knotd_qdata_t *qdata)
178
{
179
	struct ixfr_proc *ixfr = (struct ixfr_proc *)qdata->extra->ext;
180
	knot_mm_t *mm = qdata->mm;
181 182

	ptrlist_free(&ixfr->proc.nodes, mm);
183
	changeset_iter_clear(&ixfr->cur);
184
	changesets_free(&ixfr->changesets);
185
	mm_free(mm, qdata->extra->ext);
186 187 188

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

191
static int ixfr_answer_init(knotd_qdata_t *qdata)
192
{
193 194
	assert(qdata);

195
	/* Check IXFR query validity. */
196
	if (ixfr_query_check(qdata) == KNOT_STATE_FAIL) {
197 198 199 200 201
		if (qdata->rcode == KNOT_RCODE_FORMERR) {
			return KNOT_EMALF;
		} else {
			return KNOT_EDENIED;
		}
202
	}
203

204
	/* Compare serials. */
205 206
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
	const knot_rrset_t *their_soa = knot_pkt_rr(authority, 0);
207 208
	list_t chgsets;
	init_list(&chgsets);
209
	int ret = ixfr_load_chsets(&chgsets, (zone_t *)qdata->extra->zone, their_soa);
210 211 212 213 214
	if (ret != KNOT_EOK) {
		return ret;
	}

	/* Initialize transfer processing. */
215
	knot_mm_t *mm = qdata->mm;
216
	struct ixfr_proc *xfer = mm_alloc(mm, sizeof(struct ixfr_proc));
217
	if (xfer == NULL) {
218
		changesets_free(&chgsets);
219 220
		return KNOT_ENOMEM;
	}
221
	memset(xfer, 0, sizeof(struct ixfr_proc));
Jan Včelák's avatar
Jan Včelák committed
222
	xfr_stats_begin(&xfer->proc.stats);
223
	xfer->state = IXFR_SOA_DEL;
224
	init_list(&xfer->proc.nodes);
225
	init_list(&xfer->changesets);
226
	init_list(&xfer->cur.iters);
227
	knot_rrset_init_empty(&xfer->cur_rr);
228
	add_tail_list(&xfer->changesets, &chgsets);
229
	xfer->qdata = qdata;
230

231
	/* Put all changesets to processing queue. */
232
	changeset_t *chs = NULL;
233
	WALK_LIST(chs, xfer->changesets) {
234 235
		ptrlist_add(&xfer->proc.nodes, chs, mm);
	}
236

237
	/* Keep first and last serial. */
238
	chs = HEAD(xfer->changesets);
239
	xfer->soa_from = chs->soa_from;
240
	chs = TAIL(xfer->changesets);
241
	xfer->soa_to = chs->soa_to;
242

243
	/* Set up cleanup callback. */
244 245
	qdata->extra->ext = xfer;
	qdata->extra->ext_cleanup = &ixfr_answer_cleanup;
246

247
	/* No zone changes during multipacket answer (unlocked in ixfr_answer_cleanup) */
248 249
	rcu_read_lock();

250 251 252
	return KNOT_EOK;
}

253
static int ixfr_answer_soa(knot_pkt_t *pkt, knotd_qdata_t *qdata)
254
{
255 256
	assert(pkt);
	assert(qdata);
257

258 259
	/* Check query. */
	int state = ixfr_query_check(qdata);
260
	if (state == KNOT_STATE_FAIL) {
261 262
		return state; /* Malformed query. */
	}
263

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

270
	/* Guaranteed to have zone contents. */
271
	const zone_node_t *apex = qdata->extra->zone->contents->apex;
272
	knot_rrset_t soa_rr = node_rrset(apex, KNOT_RRTYPE_SOA);
273
	if (knot_rrset_empty(&soa_rr)) {
274
		return KNOT_STATE_FAIL;
275
	}
276
	ret = knot_pkt_put(pkt, 0, &soa_rr, 0);
277 278
	if (ret != KNOT_EOK) {
		qdata->rcode = KNOT_RCODE_SERVFAIL;
279
		return KNOT_STATE_FAIL;
280 281
	}

282
	return KNOT_STATE_DONE;
283
}
284

285
int ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
286 287
{
	if (pkt == NULL || qdata == NULL) {
288
		return KNOT_STATE_FAIL;
289 290 291
	}

	/* If IXFR is disabled, respond with SOA. */
292
	if (qdata->params->flags & KNOTD_QUERY_FLAG_NO_IXFR) {
293 294 295 296
		return ixfr_answer_soa(pkt, qdata);
	}

	/* Initialize on first call. */
297
	struct ixfr_proc *ixfr = qdata->extra->ext;
298 299
	if (ixfr == NULL) {
		int ret = ixfr_answer_init(qdata);
300
		ixfr = qdata->extra->ext;
Daniel Salzman's avatar
Daniel Salzman committed
301
		switch (ret) {
302
		case KNOT_EOK:       /* OK */
Jan Včelák's avatar
Jan Včelák committed
303
			IXFROUT_LOG(LOG_INFO, qdata, "started, serial %u -> %u",
304 305 306 307
			            knot_soa_serial(&ixfr->soa_from->rrs),
			            knot_soa_serial(&ixfr->soa_to->rrs));
			break;
		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
Jan Včelák's avatar
Jan Včelák committed
308
			IXFROUT_LOG(LOG_INFO, qdata, "zone is up-to-date");
309
			return ixfr_answer_soa(pkt, qdata);
310
		case KNOT_ERANGE:    /* No history -> AXFR. */
311
		case KNOT_ENOENT:
Jan Včelák's avatar
Jan Včelák committed
312
			IXFROUT_LOG(LOG_INFO, qdata, "incomplete history, fallback to AXFR");
313
			qdata->type = KNOTD_QUERY_TYPE_AXFR; /* Solve as AXFR. */
314
			return axfr_process_query(pkt, qdata);
315 316 317 318 319 320 321 322
		case KNOT_EDENIED:  /* Not authorized, already logged. */
			return KNOT_STATE_FAIL;
		case KNOT_EMALF:    /* Malformed query. */
			IXFROUT_LOG(LOG_DEBUG, qdata, "malformed query");
			return KNOT_STATE_FAIL;
		default:             /* Server errors. */
			IXFROUT_LOG(LOG_ERR, qdata, "failed to start (%s)",
			            knot_strerror(ret));
323
			return KNOT_STATE_FAIL;
324 325 326 327
		}
	}

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

	/* Answer current packet (or continue). */
334
	ret = xfr_process_list(pkt, &ixfr_process_changeset, qdata);
Daniel Salzman's avatar
Daniel Salzman committed
335
	switch (ret) {
336
	case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */
337
		return KNOT_STATE_PRODUCE; /* Check for more. */
338
	case KNOT_EOK:    /* Last response. */
Jan Včelák's avatar
Jan Včelák committed
339 340
		xfr_stats_end(&ixfr->proc.stats);
		xfr_log_finished(ZONE_NAME(qdata), LOG_OPERATION_IXFR, LOG_DIRECTION_OUT,
Daniel Salzman's avatar
Daniel Salzman committed
341
		                 REMOTE(qdata), &ixfr->proc.stats);
342
		return KNOT_STATE_DONE;
343
	default:          /* Generic error. */
Jan Včelák's avatar
Jan Včelák committed
344
		IXFROUT_LOG(LOG_ERR, qdata, "failed (%s)", knot_strerror(ret));
345
		return KNOT_STATE_FAIL;
346 347
	}
}