mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-24 02:58:38 +00:00
When dns_request was canceled via dns_requestmgr_shutdown() the cancel event would be propagated on different loop (loop 0) than the loop where request was created on. In turn this would propagate down to isc_netmgr where we require all the events to be called from the matching isc_loop. Pin the dns_requests to the loops and ensure that all the events are called on the associated loop. This in turn allows us to remove the hashed locks on the requests and change the single .requests list to be a per-loop list for the request accounting. Additionally, do some extra cleanup because some race condititions are now not possible as all events on the dns_request are serialized.
1033 lines
25 KiB
C
1033 lines
25 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/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/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;
|
|
|
|
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_dispatch_t *dispatchv4;
|
|
dns_dispatch_t *dispatchv6;
|
|
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;
|
|
unsigned int 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 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_getx(
|
|
requestmgr->mctx, nloops * sizeof(requestmgr->requests[0]),
|
|
ISC_MEM_ZERO);
|
|
for (size_t i = 0; i < nloops; i++) {
|
|
ISC_LIST_INIT(requestmgr->requests[i]);
|
|
|
|
/* unreferenced in requests_shutdown() */
|
|
isc_loop_ref(isc_loop_get(requestmgr->loopmgr, i));
|
|
}
|
|
|
|
dns_dispatchmgr_attach(dispatchmgr, &requestmgr->dispatchmgr);
|
|
|
|
if (dispatchv4 != NULL) {
|
|
dns_dispatch_attach(dispatchv4, &requestmgr->dispatchv4);
|
|
}
|
|
if (dispatchv6 != NULL) {
|
|
dns_dispatch_attach(dispatchv6, &requestmgr->dispatchv6);
|
|
}
|
|
|
|
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_shutdown(void *arg) {
|
|
dns_requestmgr_t *requestmgr = arg;
|
|
dns_request_t *request = NULL, *next = NULL;
|
|
uint32_t tid = isc_tid();
|
|
|
|
ISC_LIST_FOREACH_SAFE (requestmgr->requests[tid], request, link, next) {
|
|
req_log(ISC_LOG_DEBUG(3), "%s(%" PRIu32 ": request %p",
|
|
__func__, tid, request);
|
|
if (DNS_REQUEST_COMPLETE(request)) {
|
|
/* The callback has been already scheduled */
|
|
continue;
|
|
}
|
|
req_sendevent(request, ISC_R_SHUTTINGDOWN);
|
|
}
|
|
|
|
isc_loop_unref(isc_loop_get(requestmgr->loopmgr, tid));
|
|
dns_requestmgr_detach(&requestmgr);
|
|
}
|
|
|
|
void
|
|
dns_requestmgr_shutdown(dns_requestmgr_t *requestmgr) {
|
|
REQUIRE(VALID_REQUESTMGR(requestmgr));
|
|
|
|
req_log(ISC_LOG_DEBUG(3), "%s: %p", __func__, requestmgr);
|
|
|
|
rcu_read_lock();
|
|
INSIST(atomic_compare_exchange_strong(&requestmgr->shuttingdown,
|
|
&(bool){ false }, true));
|
|
rcu_read_unlock();
|
|
|
|
/*
|
|
* Wait until all dns_request_create{raw}() are finished, so
|
|
* there will be no new requests added to the lists.
|
|
*/
|
|
synchronize_rcu();
|
|
|
|
uint32_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 == tid) {
|
|
/* Run the current loop synchronously */
|
|
requests_shutdown(requestmgr);
|
|
continue;
|
|
}
|
|
|
|
isc_loop_t *loop = isc_loop_get(requestmgr->loopmgr, i);
|
|
isc_async_run(loop, requests_shutdown, requestmgr);
|
|
}
|
|
}
|
|
|
|
static void
|
|
requestmgr_destroy(dns_requestmgr_t *requestmgr) {
|
|
req_log(ISC_LOG_DEBUG(3), "%s", __func__);
|
|
|
|
INSIST(atomic_load(&requestmgr->shuttingdown));
|
|
|
|
isc_refcount_destroy(&requestmgr->references);
|
|
|
|
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_put(requestmgr->mctx, requestmgr->requests,
|
|
nloops * sizeof(requestmgr->requests[0]));
|
|
|
|
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);
|
|
}
|
|
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 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->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_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, 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,
|
|
dns_dispatch_t **dispatchp) {
|
|
isc_result_t result;
|
|
|
|
if (tcp) {
|
|
result = tcp_dispatch(newtcp, requestmgr, srcaddr, destaddr,
|
|
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 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(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, 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,
|
|
&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->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 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(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, 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,
|
|
&request->dispatch);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
result = dns_dispatch_add(request->dispatch, loop, 0, 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);
|
|
}
|
|
|
|
void
|
|
dns_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 */
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
isc_refcount_destroy(&request->references);
|
|
|
|
/*
|
|
* 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_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_REQUEST,
|
|
level, fmt, ap);
|
|
va_end(ap);
|
|
}
|