2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-23 02:28:55 +00:00
bind/lib/dns/xfrin.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1694 lines
42 KiB
C
Raw Normal View History

1999-08-20 05:35:16 +00:00
/*
2016-05-26 12:36:17 -07:00
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
1999-08-20 05:35:16 +00:00
*
* SPDX-License-Identifier: MPL-2.0
*
1999-08-20 05:35:16 +00:00
* 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/.
*
1999-08-20 05:35:16 +00:00
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
1999-08-20 05:35:16 +00:00
#include <inttypes.h>
#include <stdbool.h>
1999-08-20 05:35:16 +00:00
#include <isc/mem.h>
#include <isc/netmgr.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/util.h>
1999-08-20 05:35:16 +00:00
#include <dns/callbacks.h>
#include <dns/catz.h>
#include <dns/db.h>
#include <dns/diff.h>
#include <dns/events.h>
#include <dns/journal.h>
#include <dns/log.h>
#include <dns/message.h>
#include <dns/rdataclass.h>
1999-08-20 05:35:16 +00:00
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/result.h>
#include <dns/soa.h>
#include <dns/transport.h>
1999-09-10 15:01:04 +00:00
#include <dns/tsig.h>
#include <dns/view.h>
#include <dns/xfrin.h>
1999-10-14 01:37:00 +00:00
#include <dns/zone.h>
1999-08-20 05:35:16 +00:00
2000-06-02 18:59:33 +00:00
#include <dst/dst.h>
1999-08-20 05:35:16 +00:00
/*
* 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)
1999-08-20 05:35:16 +00:00
/*%
1999-08-20 05:35:16 +00:00
* 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,
1999-08-20 05:35:16 +00:00
XFRST_INITIALSOA,
XFRST_FIRSTDATA,
XFRST_IXFR_DELSOA,
XFRST_IXFR_DEL,
XFRST_IXFR_ADDSOA,
XFRST_IXFR_ADD,
XFRST_IXFR_END,
1999-08-20 05:35:16 +00:00
XFRST_AXFR,
XFRST_AXFR_END
1999-08-20 05:35:16 +00:00
} xfrin_state_t;
/*%
1999-08-20 05:35:16 +00:00
* Incoming zone transfer context.
*/
struct dns_xfrin_ctx {
unsigned int magic;
1999-08-20 05:35:16 +00:00
isc_mem_t *mctx;
dns_zone_t *zone;
1999-08-20 05:35:16 +00:00
isc_refcount_t references;
isc_nm_t *netmgr;
isc_refcount_t connects; /*%< Connect in progress */
isc_refcount_t sends; /*%< Send in progress */
isc_refcount_t recvs; /*%< Receive in progress */
1999-08-20 05:35:16 +00:00
atomic_bool shuttingdown;
isc_result_t shutdown_result;
dns_name_t name; /*%< Name of zone to transfer */
1999-08-20 05:35:16 +00:00
dns_rdataclass_t rdclass;
dns_messageid_t id;
/*%
1999-08-20 05:35:16 +00:00
* 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_dscp_t dscp;
1999-08-20 05:35:16 +00:00
isc_sockaddr_t primaryaddr;
isc_sockaddr_t sourceaddr;
isc_nmhandle_t *handle;
isc_nmhandle_t *readhandle;
isc_nmhandle_t *sendhandle;
1999-08-20 05:35:16 +00:00
/*% Buffer for IXFR/AXFR request message */
isc_buffer_t qbuffer;
1999-08-20 05:35:16 +00:00
unsigned char qbuffer_data[512];
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
/*%
* Whether the zone originally had a database attached at the time this
* transfer context was created. Used by xfrin_destroy() when making
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
* logging decisions.
*/
bool zone_had_db;
1999-08-20 05:35:16 +00:00
dns_db_t *db;
dns_dbversion_t *ver;
dns_diff_t diff; /*%< Pending database changes */
int difflen; /*%< Number of pending tuples */
1999-08-20 05:35:16 +00:00
xfrin_state_t state;
uint32_t end_serial;
bool is_ixfr;
1999-08-20 05:35:16 +00:00
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 */
1999-09-10 15:01:04 +00:00
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;
1999-09-10 15:01:04 +00:00
/*%
1999-08-20 05:35:16 +00:00
* 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;
1999-08-20 05:35:16 +00:00
struct {
uint32_t request_serial;
uint32_t current_serial;
2000-09-19 01:44:15 +00:00
dns_journal_t *journal;
1999-08-20 05:35:16 +00:00
} ixfr;
dns_rdata_t firstsoa;
unsigned char *firstsoa_data;
isc_tlsctx_cache_t *tlsctx_cache;
1999-08-20 05:35:16 +00:00
};
#define XFRIN_MAGIC ISC_MAGIC('X', 'f', 'r', 'I')
#define VALID_XFRIN(x) ISC_MAGIC_VALID(x, XFRIN_MAGIC)
1999-08-20 05:35:16 +00:00
/**************************************************************************/
/*
* Forward declarations.
*/
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
dns_tsigkey_t *tsigkey, dns_transport_t *transport,
isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp);
static isc_result_t
axfr_init(dns_xfrin_ctx_t *xfr);
static isc_result_t
axfr_makedb(dns_xfrin_ctx_t *xfr, dns_db_t **dbp);
static isc_result_t
axfr_putdata(dns_xfrin_ctx_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_ctx_t *xfr);
static isc_result_t
axfr_commit(dns_xfrin_ctx_t *xfr);
static isc_result_t
axfr_finalize(dns_xfrin_ctx_t *xfr);
2020-02-14 08:14:03 +01:00
static isc_result_t
ixfr_init(dns_xfrin_ctx_t *xfr);
static isc_result_t
ixfr_apply(dns_xfrin_ctx_t *xfr);
static isc_result_t
ixfr_putdata(dns_xfrin_ctx_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_ctx_t *xfr);
2020-02-14 08:14:03 +01:00
static isc_result_t
xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
dns_rdata_t *rdata);
2020-02-14 08:14:03 +01:00
static isc_result_t
xfrin_start(dns_xfrin_ctx_t *xfr);
2020-02-14 08:14:03 +01:00
1999-08-20 05:35:16 +00:00
static void
xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
static isc_result_t
xfrin_send_request(dns_xfrin_ctx_t *xfr);
1999-08-20 05:35:16 +00:00
static void
xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg);
1999-08-20 05:35:16 +00:00
static void
xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
isc_region_t *region, void *cbarg);
2020-02-14 08:14:03 +01:00
static void
xfrin_destroy(dns_xfrin_ctx_t *xfr);
2020-02-14 08:14:03 +01:00
static void
xfrin_fail(dns_xfrin_ctx_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);
2020-02-14 08:14:03 +01:00
static void
xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
const char *fmt, va_list ap) ISC_FORMAT_PRINTF(4, 0);
2020-02-14 08:14:03 +01:00
static void
xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
const char *fmt, ...) ISC_FORMAT_PRINTF(4, 5);
2020-02-14 08:14:03 +01:00
static void
xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...)
ISC_FORMAT_PRINTF(3, 4);
1999-08-20 05:35:16 +00:00
/**************************************************************************/
/*
* AXFR handling
*/
1999-08-20 05:35:16 +00:00
static isc_result_t
axfr_init(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
2000-06-01 18:04:37 +00:00
xfr->is_ixfr = false;
1999-08-20 05:35:16 +00:00
1999-10-14 01:37:00 +00:00
if (xfr->db != NULL) {
dns_db_detach(&xfr->db);
}
1999-10-14 01:37:00 +00:00
1999-08-20 05:35:16 +00:00
CHECK(axfr_makedb(xfr, &xfr->db));
dns_rdatacallbacks_init(&xfr->axfr);
CHECK(dns_db_beginload(xfr->db, &xfr->axfr));
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
static isc_result_t
axfr_makedb(dns_xfrin_ctx_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);
1999-08-20 05:35:16 +00:00
}
static isc_result_t
axfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
1999-08-20 05:35:16 +00:00
dns_ttl_t ttl, dns_rdata_t *rdata) {
isc_result_t result;
2000-06-01 18:04:37 +00:00
1999-08-20 05:35:16 +00:00
dns_difftuple_t *tuple = NULL;
if (rdata->rdclass != xfr->rdclass) {
return (DNS_R_BADCLASS);
}
CHECK(dns_zone_checknames(xfr->zone, name, rdata));
1999-08-24 06:43:19 +00:00
CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
&tuple));
1999-08-20 05:35:16 +00:00
dns_diff_append(&xfr->diff, &tuple);
if (++xfr->difflen > 100) {
CHECK(axfr_apply(xfr));
}
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
/*
* Store a set of AXFR RRs in the database.
*/
static isc_result_t
axfr_apply(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
uint64_t records;
2000-06-01 18:04:37 +00:00
CHECK(dns_diff_load(&xfr->diff, xfr->axfr.add, xfr->axfr.add_private));
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
static isc_result_t
axfr_commit(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
1999-08-20 05:35:16 +00:00
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_ctx_t *xfr) {
isc_result_t result;
CHECK(dns_zone_replacedb(xfr->zone, xfr->db, true));
1999-08-20 05:35:16 +00:00
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
/**************************************************************************/
/*
* IXFR handling
*/
1999-08-20 05:35:16 +00:00
static isc_result_t
ixfr_init(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
char *journalfile = NULL;
2000-06-01 18:04:37 +00:00
2000-07-03 22:42:36 +00:00
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;
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
static isc_result_t
ixfr_putdata(dns_xfrin_ctx_t *xfr, dns_diffop_t op, dns_name_t *name,
1999-08-20 05:35:16 +00:00
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));
}
1999-08-24 06:43:19 +00:00
CHECK(dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata,
&tuple));
dns_diff_append(&xfr->diff, &tuple);
1999-08-20 05:35:16 +00:00
if (++xfr->difflen > 100) {
CHECK(ixfr_apply(xfr));
}
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
/*
* Apply a set of IXFR changes to the database.
*/
static isc_result_t
ixfr_apply(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
uint64_t records;
2000-06-01 18:04:37 +00:00
1999-08-20 05:35:16 +00:00
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));
}
1999-08-20 05:35:16 +00:00
}
2000-12-11 19:24:30 +00:00
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;
}
}
1999-08-20 05:35:16 +00:00
dns_diff_clear(&xfr->diff);
xfr->difflen = 0;
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
static isc_result_t
ixfr_commit(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
2000-06-01 18:04:37 +00:00
CHECK(ixfr_apply(xfr));
1999-08-20 05:35:16 +00:00
if (xfr->ver != NULL) {
CHECK(dns_zone_verifydb(xfr->zone, xfr->db, xfr->ver));
1999-08-20 05:35:16 +00:00
/* 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);
1999-08-20 05:35:16 +00:00
}
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
return (result);
}
/**************************************************************************/
/*
* Common AXFR/IXFR protocol code
*/
1999-08-20 05:35:16 +00:00
/*
* Handle a single incoming resource record according to the current
* state.
*/
static isc_result_t
xfr_rr(dns_xfrin_ctx_t *xfr, dns_name_t *name, uint32_t ttl,
dns_rdata_t *rdata) {
isc_result_t result;
2000-06-01 18:04:37 +00:00
xfr->nrecs++;
2008-09-25 04:12:39 +00:00
if (rdata->type == dns_rdatatype_none ||
2022-11-02 19:33:14 +01:00
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 &&
2022-11-02 19:33:14 +01:00
!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);
}
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
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);
}
1999-08-20 05:35:16 +00:00
/*
2004-06-27 10:10:55 +00:00
* Remember the serial number in the initial SOA.
1999-08-20 05:35:16 +00:00
* 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))
{
1999-08-20 05:35:16 +00:00
/*
1999-08-24 06:43:19 +00:00
* This must be the single SOA record that is
* sent when the current version on the primary
1999-08-24 06:43:19 +00:00
* is not newer than the version in the request.
1999-08-20 05:35:16 +00:00
*/
2000-01-26 21:12:04 +00:00
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requested serial %u, "
"primary has %u, not updating",
xfr->ixfr.request_serial, xfr->end_serial);
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
xfr->state = XFRST_FIRSTDATA;
break;
1999-08-20 05:35:16 +00:00
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");
1999-08-20 05:35:16 +00:00
CHECK(ixfr_init(xfr));
xfr->state = XFRST_IXFR_DELSOA;
} else {
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"got nonincremental response");
1999-08-20 05:35:16 +00:00
CHECK(axfr_init(xfr));
xfr->state = XFRST_AXFR;
}
goto redo;
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
case XFRST_IXFR_DEL:
if (rdata->type == dns_rdatatype_soa) {
uint32_t soa_serial = dns_soa_getserial(rdata);
1999-08-20 05:35:16 +00:00
xfr->state = XFRST_IXFR_ADDSOA;
xfr->ixfr.current_serial = soa_serial;
1999-08-20 05:35:16 +00:00
goto redo;
}
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_DEL, name, ttl, rdata));
break;
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
case XFRST_IXFR_ADD:
if (rdata->type == dns_rdatatype_soa) {
uint32_t soa_serial = dns_soa_getserial(rdata);
1999-08-20 05:35:16 +00:00
if (soa_serial == xfr->end_serial) {
CHECK(ixfr_commit(xfr));
xfr->state = XFRST_IXFR_END;
1999-08-20 05:35:16 +00:00
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);
1999-08-20 05:35:16 +00:00
} else {
CHECK(ixfr_commit(xfr));
1999-08-20 05:35:16 +00:00
xfr->state = XFRST_IXFR_DELSOA;
goto redo;
}
}
if (rdata->type == dns_rdatatype_ns &&
2022-11-02 19:33:14 +01:00
dns_name_iswildcard(name))
{
FAIL(DNS_R_INVALIDNS);
}
1999-08-20 05:35:16 +00:00
CHECK(ixfr_putdata(xfr, DNS_DIFFOP_ADD, name, ttl, rdata));
break;
case XFRST_AXFR:
/*
2001-06-07 20:11:30 +00:00
* 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;
}
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
break;
}
break;
case XFRST_AXFR_END:
case XFRST_IXFR_END:
1999-08-20 05:35:16 +00:00
FAIL(DNS_R_EXTRADATA);
FALLTHROUGH;
1999-08-20 05:35:16 +00:00
default:
UNREACHABLE();
1999-08-20 05:35:16 +00:00
}
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
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, isc_dscp_t dscp,
dns_tsigkey_t *tsigkey, dns_transport_t *transport,
isc_tlsctx_cache_t *tlsctx_cache, isc_mem_t *mctx,
isc_nm_t *netmgr, dns_xfrindone_t done,
dns_xfrin_ctx_t **xfrp) {
dns_name_t *zonename = dns_zone_getorigin(zone);
2005-11-03 22:59:53 +00:00
dns_xfrin_ctx_t *xfr = NULL;
isc_result_t result;
dns_db_t *db = NULL;
1999-08-20 05:35:16 +00:00
REQUIRE(xfrp != NULL && *xfrp == NULL);
REQUIRE(done != NULL);
REQUIRE(isc_sockaddr_getport(primaryaddr) != 0);
2000-06-01 18:04:37 +00:00
(void)dns_zone_getdb(zone, &db);
if (xfrtype == dns_rdatatype_soa || xfrtype == dns_rdatatype_ixfr) {
REQUIRE(db != NULL);
}
xfrin_create(mctx, zone, db, netmgr, zonename, dns_zone_getclass(zone),
xfrtype, primaryaddr, sourceaddr, dscp, tsigkey, transport,
tlsctx_cache, &xfr);
1999-08-20 05:35:16 +00:00
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
if (db != NULL) {
xfr->zone_had_db = true;
}
xfr->done = done;
isc_refcount_init(&xfr->references, 1);
/*
* Set *xfrp now, before calling xfrin_start(). Asynchronous
* netmgr processing could cause the 'done' callback to run in
* another thread before we reached the end of the present
* function. In that case, if *xfrp hadn't already been
* attached, the 'done' function would be unable to detach it.
*/
*xfrp = xfr;
result = xfrin_start(xfr);
if (result != ISC_R_SUCCESS) {
atomic_store(&xfr->shuttingdown, true);
xfr->shutdown_result = result;
dns_xfrin_detach(xfrp);
}
if (db != NULL) {
dns_db_detach(&db);
}
if (result != ISC_R_SUCCESS) {
char zonetext[DNS_NAME_MAXTEXT + 32];
dns_zone_name(zone, zonetext, sizeof(zonetext));
xfrin_log1(ISC_LOG_ERROR, zonetext, primaryaddr,
"zone transfer setup failed");
}
return (result);
}
static void
xfrin_cancelio(dns_xfrin_ctx_t *xfr);
void
dns_xfrin_shutdown(dns_xfrin_ctx_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
xfrin_fail(xfr, ISC_R_CANCELED, "shut down");
}
void
dns_xfrin_attach(dns_xfrin_ctx_t *source, dns_xfrin_ctx_t **target) {
REQUIRE(VALID_XFRIN(source));
REQUIRE(target != NULL && *target == NULL);
(void)isc_refcount_increment(&source->references);
*target = source;
}
void
dns_xfrin_detach(dns_xfrin_ctx_t **xfrp) {
dns_xfrin_ctx_t *xfr = NULL;
REQUIRE(xfrp != NULL && VALID_XFRIN(*xfrp));
xfr = *xfrp;
*xfrp = NULL;
if (isc_refcount_decrement(&xfr->references) == 1) {
xfrin_destroy(xfr);
}
1999-08-20 05:35:16 +00:00
}
static void
xfrin_cancelio(dns_xfrin_ctx_t *xfr) {
if (xfr->readhandle == NULL) {
return;
}
isc_nm_cancelread(xfr->readhandle);
/* The xfr->readhandle detach will happen in xfrin_recv_done callback */
}
static void
xfrin_reset(dns_xfrin_ctx_t *xfr) {
REQUIRE(VALID_XFRIN(xfr));
xfrin_log(xfr, ISC_LOG_INFO, "resetting");
REQUIRE(xfr->readhandle == NULL);
REQUIRE(xfr->sendhandle == NULL);
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_ctx_t *xfr, isc_result_t result, const char *msg) {
/* Make sure only the first xfrin_fail() trumps */
if (atomic_compare_exchange_strong(&xfr->shuttingdown, &(bool){ false },
2022-11-02 19:33:14 +01:00
true))
{
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, result);
xfr->done = NULL;
}
xfr->shutdown_result = result;
}
1999-08-20 05:35:16 +00:00
}
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_nm_t *netmgr,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, isc_dscp_t dscp,
dns_tsigkey_t *tsigkey, dns_transport_t *transport,
isc_tlsctx_cache_t *tlsctx_cache, dns_xfrin_ctx_t **xfrp) {
dns_xfrin_ctx_t *xfr = NULL;
1999-08-20 05:35:16 +00:00
xfr = isc_mem_get(mctx, sizeof(*xfr));
*xfr = (dns_xfrin_ctx_t){ .netmgr = netmgr,
.shutdown_result = ISC_R_UNSET,
.rdclass = rdclass,
.reqtype = reqtype,
.dscp = dscp,
.id = (dns_messageid_t)isc_random16(),
.maxrecords = dns_zone_getmaxrecords(zone),
.primaryaddr = *primaryaddr,
.sourceaddr = *sourceaddr,
.firstsoa = DNS_RDATA_INIT };
isc_mem_attach(mctx, &xfr->mctx);
dns_zone_iattach(zone, &xfr->zone);
1999-08-20 05:35:16 +00:00
dns_name_init(&xfr->name, NULL);
isc_refcount_init(&xfr->connects, 0);
isc_refcount_init(&xfr->sends, 0);
isc_refcount_init(&xfr->recvs, 0);
atomic_init(&xfr->shuttingdown, false);
if (db != NULL) {
dns_db_attach(db, &xfr->db);
}
1999-08-20 05:35:16 +00:00
dns_diff_init(xfr->mctx, &xfr->diff);
if (reqtype == dns_rdatatype_soa) {
xfr->state = XFRST_SOAQUERY;
} else {
xfr->state = XFRST_INITIALSOA;
}
1999-09-10 15:01:04 +00:00
isc_time_now(&xfr->start);
1999-09-10 15:01:04 +00:00
if (tsigkey != NULL) {
dns_tsigkey_attach(tsigkey, &xfr->tsigkey);
}
1999-08-20 05:35:16 +00:00
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);
1999-08-20 05:35:16 +00:00
isc_tlsctx_cache_attach(tlsctx_cache, &xfr->tlsctx_cache);
xfr->magic = XFRIN_MAGIC;
1999-08-20 05:35:16 +00:00
*xfrp = xfr;
}
static isc_result_t
xfrin_start(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
dns_xfrin_ctx_t *connect_xfr = NULL;
dns_transport_type_t transport_type = DNS_TRANSPORT_TCP;
isc_tlsctx_t *tlsctx = NULL;
isc_tlsctx_client_session_cache_t *sess_cache = NULL;
(void)isc_refcount_increment0(&xfr->connects);
dns_xfrin_attach(xfr, &connect_xfr);
if (xfr->transport != NULL) {
transport_type = dns_transport_get_type(xfr->transport);
}
/*
* XXX: timeouts are hard-coded to 30 seconds; this needs to be
* configurable.
*/
switch (transport_type) {
case DNS_TRANSPORT_TCP:
isc_nm_streamdnsconnect(xfr->netmgr, &xfr->sourceaddr,
&xfr->primaryaddr, xfrin_connect_done,
connect_xfr, 30000, NULL, NULL);
break;
case DNS_TRANSPORT_TLS: {
result = dns_transport_get_tlsctx(
xfr->transport, &xfr->primaryaddr, xfr->tlsctx_cache,
xfr->mctx, &tlsctx, &sess_cache);
if (result != ISC_R_SUCCESS) {
goto failure;
}
INSIST(tlsctx != NULL);
isc_nm_streamdnsconnect(xfr->netmgr, &xfr->sourceaddr,
&xfr->primaryaddr, xfrin_connect_done,
connect_xfr, 30000, tlsctx, sess_cache);
} break;
default:
UNREACHABLE();
}
return (ISC_R_SUCCESS);
failure:
isc_refcount_decrement0(&xfr->connects);
dns_xfrin_detach(&connect_xfr);
return (result);
1999-08-20 05:35:16 +00:00
}
/* 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;
2000-06-01 18:04:37 +00:00
dns_compress_init(&cctx, mctx, 0);
CHECK(dns_message_renderbegin(msg, &cctx, buf));
1999-12-22 03:22:59 +00:00
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));
1999-08-20 05:35:16 +00:00
CHECK(dns_message_renderend(msg));
result = ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
failure:
dns_compress_invalidate(&cctx);
1999-08-20 05:35:16 +00:00
return (result);
}
/*
* A connection has been established.
1999-08-20 05:35:16 +00:00
*/
static void
xfrin_connect_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
char sourcetext[ISC_SOCKADDR_FORMATSIZE];
char signerbuf[DNS_NAME_FORMATSIZE];
const char *signer = "", *sep = "";
isc_sockaddr_t sockaddr;
dns_zonemgr_t *zmgr = NULL;
isc_time_t now;
REQUIRE(VALID_XFRIN(xfr));
isc_refcount_decrement0(&xfr->connects);
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
CHECK(result);
if (!isc_nm_xfr_allowed(handle)) {
goto failure;
}
zmgr = dns_zone_getmgr(xfr->zone);
if (zmgr != NULL) {
if (result != ISC_R_SUCCESS) {
TIME_NOW(&now);
dns_zonemgr_unreachableadd(zmgr, &xfr->primaryaddr,
&xfr->sourceaddr, &now);
CHECK(result);
} else {
dns_zonemgr_unreachabledel(zmgr, &xfr->primaryaddr,
&xfr->sourceaddr);
}
}
xfr->handle = handle;
sockaddr = isc_nmhandle_peeraddr(handle);
isc_sockaddr_format(&sockaddr, sourcetext, sizeof(sourcetext));
/* TODO set DSCP */
if (xfr->tsigkey != NULL && xfr->tsigkey->key != NULL) {
dns_name_format(dst_key_name(xfr->tsigkey->key), signerbuf,
sizeof(signerbuf));
sep = " TSIG ";
signer = signerbuf;
}
xfrin_log(xfr, ISC_LOG_INFO, "connected using %s%s%s", sourcetext, sep,
signer);
CHECK(xfrin_send_request(xfr));
failure:
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed to connect");
}
dns_xfrin_detach(&xfr); /* connect_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;
}
/*
* Build an *XFR request and send its length prefix.
*/
static isc_result_t
xfrin_send_request(dns_xfrin_ctx_t *xfr) {
isc_result_t result;
1999-08-20 05:35:16 +00:00
isc_region_t region;
dns_rdataset_t *qrdataset = NULL;
1999-08-20 05:35:16 +00:00
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;
dns_xfrin_ctx_t *send_xfr = NULL;
/* Create the request message */
dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTRENDER, &msg);
2000-11-03 21:36:37 +00:00
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);
2000-11-03 21:36:37 +00:00
qrdataset = NULL;
dns_message_addname(msg, qname, DNS_SECTION_QUESTION);
2000-11-03 21:36:37 +00:00
qname = NULL;
1999-08-20 05:35:16 +00:00
if (xfr->reqtype == dns_rdatatype_ixfr) {
/* Get the SOA and add it to the authority section. */
1999-08-20 05:35:16 +00:00
/* XXX is using the current version the right thing? */
dns_db_currentversion(xfr->db, &ver);
CHECK(dns_db_createsoatuple(xfr->db, ver, xfr->mctx,
DNS_DIFFOP_EXISTS, &soatuple));
1999-08-20 05:35:16 +00:00
xfr->ixfr.request_serial = dns_soa_getserial(&soatuple->rdata);
xfr->ixfr.current_serial = xfr->ixfr.request_serial;
2000-01-26 21:12:04 +00:00
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"requesting IXFR for serial %u",
xfr->ixfr.request_serial);
1999-08-20 05:35:16 +00:00
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));
}
1999-08-20 05:35:16 +00:00
xfr->id++;
xfr->nmsg = 0;
xfr->nrecs = 0;
xfr->nbytes = 0;
isc_time_now(&xfr->start);
msg->id = xfr->id;
if (xfr->tsigctx != NULL) {
dst_context_destroy(&xfr->tsigctx);
}
1999-09-10 15:01:04 +00:00
CHECK(render(msg, xfr->mctx, &xfr->qbuffer));
1999-08-20 05:35:16 +00:00
/*
* 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));
1999-09-10 15:01:04 +00:00
isc_buffer_usedregion(&xfr->qbuffer, &region);
1999-08-20 05:35:16 +00:00
INSIST(region.length <= 65535);
dns_xfrin_attach(xfr, &send_xfr);
isc_nmhandle_attach(send_xfr->handle, &xfr->sendhandle);
isc_refcount_increment0(&send_xfr->sends);
isc_nm_send(xfr->handle, &region, xfrin_send_done, send_xfr);
1999-08-20 05:35:16 +00:00
failure:
dns_message_detach(&msg);
1999-08-20 05:35:16 +00:00
if (soatuple != NULL) {
dns_difftuple_free(&soatuple);
}
if (ver != NULL) {
dns_db_closeversion(xfr->db, &ver, false);
}
return (result);
1999-08-20 05:35:16 +00:00
}
static void
xfrin_send_done(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
dns_xfrin_ctx_t *recv_xfr = NULL;
1999-08-20 05:35:16 +00:00
REQUIRE(VALID_XFRIN(xfr));
isc_refcount_decrement0(&xfr->sends);
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
CHECK(result);
xfrin_log(xfr, ISC_LOG_DEBUG(3), "sent request data");
1999-08-20 05:35:16 +00:00
dns_xfrin_attach(xfr, &recv_xfr);
isc_nmhandle_attach(handle, &recv_xfr->readhandle);
isc_refcount_increment0(&recv_xfr->recvs);
isc_nm_read(recv_xfr->handle, xfrin_recv_done, recv_xfr);
failure:
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed sending request data");
}
isc_nmhandle_detach(&xfr->sendhandle);
dns_xfrin_detach(&xfr); /* send_xfr */
1999-08-20 05:35:16 +00:00
}
static void
xfrin_recv_done(isc_nmhandle_t *handle, isc_result_t result,
isc_region_t *region, void *cbarg) {
dns_xfrin_ctx_t *xfr = (dns_xfrin_ctx_t *)cbarg;
1999-08-20 05:35:16 +00:00
dns_message_t *msg = NULL;
dns_name_t *name = NULL;
const dns_name_t *tsigowner = NULL;
isc_buffer_t buffer;
isc_sockaddr_t peer;
REQUIRE(VALID_XFRIN(xfr));
isc_refcount_decrement0(&xfr->recvs);
if (atomic_load(&xfr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
}
1999-09-10 15:01:04 +00:00
CHECK(result);
1999-08-20 05:35:16 +00:00
xfrin_log(xfr, ISC_LOG_DEBUG(7), "received %u bytes", region->length);
dns_message_create(xfr->mctx, DNS_MESSAGE_INTENTPARSE, &msg);
1999-09-10 15:01:04 +00:00
CHECK(dns_message_settsigkey(msg, xfr->tsigkey));
dns_message_setquerytsig(msg, xfr->lasttsig);
1999-09-10 15:01:04 +00:00
msg->tsigctx = xfr->tsigctx;
xfr->tsigctx = NULL;
2008-07-28 23:47:22 +00:00
dns_message_setclass(msg, xfr->rdclass);
1999-09-10 15:01:04 +00:00
if (xfr->nmsg > 0) {
msg->tcp_continuation = 1;
}
1999-09-10 15:01:04 +00:00
isc_buffer_init(&buffer, region->base, region->length);
isc_buffer_add(&buffer, region->length);
peer = isc_nmhandle_peeraddr(handle);
result = dns_message_parse(msg, &buffer,
DNS_MESSAGEPARSE_PRESERVEORDER);
2012-02-22 05:03:39 +00:00
if (result == ISC_R_SUCCESS) {
dns_message_logpacket(msg, "received message from", &peer,
DNS_LOGCATEGORY_XFER_IN,
DNS_LOGMODULE_XFER_IN, ISC_LOG_DEBUG(10),
xfr->mctx);
2012-02-22 05:03:39 +00:00
} else {
xfrin_log(xfr, ISC_LOG_DEBUG(10), "dns_message_parse: %s",
isc_result_totext(result));
}
2012-02-22 05:03:39 +00:00
if (result != ISC_R_SUCCESS || msg->rcode != dns_rcode_noerror ||
msg->opcode != dns_opcode_query || msg->rdclass != xfr->rdclass ||
msg->id != xfr->id)
{
if (result == ISC_R_SUCCESS && msg->rcode != dns_rcode_noerror)
{
result = dns_result_fromrcode(msg->rcode);
} else if (result == ISC_R_SUCCESS &&
2022-11-02 19:33:14 +01:00
msg->opcode != dns_opcode_query)
{
result = DNS_R_UNEXPECTEDOPCODE;
} else if (result == ISC_R_SUCCESS &&
2022-11-02 19:33:14 +01:00
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 ||
2022-11-02 19:33:14 +01:00
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:
isc_nmhandle_detach(&xfr->readhandle);
dns_message_detach(&msg);
xfrin_reset(xfr);
xfr->reqtype = dns_rdatatype_soa;
xfr->state = XFRST_SOAQUERY;
result = xfrin_start(xfr);
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed setting up socket");
}
dns_xfrin_detach(&xfr); /* recv_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;
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 &&
2022-11-02 19:33:14 +01:00
(msg->flags & DNS_MESSAGEFLAG_AA) == 0)
{
FAIL(DNS_R_NOTAUTHORITATIVE);
}
result = dns_message_checksig(msg, dns_zone_getview(xfr->zone));
if (result != ISC_R_SUCCESS) {
xfrin_log(xfr, ISC_LOG_DEBUG(3), "TSIG check failed: %s",
isc_result_totext(result));
goto failure;
}
1999-08-20 05:35:16 +00:00
for (result = dns_message_firstname(msg, DNS_SECTION_ANSWER);
result == ISC_R_SUCCESS;
1999-08-20 05:35:16 +00:00
result = dns_message_nextname(msg, DNS_SECTION_ANSWER))
{
dns_rdataset_t *rds = NULL;
1999-08-20 05:35:16 +00:00
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;
1999-08-20 05:35:16 +00:00
result = dns_rdataset_next(rds))
{
dns_rdata_t rdata = DNS_RDATA_INIT;
1999-08-20 05:35:16 +00:00
dns_rdataset_current(rds, &rdata);
CHECK(xfr_rr(xfr, name, rds->ttl, &rdata));
}
}
}
if (result != ISC_R_NOMORE) {
1999-08-20 05:35:16 +00:00
goto failure;
}
1999-09-10 15:01:04 +00:00
if (dns_message_gettsig(msg, &tsigowner) != NULL) {
/*
* Reset the counter.
*/
1999-09-10 15:01:04 +00:00
xfr->sincetsig = 0;
/*
* Free the last tsig, if there is one.
*/
if (xfr->lasttsig != NULL) {
isc_buffer_free(&xfr->lasttsig);
}
1999-09-10 15:01:04 +00:00
/*
* Update the last tsig pointer.
*/
2000-06-23 00:29:20 +00:00
CHECK(dns_message_getquerytsig(msg, xfr->mctx, &xfr->lasttsig));
} else if (dns_message_gettsigkey(msg) != NULL) {
1999-09-10 15:01:04 +00:00
xfr->sincetsig++;
if (xfr->sincetsig > 100 || xfr->nmsg == 0 ||
xfr->state == XFRST_AXFR_END ||
xfr->state == XFRST_IXFR_END)
{
1999-09-10 15:01:04 +00:00
result = DNS_R_EXPECTEDTSIG;
goto failure;
}
}
/*
* Update the number of messages received.
*/
1999-09-10 15:01:04 +00:00
xfr->nmsg++;
/*
* Update the number of bytes received.
*/
xfr->nbytes += buffer.used;
/*
* Take the context back.
*/
INSIST(xfr->tsigctx == NULL);
1999-09-10 15:01:04 +00:00
xfr->tsigctx = msg->tsigctx;
msg->tsigctx = NULL;
1999-09-10 15:01:04 +00:00
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) {
dns_journal_destroy(&xfr->ixfr.journal);
}
/*
* Inform the caller we succeeded.
*/
if (xfr->done != NULL) {
(xfr->done)(xfr->zone, ISC_R_SUCCESS);
xfr->done = NULL;
}
atomic_store(&xfr->shuttingdown, true);
xfr->shutdown_result = ISC_R_SUCCESS;
break;
default:
/*
* Read the next message.
*/
/* The readhandle is still attached */
/* The recv_xfr is still attached */
dns_message_detach(&msg);
isc_refcount_increment0(&xfr->recvs);
isc_nm_read(xfr->handle, xfrin_recv_done, xfr);
return;
1999-08-20 05:35:16 +00:00
}
1999-08-20 05:35:16 +00:00
failure:
if (result != ISC_R_SUCCESS) {
xfrin_fail(xfr, result, "failed while receiving responses");
}
if (msg != NULL) {
dns_message_detach(&msg);
}
isc_nmhandle_detach(&xfr->readhandle);
dns_xfrin_detach(&xfr); /* recv_xfr */
1999-08-20 05:35:16 +00:00
}
static void
xfrin_destroy(dns_xfrin_ctx_t *xfr) {
uint64_t msecs;
uint64_t persec;
const char *result_str;
REQUIRE(VALID_XFRIN(xfr));
/* Safe-guards */
REQUIRE(atomic_load(&xfr->shuttingdown));
isc_refcount_destroy(&xfr->references);
isc_refcount_destroy(&xfr->connects);
isc_refcount_destroy(&xfr->recvs);
isc_refcount_destroy(&xfr->sends);
1999-08-20 05:35:16 +00:00
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.
*/
result_str = isc_result_totext(xfr->shutdown_result);
xfrin_log(xfr, ISC_LOG_INFO, "Transfer status: %s", result_str);
/*
* Calculate the length of time the transfer took,
* and print a log message with the bytes and rate.
*/
isc_time_now(&xfr->end);
2007-12-02 23:47:01 +00:00
msecs = isc_time_microdiff(&xfr->end, &xfr->start) / 1000;
if (msecs == 0) {
msecs = 1;
}
persec = (xfr->nbytes * 1000) / msecs;
2008-07-22 23:47:04 +00:00
xfrin_log(xfr, ISC_LOG_INFO,
"Transfer completed: %d messages, %d records, "
"%" PRIu64 " bytes, "
"%u.%03u secs (%u bytes/sec) (serial %u)",
2007-12-02 23:47:01 +00:00
xfr->nmsg, xfr->nrecs, xfr->nbytes,
(unsigned int)(msecs / 1000), (unsigned int)(msecs % 1000),
(unsigned int)persec, xfr->end_serial);
if (xfr->readhandle != NULL) {
isc_nmhandle_detach(&xfr->readhandle);
}
if (xfr->sendhandle != NULL) {
isc_nmhandle_detach(&xfr->sendhandle);
}
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);
}
1999-08-20 05:35:16 +00:00
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);
}
1999-08-20 05:35:16 +00:00
if (xfr->tsigctx != NULL) {
dst_context_destroy(&xfr->tsigctx);
}
if (xfr->name.attributes.dynamic) {
1999-08-20 05:35:16 +00:00
dns_name_free(&xfr->name, xfr->mctx);
}
1999-08-20 05:35:16 +00:00
if (xfr->ver != NULL) {
dns_db_closeversion(xfr->db, &xfr->ver, false);
}
1999-08-20 05:35:16 +00:00
if (xfr->db != NULL) {
1999-10-14 01:37:00 +00:00
dns_db_detach(&xfr->db);
}
1999-08-20 05:35:16 +00:00
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
if (xfr->zone != NULL) {
if (!xfr->zone_had_db &&
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
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);
Log a message when a transferred mirror zone comes into effect Log a message when a mirror zone is successfully transferred and verified, but only if no database for that zone was yet loaded at the time the transfer was initiated. This could have been implemented in a simpler manner, e.g. by modifying zone_replacedb(), but (due to the calling order of the functions involved in finalizing a zone transfer) that would cause the resulting logs to suggest that a mirror zone comes into effect before its transfer is finished, which would be confusing given the nature of mirror zones and the fact that no message is logged upon successful mirror zone verification. Once the dns_zone_replacedb() call in axfr_finalize() is made, it becomes impossible to determine whether the transferred zone had a database attached before the transfer was started. Thus, that check is instead performed when the transfer context is first created and the result of this check is passed around in a field of the transfer context structure. If it turns out to be desired, the relevant log message is then emitted just before the transfer context is freed. Taking this approach means that the log message added by this commit is not timed precisely, i.e. mirror zone data may be used before this message is logged. However, that can only be fixed by logging the message inside zone_replacedb(), which causes arguably more dire issues discussed above. dns_zone_isloaded() is not used to double-check that transferred zone data was correctly loaded since the 'shutdown_result' field of the zone transfer context will not be set to ISC_R_SUCCESS unless axfr_finalize() succeeds (and that in turn will not happen unless dns_zone_replacedb() succeeds).
2019-01-16 15:31:48 +01:00
}
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_mem_putanddetach(&xfr->mctx, xfr, sizeof(*xfr));
1999-08-20 05:35:16 +00:00
}
/*
* Log incoming zone transfer messages in a format like
* transfer of <zone> from <address>: <message>
*/
static void
xfrin_logv(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
const char *fmt, va_list ap) {
char primarytext[ISC_SOCKADDR_FORMATSIZE];
2000-05-26 00:38:12 +00:00
char msgtext[2048];
isc_sockaddr_format(primaryaddr, primarytext, sizeof(primarytext));
2000-05-26 00:38:12 +00:00
vsnprintf(msgtext, sizeof(msgtext), fmt, ap);
isc_log_write(dns_lctx, DNS_LOGCATEGORY_XFER_IN, DNS_LOGMODULE_XFER_IN,
level, "transfer of '%s' from %s: %s", zonetext,
primarytext, msgtext);
}
/*
* Logging function for use when a xfrin_ctx_t has not yet been created.
*/
static void
xfrin_log1(int level, const char *zonetext, const isc_sockaddr_t *primaryaddr,
const char *fmt, ...) {
2000-12-11 19:24:30 +00:00
va_list ap;
2000-06-01 18:04:37 +00:00
if (!isc_log_wouldlog(dns_lctx, level)) {
return;
}
va_start(ap, fmt);
xfrin_logv(level, zonetext, primaryaddr, fmt, ap);
va_end(ap);
}
/*
* Logging function for use when there is a xfrin_ctx_t.
*/
static void
xfrin_log(dns_xfrin_ctx_t *xfr, int level, const char *fmt, ...) {
2000-12-11 19:24:30 +00:00
va_list ap;
char zonetext[DNS_NAME_MAXTEXT + 32];
2000-06-01 18:04:37 +00:00
if (!isc_log_wouldlog(dns_lctx, level)) {
return;
}
dns_zone_name(xfr->zone, zonetext, sizeof(zonetext));
va_start(ap, fmt);
xfrin_logv(level, zonetext, &xfr->primaryaddr, fmt, ap);
va_end(ap);
}