-*- coding: utf-8 -*-
Changes with Apache 2.4.18
+ *) mod_http2: reworked deallocation on connection shutdown and worker
+ abort. Separate parent pool for all workers. worker threads are joined
+ on planned worker shutdown.
+ [Yann Ylavic, Stefan Eissing]
+
+ *) core: Fix scoreboard crash (SIGBUS) on hardware requiring strict 64bit
+ alignment (SPARC64, PPC64). [Yann Ylavic]
+
+ *) mod_cache: Accept HT (Horizontal Tab) when parsing cache related header
+ fields as described in RFC7230. [Christophe Jaillet]
+
+ *) core/util_script: making REDIRECT_URL a full URL is now opt-in
+ via new 'QualifyRedirectURL' directive.
+
+ *) mod_ssl: Extend expression parser registration to support ssl variables
+ in any expression using mod_rewrite syntax "%{SSL:VARNAME}" or function
+ syntax "ssl(VARNAME)". [Rainer Jung]
+
Changes with Apache 2.4.17
*) mod_http2: added donated HTTP/2 implementation via core module. Similar
http2_objs="dnl
mod_http2.lo dnl
h2_alt_svc.lo dnl
++h2_bucket_eoc.lo dnl
++h2_bucket_eos.lo dnl
h2_config.lo dnl
h2_conn.lo dnl
h2_conn_io.lo dnl
0, /* serialize headers */
-1, /* h2 direct mode */
-1, /* # session extra files */
++ 1, /* modern TLS only */
++ -1, /* HTTP/1 Upgrade support */
++ 1024*1024, /* TLS warmup size */
++ 1, /* TLS cooldown secs */
};
static int files_per_session = 0;
conf->serialize_headers = DEF_VAL;
conf->h2_direct = DEF_VAL;
conf->session_extra_files = DEF_VAL;
++ conf->modern_tls_only = DEF_VAL;
++ conf->h2_upgrade = DEF_VAL;
++ conf->tls_warmup_size = DEF_VAL;
++ conf->tls_cooldown_secs = DEF_VAL;
++
return conf;
}
strcat(name, "]");
n->name = name;
-- n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
-- n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
-- n->min_workers = H2_CONFIG_GET(add, base, min_workers);
-- n->max_workers = H2_CONFIG_GET(add, base, max_workers);
++ n->h2_max_streams = H2_CONFIG_GET(add, base, h2_max_streams);
++ n->h2_window_size = H2_CONFIG_GET(add, base, h2_window_size);
++ n->min_workers = H2_CONFIG_GET(add, base, min_workers);
++ n->max_workers = H2_CONFIG_GET(add, base, max_workers);
n->max_worker_idle_secs = H2_CONFIG_GET(add, base, max_worker_idle_secs);
-- n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
-- n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
-- n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
-- n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
-- n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
-- n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
++ n->stream_max_mem_size = H2_CONFIG_GET(add, base, stream_max_mem_size);
++ n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
++ n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
++ n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
++ n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
++ n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
++ n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
++ n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
++ n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
++ n->tls_cooldown_secs = H2_CONFIG_GET(add, base, tls_cooldown_secs);
return n;
}
int h2_config_geti(h2_config *conf, h2_config_var_t var)
++{
++ return (int)h2_config_geti64(conf, var);
++}
++
++apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var)
{
int n;
switch(var) {
return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
case H2_CONF_SER_HEADERS:
return H2_CONFIG_GET(conf, &defconf, serialize_headers);
++ case H2_CONF_MODERN_TLS_ONLY:
++ return H2_CONFIG_GET(conf, &defconf, modern_tls_only);
++ case H2_CONF_UPGRADE:
++ return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
case H2_CONF_DIRECT:
return H2_CONFIG_GET(conf, &defconf, h2_direct);
case H2_CONF_SESSION_FILES:
n = files_per_session;
}
return n;
++ case H2_CONF_TLS_WARMUP_SIZE:
++ return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
++ case H2_CONF_TLS_COOLDOWN_SECS:
++ return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs);
default:
return DEF_VAL;
}
{
h2_config *cfg = h2_config_sget(parms->server);
apr_int64_t max = (int)apr_atoi64(value);
-- if (max <= 0) {
-- return "value must be a positive number";
++ if (max < 0) {
++ return "value must be a non-negative number";
}
cfg->session_extra_files = (int)max;
(void)arg;
return "value must be On or Off";
}
--#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
++static const char *h2_conf_set_modern_tls_only(cmd_parms *parms,
++ void *arg, const char *value)
++{
++ h2_config *cfg = h2_config_sget(parms->server);
++ if (!strcasecmp(value, "On")) {
++ cfg->modern_tls_only = 1;
++ return NULL;
++ }
++ else if (!strcasecmp(value, "Off")) {
++ cfg->modern_tls_only = 0;
++ return NULL;
++ }
++
++ (void)arg;
++ return "value must be On or Off";
++}
++static const char *h2_conf_set_upgrade(cmd_parms *parms,
++ void *arg, const char *value)
++{
++ h2_config *cfg = h2_config_sget(parms->server);
++ if (!strcasecmp(value, "On")) {
++ cfg->h2_upgrade = 1;
++ return NULL;
++ }
++ else if (!strcasecmp(value, "Off")) {
++ cfg->h2_upgrade = 0;
++ return NULL;
++ }
++
++ (void)arg;
++ return "value must be On or Off";
++}
++
++static const char *h2_conf_set_tls_warmup_size(cmd_parms *parms,
++ void *arg, const char *value)
++{
++ h2_config *cfg = h2_config_sget(parms->server);
++ cfg->tls_warmup_size = apr_atoi64(value);
++ (void)arg;
++ return NULL;
++}
++
++static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms,
++ void *arg, const char *value)
++{
++ h2_config *cfg = h2_config_sget(parms->server);
++ cfg->tls_cooldown_secs = (int)apr_atoi64(value);
++ (void)arg;
++ return NULL;
++}
++
++
++#define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
const command_rec h2_cmds[] = {
AP_INIT_TAKE1("H2MaxSessionStreams", h2_conf_set_max_streams, NULL,
RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
RSRC_CONF, "on to enable header serialization for compatibility"),
++ AP_INIT_TAKE1("H2ModernTLSOnly", h2_conf_set_modern_tls_only, NULL,
++ RSRC_CONF, "off to not impose RFC 7540 restrictions on TLS"),
++ AP_INIT_TAKE1("H2Upgrade", h2_conf_set_upgrade, NULL,
++ RSRC_CONF, "on to allow HTTP/1 Upgrades to h2/h2c"),
AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
RSRC_CONF, "on to enable direct HTTP/2 mode"),
AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
RSRC_CONF, "number of extra file a session might keep open"),
++ AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
++ RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
++ AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
++ RSRC_CONF, "seconds of idle time on TLS before shrinking writes"),
AP_END_CMD
};
H2_CONF_SER_HEADERS,
H2_CONF_DIRECT,
H2_CONF_SESSION_FILES,
++ H2_CONF_MODERN_TLS_ONLY,
++ H2_CONF_UPGRADE,
++ H2_CONF_TLS_WARMUP_SIZE,
++ H2_CONF_TLS_COOLDOWN_SECS,
} h2_config_var_t;
/* Apache httpd module configuration for h2. */
processing, better compatibility */
int h2_direct; /* if mod_h2 is active directly */
int session_extra_files; /* # of extra files a session may keep open */
++ int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
++ int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
++ apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
++ int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */
} h2_config;
h2_config *h2_config_rget(request_rec *r);
int h2_config_geti(h2_config *conf, h2_config_var_t var);
++apr_int64_t h2_config_geti64(h2_config *conf, h2_config_var_t var);
void h2_config_init(apr_pool_t *pool);
#include "h2_session.h"
#include "h2_stream.h"
#include "h2_stream_set.h"
++#include "h2_h2.h"
#include "h2_task.h"
#include "h2_worker.h"
#include "h2_workers.h"
static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
static module *mpm_module;
--static module *ssl_module;
static int checked;
static void check_modules(void)
mpm_type = H2_MPM_PREFORK;
mpm_module = m;
}
-- else if (!strcmp("mod_ssl.c", m->name)) {
-- ssl_module = m;
-- }
}
checked = 1;
}
mpm_type = H2_MPM_PREFORK;
mpm_module = m;
}
-- else if (!strcmp("mod_ssl.c", m->name)) {
-- ssl_module = m;
-- }
}
if (minw <= 0) {
return APR_EGENERAL;
}
- status = h2_session_process(session);
++ if (!h2_is_acceptable_connection(c, 1)) {
++ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
++ NGHTTP2_INADEQUATE_SECURITY, NULL, 0);
++ }
++
+ status = h2_conn_loop(session);
/* Make sure this connection gets closed properly. */
c->keepalive = AP_CONN_CLOSE;
session->c->local_addr->port);
if (status != APR_SUCCESS) {
h2_session_abort(session, status, rv);
-- h2_session_destroy(session);
++ h2_session_cleanup(session);
return status;
}
ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
"h2_session(%ld): done", session->id);
++ h2_session_close(session);
ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING,
session->c);
--
-- h2_session_close(session);
-- h2_session_destroy(session);
--
return DONE;
}
return c;
}
--apr_status_t h2_conn_setup(h2_task_env *env, struct h2_worker *worker)
++apr_status_t h2_conn_setup(h2_task *task, struct h2_worker *worker)
{
-- conn_rec *master = env->mplx->c;
++ conn_rec *master = task->mplx->c;
-- ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, env->pool,
++ ap_log_perror(APLOG_MARK, APLOG_TRACE3, 0, task->pool,
"h2_conn(%ld): created from master", master->id);
/* Ok, we are just about to start processing the connection and
* sub-resources from it, so that we get a nice reuse of
* pools.
*/
-- env->c.pool = env->pool;
-- env->c.bucket_alloc = h2_worker_get_bucket_alloc(worker);
-- env->c.current_thread = h2_worker_get_thread(worker);
++ task->c->pool = task->pool;
++ task->c->bucket_alloc = h2_worker_get_bucket_alloc(worker);
++ task->c->current_thread = h2_worker_get_thread(worker);
-- env->c.conn_config = ap_create_conn_config(env->pool);
-- env->c.notes = apr_table_make(env->pool, 5);
++ task->c->conn_config = ap_create_conn_config(task->pool);
++ task->c->notes = apr_table_make(task->pool, 5);
-- ap_set_module_config(env->c.conn_config, &core_module,
-- h2_worker_get_socket(worker));
++ /* In order to do this in 2.4.x, we need to add a member to conn_rec */
++ task->c->master = master;
-- /* If we serve http:// requests over a TLS connection, we do
-- * not want any mod_ssl vars to be visible.
-- */
-- if (ssl_module && (!env->scheme || strcmp("http", env->scheme))) {
-- /* See #19, there is a range of SSL variables to be gotten from
-- * the main connection that should be available in request handlers
-- */
-- void *sslcfg = ap_get_module_config(master->conn_config, ssl_module);
-- if (sslcfg) {
-- ap_set_module_config(env->c.conn_config, ssl_module, sslcfg);
-- }
-- }
++ ap_set_module_config(task->c->conn_config, &core_module,
++ h2_worker_get_socket(worker));
/* This works for mpm_worker so far. Other mpm modules have
* different needs, unfortunately. The most interesting one
/* all fine */
break;
case H2_MPM_EVENT:
-- fix_event_conn(&env->c, master);
++ fix_event_conn(task->c, master);
break;
default:
/* fingers crossed */
* 400 Bad Request
* when names do not match. We prefer a predictable 421 status.
*/
-- env->c.keepalives = 1;
++ task->c->keepalives = 1;
return APR_SUCCESS;
}
#define __mod_h2__h2_conn__
struct h2_task;
--struct h2_task_env;
struct h2_worker;
/* Process the connection that is now starting the HTTP/2
conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *stream_pool);
--apr_status_t h2_conn_setup(struct h2_task_env *env, struct h2_worker *worker);
++apr_status_t h2_conn_setup(struct h2_task *task, struct h2_worker *worker);
apr_status_t h2_conn_post(conn_rec *c, struct h2_worker *worker);
apr_status_t h2_conn_process(conn_rec *c, apr_socket_t *socket);
#include "h2_h2.h"
#include "h2_util.h"
--#define WRITE_BUFFER_SIZE (64*1024)
++#define TLS_DATA_MAX (16*1024)
++
++/* Calculated like this: assuming MTU 1500 bytes
++ * 1500 - 40 (IP) - 20 (TCP) - 40 (TCP options)
++ * - TLS overhead (60-100)
++ * ~= 1300 bytes */
#define WRITE_SIZE_INITIAL 1300
--#define WRITE_SIZE_MAX (16*1024)
--#define WRITE_SIZE_IDLE_USEC (1*APR_USEC_PER_SEC)
--#define WRITE_SIZE_THRESHOLD (1*1024*1024)
++/* Calculated like this: max TLS record size 16*1024
++ * - 40 (IP) - 20 (TCP) - 40 (TCP options)
++ * - TLS overhead (60-100)
++ * which seems to create less TCP packets overall
++ */
++#define WRITE_SIZE_MAX (TLS_DATA_MAX - 100)
++
++#define WRITE_BUFFER_SIZE (8*WRITE_SIZE_MAX)
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c)
{
-- io->connection = c;
-- io->input = apr_brigade_create(c->pool, c->bucket_alloc);
-- io->output = apr_brigade_create(c->pool, c->bucket_alloc);
-- io->buflen = 0;
-- /* That is where we start with,
-- * see https://issues.apache.org/jira/browse/TS-2503 */
-- io->write_size = WRITE_SIZE_INITIAL;
-- io->last_write = 0;
-- io->buffer_output = h2_h2_is_tls(c);
--
-- /* Currently we buffer only for TLS output. The reason this gives
-- * improved performance is that buckets send to the mod_ssl network
-- * filter will be encrypted in chunks. There is a special filter
-- * that tries to aggregate data, but that does not work well when
-- * bucket sizes alternate between tiny frame headers and large data
-- * chunks.
-- */
++ h2_config *cfg = h2_config_get(c);
++
++ io->connection = c;
++ io->input = apr_brigade_create(c->pool, c->bucket_alloc);
++ io->output = apr_brigade_create(c->pool, c->bucket_alloc);
++ io->buflen = 0;
++ io->is_tls = h2_h2_is_tls(c);
++ io->buffer_output = io->is_tls;
++
if (io->buffer_output) {
io->bufsize = WRITE_BUFFER_SIZE;
io->buffer = apr_pcalloc(c->pool, io->bufsize);
io->bufsize = 0;
}
++ if (io->is_tls) {
++ /* That is where we start with,
++ * see https://issues.apache.org/jira/browse/TS-2503 */
++ io->warmup_size = h2_config_geti64(cfg, H2_CONF_TLS_WARMUP_SIZE);
++ io->cooldown_usecs = (h2_config_geti(cfg, H2_CONF_TLS_COOLDOWN_SECS)
++ * APR_USEC_PER_SEC);
++ io->write_size = WRITE_SIZE_INITIAL;
++ }
++ else {
++ io->warmup_size = 0;
++ io->cooldown_usecs = 0;
++ io->write_size = io->bufsize;
++ }
++
++ if (APLOGctrace1(c)) {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
++ "h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, cd_secs=%f",
++ io->connection->id, io->buffer_output, (long)io->warmup_size,
++ ((float)io->cooldown_usecs/APR_USEC_PER_SEC));
++ }
++
return APR_SUCCESS;
}
--void h2_conn_io_destroy(h2_conn_io *io)
++int h2_conn_io_is_buffered(h2_conn_io *io)
{
-- io->input = NULL;
-- io->output = NULL;
++ return io->bufsize > 0;
}
static apr_status_t h2_conn_io_bucket_read(h2_conn_io *io,
status = ap_get_brigade(io->connection->input_filters,
io->input, AP_MODE_READBYTES,
-- block, 16 * 4096);
++ block, 64 * 4096);
switch (status) {
case APR_SUCCESS:
return h2_conn_io_bucket_read(io, block, on_read_cb, puser, &done);
return status;
}
--static apr_status_t flush_out(apr_bucket_brigade *bb, void *ctx)
++static apr_status_t pass_out(apr_bucket_brigade *bb, void *ctx)
{
h2_conn_io *io = (h2_conn_io*)ctx;
apr_status_t status;
apr_off_t bblen;
++ if (APR_BRIGADE_EMPTY(bb)) {
++ return APR_SUCCESS;
++ }
++
ap_update_child_status(io->connection->sbh, SERVER_BUSY_WRITE, NULL);
-- status = apr_brigade_length(bb, 1, &bblen);
++ status = apr_brigade_length(bb, 0, &bblen);
if (status == APR_SUCCESS) {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
++ "h2_conn_io(%ld): pass_out brigade %ld bytes",
++ io->connection->id, (long)bblen);
status = ap_pass_brigade(io->connection->output_filters, bb);
if (status == APR_SUCCESS) {
io->bytes_written += (apr_size_t)bblen;
return status;
}
++/* Bring the current buffer content into the output brigade, appropriately
++ * chunked.
++ */
static apr_status_t bucketeer_buffer(h2_conn_io *io) {
const char *data = io->buffer;
apr_size_t remaining = io->buflen;
apr_bucket *b;
int bcount, i;
-- if (io->write_size > WRITE_SIZE_INITIAL
-- && (apr_time_now() - io->last_write) >= WRITE_SIZE_IDLE_USEC) {
++ if (io->write_size > WRITE_SIZE_INITIAL
++ && (io->cooldown_usecs > 0)
++ && (apr_time_now() - io->last_write) >= io->cooldown_usecs) {
/* long time not written, reset write size */
io->write_size = WRITE_SIZE_INITIAL;
io->bytes_written = 0;
(long)io->connection->id, (long)io->write_size);
}
else if (io->write_size < WRITE_SIZE_MAX
-- && io->bytes_written >= WRITE_SIZE_THRESHOLD) {
++ && io->bytes_written >= io->warmup_size) {
/* connection is hot, use max size */
io->write_size = WRITE_SIZE_MAX;
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
const char *buf, size_t length)
{
apr_status_t status = APR_SUCCESS;
-- io->unflushed = 1;
-- if (io->buffer_output) {
++ io->unflushed = 1;
++ if (io->bufsize > 0) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
"h2_conn_io: buffering %ld bytes", (long)length);
++
++ if (!APR_BRIGADE_EMPTY(io->output)) {
++ status = h2_conn_io_flush(io);
++ io->unflushed = 1;
++ }
++
while (length > 0 && (status == APR_SUCCESS)) {
apr_size_t avail = io->bufsize - io->buflen;
if (avail <= 0) {
bucketeer_buffer(io);
-- status = flush_out(io->output, io);
++ status = pass_out(io->output, io);
io->buflen = 0;
}
else if (length > avail) {
}
else {
-- status = apr_brigade_write(io->output, flush_out, io, buf, length);
-- if (status != APR_SUCCESS) {
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
-- "h2_conn_io: write error");
-- }
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, io->connection,
++ "h2_conn_io: writing %ld bytes to brigade", (long)length);
++ status = apr_brigade_write(io->output, pass_out, io, buf, length);
}
return status;
}
++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b)
++{
++ APR_BRIGADE_INSERT_TAIL(io->output, b);
++ io->unflushed = 1;
++ return APR_SUCCESS;
++}
++
++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io)
++{
++ apr_status_t status = APR_SUCCESS;
++ int flush_now = 0;
++
++ /* The HTTP/1.1 network output buffer/flush behaviour does not
++ * give optimal performance in the HTTP/2 case, as the pattern of
++ * buckets (data/eor/eos) is different.
++ * As long as we do not have found out the "best" way to deal with
++ * this, force a flush at least every WRITE_BUFFER_SIZE amount
++ * of data which seems to work nicely.
++ */
++ if (io->unflushed) {
++ apr_off_t len = 0;
++ if (!APR_BRIGADE_EMPTY(io->output)) {
++ apr_brigade_length(io->output, 0, &len);
++ }
++ len += io->buflen;
++ flush_now = (len >= WRITE_BUFFER_SIZE);
++ }
++
++ if (flush_now) {
++ return h2_conn_io_flush(io);
++ }
++ return status;
++}
apr_status_t h2_conn_io_flush(h2_conn_io *io)
{
if (io->unflushed) {
apr_status_t status;
if (io->buflen > 0) {
++ /* something in the buffer, put it in the output brigade */
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
"h2_conn_io: flush, flushing %ld bytes", (long)io->buflen);
bucketeer_buffer(io);
io->buflen = 0;
}
-- /* Append flush.
-- */
++
APR_BRIGADE_INSERT_TAIL(io->output,
apr_bucket_flush_create(io->output->bucket_alloc));
++ /* Send it out */
++ status = pass_out(io->output, io);
-- /* Send it out through installed filters (TLS) to the client */
-- status = flush_out(io->output, io);
--
-- if (status == APR_SUCCESS) {
-- /* These are all fine and no reason for concern. Everything else
-- * is interesting. */
-- io->unflushed = 0;
-- }
-- else {
++ if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->connection,
-- "h2_conn_io: flush error");
++ "h2_conn_io: flush");
++ return status;
}
--
-- return status;
++
++ io->unflushed = 0;
}
return APR_SUCCESS;
}
conn_rec *connection;
apr_bucket_brigade *input;
apr_bucket_brigade *output;
-- int buffer_output;
++
++ int is_tls;
++ apr_time_t cooldown_usecs;
++ apr_int64_t warmup_size;
++
int write_size;
apr_time_t last_write;
-- apr_size_t bytes_written;
++ apr_int64_t bytes_written;
++ int buffer_output;
char *buffer;
apr_size_t buflen;
apr_size_t bufsize;
} h2_conn_io;
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c);
--void h2_conn_io_destroy(h2_conn_io *io);
++
++int h2_conn_io_is_buffered(h2_conn_io *io);
typedef apr_status_t (*h2_conn_io_on_read_cb)(const char *data, apr_size_t len,
apr_size_t *readlen, int *done,
apr_status_t h2_conn_io_write(h2_conn_io *io,
const char *buf,
size_t length);
++
++apr_status_t h2_conn_io_writeb(h2_conn_io *io, apr_bucket *b);
++
++apr_status_t h2_conn_io_consider_flush(h2_conn_io *io);
apr_status_t h2_conn_io_flush(h2_conn_io *io);
return ctx;
}
--h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task_env *env)
++h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task *task)
{
h2_ctx *ctx = h2_ctx_create(c);
if (ctx) {
-- ctx->task_env = env;
++ ctx->task = task;
}
return ctx;
}
int h2_ctx_is_task(h2_ctx *ctx)
{
-- return ctx && !!ctx->task_env;
++ return ctx && !!ctx->task;
}
int h2_ctx_is_active(h2_ctx *ctx)
return ctx && ctx->is_h2;
}
--struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx)
++struct h2_task *h2_ctx_get_task(h2_ctx *ctx)
{
-- return ctx->task_env;
++ return ctx->task;
}
#ifndef __mod_h2__h2_ctx__
#define __mod_h2__h2_ctx__
--struct h2_task_env;
++struct h2_task;
struct h2_config;
/**
typedef struct h2_ctx {
int is_h2; /* h2 engine is used */
const char *protocol; /* the protocol negotiated */
-- struct h2_task_env *task_env; /* the h2_task environment or NULL */
++ struct h2_task *task; /* the h2_task executing or NULL */
const char *hostname; /* hostname negotiated via SNI, optional */
server_rec *server; /* httpd server config selected. */
struct h2_config *config; /* effective config in this context */
h2_ctx *h2_ctx_get(const conn_rec *c);
h2_ctx *h2_ctx_rget(const request_rec *r);
--h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task_env *env);
++h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task *task);
/* Set the h2 protocol established on this connection context or
int h2_ctx_is_task(h2_ctx *ctx);
int h2_ctx_is_active(h2_ctx *ctx);
--struct h2_task_env *h2_ctx_get_task(h2_ctx *ctx);
++struct h2_task *h2_ctx_get_task(h2_ctx *ctx);
#endif /* defined(__mod_h2__h2_ctx__) */
static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
{
-- from_h1->response = h2_response_create(from_h1->stream_id,
-- from_h1->status, from_h1->hlines,
-- from_h1->pool);
++ from_h1->response = h2_response_create(from_h1->stream_id, 0,
++ from_h1->status, from_h1->hlines,
++ from_h1->pool);
if (from_h1->response == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
APLOGNO(02915)
apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
{
-- h2_task_env *env = f->ctx;
-- h2_from_h1 *from_h1 = env->output? env->output->from_h1 : NULL;
++ h2_task *task = f->ctx;
++ h2_from_h1 *from_h1 = task->output? task->output->from_h1 : NULL;
request_rec *r = f->r;
apr_bucket *b;
ap_bucket_error *eb = NULL;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
"h2_from_h1(%d): output_filter called", from_h1->stream_id);
-- if (r->header_only && env->output && from_h1->response) {
++ if (r->header_only && task->output && from_h1->response) {
/* throw away any data after we have compiled the response */
apr_brigade_cleanup(bb);
return OK;
static int (*opt_ssl_engine_disable)(conn_rec*);
static int (*opt_ssl_is_https)(conn_rec*);
++/*******************************************************************************
++ * SSL var lookup
++ */
++APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
++ (apr_pool_t *, server_rec *,
++ conn_rec *, request_rec *,
++ char *));
++static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
++ conn_rec *, request_rec *,
++ char *);
++
++
++/*******************************************************************************
++ * HTTP/2 error stuff
++ */
++static const char *h2_err_descr[] = {
++ "no error", /* 0x0 */
++ "protocol error",
++ "internal error",
++ "flow control error",
++ "settings timeout",
++ "stream closed", /* 0x5 */
++ "frame size error",
++ "refused stream",
++ "cancel",
++ "compression error",
++ "connect error", /* 0xa */
++ "enhance your calm",
++ "inadequate security",
++ "http/1.1 required",
++};
++
++const char *h2_h2_err_description(int h2_error)
++{
++ if (h2_error >= 0
++ && h2_error < (sizeof(h2_err_descr)/sizeof(h2_err_descr[0]))) {
++ return h2_err_descr[h2_error];
++ }
++ return "unknown http/2 errotr code";
++}
++
++/*******************************************************************************
++ * Check connection security requirements of RFC 7540
++ */
++
++/*
++ * Black Listed Ciphers from RFC 7549 Appendix A
++ *
++ */
++static const char *RFC7540_names[] = {
++ /* ciphers with NULL encrpytion */
++ "NULL-MD5", /* TLS_NULL_WITH_NULL_NULL */
++ /* same */ /* TLS_RSA_WITH_NULL_MD5 */
++ "NULL-SHA", /* TLS_RSA_WITH_NULL_SHA */
++ "NULL-SHA256", /* TLS_RSA_WITH_NULL_SHA256 */
++ "PSK-NULL-SHA", /* TLS_PSK_WITH_NULL_SHA */
++ "DHE-PSK-NULL-SHA", /* TLS_DHE_PSK_WITH_NULL_SHA */
++ "RSA-PSK-NULL-SHA", /* TLS_RSA_PSK_WITH_NULL_SHA */
++ "PSK-NULL-SHA256", /* TLS_PSK_WITH_NULL_SHA256 */
++ "PSK-NULL-SHA384", /* TLS_PSK_WITH_NULL_SHA384 */
++ "DHE-PSK-NULL-SHA256", /* TLS_DHE_PSK_WITH_NULL_SHA256 */
++ "DHE-PSK-NULL-SHA384", /* TLS_DHE_PSK_WITH_NULL_SHA384 */
++ "RSA-PSK-NULL-SHA256", /* TLS_RSA_PSK_WITH_NULL_SHA256 */
++ "RSA-PSK-NULL-SHA384", /* TLS_RSA_PSK_WITH_NULL_SHA384 */
++ "ECDH-ECDSA-NULL-SHA", /* TLS_ECDH_ECDSA_WITH_NULL_SHA */
++ "ECDHE-ECDSA-NULL-SHA", /* TLS_ECDHE_ECDSA_WITH_NULL_SHA */
++ "ECDH-RSA-NULL-SHA", /* TLS_ECDH_RSA_WITH_NULL_SHA */
++ "ECDHE-RSA-NULL-SHA", /* TLS_ECDHE_RSA_WITH_NULL_SHA */
++ "AECDH-NULL-SHA", /* TLS_ECDH_anon_WITH_NULL_SHA */
++ "ECDHE-PSK-NULL-SHA", /* TLS_ECDHE_PSK_WITH_NULL_SHA */
++ "ECDHE-PSK-NULL-SHA256", /* TLS_ECDHE_PSK_WITH_NULL_SHA256 */
++ "ECDHE-PSK-NULL-SHA384", /* TLS_ECDHE_PSK_WITH_NULL_SHA384 */
++
++ /* DES/3DES ciphers */
++ "PSK-3DES-EDE-CBC-SHA", /* TLS_PSK_WITH_3DES_EDE_CBC_SHA */
++ "DHE-PSK-3DES-EDE-CBC-SHA", /* TLS_DHE_PSK_WITH_3DES_EDE_CBC_SHA */
++ "RSA-PSK-3DES-EDE-CBC-SHA", /* TLS_RSA_PSK_WITH_3DES_EDE_CBC_SHA */
++ "ECDH-ECDSA-DES-CBC3-SHA", /* TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA */
++ "ECDHE-ECDSA-DES-CBC3-SHA", /* TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA */
++ "ECDH-RSA-DES-CBC3-SHA", /* TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA */
++ "ECDHE-RSA-DES-CBC3-SHA", /* TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA */
++ "AECDH-DES-CBC3-SHA", /* TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA */
++ "SRP-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA */
++ "SRP-RSA-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_3DES_EDE_CBC_SHA */
++ "SRP-DSS-3DES-EDE-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA */
++ "ECDHE-PSK-3DES-EDE-CBC-SHA", /* TLS_ECDHE_PSK_WITH_3DES_EDE_CBC_SHA */
++ "DES-CBC-SHA", /* TLS_RSA_WITH_DES_CBC_SHA */
++ "DES-CBC3-SHA", /* TLS_RSA_WITH_3DES_EDE_CBC_SHA */
++ "DHE-DSS-DES-CBC3-SHA", /* TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA */
++ "DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_WITH_DES_CBC_SHA */
++ "DHE-RSA-DES-CBC3-SHA", /* TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA */
++ "ADH-DES-CBC-SHA", /* TLS_DH_anon_WITH_DES_CBC_SHA */
++ "ADH-DES-CBC3-SHA", /* TLS_DH_anon_WITH_3DES_EDE_CBC_SHA */
++ "EXP-DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA */
++ "DH-DSS-DES-CBC-SHA", /* TLS_DH_DSS_WITH_DES_CBC_SHA */
++ "DH-DSS-DES-CBC3-SHA", /* TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA */
++ "EXP-DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA */
++ "DH-RSA-DES-CBC-SHA", /* TLS_DH_RSA_WITH_DES_CBC_SHA */
++ "DH-RSA-DES-CBC3-SHA", /* TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA */
++
++ /* blacklisted EXPORT ciphers */
++ "EXP-RC4-MD5", /* TLS_RSA_EXPORT_WITH_RC4_40_MD5 */
++ "EXP-RC2-CBC-MD5", /* TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 */
++ "EXP-DES-CBC-SHA", /* TLS_RSA_EXPORT_WITH_DES40_CBC_SHA */
++ "EXP-DHE-DSS-DES-CBC-SHA", /* TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA */
++ "EXP-DHE-RSA-DES-CBC-SHA", /* TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA */
++ "EXP-ADH-DES-CBC-SHA", /* TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA */
++ "EXP-ADH-RC4-MD5", /* TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 */
++
++ /* blacklisted RC4 encryption */
++ "RC4-MD5", /* TLS_RSA_WITH_RC4_128_MD5 */
++ "RC4-SHA", /* TLS_RSA_WITH_RC4_128_SHA */
++ "ADH-RC4-MD5", /* TLS_DH_anon_WITH_RC4_128_MD5 */
++ "KRB5-RC4-SHA", /* TLS_KRB5_WITH_RC4_128_SHA */
++ "KRB5-RC4-MD5", /* TLS_KRB5_WITH_RC4_128_MD5 */
++ "EXP-KRB5-RC4-SHA", /* TLS_KRB5_EXPORT_WITH_RC4_40_SHA */
++ "EXP-KRB5-RC4-MD5", /* TLS_KRB5_EXPORT_WITH_RC4_40_MD5 */
++ "PSK-RC4-SHA", /* TLS_PSK_WITH_RC4_128_SHA */
++ "DHE-PSK-RC4-SHA", /* TLS_DHE_PSK_WITH_RC4_128_SHA */
++ "RSA-PSK-RC4-SHA", /* TLS_RSA_PSK_WITH_RC4_128_SHA */
++ "ECDH-ECDSA-RC4-SHA", /* TLS_ECDH_ECDSA_WITH_RC4_128_SHA */
++ "ECDHE-ECDSA-RC4-SHA", /* TLS_ECDHE_ECDSA_WITH_RC4_128_SHA */
++ "ECDH-RSA-RC4-SHA", /* TLS_ECDH_RSA_WITH_RC4_128_SHA */
++ "ECDHE-RSA-RC4-SHA", /* TLS_ECDHE_RSA_WITH_RC4_128_SHA */
++ "AECDH-RC4-SHA", /* TLS_ECDH_anon_WITH_RC4_128_SHA */
++ "ECDHE-PSK-RC4-SHA", /* TLS_ECDHE_PSK_WITH_RC4_128_SHA */
++
++ /* blacklisted AES128 encrpytion ciphers */
++ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA */
++ "DH-DSS-AES128-SHA", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA */
++ "DH-RSA-AES128-SHA", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA */
++ "DHE-DSS-AES128-SHA", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA */
++ "DHE-RSA-AES128-SHA", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA */
++ "ADH-AES128-SHA", /* TLS_DH_anon_WITH_AES_128_CBC_SHA */
++ "AES128-SHA256", /* TLS_RSA_WITH_AES_128_CBC_SHA256 */
++ "DH-DSS-AES128-SHA256", /* TLS_DH_DSS_WITH_AES_128_CBC_SHA256 */
++ "DH-RSA-AES128-SHA256", /* TLS_DH_RSA_WITH_AES_128_CBC_SHA256 */
++ "DHE-DSS-AES128-SHA256", /* TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 */
++ "DHE-RSA-AES128-SHA256", /* TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 */
++ "ECDH-ECDSA-AES128-SHA", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA */
++ "ECDHE-ECDSA-AES128-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA */
++ "ECDH-RSA-AES128-SHA", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA */
++ "ECDHE-RSA-AES128-SHA", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA */
++ "AECDH-AES128-SHA", /* TLS_ECDH_anon_WITH_AES_128_CBC_SHA */
++ "ECDHE-ECDSA-AES128-SHA256", /* TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 */
++ "ECDH-ECDSA-AES128-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 */
++ "ECDHE-RSA-AES128-SHA256", /* TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 */
++ "ECDH-RSA-AES128-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 */
++ "ADH-AES128-SHA256", /* TLS_DH_anon_WITH_AES_128_CBC_SHA256 */
++ "PSK-AES128-CBC-SHA", /* TLS_PSK_WITH_AES_128_CBC_SHA */
++ "DHE-PSK-AES128-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA */
++ "RSA-PSK-AES128-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA */
++ "PSK-AES128-CBC-SHA256", /* TLS_PSK_WITH_AES_128_CBC_SHA256 */
++ "DHE-PSK-AES128-CBC-SHA256", /* TLS_DHE_PSK_WITH_AES_128_CBC_SHA256 */
++ "RSA-PSK-AES128-CBC-SHA256", /* TLS_RSA_PSK_WITH_AES_128_CBC_SHA256 */
++ "ECDHE-PSK-AES128-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA */
++ "ECDHE-PSK-AES128-CBC-SHA256", /* TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256 */
++ "AES128-CCM", /* TLS_RSA_WITH_AES_128_CCM */
++ "AES128-CCM8", /* TLS_RSA_WITH_AES_128_CCM_8 */
++ "PSK-AES128-CCM", /* TLS_PSK_WITH_AES_128_CCM */
++ "PSK-AES128-CCM8", /* TLS_PSK_WITH_AES_128_CCM_8 */
++ "AES128-GCM-SHA256", /* TLS_RSA_WITH_AES_128_GCM_SHA256 */
++ "DH-RSA-AES128-GCM-SHA256", /* TLS_DH_RSA_WITH_AES_128_GCM_SHA256 */
++ "DH-DSS-AES128-GCM-SHA256", /* TLS_DH_DSS_WITH_AES_128_GCM_SHA256 */
++ "ADH-AES128-GCM-SHA256", /* TLS_DH_anon_WITH_AES_128_GCM_SHA256 */
++ "PSK-AES128-GCM-SHA256", /* TLS_PSK_WITH_AES_128_GCM_SHA256 */
++ "RSA-PSK-AES128-GCM-SHA256", /* TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 */
++ "ECDH-ECDSA-AES128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 */
++ "ECDH-RSA-AES128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 */
++ "SRP-AES-128-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_128_CBC_SHA */
++ "SRP-RSA-AES-128-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_128_CBC_SHA */
++ "SRP-DSS-AES-128-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_128_CBC_SHA */
++
++ /* blacklisted AES256 encrpytion ciphers */
++ "AES256-SHA", /* TLS_RSA_WITH_AES_256_CBC_SHA */
++ "DH-DSS-AES256-SHA", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA */
++ "DH-RSA-AES256-SHA", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA */
++ "DHE-DSS-AES256-SHA", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA */
++ "DHE-RSA-AES256-SHA", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA */
++ "ADH-AES256-SHA", /* TLS_DH_anon_WITH_AES_256_CBC_SHA */
++ "AES256-SHA256", /* TLS_RSA_WITH_AES_256_CBC_SHA256 */
++ "DH-DSS-AES256-SHA256", /* TLS_DH_DSS_WITH_AES_256_CBC_SHA256 */
++ "DH-RSA-AES256-SHA256", /* TLS_DH_RSA_WITH_AES_256_CBC_SHA256 */
++ "DHE-DSS-AES256-SHA256", /* TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 */
++ "DHE-RSA-AES256-SHA256", /* TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 */
++ "ADH-AES256-SHA256", /* TLS_DH_anon_WITH_AES_256_CBC_SHA256 */
++ "ECDH-ECDSA-AES256-SHA", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA */
++ "ECDHE-ECDSA-AES256-SHA", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */
++ "ECDH-RSA-AES256-SHA", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA */
++ "ECDHE-RSA-AES256-SHA", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA */
++ "AECDH-AES256-SHA", /* TLS_ECDH_anon_WITH_AES_256_CBC_SHA */
++ "ECDHE-ECDSA-AES256-SHA384", /* TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 */
++ "ECDH-ECDSA-AES256-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 */
++ "ECDHE-RSA-AES256-SHA384", /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */
++ "ECDH-RSA-AES256-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 */
++ "PSK-AES256-CBC-SHA", /* TLS_PSK_WITH_AES_256_CBC_SHA */
++ "DHE-PSK-AES256-CBC-SHA", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA */
++ "RSA-PSK-AES256-CBC-SHA", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA */
++ "PSK-AES256-CBC-SHA384", /* TLS_PSK_WITH_AES_256_CBC_SHA384 */
++ "DHE-PSK-AES256-CBC-SHA384", /* TLS_DHE_PSK_WITH_AES_256_CBC_SHA384 */
++ "RSA-PSK-AES256-CBC-SHA384", /* TLS_RSA_PSK_WITH_AES_256_CBC_SHA384 */
++ "ECDHE-PSK-AES256-CBC-SHA", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA */
++ "ECDHE-PSK-AES256-CBC-SHA384", /* TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384 */
++ "SRP-AES-256-CBC-SHA", /* TLS_SRP_SHA_WITH_AES_256_CBC_SHA */
++ "SRP-RSA-AES-256-CBC-SHA", /* TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA */
++ "SRP-DSS-AES-256-CBC-SHA", /* TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA */
++ "AES256-CCM", /* TLS_RSA_WITH_AES_256_CCM */
++ "AES256-CCM8", /* TLS_RSA_WITH_AES_256_CCM_8 */
++ "PSK-AES256-CCM", /* TLS_PSK_WITH_AES_256_CCM */
++ "PSK-AES256-CCM8", /* TLS_PSK_WITH_AES_256_CCM_8 */
++ "AES256-GCM-SHA384", /* TLS_RSA_WITH_AES_256_GCM_SHA384 */
++ "DH-RSA-AES256-GCM-SHA384", /* TLS_DH_RSA_WITH_AES_256_GCM_SHA384 */
++ "DH-DSS-AES256-GCM-SHA384", /* TLS_DH_DSS_WITH_AES_256_GCM_SHA384 */
++ "ADH-AES256-GCM-SHA384", /* TLS_DH_anon_WITH_AES_256_GCM_SHA384 */
++ "PSK-AES256-GCM-SHA384", /* TLS_PSK_WITH_AES_256_GCM_SHA384 */
++ "RSA-PSK-AES256-GCM-SHA384", /* TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 */
++ "ECDH-ECDSA-AES256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 */
++ "ECDH-RSA-AES256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 */
++
++ /* blacklisted CAMELLIA128 encrpytion ciphers */
++ "CAMELLIA128-SHA", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA */
++ "DH-DSS-CAMELLIA128-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA */
++ "DH-RSA-CAMELLIA128-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA */
++ "DHE-DSS-CAMELLIA128-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA */
++ "DHE-RSA-CAMELLIA128-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA */
++ "ADH-CAMELLIA128-SHA", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA */
++ "ECDHE-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "ECDH-ECDSA-CAMELLIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "ECDHE-RSA-CAMELLIA128-SHA256", /* TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "ECDH-RSA-CAMELLIA128-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "PSK-CAMELLIA128-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++ "DHE-PSK-CAMELLIA128-SHA256", /* TLS_DHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++ "RSA-PSK-CAMELLIA128-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++ "ECDHE-PSK-CAMELLIA128-SHA256", /* TLS_ECDHE_PSK_WITH_CAMELLIA_128_CBC_SHA256 */
++ "CAMELLIA128-GCM-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++ "DH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++ "DH-DSS-CAMELLIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_GCM_SHA256 */
++ "ADH-CAMELLIA128-GCM-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_GCM_SHA256 */
++ "ECDH-ECDSA-CAMELLIA128-GCM-SHA256",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_128_GCM_SHA256 */
++ "ECDH-RSA-CAMELLIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_CAMELLIA_128_GCM_SHA256 */
++ "PSK-CAMELLIA128-GCM-SHA256", /* TLS_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
++ "RSA-PSK-CAMELLIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_CAMELLIA_128_GCM_SHA256 */
++ "CAMELLIA128-SHA256", /* TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "DH-DSS-CAMELLIA128-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
++ "DH-RSA-CAMELLIA128-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "DHE-DSS-CAMELLIA128-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256 */
++ "DHE-RSA-CAMELLIA128-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 */
++ "ADH-CAMELLIA128-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_128_CBC_SHA256 */
++
++ /* blacklisted CAMELLIA256 encrpytion ciphers */
++ "CAMELLIA256-SHA", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA */
++ "DH-RSA-CAMELLIA256-SHA", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA */
++ "DH-DSS-CAMELLIA256-SHA", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA */
++ "DHE-DSS-CAMELLIA256-SHA", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA */
++ "DHE-RSA-CAMELLIA256-SHA", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA */
++ "ADH-CAMELLIA256-SHA", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA */
++ "ECDHE-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
++ "ECDH-ECDSA-CAMELLIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_CBC_SHA384 */
++ "ECDHE-RSA-CAMELLIA256-SHA384", /* TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
++ "ECDH-RSA-CAMELLIA256-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_CBC_SHA384 */
++ "PSK-CAMELLIA256-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++ "DHE-PSK-CAMELLIA256-SHA384", /* TLS_DHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++ "RSA-PSK-CAMELLIA256-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++ "ECDHE-PSK-CAMELLIA256-SHA384", /* TLS_ECDHE_PSK_WITH_CAMELLIA_256_CBC_SHA384 */
++ "CAMELLIA256-SHA256", /* TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++ "DH-DSS-CAMELLIA256-SHA256", /* TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
++ "DH-RSA-CAMELLIA256-SHA256", /* TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++ "DHE-DSS-CAMELLIA256-SHA256", /* TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256 */
++ "DHE-RSA-CAMELLIA256-SHA256", /* TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 */
++ "ADH-CAMELLIA256-SHA256", /* TLS_DH_anon_WITH_CAMELLIA_256_CBC_SHA256 */
++ "CAMELLIA256-GCM-SHA384", /* TLS_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++ "DH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++ "DH-DSS-CAMELLIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_CAMELLIA_256_GCM_SHA384 */
++ "ADH-CAMELLIA256-GCM-SHA384", /* TLS_DH_anon_WITH_CAMELLIA_256_GCM_SHA384 */
++ "ECDH-ECDSA-CAMELLIA256-GCM-SHA384",/* TLS_ECDH_ECDSA_WITH_CAMELLIA_256_GCM_SHA384 */
++ "ECDH-RSA-CAMELLIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_CAMELLIA_256_GCM_SHA384 */
++ "PSK-CAMELLIA256-GCM-SHA384", /* TLS_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
++ "RSA-PSK-CAMELLIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_CAMELLIA_256_GCM_SHA384 */
++
++ /* The blacklisted ARIA encrpytion ciphers */
++ "ARIA128-SHA256", /* TLS_RSA_WITH_ARIA_128_CBC_SHA256 */
++ "ARIA256-SHA384", /* TLS_RSA_WITH_ARIA_256_CBC_SHA384 */
++ "DH-DSS-ARIA128-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_CBC_SHA256 */
++ "DH-DSS-ARIA256-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_CBC_SHA384 */
++ "DH-RSA-ARIA128-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_CBC_SHA256 */
++ "DH-RSA-ARIA256-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_CBC_SHA384 */
++ "DHE-DSS-ARIA128-SHA256", /* TLS_DHE_DSS_WITH_ARIA_128_CBC_SHA256 */
++ "DHE-DSS-ARIA256-SHA384", /* TLS_DHE_DSS_WITH_ARIA_256_CBC_SHA384 */
++ "DHE-RSA-ARIA128-SHA256", /* TLS_DHE_RSA_WITH_ARIA_128_CBC_SHA256 */
++ "DHE-RSA-ARIA256-SHA384", /* TLS_DHE_RSA_WITH_ARIA_256_CBC_SHA384 */
++ "ADH-ARIA128-SHA256", /* TLS_DH_anon_WITH_ARIA_128_CBC_SHA256 */
++ "ADH-ARIA256-SHA384", /* TLS_DH_anon_WITH_ARIA_256_CBC_SHA384 */
++ "ECDHE-ECDSA-ARIA128-SHA256", /* TLS_ECDHE_ECDSA_WITH_ARIA_128_CBC_SHA256 */
++ "ECDHE-ECDSA-ARIA256-SHA384", /* TLS_ECDHE_ECDSA_WITH_ARIA_256_CBC_SHA384 */
++ "ECDH-ECDSA-ARIA128-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_CBC_SHA256 */
++ "ECDH-ECDSA-ARIA256-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_CBC_SHA384 */
++ "ECDHE-RSA-ARIA128-SHA256", /* TLS_ECDHE_RSA_WITH_ARIA_128_CBC_SHA256 */
++ "ECDHE-RSA-ARIA256-SHA384", /* TLS_ECDHE_RSA_WITH_ARIA_256_CBC_SHA384 */
++ "ECDH-RSA-ARIA128-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_CBC_SHA256 */
++ "ECDH-RSA-ARIA256-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_CBC_SHA384 */
++ "ARIA128-GCM-SHA256", /* TLS_RSA_WITH_ARIA_128_GCM_SHA256 */
++ "ARIA256-GCM-SHA384", /* TLS_RSA_WITH_ARIA_256_GCM_SHA384 */
++ "DH-DSS-ARIA128-GCM-SHA256", /* TLS_DH_DSS_WITH_ARIA_128_GCM_SHA256 */
++ "DH-DSS-ARIA256-GCM-SHA384", /* TLS_DH_DSS_WITH_ARIA_256_GCM_SHA384 */
++ "DH-RSA-ARIA128-GCM-SHA256", /* TLS_DH_RSA_WITH_ARIA_128_GCM_SHA256 */
++ "DH-RSA-ARIA256-GCM-SHA384", /* TLS_DH_RSA_WITH_ARIA_256_GCM_SHA384 */
++ "ADH-ARIA128-GCM-SHA256", /* TLS_DH_anon_WITH_ARIA_128_GCM_SHA256 */
++ "ADH-ARIA256-GCM-SHA384", /* TLS_DH_anon_WITH_ARIA_256_GCM_SHA384 */
++ "ECDH-ECDSA-ARIA128-GCM-SHA256", /* TLS_ECDH_ECDSA_WITH_ARIA_128_GCM_SHA256 */
++ "ECDH-ECDSA-ARIA256-GCM-SHA384", /* TLS_ECDH_ECDSA_WITH_ARIA_256_GCM_SHA384 */
++ "ECDH-RSA-ARIA128-GCM-SHA256", /* TLS_ECDH_RSA_WITH_ARIA_128_GCM_SHA256 */
++ "ECDH-RSA-ARIA256-GCM-SHA384", /* TLS_ECDH_RSA_WITH_ARIA_256_GCM_SHA384 */
++ "PSK-ARIA128-SHA256", /* TLS_PSK_WITH_ARIA_128_CBC_SHA256 */
++ "PSK-ARIA256-SHA384", /* TLS_PSK_WITH_ARIA_256_CBC_SHA384 */
++ "DHE-PSK-ARIA128-SHA256", /* TLS_DHE_PSK_WITH_ARIA_128_CBC_SHA256 */
++ "DHE-PSK-ARIA256-SHA384", /* TLS_DHE_PSK_WITH_ARIA_256_CBC_SHA384 */
++ "RSA-PSK-ARIA128-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_CBC_SHA256 */
++ "RSA-PSK-ARIA256-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_CBC_SHA384 */
++ "ARIA128-GCM-SHA256", /* TLS_PSK_WITH_ARIA_128_GCM_SHA256 */
++ "ARIA256-GCM-SHA384", /* TLS_PSK_WITH_ARIA_256_GCM_SHA384 */
++ "RSA-PSK-ARIA128-GCM-SHA256", /* TLS_RSA_PSK_WITH_ARIA_128_GCM_SHA256 */
++ "RSA-PSK-ARIA256-GCM-SHA384", /* TLS_RSA_PSK_WITH_ARIA_256_GCM_SHA384 */
++ "ECDHE-PSK-ARIA128-SHA256", /* TLS_ECDHE_PSK_WITH_ARIA_128_CBC_SHA256 */
++ "ECDHE-PSK-ARIA256-SHA384", /* TLS_ECDHE_PSK_WITH_ARIA_256_CBC_SHA384 */
++
++ /* blacklisted SEED encryptions */
++ "SEED-SHA", /*TLS_RSA_WITH_SEED_CBC_SHA */
++ "DH-DSS-SEED-SHA", /* TLS_DH_DSS_WITH_SEED_CBC_SHA */
++ "DH-RSA-SEED-SHA", /* TLS_DH_RSA_WITH_SEED_CBC_SHA */
++ "DHE-DSS-SEED-SHA", /* TLS_DHE_DSS_WITH_SEED_CBC_SHA */
++ "DHE-RSA-SEED-SHA", /* TLS_DHE_RSA_WITH_SEED_CBC_SHA */
++ "ADH-SEED-SHA", /* TLS_DH_anon_WITH_SEED_CBC_SHA */
++
++ /* blacklisted KRB5 ciphers */
++ "KRB5-DES-CBC-SHA", /* TLS_KRB5_WITH_DES_CBC_SHA */
++ "KRB5-DES-CBC3-SHA", /* TLS_KRB5_WITH_3DES_EDE_CBC_SHA */
++ "KRB5-IDEA-CBC-SHA", /* TLS_KRB5_WITH_IDEA_CBC_SHA */
++ "KRB5-DES-CBC-MD5", /* TLS_KRB5_WITH_DES_CBC_MD5 */
++ "KRB5-DES-CBC3-MD5", /* TLS_KRB5_WITH_3DES_EDE_CBC_MD5 */
++ "KRB5-IDEA-CBC-MD5", /* TLS_KRB5_WITH_IDEA_CBC_MD5 */
++ "EXP-KRB5-DES-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA */
++ "EXP-KRB5-DES-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 */
++ "EXP-KRB5-RC2-CBC-SHA", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA */
++ "EXP-KRB5-RC2-CBC-MD5", /* TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 */
++
++ /* blacklisted exoticas */
++ "DHE-DSS-CBC-SHA", /* TLS_DHE_DSS_WITH_DES_CBC_SHA */
++ "IDEA-CBC-SHA", /* TLS_RSA_WITH_IDEA_CBC_SHA */
++
++ /* not really sure if the following names are correct */
++ "SSL3_CK_SCSV", /* TLS_EMPTY_RENEGOTIATION_INFO_SCSV */
++ "SSL3_CK_FALLBACK_SCSV"
++};
++static size_t RFC7540_names_LEN = sizeof(RFC7540_names)/sizeof(RFC7540_names[0]);
++
++
++static apr_hash_t *BLCNames;
++
++static void cipher_init(apr_pool_t *pool)
++{
++ apr_hash_t *hash = apr_hash_make(pool);
++ const char *source;
++ int i;
++
++ source = "rfc7540";
++ for (i = 0; i < RFC7540_names_LEN; ++i) {
++ apr_hash_set(hash, RFC7540_names[i], APR_HASH_KEY_STRING, source);
++ }
++
++ BLCNames = hash;
++}
++
++static int cipher_is_blacklisted(const char *cipher, const char **psource)
++{
++ *psource = apr_hash_get(BLCNames, cipher, APR_HASH_KEY_STRING);
++ return !!*psource;
++}
++
/*******************************************************************************
* Hooks for processing incoming connections:
* - pre_conn_before_tls switches SSL off for stream connections
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_h2, child_init");
opt_ssl_engine_disable = APR_RETRIEVE_OPTIONAL_FN(ssl_engine_disable);
opt_ssl_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
++ opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
-- if (!opt_ssl_is_https) {
++ if (!opt_ssl_is_https || !opt_ssl_var_lookup) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
APLOGNO(02951) "mod_ssl does not seem to be enabled");
}
++ cipher_init(pool);
++
return APR_SUCCESS;
}
return opt_ssl_is_https && opt_ssl_is_https(c);
}
--int h2_tls_disable(conn_rec *c)
++int h2_is_acceptable_connection(conn_rec *c, int require_all)
+ {
- if (opt_ssl_engine_disable) {
- return opt_ssl_engine_disable(c);
++ int is_tls = h2_h2_is_tls(c);
++ h2_config *cfg = h2_config_get(c);
++
++ if (is_tls && h2_config_geti(cfg, H2_CONF_MODERN_TLS_ONLY) > 0) {
++ /* Check TLS connection for modern TLS parameters, as defined in
++ * RFC 7540 and https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
++ */
++ apr_pool_t *pool = c->pool;
++ server_rec *s = c->base_server;
++ char *val;
++
++ if (!opt_ssl_var_lookup) {
++ /* unable to check */
++ return 0;
++ }
++
++ /* Need Tlsv1.2 or higher, rfc 7540, ch. 9.2
++ */
++ val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_PROTOCOL");
++ if (val && *val) {
++ if (strncmp("TLS", val, 3)
++ || !strcmp("TLSv1", val)
++ || !strcmp("TLSv1.1", val)) {
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++ "h2_h2(%ld): tls protocol not suitable: %s",
++ (long)c->id, val);
++ return 0;
++ }
++ }
++ else if (require_all) {
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++ "h2_h2(%ld): tls protocol is indetermined", (long)c->id);
++ return 0;
++ }
++
++ /* Check TLS cipher blacklist
++ */
++ val = opt_ssl_var_lookup(pool, s, c, NULL, "SSL_CIPHER");
++ if (val && *val) {
++ const char *source;
++ if (cipher_is_blacklisted(val, &source)) {
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++ "h2_h2(%ld): tls cipher %s blacklisted by %s",
++ (long)c->id, val, source);
++ return 0;
++ }
++ }
++ else if (require_all) {
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++ "h2_h2(%ld): tls cipher is indetermined", (long)c->id);
++ return 0;
++ }
+ }
- return 0;
++ return 1;
++}
++
++int h2_allows_h2_direct(conn_rec *c)
+{
- if (opt_ssl_engine_disable) {
- return opt_ssl_engine_disable(c);
++ h2_config *cfg = h2_config_get(c);
++ int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT);
++
++ if (h2_direct < 0) {
++ if (h2_h2_is_tls(c)) {
++ /* disabled by default on TLS */
++ h2_direct = 0;
++ }
++ else {
++ /* enabled if "Protocols h2c" is configured */
++ h2_direct = ap_is_allowed_protocol(c, NULL, NULL, "h2c");
++ }
+ }
- return 0;
++ return !!h2_direct;
+}
+
++int h2_allows_h2_upgrade(conn_rec *c)
++{
++ h2_config *cfg = h2_config_get(c);
++ int h2_upgrade = h2_config_geti(cfg, H2_CONF_UPGRADE);
++
++ return h2_upgrade > 0 || (h2_upgrade < 0 && !h2_h2_is_tls(c));
+ }
/*******************************************************************************
* Register various hooks
*/
static const char *const mod_reqtimeout[] = { "reqtimeout.c", NULL};
++static const char* const mod_ssl[] = {"mod_ssl.c", NULL};
void h2_h2_register_hooks(void)
{
* take over, if h2* was selected as protocol.
*/
ap_hook_process_connection(h2_h2_process_conn,
-- NULL, NULL, APR_HOOK_FIRST);
++ mod_ssl, NULL, APR_HOOK_MIDDLE);
++
/* Perform connection cleanup before the actual processing happens.
*/
ap_hook_process_connection(h2_h2_remove_timeout,
int h2_h2_process_conn(conn_rec* c)
{
h2_ctx *ctx = h2_ctx_get(c);
-- h2_config *cfg = h2_config_get(c);
-- apr_bucket_brigade* temp;
-- int is_tls = h2_h2_is_tls(c);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, process_conn");
if (h2_ctx_is_task(ctx)) {
return DECLINED;
}
-- /* If we have not already switched to a h2* protocol and the connection
-- * is on "http/1.1"
-- * -> sniff for the magic PRIamble. On TLS, this might trigger the ALPN.
-- */
-- if (!h2_ctx_protocol_get(c)
-- && !strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
++ if (h2_ctx_protocol_get(c)) {
++ /* Something has been negotiated */
++ }
++ else if (!strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))
++ && h2_allows_h2_direct(c)
++ && h2_is_acceptable_connection(c, 1)) {
++ /* connection still is on http/1.1 and H2Direct is enabled.
++ * Otherwise connection is in a fully acceptable state.
++ * -> peek at the first 24 incoming bytes
++ */
++ apr_bucket_brigade *temp;
apr_status_t status;
++ char *s = NULL;
++ apr_size_t slen;
temp = apr_brigade_create(c->pool, c->bucket_alloc);
status = ap_get_brigade(c->input_filters, temp,
AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
--
-- if (status == APR_SUCCESS) {
-- if (h2_ctx_protocol_get(c)
-- || strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
-- /* h2 or another protocol has been selected. */
-- }
-- else {
-- /* ALPN might have been triggered, but we're still on
-- * http/1.1. Check the actual bytes read for the H2 Magic
-- * Token, *if* H2Direct mode is enabled here.
-- */
-- int direct_mode = h2_config_geti(cfg, H2_CONF_DIRECT);
-- if (direct_mode > 0 || (direct_mode < 0 && !is_tls)) {
-- char *s = NULL;
-- apr_size_t slen;
--
-- apr_brigade_pflatten(temp, &s, &slen, c->pool);
-- if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-- "h2_h2, direct mode detected");
-- h2_ctx_protocol_set(ctx, is_tls? "h2" : "h2c");
-- }
-- else {
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
-- "h2_h2, not detected in %d bytes: %s",
-- (int)slen, s);
-- }
-- }
-- }
-- }
-- else {
++
++ if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
"h2_h2, error reading 24 bytes speculative");
++ apr_brigade_destroy(temp);
++ return DECLINED;
++ }
++
++ apr_brigade_pflatten(temp, &s, &slen, c->pool);
++ if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
++ "h2_h2, direct mode detected");
++ h2_ctx_protocol_set(ctx, h2_h2_is_tls(c)? "h2" : "h2c");
}
++ else {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
++ "h2_h2, not detected in %d bytes: %s",
++ (int)slen, s);
++ }
++
apr_brigade_destroy(temp);
}
++ else {
++ /* the connection is not HTTP/1.1 or not for us, don't touch it */
++ return DECLINED;
++ }
/* If "h2" was selected as protocol (by whatever mechanism), take over
* the connection.
static int h2_h2_post_read_req(request_rec *r)
{
h2_ctx *ctx = h2_ctx_rget(r);
-- struct h2_task_env *env = h2_ctx_get_task(ctx);
-- if (env) {
++ struct h2_task *task = h2_ctx_get_task(ctx);
++ if (task) {
/* h2_task connection for a stream, not for h2c */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"adding h1_to_h2_resp output filter");
-- if (env->serialize_headers) {
++ if (task->serialize_headers) {
ap_remove_output_filter_byhandle(r->output_filters, "H1_TO_H2_RESP");
-- ap_add_output_filter("H1_TO_H2_RESP", env, r, r->connection);
++ ap_add_output_filter("H1_TO_H2_RESP", task, r, r->connection);
}
else {
/* replace the core http filter that formats response headers
* in HTTP/1 with our own that collects status and headers */
ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
ap_remove_output_filter_byhandle(r->output_filters, "H2_RESPONSE");
-- ap_add_output_filter("H2_RESPONSE", env, r, r->connection);
++ ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
}
}
return DECLINED;
*/
extern const char *H2_MAGIC_TOKEN;
++#define H2_ERR_NO_ERROR (0x00)
++#define H2_ERR_PROTOCOL_ERROR (0x01)
++#define H2_ERR_INTERNAL_ERROR (0x02)
++#define H2_ERR_FLOW_CONTROL_ERROR (0x03)
++#define H2_ERR_SETTINGS_TIMEOUT (0x04)
++#define H2_ERR_STREAM_CLOSED (0x05)
++#define H2_ERR_FRAME_SIZE_ERROR (0x06)
++#define H2_ERR_REFUSED_STREAM (0x07)
++#define H2_ERR_CANCEL (0x08)
++#define H2_ERR_COMPRESSION_ERROR (0x09)
++#define H2_ERR_CONNECT_ERROR (0x0a)
++#define H2_ERR_ENHANCE_YOUR_CALM (0x0b)
++#define H2_ERR_INADEQUATE_SECURITY (0x0c)
++#define H2_ERR_HTTP_1_1_REQUIRED (0x0d)
++
++/* Maximum number of padding bytes in a frame, rfc7540 */
++#define H2_MAX_PADLEN 256
++
++/**
++ * Provide a user readable description of the HTTP/2 error code-
++ * @param h2_error http/2 error code, as in rfc 7540, ch. 7
++ * @return textual description of code or that it is unknown.
++ */
++const char *h2_h2_err_description(int h2_error);
++
/*
* One time, post config intialization.
*/
*/
int h2_h2_is_tls(conn_rec *c);
--/* Disable SSL for this connection, can only be invoked in a pre-
-- * connection hook before mod_ssl.
-- * @return != 0 iff disable worked
-- */
--int h2_tls_disable(conn_rec *c);
--
/* Register apache hooks for h2 protocol
*/
void h2_h2_register_hooks(void);
++/**
++ * Check if the given connection fulfills the requirements as configured.
++ * @param c the connection
++ * @param require_all != 0 iff any missing connection properties make
++ * the test fail. For example, a cipher might not have been selected while
++ * the handshake is still ongoing.
++ * @return != 0 iff connection requirements are met
++ */
++int h2_is_acceptable_connection(conn_rec *c, int require_all);
++
++/**
++ * Check if the "direct" HTTP/2 mode of protocol handling is enabled
++ * for the given connection.
++ * @param c the connection to check
++ * @return != 0 iff direct mode is enabled
++ */
++int h2_allows_h2_direct(conn_rec *c);
++
++/**
++ * Check if the "Upgrade" HTTP/1.1 mode of protocol switching is enabled
++ * for the given connection.
++ * @param c the connection to check
++ * @return != 0 iff Upgrade switching is enabled
++ */
++int h2_allows_h2_upgrade(conn_rec *c);
++
#endif /* defined(__mod_h2__h2_h2__) */
#include "h2_private.h"
#include "h2_io.h"
#include "h2_response.h"
++#include "h2_task.h"
#include "h2_util.h"
h2_io *h2_io_create(int id, apr_pool_t *pool, apr_bucket_alloc_t *bucket_alloc)
static void h2_io_cleanup(h2_io *io)
{
-- (void)io;
++ if (io->task) {
++ h2_task_destroy(io->task);
++ io->task = NULL;
++ }
}
void h2_io_destroy(h2_io *io)
h2_io_cleanup(io);
}
++void h2_io_set_response(h2_io *io, h2_response *response)
++{
++ AP_DEBUG_ASSERT(response);
++ AP_DEBUG_ASSERT(!io->response);
++ io->response = h2_response_copy(io->pool, response);
++ if (response->rst_error) {
++ h2_io_rst(io, response->rst_error);
++ }
++}
++
++
+void h2_io_rst(h2_io *io, int error)
+{
+ io->rst_error = error;
+ io->eos_in = 1;
+}
+
int h2_io_in_has_eos_for(h2_io *io)
{
return io->eos_in || (io->bbin && h2_util_has_eos(io->bbin, 0));
apr_brigade_length(bb, 1, &start_len);
last = APR_BRIGADE_LAST(bb);
-- status = h2_util_move(bb, io->bbin, maxlen, 0,
-- "h2_io_in_read");
++ status = h2_util_move(bb, io->bbin, maxlen, NULL, "h2_io_in_read");
if (status == APR_SUCCESS) {
apr_bucket *nlast = APR_BRIGADE_LAST(bb);
apr_off_t end_len = 0;
io->bbin = apr_brigade_create(io->bbout->p,
io->bbout->bucket_alloc);
}
-- return h2_util_move(io->bbin, bb, 0, 0, "h2_io_in_write");
++ return h2_util_move(io->bbin, bb, 0, NULL, "h2_io_in_write");
}
return APR_SUCCESS;
}
h2_io_data_cb *cb, void *ctx,
apr_size_t *plen, int *peos)
{
+ apr_status_t status;
+
+ if (io->rst_error) {
+ return APR_ECONNABORTED;
+ }
+
+ if (io->eos_out) {
+ *plen = 0;
+ *peos = 1;
+ return APR_SUCCESS;
+ }
+
if (cb == NULL) {
/* just checking length available */
- return h2_util_bb_avail(io->bbout, plen, peos);
+ status = h2_util_bb_avail(io->bbout, plen, peos);
}
- return h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
+ else {
+ status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
+ if (status == APR_SUCCESS) {
+ io->eos_out = *peos;
+ }
+ }
+
+ return status;
+}
+
++apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos)
++{
++ if (io->rst_error) {
++ return APR_ECONNABORTED;
++ }
++
++ if (io->eos_out) {
++ *plen = 0;
++ *peos = 1;
++ return APR_SUCCESS;
++ }
++
++
++ io->eos_out = *peos = h2_util_has_eos(io->bbout, *plen);
++ return h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to");
+ }
+
apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb,
apr_size_t maxlen, int *pfile_handles_allowed)
{
struct h2_task;
--typedef apr_status_t h2_io_data_cb(void *ctx,
-- const char *data, apr_size_t len);
++typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_size_t len);
++
++typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx);
typedef struct h2_io h2_io;
apr_bucket_brigade *bbin; /* input data for stream */
int eos_in;
int task_done;
+ int rst_error;
++ int zombie;
++ struct h2_task *task; /* task created for this io */
++
apr_size_t input_consumed; /* how many bytes have been read */
struct apr_thread_cond_t *input_arrived; /* block on reading */
*/
void h2_io_destroy(h2_io *io);
++/**
++ * Set the response of this stream.
++ */
++void h2_io_set_response(h2_io *io, struct h2_response *response);
++
+/**
+ * Reset the stream with the given error code.
+ */
+void h2_io_rst(h2_io *io, int error);
+
/**
* The input data is completely queued. Blocked reads will return immediately
* and give either data or EOF.
h2_io_data_cb *cb, void *ctx,
apr_size_t *plen, int *peos);
++apr_status_t h2_io_out_read_to(h2_io *io,
++ apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos);
++
apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb,
apr_size_t maxlen, int *pfile_buckets_allowed);
{
AP_DEBUG_ASSERT(m);
m->aborted = 1;
-- if (m->q) {
-- h2_tq_destroy(m->q);
-- m->q = NULL;
-- }
if (m->ready_ios) {
h2_io_set_destroy(m->ready_ios);
m->ready_ios = NULL;
m->bucket_alloc = apr_bucket_alloc_create(m->pool);
-- m->q = h2_tq_create(m->id, m->pool);
++ m->q = h2_tq_create(m->pool, h2_config_geti(conf, H2_CONF_MAX_STREAMS));
m->stream_ios = h2_io_set_create(m->pool);
m->ready_ios = h2_io_set_create(m->pool);
-- m->closed = h2_stream_set_create(m->pool);
m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
m->workers = workers;
}
--h2_stream *h2_mplx_open_io(h2_mplx *m, int stream_id)
-{
- h2_stream *stream = NULL;
- apr_status_t status;
- h2_io *io;
-
- if (m->aborted) {
- return NULL;
- }
- status = apr_thread_mutex_lock(m->lock);
- if (APR_SUCCESS == status) {
- apr_pool_t *stream_pool = m->spare_pool;
-
- if (!stream_pool) {
- apr_pool_create(&stream_pool, m->pool);
- }
- else {
- m->spare_pool = NULL;
- }
-
- stream = h2_stream_create(stream_id, stream_pool, m);
- stream->state = H2_STREAM_ST_OPEN;
-
- io = h2_io_set_get(m->stream_ios, stream_id);
- if (!io) {
- io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
- h2_io_set_add(m->stream_ios, io);
- }
- status = io? APR_SUCCESS : APR_ENOMEM;
- apr_thread_mutex_unlock(m->lock);
- }
- return stream;
-}
-
-static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
++static void io_destroy(h2_mplx *m, h2_io *io)
{
- h2_stream *stream = NULL;
- apr_status_t status;
- h2_io *io;
-
- if (m->aborted) {
- return NULL;
- }
- status = apr_thread_mutex_lock(m->lock);
- if (APR_SUCCESS == status) {
- apr_pool_t *stream_pool = m->spare_pool;
-
- if (!stream_pool) {
- apr_pool_create(&stream_pool, m->pool);
- }
- else {
- m->spare_pool = NULL;
- }
-
- stream = h2_stream_create(stream_id, stream_pool, m);
- stream->state = H2_STREAM_ST_OPEN;
-
- io = h2_io_set_get(m->stream_ios, stream_id);
- if (!io) {
- io = h2_io_create(stream_id, stream_pool, m->bucket_alloc);
- h2_io_set_add(m->stream_ios, io);
- }
- status = io? APR_SUCCESS : APR_ENOMEM;
- apr_thread_mutex_unlock(m->lock);
- }
- return stream;
- }
-
- static void stream_destroy(h2_mplx *m, h2_stream *stream, h2_io *io)
- {
-- apr_pool_t *pool = h2_stream_detach_pool(stream);
-- if (pool) {
-- apr_pool_clear(pool);
-- if (m->spare_pool) {
-- apr_pool_destroy(m->spare_pool);
-- }
-- m->spare_pool = pool;
-- }
-- h2_stream_destroy(stream);
if (io) {
++ apr_pool_t *pool = io->pool;
++ if (pool) {
++ io->pool = NULL;
++ apr_pool_clear(pool);
++ if (m->spare_pool) {
++ apr_pool_destroy(m->spare_pool);
++ }
++ m->spare_pool = pool;
++ }
/* The pool is cleared/destroyed which also closes all
* allocated file handles. Give this count back to our
* file handle pool. */
}
}
--apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, h2_stream *stream)
++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error)
{
apr_status_t status;
++
AP_DEBUG_ASSERT(m);
++ if (m->aborted) {
++ return APR_ECONNABORTED;
++ }
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
-- h2_io *io = h2_io_set_get(m->stream_ios, stream->id);
- if (!io || io->task_done) {
- /* No more io or task already done -> cleanup immediately */
- stream_destroy(m, stream, io);
- }
- else {
- /* Add stream to closed set for cleanup when task is done */
- h2_stream_set_add(m->closed, stream);
++ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
++
+ if (io) {
+ /* Remove io from ready set, we will never submit it */
+ h2_io_set_remove(m->ready_ios, io);
- if (stream->rst_error) {
- /* Forward error code to fail any further attempt to
- * write to io */
- h2_io_rst(io, stream->rst_error);
++
++ if (io->task_done) {
++ io_destroy(m, io);
++ }
++ else {
++ /* cleanup once task is done */
++ io->zombie = 1;
++ if (rst_error) {
++ /* Forward error code to fail any further attempt to
++ * write to io */
++ h2_io_rst(io, rst_error);
++ }
+ }
}
- if (!io || io->task_done) {
- /* No more io or task already done -> cleanup immediately */
- stream_destroy(m, stream, io);
- }
- else {
- /* Add stream to closed set for cleanup when task is done */
- h2_stream_set_add(m->closed, stream);
- }
+
apr_thread_mutex_unlock(m->lock);
}
return status;
{
apr_status_t status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
-- h2_stream *stream = h2_stream_set_get(m->closed, stream_id);
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
"h2_mplx(%ld): task(%d) done", m->id, stream_id);
-- if (stream) {
-- /* stream was already closed by main connection and is in
-- * zombie state. Now that the task is done with it, we
-- * can free its resources. */
-- h2_stream_set_remove(m->closed, stream);
-- stream_destroy(m, stream, io);
-- }
-- else if (io) {
-- /* main connection has not finished stream. Mark task as done
-- * so that eventual cleanup can start immediately. */
++ if (io) {
io->task_done = 1;
++ if (io->zombie) {
++ io_destroy(m, io);
++ }
++ else {
++ /* hang around until the stream deregisteres */
++ }
}
apr_thread_mutex_unlock(m->lock);
}
if (APR_SUCCESS == status) {
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
if (io) {
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre");
+
status = h2_io_out_readx(io, cb, ctx, plen, peos);
+
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post");
+ if (status == APR_SUCCESS && cb && io->output_drained) {
+ apr_thread_cond_signal(io->output_drained);
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
++apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id,
++ apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos)
++{
++ apr_status_t status;
++ AP_DEBUG_ASSERT(m);
++ if (m->aborted) {
++ return APR_ECONNABORTED;
++ }
++ status = apr_thread_mutex_lock(m->lock);
++ if (APR_SUCCESS == status) {
++ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
++ if (io) {
++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre");
++
++ status = h2_io_out_read_to(io, bb, plen, peos);
++
++ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post");
+ if (status == APR_SUCCESS && io->output_drained) {
+ apr_thread_cond_signal(io->output_drained);
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+ }
+
h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams)
{
apr_status_t status;
}
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
- h2_io *io = h2_io_set_get_highest_prio(m->ready_ios);
+ h2_io *io = h2_io_set_pop_highest_prio(m->ready_ios);
if (io) {
- h2_response *response = io->response;
-
- AP_DEBUG_ASSERT(response);
- h2_io_set_remove(m->ready_ios, io);
-
- stream = h2_stream_set_get(streams, response->stream_id);
+ stream = h2_stream_set_get(streams, io->id);
if (stream) {
- h2_stream_set_response(stream, response, io->bbout);
- if (io->output_drained) {
- apr_thread_cond_signal(io->output_drained);
+ if (io->rst_error) {
+ h2_stream_rst(stream, io->rst_error);
+ }
+ else {
+ AP_DEBUG_ASSERT(io->response);
+ h2_stream_set_response(stream, io->response, io->bbout);
}
+
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c,
- APLOGNO(02953) "h2_mplx(%ld): stream for response %d",
- m->id, response->stream_id);
+ /* We have the io ready, but the stream has gone away, maybe
+ * reset by the client. Should no longer happen since such
+ * streams should clear io's from the ready queue.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c, APLOGNO(02953)
+ "h2_mplx(%ld): stream for response %d closed, "
+ "resetting io to close request processing",
+ m->id, io->id);
- h2_io_rst(io, NGHTTP2_ERR_STREAM_CLOSED);
++ h2_io_rst(io, H2_ERR_STREAM_CLOSED);
+ }
+
+ if (io->output_drained) {
+ apr_thread_cond_signal(io->output_drained);
}
}
apr_thread_mutex_unlock(m->lock);
if (io) {
if (f) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
-- "h2_mplx(%ld-%d): open response: %s",
-- m->id, stream_id, response->status);
++ "h2_mplx(%ld-%d): open response: %s, rst=%d",
++ m->id, stream_id, response->status,
++ response->rst_error);
}
-- io->response = h2_response_copy(io->pool, response);
-- AP_DEBUG_ASSERT(io->response);
++ h2_io_set_response(io, response);
h2_io_set_add(m->ready_ios, io);
if (bb) {
status = out_write(m, io, f, bb, iowait);
* insert an error one so that our streams can properly
* reset.
*/
-- h2_response *r = h2_response_create(stream_id,
++ h2_response *r = h2_response_create(stream_id, 0,
"500", NULL, m->pool);
status = out_open(m, stream_id, r, NULL, NULL, NULL);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c,
+ "h2_mplx(%ld-%d): close, no response, no rst",
+ m->id, io->id);
}
status = h2_io_out_close(io);
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_close");
+
have_out_data_for(m, stream_id);
if (m->aborted) {
/* if we were the last output, the whole session might
return status;
}
- if (m->aborted) {
- /* if we were the last output, the whole session might
- * have gone down in the meantime.
- */
- return APR_SUCCESS;
+apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error)
+{
+ apr_status_t status;
+ AP_DEBUG_ASSERT(m);
+ if (m->aborted) {
+ return APR_ECONNABORTED;
+ }
+ status = apr_thread_mutex_lock(m->lock);
+ if (APR_SUCCESS == status) {
+ if (!m->aborted) {
+ h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
+ if (io && !io->rst_error) {
+ h2_io_rst(io, error);
+ if (!io->response) {
+ h2_io_set_add(m->ready_ios, io);
+ }
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_rst");
+
+ have_out_data_for(m, stream_id);
++ if (io->output_drained) {
++ apr_thread_cond_signal(io->output_drained);
+ }
+ }
+ else {
+ status = APR_ECONNABORTED;
+ }
+ }
+ apr_thread_mutex_unlock(m->lock);
+ }
+ return status;
+}
+
int h2_mplx_in_has_eos_for(h2_mplx *m, int stream_id)
{
int has_eos = 0;
}
}
--apr_status_t h2_mplx_do_task(h2_mplx *m, struct h2_task *task)
++typedef struct {
++ h2_stream_pri_cmp *cmp;
++ void *ctx;
++} cmp_ctx;
++
++static int task_cmp(h2_task *t1, h2_task *t2, void *ctx)
++{
++ cmp_ctx *x = ctx;
++ return x->cmp(t1->stream_id, t2->stream_id, x->ctx);
++}
++
++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
{
apr_status_t status;
++
AP_DEBUG_ASSERT(m);
if (m->aborted) {
return APR_ECONNABORTED;
}
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
-- /* TODO: needs to sort queue by priority */
++ cmp_ctx x;
++
++ x.cmp = cmp;
++ x.ctx = ctx;
++ h2_tq_sort(m->q, task_cmp, &x);
++
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-- "h2_mplx: do task(%s)", task->id);
-- h2_tq_append(m->q, task);
++ "h2_mplx(%ld): reprioritize tasks", m->id);
apr_thread_mutex_unlock(m->lock);
}
workers_register(m);
return status;
}
--h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
++static h2_io *open_io(h2_mplx *m, int stream_id)
++{
++ apr_pool_t *io_pool = m->spare_pool;
++ h2_io *io;
++
++ if (!io_pool) {
++ apr_pool_create(&io_pool, m->pool);
++ }
++ else {
++ m->spare_pool = NULL;
++ }
++
++ io = h2_io_create(stream_id, io_pool, m->bucket_alloc);
++ h2_io_set_add(m->stream_ios, io);
++
++ return io;
++}
++
++
++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
++ struct h2_request *r, int eos,
++ h2_stream_pri_cmp *cmp, void *ctx)
{
-- h2_task *task = NULL;
apr_status_t status;
++
AP_DEBUG_ASSERT(m);
if (m->aborted) {
-- *has_more = 0;
-- return NULL;
++ return APR_ECONNABORTED;
}
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
-- task = h2_tq_pop_first(m->q);
-- if (task) {
-- h2_task_set_started(task);
++ conn_rec *c;
++ h2_io *io;
++ cmp_ctx x;
++
++ io = open_io(m, stream_id);
++ c = h2_conn_create(m->c, io->pool);
++ io->task = h2_task_create(m->id, stream_id, io->pool, m, c);
++
++ status = h2_request_end_headers(r, m, io->task, eos);
++ if (status == APR_SUCCESS && eos) {
++ status = h2_io_in_close(io);
}
-- *has_more = !h2_tq_empty(m->q);
++
++ if (status == APR_SUCCESS) {
++ x.cmp = cmp;
++ x.ctx = ctx;
++ h2_tq_add(m->q, io->task, task_cmp, &x);
++ }
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
++ "h2_mplx(%ld-%d): process", m->c->id, stream_id);
apr_thread_mutex_unlock(m->lock);
}
-- return task;
++
++ if (status == APR_SUCCESS) {
++ workers_register(m);
++ }
++ return status;
}
--apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream)
++h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
{
++ h2_task *task = NULL;
apr_status_t status;
AP_DEBUG_ASSERT(m);
if (m->aborted) {
-- return APR_ECONNABORTED;
++ *has_more = 0;
++ return NULL;
}
status = apr_thread_mutex_lock(m->lock);
if (APR_SUCCESS == status) {
-- conn_rec *c = h2_conn_create(m->c, stream->pool);
-- stream->task = h2_task_create(m->id, stream->id,
-- stream->pool, m, c);
--
++ task = h2_tq_shift(m->q);
++ *has_more = !h2_tq_empty(m->q);
apr_thread_mutex_unlock(m->lock);
}
-- return status;
++ return task;
}
struct h2_response;
struct h2_task;
struct h2_stream;
++struct h2_request;
struct h2_io_set;
struct apr_thread_cond_t;
struct h2_workers;
int aborted;
apr_size_t stream_max_mem;
-- apr_pool_t *spare_pool; /* spare pool, ready for next stream */
-- struct h2_stream_set *closed; /* streams closed, but task ongoing */
++ apr_pool_t *spare_pool; /* spare pool, ready for next io */
struct h2_workers *workers;
int file_handles_allowed;
};
++
++
/*******************************************************************************
* Object lifecycle and information.
******************************************************************************/
/*******************************************************************************
* IO lifetime of streams.
******************************************************************************/
--/**
-- * Prepares the multiplexer to handle in-/output on the given stream id.
-- */
--struct h2_stream *h2_mplx_open_io(h2_mplx *mplx, int stream_id);
/**
-- * Ends cleanup of a stream in sync with execution thread.
++ * Notifies mplx that a stream has finished processing.
++ *
++ * @param m the mplx itself
++ * @param stream_id the id of the stream being done
++ * @param rst_error if != 0, the stream was reset with the error given
++ *
*/
--apr_status_t h2_mplx_cleanup_stream(h2_mplx *m, struct h2_stream *stream);
++apr_status_t h2_mplx_stream_done(h2_mplx *m, int stream_id, int rst_error);
/* Return != 0 iff the multiplexer has data for the given stream.
*/
******************************************************************************/
/**
-- * Perform the task on the given stream.
++ * Process a stream request.
++ *
++ * @param m the multiplexer
++ * @param stream_id the identifier of the stream
++ * @param r the request to be processed
++ * @param eos if input is complete
++ * @param cmp the stream priority compare function
++ * @param ctx context data for the compare function
*/
--apr_status_t h2_mplx_do_task(h2_mplx *mplx, struct h2_task *task);
++apr_status_t h2_mplx_process(h2_mplx *m, int stream_id,
++ struct h2_request *r, int eos,
++ h2_stream_pri_cmp *cmp, void *ctx);
--struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
++/**
++ * Stream priorities have changed, reschedule pending tasks.
++ *
++ * @param m the multiplexer
++ * @param cmp the stream priority compare function
++ * @param ctx context data for the compare function
++ */
++apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
--apr_status_t h2_mplx_create_task(h2_mplx *mplx, struct h2_stream *stream);
++struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
/*******************************************************************************
* Input handling of streams.
apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id,
h2_io_data_cb *cb, void *ctx,
apr_size_t *plen, int *peos);
+
++/**
++ * Reads output data into the given brigade. Will never block, but
++ * return APR_EAGAIN until data arrives or the stream is closed.
++ */
++apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id,
++ apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos);
+
/**
* Opens the output for the given stream with the specified response.
*/
apr_status_t h2_request_end_headers(h2_request *req, struct h2_mplx *m,
h2_task *task, int eos)
{
++ apr_status_t status;
++
if (!req->to_h1) {
-- apr_status_t status = insert_request_line(req, m);
++ status = insert_request_line(req, m);
if (status != APR_SUCCESS) {
return status;
}
}
-- return h2_to_h1_end_headers(req->to_h1, task, eos);
++ status = h2_to_h1_end_headers(req->to_h1, eos);
++ h2_task_set_request(task, req->to_h1->method,
++ req->to_h1->scheme,
++ req->to_h1->authority,
++ req->to_h1->path,
++ req->to_h1->headers, eos);
++ return status;
}
apr_status_t h2_request_close(h2_request *req)
#include <nghttp2/nghttp2.h>
#include "h2_private.h"
++#include "h2_h2.h"
#include "h2_util.h"
#include "h2_response.h"
}
h2_response *h2_response_create(int stream_id,
++ int rst_error,
const char *http_status,
apr_array_header_t *hlines,
apr_pool_t *pool)
}
response->stream_id = stream_id;
-- response->status = http_status;
++ response->rst_error = rst_error;
++ response->status = http_status? http_status : "500";
response->content_length = -1;
if (hlines) {
response->status = apr_psprintf(pool, "%d", r->status);
response->content_length = -1;
response->rheader = header;
++
++ if (r->status == HTTP_FORBIDDEN) {
++ const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
++ if (cause) {
++ /* This request triggered a TLS renegotiation that is now allowed
++ * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
++ */
++ ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r,
++ "h2_response(%ld-%d): renegotiate forbidden, cause: %s",
++ (long)r->connection->id, stream_id, cause);
++ response->rst_error = H2_ERR_HTTP_1_1_REQUIRED;
++ }
++ }
return response;
}
typedef struct h2_response {
int stream_id;
++ int rst_error;
const char *status;
apr_off_t content_length;
apr_table_t *rheader;
} h2_response;
h2_response *h2_response_create(int stream_id,
-- const char *http_status,
-- apr_array_header_t *hlines,
-- apr_pool_t *pool);
++ int rst_error,
++ const char *http_status,
++ apr_array_header_t *hlines,
++ apr_pool_t *pool);
h2_response *h2_response_rcreate(int stream_id, request_rec *r,
apr_table_t *header, apr_pool_t *pool);
#include <http_log.h>
#include "h2_private.h"
++#include "h2_bucket_eoc.h"
++#include "h2_bucket_eos.h"
#include "h2_config.h"
#include "h2_h2.h"
#include "h2_mplx.h"
static int stream_open(h2_session *session, int stream_id)
{
h2_stream * stream;
++ apr_pool_t *stream_pool;
if (session->aborted) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
-- stream = h2_mplx_open_io(session->mplx, stream_id);
-- if (stream) {
-- h2_stream_set_add(session->streams, stream);
-- if (stream->id > session->max_stream_received) {
-- session->max_stream_received = stream->id;
-- }
--
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-- "h2_session: stream(%ld-%d): opened",
-- session->id, stream_id);
--
-- return 0;
++ if (session->spare) {
++ stream_pool = session->spare;
++ session->spare = NULL;
++ }
++ else {
++ apr_pool_create(&stream_pool, session->pool);
++ }
++
++ stream = h2_stream_create(stream_id, stream_pool, session);
++ stream->state = H2_STREAM_ST_OPEN;
++
++ h2_stream_set_add(session->streams, stream);
++ if (stream->id > session->max_stream_received) {
++ session->max_stream_received = stream->id;
}
-- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
-- APLOGNO(02918)
-- "h2_session: stream(%ld-%d): unable to create",
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++ "h2_session: stream(%ld-%d): opened",
session->id, stream_id);
-- return NGHTTP2_ERR_INVALID_STREAM_ID;
++
++ return 0;
++}
++
++/**
++ * Determine the importance of streams when scheduling tasks.
++ * - if both stream depend on the same one, compare weights
++ * - if one stream is closer to the root, prioritize that one
++ * - if both are on the same level, use the weight of their root
++ * level ancestors
++ */
++static int spri_cmp(int sid1, nghttp2_stream *s1,
++ int sid2, nghttp2_stream *s2, h2_session *session)
++{
++ nghttp2_stream *p1, *p2;
++
++ p1 = nghttp2_stream_get_parent(s1);
++ p2 = nghttp2_stream_get_parent(s2);
++
++ if (p1 == p2) {
++ int32_t w1, w2;
++
++ w1 = nghttp2_stream_get_weight(s1);
++ w2 = nghttp2_stream_get_weight(s2);
++ return w2 - w1;
++ }
++ else if (!p1) {
++ /* stream 1 closer to root */
++ return -1;
++ }
++ else if (!p2) {
++ /* stream 2 closer to root */
++ return 1;
++ }
++ return spri_cmp(sid1, p1, sid2, p2, session);
++}
++
++static int stream_pri_cmp(int sid1, int sid2, void *ctx)
++{
++ h2_session *session = ctx;
++ nghttp2_stream *s1, *s2;
++
++ s1 = nghttp2_session_find_stream(session->ngh2, sid1);
++ s2 = nghttp2_session_find_stream(session->ngh2, sid2);
++
++ if (s1 == s2) {
++ return 0;
++ }
++ else if (!s1) {
++ return 1;
++ }
++ else if (!s2) {
++ return -1;
++ }
++ return spri_cmp(sid1, s1, sid2, s2, session);
}
static apr_status_t stream_end_headers(h2_session *session,
h2_stream *stream, int eos)
{
(void)session;
-- return h2_stream_write_eoh(stream, eos);
++ return h2_stream_schedule(stream, eos, stream_pri_cmp, session);
}
--static apr_status_t send_data(h2_session *session, const char *data,
-- apr_size_t length);
--
/*
* Callback when nghttp2 wants to send bytes back to the client.
*/
int flags, void *userp)
{
h2_session *session = (h2_session *)userp;
-- apr_status_t status = send_data(session, (const char *)data, length);
++ apr_status_t status;
(void)ngh2;
(void)flags;
++ status = h2_conn_io_write(&session->io, (const char *)data, length);
if (status == APR_SUCCESS) {
return length;
}
session->id, stream_id, (int)len);
if (status != APR_SUCCESS) {
rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
- H2_STREAM_RST(stream, NGHTTP2_INTERNAL_ERROR));
- NGHTTP2_INTERNAL_ERROR);
++ H2_STREAM_RST(stream, H2_ERR_INTERNAL_ERROR));
if (nghttp2_is_fatal(rv)) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if (session->aborted) {
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
++ /* Set the need to flush output when we have added one of the
++ * following frame types */
++ switch (frame->hd.type) {
++ case NGHTTP2_RST_STREAM:
++ case NGHTTP2_WINDOW_UPDATE:
++ case NGHTTP2_PUSH_PROMISE:
++ case NGHTTP2_PING:
++ case NGHTTP2_GOAWAY:
++ session->flush = 1;
++ break;
++ default:
++ break;
++
++ }
if (APLOGctrace2(session->c)) {
char buffer[256];
frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
void *userp)
{
h2_session *session = (h2_session *)userp;
-- (void)ngh2; (void)frame;
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-- "h2_session(%ld): on_frame_send", session->id);
++ (void)ngh2;
++ if (APLOGctrace2(session->c)) {
++ char buffer[256];
++ frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++ "h2_session(%ld): on_frame_send %s",
++ session->id, buffer);
++ }
return 0;
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
"h2_stream(%ld-%d): closing with err=%d %s",
session->id, (int)stream->id, (int)error_code,
-- nghttp2_strerror(error_code));
++ h2_h2_err_description(error_code));
+ h2_stream_rst(stream, error_code);
}
-- h2_stream_set_remove(session->streams, stream);
-- return h2_mplx_cleanup_stream(session->mplx, stream);
++ return h2_conn_io_writeb(&session->io,
++ h2_bucket_eos_create(session->c->bucket_alloc,
++ stream));
}
static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
break;
}
case NGHTTP2_PRIORITY: {
++ session->reprioritize = 1;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
"h2_session: stream(%ld-%d): PRIORITY frame "
" weight=%d, dependsOn=%d, exclusive=%d",
return 0;
}
--static apr_status_t send_data(h2_session *session, const char *data,
-- apr_size_t length)
--{
-- return h2_conn_io_write(&session->io, data, length);
--}
--
static apr_status_t pass_data(void *ctx,
const char *data, apr_size_t length)
{
-- return send_data((h2_session*)ctx, data, length);
++ return h2_conn_io_write(&((h2_session*)ctx)->io, data, length);
}
++
++static char immortal_zeros[H2_MAX_PADLEN];
++
static int on_send_data_cb(nghttp2_session *ngh2,
nghttp2_frame *frame,
const uint8_t *framehd,
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
-- status = send_data(session, (const char *)framehd, 9);
-- if (status == APR_SUCCESS) {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
++ "h2_stream(%ld-%d): send_data_cb for %ld bytes",
++ session->id, (int)stream_id, (long)length);
++
++ if (h2_conn_io_is_buffered(&session->io)) {
++ status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
++ if (status == APR_SUCCESS) {
++ if (padlen) {
++ status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
++ }
++
++ if (status == APR_SUCCESS) {
++ apr_size_t len = length;
++ status = h2_stream_readx(stream, pass_data, session,
++ &len, &eos);
++ if (status == APR_SUCCESS && len != length) {
++ status = APR_EINVAL;
++ }
++ }
++
++ if (status == APR_SUCCESS && padlen) {
++ if (padlen) {
++ status = h2_conn_io_write(&session->io, immortal_zeros, padlen);
++ }
++ }
++ }
++ }
++ else {
++ apr_bucket *b;
++ char *header = apr_pcalloc(stream->pool, 10);
++ memcpy(header, (const char *)framehd, 9);
if (padlen) {
-- status = send_data(session, (const char *)&padlen, 1);
++ header[9] = (char)padlen;
}
--
++ b = apr_bucket_pool_create(header, padlen? 10 : 9,
++ stream->pool, session->c->bucket_alloc);
++ status = h2_conn_io_writeb(&session->io, b);
++
if (status == APR_SUCCESS) {
apr_size_t len = length;
-- status = h2_stream_readx(stream, pass_data, session,
-- &len, &eos);
++ status = h2_stream_read_to(stream, session->io.output, &len, &eos);
++ session->io.unflushed = 1;
if (status == APR_SUCCESS && len != length) {
status = APR_EINVAL;
}
}
--
++
if (status == APR_SUCCESS && padlen) {
-- if (padlen) {
-- char pad[256];
-- memset(pad, 0, padlen);
-- status = send_data(session, pad, padlen);
-- }
++ b = apr_bucket_immortal_create(immortal_zeros, padlen,
++ session->c->bucket_alloc);
++ status = h2_conn_io_writeb(&session->io, b);
}
}
++
if (status == APR_SUCCESS) {
++ stream->data_frames_sent++;
++ h2_conn_io_consider_flush(&session->io);
return 0;
}
- else if (status != APR_EOF) {
- ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ else {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
APLOGNO(02925)
"h2_stream(%ld-%d): failed send_data_cb",
session->id, (int)stream_id);
return h2_session_status_from_apr_status(status);
}
++static ssize_t on_data_source_read_length_cb(nghttp2_session *session,
++ uint8_t frame_type, int32_t stream_id,
++ int32_t session_remote_window_size,
++ int32_t stream_remote_window_size,
++ uint32_t remote_max_frame_size,
++ void *user_data)
++{
++ /* DATA frames add 9 bytes header plus 1 byte for padlen and additional
++ * padlen bytes. Keep below TLS maximum record size.
++ * TODO: respect pad bytes when we have that feature.
++ */
++ return (16*1024 - 10);
++}
#define NGH2_SET_CALLBACK(callbacks, name, fn)\
nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
NGH2_SET_CALLBACK(*pcb, on_begin_headers, on_begin_headers_cb);
NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
++ NGH2_SET_CALLBACK(*pcb, data_source_read_length, on_data_source_read_length_cb);
return APR_SUCCESS;
}
nghttp2_session_del(session->ngh2);
session->ngh2 = NULL;
}
-- h2_conn_io_destroy(&session->io);
--
-- if (session->iowait) {
-- apr_thread_cond_destroy(session->iowait);
-- session->iowait = NULL;
-- }
--
++}
++
++void h2_session_cleanup(h2_session *session)
++{
++ h2_session_destroy(session);
if (session->pool) {
apr_pool_destroy(session->pool);
}
AP_DEBUG_ASSERT(session);
if (!session->aborted) {
session->aborted = 1;
-- if (session->ngh2) {
--
-- if (!reason) {
++
++ if (session->ngh2) {
++ if (NGHTTP2_ERR_EOF == reason) {
++ /* This is our way of indication that the connection is
++ * gone. No use to send any GOAWAY frames. */
++ nghttp2_session_terminate_session(session->ngh2, reason);
++ }
++ else if (!reason) {
nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
session->max_stream_received,
reason, NULL, 0);
nghttp2_session_send(session->ngh2);
-- h2_conn_io_flush(&session->io);
}
else {
const char *err = nghttp2_strerror(reason);
"session(%ld): aborting session, reason=%d %s",
session->id, reason, err);
-- if (NGHTTP2_ERR_EOF == reason) {
-- /* This is our way of indication that the connection is
-- * gone. No use to send any GOAWAY frames. */
-- nghttp2_session_terminate_session(session->ngh2, reason);
-- }
-- else {
-- /* The connection might still be there and we shut down
-- * with GOAWAY and reason information. */
-- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
-- session->max_stream_received,
-- reason, (const uint8_t *)err,
-- strlen(err));
-- nghttp2_session_send(session->ngh2);
-- h2_conn_io_flush(&session->io);
-- }
++ /* The connection might still be there and we shut down
++ * with GOAWAY and reason information. */
++ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
++ session->max_stream_received,
++ reason, (const uint8_t *)err,
++ strlen(err));
++ nghttp2_session_send(session->ngh2);
}
++ h2_conn_io_flush(&session->io);
}
h2_mplx_abort(session->mplx);
}
AP_DEBUG_ASSERT(stream);
if (h2_stream_is_suspended(stream)) {
-- if (h2_mplx_out_has_data_for(stream->m, stream->id)) {
++ if (h2_mplx_out_has_data_for(stream->session->mplx, stream->id)) {
int rv;
h2_stream_set_suspended(stream, 0);
++rctx->resume_count;
nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
}
++static apr_status_t h2_session_flush(h2_session *session)
++{
++ session->flush = 0;
++ return h2_conn_io_flush(&session->io);
++}
++
static apr_status_t h2_session_update_windows(h2_session *session)
{
return h2_mplx_in_update_windows(session->mplx, update_window, session);
{
apr_status_t status = APR_EAGAIN;
h2_stream *stream = NULL;
-- int flush_output = 0;
AP_DEBUG_ASSERT(session);
++ if (session->reprioritize) {
++ h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
++ session->reprioritize = 0;
++ }
++
/* Check that any pending window updates are sent. */
status = h2_session_update_windows(session);
-- if (status == APR_SUCCESS) {
-- flush_output = 1;
-- }
- else if (!APR_STATUS_IS_EAGAIN(status)) {
- else if (status != APR_EAGAIN) {
++ if (status != APR_SUCCESS && !APR_STATUS_IS_EAGAIN(status)) {
return status;
}
status = APR_ECONNABORTED;
}
}
-- flush_output = 1;
}
/* If we have responses ready, submit them now. */
- while ((stream = h2_mplx_next_submit(session->mplx,
- session->streams)) != NULL) {
+ while (!session->aborted
+ && (stream = h2_mplx_next_submit(session->mplx, session->streams)) != NULL) {
status = h2_session_handle_response(session, stream);
-- flush_output = 1;
}
- if (h2_session_resume_streams_with_data(session) > 0) {
- flush_output = 1;
+ if (!session->aborted && h2_session_resume_streams_with_data(session) > 0) {
- flush_output = 1;
}
- if (!session->aborted && !flush_output
- && timeout > 0 && !h2_session_want_write(session)) {
- if (!flush_output && timeout > 0 && !h2_session_want_write(session)) {
++ if (!session->aborted && !session->flush && timeout > 0
++ && !h2_session_want_write(session)) {
++ h2_session_flush(session);
status = h2_mplx_out_trywait(session->mplx, timeout, session->iowait);
if (status != APR_TIMEUP
&& h2_session_resume_streams_with_data(session) > 0) {
-- flush_output = 1;
}
else {
/* nothing happened to ongoing streams, do some house-keeping */
status = APR_ECONNABORTED;
}
}
-- flush_output = 1;
}
-- if (flush_output) {
-- h2_conn_io_flush(&session->io);
++ if (session->flush) {
++ h2_session_flush(session);
}
return status;
apr_status_t h2_session_read(h2_session *session, apr_read_type_e block)
{
AP_DEBUG_ASSERT(session);
++ if (block == APR_BLOCK_READ) {
++ /* before we do a blocking read, make sure that all our output
++ * is send out. Otherwise we might deadlock. */
++ h2_session_flush(session);
++ }
return h2_conn_io_read(&session->io, block, session_receive, session);
}
apr_status_t h2_session_close(h2_session *session)
{
AP_DEBUG_ASSERT(session);
-- return session->aborted? APR_SUCCESS : h2_conn_io_flush(&session->io);
++ if (!session->aborted) {
++ h2_session_abort_int(session, 0);
++ }
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0,session->c,
++ "h2_session: closing, writing eoc");
++ h2_conn_io_writeb(&session->io,
++ h2_bucket_eoc_create(session->c->bucket_alloc,
++ session));
++ return h2_conn_io_flush(&session->io);
}
--/* The session wants to send more DATA for the given stream.
-- */
static ssize_t stream_data_cb(nghttp2_session *ng2s,
int32_t stream_id,
uint8_t *buf,
h2_stream *stream;
AP_DEBUG_ASSERT(session);
++ /* The session wants to send more DATA for the stream. We need
++ * to find out how much of the requested length we can send without
++ * blocking.
++ * Indicate EOS when we encounter it or DEFERRED if the stream
++ * should be suspended.
++ * TODO: for handling of TRAILERS, the EOF indication needs
++ * to be aware of that.
++ */
++
(void)ng2s;
(void)buf;
(void)source;
stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) {
-- ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
++ ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02937)
"h2_stream(%ld-%d): data requested but stream not found",
session->id, (int)stream_id);
}
else {
rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
- stream->id, NGHTTP2_PROTOCOL_ERROR);
+ stream->id,
- H2_STREAM_RST(stream, NGHTTP2_PROTOCOL_ERROR));
++ H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR));
}
if (nghttp2_is_fatal(rv)) {
return status;
}
++apr_status_t h2_session_stream_destroy(h2_session *session, h2_stream *stream)
++{
++ apr_pool_t *pool = h2_stream_detach_pool(stream);
++
++ h2_mplx_stream_done(session->mplx, stream->id, stream->rst_error);
++ h2_stream_set_remove(session->streams, stream->id);
++ h2_stream_destroy(stream);
++
++ if (pool) {
++ apr_pool_clear(pool);
++ if (session->spare) {
++ apr_pool_destroy(session->spare);
++ }
++ session->spare = pool;
++ }
++ return APR_SUCCESS;
++}
++
int h2_session_is_done(h2_session *session)
{
AP_DEBUG_ASSERT(session);
request_rec *r; /* the request that started this in case
* of 'h2c', NULL otherwise */
int aborted; /* this session is being aborted */
++ int flush; /* if != 0, flush output on next occasion */
++ int reprioritize; /* scheduled streams priority needs to
++ * be re-evaluated */
apr_size_t frames_received; /* number of http/2 frames received */
apr_size_t max_stream_count; /* max number of open streams */
apr_size_t max_stream_mem; /* max buffer memory for a single stream */
int max_stream_received; /* highest stream id created */
int max_stream_handled; /* highest stream id handled successfully */
++ apr_pool_t *spare; /* spare stream pool */
++
struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
struct h2_workers *workers; /* for executing stream tasks */
};
*/
void h2_session_destroy(h2_session *session);
++/**
++ * Cleanup the session and all objects it still contains. This will not
++ * destroy h2_task instances that have not finished yet.
++ * @param session the session to destroy
++ */
++void h2_session_cleanup(h2_session *session);
++
/**
* Called once at start of session.
* Sets up the session and sends the initial SETTINGS frame.
* a maximum of timeout micro-seconds and return to the caller. If timeout
* occurred, APR_TIMEUP will be returned.
*/
--apr_status_t h2_session_write(h2_session *session,
-- apr_interval_time_t timeout);
++apr_status_t h2_session_write(h2_session *session, apr_interval_time_t timeout);
/* Start submitting the response to a stream request. This is possible
* once we have all the response headers. */
/* Get the h2_stream for the given stream idenrtifier. */
struct h2_stream *h2_session_get_stream(h2_session *session, int stream_id);
-void h2_session_log_stats(h2_session *session);
++/**
++ * Destroy the stream and release it everywhere. Reclaim all resources.
++ * @param session the session to which the stream belongs
++ * @param stream the stream to destroy
++ */
++apr_status_t h2_session_stream_destroy(h2_session *session,
++ struct h2_stream *stream);
+
#endif /* defined(__mod_h2__h2_session__) */
#include <assert.h>
#include <stddef.h>
--#define APR_POOL_DEBUG 7
--
#include <httpd.h>
#include <http_core.h>
#include <http_connection.h>
#include "h2_mplx.h"
#include "h2_request.h"
#include "h2_response.h"
++#include "h2_session.h"
#include "h2_stream.h"
#include "h2_task.h"
#include "h2_ctx.h"
}
}
--h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m)
++h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session)
{
h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
if (stream != NULL) {
stream->id = id;
stream->state = H2_STREAM_ST_IDLE;
stream->pool = pool;
-- stream->m = m;
-- stream->request = h2_request_create(id, pool, m->c->bucket_alloc);
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
-- "h2_stream(%ld-%d): created", m->id, stream->id);
++ stream->session = session;
++ stream->bbout = apr_brigade_create(stream->pool,
++ stream->session->c->bucket_alloc);
++ stream->request = h2_request_create(id, pool, session->c->bucket_alloc);
++
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
++ "h2_stream(%ld-%d): created", session->id, stream->id);
}
return stream;
}
--static void h2_stream_cleanup(h2_stream *stream)
++apr_status_t h2_stream_destroy(h2_stream *stream)
{
++ AP_DEBUG_ASSERT(stream);
++
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
++ "h2_stream(%ld-%d): destroy", stream->session->id, stream->id);
if (stream->request) {
h2_request_destroy(stream->request);
stream->request = NULL;
}
--}
--
--apr_status_t h2_stream_destroy(h2_stream *stream)
--{
-- AP_DEBUG_ASSERT(stream);
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
-- "h2_stream(%ld-%d): destroy", stream->m->id, stream->id);
-- h2_stream_cleanup(stream);
-- if (stream->task) {
-- h2_task_destroy(stream->task);
-- stream->task = NULL;
-- }
if (stream->pool) {
apr_pool_destroy(stream->pool);
}
return APR_SUCCESS;
}
-void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool)
++void h2_stream_cleanup(h2_stream *stream)
+ {
- stream->pool = pool;
++ h2_session_stream_destroy(stream->session, stream);
++ /* stream is gone */
+ }
+
apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
{
apr_pool_t *pool = stream->pool;
return pool;
}
-void h2_stream_abort(h2_stream *stream)
+void h2_stream_rst(h2_stream *stream, int error_code)
{
- AP_DEBUG_ASSERT(stream);
- stream->aborted = 1;
+ stream->rst_error = error_code;
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
+ "h2_stream(%ld-%d): reset, error=%d",
- stream->m->id, stream->id, error_code);
++ stream->session->id, stream->id, error_code);
}
apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
apr_bucket_brigade *bb)
{
+ apr_status_t status = APR_SUCCESS;
+
stream->response = response;
if (bb && !APR_BRIGADE_EMPTY(bb)) {
-- if (!stream->bbout) {
-- stream->bbout = apr_brigade_create(stream->pool,
-- stream->m->c->bucket_alloc);
-- }
- status = h2_util_move(stream->bbout, bb, 16 * 1024, NULL,
- return h2_util_move(stream->bbout, bb, 16 * 1024, NULL,
- "h2_stream_set_response");
++ int move_all = INT_MAX;
++ /* we can move file handles from h2_mplx into this h2_stream as many
++ * as we want, since the lifetimes are the same and we are not freeing
++ * the ones in h2_mplx->io before this stream is done. */
++ status = h2_util_move(stream->bbout, bb, 16 * 1024, &move_all,
+ "h2_stream_set_response");
}
- if (APLOGctrace1(stream->m->c)) {
- return APR_SUCCESS;
++ if (APLOGctrace1(stream->session->c)) {
+ apr_size_t len = 0;
+ int eos = 0;
- if (stream->bbout) {
- h2_util_bb_avail(stream->bbout, &len, &eos);
- }
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c,
- "h2_stream(%ld-%d): set_response(%s), brigade=%s, "
- "len=%ld, eos=%d",
- stream->m->id, stream->id, response->status,
- (stream->bbout? "yes" : "no"), (long)len, (int)eos);
++ h2_util_bb_avail(stream->bbout, &len, &eos);
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
++ "h2_stream(%ld-%d): set_response(%s), len=%ld, eos=%d",
++ stream->session->id, stream->id, response->status,
++ (long)len, (int)eos);
+ }
+ return status;
}
static int set_closed(h2_stream *stream)
{
apr_status_t status;
AP_DEBUG_ASSERT(stream);
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
set_state(stream, H2_STREAM_ST_OPEN);
-- status = h2_request_rwrite(stream->request, r, stream->m);
++ status = h2_request_rwrite(stream->request, r, stream->session->mplx);
return status;
}
--apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos)
++apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
++ h2_stream_pri_cmp *cmp, void *ctx)
{
apr_status_t status;
AP_DEBUG_ASSERT(stream);
/* Seeing the end-of-headers, we have everything we need to
* start processing it.
*/
-- status = h2_mplx_create_task(stream->m, stream);
-- if (status == APR_SUCCESS) {
-- status = h2_request_end_headers(stream->request,
-- stream->m, stream->task, eos);
-- if (status == APR_SUCCESS) {
-- status = h2_mplx_do_task(stream->m, stream->task);
-- }
-- if (eos) {
-- status = h2_stream_write_eos(stream);
-- }
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->m->c,
-- "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
-- stream->m->id, stream->id,
-- stream->request->method, stream->request->path,
-- stream->request->authority);
--
++ status = h2_mplx_process(stream->session->mplx, stream->id,
++ stream->request, eos, cmp, ctx);
++ if (eos) {
++ set_closed(stream);
}
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, stream->session->c,
++ "h2_mplx(%ld-%d): start stream, task %s %s (%s)",
++ stream->session->id, stream->id,
++ stream->request->method, stream->request->path,
++ stream->request->authority);
++
return status;
}
apr_status_t h2_stream_write_eos(h2_stream *stream)
{
AP_DEBUG_ASSERT(stream);
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->m->c,
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, stream->session->c,
"h2_stream(%ld-%d): closing input",
-- stream->m->id, stream->id);
++ stream->session->id, stream->id);
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
if (set_closed(stream)) {
return h2_request_close(stream->request);
}
return APR_EINVAL;
}
return h2_request_write_header(stream->request, name, nlen,
-- value, vlen, stream->m);
++ value, vlen, stream->session->mplx);
}
apr_status_t h2_stream_write_data(h2_stream *stream,
apr_status_t status = APR_SUCCESS;
const char *src;
- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
++
++ if (!APR_BRIGADE_EMPTY(stream->bbout)) {
src = "stream";
status = h2_util_bb_avail(stream->bbout, plen, peos);
if (status == APR_SUCCESS && !*peos && !*plen) {
}
else {
src = "mplx";
-- status = h2_mplx_out_readx(stream->m, stream->id,
++ status = h2_mplx_out_readx(stream->session->mplx, stream->id,
NULL, NULL, plen, peos);
}
if (status == APR_SUCCESS && !*peos && !*plen) {
status = APR_EAGAIN;
}
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->m->c,
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
"h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d",
-- stream->m->id, stream->id,
-- src, (long)*plen, *peos);
++ stream->session->id, stream->id, src, (long)*plen, *peos);
return status;
}
h2_io_data_cb *cb, void *ctx,
apr_size_t *plen, int *peos)
{
- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
- return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++ apr_status_t status = APR_SUCCESS;
++ const char *src;
++
+ if (stream->rst_error) {
+ return APR_ECONNRESET;
+ }
- if (stream->bbout && !APR_BRIGADE_EMPTY(stream->bbout)) {
- return h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++ *peos = 0;
++ if (!APR_BRIGADE_EMPTY(stream->bbout)) {
++ apr_size_t origlen = *plen;
++
++ src = "stream";
++ status = h2_util_bb_readx(stream->bbout, cb, ctx, plen, peos);
++ if (status == APR_SUCCESS && !*peos && !*plen) {
++ apr_brigade_cleanup(stream->bbout);
++ *plen = origlen;
++ return h2_stream_readx(stream, cb, ctx, plen, peos);
++ }
++ }
++ else {
++ src = "mplx";
++ status = h2_mplx_out_readx(stream->session->mplx, stream->id,
++ cb, ctx, plen, peos);
++ }
++
++ if (status == APR_SUCCESS && !*peos && !*plen) {
++ status = APR_EAGAIN;
}
-- return h2_mplx_out_readx(stream->m, stream->id,
-- cb, ctx, plen, peos);
++
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
++ "h2_stream(%ld-%d): readx %s, len=%ld eos=%d",
++ stream->session->id, stream->id, src, (long)*plen, *peos);
++ return status;
}
++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos)
++{
++ apr_status_t status = APR_SUCCESS;
++
++ if (stream->rst_error) {
++ return APR_ECONNRESET;
++ }
++
++ if (APR_BRIGADE_EMPTY(stream->bbout)) {
++ apr_size_t tlen = *plen;
++ int eos;
++ status = h2_mplx_out_read_to(stream->session->mplx, stream->id,
++ stream->bbout, &tlen, &eos);
++ }
++
++ if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->bbout)) {
++ status = h2_transfer_brigade(bb, stream->bbout, stream->pool,
++ plen, peos);
++ }
++ else {
++ *plen = 0;
++ *peos = 0;
++ }
++
++ if (status == APR_SUCCESS && !*peos && !*plen) {
++ status = APR_EAGAIN;
++ }
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
++ "h2_stream(%ld-%d): read_to, len=%ld eos=%d",
++ stream->session->id, stream->id, (long)*plen, *peos);
++ return status;
++}
void h2_stream_set_suspended(h2_stream *stream, int suspended)
{
struct h2_mplx;
struct h2_request;
struct h2_response;
++struct h2_session;
struct h2_task;
typedef struct h2_stream h2_stream;
struct h2_stream {
int id; /* http2 stream id */
h2_stream_state_t state; /* http/2 state of this stream */
-- struct h2_mplx *m; /* the multiplexer to work with */
++ struct h2_session *session; /* the session this stream belongs to */
int aborted; /* was aborted */
int suspended; /* DATA sending has been suspended */
++ int rst_error; /* stream error for RST_STREAM */
apr_pool_t *pool; /* the memory pool for this stream */
struct h2_request *request; /* the request made in this stream */
-- struct h2_task *task; /* task created for this stream */
struct h2_response *response; /* the response, once ready */
++
apr_bucket_brigade *bbout; /* output DATA */
- int rst_error; /* stream error for RST_STREAM */
++ apr_size_t data_frames_sent;/* # of DATA frames sent out for this stream */
};
-h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
+#define H2_STREAM_RST(s, def) (s->rst_error? s->rst_error : (def))
+
- h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_mplx *m);
++h2_stream *h2_stream_create(int id, apr_pool_t *pool, struct h2_session *session);
apr_status_t h2_stream_destroy(h2_stream *stream);
-apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
-void h2_stream_attach_pool(h2_stream *stream, apr_pool_t *pool);
++void h2_stream_cleanup(h2_stream *stream);
+
-void h2_stream_abort(h2_stream *stream);
+void h2_stream_rst(h2_stream *streamm, int error_code);
+
+apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
apr_status_t h2_stream_rwrite(h2_stream *stream, request_rec *r);
const char *name, size_t nlen,
const char *value, size_t vlen);
--apr_status_t h2_stream_write_eoh(h2_stream *stream, int eos);
++apr_status_t h2_stream_schedule(h2_stream *stream, int eos,
++ h2_stream_pri_cmp *cmp, void *ctx);
apr_status_t h2_stream_write_data(h2_stream *stream,
const char *data, size_t len);
apr_status_t h2_stream_readx(h2_stream *stream, h2_io_data_cb *cb,
void *ctx, apr_size_t *plen, int *peos);
++apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb,
++ apr_size_t *plen, int *peos);
++
++
void h2_stream_set_suspended(h2_stream *stream, int suspended);
int h2_stream_is_suspended(h2_stream *stream);
static int h2_stream_id_cmp(const void *s1, const void *s2)
{
-- h2_stream **pstream1 = (h2_stream **)s1;
-- h2_stream **pstream2 = (h2_stream **)s2;
-- return (*pstream1)->id - (*pstream2)->id;
++ return (*((h2_stream **)s1))->id - (*((h2_stream **)s2))->id;
}
h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
return APR_SUCCESS;
}
--h2_stream *h2_stream_set_remove(h2_stream_set *sp, h2_stream *stream)
++h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id)
{
int i;
for (i = 0; i < sp->list->nelts; ++i) {
h2_stream *s = H2_STREAM_IDX(sp->list, i);
-- if (s == stream) {
++ if (s->id == stream_id) {
int n;
--sp->list->nelts;
n = sp->list->nelts - i;
h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id);
--h2_stream *h2_stream_set_remove(h2_stream_set *sp,h2_stream *stream);
++h2_stream *h2_stream_set_remove(h2_stream_set *sp, int stream_id);
void h2_stream_set_remove_all(h2_stream_set *sp);
#include "h2_h2.h"
#include "h2_switch.h"
--/*******************************************************************************
-- * SSL var lookup
-- */
--APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
-- (apr_pool_t *, server_rec *,
-- conn_rec *, request_rec *,
-- char *));
--static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
-- conn_rec *, request_rec *,
-- char *);
--
/*******************************************************************************
* Once per lifetime init, retrieve optional functions
*/
{
(void)pool;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init");
-- opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
return APR_SUCCESS;
}
apr_array_header_t *proposals)
{
int proposed = 0;
-- const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
++ int is_tls = h2_h2_is_tls(c);
++ const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
(void)s;
if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
return DECLINED;
}
++ if (!h2_is_acceptable_connection(c, 0)) {
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
++ "protocol propose: connection requirements not met");
++ return DECLINED;
++ }
++
if (r) {
-- const char *p;
/* So far, this indicates an HTTP/1 Upgrade header initiated
* protocol switch. For that, the HTTP2-Settings header needs
* to be present and valid for the connection.
*/
++ const char *p;
++
++ if (!h2_allows_h2_upgrade(c)) {
++ return DECLINED;
++ }
++
p = apr_table_get(r->headers_in, "HTTP2-Settings");
if (!p) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes) {
-- h2_task_env *env = filter->ctx;
-- AP_DEBUG_ASSERT(env);
-- if (!env->input) {
++ h2_task *task = filter->ctx;
++ AP_DEBUG_ASSERT(task);
++ if (!task->input) {
return APR_ECONNABORTED;
}
-- return h2_task_input_read(env->input, filter, brigade,
++ return h2_task_input_read(task->input, filter, brigade,
mode, block, readbytes);
}
static apr_status_t h2_filter_stream_output(ap_filter_t* filter,
apr_bucket_brigade* brigade) {
-- h2_task_env *env = filter->ctx;
-- AP_DEBUG_ASSERT(env);
-- if (!env->output) {
++ h2_task *task = filter->ctx;
++ AP_DEBUG_ASSERT(task);
++ if (!task->output) {
return APR_ECONNABORTED;
}
-- return h2_task_output_write(env->output, filter, brigade);
++ return h2_task_output_write(task->output, filter, brigade);
}
static apr_status_t h2_filter_read_response(ap_filter_t* f,
apr_bucket_brigade* bb) {
-- h2_task_env *env = f->ctx;
-- AP_DEBUG_ASSERT(env);
-- if (!env->output || !env->output->from_h1) {
++ h2_task *task = f->ctx;
++ AP_DEBUG_ASSERT(task);
++ if (!task->output || !task->output->from_h1) {
return APR_ECONNABORTED;
}
-- return h2_from_h1_read_response(env->output->from_h1, f, bb);
++ return h2_from_h1_read_response(task->output->from_h1, f, bb);
}
++static apr_status_t h2_task_process_request(h2_task *task);
++
/*******************************************************************************
* Register various hooks
*/
(void)arg;
if (h2_ctx_is_task(ctx)) {
-- h2_task_env *env = h2_ctx_get_task(ctx);
--
-- /* This connection is a pseudo-connection used for a h2_task.
-- * Since we read/write directly from it ourselves, we need
-- * to disable a possible ssl connection filter.
-- */
-- h2_tls_disable(c);
++ h2_task *task = h2_ctx_get_task(ctx);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"h2_h2, pre_connection, found stream task");
/* Add our own, network level in- and output filters.
*/
-- ap_add_input_filter("H2_TO_H1", env, NULL, c);
-- ap_add_output_filter("H1_TO_H2", env, NULL, c);
++ ap_add_input_filter("H2_TO_H1", task, NULL, c);
++ ap_add_output_filter("H1_TO_H2", task, NULL, c);
}
return OK;
}
h2_ctx *ctx = h2_ctx_get(c);
if (h2_ctx_is_task(ctx)) {
-- if (!ctx->task_env->serialize_headers) {
++ if (!ctx->task->serialize_headers) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"h2_h2, processing request directly");
-- h2_task_process_request(ctx->task_env);
++ h2_task_process_request(ctx->task);
return DONE;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
-- "h2_task(%s), serialized handling", ctx->task_env->id);
++ "h2_task(%s), serialized handling", ctx->task->id);
}
return DECLINED;
}
return NULL;
}
-- APR_RING_ELEM_INIT(task, link);
--
task->id = apr_psprintf(stream_pool, "%ld-%d", session_id, stream_id);
task->stream_id = stream_id;
task->mplx = mplx;
{
apr_status_t status = APR_SUCCESS;
h2_config *cfg = h2_config_get(task->mplx->c);
-- h2_task_env env;
AP_DEBUG_ASSERT(task);
-- memset(&env, 0, sizeof(env));
--
-- env.id = task->id;
-- env.stream_id = task->stream_id;
-- env.mplx = task->mplx;
-- task->mplx = NULL;
--
-- env.input_eos = task->input_eos;
-- env.serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
++ task->serialize_headers = h2_config_geti(cfg, H2_CONF_SER_HEADERS);
/* Create a subpool from the worker one to be used for all things
-- * with life-time of this task_env execution.
++ * with life-time of this task execution.
*/
-- apr_pool_create(&env.pool, h2_worker_get_pool(worker));
++ apr_pool_create(&task->pool, h2_worker_get_pool(worker));
-- /* Link the env to the worker which provides useful things such
++ /* Link the task to the worker which provides useful things such
* as mutex, a socket etc. */
-- env.io = h2_worker_get_cond(worker);
++ task->io = h2_worker_get_cond(worker);
-- /* Clone fields, so that lifetimes become (more) independent. */
-- env.method = apr_pstrdup(env.pool, task->method);
-- env.scheme = apr_pstrdup(env.pool, task->scheme);
-- env.authority = apr_pstrdup(env.pool, task->authority);
-- env.path = apr_pstrdup(env.pool, task->path);
-- env.headers = apr_table_clone(env.pool, task->headers);
--
-- /* Setup the pseudo connection to use our own pool and bucket_alloc */
-- env.c = *task->c;
-- task->c = NULL;
-- status = h2_conn_setup(&env, worker);
++ status = h2_conn_setup(task, worker);
/* save in connection that this one is a pseudo connection, prevents
* other hooks from messing with it. */
-- h2_ctx_create_for(&env.c, &env);
++ h2_ctx_create_for(task->c, task);
if (status == APR_SUCCESS) {
-- env.input = h2_task_input_create(&env, env.pool,
-- env.c.bucket_alloc);
-- env.output = h2_task_output_create(&env, env.pool,
-- env.c.bucket_alloc);
-- status = h2_conn_process(&env.c, h2_worker_get_socket(worker));
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, &env.c,
-- "h2_task(%s): processing done", env.id);
++ task->input = h2_task_input_create(task, task->pool,
++ task->c->bucket_alloc);
++ task->output = h2_task_output_create(task, task->pool,
++ task->c->bucket_alloc);
++ status = h2_conn_process(task->c, h2_worker_get_socket(worker));
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, task->c,
++ "h2_task(%s): processing done", task->id);
}
else {
-- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c,
-- APLOGNO(02957) "h2_task(%s): error setting up h2_task_env",
-- env.id);
++ ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, task->c,
++ APLOGNO(02957) "h2_task(%s): error setting up h2_task",
++ task->id);
}
-- if (env.input) {
-- h2_task_input_destroy(env.input);
-- env.input = NULL;
++ if (task->input) {
++ h2_task_input_destroy(task->input);
++ task->input = NULL;
}
-- if (env.output) {
-- h2_task_output_close(env.output);
-- h2_task_output_destroy(env.output);
-- env.output = NULL;
++ if (task->output) {
++ h2_task_output_close(task->output);
++ h2_task_output_destroy(task->output);
++ task->output = NULL;
}
-- h2_task_set_finished(task);
-- if (env.io) {
-- apr_thread_cond_signal(env.io);
++ if (task->io) {
++ apr_thread_cond_signal(task->io);
}
-- if (env.pool) {
-- apr_pool_destroy(env.pool);
-- env.pool = NULL;
++ if (task->pool) {
++ apr_pool_destroy(task->pool);
++ task->pool = NULL;
}
-- if (env.c.id) {
-- h2_conn_post(&env.c, worker);
++ if (task->c->id) {
++ h2_conn_post(task->c, worker);
}
-- h2_mplx_task_done(env.mplx, env.stream_id);
++ h2_mplx_task_done(task->mplx, task->stream_id);
return status;
}
--int h2_task_has_started(h2_task *task)
--{
-- AP_DEBUG_ASSERT(task);
-- return apr_atomic_read32(&task->has_started);
--}
--
--void h2_task_set_started(h2_task *task)
- {
- AP_DEBUG_ASSERT(task);
- apr_atomic_set32(&task->has_started, 1);
- }
-
- int h2_task_has_finished(h2_task *task)
- {
- return apr_atomic_read32(&task->has_finished);
- }
-
- void h2_task_set_finished(h2_task *task)
- {
- apr_atomic_set32(&task->has_finished, 1);
- }
-
- void h2_task_die(h2_task_env *env, int status, request_rec *r)
- {
- (void)env;
- ap_die(status, r);
- }
-
- static request_rec *h2_task_create_request(h2_task_env *env)
++static request_rec *h2_task_create_request(h2_task *task)
{
- AP_DEBUG_ASSERT(task);
- apr_atomic_set32(&task->has_started, 1);
-}
-
-int h2_task_has_finished(h2_task *task)
-{
- return apr_atomic_read32(&task->has_finished);
-}
-
-void h2_task_set_finished(h2_task *task)
-{
- apr_atomic_set32(&task->has_finished, 1);
-}
-
-void h2_task_die(h2_task_env *env, int status, request_rec *r)
-{
- (void)env;
- ap_die(status, r);
-}
-
-static request_rec *h2_task_create_request(h2_task_env *env)
-{
-- conn_rec *conn = &env->c;
++ conn_rec *conn = task->c;
request_rec *r;
apr_pool_t *p;
int access_status = HTTP_OK;
r->allowed_methods = ap_make_method_list(p, 2);
-- r->headers_in = apr_table_copy(r->pool, env->headers);
++ r->headers_in = apr_table_copy(r->pool, task->headers);
r->trailers_in = apr_table_make(r->pool, 5);
r->subprocess_env = apr_table_make(r->pool, 25);
r->headers_out = apr_table_make(r->pool, 12);
/* Time to populate r with the data we have. */
r->request_time = apr_time_now();
- r->the_request = apr_psprintf(r->pool, "%s %s HTTP/1.1",
- env->method, env->path);
-- r->method = env->method;
++ r->method = task->method;
/* Provide quick information about the request method as soon as known */
r->method_number = ap_method_number_of(r->method);
if (r->method_number == M_GET && r->method[0] == 'H') {
r->header_only = 1;
}
-- ap_parse_uri(r, env->path);
++ ap_parse_uri(r, task->path);
r->protocol = (char*)"HTTP/2";
r->proto_num = HTTP_VERSION(2, 0);
- r->method, env->path, r->protocol);
+
+ r->the_request = apr_psprintf(r->pool, "%s %s %s",
++ r->method, task->path, r->protocol);
/* update what we think the virtual host is based on the headers we've
* now read. may update status.
/* Request check post hooks failed. An example of this would be a
* request for a vhost where h2 is disabled --> 421.
*/
-- h2_task_die(env, access_status, r);
++ ap_die(access_status, r);
ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
ap_run_log_transaction(r);
r = NULL;
}
--apr_status_t h2_task_process_request(h2_task_env *env)
++static apr_status_t h2_task_process_request(h2_task *task)
{
-- conn_rec *c = &env->c;
++ conn_rec *c = task->c;
request_rec *r;
conn_state_t *cs = c->cs;
-- r = h2_task_create_request(env);
++ r = h2_task_create_request(task);
if (r && (r->status == HTTP_OK)) {
ap_update_child_status(c->sbh, SERVER_BUSY_READ, r);
typedef struct h2_task h2_task;
struct h2_task {
-- /** Links to the rest of the tasks */
-- APR_RING_ENTRY(h2_task) link;
--
const char *id;
int stream_id;
struct h2_mplx *mplx;
-- volatile apr_uint32_t has_started;
-- volatile apr_uint32_t has_finished;
--
const char *method;
const char *scheme;
const char *authority;
apr_table_t *headers;
int input_eos;
-- struct conn_rec *c;
--};
++ int serialize_headers;
--typedef struct h2_task_env h2_task_env;
++ struct conn_rec *c;
--struct h2_task_env {
-- const char *id;
-- int stream_id;
-- struct h2_mplx *mplx;
--
apr_pool_t *pool; /* pool for task lifetime things */
apr_bucket_alloc_t *bucket_alloc;
--
-- const char *method;
-- const char *scheme;
-- const char *authority;
-- const char *path;
-- apr_table_t *headers;
-- int input_eos;
--
-- int serialize_headers;
--
-- struct conn_rec c;
struct h2_task_input *input;
struct h2_task_output *output;
struct apr_thread_cond_t *io; /* used to wait for events on */
};
--/**
-- * The magic pointer value that indicates the head of a h2_task list
-- * @param b The task list
-- * @return The magic pointer value
-- */
--#define H2_TASK_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task, link)
--
--/**
-- * Determine if the task list is empty
-- * @param b The list to check
-- * @return true or false
-- */
--#define H2_TASK_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task, link)
--
--/**
-- * Return the first task in a list
-- * @param b The list to query
-- * @return The first task in the list
-- */
--#define H2_TASK_LIST_FIRST(b) APR_RING_FIRST(b)
--
--/**
-- * Return the last task in a list
-- * @param b The list to query
-- * @return The last task int he list
-- */
--#define H2_TASK_LIST_LAST(b) APR_RING_LAST(b)
--
--/**
-- * Insert a single task at the front of a list
-- * @param b The list to add to
-- * @param e The task to insert
-- */
--#define H2_TASK_LIST_INSERT_HEAD(b, e) do { \
-- h2_task *ap__b = (e); \
-- APR_RING_INSERT_HEAD((b), ap__b, h2_task, link); \
--} while (0)
--
--/**
-- * Insert a single task at the end of a list
-- * @param b The list to add to
-- * @param e The task to insert
-- */
--#define H2_TASK_LIST_INSERT_TAIL(b, e) do { \
-- h2_task *ap__b = (e); \
-- APR_RING_INSERT_TAIL((b), ap__b, h2_task, link); \
--} while (0)
--
--/**
-- * Get the next task in the list
-- * @param e The current task
-- * @return The next task
-- */
--#define H2_TASK_NEXT(e) APR_RING_NEXT((e), link)
--/**
-- * Get the previous task in the list
-- * @param e The current task
-- * @return The previous task
-- */
--#define H2_TASK_PREV(e) APR_RING_PREV((e), link)
--
--/**
-- * Remove a task from its list
-- * @param e The task to remove
-- */
--#define H2_TASK_REMOVE(e) APR_RING_REMOVE((e), link)
--
--
h2_task *h2_task_create(long session_id, int stream_id,
apr_pool_t *pool, struct h2_mplx *mplx,
conn_rec *c);
apr_status_t h2_task_do(h2_task *task, struct h2_worker *worker);
--apr_status_t h2_task_process_request(h2_task_env *env);
--
--int h2_task_has_started(h2_task *task);
--void h2_task_set_started(h2_task *task);
--int h2_task_has_finished(h2_task *task);
--void h2_task_set_finished(h2_task *task);
void h2_task_register_hooks(void);
--void h2_task_die(h2_task_env *env, int status, request_rec *r);
#endif /* defined(__mod_h2__h2_task__) */
return 1;
}
--h2_task_input *h2_task_input_create(h2_task_env *env, apr_pool_t *pool,
++h2_task_input *h2_task_input_create(h2_task *task, apr_pool_t *pool,
apr_bucket_alloc_t *bucket_alloc)
{
h2_task_input *input = apr_pcalloc(pool, sizeof(h2_task_input));
if (input) {
-- input->env = env;
++ input->task = task;
input->bb = NULL;
-- if (env->serialize_headers) {
-- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, &env->c,
++ if (task->serialize_headers) {
++ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
"h2_task_input(%s): serialize request %s %s",
-- env->id, env->method, env->path);
++ task->id, task->method, task->path);
input->bb = apr_brigade_create(pool, bucket_alloc);
apr_brigade_printf(input->bb, NULL, NULL, "%s %s HTTP/1.1\r\n",
-- env->method, env->path);
-- apr_table_do(ser_header, input, env->headers, NULL);
++ task->method, task->path);
++ apr_table_do(ser_header, input, task->headers, NULL);
apr_brigade_puts(input->bb, NULL, NULL, "\r\n");
-- if (input->env->input_eos) {
++ if (input->task->input_eos) {
APR_BRIGADE_INSERT_TAIL(input->bb, apr_bucket_eos_create(bucket_alloc));
}
}
-- else if (!input->env->input_eos) {
++ else if (!input->task->input_eos) {
input->bb = apr_brigade_create(pool, bucket_alloc);
}
else {
* create a bucket brigade. */
}
-- if (APLOGcdebug(&env->c)) {
++ if (APLOGcdebug(task->c)) {
char buffer[1024];
apr_size_t len = sizeof(buffer)-1;
if (input->bb) {
len = 0;
}
buffer[len] = 0;
-- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, &env->c,
++ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
"h2_task_input(%s): request is: %s",
-- env->id, buffer);
++ task->id, buffer);
}
}
return input;
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"h2_task_input(%s): read, block=%d, mode=%d, readbytes=%ld",
-- input->env->id, block, mode, (long)readbytes);
++ input->task->id, block, mode, (long)readbytes);
if (is_aborted(f)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
"h2_task_input(%s): is aborted",
-- input->env->id);
++ input->task->id);
return APR_ECONNABORTED;
}
if (mode == AP_MODE_INIT) {
-- return APR_SUCCESS;
++ return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
}
if (input->bb) {
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c,
APLOGNO(02958) "h2_task_input(%s): brigade length fail",
-- input->env->id);
++ input->task->id);
return status;
}
}
-- if ((bblen == 0) && input->env->input_eos) {
++ if ((bblen == 0) && input->task->input_eos) {
return APR_EOF;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_input(%s): get more data from mplx, block=%d, "
"readbytes=%ld, queued=%ld",
-- input->env->id, block,
++ input->task->id, block,
(long)readbytes, (long)bblen);
/* Although we sometimes get called with APR_NONBLOCK_READs,
we seem to fill our buffer blocking. Otherwise we get EAGAIN,
return that to our caller and everyone throws up their hands,
never calling us again. */
-- status = h2_mplx_in_read(input->env->mplx, APR_BLOCK_READ,
-- input->env->stream_id, input->bb,
-- input->env->io);
++ status = h2_mplx_in_read(input->task->mplx, APR_BLOCK_READ,
++ input->task->stream_id, input->bb,
++ input->task->io);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_input(%s): mplx in read returned",
-- input->env->id);
++ input->task->id);
if (status != APR_SUCCESS) {
return status;
}
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_input(%s): mplx in read, %ld bytes in brigade",
-- input->env->id, (long)bblen);
++ input->task->id, (long)bblen);
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_input(%s): read, mode=%d, block=%d, "
"readbytes=%ld, queued=%ld",
-- input->env->id, mode, block,
++ input->task->id, mode, block,
(long)readbytes, (long)bblen);
if (!APR_BRIGADE_EMPTY(input->bb)) {
if (mode == AP_MODE_EXHAUSTIVE) {
/* return all we have */
-- return h2_util_move(bb, input->bb, readbytes, 0,
++ return h2_util_move(bb, input->bb, readbytes, NULL,
"task_input_read(exhaustive)");
}
else if (mode == AP_MODE_READBYTES) {
-- return h2_util_move(bb, input->bb, readbytes, 0,
++ return h2_util_move(bb, input->bb, readbytes, NULL,
"task_input_read(readbytes)");
}
else if (mode == AP_MODE_SPECULATIVE) {
buffer[len] = 0;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_input(%s): getline: %s",
-- input->env->id, buffer);
++ input->task->id, buffer);
}
return status;
}
*/
struct apr_thread_cond_t;
struct h2_mplx;
--struct h2_task_env;
++struct h2_task;
typedef struct h2_task_input h2_task_input;
struct h2_task_input {
-- struct h2_task_env *env;
++ struct h2_task *task;
apr_bucket_brigade *bb;
};
--h2_task_input *h2_task_input_create(struct h2_task_env *env, apr_pool_t *pool,
++h2_task_input *h2_task_input_create(struct h2_task *task, apr_pool_t *pool,
apr_bucket_alloc_t *bucket_alloc);
void h2_task_input_destroy(h2_task_input *input);
#include "h2_util.h"
--h2_task_output *h2_task_output_create(h2_task_env *env, apr_pool_t *pool,
++h2_task_output *h2_task_output_create(h2_task *task, apr_pool_t *pool,
apr_bucket_alloc_t *bucket_alloc)
{
h2_task_output *output = apr_pcalloc(pool, sizeof(h2_task_output));
(void)bucket_alloc;
if (output) {
-- output->env = env;
++ output->task = task;
output->state = H2_TASK_OUT_INIT;
-- output->from_h1 = h2_from_h1_create(env->stream_id, pool);
++ output->from_h1 = h2_from_h1_create(task->stream_id, pool);
if (!output->from_h1) {
return NULL;
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c,
"h2_task_output(%s): write without response "
"for %s %s %s",
-- output->env->id, output->env->method,
-- output->env->authority, output->env->path);
++ output->task->id, output->task->method,
++ output->task->authority, output->task->path);
f->c->aborted = 1;
}
-- if (output->env->io) {
-- apr_thread_cond_broadcast(output->env->io);
++ if (output->task->io) {
++ apr_thread_cond_broadcast(output->task->io);
}
return APR_ECONNABORTED;
}
-- return h2_mplx_out_open(output->env->mplx, output->env->stream_id,
-- response, f, bb, output->env->io);
++ return h2_mplx_out_open(output->task->mplx, output->task->stream_id,
++ response, f, bb, output->task->io);
}
return APR_EOF;
}
{
open_if_needed(output, NULL, NULL);
if (output->state != H2_TASK_OUT_DONE) {
-- h2_mplx_out_close(output->env->mplx, output->env->stream_id);
++ h2_mplx_out_close(output->task->mplx, output->task->stream_id);
output->state = H2_TASK_OUT_DONE;
}
}
apr_status_t status;
if (APR_BRIGADE_EMPTY(bb)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-- "h2_task_output(%s): empty write", output->env->id);
++ "h2_task_output(%s): empty write", output->task->id);
return APR_SUCCESS;
}
if (status != APR_EOF) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
"h2_task_output(%s): opened and passed brigade",
-- output->env->id);
++ output->task->id);
return status;
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-- "h2_task_output(%s): write brigade", output->env->id);
-- return h2_mplx_out_write(output->env->mplx, output->env->stream_id,
-- f, bb, output->env->io);
++ "h2_task_output(%s): write brigade", output->task->id);
++ return h2_mplx_out_write(output->task->mplx, output->task->stream_id,
++ f, bb, output->task->io);
}
*/
struct apr_thread_cond_t;
struct h2_mplx;
--struct h2_task_env;
++struct h2_task;
struct h2_from_h1;
typedef enum {
typedef struct h2_task_output h2_task_output;
struct h2_task_output {
-- struct h2_task_env *env;
++ struct h2_task *task;
h2_task_output_state_t state;
struct h2_from_h1 *from_h1;
};
--h2_task_output *h2_task_output_create(struct h2_task_env *env, apr_pool_t *pool,
++h2_task_output *h2_task_output_create(struct h2_task *task, apr_pool_t *pool,
apr_bucket_alloc_t *bucket_alloc);
void h2_task_output_destroy(h2_task_output *output);
#include "h2_task_queue.h"
--h2_task_queue *h2_tq_create(long id, apr_pool_t *pool)
++static void tq_grow(h2_task_queue *q, int nlen);
++static void tq_swap(h2_task_queue *q, int i, int j);
++static int tq_bubble_up(h2_task_queue *q, int i, int top,
++ h2_tq_cmp *cmp, void *ctx);
++static int tq_bubble_down(h2_task_queue *q, int i, int bottom,
++ h2_tq_cmp *cmp, void *ctx);
++
++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity)
{
h2_task_queue *q = apr_pcalloc(pool, sizeof(h2_task_queue));
if (q) {
-- q->id = id;
-- APR_RING_ELEM_INIT(q, link);
-- APR_RING_INIT(&q->tasks, h2_task, link);
++ q->pool = pool;
++ tq_grow(q, capacity);
++ q->nelts = 0;
}
return q;
}
--void h2_tq_destroy(h2_task_queue *q)
++int h2_tq_empty(h2_task_queue *q)
{
-- while (!H2_TASK_LIST_EMPTY(&q->tasks)) {
-- h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
-- H2_TASK_REMOVE(task);
++ return q->nelts == 0;
++}
++
++void h2_tq_add(h2_task_queue *q, struct h2_task *task,
++ h2_tq_cmp *cmp, void *ctx)
++{
++ int i;
++
++ if (q->nelts >= q->nalloc) {
++ tq_grow(q, q->nalloc * 2);
}
++
++ i = (q->head + q->nelts) % q->nalloc;
++ q->elts[i] = task;
++ ++q->nelts;
++
++ /* bubble it to the front of the queue */
++ tq_bubble_up(q, i, q->head, cmp, ctx);
}
--static int in_list(h2_task_queue *q, h2_task *task)
++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx)
{
-- h2_task *e;
-- for (e = H2_TASK_LIST_FIRST(&q->tasks);
-- e != H2_TASK_LIST_SENTINEL(&q->tasks);
-- e = H2_TASK_NEXT(e)) {
-- if (e == task) {
-- return 1;
-- }
++ /* Assume that changes in ordering are minimal. This needs,
++ * best case, q->nelts - 1 comparisions to check that nothing
++ * changed.
++ */
++ if (q->nelts > 0) {
++ int i, ni, prev, last;
++
++ /* Start at the end of the queue and create a tail of sorted
++ * entries. Make that tail one element longer in each iteration.
++ */
++ last = i = (q->head + q->nelts - 1) % q->nalloc;
++ while (i != q->head) {
++ prev = (q->nalloc + i - 1) % q->nalloc;
++
++ ni = tq_bubble_up(q, i, prev, cmp, ctx);
++ if (ni == prev) {
++ /* i bubbled one up, bubble the new i down, which
++ * keeps all tasks below i sorted. */
++ tq_bubble_down(q, i, last, cmp, ctx);
++ }
++ i = prev;
++ };
}
-- return 0;
}
--int h2_tq_empty(h2_task_queue *q)
++
++h2_task *h2_tq_shift(h2_task_queue *q)
{
-- return H2_TASK_LIST_EMPTY(&q->tasks);
++ h2_task *t;
++
++ if (q->nelts <= 0) {
++ return NULL;
++ }
++
++ t = q->elts[q->head];
++ q->head = (q->head + 1) % q->nalloc;
++ q->nelts--;
++
++ return t;
++}
++
++static void tq_grow(h2_task_queue *q, int nlen)
++{
++ AP_DEBUG_ASSERT(q->nalloc <= nlen);
++ if (nlen > q->nalloc) {
++ h2_task **nq = apr_pcalloc(q->pool, sizeof(h2_task *) * nlen);
++ if (q->nelts > 0) {
++ int l = ((q->head + q->nelts) % q->nalloc) - q->head;
++
++ memmove(nq, q->elts + q->head, sizeof(h2_task *) * l);
++ if (l < q->nelts) {
++ /* elts wrapped, append elts in [0, remain] to nq */
++ int remain = q->nelts - l;
++ memmove(nq + l, q->elts, sizeof(h2_task *) * remain);
++ }
++ }
++ q->elts = nq;
++ q->nalloc = nlen;
++ q->head = 0;
++ }
}
--void h2_tq_append(h2_task_queue *q, struct h2_task *task)
++static void tq_swap(h2_task_queue *q, int i, int j)
{
-- H2_TASK_LIST_INSERT_TAIL(&q->tasks, task);
++ h2_task *t = q->elts[i];
++ q->elts[i] = q->elts[j];
++ q->elts[j] = t;
}
--apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task)
++static int tq_bubble_up(h2_task_queue *q, int i, int top,
++ h2_tq_cmp *cmp, void *ctx)
{
-- if (in_list(q, task)) {
-- H2_TASK_REMOVE(task);
-- return APR_SUCCESS;
++ int prev;
++ while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top)
++ && (*cmp)(q->elts[i], q->elts[prev], ctx) < 0) {
++ tq_swap(q, prev, i);
++ i = prev;
}
-- return APR_NOTFOUND;
++ return i;
}
--h2_task *h2_tq_pop_first(h2_task_queue *q)
++static int tq_bubble_down(h2_task_queue *q, int i, int bottom,
++ h2_tq_cmp *cmp, void *ctx)
{
-- if (!H2_TASK_LIST_EMPTY(&q->tasks)) {
-- h2_task *task = H2_TASK_LIST_FIRST(&q->tasks);
-- H2_TASK_REMOVE(task);
-- return task;
++ int next;
++ while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom)
++ && (*cmp)(q->elts[i], q->elts[next], ctx) > 0) {
++ tq_swap(q, next, i);
++ i = next;
}
-- return NULL;
++ return i;
}
struct h2_task;
/**
-- * A simple ring of rings that keeps a list of h2_tasks and can
-- * be ringed itself, using the APR RING macros.
++ * h2_task_queue keeps a list of sorted h2_task* in ascending order.
*/
typedef struct h2_task_queue h2_task_queue;
struct h2_task_queue {
-- APR_RING_ENTRY(h2_task_queue) link;
-- APR_RING_HEAD(h2_tasks, h2_task) tasks;
-- long id;
++ struct h2_task **elts;
++ int head;
++ int nelts;
++ int nalloc;
++ apr_pool_t *pool;
};
/**
-- * Allocate a new queue from the pool and initialize.
-- * @param id the identifier of the queue
-- * @param pool the memory pool
++ * Comparator for two task to determine their order.
++ *
++ * @param t1 task to compare
++ * @param t2 task to compare
++ * @param ctx provided user data
++ * @return value is the same as for strcmp() and has the effect:
++ * == 0: t1 and t2 are treated equal in ordering
++ * < 0: t1 should be sorted before t2
++ * > 0: t2 should be sorted before t1
*/
--h2_task_queue *h2_tq_create(long id, apr_pool_t *pool);
++typedef int h2_tq_cmp(struct h2_task *t1, struct h2_task *t2, void *ctx);
++
/**
-- * Release all queue tasks.
-- * @param q the queue to destroy
++ * Allocate a new queue from the pool and initialize.
++ * @param id the identifier of the queue
++ * @param pool the memory pool
*/
--void h2_tq_destroy(h2_task_queue *q);
++h2_task_queue *h2_tq_create(apr_pool_t *pool, int capacity);
/**
* Return != 0 iff there are no tasks in the queue.
int h2_tq_empty(h2_task_queue *q);
/**
-- * Append the task to the end of the queue.
++ * Add the task to the queue.
++ *
* @param q the queue to append the task to
-- * @param task the task to append
-- */
--void h2_tq_append(h2_task_queue *q, struct h2_task *task);
--
--/**
-- * Remove a task from the queue. Return APR_SUCCESS if the task
-- * was indeed queued, APR_NOTFOUND otherwise.
-- * @param q the queue to remove from
-- * @param task the task to remove
-- */
--apr_status_t h2_tq_remove(h2_task_queue *q, struct h2_task *task);
--
--/**
-- * Get the first task from the queue or NULL if the queue is empty. The
-- * task will be removed.
-- * @param q the queue to pop the first task from
-- */
--h2_task *h2_tq_pop_first(h2_task_queue *q);
--
--/*******************************************************************************
-- * Queue Manipulation.
-- ******************************************************************************/
--
--/**
-- * The magic pointer value that indicates the head of a h2_task_queue list
-- * @param b The queue list
-- * @return The magic pointer value
++ * @param task the task to add
++ * @param cmp the comparator for sorting
++ * @param ctx user data for comparator
*/
--#define H2_TQ_LIST_SENTINEL(b) APR_RING_SENTINEL((b), h2_task_queue, link)
++void h2_tq_add(h2_task_queue *q, struct h2_task *task,
++ h2_tq_cmp *cmp, void *ctx);
/**
-- * Determine if the queue list is empty
-- * @param b The list to check
-- * @return true or false
-- */
--#define H2_TQ_LIST_EMPTY(b) APR_RING_EMPTY((b), h2_task_queue, link)
--
--/**
-- * Return the first queue in a list
-- * @param b The list to query
-- * @return The first queue in the list
-- */
--#define H2_TQ_LIST_FIRST(b) APR_RING_FIRST(b)
--
--/**
-- * Return the last queue in a list
-- * @param b The list to query
-- * @return The last queue int he list
-- */
--#define H2_TQ_LIST_LAST(b) APR_RING_LAST(b)
--
--/**
-- * Insert a single queue at the front of a list
-- * @param b The list to add to
-- * @param e The queue to insert
-- */
--#define H2_TQ_LIST_INSERT_HEAD(b, e) do { \
--h2_task_queue *ap__b = (e); \
--APR_RING_INSERT_HEAD((b), ap__b, h2_task_queue, link); \
--} while (0)
--
--/**
-- * Insert a single queue at the end of a list
-- * @param b The list to add to
-- * @param e The queue to insert
-- */
--#define H2_TQ_LIST_INSERT_TAIL(b, e) do { \
--h2_task_queue *ap__b = (e); \
--APR_RING_INSERT_TAIL((b), ap__b, h2_task_queue, link); \
--} while (0)
--
--/**
-- * Get the next queue in the list
-- * @param e The current queue
-- * @return The next queue
-- */
--#define H2_TQ_NEXT(e) APR_RING_NEXT((e), link)
--/**
-- * Get the previous queue in the list
-- * @param e The current queue
-- * @return The previous queue
++ * Sort the tasks queue again. Call if the task ordering
++ * has changed.
++ *
++ * @param q the queue to sort
++ * @param cmp the comparator for sorting
++ * @param ctx user data for the comparator
*/
--#define H2_TQ_PREV(e) APR_RING_PREV((e), link)
++void h2_tq_sort(h2_task_queue *q, h2_tq_cmp *cmp, void *ctx);
/**
-- * Remove a queue from its list
-- * @param e The queue to remove
++ * Get the first task from the queue or NULL if the queue is empty.
++ * The task will be removed.
++ *
++ * @param q the queue to get the first task from
++ * @return the first task of the queue, NULL if empty
*/
--#define H2_TQ_REMOVE(e) APR_RING_REMOVE((e), link)
--
--
--#define H2_TQ_EMPTY(e) H2_TASK_LIST_EMPTY(&(e)->tasks)
++h2_task *h2_tq_shift(h2_task_queue *q);
#endif /* defined(__mod_h2__h2_task_queue__) */
return APR_SUCCESS;
}
--apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
++apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos)
{
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, to_h1->m->c,
"h2_to_h1(%ld-%d): end headers",
apr_table_mergen(to_h1->headers, "Transfer-Encoding", "chunked");
}
-- h2_task_set_request(task, to_h1->method,
-- to_h1->scheme,
-- to_h1->authority,
-- to_h1->path,
-- to_h1->headers, eos);
to_h1->eoh = 1;
-- if (eos) {
-- apr_status_t status = h2_to_h1_close(to_h1);
-- if (status != APR_SUCCESS) {
-- ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
-- APLOGNO(02960)
-- "h2_to_h1(%ld-%d): end headers, eos=%d",
-- to_h1->m->id, to_h1->stream_id, eos);
-- }
-- return status;
-- }
return APR_SUCCESS;
}
/** End the request headers.
*/
--apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1,
-- struct h2_task *task, int eos);
++apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, int eos);
/* Add request body data.
*/
return status;
}
+void h2_util_bb_log(conn_rec *c, int stream_id, int level,
+ const char *tag, apr_bucket_brigade *bb)
+{
+ char buffer[16 * 1024];
+ const char *line = "(null)";
+ apr_size_t bmax = sizeof(buffer)/sizeof(buffer[0]);
+ int off = 0;
+ apr_bucket *b;
+
+ if (bb) {
+ memset(buffer, 0, bmax--);
+ for (b = APR_BRIGADE_FIRST(bb);
+ bmax && (b != APR_BRIGADE_SENTINEL(bb));
+ b = APR_BUCKET_NEXT(b)) {
+
+ if (APR_BUCKET_IS_METADATA(b)) {
+ if (APR_BUCKET_IS_EOS(b)) {
+ off += apr_snprintf(buffer+off, bmax-off, "eos ");
+ }
+ else if (APR_BUCKET_IS_FLUSH(b)) {
+ off += apr_snprintf(buffer+off, bmax-off, "flush ");
+ }
+ else if (AP_BUCKET_IS_EOR(b)) {
+ off += apr_snprintf(buffer+off, bmax-off, "eor ");
+ }
+ else {
+ off += apr_snprintf(buffer+off, bmax-off, "meta(unknown) ");
+ }
+ }
+ else {
+ const char *btype = "data";
+ if (APR_BUCKET_IS_FILE(b)) {
+ btype = "file";
+ }
+ else if (APR_BUCKET_IS_PIPE(b)) {
+ btype = "pipe";
+ }
+ else if (APR_BUCKET_IS_SOCKET(b)) {
+ btype = "socket";
+ }
+ else if (APR_BUCKET_IS_HEAP(b)) {
+ btype = "heap";
+ }
+ else if (APR_BUCKET_IS_TRANSIENT(b)) {
+ btype = "transient";
+ }
+ else if (APR_BUCKET_IS_IMMORTAL(b)) {
+ btype = "immortal";
+ }
++#if APR_HAS_MMAP
+ else if (APR_BUCKET_IS_MMAP(b)) {
+ btype = "mmap";
+ }
++#endif
+ else if (APR_BUCKET_IS_POOL(b)) {
+ btype = "pool";
+ }
+
+ off += apr_snprintf(buffer+off, bmax-off, "%s[%ld] ",
+ btype,
+ (long)(b->length == ((apr_size_t)-1)?
+ -1 : b->length));
+ }
+ }
+ line = *buffer? buffer : "(empty)";
+ }
+ ap_log_cerror(APLOG_MARK, level, 0, c, "bb_dump(%ld-%d)-%s: %s",
+ c->id, stream_id, tag, line);
+
+}
++
++AP_DECLARE(apr_status_t) h2_transfer_brigade(apr_bucket_brigade *to,
++ apr_bucket_brigade *from,
++ apr_pool_t *p,
++ apr_size_t *plen,
++ int *peos)
++{
++ apr_bucket *e;
++ apr_size_t len = 0, remain = *plen;
++ apr_status_t rv;
++
++ *peos = 0;
++
++ while (!APR_BRIGADE_EMPTY(from)) {
++ e = APR_BRIGADE_FIRST(from);
++
++ if (APR_BUCKET_IS_METADATA(e)) {
++ if (APR_BUCKET_IS_EOS(e)) {
++ *peos = 1;
++ }
++ }
++ else {
++ if (remain > 0 && e->length == ((apr_size_t)-1)) {
++ const char *ign;
++ apr_size_t ilen;
++ rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
++ if (rv != APR_SUCCESS) {
++ return rv;
++ }
++ }
++
++ if (remain < e->length) {
++ if (remain <= 0) {
++ return APR_SUCCESS;
++ }
++ apr_bucket_split(e, remain);
++ }
++ }
++
++ rv = apr_bucket_setaside(e, p);
++
++ /* If the bucket type does not implement setaside, then
++ * (hopefully) morph it into a bucket type which does, and set
++ * *that* aside... */
++ if (rv == APR_ENOTIMPL) {
++ const char *s;
++ apr_size_t n;
++
++ rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ);
++ if (rv == APR_SUCCESS) {
++ rv = apr_bucket_setaside(e, p);
++ }
++ }
++
++ if (rv != APR_SUCCESS) {
++ /* Return an error but still save the brigade if
++ * ->setaside() is really not implemented. */
++ if (rv != APR_ENOTIMPL) {
++ return rv;
++ }
++ }
++
++ APR_BUCKET_REMOVE(e);
++ APR_BRIGADE_INSERT_TAIL(to, e);
++ len += e->length;
++ remain -= e->length;
++ }
++
++ *plen = len;
++ return APR_SUCCESS;
++}
++
h2_util_pass_cb *cb, void *ctx,
apr_size_t *plen, int *peos);
+/**
+ * Logs the bucket brigade (which bucket types with what length)
+ * to the log at the given level.
+ * @param c the connection to log for
+ * @param stream_id the stream identifier this brigade belongs to
+ * @param level the log level (as in APLOG_*)
+ * @param tag a short message text about the context
+ * @param bb the brigade to log
+ */
+void h2_util_bb_log(conn_rec *c, int stream_id, int level,
+ const char *tag, apr_bucket_brigade *bb);
+
++/**
++ * Transfer buckets from one brigade to another with a limit on the
++ * maximum amount of bytes transfered.
++ * @param to brigade to transfer buckets to
++ * @param from brigades to remove buckets from
++ * @param p pool that buckets should be setaside to
++ * @param plen maximum bytes to transfer, actual bytes transferred
++ * @param peos if an EOS bucket was transferred
++ */
++AP_DECLARE(apr_status_t) h2_transfer_brigade(apr_bucket_brigade *to,
++ apr_bucket_brigade *from,
++ apr_pool_t *p,
++ apr_size_t *plen,
++ int *peos);
++
#endif /* defined(__mod_h2__h2_util__) */
* @macro
* Version number of the h2 module as c string
*/
- #define MOD_HTTP2_VERSION "1.0.2-DEV"
-#define MOD_HTTP2_VERSION "1.0.0"
++#define MOD_HTTP2_VERSION "1.0.3-DEV"
/**
* @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 0x010002
-#define MOD_HTTP2_VERSION_NUM 0x010000
++#define MOD_HTTP2_VERSION_NUM 0x010003
#endif /* mod_h2_h2_version_h */
# End Source File
# Begin Source File
++SOURCE=./h2_bucket_eoc.c
++# End Source File
++# Begin Source File
++
++SOURCE=./h2_bucket_eos.c
++# End Source File
++# Begin Source File
++
SOURCE=./h2_config.c
# End Source File
# Begin Source File