Exemple #1
0
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));
}
Exemple #2
0
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;
}
Exemple #3
0
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;
}
Exemple #4
0
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));
}
Exemple #5
0
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;
}
Exemple #6
0
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;
}
Exemple #7
0
/**
 * 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;
}
Exemple #8
0
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;
}
Exemple #9
0
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;
}
Exemple #10
0
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;
}
Exemple #11
0
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;
}
Exemple #12
0
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;
}
Exemple #13
0
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;
}
Exemple #14
0
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;
}
Exemple #15
0
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;
}