ixfr.c 8.81 KB
Newer Older
1
/*  Copyright (C) 2018 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/journal/chgset_ctx.h"
22
#include "knot/nameserver/axfr.h"
23
#include "knot/nameserver/internet.h"
24
#include "knot/nameserver/ixfr.h"
Jan Včelák's avatar
Jan Včelák committed
25 26
#include "knot/nameserver/log.h"
#include "knot/nameserver/xfr.h"
27
#include "knot/zone/serial.h"
28
#include "libknot/libknot.h"
29

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

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

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

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,
46
                             chgset_ctx_t *itt)
47
{
48 49
	assert(pkt);
	assert(ixfr);
50
	assert(itt);
51

52 53 54
	if (!knot_rrset_empty(&ixfr->cur_rr)) {
		IXFR_SAFE_PUT(pkt, &ixfr->cur_rr);
		knot_rrset_clear(&ixfr->cur_rr, NULL);
55
	}
Daniel Salzman's avatar
Daniel Salzman committed
56

57 58 59 60 61 62
	while (itt->phase != CHGSET_CTX_DONE) {
		int r = chgset_ctx_next(itt, &ixfr->cur_rr);
		if (r != KNOT_EOK) {
			knot_rrset_clear(&ixfr->cur_rr, NULL);
			return r;
		}
63
		IXFR_SAFE_PUT(pkt, &ixfr->cur_rr);
64
		knot_rrset_clear(&ixfr->cur_rr, NULL);
65
	}
66

67
	return KNOT_EOK;
68 69
}

70 71 72 73 74 75 76
/*!
 * \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.
 */
77 78
static int ixfr_process_changeset(knot_pkt_t *pkt, const void *item,
                                  struct xfr_proc *xfer)
79
{
80
	int ret = KNOT_EOK;
81
	struct ixfr_proc *ixfr = (struct ixfr_proc *)xfer;
82
	chgset_ctx_t *chgset = (chgset_ctx_t *)item;
83

84 85 86
	ret = ixfr_put_chg_part(pkt, ixfr, chgset);
	if (ret != KNOT_EOK) {
		return ret;
87
	}
88

89
	/* Finished change set. */
90
	IXFROUT_LOG(LOG_DEBUG, ixfr->qdata, "serial %u -> %u", chgset->serial_from, chgset->serial_to);
91 92 93 94 95 96

	return ret;
}

#undef IXFR_SAFE_PUT

97
static int ixfr_load_chsets(chgset_ctx_list_t *chgsets, zone_t *zone,
98
                            const knot_rrset_t *their_soa)
99 100 101 102 103
{
	assert(chgsets);
	assert(zone);

	/* Compare serials. */
104
	uint32_t serial_to = zone_contents_serial(zone->contents);
105
	uint32_t serial_from = knot_soa_serial(their_soa->rrs.rdata);
106
	if (serial_compare(serial_to, serial_from) & SERIAL_MASK_LEQ) { /* We have older/same age zone. */
107 108 109
		return KNOT_EUPTODATE;
	}

110
	return zone_chgset_ctx_load(conf(), zone, chgsets, serial_from);
111 112
}

113
static int ixfr_query_check(knotd_qdata_t *qdata)
114
{
115
	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);
116 117
	NS_NEED_AUTH(qdata, qdata->extra->zone->name, ACL_ACTION_TRANSFER);
	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL);
118

119
	/* Need SOA authority record. */
120
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
121
	const knot_rrset_t *their_soa = knot_pkt_rr(authority, 0);
122
	if (authority->count < 1 || their_soa->type != KNOT_RRTYPE_SOA) {
123
		qdata->rcode = KNOT_RCODE_FORMERR;
124
		return KNOT_STATE_FAIL;
125 126 127
	}
	/* SOA needs to match QNAME. */
	NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
128

129
	return KNOT_STATE_DONE;
130
}
131

132
static void ixfr_answer_cleanup(knotd_qdata_t *qdata)
133
{
134
	struct ixfr_proc *ixfr = (struct ixfr_proc *)qdata->extra->ext;
135
	knot_mm_t *mm = qdata->mm;
136 137

	ptrlist_free(&ixfr->proc.nodes, mm);
138
	chgset_ctx_list_close(&ixfr->changesets);
139
	mm_free(mm, qdata->extra->ext);
140 141 142

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

145
static int ixfr_answer_init(knotd_qdata_t *qdata)
146
{
147 148
	assert(qdata);

149
	/* Check IXFR query validity. */
150
	if (ixfr_query_check(qdata) == KNOT_STATE_FAIL) {
151 152 153 154 155
		if (qdata->rcode == KNOT_RCODE_FORMERR) {
			return KNOT_EMALF;
		} else {
			return KNOT_EDENIED;
		}
156
	}
157

158
	/* Compare serials. */
159 160
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
	const knot_rrset_t *their_soa = knot_pkt_rr(authority, 0);
161 162

	/* Initialize transfer processing. */
163
	knot_mm_t *mm = qdata->mm;
164
	struct ixfr_proc *xfer = mm_alloc(mm, sizeof(struct ixfr_proc));
165 166 167
	if (xfer == NULL) {
		return KNOT_ENOMEM;
	}
168
	memset(xfer, 0, sizeof(struct ixfr_proc));
169 170 171 172 173 174 175

	int ret = ixfr_load_chsets(&xfer->changesets, (zone_t *)qdata->extra->zone, their_soa);
	if (ret != KNOT_EOK) {
		mm_free(mm, xfer);
		return ret;
	}

Jan Včelák's avatar
Jan Včelák committed
176
	xfr_stats_begin(&xfer->proc.stats);
177
	xfer->state = IXFR_SOA_DEL;
178
	init_list(&xfer->proc.nodes);
179
	knot_rrset_init_empty(&xfer->cur_rr);
180
	xfer->qdata = qdata;
181

182
	/* Put all changesets to processing queue. */
183 184
	chgset_ctx_t *chs = NULL;
	WALK_LIST(chs, xfer->changesets.l) {
185
		ptrlist_add(&xfer->proc.nodes, chs, mm);
186
		chgset_ctx_iterate(chs); // init already, so we don't have to decide later, no harm
187
	}
188

189
	/* Keep first and last serial. */
190 191 192 193
	chs = HEAD(xfer->changesets.l);
	xfer->soa_from = chs->serial_from;
	chs = TAIL(xfer->changesets.l);
	xfer->soa_to = chs->serial_to;
194

195
	/* Set up cleanup callback. */
196 197
	qdata->extra->ext = xfer;
	qdata->extra->ext_cleanup = &ixfr_answer_cleanup;
198

199
	/* No zone changes during multipacket answer (unlocked in ixfr_answer_cleanup) */
200 201
	rcu_read_lock();

202 203 204
	return KNOT_EOK;
}

205
static int ixfr_answer_soa(knot_pkt_t *pkt, knotd_qdata_t *qdata)
206
{
207 208
	assert(pkt);
	assert(qdata);
209

210 211
	/* Check query. */
	int state = ixfr_query_check(qdata);
212
	if (state == KNOT_STATE_FAIL) {
213 214
		return state; /* Malformed query. */
	}
215

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

222
	/* Guaranteed to have zone contents. */
223
	const zone_node_t *apex = qdata->extra->zone->contents->apex;
224
	knot_rrset_t soa_rr = node_rrset(apex, KNOT_RRTYPE_SOA);
225
	if (knot_rrset_empty(&soa_rr)) {
226
		return KNOT_STATE_FAIL;
227
	}
228
	ret = knot_pkt_put(pkt, 0, &soa_rr, 0);
229 230
	if (ret != KNOT_EOK) {
		qdata->rcode = KNOT_RCODE_SERVFAIL;
231
		return KNOT_STATE_FAIL;
232 233
	}

234
	return KNOT_STATE_DONE;
235
}
236

237
int ixfr_process_query(knot_pkt_t *pkt, knotd_qdata_t *qdata)
238 239
{
	if (pkt == NULL || qdata == NULL) {
240
		return KNOT_STATE_FAIL;
241 242 243
	}

	/* If IXFR is disabled, respond with SOA. */
244
	if (qdata->params->flags & KNOTD_QUERY_FLAG_NO_IXFR) {
245 246 247 248
		return ixfr_answer_soa(pkt, qdata);
	}

	/* Initialize on first call. */
249
	struct ixfr_proc *ixfr = qdata->extra->ext;
250 251
	if (ixfr == NULL) {
		int ret = ixfr_answer_init(qdata);
252
		ixfr = qdata->extra->ext;
Daniel Salzman's avatar
Daniel Salzman committed
253
		switch (ret) {
254
		case KNOT_EOK:       /* OK */
Jan Včelák's avatar
Jan Včelák committed
255
			IXFROUT_LOG(LOG_INFO, qdata, "started, serial %u -> %u",
256
				    ixfr->soa_from, ixfr->soa_to);
257 258
			break;
		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
Jan Včelák's avatar
Jan Včelák committed
259
			IXFROUT_LOG(LOG_INFO, qdata, "zone is up-to-date");
260
			return ixfr_answer_soa(pkt, qdata);
261
		case KNOT_ERANGE:    /* No history -> AXFR. */
262
		case KNOT_ENOENT:
Jan Včelák's avatar
Jan Včelák committed
263
			IXFROUT_LOG(LOG_INFO, qdata, "incomplete history, fallback to AXFR");
264
			qdata->type = KNOTD_QUERY_TYPE_AXFR; /* Solve as AXFR. */
265
			return axfr_process_query(pkt, qdata);
266 267 268 269 270 271 272 273
		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));
274
			return KNOT_STATE_FAIL;
275 276 277 278
		}
	}

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

	/* Answer current packet (or continue). */
285
	ret = xfr_process_list(pkt, &ixfr_process_changeset, qdata);
Daniel Salzman's avatar
Daniel Salzman committed
286
	switch (ret) {
287
	case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */
288
		return KNOT_STATE_PRODUCE; /* Check for more. */
289
	case KNOT_EOK:    /* Last response. */
Jan Včelák's avatar
Jan Včelák committed
290 291
		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
292
		                 REMOTE(qdata), &ixfr->proc.stats);
293
		return KNOT_STATE_DONE;
294
	default:          /* Generic error. */
Jan Včelák's avatar
Jan Včelák committed
295
		IXFROUT_LOG(LOG_ERR, qdata, "failed (%s)", knot_strerror(ret));
296
		return KNOT_STATE_FAIL;
297 298
	}
}