mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-22 18:17:05 -04:00
Prevent retrying the notify over TCP in case the source address is not available or the source vs the destination address family mismatch or when the destination address has been blackholed. Properly log the hard notify failures.
798 lines
20 KiB
C
798 lines
20 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 <isc/netmgr.h>
|
|
#include <isc/ratelimiter.h>
|
|
#include <isc/result.h>
|
|
|
|
#include <dns/adb.h>
|
|
#include <dns/notify.h>
|
|
#include <dns/peer.h>
|
|
#include <dns/rcode.h>
|
|
#include <dns/rdatalist.h>
|
|
#include <dns/request.h>
|
|
#include <dns/stats.h>
|
|
#include <dns/tsig.h>
|
|
#include <dns/zone.h>
|
|
|
|
#include "zone_p.h"
|
|
|
|
static void
|
|
notify_log(dns_notify_t *notify, int level, const char *fmt, ...) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
dns_zone_logv(notify->zone, DNS_LOGCATEGORY_NOTIFY, level, NULL, fmt,
|
|
ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void
|
|
dns_notifyctx_init(dns_notifyctx_t *nctx, dns_rdatatype_t type) {
|
|
dns_notifyctx_t ctx = {
|
|
.type = type,
|
|
.notifytype = dns_notifytype_yes,
|
|
.notifydelay = 5,
|
|
.notifies = ISC_LIST_INITIALIZER,
|
|
};
|
|
isc_sockaddr_any(&ctx.notifysrc4);
|
|
isc_sockaddr_any6(&ctx.notifysrc6);
|
|
|
|
*nctx = ctx;
|
|
}
|
|
|
|
void
|
|
dns_notify_create(isc_mem_t *mctx, dns_rdatatype_t type, in_port_t port,
|
|
unsigned int flags, dns_notify_t **notifyp) {
|
|
dns_notify_t *notify;
|
|
|
|
REQUIRE(notifyp != NULL && *notifyp == NULL);
|
|
|
|
notify = isc_mem_get(mctx, sizeof(*notify));
|
|
*notify = (dns_notify_t){
|
|
.flags = flags,
|
|
.port = port,
|
|
.type = type,
|
|
};
|
|
|
|
isc_mem_attach(mctx, ¬ify->mctx);
|
|
isc_sockaddr_any(¬ify->src);
|
|
isc_sockaddr_any(¬ify->dst);
|
|
dns_name_init(¬ify->ns);
|
|
ISC_LINK_INIT(notify, link);
|
|
notify->magic = NOTIFY_MAGIC;
|
|
*notifyp = notify;
|
|
}
|
|
|
|
void
|
|
dns_notify_destroy(dns_notify_t *notify, bool locked) {
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
|
|
isc_mem_t *mctx;
|
|
dns_notifyctx_t *nctx;
|
|
|
|
if (notify->zone != NULL) {
|
|
if (!locked) {
|
|
dns__zone_lock(notify->zone);
|
|
}
|
|
REQUIRE(dns__zone_locked(notify->zone));
|
|
nctx = dns__zone_getnotifyctx(notify->zone, notify->type);
|
|
if (ISC_LINK_LINKED(notify, link)) {
|
|
ISC_LIST_UNLINK(nctx->notifies, notify, link);
|
|
}
|
|
if (!locked) {
|
|
dns__zone_unlock(notify->zone);
|
|
}
|
|
if (locked) {
|
|
dns__zone_idetach_locked(¬ify->zone);
|
|
} else {
|
|
dns_zone_idetach(¬ify->zone);
|
|
}
|
|
}
|
|
if (notify->find != NULL) {
|
|
dns_adb_destroyfind(¬ify->find);
|
|
}
|
|
if (notify->request != NULL) {
|
|
dns_request_destroy(¬ify->request);
|
|
}
|
|
if (dns_name_dynamic(¬ify->ns)) {
|
|
dns_name_free(¬ify->ns, notify->mctx);
|
|
}
|
|
if (notify->key != NULL) {
|
|
dns_tsigkey_detach(¬ify->key);
|
|
}
|
|
if (notify->transport != NULL) {
|
|
dns_transport_detach(¬ify->transport);
|
|
}
|
|
mctx = notify->mctx;
|
|
isc_mem_put(notify->mctx, notify, sizeof(*notify));
|
|
isc_mem_detach(&mctx);
|
|
}
|
|
|
|
static void
|
|
notify_done(void *arg) {
|
|
dns_request_t *request = (dns_request_t *)arg;
|
|
dns_notify_t *notify = dns_request_getarg(request);
|
|
isc_result_t result;
|
|
dns_message_t *message = NULL;
|
|
isc_buffer_t buf;
|
|
char rcode[128];
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
|
|
isc_buffer_init(&buf, rcode, sizeof(rcode));
|
|
isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf));
|
|
|
|
dns_rdatatype_format(notify->type, typebuf, sizeof(typebuf));
|
|
|
|
/* WMM: This is changing the mctx from zone to notify. */
|
|
dns_message_create(notify->mctx, NULL, NULL, DNS_MESSAGE_INTENTPARSE,
|
|
&message);
|
|
|
|
result = dns_request_getresult(request);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
result = dns_request_getresponse(request, message,
|
|
DNS_MESSAGEPARSE_PRESERVEORDER);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto fail;
|
|
}
|
|
|
|
result = dns_rcode_totext(message->rcode, &buf);
|
|
if (result == ISC_R_SUCCESS) {
|
|
notify_log(notify, ISC_LOG_DEBUG(3),
|
|
"notify(%s) response from %s: %.*s", typebuf,
|
|
addrbuf, (int)buf.used, rcode);
|
|
}
|
|
fail:
|
|
dns_message_detach(&message);
|
|
|
|
if (result == ISC_R_SUCCESS) {
|
|
notify_log(notify, ISC_LOG_DEBUG(1),
|
|
"notify(%s) to %s successful", typebuf, addrbuf);
|
|
} else if (result == ISC_R_SHUTTINGDOWN || result == ISC_R_CANCELED) {
|
|
/* just destroy the notify */
|
|
} else if ((notify->flags & DNS_NOTIFY_TCP) == 0) {
|
|
notify_log(notify, ISC_LOG_NOTICE,
|
|
"notify(%s) to %s failed: %s: retrying over TCP",
|
|
typebuf, addrbuf, isc_result_totext(result));
|
|
notify->flags |= DNS_NOTIFY_TCP;
|
|
dns_request_destroy(¬ify->request);
|
|
dns_notify_queue(notify, notify->flags & DNS_NOTIFY_STARTUP);
|
|
return;
|
|
} else if (result == ISC_R_TIMEDOUT) {
|
|
notify_log(notify, ISC_LOG_WARNING,
|
|
"notify(%s) to %s failed: %s: retries exceeded",
|
|
typebuf, addrbuf, isc_result_totext(result));
|
|
} else {
|
|
notify_log(notify, ISC_LOG_WARNING,
|
|
"notify(%s) to %s failed: %s", typebuf, addrbuf,
|
|
isc_result_totext(result));
|
|
}
|
|
dns_notify_destroy(notify, false);
|
|
}
|
|
|
|
static isc_result_t
|
|
notify_createmessage(dns_notify_t *notify, dns_message_t **messagep) {
|
|
dns_db_t *zonedb = NULL;
|
|
dns_dbnode_t *node = NULL;
|
|
dns_dbversion_t *version = NULL;
|
|
dns_message_t *message = NULL;
|
|
dns_rdataset_t rdataset;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
|
|
dns_name_t *tempname = NULL;
|
|
dns_rdata_t *temprdata = NULL;
|
|
dns_rdatalist_t *temprdatalist = NULL;
|
|
dns_rdataset_t *temprdataset = NULL;
|
|
|
|
isc_result_t result;
|
|
isc_region_t r;
|
|
isc_buffer_t *b = NULL;
|
|
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
REQUIRE(messagep != NULL && *messagep == NULL);
|
|
|
|
/* WMM: This is changing the mctx from zone to notify. */
|
|
dns_message_create(notify->mctx, NULL, NULL, DNS_MESSAGE_INTENTRENDER,
|
|
&message);
|
|
|
|
message->opcode = dns_opcode_notify;
|
|
message->flags |= DNS_MESSAGEFLAG_AA;
|
|
message->rdclass = dns_zone_getrdclass(notify->zone);
|
|
|
|
dns_message_gettempname(message, &tempname);
|
|
dns_message_gettemprdataset(message, &temprdataset);
|
|
|
|
/*
|
|
* Make question.
|
|
*/
|
|
dns_name_clone(dns_zone_getorigin(notify->zone), tempname);
|
|
dns_rdataset_makequestion(temprdataset,
|
|
dns_zone_getrdclass(notify->zone),
|
|
dns_rdatatype_soa);
|
|
ISC_LIST_APPEND(tempname->list, temprdataset, link);
|
|
dns_message_addname(message, tempname, DNS_SECTION_QUESTION);
|
|
tempname = NULL;
|
|
temprdataset = NULL;
|
|
|
|
if ((notify->flags & DNS_NOTIFY_NOSOA) != 0) {
|
|
goto done;
|
|
}
|
|
|
|
dns_message_gettempname(message, &tempname);
|
|
dns_message_gettemprdata(message, &temprdata);
|
|
dns_message_gettemprdataset(message, &temprdataset);
|
|
dns_message_gettemprdatalist(message, &temprdatalist);
|
|
|
|
result = dns_zone_getdb(notify->zone, &zonedb);
|
|
INSIST(result == ISC_R_SUCCESS);
|
|
INSIST(zonedb != NULL); /* XXXJT: is this assumption correct? */
|
|
|
|
dns_name_clone(dns_zone_getorigin(notify->zone), tempname);
|
|
dns_db_currentversion(zonedb, &version);
|
|
result = dns_db_findnode(zonedb, tempname, false, &node);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto soa_cleanup;
|
|
}
|
|
|
|
dns_rdataset_init(&rdataset);
|
|
result = dns_db_findrdataset(zonedb, node, version, dns_rdatatype_soa,
|
|
dns_rdatatype_none, 0, &rdataset, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto soa_cleanup;
|
|
}
|
|
result = dns_rdataset_first(&rdataset);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto soa_cleanup;
|
|
}
|
|
dns_rdataset_current(&rdataset, &rdata);
|
|
dns_rdata_toregion(&rdata, &r);
|
|
/* WMM: This is changing the mctx from zone to notify. */
|
|
isc_buffer_allocate(notify->mctx, &b, r.length);
|
|
isc_buffer_putmem(b, r.base, r.length);
|
|
isc_buffer_usedregion(b, &r);
|
|
dns_rdata_fromregion(temprdata, rdata.rdclass, rdata.type, &r);
|
|
dns_message_takebuffer(message, &b);
|
|
result = dns_rdataset_next(&rdataset);
|
|
dns_rdataset_disassociate(&rdataset);
|
|
if (result != ISC_R_NOMORE) {
|
|
goto soa_cleanup;
|
|
}
|
|
temprdatalist->rdclass = rdata.rdclass;
|
|
temprdatalist->type = rdata.type;
|
|
temprdatalist->ttl = rdataset.ttl;
|
|
ISC_LIST_APPEND(temprdatalist->rdata, temprdata, link);
|
|
|
|
dns_rdatalist_tordataset(temprdatalist, temprdataset);
|
|
|
|
ISC_LIST_APPEND(tempname->list, temprdataset, link);
|
|
dns_message_addname(message, tempname, DNS_SECTION_ANSWER);
|
|
temprdatalist = NULL;
|
|
temprdataset = NULL;
|
|
temprdata = NULL;
|
|
tempname = NULL;
|
|
|
|
soa_cleanup:
|
|
if (node != NULL) {
|
|
dns_db_detachnode(&node);
|
|
}
|
|
if (version != NULL) {
|
|
dns_db_closeversion(zonedb, &version, false);
|
|
}
|
|
if (zonedb != NULL) {
|
|
dns_db_detach(&zonedb);
|
|
}
|
|
if (tempname != NULL) {
|
|
dns_message_puttempname(message, &tempname);
|
|
}
|
|
if (temprdata != NULL) {
|
|
dns_message_puttemprdata(message, &temprdata);
|
|
}
|
|
if (temprdataset != NULL) {
|
|
dns_message_puttemprdataset(message, &temprdataset);
|
|
}
|
|
if (temprdatalist != NULL) {
|
|
dns_message_puttemprdatalist(message, &temprdatalist);
|
|
}
|
|
|
|
done:
|
|
*messagep = message;
|
|
return ISC_R_SUCCESS;
|
|
}
|
|
|
|
static void
|
|
notify_send_toaddr(void *arg) {
|
|
dns_notify_t *notify = (dns_notify_t *)arg;
|
|
dns_notifyctx_t *notifyctx = NULL;
|
|
isc_result_t result;
|
|
dns_db_t *zonedb = NULL;
|
|
dns_view_t *view = NULL;
|
|
isc_loop_t *loop = NULL;
|
|
dns_zonemgr_t *zmgr = NULL;
|
|
dns_message_t *message = NULL;
|
|
isc_netaddr_t dstip;
|
|
dns_tsigkey_t *key = NULL;
|
|
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
|
|
char typebuf[DNS_RDATATYPE_FORMATSIZE];
|
|
isc_sockaddr_t src;
|
|
unsigned int options;
|
|
bool have_notifysource = false;
|
|
isc_tlsctx_cache_t *zmgr_tlsctx_cache = NULL;
|
|
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
|
|
dns__zone_lock(notify->zone);
|
|
|
|
notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type);
|
|
zmgr = dns_zone_getmgr(notify->zone);
|
|
view = dns_zone_getview(notify->zone);
|
|
loop = dns_zone_getloop(notify->zone);
|
|
result = dns_zone_getdb(notify->zone, &zonedb);
|
|
|
|
isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf));
|
|
|
|
dns_rdatatype_format(notify->type, typebuf, sizeof(typebuf));
|
|
|
|
if (!dns__zone_loaded(notify->zone) || notify->rlevent->canceled ||
|
|
dns__zone_exiting(notify->zone) || zmgr == NULL || view == NULL ||
|
|
view->requestmgr == NULL || loop == NULL || zonedb == NULL ||
|
|
result != ISC_R_SUCCESS)
|
|
{
|
|
result = ISC_R_CANCELED;
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* The raw IPv4 address should also exist. Don't send to the
|
|
* mapped form.
|
|
*/
|
|
if (isc_sockaddr_pf(¬ify->dst) == PF_INET6 &&
|
|
IN6_IS_ADDR_V4MAPPED(¬ify->dst.type.sin6.sin6_addr))
|
|
{
|
|
notify_log(notify, ISC_LOG_DEBUG(3),
|
|
"notify(%s): ignoring IPv6 mapped IPV4 address: %s",
|
|
typebuf, addrbuf);
|
|
result = ISC_R_CANCELED;
|
|
goto cleanup;
|
|
}
|
|
|
|
CHECK(notify_createmessage(notify, &message));
|
|
|
|
if (notify->key != NULL) {
|
|
/* Transfer ownership of key */
|
|
key = notify->key;
|
|
notify->key = NULL;
|
|
} else {
|
|
isc_netaddr_fromsockaddr(&dstip, ¬ify->dst);
|
|
result = dns_view_getpeertsig(view, &dstip, &key);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
notify_log(notify, ISC_LOG_ERROR,
|
|
"NOTIFY(%s) to %s not sent. "
|
|
"Peer TSIG key lookup failure.",
|
|
typebuf, addrbuf);
|
|
goto cleanup_message;
|
|
}
|
|
}
|
|
|
|
if (key != NULL) {
|
|
char namebuf[DNS_NAME_FORMATSIZE];
|
|
|
|
dns_name_format(key->name, namebuf, sizeof(namebuf));
|
|
notify_log(notify, ISC_LOG_INFO,
|
|
"sending notify(%s) to %s : TSIG (%s)", typebuf,
|
|
addrbuf, namebuf);
|
|
} else {
|
|
notify_log(notify, ISC_LOG_INFO, "sending notify(%s) to %s",
|
|
typebuf, addrbuf);
|
|
}
|
|
options = 0;
|
|
if (view->peers != NULL) {
|
|
dns_peer_t *peer = NULL;
|
|
bool usetcp = false;
|
|
result = dns_peerlist_peerbyaddr(view->peers, &dstip, &peer);
|
|
if (result == ISC_R_SUCCESS) {
|
|
result = dns_peer_getnotifysource(peer, &src);
|
|
if (result == ISC_R_SUCCESS) {
|
|
have_notifysource = true;
|
|
}
|
|
result = dns_peer_getforcetcp(peer, &usetcp);
|
|
if (result == ISC_R_SUCCESS && usetcp) {
|
|
options |= DNS_FETCHOPT_TCP;
|
|
}
|
|
}
|
|
}
|
|
switch (isc_sockaddr_pf(¬ify->dst)) {
|
|
case PF_INET:
|
|
if (!have_notifysource) {
|
|
isc_sockaddr_t any;
|
|
isc_sockaddr_any(&any);
|
|
|
|
src = notify->src;
|
|
if (isc_sockaddr_equal(&src, &any)) {
|
|
src = notifyctx->notifysrc4;
|
|
}
|
|
}
|
|
break;
|
|
case PF_INET6:
|
|
if (!have_notifysource) {
|
|
isc_sockaddr_t any;
|
|
isc_sockaddr_any6(&any);
|
|
|
|
src = notify->src;
|
|
if (isc_sockaddr_equal(&src, &any)) {
|
|
src = notifyctx->notifysrc6;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
result = ISC_R_NOTIMPLEMENTED;
|
|
goto cleanup_key;
|
|
}
|
|
|
|
again:
|
|
if ((notify->flags & DNS_NOTIFY_TCP) != 0) {
|
|
options |= DNS_REQUESTOPT_TCP;
|
|
}
|
|
|
|
dns__zonemgr_tlsctx_attach(zmgr, &zmgr_tlsctx_cache);
|
|
|
|
const unsigned int connect_timeout = isc_nm_getinitialtimeout() /
|
|
MS_PER_SEC;
|
|
result = dns_request_create(
|
|
view->requestmgr, message, &src, ¬ify->dst,
|
|
notify->transport, zmgr_tlsctx_cache, options, key,
|
|
connect_timeout, TCP_REQUEST_TIMEOUT, UDP_REQUEST_TIMEOUT,
|
|
UDP_REQUEST_RETRIES, loop, notify_done, notify,
|
|
¬ify->request);
|
|
|
|
isc_tlsctx_cache_detach(&zmgr_tlsctx_cache);
|
|
|
|
switch (result) {
|
|
case ISC_R_SUCCESS:
|
|
if (isc_sockaddr_pf(¬ify->dst) == AF_INET) {
|
|
dns__zone_stats_increment(
|
|
notify->zone, dns_zonestatscounter_notifyoutv4);
|
|
} else {
|
|
dns__zone_stats_increment(
|
|
notify->zone, dns_zonestatscounter_notifyoutv6);
|
|
}
|
|
break;
|
|
case ISC_R_SHUTTINGDOWN:
|
|
case ISC_R_CANCELED:
|
|
case ISC_R_ADDRNOTAVAIL:
|
|
case DNS_R_BLACKHOLED:
|
|
case ISC_R_FAMILYNOSUPPORT:
|
|
notify_log(notify, ISC_LOG_NOTICE,
|
|
"notify(%s) to %s failed: %s", typebuf, addrbuf,
|
|
isc_result_totext(result));
|
|
break;
|
|
default:
|
|
if ((notify->flags & DNS_NOTIFY_TCP) == 0) {
|
|
notify_log(notify, ISC_LOG_NOTICE,
|
|
"notify(%s) to %s failed: %s: retrying over "
|
|
"TCP",
|
|
typebuf, addrbuf, isc_result_totext(result));
|
|
notify->flags |= DNS_NOTIFY_TCP;
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
cleanup_key:
|
|
if (key != NULL) {
|
|
dns_tsigkey_detach(&key);
|
|
}
|
|
cleanup_message:
|
|
dns_message_detach(&message);
|
|
cleanup:
|
|
dns__zone_unlock(notify->zone);
|
|
|
|
if (zonedb != NULL) {
|
|
dns_db_detach(&zonedb);
|
|
}
|
|
|
|
if (notify->rlevent != NULL) {
|
|
isc_rlevent_free(¬ify->rlevent);
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
isc_sockaddr_format(¬ify->dst, addrbuf, sizeof(addrbuf));
|
|
notify_log(notify, ISC_LOG_WARNING,
|
|
"notify(%s) to %s failed: %s", typebuf, addrbuf,
|
|
isc_result_totext(result));
|
|
dns_notify_destroy(notify, false);
|
|
}
|
|
}
|
|
|
|
static isc_result_t
|
|
notify_queue(dns_notify_t *notify, bool startup, bool dequeue) {
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
|
|
isc_loop_t *loop = dns_zone_getloop(notify->zone);
|
|
dns_zonemgr_t *zmgr = dns_zone_getmgr(notify->zone);
|
|
isc_ratelimiter_t *notifyrl = NULL;
|
|
isc_ratelimiter_t *startupnotifyrl = NULL;
|
|
|
|
INSIST(loop != NULL);
|
|
INSIST(zmgr != NULL);
|
|
|
|
dns__zonemgr_getnotifyrl(zmgr, ¬ifyrl);
|
|
dns__zonemgr_getstartupnotifyrl(zmgr, &startupnotifyrl);
|
|
|
|
if (dequeue) {
|
|
return isc_ratelimiter_dequeue(
|
|
startup ? startupnotifyrl : notifyrl, ¬ify->rlevent);
|
|
}
|
|
|
|
return isc_ratelimiter_enqueue(startup ? startupnotifyrl : notifyrl,
|
|
loop, notify_send_toaddr, notify,
|
|
¬ify->rlevent);
|
|
}
|
|
|
|
isc_result_t
|
|
dns_notify_queue(dns_notify_t *notify, bool startup) {
|
|
return notify_queue(notify, startup, false);
|
|
}
|
|
|
|
bool
|
|
dns_notify_isqueued(dns_notifyctx_t *nctx, dns_rdatatype_t type, in_port_t port,
|
|
unsigned int flags, dns_name_t *name, isc_sockaddr_t *addr,
|
|
dns_tsigkey_t *key, dns_transport_t *transport) {
|
|
dns_notify_t *notify = NULL;
|
|
isc_result_t result;
|
|
|
|
REQUIRE(nctx != NULL);
|
|
|
|
ISC_LIST_FOREACH(nctx->notifies, n, link) {
|
|
if (n->request != NULL) {
|
|
continue;
|
|
}
|
|
if (n->type != type) {
|
|
continue;
|
|
}
|
|
if ((name != NULL && dns_name_dynamic(&n->ns) &&
|
|
dns_name_equal(name, &n->ns)) ||
|
|
(addr != NULL && isc_sockaddr_equal(addr, &n->dst) &&
|
|
n->port == port && n->key == key &&
|
|
n->transport == transport))
|
|
{
|
|
notify = n;
|
|
goto requeue;
|
|
}
|
|
}
|
|
return false;
|
|
requeue:
|
|
/*
|
|
* If we are enqueued on the startup ratelimiter and this is
|
|
* not a startup notify, re-enqueue on the normal notify
|
|
* ratelimiter.
|
|
*/
|
|
if (notify->rlevent != NULL && (flags & DNS_NOTIFY_STARTUP) == 0 &&
|
|
(notify->flags & DNS_NOTIFY_STARTUP) != 0)
|
|
{
|
|
result = notify_queue(notify, true, true);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return true;
|
|
}
|
|
|
|
notify->flags &= ~DNS_NOTIFY_STARTUP;
|
|
result = notify_queue(notify, false, false);
|
|
if (result != ISC_R_SUCCESS) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
notify_isself(dns_notify_t *notify, isc_sockaddr_t *dst) {
|
|
dns_tsigkey_t *key = NULL;
|
|
isc_sockaddr_t src;
|
|
isc_sockaddr_t any;
|
|
bool isself;
|
|
isc_netaddr_t dstaddr;
|
|
isc_result_t result;
|
|
dns_notifyctx_t *notifyctx = NULL;
|
|
dns_view_t *view = NULL;
|
|
dns_isselffunc_t isselffunc;
|
|
void *isselfarg = NULL;
|
|
|
|
notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type);
|
|
view = dns_zone_getview(notify->zone);
|
|
dns__zone_getisself(notify->zone, &isselffunc, &isselfarg);
|
|
if (view == NULL || isselffunc == NULL) {
|
|
return false;
|
|
}
|
|
|
|
switch (isc_sockaddr_pf(dst)) {
|
|
case PF_INET:
|
|
src = notifyctx->notifysrc4;
|
|
isc_sockaddr_any(&any);
|
|
break;
|
|
case PF_INET6:
|
|
src = notifyctx->notifysrc6;
|
|
isc_sockaddr_any6(&any);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* When sending from any the kernel will assign a source address
|
|
* that matches the destination address.
|
|
*/
|
|
if (isc_sockaddr_eqaddr(&any, &src)) {
|
|
src = *dst;
|
|
}
|
|
|
|
isc_netaddr_fromsockaddr(&dstaddr, dst);
|
|
result = dns_view_getpeertsig(view, &dstaddr, &key);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
return false;
|
|
}
|
|
isself = (isselffunc)(view, key, &src, dst,
|
|
dns_zone_getrdclass(notify->zone), isselfarg);
|
|
if (key != NULL) {
|
|
dns_tsigkey_detach(&key);
|
|
}
|
|
return isself;
|
|
}
|
|
|
|
static void
|
|
notify_send(dns_notify_t *notify) {
|
|
isc_sockaddr_t dst;
|
|
isc_result_t result;
|
|
dns_notify_t *newnotify = NULL;
|
|
dns_notifyctx_t *notifyctx = NULL;
|
|
unsigned int flags;
|
|
bool startup;
|
|
|
|
/*
|
|
* Zone lock held by caller.
|
|
*/
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
REQUIRE(dns__zone_locked(notify->zone));
|
|
if (dns__zone_exiting(notify->zone)) {
|
|
return;
|
|
}
|
|
notifyctx = dns__zone_getnotifyctx(notify->zone, notify->type);
|
|
|
|
ISC_LIST_FOREACH(notify->find->list, ai, publink) {
|
|
dst = ai->sockaddr;
|
|
if (dns_notify_isqueued(notifyctx, notify->type, notify->port,
|
|
notify->flags, NULL, &dst, NULL, NULL))
|
|
{
|
|
continue;
|
|
}
|
|
if (notify_isself(notify, &dst)) {
|
|
continue;
|
|
}
|
|
newnotify = NULL;
|
|
flags = notify->flags & DNS_NOTIFY_NOSOA;
|
|
dns_notify_create(notify->mctx, notify->type, notify->port,
|
|
flags, &newnotify);
|
|
dns__zone_iattach_locked(notify->zone, &newnotify->zone);
|
|
ISC_LIST_APPEND(notifyctx->notifies, newnotify, link);
|
|
newnotify->dst = dst;
|
|
if (isc_sockaddr_pf(&dst) == AF_INET6) {
|
|
isc_sockaddr_any6(&newnotify->src);
|
|
}
|
|
startup = ((notify->flags & DNS_NOTIFY_STARTUP) != 0);
|
|
CHECK(dns_notify_queue(newnotify, startup));
|
|
newnotify = NULL;
|
|
}
|
|
|
|
cleanup:
|
|
if (newnotify != NULL) {
|
|
dns_notify_destroy(newnotify, true);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* XXXAG should check for DNS_ZONEFLG_EXITING
|
|
*/
|
|
static void
|
|
process_notify_adb_event(void *arg) {
|
|
dns_adbfind_t *find = (dns_adbfind_t *)arg;
|
|
dns_notify_t *notify = (dns_notify_t *)find->cbarg;
|
|
dns_adbstatus_t astat = find->status;
|
|
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
REQUIRE(find == notify->find);
|
|
|
|
switch (astat) {
|
|
case DNS_ADB_MOREADDRESSES:
|
|
dns_adb_destroyfind(¬ify->find);
|
|
dns_notify_find_address(notify);
|
|
return;
|
|
|
|
case DNS_ADB_NOMOREADDRESSES:
|
|
dns__zone_lock(notify->zone);
|
|
notify_send(notify);
|
|
dns__zone_unlock(notify->zone);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dns_notify_destroy(notify, false);
|
|
}
|
|
|
|
void
|
|
dns_notify_find_address(dns_notify_t *notify) {
|
|
isc_result_t result;
|
|
unsigned int options;
|
|
dns_adb_t *adb = NULL;
|
|
dns_view_t *view = NULL;
|
|
isc_loop_t *loop = NULL;
|
|
|
|
REQUIRE(DNS_NOTIFY_VALID(notify));
|
|
|
|
options = DNS_ADBFIND_WANTEVENT;
|
|
if (isc_net_probeipv4() != ISC_R_DISABLED) {
|
|
options |= DNS_ADBFIND_INET;
|
|
}
|
|
if (isc_net_probeipv6() != ISC_R_DISABLED) {
|
|
options |= DNS_ADBFIND_INET6;
|
|
}
|
|
|
|
loop = dns_zone_getloop(notify->zone);
|
|
view = dns_zone_getview(notify->zone);
|
|
dns_view_getadb(view, &adb);
|
|
if (loop == NULL || view == NULL || adb == NULL) {
|
|
goto destroy;
|
|
}
|
|
|
|
result = dns_adb_createfind(adb, loop, process_notify_adb_event, notify,
|
|
¬ify->ns, options, 0, notify->port, 0,
|
|
NULL, NULL, NULL, ¬ify->find);
|
|
dns_adb_detach(&adb);
|
|
|
|
/* Something failed? */
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto destroy;
|
|
}
|
|
|
|
/* More addresses pending? */
|
|
if ((notify->find->options & DNS_ADBFIND_WANTEVENT) != 0) {
|
|
return;
|
|
}
|
|
|
|
/* We have as many addresses as we can get. */
|
|
dns__zone_lock(notify->zone);
|
|
notify_send(notify);
|
|
dns__zone_unlock(notify->zone);
|
|
destroy:
|
|
dns_notify_destroy(notify, false);
|
|
}
|
|
|
|
void
|
|
dns_notify_cancel(dns_notifyctx_t *nctx) {
|
|
ISC_LIST_FOREACH(nctx->notifies, notify, link) {
|
|
INSIST(dns__zone_locked(notify->zone));
|
|
if (notify->find != NULL) {
|
|
dns_adb_cancelfind(notify->find);
|
|
}
|
|
if (notify->request != NULL) {
|
|
dns_request_cancel(notify->request);
|
|
}
|
|
}
|
|
}
|