2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00
bind/lib/dns/xfrin.c
2023-08-31 11:32:29 +10:00

1835 lines
45 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/mem.h>
#include <isc/random.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.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.
*/
/*%
* It would be non-sensical (or at least obtuse) to use FAIL() with an
* ISC_R_SUCCESS code, but the test is there to keep the Solaris compiler
* from complaining about "end-of-loop code not reached".
*/
#define FAIL(code) \
do { \
result = (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)
/*%
* 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_INITIALSOA,
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 */
int difflen; /*%< Number of pending tuples */
xfrin_state_t state;
uint32_t end_serial;
uint32_t expireopt;
bool edns, is_ixfr, expireoptset;
unsigned int nmsg; /*%< Number of messages recvd */
unsigned int nrecs; /*%< Number of records recvd */
uint64_t nbytes; /*%< Number of bytes received */
unsigned int maxrecords; /*%< The maximum number of
* records set for the zone */
isc_time_t start; /*%< Start time of the transfer */
isc_time_t end; /*%< End time of the transfer */
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_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)
/**************************************************************************/
/*
* Forward declarations.
*/
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
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_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_makedb(dns_xfrin_t *xfr, dns_db_t **dbp);
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 isc_result_t
axfr_apply(dns_xfrin_t *xfr);
static isc_result_t
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_apply(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_destroy(dns_xfrin_t *xfr);
static void
xfrin_timedout(void *);
static void
xfrin_idledout(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;
xfr->is_ixfr = false;
if (xfr->db != NULL) {
dns_db_detach(&xfr->db);
}
CHECK(axfr_makedb(xfr, &xfr->db));
dns_rdatacallbacks_init(&xfr->axfr);
CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
result = ISC_R_SUCCESS;
failure:
return (result);
}
static isc_result_t
axfr_makedb(dns_xfrin_t *xfr, dns_db_t **dbp) {
isc_result_t result;
result = dns_db_create(xfr->mctx, /* XXX */
"rbt", /* XXX guess */
&xfr->name, dns_dbtype_zone, xfr->rdclass, 0,
NULL, /* XXX guess */
dbp);
if (result == ISC_R_SUCCESS) {
dns_zone_rpz_enable_db(xfr->zone, *dbp);
dns_zone_catz_enable_db(xfr->zone, *dbp);
}
return (result);
}
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));
CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
&tuple));
dns_diff_append(&xfr->diff, &tuple);
if (++xfr->difflen > 100) {
CHECK(axfr_apply(xfr));
}
result = ISC_R_SUCCESS;
failure:
return (result);
}
/*
* Store a set of AXFR RRs in the database.
*/
static isc_result_t
axfr_apply(dns_xfrin_t *xfr) {
isc_result_t result;
uint64_t records;
CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private));
xfr->difflen = 0;
dns_diff_clear(&xfr->diff);
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;
}
}
result = ISC_R_SUCCESS;
failure:
return (result);
}
static isc_result_t
axfr_commit(dns_xfrin_t *xfr) {
isc_result_t result;
CHECK(axfr_apply(xfr));
CHECK(dns_db_endload(xfr->db, &xfr->axfr));
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, NULL));
result = ISC_R_SUCCESS;
failure:
return (result);
}
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
*/
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_ERROR,
"got incremental response to AXFR request");
return (DNS_R_FORMERR);
}
xfr->is_ixfr = true;
INSIST(xfr->db != NULL);
xfr->difflen = 0;
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);
if (++xfr->difflen > 100) {
CHECK(ixfr_apply(xfr));
}
result = ISC_R_SUCCESS;
failure:
return (result);
}
/*
* Apply a set of IXFR changes to the database.
*/
static isc_result_t
ixfr_apply(dns_xfrin_t *xfr) {
isc_result_t result;
uint64_t records;
if (xfr->ver == NULL) {
CHECK(dns_db_newversion(xfr->db, &xfr->ver));
if (xfr->ixfr.journal != NULL) {
CHECK(dns_journal_begin_transaction(xfr->ixfr.journal));
}
}
CHECK(dns_diff_apply(&xfr->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) {
result = dns_journal_writediff(xfr->ixfr.journal, &xfr->diff);
if (result != ISC_R_SUCCESS) {
goto failure;
}
}
dns_diff_clear(&xfr->diff);
xfr->difflen = 0;
result = ISC_R_SUCCESS;
failure:
return (result);
}
static isc_result_t
ixfr_commit(dns_xfrin_t *xfr) {
isc_result_t result;
CHECK(ixfr_apply(xfr));
if (xfr->ver != NULL) {
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));
}
dns_db_closeversion(xfr->db, &xfr->ver, true);
dns_zone_markdirty(xfr->zone);
}
result = ISC_R_SUCCESS;
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;
xfr->nrecs++;
if (rdata->type == dns_rdatatype_none ||
dns_rdatatype_ismeta(rdata->type))
{
FAIL(DNS_R_FORMERR);
}
/*
* 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);
FAIL(DNS_R_NOTZONETOP);
}
redo:
switch (xfr->state) {
case XFRST_SOAQUERY:
if (rdata->type != dns_rdatatype_soa) {
xfrin_log(xfr, ISC_LOG_ERROR,
"non-SOA response to SOA query");
FAIL(DNS_R_FORMERR);
}
xfr->end_serial = dns_soa_getserial(rdata);
if (!DNS_SERIAL_GT(xfr->end_serial, xfr->ixfr.request_serial) &&
!dns_zone_isforced(xfr->zone))
{
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requested serial %u, "
"primary has %u, not updating",
xfr->ixfr.request_serial, xfr->end_serial);
FAIL(DNS_R_UPTODATE);
}
xfr->state = XFRST_GOTSOA;
break;
case XFRST_GOTSOA:
/*
* Skip other records in the answer section.
*/
break;
case XFRST_INITIALSOA:
if (rdata->type != dns_rdatatype_soa) {
xfrin_log(xfr, ISC_LOG_ERROR,
"first RR in zone transfer must be SOA");
FAIL(DNS_R_FORMERR);
}
/*
* Remember the serial number in the initial SOA.
* We need it to recognize the end of an IXFR.
*/
xfr->end_serial = dns_soa_getserial(rdata);
if (xfr->reqtype == dns_rdatatype_ixfr &&
!DNS_SERIAL_GT(xfr->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 %u, not updating",
xfr->ixfr.request_serial, xfr->end_serial);
FAIL(DNS_R_UPTODATE);
}
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;
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));
xfr->state = XFRST_IXFR_DELSOA;
} else {
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"got nonincremental response");
CHECK(axfr_init(xfr));
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));
xfr->state = XFRST_IXFR_DEL;
break;
case XFRST_IXFR_DEL:
if (rdata->type == dns_rdatatype_soa) {
uint32_t soa_serial = dns_soa_getserial(rdata);
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));
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 == xfr->end_serial) {
CHECK(ixfr_commit(xfr));
xfr->state = XFRST_IXFR_END;
break;
} else if (soa_serial != xfr->ixfr.current_serial) {
xfrin_log(xfr, ISC_LOG_ERROR,
"IXFR out of sync: "
"expected serial %u, got %u",
xfr->ixfr.current_serial, soa_serial);
FAIL(DNS_R_FORMERR);
} else {
CHECK(ixfr_commit(xfr));
xfr->state = XFRST_IXFR_DELSOA;
goto redo;
}
}
if (rdata->type == dns_rdatatype_ns &&
dns_name_iswildcard(name))
{
FAIL(DNS_R_INVALIDNS);
}
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_ERROR,
"start and ending SOA records "
"mismatch");
FAIL(DNS_R_FORMERR);
}
CHECK(axfr_commit(xfr));
xfr->state = XFRST_AXFR_END;
break;
}
break;
case XFRST_AXFR_END:
case XFRST_IXFR_END:
FAIL(DNS_R_EXTRADATA);
FALLTHROUGH;
default:
UNREACHABLE();
}
result = ISC_R_SUCCESS;
failure:
return (result);
}
isc_result_t
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_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
isc_mem_t *mctx, dns_xfrindone_t done, dns_xfrin_t **xfrp) {
dns_name_t *zonename = dns_zone_getorigin(zone);
dns_xfrin_t *xfr = NULL;
isc_result_t result;
dns_db_t *db = NULL;
REQUIRE(xfrp != NULL && *xfrp == NULL);
REQUIRE(done != NULL);
REQUIRE(isc_sockaddr_getport(primaryaddr) != 0);
REQUIRE(zone != NULL);
REQUIRE(dns_zone_getview(zone) != NULL);
REQUIRE(dns_zone_gettid(zone) == isc_tid());
(void)dns_zone_getdb(zone, &db);
if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
REQUIRE(db != NULL);
}
xfrin_create(mctx, zone, db, zonename, dns_zone_getclass(zone), xfrtype,
primaryaddr, sourceaddr, tsigkey, transport, tlsctx_cache,
&xfr);
if (db != NULL) {
xfr->zone_had_db = true;
}
xfr->done = done;
isc_refcount_init(&xfr->references, 1);
/*
* Set *xfrp now, before calling xfrin_start(), otherwise it's
* possible the 'done' callback could be run before *xfrp
* was attached.
*/
*xfrp = xfr;
result = xfrin_start(xfr);
if (result != ISC_R_SUCCESS) {
atomic_store(&xfr->shuttingdown, true);
xfr->shutdown_result = result;
xfrin_log(xfr, ISC_LOG_ERROR, "zone transfer setup failed");
dns_xfrin_detach(xfrp);
}
if (db != NULL) {
dns_db_detach(&db);
}
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");
}
void
dns_xfrin_shutdown(dns_xfrin_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
REQUIRE(dns_zone_gettid(xfr->zone) == isc_tid());
xfrin_fail(xfr, ISC_R_CANCELED, "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) {
dns_dispatch_done(&xfr->dispentry);
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);
xfr->difflen = 0;
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) {
dns_xfrin_ref(xfr);
/* Make sure only the first xfrin_fail() trumps */
if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false },
true))
{
isc_timer_stop(xfr->max_time_timer);
isc_timer_stop(xfr->max_idle_timer);
if (result != DNS_R_UPTODATE && result != DNS_R_TOOMANYRECORDS)
{
xfrin_log(xfr, ISC_LOG_ERROR, "%s: %s", msg,
isc_result_totext(result));
if (xfr->is_ixfr) {
/*
* Pass special result code to force AXFR retry
*/
result = DNS_R_BADIXFR;
}
}
xfrin_cancelio(xfr);
/*
* Close the journal.
*/
if (xfr->ixfr.journal != NULL) {
dns_journal_destroy(&xfr->ixfr.journal);
}
if (xfr->done != NULL) {
(xfr->done)(xfr->zone,
xfr->expireoptset ? &xfr->expireopt : NULL,
result);
xfr->done = NULL;
}
xfr->shutdown_result = result;
}
dns_xfrin_detach(&xfr);
}
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db,
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_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,
.firstsoa = DNS_RDATA_INIT,
.edns = true,
.magic = XFRIN_MAGIC,
};
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);
atomic_init(&xfr->shuttingdown, false);
if (db != NULL) {
dns_db_attach(db, &xfr->db);
}
dns_diff_init(xfr->mctx, &xfr->diff);
if (reqtype == dns_rdatatype_soa) {
xfr->state = XFRST_SOAQUERY;
} else {
xfr->state = XFRST_INITIALSOA;
}
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);
isc_timer_create(dns_zone_getloop(zone), xfrin_timedout, xfr,
&xfr->max_time_timer);
isc_timer_create(dns_zone_getloop(zone), xfrin_idledout, xfr,
&xfr->max_idle_timer);
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);
/*
* Reuse an existing TCP connection if possible. For XoT, we can't
* do this because other connections could be using a different
* certificate, so we just create a new dispatch every time.
*/
if (xfr->transport == NULL ||
dns_transport_get_type(xfr->transport) == DNS_TRANSPORT_TCP)
{
dns_dispatchmgr_t *dispmgr = dns_view_getdispatchmgr(xfr->view);
if (dispmgr == NULL) {
result = ISC_R_SHUTTINGDOWN;
} else {
result = dns_dispatch_gettcp(dispmgr, &xfr->primaryaddr,
&xfr->sourceaddr,
&xfr->disp);
dns_dispatchmgr_detach(&dispmgr);
}
}
if (result == ISC_R_SUCCESS) {
char peer[ISC_SOCKADDR_FORMATSIZE];
isc_sockaddr_format(&xfr->primaryaddr, peer, sizeof(peer));
xfrin_log(xfr, ISC_LOG_DEBUG(1),
"attached to TCP connection to %s", peer);
} else {
dns_dispatchmgr_t *dispmgr = dns_view_getdispatchmgr(xfr->view);
if (dispmgr == NULL) {
result = ISC_R_SHUTTINGDOWN;
} else {
result = dns_dispatch_createtcp(
dispmgr, &xfr->sourceaddr, &xfr->primaryaddr,
&xfr->disp);
dns_dispatchmgr_detach(&dispmgr);
}
CHECK(result);
}
LIBDNS_XFRIN_START(xfr, xfr->info);
/* Set the maximum 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 */
isc_interval_set(&interval, dns_zone_getidlein(xfr->zone), 0);
isc_timer_start(xfr->max_idle_timer, isc_timertype_once, &interval);
/*
* XXX: timeouts are hard-coded to 30 seconds; this needs to be
* configurable.
*/
CHECK(dns_dispatch_add(xfr->disp, dns_zone_getloop(xfr->zone), 0, 30000,
&xfr->primaryaddr, xfr->transport,
xfr->tlsctx_cache, xfrin_connect_done,
xfrin_send_done, xfrin_recv_done, xfr, &xfr->id,
&xfr->dispentry));
CHECK(dns_dispatch_connect(xfr->dispentry));
return (ISC_R_SUCCESS);
failure:
if (xfr->dispentry != NULL) {
dns_dispatch_done(&xfr->dispentry);
}
if (xfr->disp != NULL) {
dns_dispatch_detach(&xfr->disp);
}
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_unref(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, 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));
}
xfr->nmsg = 0;
xfr->nrecs = 0;
xfr->nbytes = 0;
xfr->start = isc_time_now();
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_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, 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);
if (xfr->nmsg > 0) {
msg->tcp_continuation = 1;
}
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 &&
(xfr->state == XFRST_SOAQUERY ||
xfr->state == XFRST_INITIALSOA))
{
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;
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_DEBUG(3), "too many questions (%u)",
msg->counts[DNS_SECTION_QUESTION]);
result = DNS_R_FORMERR;
goto failure;
}
if ((xfr->state == XFRST_SOAQUERY || xfr->state == XFRST_INITIALSOA) &&
msg->counts[DNS_SECTION_QUESTION] != 1)
{
xfrin_log(xfr, ISC_LOG_DEBUG(3), "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)) {
result = DNS_R_FORMERR;
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"question name mismatch");
goto failure;
}
rds = ISC_LIST_HEAD(name->list);
INSIST(rds != NULL);
if (rds->type != xfr->reqtype) {
result = DNS_R_FORMERR;
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"question type mismatch");
goto failure;
}
if (rds->rdclass != xfr->rdclass) {
result = DNS_R_FORMERR;
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"question class mismatch");
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 &&
xfr->state == XFRST_INITIALSOA &&
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)
{
FAIL(DNS_R_NOTAUTHORITATIVE);
}
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) {
goto failure;
}
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 || xfr->nmsg == 0 ||
xfr->state == XFRST_AXFR_END ||
xfr->state == XFRST_IXFR_END)
{
result = DNS_R_EXPECTEDTSIG;
goto failure;
}
}
/*
* Update the number of messages received.
*/
xfr->nmsg++;
/*
* Update the number of bytes received.
*/
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 (xfr->state) {
case XFRST_GOTSOA:
xfr->reqtype = dns_rdatatype_axfr;
xfr->state = XFRST_INITIALSOA;
CHECK(xfrin_send_request(xfr));
break;
case XFRST_AXFR_END:
CHECK(axfr_finalize(xfr));
FALLTHROUGH;
case XFRST_IXFR_END:
/*
* Close the journal.
*/
if (xfr->ixfr.journal != NULL) {
LIBDNS_XFRIN_JOURNAL_DESTROY_BEGIN(xfr, xfr->info,
result);
dns_journal_destroy(&xfr->ixfr.journal);
LIBDNS_XFRIN_JOURNAL_DESTROY_END(xfr, xfr->info,
result);
}
/*
* Inform the caller we succeeded.
*/
if (xfr->done != NULL) {
LIBDNS_XFRIN_DONE_CALLBACK_BEGIN(xfr, xfr->info,
result);
(xfr->done)(xfr->zone,
xfr->expireoptset ? &xfr->expireopt : NULL,
ISC_R_SUCCESS);
xfr->done = NULL;
LIBDNS_XFRIN_DONE_CALLBACK_END(xfr, xfr->info, result);
}
atomic_store(&xfr->shuttingdown, true);
isc_timer_stop(xfr->max_time_timer);
xfr->shutdown_result = ISC_R_SUCCESS;
break;
default:
/*
* Read the next message.
*/
dns_message_detach(&msg);
dns_dispatch_getnext(xfr->dispentry);
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;
REQUIRE(VALID_XFRIN(xfr));
REQUIRE(dns_zone_gettid(xfr->zone) == isc_tid());
/* Safe-guards */
REQUIRE(atomic_load(&xfr->shuttingdown));
isc_refcount_destroy(&xfr->references);
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.
*/
xfr->end = isc_time_now();
msecs = isc_time_microdiff(&xfr->end, &xfr->start) / 1000;
if (msecs == 0) {
msecs = 1;
}
persec = (xfr->nbytes * 1000) / msecs;
xfrin_log(xfr, ISC_LOG_INFO,
"Transfer completed: %d messages, %d records, "
"%" PRIu64 " bytes, "
"%u.%03u secs (%u bytes/sec) (serial %u)",
xfr->nmsg, xfr->nrecs, xfr->nbytes,
(unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000),
(unsigned int)persec, xfr->end_serial);
if (xfr->dispentry != NULL) {
dns_dispatch_done(&xfr->dispentry);
}
if (xfr->disp != NULL) {
dns_dispatch_detach(&xfr->disp);
}
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);
}
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->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);
}
isc_timer_destroy(&xfr->max_idle_timer);
isc_timer_destroy(&xfr->max_time_timer);
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);
}