2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-23 10:39:16 +00:00
bind/lib/ns/xfrout.c
Ondřej Surý 091d738c72 Convert all categories and modules into static lists
Remove the complicated mechanism that could be (in theory) used by
external libraries to register new categories and modules with
statically defined lists in <isc/log.h>.  This is similar to what we
have done for <isc/result.h> result codes.  All the libraries are now
internal to BIND 9, so we don't need to provide a mechanism to register
extra categories and modules.
2024-08-20 12:50:39 +00:00

1817 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/log.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/server.h>
#include <ns/stats.h>
#include <ns/xfrout.h>
/*! \file
* \brief
* Outgoing AXFR and IXFR.
*/
/*
* TODO:
* - IXFR over UDP
*/
#define XFROUT_PROTOCOL_LOGARGS \
DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT, ISC_LOG_INFO
#define XFROUT_DEBUG_LOGARGS(n) \
DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT, ISC_LOG_DEBUG(n)
#define XFROUT_RR_LOGARGS \
DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT, 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(DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
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,
&current_soa_tuple));
current_serial = dns_soa_getserial(&current_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(&current_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(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);
}