mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-28 04:34:54 -04:00
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:
parent
03a557a9bb
commit
590e8e0b86
13 changed files with 67 additions and 23 deletions
|
|
@ -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\
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue