From 03a557a9bbfb1ef930620d73ae8c0c6c763088be Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Fri, 14 May 2021 14:18:57 +0300 Subject: [PATCH] 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. --- bin/named/config.c | 1 + bin/named/include/named/globals.h | 2 + bin/named/server.c | 60 ++++++++++++++++++---- bin/tests/system/checkconf/good-doh-1.conf | 2 + bin/tests/system/checkconf/good-doh-4.conf | 29 +++++++++++ lib/bind9/check.c | 24 +++++---- lib/isc/include/isc/quota.h | 1 + lib/isc/quota.c | 1 + lib/isccfg/namedconf.c | 5 ++ lib/ns/include/ns/listenlist.h | 3 +- lib/ns/include/ns/server.h | 1 + lib/ns/interfacemgr.c | 10 ++-- lib/ns/listenlist.c | 4 +- lib/ns/server.c | 14 +++++ 14 files changed, 128 insertions(+), 29 deletions(-) create mode 100644 bin/tests/system/checkconf/good-doh-4.conf diff --git a/bin/named/config.c b/bin/named/config.c index 6f63b0130b..3c34dea044 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 8663eaf012..78c0bd032f 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 0339f0bef8..dd2d64aeaf 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); - result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls, - key, cert, endpoints, len, &delt); - if (result != ISC_R_SUCCESS) { - if (delt != NULL) { - ns_listenelt_destroy(delt); - } - return (result); + 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, quota, + &delt); + if (result != ISC_R_SUCCESS) { + 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 8f983778b1..bd23227ce5 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 0000000000..2d4c342d43 --- /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 e97983cce6..d247962fc7 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 3431006595..6498c6e956 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 709f174042..7474a3d603 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 7d283b0e4a..decc0cfe88 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 8393d67980..d0c91480ea 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 6af3436abc..d104b85f33 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 2d0d60ca6d..38a1691c87 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 2d88c8654f..141416a93c 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 a970a28e89..b9dbac6fc8 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); }