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 <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.
This commit is contained in:
Artem Boldariev 2021-05-18 12:03:58 +03:00
parent 03a557a9bb
commit 590e8e0b86
13 changed files with 67 additions and 23 deletions

View 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\

View 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);

View 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;
}

View 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; };
};

View 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);

View 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,

View 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);

View 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;

View 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,

View 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 }
};

View 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).
*/

View 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;
}

View 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++) {