1
0
Fork 0
bind9/lib/dns/xfrin.c
Daniel Baumann f66ff7eae6
Adding upstream version 1:9.20.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:32:37 +02:00

2249 lines
56 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.
*/
/*! \file */
#include <inttypes.h>
#include <stdbool.h>
#include <isc/async.h>
#include <isc/atomic.h>
#include <isc/mem.h>
#include <isc/random.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.h>
#include <isc/work.h>
#include <dns/callbacks.h>
#include <dns/catz.h>
#include <dns/db.h>
#include <dns/diff.h>
#include <dns/dispatch.h>
#include <dns/journal.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/peer.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/soa.h>
#include <dns/trace.h>
#include <dns/transport.h>
#include <dns/tsig.h>
#include <dns/view.h>
#include <dns/xfrin.h>
#include <dns/zone.h>
#include <dst/dst.h>
#include "probes.h"
/*
* Incoming AXFR and IXFR.
*/
#define CHECK(op) \
{ \
result = (op); \
if (result != ISC_R_SUCCESS) { \
goto failure; \
} \
}
/*%
* The states of the *XFR state machine. We handle both IXFR and AXFR
* with a single integrated state machine because they cannot be distinguished
* immediately - an AXFR response to an IXFR request can only be detected
* when the first two (2) response RRs have already been received.
*/
typedef enum {
XFRST_SOAQUERY,
XFRST_GOTSOA,
XFRST_ZONEXFRREQUEST,
XFRST_FIRSTDATA,
XFRST_IXFR_DELSOA,
XFRST_IXFR_DEL,
XFRST_IXFR_ADDSOA,
XFRST_IXFR_ADD,
XFRST_IXFR_END,
XFRST_AXFR,
XFRST_AXFR_END
} xfrin_state_t;
/*%
* Incoming zone transfer context.
*/
struct dns_xfrin {
unsigned int magic;
isc_mem_t *mctx;
dns_zone_t *zone;
dns_view_t *view;
isc_refcount_t references;
atomic_bool shuttingdown;
isc_result_t shutdown_result;
dns_name_t name; /*%< Name of zone to transfer */
dns_rdataclass_t rdclass;
dns_messageid_t id;
/*%
* Requested transfer type (dns_rdatatype_axfr or
* dns_rdatatype_ixfr). The actual transfer type
* may differ due to IXFR->AXFR fallback.
*/
dns_rdatatype_t reqtype;
isc_sockaddr_t primaryaddr;
isc_sockaddr_t sourceaddr;
dns_dispatch_t *disp;
dns_dispentry_t *dispentry;
/*% Buffer for IXFR/AXFR request message */
isc_buffer_t qbuffer;
unsigned char qbuffer_data[512];
/*%
* Whether the zone originally had a database attached at the time this
* transfer context was created. Used by xfrin_destroy() when making
* logging decisions.
*/
bool zone_had_db;
dns_db_t *db;
dns_dbversion_t *ver;
dns_diff_t diff; /*%< Pending database changes */
/* Diff queue */
bool diff_running;
struct __cds_wfcq_head diff_head;
struct cds_wfcq_tail diff_tail;
_Atomic xfrin_state_t state;
uint32_t expireopt;
bool edns, expireoptset;
atomic_bool is_ixfr;
/*
* Following variable were made atomic only for loading the values for
* the statistics channel, thus all accesses can be **relaxed** because
* all store and load operations that affect XFR are done on the same
* thread and only the statistics channel thread could perform a load
* operation from a different thread and it's ok to not be precise in
* the statistics.
*/
atomic_uint nmsg; /*%< Number of messages recvd */
atomic_uint nrecs; /*%< Number of records recvd */
atomic_uint_fast64_t nbytes; /*%< Number of bytes received */
_Atomic(isc_time_t) start; /*%< Start time of the transfer */
atomic_uint_fast64_t rate_bytes_per_second;
_Atomic(dns_transport_type_t) soa_transport_type;
atomic_uint_fast32_t end_serial;
unsigned int maxrecords; /*%< The maximum number of
* records set for the zone */
uint64_t nbytes_saved; /*%< For enforcing the minimum transfer rate */
dns_tsigkey_t *tsigkey; /*%< Key used to create TSIG */
isc_buffer_t *lasttsig; /*%< The last TSIG */
dst_context_t *tsigctx; /*%< TSIG verification context */
unsigned int sincetsig; /*%< recvd since the last TSIG */
dns_transport_t *transport;
dns_xfrindone_t done;
/*%
* AXFR- and IXFR-specific data. Only one is used at a time
* according to the is_ixfr flag, so this could be a union,
* but keeping them separate makes it a bit simpler to clean
* things up when destroying the context.
*/
dns_rdatacallbacks_t axfr;
struct {
uint32_t request_serial;
uint32_t current_serial;
dns_journal_t *journal;
} ixfr;
dns_rdata_t firstsoa;
unsigned char *firstsoa_data;
isc_tlsctx_cache_t *tlsctx_cache;
isc_loop_t *loop;
isc_timer_t *min_rate_timer;
isc_timer_t *max_time_timer;
isc_timer_t *max_idle_timer;
char info[DNS_NAME_MAXTEXT + 32];
};
#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I')
#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC)
#define XFRIN_WORK_MAGIC ISC_MAGIC('X', 'f', 'r', 'W')
#define VALID_XFRIN_WORK(x) ISC_MAGIC_VALID(x, XFRIN_WORK_MAGIC)
typedef struct xfrin_work {
unsigned int magic;
isc_result_t result;
dns_xfrin_t *xfr;
} xfrin_work_t;
/**************************************************************************/
/*
* Forward declarations.
*/
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_loop_t *loop,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
dns_xfrin_t **xfrp);
static isc_result_t
axfr_init(dns_xfrin_t *xfr);
static isc_result_t
axfr_putdata(dns_xfrin_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
dns_rdata_t *rdata);
static void
axfr_commit(dns_xfrin_t *xfr);
static isc_result_t
axfr_finalize(dns_xfrin_t *xfr);
static isc_result_t
ixfr_init(dns_xfrin_t *xfr);
static isc_result_t
ixfr_putdata(dns_xfrin_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
dns_rdata_t *rdata);
static isc_result_t
ixfr_commit(dns_xfrin_t *xfr);
static isc_result_t
xfr_rr(dns_xfrin_t *xfr, dns_name_t *name, uint32_t ttl, dns_rdata_t *rdata);
static isc_result_t
xfrin_start(dns_xfrin_t *xfr);
static void
xfrin_connect_done(isc_result_t result, isc_region_t *region, void *arg);
static isc_result_t
xfrin_send_request(dns_xfrin_t *xfr);
static void
xfrin_send_done(isc_result_t eresult, isc_region_t *region, void *arg);
static void
xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg);
static void
xfrin_end(dns_xfrin_t *xfr, isc_result_t result);
static void
xfrin_destroy(dns_xfrin_t *xfr);
static void
xfrin_timedout(void *);
static void
xfrin_idledout(void *);
static void
xfrin_minratecheck(void *);
static void
xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg);
static isc_result_t
render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf);
static void
xfrin_log(dns_xfrin_t *xfr, int level, const char *fmt, ...)
ISC_FORMAT_PRINTF(3, 4);
/**************************************************************************/
/*
* AXFR handling
*/
static isc_result_t
axfr_init(dns_xfrin_t *xfr) {
isc_result_t result;
atomic_store(&xfr->is_ixfr, false);
if (xfr->db != NULL) {
dns_db_detach(&xfr->db);
}
CHECK(dns_zone_makedb(xfr->zone, &xfr->db));
dns_zone_rpz_enable_db(xfr->zone, xfr->db);
dns_zone_catz_enable_db(xfr->zone, xfr->db);
dns_rdatacallbacks_init(&xfr->axfr);
CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
result = ISC_R_SUCCESS;
failure:
return result;
}
static void
axfr_apply(void *arg);
static isc_result_t
axfr_putdata(dns_xfrin_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
dns_rdata_t *rdata) {
isc_result_t result;
dns_difftuple_t *tuple = NULL;
if (rdata->rdclass != xfr->rdclass) {
return DNS_R_BADCLASS;
}
CHECK(dns_zone_checknames(xfr->zone, name, rdata));
if (dns_diff_size(&xfr->diff) > 128 &&
dns_diff_is_boundary(&xfr->diff, name))
{
xfrin_work_t work = (xfrin_work_t){
.magic = XFRIN_WORK_MAGIC,
.result = ISC_R_UNSET,
.xfr = xfr,
};
axfr_apply((void *)&work);
CHECK(work.result);
}
CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
&tuple));
dns_diff_append(&xfr->diff, &tuple);
result = ISC_R_SUCCESS;
failure:
return result;
}
/*
* Store a set of AXFR RRs in the database.
*/
static void
axfr_apply(void *arg) {
xfrin_work_t *work = arg;
REQUIRE(VALID_XFRIN_WORK(work));
dns_xfrin_t *xfr = work->xfr;
REQUIRE(VALID_XFRIN(xfr));
isc_result_t result = ISC_R_SUCCESS;
uint64_t records;
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
goto failure;
}
CHECK(dns_diff_load(&xfr->diff, &xfr->axfr));
if (xfr->maxrecords != 0U) {
result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
result = DNS_R_TOOMANYRECORDS;
goto failure;
}
}
failure:
dns_diff_clear(&xfr->diff);
work->result = result;
}
static void
axfr_apply_done(void *arg) {
xfrin_work_t *work = arg;
REQUIRE(VALID_XFRIN_WORK(work));
dns_xfrin_t *xfr = work->xfr;
isc_result_t result = work->result;
REQUIRE(VALID_XFRIN(xfr));
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
if (result == ISC_R_SUCCESS) {
CHECK(dns_db_endload(xfr->db, &xfr->axfr));
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
CHECK(axfr_finalize(xfr));
} else {
(void)dns_db_endload(xfr->db, &xfr->axfr);
}
failure:
xfr->diff_running = false;
isc_mem_put(xfr->mctx, work, sizeof(*work));
if (result == ISC_R_SUCCESS) {
if (atomic_load(&xfr->state) == XFRST_AXFR_END) {
xfrin_end(xfr, result);
}
} else {
xfrin_fail(xfr, result, "failed while processing responses");
}
dns_xfrin_detach(&xfr);
}
static void
axfr_commit(dns_xfrin_t *xfr) {
REQUIRE(!xfr->diff_running);
xfrin_work_t *work = isc_mem_get(xfr->mctx, sizeof(*work));
*work = (xfrin_work_t){
.magic = XFRIN_WORK_MAGIC,
.result = ISC_R_UNSET,
.xfr = dns_xfrin_ref(xfr),
};
xfr->diff_running = true;
isc_work_enqueue(xfr->loop, axfr_apply, axfr_apply_done, work);
}
static isc_result_t
axfr_finalize(dns_xfrin_t *xfr) {
isc_result_t result;
LIBDNS_XFRIN_AXFR_FINALIZE_BEGIN(xfr, xfr->info);
result = dns_zone_replacedb(xfr->zone, xfr->db, true);
LIBDNS_XFRIN_AXFR_FINALIZE_END(xfr, xfr->info, result);
return result;
}
/**************************************************************************/
/*
* IXFR handling
*/
typedef struct ixfr_apply_data {
dns_diff_t diff; /*%< Pending database changes */
struct cds_wfcq_node wfcq_node;
} ixfr_apply_data_t;
static isc_result_t
ixfr_init(dns_xfrin_t *xfr) {
isc_result_t result;
char *journalfile = NULL;
if (xfr->reqtype != dns_rdatatype_ixfr) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"got incremental response to AXFR request");
return DNS_R_FORMERR;
}
atomic_store(&xfr->is_ixfr, true);
INSIST(xfr->db != NULL);
journalfile = dns_zone_getjournal(xfr->zone);
if (journalfile != NULL) {
CHECK(dns_journal_open(xfr->mctx, journalfile,
DNS_JOURNAL_CREATE, &xfr->ixfr.journal));
}
result = ISC_R_SUCCESS;
failure:
return result;
}
static isc_result_t
ixfr_putdata(dns_xfrin_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
dns_rdata_t *rdata) {
isc_result_t result;
dns_difftuple_t *tuple = NULL;
if (rdata->rdclass != xfr->rdclass) {
return DNS_R_BADCLASS;
}
if (op == DNS_DIFFOP_ADD) {
CHECK(dns_zone_checknames(xfr->zone, name, rdata));
}
CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
&tuple));
dns_diff_append(&xfr->diff, &tuple);
result = ISC_R_SUCCESS;
failure:
return result;
}
static isc_result_t
ixfr_begin_transaction(dns_xfrin_t *xfr) {
isc_result_t result = ISC_R_SUCCESS;
if (xfr->ixfr.journal != NULL) {
CHECK(dns_journal_begin_transaction(xfr->ixfr.journal));
}
failure:
return result;
}
static isc_result_t
ixfr_end_transaction(dns_xfrin_t *xfr) {
isc_result_t result = ISC_R_SUCCESS;
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
/* XXX enter ready-to-commit state here */
if (xfr->ixfr.journal != NULL) {
CHECK(dns_journal_commit(xfr->ixfr.journal));
}
failure:
return result;
}
static isc_result_t
ixfr_apply_one(dns_xfrin_t *xfr, ixfr_apply_data_t *data) {
isc_result_t result = ISC_R_SUCCESS;
uint64_t records;
CHECK(ixfr_begin_transaction(xfr));
CHECK(dns_diff_apply(&data->diff, xfr->db, xfr->ver));
if (xfr->maxrecords != 0U) {
result = dns_db_getsize(xfr->db, xfr->ver, &records, NULL);
if (result == ISC_R_SUCCESS && records > xfr->maxrecords) {
result = DNS_R_TOOMANYRECORDS;
goto failure;
}
}
if (xfr->ixfr.journal != NULL) {
CHECK(dns_journal_writediff(xfr->ixfr.journal, &data->diff));
}
result = ixfr_end_transaction(xfr);
return result;
failure:
/* We need to end the transaction, but keep the previous error */
(void)ixfr_end_transaction(xfr);
return result;
}
static void
ixfr_apply(void *arg) {
xfrin_work_t *work = arg;
dns_xfrin_t *xfr = work->xfr;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(VALID_XFRIN(xfr));
REQUIRE(VALID_XFRIN_WORK(work));
struct __cds_wfcq_head diff_head;
struct cds_wfcq_tail diff_tail;
/* Initialize local wfcqueue */
__cds_wfcq_init(&diff_head, &diff_tail);
enum cds_wfcq_ret ret = __cds_wfcq_splice_blocking(
&diff_head, &diff_tail, &xfr->diff_head, &xfr->diff_tail);
INSIST(ret == CDS_WFCQ_RET_DEST_EMPTY);
struct cds_wfcq_node *node, *next;
__cds_wfcq_for_each_blocking_safe(&diff_head, &diff_tail, node, next) {
ixfr_apply_data_t *data =
caa_container_of(node, ixfr_apply_data_t, wfcq_node);
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
/* Apply only until first failure */
if (result == ISC_R_SUCCESS) {
/* This also checks for shuttingdown condition */
result = ixfr_apply_one(xfr, data);
}
/* We need to clear and free all data chunks */
dns_diff_clear(&data->diff);
isc_mem_put(xfr->mctx, data, sizeof(*data));
}
work->result = result;
}
static void
ixfr_apply_done(void *arg) {
xfrin_work_t *work = arg;
REQUIRE(VALID_XFRIN_WORK(work));
dns_xfrin_t *xfr = work->xfr;
REQUIRE(VALID_XFRIN(xfr));
isc_result_t result = work->result;
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
if (result != ISC_R_SUCCESS) {
goto failure;
}
/* Reschedule */
if (!cds_wfcq_empty(&xfr->diff_head, &xfr->diff_tail)) {
isc_work_enqueue(xfr->loop, ixfr_apply, ixfr_apply_done, work);
return;
}
failure:
xfr->diff_running = false;
isc_mem_put(xfr->mctx, work, sizeof(*work));
if (result == ISC_R_SUCCESS) {
dns_db_closeversion(xfr->db, &xfr->ver, true);
dns_zone_markdirty(xfr->zone);
if (atomic_load(&xfr->state) == XFRST_IXFR_END) {
xfrin_end(xfr, result);
}
} else {
dns_db_closeversion(xfr->db, &xfr->ver, false);
xfrin_fail(xfr, result, "failed while processing responses");
}
dns_xfrin_detach(&xfr);
}
/*
* Apply a set of IXFR changes to the database.
*/
static isc_result_t
ixfr_commit(dns_xfrin_t *xfr) {
isc_result_t result = ISC_R_SUCCESS;
ixfr_apply_data_t *data = isc_mem_get(xfr->mctx, sizeof(*data));
*data = (ixfr_apply_data_t){ 0 };
cds_wfcq_node_init(&data->wfcq_node);
if (xfr->ver == NULL) {
CHECK(dns_db_newversion(xfr->db, &xfr->ver));
}
dns_diff_init(xfr->mctx, &data->diff);
/* FIXME: Should we add dns_diff_move() */
ISC_LIST_MOVE(data->diff.tuples, xfr->diff.tuples);
(void)cds_wfcq_enqueue(&xfr->diff_head, &xfr->diff_tail,
&data->wfcq_node);
if (!xfr->diff_running) {
xfrin_work_t *work = isc_mem_get(xfr->mctx, sizeof(*work));
*work = (xfrin_work_t){
.magic = XFRIN_WORK_MAGIC,
.result = ISC_R_UNSET,
.xfr = dns_xfrin_ref(xfr),
};
xfr->diff_running = true;
isc_work_enqueue(xfr->loop, ixfr_apply, ixfr_apply_done, work);
}
failure:
return result;
}
/**************************************************************************/
/*
* Common AXFR/IXFR protocol code
*/
/*
* Handle a single incoming resource record according to the current
* state.
*/
static isc_result_t
xfr_rr(dns_xfrin_t *xfr, dns_name_t *name, uint32_t ttl, dns_rdata_t *rdata) {
isc_result_t result;
uint_fast32_t end_serial;
atomic_fetch_add_relaxed(&xfr->nrecs, 1);
if (rdata->type == dns_rdatatype_none ||
dns_rdatatype_ismeta(rdata->type))
{
char buf[64];
dns_rdatatype_format(rdata->type, buf, sizeof(buf));
xfrin_log(xfr, ISC_LOG_NOTICE,
"Unexpected %s record in zone transfer", buf);
result = DNS_R_FORMERR;
goto failure;
}
/*
* Immediately reject the entire transfer if the RR that is currently
* being processed is an SOA record that is not placed at the zone
* apex.
*/
if (rdata->type == dns_rdatatype_soa &&
!dns_name_equal(&xfr->name, name))
{
char namebuf[DNS_NAME_FORMATSIZE];
dns_name_format(name, namebuf, sizeof(namebuf));
xfrin_log(xfr, ISC_LOG_DEBUG(3), "SOA name mismatch: '%s'",
namebuf);
result = DNS_R_NOTZONETOP;
goto failure;
}
redo:
switch (atomic_load(&xfr->state)) {
case XFRST_SOAQUERY:
if (rdata->type != dns_rdatatype_soa) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"non-SOA response to SOA query");
result = DNS_R_FORMERR;
goto failure;
}
end_serial = dns_soa_getserial(rdata);
atomic_store_relaxed(&xfr->end_serial, end_serial);
if (!DNS_SERIAL_GT(end_serial, xfr->ixfr.request_serial) &&
!dns_zone_isforced(xfr->zone))
{
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requested serial %u, "
"primary has %" PRIuFAST32 ", not updating",
xfr->ixfr.request_serial, end_serial);
result = DNS_R_UPTODATE;
goto failure;
}
atomic_store(&xfr->state, XFRST_GOTSOA);
break;
case XFRST_GOTSOA:
/*
* Skip other records in the answer section.
*/
break;
case XFRST_ZONEXFRREQUEST:
if (rdata->type != dns_rdatatype_soa) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"first RR in zone transfer must be SOA");
result = DNS_R_FORMERR;
goto failure;
}
/*
* Remember the serial number in the initial SOA.
* We need it to recognize the end of an IXFR.
*/
end_serial = dns_soa_getserial(rdata);
atomic_store_relaxed(&xfr->end_serial, end_serial);
if (xfr->reqtype == dns_rdatatype_ixfr &&
!DNS_SERIAL_GT(end_serial, xfr->ixfr.request_serial) &&
!dns_zone_isforced(xfr->zone))
{
/*
* This must be the single SOA record that is
* sent when the current version on the primary
* is not newer than the version in the request.
*/
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requested serial %u, "
"primary has %" PRIuFAST32 ", not updating",
xfr->ixfr.request_serial, end_serial);
result = DNS_R_UPTODATE;
goto failure;
}
xfr->firstsoa = *rdata;
if (xfr->firstsoa_data != NULL) {
isc_mem_free(xfr->mctx, xfr->firstsoa_data);
}
xfr->firstsoa_data = isc_mem_allocate(xfr->mctx, rdata->length);
memcpy(xfr->firstsoa_data, rdata->data, rdata->length);
xfr->firstsoa.data = xfr->firstsoa_data;
atomic_store(&xfr->state, XFRST_FIRSTDATA);
break;
case XFRST_FIRSTDATA:
/*
* If the transfer begins with one SOA record, it is an AXFR,
* if it begins with two SOAs, it is an IXFR.
*/
if (xfr->reqtype == dns_rdatatype_ixfr &&
rdata->type == dns_rdatatype_soa &&
xfr->ixfr.request_serial == dns_soa_getserial(rdata))
{
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"got incremental response");
CHECK(ixfr_init(xfr));
atomic_store(&xfr->state, XFRST_IXFR_DELSOA);
} else {
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"got nonincremental response");
CHECK(axfr_init(xfr));
atomic_store(&xfr->state, XFRST_AXFR);
}
goto redo;
case XFRST_IXFR_DELSOA:
INSIST(rdata->type == dns_rdatatype_soa);
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
atomic_store(&xfr->state, XFRST_IXFR_DEL);
break;
case XFRST_IXFR_DEL:
if (rdata->type == dns_rdatatype_soa) {
uint32_t soa_serial = dns_soa_getserial(rdata);
atomic_store(&xfr->state, XFRST_IXFR_ADDSOA);
xfr->ixfr.current_serial = soa_serial;
goto redo;
}
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
break;
case XFRST_IXFR_ADDSOA:
INSIST(rdata->type == dns_rdatatype_soa);
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
atomic_store(&xfr->state, XFRST_IXFR_ADD);
break;
case XFRST_IXFR_ADD:
if (rdata->type == dns_rdatatype_soa) {
uint32_t soa_serial = dns_soa_getserial(rdata);
if (soa_serial == atomic_load_relaxed(&xfr->end_serial))
{
CHECK(ixfr_commit(xfr));
atomic_store(&xfr->state, XFRST_IXFR_END);
break;
} else if (soa_serial != xfr->ixfr.current_serial) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"IXFR out of sync: "
"expected serial %u, got %u",
xfr->ixfr.current_serial, soa_serial);
result = DNS_R_FORMERR;
goto failure;
} else {
CHECK(ixfr_commit(xfr));
atomic_store(&xfr->state, XFRST_IXFR_DELSOA);
goto redo;
}
}
if (rdata->type == dns_rdatatype_ns &&
dns_name_iswildcard(name))
{
result = DNS_R_INVALIDNS;
goto failure;
}
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
break;
case XFRST_AXFR:
/*
* Old BINDs sent cross class A records for non IN classes.
*/
if (rdata->type == dns_rdatatype_a &&
rdata->rdclass != xfr->rdclass &&
xfr->rdclass != dns_rdataclass_in)
{
break;
}
CHECK(axfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
if (rdata->type == dns_rdatatype_soa) {
/*
* Use dns_rdata_compare instead of memcmp to
* allow for case differences.
*/
if (dns_rdata_compare(rdata, &xfr->firstsoa) != 0) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"start and ending SOA records "
"mismatch");
result = DNS_R_FORMERR;
goto failure;
}
axfr_commit(xfr);
atomic_store(&xfr->state, XFRST_AXFR_END);
break;
}
break;
case XFRST_AXFR_END:
case XFRST_IXFR_END:
result = DNS_R_EXTRADATA;
goto failure;
default:
UNREACHABLE();
}
result = ISC_R_SUCCESS;
failure:
return result;
}
void
dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
isc_mem_t *mctx, dns_xfrin_t **xfrp) {
dns_name_t *zonename = dns_zone_getorigin(zone);
dns_xfrin_t *xfr = NULL;
dns_db_t *db = NULL;
isc_loop_t *loop = NULL;
REQUIRE(xfrp != NULL && *xfrp == NULL);
REQUIRE(isc_sockaddr_getport(primaryaddr) != 0);
REQUIRE(zone != NULL);
REQUIRE(dns_zone_getview(zone) != NULL);
loop = dns_zone_getloop(zone);
(void)dns_zone_getdb(zone, &db);
if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
REQUIRE(db != NULL);
}
xfrin_create(mctx, zone, db, loop, zonename, dns_zone_getclass(zone),
xfrtype, primaryaddr, sourceaddr, tsigkey,
soa_transport_type, transport, tlsctx_cache, &xfr);
if (db != NULL) {
xfr->zone_had_db = true;
dns_db_detach(&db);
}
*xfrp = xfr;
}
isc_result_t
dns_xfrin_start(dns_xfrin_t *xfr, dns_xfrindone_t done) {
isc_result_t result;
REQUIRE(xfr != NULL);
REQUIRE(xfr->zone != NULL);
REQUIRE(done != NULL);
xfr->done = done;
result = xfrin_start(xfr);
if (result != ISC_R_SUCCESS) {
xfr->done = NULL;
xfrin_fail(xfr, result, "zone transfer start failed");
}
return result;
}
static void
xfrin_timedout(void *xfr) {
REQUIRE(VALID_XFRIN(xfr));
xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum transfer time exceeded");
}
static void
xfrin_idledout(void *xfr) {
REQUIRE(VALID_XFRIN(xfr));
xfrin_fail(xfr, ISC_R_TIMEDOUT, "maximum idle time exceeded");
}
static void
xfrin_minratecheck(void *arg) {
dns_xfrin_t *xfr = arg;
REQUIRE(VALID_XFRIN(xfr));
const uint64_t nbytes = atomic_load_relaxed(&xfr->nbytes);
const uint64_t min = dns_zone_getminxfrratebytesin(xfr->zone);
uint64_t rate = nbytes - xfr->nbytes_saved;
if (rate < min) {
isc_timer_stop(xfr->min_rate_timer);
xfrin_fail(xfr, ISC_R_TIMEDOUT,
"minimum transfer rate reached");
} else {
xfr->nbytes_saved = nbytes;
/*
* Calculate and store for the statistics channel the transfer
* rate in bytes-per-second for the latest interval.
*/
rate /= dns_zone_getminxfrratesecondsin(xfr->zone);
atomic_store_relaxed(&xfr->rate_bytes_per_second, rate);
}
}
isc_time_t
dns_xfrin_getstarttime(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
return atomic_load_relaxed(&xfr->start);
}
void
dns_xfrin_getstate(const dns_xfrin_t *xfr, const char **statestr,
bool *is_first_data_received, bool *is_ixfr) {
xfrin_state_t state;
REQUIRE(VALID_XFRIN(xfr));
REQUIRE(statestr != NULL && *statestr == NULL);
REQUIRE(is_ixfr != NULL);
state = atomic_load(&xfr->state);
*statestr = "";
*is_first_data_received = (state > XFRST_FIRSTDATA);
*is_ixfr = atomic_load(&xfr->is_ixfr);
switch (state) {
case XFRST_SOAQUERY:
*statestr = "SOA Query";
break;
case XFRST_GOTSOA:
*statestr = "Got SOA";
break;
case XFRST_ZONEXFRREQUEST:
*statestr = "Zone Transfer Request";
break;
case XFRST_FIRSTDATA:
*statestr = "First Data";
break;
case XFRST_IXFR_DELSOA:
case XFRST_IXFR_DEL:
case XFRST_IXFR_ADDSOA:
case XFRST_IXFR_ADD:
*statestr = "Receiving IXFR Data";
break;
case XFRST_IXFR_END:
*statestr = "Finalizing IXFR";
break;
case XFRST_AXFR:
*statestr = "Receiving AXFR Data";
break;
case XFRST_AXFR_END:
*statestr = "Finalizing AXFR";
break;
}
}
uint32_t
dns_xfrin_getendserial(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
return atomic_load_relaxed(&xfr->end_serial);
}
void
dns_xfrin_getstats(dns_xfrin_t *xfr, unsigned int *nmsgp, unsigned int *nrecsp,
uint64_t *nbytesp, uint64_t *ratep) {
REQUIRE(VALID_XFRIN(xfr));
REQUIRE(nmsgp != NULL && nrecsp != NULL && nbytesp != NULL);
uint64_t rate = atomic_load_relaxed(&xfr->rate_bytes_per_second);
if (rate == 0) {
/*
* Likely the first 'min-transfer-rate-in <bytes> <minutes>'
* minutes interval hasn't passed yet. Calculate the overall
* average transfer rate instead.
*/
isc_time_t now = isc_time_now();
isc_time_t start = atomic_load_relaxed(&xfr->start);
uint64_t sec = isc_time_microdiff(&now, &start) / US_PER_SEC;
if (sec > 0) {
rate = atomic_load_relaxed(&xfr->nbytes) / sec;
}
}
SET_IF_NOT_NULL(nmsgp, atomic_load_relaxed(&xfr->nmsg));
SET_IF_NOT_NULL(nrecsp, atomic_load_relaxed(&xfr->nrecs));
SET_IF_NOT_NULL(nbytesp, atomic_load_relaxed(&xfr->nbytes));
SET_IF_NOT_NULL(ratep, rate);
}
const isc_sockaddr_t *
dns_xfrin_getsourceaddr(const dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
return &xfr->sourceaddr;
}
const isc_sockaddr_t *
dns_xfrin_getprimaryaddr(const dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
return &xfr->primaryaddr;
}
dns_transport_type_t
dns_xfrin_gettransporttype(const dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
if (xfr->transport != NULL) {
return dns_transport_get_type(xfr->transport);
}
return DNS_TRANSPORT_TCP;
}
dns_transport_type_t
dns_xfrin_getsoatransporttype(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
return atomic_load_relaxed(&xfr->soa_transport_type);
}
const dns_name_t *
dns_xfrin_gettsigkeyname(const dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
if (xfr->tsigkey == NULL || xfr->tsigkey->key == NULL) {
return NULL;
}
return dst_key_name(xfr->tsigkey->key);
}
static void
xfrin_shutdown(void *arg) {
dns_xfrin_t *xfr = arg;
REQUIRE(VALID_XFRIN(xfr));
xfrin_fail(xfr, ISC_R_SHUTTINGDOWN, "shut down");
dns_xfrin_detach(&xfr);
}
void
dns_xfrin_shutdown(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
if (xfr->loop != isc_loop()) {
dns_xfrin_ref(xfr);
isc_async_run(xfr->loop, xfrin_shutdown, xfr);
} else {
xfrin_fail(xfr, ISC_R_SHUTTINGDOWN, "shut down");
}
}
#if DNS_XFRIN_TRACE
ISC_REFCOUNT_TRACE_IMPL(dns_xfrin, xfrin_destroy);
#else
ISC_REFCOUNT_IMPL(dns_xfrin, xfrin_destroy);
#endif
static void
xfrin_cancelio(dns_xfrin_t *xfr) {
if (xfr->dispentry != NULL) {
dns_dispatch_done(&xfr->dispentry);
}
if (xfr->disp != NULL) {
dns_dispatch_detach(&xfr->disp);
}
}
static void
xfrin_reset(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
xfrin_log(xfr, ISC_LOG_INFO, "resetting");
if (xfr->lasttsig != NULL) {
isc_buffer_free(&xfr->lasttsig);
}
dns_diff_clear(&xfr->diff);
if (xfr->ixfr.journal != NULL) {
dns_journal_destroy(&xfr->ixfr.journal);
}
if (xfr->axfr.add_private != NULL) {
(void)dns_db_endload(xfr->db, &xfr->axfr);
}
if (xfr->ver != NULL) {
dns_db_closeversion(xfr->db, &xfr->ver, false);
}
}
static void
xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg) {
REQUIRE(VALID_XFRIN(xfr));
dns_xfrin_ref(xfr);
/* Make sure only the first xfrin_fail() trumps */
if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false },
true))
{
if (result != DNS_R_UPTODATE) {
xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
isc_result_totext(result));
if (atomic_load(&xfr->is_ixfr) &&
result != ISC_R_CANCELED &&
result != ISC_R_SHUTTINGDOWN)
{
/*
* Pass special result code to force AXFR retry
*/
result = DNS_R_BADIXFR;
}
}
xfrin_cancelio(xfr);
xfrin_end(xfr, result);
}
dns_xfrin_detach(&xfr);
}
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_loop_t *loop,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
dns_xfrin_t **xfrp) {
dns_xfrin_t *xfr = NULL;
xfr = isc_mem_get(mctx, sizeof(*xfr));
*xfr = (dns_xfrin_t){
.shutdown_result = ISC_R_UNSET,
.rdclass = rdclass,
.reqtype = reqtype,
.maxrecords = dns_zone_getmaxrecords(zone),
.primaryaddr = *primaryaddr,
.sourceaddr = *sourceaddr,
.soa_transport_type = soa_transport_type,
.firstsoa = DNS_RDATA_INIT,
.edns = true,
.references = 1,
.magic = XFRIN_MAGIC,
};
isc_loop_attach(loop, &xfr->loop);
isc_mem_attach(mctx, &xfr->mctx);
dns_zone_iattach(zone, &xfr->zone);
dns_view_weakattach(dns_zone_getview(zone), &xfr->view);
dns_name_init(&xfr->name, NULL);
__cds_wfcq_init(&xfr->diff_head, &xfr->diff_tail);
atomic_init(&xfr->is_ixfr, false);
if (db != NULL) {
dns_db_attach(db, &xfr->db);
}
dns_diff_init(xfr->mctx, &xfr->diff);
if (reqtype == dns_rdatatype_soa) {
atomic_init(&xfr->state, XFRST_SOAQUERY);
} else {
atomic_init(&xfr->state, XFRST_ZONEXFRREQUEST);
}
atomic_init(&xfr->start, isc_time_now());
if (tsigkey != NULL) {
dns_tsigkey_attach(tsigkey, &xfr->tsigkey);
}
if (transport != NULL) {
dns_transport_attach(transport, &xfr->transport);
}
dns_name_dup(zonename, mctx, &xfr->name);
INSIST(isc_sockaddr_pf(primaryaddr) == isc_sockaddr_pf(sourceaddr));
isc_sockaddr_setport(&xfr->sourceaddr, 0);
/*
* Reserve 2 bytes for TCP length at the beginning of the buffer.
*/
isc_buffer_init(&xfr->qbuffer, &xfr->qbuffer_data[2],
sizeof(xfr->qbuffer_data) - 2);
isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache);
dns_zone_name(xfr->zone, xfr->info, sizeof(xfr->info));
*xfrp = xfr;
}
static isc_result_t
xfrin_start(dns_xfrin_t *xfr) {
isc_result_t result = ISC_R_FAILURE;
isc_interval_t interval;
dns_xfrin_ref(xfr);
/* If this is a retry, we need to cancel the previous dispentry */
xfrin_cancelio(xfr);
dns_dispatchmgr_t *dispmgr = dns_view_getdispatchmgr(xfr->view);
if (dispmgr == NULL) {
result = ISC_R_SHUTTINGDOWN;
goto failure;
} else {
result = dns_dispatch_createtcp(
dispmgr, &xfr->sourceaddr, &xfr->primaryaddr,
xfr->transport, DNS_DISPATCHOPT_UNSHARED, &xfr->disp);
dns_dispatchmgr_detach(&dispmgr);
if (result != ISC_R_SUCCESS) {
goto failure;
}
}
LIBDNS_XFRIN_START(xfr, xfr->info);
/*
* If the transfer is started when the 'state' is XFRST_SOAQUERY, it
* means the SOA query will be performed by xfrin. A transfer could also
* be initiated starting from the XFRST_ZONEXFRREQUEST state, which
* means that the SOA query was already performed by other means (e.g.
* by zone.c:soa_query()), or that it's a transfer without a preceding
* SOA request, and 'soa_transport_type' is already correctly
* set by the creator of the xfrin.
*/
if (atomic_load(&xfr->state) == XFRST_SOAQUERY) {
/*
* The "SOA before" mode is used, where the SOA request is
* using the same transport as the XFR.
*/
atomic_store_relaxed(&xfr->soa_transport_type,
dns_xfrin_gettransporttype(xfr));
}
CHECK(dns_dispatch_add(
xfr->disp, xfr->loop, 0, 0, &xfr->primaryaddr, xfr->transport,
xfr->tlsctx_cache, xfrin_connect_done, xfrin_send_done,
xfrin_recv_done, xfr, &xfr->id, &xfr->dispentry));
/* Set the maximum timer */
if (xfr->max_time_timer == NULL) {
isc_timer_create(dns_zone_getloop(xfr->zone), xfrin_timedout,
xfr, &xfr->max_time_timer);
}
isc_interval_set(&interval, dns_zone_getmaxxfrin(xfr->zone), 0);
isc_timer_start(xfr->max_time_timer, isc_timertype_once, &interval);
/* Set the idle timer */
if (xfr->max_idle_timer == NULL) {
isc_timer_create(dns_zone_getloop(xfr->zone), xfrin_idledout,
xfr, &xfr->max_idle_timer);
}
isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
isc_timer_start(xfr->max_idle_timer, isc_timertype_once, &interval);
/* Set the minimum transfer rate checking timer */
if (xfr->min_rate_timer == NULL) {
isc_timer_create(dns_zone_getloop(xfr->zone),
xfrin_minratecheck, xfr, &xfr->min_rate_timer);
}
isc_interval_set(&interval, dns_zone_getminxfrratesecondsin(xfr->zone),
0);
isc_timer_start(xfr->min_rate_timer, isc_timertype_ticker, &interval);
/*
* The connect has to be the last thing that is called before returning,
* as it can end synchronously and destroy the xfr object.
*/
CHECK(dns_dispatch_connect(xfr->dispentry));
return ISC_R_SUCCESS;
failure:
xfrin_cancelio(xfr);
dns_xfrin_detach(&xfr);
return result;
}
/* XXX the resolver could use this, too */
static isc_result_t
render(dns_message_t *msg, isc_mem_t *mctx, isc_buffer_t *buf) {
dns_compress_t cctx;
isc_result_t result;
dns_compress_init(&cctx, mctx, 0);
CHECK(dns_message_renderbegin(msg, &cctx, buf));
CHECK(dns_message_rendersection(msg, DNS_SECTION_QUESTION, 0));
CHECK(dns_message_rendersection(msg, DNS_SECTION_ANSWER, 0));
CHECK(dns_message_rendersection(msg, DNS_SECTION_AUTHORITY, 0));
CHECK(dns_message_rendersection(msg, DNS_SECTION_ADDITIONAL, 0));
CHECK(dns_message_renderend(msg));
result = ISC_R_SUCCESS;
failure:
dns_compress_invalidate(&cctx);
return result;
}
/*
* A connection has been established.
*/
static void
xfrin_connect_done(isc_result_t result, isc_region_t *region ISC_ATTR_UNUSED,
void *arg) {
dns_xfrin_t *xfr = (dns_xfrin_t *)arg;
char addrtext[ISC_SOCKADDR_FORMATSIZE];
char signerbuf[DNS_NAME_FORMATSIZE];
const char *signer = "", *sep = "";
dns_zonemgr_t *zmgr = NULL;
REQUIRE(VALID_XFRIN(xfr));
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
LIBDNS_XFRIN_CONNECTED(xfr, xfr->info, result);
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed to connect");
goto failure;
}
result = dns_dispatch_checkperm(xfr->disp);
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "connected but unable to transfer");
goto failure;
}
zmgr = dns_zone_getmgr(xfr->zone);
if (zmgr != NULL) {
dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr,
&xfr->sourceaddr);
}
if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) {
dns_name_format(dst_key_name(xfr->tsigkey->key), signerbuf,
sizeof(signerbuf));
sep = " TSIG ";
signer = signerbuf;
}
isc_sockaddr_format(&xfr->primaryaddr, addrtext, sizeof(addrtext));
xfrin_log(xfr, ISC_LOG_INFO, "connected using %s%s%s", addrtext, sep,
signer);
result = xfrin_send_request(xfr);
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "connected but unable to send");
goto detach;
}
return;
failure:
switch (result) {
case ISC_R_NETDOWN:
case ISC_R_HOSTDOWN:
case ISC_R_NETUNREACH:
case ISC_R_HOSTUNREACH:
case ISC_R_CONNREFUSED:
case ISC_R_TIMEDOUT:
/*
* Add the server to unreachable primaries table if
* the server has a permanent networking error or
* the connection attempt as timed out.
*/
zmgr = dns_zone_getmgr(xfr->zone);
if (zmgr != NULL) {
isc_time_t now = isc_time_now();
dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr,
&xfr->sourceaddr, &now);
}
break;
default:
/* Retry sooner than in 10 minutes */
break;
}
detach:
dns_xfrin_detach(&xfr);
}
/*
* Convert a tuple into a dns_name_t suitable for inserting
* into the given dns_message_t.
*/
static void
tuple2msgname(dns_difftuple_t *tuple, dns_message_t *msg, dns_name_t **target) {
dns_rdata_t *rdata = NULL;
dns_rdatalist_t *rdl = NULL;
dns_rdataset_t *rds = NULL;
dns_name_t *name = NULL;
REQUIRE(target != NULL && *target == NULL);
dns_message_gettemprdata(msg, &rdata);
dns_rdata_init(rdata);
dns_rdata_clone(&tuple->rdata, rdata);
dns_message_gettemprdatalist(msg, &rdl);
dns_rdatalist_init(rdl);
rdl->type = tuple->rdata.type;
rdl->rdclass = tuple->rdata.rdclass;
rdl->ttl = tuple->ttl;
ISC_LIST_APPEND(rdl->rdata, rdata, link);
dns_message_gettemprdataset(msg, &rds);
dns_rdatalist_tordataset(rdl, rds);
dns_message_gettempname(msg, &name);
dns_name_clone(&tuple->name, name);
ISC_LIST_APPEND(name->list, rds, link);
*target = name;
}
static const char *
request_type(dns_xfrin_t *xfr) {
switch (xfr->reqtype) {
case dns_rdatatype_soa:
return "SOA";
case dns_rdatatype_axfr:
return "AXFR";
case dns_rdatatype_ixfr:
return "IXFR";
default:
ISC_UNREACHABLE();
}
}
static isc_result_t
add_opt(dns_message_t *message, uint16_t udpsize, bool reqnsid,
bool reqexpire) {
isc_result_t result;
dns_rdataset_t *rdataset = NULL;
dns_ednsopt_t ednsopts[DNS_EDNSOPTIONS];
int count = 0;
/* Set EDNS options if applicable. */
if (reqnsid) {
INSIST(count < DNS_EDNSOPTIONS);
ednsopts[count].code = DNS_OPT_NSID;
ednsopts[count].length = 0;
ednsopts[count].value = NULL;
count++;
}
if (reqexpire) {
INSIST(count < DNS_EDNSOPTIONS);
ednsopts[count].code = DNS_OPT_EXPIRE;
ednsopts[count].length = 0;
ednsopts[count].value = NULL;
count++;
}
result = dns_message_buildopt(message, &rdataset, 0, udpsize, 0,
ednsopts, count);
if (result != ISC_R_SUCCESS) {
return result;
}
return dns_message_setopt(message, rdataset);
}
/*
* Build an *XFR request and send its length prefix.
*/
static isc_result_t
xfrin_send_request(dns_xfrin_t *xfr) {
isc_result_t result;
isc_region_t region;
dns_rdataset_t *qrdataset = NULL;
dns_message_t *msg = NULL;
dns_difftuple_t *soatuple = NULL;
dns_name_t *qname = NULL;
dns_dbversion_t *ver = NULL;
dns_name_t *msgsoaname = NULL;
bool edns = xfr->edns;
bool reqnsid = xfr->view->requestnsid;
bool reqexpire = dns_zone_getrequestexpire(xfr->zone);
uint16_t udpsize = dns_view_getudpsize(xfr->view);
LIBDNS_XFRIN_RECV_SEND_REQUEST(xfr, xfr->info);
/* Create the request message */
dns_message_create(xfr->mctx, NULL, NULL, DNS_MESSAGE_INTENTRENDER,
&msg);
CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
/* Create a name for the question section. */
dns_message_gettempname(msg, &qname);
dns_name_clone(&xfr->name, qname);
/* Formulate the question and attach it to the question name. */
dns_message_gettemprdataset(msg, &qrdataset);
dns_rdataset_makequestion(qrdataset, xfr->rdclass, xfr->reqtype);
ISC_LIST_APPEND(qname->list, qrdataset, link);
qrdataset = NULL;
dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
qname = NULL;
if (xfr->reqtype == dns_rdatatype_ixfr) {
/* Get the SOA and add it to the authority section. */
dns_db_currentversion(xfr->db, &ver);
CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx,
DNS_DIFFOP_EXISTS, &soatuple));
xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata);
xfr->ixfr.current_serial = xfr->ixfr.request_serial;
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requesting IXFR for serial %u",
xfr->ixfr.request_serial);
tuple2msgname(soatuple, msg, &msgsoaname);
dns_message_addname(msg, msgsoaname, DNS_SECTION_AUTHORITY);
} else if (xfr->reqtype == dns_rdatatype_soa) {
CHECK(dns_db_getsoaserial(xfr->db, NULL,
&xfr->ixfr.request_serial));
}
if (edns && xfr->view->peers != NULL) {
dns_peer_t *peer = NULL;
isc_netaddr_t primaryip;
isc_netaddr_fromsockaddr(&primaryip, &xfr->primaryaddr);
result = dns_peerlist_peerbyaddr(xfr->view->peers, &primaryip,
&peer);
if (result == ISC_R_SUCCESS) {
(void)dns_peer_getsupportedns(peer, &edns);
(void)dns_peer_getudpsize(peer, &udpsize);
(void)dns_peer_getrequestnsid(peer, &reqnsid);
(void)dns_peer_getrequestexpire(peer, &reqexpire);
}
}
if (edns) {
CHECK(add_opt(msg, udpsize, reqnsid, reqexpire));
}
atomic_store_relaxed(&xfr->nmsg, 0);
atomic_store_relaxed(&xfr->nrecs, 0);
atomic_store_relaxed(&xfr->nbytes, 0);
atomic_store_relaxed(&xfr->start, isc_time_now());
xfr->nbytes_saved = 0;
msg->id = xfr->id;
if (xfr->tsigctx != NULL) {
dst_context_destroy(&xfr->tsigctx);
}
CHECK(render(msg, xfr->mctx, &xfr->qbuffer));
/*
* Free the last tsig, if there is one.
*/
if (xfr->lasttsig != NULL) {
isc_buffer_free(&xfr->lasttsig);
}
/*
* Save the query TSIG and don't let message_destroy free it.
*/
CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
isc_buffer_usedregion(&xfr->qbuffer, &region);
INSIST(region.length <= 65535);
dns_xfrin_ref(xfr);
dns_dispatch_send(xfr->dispentry, &region);
xfrin_log(xfr, ISC_LOG_DEBUG(3), "sending %s request, QID %d",
request_type(xfr), xfr->id);
failure:
dns_message_detach(&msg);
if (soatuple != NULL) {
dns_difftuple_free(&soatuple);
}
if (ver != NULL) {
dns_db_closeversion(xfr->db, &ver, false);
}
return result;
}
static void
xfrin_send_done(isc_result_t result, isc_region_t *region, void *arg) {
dns_xfrin_t *xfr = (dns_xfrin_t *)arg;
UNUSED(region);
REQUIRE(VALID_XFRIN(xfr));
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
LIBDNS_XFRIN_SENT(xfr, xfr->info, result);
CHECK(result);
xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data");
failure:
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed sending request data");
}
dns_xfrin_detach(&xfr);
}
static void
get_edns_expire(dns_xfrin_t *xfr, dns_message_t *msg) {
isc_result_t result;
dns_rdata_t rdata = DNS_RDATA_INIT;
isc_buffer_t optbuf;
uint16_t optcode;
uint16_t optlen;
result = dns_rdataset_first(msg->opt);
if (result == ISC_R_SUCCESS) {
dns_rdataset_current(msg->opt, &rdata);
isc_buffer_init(&optbuf, rdata.data, rdata.length);
isc_buffer_add(&optbuf, rdata.length);
while (isc_buffer_remaininglength(&optbuf) >= 4) {
optcode = isc_buffer_getuint16(&optbuf);
optlen = isc_buffer_getuint16(&optbuf);
/*
* A EDNS EXPIRE response has a length of 4.
*/
if (optcode != DNS_OPT_EXPIRE || optlen != 4) {
isc_buffer_forward(&optbuf, optlen);
continue;
}
xfr->expireopt = isc_buffer_getuint32(&optbuf);
xfr->expireoptset = true;
dns_zone_log(xfr->zone, ISC_LOG_DEBUG(1),
"got EDNS EXPIRE of %u", xfr->expireopt);
break;
}
}
}
static void
xfrin_end(dns_xfrin_t *xfr, isc_result_t result) {
/* Inform the caller. */
if (xfr->done != NULL) {
LIBDNS_XFRIN_DONE_CALLBACK_BEGIN(xfr, xfr->info, result);
(xfr->done)(xfr->zone,
xfr->expireoptset ? &xfr->expireopt : NULL, result);
xfr->done = NULL;
LIBDNS_XFRIN_DONE_CALLBACK_END(xfr, xfr->info, result);
}
atomic_store(&xfr->shuttingdown, true);
if (xfr->max_time_timer != NULL) {
isc_timer_stop(xfr->max_time_timer);
isc_timer_destroy(&xfr->max_time_timer);
}
if (xfr->max_idle_timer != NULL) {
isc_timer_stop(xfr->max_idle_timer);
isc_timer_destroy(&xfr->max_idle_timer);
}
if (xfr->min_rate_timer != NULL) {
isc_timer_stop(xfr->min_rate_timer);
isc_timer_destroy(&xfr->min_rate_timer);
}
if (xfr->shutdown_result == ISC_R_UNSET) {
xfr->shutdown_result = result;
}
}
static void
xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
dns_xfrin_t *xfr = (dns_xfrin_t *)arg;
dns_message_t *msg = NULL;
dns_name_t *name = NULL;
const dns_name_t *tsigowner = NULL;
isc_buffer_t buffer;
REQUIRE(VALID_XFRIN(xfr));
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
/* Stop the idle timer */
isc_timer_stop(xfr->max_idle_timer);
LIBDNS_XFRIN_RECV_START(xfr, xfr->info, result);
CHECK(result);
xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", region->length);
dns_message_create(xfr->mctx, NULL, NULL, DNS_MESSAGE_INTENTPARSE,
&msg);
CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
dns_message_setquerytsig(msg, xfr->lasttsig);
msg->tsigctx = xfr->tsigctx;
xfr->tsigctx = NULL;
dns_message_setclass(msg, xfr->rdclass);
msg->tcp_continuation = (atomic_load_relaxed(&xfr->nmsg) > 0) ? 1 : 0;
isc_buffer_init(&buffer, region->base, region->length);
isc_buffer_add(&buffer, region->length);
result = dns_message_parse(msg, &buffer,
DNS_MESSAGEPARSE_PRESERVEORDER);
if (result == ISC_R_SUCCESS) {
dns_message_logpacket(
msg, "received message from", &xfr->primaryaddr,
DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN,
ISC_LOG_DEBUG(10), xfr->mctx);
} else {
xfrin_log(xfr, ISC_LOG_DEBUG(10), "dns_message_parse: %s",
isc_result_totext(result));
}
LIBDNS_XFRIN_RECV_PARSED(xfr, xfr->info, result);
if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass)
{
if (result == ISC_R_SUCCESS &&
msg->rcode == dns_rcode_formerr && xfr->edns &&
(atomic_load(&xfr->state) == XFRST_SOAQUERY ||
atomic_load(&xfr->state) == XFRST_ZONEXFRREQUEST))
{
xfr->edns = false;
dns_message_detach(&msg);
xfrin_reset(xfr);
goto try_again;
} else if (result == ISC_R_SUCCESS &&
msg->rcode != dns_rcode_noerror)
{
result = dns_result_fromrcode(msg->rcode);
} else if (result == ISC_R_SUCCESS &&
msg->opcode != dns_opcode_query)
{
result = DNS_R_UNEXPECTEDOPCODE;
} else if (result == ISC_R_SUCCESS &&
msg->rdclass != xfr->rdclass)
{
result = DNS_R_BADCLASS;
} else if (result == ISC_R_SUCCESS || result == DNS_R_NOERROR) {
result = DNS_R_UNEXPECTEDID;
}
if (xfr->reqtype == dns_rdatatype_axfr ||
xfr->reqtype == dns_rdatatype_soa)
{
goto failure;
}
xfrin_log(xfr, ISC_LOG_DEBUG(3), "got %s, retrying with AXFR",
isc_result_totext(result));
try_axfr:
LIBDNS_XFRIN_RECV_TRY_AXFR(xfr, xfr->info, result);
dns_message_detach(&msg);
xfrin_reset(xfr);
xfr->reqtype = dns_rdatatype_soa;
atomic_store(&xfr->state, XFRST_SOAQUERY);
try_again:
result = xfrin_start(xfr);
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed setting up socket");
}
dns_xfrin_detach(&xfr);
return;
}
/*
* The question section should exist for SOA and in the first
* message of a AXFR or IXFR response. The question section
* may exist in the 2nd and subsequent messages in a AXFR or
* IXFR response. If the question section exists it should
* match the question that was sent.
*/
if (msg->counts[DNS_SECTION_QUESTION] > 1) {
xfrin_log(xfr, ISC_LOG_NOTICE, "too many questions (%u)",
msg->counts[DNS_SECTION_QUESTION]);
result = DNS_R_FORMERR;
goto failure;
}
if ((atomic_load(&xfr->state) == XFRST_SOAQUERY ||
atomic_load(&xfr->state) == XFRST_ZONEXFRREQUEST) &&
msg->counts[DNS_SECTION_QUESTION] != 1)
{
xfrin_log(xfr, ISC_LOG_NOTICE, "missing question section");
result = DNS_R_FORMERR;
goto failure;
}
for (result = dns_message_firstname(msg, DNS_SECTION_QUESTION);
result == ISC_R_SUCCESS;
result = dns_message_nextname(msg, DNS_SECTION_QUESTION))
{
dns_rdataset_t *rds = NULL;
LIBDNS_XFRIN_RECV_QUESTION(xfr, xfr->info, msg);
name = NULL;
dns_message_currentname(msg, DNS_SECTION_QUESTION, &name);
if (!dns_name_equal(name, &xfr->name)) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"question name mismatch");
result = DNS_R_FORMERR;
goto failure;
}
rds = ISC_LIST_HEAD(name->list);
INSIST(rds != NULL);
if (rds->type != xfr->reqtype) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"question type mismatch");
result = DNS_R_FORMERR;
goto failure;
}
if (rds->rdclass != xfr->rdclass) {
xfrin_log(xfr, ISC_LOG_NOTICE,
"question class mismatch");
result = DNS_R_FORMERR;
goto failure;
}
}
if (result != ISC_R_NOMORE) {
goto failure;
}
/*
* Does the server know about IXFR? If it doesn't we will get
* a message with a empty answer section or a potentially a CNAME /
* DNAME, the later is handled by xfr_rr() which will return FORMERR
* if the first RR in the answer section is not a SOA record.
*/
if (xfr->reqtype == dns_rdatatype_ixfr &&
atomic_load(&xfr->state) == XFRST_ZONEXFRREQUEST &&
msg->counts[DNS_SECTION_ANSWER] == 0)
{
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"empty answer section, retrying with AXFR");
goto try_axfr;
}
if (xfr->reqtype == dns_rdatatype_soa &&
(msg->flags & DNS_MESSAGEFLAG_AA) == 0)
{
result = DNS_R_NOTAUTHORITATIVE;
goto failure;
}
result = dns_message_checksig(msg, xfr->view);
if (result != ISC_R_SUCCESS) {
xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s",
isc_result_totext(result));
goto failure;
}
for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
result == ISC_R_SUCCESS;
result = dns_message_nextname(msg, DNS_SECTION_ANSWER))
{
dns_rdataset_t *rds = NULL;
LIBDNS_XFRIN_RECV_ANSWER(xfr, xfr->info, msg);
name = NULL;
dns_message_currentname(msg, DNS_SECTION_ANSWER, &name);
for (rds = ISC_LIST_HEAD(name->list); rds != NULL;
rds = ISC_LIST_NEXT(rds, link))
{
for (result = dns_rdataset_first(rds);
result == ISC_R_SUCCESS;
result = dns_rdataset_next(rds))
{
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(rds, &rdata);
CHECK(xfr_rr(xfr, name, rds->ttl, &rdata));
}
}
}
if (result == ISC_R_NOMORE) {
result = ISC_R_SUCCESS;
}
CHECK(result);
if (dns_message_gettsig(msg, &tsigowner) != NULL) {
/*
* Reset the counter.
*/
xfr->sincetsig = 0;
/*
* Free the last tsig, if there is one.
*/
if (xfr->lasttsig != NULL) {
isc_buffer_free(&xfr->lasttsig);
}
/*
* Update the last tsig pointer.
*/
CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
} else if (dns_message_gettsigkey(msg) != NULL) {
xfr->sincetsig++;
if (xfr->sincetsig > 100 ||
atomic_load_relaxed(&xfr->nmsg) == 0 ||
atomic_load(&xfr->state) == XFRST_AXFR_END ||
atomic_load(&xfr->state) == XFRST_IXFR_END)
{
result = DNS_R_EXPECTEDTSIG;
goto failure;
}
}
/*
* Update the number of messages and bytes received.
*/
atomic_fetch_add_relaxed(&xfr->nmsg, 1);
atomic_fetch_add_relaxed(&xfr->nbytes, buffer.used);
/*
* Take the context back.
*/
INSIST(xfr->tsigctx == NULL);
xfr->tsigctx = msg->tsigctx;
msg->tsigctx = NULL;
if (!xfr->expireoptset && msg->opt != NULL) {
get_edns_expire(xfr, msg);
}
switch (atomic_load(&xfr->state)) {
case XFRST_GOTSOA:
xfr->reqtype = dns_rdatatype_axfr;
atomic_store(&xfr->state, XFRST_ZONEXFRREQUEST);
CHECK(xfrin_start(xfr));
break;
case XFRST_AXFR_END:
case XFRST_IXFR_END:
/* We are at the end, cancel the timers and IO */
isc_timer_stop(xfr->min_rate_timer);
isc_timer_stop(xfr->max_idle_timer);
isc_timer_stop(xfr->max_time_timer);
xfrin_cancelio(xfr);
break;
default:
/*
* Read the next message.
*/
dns_message_detach(&msg);
result = dns_dispatch_getnext(xfr->dispentry);
if (result != ISC_R_SUCCESS) {
goto failure;
}
isc_interval_t interval;
isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
isc_timer_start(xfr->max_idle_timer, isc_timertype_once,
&interval);
LIBDNS_XFRIN_READ(xfr, xfr->info, result);
return;
}
failure:
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed while receiving responses");
}
if (msg != NULL) {
dns_message_detach(&msg);
}
dns_xfrin_detach(&xfr);
LIBDNS_XFRIN_RECV_DONE(xfr, xfr->info, result);
}
static void
xfrin_destroy(dns_xfrin_t *xfr) {
uint64_t msecs, persec;
isc_time_t now = isc_time_now();
char expireopt[sizeof("4000000000")] = { 0 };
const char *sep = "";
REQUIRE(VALID_XFRIN(xfr));
/* Safe-guards */
REQUIRE(atomic_load(&xfr->shuttingdown));
INSIST(xfr->shutdown_result != ISC_R_UNSET);
/*
* If we're called through dns_xfrin_detach() and are not
* shutting down, we can't know what the transfer status is as
* we are only called when the last reference is lost.
*/
xfrin_log(xfr, ISC_LOG_INFO, "Transfer status: %s",
isc_result_totext(xfr->shutdown_result));
/*
* Calculate the length of time the transfer took,
* and print a log message with the bytes and rate.
*/
isc_time_t start = atomic_load_relaxed(&xfr->start);
msecs = isc_time_microdiff(&now, &start) / 1000;
if (msecs == 0) {
msecs = 1;
}
persec = (atomic_load_relaxed(&xfr->nbytes) * 1000) / msecs;
if (xfr->expireoptset) {
sep = ", expire option ";
snprintf(expireopt, sizeof(expireopt), "%u", xfr->expireopt);
}
xfrin_log(xfr, ISC_LOG_INFO,
"Transfer completed: %d messages, %d records, "
"%" PRIu64 " bytes, "
"%u.%03u secs (%u bytes/sec) (serial %" PRIuFAST32 "%s%s)",
atomic_load_relaxed(&xfr->nmsg),
atomic_load_relaxed(&xfr->nrecs),
atomic_load_relaxed(&xfr->nbytes),
(unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000),
(unsigned int)persec, atomic_load_relaxed(&xfr->end_serial),
sep, expireopt);
/* Cleanup unprocessed IXFR data */
struct cds_wfcq_node *node, *next;
__cds_wfcq_for_each_blocking_safe(&xfr->diff_head, &xfr->diff_tail,
node, next) {
ixfr_apply_data_t *data =
caa_container_of(node, ixfr_apply_data_t, wfcq_node);
/* We need to clear and free all data chunks */
dns_diff_clear(&data->diff);
isc_mem_put(xfr->mctx, data, sizeof(*data));
}
/* Cleanup unprocessed AXFR data */
dns_diff_clear(&xfr->diff);
xfrin_cancelio(xfr);
if (xfr->transport != NULL) {
dns_transport_detach(&xfr->transport);
}
if (xfr->tsigkey != NULL) {
dns_tsigkey_detach(&xfr->tsigkey);
}
if (xfr->lasttsig != NULL) {
isc_buffer_free(&xfr->lasttsig);
}
if (xfr->ixfr.journal != NULL) {
dns_journal_destroy(&xfr->ixfr.journal);
}
if (xfr->axfr.add_private != NULL) {
(void)dns_db_endload(xfr->db, &xfr->axfr);
}
if (xfr->tsigctx != NULL) {
dst_context_destroy(&xfr->tsigctx);
}
if (xfr->name.attributes.dynamic) {
dns_name_free(&xfr->name, xfr->mctx);
}
if (xfr->ver != NULL) {
dns_db_closeversion(xfr->db, &xfr->ver, false);
}
if (xfr->db != NULL) {
dns_db_detach(&xfr->db);
}
if (xfr->zone != NULL) {
if (!xfr->zone_had_db &&
xfr->shutdown_result == ISC_R_SUCCESS &&
dns_zone_gettype(xfr->zone) == dns_zone_mirror)
{
dns_zone_log(xfr->zone, ISC_LOG_INFO,
"mirror zone is now in use");
}
xfrin_log(xfr, ISC_LOG_DEBUG(99), "freeing transfer context");
/*
* xfr->zone must not be detached before xfrin_log() is called.
*/
dns_zone_idetach(&xfr->zone);
}
if (xfr->view != NULL) {
dns_view_weakdetach(&xfr->view);
}
if (xfr->firstsoa_data != NULL) {
isc_mem_free(xfr->mctx, xfr->firstsoa_data);
}
if (xfr->tlsctx_cache != NULL) {
isc_tlsctx_cache_detach(&xfr->tlsctx_cache);
}
INSIST(xfr->max_time_timer == NULL);
INSIST(xfr->max_idle_timer == NULL);
INSIST(xfr->min_rate_timer == NULL);
isc_loop_detach(&xfr->loop);
isc_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
}
/*
* Log incoming zone transfer messages in a format like
* transfer of <zone> from <address>: <message>
*/
static void
xfrin_log(dns_xfrin_t *xfr, int level, const char *fmt, ...) {
va_list ap;
char primarytext[ISC_SOCKADDR_FORMATSIZE];
char msgtext[2048];
if (!isc_log_wouldlog(dns_lctx, level)) {
return;
}
isc_sockaddr_format(&xfr->primaryaddr, primarytext,
sizeof(primarytext));
va_start(ap, fmt);
vsnprintf(msgtext, sizeof(msgtext), fmt, ap);
va_end(ap);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN,
level, "%p: transfer of '%s' from %s: %s", xfr, xfr->info,
primarytext, msgtext);
}