]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Make max number of HTTP/2 streams configurable
authorArtem Boldariev <artem@boldariev.com>
Tue, 18 May 2021 09:03:58 +0000 (12:03 +0300)
committerArtem Boldariev <artem@boldariev.com>
Fri, 16 Jul 2021 08:50:22 +0000 (11:50 +0300)
This commit makes number of concurrent HTTP/2 streams per connection
configurable as a mean to fight DDoS attacks. As soon as the limit is
reached, BIND terminates the whole session.

The commit adds a global configuration
option (http-streams-per-connection) which can be overridden in an
http <name> {...} statement like follows:

http local-http-server {
    ...
    streams-per-connection 100;
    ...
};

For now the default value is 100, which should be enough (e.g. NGINX
uses 128, but it is a full-featured WEB-server). When using lower
numbers (e.g. ~70), it is possible to hit the limit with
e.g. flamethrower.

13 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/test_server.c
lib/isc/include/isc/netmgr.h
lib/isc/netmgr/http.c
lib/isc/netmgr/netmgr-int.h
lib/isc/tests/doh_test.c
lib/isccfg/namedconf.c
lib/ns/include/ns/listenlist.h
lib/ns/interfacemgr.c
lib/ns/listenlist.c

index 3c34dea044fab6c4bae67698f762de8075d22de9..96963ae3a8878bf87f521bc8e1dfe6134b52b7ba 100644 (file)
@@ -94,6 +94,7 @@ options {\n\
                            "http-port 80;\n"
                            "https-port 443;\n"
                            "http-listener-clients 300;\n"
+                           "http-streams-per-connection 100;\n"
 #endif
                            "\
        prefetch 2 9;\n\
index 78c0bd032f280316e4cc29da70365e403820061c..7c5c7ca61dde81073ef13bca82359412c699e538 100644 (file)
@@ -78,6 +78,7 @@ 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 in_port_t named_g_http_streams_per_conn INIT(0);
 
 EXTERN named_server_t *named_g_server INIT(NULL);
 
index dd2d64aeaf04dc44bcd097bcbc333ab5b742c429..9cc1c6e692bebe09b5718b9c6802eff14aabd34a 100644 (file)
@@ -8634,6 +8634,11 @@ load_configuration(const char *filename, named_server_t *server,
        result = named_config_get(maps, "http-listener-clients", &obj);
        INSIST(result == ISC_R_SUCCESS);
        named_g_http_listener_clients = cfg_obj_asuint32(obj);
+
+       obj = NULL;
+       result = named_config_get(maps, "http-streams-per-connection", &obj);
+       INSIST(result == ISC_R_SUCCESS);
+       named_g_http_streams_per_conn = cfg_obj_asuint32(obj);
 #endif
 
        /*
@@ -11332,6 +11337,7 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key,
        const cfg_listelt_t *elt = NULL;
        size_t len = 1, i = 0;
        uint32_t max_clients = named_g_http_listener_clients;
+       uint32_t max_streams = named_g_http_streams_per_conn;
        ns_server_t *server = NULL;
        isc_quota_t *quota = NULL;
 
@@ -11348,6 +11354,8 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key,
         */
        if (http != NULL) {
                const cfg_obj_t *cfg_max_clients = NULL;
+               const cfg_obj_t *cfg_max_streams = NULL;
+
                if (cfg_map_get(http, "endpoints", &eplist) == ISC_R_SUCCESS) {
                        INSIST(eplist != NULL);
                        len = cfg_list_length(eplist, false);
@@ -11358,6 +11366,13 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key,
                        INSIST(cfg_max_clients != NULL);
                        max_clients = cfg_obj_asuint32(cfg_max_clients);
                }
+
+               if (cfg_map_get(http, "streams-per-connection",
+                               &cfg_max_streams) == ISC_R_SUCCESS)
+               {
+                       INSIST(cfg_max_streams != NULL);
+                       max_streams = cfg_obj_asuint32(cfg_max_streams);
+               }
        }
 
        endpoints = isc_mem_allocate(mctx, sizeof(endpoints[0]) * len);
@@ -11383,7 +11398,7 @@ listenelt_http(const cfg_obj_t *http, bool tls, const char *key,
        }
        result = ns_listenelt_create_http(mctx, port, named_g_dscp, NULL, tls,
                                          key, cert, endpoints, len, quota,
-                                         &delt);
+                                         max_streams, &delt);
        if (result != ISC_R_SUCCESS) {
                goto error;
        }
index bd23227ce52835ca073496616c256e24e9e8103a..e99a9e4e70123f72667e692c96707f4e1890a66b 100644 (file)
@@ -17,6 +17,7 @@ tls local-tls {
 http local-http-server {
        endpoints { "/dns-query"; };
        listener-clients 100;
+       streams-per-connection 100;
 };
 
 options {
@@ -24,6 +25,7 @@ options {
        http-port 80;
        https-port 443;
        http-listener-clients 100;
+       http-streams-per-connection 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; };
 };
index 4e566ad14232e05dc291d9e48c7bcbc27d83f7fa..8b0d8ac8a921baeb3ba06fb96f9e253139e1d81f 100644 (file)
@@ -290,7 +290,7 @@ run(void) {
                        isc_tlsctx_createserver(NULL, NULL, &tls_ctx);
                }
                result = isc_nm_listenhttp(netmgr, &sockaddr, 0, NULL, tls_ctx,
-                                          &sock);
+                                          0, &sock);
                if (result == ISC_R_SUCCESS) {
                        result = isc_nm_http_endpoint(sock, DEFAULT_DOH_PATH,
                                                      read_cb, NULL, 0);
index b494426e25afd68f638122323cafc56644243aaa..a7ecf01aba7f542169e6b4bee9e47aa62c761653 100644 (file)
@@ -499,7 +499,7 @@ isc_nm_httpconnect(isc_nm_t *mgr, isc_sockaddr_t *local, isc_sockaddr_t *peer,
 isc_result_t
 isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog,
                  isc_quota_t *quota, isc_tlsctx_t *ctx,
-                 isc_nmsocket_t **sockp);
+                 uint32_t max_concurrent_streams, isc_nmsocket_t **sockp);
 
 isc_result_t
 isc_nm_http_endpoint(isc_nmsocket_t *sock, const char *uri, isc_nm_recv_cb_t cb,
index a7de43aeafaeb72ddce3f2fed66cebd4ffbf0154..a8132e69a91771b73fa977a164e5f8ea1b5ff61a 100644 (file)
@@ -50,8 +50,6 @@
 #define MAX_ALLOWED_DATA_IN_POST \
        (MAX_DNS_MESSAGE_SIZE + MAX_DNS_MESSAGE_SIZE / 2)
 
-#define MAX_STREAMS_PER_SESSION (100)
-
 #define HEADER_MATCH(header, name, namelen)   \
        (((namelen) == sizeof(header) - 1) && \
         (strncasecmp((header), (const char *)(name), (namelen)) == 0))
@@ -145,6 +143,7 @@ struct isc_nm_http_session {
        size_t bufsize;
 
        isc_tlsctx_t *tlsctx;
+       uint32_t max_concurrent_streams;
 
        isc__nm_http_pending_callbacks_t pending_write_callbacks;
        isc_buffer_t *pending_write_data;
@@ -1626,7 +1625,7 @@ server_on_begin_headers_callback(nghttp2_session *ngsession,
                return (NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE);
        }
 
-       if (session->nsstreams >= MAX_STREAMS_PER_SESSION) {
+       if (session->nsstreams >= session->max_concurrent_streams) {
                return (NGHTTP2_ERR_CALLBACK_FAILURE);
        }
 
@@ -2322,7 +2321,7 @@ static int
 server_send_connection_header(isc_nm_http_session_t *session) {
        nghttp2_settings_entry iv[1] = {
                { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS,
-                 MAX_STREAMS_PER_SESSION }
+                 session->max_concurrent_streams }
        };
        int rv;
 
@@ -2402,6 +2401,8 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
        http_transpost_tcp_nodelay(handle);
 
        new_session(httplistensock->mgr->mctx, NULL, &session);
+       session->max_concurrent_streams =
+               httplistensock->h2.max_concurrent_streams;
        initialize_nghttp2_server_session(session);
        handle->sock->h2.session = session;
 
@@ -2417,12 +2418,20 @@ httplisten_acceptcb(isc_nmhandle_t *handle, isc_result_t result, void *cbarg) {
 isc_result_t
 isc_nm_listenhttp(isc_nm_t *mgr, isc_sockaddr_t *iface, int backlog,
                  isc_quota_t *quota, isc_tlsctx_t *ctx,
-                 isc_nmsocket_t **sockp) {
+                 uint32_t max_concurrent_streams, isc_nmsocket_t **sockp) {
        isc_nmsocket_t *sock = NULL;
        isc_result_t result;
 
        sock = isc_mem_get(mgr->mctx, sizeof(*sock));
        isc__nmsocket_init(sock, mgr, isc_nm_httplistener, iface);
+       sock->h2.max_concurrent_streams =
+               NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS;
+
+       if (max_concurrent_streams > 0 &&
+           max_concurrent_streams < NGHTTP2_INITIAL_MAX_CONCURRENT_STREAMS)
+       {
+               sock->h2.max_concurrent_streams = max_concurrent_streams;
+       }
 
        if (ctx != NULL) {
                isc_tlsctx_enable_http2server_alpn(ctx);
index dcc2cb9e245b9b6398c5a99e16a52a261ab33ddf..4a14a57a12c7a27e10de36783d367e3a62d6e52a 100644 (file)
@@ -800,6 +800,9 @@ typedef struct isc_nmsocket_h2 {
 
        isc_nmsocket_t *httpserver;
 
+       /* maximum concurrent streams (server-side) */
+       uint32_t max_concurrent_streams;
+
        isc_http_request_type_t request_type;
        isc_http_scheme_type_t request_scheme;
 
index 7b6ccb1cc35828ad5565329d0b62f3a37273130b..2aaa8727f8a26941d139eeec7c89ceabcc5a968d 100644 (file)
@@ -484,7 +484,7 @@ mock_doh_uv_tcp_bind(void **state) {
        WILL_RETURN(uv_tcp_bind, UV_EADDRINUSE);
 
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, NULL, NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_not_equal(result, ISC_R_SUCCESS);
        assert_null(listen_sock);
 
@@ -501,7 +501,7 @@ doh_noop(void **state) {
        char req_url[256];
 
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, NULL, NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH, noop_read_cb, NULL,
                                      0);
@@ -547,7 +547,7 @@ doh_noresponse(void **state) {
        char req_url[256];
 
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, NULL, NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH, noop_read_cb, NULL,
@@ -648,7 +648,7 @@ doh_timeout_recovery(void **state) {
        char req_url[256];
 
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, NULL, NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        /*
@@ -779,7 +779,7 @@ doh_recv_one(void **state) {
        atomic_store(&nsends, atomic_load(&total_sends));
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
@@ -929,7 +929,7 @@ doh_recv_two(void **state) {
        atomic_store(&nsends, atomic_load(&total_sends));
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
@@ -1049,7 +1049,7 @@ doh_recv_send(void **state) {
 
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
@@ -1153,7 +1153,7 @@ doh_recv_half_send(void **state) {
        atomic_store(&nsends, atomic_load(&total_sends));
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
@@ -1262,7 +1262,7 @@ doh_half_recv_send(void **state) {
        atomic_store(&nsends, atomic_load(&total_sends));
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
@@ -1371,7 +1371,7 @@ doh_half_recv_half_send(void **state) {
        atomic_store(&nsends, atomic_load(&total_sends));
        result = isc_nm_listenhttp(listen_nm, &tcp_listen_addr, 0, quotap,
                                   atomic_load(&use_TLS) ? server_tlsctx : NULL,
-                                  &listen_sock);
+                                  0, &listen_sock);
        assert_int_equal(result, ISC_R_SUCCESS);
 
        result = isc_nm_http_endpoint(listen_sock, DOH_PATH,
index decc0cfe883bd31d39d9c44576e3b2f40e1a2c04..a216cb2c45de0c4cb52b8ba4ef899abbf67d0778 100644 (file)
@@ -1252,11 +1252,14 @@ static cfg_clausedef_t options_clauses[] = {
 #if HAVE_LIBNGHTTP2
        { "http-port", &cfg_type_uint32, 0 },
        { "http-listener-clients", &cfg_type_uint32, 0 },
+       { "http-streams-per-connection", &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 },
+       { "http-streams-per-connection", &cfg_type_uint32,
+         CFG_CLAUSEFLAG_NOTCONFIGURED },
        { "https-port", &cfg_type_uint32, CFG_CLAUSEFLAG_NOTCONFIGURED },
 #endif
        { "querylog", &cfg_type_boolean, 0 },
@@ -3906,6 +3909,7 @@ 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 },
+       { "streams-per-connection", &cfg_type_uint32, 0 },
        { NULL, NULL, 0 }
 };
 
index d0c91480eaada5f0476cdacf47dfeaffc1f1f1f2..348b1ca894a48c04a8d85ad19ca17b7309aef0ee 100644 (file)
@@ -49,6 +49,7 @@ struct ns_listenelt {
        char **       http_endpoints;
        size_t        http_endpoints_number;
        isc_quota_t * http_quota;
+       uint32_t      max_concurrent_streams;
        ISC_LINK(ns_listenelt_t) link;
 };
 
@@ -74,7 +75,8 @@ 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,
-                        isc_quota_t *quota, ns_listenelt_t **target);
+                        isc_quota_t *quota, const uint32_t max_streams,
+                        ns_listenelt_t **target);
 /*%<
  * Create a listen-on list element for HTTP(S).
  */
index 38a1691c87434db7a41efba234f2e1ad31cf2519..2b0f192aa07a0afa864eb0c74a71e3528f81dc42 100644 (file)
@@ -539,13 +539,15 @@ 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, isc_quota_t *quota) {
+                       size_t neps, isc_quota_t *quota,
+                       uint32_t max_concurrent_streams) {
 #if HAVE_LIBNGHTTP2
        isc_result_t result;
        isc_nmsocket_t *sock = NULL;
 
        result = isc_nm_listenhttp(ifp->mgr->nm, &ifp->addr, ifp->mgr->backlog,
-                                  quota, sslctx, &sock);
+                                  quota, sslctx, max_concurrent_streams,
+                                  &sock);
 
        if (result == ISC_R_SUCCESS) {
                for (size_t i = 0; i < neps; i++) {
@@ -588,6 +590,8 @@ ns_interface_listenhttp(ns_interface_t *ifp, isc_tlsctx_t *sslctx, char **eps,
        UNUSED(sslctx);
        UNUSED(eps);
        UNUSED(neps);
+       UNUSED(quota);
+       UNUSED(max_concurrent_streams);
        return (ISC_R_NOTIMPLEMENTED);
 #endif
 }
@@ -611,7 +615,8 @@ ns_interface_setup(ns_interfacemgr_t *mgr, isc_sockaddr_t *addr,
        if (elt->is_http) {
                result = ns_interface_listenhttp(
                        ifp, elt->sslctx, elt->http_endpoints,
-                       elt->http_endpoints_number, elt->http_quota);
+                       elt->http_endpoints_number, elt->http_quota,
+                       elt->max_concurrent_streams);
                if (result != ISC_R_SUCCESS) {
                        goto cleanup_interface;
                }
index 141416a93c33cf40a8577170859a7d9af21cb880..9c445a710afe755bd22422d005f0d49729262d27 100644 (file)
@@ -61,7 +61,8 @@ 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,
-                        isc_quota_t *quota, ns_listenelt_t **target) {
+                        isc_quota_t *quota, const uint32_t max_streams,
+                        ns_listenelt_t **target) {
        isc_result_t result;
 
        REQUIRE(target != NULL && *target == NULL);
@@ -75,6 +76,7 @@ ns_listenelt_create_http(isc_mem_t *mctx, in_port_t http_port, isc_dscp_t dscp,
                (*target)->http_endpoints = endpoints;
                (*target)->http_endpoints_number = nendpoints;
                (*target)->http_quota = quota;
+               (*target)->max_concurrent_streams = max_streams;
        } else {
                size_t i;
                for (i = 0; i < nendpoints; i++) {