ixfr.c 8.8 KB
Newer Older
1 2 3
#include <config.h>

#include "libknot/nameserver/ixfr.h"
4 5
#include "libknot/nameserver/axfr.h"
#include "libknot/nameserver/internet.h"
6
#include "libknot/nameserver/ns_proc_query.h"
7 8 9
#include "libknot/util/debug.h"
#include "libknot/rdata.h"
#include "knot/server/zones.h"
10 11
#include "common/descriptor.h"

12 13 14 15 16 17 18 19 20 21 22 23 24 25
/*! \brief Current IXFR answer sections. */
enum {
	SOA_REMOVE = 0,
	REMOVE,
	SOA_ADD,
	ADD
};

/*! \brief Extended structure for IXFR processing. */
struct ixfr_proc {
	struct xfr_proc proc;
	node_t *cur;
	unsigned state;
	knot_changesets_t *changesets;
26
	struct query_data *qdata;
27
	const knot_rrset_t *soa_from, *soa_to;
28 29
};

30 31
/* IXFR-specific logging (internal, expects 'qdata' variable set). */
#define IXFR_LOG(severity, msg...) \
32
	ANSWER_LOG(severity, qdata, "Outgoing IXFR", msg)
33

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

static int ixfr_put_rrlist(knot_pkt_t *pkt, struct ixfr_proc *ixfr, list_t *list)
42
{
43 44 45 46 47 48 49 50 51 52 53 54
	assert(pkt);
	assert(ixfr);
	assert(list);

	/* If at the beginning, fetch first RR. */
	int ret = KNOT_EOK;
	if (ixfr->cur == NULL) {
		ixfr->cur = HEAD(*list);
	}
	/* Now iterate until it hits the last one,
	 * this is done without for() loop because we can
	 * rejoin the iteration at any point. */
55 56
	while(ixfr->cur->next) {
		knot_rr_ln_t *rr_item = (knot_rr_ln_t *)(ixfr->cur);
57 58 59 60 61 62 63 64
		if (knot_rrset_rdata_rr_count(rr_item->rr) > 0) {
			IXFR_SAFE_PUT(pkt, rr_item->rr);
		} else {
			dbg_ns("%s: empty RR %p, skipping\n", __func__, rr_item->rr);
		}

		ixfr->cur = ixfr->cur->next;
	}
65 66

	ixfr->cur = NULL;
67
	return ret;
68 69
}

70 71 72 73 74 75 76 77 78

/*!
 * \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.
 */
static int ixfr_process_item(knot_pkt_t *pkt, const void *item, struct xfr_proc *xfer)
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
	int ret = KNOT_EOK;
	struct ixfr_proc *ixfr = (struct ixfr_proc *)xfer;
	knot_changeset_t *chgset = (knot_changeset_t *)item;

	/* Put former SOA. */
	if (ixfr->state == SOA_REMOVE) {
		IXFR_SAFE_PUT(pkt, chgset->soa_from);
		dbg_ns("%s: put 'REMOVE' SOA\n", __func__);
		ixfr->state = REMOVE;
	}

	/* Put REMOVE RRSets. */
	if (ixfr->state == REMOVE) {
		ret = ixfr_put_rrlist(pkt, ixfr, &chgset->remove);
		if (ret != KNOT_EOK) {
			return ret;
		}
		dbg_ns("%s: put 'REMOVE' RRs\n", __func__);
		ixfr->state = SOA_ADD;
	}

	/* Put next SOA. */
	if (ixfr->state == SOA_ADD) {
		IXFR_SAFE_PUT(pkt, chgset->soa_to);
		dbg_ns("%s: put 'ADD' SOA\n", __func__);
		ixfr->state = ADD;
106 107
	}

108 109 110 111 112 113 114 115 116
	/* Put REMOVE RRSets. */
	if (ixfr->state == ADD) {
		ret = ixfr_put_rrlist(pkt, ixfr, &chgset->add);
		if (ret != KNOT_EOK) {
			return ret;
		}
		dbg_ns("%s: put 'ADD' RRs\n", __func__);
		ixfr->state = SOA_REMOVE;
	}
117 118
	
	/* Finished change set. */
119
	struct query_data *qdata = ixfr->qdata; /*< Required for IXFR_LOG() */
120
	IXFR_LOG(LOG_INFO, "Serial %u -> %u.", chgset->serial_from, chgset->serial_to);
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

	return ret;
}

#undef IXFR_SAFE_PUT

static int ixfr_load_chsets(knot_changesets_t **chgsets, const knot_zone_t *zone,
			    const knot_rrset_t *their_soa)
{
	assert(chgsets);
	assert(zone);

	/* Compare serials. */
	const knot_node_t *apex = zone->contents->apex;
	const knot_rrset_t *our_soa = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
	uint32_t serial_to = knot_rdata_soa_serial(our_soa);
	uint32_t serial_from = knot_rdata_soa_serial(their_soa);
	int ret = ns_serial_compare(serial_to, serial_from);
	if (ret <= 0) { /* We have older/same age zone. */
		return KNOT_EUPTODATE;
	}

	*chgsets = knot_changesets_create();
	if (*chgsets == NULL) {
		return KNOT_ENOMEM;
	}

	/*! \todo This is a candidate for function relocation. */
	ret = zones_load_changesets(zone, *chgsets, serial_from, serial_to);
	if (ret != KNOT_EOK) {
		knot_changesets_free(chgsets);
	}

	return ret;
}

157
static int ixfr_query_check(struct query_data *qdata)
158
{
159
	/* Check zone state. */
160 161 162 163
	NS_NEED_VALID_ZONE(qdata, KNOT_RCODE_NOTAUTH);
	/* Need IXFR query type. */
	NS_NEED_QTYPE(qdata, KNOT_RRTYPE_IXFR, KNOT_RCODE_FORMERR);
	/* Need SOA authority record. */
164
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
165 166 167
	const knot_rrset_t *their_soa = authority->rr[0];
	if (authority->count < 1 || knot_rrset_type(their_soa) != KNOT_RRTYPE_SOA) {
		qdata->rcode = KNOT_RCODE_FORMERR;
168
		return NS_PROC_FAIL;
169 170 171
	}
	/* SOA needs to match QNAME. */
	NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
172 173 174 175 176
	
	/* Need valid transaction security. */
	zonedata_t *zone_data = (zonedata_t *)knot_zone_data(qdata->zone);
	NS_NEED_AUTH(zone_data->xfr_out, qdata);

177
	return NS_PROC_DONE;
178
}
179

180 181 182 183 184 185 186
static int ixfr_answer_init(struct query_data *qdata)
{
	/* Check query. */
	int state = ixfr_query_check(qdata);
	if (state == NS_PROC_FAIL) {
		return KNOT_EMALF; /* Malformed query. */
	}
187
	/* Compare serials. */
188
	const knot_rrset_t *their_soa = knot_pkt_section(qdata->query, KNOT_AUTHORITY)->rr[0];
189 190 191
	knot_changesets_t *chgsets = NULL;
	int ret = ixfr_load_chsets(&chgsets, qdata->zone, their_soa);
	if (ret != KNOT_EOK) {
192
		dbg_ns("%s: failed to load changesets => %d\n", __func__, ret);
193 194 195 196 197 198 199 200 201 202 203
		return ret;
	}

	/* Initialize transfer processing. */
	mm_ctx_t *mm = qdata->mm;
	struct ixfr_proc *xfer = mm->alloc(mm->ctx, sizeof(struct ixfr_proc));
	if (xfer == NULL) {
		knot_changesets_free(&chgsets);
		return KNOT_ENOMEM;
	}
	memset(xfer, 0, sizeof(struct ixfr_proc));
204
	gettimeofday(&xfer->proc.tstamp, NULL);
205 206
	init_list(&xfer->proc.nodes);
	qdata->ext = xfer;
207
	xfer->qdata = qdata;
208 209 210 211 212 213 214 215

	/* Put all changesets to process. */
	xfer->changesets = chgsets;
	knot_changeset_t *chs = NULL;
	WALK_LIST(chs, chgsets->sets) {
		ptrlist_add(&xfer->proc.nodes, chs, mm);
		dbg_ns("%s: preparing %u -> %u\n", __func__, chs->serial_from, chs->serial_to);
	}
216 217 218 219 220 221
	
	/* Keep first and last serial. */
	chs = HEAD(chgsets->sets);
	xfer->soa_from = chs->soa_from;
	chs = TAIL(chgsets->sets);
	xfer->soa_to = chs->soa_to;
222 223 224 225 226 227 228 229

	return KNOT_EOK;
}

int ixfr_answer_soa(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata)
{
	dbg_ns("%s: answering IXFR/SOA\n", __func__);
	if (pkt == NULL || ns == NULL || qdata == NULL) {
230 231 232
		return NS_PROC_FAIL;
	}

233 234 235 236 237 238 239 240
	/* Check query. */
	int state = ixfr_query_check(qdata);
	if (state == NS_PROC_FAIL) {
		return state; /* Malformed query. */
	}
	
	/* Reserve space for TSIG. */
	knot_pkt_tsig_set(pkt, qdata->sign.tsig_key);	
241

242
	/* Guaranteed to have zone contents. */
243
	const knot_node_t *apex = qdata->zone->contents->apex;
244 245 246 247 248 249 250
	const knot_rrset_t *soa_rr = knot_node_rrset(apex, KNOT_RRTYPE_SOA);
	int ret = knot_pkt_put(pkt, 0, soa_rr, 0);
	if (ret != KNOT_EOK) {
		qdata->rcode = KNOT_RCODE_SERVFAIL;
		return NS_PROC_FAIL;
	}

251
	return NS_PROC_DONE;
252
}
253 254 255 256 257 258 259 260 261

int ixfr_answer(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata)
{
	if (pkt == NULL || ns == NULL || qdata == NULL) {
		return NS_PROC_FAIL;
	}

	int ret = KNOT_EOK;
	mm_ctx_t *mm = qdata->mm;
262
	struct timeval now = {0};
263
	struct ixfr_proc *ixfr = (struct ixfr_proc*)qdata->ext;
264 265 266 267 268 269

	/* Initialize on first call. */
	if (qdata->ext == NULL) {
		ret = ixfr_answer_init(qdata);
		switch(ret) {
		case KNOT_EOK:      /* OK */
270
			ixfr = (struct ixfr_proc*)qdata->ext;
271
			IXFR_LOG(LOG_INFO, "Started (serial %u -> %u).",
272 273
			         knot_rdata_soa_serial(ixfr->soa_from),
			         knot_rdata_soa_serial(ixfr->soa_to));
274 275
			break;
		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
276
			IXFR_LOG(LOG_INFO, "Zone is up-to-date.");
277 278 279
			return ixfr_answer_soa(pkt, ns, qdata);
		case KNOT_ERANGE:   /* No history -> AXFR. */
		case KNOT_ENOENT:
280 281
			IXFR_LOG(LOG_INFO, "Incomplete history, fallback to AXFR.");
			qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */
282 283
			return axfr_answer(pkt, ns, qdata);
		default:            /* Server errors. */
284
			IXFR_LOG(LOG_ERR, "Failed to start (%s).", knot_strerror(ret));
285 286 287
			return NS_PROC_FAIL;
		}
	}
288 289 290
	
	/* Reserve space for TSIG. */
	knot_pkt_tsig_set(pkt, qdata->sign.tsig_key);
291 292 293 294 295 296 297

	/* Answer current packet (or continue). */
	ret = xfr_process_list(pkt, &ixfr_process_item, qdata);
	switch(ret) {
	case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */
		return NS_PROC_FULL; /* Check for more. */
	case KNOT_EOK:    /* Last response. */
298 299 300 301
		gettimeofday(&now, NULL);
		IXFR_LOG(LOG_INFO, "Finished in %.02fs (%u messages, ~%.01fkB).",
		         time_diff(&ixfr->proc.tstamp, &now) / 1000.0,
		         ixfr->proc.npkts, ixfr->proc.nbytes / 1024.0);
302
		ret = NS_PROC_DONE;
303 304
		break;
	default:          /* Generic error. */
305
		IXFR_LOG(LOG_ERR, "%s", knot_strerror(ret));
306 307 308 309 310 311 312 313 314 315 316 317
		ret = NS_PROC_FAIL;
		break;
	}

	/* Finished successfuly or fatal error. */
	ptrlist_free(&ixfr->proc.nodes, mm);
	knot_changesets_free(&ixfr->changesets);
	mm->free(qdata->ext);
	qdata->ext = NULL;

	return ret;
}
318 319

#undef IXFR_LOG