#define AP_HTTP_EXPECT_STRICT_ENABLE 1
#define AP_HTTP_EXPECT_STRICT_DISABLE 2
int http_expect_strict;
+
+
+ apr_array_header_t *protocols;
} core_server_config;
/* for AddOutputFiltersByType in core.c */
*/
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 */
typedef struct ap_bucket_error ap_bucket_error;
dnl # list of module object files
h2_objs="dnl
mod_h2.lo dnl
-h2_alpn.lo dnl
h2_alt_svc.lo dnl
h2_config.lo dnl
h2_conn.lo dnl
h2_session.lo dnl
h2_stream.lo dnl
h2_stream_set.lo dnl
+h2_switch.lo dnl
h2_task.lo dnl
h2_task_input.lo dnl
h2_task_output.lo dnl
h2_task_queue.lo dnl
h2_to_h1.lo dnl
-h2_upgrade.lo dnl
h2_util.lo dnl
h2_worker.lo dnl
h2_workers.lo dnl
+++ /dev/null
-/* 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;
-}
-
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start");
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;
}
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, "h2_conn_main start");
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;
}
break;
default:
ap_log_cerror( APLOG_MARK, APLOG_WARNING, status, session->c,
+ APLOGNO(02950)
"h2_session(%ld): error reading, terminating",
session->id);
h2_session_abort(session, status, 0);
master->sbh,
master->bucket_alloc);
if (c == NULL) {
- ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
- "h2_task: creating conn");
+ ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, pool,
+ APLOGNO(02913) "h2_task: creating conn");
return NULL;
}
/* TODO: we simulate that we had already a request on this connection.
master->bucket_alloc);
if (c == NULL) {
ap_log_perror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, env->pool,
- "h2_task: creating conn");
+ APLOGNO(02914) "h2_task: creating conn");
return APR_ENOMEM;
}
#include "h2_ctx.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));
AP_DEBUG_ASSERT(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);
if (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);
if (ctx == NULL) {
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);
}
-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;
}
-void h2_ctx_pnego_set_started(h2_ctx *ctx)
-{
- ctx->pnego_state = H2_PNEGO_STARTED;
-}
-
-h2_ctx *h2_ctx_pnego_set_done(h2_ctx *ctx, const char *proto)
+h2_ctx *h2_ctx_protocol_set(h2_ctx *ctx, const char *proto)
{
ctx->protocol = proto;
ctx->pnego_state = H2_PNEGO_DONE;
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)
{
return ctx && ctx->is_h2;
struct h2_config *config; /* effective config in this context */
} h2_ctx;
-h2_ctx *h2_ctx_get(conn_rec *c);
-h2_ctx *h2_ctx_rget(request_rec *r);
-h2_ctx *h2_ctx_create_for(conn_rec *c, struct h2_task_env *env);
+h2_ctx *h2_ctx_get(const conn_rec *c);
+h2_ctx *h2_ctx_rget(const request_rec *r);
+h2_ctx *h2_ctx_create_for(const conn_rec *c, struct h2_task_env *env);
-void h2_ctx_pnego_set_started(h2_ctx *ctx);
-h2_ctx *h2_ctx_pnego_set_done(h2_ctx *ctx, const char *proto);
-/**
- * Returns != 0 iff protocol negitiation did happen, not matter
- * what the outcome was.
+/* Set the h2 protocol established on this connection context or
+ * NULL when other protocols are in place.
*/
-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
* done yet.
/**
* 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_active(h2_ctx *ctx);
from_h1->pool);
if (from_h1->response == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, r->connection,
+ APLOGNO(02915)
"h2_from_h1(%d): unable to create resp_head",
from_h1->stream_id);
return APR_EINVAL;
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_conn.h"
-#include "h2_alpn.h"
#include "h2_h2.h"
-const char *h2_alpn_protos[] = {
- "h2",
+const char *h2_tls_protos[] = {
+ "h2", NULL
};
-apr_size_t h2_alpn_protos_len = (sizeof(h2_alpn_protos)
- / sizeof(h2_alpn_protos[0]));
-const char *h2_upgrade_protos[] = {
- "h2c",
+const char *h2_clear_protos[] = {
+ "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";
if (!opt_ssl_is_https) {
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;
void h2_h2_register_hooks(void)
{
/* 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,
NULL, NULL, APR_HOOK_FIRST);
apr_bucket_brigade* temp;
if (h2_ctx_is_task(ctx)) {
- /* out stream pseudo connection */
+ /* our stream pseudo connection */
return DECLINED;
}
apr_brigade_destroy(temp);
}
- /* If we still do not know the protocol and H2Direct is enabled, check
- * if we receive the magic PRIamble. A client sending this on connection
- * start should know what it is doing.
+ /* If we have not already switched to a h2* protocol
+ * 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.
*/
- 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;
temp = apr_brigade_create(c->pool, c->bucket_alloc);
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) {
char *s = NULL;
apr_size_t slen;
if ((slen >= 24) && !memcmp(H2_MAGIC_TOKEN, s, 24)) {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
"h2_h2, direct mode detected");
- h2_ctx_pnego_set_done(ctx, "h2");
+ h2_ctx_protocol_set(ctx, "h2");
}
else {
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
#define __mod_h2__h2_h2__
/**
- * List of ALPN protocol identifiers that we support in ALPN/NPN
- * negotiations.
+ * List of ALPN protocol identifiers that we suport in cleartext
+ * negotiations. NULL terminated.
*/
-extern const char *h2_alpn_protos[];
-extern apr_size_t h2_alpn_protos_len;
+extern const char *h2_clear_protos[];
/**
- * List of ALPN protocol identifiers that we suport in HTTP/1 Upgrade:
- * negotiations.
+ * List of ALPN protocol identifiers that we support in TLS encrypted
+ * negotiations. NULL terminated.
*/
-extern const char *h2_upgrade_protos[];
-extern apr_size_t h2_upgrade_protos_len;
+extern const char *h2_tls_protos[];
/**
* The magic PRIamble of RFC 7540 that is always sent when starting
apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10));
if (++attempts >= 6) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
+ APLOGNO(02952)
"h2_mplx(%ld): join attempts exhausted, refs=%d",
m->id, m->refs);
break;
}
else {
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);
}
}
conn_rec *c = h2_conn_create(m->c, stream->pool);
if (c == NULL) {
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);
return APR_ENOMEM;
}
/* pseudo header, see ch. 8.1.2.3, always should come first */
if (req->to_h1) {
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, req->pool,
+ APLOGNO(02917)
"h2_request(%d): pseudo header after request start",
req->id);
return APR_EGENERAL;
memset(buffer, 0, 32);
strncpy(buffer, name, (nlen > 31)? 31 : nlen);
ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, req->pool,
+ APLOGNO(02954)
"h2_request(%d): ignoring unknown pseudo header %s",
req->id, buffer);
}
char *sep = strchr(hline, ':');
if (!sep) {
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);
/* not valid format, abort */
return NULL;
response->content_length = apr_strtoi64(sep, &end, 10);
if (sep == end) {
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",
response->stream_id, sep);
response->content_length = -1;
if (!ignore_header(key)) {
nvctx_t *nvctx = (nvctx_t*)ctx;
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->response->stream_id, key, value);
}
}
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
+ APLOGNO(02918)
"h2_session: stream(%ld-%d): unable to create",
session->id, stream_id);
return NGHTTP2_ERR_INVALID_STREAM_ID;
stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02919)
"h2_session: stream(%ld-%d): on_data_chunk for unknown stream",
session->id, (int)stream_id);
rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
frame->hd.stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02920)
"h2_session: stream(%ld-%d): on_header for unknown stream",
session->id, (int)frame->hd.stream_id);
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
frame->hd.stream_id);
if (stream == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02921)
"h2_session: stream(%ld-%d): HEADERS frame "
"for unknown stream", session->id,
(int)frame->hd.stream_id);
frame->hd.stream_id);
if (stream == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
+ APLOGNO(02922)
"h2_session: stream(%ld-%d): DATA frame "
"for unknown stream", session->id,
(int)frame->hd.stream_id);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02923)
"h2_session: stream(%ld-%d): error handling frame",
session->id, (int)frame->hd.stream_id);
rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+ APLOGNO(02924)
"h2_stream(%ld-%d): send_data",
session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
else if (status != APR_EOF) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
+ APLOGNO(02925)
"h2_stream(%ld-%d): failed send_data_cb",
session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE;
int rv = nghttp2_session_callbacks_new(pcb);
if (rv != 0) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c,
- "nghttp2_session_callbacks_new: %s",
+ APLOGNO(02926) "nghttp2_session_callbacks_new: %s",
nghttp2_strerror(rv));
return APR_EGENERAL;
}
status = init_callbacks(c, &callbacks);
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");
h2_session_destroy(session);
return NULL;
rv = nghttp2_option_new(&options);
if (rv != 0) {
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);
return NULL;
}
if (rv != 0) {
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));
h2_session_destroy(session);
return NULL;
if (rv != 0) {
status = APR_EGENERAL;
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));
}
return status;
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;
}
if (*rv != 0) {
status = APR_EINVAL;
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;
}
if (*rv != 0) {
status = APR_EGENERAL;
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;
}
if (stream == NULL) {
status = APR_EGENERAL;
ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
- "lookup of stream 1");
+ APLOGNO(02934) "lookup of stream 1");
return status;
}
if (*rv != 0) {
status = APR_EGENERAL;
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;
rv = nghttp2_session_resume_data(session->ngh2, stream->id);
ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
APLOG_ERR : APLOG_DEBUG, 0, session->c,
+ APLOGNO(02936)
"h2_stream(%ld-%d): resuming stream %s",
session->id, stream->id, nghttp2_strerror(rv));
}
stream = h2_stream_set_get(session->streams, stream_id);
if (!stream) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
+ APLOGNO(02937)
"h2_stream(%ld-%d): data requested but stream not found",
session->id, (int)stream_id);
return NGHTTP2_ERR_CALLBACK_FAILURE;
default:
nread = 0;
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);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
if (rv != 0) {
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));
}
else {
status = APR_EGENERAL;
h2_session_abort_int(session, rv);
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;
}
--- /dev/null
+/* 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);
+}
+
* limitations under the License.
*/
-#ifndef __mod_h2__h2_alpn__
-#define __mod_h2__h2_alpn__
+#ifndef __mod_h2__h2_switch__
+#define __mod_h2__h2_switch__
/*
* 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__) */
h2_task *task = apr_pcalloc(stream_pool, sizeof(h2_task));
if (task == NULL) {
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);
h2_mplx_out_close(mplx, stream_id);
return NULL;
}
else {
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) {
status = apr_brigade_length(input->bb, 1, &bblen);
if (status != APR_SUCCESS) {
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);
return status;
}
/* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
* to support it. Seems to work. */
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
- "h2_task_input, unsupported READ mode %d",
- mode);
+ APLOGNO(02942)
+ "h2_task_input, unsupported READ mode %d", mode);
return APR_ENOTIMPL;
}
}
h2_to_h1 *to_h1;
if (!method) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
+ APLOGNO(02943)
"h2_to_h1: header start but :method missing");
return NULL;
}
if (!path) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, m->c,
+ APLOGNO(02944)
"h2_to_h1: header start but :path missing");
return NULL;
}
if (!apr_strnatcasecmp("chunked", value)) {
/* This should never arrive here in a HTTP/2 request */
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_BADARG, to_h1->m->c,
+ APLOGNO(02945)
"h2_to_h1: 'transfer-encoding: chunked' received");
return APR_BADARG;
}
to_h1->content_len = apr_strtoi64(value, &end, 10);
if (value == end) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, to_h1->m->c,
+ APLOGNO(02959)
"h2_request(%d): content-length value not parsed: %s",
to_h1->stream_id, value);
return APR_EINVAL;
apr_status_t status = h2_to_h1_close(to_h1);
if (status != APR_SUCCESS) {
ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, to_h1->m->c,
+ APLOGNO(02960)
"h2_to_h1(%ld-%d): end headers, eos=%d",
to_h1->m->id, to_h1->stream_id, eos);
}
to_h1->remain_len -= len;
if (to_h1->remain_len < 0) {
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 "
"in content-length header: %ld",
to_h1->m->id, to_h1->stream_id,
status = h2_mplx_in_write(to_h1->m, to_h1->stream_id, to_h1->bb);
if (status != APR_SUCCESS) {
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);
}
}
+++ /dev/null
-/* 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;
-}
-
+++ /dev/null
-/* 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__) */
-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)
{
const unsigned char *e = (const unsigned char *)encoded;
*decoded = apr_pcalloc(pool, len+1);
i = 0;
- d = *decoded;
+ d = (unsigned char*)*decoded;
for (; i < mlen; i += 4) {
n = ((BASE64URL_TABLE[ e[i+0] ] << 18) +
(BASE64URL_TABLE[ e[i+1] ] << 12) +
status = apr_file_setaside(&fd, fd, to->p);
if (status != APR_SUCCESS) {
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;
}
}
* I always wanted to write my own base64url decoder...not. See
* 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,
apr_pool_t *pool);
* @macro
* Version number of the h2 module as c string
*/
-#define MOD_H2_VERSION "0.8.1"
+#define MOD_H2_VERSION "1.0.0"
/**
* @macro
* release. This is a 24 bit number with 8 bits for major number, 8 bits
* for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203.
*/
-#define MOD_H2_VERSION_NUM 0x000801
+#define MOD_H2_VERSION_NUM 0x010000
#endif /* mod_h2_h2_version_h */
APR_PROTO_TCP, worker->pool);
if (status != APR_SUCCESS) {
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);
return NULL;
}
{
if (idle_secs <= 0) {
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);
return;
}
#include "h2_config.h"
#include "h2_ctx.h"
#include "h2_h2.h"
-#include "h2_alpn.h"
-#include "h2_upgrade.h"
+#include "h2_switch.h"
#include "h2_version.h"
break;
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");
break;
}
status = h2_h2_init(p, s);
if (status == APR_SUCCESS) {
- status = h2_alpn_init(p, s);
+ status = h2_switch_init(p, s);
}
return status;
apr_status_t status = h2_conn_child_init(pool, s);
if (status != APR_SUCCESS) {
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.
*/
static void h2_hooks(apr_pool_t *pool)
ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
h2_h2_register_hooks();
- h2_alpn_register_hooks();
- h2_upgrade_register_hooks();
+ h2_switch_register_hooks();
h2_task_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);
}
#ifndef 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
"OpenSSL configuration command")
#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. */
AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL,
"SSLLog directive is no longer supported - use ErrorLog."),
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)
{
SSLSrvConfigRec *sc;
APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable);
APR_REGISTER_OPTIONAL_FN(ssl_engine_disable);
- APR_REGISTER_OPTIONAL_FN(modssl_register_alpn);
ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl",
AUTHZ_PROVIDER_VERSION,
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__ */
/** @} */
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));
#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,
#ifdef HAVE_SSL_CONF_CMD
cfgMergeArray(ssl_ctx_param);
#endif
-#ifdef HAVE_TLS_ALPN
- cfgMergeArray(ssl_alpn_pref);
-#endif
}
static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p,
}
#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
const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg,
SSLConnRec *sslconn = myConnConfig(f->c);
const unsigned char *next_proto = NULL;
unsigned next_proto_len = 0;
+ const char *protocol;
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,
APLOGNO(02836) "ALPN selected protocol: '%s'",
- (next_proto && next_proto_len) ?
- 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)
- break;
+ protocol);
+
+ if (strcmp(protocol, ap_run_protocol_get(f->c))) {
+ 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;
#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).
*/
-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 =
- SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
- conn_rec *c = (conn_rec *)SSL_get_app_data(ssl);
-
+ const char *servername;
+
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 (ap_vhost_iterate_given_conn(c, ssl_find_vhost,
(void *)servername)) {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02043)
"SSL virtual host for servername %s found",
servername);
- return SSL_TLSEXT_ERR_OK;
+ return APR_SUCCESS;
}
else {
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02044)
"(using default/first virtual host)");
}
}
+
+ return APR_NOTFOUND;
+}
- return SSL_TLSEXT_ERR_NOACK;
+/*
+ * 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;
}
/*
#endif /* HAVE_TLS_SESSION_TICKETS */
#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
{
conn_rec *c = (conn_rec*)SSL_get_app_data(ssl);
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 *proposed_protos;
- int i;
size_t len;
+ int i;
/* If the connection object is not available,
* then there's nothing for us to do. */
i += plen;
}
- proposed_protos = apr_array_make(c->pool, client_protos->nelts+1,
- sizeof(char *));
-
- if (sslconn->alpn_proposefns != NULL) {
- /* 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++) {
- 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;
- }
- }
-
+ /* The order the callbacks are invoked from TLS extensions is, unfortunately
+ * 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
+ * we are dealing with so we respect the correct protocols.
+ */
+ init_vhost(c, ssl);
+
+ *out = (const unsigned char *)ap_select_protocol(c, NULL, sslconn->server,
+ client_protos);
len = strlen((const char*)*out);
if (len > 255) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840)
* connection */
} 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;
} SSLConnRec;
SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */
apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */
#endif
-
-#ifdef HAVE_TLS_ALPN
- apr_array_header_t *ssl_alpn_pref; /* list of ALPN protocol IDs */
-#endif
} modssl_ctx_t;
struct SSLSrvConfigRec {
const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2);
#endif
-#ifdef HAVE_TLS_ALPN
-const char *ssl_cmd_SSLALPNPreference(cmd_parms *cmd, void *dcfg, const char *protocol);
-#endif
-
#ifdef HAVE_SRP
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);
conf->trace_enable = AP_TRACE_UNSET;
+ conf->protocols = apr_array_make(a, 5, sizeof(const char *));
+
return (void *)conf;
}
? virt->merge_trailers
: base->merge_trailers;
+ conf->protocols = apr_array_append(p, base->protocols, virt->protocols);
+
return conf;
}
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,
const char *arg)
{
core_server_config *conf =
ap_get_core_module_config(cmd->server->module_config);
+ if (!conf->protocols) {
+
+ }
if (strncmp(arg, "min=", 4) == 0) {
arg += 4;
if (strcmp(arg, "0.9") == 0)
"whether to permit Content-Length of 0 responses to HEAD requests"),
AP_INIT_FLAG("HttpExpectStrict", set_expect_strict, NULL, OR_OPTIONS,
"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 }
};
}
}
+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)
{
errorlog_hash = apr_hash_make(p);
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_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_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(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);
/* 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_open_htaccess(ap_open_htaccess, NULL, NULL, APR_HOOK_REALLY_LAST);
ap_hook_optional_fn_retrieve(core_optional_fn_retrieve, NULL, NULL,
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
* filters
APR_HOOK_LINK(http_scheme)
APR_HOOK_LINK(default_port)
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;
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,
(request_rec *r, conn_rec *c),
AP_IMPLEMENT_HOOK_RUN_FIRST(int, note_auth_failure,
(request_rec *r, const char *auth_type),
(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)