From: Artem Boldariev Date: Fri, 14 May 2021 11:18:57 +0000 (+0300) Subject: Add (http-)listener-clients option (DoH quota mechanism) X-Git-Tag: v9.17.17~20^2~6 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=03a557a9bbfb1ef930620d73ae8c0c6c763088be;p=thirdparty%2Fbind9.git Add (http-)listener-clients option (DoH quota mechanism) This commit adds support for http-listener-clients global options as well as ability to override the default in an HTTP server description, like: http local-http-server { ... listener-clients 100; ... }; This way we have ability to specify per-listener active connections quota globally and then override it when required. This is exactly what AT&T requested us: they wanted a functionality to specify quota globally and then override it for specific IPs. This change functionality makes such a configuration possible. It makes sense: for example, one could have different quotas for internal and external clients. Or, for example, one could use BIND's internal ability to serve encrypted DoH with some sane quota value for internal clients, while having un-encrypted DoH listener without quota to put BIND behind a load balancer doing TLS offloading for external clients. Moreover, the code no more shares the quota with TCP, which makes little sense anyway (see tcp-clients option), because of the nature of interaction of DoH clients: they tend to keep idle opened connections for longer periods of time, preventing the TCP and TLS client from being served. Thus, the need to have a separate, generally larger, quota for them. Also, the change makes any option within "http { ... };" statement optional, making it easier to override only required default options. By default, the DoH connections are limited to 300 per listener. I hope that it is a good initial guesstimate. --- diff --git a/bin/named/config.c b/bin/named/config.c index 6f63b0130b8..3c34dea044f 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -93,6 +93,7 @@ options {\n\ #if HAVE_LIBNGHTTP2 "http-port 80;\n" "https-port 443;\n" + "http-listener-clients 300;\n" #endif "\ prefetch 2 9;\n\ diff --git a/bin/named/include/named/globals.h b/bin/named/include/named/globals.h index 8663eaf012e..78c0bd032f2 100644 --- a/bin/named/include/named/globals.h +++ b/bin/named/include/named/globals.h @@ -77,6 +77,8 @@ EXTERN in_port_t named_g_httpsport INIT(0); EXTERN in_port_t named_g_httpport INIT(0); EXTERN isc_dscp_t named_g_dscp INIT(-1); +EXTERN in_port_t named_g_http_listener_clients INIT(0); + EXTERN named_server_t *named_g_server INIT(NULL); /* diff --git a/bin/named/server.c b/bin/named/server.c index 0339f0bef80..dd2d64aeaf0 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -8629,6 +8629,11 @@ load_configuration(const char *filename, named_server_t *server, result = named_config_get(maps, "https-port", &obj); INSIST(result == ISC_R_SUCCESS); named_g_httpsport = (in_port_t)cfg_obj_asuint32(obj); + + obj = NULL; + result = named_config_get(maps, "http-listener-clients", &obj); + INSIST(result == ISC_R_SUCCESS); + named_g_http_listener_clients = cfg_obj_asuint32(obj); #endif /* @@ -11326,6 +11331,9 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key, const cfg_obj_t *eplist = NULL; const cfg_listelt_t *elt = NULL; size_t len = 1, i = 0; + uint32_t max_clients = named_g_http_listener_clients; + ns_server_t *server = NULL; + isc_quota_t *quota = NULL; REQUIRE(target != NULL && *target == NULL); REQUIRE((key == NULL) == (cert == NULL)); @@ -11339,13 +11347,22 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key, * of "/dns-query". */ if (http != NULL) { - CHECK(cfg_map_get(http, "endpoints", &eplist)); - len = cfg_list_length(eplist, false); + const cfg_obj_t *cfg_max_clients = NULL; + if (cfg_map_get(http, "endpoints", &eplist) == ISC_R_SUCCESS) { + INSIST(eplist != NULL); + len = cfg_list_length(eplist, false); + } + + if (cfg_map_get(http, "listener-clients", &cfg_max_clients) == + ISC_R_SUCCESS) { + INSIST(cfg_max_clients != NULL); + max_clients = cfg_obj_asuint32(cfg_max_clients); + } } endpoints = isc_mem_allocate(mctx, sizeof(endpoints[0]) * len); - if (http != NULL) { + if (http != NULL && eplist != NULL) { for (elt = cfg_list_first(eplist); elt != NULL; elt = cfg_list_next(elt)) { const cfg_obj_t *ep = cfg_listelt_value(elt); @@ -11358,18 +11375,39 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key, INSIST(i == len); + INSIST(named_g_server != NULL); + ns_server_attach(named_g_server->sctx, &server); + if (max_clients > 0) { + quota = isc_mem_get(mctx, sizeof(isc_quota_t)); + isc_quota_init(quota, max_clients); + } result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls, - key, cert, endpoints, len, &delt); + key, cert, endpoints, len, quota, + &delt); if (result != ISC_R_SUCCESS) { - if (delt != NULL) { - ns_listenelt_destroy(delt); - } - return (result); + goto error; } + if (quota != NULL) { + ISC_LIST_APPEND(server->http_quotas, quota, link); + } + ns_server_detach(&server); + *target = delt; -cleanup: + return (result); +error: + if (delt != NULL) { + ns_listenelt_destroy(delt); + } + if (quota != NULL) { + isc_quota_destroy(quota); + isc_mem_put(mctx, quota, sizeof(*quota)); + } + + if (server != NULL) { + ns_server_detach(&server); + } return (result); } diff --git a/bin/tests/system/checkconf/good-doh-1.conf b/bin/tests/system/checkconf/good-doh-1.conf index 8f983778b15..bd23227ce52 100644 --- a/bin/tests/system/checkconf/good-doh-1.conf +++ b/bin/tests/system/checkconf/good-doh-1.conf @@ -16,12 +16,14 @@ tls local-tls { http local-http-server { endpoints { "/dns-query"; }; + listener-clients 100; }; options { listen-on { 10.53.0.1; }; http-port 80; https-port 443; + http-listener-clients 100; listen-on port 443 tls local-tls http local-http-server { 10.53.0.1; }; listen-on port 8080 tls none http local-http-server { 10.53.0.1; }; }; diff --git a/bin/tests/system/checkconf/good-doh-4.conf b/bin/tests/system/checkconf/good-doh-4.conf new file mode 100644 index 00000000000..2d4c342d43d --- /dev/null +++ b/bin/tests/system/checkconf/good-doh-4.conf @@ -0,0 +1,29 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +tls local-tls { + key-file "key.pem"; + cert-file "cert.pem"; +}; + +# Use the default values only - just to make sure that we could +# override only values which we need and there is no required ones. +http empty-http-server { +}; + +options { + listen-on { 10.53.0.1; }; + http-port 80; + https-port 443; + http-listener-clients 100; + listen-on port 443 tls local-tls http empty-http-server { 10.53.0.1; }; + listen-on port 8080 tls none http empty-http-server { 10.53.0.1; }; +}; diff --git a/lib/bind9/check.c b/lib/bind9/check.c index e97983cce61..d247962fc7f 100644 --- a/lib/bind9/check.c +++ b/lib/bind9/check.c @@ -1986,17 +1986,19 @@ bind9_check_httpserver(const cfg_obj_t *http, isc_log_t *logctx, /* Check endpoints are valid */ tresult = cfg_map_get(http, "endpoints", &eps); - RUNTIME_CHECK(tresult == ISC_R_SUCCESS); - for (elt = cfg_list_first(eps); elt != NULL; elt = cfg_list_next(elt)) { - const cfg_obj_t *ep = cfg_listelt_value(elt); - const char *path = cfg_obj_asstring(ep); - if (!isc_nm_http_path_isvalid(path)) { - cfg_obj_log(eps, logctx, ISC_LOG_ERROR, - "endpoint '%s' is not a " - "valid absolute HTTP path", - path); - if (result == ISC_R_SUCCESS) { - result = ISC_R_FAILURE; + if (tresult == ISC_R_SUCCESS) { + for (elt = cfg_list_first(eps); elt != NULL; + elt = cfg_list_next(elt)) { + const cfg_obj_t *ep = cfg_listelt_value(elt); + const char *path = cfg_obj_asstring(ep); + if (!isc_nm_http_path_isvalid(path)) { + cfg_obj_log(eps, logctx, ISC_LOG_ERROR, + "endpoint '%s' is not a " + "valid absolute HTTP path", + path); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } } } } diff --git a/lib/isc/include/isc/quota.h b/lib/isc/include/isc/quota.h index 3431006595b..6498c6e9568 100644 --- a/lib/isc/include/isc/quota.h +++ b/lib/isc/include/isc/quota.h @@ -60,6 +60,7 @@ struct isc_quota { atomic_uint_fast32_t waiting; isc_mutex_t cblock; ISC_LIST(isc_quota_cb_t) cbs; + ISC_LINK(isc_quota_t) link; }; void diff --git a/lib/isc/quota.c b/lib/isc/quota.c index 709f1740422..7474a3d6030 100644 --- a/lib/isc/quota.c +++ b/lib/isc/quota.c @@ -31,6 +31,7 @@ isc_quota_init(isc_quota_t *quota, unsigned int max) { atomic_init("a->waiting, 0); ISC_LIST_INIT(quota->cbs); isc_mutex_init("a->cblock); + ISC_LINK_INIT(quota, link); quota->magic = QUOTA_MAGIC; } diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 7d283b0e4a6..decc0cfe883 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -1251,9 +1251,12 @@ static cfg_clausedef_t options_clauses[] = { { "tls-port", &cfg_type_uint32, 0 }, #if HAVE_LIBNGHTTP2 { "http-port", &cfg_type_uint32, 0 }, + { "http-listener-clients", &cfg_type_uint32, 0 }, { "https-port", &cfg_type_uint32, 0 }, #else { "http-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, + { "http-listener-clients", &cfg_type_uint32, + CFG_CLAUSEFLAG_NOTCONFIGURED }, { "https-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif { "querylog", &cfg_type_boolean, 0 }, @@ -3902,6 +3905,8 @@ static cfg_type_t cfg_type_bracketed_http_endpoint_list = { static cfg_clausedef_t cfg_http_description_clauses[] = { { "endpoints", &cfg_type_bracketed_http_endpoint_list, 0 }, + { "listener-clients", &cfg_type_uint32, 0 }, + { NULL, NULL, 0 } }; static cfg_clausedef_t *http_description_clausesets[] = { diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index 8393d679808..d0c91480eaa 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -48,6 +48,7 @@ struct ns_listenelt { isc_tlsctx_t *sslctx; char ** http_endpoints; size_t http_endpoints_number; + isc_quota_t * http_quota; ISC_LINK(ns_listenelt_t) link; }; @@ -73,7 +74,7 @@ isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, dns_acl_t *acl, bool tls, const char *key, const char *cert, char **endpoints, size_t nendpoints, - ns_listenelt_t **target); + isc_quota_t *quota, ns_listenelt_t **target); /*%< * Create a listen-on list element for HTTP(S). */ diff --git a/lib/ns/include/ns/server.h b/lib/ns/include/ns/server.h index 6af3436abcb..d104b85f33e 100644 --- a/lib/ns/include/ns/server.h +++ b/lib/ns/include/ns/server.h @@ -83,6 +83,7 @@ struct ns_server { isc_quota_t recursionquota; isc_quota_t tcpquota; isc_quota_t xfroutquota; + ISC_LIST(isc_quota_t) http_quotas; /*% Test options and other configurables */ uint32_t options; diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index 2d0d60ca6da..38a1691c874 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -539,13 +539,13 @@ ns_interface_listentls(ns_interface_t *ifp, isc_tlsctx_t *sslctx) { static isc_result_t ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps, - size_t neps) { + size_t neps, isc_quota_t *quota) { #if HAVE_LIBNGHTTP2 isc_result_t result; isc_nmsocket_t *sock = NULL; result = isc_nm_listenhttp(ifp->mgr->nm, &ifp->addr, ifp->mgr->backlog, - &ifp->mgr->sctx->tcpquota, sslctx, &sock); + quota, sslctx, &sock); if (result == ISC_R_SUCCESS) { for (size_t i = 0; i < neps; i++) { @@ -609,9 +609,9 @@ ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr, ifp->dscp = elt->dscp; if (elt->is_http) { - result = ns_interface_listenhttp(ifp, elt->sslctx, - elt->http_endpoints, - elt->http_endpoints_number); + result = ns_interface_listenhttp( + ifp, elt->sslctx, elt->http_endpoints, + elt->http_endpoints_number, elt->http_quota); if (result != ISC_R_SUCCESS) { goto cleanup_interface; } diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index 2d88c8654f6..141416a93c3 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -51,6 +51,7 @@ ns_listenelt_create(isc_mem_t *mctx, in_port_t port, isc_dscp_t dscp, elt->sslctx = sslctx; elt->http_endpoints = NULL; elt->http_endpoints_number = 0; + elt->http_quota = NULL; *target = elt; return (ISC_R_SUCCESS); @@ -60,7 +61,7 @@ isc_result_t ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, dns_acl_t *acl, bool tls, const char *key, const char *cert, char **endpoints, size_t nendpoints, - ns_listenelt_t **target) { + isc_quota_t *quota, ns_listenelt_t **target) { isc_result_t result; REQUIRE(target != NULL && *target == NULL); @@ -73,6 +74,7 @@ ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp, (*target)->is_http = true; (*target)->http_endpoints = endpoints; (*target)->http_endpoints_number = nendpoints; + (*target)->http_quota = quota; } else { size_t i; for (i = 0; i < nendpoints; i++) { diff --git a/lib/ns/server.c b/lib/ns/server.c index a970a28e89f..b9dbac6fc8f 100644 --- a/lib/ns/server.c +++ b/lib/ns/server.c @@ -52,6 +52,7 @@ ns_server_create(isc_mem_t *mctx, ns_matchview_t matchingview, isc_quota_init(&sctx->xfroutquota, 10); isc_quota_init(&sctx->tcpquota, 10); isc_quota_init(&sctx->recursionquota, 100); + ISC_LIST_INIT(sctx->http_quotas); CHECKFATAL(dns_tkeyctx_create(mctx, &sctx->tkeyctx)); @@ -125,6 +126,7 @@ ns_server_detach(ns_server_t **sctxp) { if (isc_refcount_decrement(&sctx->references) == 1) { ns_altsecret_t *altsecret; + isc_quota_t *http_quota; while ((altsecret = ISC_LIST_HEAD(sctx->altsecrets)) != NULL) { ISC_LIST_UNLINK(sctx->altsecrets, altsecret, link); @@ -135,6 +137,18 @@ ns_server_detach(ns_server_t **sctxp) { isc_quota_destroy(&sctx->tcpquota); isc_quota_destroy(&sctx->xfroutquota); + http_quota = ISC_LIST_HEAD(sctx->http_quotas); + while (http_quota != NULL) { + isc_quota_t *next = NULL; + + next = ISC_LIST_NEXT(http_quota, link); + ISC_LIST_DEQUEUE(sctx->http_quotas, http_quota, link); + isc_quota_destroy(http_quota); + isc_mem_put(sctx->mctx, http_quota, + sizeof(*http_quota)); + http_quota = next; + } + if (sctx->server_id != NULL) { isc_mem_free(sctx->mctx, sctx->server_id); }