Commit a5af9e4d authored by Jan Kadlec's avatar Jan Kadlec

zone-events: Moved IXFR-in processing from xfr-in.c to ixfr.c

parent 00145bbc
......@@ -9,6 +9,8 @@
#include "libknot/util/utils.h"
#include "libknot/rdata/soa.h"
/* ------------------------ IXFR-out processing ----------------------------- */
/*! \brief Current IXFR answer sections. */
enum {
SOA_REMOVE = 0,
......@@ -27,13 +29,10 @@ struct ixfrout_proc {
const knot_rrset_t *soa_from, *soa_to;
};
/* IXFR-specific logging (internal, expects 'qdata' variable set). */
/* IXFR-out-specific logging (internal, expects 'qdata' variable set). */
#define IXFROUT_LOG(severity, msg...) \
QUERY_LOG(severity, qdata, "Outgoing IXFR", msg)
#define IXFRIN_LOG(severity, msg...) \
ANSWER_LOG(severity, adata, "Incoming IXFR", msg)
/*! \brief Helper macro for putting RRs into packet. */
#define IXFR_SAFE_PUT(pkt, rr) \
ret = knot_pkt_put((pkt), 0, (rr), KNOT_PF_NOTRUNC); \
......@@ -279,69 +278,31 @@ static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata)
return NS_PROC_DONE;
}
int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
{
if (pkt == NULL || qdata == NULL) {
return NS_PROC_FAIL;
}
int ret = KNOT_EOK;
struct timeval now = {0};
struct ixfrout_proc *ixfr = (struct ixfrout_proc*)qdata->ext;
/* ------------------------- IXFR-in processing ----------------------------- */
/* If IXFR is disabled, respond with SOA. */
if (qdata->param->proc_flags & NS_QUERY_NO_IXFR) {
return ixfr_answer_soa(pkt, qdata);
}
/* Initialize on first call. */
if (qdata->ext == NULL) {
ret = ixfr_answer_init(qdata);
switch(ret) {
case KNOT_EOK: /* OK */
ixfr = (struct ixfrout_proc*)qdata->ext;
IXFROUT_LOG(LOG_INFO, "Started (serial %u -> %u).",
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. */
IXFROUT_LOG(LOG_INFO, "Zone is up-to-date.");
return ixfr_answer_soa(pkt, qdata);
case KNOT_ERANGE: /* No history -> AXFR. */
case KNOT_ENOENT:
IXFROUT_LOG(LOG_INFO, "Incomplete history, fallback to AXFR.");
qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */
return axfr_query(pkt, qdata);
default: /* Server errors. */
IXFROUT_LOG(LOG_ERR, "Failed to start (%s).", knot_strerror(ret));
return NS_PROC_FAIL;
}
}
/* Reserve space for TSIG. */
knot_pkt_reserve(pkt, tsig_wire_maxsize(qdata->sign.tsig_key));
/*! \brief IXFR-in processing states. */
enum ixfrin_states {
IXFR_START = 0, /* IXFR-in starting, expecting final SOA. */
IXFR_SOA_FROM, /* Expecting starting SOA. */
IXFR_SOA_TO, /* Expecting ending SOA. */
IXFR_DEL, /* Expecting RR to delete. */
IXFR_ADD, /* Expecting RR to add. */
IXFR_DONE /* Processing done, IXFR-in complete. */
};
/* Answer current packet (or continue). */
ret = xfr_process_list(pkt, &ixfr_process_changeset, 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. */
gettimeofday(&now, NULL);
IXFROUT_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);
ret = NS_PROC_DONE;
break;
default: /* Generic error. */
IXFROUT_LOG(LOG_ERR, "%s", knot_strerror(ret));
ret = NS_PROC_FAIL;
break;
}
/*! \brief Extended structure for IXFR-in processing. */
struct ixfrin_proc {
int state; /* IXFR-in state. */
knot_changesets_t *changesets; /* Created changesets. */
zone_t *zone; /* Modified zone. */
mm_ctx_t *mm; /* Memory context for RR allocations. */
};
return ret;
}
/* IXFR-in-specific logging (internal, expects 'adata' variable set). */
#define IXFRIN_LOG(severity, msg...) \
ANSWER_LOG(severity, adata, "Incoming IXFR", msg)
/*! \brief Cleans up data allocated by IXFR-in processing. */
static void ixfrin_cleanup(struct answer_data *data)
{
struct ixfrin_proc *proc = data->ext;
......@@ -352,6 +313,7 @@ static void ixfrin_cleanup(struct answer_data *data)
}
}
/*! \brief Initializes IXFR-in processing context. */
static int ixfrin_answer_init(struct answer_data *data)
{
struct ixfrin_proc *proc = mm_alloc(data->mm, sizeof(struct ixfrin_proc));
......@@ -375,6 +337,7 @@ static int ixfrin_answer_init(struct answer_data *data)
return KNOT_EOK;
}
/*! \brief Finalizes IXFR-in processing. */
static int ixfrin_finalize(struct answer_data *adata)
{
struct ixfrin_proc *proc = adata->ext;
......@@ -404,6 +367,273 @@ static int ixfrin_finalize(struct answer_data *adata)
return KNOT_EOK;
}
/*! \brief Stores starting SOA into changesets structure. */
static int solve_start(const knot_rrset_t *rr, knot_changesets_t *changesets, mm_ctx_t *mm)
{
assert(changesets->first_soa == NULL);
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
// Store the first SOA for later use.
changesets->first_soa = knot_rrset_copy(rr, mm);
if (changesets->first_soa == NULL) {
return NS_PROC_FAIL;
}
return NS_PROC_MORE;
}
/*!
* \brief Decides what to do with a starting SOA - either ends the processing or
* creates a new changeset and stores the SOA into it.
*/
static int solve_soa_from(const knot_rrset_t *rr, knot_changesets_t *changesets,
int *state, mm_ctx_t *mm)
{
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
if (knot_rrset_equal(rr, changesets->first_soa, KNOT_RRSET_COMPARE_WHOLE)) {
// Last SOA encountered, transfer done.
*state = IXFR_DONE;
return NS_PROC_DONE;
}
// Create new changeset.
knot_changeset_t *change = knot_changesets_create_changeset(changesets);
if (change == NULL) {
return NS_PROC_FAIL;
}
// Store SOA into changeset.
change->soa_from = knot_rrset_copy(rr, mm);
if (change->soa_from == NULL) {
return NS_PROC_FAIL;
}
change->serial_from = knot_soa_serial(&rr->rrs);
return NS_PROC_MORE;
}
/*! \brief Stores ending SOA into changeset. */
static int solve_soa_to(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
change->soa_to= knot_rrset_copy(rr, mm);
if (change->soa_to == NULL) {
return NS_PROC_FAIL;
}
change->serial_to = knot_soa_serial(&rr->rrs);
return NS_PROC_MORE;
}
/*! \brief Adds single RR into given section of changeset. */
static int add_part(const knot_rrset_t *rr, knot_changeset_t *change, int part, mm_ctx_t *mm)
{
assert(rr->type != KNOT_RRTYPE_SOA);
knot_rrset_t *copy = knot_rrset_copy(rr, mm);
if (copy) {
int ret = knot_changeset_add_rrset(change, copy, part);
if (ret != KNOT_EOK) {
return NS_PROC_FAIL;
} else {
return NS_PROC_MORE;
}
} else {
return NS_PROC_FAIL;
}
}
/*! \brief Adds single RR into REMOVE section of changeset. */
static int solve_del(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
return add_part(rr, change, KNOT_CHANGESET_REMOVE, mm);
}
/*! \brief Adds single RR into ADD section of changeset. */
static int solve_add(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
return add_part(rr, change, KNOT_CHANGESET_ADD, mm);
}
/*!
* \brief Processes single RR according to current IXFR-in state. The states
* correspond with IXFR-in message structure, in the order they are
* mentioned in the code.
*
* \param rr RR to process.
* \param changesets Output changesets.
* \param state Current IXFR-in state.
* \param next Output parameter - set to true if next RR should be fetched.
* \param mm Memory context used to create RR copies.
*
* \return NS_PROC_MORE, NS_PROC_DONE, NS_PROC_FAIL.
*/
static int ixfrin_step(const knot_rrset_t *rr, knot_changesets_t *changesets,
int *state, bool *next, mm_ctx_t *mm)
{
switch (*state) {
case IXFR_START:
*state = IXFR_SOA_FROM;
*next = true;
return solve_start(rr, changesets, mm);
case IXFR_SOA_FROM:
*state = IXFR_DEL;
*next = true;
return solve_soa_from(rr, changesets, state, mm);
case IXFR_DEL:
if (rr->type == KNOT_RRTYPE_SOA) {
// Encountered SOA, do not consume the RR.
*state = IXFR_SOA_TO;
*next = false;
return NS_PROC_MORE;
}
*next = true;
return solve_del(rr, knot_changesets_get_last(changesets), mm);
case IXFR_SOA_TO:
*state = IXFR_ADD;
*next = true;
return solve_soa_to(rr, knot_changesets_get_last(changesets), mm);
case IXFR_ADD:
if (rr->type == KNOT_RRTYPE_SOA) {
// Encountered SOA, do not consume the RR.
*state = IXFR_SOA_FROM;
*next = false;
return NS_PROC_MORE;
}
*next = true;
return solve_add(rr, knot_changesets_get_last(changesets), mm);
default:
return NS_PROC_FAIL;
}
}
/*! \brief Checks whether journal node limit has not been exceeded. */
static bool journal_limit_exceeded(struct ixfrin_proc *proc)
{
return proc->changesets->count > JOURNAL_NCOUNT;
}
/*! \brief Checks whether RR belongs into zone. */
static bool out_of_zone(const knot_rrset_t *rr, struct ixfrin_proc *proc)
{
return !knot_dname_is_sub(rr->owner, proc->zone->name) &&
!knot_dname_is_equal(rr->owner, proc->zone->name);
}
/*!
* \brief Processes IXFR reply packet and fills in the changesets structure.
*
* \param pkt Packet containing the IXFR reply in wire format.
* \param proc Processing context.
*
* \return NS_PROC_MORE, NS_PROC_DONE, NS_PROC_FAIL
*/
static int xfrin_process_ixfr_packet(knot_pkt_t *pkt, struct ixfrin_proc *proc)
{
const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
int ret = NS_PROC_NOOP;
for (uint16_t i = 0; i < answer->count; /* NOOP */) {
if (journal_limit_exceeded(proc)) {
// Will revert to AXFR.
assert(proc->state != IXFR_DONE);
return NS_PROC_DONE;
}
const knot_rrset_t *rr = &answer->rr[i];
if (out_of_zone(rr, proc)) {
continue;
}
// Process RR.
bool next = false;
ret = ixfrin_step(rr, proc->changesets,
&proc->state, &next, proc->mm);
if (ret == NS_PROC_FAIL || ret == NS_PROC_DONE) {
// Quit on errors and if we're done.
return ret;
}
if (next) {
++i;
}
}
#warning TODO TSIG
assert(ret == NS_PROC_MORE);
return ret;
}
/* --------------------------------- API ------------------------------------ */
int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
{
if (pkt == NULL || qdata == NULL) {
return NS_PROC_FAIL;
}
int ret = KNOT_EOK;
struct timeval now = {0};
struct ixfrout_proc *ixfr = (struct ixfrout_proc*)qdata->ext;
/* If IXFR is disabled, respond with SOA. */
if (qdata->param->proc_flags & NS_QUERY_NO_IXFR) {
return ixfr_answer_soa(pkt, qdata);
}
/* Initialize on first call. */
if (qdata->ext == NULL) {
ret = ixfr_answer_init(qdata);
switch(ret) {
case KNOT_EOK: /* OK */
ixfr = (struct ixfrout_proc*)qdata->ext;
IXFROUT_LOG(LOG_INFO, "Started (serial %u -> %u).",
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. */
IXFROUT_LOG(LOG_INFO, "Zone is up-to-date.");
return ixfr_answer_soa(pkt, qdata);
case KNOT_ERANGE: /* No history -> AXFR. */
case KNOT_ENOENT:
IXFROUT_LOG(LOG_INFO, "Incomplete history, fallback to AXFR.");
qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */
return axfr_query(pkt, qdata);
default: /* Server errors. */
IXFROUT_LOG(LOG_ERR, "Failed to start (%s).", knot_strerror(ret));
return NS_PROC_FAIL;
}
}
/* Reserve space for TSIG. */
knot_pkt_reserve(pkt, tsig_wire_maxsize(qdata->sign.tsig_key));
/* Answer current packet (or continue). */
ret = xfr_process_list(pkt, &ixfr_process_changeset, 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. */
gettimeofday(&now, NULL);
IXFROUT_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);
ret = NS_PROC_DONE;
break;
default: /* Generic error. */
IXFROUT_LOG(LOG_ERR, "%s", knot_strerror(ret));
ret = NS_PROC_FAIL;
break;
}
return ret;
}
int ixfrin_process_answer(knot_pkt_t *pkt, struct answer_data *adata)
{
if (adata->ext == NULL) {
......
......@@ -35,24 +35,6 @@ struct query_data;
struct answer_data;
struct xfr_proc;
/*! \brief IXFR-in processing states. */
enum ixfrin_states {
IXFR_START = 0, /* IXFR-in starting, expecting final SOA. */
IXFR_SOA_FROM = 1, /* Expecting starting SOA. */
IXFR_SOA_TO = 2, /* Expecting ending SOA. */
IXFR_DEL = 3, /* Expecting RR to delete. */
IXFR_ADD = 4, /* Expecting RR to add. */
IXFR_DONE = 5 /* Processing done, IXFR-in complete. */
};
/*! \brief Extended structure for IXFR-in processing. */
struct ixfrin_proc {
int state;
knot_changesets_t *changesets;
zone_t *zone;
mm_ctx_t *mm;
};
/*!
* \brief IXFR query processing module.
*
......@@ -63,27 +45,11 @@ struct ixfrin_proc {
int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata);
/*!
* \brief Process an IXFR query response.
*
* \param pkt Processed packet.
* \param xfr Persistent transfer-specific data.
*
* \retval KNOT_EOK If this packet was processed successfuly and another packet
* is expected. (RFC1995bis, case c)
* \retval KNOT_ENOXFR If the transfer is not taking place because server's
* SERIAL is the same as this client's SERIAL. The client
* should close the connection and do no further processing.
* (RFC1995bis case a).
* \retval KNOT_EAGAIN If the server could not fit the transfer into the packet.
* This should happen only if UDP was used. In this case
* the client should retry the request via TCP. If UDP was
* not used, it should be considered that the transfer was
* malformed and the connection should be closed.
* (RFC1995bis case b).
* \retval >0 Transfer successully finished. Changesets are created and furter
* processing is needed.
* \retval Other If any other error occured. The connection should be closed.
* \brief IXFR query response processing module.
*
* \retval MORE if more data are required.
* \retval FAIL if it encountered an error, retry over AXFR will be done.
* \retval DONE if finished.
*/
int ixfrin_process_answer(knot_pkt_t *pkt, struct answer_data *adata);
......
......@@ -114,6 +114,7 @@ int xfrin_transfer_needed(const zone_contents_t *zone,
static int xfrin_check_tsig(knot_pkt_t *packet, knot_ns_xfr_t *xfr,
int tsig_req)
{
#warning reimplement, but not inside this file
assert(packet != NULL);
assert(xfr != NULL);
......@@ -254,204 +255,6 @@ int xfrin_process_axfr_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
/*----------------------------------------------------------------------------*/
/* ------------------------- IXFR-in processing ----------------------------- */
/*! \brief Stores starting SOA into changesets structure. */
static int solve_start(const knot_rrset_t *rr, knot_changesets_t *changesets, mm_ctx_t *mm)
{
assert(changesets->first_soa == NULL);
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
// Store the first SOA for later use.
changesets->first_soa = knot_rrset_copy(rr, mm);
if (changesets->first_soa == NULL) {
return NS_PROC_FAIL;
}
return NS_PROC_MORE;
}
/*!
* \brief Decides what to do with a starting SOA - either ends the processing or
* creates a new changeset and stores the SOA into it.
*/
static int solve_soa_from(const knot_rrset_t *rr, knot_changesets_t *changesets,
int *state, mm_ctx_t *mm)
{
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
if (knot_rrset_equal(rr, changesets->first_soa, KNOT_RRSET_COMPARE_WHOLE)) {
// Last SOA encountered, transfer done.
*state = IXFR_DONE;
return NS_PROC_DONE;
}
// Create new changeset.
knot_changeset_t *change = knot_changesets_create_changeset(changesets);
if (change == NULL) {
return NS_PROC_FAIL;
}
// Store SOA into changeset.
change->soa_from = knot_rrset_copy(rr, mm);
if (change->soa_from == NULL) {
return NS_PROC_FAIL;
}
change->serial_from = knot_soa_serial(&rr->rrs);
return NS_PROC_MORE;
}
/*! \brief Stores ending SOA into changeset. */
static int solve_soa_to(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
if (rr->type != KNOT_RRTYPE_SOA) {
return NS_PROC_FAIL;
}
change->soa_to= knot_rrset_copy(rr, mm);
if (change->soa_to == NULL) {
return NS_PROC_FAIL;
}
change->serial_to = knot_soa_serial(&rr->rrs);
return NS_PROC_MORE;
}
/*! \brief Adds single RR into given section of changeset. */
static int add_part(const knot_rrset_t *rr, knot_changeset_t *change, int part, mm_ctx_t *mm)
{
assert(rr->type != KNOT_RRTYPE_SOA);
knot_rrset_t *copy = knot_rrset_copy(rr, mm);
if (copy) {
int ret = knot_changeset_add_rrset(change, copy, part);
if (ret != KNOT_EOK) {
return NS_PROC_FAIL;
} else {
return NS_PROC_MORE;
}
} else {
return NS_PROC_FAIL;
}
}
/*! \brief Adds single RR into REMOVE section of changeset. */
static int solve_del(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
return add_part(rr, change, KNOT_CHANGESET_REMOVE, mm);
}
/*! \brief Adds single RR into ADD section of changeset. */
static int solve_add(const knot_rrset_t *rr, knot_changeset_t *change, mm_ctx_t *mm)
{
return add_part(rr, change, KNOT_CHANGESET_ADD, mm);
}
/*!
* \brief Processes single RR according to current IXFR-in state. The states
* correspond with IXFR-in message structure, in the order they are
* mentioned in the code.
* \param rr RR to process.
* \param changesets Output changesets.
* \param state Current IXFR-in state.
* \param next Output parameter - set to true if next RR should be fetched.
* \param mm Memory context used to create RR copies.
* \return NS_PROC_MORE, NS_PROC_DONE, NS_PROC_FAIL.
*/
static int ixfrin_step(const knot_rrset_t *rr, knot_changesets_t *changesets,
int *state, bool *next, mm_ctx_t *mm)
{
switch (*state) {
case IXFR_START:
*state = IXFR_SOA_FROM;
*next = true;
return solve_start(rr, changesets, mm);
case IXFR_SOA_FROM:
*state = IXFR_DEL;
*next = true;
return solve_soa_from(rr, changesets, state, mm);
case IXFR_DEL:
if (rr->type == KNOT_RRTYPE_SOA) {
// Encountered SOA, do not consume the RR.
*state = IXFR_SOA_TO;
*next = false;
return NS_PROC_MORE;
}
*next = true;
return solve_del(rr, knot_changesets_get_last(changesets), mm);
case IXFR_SOA_TO:
*state = IXFR_ADD;
*next = true;
return solve_soa_to(rr, knot_changesets_get_last(changesets), mm);
case IXFR_ADD:
if (rr->type == KNOT_RRTYPE_SOA) {
// Encountered SOA, do not consume the RR.
*state = IXFR_SOA_FROM;
*next = false;
return NS_PROC_MORE;
}
*next = true;
return solve_add(rr, knot_changesets_get_last(changesets), mm);
default:
return NS_PROC_FAIL;
}
}
/*! \brief Checks whether journal node limit has not been exceeded. */
static bool journal_limit_exceeded(struct ixfrin_proc *proc)
{
return proc->changesets->count > JOURNAL_NCOUNT;
}
/*! \brief Checks whether RR belongs into zone. */
static bool out_of_zone(const knot_rrset_t *rr, struct ixfrin_proc *proc)
{
return !knot_dname_is_sub(rr->owner, proc->zone->name) &&
!knot_dname_is_equal(rr->owner, proc->zone->name);
}
int xfrin_process_ixfr_packet(knot_pkt_t *pkt, struct ixfrin_proc *proc)
{
uint16_t rr_id = 0;
knot_changesets_t *changesets = proc->changesets;
const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
const knot_rrset_t *rr = NULL;
xfrin_take_rr(answer, &rr, &rr_id);
int ret = NS_PROC_NOOP;
while (rr) {
if (journal_limit_exceeded(proc)) {
// Will revert to AXFR.
assert(proc->state != IXFR_DONE);
return NS_PROC_DONE;
}
if (out_of_zone(rr, proc)) {
xfrin_take_rr(answer, &rr, &rr_id);
continue;
}
// Process RR.
bool next = false;
ret = ixfrin_step(rr, changesets, &proc->state, &next, proc->mm);
if (ret == NS_PROC_FAIL || ret == NS_PROC_DONE) {
// Quit on errors and if we're done.
return ret;
}
if (next) {
xfrin_take_rr(answer, &rr, &rr_id);
}
}
#warning TODO TSIG
assert(ret == NS_PROC_MORE);
return ret;
}
/*----------------------------------------------------------------------------*/
/* Applying changesets to zone */
/*----------------------------------------------------------------------------*/
......
......@@ -79,16 +79,6 @@ int xfrin_transfer_needed(const zone_contents_t *zone,
*/
int xfrin_process_axfr_packet(knot_pkt_t *pkt, struct xfr_proc *proc);
/*!
* \brief Parses IXFR reply packet and fills in the changesets structure.
*
* \param pkt Packet containing the IXFR reply in wire format.
* \param proc Processing context.
*
* \return NS_PROC_MORE, NS_PROC_DONE, NS_PROC_FAIL
*/
int xfrin_process_ixfr_packet(knot_pkt_t *pkt, struct ixfrin_proc *proc);
/*!
* \brief Applies changesets *with* zone shallow copy.
*
......
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