1816 lines
48 KiB
C
1816 lines
48 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
|
|
#include <isc/formatcheck.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/netmgr.h>
|
|
#include <isc/result.h>
|
|
#include <isc/stats.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/db.h>
|
|
#include <dns/dbiterator.h>
|
|
#include <dns/dlz.h>
|
|
#include <dns/fixedname.h>
|
|
#include <dns/journal.h>
|
|
#include <dns/message.h>
|
|
#include <dns/peer.h>
|
|
#include <dns/rdataclass.h>
|
|
#include <dns/rdatalist.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/rdatasetiter.h>
|
|
#include <dns/rriterator.h>
|
|
#include <dns/soa.h>
|
|
#include <dns/stats.h>
|
|
#include <dns/tsig.h>
|
|
#include <dns/view.h>
|
|
#include <dns/zone.h>
|
|
#include <dns/zt.h>
|
|
|
|
#include <ns/client.h>
|
|
#include <ns/log.h>
|
|
#include <ns/server.h>
|
|
#include <ns/stats.h>
|
|
#include <ns/xfrout.h>
|
|
|
|
/*! \file
|
|
* \brief
|
|
* Outgoing AXFR and IXFR.
|
|
*/
|
|
|
|
/*
|
|
* TODO:
|
|
* - IXFR over UDP
|
|
*/
|
|
|
|
#define XFROUT_COMMON_LOGARGS \
|
|
ns_lctx, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT
|
|
|
|
#define XFROUT_PROTOCOL_LOGARGS XFROUT_COMMON_LOGARGS, ISC_LOG_INFO
|
|
|
|
#define XFROUT_DEBUG_LOGARGS(n) XFROUT_COMMON_LOGARGS, ISC_LOG_DEBUG(n)
|
|
|
|
#define XFROUT_RR_LOGARGS XFROUT_COMMON_LOGARGS, XFROUT_RR_LOGLEVEL
|
|
|
|
#define XFROUT_RR_LOGLEVEL ISC_LOG_DEBUG(8)
|
|
|
|
/*%
|
|
* Fail unconditionally and log as a client error.
|
|
* The test against ISC_R_SUCCESS is there to keep the Solaris compiler
|
|
* from complaining about "end-of-loop code not reached".
|
|
*/
|
|
#define FAILC(code, msg) \
|
|
do { \
|
|
result = (code); \
|
|
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
|
|
NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
|
|
"bad zone transfer request: %s (%s)", msg, \
|
|
isc_result_totext(code)); \
|
|
if (result != ISC_R_SUCCESS) \
|
|
goto failure; \
|
|
} while (0)
|
|
|
|
#define FAILQ(code, msg, question, rdclass) \
|
|
do { \
|
|
char _buf1[DNS_NAME_FORMATSIZE]; \
|
|
char _buf2[DNS_RDATACLASS_FORMATSIZE]; \
|
|
result = (code); \
|
|
dns_name_format(question, _buf1, sizeof(_buf1)); \
|
|
dns_rdataclass_format(rdclass, _buf2, sizeof(_buf2)); \
|
|
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, \
|
|
NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO, \
|
|
"bad zone transfer request: '%s/%s': %s (%s)", \
|
|
_buf1, _buf2, msg, isc_result_totext(code)); \
|
|
if (result != ISC_R_SUCCESS) \
|
|
goto failure; \
|
|
} while (0)
|
|
|
|
#define CHECK(op) \
|
|
do { \
|
|
result = (op); \
|
|
if (result != ISC_R_SUCCESS) \
|
|
goto failure; \
|
|
} while (0)
|
|
|
|
/**************************************************************************/
|
|
|
|
static void
|
|
inc_stats(ns_client_t *client, dns_zone_t *zone, isc_statscounter_t counter) {
|
|
ns_stats_increment(client->manager->sctx->nsstats, counter);
|
|
if (zone != NULL) {
|
|
isc_stats_t *zonestats = dns_zone_getrequeststats(zone);
|
|
if (zonestats != NULL) {
|
|
isc_stats_increment(zonestats, counter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************/
|
|
|
|
/*% Log an RR (for debugging) */
|
|
|
|
static void
|
|
log_rr(dns_name_t *name, dns_rdata_t *rdata, uint32_t ttl) {
|
|
isc_result_t result;
|
|
isc_buffer_t buf;
|
|
char mem[2000];
|
|
dns_rdatalist_t rdl;
|
|
dns_rdataset_t rds;
|
|
dns_rdata_t rd = DNS_RDATA_INIT;
|
|
|
|
dns_rdatalist_init(&rdl);
|
|
rdl.type = rdata->type;
|
|
rdl.rdclass = rdata->rdclass;
|
|
rdl.ttl = ttl;
|
|
if (rdata->type == dns_rdatatype_sig ||
|
|
rdata->type == dns_rdatatype_rrsig)
|
|
{
|
|
rdl.covers = dns_rdata_covers(rdata);
|
|
} else {
|
|
rdl.covers = dns_rdatatype_none;
|
|
}
|
|
dns_rdataset_init(&rds);
|
|
dns_rdata_init(&rd);
|
|
dns_rdata_clone(rdata, &rd);
|
|
ISC_LIST_APPEND(rdl.rdata, &rd, link);
|
|
dns_rdatalist_tordataset(&rdl, &rds);
|
|
|
|
isc_buffer_init(&buf, mem, sizeof(mem));
|
|
result = dns_rdataset_totext(&rds, name, false, false, &buf);
|
|
|
|
/*
|
|
* We could use xfrout_log(), but that would produce
|
|
* very long lines with a repetitive prefix.
|
|
*/
|
|
if (result == ISC_R_SUCCESS) {
|
|
/*
|
|
* Get rid of final newline.
|
|
*/
|
|
INSIST(buf.used >= 1 &&
|
|
((char *)buf.base)[buf.used - 1] == '\n');
|
|
buf.used--;
|
|
|
|
isc_log_write(XFROUT_RR_LOGARGS, "%.*s",
|
|
(int)isc_buffer_usedlength(&buf),
|
|
(char *)isc_buffer_base(&buf));
|
|
} else {
|
|
isc_log_write(XFROUT_RR_LOGARGS, "<RR too large to print>");
|
|
}
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*
|
|
* An 'rrstream_t' is a polymorphic iterator that returns
|
|
* a stream of resource records. There are multiple implementations,
|
|
* e.g. for generating AXFR and IXFR records streams.
|
|
*/
|
|
|
|
typedef struct rrstream_methods rrstream_methods_t;
|
|
|
|
typedef struct rrstream {
|
|
isc_mem_t *mctx;
|
|
rrstream_methods_t *methods;
|
|
} rrstream_t;
|
|
|
|
struct rrstream_methods {
|
|
isc_result_t (*first)(rrstream_t *);
|
|
isc_result_t (*next)(rrstream_t *);
|
|
void (*current)(rrstream_t *, dns_name_t **, uint32_t *,
|
|
dns_rdata_t **);
|
|
void (*pause)(rrstream_t *);
|
|
void (*destroy)(rrstream_t **);
|
|
};
|
|
|
|
static void
|
|
rrstream_noop_pause(rrstream_t *rs) {
|
|
UNUSED(rs);
|
|
}
|
|
|
|
/**************************************************************************/
|
|
/*
|
|
* An 'ixfr_rrstream_t' is an 'rrstream_t' that returns
|
|
* an IXFR-like RR stream from a journal file.
|
|
*
|
|
* The SOA at the beginning of each sequence of additions
|
|
* or deletions are included in the stream, but the extra
|
|
* SOAs at the beginning and end of the entire transfer are
|
|
* not included.
|
|
*/
|
|
|
|
typedef struct ixfr_rrstream {
|
|
rrstream_t common;
|
|
dns_journal_t *journal;
|
|
} ixfr_rrstream_t;
|
|
|
|
/* Forward declarations. */
|
|
static void
|
|
ixfr_rrstream_destroy(rrstream_t **sp);
|
|
|
|
static rrstream_methods_t ixfr_rrstream_methods;
|
|
|
|
/*
|
|
* Returns: anything dns_journal_open() or dns_journal_iter_init()
|
|
* may return.
|
|
*/
|
|
|
|
static isc_result_t
|
|
ixfr_rrstream_create(isc_mem_t *mctx, const char *journal_filename,
|
|
uint32_t begin_serial, uint32_t end_serial, size_t *sizep,
|
|
rrstream_t **sp) {
|
|
isc_result_t result;
|
|
ixfr_rrstream_t *s = NULL;
|
|
|
|
INSIST(sp != NULL && *sp == NULL);
|
|
|
|
s = isc_mem_get(mctx, sizeof(*s));
|
|
s->common.mctx = NULL;
|
|
isc_mem_attach(mctx, &s->common.mctx);
|
|
s->common.methods = &ixfr_rrstream_methods;
|
|
s->journal = NULL;
|
|
|
|
CHECK(dns_journal_open(mctx, journal_filename, DNS_JOURNAL_READ,
|
|
&s->journal));
|
|
CHECK(dns_journal_iter_init(s->journal, begin_serial, end_serial,
|
|
sizep));
|
|
|
|
*sp = (rrstream_t *)s;
|
|
return ISC_R_SUCCESS;
|
|
|
|
failure:
|
|
ixfr_rrstream_destroy((rrstream_t **)(void *)&s);
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
ixfr_rrstream_first(rrstream_t *rs) {
|
|
ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
|
|
return dns_journal_first_rr(s->journal);
|
|
}
|
|
|
|
static isc_result_t
|
|
ixfr_rrstream_next(rrstream_t *rs) {
|
|
ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
|
|
return dns_journal_next_rr(s->journal);
|
|
}
|
|
|
|
static void
|
|
ixfr_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
|
|
dns_rdata_t **rdata) {
|
|
ixfr_rrstream_t *s = (ixfr_rrstream_t *)rs;
|
|
dns_journal_current_rr(s->journal, name, ttl, rdata);
|
|
}
|
|
|
|
static void
|
|
ixfr_rrstream_destroy(rrstream_t **rsp) {
|
|
ixfr_rrstream_t *s = (ixfr_rrstream_t *)*rsp;
|
|
if (s->journal != NULL) {
|
|
dns_journal_destroy(&s->journal);
|
|
}
|
|
isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
|
|
}
|
|
|
|
static rrstream_methods_t ixfr_rrstream_methods = {
|
|
ixfr_rrstream_first, ixfr_rrstream_next, ixfr_rrstream_current,
|
|
rrstream_noop_pause, ixfr_rrstream_destroy
|
|
};
|
|
|
|
/**************************************************************************/
|
|
/*
|
|
* An 'axfr_rrstream_t' is an 'rrstream_t' that returns
|
|
* an AXFR-like RR stream from a database.
|
|
*
|
|
* The SOAs at the beginning and end of the transfer are
|
|
* not included in the stream.
|
|
*/
|
|
|
|
typedef struct axfr_rrstream {
|
|
rrstream_t common;
|
|
dns_rriterator_t it;
|
|
bool it_valid;
|
|
} axfr_rrstream_t;
|
|
|
|
/*
|
|
* Forward declarations.
|
|
*/
|
|
static void
|
|
axfr_rrstream_destroy(rrstream_t **rsp);
|
|
|
|
static rrstream_methods_t axfr_rrstream_methods;
|
|
|
|
static isc_result_t
|
|
axfr_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
|
|
rrstream_t **sp) {
|
|
axfr_rrstream_t *s;
|
|
isc_result_t result;
|
|
|
|
INSIST(sp != NULL && *sp == NULL);
|
|
|
|
s = isc_mem_get(mctx, sizeof(*s));
|
|
s->common.mctx = NULL;
|
|
isc_mem_attach(mctx, &s->common.mctx);
|
|
s->common.methods = &axfr_rrstream_methods;
|
|
s->it_valid = false;
|
|
|
|
CHECK(dns_rriterator_init(&s->it, db, ver, 0));
|
|
s->it_valid = true;
|
|
|
|
*sp = (rrstream_t *)s;
|
|
return ISC_R_SUCCESS;
|
|
|
|
failure:
|
|
axfr_rrstream_destroy((rrstream_t **)(void *)&s);
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
axfr_rrstream_first(rrstream_t *rs) {
|
|
axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
|
|
isc_result_t result;
|
|
result = dns_rriterator_first(&s->it);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return result;
|
|
}
|
|
/* Skip SOA records. */
|
|
for (;;) {
|
|
dns_name_t *name_dummy = NULL;
|
|
uint32_t ttl_dummy;
|
|
dns_rdata_t *rdata = NULL;
|
|
dns_rriterator_current(&s->it, &name_dummy, &ttl_dummy, NULL,
|
|
&rdata);
|
|
if (rdata->type != dns_rdatatype_soa) {
|
|
break;
|
|
}
|
|
result = dns_rriterator_next(&s->it);
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
axfr_rrstream_next(rrstream_t *rs) {
|
|
axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
|
|
isc_result_t result;
|
|
|
|
/* Skip SOA records. */
|
|
for (;;) {
|
|
dns_name_t *name_dummy = NULL;
|
|
uint32_t ttl_dummy;
|
|
dns_rdata_t *rdata = NULL;
|
|
result = dns_rriterator_next(&s->it);
|
|
if (result != ISC_R_SUCCESS) {
|
|
break;
|
|
}
|
|
dns_rriterator_current(&s->it, &name_dummy, &ttl_dummy, NULL,
|
|
&rdata);
|
|
if (rdata->type != dns_rdatatype_soa) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static void
|
|
axfr_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
|
|
dns_rdata_t **rdata) {
|
|
axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
|
|
dns_rriterator_current(&s->it, name, ttl, NULL, rdata);
|
|
}
|
|
|
|
static void
|
|
axfr_rrstream_pause(rrstream_t *rs) {
|
|
axfr_rrstream_t *s = (axfr_rrstream_t *)rs;
|
|
dns_rriterator_pause(&s->it);
|
|
}
|
|
|
|
static void
|
|
axfr_rrstream_destroy(rrstream_t **rsp) {
|
|
axfr_rrstream_t *s = (axfr_rrstream_t *)*rsp;
|
|
if (s->it_valid) {
|
|
dns_rriterator_destroy(&s->it);
|
|
}
|
|
isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
|
|
}
|
|
|
|
static rrstream_methods_t axfr_rrstream_methods = {
|
|
axfr_rrstream_first, axfr_rrstream_next, axfr_rrstream_current,
|
|
axfr_rrstream_pause, axfr_rrstream_destroy
|
|
};
|
|
|
|
/**************************************************************************/
|
|
/*
|
|
* An 'soa_rrstream_t' is a degenerate 'rrstream_t' that returns
|
|
* a single SOA record.
|
|
*/
|
|
|
|
typedef struct soa_rrstream {
|
|
rrstream_t common;
|
|
dns_difftuple_t *soa_tuple;
|
|
} soa_rrstream_t;
|
|
|
|
/*
|
|
* Forward declarations.
|
|
*/
|
|
static void
|
|
soa_rrstream_destroy(rrstream_t **rsp);
|
|
|
|
static rrstream_methods_t soa_rrstream_methods;
|
|
|
|
static isc_result_t
|
|
soa_rrstream_create(isc_mem_t *mctx, dns_db_t *db, dns_dbversion_t *ver,
|
|
rrstream_t **sp) {
|
|
soa_rrstream_t *s;
|
|
isc_result_t result;
|
|
|
|
INSIST(sp != NULL && *sp == NULL);
|
|
|
|
s = isc_mem_get(mctx, sizeof(*s));
|
|
s->common.mctx = NULL;
|
|
isc_mem_attach(mctx, &s->common.mctx);
|
|
s->common.methods = &soa_rrstream_methods;
|
|
s->soa_tuple = NULL;
|
|
|
|
CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
|
|
&s->soa_tuple));
|
|
|
|
*sp = (rrstream_t *)s;
|
|
return ISC_R_SUCCESS;
|
|
|
|
failure:
|
|
soa_rrstream_destroy((rrstream_t **)(void *)&s);
|
|
return result;
|
|
}
|
|
|
|
static isc_result_t
|
|
soa_rrstream_first(rrstream_t *rs) {
|
|
UNUSED(rs);
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
soa_rrstream_next(rrstream_t *rs) {
|
|
UNUSED(rs);
|
|
return ISC_R_NOMORE;
|
|
}
|
|
|
|
static void
|
|
soa_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
|
|
dns_rdata_t **rdata) {
|
|
soa_rrstream_t *s = (soa_rrstream_t *)rs;
|
|
*name = &s->soa_tuple->name;
|
|
*ttl = s->soa_tuple->ttl;
|
|
*rdata = &s->soa_tuple->rdata;
|
|
}
|
|
|
|
static void
|
|
soa_rrstream_destroy(rrstream_t **rsp) {
|
|
soa_rrstream_t *s = (soa_rrstream_t *)*rsp;
|
|
if (s->soa_tuple != NULL) {
|
|
dns_difftuple_free(&s->soa_tuple);
|
|
}
|
|
isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
|
|
}
|
|
|
|
static rrstream_methods_t soa_rrstream_methods = {
|
|
soa_rrstream_first, soa_rrstream_next, soa_rrstream_current,
|
|
rrstream_noop_pause, soa_rrstream_destroy
|
|
};
|
|
|
|
/**************************************************************************/
|
|
/*
|
|
* A 'compound_rrstream_t' objects owns a soa_rrstream
|
|
* and another rrstream, the "data stream". It returns
|
|
* a concatenated stream consisting of the soa_rrstream, then
|
|
* the data stream, then the soa_rrstream again.
|
|
*
|
|
* The component streams are owned by the compound_rrstream_t
|
|
* and are destroyed with it.
|
|
*/
|
|
|
|
typedef struct compound_rrstream {
|
|
rrstream_t common;
|
|
rrstream_t *components[3];
|
|
int state;
|
|
isc_result_t result;
|
|
} compound_rrstream_t;
|
|
|
|
/*
|
|
* Forward declarations.
|
|
*/
|
|
static void
|
|
compound_rrstream_destroy(rrstream_t **rsp);
|
|
|
|
static isc_result_t
|
|
compound_rrstream_next(rrstream_t *rs);
|
|
|
|
static rrstream_methods_t compound_rrstream_methods;
|
|
|
|
/*
|
|
* Requires:
|
|
* soa_stream != NULL && *soa_stream != NULL
|
|
* data_stream != NULL && *data_stream != NULL
|
|
* sp != NULL && *sp == NULL
|
|
*
|
|
* Ensures:
|
|
* *soa_stream == NULL
|
|
* *data_stream == NULL
|
|
* *sp points to a valid compound_rrstream_t
|
|
* The soa and data streams will be destroyed
|
|
* when the compound_rrstream_t is destroyed.
|
|
*/
|
|
static isc_result_t
|
|
compound_rrstream_create(isc_mem_t *mctx, rrstream_t **soa_stream,
|
|
rrstream_t **data_stream, rrstream_t **sp) {
|
|
compound_rrstream_t *s;
|
|
|
|
INSIST(sp != NULL && *sp == NULL);
|
|
|
|
s = isc_mem_get(mctx, sizeof(*s));
|
|
s->common.mctx = NULL;
|
|
isc_mem_attach(mctx, &s->common.mctx);
|
|
s->common.methods = &compound_rrstream_methods;
|
|
s->components[0] = *soa_stream;
|
|
s->components[1] = *data_stream;
|
|
s->components[2] = *soa_stream;
|
|
s->state = -1;
|
|
s->result = ISC_R_FAILURE;
|
|
|
|
*data_stream = NULL;
|
|
*soa_stream = NULL;
|
|
*sp = (rrstream_t *)s;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static isc_result_t
|
|
compound_rrstream_first(rrstream_t *rs) {
|
|
compound_rrstream_t *s = (compound_rrstream_t *)rs;
|
|
s->state = 0;
|
|
do {
|
|
rrstream_t *curstream = s->components[s->state];
|
|
s->result = curstream->methods->first(curstream);
|
|
} while (s->result == ISC_R_NOMORE && s->state < 2);
|
|
return s->result;
|
|
}
|
|
|
|
static isc_result_t
|
|
compound_rrstream_next(rrstream_t *rs) {
|
|
compound_rrstream_t *s = (compound_rrstream_t *)rs;
|
|
rrstream_t *curstream = s->components[s->state];
|
|
s->result = curstream->methods->next(curstream);
|
|
while (s->result == ISC_R_NOMORE) {
|
|
/*
|
|
* Make sure locks held by the current stream
|
|
* are released before we switch streams.
|
|
*/
|
|
curstream->methods->pause(curstream);
|
|
if (s->state == 2) {
|
|
return ISC_R_NOMORE;
|
|
}
|
|
s->state++;
|
|
curstream = s->components[s->state];
|
|
s->result = curstream->methods->first(curstream);
|
|
}
|
|
return s->result;
|
|
}
|
|
|
|
static void
|
|
compound_rrstream_current(rrstream_t *rs, dns_name_t **name, uint32_t *ttl,
|
|
dns_rdata_t **rdata) {
|
|
compound_rrstream_t *s = (compound_rrstream_t *)rs;
|
|
rrstream_t *curstream;
|
|
INSIST(0 <= s->state && s->state < 3);
|
|
INSIST(s->result == ISC_R_SUCCESS);
|
|
curstream = s->components[s->state];
|
|
curstream->methods->current(curstream, name, ttl, rdata);
|
|
}
|
|
|
|
static void
|
|
compound_rrstream_pause(rrstream_t *rs) {
|
|
compound_rrstream_t *s = (compound_rrstream_t *)rs;
|
|
rrstream_t *curstream;
|
|
INSIST(0 <= s->state && s->state < 3);
|
|
curstream = s->components[s->state];
|
|
curstream->methods->pause(curstream);
|
|
}
|
|
|
|
static void
|
|
compound_rrstream_destroy(rrstream_t **rsp) {
|
|
compound_rrstream_t *s = (compound_rrstream_t *)*rsp;
|
|
s->components[0]->methods->destroy(&s->components[0]);
|
|
s->components[1]->methods->destroy(&s->components[1]);
|
|
s->components[2] = NULL; /* Copy of components[0]. */
|
|
isc_mem_putanddetach(&s->common.mctx, s, sizeof(*s));
|
|
}
|
|
|
|
static rrstream_methods_t compound_rrstream_methods = {
|
|
compound_rrstream_first, compound_rrstream_next,
|
|
compound_rrstream_current, compound_rrstream_pause,
|
|
compound_rrstream_destroy
|
|
};
|
|
|
|
/**************************************************************************/
|
|
|
|
/*%
|
|
* Structure holding outgoing transfer statistics
|
|
*/
|
|
struct xfr_stats {
|
|
uint64_t nmsg; /*%< Number of messages sent */
|
|
uint64_t nrecs; /*%< Number of records sent */
|
|
uint64_t nbytes; /*%< Number of bytes sent */
|
|
isc_time_t start; /*%< Start time of the transfer */
|
|
isc_time_t end; /*%< End time of the transfer */
|
|
};
|
|
|
|
/*%
|
|
* An 'xfrout_ctx_t' contains the state of an outgoing AXFR or IXFR
|
|
* in progress.
|
|
*/
|
|
typedef struct {
|
|
isc_mem_t *mctx;
|
|
ns_client_t *client;
|
|
unsigned int id; /* ID of request */
|
|
dns_name_t *qname; /* Question name of request */
|
|
dns_rdatatype_t qtype; /* dns_rdatatype_{a,i}xfr */
|
|
dns_rdataclass_t qclass;
|
|
dns_zone_t *zone; /* (necessary for stats) */
|
|
dns_db_t *db;
|
|
dns_dbversion_t *ver;
|
|
rrstream_t *stream; /* The XFR RR stream */
|
|
bool question_added; /* QUESTION section sent? */
|
|
bool end_of_stream; /* EOS has been reached */
|
|
isc_buffer_t buf; /* Buffer for message owner
|
|
* names and rdatas */
|
|
isc_buffer_t txbuf; /* Transmit message buffer */
|
|
size_t cbytes; /* Length of current message */
|
|
void *txmem;
|
|
unsigned int txmemlen;
|
|
dns_tsigkey_t *tsigkey; /* Key used to create TSIG */
|
|
isc_buffer_t *lasttsig; /* the last TSIG */
|
|
bool verified_tsig; /* verified request MAC */
|
|
bool many_answers;
|
|
int sends; /* Send in progress */
|
|
bool shuttingdown;
|
|
bool poll;
|
|
const char *mnemonic; /* Style of transfer */
|
|
uint32_t end_serial; /* Serial number after XFR is done */
|
|
struct xfr_stats stats; /*%< Transfer statistics */
|
|
|
|
/* Timeouts */
|
|
uint64_t maxtime; /*%< Maximum XFR timeout (in ms) */
|
|
isc_nm_timer_t *maxtime_timer;
|
|
|
|
uint64_t idletime; /*%< XFR idle timeout (in ms) */
|
|
|
|
/* Delayed send */
|
|
isc_nm_timer_t *delayed_send_timer;
|
|
} xfrout_ctx_t;
|
|
|
|
static void
|
|
xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client, unsigned int id,
|
|
dns_name_t *qname, dns_rdatatype_t qtype,
|
|
dns_rdataclass_t qclass, dns_zone_t *zone, dns_db_t *db,
|
|
dns_dbversion_t *ver, rrstream_t *stream,
|
|
dns_tsigkey_t *tsigkey, isc_buffer_t *lasttsig,
|
|
bool verified_tsig, unsigned int maxtime,
|
|
unsigned int idletime, bool many_answers,
|
|
xfrout_ctx_t **xfrp);
|
|
|
|
static void
|
|
sendstream(xfrout_ctx_t *xfr);
|
|
|
|
static void
|
|
xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg);
|
|
|
|
static void
|
|
xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg);
|
|
|
|
static void
|
|
xfrout_maybe_destroy(xfrout_ctx_t *xfr);
|
|
|
|
static void
|
|
xfrout_ctx_destroy(xfrout_ctx_t **xfrp);
|
|
|
|
static void
|
|
xfrout_client_timeout(void *arg, isc_result_t result);
|
|
|
|
static void
|
|
xfrout_log1(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
|
|
int level, const char *fmt, ...) ISC_FORMAT_PRINTF(5, 6);
|
|
|
|
static void
|
|
xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...)
|
|
ISC_FORMAT_PRINTF(3, 4);
|
|
|
|
static void
|
|
xfrout_delayed_timeout(void *arg, isc_result_t result);
|
|
|
|
/**************************************************************************/
|
|
|
|
void
|
|
ns_xfr_start(ns_client_t *client, dns_rdatatype_t reqtype) {
|
|
isc_result_t result;
|
|
dns_name_t *question_name;
|
|
dns_rdataset_t *question_rdataset;
|
|
dns_zone_t *zone = NULL, *raw = NULL, *mayberaw;
|
|
dns_db_t *db = NULL;
|
|
dns_dbversion_t *ver = NULL;
|
|
dns_rdataclass_t question_class;
|
|
rrstream_t *soa_stream = NULL;
|
|
rrstream_t *data_stream = NULL;
|
|
rrstream_t *stream = NULL;
|
|
dns_difftuple_t *current_soa_tuple = NULL;
|
|
dns_name_t *soa_name;
|
|
dns_rdataset_t *soa_rdataset;
|
|
dns_rdata_t soa_rdata = DNS_RDATA_INIT;
|
|
bool have_soa = false;
|
|
const char *mnemonic = NULL;
|
|
isc_mem_t *mctx = client->manager->mctx;
|
|
dns_message_t *request = client->message;
|
|
xfrout_ctx_t *xfr = NULL;
|
|
dns_transfer_format_t format = client->view->transfer_format;
|
|
isc_netaddr_t na;
|
|
dns_peer_t *peer = NULL;
|
|
isc_buffer_t *tsigbuf = NULL;
|
|
char *journalfile;
|
|
char msg[NS_CLIENT_ACLMSGSIZE("zone transfer")];
|
|
char keyname[DNS_NAME_FORMATSIZE];
|
|
bool is_poll = false;
|
|
bool is_dlz = false;
|
|
bool is_ixfr = false;
|
|
bool useviewacl = false;
|
|
uint32_t begin_serial = 0, current_serial;
|
|
|
|
switch (reqtype) {
|
|
case dns_rdatatype_axfr:
|
|
mnemonic = "AXFR";
|
|
break;
|
|
case dns_rdatatype_ixfr:
|
|
mnemonic = "IXFR";
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
}
|
|
|
|
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
|
|
ISC_LOG_DEBUG(6), "%s request", mnemonic);
|
|
/*
|
|
* Apply quota.
|
|
*/
|
|
result = isc_quota_acquire(&client->manager->sctx->xfroutquota);
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_log_write(XFROUT_COMMON_LOGARGS, ISC_LOG_WARNING,
|
|
"%s request denied: %s", mnemonic,
|
|
isc_result_totext(result));
|
|
goto max_quota;
|
|
}
|
|
|
|
/*
|
|
* Interpret the question section.
|
|
*/
|
|
result = dns_message_firstname(request, DNS_SECTION_QUESTION);
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
|
|
/*
|
|
* The question section must contain exactly one question, and
|
|
* it must be for AXFR/IXFR as appropriate.
|
|
*/
|
|
question_name = NULL;
|
|
dns_message_currentname(request, DNS_SECTION_QUESTION, &question_name);
|
|
question_rdataset = ISC_LIST_HEAD(question_name->list);
|
|
question_class = question_rdataset->rdclass;
|
|
INSIST(question_rdataset->type == reqtype);
|
|
if (ISC_LIST_NEXT(question_rdataset, link) != NULL) {
|
|
FAILC(DNS_R_FORMERR, "multiple questions");
|
|
}
|
|
result = dns_message_nextname(request, DNS_SECTION_QUESTION);
|
|
if (result != ISC_R_NOMORE) {
|
|
FAILC(DNS_R_FORMERR, "multiple questions");
|
|
}
|
|
|
|
result = dns_view_findzone(client->view, question_name,
|
|
DNS_ZTFIND_EXACT, &zone);
|
|
if (result != ISC_R_SUCCESS || dns_zone_gettype(zone) == dns_zone_dlz) {
|
|
/*
|
|
* The normal zone table does not have a match, or this is
|
|
* marked in the zone table as a DLZ zone. Check the DLZ
|
|
* databases for a match.
|
|
*/
|
|
if (!ISC_LIST_EMPTY(client->view->dlz_searched)) {
|
|
result = dns_dlzallowzonexfr(client->view,
|
|
question_name,
|
|
&client->peeraddr, &db);
|
|
if (result == ISC_R_DEFAULT) {
|
|
useviewacl = true;
|
|
result = ISC_R_SUCCESS;
|
|
}
|
|
if (result == ISC_R_NOPERM) {
|
|
char _buf1[DNS_NAME_FORMATSIZE];
|
|
char _buf2[DNS_RDATACLASS_FORMATSIZE];
|
|
|
|
result = DNS_R_REFUSED;
|
|
dns_name_format(question_name, _buf1,
|
|
sizeof(_buf1));
|
|
dns_rdataclass_format(question_class, _buf2,
|
|
sizeof(_buf2));
|
|
ns_client_log(client, DNS_LOGCATEGORY_SECURITY,
|
|
NS_LOGMODULE_XFER_OUT,
|
|
ISC_LOG_ERROR,
|
|
"zone transfer '%s/%s' denied",
|
|
_buf1, _buf2);
|
|
goto failure;
|
|
}
|
|
if (result != ISC_R_SUCCESS) {
|
|
FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
|
|
question_name, question_class);
|
|
}
|
|
is_dlz = true;
|
|
} else {
|
|
/*
|
|
* not DLZ and not in normal zone table, we are
|
|
* not authoritative
|
|
*/
|
|
FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
|
|
question_name, question_class);
|
|
}
|
|
} else {
|
|
/* zone table has a match */
|
|
switch (dns_zone_gettype(zone)) {
|
|
/*
|
|
* Primary, secondary, and mirror zones are OK for transfer.
|
|
*/
|
|
case dns_zone_primary:
|
|
case dns_zone_secondary:
|
|
case dns_zone_mirror:
|
|
case dns_zone_dlz:
|
|
break;
|
|
default:
|
|
FAILQ(DNS_R_NOTAUTH, "non-authoritative zone",
|
|
question_name, question_class);
|
|
}
|
|
CHECK(dns_zone_getdb(zone, &db));
|
|
dns_db_currentversion(db, &ver);
|
|
}
|
|
|
|
xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
|
|
"%s question section OK", mnemonic);
|
|
|
|
/*
|
|
* Check the authority section. Look for a SOA record with
|
|
* the same name and class as the question.
|
|
*/
|
|
for (result = dns_message_firstname(request, DNS_SECTION_AUTHORITY);
|
|
result == ISC_R_SUCCESS;
|
|
result = dns_message_nextname(request, DNS_SECTION_AUTHORITY))
|
|
{
|
|
soa_name = NULL;
|
|
dns_message_currentname(request, DNS_SECTION_AUTHORITY,
|
|
&soa_name);
|
|
|
|
/*
|
|
* Ignore data whose owner name is not the zone apex.
|
|
*/
|
|
if (!dns_name_equal(soa_name, question_name)) {
|
|
continue;
|
|
}
|
|
|
|
for (soa_rdataset = ISC_LIST_HEAD(soa_name->list);
|
|
soa_rdataset != NULL;
|
|
soa_rdataset = ISC_LIST_NEXT(soa_rdataset, link))
|
|
{
|
|
/*
|
|
* Ignore non-SOA data.
|
|
*/
|
|
if (soa_rdataset->type != dns_rdatatype_soa) {
|
|
continue;
|
|
}
|
|
if (soa_rdataset->rdclass != question_class) {
|
|
continue;
|
|
}
|
|
|
|
CHECK(dns_rdataset_first(soa_rdataset));
|
|
dns_rdataset_current(soa_rdataset, &soa_rdata);
|
|
result = dns_rdataset_next(soa_rdataset);
|
|
if (result == ISC_R_SUCCESS) {
|
|
FAILC(DNS_R_FORMERR, "IXFR authority section "
|
|
"has multiple SOAs");
|
|
}
|
|
have_soa = true;
|
|
goto got_soa;
|
|
}
|
|
}
|
|
got_soa:
|
|
if (result != ISC_R_NOMORE) {
|
|
CHECK(result);
|
|
}
|
|
|
|
xfrout_log1(client, question_name, question_class, ISC_LOG_DEBUG(6),
|
|
"%s authority section OK", mnemonic);
|
|
|
|
/*
|
|
* If not a DLZ zone or we are falling back to the view's transfer
|
|
* ACL, decide whether to allow this transfer.
|
|
*/
|
|
if (!is_dlz || useviewacl) {
|
|
dns_acl_t *acl;
|
|
|
|
ns_client_aclmsg("zone transfer", question_name, reqtype,
|
|
client->view->rdclass, msg, sizeof(msg));
|
|
if (useviewacl) {
|
|
acl = client->view->transferacl;
|
|
} else {
|
|
acl = dns_zone_getxfracl(zone);
|
|
}
|
|
CHECK(ns_client_checkacl(client, NULL, msg, acl, true,
|
|
ISC_LOG_ERROR));
|
|
}
|
|
|
|
/*
|
|
* AXFR over UDP is not possible.
|
|
*/
|
|
if (reqtype == dns_rdatatype_axfr &&
|
|
(client->attributes & NS_CLIENTATTR_TCP) == 0)
|
|
{
|
|
FAILC(DNS_R_FORMERR, "attempted AXFR over UDP");
|
|
}
|
|
|
|
/*
|
|
* Look up the requesting server in the peer table.
|
|
*/
|
|
isc_netaddr_fromsockaddr(&na, &client->peeraddr);
|
|
(void)dns_peerlist_peerbyaddr(client->view->peers, &na, &peer);
|
|
|
|
/*
|
|
* Decide on the transfer format (one-answer or many-answers).
|
|
*/
|
|
if (peer != NULL) {
|
|
(void)dns_peer_gettransferformat(peer, &format);
|
|
}
|
|
|
|
/*
|
|
* Get a dynamically allocated copy of the current SOA.
|
|
*/
|
|
if (is_dlz) {
|
|
dns_db_currentversion(db, &ver);
|
|
}
|
|
|
|
CHECK(dns_db_createsoatuple(db, ver, mctx, DNS_DIFFOP_EXISTS,
|
|
¤t_soa_tuple));
|
|
|
|
current_serial = dns_soa_getserial(¤t_soa_tuple->rdata);
|
|
if (reqtype == dns_rdatatype_ixfr) {
|
|
size_t jsize;
|
|
uint64_t dbsize;
|
|
|
|
if (!have_soa) {
|
|
FAILC(DNS_R_FORMERR, "IXFR request missing SOA");
|
|
}
|
|
|
|
begin_serial = dns_soa_getserial(&soa_rdata);
|
|
|
|
/*
|
|
* RFC1995 says "If an IXFR query with the same or
|
|
* newer version number than that of the server
|
|
* is received, it is replied to with a single SOA
|
|
* record of the server's current version, just as
|
|
* in AXFR". The claim about AXFR is incorrect,
|
|
* but other than that, we do as the RFC says.
|
|
*
|
|
* Sending a single SOA record is also how we refuse
|
|
* IXFR over UDP (currently, we always do).
|
|
*/
|
|
if (DNS_SERIAL_GE(begin_serial, current_serial) ||
|
|
(client->attributes & NS_CLIENTATTR_TCP) == 0)
|
|
{
|
|
CHECK(soa_rrstream_create(mctx, db, ver, &stream));
|
|
is_poll = true;
|
|
goto have_stream;
|
|
}
|
|
|
|
/*
|
|
* Outgoing IXFR may have been disabled for this peer
|
|
* or globally.
|
|
*/
|
|
if ((client->attributes & NS_CLIENTATTR_TCP) != 0) {
|
|
bool provide_ixfr;
|
|
|
|
provide_ixfr = client->view->provideixfr;
|
|
if (peer != NULL) {
|
|
(void)dns_peer_getprovideixfr(peer,
|
|
&provide_ixfr);
|
|
}
|
|
if (!provide_ixfr) {
|
|
xfrout_log1(client, question_name,
|
|
question_class, ISC_LOG_DEBUG(4),
|
|
"IXFR delta response disabled due "
|
|
"to 'provide-ixfr no;' being set");
|
|
mnemonic = "AXFR-style IXFR";
|
|
goto axfr_fallback;
|
|
}
|
|
}
|
|
|
|
journalfile = is_dlz ? NULL : dns_zone_getjournal(zone);
|
|
if (journalfile != NULL) {
|
|
result = ixfr_rrstream_create(
|
|
mctx, journalfile, begin_serial, current_serial,
|
|
&jsize, &data_stream);
|
|
} else {
|
|
result = ISC_R_NOTFOUND;
|
|
}
|
|
if (result == ISC_R_NOTFOUND || result == ISC_R_RANGE) {
|
|
xfrout_log1(client, question_name, question_class,
|
|
ISC_LOG_INFO,
|
|
"IXFR version not in journal, "
|
|
"falling back to AXFR");
|
|
mnemonic = "AXFR-style IXFR";
|
|
goto axfr_fallback;
|
|
}
|
|
CHECK(result);
|
|
|
|
result = dns_db_getsize(db, ver, NULL, &dbsize);
|
|
if (result == ISC_R_SUCCESS) {
|
|
uint32_t ratio = dns_zone_getixfrratio(zone);
|
|
if (ratio != 0 && ((100 * jsize) / dbsize) > ratio) {
|
|
data_stream->methods->destroy(&data_stream);
|
|
data_stream = NULL;
|
|
xfrout_log1(client, question_name,
|
|
question_class, ISC_LOG_INFO,
|
|
"IXFR delta size (%zu bytes) "
|
|
"exceeds the maximum ratio to "
|
|
"database size "
|
|
"(%" PRIu64 " bytes), "
|
|
"falling back to AXFR",
|
|
jsize, dbsize);
|
|
mnemonic = "AXFR-style IXFR";
|
|
goto axfr_fallback;
|
|
} else {
|
|
xfrout_log1(client, question_name,
|
|
question_class, ISC_LOG_DEBUG(4),
|
|
"IXFR delta size (%zu bytes); "
|
|
"database size "
|
|
"(%" PRIu64 " bytes)",
|
|
jsize, dbsize);
|
|
}
|
|
}
|
|
is_ixfr = true;
|
|
} else {
|
|
axfr_fallback:
|
|
CHECK(axfr_rrstream_create(mctx, db, ver, &data_stream));
|
|
}
|
|
|
|
/*
|
|
* Bracket the data stream with SOAs.
|
|
*/
|
|
CHECK(soa_rrstream_create(mctx, db, ver, &soa_stream));
|
|
CHECK(compound_rrstream_create(mctx, &soa_stream, &data_stream,
|
|
&stream));
|
|
soa_stream = NULL;
|
|
data_stream = NULL;
|
|
|
|
have_stream:
|
|
CHECK(dns_message_getquerytsig(request, mctx, &tsigbuf));
|
|
/*
|
|
* Create the xfrout context object. This transfers the ownership
|
|
* of "stream", "db", "ver", and "quota" to the xfrout context object.
|
|
*/
|
|
|
|
if (is_dlz) {
|
|
xfrout_ctx_create(mctx, client, request->id, question_name,
|
|
reqtype, question_class, zone, db, ver,
|
|
stream, dns_message_gettsigkey(request),
|
|
tsigbuf, request->verified_sig, 3600, 3600,
|
|
(format == dns_many_answers) ? true : false,
|
|
&xfr);
|
|
} else {
|
|
xfrout_ctx_create(
|
|
mctx, client, request->id, question_name, reqtype,
|
|
question_class, zone, db, ver, stream,
|
|
dns_message_gettsigkey(request), tsigbuf,
|
|
request->verified_sig, dns_zone_getmaxxfrout(zone),
|
|
dns_zone_getidleout(zone),
|
|
(format == dns_many_answers) ? true : false, &xfr);
|
|
}
|
|
|
|
xfr->end_serial = current_serial;
|
|
xfr->mnemonic = mnemonic;
|
|
stream = NULL;
|
|
|
|
CHECK(xfr->stream->methods->first(xfr->stream));
|
|
|
|
if (xfr->tsigkey != NULL) {
|
|
dns_name_format(xfr->tsigkey->name, keyname, sizeof(keyname));
|
|
} else {
|
|
keyname[0] = '\0';
|
|
}
|
|
xfr->poll = is_poll;
|
|
if (is_poll) {
|
|
xfr->mnemonic = "IXFR poll response";
|
|
xfrout_log1(client, question_name, question_class,
|
|
ISC_LOG_DEBUG(1), "IXFR poll up to date%s%s",
|
|
(xfr->tsigkey != NULL) ? ": TSIG " : "", keyname);
|
|
} else if (is_ixfr) {
|
|
xfrout_log1(client, question_name, question_class, ISC_LOG_INFO,
|
|
"%s started%s%s (serial %u -> %u)", mnemonic,
|
|
(xfr->tsigkey != NULL) ? ": TSIG " : "", keyname,
|
|
begin_serial, current_serial);
|
|
} else {
|
|
xfrout_log1(client, question_name, question_class, ISC_LOG_INFO,
|
|
"%s started%s%s (serial %u)", mnemonic,
|
|
(xfr->tsigkey != NULL) ? ": TSIG " : "", keyname,
|
|
current_serial);
|
|
}
|
|
|
|
if (zone != NULL) {
|
|
dns_zone_getraw(zone, &raw);
|
|
mayberaw = (raw != NULL) ? raw : zone;
|
|
if ((client->attributes & NS_CLIENTATTR_WANTEXPIRE) != 0 &&
|
|
(dns_zone_gettype(mayberaw) == dns_zone_secondary ||
|
|
dns_zone_gettype(mayberaw) == dns_zone_mirror))
|
|
{
|
|
isc_time_t expiretime;
|
|
uint32_t secs;
|
|
dns_zone_getexpiretime(zone, &expiretime);
|
|
secs = isc_time_seconds(&expiretime);
|
|
if (secs >= client->now && result == ISC_R_SUCCESS) {
|
|
client->attributes |= NS_CLIENTATTR_HAVEEXPIRE;
|
|
client->expire = secs - client->now;
|
|
}
|
|
}
|
|
if (raw != NULL) {
|
|
dns_zone_detach(&raw);
|
|
}
|
|
}
|
|
|
|
/* Start the timers */
|
|
if (xfr->maxtime > 0) {
|
|
xfrout_log(xfr, ISC_LOG_DEBUG(1),
|
|
"starting maxtime timer %" PRIu64 " ms",
|
|
xfr->maxtime);
|
|
isc_nm_timer_start(xfr->maxtime_timer, xfr->maxtime);
|
|
}
|
|
|
|
/*
|
|
* Hand the context over to sendstream(). Set xfr to NULL;
|
|
* sendstream() is responsible for either passing the
|
|
* context on to a later event handler or destroying it.
|
|
*/
|
|
sendstream(xfr);
|
|
xfr = NULL;
|
|
|
|
result = ISC_R_SUCCESS;
|
|
|
|
failure:
|
|
if (result == DNS_R_REFUSED) {
|
|
inc_stats(client, zone, ns_statscounter_xfrrej);
|
|
}
|
|
if (current_soa_tuple != NULL) {
|
|
dns_difftuple_free(¤t_soa_tuple);
|
|
}
|
|
if (stream != NULL) {
|
|
stream->methods->destroy(&stream);
|
|
}
|
|
if (soa_stream != NULL) {
|
|
soa_stream->methods->destroy(&soa_stream);
|
|
}
|
|
if (data_stream != NULL) {
|
|
data_stream->methods->destroy(&data_stream);
|
|
}
|
|
if (ver != NULL) {
|
|
dns_db_closeversion(db, &ver, false);
|
|
}
|
|
if (db != NULL) {
|
|
dns_db_detach(&db);
|
|
}
|
|
if (zone != NULL) {
|
|
dns_zone_detach(&zone);
|
|
}
|
|
|
|
if (xfr != NULL) {
|
|
xfrout_fail(xfr, result, "setting up zone transfer");
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
isc_quota_release(&client->manager->sctx->xfroutquota);
|
|
max_quota:
|
|
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
|
|
NS_LOGMODULE_XFER_OUT, ISC_LOG_DEBUG(3),
|
|
"zone transfer setup failed");
|
|
ns_client_error(client, result);
|
|
isc_nmhandle_detach(&client->reqhandle);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xfrout_ctx_create(isc_mem_t *mctx, ns_client_t *client, unsigned int id,
|
|
dns_name_t *qname, dns_rdatatype_t qtype,
|
|
dns_rdataclass_t qclass, dns_zone_t *zone, dns_db_t *db,
|
|
dns_dbversion_t *ver, rrstream_t *stream,
|
|
dns_tsigkey_t *tsigkey, isc_buffer_t *lasttsig,
|
|
bool verified_tsig, unsigned int maxtime,
|
|
unsigned int idletime, bool many_answers,
|
|
xfrout_ctx_t **xfrp) {
|
|
xfrout_ctx_t *xfr = NULL;
|
|
unsigned int len = NS_CLIENT_TCP_BUFFER_SIZE;
|
|
void *mem = NULL;
|
|
|
|
REQUIRE(xfrp != NULL && *xfrp == NULL);
|
|
|
|
xfr = isc_mem_get(mctx, sizeof(*xfr));
|
|
*xfr = (xfrout_ctx_t){
|
|
.client = client,
|
|
.id = id,
|
|
.qname = qname,
|
|
.qtype = qtype,
|
|
.qclass = qclass,
|
|
.maxtime = maxtime * 1000, /* in milliseconds */
|
|
.idletime = idletime * 1000, /* In milliseconds */
|
|
.tsigkey = tsigkey,
|
|
.lasttsig = lasttsig,
|
|
.verified_tsig = verified_tsig,
|
|
.many_answers = many_answers,
|
|
};
|
|
|
|
isc_mem_attach(mctx, &xfr->mctx);
|
|
|
|
if (zone != NULL) { /* zone will be NULL if it's DLZ */
|
|
dns_zone_attach(zone, &xfr->zone);
|
|
}
|
|
dns_db_attach(db, &xfr->db);
|
|
dns_db_attachversion(db, ver, &xfr->ver);
|
|
|
|
xfr->stats.start = isc_time_now();
|
|
|
|
isc_nm_timer_create(xfr->client->handle, xfrout_client_timeout, xfr,
|
|
&xfr->maxtime_timer);
|
|
|
|
isc_nm_timer_create(xfr->client->handle, xfrout_delayed_timeout, xfr,
|
|
&xfr->delayed_send_timer);
|
|
|
|
/*
|
|
* Allocate a temporary buffer for the uncompressed response
|
|
* message data. The buffer size must be 65535 bytes
|
|
* (NS_CLIENT_TCP_BUFFER_SIZE): small enough that compressed
|
|
* data will fit in a single TCP message, and big enough to
|
|
* hold a maximum-sized RR.
|
|
*
|
|
* Note that although 65535-byte RRs are allowed in principle, they
|
|
* cannot be zone-transferred (at least not if uncompressible),
|
|
* because the message and RR headers would push the size of the
|
|
* TCP message over the 65535 byte limit.
|
|
*/
|
|
mem = isc_mem_get(mctx, len);
|
|
isc_buffer_init(&xfr->buf, mem, len);
|
|
|
|
/*
|
|
* Allocate another temporary buffer for the compressed
|
|
* response message.
|
|
*/
|
|
mem = isc_mem_get(mctx, len);
|
|
isc_buffer_init(&xfr->txbuf, (char *)mem, len);
|
|
xfr->txmem = mem;
|
|
xfr->txmemlen = len;
|
|
|
|
/*
|
|
* These MUST be after the last "goto failure;" / CHECK to
|
|
* prevent a double free by the caller.
|
|
*/
|
|
xfr->stream = stream;
|
|
|
|
*xfrp = xfr;
|
|
}
|
|
|
|
static void
|
|
xfrout_send(xfrout_ctx_t *xfr) {
|
|
const bool is_tcp = ((xfr->client->attributes & NS_CLIENTATTR_TCP) !=
|
|
0);
|
|
|
|
if (is_tcp) {
|
|
isc_region_t used;
|
|
|
|
isc_buffer_usedregion(&xfr->txbuf, &used);
|
|
|
|
isc_nmhandle_attach(xfr->client->handle,
|
|
&xfr->client->sendhandle);
|
|
if (xfr->idletime > 0) {
|
|
isc_nmhandle_setwritetimeout(xfr->client->sendhandle,
|
|
xfr->idletime);
|
|
}
|
|
isc_nm_send(xfr->client->sendhandle, &used, xfrout_senddone,
|
|
xfr);
|
|
xfr->sends++;
|
|
xfr->cbytes = used.length;
|
|
} else {
|
|
ns_client_send(xfr->client);
|
|
xfr->stream->methods->pause(xfr->stream);
|
|
isc_nmhandle_detach(&xfr->client->reqhandle);
|
|
xfrout_ctx_destroy(&xfr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xfrout_delayed_timeout(void *arg, isc_result_t result) {
|
|
xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg;
|
|
UNUSED(result);
|
|
|
|
isc_nm_timer_stop(xfr->delayed_send_timer);
|
|
xfrout_send(xfr);
|
|
}
|
|
|
|
static void
|
|
xfrout_enqueue_send(xfrout_ctx_t *xfr) {
|
|
uint64_t timeout = 0;
|
|
|
|
/*
|
|
* System test helper options to simulate network issues.
|
|
*
|
|
* Both "transferslowly" and "transferstuck" are not meant to be
|
|
* used together (and are not actually used this way).
|
|
*/
|
|
if (ns_server_getoption(xfr->client->manager->sctx,
|
|
NS_SERVER_TRANSFERSLOWLY))
|
|
{
|
|
/* Sleep for a bit over a second. */
|
|
timeout = 1000;
|
|
} else if (ns_server_getoption(xfr->client->manager->sctx,
|
|
NS_SERVER_TRANSFERSTUCK))
|
|
{
|
|
/* Sleep for a bit over a minute. */
|
|
timeout = 60 * 1000;
|
|
}
|
|
|
|
if (timeout == 0) {
|
|
xfrout_send(xfr);
|
|
return;
|
|
}
|
|
|
|
/* delay */
|
|
isc_nm_timer_start(xfr->delayed_send_timer, timeout);
|
|
}
|
|
|
|
/*
|
|
* Arrange to send as much as we can of "stream" without blocking.
|
|
*
|
|
* Requires:
|
|
* The stream iterator is initialized and points at an RR,
|
|
* or possibly at the end of the stream (that is, the
|
|
* _first method of the iterator has been called).
|
|
*/
|
|
static void
|
|
sendstream(xfrout_ctx_t *xfr) {
|
|
dns_message_t *tcpmsg = NULL;
|
|
dns_message_t *msg = NULL; /* Client message if UDP, tcpmsg if TCP */
|
|
isc_result_t result;
|
|
dns_rdataset_t *qrdataset;
|
|
dns_name_t *msgname = NULL;
|
|
dns_rdata_t *msgrdata = NULL;
|
|
dns_rdatalist_t *msgrdl = NULL;
|
|
dns_rdataset_t *msgrds = NULL;
|
|
dns_compress_t cctx;
|
|
bool cleanup_cctx = false;
|
|
bool is_tcp;
|
|
int n_rrs;
|
|
|
|
isc_buffer_clear(&xfr->buf);
|
|
isc_buffer_clear(&xfr->txbuf);
|
|
|
|
is_tcp = ((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0);
|
|
if (!is_tcp) {
|
|
/*
|
|
* In the UDP case, we put the response data directly into
|
|
* the client message.
|
|
*/
|
|
msg = xfr->client->message;
|
|
CHECK(dns_message_reply(msg, true));
|
|
} else {
|
|
/*
|
|
* TCP. Build a response dns_message_t, temporarily storing
|
|
* the raw, uncompressed owner names and RR data contiguously
|
|
* in xfr->buf. We know that if the uncompressed data fits
|
|
* in xfr->buf, the compressed data will surely fit in a TCP
|
|
* message.
|
|
*/
|
|
|
|
dns_message_create(xfr->mctx, NULL, NULL,
|
|
DNS_MESSAGE_INTENTRENDER, &tcpmsg);
|
|
msg = tcpmsg;
|
|
|
|
msg->id = xfr->id;
|
|
msg->rcode = dns_rcode_noerror;
|
|
msg->flags = DNS_MESSAGEFLAG_QR | DNS_MESSAGEFLAG_AA;
|
|
if ((xfr->client->attributes & NS_CLIENTATTR_RA) != 0) {
|
|
msg->flags |= DNS_MESSAGEFLAG_RA;
|
|
}
|
|
CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
|
|
dns_message_setquerytsig(msg, xfr->lasttsig);
|
|
if (xfr->lasttsig != NULL) {
|
|
isc_buffer_free(&xfr->lasttsig);
|
|
}
|
|
msg->verified_sig = xfr->verified_tsig;
|
|
|
|
/*
|
|
* Add a EDNS option to the message?
|
|
*/
|
|
if ((xfr->client->attributes & NS_CLIENTATTR_WANTOPT) != 0) {
|
|
dns_rdataset_t *opt = NULL;
|
|
|
|
CHECK(ns_client_addopt(xfr->client, msg, &opt));
|
|
CHECK(dns_message_setopt(msg, opt));
|
|
/*
|
|
* Add to first message only.
|
|
*/
|
|
xfr->client->attributes &= ~NS_CLIENTATTR_WANTNSID;
|
|
xfr->client->attributes &= ~NS_CLIENTATTR_HAVEEXPIRE;
|
|
}
|
|
|
|
/*
|
|
* Account for reserved space.
|
|
*/
|
|
if (xfr->tsigkey != NULL) {
|
|
INSIST(msg->reserved != 0U);
|
|
}
|
|
isc_buffer_add(&xfr->buf, msg->reserved);
|
|
|
|
/*
|
|
* Include a question section in the first message only.
|
|
* BIND 8.2.1 will not recognize an IXFR if it does not
|
|
* have a question section.
|
|
*/
|
|
if (!xfr->question_added) {
|
|
dns_name_t *qname = NULL;
|
|
isc_region_t r;
|
|
|
|
/*
|
|
* Reserve space for the 12-byte message header
|
|
* and 4 bytes of question.
|
|
*/
|
|
isc_buffer_add(&xfr->buf, 12 + 4);
|
|
|
|
qrdataset = NULL;
|
|
dns_message_gettemprdataset(msg, &qrdataset);
|
|
dns_rdataset_makequestion(qrdataset,
|
|
xfr->client->message->rdclass,
|
|
xfr->qtype);
|
|
|
|
dns_message_gettempname(msg, &qname);
|
|
isc_buffer_availableregion(&xfr->buf, &r);
|
|
INSIST(r.length >= xfr->qname->length);
|
|
r.length = xfr->qname->length;
|
|
isc_buffer_putmem(&xfr->buf, xfr->qname->ndata,
|
|
xfr->qname->length);
|
|
dns_name_fromregion(qname, &r);
|
|
ISC_LIST_INIT(qname->list);
|
|
ISC_LIST_APPEND(qname->list, qrdataset, link);
|
|
|
|
dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
|
|
xfr->question_added = true;
|
|
} else {
|
|
/*
|
|
* Reserve space for the 12-byte message header
|
|
*/
|
|
isc_buffer_add(&xfr->buf, 12);
|
|
msg->tcp_continuation = 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Try to fit in as many RRs as possible, unless "one-answer"
|
|
* format has been requested.
|
|
*/
|
|
for (n_rrs = 0;; n_rrs++) {
|
|
dns_name_t *name = NULL;
|
|
uint32_t ttl;
|
|
dns_rdata_t *rdata = NULL;
|
|
|
|
unsigned int size;
|
|
isc_region_t r;
|
|
|
|
msgname = NULL;
|
|
msgrdata = NULL;
|
|
msgrdl = NULL;
|
|
msgrds = NULL;
|
|
|
|
xfr->stream->methods->current(xfr->stream, &name, &ttl, &rdata);
|
|
size = name->length + 10 + rdata->length;
|
|
isc_buffer_availableregion(&xfr->buf, &r);
|
|
if (size >= r.length) {
|
|
/*
|
|
* RR would not fit. If there are other RRs in the
|
|
* buffer, send them now and leave this RR to the
|
|
* next message. If this RR overflows the buffer
|
|
* all by itself, fail.
|
|
*
|
|
* In theory some RRs might fit in a TCP message
|
|
* when compressed even if they do not fit when
|
|
* uncompressed, but surely we don't want
|
|
* to send such monstrosities to an unsuspecting
|
|
* secondary.
|
|
*/
|
|
if (n_rrs == 0) {
|
|
xfrout_log(xfr, ISC_LOG_WARNING,
|
|
"RR too large for zone transfer "
|
|
"(%d bytes)",
|
|
size);
|
|
/* XXX DNS_R_RRTOOLARGE? */
|
|
result = ISC_R_NOSPACE;
|
|
goto failure;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (isc_log_wouldlog(ns_lctx, XFROUT_RR_LOGLEVEL)) {
|
|
log_rr(name, rdata, ttl); /* XXX */
|
|
}
|
|
|
|
dns_message_gettempname(msg, &msgname);
|
|
isc_buffer_availableregion(&xfr->buf, &r);
|
|
INSIST(r.length >= name->length);
|
|
r.length = name->length;
|
|
isc_buffer_putmem(&xfr->buf, name->ndata, name->length);
|
|
dns_name_fromregion(msgname, &r);
|
|
|
|
/* Reserve space for RR header. */
|
|
isc_buffer_add(&xfr->buf, 10);
|
|
|
|
dns_message_gettemprdata(msg, &msgrdata);
|
|
isc_buffer_availableregion(&xfr->buf, &r);
|
|
r.length = rdata->length;
|
|
isc_buffer_putmem(&xfr->buf, rdata->data, rdata->length);
|
|
dns_rdata_init(msgrdata);
|
|
dns_rdata_fromregion(msgrdata, rdata->rdclass, rdata->type, &r);
|
|
|
|
dns_message_gettemprdatalist(msg, &msgrdl);
|
|
msgrdl->type = rdata->type;
|
|
msgrdl->rdclass = rdata->rdclass;
|
|
msgrdl->ttl = ttl;
|
|
if (rdata->type == dns_rdatatype_sig ||
|
|
rdata->type == dns_rdatatype_rrsig)
|
|
{
|
|
msgrdl->covers = dns_rdata_covers(rdata);
|
|
} else {
|
|
msgrdl->covers = dns_rdatatype_none;
|
|
}
|
|
ISC_LIST_APPEND(msgrdl->rdata, msgrdata, link);
|
|
|
|
dns_message_gettemprdataset(msg, &msgrds);
|
|
dns_rdatalist_tordataset(msgrdl, msgrds);
|
|
|
|
ISC_LIST_APPEND(msgname->list, msgrds, link);
|
|
|
|
dns_message_addname(msg, msgname, DNS_SECTION_ANSWER);
|
|
msgname = NULL;
|
|
|
|
xfr->stats.nrecs++;
|
|
|
|
result = xfr->stream->methods->next(xfr->stream);
|
|
if (result == ISC_R_NOMORE) {
|
|
xfr->end_of_stream = true;
|
|
break;
|
|
}
|
|
CHECK(result);
|
|
|
|
if (!xfr->many_answers) {
|
|
break;
|
|
}
|
|
/*
|
|
* At this stage, at least 1 RR has been rendered into
|
|
* the message. Check if we want to clamp this message
|
|
* here (TCP only).
|
|
*/
|
|
if ((isc_buffer_usedlength(&xfr->buf) >=
|
|
xfr->client->manager->sctx->transfer_tcp_message_size) &&
|
|
is_tcp)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_tcp) {
|
|
dns_compress_init(&cctx, xfr->mctx,
|
|
DNS_COMPRESS_CASE | DNS_COMPRESS_LARGE);
|
|
cleanup_cctx = true;
|
|
CHECK(dns_message_renderbegin(msg, &cctx, &xfr->txbuf));
|
|
CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
|
|
CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
|
|
CHECK(dns_message_renderend(msg));
|
|
dns_compress_invalidate(&cctx);
|
|
cleanup_cctx = false;
|
|
|
|
xfrout_log(xfr, ISC_LOG_DEBUG(8),
|
|
"sending TCP message of %d bytes",
|
|
isc_buffer_usedlength(&xfr->txbuf));
|
|
|
|
xfrout_enqueue_send(xfr);
|
|
} else {
|
|
xfrout_log(xfr, ISC_LOG_DEBUG(8), "sending IXFR UDP response");
|
|
|
|
xfrout_enqueue_send(xfr);
|
|
return;
|
|
}
|
|
|
|
/* Advance lasttsig to be the last TSIG generated */
|
|
CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
|
|
|
|
failure:
|
|
if (tcpmsg != NULL) {
|
|
dns_message_detach(&tcpmsg);
|
|
}
|
|
|
|
if (cleanup_cctx) {
|
|
dns_compress_invalidate(&cctx);
|
|
}
|
|
/*
|
|
* Make sure to release any locks held by database
|
|
* iterators before returning from the event handler.
|
|
*/
|
|
xfr->stream->methods->pause(xfr->stream);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
return;
|
|
}
|
|
|
|
xfrout_fail(xfr, result, "sending zone data");
|
|
}
|
|
|
|
static void
|
|
xfrout_ctx_destroy(xfrout_ctx_t **xfrp) {
|
|
xfrout_ctx_t *xfr = *xfrp;
|
|
*xfrp = NULL;
|
|
|
|
INSIST(xfr->sends == 0);
|
|
|
|
isc_nm_timer_stop(xfr->delayed_send_timer);
|
|
isc_nm_timer_detach(&xfr->delayed_send_timer);
|
|
|
|
isc_nm_timer_stop(xfr->maxtime_timer);
|
|
isc_nm_timer_detach(&xfr->maxtime_timer);
|
|
|
|
if (xfr->stream != NULL) {
|
|
xfr->stream->methods->destroy(&xfr->stream);
|
|
}
|
|
if (xfr->buf.base != NULL) {
|
|
isc_mem_put(xfr->mctx, xfr->buf.base, xfr->buf.length);
|
|
}
|
|
if (xfr->txmem != NULL) {
|
|
isc_mem_put(xfr->mctx, xfr->txmem, xfr->txmemlen);
|
|
}
|
|
if (xfr->lasttsig != NULL) {
|
|
isc_buffer_free(&xfr->lasttsig);
|
|
}
|
|
|
|
isc_quota_release(&xfr->client->manager->sctx->xfroutquota);
|
|
|
|
if (xfr->ver != NULL) {
|
|
dns_db_closeversion(xfr->db, &xfr->ver, false);
|
|
}
|
|
if (xfr->zone != NULL) {
|
|
dns_zone_detach(&xfr->zone);
|
|
}
|
|
if (xfr->db != NULL) {
|
|
dns_db_detach(&xfr->db);
|
|
}
|
|
|
|
isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
|
|
}
|
|
|
|
static void
|
|
xfrout_senddone(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
|
|
xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg;
|
|
|
|
REQUIRE((xfr->client->attributes & NS_CLIENTATTR_TCP) != 0);
|
|
|
|
INSIST(handle == xfr->client->handle);
|
|
|
|
xfr->sends--;
|
|
INSIST(xfr->sends == 0);
|
|
|
|
isc_nmhandle_detach(&xfr->client->sendhandle);
|
|
|
|
/*
|
|
* Update transfer statistics if sending succeeded, accounting for the
|
|
* two-byte TCP length prefix included in the number of bytes sent.
|
|
*/
|
|
if (result == ISC_R_SUCCESS) {
|
|
xfr->stats.nmsg++;
|
|
xfr->stats.nbytes += xfr->cbytes;
|
|
}
|
|
|
|
if (xfr->shuttingdown) {
|
|
xfrout_maybe_destroy(xfr);
|
|
} else if (result != ISC_R_SUCCESS) {
|
|
xfrout_fail(xfr, result, "send");
|
|
} else if (!xfr->end_of_stream) {
|
|
sendstream(xfr);
|
|
} else {
|
|
/* End of zone transfer stream. */
|
|
uint64_t msecs, persec;
|
|
|
|
inc_stats(xfr->client, xfr->zone, ns_statscounter_xfrdone);
|
|
xfr->stats.end = isc_time_now();
|
|
msecs = isc_time_microdiff(&xfr->stats.end, &xfr->stats.start);
|
|
msecs /= 1000;
|
|
if (msecs == 0) {
|
|
msecs = 1;
|
|
}
|
|
persec = (xfr->stats.nbytes * 1000) / msecs;
|
|
xfrout_log(xfr, xfr->poll ? ISC_LOG_DEBUG(1) : ISC_LOG_INFO,
|
|
"%s ended: "
|
|
"%" PRIu64 " messages, %" PRIu64 " records, "
|
|
"%" PRIu64 " bytes, "
|
|
"%u.%03u secs (%u bytes/sec) (serial %u)",
|
|
xfr->mnemonic, xfr->stats.nmsg, xfr->stats.nrecs,
|
|
xfr->stats.nbytes, (unsigned int)(msecs / 1000),
|
|
(unsigned int)(msecs % 1000), (unsigned int)persec,
|
|
xfr->end_serial);
|
|
|
|
/*
|
|
* We're done, unreference the handle and destroy the xfr
|
|
* context.
|
|
*/
|
|
isc_nmhandle_detach(&xfr->client->reqhandle);
|
|
xfrout_ctx_destroy(&xfr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
xfrout_fail(xfrout_ctx_t *xfr, isc_result_t result, const char *msg) {
|
|
xfr->shuttingdown = true;
|
|
xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
|
|
isc_result_totext(result));
|
|
xfrout_maybe_destroy(xfr);
|
|
}
|
|
|
|
static void
|
|
xfrout_maybe_destroy(xfrout_ctx_t *xfr) {
|
|
REQUIRE(xfr->shuttingdown);
|
|
|
|
ns_client_drop(xfr->client, ISC_R_CANCELED);
|
|
isc_nmhandle_detach(&xfr->client->reqhandle);
|
|
xfrout_ctx_destroy(&xfr);
|
|
}
|
|
|
|
static void
|
|
xfrout_client_timeout(void *arg, isc_result_t result) {
|
|
xfrout_ctx_t *xfr = (xfrout_ctx_t *)arg;
|
|
|
|
xfr->shuttingdown = true;
|
|
xfrout_log(xfr, ISC_LOG_ERROR, "%s: %s", "aborted",
|
|
isc_result_totext(result));
|
|
}
|
|
|
|
/*
|
|
* Log outgoing zone transfer messages in a format like
|
|
* <client>: transfer of <zone>: <message>
|
|
*/
|
|
|
|
static void
|
|
xfrout_logv(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
|
|
int level, const char *fmt, va_list ap) ISC_FORMAT_PRINTF(5, 0);
|
|
|
|
static void
|
|
xfrout_logv(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
|
|
int level, const char *fmt, va_list ap) {
|
|
char msgbuf[2048];
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
char classbuf[DNS_RDATACLASS_FORMATSIZE];
|
|
|
|
dns_name_format(zonename, namebuf, sizeof(namebuf));
|
|
dns_rdataclass_format(rdclass, classbuf, sizeof(classbuf));
|
|
vsnprintf(msgbuf, sizeof(msgbuf), fmt, ap);
|
|
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
|
|
level, "transfer of '%s/%s': %s", namebuf, classbuf,
|
|
msgbuf);
|
|
}
|
|
|
|
/*
|
|
* Logging function for use when a xfrout_ctx_t has not yet been created.
|
|
*/
|
|
static void
|
|
xfrout_log1(ns_client_t *client, dns_name_t *zonename, dns_rdataclass_t rdclass,
|
|
int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
xfrout_logv(client, zonename, rdclass, level, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/*
|
|
* Logging function for use when there is a xfrout_ctx_t.
|
|
*/
|
|
static void
|
|
xfrout_log(xfrout_ctx_t *xfr, int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
xfrout_logv(xfr->client, xfr->qname, xfr->qclass, level, fmt, ap);
|
|
va_end(ap);
|
|
}
|