Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
v2.0.34
--------------------------------------------------------------------------------
* Added support for "ProxyErrorOverride" directive in mod_proxy_http2.
* Fix a bug in calculating the log2 value of integers, used in push
diaries and proxy window size calculations. Apache PR69741.
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ The following configuration directives are available:
* [H2MaxDataFrameLen](#h2maxdataframelen)
* [H2MaxHeaderBlockLen](#h2maxheaderblocklen)
* [H2MaxSessionStreams](#h2maxsessionstreams)
* [H2MaxStreamErrors](#h2maxstreamerrors)
* [H2MaxWorkerIdleSeconds](#h2maxworkeridleseconds)
* [H2MaxWorkers](#h2maxworkers)
* [H2MinWorkers](#h2minworkers)
Expand Down Expand Up @@ -243,6 +244,20 @@ Context: server config, virtual host
```
This directive sets the maximum number of active streams per HTTP/2 session (e.g. connection) that the server allows. A stream is active if it is not idle or closed according to RFC 7540.

### H2MaxStreamErrors
```
Syntax: H2MaxStreamErrors n
Default: H2MaxStreamErrors 8
Context: server config, virtual host
```
H2MaxStreamErrors set maxmimum amount of tolerated HTTP/2 stream errors
caused by the client. When exceeding this limit, the connection will be closed.
Stream errors are protocol violations on an individual HTTP/2 stream that
do not necessitate a connection close by the protocol specification, but
can be a sign of malicious activity by a client.

Set to 0 to tolerate faulty clients.

### H2MaxWorkerIdleSeconds
```
Syntax: H2MaxWorkerIdleSeconds n
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
#

AC_PREREQ([2.69])
AC_INIT([mod_http2], [2.0.33], [stefan@eissing.org])
AC_INIT([mod_http2], [2.0.34], [stefan@eissing.org])

LT_PREREQ([2.2.6])
LT_INIT()
Expand Down
23 changes: 23 additions & 0 deletions mod_http2/h2_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ typedef struct h2_config {
apr_interval_time_t stream_timeout;/* beam timeout */
int max_data_frame_len; /* max # bytes in a single h2 DATA frame */
int max_hd_block_len; /* max # bytes in a response header block */
int max_stream_errors; /* max # of tolerated stream errors */
int proxy_requests; /* act as forward proxy */
int h2_websockets; /* if mod_h2 negotiating WebSockets */
} h2_config;
Expand Down Expand Up @@ -119,6 +120,7 @@ static h2_config defconf = {
-1, /* beam timeout */
0, /* max DATA frame len, 0 == no extra limit */
0, /* max header block len, 0 == no extra limit */
8, /* max stream errors tolerated */
0, /* forward proxy */
0, /* WebSockets negotiation, enabled */
};
Expand Down Expand Up @@ -168,6 +170,7 @@ void *h2_config_create_svr(apr_pool_t *pool, server_rec *s)
conf->stream_timeout = DEF_VAL;
conf->max_data_frame_len = DEF_VAL;
conf->max_hd_block_len = DEF_VAL;
conf->max_stream_errors = DEF_VAL;
conf->proxy_requests = DEF_VAL;
conf->h2_websockets = DEF_VAL;
return conf;
Expand Down Expand Up @@ -220,6 +223,7 @@ static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
n->stream_timeout = H2_CONFIG_GET(add, base, stream_timeout);
n->max_data_frame_len = H2_CONFIG_GET(add, base, max_data_frame_len);
n->max_hd_block_len = H2_CONFIG_GET(add, base, max_hd_block_len);
n->max_stream_errors = H2_CONFIG_GET(add, base, max_stream_errors);
n->proxy_requests = H2_CONFIG_GET(add, base, proxy_requests);
n->h2_websockets = H2_CONFIG_GET(add, base, h2_websockets);
return n;
Expand Down Expand Up @@ -319,6 +323,9 @@ static apr_int64_t h2_srv_config_geti64(const h2_config *conf, h2_config_var_t v
return H2_CONFIG_GET(conf, &defconf, h2_websockets);
case H2_CONF_MAX_HEADER_BLOCK_LEN:
return H2_CONFIG_GET(conf, &defconf, max_hd_block_len);
case H2_CONF_MAX_STREAM_ERRORS:
return H2_CONFIG_GET(conf, &defconf, max_stream_errors);

default:
return DEF_VAL;
}
Expand Down Expand Up @@ -389,6 +396,9 @@ static void h2_srv_config_seti(h2_config *conf, h2_config_var_t var, int val)
break;
case H2_CONF_MAX_HEADER_BLOCK_LEN:
H2_CONFIG_SET(conf, max_hd_block_len, val);
break;
case H2_CONF_MAX_STREAM_ERRORS:
H2_CONFIG_SET(conf, max_stream_errors, val);
default:
break;
}
Expand Down Expand Up @@ -669,6 +679,17 @@ static const char *h2_conf_set_max_hd_block_len(cmd_parms *cmd,
return NULL;
}

static const char *h2_conf_set_max_stream_errors(cmd_parms *cmd,
void *dirconf, const char *value)
{
int val = (int)apr_atoi64(value);
if (val < 0) {
return "value must be 0 or larger";
}
CONFIG_CMD_SET(cmd, dirconf, H2_CONF_MAX_STREAM_ERRORS, val);
return NULL;
}

static const char *h2_conf_set_session_extra_files(cmd_parms *cmd,
void *dirconf, const char *value)
{
Expand Down Expand Up @@ -1092,6 +1113,8 @@ const command_rec h2_cmds[] = {
RSRC_CONF, "maximum number of bytes in a single HTTP/2 DATA frame"),
AP_INIT_TAKE1("H2MaxHeaderBlockLen", h2_conf_set_max_hd_block_len, NULL,
RSRC_CONF, "maximum number of bytes in a response header block"),
AP_INIT_TAKE1("H2MaxStreamErrors", h2_conf_set_max_stream_errors, NULL,
RSRC_CONF, "maximum number of flow control errors tolerated"),
AP_INIT_TAKE2("H2EarlyHint", h2_conf_add_early_hint, NULL,
OR_FILEINFO|OR_AUTHCFG, "add a a 'Link:' header for a 103 Early Hints response."),
AP_INIT_TAKE1("H2ProxyRequests", h2_conf_set_proxy_requests, NULL,
Expand Down
1 change: 1 addition & 0 deletions mod_http2/h2_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ typedef enum {
H2_CONF_PROXY_REQUESTS,
H2_CONF_WEBSOCKETS,
H2_CONF_MAX_HEADER_BLOCK_LEN,
H2_CONF_MAX_STREAM_ERRORS,
} h2_config_var_t;

struct apr_hash_t;
Expand Down
11 changes: 7 additions & 4 deletions mod_http2/h2_mplx.c
Original file line number Diff line number Diff line change
Expand Up @@ -1086,8 +1086,9 @@ static void s_mplx_be_happy(h2_mplx *m, conn_rec *c, h2_conn_ctx_t *conn_ctx)
m->last_mood_change = now;
m->irritations_since = 0;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
H2_MPLX_MSG(m, "mood update, increasing worker limit to %d"),
m->processing_limit);
H2_MPLX_MSG(m, "mood update, increasing worker limit"
"to %d, processing %d right now"),
m->processing_limit, m->processing_count);
}
}
}
Expand Down Expand Up @@ -1116,8 +1117,9 @@ static void m_be_annoyed(h2_mplx *m)
m->last_mood_change = now;
m->irritations_since = 0;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c1,
H2_MPLX_MSG(m, "mood update, decreasing worker limit to %d"),
m->processing_limit);
H2_MPLX_MSG(m, "mood update, decreasing worker limit "
"to %d, processing %d right now"),
m->processing_limit, m->processing_count);
}
}
}
Expand All @@ -1141,6 +1143,7 @@ static int reset_is_acceptable(h2_stream *stream)
* The responses to such requests continue forever otherwise.
*
*/
if (stream->rst_error) return 0; /* errored stream. bad. */
if (!stream_is_running(stream)) return 1;
if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
if (!stream->response) return 0; /* no response headers produced yet. bad. */
Expand Down
42 changes: 35 additions & 7 deletions mod_http2/h2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ static void transit(h2_session *session, const char *action,
static void on_stream_state_enter(void *ctx, h2_stream *stream);
static void on_stream_state_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
static apr_status_t h2_session_shutdown(h2_session *session, int error,
const char *msg, int force_close);

static int h2_session_status_from_apr_status(apr_status_t rv)
{
Expand Down Expand Up @@ -290,6 +292,7 @@ static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
"closing with err=%d %s"),
(int)error_code, h2_protocol_err_description(error_code));
h2_stream_rst(stream, error_code);
h2_mplx_c1_client_rst(session->mplx, stream_id, stream);
}
}
return 0;
Expand Down Expand Up @@ -608,7 +611,11 @@ static int on_frame_send_cb(nghttp2_session *ngh2,
/* PUSH_PROMISE we report on the promised stream */
stream_id = frame->push_promise.promised_stream_id;
break;
default:
case NGHTTP2_RST_STREAM:
if(frame->rst_stream.error_code == NGHTTP2_FLOW_CONTROL_ERROR)
++session->stream_errors;
break;
default:
break;
}

Expand Down Expand Up @@ -652,10 +659,11 @@ static int on_frame_not_send_cb(nghttp2_session *ngh2,

stream = get_stream(session, stream_id);
h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c1,
H2_SSSN_LOG(APLOGNO(10509), session,
"not sent FRAME[%s], error %d: %s"),
buffer, ngh2_err, nghttp2_strerror(ngh2_err));
if (!stream || !stream->rst_error)
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c1,
H2_SSSN_LOG(APLOGNO(10509), session,
"not sent FRAME[%s], error %d: %s"),
buffer, ngh2_err, nghttp2_strerror(ngh2_err));
if(stream) {
h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
return 0;
Expand Down Expand Up @@ -968,6 +976,7 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
session->max_stream_count = h2_config_sgeti(s, H2_CONF_MAX_STREAMS);
session->max_stream_mem = h2_config_sgeti(s, H2_CONF_STREAM_MAX_MEM);
session->max_data_frame_len = h2_config_sgeti(s, H2_CONF_MAX_DATA_FRAME_LEN);
session->max_stream_errors = h2_config_sgeti(s, H2_CONF_MAX_STREAM_ERRORS);

session->out_c1_blocked = h2_iq_create(session->pool, (int)session->max_stream_count);
session->ready_to_process = h2_iq_create(session->pool, (int)session->max_stream_count);
Expand Down Expand Up @@ -1063,14 +1072,16 @@ apr_status_t h2_session_create(h2_session **psession, conn_rec *c, request_rec *
"created, max_streams=%d, stream_mem=%d, "
"workers_limit=%d, workers_max=%d, "
"push_diary(type=%d,N=%d), "
"max_data_frame_len=%d"),
"max_data_frame_len=%d, "
"max_stream_errors=%d"),
(int)session->max_stream_count,
(int)session->max_stream_mem,
session->mplx->processing_limit,
session->mplx->processing_max,
session->push_diary->dtype,
(int)session->push_diary->N,
(int)session->max_data_frame_len);
(int)session->max_data_frame_len,
session->max_stream_errors);
}

apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);
Expand Down Expand Up @@ -1609,6 +1620,14 @@ static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char
}
}

static void h2_session_ev_bad_client(h2_session *session, int arg, const char *msg)
{
transit(session, msg, H2_SESSION_ST_DONE);
if (!session->local.shutdown) {
h2_session_shutdown(session, arg, msg, 1);
}
}

static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
{
h2_session_shutdown(session, arg, msg, 1);
Expand Down Expand Up @@ -1806,6 +1825,9 @@ void h2_session_dispatch_event(h2_session *session, h2_session_event_t ev,
case H2_SESSION_EV_NO_MORE_STREAMS:
h2_session_ev_no_more_streams(session);
break;
case H2_SESSION_EV_BAD_CLIENT:
h2_session_ev_bad_client(session, arg, msg);
break;
default:
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c1,
H2_SSSN_MSG(session, "unknown event %d"), ev);
Expand Down Expand Up @@ -1884,6 +1906,12 @@ apr_status_t h2_session_process(h2_session *session, int async,
}
}

if (session->max_stream_errors &&
session->stream_errors > session->max_stream_errors) {
h2_session_dispatch_event(session, H2_SESSION_EV_BAD_CLIENT,
NGHTTP2_PROTOCOL_ERROR, NULL);
}

session->status[0] = '\0';

if (h2_session_want_send(session)) {
Expand Down
5 changes: 4 additions & 1 deletion mod_http2/h2_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ typedef enum {
H2_SESSION_EV_MPM_STOPPING, /* the process is stopping */
H2_SESSION_EV_PRE_CLOSE, /* connection will close after this */
H2_SESSION_EV_NO_MORE_STREAMS, /* no more streams to process */
H2_SESSION_EV_BAD_CLIENT, /* client misbehaving badly */
} h2_session_event_t;

typedef struct h2_session {
Expand Down Expand Up @@ -98,7 +99,9 @@ typedef struct h2_session {
unsigned int pushes_promised; /* number of http/2 push promises submitted */
unsigned int pushes_submitted; /* number of http/2 pushed responses submitted */
unsigned int pushes_reset; /* number of http/2 pushed reset by client */

unsigned int max_stream_errors; /* max client stream errors tolerated */
unsigned int stream_errors; /* number of stream errors by client */

apr_size_t frames_received; /* number of http/2 frames received */
apr_size_t frames_sent; /* number of http/2 frames sent */

Expand Down
6 changes: 4 additions & 2 deletions mod_http2/h2_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -1805,9 +1805,11 @@ int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
}
case NGHTTP2_RST_STREAM: {
return apr_snprintf(buffer, maxlen,
"RST_STREAM[length=%d, flags=%d, stream=%d]",
"RST_STREAM[length=%d, flags=%d, stream=%d"
",error=%d]",
(int)frame->hd.length,
frame->hd.flags, frame->hd.stream_id);
frame->hd.flags, frame->hd.stream_id,
frame->rst_stream.error_code);
}
case NGHTTP2_SETTINGS: {
if (frame->hd.flags & NGHTTP2_FLAG_ACK) {
Expand Down
4 changes: 2 additions & 2 deletions mod_http2/h2_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
* @macro
* Version number of the http2 module as c string
*/
#define MOD_HTTP2_VERSION "2.0.33-git"
#define MOD_HTTP2_VERSION "2.0.34-git"

/**
* @macro
* Numerical representation of the version number of the http2 module
* 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.
*/
#define MOD_HTTP2_VERSION_NUM 0x020021
#define MOD_HTTP2_VERSION_NUM 0x020022


#endif /* mod_h2_h2_version_h */