2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-25 03:27:18 +00:00
bind/lib/dns/request.c
Ondřej Surý 6f317f27ea Fix the thread safety in the dns_dispatch unit
The dispatches are not thread-bound, and used freely between various
threads (see the dns_resolver and dns_request units for details).

This refactoring make sure that all non-const dns_dispatch_t and
dns_dispentry_t members are accessed under a lock, and both object now
track their internal state (NONE, CONNECTING, CONNECTED, CANCELED)
instead of guessing the state from the state of various struct members.

During the refactoring, the artificial limit DNS_DISPATCH_SOCKSQUOTA on
UDP sockets per dispatch was removed as the limiting needs to happen and
happens on in dns_resolver and limiting the number of UDP sockets
artificially in dispatch could lead to unpredictable behaviour in case
one dispatch has the limit exhausted by others are idle.

The TCP artificial limit of DNS_DISPATCH_MAXREQUESTS makes even less
sense as the TCP connections are only reused in the dns_request API
that's not a heavy user of the outgoing connections.

As a side note, the fact that UDP and TCP dispatch pretends to be same
thing, but in fact the connected UDP is handled from dns_dispentry_t and
dns_dispatch_t acts as a broker, but connected TCP is handled from
dns_dispatch_t and dns_dispatchmgr_t acts as a broker doesn't really
help the clarity of this unit.

This refactoring kept to API almost same - only dns_dispatch_cancel()
and dns_dispatch_done() were merged into dns_dispatch_done() as we need
to cancel active netmgr handles in any case to not leave dangling
connections around.  The functions handling UDP and TCP have been mostly
split to their matching counterparts and the dns_dispatch_<function>
functions are now thing wrappers that call <udp|tcp>_dispatch_<function>
based on the socket type.

More debugging-level logging was added to the unit to accomodate for
this fact.
2022-12-19 11:42:13 +01:00

1137 lines
28 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/magic.h>
#include <isc/mem.h>
#include <isc/netmgr.h>
#include <isc/result.h>
#include <isc/task.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/events.h>
#include <dns/log.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;
#define DNS_REQUEST_NLOCKS 7
struct dns_requestmgr {
unsigned int magic;
isc_refcount_t references;
isc_mutex_t lock;
isc_mem_t *mctx;
/* locked */
isc_taskmgr_t *taskmgr;
dns_dispatchmgr_t *dispatchmgr;
dns_dispatch_t *dispatchv4;
dns_dispatch_t *dispatchv6;
atomic_bool exiting;
unsigned int hash;
isc_mutex_t locks[DNS_REQUEST_NLOCKS];
dns_requestlist_t requests;
};
struct dns_request {
unsigned int magic;
isc_refcount_t references;
unsigned int hash;
isc_mem_t *mctx;
int32_t flags;
ISC_LINK(dns_request_t) link;
isc_buffer_t *query;
isc_buffer_t *answer;
dns_requestevent_t *event;
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 timeout;
unsigned int udpcount;
isc_dscp_t dscp;
};
#define DNS_REQUEST_F_CONNECTING 0x0001
#define DNS_REQUEST_F_SENDING 0x0002
#define DNS_REQUEST_F_CANCELED 0x0004
#define DNS_REQUEST_F_TCP 0x0010
#define DNS_REQUEST_CANCELED(r) (((r)->flags & DNS_REQUEST_F_CANCELED) != 0)
#define DNS_REQUEST_CONNECTING(r) (((r)->flags & DNS_REQUEST_F_CONNECTING) != 0)
#define DNS_REQUEST_SENDING(r) (((r)->flags & DNS_REQUEST_F_SENDING) != 0)
/***
*** Forward
***/
static void
mgr_destroy(dns_requestmgr_t *requestmgr);
static unsigned int
mgr_gethash(dns_requestmgr_t *requestmgr);
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_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_attach(dns_request_t *source, dns_request_t **targetp);
static void
req_detach(dns_request_t **requestp);
static void
req_destroy(dns_request_t *request);
static void
req_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3);
void
request_cancel(dns_request_t *request);
/***
*** Public
***/
isc_result_t
dns_requestmgr_create(isc_mem_t *mctx, isc_taskmgr_t *taskmgr,
dns_dispatchmgr_t *dispatchmgr,
dns_dispatch_t *dispatchv4, dns_dispatch_t *dispatchv6,
dns_requestmgr_t **requestmgrp) {
dns_requestmgr_t *requestmgr;
int i;
req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create");
REQUIRE(requestmgrp != NULL && *requestmgrp == NULL);
REQUIRE(taskmgr != NULL);
REQUIRE(dispatchmgr != NULL);
requestmgr = isc_mem_get(mctx, sizeof(*requestmgr));
*requestmgr = (dns_requestmgr_t){ 0 };
isc_taskmgr_attach(taskmgr, &requestmgr->taskmgr);
dns_dispatchmgr_attach(dispatchmgr, &requestmgr->dispatchmgr);
isc_mutex_init(&requestmgr->lock);
for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
isc_mutex_init(&requestmgr->locks[i]);
}
if (dispatchv4 != NULL) {
dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4);
}
if (dispatchv6 != NULL) {
dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6);
}
isc_mem_attach(mctx, &requestmgr->mctx);
isc_refcount_init(&requestmgr->references, 1);
ISC_LIST_INIT(requestmgr->requests);
atomic_init(&requestmgr->exiting, false);
requestmgr->magic = REQUESTMGR_MAGIC;
req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_create: %p", requestmgr);
*requestmgrp = requestmgr;
return (ISC_R_SUCCESS);
}
void
dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) {
dns_request_t *request;
REQUIRE(VALID_REQUESTMGR(requestmgr));
req_log(ISC_LOG_DEBUG(3), "dns_requestmgr_shutdown: %p", requestmgr);
if (!atomic_compare_exchange_strong(&requestmgr->exiting,
&(bool){ false }, true))
{
return;
}
LOCK(&requestmgr->lock);
for (request = ISC_LIST_HEAD(requestmgr->requests); request != NULL;
request = ISC_LIST_NEXT(request, link))
{
dns_request_cancel(request);
}
UNLOCK(&requestmgr->lock);
}
void
dns_requestmgr_attach(dns_requestmgr_t *source, dns_requestmgr_t **targetp) {
uint_fast32_t ref;
REQUIRE(VALID_REQUESTMGR(source));
REQUIRE(targetp != NULL && *targetp == NULL);
REQUIRE(!atomic_load_acquire(&source->exiting));
ref = isc_refcount_increment(&source->references);
req_log(ISC_LOG_DEBUG(3),
"dns_requestmgr_attach: %p: references = %" PRIuFAST32, source,
ref + 1);
*targetp = source;
}
void
dns_requestmgr_detach(dns_requestmgr_t **requestmgrp) {
dns_requestmgr_t *requestmgr = NULL;
uint_fast32_t ref;
REQUIRE(requestmgrp != NULL && VALID_REQUESTMGR(*requestmgrp));
requestmgr = *requestmgrp;
*requestmgrp = NULL;
ref = isc_refcount_decrement(&requestmgr->references);
req_log(ISC_LOG_DEBUG(3),
"dns_requestmgr_detach: %p: references = %" PRIuFAST32,
requestmgr, ref - 1);
if (ref == 1) {
INSIST(ISC_LIST_EMPTY(requestmgr->requests));
mgr_destroy(requestmgr);
}
}
static void
mgr_destroy(dns_requestmgr_t *requestmgr) {
int i;
req_log(ISC_LOG_DEBUG(3), "mgr_destroy");
isc_refcount_destroy(&requestmgr->references);
isc_mutex_destroy(&requestmgr->lock);
for (i = 0; i < DNS_REQUEST_NLOCKS; i++) {
isc_mutex_destroy(&requestmgr->locks[i]);
}
if (requestmgr->dispatchv4 != NULL) {
dns_dispatch_detach(&requestmgr->dispatchv4);
}
if (requestmgr->dispatchv6 != NULL) {
dns_dispatch_detach(&requestmgr->dispatchv6);
}
if (requestmgr->dispatchmgr != NULL) {
dns_dispatchmgr_detach(&requestmgr->dispatchmgr);
}
if (requestmgr->taskmgr != NULL) {
isc_taskmgr_detach(&requestmgr->taskmgr);
}
requestmgr->magic = 0;
isc_mem_putanddetach(&requestmgr->mctx, requestmgr,
sizeof(*requestmgr));
}
static unsigned int
mgr_gethash(dns_requestmgr_t *requestmgr) {
req_log(ISC_LOG_DEBUG(3), "mgr_gethash");
/*
* Locked by caller.
*/
requestmgr->hash++;
return (requestmgr->hash % DNS_REQUEST_NLOCKS);
}
static void
req_send(dns_request_t *request) {
isc_region_t r;
req_log(ISC_LOG_DEBUG(3), "req_send: request %p", request);
REQUIRE(VALID_REQUEST(request));
isc_buffer_usedregion(request->query, &r);
request->flags |= DNS_REQUEST_F_SENDING;
/* detached in req_senddone() */
req_attach(request, &(dns_request_t *){ NULL });
dns_dispatch_send(request->dispentry, &r, request->dscp);
}
static isc_result_t
new_request(isc_mem_t *mctx, dns_request_t **requestp) {
dns_request_t *request = NULL;
request = isc_mem_get(mctx, sizeof(*request));
*request = (dns_request_t){ .dscp = -1 };
ISC_LINK_INIT(request, link);
isc_refcount_init(&request->references, 1);
isc_mem_attach(mctx, &request->mctx);
request->magic = REQUEST_MAGIC;
*requestp = request;
return (ISC_R_SUCCESS);
}
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,
isc_dscp_t dscp, dns_dispatch_t **dispatchp) {
isc_result_t result;
if (!newtcp) {
result = dns_dispatch_gettcp(requestmgr->dispatchmgr, destaddr,
srcaddr, 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, dscp, 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 = requestmgr->dispatchv4;
break;
case PF_INET6:
disp = requestmgr->dispatchv6;
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,
isc_dscp_t dscp, dns_dispatch_t **dispatchp) {
isc_result_t result;
if (tcp) {
result = tcp_dispatch(newtcp, requestmgr, srcaddr, destaddr,
dscp, 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, isc_dscp_t dscp,
unsigned int options, unsigned int timeout,
unsigned int udptimeout, unsigned int udpretries,
isc_task_t *task, isc_taskaction_t action, 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(task != NULL);
REQUIRE(action != NULL);
REQUIRE(requestp != NULL && *requestp == NULL);
REQUIRE(timeout > 0);
if (srcaddr != NULL) {
REQUIRE(isc_sockaddr_pf(srcaddr) == isc_sockaddr_pf(destaddr));
}
mctx = requestmgr->mctx;
req_log(ISC_LOG_DEBUG(3), "dns_request_createraw");
if (atomic_load_acquire(&requestmgr->exiting)) {
return (ISC_R_SHUTTINGDOWN);
}
if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
return (DNS_R_BLACKHOLED);
}
/* detached in dns_request_destroy() */
result = new_request(mctx, &request);
if (result != ISC_R_SUCCESS) {
return (result);
}
request->udpcount = udpretries;
request->dscp = dscp;
request->event = (dns_requestevent_t *)isc_event_allocate(
mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
sizeof(dns_requestevent_t));
isc_task_attach(task, &(isc_task_t *){ NULL });
request->event->ev_sender = task;
request->event->request = request;
request->event->result = ISC_R_FAILURE;
isc_buffer_usedregion(msgbuf, &r);
if (r.length < DNS_MESSAGE_HEADERLEN || r.length > 65535) {
result = DNS_R_FORMERR;
goto cleanup;
}
if ((options & DNS_REQUESTOPT_TCP) != 0 || r.length > 512) {
tcp = true;
request->timeout = timeout * 1000;
} else {
if (udptimeout == 0) {
udptimeout = timeout / (udpretries + 1);
}
if (udptimeout == 0) {
udptimeout = 1;
}
request->timeout = udptimeout * 1000;
}
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;
}
/* detached in req_connected() */
req_attach(request, &(dns_request_t *){ NULL });
again:
result = get_dispatch(tcp, newtcp, requestmgr, srcaddr, destaddr, dscp,
&request->dispatch);
if (result != ISC_R_SUCCESS) {
goto detach;
}
if ((options & DNS_REQUESTOPT_FIXEDID) != 0) {
id = (r.base[0] << 8) | r.base[1];
dispopt |= DNS_DISPATCHOPT_FIXEDID;
}
result = dns_dispatch_add(request->dispatch, dispopt, 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) {
newtcp = true;
dns_dispatch_detach(&request->dispatch);
goto again;
}
goto detach;
}
/* Add message ID. */
isc_buffer_usedregion(request->query, &r);
r.base[0] = (id >> 8) & 0xff;
r.base[1] = id & 0xff;
LOCK(&requestmgr->lock);
dns_requestmgr_attach(requestmgr, &request->requestmgr);
request->hash = mgr_gethash(requestmgr);
ISC_LIST_APPEND(requestmgr->requests, request, link);
UNLOCK(&requestmgr->lock);
request->destaddr = *destaddr;
request->flags |= DNS_REQUEST_F_CONNECTING;
if (tcp) {
request->flags |= DNS_REQUEST_F_TCP;
}
result = dns_dispatch_connect(request->dispentry);
if (result != ISC_R_SUCCESS) {
goto unlink;
}
req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: request %p", request);
*requestp = request;
return (ISC_R_SUCCESS);
unlink:
LOCK(&requestmgr->lock);
ISC_LIST_UNLINK(requestmgr->requests, request, link);
UNLOCK(&requestmgr->lock);
detach:
/* connect failed, detach here */
req_detach(&(dns_request_t *){ request });
cleanup:
isc_task_detach(&(isc_task_t *){ task });
/* final detach to shut down request */
req_detach(&request);
req_log(ISC_LOG_DEBUG(3), "dns_request_createraw: failed %s",
isc_result_totext(result));
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, isc_dscp_t dscp,
unsigned int options, dns_tsigkey_t *key,
unsigned int timeout, unsigned int udptimeout,
unsigned int udpretries, isc_task_t *task,
isc_taskaction_t action, 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 connected = false;
REQUIRE(VALID_REQUESTMGR(requestmgr));
REQUIRE(message != NULL);
REQUIRE(destaddr != NULL);
REQUIRE(task != NULL);
REQUIRE(action != NULL);
REQUIRE(requestp != NULL && *requestp == NULL);
REQUIRE(timeout > 0);
mctx = requestmgr->mctx;
req_log(ISC_LOG_DEBUG(3), "dns_request_create");
if (atomic_load_acquire(&requestmgr->exiting)) {
return (ISC_R_SHUTTINGDOWN);
}
if (srcaddr != NULL &&
isc_sockaddr_pf(srcaddr) != isc_sockaddr_pf(destaddr))
{
return (ISC_R_FAMILYMISMATCH);
}
if (isblackholed(requestmgr->dispatchmgr, destaddr)) {
return (DNS_R_BLACKHOLED);
}
/* detached in dns_request_destroy() */
result = new_request(mctx, &request);
if (result != ISC_R_SUCCESS) {
return (result);
}
request->udpcount = udpretries;
request->dscp = dscp;
request->event = (dns_requestevent_t *)isc_event_allocate(
mctx, task, DNS_EVENT_REQUESTDONE, action, arg,
sizeof(dns_requestevent_t));
isc_task_attach(task, &(isc_task_t *){ NULL });
request->event->ev_sender = task;
request->event->request = request;
request->event->result = ISC_R_FAILURE;
if (key != NULL) {
dns_tsigkey_attach(key, &request->tsigkey);
}
result = dns_message_settsigkey(message, request->tsigkey);
if (result != ISC_R_SUCCESS) {
goto cleanup;
}
if ((options & DNS_REQUESTOPT_TCP) != 0) {
tcp = true;
request->timeout = timeout * 1000;
} else {
if (udptimeout == 0 && udpretries != 0) {
udptimeout = timeout / (udpretries + 1);
}
if (udptimeout == 0) {
udptimeout = 1;
}
request->timeout = udptimeout * 1000;
}
/* detached in req_connected() */
req_attach(request, &(dns_request_t *){ NULL });
again:
result = get_dispatch(tcp, false, requestmgr, srcaddr, destaddr, dscp,
&request->dispatch);
if (result != ISC_R_SUCCESS) {
goto detach;
}
result = dns_dispatch_add(request->dispatch, 0, request->timeout,
destaddr, transport, tlsctx_cache,
req_connected, req_senddone, req_response,
request, &id, &request->dispentry);
if (result != ISC_R_SUCCESS) {
goto detach;
}
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;
}
if (result != ISC_R_SUCCESS) {
goto detach;
}
result = dns_message_getquerytsig(message, mctx, &request->tsig);
if (result != ISC_R_SUCCESS) {
goto detach;
}
LOCK(&requestmgr->lock);
dns_requestmgr_attach(requestmgr, &request->requestmgr);
request->hash = mgr_gethash(requestmgr);
ISC_LIST_APPEND(requestmgr->requests, request, link);
UNLOCK(&requestmgr->lock);
request->destaddr = *destaddr;
if (tcp && connected) {
req_send(request);
/* no need to call req_connected(), detach here */
req_detach(&(dns_request_t *){ request });
} else {
request->flags |= DNS_REQUEST_F_CONNECTING;
if (tcp) {
request->flags |= DNS_REQUEST_F_TCP;
}
result = dns_dispatch_connect(request->dispentry);
if (result != ISC_R_SUCCESS) {
goto unlink;
}
}
req_log(ISC_LOG_DEBUG(3), "dns_request_create: request %p", request);
*requestp = request;
return (ISC_R_SUCCESS);
unlink:
LOCK(&requestmgr->lock);
ISC_LIST_UNLINK(requestmgr->requests, request, link);
UNLOCK(&requestmgr->lock);
detach:
/* connect failed, detach here */
req_detach(&(dns_request_t *){ request });
cleanup:
isc_task_detach(&(isc_task_t *){ task });
/* final detach to shut down request */
req_detach(&request);
req_log(ISC_LOG_DEBUG(3), "dns_request_create: failed %s",
isc_result_totext(result));
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), "request_render");
/*
* 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);
}
void
request_cancel(dns_request_t *request) {
if (!DNS_REQUEST_CANCELED(request)) {
req_log(ISC_LOG_DEBUG(3), "request_cancel: request %p",
request);
request->flags |= DNS_REQUEST_F_CANCELED;
request->flags &= ~DNS_REQUEST_F_CONNECTING;
if (request->dispentry != NULL) {
dns_dispatch_done(&request->dispentry);
}
dns_dispatch_detach(&request->dispatch);
}
}
void
dns_request_cancel(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
req_log(ISC_LOG_DEBUG(3), "dns_request_cancel: request %p", request);
LOCK(&request->requestmgr->locks[request->hash]);
request_cancel(request);
req_sendevent(request, ISC_R_CANCELED);
UNLOCK(&request->requestmgr->locks[request->hash]);
}
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->answer != NULL);
req_log(ISC_LOG_DEBUG(3), "dns_request_getresponse: request %p",
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));
return (request->answer);
}
bool
dns_request_usedtcp(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
return ((request->flags & DNS_REQUEST_F_TCP) != 0);
}
void
dns_request_destroy(dns_request_t **requestp) {
dns_request_t *request;
REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
request = *requestp;
*requestp = NULL;
req_log(ISC_LOG_DEBUG(3), "dns_request_destroy: request %p", request);
LOCK(&request->requestmgr->lock);
LOCK(&request->requestmgr->locks[request->hash]);
ISC_LIST_UNLINK(request->requestmgr->requests, request, link);
UNLOCK(&request->requestmgr->locks[request->hash]);
UNLOCK(&request->requestmgr->lock);
/*
* These should have been cleaned up before the completion
* event was sent.
*/
INSIST(request->dispentry == NULL);
INSIST(request->dispatch == NULL);
/* final detach to shut down request */
req_detach(&request);
}
static void
req_connected(isc_result_t eresult, isc_region_t *region, void *arg) {
dns_request_t *request = (dns_request_t *)arg;
UNUSED(region);
req_log(ISC_LOG_DEBUG(3), "req_connected: request %p: %s", request,
isc_result_totext(eresult));
REQUIRE(VALID_REQUEST(request));
REQUIRE(DNS_REQUEST_CONNECTING(request) ||
DNS_REQUEST_CANCELED(request));
LOCK(&request->requestmgr->locks[request->hash]);
request->flags &= ~DNS_REQUEST_F_CONNECTING;
if (eresult == ISC_R_TIMEDOUT) {
dns_dispatch_done(&request->dispentry);
dns_dispatch_detach(&request->dispatch);
req_sendevent(request, eresult);
} else if (DNS_REQUEST_CANCELED(request)) {
req_sendevent(request, ISC_R_CANCELED);
} else if (eresult == ISC_R_SUCCESS) {
req_send(request);
} else {
request_cancel(request);
req_sendevent(request, ISC_R_CANCELED);
}
UNLOCK(&request->requestmgr->locks[request->hash]);
/* attached in dns_request_create/_createraw() */
req_detach(&(dns_request_t *){ request });
}
static void
req_senddone(isc_result_t eresult, isc_region_t *region, void *arg) {
dns_request_t *request = (dns_request_t *)arg;
REQUIRE(VALID_REQUEST(request));
REQUIRE(DNS_REQUEST_SENDING(request));
UNUSED(region);
req_log(ISC_LOG_DEBUG(3), "req_senddone: request %p", request);
LOCK(&request->requestmgr->locks[request->hash]);
request->flags &= ~DNS_REQUEST_F_SENDING;
if (DNS_REQUEST_CANCELED(request)) {
if (eresult == ISC_R_TIMEDOUT) {
req_sendevent(request, eresult);
} else {
req_sendevent(request, ISC_R_CANCELED);
}
} else if (eresult != ISC_R_SUCCESS) {
request_cancel(request);
req_sendevent(request, ISC_R_CANCELED);
}
UNLOCK(&request->requestmgr->locks[request->hash]);
/* attached in req_send() */
req_detach(&request);
}
static void
req_response(isc_result_t result, isc_region_t *region, void *arg) {
dns_request_t *request = (dns_request_t *)arg;
if (result == ISC_R_CANCELED) {
return;
}
req_log(ISC_LOG_DEBUG(3), "req_response: request %p: %s", request,
isc_result_totext(result));
if (result == ISC_R_TIMEDOUT) {
LOCK(&request->requestmgr->locks[request->hash]);
if (request->udpcount != 0) {
request->udpcount -= 1;
dns_dispatch_resume(request->dispentry,
request->timeout);
if (!DNS_REQUEST_SENDING(request)) {
req_send(request);
}
UNLOCK(&request->requestmgr->locks[request->hash]);
return;
}
/* The lock is unlocked below */
goto done;
}
REQUIRE(VALID_REQUEST(request));
LOCK(&request->requestmgr->locks[request->hash]);
if (result != ISC_R_SUCCESS) {
goto done;
}
/*
* Copy region to request.
*/
isc_buffer_allocate(request->mctx, &request->answer, region->length);
result = isc_buffer_copyregion(request->answer, region);
if (result != ISC_R_SUCCESS) {
isc_buffer_free(&request->answer);
}
done:
/*
* Cleanup.
*/
if (request->dispentry != NULL) {
dns_dispatch_done(&request->dispentry);
}
request_cancel(request);
/*
* Send completion event.
*/
req_sendevent(request, result);
UNLOCK(&request->requestmgr->locks[request->hash]);
}
static void
req_sendevent(dns_request_t *request, isc_result_t result) {
isc_task_t *task = NULL;
REQUIRE(VALID_REQUEST(request));
if (request->event == NULL) {
return;
}
req_log(ISC_LOG_DEBUG(3), "req_sendevent: request %p", request);
/*
* Lock held by caller.
*/
task = request->event->ev_sender;
request->event->ev_sender = request;
request->event->result = result;
isc_task_sendanddetach(&task, (isc_event_t **)&request->event);
}
static void
req_attach(dns_request_t *source, dns_request_t **targetp) {
REQUIRE(VALID_REQUEST(source));
REQUIRE(targetp != NULL && *targetp == NULL);
isc_refcount_increment(&source->references);
*targetp = source;
}
static void
req_detach(dns_request_t **requestp) {
dns_request_t *request = NULL;
REQUIRE(requestp != NULL && VALID_REQUEST(*requestp));
request = *requestp;
*requestp = NULL;
if (isc_refcount_decrement(&request->references) == 1) {
req_destroy(request);
}
}
static void
req_destroy(dns_request_t *request) {
REQUIRE(VALID_REQUEST(request));
req_log(ISC_LOG_DEBUG(3), "req_destroy: request %p", request);
isc_refcount_destroy(&request->references);
request->magic = 0;
if (request->query != NULL) {
isc_buffer_free(&request->query);
}
if (request->answer != NULL) {
isc_buffer_free(&request->answer);
}
if (request->event != NULL) {
isc_event_free((isc_event_t **)&request->event);
}
if (request->dispentry != NULL) {
dns_dispatch_done(&request->dispentry);
}
if (request->dispatch != NULL) {
dns_dispatch_detach(&request->dispatch);
}
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));
}
static void
req_log(int level, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST,
level, fmt, ap);
va_end(ap);
}