mirror of
https://github.com/apache/httpd.git
synced 2025-08-08 15:02:10 +03:00
mod_http2: some DoS protection, fix for read after free
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1733113 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
12
CHANGES
12
CHANGES
@@ -1,6 +1,18 @@
|
|||||||
-*- coding: utf-8 -*-
|
-*- coding: utf-8 -*-
|
||||||
Changes with Apache 2.5.0
|
Changes with Apache 2.5.0
|
||||||
|
|
||||||
|
*) mod_http2: Fixed possible read after free when streams were cancelled early
|
||||||
|
by the client.
|
||||||
|
Fixed apr_uint64_t formatting in a log statement to user proper APR def.
|
||||||
|
Number of worker threads allowed to a connection is adjusting dynamically.
|
||||||
|
Starting with 4, the number is doubled when streams can be served without
|
||||||
|
the server ever having to wait on the client. The number is halfed, when
|
||||||
|
the server has to wait on flow control grants. This can happen with a
|
||||||
|
maximum frequency of 5 times per second. When a connection occupies too
|
||||||
|
many workers, repeatable requests (GET/HEAD/OPTIONS) are cancelled and
|
||||||
|
placed back in the queue. Should that not suffice and a stream is busy
|
||||||
|
longer than the server timeout, the connection will be aborted.
|
||||||
|
|
||||||
*) mod_ssl: Fix a possible memory leak on restart for custom [EC]DH params.
|
*) mod_ssl: Fix a possible memory leak on restart for custom [EC]DH params.
|
||||||
[Jan Kaluza, Yann Ylavic]
|
[Jan Kaluza, Yann Ylavic]
|
||||||
|
|
||||||
|
@@ -33,17 +33,47 @@
|
|||||||
#include "h2_task.h"
|
#include "h2_task.h"
|
||||||
#include "h2_util.h"
|
#include "h2_util.h"
|
||||||
|
|
||||||
h2_io *h2_io_create(int id, apr_pool_t *pool)
|
h2_io *h2_io_create(int id, apr_pool_t *pool, const h2_request *request)
|
||||||
{
|
{
|
||||||
h2_io *io = apr_pcalloc(pool, sizeof(*io));
|
h2_io *io = apr_pcalloc(pool, sizeof(*io));
|
||||||
if (io) {
|
if (io) {
|
||||||
io->id = id;
|
io->id = id;
|
||||||
io->pool = pool;
|
io->pool = pool;
|
||||||
io->bucket_alloc = apr_bucket_alloc_create(pool);
|
io->bucket_alloc = apr_bucket_alloc_create(pool);
|
||||||
|
io->request = h2_request_clone(pool, request);
|
||||||
}
|
}
|
||||||
return io;
|
return io;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void h2_io_redo(h2_io *io)
|
||||||
|
{
|
||||||
|
io->worker_started = 0;
|
||||||
|
io->response = NULL;
|
||||||
|
io->rst_error = 0;
|
||||||
|
if (io->bbin) {
|
||||||
|
apr_brigade_cleanup(io->bbin);
|
||||||
|
}
|
||||||
|
if (io->bbout) {
|
||||||
|
apr_brigade_cleanup(io->bbout);
|
||||||
|
}
|
||||||
|
if (io->tmp) {
|
||||||
|
apr_brigade_cleanup(io->tmp);
|
||||||
|
}
|
||||||
|
io->started_at = io->done_at = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int h2_io_is_repeatable(h2_io *io) {
|
||||||
|
if (io->submitted
|
||||||
|
|| io->input_consumed > 0
|
||||||
|
|| !io->request) {
|
||||||
|
/* cannot repeat that. */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (!strcmp("GET", io->request->method)
|
||||||
|
|| !strcmp("HEAD", io->request->method)
|
||||||
|
|| !strcmp("OPTIONS", io->request->method));
|
||||||
|
}
|
||||||
|
|
||||||
void h2_io_set_response(h2_io *io, h2_response *response)
|
void h2_io_set_response(h2_io *io, h2_response *response)
|
||||||
{
|
{
|
||||||
AP_DEBUG_ASSERT(io->pool);
|
AP_DEBUG_ASSERT(io->pool);
|
||||||
|
@@ -49,8 +49,9 @@ struct h2_io {
|
|||||||
apr_bucket_brigade *tmp; /* temporary data for chunking */
|
apr_bucket_brigade *tmp; /* temporary data for chunking */
|
||||||
|
|
||||||
unsigned int orphaned : 1; /* h2_stream is gone for this io */
|
unsigned int orphaned : 1; /* h2_stream is gone for this io */
|
||||||
unsigned int processing_started : 1; /* h2_worker started processing for this io */
|
unsigned int worker_started : 1; /* h2_worker started processing for this io */
|
||||||
unsigned int processing_done: 1; /* h2_worker finished for this io */
|
unsigned int worker_done : 1; /* h2_worker finished for this io */
|
||||||
|
unsigned int submitted : 1; /* response has been submitted to client */
|
||||||
unsigned int request_body : 1; /* iff request has body */
|
unsigned int request_body : 1; /* iff request has body */
|
||||||
unsigned int eos_in : 1; /* input eos has been seen */
|
unsigned int eos_in : 1; /* input eos has been seen */
|
||||||
unsigned int eos_in_written : 1; /* input eos has been forwarded */
|
unsigned int eos_in_written : 1; /* input eos has been forwarded */
|
||||||
@@ -61,6 +62,8 @@ struct h2_io {
|
|||||||
struct apr_thread_cond_t *timed_cond; /* condition to wait on, maybe NULL */
|
struct apr_thread_cond_t *timed_cond; /* condition to wait on, maybe NULL */
|
||||||
apr_time_t timeout_at; /* when IO wait will time out */
|
apr_time_t timeout_at; /* when IO wait will time out */
|
||||||
|
|
||||||
|
apr_time_t started_at; /* when processing started */
|
||||||
|
apr_time_t done_at; /* when processing was done */
|
||||||
apr_size_t input_consumed; /* how many bytes have been read */
|
apr_size_t input_consumed; /* how many bytes have been read */
|
||||||
|
|
||||||
int files_handles_owned;
|
int files_handles_owned;
|
||||||
@@ -73,7 +76,7 @@ struct h2_io {
|
|||||||
/**
|
/**
|
||||||
* Creates a new h2_io for the given stream id.
|
* Creates a new h2_io for the given stream id.
|
||||||
*/
|
*/
|
||||||
h2_io *h2_io_create(int id, apr_pool_t *pool);
|
h2_io *h2_io_create(int id, apr_pool_t *pool, const struct h2_request *request);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the response of this stream.
|
* Set the response of this stream.
|
||||||
@@ -85,6 +88,9 @@ void h2_io_set_response(h2_io *io, struct h2_response *response);
|
|||||||
*/
|
*/
|
||||||
void h2_io_rst(h2_io *io, int error);
|
void h2_io_rst(h2_io *io, int error);
|
||||||
|
|
||||||
|
int h2_io_is_repeatable(h2_io *io);
|
||||||
|
void h2_io_redo(h2_io *io);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The input data is completely queued. Blocked reads will return immediately
|
* The input data is completely queued. Blocked reads will return immediately
|
||||||
* and give either data or EOF.
|
* and give either data or EOF.
|
||||||
|
@@ -209,7 +209,11 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
|
|||||||
m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
|
m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
|
||||||
m->stream_timeout = stream_timeout;
|
m->stream_timeout = stream_timeout;
|
||||||
m->workers = workers;
|
m->workers = workers;
|
||||||
m->workers_max = 6;
|
m->workers_max = h2_config_geti(conf, H2_CONF_MAX_WORKERS);
|
||||||
|
m->workers_def_limit = 4;
|
||||||
|
m->workers_limit = m->workers_def_limit;
|
||||||
|
m->last_limit_change = m->last_idle_block = apr_time_now();
|
||||||
|
m->limit_change_interval = apr_time_from_msec(200);
|
||||||
|
|
||||||
m->tx_handles_reserved = 0;
|
m->tx_handles_reserved = 0;
|
||||||
m->tx_chunk_size = 4;
|
m->tx_chunk_size = 4;
|
||||||
@@ -276,6 +280,9 @@ static void io_destroy(h2_mplx *m, h2_io *io, int events)
|
|||||||
|
|
||||||
h2_io_set_remove(m->stream_ios, io);
|
h2_io_set_remove(m->stream_ios, io);
|
||||||
h2_io_set_remove(m->ready_ios, io);
|
h2_io_set_remove(m->ready_ios, io);
|
||||||
|
if (m->redo_ios) {
|
||||||
|
h2_io_set_remove(m->redo_ios, io);
|
||||||
|
}
|
||||||
|
|
||||||
if (pool) {
|
if (pool) {
|
||||||
apr_pool_clear(pool);
|
apr_pool_clear(pool);
|
||||||
@@ -292,7 +299,7 @@ static int io_stream_done(h2_mplx *m, h2_io *io, int rst_error)
|
|||||||
{
|
{
|
||||||
/* Remove io from ready set, we will never submit it */
|
/* Remove io from ready set, we will never submit it */
|
||||||
h2_io_set_remove(m->ready_ios, io);
|
h2_io_set_remove(m->ready_ios, io);
|
||||||
if (!io->processing_started || io->processing_done) {
|
if (!io->worker_started || io->worker_done) {
|
||||||
/* already finished or not even started yet */
|
/* already finished or not even started yet */
|
||||||
h2_iq_remove(m->q, io->id);
|
h2_iq_remove(m->q, io->id);
|
||||||
io_destroy(m, io, 1);
|
io_destroy(m, io, 1);
|
||||||
@@ -321,7 +328,7 @@ static int stream_print(void *ctx, h2_io *io)
|
|||||||
io->request->method, io->request->authority, io->request->path,
|
io->request->method, io->request->authority, io->request->path,
|
||||||
io->response? "http" : (io->rst_error? "reset" : "?"),
|
io->response? "http" : (io->rst_error? "reset" : "?"),
|
||||||
io->response? io->response->http_status : io->rst_error,
|
io->response? io->response->http_status : io->rst_error,
|
||||||
io->orphaned, io->processing_started, io->processing_done,
|
io->orphaned, io->worker_started, io->worker_done,
|
||||||
io->eos_in, io->eos_out);
|
io->eos_in, io->eos_out);
|
||||||
}
|
}
|
||||||
else if (io) {
|
else if (io) {
|
||||||
@@ -331,7 +338,7 @@ static int stream_print(void *ctx, h2_io *io)
|
|||||||
m->id, io->id,
|
m->id, io->id,
|
||||||
io->response? "http" : (io->rst_error? "reset" : "?"),
|
io->response? "http" : (io->rst_error? "reset" : "?"),
|
||||||
io->response? io->response->http_status : io->rst_error,
|
io->response? io->response->http_status : io->rst_error,
|
||||||
io->orphaned, io->processing_started, io->processing_done,
|
io->orphaned, io->worker_started, io->worker_done,
|
||||||
io->eos_in, io->eos_out);
|
io->eos_in, io->eos_out);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -647,6 +654,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_ihash_t *streams)
|
|||||||
if (io && !m->aborted) {
|
if (io && !m->aborted) {
|
||||||
stream = h2_ihash_get(streams, io->id);
|
stream = h2_ihash_get(streams, io->id);
|
||||||
if (stream) {
|
if (stream) {
|
||||||
|
io->submitted = 1;
|
||||||
if (io->rst_error) {
|
if (io->rst_error) {
|
||||||
h2_stream_rst(stream, io->rst_error);
|
h2_stream_rst(stream, io->rst_error);
|
||||||
}
|
}
|
||||||
@@ -667,7 +675,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_ihash_t *streams)
|
|||||||
"resetting io to close request processing",
|
"resetting io to close request processing",
|
||||||
m->id, io->id);
|
m->id, io->id);
|
||||||
h2_io_make_orphaned(io, H2_ERR_STREAM_CLOSED);
|
h2_io_make_orphaned(io, H2_ERR_STREAM_CLOSED);
|
||||||
if (!io->processing_started || io->processing_done) {
|
if (!io->worker_started || io->worker_done) {
|
||||||
io_destroy(m, io, 1);
|
io_destroy(m, io, 1);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -989,7 +997,7 @@ apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
static h2_io *open_io(h2_mplx *m, int stream_id)
|
static h2_io *open_io(h2_mplx *m, int stream_id, const h2_request *request)
|
||||||
{
|
{
|
||||||
apr_pool_t *io_pool = m->spare_pool;
|
apr_pool_t *io_pool = m->spare_pool;
|
||||||
h2_io *io;
|
h2_io *io;
|
||||||
@@ -1002,7 +1010,7 @@ static h2_io *open_io(h2_mplx *m, int stream_id)
|
|||||||
m->spare_pool = NULL;
|
m->spare_pool = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
io = h2_io_create(stream_id, io_pool);
|
io = h2_io_create(stream_id, io_pool, request);
|
||||||
h2_io_set_add(m->stream_ios, io);
|
h2_io_set_add(m->stream_ios, io);
|
||||||
|
|
||||||
return io;
|
return io;
|
||||||
@@ -1022,8 +1030,7 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const h2_request *req,
|
|||||||
status = APR_ECONNABORTED;
|
status = APR_ECONNABORTED;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
h2_io *io = open_io(m, stream_id);
|
h2_io *io = open_io(m, stream_id, req);
|
||||||
io->request = req;
|
|
||||||
|
|
||||||
if (!io->request->body) {
|
if (!io->request->body) {
|
||||||
status = h2_io_in_close(io);
|
status = h2_io_in_close(io);
|
||||||
@@ -1050,15 +1057,21 @@ static h2_task *pop_task(h2_mplx *m)
|
|||||||
h2_task *task = NULL;
|
h2_task *task = NULL;
|
||||||
int sid;
|
int sid;
|
||||||
while (!m->aborted && !task
|
while (!m->aborted && !task
|
||||||
&& (m->workers_busy < m->workers_max)
|
&& (m->workers_busy < m->workers_limit)
|
||||||
&& (sid = h2_iq_shift(m->q)) > 0) {
|
&& (sid = h2_iq_shift(m->q)) > 0) {
|
||||||
h2_io *io = h2_io_set_get(m->stream_ios, sid);
|
h2_io *io = h2_io_set_get(m->stream_ios, sid);
|
||||||
if (io) {
|
if (io && io->orphaned) {
|
||||||
|
io_destroy(m, io, 0);
|
||||||
|
if (m->join_wait) {
|
||||||
|
apr_thread_cond_signal(m->join_wait);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (io) {
|
||||||
conn_rec *slave = h2_slave_create(m->c, m->pool, m->spare_allocator);
|
conn_rec *slave = h2_slave_create(m->c, m->pool, m->spare_allocator);
|
||||||
m->spare_allocator = NULL;
|
m->spare_allocator = NULL;
|
||||||
task = h2_task_create(m->id, io->request, slave, m);
|
task = h2_task_create(m->id, io->request, slave, m);
|
||||||
|
io->worker_started = 1;
|
||||||
io->processing_started = 1;
|
io->started_at = apr_time_now();
|
||||||
if (sid > m->max_stream_started) {
|
if (sid > m->max_stream_started) {
|
||||||
m->max_stream_started = sid;
|
m->max_stream_started = sid;
|
||||||
}
|
}
|
||||||
@@ -1102,6 +1115,7 @@ static void task_done(h2_mplx *m, h2_task *task)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
h2_io *io = h2_io_set_get(m->stream_ios, task->stream_id);
|
h2_io *io = h2_io_set_get(m->stream_ios, task->stream_id);
|
||||||
|
|
||||||
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
|
||||||
"h2_mplx(%ld): task(%s) done", m->id, task->id);
|
"h2_mplx(%ld): task(%s) done", m->id, task->id);
|
||||||
/* clean our references and report request as done. Signal
|
/* clean our references and report request as done. Signal
|
||||||
@@ -1117,7 +1131,39 @@ static void task_done(h2_mplx *m, h2_task *task)
|
|||||||
h2_slave_destroy(task->c, &m->spare_allocator);
|
h2_slave_destroy(task->c, &m->spare_allocator);
|
||||||
task = NULL;
|
task = NULL;
|
||||||
if (io) {
|
if (io) {
|
||||||
io->processing_done = 1;
|
apr_time_t now = apr_time_now();
|
||||||
|
if (!io->orphaned && m->redo_ios
|
||||||
|
&& h2_io_set_get(m->redo_ios, io->id)) {
|
||||||
|
/* reset and schedule again */
|
||||||
|
h2_io_redo(io);
|
||||||
|
h2_io_set_remove(m->redo_ios, io);
|
||||||
|
h2_iq_add(m->q, io->id, NULL, NULL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
io->worker_done = 1;
|
||||||
|
io->done_at = now;
|
||||||
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
|
||||||
|
"h2_mplx(%ld): request(%d) done, %f ms"
|
||||||
|
" elapsed", m->id, io->id,
|
||||||
|
(io->done_at - io->started_at) / 1000.0);
|
||||||
|
if (io->started_at > m->last_idle_block) {
|
||||||
|
/* this task finished without causing an 'idle block', e.g.
|
||||||
|
* a block by flow control.
|
||||||
|
*/
|
||||||
|
if (now - m->last_limit_change >= m->limit_change_interval
|
||||||
|
&& m->workers_limit < m->workers_max) {
|
||||||
|
/* Well behaving stream, allow it more workers */
|
||||||
|
m->workers_limit = H2MIN(m->workers_limit * 2,
|
||||||
|
m->workers_max);
|
||||||
|
m->last_limit_change = now;
|
||||||
|
m->need_registration = 1;
|
||||||
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
|
||||||
|
"h2_mplx(%ld): increase worker limit to %d",
|
||||||
|
m->id, m->workers_limit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (io->orphaned) {
|
if (io->orphaned) {
|
||||||
io_destroy(m, io, 0);
|
io_destroy(m, io, 0);
|
||||||
if (m->join_wait) {
|
if (m->join_wait) {
|
||||||
@@ -1131,12 +1177,11 @@ static void task_done(h2_mplx *m, h2_task *task)
|
|||||||
apr_thread_cond_broadcast(m->task_done);
|
apr_thread_cond_broadcast(m->task_done);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
|
void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
|
||||||
{
|
{
|
||||||
int acquired, do_registration = 0;
|
int acquired;
|
||||||
|
|
||||||
if (enter_mutex(m, &acquired) == APR_SUCCESS) {
|
if (enter_mutex(m, &acquired) == APR_SUCCESS) {
|
||||||
task_done(m, task);
|
task_done(m, task);
|
||||||
@@ -1145,12 +1190,147 @@ void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
|
|||||||
/* caller wants another task */
|
/* caller wants another task */
|
||||||
*ptask = pop_task(m);
|
*ptask = pop_task(m);
|
||||||
}
|
}
|
||||||
do_registration = (m->workers_busy+1 == m->workers_max);
|
|
||||||
leave_mutex(m, acquired);
|
leave_mutex(m, acquired);
|
||||||
}
|
}
|
||||||
if (do_registration) {
|
}
|
||||||
workers_register(m);
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* h2_mplx DoS protection
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
h2_mplx *m;
|
||||||
|
h2_io *io;
|
||||||
|
apr_time_t now;
|
||||||
|
} io_iter_ctx;
|
||||||
|
|
||||||
|
static int latest_repeatable_busy_unsubmitted_iter(void *data, h2_io *io)
|
||||||
|
{
|
||||||
|
io_iter_ctx *ctx = data;
|
||||||
|
if (io->worker_started && !io->worker_done
|
||||||
|
&& h2_io_is_repeatable(io)
|
||||||
|
&& !h2_io_set_get(ctx->m->redo_ios, io->id)) {
|
||||||
|
/* this io occupies a worker, the response has not been submitted yet,
|
||||||
|
* not been cancelled and it is a repeatable request
|
||||||
|
* -> it can be re-scheduled later */
|
||||||
|
if (!ctx->io || ctx->io->started_at < io->started_at) {
|
||||||
|
/* we did not have one or this one was started later */
|
||||||
|
ctx->io = io;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static h2_io *get_latest_repeatable_busy_unsubmitted_io(h2_mplx *m)
|
||||||
|
{
|
||||||
|
io_iter_ctx ctx;
|
||||||
|
ctx.m = m;
|
||||||
|
ctx.io = NULL;
|
||||||
|
h2_io_set_iter(m->stream_ios, latest_repeatable_busy_unsubmitted_iter, &ctx);
|
||||||
|
return ctx.io;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int timed_out_busy_iter(void *data, h2_io *io)
|
||||||
|
{
|
||||||
|
io_iter_ctx *ctx = data;
|
||||||
|
if (io->worker_started && !io->worker_done
|
||||||
|
&& (ctx->now - io->started_at) > ctx->m->stream_timeout) {
|
||||||
|
/* timed out stream occupying a worker, found */
|
||||||
|
ctx->io = io;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
static h2_io *get_timed_out_busy_stream(h2_mplx *m)
|
||||||
|
{
|
||||||
|
io_iter_ctx ctx;
|
||||||
|
ctx.m = m;
|
||||||
|
ctx.io = NULL;
|
||||||
|
ctx.now = apr_time_now();
|
||||||
|
h2_io_set_iter(m->stream_ios, timed_out_busy_iter, &ctx);
|
||||||
|
return ctx.io;
|
||||||
|
}
|
||||||
|
|
||||||
|
static apr_status_t unschedule_slow_ios(h2_mplx *m)
|
||||||
|
{
|
||||||
|
h2_io *io;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
if (!m->redo_ios) {
|
||||||
|
m->redo_ios = h2_io_set_create(m->pool);
|
||||||
|
}
|
||||||
|
/* Try to get rid of streams that occupy workers. Look for safe requests
|
||||||
|
* that are repeatable. If none found, fail the connection.
|
||||||
|
*/
|
||||||
|
n = (m->workers_busy - m->workers_limit - h2_io_set_size(m->redo_ios));
|
||||||
|
while (n > 0 && (io = get_latest_repeatable_busy_unsubmitted_io(m))) {
|
||||||
|
h2_io_set_add(m->redo_ios, io);
|
||||||
|
h2_io_rst(io, H2_ERR_CANCEL);
|
||||||
|
--n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((m->workers_busy - h2_io_set_size(m->redo_ios)) > m->workers_limit) {
|
||||||
|
io = get_timed_out_busy_stream(m);
|
||||||
|
if (io) {
|
||||||
|
/* Too many busy workers, unable to cancel enough streams
|
||||||
|
* and with a busy, timed out stream, we tell the client
|
||||||
|
* to go away... */
|
||||||
|
return APR_TIMEUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APR_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
apr_status_t h2_mplx_idle(h2_mplx *m)
|
||||||
|
{
|
||||||
|
apr_status_t status = APR_SUCCESS;
|
||||||
|
apr_time_t now;
|
||||||
|
int acquired;
|
||||||
|
|
||||||
|
if (enter_mutex(m, &acquired) == APR_SUCCESS) {
|
||||||
|
apr_size_t scount = h2_io_set_size(m->stream_ios);
|
||||||
|
if (scount > 0 && m->workers_busy) {
|
||||||
|
/* If we have streams in connection state 'IDLE', meaning
|
||||||
|
* all streams are ready to sent data out, but lack
|
||||||
|
* WINDOW_UPDATEs.
|
||||||
|
*
|
||||||
|
* This is ok, unless we have streams that still occupy
|
||||||
|
* h2 workers. As worker threads are a scarce resource,
|
||||||
|
* we need to take measures that we do not get DoSed.
|
||||||
|
*
|
||||||
|
* This is what we call an 'idle block'. Limit the amount
|
||||||
|
* of busy workers we allow for this connection until it
|
||||||
|
* well behaves.
|
||||||
|
*/
|
||||||
|
now = apr_time_now();
|
||||||
|
m->last_idle_block = now;
|
||||||
|
if (m->workers_limit > 2
|
||||||
|
&& now - m->last_limit_change >= m->limit_change_interval) {
|
||||||
|
if (m->workers_limit > 16) {
|
||||||
|
m->workers_limit = 16;
|
||||||
|
}
|
||||||
|
else if (m->workers_limit > 8) {
|
||||||
|
m->workers_limit = 8;
|
||||||
|
}
|
||||||
|
else if (m->workers_limit > 4) {
|
||||||
|
m->workers_limit = 4;
|
||||||
|
}
|
||||||
|
else if (m->workers_limit > 2) {
|
||||||
|
m->workers_limit = 2;
|
||||||
|
}
|
||||||
|
m->last_limit_change = now;
|
||||||
|
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
|
||||||
|
"h2_mplx(%ld): decrease worker limit to %d",
|
||||||
|
m->id, m->workers_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m->workers_busy > m->workers_limit) {
|
||||||
|
status = unschedule_slow_ios(m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
leave_mutex(m, acquired);
|
||||||
|
}
|
||||||
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
|
@@ -68,15 +68,22 @@ struct h2_mplx {
|
|||||||
apr_pool_t *pool;
|
apr_pool_t *pool;
|
||||||
|
|
||||||
unsigned int aborted : 1;
|
unsigned int aborted : 1;
|
||||||
|
unsigned int need_registration : 1;
|
||||||
|
|
||||||
struct h2_int_queue *q;
|
struct h2_int_queue *q;
|
||||||
struct h2_io_set *stream_ios;
|
struct h2_io_set *stream_ios;
|
||||||
struct h2_io_set *ready_ios;
|
struct h2_io_set *ready_ios;
|
||||||
|
struct h2_io_set *redo_ios;
|
||||||
|
|
||||||
int max_stream_started; /* highest stream id that started processing */
|
int max_stream_started; /* highest stream id that started processing */
|
||||||
int workers_busy; /* # of workers processing on this mplx */
|
int workers_busy; /* # of workers processing on this mplx */
|
||||||
int workers_max; /* max # of workers occupied by this mplx */
|
int workers_limit; /* current # of workers limit, dynamic */
|
||||||
int need_registration;
|
int workers_def_limit; /* default # of workers limit */
|
||||||
|
int workers_max; /* max, hard limit # of workers in a process */
|
||||||
|
apr_time_t last_idle_block; /* last time, this mplx entered IDLE while
|
||||||
|
* streams were ready */
|
||||||
|
apr_time_t last_limit_change;/* last time, worker limit changed */
|
||||||
|
apr_interval_time_t limit_change_interval;
|
||||||
|
|
||||||
apr_thread_mutex_t *lock;
|
apr_thread_mutex_t *lock;
|
||||||
struct apr_thread_cond_t *added_output;
|
struct apr_thread_cond_t *added_output;
|
||||||
@@ -389,6 +396,16 @@ APR_RING_INSERT_TAIL((b), ap__b, h2_mplx, link); \
|
|||||||
*/
|
*/
|
||||||
#define H2_MPLX_REMOVE(e) APR_RING_REMOVE((e), link)
|
#define H2_MPLX_REMOVE(e) APR_RING_REMOVE((e), link)
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* h2_mplx DoS protection
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Master connection has entered idle mode.
|
||||||
|
* @param m the mplx instance of the master connection
|
||||||
|
* @return != SUCCESS iff connection should be terminated
|
||||||
|
*/
|
||||||
|
apr_status_t h2_mplx_idle(h2_mplx *m);
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* h2_mplx h2_req_engine handling.
|
* h2_mplx h2_req_engine handling.
|
||||||
|
@@ -778,7 +778,7 @@ static apr_status_t gset_encode_next(gset_encoder *encoder, apr_uint64_t pval)
|
|||||||
/* Intentional no APLOGNO */
|
/* Intentional no APLOGNO */
|
||||||
ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
|
ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
|
||||||
"h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
|
"h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
|
||||||
APR_UINT64_T_HEX_FMT" flex_bits=%" APR_UINT64_T_FMT
|
APR_UINT64_T_HEX_FMT" flex_bits=%"APR_UINT64_T_FMT", "
|
||||||
", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT,
|
", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT,
|
||||||
pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
|
pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
|
||||||
for (; flex_bits != 0; --flex_bits) {
|
for (; flex_bits != 0; --flex_bits) {
|
||||||
|
@@ -60,10 +60,6 @@ h2_request *h2_request_createn(int id, apr_pool_t *pool,
|
|||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
void h2_request_destroy(h2_request *req)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static apr_status_t inspect_clen(h2_request *req, const char *s)
|
static apr_status_t inspect_clen(h2_request *req, const char *s)
|
||||||
{
|
{
|
||||||
char *end;
|
char *end;
|
||||||
@@ -342,11 +338,22 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src)
|
|||||||
dst->authority = OPT_COPY(p, src->authority);
|
dst->authority = OPT_COPY(p, src->authority);
|
||||||
dst->path = OPT_COPY(p, src->path);
|
dst->path = OPT_COPY(p, src->path);
|
||||||
dst->headers = apr_table_clone(p, src->headers);
|
dst->headers = apr_table_clone(p, src->headers);
|
||||||
|
if (src->trailers) {
|
||||||
|
dst->trailers = apr_table_clone(p, src->trailers);
|
||||||
|
}
|
||||||
dst->content_length = src->content_length;
|
dst->content_length = src->content_length;
|
||||||
dst->chunked = src->chunked;
|
dst->chunked = src->chunked;
|
||||||
dst->eoh = src->eoh;
|
dst->eoh = src->eoh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
|
||||||
|
{
|
||||||
|
h2_request *nreq = apr_pcalloc(p, sizeof(*nreq));
|
||||||
|
memcpy(nreq, src, sizeof(*nreq));
|
||||||
|
h2_request_copy(p, nreq, src);
|
||||||
|
return nreq;
|
||||||
|
}
|
||||||
|
|
||||||
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
|
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
|
||||||
{
|
{
|
||||||
request_rec *r;
|
request_rec *r;
|
||||||
|
@@ -30,8 +30,6 @@ apr_status_t h2_request_make(h2_request *req, apr_pool_t *pool,
|
|||||||
const char *authority, const char *path,
|
const char *authority, const char *path,
|
||||||
apr_table_t *headers);
|
apr_table_t *headers);
|
||||||
|
|
||||||
void h2_request_destroy(h2_request *req);
|
|
||||||
|
|
||||||
apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
|
apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
|
||||||
|
|
||||||
apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
|
apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
|
||||||
@@ -47,6 +45,8 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
|
|||||||
|
|
||||||
void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
|
void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
|
||||||
|
|
||||||
|
h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a request_rec representing the h2_request to be
|
* Create a request_rec representing the h2_request to be
|
||||||
* processed on the given connection.
|
* processed on the given connection.
|
||||||
|
@@ -2015,7 +2015,7 @@ apr_status_t h2_session_process(h2_session *session, int async)
|
|||||||
no_streams = h2_ihash_is_empty(session->streams);
|
no_streams = h2_ihash_is_empty(session->streams);
|
||||||
update_child_status(session, (no_streams? SERVER_BUSY_KEEPALIVE
|
update_child_status(session, (no_streams? SERVER_BUSY_KEEPALIVE
|
||||||
: SERVER_BUSY_READ), "idle");
|
: SERVER_BUSY_READ), "idle");
|
||||||
if (async && !session->r && session->requests_received && no_streams) {
|
if (async && no_streams && !session->r && session->requests_received) {
|
||||||
ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
|
ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
|
||||||
"h2_session(%ld): async idle, nonblock read", session->id);
|
"h2_session(%ld): async idle, nonblock read", session->id);
|
||||||
/* We do not return to the async mpm immediately, since under
|
/* We do not return to the async mpm immediately, since under
|
||||||
@@ -2051,7 +2051,13 @@ apr_status_t h2_session_process(h2_session *session, int async)
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* We wait in smaller increments, using a 1 second timeout.
|
/* We wait in smaller increments, using a 1 second timeout.
|
||||||
* That gives us the chance to check for MPMQ_STOPPING often. */
|
* That gives us the chance to check for MPMQ_STOPPING often.
|
||||||
|
*/
|
||||||
|
status = h2_mplx_idle(session->mplx);
|
||||||
|
if (status != APR_SUCCESS) {
|
||||||
|
dispatch_event(session, H2_SESSION_EV_CONN_ERROR,
|
||||||
|
H2_ERR_ENHANCE_YOUR_CALM, "less is more");
|
||||||
|
}
|
||||||
h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
|
h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
|
||||||
status = h2_session_read(session, 1);
|
status = h2_session_read(session, 1);
|
||||||
if (status == APR_SUCCESS) {
|
if (status == APR_SUCCESS) {
|
||||||
|
@@ -169,11 +169,6 @@ h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session)
|
|||||||
apr_status_t h2_stream_destroy(h2_stream *stream)
|
apr_status_t h2_stream_destroy(h2_stream *stream)
|
||||||
{
|
{
|
||||||
AP_DEBUG_ASSERT(stream);
|
AP_DEBUG_ASSERT(stream);
|
||||||
if (stream->request) {
|
|
||||||
h2_request_destroy(stream->request);
|
|
||||||
stream->request = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream->pool) {
|
if (stream->pool) {
|
||||||
apr_pool_destroy(stream->pool);
|
apr_pool_destroy(stream->pool);
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
* @macro
|
* @macro
|
||||||
* Version number of the http2 module as c string
|
* Version number of the http2 module as c string
|
||||||
*/
|
*/
|
||||||
#define MOD_HTTP2_VERSION "1.3.1-DEV"
|
#define MOD_HTTP2_VERSION "1.3.2-DEV"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @macro
|
* @macro
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
* release. This is a 24 bit number with 8 bits for major number, 8 bits
|
* release. This is a 24 bit number with 8 bits for major number, 8 bits
|
||||||
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
|
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
|
||||||
*/
|
*/
|
||||||
#define MOD_HTTP2_VERSION_NUM 0x010301
|
#define MOD_HTTP2_VERSION_NUM 0x010302
|
||||||
|
|
||||||
|
|
||||||
#endif /* mod_h2_h2_version_h */
|
#endif /* mod_h2_h2_version_h */
|
||||||
|
@@ -86,7 +86,6 @@ static h2_task *next_task(h2_workers *workers)
|
|||||||
--workers->mplx_count;
|
--workers->mplx_count;
|
||||||
|
|
||||||
task = h2_mplx_pop_task(m, &has_more);
|
task = h2_mplx_pop_task(m, &has_more);
|
||||||
|
|
||||||
if (has_more) {
|
if (has_more) {
|
||||||
H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
|
H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
|
||||||
++workers->mplx_count;
|
++workers->mplx_count;
|
||||||
|
Reference in New Issue
Block a user