1
0
mirror of https://github.com/apache/httpd.git synced 2025-08-08 15:02:10 +03:00

new Protocols directive and core API changes to enable protocol switching on HTTP Upgrade or ALPN, implemented in mod_ssl and mod_h2

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1692486 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stefan Eissing
2015-07-24 12:09:44 +00:00
parent 546bf924b3
commit b9ba839d50
37 changed files with 709 additions and 860 deletions

View File

@@ -1 +1 @@
2905 2963

View File

@@ -707,6 +707,9 @@ typedef struct {
#define AP_HTTP_EXPECT_STRICT_ENABLE 1 #define AP_HTTP_EXPECT_STRICT_ENABLE 1
#define AP_HTTP_EXPECT_STRICT_DISABLE 2 #define AP_HTTP_EXPECT_STRICT_DISABLE 2
int http_expect_strict; int http_expect_strict;
apr_array_header_t *protocols;
} core_server_config; } core_server_config;
/* for AddOutputFiltersByType in core.c */ /* for AddOutputFiltersByType in core.c */

View File

@@ -700,6 +700,109 @@ AP_DECLARE_HOOK(const char *,http_scheme,(const request_rec *r))
*/ */
AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r)) AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r))
#define AP_PROTOCOL_HTTP1 "http/1.1"
/**
* Negotiate a possible protocol switch on the connection. The negotiation
* may start without any request sent, in which case the request is NULL. Or
* it may be triggered by the request received, e.g. through the "Upgrade"
* header.
*
* The identifiers for protocols are taken from the TLS extension type ALPN:
* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xml
*
* If no protocols are added to the proposals, the server will always fallback
* to "http/1.1" which is the default protocol for connections that Apache
* handles. If the protocol selected from the proposals is the protocol
* already in place, no "protocol_switch" will be invoked.
*
* All hooks are run, unless one returns an error. Proposals may contain
* duplicates. The order in which proposals are added is usually ignored.
*
* @param c The current connection
* @param r The current request or NULL
* @param s The server/virtual host selected
* @param offers a list of protocol identifiers offered by the client
* @param proposals the list of protocol identifiers proposed by the hooks
* @return OK or DECLINED
*/
AP_DECLARE_HOOK(int,protocol_propose,(conn_rec *c, request_rec *r,
server_rec *s,
const apr_array_header_t *offers,
apr_array_header_t *proposals))
/**
* Perform a protocol switch on the connection. The exact requirements for
* that depend on the protocol in place and the one switched to. The first
* protocol module to handle the switch is the last module run.
*
* For a connection level switch (r == NULL), the handler must on return
* leave the conn_rec in a state suitable for processing the switched
* protocol, e.g. correct filters in place.
*
* For a request triggered switch (r != NULL), the protocol switch is done
* before the response is sent out. When switching from "http/1.1" via Upgrade
* header, the 101 intermediate response will have been sent. The
* hook needs then to process the connection until it can be closed. Which
* the server will enforce on hook return.
* Any error the hook might encounter must already be sent by the hook itself
* to the client in whatever form the new protocol requires.
*
* @param c The current connection
* @param r The current request or NULL
* @param s The server/virtual host selected
* @param choices a list of protocol identifiers, normally the clients whishes
* @param proposals the list of protocol identifiers proposed by the hooks
* @return OK or DECLINED
*/
AP_DECLARE_HOOK(int,protocol_switch,(conn_rec *c, request_rec *r,
server_rec *s,
const char *protocol))
/**
* Return the protocol used on the connection. Modules implementing
* protocol switching must register here and return the correct protocol
* identifier for connections they switched.
*
* @param c The current connection
* @return The identifier of the protocol in place
*/
AP_DECLARE_HOOK(const char *,protocol_get,(const conn_rec *c))
/**
* Select a protocol for the given connection and optional request. Will return
* the protocol identifier selected which may be the protocol already in place
* on the connection. The server may ignore the choices given.
*
* @param c The current connection
* @param r The current request or NULL
* @param s The server/virtual host selected
* @param choices a list of protocol identifiers, normally the clients whishes
* @return the selected protocol
*/
AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
server_rec *s,
apr_array_header_t *choices);
/**
* Perform the actual protocol switch. The protocol given must have been
* selected before on the very same connection and request pair.
*
* @param c The current connection
* @param r The current request or NULL
* @param s The server/virtual host selected
* @param protocol the protocol to switch to
* @return APR_SUCCESS, if caller may continue processing as usual
* APR_EOF, if caller needs to stop processing the connection
* APR_EINVAL, if the protocol is already in place
* APR_NOTIMPL, if no module performed the switch
* Other errors where appropriate
*/
AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
server_rec *s,
const char *protocol);
/** @see ap_bucket_type_error */ /** @see ap_bucket_type_error */
typedef struct ap_bucket_error ap_bucket_error; typedef struct ap_bucket_error ap_bucket_error;

View File

@@ -19,7 +19,6 @@ APACHE_MODPATH_INIT(http2)
dnl # list of module object files dnl # list of module object files
h2_objs="dnl h2_objs="dnl
mod_h2.lo dnl mod_h2.lo dnl
h2_alpn.lo dnl
h2_alt_svc.lo dnl h2_alt_svc.lo dnl
h2_config.lo dnl h2_config.lo dnl
h2_conn.lo dnl h2_conn.lo dnl
@@ -35,12 +34,12 @@ h2_response.lo dnl
h2_session.lo dnl h2_session.lo dnl
h2_stream.lo dnl h2_stream.lo dnl
h2_stream_set.lo dnl h2_stream_set.lo dnl
h2_switch.lo dnl
h2_task.lo dnl h2_task.lo dnl
h2_task_input.lo dnl h2_task_input.lo dnl
h2_task_output.lo dnl h2_task_output.lo dnl
h2_task_queue.lo dnl h2_task_queue.lo dnl
h2_to_h1.lo dnl h2_to_h1.lo dnl
h2_upgrade.lo dnl
h2_util.lo dnl h2_util.lo dnl
h2_worker.lo dnl h2_worker.lo dnl
h2_workers.lo dnl h2_workers.lo dnl

View File

@@ -1,299 +0,0 @@
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <apr_strings.h>
#include <apr_optional.h>
#include <apr_optional_hooks.h>
#include <httpd.h>
#include <http_core.h>
#include <http_config.h>
#include <http_connection.h>
#include <http_protocol.h>
#include <http_log.h>
#include "h2_private.h"
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_conn.h"
#include "h2_h2.h"
#include "h2_alpn.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 *);
/*******************************************************************************
* NPN callbacks and registry, deprecated
*/
typedef int (*ssl_npn_advertise_protos)(conn_rec *connection,
apr_array_header_t *protos);
typedef int (*ssl_npn_proto_negotiated)(conn_rec *connection,
const char *proto_name, apr_size_t proto_name_len);
APR_DECLARE_OPTIONAL_FN(int, modssl_register_npn,
(conn_rec *conn,
ssl_npn_advertise_protos advertisefn,
ssl_npn_proto_negotiated negotiatedfn));
static int (*opt_ssl_register_npn)(conn_rec*,
ssl_npn_advertise_protos,
ssl_npn_proto_negotiated);
/*******************************************************************************
* ALPN callbacks and registry
*/
typedef int (*ssl_alpn_propose_protos)(conn_rec *connection,
apr_array_header_t *client_protos, apr_array_header_t *protos);
typedef int (*ssl_alpn_proto_negotiated)(conn_rec *connection,
const char *proto_name, apr_size_t proto_name_len);
APR_DECLARE_OPTIONAL_FN(int, modssl_register_alpn,
(conn_rec *conn,
ssl_alpn_propose_protos proposefn,
ssl_alpn_proto_negotiated negotiatedfn));
static int (*opt_ssl_register_alpn)(conn_rec*,
ssl_alpn_propose_protos,
ssl_alpn_proto_negotiated);
/*******************************************************************************
* Hooks for processing incoming connections:
* - pre_conn_after_tls registers for ALPN handling
*/
static int h2_alpn_pre_conn(conn_rec* c, void *arg);
/*******************************************************************************
* Once per lifetime init, retrieve optional functions
*/
apr_status_t h2_alpn_init(apr_pool_t *pool, server_rec *s)
{
(void)pool;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_alpn init");
opt_ssl_register_npn = APR_RETRIEVE_OPTIONAL_FN(modssl_register_npn);
opt_ssl_register_alpn = APR_RETRIEVE_OPTIONAL_FN(modssl_register_alpn);
opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
if (!opt_ssl_register_alpn && !opt_ssl_register_npn) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
"mod_ssl does not offer ALPN or NPN registration");
}
return APR_SUCCESS;
}
/*******************************************************************************
* Register various hooks
*/
static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
static const char *const mod_core[] = { "core.c", NULL};
static void check_sni_host(conn_rec *c)
{
/* If we have not done so already, ask the connection for the
* hostname send to us via SNI. This information is later used
* to retrieve the correct server settings for this connection.
*/
h2_ctx *ctx = h2_ctx_get(c);
if (opt_ssl_var_lookup && !ctx->hostname) {
const char *p = opt_ssl_var_lookup(c->pool, c->base_server, c,
NULL, (char*)"SSL_TLS_SNI");
if (p && *p) {
ctx->hostname = apr_pstrdup(c->pool, p);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"h2_h2, connection, SNI %s",
ctx->hostname? ctx->hostname : "NULL");
}
}
}
void h2_alpn_register_hooks(void)
{
/* This hook runs on new connection after mod_ssl, but before the core
* httpd. Its purpose is to register, if TLS is used, the ALPN callbacks
* that enable us to chose "h2" as next procotol if the client supports it.
*/
ap_hook_pre_connection(h2_alpn_pre_conn,
mod_ssl, mod_core, APR_HOOK_LAST);
}
static int h2_util_array_index(apr_array_header_t *array, const char *s)
{
int i;
for (i = 0; i < array->nelts; i++) {
const char *p = APR_ARRAY_IDX(array, i, const char*);
if (!strcmp(p, s)) {
return i;
}
}
return -1;
}
static int h2_npn_advertise(conn_rec *c, apr_array_header_t *protos)
{
h2_config *cfg;
apr_size_t i;
check_sni_host(c);
cfg = h2_config_get(c);
if (!h2_config_geti(cfg, H2_CONF_ENABLED)) {
return DECLINED;
}
for (i = 0; i < h2_alpn_protos_len; ++i) {
const char *proto = h2_alpn_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;
}
static int h2_negotiated(conn_rec *c, const char *via,
const char *proto_name,
apr_size_t proto_name_len)
{
h2_ctx *ctx = h2_ctx_get(c);
apr_size_t i;
if (h2_ctx_is_task(ctx) ) {
return DECLINED;
}
if (h2_ctx_pnego_is_done(ctx)) {
/* called twice? refraing from overriding existing selection.
* NPN is fading...
*/
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"protocol negotiated via %s called, but already set",
via);
return DECLINED;
}
if (APLOGctrace1(c)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"protocol negotiated via %s is %s", via,
apr_pstrndup(c->pool, proto_name, proto_name_len));
}
for (i = 0; i < h2_alpn_protos_len; ++i) {
const char *proto = h2_alpn_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 via %s to %s", via, proto);
h2_ctx_pnego_set_done(ctx, proto);
break;
}
}
return OK;
}
static int h2_npn_negotiated(conn_rec *c,
const char *proto_name,
apr_size_t proto_name_len)
{
return h2_negotiated(c, "NPN", proto_name, proto_name_len);
}
static int h2_alpn_propose(conn_rec *c,
apr_array_header_t *client_protos,
apr_array_header_t *protos)
{
h2_config *cfg;
apr_size_t i;
check_sni_host(c);
cfg = h2_config_get(c);
if (!h2_config_geti(cfg, H2_CONF_ENABLED)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"ALPN propose, h2 disabled for config %s", cfg->name);
return DECLINED;
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"ALPN propose for config %s", cfg->name);
/* */
for (i = 0; i < h2_alpn_protos_len; ++i) {
const char *proto = h2_alpn_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_alpn_negotiated(conn_rec *c,
const char *proto_name,
apr_size_t proto_name_len)
{
return h2_negotiated(c, "ALPN", proto_name, proto_name_len);
}
int h2_alpn_pre_conn(conn_rec* c, void *arg)
{
h2_ctx *ctx = h2_ctx_get(c);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"h2_h2, pre_connection, start");
if (h2_ctx_is_task(ctx)) {
/* our stream pseudo connection */
return DECLINED;
}
if (h2_h2_is_tls(c)) {
/* Brand new TLS connection: Does mod_ssl offer ALPN/NPN support?
* If so, register at all present, clients may use either/or.
*/
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;
}
if (opt_ssl_register_alpn) {
opt_ssl_register_alpn(c, h2_alpn_propose, h2_alpn_negotiated);
}
if (opt_ssl_register_npn) {
opt_ssl_register_npn(c, h2_npn_advertise, h2_npn_negotiated);
}
h2_ctx_pnego_set_started(ctx);
ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
"h2_alpn, pre_connection, ALPN callback registered");
}
return DECLINED;
}

View File

@@ -144,7 +144,8 @@ apr_status_t h2_conn_rprocess(request_rec *r)
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start"); ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start");
if (!workers) { if (!workers) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "workers not initialized"); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02911)
"workers not initialized");
return APR_EGENERAL; return APR_EGENERAL;
} }
@@ -163,7 +164,8 @@ apr_status_t h2_conn_main(conn_rec *c)
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start"); ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start");
if (!workers) { if (!workers) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, "workers not initialized"); ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02912)
"workers not initialized");
return APR_EGENERAL; return APR_EGENERAL;
} }
@@ -293,6 +295,7 @@ apr_status_t h2_session_process(h2_session *session)
break; break;
default: default:
ap_log_cerror( APLOG_MARK, APLOG_WARNING, status, session->c, ap_log_cerror( APLOG_MARK, APLOG_WARNING, status, session->c,
APLOGNO(02950)
"h2_session(%ld): error reading, terminating", "h2_session(%ld): error reading, terminating",
session->id); session->id);
h2_session_abort(session, status, 0); h2_session_abort(session, status, 0);
@@ -354,7 +357,7 @@ conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool)
master->bucket_alloc); master->bucket_alloc);
if (c == NULL) { if (c == NULL) {
ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool, ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
"h2_task: creating conn"); APLOGNO(02913) "h2_task: creating conn");
return NULL; return NULL;
} }
/* TODO: we simulate that we had already a request on this connection. /* TODO: we simulate that we had already a request on this connection.
@@ -440,7 +443,7 @@ apr_status_t h2_conn_init(struct h2_task_env *env, struct h2_worker *worker)
master->bucket_alloc); master->bucket_alloc);
if (c == NULL) { if (c == NULL) {
ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, env->pool, ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, env->pool,
"h2_task: creating conn"); APLOGNO(02914) "h2_task: creating conn");
return APR_ENOMEM; return APR_ENOMEM;
} }

View File

@@ -24,7 +24,7 @@
#include "h2_ctx.h" #include "h2_ctx.h"
#include "h2_private.h" #include "h2_private.h"
static h2_ctx *h2_ctx_create(conn_rec *c) static h2_ctx *h2_ctx_create(const conn_rec *c)
{ {
h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx)); h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
AP_DEBUG_ASSERT(ctx); AP_DEBUG_ASSERT(ctx);
@@ -33,7 +33,7 @@ static h2_ctx *h2_ctx_create(conn_rec *c)
return ctx; return ctx;
} }
h2_ctx *h2_ctx_create_for(conn_rec *c, h2_task_env *env) h2_ctx *h2_ctx_create_for(const conn_rec *c, h2_task_env *env)
{ {
h2_ctx *ctx = h2_ctx_create(c); h2_ctx *ctx = h2_ctx_create(c);
if (ctx) { if (ctx) {
@@ -42,7 +42,7 @@ h2_ctx *h2_ctx_create_for(conn_rec *c, h2_task_env *env)
return ctx; return ctx;
} }
h2_ctx *h2_ctx_get(conn_rec *c) h2_ctx *h2_ctx_get(const conn_rec *c)
{ {
h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module); h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module);
if (ctx == NULL) { if (ctx == NULL) {
@@ -51,22 +51,18 @@ h2_ctx *h2_ctx_get(conn_rec *c)
return ctx; return ctx;
} }
h2_ctx *h2_ctx_rget(request_rec *r) h2_ctx *h2_ctx_rget(const request_rec *r)
{ {
return h2_ctx_get(r->connection); return h2_ctx_get(r->connection);
} }
const char *h2_ctx_pnego_get(h2_ctx *ctx) const char *h2_ctx_protocol_get(const conn_rec *c)
{ {
h2_ctx *ctx = (h2_ctx*)ap_get_module_config(c->conn_config, &h2_module);
return ctx? ctx->protocol : NULL; return ctx? ctx->protocol : NULL;
} }
void h2_ctx_pnego_set_started(h2_ctx *ctx) h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
{
ctx->pnego_state = H2_PNEGO_STARTED;
}
h2_ctx *h2_ctx_pnego_set_done(h2_ctx *ctx, const char *proto)
{ {
ctx->protocol = proto; ctx->protocol = proto;
ctx->pnego_state = H2_PNEGO_DONE; ctx->pnego_state = H2_PNEGO_DONE;
@@ -84,11 +80,6 @@ int h2_ctx_pnego_is_ongoing(h2_ctx *ctx)
return ctx && (ctx->pnego_state == H2_PNEGO_STARTED); return ctx && (ctx->pnego_state == H2_PNEGO_STARTED);
} }
int h2_ctx_pnego_is_done(h2_ctx *ctx)
{
return ctx && (ctx->pnego_state == H2_PNEGO_DONE);
}
int h2_ctx_is_active(h2_ctx *ctx) int h2_ctx_is_active(h2_ctx *ctx)
{ {
return ctx && ctx->is_h2; return ctx && ctx->is_h2;

View File

@@ -43,18 +43,16 @@ typedef struct h2_ctx {
struct h2_config *config; /* effective config in this context */ struct h2_config *config; /* effective config in this context */
} h2_ctx; } h2_ctx;
h2_ctx *h2_ctx_get(conn_rec *c); h2_ctx *h2_ctx_get(const conn_rec *c);
h2_ctx *h2_ctx_rget(request_rec *r); h2_ctx *h2_ctx_rget(const request_rec *r);
h2_ctx *h2_ctx_create_for(conn_rec *c, struct h2_task_env *env); h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task_env *env);
void h2_ctx_pnego_set_started(h2_ctx *ctx); /* Set the h2 protocol established on this connection context or
h2_ctx *h2_ctx_pnego_set_done(h2_ctx *ctx, const char *proto); * NULL when other protocols are in place.
/**
* Returns != 0 iff protocol negitiation did happen, not matter
* what the outcome was.
*/ */
int h2_ctx_pnego_is_done(h2_ctx *ctx); h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto);
/** /**
* Returns != 0 iff protocol negotiation has started but is not * Returns != 0 iff protocol negotiation has started but is not
* done yet. * done yet.
@@ -64,7 +62,7 @@ int h2_ctx_pnego_is_ongoing(h2_ctx *ctx);
/** /**
* Get the h2 protocol negotiated for this connection, or NULL. * Get the h2 protocol negotiated for this connection, or NULL.
*/ */
const char *h2_ctx_pnego_get(h2_ctx *ctx); const char *h2_ctx_protocol_get(const conn_rec *c);
int h2_ctx_is_task(h2_ctx *ctx); int h2_ctx_is_task(h2_ctx *ctx);
int h2_ctx_is_active(h2_ctx *ctx); int h2_ctx_is_active(h2_ctx *ctx);

View File

@@ -83,6 +83,7 @@ static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
from_h1->pool); from_h1->pool);
if (from_h1->response == NULL) { if (from_h1->response == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
APLOGNO(02915)
"h2_from_h1(%d): unable to create resp_head", "h2_from_h1(%d): unable to create resp_head",
from_h1->stream_id); from_h1->stream_id);
return APR_EINVAL; return APR_EINVAL;

View File

@@ -33,20 +33,15 @@
#include "h2_config.h" #include "h2_config.h"
#include "h2_ctx.h" #include "h2_ctx.h"
#include "h2_conn.h" #include "h2_conn.h"
#include "h2_alpn.h"
#include "h2_h2.h" #include "h2_h2.h"
const char *h2_alpn_protos[] = { const char *h2_tls_protos[] = {
"h2", "h2", NULL
}; };
apr_size_t h2_alpn_protos_len = (sizeof(h2_alpn_protos)
/ sizeof(h2_alpn_protos[0]));
const char *h2_upgrade_protos[] = { const char *h2_clear_protos[] = {
"h2c", "h2c", NULL
}; };
apr_size_t h2_upgrade_protos_len = (sizeof(h2_upgrade_protos)
/ sizeof(h2_upgrade_protos[0]));
const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"; const char *H2_MAGIC_TOKEN = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
@@ -80,7 +75,7 @@ apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s)
if (!opt_ssl_is_https) { if (!opt_ssl_is_https) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
"mod_ssl does not seem to be enabled"); APLOGNO(02951) "mod_ssl does not seem to be enabled");
} }
return APR_SUCCESS; return APR_SUCCESS;
@@ -107,7 +102,7 @@ static const char *const mod_reqtimeout[] = { "reqtimeout.c", NULL};
void h2_h2_register_hooks(void) void h2_h2_register_hooks(void)
{ {
/* When the connection processing actually starts, we might to /* When the connection processing actually starts, we might to
* take over, if h2* was selected by ALPN on a TLS connection. * take over, if h2* was selected as protocol.
*/ */
ap_hook_process_connection(h2_h2_process_conn, ap_hook_process_connection(h2_h2_process_conn,
NULL, NULL, APR_HOOK_FIRST); NULL, NULL, APR_HOOK_FIRST);
@@ -156,7 +151,7 @@ int h2_h2_process_conn(conn_rec* c)
apr_bucket_brigade* temp; apr_bucket_brigade* temp;
if (h2_ctx_is_task(ctx)) { if (h2_ctx_is_task(ctx)) {
/* out stream pseudo connection */ /* our stream pseudo connection */
return DECLINED; return DECLINED;
} }
@@ -170,15 +165,19 @@ int h2_h2_process_conn(conn_rec* c)
apr_brigade_destroy(temp); apr_brigade_destroy(temp);
} }
/* If we still do not know the protocol and H2Direct is enabled, check /* If we have not already switched to a h2* protocol
* if we receive the magic PRIamble. A client sending this on connection * and the connection is on "http/1.1"
* and H2Direct is enabled,
* -> sniff for the magic PRIamble. A client sending this on connection
* start should know what it is doing. * start should know what it is doing.
*/ */
if (!h2_ctx_pnego_is_done(ctx) && h2_config_geti(cfg, H2_CONF_DIRECT)) { if (!h2_ctx_protocol_get(c)
&& !strcmp(AP_PROTOCOL_HTTP1, ap_run_protocol_get(c))
&& h2_config_geti(cfg, H2_CONF_DIRECT)) {
apr_status_t status; apr_status_t status;
temp = apr_brigade_create(c->pool, c->bucket_alloc); temp = apr_brigade_create(c->pool, c->bucket_alloc);
status = ap_get_brigade(c->input_filters, temp, status = ap_get_brigade(c->input_filters, temp,
/*h2_h2_is_tls(c)? AP_MODE_READBYTES :*/ AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24); AP_MODE_SPECULATIVE, APR_BLOCK_READ, 24);
if (status == APR_SUCCESS) { if (status == APR_SUCCESS) {
char *s = NULL; char *s = NULL;
apr_size_t slen; apr_size_t slen;
@@ -187,7 +186,7 @@ int h2_h2_process_conn(conn_rec* c)
if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) { if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"h2_h2, direct mode detected"); "h2_h2, direct mode detected");
h2_ctx_pnego_set_done(ctx, "h2"); h2_ctx_protocol_set(ctx, "h2");
} }
else { else {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,

View File

@@ -17,18 +17,16 @@
#define __mod_h2__h2_h2__ #define __mod_h2__h2_h2__
/** /**
* List of ALPN protocol identifiers that we support in ALPN/NPN * List of ALPN protocol identifiers that we suport in cleartext
* negotiations. * negotiations. NULL terminated.
*/ */
extern const char *h2_alpn_protos[]; extern const char *h2_clear_protos[];
extern apr_size_t h2_alpn_protos_len;
/** /**
* List of ALPN protocol identifiers that we suport in HTTP/1 Upgrade: * List of ALPN protocol identifiers that we support in TLS encrypted
* negotiations. * negotiations. NULL terminated.
*/ */
extern const char *h2_upgrade_protos[]; extern const char *h2_tls_protos[];
extern apr_size_t h2_upgrade_protos_len;
/** /**
* The magic PRIamble of RFC 7540 that is always sent when starting * The magic PRIamble of RFC 7540 that is always sent when starting

View File

@@ -199,6 +199,7 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10)); apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10));
if (++attempts >= 6) { if (++attempts >= 6) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
APLOGNO(02952)
"h2_mplx(%ld): join attempts exhausted, refs=%d", "h2_mplx(%ld): join attempts exhausted, refs=%d",
m->id, m->refs); m->id, m->refs);
break; break;
@@ -497,7 +498,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams)
} }
else { else {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_NOTFOUND, m->c,
"h2_mplx(%ld): stream for response %d", APLOGNO(02953) "h2_mplx(%ld): stream for response %d",
m->id, response->stream_id); m->id, response->stream_id);
} }
} }
@@ -785,7 +786,7 @@ apr_status_t h2_mplx_create_task(h2_mplx *m, struct h2_stream *stream)
conn_rec *c = h2_conn_create(m->c, stream->pool); conn_rec *c = h2_conn_create(m->c, stream->pool);
if (c == NULL) { if (c == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, m->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, m->c,
"h2_mplx(%ld-%d): start stream", APLOGNO(02916) "h2_mplx(%ld-%d): start stream",
m->id, stream->id); m->id, stream->id);
return APR_ENOMEM; return APR_ENOMEM;
} }

View File

@@ -92,6 +92,7 @@ apr_status_t h2_request_write_header(h2_request *req,
/* pseudo header, see ch. 8.1.2.3, always should come first */ /* pseudo header, see ch. 8.1.2.3, always should come first */
if (req->to_h1) { if (req->to_h1) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool, ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool,
APLOGNO(02917)
"h2_request(%d): pseudo header after request start", "h2_request(%d): pseudo header after request start",
req->id); req->id);
return APR_EGENERAL; return APR_EGENERAL;
@@ -118,6 +119,7 @@ apr_status_t h2_request_write_header(h2_request *req,
memset(buffer, 0, 32); memset(buffer, 0, 32);
strncpy(buffer, name, (nlen > 31)? 31 : nlen); strncpy(buffer, name, (nlen > 31)? 31 : nlen);
ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool, ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool,
APLOGNO(02954)
"h2_request(%d): ignoring unknown pseudo header %s", "h2_request(%d): ignoring unknown pseudo header %s",
req->id, buffer); req->id, buffer);
} }

View File

@@ -61,7 +61,7 @@ h2_response *h2_response_create(int stream_id,
char *sep = strchr(hline, ':'); char *sep = strchr(hline, ':');
if (!sep) { if (!sep) {
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool, ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
"h2_response(%d): invalid header[%d] '%s'", APLOGNO(02955) "h2_response(%d): invalid header[%d] '%s'",
response->stream_id, i, (char*)hline); response->stream_id, i, (char*)hline);
/* not valid format, abort */ /* not valid format, abort */
return NULL; return NULL;
@@ -80,7 +80,8 @@ h2_response *h2_response_create(int stream_id,
response->content_length = apr_strtoi64(sep, &end, 10); response->content_length = apr_strtoi64(sep, &end, 10);
if (sep == end) { if (sep == end) {
ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL,
pool, "h2_response(%d): content-length" pool, APLOGNO(02956)
"h2_response(%d): content-length"
" value not parsed: %s", " value not parsed: %s",
response->stream_id, sep); response->stream_id, sep);
response->content_length = -1; response->content_length = -1;
@@ -199,7 +200,7 @@ static int add_header(void *ctx, const char *key, const char *value)
if (!ignore_header(key)) { if (!ignore_header(key)) {
nvctx_t *nvctx = (nvctx_t*)ctx; nvctx_t *nvctx = (nvctx_t*)ctx;
if (nvctx->debug) { if (nvctx->debug) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL,
nvctx->r, "h2_response(%d) header -> %s: %s", nvctx->r, "h2_response(%d) header -> %s: %s",
nvctx->response->stream_id, key, value); nvctx->response->stream_id, key, value);
} }

View File

@@ -73,6 +73,7 @@ static int stream_open(h2_session *session, int stream_id)
} }
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
APLOGNO(02918)
"h2_session: stream(%ld-%d): unable to create", "h2_session: stream(%ld-%d): unable to create",
session->id, stream_id); session->id, stream_id);
return NGHTTP2_ERR_INVALID_STREAM_ID; return NGHTTP2_ERR_INVALID_STREAM_ID;
@@ -145,6 +146,7 @@ static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
stream = h2_stream_set_get(session->streams, stream_id); stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) { if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02919)
"h2_session: stream(%ld-%d): on_data_chunk for unknown stream", "h2_session: stream(%ld-%d): on_data_chunk for unknown stream",
session->id, (int)stream_id); session->id, (int)stream_id);
rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id, rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
@@ -279,6 +281,7 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame,
frame->hd.stream_id); frame->hd.stream_id);
if (!stream) { if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02920)
"h2_session: stream(%ld-%d): on_header for unknown stream", "h2_session: stream(%ld-%d): on_header for unknown stream",
session->id, (int)frame->hd.stream_id); session->id, (int)frame->hd.stream_id);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
@@ -320,6 +323,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
frame->hd.stream_id); frame->hd.stream_id);
if (stream == NULL) { if (stream == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02921)
"h2_session: stream(%ld-%d): HEADERS frame " "h2_session: stream(%ld-%d): HEADERS frame "
"for unknown stream", session->id, "for unknown stream", session->id,
(int)frame->hd.stream_id); (int)frame->hd.stream_id);
@@ -342,6 +346,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
frame->hd.stream_id); frame->hd.stream_id);
if (stream == NULL) { if (stream == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
APLOGNO(02922)
"h2_session: stream(%ld-%d): DATA frame " "h2_session: stream(%ld-%d): DATA frame "
"for unknown stream", session->id, "for unknown stream", session->id,
(int)frame->hd.stream_id); (int)frame->hd.stream_id);
@@ -394,6 +399,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
APLOGNO(02923)
"h2_session: stream(%ld-%d): error handling frame", "h2_session: stream(%ld-%d): error handling frame",
session->id, (int)frame->hd.stream_id); session->id, (int)frame->hd.stream_id);
rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE, rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
@@ -443,6 +449,7 @@ static int on_send_data_cb(nghttp2_session *ngh2,
stream = h2_stream_set_get(session->streams, stream_id); stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) { if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
APLOGNO(02924)
"h2_stream(%ld-%d): send_data", "h2_stream(%ld-%d): send_data",
session->id, (int)stream_id); session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -477,6 +484,7 @@ static int on_send_data_cb(nghttp2_session *ngh2,
} }
else if (status != APR_EOF) { else if (status != APR_EOF) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
APLOGNO(02925)
"h2_stream(%ld-%d): failed send_data_cb", "h2_stream(%ld-%d): failed send_data_cb",
session->id, (int)stream_id); session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -494,7 +502,7 @@ static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb)
int rv = nghttp2_session_callbacks_new(pcb); int rv = nghttp2_session_callbacks_new(pcb);
if (rv != 0) { if (rv != 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
"nghttp2_session_callbacks_new: %s", APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
nghttp2_strerror(rv)); nghttp2_strerror(rv));
return APR_EGENERAL; return APR_EGENERAL;
} }
@@ -556,7 +564,7 @@ static h2_session *h2_session_create_int(conn_rec *c,
status = init_callbacks(c, &callbacks); status = init_callbacks(c, &callbacks);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927)
"nghttp2: error in init_callbacks"); "nghttp2: error in init_callbacks");
h2_session_destroy(session); h2_session_destroy(session);
return NULL; return NULL;
@@ -565,7 +573,8 @@ static h2_session *h2_session_create_int(conn_rec *c,
rv = nghttp2_option_new(&options); rv = nghttp2_option_new(&options);
if (rv != 0) { if (rv != 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
"nghttp2_option_new: %s", nghttp2_strerror(rv)); APLOGNO(02928) "nghttp2_option_new: %s",
nghttp2_strerror(rv));
h2_session_destroy(session); h2_session_destroy(session);
return NULL; return NULL;
} }
@@ -584,7 +593,7 @@ static h2_session *h2_session_create_int(conn_rec *c,
if (rv != 0) { if (rv != 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
"nghttp2_session_server_new: %s", APLOGNO(02929) "nghttp2_session_server_new: %s",
nghttp2_strerror(rv)); nghttp2_strerror(rv));
h2_session_destroy(session); h2_session_destroy(session);
return NULL; return NULL;
@@ -660,7 +669,7 @@ apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason)
if (rv != 0) { if (rv != 0) {
status = APR_EGENERAL; status = APR_EGENERAL;
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
"session(%ld): submit goaway: %s", APLOGNO(02930) "session(%ld): submit goaway: %s",
session->id, nghttp2_strerror(rv)); session->id, nghttp2_strerror(rv));
} }
return status; return status;
@@ -733,6 +742,7 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
s = apr_table_get(session->r->headers_in, "HTTP2-Settings"); s = apr_table_get(session->r->headers_in, "HTTP2-Settings");
if (!s) { if (!s) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r,
APLOGNO(02931)
"HTTP2-Settings header missing in request"); "HTTP2-Settings header missing in request");
return APR_EINVAL; return APR_EINVAL;
} }
@@ -751,7 +761,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
if (*rv != 0) { if (*rv != 0) {
status = APR_EINVAL; status = APR_EINVAL;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
"nghttp2_session_upgrade: %s", nghttp2_strerror(*rv)); APLOGNO(02932) "nghttp2_session_upgrade: %s",
nghttp2_strerror(*rv));
return status; return status;
} }
@@ -760,7 +771,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
if (*rv != 0) { if (*rv != 0) {
status = APR_EGENERAL; status = APR_EGENERAL;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
"open stream 1: %s", nghttp2_strerror(*rv)); APLOGNO(02933) "open stream 1: %s",
nghttp2_strerror(*rv));
return status; return status;
} }
@@ -768,7 +780,7 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
if (stream == NULL) { if (stream == NULL) {
status = APR_EGENERAL; status = APR_EGENERAL;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r, ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
"lookup of stream 1"); APLOGNO(02934) "lookup of stream 1");
return status; return status;
} }
@@ -795,7 +807,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv)
if (*rv != 0) { if (*rv != 0) {
status = APR_EGENERAL; status = APR_EGENERAL;
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
"nghttp2_submit_settings: %s", nghttp2_strerror(*rv)); APLOGNO(02935) "nghttp2_submit_settings: %s",
nghttp2_strerror(*rv));
} }
return status; return status;
@@ -826,6 +839,7 @@ static int resume_on_data(void *ctx, h2_stream *stream) {
rv = nghttp2_session_resume_data(session->ngh2, stream->id); rv = nghttp2_session_resume_data(session->ngh2, stream->id);
ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)? ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
APLOG_ERR : APLOG_DEBUG, 0, session->c, APLOG_ERR : APLOG_DEBUG, 0, session->c,
APLOGNO(02936)
"h2_stream(%ld-%d): resuming stream %s", "h2_stream(%ld-%d): resuming stream %s",
session->id, stream->id, nghttp2_strerror(rv)); session->id, stream->id, nghttp2_strerror(rv));
} }
@@ -1002,6 +1016,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
stream = h2_stream_set_get(session->streams, stream_id); stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) { if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
APLOGNO(02937)
"h2_stream(%ld-%d): data requested but stream not found", "h2_stream(%ld-%d): data requested but stream not found",
session->id, (int)stream_id); session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
@@ -1038,7 +1053,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s,
default: default:
nread = 0; nread = 0;
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
"h2_stream(%ld-%d): reading data", APLOGNO(02938) "h2_stream(%ld-%d): reading data",
session->id, (int)stream_id); session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
@@ -1074,7 +1089,7 @@ static int submit_response(h2_session *session, h2_response *response)
if (rv != 0) { if (rv != 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
"h2_stream(%ld-%d): submit_response: %s", APLOGNO(02939) "h2_stream(%ld-%d): submit_response: %s",
session->id, response->stream_id, nghttp2_strerror(rv)); session->id, response->stream_id, nghttp2_strerror(rv));
} }
else { else {
@@ -1110,7 +1125,8 @@ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream)
status = APR_EGENERAL; status = APR_EGENERAL;
h2_session_abort_int(session, rv); h2_session_abort_int(session, rv);
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
"submit_response: %s", nghttp2_strerror(rv)); APLOGNO(02940) "submit_response: %s",
nghttp2_strerror(rv));
} }
return status; return status;
} }

201
modules/http2/h2_switch.c Normal file
View File

@@ -0,0 +1,201 @@
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <apr_strings.h>
#include <apr_optional.h>
#include <apr_optional_hooks.h>
#include <httpd.h>
#include <http_core.h>
#include <http_config.h>
#include <http_connection.h>
#include <http_protocol.h>
#include <http_log.h>
#include "h2_private.h"
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_conn.h"
#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
*/
apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s)
{
(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;
}
static const char *const mod_ssl[] = { "mod_ssl.c", NULL};
static const char *const mod_core[] = { "core.c", NULL};
static int h2_util_array_index(const apr_array_header_t *array, const char *s)
{
int i;
for (i = 0; i < array->nelts; i++) {
const char *p = APR_ARRAY_IDX(array, i, const char*);
if (!strcmp(p, s)) {
return i;
}
}
return -1;
}
static int h2_protocol_propose(conn_rec *c, request_rec *r,
server_rec *s,
const apr_array_header_t *offers,
apr_array_header_t *proposals)
{
h2_config *cfg;
int proposed = 0;
const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
if (strcmp(AP_PROTOCOL_HTTP1, ap_run_protocol_get(c))) {
/* We do not know how to switch from anything else but http/1.1.
*/
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"protocol switch: current proto != http/1.1, declined");
return DECLINED;
}
cfg = h2_config_sget(s);
if (!h2_config_geti(cfg, H2_CONF_ENABLED)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"protocol propose, h2 disabled for config %s", cfg->name);
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.
*/
p = apr_table_get(r->headers_in, "HTTP2-Settings");
if (!p) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"upgrade without HTTP2-Settings declined");
return DECLINED;
}
p = apr_table_get(r->headers_in, "Connection");
if (!ap_find_token(r->pool, p, "http2-settings")) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"upgrade without HTTP2-Settings declined");
return DECLINED;
}
/* We also allow switching only for requests that have no body.
*/
p = apr_table_get(r->headers_in, "Content-Length");
if (p && strcmp(p, "0")) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"upgrade with content-length: %s, declined", p);
return DECLINED;
}
}
while (*protos) {
/* Add all protocols we know (tls or clear) and that
* were offered as options for the switch.
*/
if (h2_util_array_index(offers, *protos) >= 0) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"proposing protocol '%s'", *protos);
APR_ARRAY_PUSH(proposals, const char*) = *protos;
proposed = 1;
}
++protos;
}
return proposed? DECLINED : OK;
}
static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
const char *protocol)
{
int found = 0;
const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
const char **p = protos;
while (*p) {
if (!strcmp(*p, protocol)) {
found = 1;
break;
}
p++;
}
if (found) {
h2_ctx *ctx = h2_ctx_get(c);
h2_ctx_protocol_set(ctx, protocol);
if (r != NULL) {
/* Switching in the middle of a request means that
* we have to send out the response to this one in h2
* format. So we need to take over the connection
* right away.
*/
ap_remove_input_filter_byhandle(r->input_filters, "http_in");
ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
/* Ok, start an h2_conn on this one. */
apr_status_t status = h2_conn_rprocess(r);
if (status != DONE) {
/* Nothing really to do about this. */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
"session proessed, unexpected status");
}
}
else {
}
return DONE;
}
return DECLINED;
}
static const char *h2_protocol_get(const conn_rec *c)
{
return h2_ctx_protocol_get(c);
}
void h2_switch_register_hooks(void)
{
ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
}

View File

@@ -13,17 +13,17 @@
* limitations under the License. * limitations under the License.
*/ */
#ifndef __mod_h2__h2_alpn__ #ifndef __mod_h2__h2_switch__
#define __mod_h2__h2_alpn__ #define __mod_h2__h2_switch__
/* /*
* One time, post config intialization. * One time, post config intialization.
*/ */
apr_status_t h2_alpn_init(apr_pool_t *pool, server_rec *s); apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s);
/* Register apache hooks for ALPN protocol /* Register apache hooks for protocol switching
*/ */
void h2_alpn_register_hooks(void); void h2_switch_register_hooks(void);
#endif /* defined(__mod_h2__h2_h2__) */ #endif /* defined(__mod_h2__h2_switch__) */

View File

@@ -162,7 +162,7 @@ h2_task *h2_task_create(long session_id,
h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task)); h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task));
if (task == NULL) { if (task == NULL) {
ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool, ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, stream_pool,
"h2_task(%ld-%d): create stream task", APLOGNO(02941) "h2_task(%ld-%d): create stream task",
session_id, stream_id); session_id, stream_id);
h2_mplx_out_close(mplx, stream_id); h2_mplx_out_close(mplx, stream_id);
return NULL; return NULL;
@@ -256,7 +256,8 @@ apr_status_t h2_task_do(h2_task *task, h2_worker *worker)
} }
else { else {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, &env.c,
"h2_task(%s): error setting up h2_task_env", env.id); APLOGNO(02957) "h2_task(%s): error setting up h2_task_env",
env.id);
} }
if (env.input) { if (env.input) {

View File

@@ -117,7 +117,7 @@ apr_status_t h2_task_input_read(h2_task_input *input,
status = apr_brigade_length(input->bb, 1, &bblen); status = apr_brigade_length(input->bb, 1, &bblen);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, f->c,
"h2_task_input(%s): brigade length fail", APLOGNO(02958) "h2_task_input(%s): brigade length fail",
input->env->id); input->env->id);
return status; return status;
} }
@@ -201,8 +201,8 @@ apr_status_t h2_task_input_read(h2_task_input *input,
/* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
* to support it. Seems to work. */ * to support it. Seems to work. */
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
"h2_task_input, unsupported READ mode %d", APLOGNO(02942)
mode); "h2_task_input, unsupported READ mode %d", mode);
return APR_ENOTIMPL; return APR_ENOTIMPL;
} }
} }

View File

@@ -39,11 +39,13 @@ h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool,
h2_to_h1 *to_h1; h2_to_h1 *to_h1;
if (!method) { if (!method) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
APLOGNO(02943)
"h2_to_h1: header start but :method missing"); "h2_to_h1: header start but :method missing");
return NULL; return NULL;
} }
if (!path) { if (!path) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
APLOGNO(02944)
"h2_to_h1: header start but :path missing"); "h2_to_h1: header start but :path missing");
return NULL; return NULL;
} }
@@ -78,6 +80,7 @@ apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
if (!apr_strnatcasecmp("chunked", value)) { if (!apr_strnatcasecmp("chunked", value)) {
/* This should never arrive here in a HTTP/2 request */ /* This should never arrive here in a HTTP/2 request */
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
APLOGNO(02945)
"h2_to_h1: 'transfer-encoding: chunked' received"); "h2_to_h1: 'transfer-encoding: chunked' received");
return APR_BADARG; return APR_BADARG;
} }
@@ -87,6 +90,7 @@ apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1,
to_h1->content_len = apr_strtoi64(value, &end, 10); to_h1->content_len = apr_strtoi64(value, &end, 10);
if (value == end) { if (value == end) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
APLOGNO(02959)
"h2_request(%d): content-length value not parsed: %s", "h2_request(%d): content-length value not parsed: %s",
to_h1->stream_id, value); to_h1->stream_id, value);
return APR_EINVAL; return APR_EINVAL;
@@ -187,6 +191,7 @@ apr_status_t h2_to_h1_end_headers(h2_to_h1 *to_h1, h2_task *task, int eos)
apr_status_t status = h2_to_h1_close(to_h1); apr_status_t status = h2_to_h1_close(to_h1);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
APLOGNO(02960)
"h2_to_h1(%ld-%d): end headers, eos=%d", "h2_to_h1(%ld-%d): end headers, eos=%d",
to_h1->m->id, to_h1->stream_id, eos); to_h1->m->id, to_h1->stream_id, eos);
} }
@@ -243,6 +248,7 @@ apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1,
to_h1->remain_len -= len; to_h1->remain_len -= len;
if (to_h1->remain_len < 0) { if (to_h1->remain_len < 0) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c, ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, to_h1->m->c,
APLOGNO(02961)
"h2_to_h1(%ld-%d): got %ld more content bytes than announced " "h2_to_h1(%ld-%d): got %ld more content bytes than announced "
"in content-length header: %ld", "in content-length header: %ld",
to_h1->m->id, to_h1->stream_id, to_h1->m->id, to_h1->stream_id,
@@ -263,7 +269,7 @@ apr_status_t h2_to_h1_flush(h2_to_h1 *to_h1)
status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb); status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c, ap_log_cerror(APLOG_MARK, APLOG_ERR, status, to_h1->m->c,
"h2_request(%d): pushing request data", APLOGNO(02946) "h2_request(%d): pushing request data",
to_h1->stream_id); to_h1->stream_id);
} }
} }

View File

@@ -1,201 +0,0 @@
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <apr_optional.h>
#include <apr_optional_hooks.h>
#include <ap_mpm.h>
#include <httpd.h>
#include <http_core.h>
#include <http_config.h>
#include <http_connection.h>
#include <http_log.h>
#include <http_protocol.h>
#include <http_request.h>
#include "h2_private.h"
#include "h2_conn.h"
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_h2.h"
#include "h2_upgrade.h"
#include "h2_util.h"
static int h2_upgrade_request_handler(request_rec *r);
static const char *h2_get_upgrade_proto(request_rec *r);
static int h2_upgrade_to(request_rec *r, const char *proto);
static int h2_upgrade_options(request_rec *r);
void h2_upgrade_register_hooks(void)
{
ap_hook_handler(h2_upgrade_request_handler, NULL, NULL, APR_HOOK_FIRST - 1);
ap_hook_map_to_storage(h2_upgrade_options, NULL, NULL, APR_HOOK_FIRST);
}
static int h2_upgrade_options(request_rec *r)
{
if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
(r->uri[1] == '\0')) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"h2c: request OPTIONS * seen");
return h2_upgrade_request_handler(r);
}
return DECLINED;
}
static int h2_upgrade_request_handler(request_rec *r)
{
h2_ctx *ctx = h2_ctx_rget(r);
h2_config *cfg = h2_config_rget(r);
int enabled_for_request = h2_config_geti(cfg, H2_CONF_ENABLED);
if (h2_ctx_is_task(ctx) || h2_ctx_is_active(ctx)) {
/* talking h2 already, either task for main conn */
if (!enabled_for_request) {
/* we have a request for a server (vhost) where h2 is
* not enabled. This happened over a connection on which
* we talk h2.
* Tell the client, she should open a new connection to that
* vhost to get fresh protocol negotiations.
*/
r->status = 421;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, r->status, r,
"421-ing h2 request to host %s", r->hostname);
return DONE;
}
return DECLINED;
}
/* not talking h2 (yet) */
if (enabled_for_request) {
/* Check for the start of an h2c Upgrade dance. */
const char *proto = h2_get_upgrade_proto(r);
if (proto) {
const char *clen;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"seeing %s upgrade invitation", proto);
/* We do not handle upgradeable requests with a body.
* The reason being that we would need to read the body in full
* before we ca use HTTP2 frames on the wire.
*
* This seems to be consensus among server implemntations and
* clients are advised to use an "OPTIONS *" before a POST.
*/
clen = apr_table_get(r->headers_in, "Content-Length");
if (clen && strcmp(clen, "0")) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"upgrade with content-length: %s, declined", clen);
return DECLINED;
}
return h2_upgrade_to(r, proto);
}
}
return DECLINED;
}
static const char *h2_get_upgrade_proto(request_rec *r)
{
const char *proto, *conn;
const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
if (upgrade && *upgrade) {
conn = apr_table_get(r->headers_in, "Connection");
if (h2_util_contains_token(r->pool, conn, "Upgrade")
&& apr_table_get(r->headers_in, "HTTP2-Settings")) {
/* HTTP/1 Upgrade: is just another mechanism to switch
* protocols on a connection, same as ALPN or NPN.
* Security desirability aside, the bit protocol spoken
* afterwards is the same. Why require different identifier?
*
* We allow the same tokens as in ALPN negotiation, plus the
* special 'c' variants that RFC 7540 defines. We just do not
* care about the transport here.
*/
proto = h2_util_first_token_match(r->pool, upgrade,
h2_alpn_protos,
h2_alpn_protos_len);
if (!proto) {
proto = h2_util_first_token_match(r->pool, upgrade,
h2_upgrade_protos,
h2_upgrade_protos_len);
}
if (proto) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
"suiteable upgrade detected: %s %s, "
"Upgrade: %s", r->method, r->uri, upgrade);
return proto;
}
}
}
return NULL;
}
static int h2_upgrade_to(request_rec *r, const char *proto)
{
conn_rec *c = r->connection;
h2_ctx *ctx = h2_ctx_rget(r);
apr_status_t status;
h2_ctx_pnego_set_done(ctx, proto);
/* Let the client know what we are upgrading to. */
apr_table_clear(r->headers_out);
apr_table_setn(r->headers_out, "Upgrade", proto);
apr_table_setn(r->headers_out, "Connection", "Upgrade");
r->status = HTTP_SWITCHING_PROTOCOLS;
r->status_line = ap_get_status_line(r->status);
ap_send_interim_response(r, 1);
/* Make sure the core filter that parses http1 requests does
* not mess with our http2 frames. */
if (APLOGrtrace2(r)) {
ap_filter_t *filter = r->input_filters;
while (filter) {
ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
"h2_conn(%ld), has request filter %s",
r->connection->id, filter->frec->name);
filter = filter->next;
}
}
ap_remove_input_filter_byhandle(r->input_filters, "http_in");
ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
/* Ok, start an h2_conn on this one. */
status = h2_conn_rprocess(r);
if (status != DONE) {
/* Nothing really to do about this. */
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
"session proessed, unexpected status");
}
/* make sure httpd closes the connection after this */
c->keepalive = AP_CONN_CLOSE;
ap_lingering_close(c);
if (c->sbh) {
ap_update_child_status_from_conn(c->sbh, SERVER_CLOSING, c);
}
return DONE;
}

View File

@@ -1,24 +0,0 @@
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef __mod_h2__h2_upgrade__
#define __mod_h2__h2_upgrade__
/* Specific function to HTTP/1 connection Upgradeds.
*/
void h2_upgrade_register_hooks(void);
#endif /* defined(__mod_h2__h2_upgrade__) */

View File

@@ -109,7 +109,7 @@ static const int BASE64URL_TABLE[] = {
-1, -1, -1, -1 -1, -1, -1, -1
}; };
apr_size_t h2_util_base64url_decode(unsigned char **decoded, const char *encoded, apr_size_t h2_util_base64url_decode(const char **decoded, const char *encoded,
apr_pool_t *pool) apr_pool_t *pool)
{ {
const unsigned char *e = (const unsigned char *)encoded; const unsigned char *e = (const unsigned char *)encoded;
@@ -126,7 +126,7 @@ apr_size_t h2_util_base64url_decode(unsigned char **decoded, const char *encoded
*decoded = apr_pcalloc(pool, len+1); *decoded = apr_pcalloc(pool, len+1);
i = 0; i = 0;
d = *decoded; d = (unsigned char*)*decoded;
for (; i < mlen; i += 4) { for (; i < mlen; i += 4) {
n = ((BASE64URL_TABLE[ e[i+0] ] << 18) + n = ((BASE64URL_TABLE[ e[i+0] ] << 18) +
(BASE64URL_TABLE[ e[i+1] ] << 12) + (BASE64URL_TABLE[ e[i+1] ] << 12) +
@@ -359,7 +359,8 @@ apr_status_t h2_util_move(apr_bucket_brigade *to, apr_bucket_brigade *from,
status = apr_file_setaside(&fd, fd, to->p); status = apr_file_setaside(&fd, fd, to->p);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_perror(APLOG_MARK, APLOG_ERR, status, to->p, ap_log_perror(APLOG_MARK, APLOG_ERR, status, to->p,
"h2_util: %s, setaside FILE", msg); APLOGNO(02947) "h2_util: %s, setaside FILE",
msg);
return status; return status;
} }
} }

View File

@@ -42,7 +42,7 @@ const char *h2_util_first_token_match(apr_pool_t *pool, const char *s,
* I always wanted to write my own base64url decoder...not. See * I always wanted to write my own base64url decoder...not. See
* https://tools.ietf.org/html/rfc4648#section-5 for description. * https://tools.ietf.org/html/rfc4648#section-5 for description.
*/ */
apr_size_t h2_util_base64url_decode(unsigned char **decoded, apr_size_t h2_util_base64url_decode(const char **decoded,
const char *encoded, const char *encoded,
apr_pool_t *pool); apr_pool_t *pool);

View File

@@ -20,7 +20,7 @@
* @macro * @macro
* Version number of the h2 module as c string * Version number of the h2 module as c string
*/ */
#define MOD_H2_VERSION "0.8.1" #define MOD_H2_VERSION "1.0.0"
/** /**
* @macro * @macro
@@ -28,7 +28,7 @@
* release. This is a 24 bit number with 8 bits for major number, 8 bits * 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. * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/ */
#define MOD_H2_VERSION_NUM 0x000801 #define MOD_H2_VERSION_NUM 0x010000
#endif /* mod_h2_h2_version_h */ #endif /* mod_h2_h2_version_h */

View File

@@ -41,7 +41,8 @@ static void *execute(apr_thread_t *thread, void *wctx)
APR_PROTO_TCP, worker->pool); APR_PROTO_TCP, worker->pool);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_perror(APLOG_MARK, APLOG_ERR, status, worker->pool, ap_log_perror(APLOG_MARK, APLOG_ERR, status, worker->pool,
"h2_worker(%d): alloc socket", worker->id); APLOGNO(02948) "h2_worker(%d): alloc socket",
worker->id);
worker->worker_done(worker, worker->ctx); worker->worker_done(worker, worker->ctx);
return NULL; return NULL;
} }

View File

@@ -343,7 +343,7 @@ void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs)
{ {
if (idle_secs <= 0) { if (idle_secs <= 0) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s, ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
"h2_workers: max_worker_idle_sec value of %d" APLOGNO(02962) "h2_workers: max_worker_idle_sec value of %d"
" is not valid, ignored.", idle_secs); " is not valid, ignored.", idle_secs);
return; return;
} }

View File

@@ -31,8 +31,7 @@
#include "h2_config.h" #include "h2_config.h"
#include "h2_ctx.h" #include "h2_ctx.h"
#include "h2_h2.h" #include "h2_h2.h"
#include "h2_alpn.h" #include "h2_switch.h"
#include "h2_upgrade.h"
#include "h2_version.h" #include "h2_version.h"
@@ -94,14 +93,14 @@ static int h2_post_config(apr_pool_t *p, apr_pool_t *plog,
break; break;
case H2_MPM_UNKNOWN: case H2_MPM_UNKNOWN:
/* ??? */ /* ??? */
ap_log_error( APLOG_MARK, APLOG_ERR, 0, s, ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
"post_config: mpm type unknown"); "post_config: mpm type unknown");
break; break;
} }
status = h2_h2_init(p, s); status = h2_h2_init(p, s);
if (status == APR_SUCCESS) { if (status == APR_SUCCESS) {
status = h2_alpn_init(p, s); status = h2_switch_init(p, s);
} }
return status; return status;
@@ -116,15 +115,10 @@ static void h2_child_init(apr_pool_t *pool, server_rec *s)
apr_status_t status = h2_conn_child_init(pool, s); apr_status_t status = h2_conn_child_init(pool, s);
if (status != APR_SUCCESS) { if (status != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_ERR, status, s, ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
"initializing connection handling"); APLOGNO(02949) "initializing connection handling");
} }
} }
const char *h2_get_protocol(conn_rec *c)
{
return h2_ctx_pnego_get(h2_ctx_get(c));
}
/* Install this module into the apache2 infrastructure. /* Install this module into the apache2 infrastructure.
*/ */
static void h2_hooks(apr_pool_t *pool) static void h2_hooks(apr_pool_t *pool)
@@ -142,16 +136,11 @@ static void h2_hooks(apr_pool_t *pool)
ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
h2_h2_register_hooks(); h2_h2_register_hooks();
h2_alpn_register_hooks(); h2_switch_register_hooks();
h2_upgrade_register_hooks();
h2_task_register_hooks(); h2_task_register_hooks();
h2_alt_svc_register_hooks(); h2_alt_svc_register_hooks();
/* We offer a function to other modules that lets them retrieve
* the h2 protocol used on a connection (if any).
*/
APR_REGISTER_OPTIONAL_FN(h2_get_protocol);
} }

View File

@@ -16,13 +16,4 @@
#ifndef mod_h2_mod_h2_h #ifndef mod_h2_mod_h2_h
#define mod_h2_mod_h2_h #define mod_h2_mod_h2_h
const char *h2_get_protocol(conn_rec *c);
/**
* An optional function which returns the h2 protocol used on the given
* connection and NULL if no h2* protocol is active on it.
*/
APR_DECLARE_OPTIONAL_FN(const char *, h2_get_protocol, (conn_rec*));
#endif #endif

View File

@@ -283,12 +283,6 @@ static const command_rec ssl_config_cmds[] = {
"OpenSSL configuration command") "OpenSSL configuration command")
#endif #endif
#ifdef HAVE_TLS_ALPN
SSL_CMD_SRV(ALPNPreference, ITERATE,
"Preference in Application-Layer Protocol Negotiation (ALPN), "
"protocols are chosen in the specified order")
#endif
/* Deprecated directives. */ /* Deprecated directives. */
AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL, AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL,
"SSLLog directive is no longer supported - use ErrorLog."), "SSLLog directive is no longer supported - use ErrorLog."),
@@ -451,37 +445,6 @@ static int ssl_engine_disable(conn_rec *c)
return 1; return 1;
} }
static int modssl_register_alpn(conn_rec *c,
ssl_alpn_propose_protos advertisefn,
ssl_alpn_proto_negotiated negotiatedfn)
{
#ifdef HAVE_TLS_ALPN
SSLConnRec *sslconn = myConnConfig(c);
if (!sslconn) {
return DECLINED;
}
if (!sslconn->alpn_proposefns) {
sslconn->alpn_proposefns =
apr_array_make(c->pool, 5, sizeof(ssl_alpn_propose_protos));
sslconn->alpn_negofns =
apr_array_make(c->pool, 5, sizeof(ssl_alpn_proto_negotiated));
}
if (advertisefn)
APR_ARRAY_PUSH(sslconn->alpn_proposefns, ssl_alpn_propose_protos) =
advertisefn;
if (negotiatedfn)
APR_ARRAY_PUSH(sslconn->alpn_negofns, ssl_alpn_proto_negotiated) =
negotiatedfn;
return OK;
#else
return DECLINED;
#endif
}
int ssl_init_ssl_connection(conn_rec *c, request_rec *r) int ssl_init_ssl_connection(conn_rec *c, request_rec *r)
{ {
SSLSrvConfigRec *sc; SSLSrvConfigRec *sc;
@@ -650,7 +613,6 @@ static void ssl_register_hooks(apr_pool_t *p)
APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable);
APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); APR_REGISTER_OPTIONAL_FN(ssl_engine_disable);
APR_REGISTER_OPTIONAL_FN(modssl_register_alpn);
ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl", ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl",
AUTHZ_PROVIDER_VERSION, AUTHZ_PROVIDER_VERSION,

View File

@@ -93,46 +93,5 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
/** The alpn_propose_proto callback allows other modules to propose
* the name of the protocol that will be chosen during the
* Application-Layer Protocol Negotiation (ALPN) portion of the SSL handshake.
* The callback is given the connection and a list of NULL-terminated
* protocol strings as supported by the client. If this client_protos is
* non-empty, it must pick its preferred protocol from that list. Otherwise
* it should add its supported protocols in order of precedence.
* The callback should not yet modify the connection or install any filters
* as its proposal(s) may be overridden by another callback or server
* configuration.
* It should return OK or, to prevent further processing of (other modules')
* callbacks, return DONE.
*/
typedef int (*ssl_alpn_propose_protos)(conn_rec *connection,
apr_array_header_t *client_protos,
apr_array_header_t *proposed_protos);
/** The alpn_proto_negotiated callback allows other modules to discover
* the name of the protocol that was chosen during the Application-Layer
* Protocol Negotiation (ALPN) portion of the SSL handshake.
* The callback is given the connection, a
* non-NUL-terminated string containing the protocol name, and the
* length of the string; it should do something appropriate
* (i.e. insert or remove filters) and return OK. To prevent further
* processing of (other modules') callbacks, return DONE. */
typedef int (*ssl_alpn_proto_negotiated)(conn_rec *connection,
const char *proto_name,
apr_size_t proto_name_len);
/* An optional function which can be used to register a pair of callbacks
* for ALPN handling.
* This optional function should be invoked from a pre_connection hook
* which runs *after* mod_ssl.c's pre_connection hook. The function returns
* OK if the callbacks are registered, or DECLINED otherwise (for example if
* mod_ssl does not support ALPN).
*/
APR_DECLARE_OPTIONAL_FN(int, modssl_register_alpn,
(conn_rec *conn,
ssl_alpn_propose_protos proposefn,
ssl_alpn_proto_negotiated negotiatedfn));
#endif /* __MOD_SSL_H__ */ #endif /* __MOD_SSL_H__ */
/** @} */ /** @} */

View File

@@ -161,9 +161,6 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_CERTIFICATE); SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_CERTIFICATE);
mctx->ssl_ctx_param = apr_array_make(p, 5, sizeof(ssl_ctx_param_t)); mctx->ssl_ctx_param = apr_array_make(p, 5, sizeof(ssl_ctx_param_t));
#endif #endif
#ifdef HAVE_TLS_ALPN
mctx->ssl_alpn_pref = apr_array_make(p, 5, sizeof(const char *));
#endif
} }
static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc, static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc,
@@ -308,9 +305,6 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p,
#ifdef HAVE_SSL_CONF_CMD #ifdef HAVE_SSL_CONF_CMD
cfgMergeArray(ssl_ctx_param); cfgMergeArray(ssl_ctx_param);
#endif #endif
#ifdef HAVE_TLS_ALPN
cfgMergeArray(ssl_alpn_pref);
#endif
} }
static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p, static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p,
@@ -1863,16 +1857,6 @@ const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg,
} }
#endif #endif
#ifdef HAVE_TLS_ALPN
const char *ssl_cmd_SSLALPNPreference(cmd_parms *cmd, void *dcfg,
const char *protocol)
{
SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
APR_ARRAY_PUSH(sc->server->ssl_alpn_pref, const char *) = protocol;
return NULL;
}
#endif
#ifdef HAVE_SRP #ifdef HAVE_SRP
const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg,

View File

@@ -1495,24 +1495,26 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f,
SSLConnRec *sslconn = myConnConfig(f->c); SSLConnRec *sslconn = myConnConfig(f->c);
const unsigned char *next_proto = NULL; const unsigned char *next_proto = NULL;
unsigned next_proto_len = 0; unsigned next_proto_len = 0;
const char *protocol;
int n; int n;
if (sslconn->alpn_negofns) {
SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len); SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len);
if (next_proto && next_proto_len) {
protocol = apr_pstrmemdup(f->c->pool, (const char *)next_proto,
next_proto_len);
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c, ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c,
APLOGNO(02836) "ALPN selected protocol: '%s'", APLOGNO(02836) "ALPN selected protocol: '%s'",
(next_proto && next_proto_len) ? protocol);
apr_pstrmemdup(f->c->pool,
(const char *)next_proto,
next_proto_len) :
"(null)");
for (n = 0; n < sslconn->alpn_negofns->nelts; n++) {
ssl_alpn_proto_negotiated fn =
APR_ARRAY_IDX(sslconn->alpn_negofns, n,
ssl_alpn_proto_negotiated);
if (fn(f->c, (const char *)next_proto, next_proto_len) == DONE) if (strcmp(protocol, ap_run_protocol_get(f->c))) {
break; status = ap_switch_protocol(f->c, NULL, sslconn->server,
protocol);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, f->c,
APLOGNO(02908) "protocol switch to '%s' failed",
protocol);
return status;
}
} }
} }
inctx->alpn_finished = 1; inctx->alpn_finished = 1;

View File

@@ -1923,23 +1923,29 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc)
#ifdef HAVE_TLSEXT #ifdef HAVE_TLSEXT
/* /*
* This callback function is executed when OpenSSL encounters an extended * This function sets the virtual host from an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066). * client hello with a server name indication extension ("SNI", cf. RFC 6066).
*/ */
int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx) static apr_status_t init_vhost(conn_rec *c, SSL *ssl)
{ {
const char *servername = const char *servername;
SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
if (c) { if (c) {
SSLConnRec *sslcon = myConnConfig(c);
if (sslcon->server != c->base_server) {
/* already found the vhost */
return APR_SUCCESS;
}
servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
if (servername) { if (servername) {
if (ap_vhost_iterate_given_conn(c, ssl_find_vhost, if (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
(void *)servername)) { (void *)servername)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043) ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043)
"SSL virtual host for servername %s found", "SSL virtual host for servername %s found",
servername); servername);
return SSL_TLSEXT_ERR_OK; return APR_SUCCESS;
} }
else { else {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044) ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044)
@@ -1970,7 +1976,19 @@ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
} }
} }
return SSL_TLSEXT_ERR_NOACK; return APR_NOTFOUND;
}
/*
* This callback function is executed when OpenSSL encounters an extended
* client hello with a server name indication extension ("SNI", cf. RFC 6066).
*/
int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx)
{
conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
apr_status_t status = init_vhost(c, ssl);
return (status == APR_SUCCESS)? SSL_TLSEXT_ERR_OK : SSL_TLSEXT_ERR_NOACK;
} }
/* /*
@@ -2170,41 +2188,6 @@ int ssl_callback_SessionTicket(SSL *ssl,
#endif /* HAVE_TLS_SESSION_TICKETS */ #endif /* HAVE_TLS_SESSION_TICKETS */
#ifdef HAVE_TLS_ALPN #ifdef HAVE_TLS_ALPN
static int ssl_array_index(apr_array_header_t *array, const char *s)
{
int i;
for (i = 0; i < array->nelts; i++) {
const char *p = APR_ARRAY_IDX(array, i, const char *);
if (!strcmp(p, s)) {
return i;
}
}
return -1;
}
/*
* Compare two ALPN protocol proposal. Result is similar to strcmp():
* 0 gives same precedence, >0 means proto1 is preferred.
*/
static int ssl_cmp_alpn_protos(modssl_ctx_t *ctx,
const char *proto1,
const char *proto2)
{
if (ctx && ctx->ssl_alpn_pref) {
int index1 = ssl_array_index(ctx->ssl_alpn_pref, proto1);
int index2 = ssl_array_index(ctx->ssl_alpn_pref, proto2);
if (index2 > index1) {
return (index1 >= 0) ? 1 : -1;
}
else if (index1 > index2) {
return (index2 >= 0) ? -1 : 1;
}
}
/* both have the same index (mabye -1 or no pref configured) and we compare
* the names so that spdy3 gets precedence over spdy2. That makes
* the outcome at least deterministic. */
return strcmp((const char *)proto1, (const char *)proto2);
}
/* /*
* This callback function is executed when the TLS Application-Layer * This callback function is executed when the TLS Application-Layer
@@ -2225,14 +2208,9 @@ int ssl_callback_alpn_select(SSL *ssl,
{ {
conn_rec *c = (conn_rec*)SSL_get_app_data(ssl); conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
SSLConnRec *sslconn = myConnConfig(c); SSLConnRec *sslconn = myConnConfig(c);
server_rec *s = mySrvFromConn(c);
SSLSrvConfigRec *sc = mySrvConfig(s);
modssl_ctx_t *mctx = myCtxConfig(sslconn, sc);
const char *alpn_http1 = "http/1.1";
apr_array_header_t *client_protos; apr_array_header_t *client_protos;
apr_array_header_t *proposed_protos;
int i;
size_t len; size_t len;
int i;
/* If the connection object is not available, /* If the connection object is not available,
* then there's nothing for us to do. */ * then there's nothing for us to do. */
@@ -2261,48 +2239,15 @@ int ssl_callback_alpn_select(SSL *ssl,
i += plen; i += plen;
} }
proposed_protos = apr_array_make(c->pool, client_protos->nelts+1, /* The order the callbacks are invoked from TLS extensions is, unfortunately
sizeof(char *)); * not defined and older openssl versions do call ALPN selection before
* they callback the SNI. We need to make sure that we know which vhost
if (sslconn->alpn_proposefns != NULL) { * we are dealing with so we respect the correct protocols.
/* Invoke our alpn_propose functions, giving other modules a chance to
* propose protocol names for selection. We might have several such
* functions installed and if two make a proposal, we need to give
* preference to one.
*/ */
for (i = 0; i < sslconn->alpn_proposefns->nelts; i++) { init_vhost(c, ssl);
ssl_alpn_propose_protos fn =
APR_ARRAY_IDX(sslconn->alpn_proposefns, i,
ssl_alpn_propose_protos);
if (fn(c, client_protos, proposed_protos) == DONE)
break;
}
}
if (proposed_protos->nelts <= 0) {
/* Regardless of installed hooks, the http/1.1 protocol is always
* supported by us. Choose it if none other matches. */
if (ssl_array_index(client_protos, alpn_http1) < 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02839)
"none of the client ALPN protocols are supported");
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
*out = (const unsigned char*)alpn_http1;
*outlen = (unsigned char)strlen(alpn_http1);
return SSL_TLSEXT_ERR_OK;
}
/* Now select the most preferred protocol from the proposals. */
*out = APR_ARRAY_IDX(proposed_protos, 0, const unsigned char *);
for (i = 1; i < proposed_protos->nelts; ++i) {
const char *proto = APR_ARRAY_IDX(proposed_protos, i, const char *);
/* Do we prefer it over existing candidate? */
if (ssl_cmp_alpn_protos(mctx, (const char *)*out, proto) < 0) {
*out = (const unsigned char *)proto;
}
}
*out = (const unsigned char *)ap_select_protocol(c, NULL, sslconn->server,
client_protos);
len = strlen((const char*)*out); len = strlen((const char*)*out);
if (len > 255) { if (len > 255) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840) ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840)

View File

@@ -438,12 +438,6 @@ typedef struct {
* connection */ * connection */
} reneg_state; } reneg_state;
#ifdef HAVE_TLS_ALPN
/* Poor man's inter-module optional hooks for ALPN. */
apr_array_header_t *alpn_proposefns; /* list of ALPN propose callbacks */
apr_array_header_t *alpn_negofns; /* list of ALPN negotiation callbacks. */
#endif
server_rec *server; server_rec *server;
} SSLConnRec; } SSLConnRec;
@@ -625,10 +619,6 @@ typedef struct {
SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */ SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */
apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */ apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */
#endif #endif
#ifdef HAVE_TLS_ALPN
apr_array_header_t *ssl_alpn_pref; /* list of ALPN protocol IDs */
#endif
} modssl_ctx_t; } modssl_ctx_t;
struct SSLSrvConfigRec { struct SSLSrvConfigRec {
@@ -755,10 +745,6 @@ const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, int flag);
const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2); const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2);
#endif #endif
#ifdef HAVE_TLS_ALPN
const char *ssl_cmd_SSLALPNPreference(cmd_parms *cmd, void *dcfg, const char *protocol);
#endif
#ifdef HAVE_SRP #ifdef HAVE_SRP
const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, const char *arg); const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, const char *arg);
const char *ssl_cmd_SSLSRPUnknownUserSeed(cmd_parms *cmd, void *dcfg, const char *arg); const char *ssl_cmd_SSLSRPUnknownUserSeed(cmd_parms *cmd, void *dcfg, const char *arg);

View File

@@ -478,6 +478,8 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
conf->trace_enable = AP_TRACE_UNSET; conf->trace_enable = AP_TRACE_UNSET;
conf->protocols = apr_array_make(a, 5, sizeof(const char *));
return (void *)conf; return (void *)conf;
} }
@@ -551,6 +553,8 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
? virt->merge_trailers ? virt->merge_trailers
: base->merge_trailers; : base->merge_trailers;
conf->protocols = apr_array_append(p, base->protocols, virt->protocols);
return conf; return conf;
} }
@@ -3799,12 +3803,33 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy,
return NULL; return NULL;
} }
static const char *set_protocols(cmd_parms *cmd, void *dummy,
const char *arg)
{
core_server_config *conf =
ap_get_core_module_config(cmd->server->module_config);
const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
if (err) {
return err;
}
/* Should we check for some ALPN valid char sequence here? */
const char **np = (const char **)apr_array_push(conf->protocols);
*np = arg;
return NULL;
}
static const char *set_http_protocol(cmd_parms *cmd, void *dummy, static const char *set_http_protocol(cmd_parms *cmd, void *dummy,
const char *arg) const char *arg)
{ {
core_server_config *conf = core_server_config *conf =
ap_get_core_module_config(cmd->server->module_config); ap_get_core_module_config(cmd->server->module_config);
if (!conf->protocols) {
}
if (strncmp(arg, "min=", 4) == 0) { if (strncmp(arg, "min=", 4) == 0) {
arg += 4; arg += 4;
if (strcmp(arg, "0.9") == 0) if (strcmp(arg, "0.9") == 0)
@@ -4445,6 +4470,8 @@ AP_INIT_FLAG("HttpContentLengthHeadZero", set_cl_head_zero, NULL, OR_OPTIONS,
"whether to permit Content-Length of 0 responses to HEAD requests"), "whether to permit Content-Length of 0 responses to HEAD requests"),
AP_INIT_FLAG("HttpExpectStrict", set_expect_strict, NULL, OR_OPTIONS, AP_INIT_FLAG("HttpExpectStrict", set_expect_strict, NULL, OR_OPTIONS,
"whether to return a 417 if a client doesn't send 100-Continue"), "whether to return a 417 if a client doesn't send 100-Continue"),
AP_INIT_ITERATE("Protocols", set_protocols, NULL, RSRC_CONF,
"Controls which protocols are allowed, sorted by preference"),
{ NULL } { NULL }
}; };
@@ -5226,6 +5253,73 @@ static void core_dump_config(apr_pool_t *p, server_rec *s)
} }
} }
static const char *core_protocol_get(const conn_rec *c)
{
return AP_PROTOCOL_HTTP1;
}
static int core_upgrade_handler(request_rec *r)
{
conn_rec *c = r->connection;
const char *upgrade = apr_table_get(r->headers_in, "Upgrade");
if (upgrade && *upgrade) {
const char *conn = apr_table_get(r->headers_in, "Connection");
if (ap_find_token(r->pool, conn, "upgrade")) {
apr_array_header_t *offers = NULL;
const char *err;
err = ap_parse_token_list_strict(r->pool, upgrade, &offers, 0);
if (err) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02910)
"parsing Upgrade header: %s", err);
return DECLINED;
}
if (offers && offers->nelts > 0) {
const char *protocol = ap_select_protocol(c, r, r->server,
offers);
if (strcmp(protocol, ap_run_protocol_get(c))) {
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02909)
"Upgrade selects '%s'", protocol);
/* Let the client know what we are upgrading to. */
apr_table_clear(r->headers_out);
apr_table_setn(r->headers_out, "Upgrade", protocol);
apr_table_setn(r->headers_out, "Connection", "Upgrade");
r->status = HTTP_SWITCHING_PROTOCOLS;
r->status_line = ap_get_status_line(r->status);
ap_send_interim_response(r, 1);
ap_switch_protocol(c, r, r->server, protocol);
/* make sure httpd closes the connection after this */
c->keepalive = AP_CONN_CLOSE;
ap_lingering_close(c);
if (c->sbh) {
ap_update_child_status_from_conn(c->sbh,
SERVER_CLOSING, c);
}
return DONE;
}
}
}
}
return DECLINED;
}
static int core_upgrade_storage(request_rec *r)
{
if ((r->method_number == M_OPTIONS) && r->uri && (r->uri[0] == '*') &&
(r->uri[1] == '\0')) {
return core_upgrade_handler(r);
}
return DECLINED;
}
static void register_hooks(apr_pool_t *p) static void register_hooks(apr_pool_t *p)
{ {
errorlog_hash = apr_hash_make(p); errorlog_hash = apr_hash_make(p);
@@ -5248,10 +5342,12 @@ static void register_hooks(apr_pool_t *p)
ap_hook_check_config(core_check_config,NULL,NULL,APR_HOOK_FIRST); ap_hook_check_config(core_check_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_FIRST); ap_hook_test_config(core_dump_config,NULL,NULL,APR_HOOK_FIRST);
ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_translate_name(ap_core_translate,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_map_to_storage(core_upgrade_storage,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_map_to_storage(core_map_to_storage,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST); ap_hook_open_logs(ap_open_logs,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(core_child_init,NULL,NULL,APR_HOOK_REALLY_FIRST); ap_hook_child_init(core_child_init,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE); ap_hook_child_init(ap_logs_child_init,NULL,NULL,APR_HOOK_MIDDLE);
ap_hook_handler(core_upgrade_handler,NULL,NULL,APR_HOOK_REALLY_FIRST);
ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_handler(default_handler,NULL,NULL,APR_HOOK_REALLY_LAST);
/* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */ /* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */
ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST); ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
@@ -5267,6 +5363,7 @@ static void register_hooks(apr_pool_t *p)
ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST); ap_hook_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST);
ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL, ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL,
APR_HOOK_MIDDLE); APR_HOOK_MIDDLE);
ap_hook_protocol_get(core_protocol_get, NULL, NULL, APR_HOOK_REALLY_LAST);
/* register the core's insert_filter hook and register core-provided /* register the core's insert_filter hook and register core-provided
* filters * filters

View File

@@ -67,6 +67,9 @@ APR_HOOK_STRUCT(
APR_HOOK_LINK(http_scheme) APR_HOOK_LINK(http_scheme)
APR_HOOK_LINK(default_port) APR_HOOK_LINK(default_port)
APR_HOOK_LINK(note_auth_failure) APR_HOOK_LINK(note_auth_failure)
APR_HOOK_LINK(protocol_propose)
APR_HOOK_LINK(protocol_switch)
APR_HOOK_LINK(protocol_get)
) )
AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL; AP_DECLARE_DATA ap_filter_rec_t *ap_old_write_func = NULL;
@@ -1944,6 +1947,125 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers)
apr_brigade_destroy(x.bb); apr_brigade_destroy(x.bb);
} }
/* Something like this must be in APR, only I do not find it... */
static int array_index(apr_array_header_t *array, const char *s)
{
int i;
for (i = 0; i < array->nelts; i++) {
const char *p = APR_ARRAY_IDX(array, i, const char *);
if (!strcmp(p, s)) {
return i;
}
}
return -1;
}
/*
* Compare two protocol identifier. Result is similar to strcmp():
* 0 gives same precedence, >0 means proto1 is preferred.
*/
static int protocol_cmp(apr_array_header_t *preferences,
const char *proto1,
const char *proto2)
{
if (preferences && preferences->nelts > 0) {
int index1 = array_index(preferences, proto1);
int index2 = array_index(preferences, proto2);
if (index2 > index1) {
return (index1 >= 0) ? 1 : -1;
}
else if (index1 > index2) {
return (index2 >= 0) ? -1 : 1;
}
}
/* both have the same index (mabye -1 or no pref configured) and we compare
* the names so that spdy3 gets precedence over spdy2. That makes
* the outcome at least deterministic. */
return strcmp(proto1, proto2);
}
AP_DECLARE(const char *) ap_select_protocol(conn_rec *c, request_rec *r,
server_rec *s,
apr_array_header_t *choices)
{
apr_pool_t *pool = r? r->pool : c->pool;
apr_array_header_t *proposals;
const char *protocol = NULL;
core_server_config *conf = ap_get_core_module_config(s->module_config);
if (APLOGcdebug(c)) {
const char *p = apr_array_pstrcat(pool, conf->protocols, ',');
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"select protocol from %s, choices=%s for server %s",
p, apr_array_pstrcat(pool, choices, ','),
s->server_hostname);
}
proposals = apr_array_make(pool, choices->nelts+1, sizeof(char *));
ap_run_protocol_propose(c, r, s, choices, proposals);
if (proposals->nelts > 0) {
int i;
/* Select the most preferred protocol */
if (APLOGcdebug(c)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
"select protocol, proposals=%s",
apr_array_pstrcat(pool, proposals, ','));
}
for (i = 0; i < proposals->nelts; ++i) {
const char *p = APR_ARRAY_IDX(proposals, i, const char *);
if (conf->protocols->nelts > 0
&& array_index(conf->protocols, p) < 0) {
/* not a permitted protocol here */
continue;
}
else if (!protocol
|| (protocol_cmp(conf->protocols, protocol, p) < 0)) {
/* none selected yet or this on has preference */
protocol = p;
}
}
}
if (APLOGcdebug(c)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "selected protocol=%s",
protocol? protocol : "(none)");
}
return protocol? protocol : ap_run_protocol_get(c);
}
AP_DECLARE(apr_status_t) ap_switch_protocol(conn_rec *c, request_rec *r,
server_rec *s,
const char *protocol)
{
const char *current = ap_run_protocol_get(c);
int rc;
if (!strcmp(current, protocol)) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c, APLOGNO(02906)
"already at it, protocol_switch to %s",
protocol);
return APR_SUCCESS;
}
rc = ap_run_protocol_switch(c, r, s, protocol);
switch (rc) {
case DECLINED:
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02907)
"no implementation for protocol_switch to %s",
protocol);
return APR_ENOTIMPL;
case OK:
case DONE:
return APR_SUCCESS;
default:
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02905)
"unexpected return code %d from protocol_switch to %s"
, rc, protocol);
return APR_EOF;
}
}
AP_IMPLEMENT_HOOK_VOID(pre_read_request, AP_IMPLEMENT_HOOK_VOID(pre_read_request,
(request_rec *r, conn_rec *c), (request_rec *r, conn_rec *c),
@@ -1959,3 +2081,14 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port,
AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure, AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure,
(request_rec *r, const char *auth_type), (request_rec *r, const char *auth_type),
(r, auth_type), DECLINED) (r, auth_type), DECLINED)
AP_IMPLEMENT_HOOK_RUN_ALL(int,protocol_propose,
(conn_rec *c, request_rec *r, server_rec *s,
const apr_array_header_t *offers,
apr_array_header_t *proposals),
(c, r, s, offers, proposals), OK, DECLINED)
AP_IMPLEMENT_HOOK_RUN_FIRST(int,protocol_switch,
(conn_rec *c, request_rec *r, server_rec *s,
const char *protocol),
(c, r, s, protocol), DECLINED)
AP_IMPLEMENT_HOOK_RUN_FIRST(const char *,protocol_get,
(const conn_rec *c), (c), NULL)