2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00
bind/lib/dns/request.c
Ondřej Surý 1032681af0 Convert the isc/tid.h to use own signed integer isc_tid_t type
Change the internal type used for isc_tid unit to isc_tid_t to hide the
specific integer type being used for the 'tid'.  Internally, the signed
integer type is being used.  This allows us to have negatively indexed
arrays that works both for threads with assigned tid and the threads
with unassigned tid.  This should be used only in specific situations.
2025-06-28 13:32:12 +02:00

1066 lines
26 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*! \file */
#include <inttypes.h>
#include <stdbool.h>
#include <isc/async.h>
#include <isc/log.h>
#include <isc/loop.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/netmgr.h>
#include <isc/result.h>
#include <isc/thread.h>
#include <isc/tls.h>
#include <isc/util.h>
#include <dns/acl.h>
#include <dns/compress.h>
#include <dns/dispatch.h>
#include <dns/message.h>
#include <dns/rdata.h>
#include <dns/rdatastruct.h>
#include <dns/request.h>
#include <dns/transport.h>
#include <dns/tsig.h>
#define REQUESTMGR_MAGIC ISC_MAGIC('R', 'q', 'u', 'M')
#define VALID_REQUESTMGR(mgr) ISC_MAGIC_VALID(mgr, REQUESTMGR_MAGIC)
#define REQUEST_MAGIC ISC_MAGIC('R', 'q', 'u', '!')
#define VALID_REQUEST(request) ISC_MAGIC_VALID(request, REQUEST_MAGIC)
typedef ISC_LIST(dns_request_t) dns_requestlist_t;
struct dns_requestmgr {
unsigned int magic;
isc_mem_t *mctx;
isc_refcount_t references;
isc_loopmgr_t *loopmgr;
atomic_bool shuttingdown;
dns_dispatchmgr_t *dispatchmgr;
dns_dispatchset_t *dispatches4;
dns_dispatchset_t *dispatches6;
dns_requestlist_t *requests;
};
struct dns_request {
unsigned int magic;
isc_refcount_t references;
isc_mem_t *mctx;
int32_t flags;
isc_loop_t *loop;
isc_tid_t tid;
isc_result_t result;
isc_job_cb cb;
void *arg;
ISC_LINK(dns_request_t) link;
isc_buffer_t *query;
isc_buffer_t *answer;
dns_dispatch_t *dispatch;
dns_dispentry_t *dispentry;
dns_requestmgr_t *requestmgr;
isc_buffer_t *tsig;
dns_tsigkey_t *tsigkey;
isc_sockaddr_t destaddr;
unsigned int connect_timeout;
unsigned int timeout;
unsigned int udpcount;
};
#define DNS_REQUEST_F_CONNECTING (1 << 0)
#define DNS_REQUEST_F_SENDING (1 << 1)
#define DNS_REQUEST_F_COMPLETE (1 << 2)
#define DNS_REQUEST_F_TCP (1 << 3)
#define DNS_REQUEST_CONNECTING(r) (((r)->flags & DNS_REQUEST_F_CONNECTING) != 0)
#define DNS_REQUEST_SENDING(r) (((r)->flags & DNS_REQUEST_F_SENDING) != 0)
#define DNS_REQUEST_COMPLETE(r) (((r)->flags & DNS_REQUEST_F_COMPLETE) != 0)
/***
*** Forward
***/
static isc_result_t
req_render(dns_message_t *message, isc_buffer_t **buffer, unsigned int options,
isc_mem_t *mctx);
static void
req_response(isc_result_t result, isc_region_t *region, void *arg);
static void
req_senddone(isc_result_t eresult, isc_region_t *region, void *arg);
static void
req_cleanup(dns_request_t *request);
static void
req_sendevent(dns_request_t *request, isc_result_t result);
static void
req_connected(isc_result_t eresult, isc_region_t *region, void *arg);
static void
req_destroy(dns_request_t *request);
static void
req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
/***
*** Public
***/
isc_result_t
dns_requestmgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr,
dns_dispatchmgr_t *dispatchmgr,
dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
dns_requestmgr_t **requestmgrp) {
REQUIRE(requestmgrp != NULL && *requestmgrp == NULL);
REQUIRE(dispatchmgr != NULL);
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
dns_requestmgr_t *requestmgr = isc_mem_get(mctx, sizeof(*requestmgr));
*requestmgr = (dns_requestmgr_t){
.magic = REQUESTMGR_MAGIC,
.loopmgr = loopmgr,
};
isc_mem_attach(mctx, &requestmgr->mctx);
uint32_t nloops = isc_loopmgr_nloops(requestmgr->loopmgr);
requestmgr->requests = isc_mem_cget(requestmgr->mctx, nloops,
sizeof(requestmgr->requests[0]));
for (size_t i = 0; i < nloops; i++) {
ISC_LIST_INIT(requestmgr->requests[i]);
/* unreferenced in requests_cancel() */
isc_loop_ref(isc_loop_get(requestmgr->loopmgr, i));
}
dns_dispatchmgr_attach(dispatchmgr, &requestmgr->dispatchmgr);
if (dispatchv4 != NULL) {
dns_dispatchset_create(requestmgr->mctx, dispatchv4,
&requestmgr->dispatches4,
isc_loopmgr_nloops(requestmgr->loopmgr));
}
if (dispatchv6 != NULL) {
dns_dispatchset_create(requestmgr->mctx, dispatchv6,
&requestmgr->dispatches6,
isc_loopmgr_nloops(requestmgr->loopmgr));
}
isc_refcount_init(&requestmgr->references, 1);
req_log(ISC_LOG_DEBUG(3), "%s: %p", __func__, requestmgr);
*requestmgrp = requestmgr;
return ISC_R_SUCCESS;
}
static void
requests_cancel(void *arg) {
dns_requestmgr_t *requestmgr = arg;
isc_tid_t tid = isc_tid();
ISC_LIST_FOREACH (requestmgr->requests[tid], request, link) {
req_log(ISC_LOG_DEBUG(3), "%s(%" PRItid ": request %p",
__func__, tid, request);
if (DNS_REQUEST_COMPLETE(request)) {
/* The callback has been already scheduled */
continue;
}
req_sendevent(request, ISC_R_CANCELED);
}
isc_loop_unref(isc_loop_get(requestmgr->loopmgr, tid));
dns_requestmgr_detach(&requestmgr);
}
void
dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) {
bool first;
REQUIRE(VALID_REQUESTMGR(requestmgr));
req_log(ISC_LOG_DEBUG(3), "%s: %p", __func__, requestmgr);
rcu_read_lock();
first = atomic_compare_exchange_strong(&requestmgr->shuttingdown,
&(bool){ false }, true);
rcu_read_unlock();
if (!first) {
return;
}
/*
* Wait until all dns_request_create{raw}() are finished, so
* there will be no new requests added to the lists.
*/
synchronize_rcu();
isc_tid_t tid = isc_tid();
uint32_t nloops = isc_loopmgr_nloops(requestmgr->loopmgr);
for (size_t i = 0; i < nloops; i++) {
dns_requestmgr_ref(requestmgr);
if (i == (uint32_t)tid) {
/* Run the current loop synchronously */
requests_cancel(requestmgr);
continue;
}
isc_loop_t *loop = isc_loop_get(requestmgr->loopmgr, i);
isc_async_run(loop, requests_cancel, requestmgr);
}
}
static void
requestmgr_destroy(dns_requestmgr_t *requestmgr) {
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
INSIST(atomic_load(&requestmgr->shuttingdown));
size_t nloops = isc_loopmgr_nloops(requestmgr->loopmgr);
for (size_t i = 0; i < nloops; i++) {
INSIST(ISC_LIST_EMPTY(requestmgr->requests[i]));
}
isc_mem_cput(requestmgr->mctx, requestmgr->requests, nloops,
sizeof(requestmgr->requests[0]));
if (requestmgr->dispatches4 != NULL) {
dns_dispatchset_destroy(&requestmgr->dispatches4);
}
if (requestmgr->dispatches6 != NULL) {
dns_dispatchset_destroy(&requestmgr->dispatches6);
}
if (requestmgr->dispatchmgr != NULL) {
dns_dispatchmgr_detach(&requestmgr->dispatchmgr);
}
requestmgr->magic = 0;
isc_mem_putanddetach(&requestmgr->mctx, requestmgr,
sizeof(*requestmgr));
}
#if DNS_REQUEST_TRACE
ISC_REFCOUNT_TRACE_IMPL(dns_requestmgr, requestmgr_destroy);
#else
ISC_REFCOUNT_IMPL(dns_requestmgr, requestmgr_destroy);
#endif
static void
req_send(dns_request_t *request) {
isc_region_t r;
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
REQUIRE(VALID_REQUEST(request));
isc_buffer_usedregion(request->query, &r);
request->flags |= DNS_REQUEST_F_SENDING;
/* detached in req_senddone() */
dns_request_ref(request);
dns_dispatch_send(request->dispentry, &r);
}
static dns_request_t *
new_request(isc_mem_t *mctx, isc_loop_t *loop, isc_job_cb cb, void *arg,
bool tcp, unsigned int connect_timeout, unsigned int timeout,
unsigned int udptimeout, unsigned int udpretries) {
dns_request_t *request = isc_mem_get(mctx, sizeof(*request));
*request = (dns_request_t){
.magic = REQUEST_MAGIC,
.loop = loop,
.tid = isc_tid(),
.cb = cb,
.arg = arg,
.link = ISC_LINK_INITIALIZER,
.result = ISC_R_FAILURE,
.udpcount = udpretries + 1,
};
isc_refcount_init(&request->references, 1);
isc_mem_attach(mctx, &request->mctx);
if (tcp) {
request->connect_timeout = connect_timeout * 1000;
request->timeout = timeout * 1000;
} else {
if (udptimeout == 0) {
udptimeout = timeout / request->udpcount;
}
if (udptimeout == 0) {
udptimeout = 1;
}
request->timeout = udptimeout * 1000;
}
return request;
}
static bool
isblackholed(dns_dispatchmgr_t *dispatchmgr, const isc_sockaddr_t *destaddr) {
dns_acl_t *blackhole;
isc_netaddr_t netaddr;
char netaddrstr[ISC_NETADDR_FORMATSIZE];
int match;
isc_result_t result;
blackhole = dns_dispatchmgr_getblackhole(dispatchmgr);
if (blackhole == NULL) {
return false;
}
isc_netaddr_fromsockaddr(&netaddr, destaddr);
result = dns_acl_match(&netaddr, NULL, blackhole, NULL, &match, NULL);
if (result != ISC_R_SUCCESS || match <= 0) {
return false;
}
isc_netaddr_format(&netaddr, netaddrstr, sizeof(netaddrstr));
req_log(ISC_LOG_DEBUG(10), "blackholed address %s", netaddrstr);
return true;
}
static isc_result_t
tcp_dispatch(bool newtcp, dns_requestmgr_t *requestmgr,
const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr,
dns_transport_t *transport, dns_dispatch_t **dispatchp) {
isc_result_t result;
if (!newtcp) {
result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr,
srcaddr, transport, dispatchp);
if (result == ISC_R_SUCCESS) {
char peer[ISC_SOCKADDR_FORMATSIZE];
isc_sockaddr_format(destaddr, peer, sizeof(peer));
req_log(ISC_LOG_DEBUG(1),
"attached to TCP connection to %s", peer);
return result;
}
}
result = dns_dispatch_createtcp(requestmgr->dispatchmgr, srcaddr,
destaddr, transport, 0, dispatchp);
return result;
}
static isc_result_t
udp_dispatch(dns_requestmgr_t *requestmgr, const isc_sockaddr_t *srcaddr,
const isc_sockaddr_t *destaddr, dns_dispatch_t **dispatchp) {
dns_dispatch_t *disp = NULL;
if (srcaddr == NULL) {
switch (isc_sockaddr_pf(destaddr)) {
case PF_INET:
disp = dns_dispatchset_get(requestmgr->dispatches4);
break;
case PF_INET6:
disp = dns_dispatchset_get(requestmgr->dispatches6);
break;
default:
return ISC_R_NOTIMPLEMENTED;
}
if (disp == NULL) {
return ISC_R_FAMILYNOSUPPORT;
}
dns_dispatch_attach(disp, dispatchp);
return ISC_R_SUCCESS;
}
return dns_dispatch_createudp(requestmgr->dispatchmgr, srcaddr,
dispatchp);
}
static isc_result_t
get_dispatch(bool tcp, bool newtcp, dns_requestmgr_t *requestmgr,
const isc_sockaddr_t *srcaddr, const isc_sockaddr_t *destaddr,
dns_transport_t *transport, dns_dispatch_t **dispatchp) {
isc_result_t result;
if (tcp) {
result = tcp_dispatch(newtcp, requestmgr, srcaddr, destaddr,
transport, dispatchp);
} else {
result = udp_dispatch(requestmgr, srcaddr, destaddr, dispatchp);
}
return result;
}
isc_result_t
dns_request_createraw(dns_requestmgr_t *requestmgr, isc_buffer_t *msgbuf,
const isc_sockaddr_t *srcaddr,
const isc_sockaddr_t *destaddr,
dns_transport_t *transport,
isc_tlsctx_cache_t *tlsctx_cache, unsigned int options,
unsigned int connect_timeout, unsigned int timeout,
unsigned int udptimeout, unsigned int udpretries,
isc_loop_t *loop, isc_job_cb cb, void *arg,
dns_request_t **requestp) {
dns_request_t *request = NULL;
isc_result_t result;
isc_mem_t *mctx = NULL;
dns_messageid_t id;
bool tcp = false;
bool newtcp = false;
isc_region_t r;
unsigned int dispopt = 0;
REQUIRE(VALID_REQUESTMGR(requestmgr));
REQUIRE(msgbuf != NULL);
REQUIRE(destaddr != NULL);
REQUIRE(loop != NULL);
REQUIRE(cb != NULL);
REQUIRE(requestp != NULL && *requestp == NULL);
REQUIRE(connect_timeout > 0 && timeout > 0);
REQUIRE(udpretries != UINT_MAX);
if (srcaddr != NULL) {
REQUIRE(isc_sockaddr_pf(srcaddr) == isc_sockaddr_pf(destaddr));
}
mctx = requestmgr->mctx;
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
rcu_read_lock();
if (atomic_load_acquire(&requestmgr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
goto done;
}
if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
result = DNS_R_BLACKHOLED;
goto done;
}
isc_buffer_usedregion(msgbuf, &r);
if (r.length < DNS_MESSAGE_HEADERLEN || r.length > 65535) {
result = DNS_R_FORMERR;
goto done;
}
if ((options & DNS_REQUESTOPT_TCP) != 0 || r.length > 512) {
tcp = true;
}
request = new_request(mctx, loop, cb, arg, tcp, connect_timeout,
timeout, udptimeout, udpretries);
isc_buffer_allocate(mctx, &request->query, r.length + (tcp ? 2 : 0));
result = isc_buffer_copyregion(request->query, &r);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
again:
result = get_dispatch(tcp, newtcp, requestmgr, srcaddr, destaddr,
transport, &request->dispatch);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
if ((options & DNS_REQUESTOPT_FIXEDID) != 0) {
id = (r.base[0] << 8) | r.base[1];
dispopt |= DNS_DISPATCHOPT_FIXEDID;
}
result = dns_dispatch_add(request->dispatch, loop, dispopt,
request->connect_timeout, request->timeout,
destaddr, transport, tlsctx_cache,
req_connected, req_senddone, req_response,
request, &id, &request->dispentry);
if (result != ISC_R_SUCCESS) {
if ((options & DNS_REQUESTOPT_FIXEDID) != 0 && !newtcp) {
dns_dispatch_detach(&request->dispatch);
newtcp = true;
goto again;
}
goto cleanup;
}
/* Add message ID. */
isc_buffer_usedregion(request->query, &r);
r.base[0] = (id >> 8) & 0xff;
r.base[1] = id & 0xff;
request->destaddr = *destaddr;
request->flags |= DNS_REQUEST_F_CONNECTING;
if (tcp) {
request->flags |= DNS_REQUEST_F_TCP;
}
dns_requestmgr_attach(requestmgr, &request->requestmgr);
ISC_LIST_APPEND(requestmgr->requests[request->tid], request, link);
dns_request_ref(request); /* detached in req_connected() */
result = dns_dispatch_connect(request->dispentry);
if (result != ISC_R_SUCCESS) {
dns_request_unref(request);
goto cleanup;
}
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
*requestp = request;
cleanup:
if (result != ISC_R_SUCCESS) {
req_cleanup(request);
dns_request_detach(&request);
req_log(ISC_LOG_DEBUG(3), "%s: failed %s", __func__,
isc_result_totext(result));
}
done:
rcu_read_unlock();
return result;
}
isc_result_t
dns_request_create(dns_requestmgr_t *requestmgr, dns_message_t *message,
const isc_sockaddr_t *srcaddr,
const isc_sockaddr_t *destaddr, dns_transport_t *transport,
isc_tlsctx_cache_t *tlsctx_cache, unsigned int options,
dns_tsigkey_t *key, unsigned int connect_timeout,
unsigned int timeout, unsigned int udptimeout,
unsigned int udpretries, isc_loop_t *loop, isc_job_cb cb,
void *arg, dns_request_t **requestp) {
dns_request_t *request = NULL;
isc_result_t result;
isc_mem_t *mctx = NULL;
dns_messageid_t id;
bool tcp = false;
REQUIRE(VALID_REQUESTMGR(requestmgr));
REQUIRE(message != NULL);
REQUIRE(destaddr != NULL);
REQUIRE(loop != NULL);
REQUIRE(cb != NULL);
REQUIRE(requestp != NULL && *requestp == NULL);
REQUIRE(connect_timeout > 0 && timeout > 0);
REQUIRE(udpretries != UINT_MAX);
if (srcaddr != NULL &&
isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr))
{
return ISC_R_FAMILYMISMATCH;
}
mctx = requestmgr->mctx;
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
rcu_read_lock();
if (atomic_load_acquire(&requestmgr->shuttingdown)) {
result = ISC_R_SHUTTINGDOWN;
goto done;
}
if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
result = DNS_R_BLACKHOLED;
goto done;
}
if ((options & DNS_REQUESTOPT_TCP) != 0) {
tcp = true;
}
request = new_request(mctx, loop, cb, arg, tcp, connect_timeout,
timeout, udptimeout, udpretries);
if (key != NULL) {
dns_tsigkey_attach(key, &request->tsigkey);
}
result = dns_message_settsigkey(message, request->tsigkey);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
again:
result = get_dispatch(tcp, false, requestmgr, srcaddr, destaddr,
transport, &request->dispatch);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_dispatch_add(request->dispatch, loop, 0,
request->connect_timeout, request->timeout,
destaddr, transport, tlsctx_cache,
req_connected, req_senddone, req_response,
request, &id, &request->dispentry);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
message->id = id;
result = req_render(message, &request->query, options, mctx);
if (result == DNS_R_USETCP && !tcp) {
/* Try again using TCP. */
dns_message_renderreset(message);
dns_dispatch_done(&request->dispentry);
dns_dispatch_detach(&request->dispatch);
options |= DNS_REQUESTOPT_TCP;
tcp = true;
goto again;
} else if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_getquerytsig(message, mctx, &request->tsig);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
request->destaddr = *destaddr;
request->flags |= DNS_REQUEST_F_CONNECTING;
if (tcp) {
request->flags |= DNS_REQUEST_F_TCP;
}
dns_requestmgr_attach(requestmgr, &request->requestmgr);
ISC_LIST_APPEND(requestmgr->requests[request->tid], request, link);
dns_request_ref(request); /* detached in req_connected() */
result = dns_dispatch_connect(request->dispentry);
if (result != ISC_R_SUCCESS) {
dns_request_unref(request);
goto cleanup;
}
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
*requestp = request;
cleanup:
if (result != ISC_R_SUCCESS) {
req_cleanup(request);
dns_request_detach(&request);
req_log(ISC_LOG_DEBUG(3), "%s: failed %s", __func__,
isc_result_totext(result));
}
done:
rcu_read_unlock();
return result;
}
static isc_result_t
req_render(dns_message_t *message, isc_buffer_t **bufferp, unsigned int options,
isc_mem_t *mctx) {
isc_buffer_t *buf1 = NULL;
isc_buffer_t *buf2 = NULL;
isc_result_t result;
isc_region_t r;
dns_compress_t cctx;
unsigned int compflags;
REQUIRE(bufferp != NULL && *bufferp == NULL);
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
/*
* Create buffer able to hold largest possible message.
*/
isc_buffer_allocate(mctx, &buf1, 65535);
compflags = 0;
if ((options & DNS_REQUESTOPT_LARGE) != 0) {
compflags |= DNS_COMPRESS_LARGE;
}
if ((options & DNS_REQUESTOPT_CASE) != 0) {
compflags |= DNS_COMPRESS_CASE;
}
dns_compress_init(&cctx, mctx, compflags);
/*
* Render message.
*/
result = dns_message_renderbegin(message, &cctx, buf1);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_rendersection(message, DNS_SECTION_QUESTION, 0);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_rendersection(message, DNS_SECTION_ANSWER, 0);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_rendersection(message, DNS_SECTION_AUTHORITY, 0);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_rendersection(message, DNS_SECTION_ADDITIONAL, 0);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
result = dns_message_renderend(message);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Copy rendered message to exact sized buffer.
*/
isc_buffer_usedregion(buf1, &r);
if ((options & DNS_REQUESTOPT_TCP) == 0 && r.length > 512) {
result = DNS_R_USETCP;
goto cleanup;
}
isc_buffer_allocate(mctx, &buf2, r.length);
result = isc_buffer_copyregion(buf2, &r);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Cleanup and return.
*/
dns_compress_invalidate(&cctx);
isc_buffer_free(&buf1);
*bufferp = buf2;
return ISC_R_SUCCESS;
cleanup:
dns_message_renderreset(message);
dns_compress_invalidate(&cctx);
if (buf1 != NULL) {
isc_buffer_free(&buf1);
}
if (buf2 != NULL) {
isc_buffer_free(&buf2);
}
return result;
}
static void
request_cancel(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
if (DNS_REQUEST_COMPLETE(request)) {
/* The request callback was already called */
return;
}
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
req_sendevent(request, ISC_R_CANCELED); /* call asynchronously */
}
static void
req_cancel_cb(void *arg) {
dns_request_t *request = arg;
request_cancel(request);
dns_request_unref(request);
}
void
dns_request_cancel(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
if (request->tid == isc_tid()) {
request_cancel(request);
} else {
dns_request_ref(request);
isc_async_run(request->loop, req_cancel_cb, request);
}
}
isc_result_t
dns_request_getresponse(dns_request_t *request, dns_message_t *message,
unsigned int options) {
isc_result_t result;
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
REQUIRE(request->answer != NULL);
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
dns_message_setquerytsig(message, request->tsig);
result = dns_message_settsigkey(message, request->tsigkey);
if (result != ISC_R_SUCCESS) {
return result;
}
result = dns_message_parse(message, request->answer, options);
if (result != ISC_R_SUCCESS) {
return result;
}
if (request->tsigkey != NULL) {
result = dns_tsig_verify(request->answer, message, NULL, NULL);
}
return result;
}
isc_buffer_t *
dns_request_getanswer(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
return request->answer;
}
bool
dns_request_usedtcp(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
return (request->flags & DNS_REQUEST_F_TCP) != 0;
}
void
dns_request_destroy(dns_request_t **requestp) {
REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
dns_request_t *request = *requestp;
*requestp = NULL;
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
if (DNS_REQUEST_COMPLETE(request)) {
dns_request_cancel(request);
}
/* final detach to shut down request */
dns_request_detach(&request);
}
static void
req_connected(isc_result_t eresult, isc_region_t *region ISC_ATTR_UNUSED,
void *arg) {
dns_request_t *request = (dns_request_t *)arg;
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
REQUIRE(DNS_REQUEST_CONNECTING(request));
req_log(ISC_LOG_DEBUG(3), "%s: request %p: %s", __func__, request,
isc_result_totext(eresult));
request->flags &= ~DNS_REQUEST_F_CONNECTING;
if (DNS_REQUEST_COMPLETE(request)) {
/* The request callback was already called */
goto detach;
}
if (eresult == ISC_R_SUCCESS) {
req_send(request);
} else {
req_sendevent(request, eresult);
}
detach:
/* attached in dns_request_create/_createraw() */
dns_request_unref(request);
}
static void
req_senddone(isc_result_t eresult, isc_region_t *region ISC_ATTR_UNUSED,
void *arg) {
dns_request_t *request = (dns_request_t *)arg;
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
REQUIRE(DNS_REQUEST_SENDING(request));
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
request->flags &= ~DNS_REQUEST_F_SENDING;
if (DNS_REQUEST_COMPLETE(request)) {
/* The request callback was already called */
goto detach;
}
if (eresult != ISC_R_SUCCESS) {
req_sendevent(request, eresult);
}
detach:
/* attached in req_send() */
dns_request_unref(request);
}
static void
req_response(isc_result_t eresult, isc_region_t *region, void *arg) {
dns_request_t *request = (dns_request_t *)arg;
if (eresult == ISC_R_CANCELED) {
return;
}
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
req_log(ISC_LOG_DEBUG(3), "%s: request %p: %s", __func__, request,
isc_result_totext(eresult));
if (DNS_REQUEST_COMPLETE(request)) {
/* The request callback was already called */
return;
}
switch (eresult) {
case ISC_R_TIMEDOUT:
if (request->udpcount > 1 && !dns_request_usedtcp(request)) {
request->udpcount -= 1;
dns_dispatch_resume(request->dispentry,
request->timeout);
if (!DNS_REQUEST_SENDING(request)) {
req_send(request);
}
return;
}
break;
case ISC_R_SUCCESS:
/* Copy region to request. */
isc_buffer_allocate(request->mctx, &request->answer,
region->length);
eresult = isc_buffer_copyregion(request->answer, region);
if (eresult != ISC_R_SUCCESS) {
isc_buffer_free(&request->answer);
}
break;
default:
break;
}
req_sendevent(request, eresult);
}
static void
req_sendevent_cb(void *arg) {
dns_request_t *request = arg;
request->cb(request);
dns_request_unref(request);
}
static void
req_cleanup(dns_request_t *request) {
if (ISC_LINK_LINKED(request, link)) {
ISC_LIST_UNLINK(request->requestmgr->requests[request->tid],
request, link);
}
if (request->dispentry != NULL) {
dns_dispatch_done(&request->dispentry);
}
if (request->dispatch != NULL) {
dns_dispatch_detach(&request->dispatch);
}
}
static void
req_sendevent(dns_request_t *request, isc_result_t result) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
REQUIRE(!DNS_REQUEST_COMPLETE(request));
request->flags |= DNS_REQUEST_F_COMPLETE;
req_cleanup(request);
req_log(ISC_LOG_DEBUG(3), "%s: request %p: %s", __func__, request,
isc_result_totext(result));
request->result = result;
/*
* Do not call request->cb directly as it introduces a dead lock
* between dns_zonemgr_shutdown and sendtoprimary in lib/dns/zone.c
* zone->lock.
*/
dns_request_ref(request);
isc_async_run(request->loop, req_sendevent_cb, request);
}
static void
req_destroy(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
REQUIRE(!ISC_LINK_LINKED(request, link));
req_log(ISC_LOG_DEBUG(3), "%s: request %p", __func__, request);
/*
* These should have been cleaned up before the
* completion event was sent.
*/
INSIST(!ISC_LINK_LINKED(request, link));
INSIST(request->dispentry == NULL);
INSIST(request->dispatch == NULL);
request->magic = 0;
if (request->query != NULL) {
isc_buffer_free(&request->query);
}
if (request->answer != NULL) {
isc_buffer_free(&request->answer);
}
if (request->tsig != NULL) {
isc_buffer_free(&request->tsig);
}
if (request->tsigkey != NULL) {
dns_tsigkey_detach(&request->tsigkey);
}
if (request->requestmgr != NULL) {
dns_requestmgr_detach(&request->requestmgr);
}
isc_mem_putanddetach(&request->mctx, request, sizeof(*request));
}
void *
dns_request_getarg(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
return request->arg;
}
isc_result_t
dns_request_getresult(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
REQUIRE(request->tid == isc_tid());
return request->result;
}
#if DNS_REQUEST_TRACE
ISC_REFCOUNT_TRACE_IMPL(dns_request, req_destroy);
#else
ISC_REFCOUNT_IMPL(dns_request, req_destroy);
#endif
static void
req_log(int level, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
isc_log_vwrite(DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST, level,
fmt, ap);
va_end(ap);
}