int h2_allows_h2_upgrade(conn_rec *c) { const 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)); }
static int h2_alt_svc_handler(request_rec *r) { h2_ctx *ctx; h2_config *cfg; int i; if (r->connection->keepalives > 0) { /* Only announce Alt-Svc on the first response */ return DECLINED; } ctx = h2_ctx_rget(r); if (h2_ctx_is_active(ctx) || h2_ctx_is_task(ctx)) { return DECLINED; } cfg = h2_config_rget(r); if (r->hostname && cfg && cfg->alt_svcs && cfg->alt_svcs->nelts > 0) { const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used"); if (!alt_svc_used) { /* We have alt-svcs defined and client is not already using * one, announce the services that were configured and match. * The security of this connection determines if we allow * other host names or ports only. */ const char *alt_svc = ""; const char *svc_ma = ""; int secure = h2_h2_is_tls(r->connection); int ma = h2_config_geti(cfg, H2_CONF_ALT_SVC_MAX_AGE); if (ma >= 0) { svc_ma = apr_psprintf(r->pool, "; ma=%d", ma); } ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_alt_svc: announce %s for %s:%d", (secure? "secure" : "insecure"), r->hostname, (int)r->server->port); for (i = 0; i < cfg->alt_svcs->nelts; ++i) { h2_alt_svc *as = h2_alt_svc_IDX(cfg->alt_svcs, i); const char *ahost = as->host; if (ahost && !apr_strnatcasecmp(ahost, r->hostname)) { ahost = NULL; } if (secure || !ahost) { alt_svc = apr_psprintf(r->pool, "%s%s%s=\"%s:%d\"%s", alt_svc, (*alt_svc? ", " : ""), as->alpn, ahost? ahost : "", as->port, svc_ma); } } if (*alt_svc) { apr_table_set(r->headers_out, "Alt-Svc", alt_svc); } } } return DECLINED; }
/** * A h2_mplx needs to be thread-safe *and* if will be called by * the h2_session thread *and* the h2_worker threads. Therefore: * - calls are protected by a mutex lock, m->lock * - the pool needs its own allocator, since apr_allocator_t are * not re-entrant. The separate allocator works without a * separate lock since we already protect h2_mplx itself. * Since HTTP/2 connections can be expected to live longer than * their HTTP/1 cousins, the separate allocator seems to work better * than protecting a shared h2_session one with an own lock. */ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, h2_workers *workers) { apr_status_t status = APR_SUCCESS; h2_config *conf = h2_config_get(c); apr_allocator_t *allocator = NULL; h2_mplx *m; AP_DEBUG_ASSERT(conf); status = apr_allocator_create(&allocator); if (status != APR_SUCCESS) { return NULL; } m = apr_pcalloc(parent, sizeof(h2_mplx)); if (m) { m->id = c->id; APR_RING_ELEM_INIT(m, link); apr_atomic_set32(&m->refs, 1); m->c = c; apr_pool_create_ex(&m->pool, parent, NULL, allocator); if (!m->pool) { return NULL; } apr_allocator_owner_set(allocator, m->pool); status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT, m->pool); if (status != APR_SUCCESS) { h2_mplx_destroy(m); return NULL; } m->bucket_alloc = apr_bucket_alloc_create(m->pool); m->q = h2_tq_create(m->id, m->pool); 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; m->file_handles_allowed = h2_config_geti(conf, H2_CONF_SESSION_FILES); } return m; }
/** * A h2_mplx needs to be thread-safe *and* if will be called by * the h2_session thread *and* the h2_worker threads. Therefore: * - calls are protected by a mutex lock, m->lock * - the pool needs its own allocator, since apr_allocator_t are * not re-entrant. The separate allocator works without a * separate lock since we already protect h2_mplx itself. * Since HTTP/2 connections can be expected to live longer than * their HTTP/1 cousins, the separate allocator seems to work better * than protecting a shared h2_session one with an own lock. */ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, const h2_config *conf, apr_interval_time_t stream_timeout, h2_workers *workers) { apr_status_t status = APR_SUCCESS; apr_allocator_t *allocator = NULL; h2_mplx *m; AP_DEBUG_ASSERT(conf); status = apr_allocator_create(&allocator); if (status != APR_SUCCESS) { return NULL; } m = apr_pcalloc(parent, sizeof(h2_mplx)); if (m) { m->id = c->id; APR_RING_ELEM_INIT(m, link); m->c = c; apr_pool_create_ex(&m->pool, parent, NULL, allocator); if (!m->pool) { return NULL; } apr_allocator_owner_set(allocator, m->pool); status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT, m->pool); if (status != APR_SUCCESS) { h2_mplx_destroy(m); return NULL; } 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->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM); m->stream_timeout = stream_timeout; m->workers = workers; m->tx_handles_reserved = 0; m->tx_chunk_size = 4; } return m; }
int h2_is_acceptable_connection(conn_rec *c, int require_all) { int is_tls = h2_h2_is_tls(c); const 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, (char*)"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, APLOGNO(03050) "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, APLOGNO(03051) "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, (char*)"SSL_CIPHER"); if (val && *val) { const char *source; if (cipher_is_blacklisted(val, &source)) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03052) "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, APLOGNO(03053) "h2_h2(%ld): tls cipher is indetermined", (long)c->id); return 0; } } return 1; }
int h2_h2_pre_conn(conn_rec* c, void *arg) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, start"); h2_ctx *ctx = h2_ctx_get(c, 0); if (!ctx) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, no ctx"); /* We have not seen this one yet, are we active? */ h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, h2 not enabled"); return DECLINED; } /* Are we using TLS on this connection? */ if (!h2_h2_is_tls(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, no TLS"); return DECLINED; } /* Does mod_ssl offer ALPN/NPN support? */ if (opt_ssl_register_alpn == NULL && opt_ssl_register_npn == NULL) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_h2, pre_connection, no ALPN/NPN support in mod_ssl"); return DECLINED; } ctx = h2_ctx_get(c, 1); if (opt_ssl_register_alpn) { opt_ssl_register_alpn(c, h2_h2_alpn_propose, h2_h2_alpn_negotiated); } if (opt_ssl_register_npn) { opt_ssl_register_npn(c, h2_h2_npn_advertise, h2_h2_npn_negotiated); } ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, ALPN callback registered"); ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, end"); } else if (h2_ctx_is_task(c)) { /* A connection that represents a http2 stream from another connection. */ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, "h2_h2, pre_connection, found stream task"); h2_task *task = h2_ctx_get_task(ctx); return h2_task_pre_conn(task, c); } return DECLINED; }
int h2_allows_h2_direct(conn_rec *c) { const h2_config *cfg = h2_config_get(c); int is_tls = h2_h2_is_tls(c); const char *needed_protocol = is_tls? "h2" : "h2c"; int h2_direct = h2_config_geti(cfg, H2_CONF_DIRECT); if (h2_direct < 0) { h2_direct = is_tls? 0 : 1; } return (h2_direct && ap_is_allowed_protocol(c, NULL, NULL, needed_protocol)); }
apr_status_t h2_task_do(h2_task *task, h2_worker *worker) { apr_status_t status = APR_SUCCESS; AP_DEBUG_ASSERT(task); task->serialize_headers = h2_config_geti(task->request->config, H2_CONF_SER_HEADERS); status = h2_worker_setup_task(worker, task); /* save in connection that this one is a pseudo connection */ h2_ctx_create_for(task->c, task); if (status == APR_SUCCESS) { task->input = h2_task_input_create(task, task->pool, task->c->bucket_alloc); task->output = h2_task_output_create(task, task->pool); ap_process_connection(task->c, h2_worker_get_socket(worker)); ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c, "h2_task(%s): processing done", task->id); } else { ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, task->c, APLOGNO(02957) "h2_task(%s): error setting up h2_task", task->id); } if (task->input) { h2_task_input_destroy(task->input); task->input = NULL; } if (task->output) { h2_task_output_close(task->output); h2_task_output_destroy(task->output); task->output = NULL; } if (task->io) { apr_thread_cond_signal(task->io); } h2_worker_release_task(worker, task); h2_mplx_task_done(task->mplx, task->stream_id); return status; }
static int h2_h2_npn_advertise(conn_rec *c, apr_array_header_t *protos) { check_sni_host(c); h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { return DECLINED; } for (int i = 0; i < h2_protos_len; ++i) { const char *proto = h2_protos[i]; ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "NPN proposing %s from client selection", proto); APR_ARRAY_PUSH(protos, const char*) = proto; } return OK; }
int h2_allows_h2_direct(conn_rec *c) { const 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 !!h2_direct; }
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c) { 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); } else { 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 = (int)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; }
static int h2_h2_late_fixups(request_rec *r) { /* slave connection? */ if (r->connection->master) { h2_ctx *ctx = h2_ctx_rget(r); struct h2_task *task = h2_ctx_get_task(ctx); if (task) { /* check if we copy vs. setaside files in this location */ task->output.copy_files = h2_config_geti(h2_config_rget(r), H2_CONF_COPY_FILES); if (task->output.copy_files) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c, "h2_slave_out(%s): copy_files on", task->id); h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL); } check_push(r, "late_fixup"); } } return DECLINED; }
static int h2_h2_alpn_propose(conn_rec *c, apr_array_header_t *client_protos, apr_array_header_t *protos) { check_sni_host(c); h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { return DECLINED; } for (int i = 0; i < h2_protos_len; ++i) { const char *proto = h2_protos[i]; if (h2_util_array_index(client_protos, proto) >= 0) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "ALPN proposing %s", proto); APR_ARRAY_PUSH(protos, const char*) = proto; return OK; /* propose only one, the first match from our list */ } } return OK; }
static int h2_h2_alpn_negotiated(conn_rec *c, const char *proto_name, apr_size_t proto_name_len) { if (APLOGctrace1(c)) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "ALPN negotiated is %s", apr_pstrndup(c->pool, proto_name, proto_name_len)); } h2_config *cfg = h2_config_get(c); if (!h2_config_geti(cfg, H2_CONF_ENABLED)) { return DECLINED; } if (!h2_ctx_is_session(c) ) { return DECLINED; } if (h2_ctx_is_negotiated(c)) { // called twice? maybe alpn+npn overlap... return DECLINED; } for (int i = 0; i < h2_protos_len; ++i) { const char *proto = h2_protos[i]; if (proto_name_len == strlen(proto) && strncmp(proto, proto_name, proto_name_len) == 0) { ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "protocol set va ALPN to %s", proto); h2_ctx_set_protocol(c, proto); break; } } return OK; }
apr_status_t h2_task_do(h2_task *task, h2_worker *worker) { 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; /* Not cloning these task fields: * If the stream is destroyed before the task is done, this might * be a problem. However that should never happen as stream destruction * explicitly checks if task processing has finished. */ env.method = task->method; env.path = task->path; env.authority = task->authority; env.headers = task->headers; env.input_eos = task->input_eos; task->io = env.io = h2_worker_get_cond(worker); env.conn = task->conn; task->conn = NULL; env.serialize_headers = !!h2_config_geti(cfg, H2_CONF_SER_HEADERS); status = h2_conn_prep(env.conn, task->mplx->c, worker); /* save in connection that this one is for us, prevents * other hooks from messing with it. */ h2_ctx_create_for(env.conn->c, &env); if (status == APR_SUCCESS) { apr_pool_t *pool = env.conn->pool; apr_bucket_alloc_t *bucket_alloc = env.conn->bucket_alloc; env.input = h2_task_input_create(&env, pool, bucket_alloc); env.output = h2_task_output_create(&env, pool, bucket_alloc); status = h2_conn_process(env.conn); } ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, env.conn->c, "h2_task(%s):processing done", task->id); if (env.input) { h2_task_input_destroy(env.input); env.input = NULL; } if (env.conn) { h2_conn_post(env.conn, worker); env.conn = NULL; } if (env.output) { h2_task_output_close(env.output); h2_task_output_destroy(env.output); env.output = NULL; } h2_task_set_finished(task); if (env.io) { apr_thread_cond_signal(env.io); } return status; }
apr_status_t h2_task_do(h2_task *task, h2_worker *worker) { 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); /* Create a subpool from the worker one to be used for all things * with life-time of this task_env execution. */ apr_pool_create(&env.pool, h2_worker_get_pool(worker)); /* Link the env to the worker which provides useful things such * as mutex, a socket etc. */ env.io = h2_worker_get_cond(worker); /* Clone fields, so that lifetimes become (more) independent. */ env.method = apr_pstrdup(env.pool, task->method); env.path = apr_pstrdup(env.pool, task->path); env.authority = apr_pstrdup(env.pool, task->authority); env.headers = apr_table_clone(env.pool, task->headers); /* Setup the pseudo connection to use our own pool and bucket_alloc */ if (task->c) { env.c = *task->c; task->c = NULL; status = h2_conn_setup(&env, worker); } else { status = h2_conn_init(&env, 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); 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); } else { ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c, "h2_task(%s): error setting up h2_task_env", env.id); } if (env.input) { h2_task_input_destroy(env.input); env.input = NULL; } if (env.output) { h2_task_output_close(env.output); h2_task_output_destroy(env.output); env.output = NULL; } h2_task_set_finished(task); if (env.io) { apr_thread_cond_signal(env.io); } if (env.pool) { apr_pool_destroy(env.pool); env.pool = NULL; } if (env.c.id) { h2_conn_post(&env.c, worker); } h2_mplx_task_done(env.mplx, env.stream_id); return status; }
apr_status_t h2_session_start(h2_session *session) { assert(session); /* Start the conversation by submitting our SETTINGS frame */ apr_status_t status = APR_SUCCESS; h2_config *config = h2_config_get(session->c); int rv = 0; if (session->r) { /* 'h2c' mode: we should have a 'HTTP2-Settings' header with * base64 encoded client settings. */ const char *s = apr_table_get(session->r->headers_in, "HTTP2-Settings"); if (!s) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, "HTTP2-Settings header missing in request"); return APR_EINVAL; } int cslen = apr_base64_decode_len(s); char *cs = apr_pcalloc(session->r->pool, cslen); --cslen; /* apr also counts the terminating 0 */ apr_base64_decode(cs, s); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, "upgrading h2c session with nghttp2 from %s (%d)", s, cslen); rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, cslen, NULL); if (rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "nghttp2_session_upgrade: %s", nghttp2_strerror(rv)); return status; } /* Now we need to auto-open stream 1 for the request we got. */ rv = stream_open(session, 1); if (rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "open stream 1: %s", nghttp2_strerror(rv)); return status; } h2_stream * stream = h2_stream_set_get(session->streams, 1); if (stream == NULL) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, "lookup of stream 1"); return status; } status = h2_stream_rwrite(stream, session->r); if (status != APR_SUCCESS) { return status; } status = stream_end_headers(session, stream, 1); if (status != APR_SUCCESS) { return status; } status = h2_stream_write_eos(stream); if (status != APR_SUCCESS) { return status; } } nghttp2_settings_entry settings[] = { { NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE, h2_config_geti(config, H2_CONF_MAX_HL_SIZE) }, { NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, h2_config_geti(config, H2_CONF_WIN_SIZE) }, {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, h2_config_geti(config, H2_CONF_MAX_STREAMS) }, }; rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, sizeof(settings)/sizeof(settings[0])); if (rv != 0) { status = APR_EGENERAL; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, "nghttp2_submit_settings: %s", nghttp2_strerror(rv)); } return status; }
/** * A h2_mplx needs to be thread-safe *and* if will be called by * the h2_session thread *and* the h2_worker threads. Therefore: * - calls are protected by a mutex lock, m->lock * - the pool needs its own allocator, since apr_allocator_t are * not re-entrant. The separate allocator works without a * separate lock since we already protect h2_mplx itself. * Since HTTP/2 connections can be expected to live longer than * their HTTP/1 cousins, the separate allocator seems to work better * than protecting a shared h2_session one with an own lock. */ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, const h2_config *conf, apr_interval_time_t stream_timeout, h2_workers *workers) { apr_status_t status = APR_SUCCESS; apr_allocator_t *allocator = NULL; h2_mplx *m; AP_DEBUG_ASSERT(conf); status = apr_allocator_create(&allocator); if (status != APR_SUCCESS) { return NULL; } m = apr_pcalloc(parent, sizeof(h2_mplx)); if (m) { m->id = c->id; APR_RING_ELEM_INIT(m, link); m->c = c; apr_pool_create_ex(&m->pool, parent, NULL, allocator); if (!m->pool) { return NULL; } apr_pool_tag(m->pool, "h2_mplx"); apr_allocator_owner_set(allocator, m->pool); status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT, m->pool); if (status != APR_SUCCESS) { h2_mplx_destroy(m); return NULL; } status = apr_thread_cond_create(&m->task_thawed, m->pool); if (status != APR_SUCCESS) { h2_mplx_destroy(m); return NULL; } m->bucket_alloc = apr_bucket_alloc_create(m->pool); m->max_streams = h2_config_geti(conf, H2_CONF_MAX_STREAMS); m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM); m->q = h2_iq_create(m->pool, m->max_streams); m->stream_ios = h2_io_set_create(m->pool); m->ready_ios = h2_io_set_create(m->pool); m->stream_timeout = stream_timeout; m->workers = workers; m->workers_max = workers->max_workers; m->workers_def_limit = 4; m->workers_limit = m->workers_def_limit; m->last_limit_change = m->last_idle_block = apr_time_now(); m->limit_change_interval = apr_time_from_msec(200); m->tx_handles_reserved = 0; m->tx_chunk_size = 4; m->spare_slaves = apr_array_make(m->pool, 10, sizeof(conn_rec*)); m->ngn_shed = h2_ngn_shed_create(m->pool, m->c, m->max_streams, m->stream_max_mem); h2_ngn_shed_set_ctx(m->ngn_shed , m); } return m; }
apr_status_t h2_session_start(h2_session *session, int *rv) { apr_status_t status = APR_SUCCESS; h2_config *config; nghttp2_settings_entry settings[3]; AP_DEBUG_ASSERT(session); /* Start the conversation by submitting our SETTINGS frame */ *rv = 0; config = h2_config_get(session->c); if (session->r) { const char *s, *cs; apr_size_t dlen; h2_stream * stream; /* better for vhost matching */ config = h2_config_rget(session->r); /* 'h2c' mode: we should have a 'HTTP2-Settings' header with * base64 encoded client settings. */ s = apr_table_get(session->r->headers_in, "HTTP2-Settings"); if (!s) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, APLOGNO(02931) "HTTP2-Settings header missing in request"); return APR_EINVAL; } cs = NULL; dlen = h2_util_base64url_decode(&cs, s, session->pool); if (APLOGrdebug(session->r)) { char buffer[128]; h2_util_hex_dump(buffer, 128, (char*)cs, dlen); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, session->r, "upgrading h2c session with HTTP2-Settings: %s -> %s (%d)", s, buffer, (int)dlen); } *rv = nghttp2_session_upgrade(session->ngh2, (uint8_t*)cs, dlen, NULL); if (*rv != 0) { status = APR_EINVAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02932) "nghttp2_session_upgrade: %s", nghttp2_strerror(*rv)); return status; } /* Now we need to auto-open stream 1 for the request we got. */ *rv = stream_open(session, 1); if (*rv != 0) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02933) "open stream 1: %s", nghttp2_strerror(*rv)); return status; } stream = h2_stream_set_get(session->streams, 1); if (stream == NULL) { status = APR_EGENERAL; ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, APLOGNO(02934) "lookup of stream 1"); return status; } status = h2_stream_rwrite(stream, session->r); if (status != APR_SUCCESS) { return status; } status = stream_end_headers(session, stream, 1); if (status != APR_SUCCESS) { return status; } } settings[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS; settings[0].value = (uint32_t)session->max_stream_count; settings[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE; settings[1].value = h2_config_geti(config, H2_CONF_WIN_SIZE); settings[2].settings_id = NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE; settings[2].value = 64*1024; *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE, settings, sizeof(settings)/sizeof(settings[0])); if (*rv != 0) { status = APR_EGENERAL; ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, APLOGNO(02935) "nghttp2_submit_settings: %s", nghttp2_strerror(*rv)); } return status; }
static h2_session *h2_session_create_int(conn_rec *c, request_rec *r, h2_config *config, h2_workers *workers) { nghttp2_session_callbacks *callbacks = NULL; nghttp2_option *options = NULL; apr_pool_t *pool = NULL; apr_status_t status = apr_pool_create(&pool, r? r->pool : c->pool); h2_session *session; if (status != APR_SUCCESS) { return NULL; } session = apr_pcalloc(pool, sizeof(h2_session)); if (session) { int rv; session->id = c->id; session->c = c; session->r = r; session->max_stream_count = h2_config_geti(config, H2_CONF_MAX_STREAMS); session->max_stream_mem = h2_config_geti(config, H2_CONF_STREAM_MAX_MEM); session->pool = pool; status = apr_thread_cond_create(&session->iowait, session->pool); if (status != APR_SUCCESS) { return NULL; } session->streams = h2_stream_set_create(session->pool); session->workers = workers; session->mplx = h2_mplx_create(c, session->pool, workers); h2_conn_io_init(&session->io, c); session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc); status = init_callbacks(c, &callbacks); if (status != APR_SUCCESS) { ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927) "nghttp2: error in init_callbacks"); h2_session_destroy(session); return NULL; } rv = nghttp2_option_new(&options); if (rv != 0) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, APLOGNO(02928) "nghttp2_option_new: %s", nghttp2_strerror(rv)); h2_session_destroy(session); return NULL; } nghttp2_option_set_peer_max_concurrent_streams(options, (uint32_t)session->max_stream_count); /* We need to handle window updates ourself, otherwise we * get flooded by nghttp2. */ nghttp2_option_set_no_auto_window_update(options, 1); rv = nghttp2_session_server_new2(&session->ngh2, callbacks, session, options); nghttp2_session_callbacks_del(callbacks); nghttp2_option_del(options); if (rv != 0) { ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, APLOGNO(02929) "nghttp2_session_server_new: %s", nghttp2_strerror(rv)); h2_session_destroy(session); return NULL; } } return session; }
apr_status_t h2_conn_child_init(apr_pool_t *pool, server_rec *s) { const h2_config *config = h2_config_sget(s); apr_status_t status = APR_SUCCESS; int minw, maxw, max_tx_handles, n; int max_threads_per_child = 0; int idle_secs = 0; check_modules(1); ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child); status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm); if (status != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_TRACE1, status, s, "querying MPM for async"); /* some MPMs do not implemnent this */ async_mpm = 0; status = APR_SUCCESS; } h2_config_init(pool); minw = h2_config_geti(config, H2_CONF_MIN_WORKERS); maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS); if (minw <= 0) { minw = max_threads_per_child; } if (maxw <= 0) { maxw = minw; } /* How many file handles is it safe to use for transfer * to the master connection to be streamed out? * Is there a portable APR rlimit on NOFILES? Have not * found it. And if, how many of those would we set aside? * This leads all into a process wide handle allocation strategy * which ultimately would limit the number of accepted connections * with the assumption of implicitly reserving n handles for every * connection and requiring modules with excessive needs to allocate * from a central pool. */ n = h2_config_geti(config, H2_CONF_SESSION_FILES); if (n < 0) { max_tx_handles = maxw * 2; } else { max_tx_handles = maxw * n; } ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s, "h2_workers: min=%d max=%d, mthrpchild=%d, tx_files=%d", minw, maxw, max_threads_per_child, max_tx_handles); workers = h2_workers_create(s, pool, minw, maxw, max_tx_handles); idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS); h2_workers_set_max_idle_secs(workers, idle_secs); ap_register_input_filter("H2_IN", h2_filter_core_input, NULL, AP_FTYPE_CONNECTION); status = h2_mplx_child_init(pool, s); return status; }
apr_status_t h2_task_do(h2_task *task, h2_worker *worker) { apr_status_t status = APR_SUCCESS; h2_config *cfg = h2_config_get(task->mplx->c); AP_DEBUG_ASSERT(task); 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 execution. */ apr_pool_create(&task->pool, h2_worker_get_pool(worker)); /* Link the task to the worker which provides useful things such * as mutex, a socket etc. */ task->io = h2_worker_get_cond(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(task->c, task); if (status == APR_SUCCESS) { 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, task->c, APLOGNO(02957) "h2_task(%s): error setting up h2_task", task->id); } if (task->input) { h2_task_input_destroy(task->input); task->input = NULL; } if (task->output) { h2_task_output_close(task->output); h2_task_output_destroy(task->output); task->output = NULL; } if (task->io) { apr_thread_cond_signal(task->io); } if (task->pool) { apr_pool_destroy(task->pool); task->pool = NULL; } if (task->c->id) { h2_conn_post(task->c, worker); } h2_mplx_task_done(task->mplx, task->stream_id); return status; }