]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Add (http-)listener-clients option (DoH quota mechanism)
authorArtem Boldariev <artem@boldariev.com>
Fri, 14 May 2021 11:18:57 +0000 (14:18 +0300)
committerArtem Boldariev <artem@boldariev.com>
Fri, 16 Jul 2021 08:50:20 +0000 (11:50 +0300)
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 <name> { ... };"
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.

14 files changed:
bin/named/config.c
bin/named/include/named/globals.h
bin/named/server.c
bin/tests/system/checkconf/good-doh-1.conf
bin/tests/system/checkconf/good-doh-4.conf [new file with mode: 0644]
lib/bind9/check.c
lib/isc/include/isc/quota.h
lib/isc/quota.c
lib/isccfg/namedconf.c
lib/ns/include/ns/listenlist.h
lib/ns/include/ns/server.h
lib/ns/interfacemgr.c
lib/ns/listenlist.c
lib/ns/server.c

index 6f63b0130b8d820f4eec6dc72065052846731d04..3c34dea044fab6c4bae67698f762de8075d22de9 100644 (file)
@@ -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\
index 8663eaf012e738deaa33787ae487838fc4c156e5..78c0bd032f280316e4cc29da70365e403820061c 100644 (file)
@@ -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);
 
 /*
index 0339f0bef804b95c84429a42542de92f21bdbca6..dd2d64aeaf04dc44bcd097bcbc333ab5b742c429 100644 (file)
@@ -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);
 }
 
index 8f983778b153a14f2e45461cc22e539639c2691a..bd23227ce52835ca073496616c256e24e9e8103a 100644 (file)
@@ -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 (file)
index 0000000..2d4c342
--- /dev/null
@@ -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; };
+};
index e97983cce6116e902f49c0cd63feca8a071cafdf..d247962fc7f5734ac3a18c0c30cf823fb57fecf3 100644 (file)
@@ -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;
+                               }
                        }
                }
        }
index 3431006595be845415b0af40945465b3490741ff..6498c6e9568425b7ab4cd0855986210f1bb46972 100644 (file)
@@ -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
index 709f17404228eb935f70c75d29c3f32ecdaa6cf2..7474a3d603003e3ca5934a8b3cd506f0f160b6d4 100644 (file)
@@ -31,6 +31,7 @@ isc_quota_init(isc_quota_t *quota, unsigned int max) {
        atomic_init(&quota->waiting, 0);
        ISC_LIST_INIT(quota->cbs);
        isc_mutex_init(&quota->cblock);
+       ISC_LINK_INIT(quota, link);
        quota->magic = QUOTA_MAGIC;
 }
 
index 7d283b0e4a604d75aff9de519694e61c11c25466..decc0cfe883bd31d39d9c44576e3b2f40e1a2c04 100644 (file)
@@ -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[] = {
index 8393d679808cf4a421a22e32f9090e5acdb45974..d0c91480eaada5f0476cdacf47dfeaffc1f1f1f2 100644 (file)
@@ -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).
  */
index 6af3436abcba77624ced08c277cd4655e192b8bd..d104b85f33e8ebb72032b5f443f7a7c1d9e10a8d 100644 (file)
@@ -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;
index 2d0d60ca6da56338bdaccb7219e56ee0a7c006e8..38a1691c87434db7a41efba234f2e1ad31cf2519 100644 (file)
@@ -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;
                }
index 2d88c8654f6a285a71ea023232d778743c9350ce..141416a93c33cf40a8577170859a7d9af21cb880 100644 (file)
@@ -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++) {
index a970a28e89f46657d5f90ef3ca5287a139310ca3..b9dbac6fc8f7926cfef3d11dd4b26030a21f6c80 100644 (file)
@@ -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);
                }