PATCHES ACCEPTED TO BACKPORT FROM TRUNK:
[ start all new proposals below, under PATCHES PROPOSED. ]
- *) mod_http2/mod_proxy_http2: Fix bug in log2() calculation
- trunk patch: http://svn.apache.org/r1927235
- 2.4.x patch: svn merge -c 1927235 ^/httpd/httpd/trunk .
- +1: icing, rpluem, jorton
-
- *) mod_proxy_http2: add support for ProxyErrorOverride. PR69771
- trunk patch: http://svn.apache.org/r1927647
- 2.4.x patch: svn merge -c 1927647 ^/httpd/httpd/trunk .
- +1: icing, rpluem, jorton
-
- *) mod_http2: new directive H2MaxStreamErrors.
- trunk patch: http://svn.apache.org/r1927792
- 2.4.x patch: svn merge -c 1927792 ^/httpd/httpd/trunk .
- +1: icing, rpluem, jorton
-
- *) mod_http2: Fix handling of 304 responses from mod_cache. PR 69580.
- Trunk version of patch:
- https://svn.apache.org/r1924267
- Backport version for 2.4.x of patch:
- Trunk version of patch works
- svn merge -c 1924267 ^/httpd/httpd/trunk .
- +1: rpluem, jorton, covener
-
- *) mod_http2: use nghttp2 supplied lengths when checking trailers.
- Trunk version of patch:
- https://svn.apache.org/r1929517
- https://svn.apache.org/r1929527
- Backport version for 2.4.x of patch:
- Trunk version of patch works
- svn merge -c 1929517,1929527 ^/httpd/httpd/trunk .
- +1: icing, covener, rpluem
-
*) mod_ssl: fix strict mode handling in SSLVHostSNIPolicy
Trunk version of patch:
https://svn.apache.org/r1929631
--- /dev/null
+ *) mod_http2: update to version 2.0.35
+ New directive `H2MaxStreamErrors` to control how much bad behaviour
+ by clients is tolerated before the connection is closed.
+ [Stefan Eissing]
--- /dev/null
+ *) mod_http2: Fix handling of 304 responses from mod_cache. PR 69580.
+ [Stefan Eissing]
--- /dev/null
+ * mod_http2/mod_proxy_http2: fix a bug in calculating the log2 value of
+ integers, used in push diaries and proxy window size calculations.
+ PR69741 [Benjamin P. Kallus]
--- /dev/null
+ * mod_proxy_http2: add support for ProxyErrorOverride directive. PR69771
</usage>
</directivesynopsis>
+ <directivesynopsis>
+ <name>H2MaxStreamErrors</name>
+ <description>Maximum amount of client caused errors to tolerate</description>
+ <syntax>H2MaxStreamErrors <em>n</em></syntax>
+ <default>H2MaxStreamErrors 8</default>
+ <contextlist>
+ <context>server config</context>
+ <context>virtual host</context>
+ </contextlist>
+ <compatibility>Available in version 2.5.1 and later.</compatibility>
+
+ <usage>
+ <p>
+ <directive>H2MaxStreamErrors</directive> sets the 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.
+ </p>
+ <p>
+ Set to 0 to tolerate faulty clients.
+ </p>
+ </usage>
+ </directivesynopsis>
+
</modulesynopsis>
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;
-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 */
};
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;
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;
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;
}
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;
}
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)
{
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,
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;
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);
}
}
}
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);
}
}
}
* 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. */
unsigned int waiting_on_ping : 1;
unsigned int headers_ended : 1;
uint32_t error_code;
+ int proxy_status;
apr_bucket_brigade *input;
apr_off_t data_sent;
ap_send_interim_response(r, 1);
}
}
+ else if (r->status >= 400) {
+ proxy_dir_conf *dconf;
+ dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+ if (ap_proxy_should_override(dconf, r->status)) {
+ apr_table_setn(r->notes, "proxy-error-override", "1");
+ nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE,
+ frame->hd.stream_id, NGHTTP2_STREAM_CLOSED);
+ }
+ }
stream_resume(stream);
break;
case NGHTTP2_PING:
APLOG_USE_MODULE(proxy_http2);
/* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_proxy_log2(int n)
+unsigned char h2_proxy_log2(unsigned int n)
{
int lz = 0;
if (!n) {
* common helpers
******************************************************************************/
/* h2_proxy_log2(n) iff n is a power of 2 */
-unsigned char h2_proxy_log2(int n);
+unsigned char h2_proxy_log2(unsigned int n);
/*******************************************************************************
* HTTP/2 header helpers
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)
{
"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;
/* 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;
}
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;
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);
"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);
}
}
+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);
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);
}
}
+ 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)) {
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 {
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 */
buf_len = output_data_buffered(stream, &eos, &header_blocked);
}
else if (APR_EOF == rv) {
- if (!stream->output_eos) {
+ if (!stream->output_eos &&
+ !AP_STATUS_IS_HEADER_ONLY(stream->response->status)) {
/* Seeing APR_EOF without an EOS bucket received before indicates
* that stream output is incomplete. Commonly, we expect to see
* an ERROR bucket to have been generated. But faulty handlers
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c1,
H2_STRM_MSG(stream, "process response %d"),
resp->status);
- is_empty = (e != APR_BRIGADE_SENTINEL(stream->out_buffer)
- && APR_BUCKET_IS_EOS(e));
+ is_empty = AP_STATUS_IS_HEADER_ONLY(resp->status) ||
+ ((e != APR_BRIGADE_SENTINEL(stream->out_buffer) &&
+ APR_BUCKET_IS_EOS(e)));
break;
}
else if (APR_BUCKET_IS_EOS(b)) {
#include "h2_util.h"
/* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(int n)
+unsigned char h2_log2(unsigned int n)
{
int lz = 0;
if (!n) {
for (i = 0; i < llen; ++i) {
lit = &lits[i];
if (lit->len == nv->namelen
- && !ap_cstr_casecmp(lit->name, (const char *)nv->name)) {
+ && !ap_cstr_casecmpn(lit->name, (const char *)nv->name, nv->namelen)) {
return 1;
}
}
nghttp2_nv nv;
nv.name = (uint8_t*)name;
- nv.namelen = strlen(name);
+ nv.namelen = len;
return (h2_req_ignore_header(&nv)
|| contains_name(H2_LIT_ARGS(IgnoredRequestTrailers), &nv));
}
nghttp2_nv nv;
nv.name = (uint8_t*)name;
- nv.namelen = strlen(name);
+ nv.namelen = len;
return (contains_name(H2_LIT_ARGS(IgnoredResponseHeaders), &nv)
|| contains_name(H2_LIT_ARGS(IgnoredResponseTrailers), &nv));
}
return APR_SUCCESS;
}
else if (nv->namelen == sizeof("cookie")-1
- && !ap_cstr_casecmp("cookie", (const char *)nv->name)) {
+ && !ap_cstr_casecmpn("cookie", (const char *)nv->name, nv->namelen)) {
existing = apr_table_get(headers, "cookie");
if (existing) {
/* Cookie header come separately in HTTP/2, but need
}
}
else if (nv->namelen == sizeof("host")-1
- && !ap_cstr_casecmp("host", (const char *)nv->name)) {
+ && !ap_cstr_casecmpn("host", (const char *)nv->name, nv->namelen)) {
if (apr_table_get(headers, "Host")) {
return APR_SUCCESS; /* ignore duplicate */
}
}
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) {
* common helpers
******************************************************************************/
/* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(int n);
+unsigned char h2_log2(unsigned int n);
/**
* Count the bytes that all key/value pairs in a table have
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "2.0.32"
+#define MOD_HTTP2_VERSION "2.0.35"
/**
* @macro
* 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 0x020020
+#define MOD_HTTP2_VERSION_NUM 0x020023
#endif /* mod_h2_h2_version_h */
ctx->id, touched, error_code);
ctx->r_done = 1;
if (touched) ctx->r_may_retry = 0;
- ctx->r_status = error_code? HTTP_BAD_GATEWAY :
- ((status == APR_SUCCESS)? OK :
- ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE));
+ if (apr_table_get(r->notes, "proxy-error-override")) {
+ ctx->r_status = r->status;
+ r->status = OK;
+ }
+ else {
+ ctx->r_status = error_code? HTTP_BAD_GATEWAY :
+ ((status == APR_SUCCESS)? OK :
+ ap_map_http_request_error(status, HTTP_SERVICE_UNAVAILABLE));
+ }
}
}
if (ctx->cfront->aborted) goto cleanup;
status = ctx_run(ctx);
- if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->cfront->aborted) {
+ if (apr_table_get(r->notes, "proxy-error-override")) {
+ /* pass on out */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->cfront,
+ "proxy-error-override status %d", ctx->r_status);
+ }
+ else if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->cfront->aborted) {
/* Not successfully processed, but may retry, tear down old conn and start over */
if (ctx->p_conn) {
ctx->p_conn->close = 1;
ap_set_module_config(ctx->cfront->conn_config, &proxy_http2_module, NULL);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->cfront,
- APLOGNO(03377) "leaving handler");
+ APLOGNO(03377) "leaving handler -> %d", ctx->r_status);
return ctx->r_status;
}