configure_server_quota(maps, "recursive-clients",
&server->sctx->recursionquota);
configure_server_quota(maps, "update-quota", &server->sctx->updquota);
+ configure_server_quota(maps, "sig0checks-quota",
+ &server->sctx->sig0checksquota);
max = isc_quota_getmax(&server->sctx->recursionquota);
if (max > 1000) {
} else {
softquota = (max * 90) / 100;
}
-
isc_quota_soft(&server->sctx->recursionquota, softquota);
+ obj = NULL;
+ result = named_config_get(maps, "sig0checks-quota-exempt", &obj);
+ if (result == ISC_R_SUCCESS) {
+ result = cfg_acl_fromconfig(
+ obj, config, named_g_lctx, named_g_aclconfctx,
+ named_g_mctx, 0, &server->sctx->sig0checksquota_exempt);
+ INSIST(result == ISC_R_SUCCESS);
+ }
+
+ obj = NULL;
+ result = named_config_get(maps, "sig0checks-quota-maxwait-ms", &obj);
+ if (result == ISC_R_SUCCESS) {
+ server->sctx->sig0checksquota_maxwaitms = cfg_obj_asuint32(obj);
+ }
+
/*
* Set "blackhole". Only legal at options level; there is
* no default.
*/
static isc_result_t
get_matching_view(isc_netaddr_t *srcaddr, isc_netaddr_t *destaddr,
- dns_message_t *message, dns_aclenv_t *env,
+ dns_message_t *message, dns_aclenv_t *env, ns_server_t *sctx,
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);
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 */
*sigresult = dns_message_rechecksig(message, view);
+ if (sig0_qresult == ISC_R_SUCCESS) {
+ isc_quota_release(&sctx->sig0checksquota);
+ }
if (*sigresult == ISC_R_SUCCESS) {
- dns_tsigkey_t *tsigkey;
-
- tsigkey = message->tsigkey;
- tsig = dns_tsigkey_identity(tsigkey);
+ tsig = dns_tsigkey_identity(message->tsigkey);
}
if (dns_acl_allowed(srcaddr, tsig, view->matchclients,
atomic_uint_fast64_t ns_client_requests = 0;
+static atomic_uint_fast32_t last_sigchecks_quota_log = 0;
+
+static bool
+can_log_sigchecks_quota(void) {
+ isc_stdtime_t last;
+ isc_stdtime_t now = isc_stdtime_now();
+ last = atomic_exchange_relaxed(&last_sigchecks_quota_log, now);
+ if (now != last) {
+ return (true);
+ }
+
+ return (false);
+}
+
static void
clientmgr_destroy_cb(void *arg);
static void
ns_client_dumpmessage(ns_client_t *client, const char *reason);
static void
+ns_client_request_continue(void *arg);
+static void
compute_cookie(ns_client_t *client, uint32_t when, const unsigned char *secret,
isc_buffer_t *buf);
ns_clientmgr_detach(&manager);
}
+static isc_result_t
+ns_client_setup_view(ns_client_t *client, isc_netaddr_t *netaddr) {
+ isc_result_t result;
+
+ 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);
+
+ 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");
+ }
+ }
+
+ return (result);
+}
+
/*
* Handle an incoming request event from the socket (UDP case)
* or tcpmsg (TCP case).
isc_region_t *region, void *arg) {
ns_client_t *client = NULL;
isc_result_t result;
- isc_result_t sigresult = ISC_R_SUCCESS;
- isc_buffer_t *buffer = NULL;
- isc_buffer_t tbuffer;
dns_rdataset_t *opt = NULL;
- const dns_name_t *signame = NULL;
- bool ra; /* Recursion available. */
isc_netaddr_t netaddr;
int match;
dns_messageid_t id;
bool notimp;
size_t reqsize;
dns_aclenv_t *env = NULL;
-#ifdef HAVE_DNSTAP
- dns_transport_type_t transport_type;
- dns_dtmsgtype_t dtmsgtype;
-#endif /* ifdef HAVE_DNSTAP */
- static const char *ra_reasons[] = {
- "ACLs not processed yet",
- "no resolver in view",
- "recursion not enabled for view",
- "allow-recursion did not match",
- "allow-query-cache did not match",
- "allow-recursion-on did not match",
- "allow-query-cache-on did not match",
- };
- enum refusal_reasons {
- INVALID,
- NO_RESOLVER,
- RECURSION_DISABLED,
- ALLOW_RECURSION,
- ALLOW_QUERY_CACHE,
- ALLOW_RECURSION_ON,
- ALLOW_QUERY_CACHE_ON
- } ra_refusal_reason = INVALID;
if (eresult != ISC_R_SUCCESS) {
return;
(void)atomic_fetch_add_relaxed(&ns_client_requests, 1);
- isc_buffer_init(&tbuffer, region->base, region->length);
- isc_buffer_add(&tbuffer, region->length);
- buffer = &tbuffer;
+ isc_buffer_init(&client->tbuffer, region->base, region->length);
+ isc_buffer_add(&client->tbuffer, region->length);
+ client->buffer = &client->tbuffer;
client->peeraddr = isc_nmhandle_peeraddr(handle);
client->peeraddr_valid = true;
- reqsize = isc_buffer_usedlength(buffer);
+ reqsize = isc_buffer_usedlength(client->buffer);
client->state = NS_CLIENTSTATE_WORKING;
ISC_LOG_DEBUG(3), "%s request",
TCP_CLIENT(client) ? "TCP" : "UDP");
- result = dns_message_peekheader(buffer, &id, &flags);
+ result = dns_message_peekheader(client->buffer, &id, &flags);
if (result != ISC_R_SUCCESS) {
/*
* There isn't enough header to determine whether
/*
* It's a request. Parse it.
*/
- result = dns_message_parse(client->message, buffer, 0);
+ result = dns_message_parse(client->message, client->buffer, 0);
if (result != ISC_R_SUCCESS) {
/*
* Parsing the request failed. Send a response
client->destsockaddr = isc_nmhandle_localaddr(handle);
isc_netaddr_fromsockaddr(&client->destaddr, &client->destsockaddr);
- result = client->manager->sctx->matchingview(
- &netaddr, &client->destaddr, client->message, env, &sigresult,
- &client->view);
- if (result != ISC_R_SUCCESS) {
- char classname[DNS_RDATACLASS_FORMATSIZE];
-
- /*
- * Do a dummy TSIG verification attempt so that the
- * response will have a TSIG if the query did, as
- * required by RFC2845.
- */
- isc_buffer_t b;
- isc_region_t *r;
-
- 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));
+ 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(1),
- "no matching view in class '%s'", classname);
- ns_client_dumpmessage(client, "no matching view in class");
+ 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);
return;
}
+ ns_client_request_continue(client);
+}
+
+static void
+ns_client_request_continue(void *arg) {
+ ns_client_t *client = arg;
+ isc_netaddr_t netaddr;
+ const dns_name_t *signame = NULL;
+ bool ra; /* Recursion available. */
+ isc_result_t result;
+ static const char *ra_reasons[] = {
+ "ACLs not processed yet",
+ "no resolver in view",
+ "recursion not enabled for view",
+ "allow-recursion did not match",
+ "allow-query-cache did not match",
+ "allow-recursion-on did not match",
+ "allow-query-cache-on did not match",
+ };
+ enum refusal_reasons {
+ INVALID,
+ NO_RESOLVER,
+ RECURSION_DISABLED,
+ ALLOW_RECURSION,
+ ALLOW_QUERY_CACHE,
+ ALLOW_RECURSION_ON,
+ ALLOW_QUERY_CACHE_ON
+ } ra_refusal_reason = INVALID;
+#ifdef HAVE_DNSTAP
+ dns_transport_type_t transport_type;
+ dns_dtmsgtype_t dtmsgtype;
+#endif /* ifdef HAVE_DNSTAP */
+
+ /*
+ * This function could be running asynchronously if a quota was reached
+ * before, and named was waiting for available quota. 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);
+
+ wait_us = isc_time_microdiff(&client->tnow,
+ &client->requesttime);
+ maxwait_us = US_PER_MS *
+ client->manager->sctx->sig0checksquota_maxwaitms;
+ if (wait_us > maxwait_us) {
+ isc_buffer_t b;
+ isc_region_t *r;
+
+ ns_client_log(client, NS_LOGCATEGORY_CLIENT,
+ NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(5),
+ "client reached max wait time for quota");
+
+ /*
+ * 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);
+
+ ns_client_extendederror(client, DNS_EDE_PROHIBITED,
+ NULL);
+ 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) {
+ 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;
+ }
+ }
+
if (isc_nm_is_proxy_handle(client->handle)) {
char fmtbuf[ISC_SOCKADDR_FORMATSIZE] = { 0 };
isc_netaddr_t real_local_addr, real_peer_addr;
"ACL",
fmtbuf);
}
- isc_nm_bad_request(handle);
- return;
+ isc_nm_bad_request(client->handle);
+ goto cleanup;
}
/* allow by default */
"'allow-proxy-on' ACL",
fmtbuf);
}
- isc_nm_bad_request(handle);
- return;
+ isc_nm_bad_request(client->handle);
+ goto cleanup;
}
}
if (!(client->message->tsigstatus == dns_tsigerror_badkey &&
client->message->opcode == dns_opcode_update))
{
- ns_client_error(client, sigresult);
- return;
+ ns_client_error(client, client->sigresult);
+ goto cleanup;
}
}
dns_dt_send(client->view, dtmsgtype, &client->peeraddr,
&client->destsockaddr, transport_type, NULL,
- &client->requesttime, NULL, buffer);
+ &client->requesttime, NULL, client->buffer);
#endif /* HAVE_DNSTAP */
- ns_query_start(client, handle);
+ ns_query_start(client, client->handle);
break;
case dns_opcode_update:
CTRACE("update");
#ifdef HAVE_DNSTAP
dns_dt_send(client->view, DNS_DTTYPE_UQ, &client->peeraddr,
&client->destsockaddr, transport_type, NULL,
- &client->requesttime, NULL, buffer);
+ &client->requesttime, NULL, client->buffer);
#endif /* HAVE_DNSTAP */
ns_client_settimeout(client, 60);
- ns_update_start(client, handle, sigresult);
+ ns_update_start(client, client->handle, client->sigresult);
break;
case dns_opcode_notify:
CTRACE("notify");
ns_client_settimeout(client, 60);
- ns_notify_start(client, handle);
+ ns_notify_start(client, client->handle);
break;
case dns_opcode_iquery:
CTRACE("iquery");
CTRACE("unknown opcode");
ns_client_error(client, DNS_R_NOTIMP);
}
+
+cleanup:
+ 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);
+ }
}
isc_result_t