From: Stefan Eissing Date: Wed, 4 Nov 2015 15:44:24 +0000 (+0000) Subject: merged backport proposed trunk changes re HTTP/2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=8c99893c2e68b68f17439a2f01af12536f2bf116;p=thirdparty%2Fapache%2Fhttpd.git merged backport proposed trunk changes re HTTP/2 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4-http2-alpha@1712570 13f79535-47bb-0310-9956-ffa450edef68 --- 8c99893c2e68b68f17439a2f01af12536f2bf116 diff --cc CHANGES index c12e96e957c,2703361a916..b396b2f15a6 --- a/CHANGES +++ b/CHANGES @@@ -1,12 -1,20 +1,25 @@@ -*- 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 diff --cc modules/http2/config.m4 index 9c5eb86740e,9c5eb86740e..35b25e11a3a --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@@ -20,6 -20,6 +20,8 @@@ dnl # list of module object file 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 diff --cc modules/http2/h2_config.c index 6db702eca80,6db702eca80..25fdd29908f --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@@ -49,6 -49,6 +49,10 @@@ static h2_config defconf = 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; @@@ -100,6 -100,6 +104,11 @@@ static void *h2_config_create(apr_pool_ 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; } @@@ -127,22 -127,22 +136,31 @@@ void *h2_config_merge(apr_pool_t *pool 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) { @@@ -162,6 -162,6 +180,10 @@@ 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: @@@ -170,6 -170,6 +192,10 @@@ 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; } @@@ -290,8 -290,8 +316,8 @@@ static const char *h2_conf_set_session_ { 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; @@@ -332,8 -332,8 +358,60 @@@ static const char *h2_conf_set_direct(c 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, @@@ -354,10 -354,10 +432,18 @@@ 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 }; diff --cc modules/http2/h2_config.h index 931af59d302,931af59d302..9c59817a992 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@@ -34,6 -34,6 +34,10 @@@ typedef enum 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. */ @@@ -51,6 -51,6 +55,10 @@@ typedef struct h2_config 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; @@@ -67,6 -67,6 +75,7 @@@ h2_config *h2_config_sget(server_rec *s 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); diff --cc modules/http2/h2_conn.c index 9794699f884,8bffbc42693..877447e2ffe --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@@ -32,6 -32,6 +32,7 @@@ #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" @@@ -43,7 -43,7 +44,6 @@@ static apr_status_t h2_conn_loop(h2_ses 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) @@@ -64,9 -64,9 +64,6 @@@ mpm_type = H2_MPM_PREFORK; mpm_module = m; } -- else if (!strcmp("mod_ssl.c", m->name)) { -- ssl_module = m; -- } } checked = 1; } @@@ -103,9 -103,9 +100,6 @@@ apr_status_t h2_conn_child_init(apr_poo mpm_type = H2_MPM_PREFORK; mpm_module = m; } -- else if (!strcmp("mod_ssl.c", m->name)) { -- ssl_module = m; -- } } if (minw <= 0) { @@@ -177,7 -177,7 +171,12 @@@ apr_status_t h2_conn_main(conn_rec *c 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; @@@ -241,7 -241,7 +240,7 @@@ static apr_status_t h2_conn_loop(h2_ses 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; } @@@ -344,12 -328,12 +343,9 @@@ 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; } @@@ -412,11 -396,11 +408,11 @@@ conn_rec *h2_conn_create(conn_rec *mast 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 @@@ -425,28 -409,28 +421,18 @@@ * 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 @@@ -457,7 -441,7 +443,7 @@@ /* all fine */ break; case H2_MPM_EVENT: -- fix_event_conn(&env->c, master); ++ fix_event_conn(task->c, master); break; default: /* fingers crossed */ @@@ -469,7 -453,7 +455,7 @@@ * 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; } diff --cc modules/http2/h2_conn.h index 49a70db8507,49a70db8507..752f28a74cb --- a/modules/http2/h2_conn.h +++ b/modules/http2/h2_conn.h @@@ -17,7 -17,7 +17,6 @@@ #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 @@@ -52,7 -52,7 +51,7 @@@ h2_mpm_type_t h2_conn_mpm_type(void) 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); diff --cc modules/http2/h2_conn_io.c index 08d00a4f3a6,08d00a4f3a6..0299343063c --- a/modules/http2/h2_conn_io.c +++ b/modules/http2/h2_conn_io.c @@@ -28,31 -28,31 +28,33 @@@ #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); @@@ -61,13 -61,13 +63,33 @@@ 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, @@@ -159,7 -159,7 +181,7 @@@ apr_status_t h2_conn_io_read(h2_conn_i 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); @@@ -174,15 -174,15 +196,22 @@@ 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; @@@ -193,14 -193,14 +222,18 @@@ 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; @@@ -209,7 -209,7 +242,7 @@@ (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, @@@ -238,16 -238,16 +271,22 @@@ apr_status_t h2_conn_io_write(h2_conn_i 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) { @@@ -266,46 -266,46 +305,72 @@@ } 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; } diff --cc modules/http2/h2_conn_io.h index 084445ef2b0,084445ef2b0..c5a861605c6 --- a/modules/http2/h2_conn_io.h +++ b/modules/http2/h2_conn_io.h @@@ -26,11 -26,11 +26,16 @@@ typedef struct 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; @@@ -38,7 -38,7 +43,8 @@@ } 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, @@@ -52,6 -52,6 +58,10 @@@ apr_status_t h2_conn_io_read(h2_conn_i 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); diff --cc modules/http2/h2_ctx.c index 422835c2df8,422835c2df8..08bdd8612de --- a/modules/http2/h2_ctx.c +++ b/modules/http2/h2_ctx.c @@@ -32,11 -32,11 +32,11 @@@ static h2_ctx *h2_ctx_create(const conn 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; } @@@ -76,7 -76,7 +76,7 @@@ h2_ctx *h2_ctx_server_set(h2_ctx *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) @@@ -84,7 -84,7 +84,7 @@@ 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; } diff --cc modules/http2/h2_ctx.h index 86c59206eda,86c59206eda..e4bc7506ae6 --- a/modules/http2/h2_ctx.h +++ b/modules/http2/h2_ctx.h @@@ -16,7 -16,7 +16,7 @@@ #ifndef __mod_h2__h2_ctx__ #define __mod_h2__h2_ctx__ --struct h2_task_env; ++struct h2_task; struct h2_config; /** @@@ -30,7 -30,7 +30,7 @@@ 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 */ @@@ -38,7 -38,7 +38,7 @@@ 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 @@@ -58,6 -58,6 +58,6 @@@ const char *h2_ctx_protocol_get(const c 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__) */ diff --cc modules/http2/h2_from_h1.c index be11f5c317e,be11f5c317e..c763ca56ea2 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@@ -78,9 -78,9 +78,9 @@@ h2_response *h2_from_h1_get_response(h2 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) @@@ -492,8 -492,8 +492,8 @@@ static h2_response *create_response(h2_ 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; @@@ -503,7 -503,7 +503,7 @@@ 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; diff --cc modules/http2/h2_h2.c index ac460ef899f,221f5118df9..64ac321db19 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@@ -53,6 -53,6 +53,384 @@@ APR_DECLARE_OPTIONAL_FN(int, ssl_is_htt 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 @@@ -72,12 -72,12 +450,15 @@@ apr_status_t h2_h2_init(apr_pool_t *poo 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; } @@@ -86,19 -86,18 +467,95 @@@ int h2_h2_is_tls(conn_rec *c 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) { @@@ -106,7 -105,7 +563,8 @@@ * 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, @@@ -135,9 -134,9 +593,6 @@@ static int h2_h2_remove_timeout(conn_re 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)) { @@@ -145,53 -144,53 +600,50 @@@ 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. @@@ -210,21 -209,21 +662,21 @@@ 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; diff --cc modules/http2/h2_h2.h index 9a1184d8b69,9a1184d8b69..4cf2785929d --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@@ -34,6 -34,6 +34,31 @@@ extern const char *h2_tls_protos[] */ 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. */ @@@ -43,15 -43,15 +68,35 @@@ apr_status_t h2_h2_init(apr_pool_t *poo */ 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__) */ diff --cc modules/http2/h2_io.c index 93bcca86085,42734430fa4..e952ad0f6b4 --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@@ -23,6 -23,6 +23,7 @@@ #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) @@@ -39,7 -39,7 +40,10 @@@ 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) @@@ -47,12 -47,6 +51,23 @@@ 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)); @@@ -90,8 -80,8 +105,7 @@@ apr_status_t h2_io_in_read(h2_io *io, a 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; @@@ -119,7 -105,7 +133,7 @@@ apr_status_t h2_io_in_write(h2_io *io, 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; } @@@ -142,32 -124,13 +156,50 @@@ apr_status_t h2_io_out_readx(h2_io *io 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) { diff --cc modules/http2/h2_io.h index 23655d21dfa,946ee44334e..d8769dd1182 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@@ -21,8 -21,8 +21,9 @@@ struct apr_thread_cond_t 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; @@@ -33,8 -33,7 +34,11 @@@ struct 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 */ @@@ -60,11 -58,6 +64,16 @@@ h2_io *h2_io_create(int id, apr_pool_t */ 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. @@@ -114,6 -107,6 +123,10 @@@ apr_status_t h2_io_out_readx(h2_io *io 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); diff --cc modules/http2/h2_mplx.c index ae33766e4ac,2d07b1eb6c3..ad6bd30e32d --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@@ -60,10 -58,10 +60,6 @@@ static void h2_mplx_destroy(h2_mplx *m { 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; @@@ -128,10 -126,10 +124,9 @@@ h2_mplx *h2_mplx_create(conn_rec *c, ap 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; @@@ -226,52 -230,52 +221,18 @@@ void h2_mplx_abort(h2_mplx *m } --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. */ @@@ -282,31 -286,21 +243,36 @@@ } } --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; @@@ -316,21 -310,21 +282,17 @@@ void h2_mplx_task_done(h2_mplx *m, int { 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); } @@@ -480,23 -467,19 +442,53 @@@ apr_status_t h2_mplx_out_readx(h2_mplx 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; @@@ -507,33 -490,24 +499,33 @@@ } 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); @@@ -588,12 -562,12 +580,12 @@@ static apr_status_t out_open(h2_mplx *m 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); @@@ -681,16 -650,11 +673,16 @@@ apr_status_t h2_mplx_out_close(h2_mplx * 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 @@@ -708,41 -672,6 +700,38 @@@ return status; } +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 (m->aborted) { - /* if we were the last output, the whole session might - * have gone down in the meantime. - */ - return APR_SUCCESS; ++ 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; @@@ -813,61 -742,61 +802,116 @@@ static void have_out_data_for(h2_mplx * } } --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; } diff --cc modules/http2/h2_mplx.h index 5cb40af16fa,62977d6157c..2bb650535ef --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@@ -41,6 -41,6 +41,7 @@@ struct h2_config struct h2_response; struct h2_task; struct h2_stream; ++struct h2_request; struct h2_io_set; struct apr_thread_cond_t; struct h2_workers; @@@ -70,12 -70,12 +71,13 @@@ struct h2_mplx 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. ******************************************************************************/ @@@ -118,15 -117,15 +120,16 @@@ void h2_mplx_task_done(h2_mplx *m, int /******************************************************************************* * 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. */ @@@ -144,13 -143,13 +147,29 @@@ apr_status_t h2_mplx_out_trywait(h2_mpl ******************************************************************************/ /** -- * 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. @@@ -221,7 -220,7 +240,15 @@@ struct h2_stream *h2_mplx_next_submit(h 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. */ diff --cc modules/http2/h2_request.c index ca9a362cb56,ca9a362cb56..4a1e19058a6 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@@ -151,13 -151,13 +151,21 @@@ apr_status_t h2_request_write_data(h2_r 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) diff --cc modules/http2/h2_response.c index 9cedd855615,9cedd855615..bd0a5fba979 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@@ -25,6 -25,6 +25,7 @@@ #include #include "h2_private.h" ++#include "h2_h2.h" #include "h2_util.h" #include "h2_response.h" @@@ -41,6 -41,6 +42,7 @@@ static int ignore_header(const char *na } h2_response *h2_response_create(int stream_id, ++ int rst_error, const char *http_status, apr_array_header_t *hlines, apr_pool_t *pool) @@@ -53,7 -53,7 +55,8 @@@ } 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) { @@@ -112,6 -112,6 +115,19 @@@ h2_response *h2_response_rcreate(int st 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; } diff --cc modules/http2/h2_response.h index 456d2226ed2,456d2226ed2..64d68cc9c22 --- a/modules/http2/h2_response.h +++ b/modules/http2/h2_response.h @@@ -26,6 -26,6 +26,7 @@@ typedef struct h2_ngheader typedef struct h2_response { int stream_id; ++ int rst_error; const char *status; apr_off_t content_length; apr_table_t *rheader; @@@ -33,9 -33,9 +34,10 @@@ } 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); diff --cc modules/http2/h2_session.c index fe567137de1,c3456a0654d..fc0287042bb --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@@ -24,6 -24,6 +24,8 @@@ #include #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" @@@ -56,41 -57,41 +58,94 @@@ static int h2_session_status_from_apr_s 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. */ @@@ -99,10 -100,10 +154,11 @@@ static ssize_t send_cb(nghttp2_session 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; } @@@ -168,7 -169,7 +224,7 @@@ static int on_data_chunk_recv_cb(nghttp 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; } @@@ -186,6 -187,6 +242,20 @@@ static int before_frame_send_cb(nghttp2 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])); @@@ -201,9 -202,9 +271,14 @@@ static int on_frame_send_cb(nghttp2_ses 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; } @@@ -241,12 -242,11 +316,13 @@@ static apr_status_t stream_destroy(h2_s 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, @@@ -387,6 -387,6 +463,7 @@@ static int on_frame_recv_cb(nghttp2_ses 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", @@@ -440,18 -440,18 +517,15 @@@ 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, @@@ -481,35 -481,35 +555,68 @@@ 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); @@@ -518,6 -519,6 +625,19 @@@ 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) @@@ -543,6 -544,6 +663,7 @@@ static apr_status_t init_callbacks(conn 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; } @@@ -660,13 -661,13 +781,11 @@@ void h2_session_destroy(h2_session *ses 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); } @@@ -677,14 -678,14 +796,18 @@@ static apr_status_t h2_session_abort_in 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); @@@ -693,22 -694,22 +816,15 @@@ "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); } @@@ -854,7 -853,7 +970,7 @@@ static int resume_on_data(void *ctx, h2 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; @@@ -893,6 -892,6 +1009,12 @@@ static void update_window(void *ctx, in 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); @@@ -902,16 -901,16 +1024,17 @@@ apr_status_t h2_session_write(h2_sessio { 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; } @@@ -927,27 -926,26 +1050,24 @@@ 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 */ @@@ -966,11 -964,11 +1086,10 @@@ status = APR_ECONNABORTED; } } -- flush_output = 1; } -- if (flush_output) { -- h2_conn_io_flush(&session->io); ++ if (session->flush) { ++ h2_session_flush(session); } return status; @@@ -1015,17 -1013,17 +1134,28 @@@ static apr_status_t session_receive(con 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, @@@ -1041,12 -1039,12 +1171,21 @@@ 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); @@@ -1154,8 -1148,7 +1293,8 @@@ apr_status_t h2_session_handle_response } 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)) { @@@ -1168,6 -1161,6 +1307,24 @@@ 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); diff --cc modules/http2/h2_session.h index ddf29a06df1,12768a90fb3..a1d718a9ca6 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@@ -58,6 -58,6 +58,9 @@@ struct h2_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 */ @@@ -74,6 -74,6 +77,8 @@@ 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 */ }; @@@ -108,6 -108,6 +113,13 @@@ h2_session *h2_session_rcreate(request_ */ 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. @@@ -147,8 -147,8 +159,7 @@@ apr_status_t h2_session_read(h2_sessio * 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. */ @@@ -158,4 -158,6 +169,12 @@@ apr_status_t h2_session_handle_response /* 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__) */ diff --cc modules/http2/h2_stream.c index d5a716f4490,52781d8474f..5a5af6372b7 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@@ -16,8 -16,8 +16,6 @@@ #include #include --#define APR_POOL_DEBUG 7 -- #include #include #include @@@ -30,6 -30,6 +28,7 @@@ #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" @@@ -46,46 -46,51 +45,47 @@@ static void set_state(h2_stream *stream } } --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; @@@ -93,41 -98,25 +93,38 @@@ 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) @@@ -152,15 -141,12 +149,16 @@@ apr_status_t h2_stream_rwrite(h2_strea { 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); @@@ -171,35 -154,32 +169,29 @@@ /* 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); } @@@ -224,7 -201,7 +216,7 @@@ apr_status_t h2_stream_write_header(h2_ 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, @@@ -249,10 -224,7 +241,11 @@@ apr_status_t h2_stream_prep_read(h2_str 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) { @@@ -262,16 -234,16 +255,15 @@@ } 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; } @@@ -279,16 -251,13 +271,73 @@@ apr_status_t h2_stream_readx(h2_stream 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) { diff --cc modules/http2/h2_stream.h index f885bc439f9,0608f2f3400..d09d848a66a --- a/modules/http2/h2_stream.h +++ b/modules/http2/h2_stream.h @@@ -48,6 -48,6 +48,7 @@@ typedef enum struct h2_mplx; struct h2_request; struct h2_response; ++struct h2_session; struct h2_task; typedef struct h2_stream h2_stream; @@@ -55,30 -55,28 +56,33 @@@ 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); @@@ -88,7 -86,7 +92,8 @@@ apr_status_t h2_stream_write_header(h2_ 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); @@@ -103,6 -101,6 +108,10 @@@ apr_status_t h2_stream_prep_read(h2_str 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); diff --cc modules/http2/h2_stream_set.c index dddd2e39908,dddd2e39908..5ef48a13e17 --- a/modules/http2/h2_stream_set.c +++ b/modules/http2/h2_stream_set.c @@@ -54,9 -54,9 +54,7 @@@ void h2_stream_set_destroy(h2_stream_se 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) @@@ -101,12 -101,12 +99,12 @@@ apr_status_t h2_stream_set_add(h2_strea 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; diff --cc modules/http2/h2_stream_set.h index 56075834555,56075834555..8bc6409223b --- a/modules/http2/h2_stream_set.h +++ b/modules/http2/h2_stream_set.h @@@ -35,7 -35,7 +35,7 @@@ apr_status_t h2_stream_set_add(h2_strea 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); diff --cc modules/http2/h2_switch.c index 23c34490eaa,23c34490eaa..97f07f891d7 --- a/modules/http2/h2_switch.c +++ b/modules/http2/h2_switch.c @@@ -34,17 -34,17 +34,6 @@@ #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 */ @@@ -52,7 -52,7 +41,6 @@@ apr_status_t h2_switch_init(apr_pool_t { (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; } @@@ -63,7 -63,7 +51,8 @@@ static int h2_protocol_propose(conn_re 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))) { @@@ -74,12 -74,12 +63,23 @@@ 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, diff --cc modules/http2/h2_task.c index 58b39c53970,bbea7b20f8a..ee78f4347e1 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@@ -52,35 -52,35 +52,37 @@@ static apr_status_t h2_filter_stream_in 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 */ @@@ -119,21 -119,21 +121,15 @@@ static int h2_task_pre_conn(conn_rec* c (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; } @@@ -143,14 -143,14 +139,14 @@@ static int h2_task_process_conn(conn_re 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; } @@@ -170,8 -170,8 +166,6 @@@ h2_task *h2_task_create(long session_id 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; @@@ -208,121 -208,121 +202,73 @@@ apr_status_t h2_task_do(h2_task *task, { 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; @@@ -340,7 -340,7 +286,7 @@@ 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); @@@ -379,19 -379,18 +325,19 @@@ /* 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->the_request = apr_psprintf(r->pool, "%s %s %s", - r->method, env->path, r->protocol); ++ 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. @@@ -418,7 -417,7 +364,7 @@@ /* 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; @@@ -435,13 -434,13 +381,13 @@@ traceout } --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); diff --cc modules/http2/h2_task.h index b66ce38c251,b66ce38c251..1877a3920b5 --- a/modules/http2/h2_task.h +++ b/modules/http2/h2_task.h @@@ -45,16 -45,16 +45,10 @@@ struct h2_worker 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; @@@ -62,103 -62,103 +56,18 @@@ 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); @@@ -174,14 -174,14 +83,7 @@@ void h2_task_set_request(h2_task *task 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__) */ diff --cc modules/http2/h2_task_input.c index 487f7e60692,487f7e60692..1eac749f42a --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@@ -42,28 -42,28 +42,28 @@@ static int ser_header(void *ctx, const 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 { @@@ -71,7 -71,7 +71,7 @@@ * 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) { @@@ -81,9 -81,9 +81,9 @@@ 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; @@@ -106,17 -106,17 +106,17 @@@ apr_status_t h2_task_input_read(h2_task 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) { @@@ -124,12 -124,12 +124,12 @@@ 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; } @@@ -139,19 -139,19 +139,19 @@@ 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; } @@@ -164,23 -164,23 +164,23 @@@ } 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) { @@@ -199,7 -199,7 +199,7 @@@ 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; } diff --cc modules/http2/h2_task_input.h index 32adc1770df,32adc1770df..ed0a99faba2 --- a/modules/http2/h2_task_input.h +++ b/modules/http2/h2_task_input.h @@@ -22,16 -22,16 +22,16 @@@ */ 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); diff --cc modules/http2/h2_task_output.c index 879cb5fa21b,879cb5fa21b..053e2d69e4e --- a/modules/http2/h2_task_output.c +++ b/modules/http2/h2_task_output.c @@@ -33,16 -33,16 +33,16 @@@ #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; } @@@ -73,18 -73,18 +73,18 @@@ static apr_status_t open_if_needed(h2_t 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; } @@@ -93,7 -93,7 +93,7 @@@ void h2_task_output_close(h2_task_outpu { 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; } } @@@ -113,7 -113,7 +113,7 @@@ apr_status_t h2_task_output_write(h2_ta 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; } @@@ -121,12 -121,12 +121,12 @@@ 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); } diff --cc modules/http2/h2_task_output.h index 86571a1e107,86571a1e107..79cb6816c7a --- a/modules/http2/h2_task_output.h +++ b/modules/http2/h2_task_output.h @@@ -23,7 -23,7 +23,7 @@@ */ struct apr_thread_cond_t; struct h2_mplx; --struct h2_task_env; ++struct h2_task; struct h2_from_h1; typedef enum { @@@ -35,12 -35,12 +35,12 @@@ 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); diff --cc modules/http2/h2_task_queue.c index a81cc100fb9,a81cc100fb9..1653aa26e28 --- a/modules/http2/h2_task_queue.c +++ b/modules/http2/h2_task_queue.c @@@ -23,65 -23,65 +23,139 @@@ #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; } diff --cc modules/http2/h2_task_queue.h index d93d74ac502,d93d74ac502..36fad2c4d8d --- a/modules/http2/h2_task_queue.h +++ b/modules/http2/h2_task_queue.h @@@ -19,29 -19,29 +19,38 @@@ 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. @@@ -50,99 -50,99 +59,33 @@@ 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__) */ diff --cc modules/http2/h2_to_h1.c index 8dacfe801fc,8dacfe801fc..159fde31dd2 --- a/modules/http2/h2_to_h1.c +++ b/modules/http2/h2_to_h1.c @@@ -156,7 -156,7 +156,7 @@@ apr_status_t h2_to_h1_add_headers(h2_to 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", @@@ -189,23 -189,23 +189,8 @@@ 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; } diff --cc modules/http2/h2_to_h1.h index 74586e2b7ea,74586e2b7ea..6fc06fbf87a --- a/modules/http2/h2_to_h1.h +++ b/modules/http2/h2_to_h1.h @@@ -68,8 -68,8 +68,7 @@@ apr_status_t h2_to_h1_add_headers(h2_to /** 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. */ diff --cc modules/http2/h2_util.c index 6f29b461d66,9d141be93bf..2713aeb2dab --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@@ -648,71 -647,3 +648,145 @@@ apr_status_t h2_util_bb_readx(apr_bucke 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; ++} ++ diff --cc modules/http2/h2_util.h index e1a6b3c4d20,9a1b5c6d35b..a488e2a041e --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@@ -133,16 -121,4 +133,31 @@@ apr_status_t h2_util_bb_readx(apr_bucke 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__) */ diff --cc modules/http2/h2_version.h index 10219daf191,7a03865c87c..e72f6628739 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@@ -20,7 -20,7 +20,7 @@@ * @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 @@@ -28,7 -28,7 +28,7 @@@ * 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 */ diff --cc modules/http2/mod_http2.dsp index c3e139e9553,c3e139e9553..fbff711df77 --- a/modules/http2/mod_http2.dsp +++ b/modules/http2/mod_http2.dsp @@@ -105,6 -105,6 +105,14 @@@ SOURCE=./h2_alt_svc. # 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