static isc_result_t
matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
dns_message_t *message, dns_aclenv_t *env, ns_server_t *lsctx,
- isc_result_t *sigresultp, dns_view_t **viewp) {
+ isc_loop_t *loop, isc_job_cb cb, void *cbarg,
+ isc_result_t *sigresultp, isc_result_t *viewpatchresultp,
+ dns_view_t **viewp) {
UNUSED(srcaddr);
UNUSED(destaddr);
UNUSED(message);
UNUSED(env);
UNUSED(lsctx);
+ UNUSED(loop);
+ UNUSED(cb);
+ UNUSED(cbarg);
UNUSED(sigresultp);
*viewp = view;
+ *viewpatchresultp = ISC_R_SUCCESS;
return (ISC_R_SUCCESS);
}
ISC_LINK(struct zonelistentry) link;
};
+/*%
+ * Message-to-view matching context to run message signature validation
+ * asynchronously.
+ */
+typedef struct matching_view_ctx {
+ isc_netaddr_t *srcaddr;
+ isc_netaddr_t *destaddr;
+ dns_message_t *message;
+ dns_aclenv_t *env;
+ ns_server_t *sctx;
+ isc_loop_t *loop;
+ isc_job_cb cb;
+ void *cbarg;
+ isc_result_t *sigresult;
+ isc_result_t *viewmatchresult;
+ isc_result_t quota_result;
+ dns_view_t **viewp;
+ dns_view_t *view;
+} matching_view_ctx_t;
+
/*%
* Configuration context to retain for each view that allows
* new zones to be added at runtime.
isc_loopmgr_resume(named_g_loopmgr);
}
-/*%
- * Find a view that matches the source and destination addresses of a query.
- */
static isc_result_t
-get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
- dns_message_t *message, dns_aclenv_t *env, ns_server_t *sctx,
- isc_result_t *sigresult, dns_view_t **viewp) {
+get_matching_view_sync(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
+ dns_message_t *message, dns_aclenv_t *env,
+ isc_result_t *sigresult, dns_view_t **viewp) {
dns_view_t *view;
- REQUIRE(message != NULL);
- REQUIRE(sctx != NULL);
- REQUIRE(sigresult != NULL);
- REQUIRE(viewp != NULL && *viewp == NULL);
+ /*
+ * We should not be running synchronous view matching if signature
+ * checking involves SIG(0). TSIG has priority of SIG(0), so if TSIG
+ * is set then we proceed anyway.
+ */
+ INSIST(message->tsigkey != NULL || message->tsig != NULL ||
+ message->sig0 == NULL);
for (view = ISC_LIST_HEAD(named_g_server->viewlist); view != NULL;
view = ISC_LIST_NEXT(view, link))
message->rdclass == dns_rdataclass_any)
{
const dns_name_t *tsig = NULL;
- int exempt_match;
- isc_result_t sig0_qresult = ISC_R_UNSET;
-
- if (message->sig0 != NULL) {
- /*
- * If the message has a SIG0 signature and the
- * client is not exempt from the quota, then
- * acquire a quota. If quota is reached, then
- * return early.
- */
- if (sctx->sig0checksquota_exempt != NULL) {
- isc_result_t result = dns_acl_match(
- srcaddr, NULL,
- sctx->sig0checksquota_exempt,
- env, &exempt_match, NULL);
- if (result == ISC_R_SUCCESS &&
- exempt_match > 0)
- {
- sig0_qresult = ISC_R_EXISTS;
- }
- }
- if (sig0_qresult == ISC_R_UNSET) {
- sig0_qresult = isc_quota_acquire(
- &sctx->sig0checksquota);
- }
- if (sig0_qresult == ISC_R_SOFTQUOTA) {
- isc_quota_release(
- &sctx->sig0checksquota);
- }
- if (sig0_qresult != ISC_R_SUCCESS &&
- sig0_qresult != ISC_R_EXISTS)
- {
- return (ISC_R_QUOTA);
- }
- }
- /* Check the signature, then release the quota */
dns_message_resetsig(message);
*sigresult = dns_message_checksig(message, view);
- if (sig0_qresult == ISC_R_SUCCESS) {
- isc_quota_release(&sctx->sig0checksquota);
- }
if (*sigresult == ISC_R_SUCCESS) {
tsig = dns_tsigkey_identity(message->tsigkey);
}
return (ISC_R_NOTFOUND);
}
+static void
+get_matching_view_done(void *cbarg) {
+ matching_view_ctx_t *mvctx = cbarg;
+ dns_message_t *message = mvctx->message;
+
+ if (*mvctx->viewmatchresult == ISC_R_SUCCESS) {
+ INSIST(mvctx->view != NULL);
+ dns_view_attach(mvctx->view, mvctx->viewp);
+ }
+
+ mvctx->cb(mvctx->cbarg);
+
+ if (mvctx->quota_result == ISC_R_SUCCESS) {
+ isc_quota_release(&mvctx->sctx->sig0checksquota);
+ }
+ if (mvctx->view != NULL) {
+ dns_view_detach(&mvctx->view);
+ }
+ isc_loop_detach(&mvctx->loop);
+ ns_server_detach(&mvctx->sctx);
+ isc_mem_put(message->mctx, mvctx, sizeof(*mvctx));
+ dns_message_detach(&message);
+}
+
+static dns_view_t *
+get_matching_view_next(dns_view_t *view, dns_rdataclass_t rdclass) {
+ if (view == NULL) {
+ view = ISC_LIST_HEAD(named_g_server->viewlist);
+ } else {
+ view = ISC_LIST_NEXT(view, link);
+ }
+ while (true) {
+ if (view == NULL || rdclass == view->rdclass ||
+ rdclass == dns_rdataclass_any)
+ {
+ return (view);
+ }
+ view = ISC_LIST_NEXT(view, link);
+ };
+}
+
+static void
+get_matching_view_continue(void *cbarg, isc_result_t result) {
+ matching_view_ctx_t *mvctx = cbarg;
+ dns_view_t *view = NULL;
+ const dns_name_t *tsig = NULL;
+
+ *mvctx->sigresult = result;
+
+ if (result == ISC_R_SUCCESS) {
+ tsig = dns_tsigkey_identity(mvctx->message->tsigkey);
+ }
+
+ if (dns_acl_allowed(mvctx->srcaddr, tsig, mvctx->view->matchclients,
+ mvctx->env) &&
+ dns_acl_allowed(mvctx->destaddr, tsig,
+ mvctx->view->matchdestinations, mvctx->env) &&
+ !(mvctx->view->matchrecursiveonly &&
+ (mvctx->message->flags & DNS_MESSAGEFLAG_RD) == 0))
+ {
+ /*
+ * A matching view is found.
+ */
+ *mvctx->viewmatchresult = ISC_R_SUCCESS;
+ get_matching_view_done(cbarg);
+ return;
+ }
+
+ dns_message_resetsig(mvctx->message);
+
+ view = get_matching_view_next(mvctx->view, mvctx->message->rdclass);
+ dns_view_detach(&mvctx->view);
+ if (view != NULL) {
+ /*
+ * Try the next view.
+ */
+ dns_view_attach(view, &mvctx->view);
+ result = dns_message_checksig_async(
+ mvctx->message, view, mvctx->loop,
+ get_matching_view_continue, mvctx);
+ INSIST(result == DNS_R_WAIT);
+ return;
+ }
+
+ /*
+ * No matching view is found.
+ */
+ *mvctx->viewmatchresult = ISC_R_NOTFOUND;
+ get_matching_view_done(cbarg);
+}
+
+/*%
+ * Find a view that matches the source and destination addresses of a query.
+ */
+static isc_result_t
+get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
+ dns_message_t *message, dns_aclenv_t *env, ns_server_t *sctx,
+ isc_loop_t *loop, isc_job_cb cb, void *cbarg,
+ isc_result_t *sigresult, isc_result_t *viewmatchresult,
+ dns_view_t **viewp) {
+ dns_view_t *view = NULL;
+ isc_result_t result;
+
+ REQUIRE(message != NULL);
+ REQUIRE(sctx != NULL);
+ REQUIRE(loop == NULL || cb != NULL);
+ REQUIRE(sigresult != NULL);
+ REQUIRE(viewmatchresult != NULL);
+ REQUIRE(viewp != NULL && *viewp == NULL);
+
+ /* No offloading is requested if the loop is unset. */
+ if (loop == NULL) {
+ *viewmatchresult = get_matching_view_sync(
+ srcaddr, destaddr, message, env, sigresult, viewp);
+ return (*viewmatchresult);
+ }
+
+ matching_view_ctx_t *mvctx = isc_mem_get(message->mctx, sizeof(*mvctx));
+ *mvctx = (matching_view_ctx_t){
+ .srcaddr = srcaddr,
+ .destaddr = destaddr,
+ .env = env,
+ .cb = cb,
+ .cbarg = cbarg,
+ .sigresult = sigresult,
+ .viewmatchresult = viewmatchresult,
+ .quota_result = ISC_R_UNSET,
+ .viewp = viewp,
+ };
+ ns_server_attach(sctx, &mvctx->sctx);
+ isc_loop_attach(loop, &mvctx->loop);
+ dns_message_attach(message, &mvctx->message);
+
+ dns_message_resetsig(message);
+
+ view = get_matching_view_next(NULL, message->rdclass);
+ if (view == NULL) {
+ *mvctx->viewmatchresult = ISC_R_NOTFOUND;
+ isc_async_run(loop, get_matching_view_done, mvctx);
+ return (DNS_R_WAIT);
+ }
+
+ /*
+ * If the message has a SIG0 signature which we are going to
+ * check, and the client is not exempt from the SIG(0) quota,
+ * then acquire a quota. TSIG has priority over SIG(0), so if
+ * TSIG is set then we don't care.
+ */
+ if (message->tsigkey == NULL && message->tsig == NULL &&
+ message->sig0 != NULL)
+ {
+ if (sctx->sig0checksquota_exempt != NULL) {
+ int exempt_match;
+
+ result = dns_acl_match(srcaddr, NULL,
+ sctx->sig0checksquota_exempt,
+ env, &exempt_match, NULL);
+ if (result == ISC_R_SUCCESS && exempt_match > 0) {
+ mvctx->quota_result = ISC_R_EXISTS;
+ }
+ }
+ if (mvctx->quota_result == ISC_R_UNSET) {
+ mvctx->quota_result =
+ isc_quota_acquire(&sctx->sig0checksquota);
+ }
+ if (mvctx->quota_result == ISC_R_SOFTQUOTA) {
+ isc_quota_release(&sctx->sig0checksquota);
+ }
+ if (mvctx->quota_result != ISC_R_SUCCESS &&
+ mvctx->quota_result != ISC_R_EXISTS)
+ {
+ *mvctx->viewmatchresult = ISC_R_QUOTA;
+ isc_async_run(loop, get_matching_view_done, mvctx);
+ return (DNS_R_WAIT);
+ }
+ }
+
+ dns_view_attach(view, &mvctx->view);
+ result = dns_message_checksig_async(message, view, loop,
+ get_matching_view_continue, mvctx);
+ INSIST(result == DNS_R_WAIT);
+
+ return (DNS_R_WAIT);
+}
+
void
named_server_create(isc_mem_t *mctx, named_server_t **serverp) {
isc_result_t result;
client->keytag_len = 0;
}
+ isc_timer_stop(client->quotatimer);
+
+ if (client->async) {
+ client->async = false;
+ if (client->handle != NULL) {
+ isc_nmhandle_unref(client->handle);
+ }
+ }
+
client->state = NS_CLIENTSTATE_READY;
#ifdef WANT_SINGLETRACE
dns_message_puttemprdataset(client->message, &client->opt);
}
+ isc_timer_async_destroy(&client->quotatimer);
+
+ if (client->async) {
+ client->async = false;
+ if (client->handle != NULL) {
+ isc_nmhandle_unref(client->handle);
+ }
+ }
+
dns_message_detach(&client->message);
/*
ns_client_setup_view(ns_client_t *client, isc_netaddr_t *netaddr) {
isc_result_t result;
+ client->sigresult = client->viewmatchresult = ISC_R_UNSET;
+
+ if (client->async) {
+ isc_nmhandle_ref(client->handle);
+ }
+
result = client->manager->sctx->matchingview(
netaddr, &client->destaddr, client->message,
client->manager->aclenv, client->manager->sctx,
- &client->sigresult, &client->view);
- if (result != ISC_R_SUCCESS) {
- if (result == ISC_R_QUOTA) {
- if (can_log_sigchecks_quota()) {
- ns_client_log(client, NS_LOGCATEGORY_CLIENT,
- NS_LOGMODULE_CLIENT, ISC_LOG_INFO,
- "SIG(0) checks quota reached");
- ns_client_dumpmessage(
- client, "SIG(0) checks quota reached");
- }
- } else {
- char classname[DNS_RDATACLASS_FORMATSIZE];
- isc_buffer_t b;
- isc_region_t *r;
-
- /*
- * Do a dummy TSIG verification attempt so that the
- * response will have a TSIG if the query did, as
- * required by RFC2845.
- */
- dns_message_resetsig(client->message);
- r = dns_message_getrawmessage(client->message);
- isc_buffer_init(&b, r->base, r->length);
- isc_buffer_add(&b, r->length);
- (void)dns_tsig_verify(&b, client->message, NULL, NULL);
+ client->async ? client->manager->loop : NULL,
+ ns_client_request_continue, client, &client->sigresult,
+ &client->viewmatchresult, &client->view);
- dns_rdataclass_format(client->message->rdclass,
- classname, sizeof(classname));
- ns_client_log(client, NS_LOGCATEGORY_CLIENT,
- NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
- "no matching view in class '%s'",
- classname);
- ns_client_dumpmessage(client,
- "no matching view in class");
- }
+ if (result == DNS_R_WAIT) {
+ INSIST(client->async == true);
+ return (DNS_R_WAIT);
}
+ /*
+ * matchingview() is allowed to return anything other than DNS_R_WAIT
+ * only in non-async mode, in which case 'result' must be equal to
+ * 'client->viewmatchresult'.
+ */
+ INSIST(client->async == false);
+ INSIST(result == client->viewmatchresult);
+
return (result);
}
client->destsockaddr = isc_nmhandle_localaddr(handle);
isc_netaddr_fromsockaddr(&client->destaddr, &client->destsockaddr);
+ /*
+ * Offload view matching only if we are going to check a SIG(0)
+ * signature.
+ */
+ client->async = (client->message->tsigkey == NULL &&
+ client->message->tsig == NULL &&
+ client->message->sig0 != NULL);
+
result = ns_client_setup_view(client, &netaddr);
- if (result == ISC_R_QUOTA) {
- ns_client_log(client, NS_LOGCATEGORY_CLIENT,
- NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(5),
- "client is starting to wait for quota");
- client->async = true;
- isc_nmhandle_ref(client->handle);
- isc_async_run(client->manager->loop, ns_client_request_continue,
- client);
- return;
- } else if (result != ISC_R_SUCCESS) {
- ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
- ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED);
+ if (result == DNS_R_WAIT) {
return;
}
isc_netaddr_t netaddr;
const dns_name_t *signame = NULL;
bool ra; /* Recursion available. */
- isc_result_t result;
+ isc_result_t result = ISC_R_UNSET;
static const char *ra_reasons[] = {
"ACLs not processed yet",
"no resolver in view",
dns_dtmsgtype_t dtmsgtype;
#endif /* ifdef HAVE_DNSTAP */
+ INSIST(client->viewmatchresult != ISC_R_UNSET);
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+
/*
- * This function could be running asynchronously if a quota was reached
- * before, and named was waiting for available quota. In that case we
+ * This function could be running asynchronously when validating a
+ * SIG(0) signature or waiting for a quota for it. In that case we
* need to update the current 'now', and check that named doesn't wait
* for too long.
*/
if (client->async) {
- uint64_t wait_us;
- uint64_t maxwait_us;
-
client->tnow = isc_time_now();
client->now = isc_time_seconds(&client->tnow);
+ }
+ if (client->async && client->viewmatchresult == ISC_R_QUOTA) {
+ uint64_t wait_us, maxwait_us;
+ /* Waiting for a quota. */
wait_us = isc_time_microdiff(&client->tnow,
&client->requesttime);
maxwait_us = US_PER_MS *
ns_client_error(client, DNS_R_REFUSED);
goto cleanup;
}
- }
-
- isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
- if (client->view == NULL) {
- result = ns_client_setup_view(client, &netaddr);
- if (result == ISC_R_QUOTA) {
+ if (can_log_sigchecks_quota()) {
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
- NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(5),
- "client continues waiting for quota");
- client->async = true;
- isc_nmhandle_ref(client->handle);
- isc_async_run(client->manager->loop,
- ns_client_request_continue, client);
- goto cleanup;
- } else if (result != ISC_R_SUCCESS) {
- ns_client_extendederror(client, DNS_EDE_PROHIBITED,
- NULL);
- ns_client_error(client, DNS_R_REFUSED);
- goto cleanup;
+ NS_LOGMODULE_CLIENT, ISC_LOG_INFO,
+ "SIG(0) checks quota reached");
+ ns_client_dumpmessage(client,
+ "SIG(0) checks quota reached");
}
+
+ /* Retry after 10 milliseconds. */
+ isc_interval_t interval;
+ isc_interval_set(&interval, 0, 10 * NS_PER_MS);
+ isc_nmhandle_ref(client->handle);
+ isc_timer_start(client->quotatimer, isc_timertype_once,
+ &interval);
+
+ result = DNS_R_WAIT;
+ goto cleanup;
+ }
+
+ if (client->viewmatchresult != ISC_R_SUCCESS) {
+ char classname[DNS_RDATACLASS_FORMATSIZE];
+ isc_buffer_t b;
+ isc_region_t *r;
+
+ /*
+ * Do a dummy TSIG verification attempt so that the
+ * response will have a TSIG if the query did, as
+ * required by RFC2845.
+ */
+ dns_message_resetsig(client->message);
+ r = dns_message_getrawmessage(client->message);
+ isc_buffer_init(&b, r->base, r->length);
+ isc_buffer_add(&b, r->length);
+ (void)dns_tsig_verify(&b, client->message, NULL, NULL);
+
+ dns_rdataclass_format(client->message->rdclass, classname,
+ sizeof(classname));
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
+ "no matching view in class '%s'", classname);
+ ns_client_dumpmessage(client, "no matching view in class");
+
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
+ ns_client_error(client, DNS_R_REFUSED);
+
+ goto cleanup;
}
if (isc_nm_is_proxy_handle(client->handle)) {
}
cleanup:
+
+ /*
+ * If we are running async then 'unref' the handle, and also if there
+ * are no more async calls scheduled the reset the async flag, so that
+ * the destructor doesn't try to 'unref' the handle too.
+ */
if (client->async) {
- /*
- * Do not detach, only 'unref' the corresponding 'ref' when
- * async was used, because the client can still be reused.
- */
- isc_nmhandle_unref(client->handle);
+ if (client->handle != NULL) {
+ isc_nmhandle_unref(client->handle);
+ }
+ if (result != DNS_R_WAIT) {
+ client->async = false;
+ }
}
}
return (ISC_R_SUCCESS);
}
+static void
+client_quotatimer_event(void *arg) {
+ ns_client_t *client = arg;
+ isc_netaddr_t netaddr;
+ isc_result_t result;
+
+ isc_netaddr_fromsockaddr(&netaddr, &client->peeraddr);
+ result = ns_client_setup_view(client, &netaddr);
+ INSIST(result == DNS_R_WAIT); /* We are in async mode. */
+ isc_nmhandle_unref(client->handle);
+}
+
isc_result_t
ns__client_setup(ns_client_t *client, ns_clientmgr_t *mgr, bool new) {
isc_result_t result;
client->manager->rdspool,
DNS_MESSAGE_INTENTPARSE, &client->message);
+ isc_timer_create(client->manager->loop, client_quotatimer_event,
+ client, &client->quotatimer);
+
/*
* Set magic earlier than usual because ns_query_init()
* and the functions it calls will require it.
.magic = 0,
.manager = client->manager,
.message = client->message,
+ .quotatimer = client->quotatimer,
.query = client->query,
};
}
return (ISC_R_SUCCESS);
cleanup:
+ isc_timer_destroy(&client->quotatimer);
dns_message_detach(&client->message);
ns_clientmgr_detach(&client->manager);
dns_name_t signername; /*%< [T]SIG key name */
dns_name_t *signer; /*%< NULL if not valid sig */
isc_result_t sigresult;
+ isc_result_t viewmatchresult;
isc_buffer_t *buffer;
isc_buffer_t tbuffer;
isc_netaddr_t destaddr;
isc_sockaddr_t destsockaddr;
+ isc_timer_t *quotatimer;
+
dns_ecs_t ecs; /*%< EDNS client subnet sent by client */
/*%
/*%
* Type for callback function to get the view that can answer a query.
*/
-typedef isc_result_t (*ns_matchview_t)(isc_netaddr_t *srcaddr,
- isc_netaddr_t *destaddr,
- dns_message_t *message,
- dns_aclenv_t *env, ns_server_t *sctx,
- isc_result_t *sigresultp,
- dns_view_t **viewp);
+typedef isc_result_t (*ns_matchview_t)(
+ isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr, dns_message_t *message,
+ dns_aclenv_t *env, ns_server_t *sctx, isc_loop_t *loop, isc_job_cb cb,
+ void *cbarg, isc_result_t *sigresultp, isc_result_t *viewmatchresult,
+ dns_view_t **viewp);
/*%
* Server context.
static isc_result_t
matchview(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
dns_message_t *message, dns_aclenv_t *env, ns_server_t *lsctx,
- isc_result_t *sigresultp, dns_view_t **viewp) {
+ isc_loop_t *loop, isc_job_cb cb, void *cbarg,
+ isc_result_t *sigresultp, isc_result_t *viewmatchresultp,
+ dns_view_t **viewp) {
UNUSED(srcaddr);
UNUSED(destaddr);
UNUSED(message);
UNUSED(env);
UNUSED(lsctx);
+ UNUSED(loop);
+ UNUSED(cb);
+ UNUSED(cbarg);
UNUSED(sigresultp);
UNUSED(viewp);
+ *viewmatchresultp = ISC_R_NOTIMPLEMENTED;
return (ISC_R_NOTIMPLEMENTED);
}