Commit ff7e7058 authored by Marek Vavrusa's avatar Marek Vavrusa

Implemented outgoing AXFR and IXFR with mostly shared code.

The general idea is a processor function which takes list_t of someting
and passes it to the specific processing function one by one.
AXFR uses list of two zone trees, IXFR list of changesets.
parent 2220173e
#include <config.h>
#include "libknot/nameserver/axfr.h"
#include "libknot/nameserver/internet.h"
#include "libknot/nameserver/ns_proc_query.h"
#include "libknot/util/debug.h"
#include "common/descriptor.h"
#include "common/lists.h"
struct axfr_proc {
struct xfr_proc proc;
hattrie_iter_t *i;
unsigned cur_rrset;
list_t nodes;
};
static int axfr_put(knot_pkt_t *pkt, const knot_rrset_t *rrset)
static int put_rrset_and_rrsig(knot_pkt_t *pkt, const knot_rrset_t *rrset)
{
const unsigned flags = KNOT_PF_NOTRUNC;
int ret = knot_pkt_put(pkt, 0, rrset, flags);
......@@ -39,7 +40,7 @@ static int put_rrsets(knot_pkt_t *pkt, knot_node_t *node, struct axfr_proc *stat
}
/* Put into packet. */
ret = axfr_put(pkt, rrset[i]);
ret = put_rrset_and_rrsig(pkt, rrset[i]);
if (ret != KNOT_EOK) { /* Keep for continuing. */
state->cur_rrset = i;
return ret;
......@@ -50,81 +51,96 @@ static int put_rrsets(knot_pkt_t *pkt, knot_node_t *node, struct axfr_proc *stat
return ret;
}
static int answer_put_nodes(knot_pkt_t *pkt, struct axfr_proc *state)
static int axfr_process_item(knot_pkt_t *pkt, const void *item, struct xfr_proc *state)
{
struct axfr_proc *axfr = (struct axfr_proc*)state;
if (axfr->i == NULL) {
axfr->i = hattrie_iter_begin(item, true);
}
/* Put responses. */
int ret = KNOT_EOK;
knot_node_t *node = NULL;
while(!hattrie_iter_finished(state->i)) {
node = (knot_node_t *)*hattrie_iter_val(state->i);
ret = put_rrsets(pkt, node, state);
while(!hattrie_iter_finished(axfr->i)) {
node = (knot_node_t *)*hattrie_iter_val(axfr->i);
ret = put_rrsets(pkt, node, axfr);
if (ret != KNOT_EOK) {
break;
}
hattrie_iter_next(state->i);
hattrie_iter_next(axfr->i);
}
/* Finished all nodes. */
if (ret == KNOT_EOK) {
hattrie_iter_free(axfr->i);
axfr->i = NULL;
}
return ret;
}
static int answer_pkt(knot_pkt_t *pkt, struct query_data *qdata)
static int axfr_answer_init(struct query_data *qdata)
{
assert(qdata);
/* Check zone state. */
NS_NEED_VALID_ZONE(qdata, KNOT_RCODE_NOTAUTH);
/* Begin zone iterator. */
mm_ctx_t *mm = qdata->mm;
knot_zone_contents_t *zone = qdata->zone->contents;
struct xfr_proc *xfer = mm->alloc(mm->ctx, sizeof(struct axfr_proc));
if (xfer == NULL) {
return KNOT_ENOMEM;
}
memset(xfer, 0, sizeof(struct axfr_proc));
init_list(&xfer->nodes);
qdata->ext = xfer;
/* Put data to process. */
ptrlist_add(&xfer->nodes, zone->nodes, mm);
ptrlist_add(&xfer->nodes, zone->nsec3_nodes, mm);
return KNOT_EOK;
}
int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb process_item, struct query_data *qdata)
{
int ret = KNOT_EOK;
mm_ctx_t *mm = qdata->mm;
struct axfr_proc *state = qdata->ext;
knot_zone_contents_t *zone = pkt->zone->contents;
struct xfr_proc *xfer = qdata->ext;
knot_zone_contents_t *zone = qdata->zone->contents;
knot_rrset_t *soa_rr = knot_node_get_rrset(zone->apex, KNOT_RRTYPE_SOA);
/* Prepend SOA on first packet. */
if (state == NULL) {
ret = axfr_put(pkt, soa_rr);
if (xfer->npkts == 0) {
ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC);
if (ret != KNOT_EOK) {
return ret;
}
/* Begin zone iterator. */
state = mm->alloc(mm->ctx, sizeof(struct axfr_proc));
if (state == NULL) {
return KNOT_ENOMEM;
}
memset(state, 0, sizeof(struct axfr_proc));
init_list(&state->nodes);
ptrlist_add(&state->nodes, zone->nodes, mm);
ptrlist_add(&state->nodes, zone->nsec3_nodes, mm);
qdata->ext = state;
}
}
/* Put zone contents and then NSEC3-related contents. */
while (!EMPTY_LIST(state->nodes)) {
ptrnode_t *head = HEAD(state->nodes);
if (state->i == NULL) {
state->i = hattrie_iter_begin(head->d, true);
}
ret = answer_put_nodes(pkt, state);
/* Process all items in the list. */
while (!EMPTY_LIST(xfer->nodes)) {
ptrnode_t *head = HEAD(xfer->nodes);
ret = process_item(pkt, head->d, xfer);
if (ret == KNOT_EOK) { /* Finished. */
hattrie_iter_free(state->i);
state->i = NULL;
rem_node((node_t *)head);
mm->free(head);
} else { /* Packet full or error. */
} else { /* Packet full or other error. */
break;
}
}
/* Append SOA on last packet. */
if (ret == KNOT_EOK) {
ret = axfr_put(pkt, soa_rr);
}
/* Check if finished or not. */
if (ret != KNOT_ESPACE) {
/* Finished successfuly or fatal error. */
ptrlist_free(&state->nodes, mm);
mm->free(state);
qdata->ext = NULL;
ret = knot_pkt_put(pkt, 0, soa_rr, KNOT_PF_NOTRUNC);
}
/* Update counters. */
xfer->npkts += 1;
xfer->nbytes += pkt->size;
return ret;
}
......@@ -134,29 +150,40 @@ int axfr_answer(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata
assert(ns);
assert(qdata);
int ret = KNOT_EOK;
mm_ctx_t *mm = qdata->mm;
/* Initialize on first call. */
if (qdata->ext == NULL) {
ret = axfr_answer_init(qdata);
dbg_ns("%s: init => %s\n", __func__, knot_strerror(ret));
if (ret != KNOT_EOK) {
return ret;
}
/* Check zone state. */
switch(knot_zone_state(pkt->zone)) {
case KNOT_EOK:
break;
case KNOT_ENOENT:
qdata->rcode = KNOT_RCODE_NOTAUTH;
return NS_PROC_FAIL;
default:
return NS_PROC_FAIL;
}
/* Answer current packet (or continue). */
int ret = answer_pkt(pkt, qdata);
struct xfr_proc *xfer = qdata->ext;
ret = xfr_process_list(pkt, &axfr_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. */
dbg_ns("%s: finished AXFR, %u pkts, ~%.01fkB\n", __func__,
xfer->npkts, xfer->nbytes/1024.0);
ret = NS_PROC_FINISH;
break;
default: /* Generic error. */
dbg_ns("%s: answered with ret = %s\n", __func__, knot_strerror(ret));
return NS_PROC_FAIL;
ret = NS_PROC_FAIL;
break;
}
return NS_PROC_FINISH;
/* Finished successfuly or fatal error. */
ptrlist_free(&xfer->nodes, mm);
mm->free(xfer);
qdata->ext = NULL;
return ret;
}
......@@ -33,6 +33,29 @@
struct query_data;
/*! \brief Generic transfer processing state. */
struct xfr_proc {
list_t nodes; /* Items to process (ptrnode_t). */
unsigned npkts; /* Packets processed. */
unsigned nbytes; /* Bytes processed. */
};
/*! \brief Generic transfer processing.
*/
typedef int (*xfr_put_cb)(knot_pkt_t *pkt, const void *item, struct xfr_proc *xfer);
/*! \brief Put all items from xfr_proc.nodes to packet using a callback function.
* \note qdata->ext points to struct xfr_proc* (this is xfer-specific context)
*/
int xfr_process_list(knot_pkt_t *pkt, xfr_put_cb put, struct query_data *qdata);
/*!
* \brief AXFR query processing module.
*
* \retval NS_PROC_FULL if it has an answer, but not yet finished.
* \retval NS_PROC_FAIL if it encountered an error.
* \retval NS_PROC_EOK if finished.
*/
int axfr_answer(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata);
#endif /* _KNOT_AXFR_H_ */
......
#include <config.h>
#include "libknot/nameserver/ixfr.h"
#include "libknot/nameserver/axfr.h"
#include "libknot/nameserver/internet.h"
#include "libknot/nameserver/ns_proc_query.h"
#include "libknot/util/debug.h"
#include "libknot/rdata.h"
#include "knot/server/zones.h"
#include "common/descriptor.h"
int ixfr_answer(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata)
/*! \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;
};
/*! \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)
{
qdata->rcode = KNOT_RCODE_NOTIMPL;
return NS_PROC_FAIL;
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. */
knot_rr_ln_t *rr_item = NULL;
while(ixfr->cur != NULL) {
rr_item = (knot_rr_ln_t *)ixfr->cur;
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;
}
return ret;
}
int ixfr_answer_soa(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *qdata)
/*!
* \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)
{
if (pkt == NULL || ns == NULL || qdata == NULL) {
return NS_PROC_FAIL;
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;
}
/* 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;
}
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;
}
static int ixfr_answer_init(struct query_data *qdata)
{
/* Check zone state. */
const knot_zone_t *zone = pkt->zone;
switch(knot_zone_state(zone)) {
case KNOT_EOK:
break;
case KNOT_ENOENT:
qdata->rcode = KNOT_RCODE_NOTAUTH;
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. */
const knot_pktsection_t *authority = knot_pkt_section(qdata->pkt, KNOT_AUTHORITY);
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;
return NS_PROC_FAIL;
default:
qdata->rcode = KNOT_RCODE_SERVFAIL;
}
/* SOA needs to match QNAME. */
NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
/* Compare serials. */
knot_changesets_t *chgsets = NULL;
int ret = ixfr_load_chsets(&chgsets, qdata->zone, their_soa);
if (ret != KNOT_EOK) {
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));
init_list(&xfer->proc.nodes);
qdata->ext = xfer;
/* 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);
}
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) {
return NS_PROC_FAIL;
}
/* Check zone state. */
NS_NEED_VALID_ZONE(qdata, KNOT_RCODE_NOTAUTH);
/* Guaranteed to have zone contents. */
const knot_node_t *apex = zone->contents->apex;
const knot_node_t *apex = qdata->zone->contents->apex;
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) {
......@@ -40,3 +210,55 @@ int ixfr_answer_soa(knot_pkt_t *pkt, knot_nameserver_t *ns, struct query_data *q
return NS_PROC_FINISH;
}
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;
/* Initialize on first call. */
if (qdata->ext == NULL) {
ret = ixfr_answer_init(qdata);
dbg_ns("%s: init => %s\n", __func__, knot_strerror(ret));
switch(ret) {
case KNOT_EOK: /* OK */
break;
case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
return ixfr_answer_soa(pkt, ns, qdata);
case KNOT_ERANGE: /* No history -> AXFR. */
case KNOT_ENOENT:
return axfr_answer(pkt, ns, qdata);
default: /* Server errors. */
return NS_PROC_FAIL;
}
}
/* Answer current packet (or continue). */
struct ixfr_proc *ixfr = (struct ixfr_proc*)qdata->ext;
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. */
dbg_ns("%s: finished IXFR, %u pkts, %.01fkB\n", __func__,
ixfr->proc.npkts, ixfr->proc.nbytes/1024.0);
ret = NS_PROC_FINISH;
break;
default: /* Generic error. */
dbg_ns("%s: answered with ret = %s\n", __func__, knot_strerror(ret));
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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment