From: Stefan Eissing Date: Fri, 24 Jul 2015 12:09:44 +0000 (+0000) Subject: new Protocols directive and core API changes to enable protocol switching on HTTP... X-Git-Tag: 2.5.0-alpha~2990 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b9ba839d50a8fe73143ff2d64e32f6ab990b9f83;p=apache 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 --- diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number index 60ca853552..e75f40a40b 100644 --- a/docs/log-message-tags/next-number +++ b/docs/log-message-tags/next-number @@ -1 +1 @@ -2905 +2963 diff --git a/include/http_core.h b/include/http_core.h index 87481268c1..0b19deedd4 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -707,6 +707,9 @@ typedef struct { #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 */ diff --git a/include/http_protocol.h b/include/http_protocol.h index ee61b68769..cd52fa5bb0 100644 --- a/include/http_protocol.h +++ b/include/http_protocol.h @@ -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)) + +#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; diff --git a/modules/http2/config.m4 b/modules/http2/config.m4 index b6a5c43af8..0fbe25bc8d 100644 --- a/modules/http2/config.m4 +++ b/modules/http2/config.m4 @@ -19,7 +19,6 @@ APACHE_MODPATH_INIT(http2) 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 @@ -35,12 +34,12 @@ h2_response.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 diff --git a/modules/http2/h2_alpn.c b/modules/http2/h2_alpn.c deleted file mode 100644 index bb71e71885..0000000000 --- a/modules/http2/h2_alpn.c +++ /dev/null @@ -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 - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#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; -} - diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c index 2f98090b9d..5d5e717080 100644 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@ -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"); 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; } @@ -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"); 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; } @@ -293,6 +295,7 @@ apr_status_t h2_session_process(h2_session *session) 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); @@ -353,8 +356,8 @@ conn_rec *h2_conn_create(conn_rec *master, apr_pool_t *pool) 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. @@ -440,7 +443,7 @@ apr_status_t h2_conn_init(struct h2_task_env *env, struct h2_worker *worker) 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; } diff --git a/modules/http2/h2_ctx.c b/modules/http2/h2_ctx.c index f813d533df..2894e53e64 100644 --- a/modules/http2/h2_ctx.c +++ b/modules/http2/h2_ctx.c @@ -24,7 +24,7 @@ #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); @@ -33,7 +33,7 @@ static h2_ctx *h2_ctx_create(conn_rec *c) 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) { @@ -42,7 +42,7 @@ h2_ctx *h2_ctx_create_for(conn_rec *c, h2_task_env *env) 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) { @@ -51,22 +51,18 @@ h2_ctx *h2_ctx_get(conn_rec *c) 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; @@ -84,11 +80,6 @@ int h2_ctx_pnego_is_ongoing(h2_ctx *ctx) 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; diff --git a/modules/http2/h2_ctx.h b/modules/http2/h2_ctx.h index fc10159708..c47f7ab1e1 100644 --- a/modules/http2/h2_ctx.h +++ b/modules/http2/h2_ctx.h @@ -43,18 +43,16 @@ typedef struct h2_ctx { 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. @@ -64,7 +62,7 @@ int h2_ctx_pnego_is_ongoing(h2_ctx *ctx); /** * 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); diff --git a/modules/http2/h2_from_h1.c b/modules/http2/h2_from_h1.c index 5e5a92753b..a382f3311a 100644 --- a/modules/http2/h2_from_h1.c +++ b/modules/http2/h2_from_h1.c @@ -83,6 +83,7 @@ static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r) 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; diff --git a/modules/http2/h2_h2.c b/modules/http2/h2_h2.c index 7414c53bba..6cb0f475e3 100644 --- a/modules/http2/h2_h2.c +++ b/modules/http2/h2_h2.c @@ -33,20 +33,15 @@ #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"; @@ -80,7 +75,7 @@ apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s) 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; @@ -107,7 +102,7 @@ static const char *const mod_reqtimeout[] = { "reqtimeout.c", NULL}; 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); @@ -156,7 +151,7 @@ int h2_h2_process_conn(conn_rec* c) apr_bucket_brigade* temp; if (h2_ctx_is_task(ctx)) { - /* out stream pseudo connection */ + /* our stream pseudo connection */ return DECLINED; } @@ -170,15 +165,19 @@ int h2_h2_process_conn(conn_rec* c) 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; @@ -187,7 +186,7 @@ int h2_h2_process_conn(conn_rec* c) 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, diff --git a/modules/http2/h2_h2.h b/modules/http2/h2_h2.h index 2640a8caf9..9a1184d8b6 100644 --- a/modules/http2/h2_h2.h +++ b/modules/http2/h2_h2.h @@ -17,18 +17,16 @@ #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 diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index dce33b59ea..673b46248f 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -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)); 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; @@ -497,7 +498,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) } 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); } } @@ -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); 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; } diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 41e64b4ae8..1405c5bc43 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -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 */ 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; @@ -118,6 +119,7 @@ apr_status_t h2_request_write_header(h2_request *req, 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); } diff --git a/modules/http2/h2_response.c b/modules/http2/h2_response.c index b902e5d555..01b48203a6 100644 --- a/modules/http2/h2_response.c +++ b/modules/http2/h2_response.c @@ -61,7 +61,7 @@ h2_response *h2_response_create(int stream_id, 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; @@ -80,7 +80,8 @@ h2_response *h2_response_create(int stream_id, 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; @@ -199,7 +200,7 @@ static int add_header(void *ctx, const char *key, const char *value) 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); } diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index 4a24973055..741bf0821c 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -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, + APLOGNO(02918) "h2_session: stream(%ld-%d): unable to create", session->id, 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); 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, @@ -279,6 +281,7 @@ static int on_header_cb(nghttp2_session *ngh2, const nghttp2_frame *frame, 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; @@ -320,6 +323,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, 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); @@ -342,6 +346,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, 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); @@ -394,6 +399,7 @@ static int on_frame_recv_cb(nghttp2_session *ng2s, 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, @@ -443,6 +449,7 @@ static int on_send_data_cb(nghttp2_session *ngh2, 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; @@ -477,6 +484,7 @@ static int on_send_data_cb(nghttp2_session *ngh2, } 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; @@ -494,7 +502,7 @@ static apr_status_t init_callbacks(conn_rec *c, nghttp2_session_callbacks **pcb) 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; } @@ -556,7 +564,7 @@ static h2_session *h2_session_create_int(conn_rec *c, 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; @@ -565,7 +573,8 @@ static h2_session *h2_session_create_int(conn_rec *c, 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; } @@ -584,7 +593,7 @@ static h2_session *h2_session_create_int(conn_rec *c, 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; @@ -660,7 +669,7 @@ apr_status_t h2_session_goaway(h2_session *session, apr_status_t reason) 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; @@ -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"); if (!s) { ap_log_rerror(APLOG_MARK, APLOG_ERR, APR_EINVAL, session->r, + APLOGNO(02931) "HTTP2-Settings header missing in request"); return APR_EINVAL; } @@ -751,7 +761,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv) 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; } @@ -760,7 +771,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv) 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; } @@ -768,7 +780,7 @@ apr_status_t h2_session_start(h2_session *session, int *rv) 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; } @@ -795,7 +807,8 @@ apr_status_t h2_session_start(h2_session *session, int *rv) 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; @@ -826,6 +839,7 @@ static int resume_on_data(void *ctx, h2_stream *stream) { 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)); } @@ -1002,6 +1016,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, 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; @@ -1038,7 +1053,7 @@ static ssize_t stream_data_cb(nghttp2_session *ng2s, 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; } @@ -1074,7 +1089,7 @@ static int submit_response(h2_session *session, h2_response *response) 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 { @@ -1110,7 +1125,8 @@ apr_status_t h2_session_handle_response(h2_session *session, h2_stream *stream) 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; } diff --git a/modules/http2/h2_switch.c b/modules/http2/h2_switch.c new file mode 100644 index 0000000000..bd0591a8c1 --- /dev/null +++ b/modules/http2/h2_switch.c @@ -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 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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); +} + diff --git a/modules/http2/h2_alpn.h b/modules/http2/h2_switch.h similarity index 72% rename from modules/http2/h2_alpn.h rename to modules/http2/h2_switch.h index ab6c8d418e..3d9c628c76 100644 --- a/modules/http2/h2_alpn.h +++ b/modules/http2/h2_switch.h @@ -13,17 +13,17 @@ * 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__) */ diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c index 09bc1472d1..71b212eaac 100644 --- a/modules/http2/h2_task.c +++ b/modules/http2/h2_task.c @@ -162,7 +162,7 @@ h2_task *h2_task_create(long session_id, 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; @@ -256,7 +256,8 @@ apr_status_t h2_task_do(h2_task *task, h2_worker *worker) } 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) { diff --git a/modules/http2/h2_task_input.c b/modules/http2/h2_task_input.c index 6ea1a3f847..0070c1eea1 100644 --- a/modules/http2/h2_task_input.c +++ b/modules/http2/h2_task_input.c @@ -117,7 +117,7 @@ apr_status_t h2_task_input_read(h2_task_input *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; } @@ -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 * 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; } } diff --git a/modules/http2/h2_to_h1.c b/modules/http2/h2_to_h1.c index f07c97c9ca..3ddf455c42 100644 --- a/modules/http2/h2_to_h1.c +++ b/modules/http2/h2_to_h1.c @@ -39,11 +39,13 @@ h2_to_h1 *h2_to_h1_create(int stream_id, apr_pool_t *pool, 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; } @@ -78,6 +80,7 @@ apr_status_t h2_to_h1_add_header(h2_to_h1 *to_h1, 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; } @@ -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); 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; @@ -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); 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); } @@ -243,6 +248,7 @@ apr_status_t h2_to_h1_add_data(h2_to_h1 *to_h1, 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, @@ -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); 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); } } diff --git a/modules/http2/h2_upgrade.c b/modules/http2/h2_upgrade.c deleted file mode 100644 index 432c267f37..0000000000 --- a/modules/http2/h2_upgrade.c +++ /dev/null @@ -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 - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#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; -} - diff --git a/modules/http2/h2_upgrade.h b/modules/http2/h2_upgrade.h deleted file mode 100644 index ee2dbaa9f5..0000000000 --- a/modules/http2/h2_upgrade.h +++ /dev/null @@ -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__) */ diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 65fe449659..af0da706ba 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -109,7 +109,7 @@ static const int BASE64URL_TABLE[] = { -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; @@ -126,7 +126,7 @@ apr_size_t h2_util_base64url_decode(unsigned char **decoded, const 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) + @@ -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); 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; } } diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index e4c68dca9f..9a1b5c6d35 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -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 * 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); diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 6aa968dc88..c36efa16a6 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -20,7 +20,7 @@ * @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 @@ -28,7 +28,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_H2_VERSION_NUM 0x000801 +#define MOD_H2_VERSION_NUM 0x010000 #endif /* mod_h2_h2_version_h */ diff --git a/modules/http2/h2_worker.c b/modules/http2/h2_worker.c index 1466a61907..b34f2b1f49 100644 --- a/modules/http2/h2_worker.c +++ b/modules/http2/h2_worker.c @@ -41,7 +41,8 @@ static void *execute(apr_thread_t *thread, void *wctx) 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; } diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c index f446cd237c..cf3009585b 100644 --- a/modules/http2/h2_workers.c +++ b/modules/http2/h2_workers.c @@ -343,7 +343,7 @@ void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs) { 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; } diff --git a/modules/http2/mod_h2.c b/modules/http2/mod_h2.c index 4588bde2fa..1c7cd2d892 100644 --- a/modules/http2/mod_h2.c +++ b/modules/http2/mod_h2.c @@ -31,8 +31,7 @@ #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" @@ -94,14 +93,14 @@ static int h2_post_config(apr_pool_t *p, apr_pool_t *plog, 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; @@ -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); 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) @@ -142,16 +136,11 @@ 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); } diff --git a/modules/http2/mod_h2.h b/modules/http2/mod_h2.h index d27c8b2c25..bb895dd2f1 100644 --- a/modules/http2/mod_h2.h +++ b/modules/http2/mod_h2.h @@ -16,13 +16,4 @@ #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 diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index d53b99392f..d5602ea5d1 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -283,12 +283,6 @@ static const command_rec ssl_config_cmds[] = { "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."), @@ -451,37 +445,6 @@ static int ssl_engine_disable(conn_rec *c) 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; @@ -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_engine_disable); - APR_REGISTER_OPTIONAL_FN(modssl_register_alpn); ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl", AUTHZ_PROVIDER_VERSION, diff --git a/modules/ssl/mod_ssl.h b/modules/ssl/mod_ssl.h index 6ccf9a4e81..070da79b68 100644 --- a/modules/ssl/mod_ssl.h +++ b/modules/ssl/mod_ssl.h @@ -93,46 +93,5 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (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__ */ /** @} */ diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 8e84fa4131..af79ad5179 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -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); 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, @@ -308,9 +305,6 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p, #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, @@ -1863,16 +1857,6 @@ const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, } #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, diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index 9a549eff29..65a254397a 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -1495,24 +1495,26 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, 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; diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 265a280973..bd1b7cbb00 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -1923,23 +1923,29 @@ void ssl_callback_Info(const SSL *ssl, int where, int rc) #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) @@ -1969,8 +1975,20 @@ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, modssl_ctx_t *mctx) "(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; } /* @@ -2170,41 +2188,6 @@ int ssl_callback_SessionTicket(SSL *ssl, #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 @@ -2225,14 +2208,9 @@ int ssl_callback_alpn_select(SSL *ssl, { 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. */ @@ -2261,48 +2239,15 @@ int ssl_callback_alpn_select(SSL *ssl, 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) diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index c4085f52c1..48ad0e7206 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -438,12 +438,6 @@ typedef struct { * 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; @@ -625,10 +619,6 @@ typedef struct { 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 { @@ -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); #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); diff --git a/server/core.c b/server/core.c index 3ad1931a89..bc964c40b4 100644 --- a/server/core.c +++ b/server/core.c @@ -478,6 +478,8 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s) conf->trace_enable = AP_TRACE_UNSET; + conf->protocols = apr_array_make(a, 5, sizeof(const char *)); + return (void *)conf; } @@ -551,6 +553,8 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv) ? virt->merge_trailers : base->merge_trailers; + conf->protocols = apr_array_append(p, base->protocols, virt->protocols); + return conf; } @@ -3799,12 +3803,33 @@ static const char *set_trace_enable(cmd_parms *cmd, void *dummy, 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) @@ -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"), 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 } }; @@ -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) { 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_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); @@ -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_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 diff --git a/server/protocol.c b/server/protocol.c index 1e7a6268c8..bf1dc518cf 100644 --- a/server/protocol.c +++ b/server/protocol.c @@ -67,6 +67,9 @@ APR_HOOK_STRUCT( 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; @@ -1944,6 +1947,125 @@ AP_DECLARE(void) ap_send_interim_response(request_rec *r, int send_headers) 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), @@ -1959,3 +2081,14 @@ AP_IMPLEMENT_HOOK_RUN_FIRST(unsigned short,default_port, 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)