From 590e8e0b86492cd251ffac99c5d790d230d85535 Mon Sep 17 00:00:00 2001 From: Artem Boldariev Date: Tue, 18 May 2021 12:03:58 +0300 Subject: [PATCH] Make max number of HTTP/2 streams configurable 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 {...} 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. --- bin/named/config.c | 1 + bin/named/include/named/globals.h | 1 + bin/named/server.c | 17 ++++++++++++++++- bin/tests/system/checkconf/good-doh-1.conf | 2 ++ bin/tests/test_server.c | 2 +- lib/isc/include/isc/netmgr.h | 2 +- lib/isc/netmgr/http.c | 19 ++++++++++++++----- lib/isc/netmgr/netmgr-int.h | 3 +++ lib/isc/tests/doh_test.c | 20 ++++++++++---------- lib/isccfg/namedconf.c | 4 ++++ lib/ns/include/ns/listenlist.h | 4 +++- lib/ns/interfacemgr.c | 11 ++++++++--- lib/ns/listenlist.c | 4 +++- 13 files changed, 67 insertions(+), 23 deletions(-) diff --git a/bin/named/config.c b/bin/named/config.c index 3c34dea044..96963ae3a8 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -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\ diff --git a/bin/named/include/named/globals.h b/bin/named/include/named/globals.h index 78c0bd032f..7c5c7ca61d 100644 --- a/bin/named/include/named/globals.h +++ b/bin/named/include/named/globals.h @@ -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); diff --git a/bin/named/server.c b/bin/named/server.c index dd2d64aeaf..9cc1c6e692 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -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; } diff --git a/bin/tests/system/checkconf/good-doh-1.conf b/bin/tests/system/checkconf/good-doh-1.conf index bd23227ce5..e99a9e4e70 100644 --- a/bin/tests/system/checkconf/good-doh-1.conf +++ b/bin/tests/system/checkconf/good-doh-1.conf @@ -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; }; }; diff --git a/bin/tests/test_server.c b/bin/tests/test_server.c index 4e566ad142..8b0d8ac8a9 100644 --- a/bin/tests/test_server.c +++ b/bin/tests/test_server.c @@ -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); diff --git a/lib/isc/include/isc/netmgr.h b/lib/isc/include/isc/netmgr.h index b494426e25..a7ecf01aba 100644 --- a/lib/isc/include/isc/netmgr.h +++ b/lib/isc/include/isc/netmgr.h @@ -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, diff --git a/lib/isc/netmgr/http.c b/lib/isc/netmgr/http.c index a7de43aeaf..a8132e69a9 100644 --- a/lib/isc/netmgr/http.c +++ b/lib/isc/netmgr/http.c @@ -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); diff --git a/lib/isc/netmgr/netmgr-int.h b/lib/isc/netmgr/netmgr-int.h index dcc2cb9e24..4a14a57a12 100644 --- a/lib/isc/netmgr/netmgr-int.h +++ b/lib/isc/netmgr/netmgr-int.h @@ -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; diff --git a/lib/isc/tests/doh_test.c b/lib/isc/tests/doh_test.c index 7b6ccb1cc3..2aaa8727f8 100644 --- a/lib/isc/tests/doh_test.c +++ b/lib/isc/tests/doh_test.c @@ -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, diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index decc0cfe88..a216cb2c45 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -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 } }; diff --git a/lib/ns/include/ns/listenlist.h b/lib/ns/include/ns/listenlist.h index d0c91480ea..348b1ca894 100644 --- a/lib/ns/include/ns/listenlist.h +++ b/lib/ns/include/ns/listenlist.h @@ -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). */ diff --git a/lib/ns/interfacemgr.c b/lib/ns/interfacemgr.c index 38a1691c87..2b0f192aa0 100644 --- a/lib/ns/interfacemgr.c +++ b/lib/ns/interfacemgr.c @@ -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; } diff --git a/lib/ns/listenlist.c b/lib/ns/listenlist.c index 141416a93c..9c445a710a 100644 --- a/lib/ns/listenlist.c +++ b/lib/ns/listenlist.c @@ -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++) {