--- /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]
</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. */
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 */
}
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) {
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "2.0.34"
+#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 0x020022
+#define MOD_HTTP2_VERSION_NUM 0x020023
#endif /* mod_h2_h2_version_h */