]> granicus.if.org Git - apache/commitdiff
new Protocols directive and core API changes to enable protocol switching on HTTP...
authorStefan Eissing <icing@apache.org>
Fri, 24 Jul 2015 12:09:44 +0000 (12:09 +0000)
committerStefan Eissing <icing@apache.org>
Fri, 24 Jul 2015 12:09:44 +0000 (12:09 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1692486 13f79535-47bb-0310-9956-ffa450edef68

37 files changed:
docs/log-message-tags/next-number
include/http_core.h
include/http_protocol.h
modules/http2/config.m4
modules/http2/h2_alpn.c [deleted file]
modules/http2/h2_conn.c
modules/http2/h2_ctx.c
modules/http2/h2_ctx.h
modules/http2/h2_from_h1.c
modules/http2/h2_h2.c
modules/http2/h2_h2.h
modules/http2/h2_mplx.c
modules/http2/h2_request.c
modules/http2/h2_response.c
modules/http2/h2_session.c
modules/http2/h2_switch.c [new file with mode: 0644]
modules/http2/h2_switch.h [moved from modules/http2/h2_alpn.h with 72% similarity]
modules/http2/h2_task.c
modules/http2/h2_task_input.c
modules/http2/h2_to_h1.c
modules/http2/h2_upgrade.c [deleted file]
modules/http2/h2_upgrade.h [deleted file]
modules/http2/h2_util.c
modules/http2/h2_util.h
modules/http2/h2_version.h
modules/http2/h2_worker.c
modules/http2/h2_workers.c
modules/http2/mod_h2.c
modules/http2/mod_h2.h
modules/ssl/mod_ssl.c
modules/ssl/mod_ssl.h
modules/ssl/ssl_engine_config.c
modules/ssl/ssl_engine_io.c
modules/ssl/ssl_engine_kernel.c
modules/ssl/ssl_private.h
server/core.c
server/protocol.c

index 60ca85355257141aca86772b62f2d135c299d983..e75f40a40b51f34fd85433bbfe5759595500d6cf 100644 (file)
@@ -1 +1 @@
-2905
+2963
index 87481268c197dac99353da17defe7622a2fb720e..0b19deedd4e3b443892f28356ace66a1b9c5a3dc 100644 (file)
@@ -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 */
index ee61b6876950e3bfc3df3cc3d6eb4b6257dda201..cd52fa5bb0c06dc34c795d21bdbd526ba82f2999 100644 (file)
@@ -700,6 +700,109 @@ AP_DECLARE_HOOK(const char *,http_scheme,(const request_rec *r))
  */
 AP_DECLARE_HOOK(apr_port_t,default_port,(const request_rec *r))
 
+
+#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;
 
index b6a5c43af8c922e6112720dd93a3e0f5e743e114..0fbe25bc8de443006b68f71037be0417ebfec14e 100644 (file)
@@ -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 (file)
index bb71e71..0000000
+++ /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 <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;
-}
-
index 2f98090b9d3954161c7b56242940d230927b8a6a..5d5e717080c5fe42892db50826b376efdbedbf48 100644 (file)
@@ -144,7 +144,8 @@ apr_status_t h2_conn_rprocess(request_rec *r)
     
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "h2_conn_process start");
     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;
     }
     
index f813d533df0994221914101a620d6244cc69a4d3..2894e53e644198d17a5c35cc5d76b51769e24210 100644 (file)
@@ -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;
index fc10159708fa898665eda8e69dd9390e92679fcd..c47f7ab1e16cb1a15f21801f0849226879feeff8 100644 (file)
@@ -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);
index 5e5a92753b57fdb838a0cd831259c342aad0c764..a382f3311ad071fac452ecf9d40f11b8fd84d8b2 100644 (file)
@@ -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;
index 7414c53bbab291d22b67df37ef2749e137f649a7..6cb0f475e375decf570598dbc220202dbc3271eb 100644 (file)
 #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,
index 2640a8caf9ffa949952fad7f20f61a8d8717af0f..9a1184d8b697d37f1b24de6faedadc5216905078 100644 (file)
 #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
index dce33b59eadf23c7fb92ee8db8798845bdfeaa17..673b46248ffd07a3baf4d4b5527128f113ded629 100644 (file)
@@ -199,6 +199,7 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
             apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(10));
             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;
         }
index 41e64b4ae8d17f5fe4b149202414caeb545e5ae2..1405c5bc43e0a0405cb5a04fe4628e8c99c88cd2 100644 (file)
@@ -92,6 +92,7 @@ apr_status_t h2_request_write_header(h2_request *req,
         /* pseudo header, see ch. 8.1.2.3, always should come first */
         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);
         }
index b902e5d555e12556fbe16612927711a9bfb858cd..01b48203a619d4b9c121e01f02f745bddb86da28 100644 (file)
@@ -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);
         }
index 4a2497305536c586f4f8a15dd0ecef53e12deb29..741bf0821c48a571a9d7ab796fb1cfea2669af8e 100644 (file)
@@ -73,6 +73,7 @@ static int stream_open(h2_session *session, int stream_id)
     }
     
     ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, session->c,
+                  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 (file)
index 0000000..bd0591a
--- /dev/null
@@ -0,0 +1,201 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+
+#include <apr_strings.h>
+#include <apr_optional.h>
+#include <apr_optional_hooks.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_config.h>
+#include <http_connection.h>
+#include <http_protocol.h>
+#include <http_log.h>
+
+#include "h2_private.h"
+
+#include "h2_config.h"
+#include "h2_ctx.h"
+#include "h2_conn.h"
+#include "h2_h2.h"
+#include "h2_switch.h"
+
+/*******************************************************************************
+ * SSL var lookup
+ */
+APR_DECLARE_OPTIONAL_FN(char *, ssl_var_lookup,
+                        (apr_pool_t *, server_rec *,
+                         conn_rec *, request_rec *,
+                         char *));
+static char *(*opt_ssl_var_lookup)(apr_pool_t *, server_rec *,
+                                   conn_rec *, request_rec *,
+                                   char *);
+
+/*******************************************************************************
+ * Once per lifetime init, retrieve optional functions
+ */
+apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s)
+{
+    (void)pool;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h2_switch init");
+    opt_ssl_var_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
+
+    return APR_SUCCESS;
+}
+
+static const char *const mod_ssl[]        = { "mod_ssl.c", NULL};
+static const char *const mod_core[]       = { "core.c", NULL};
+
+static int h2_util_array_index(const apr_array_header_t *array, const char *s)
+{
+    int i;
+    for (i = 0; i < array->nelts; i++) {
+        const char *p = APR_ARRAY_IDX(array, i, const char*);
+        if (!strcmp(p, s)) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static int h2_protocol_propose(conn_rec *c, request_rec *r,
+                               server_rec *s,
+                               const apr_array_header_t *offers,
+                               apr_array_header_t *proposals)
+{
+    h2_config *cfg;
+    int proposed = 0;
+    const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
+    
+    if (strcmp(AP_PROTOCOL_HTTP1, ap_run_protocol_get(c))) {
+        /* We do not know how to switch from anything else but http/1.1.
+         */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "protocol switch: current proto != http/1.1, declined");
+        return DECLINED;
+    }
+    
+    cfg = h2_config_sget(s);
+    
+    if (!h2_config_geti(cfg, H2_CONF_ENABLED)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c,
+                      "protocol propose, h2 disabled for config %s", cfg->name);
+        return DECLINED;
+    }
+    
+    if (r) {
+        const char *p;
+        /* So far, this indicates an HTTP/1 Upgrade header initiated
+         * protocol switch. For that, the HTTP2-Settings header needs
+         * to be present and valid for the connection.
+         */
+        p = apr_table_get(r->headers_in, "HTTP2-Settings");
+        if (!p) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "upgrade without HTTP2-Settings declined");
+            return DECLINED;
+        }
+        
+        p = apr_table_get(r->headers_in, "Connection");
+        if (!ap_find_token(r->pool, p, "http2-settings")) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "upgrade without HTTP2-Settings declined");
+            return DECLINED;
+        }
+        
+        /* We also allow switching only for requests that have no body.
+         */
+        p = apr_table_get(r->headers_in, "Content-Length");
+        if (p && strcmp(p, "0")) {
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
+                          "upgrade with content-length: %s, declined", p);
+            return DECLINED;
+        }
+    }
+    
+    while (*protos) {
+        /* Add all protocols we know (tls or clear) and that
+         * were offered as options for the switch. 
+         */
+        if (h2_util_array_index(offers, *protos) >= 0) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                          "proposing protocol '%s'", *protos);
+            APR_ARRAY_PUSH(proposals, const char*) = *protos;
+            proposed = 1;
+        }
+        ++protos;
+    }
+    return proposed? DECLINED : OK;
+}
+
+static int h2_protocol_switch(conn_rec *c, request_rec *r, server_rec *s,
+                              const char *protocol)
+{
+    int found = 0;
+    const char **protos = h2_h2_is_tls(c)? h2_tls_protos : h2_clear_protos;
+    const char **p = protos;
+    
+    while (*p) {
+        if (!strcmp(*p, protocol)) {
+            found = 1;
+            break;
+        }
+        p++;
+    }
+    
+    if (found) {
+        h2_ctx *ctx = h2_ctx_get(c);
+        h2_ctx_protocol_set(ctx, protocol);
+        
+        if (r != NULL) {
+            /* Switching in the middle of a request means that
+             * we have to send out the response to this one in h2
+             * format. So we need to take over the connection
+             * right away.
+             */
+            ap_remove_input_filter_byhandle(r->input_filters, "http_in");
+            ap_remove_input_filter_byhandle(r->input_filters, "reqtimeout");
+            
+            /* Ok, start an h2_conn on this one. */
+            apr_status_t status = h2_conn_rprocess(r);
+            if (status != DONE) {
+                /* Nothing really to do about this. */
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r,
+                              "session proessed, unexpected status");
+            }
+        }
+        else {
+            
+        }
+        return DONE;
+    }
+    
+    return DECLINED;
+}
+
+static const char *h2_protocol_get(const conn_rec *c)
+{
+    return h2_ctx_protocol_get(c);
+}
+
+void h2_switch_register_hooks(void)
+{
+    ap_hook_protocol_propose(h2_protocol_propose, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_protocol_switch(h2_protocol_switch, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_protocol_get(h2_protocol_get, NULL, NULL, APR_HOOK_MIDDLE);
+}
+
similarity index 72%
rename from modules/http2/h2_alpn.h
rename to modules/http2/h2_switch.h
index ab6c8d418e795156cb4d39fef5f0e174f916fe81..3d9c628c7692476a5195a515b470e307beb9f02a 100644 (file)
  * 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__) */
index 09bc1472d1405b9f8ad696b7ea5500cbaa5d8e52..71b212eaaccc151a89d9673068631ba49e7bac76 100644 (file)
@@ -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) {
index 6ea1a3f84720a4b79e512c4714ca0fe62e54d061..0070c1eea1119f6e9d3b845a6ebfdfaf657c6ff4 100644 (file)
@@ -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;
         }
     }
index f07c97c9cac6f92d0928312e533e9af95b0c917d..3ddf455c4257d259451bcf45f60df35777c84e2f 100644 (file)
@@ -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 (file)
index 432c267..0000000
+++ /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 <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;
-}
-
diff --git a/modules/http2/h2_upgrade.h b/modules/http2/h2_upgrade.h
deleted file mode 100644 (file)
index ee2dbaa..0000000
+++ /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__) */
index 65fe4496596f6e0c6b51dcad3e2ac47272869561..af0da706ba455938ad285f91f8aa65756bf33c82 100644 (file)
@@ -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;
                         }
                     }
index e4c68dca9f77eb58c2bdfa581fa9bb33a7f963ee..9a1b5c6d35bf9ae36b271b5c3235d592f77e0837 100644 (file)
@@ -42,7 +42,7 @@ const char *h2_util_first_token_match(apr_pool_t *pool, const char *s,
  * I always wanted to write my own base64url decoder...not. See 
  * 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);
 
index 6aa968dc8841a3bb0d2bdc087fcaffe0b2594bd2..c36efa16a6a5409b8b7c3b8d6e591d86681a45c5 100644 (file)
@@ -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 */
index 1466a6190743e67216704d904588883ed81212f2..b34f2b1f49a18233870fb4c24d4f359d0420cf80 100644 (file)
@@ -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;
     }
index f446cd237cf0b80b5515d360e801cb141bf93dd6..cf3009585b7088c549aacc31ddcaeeb78818855d 100644 (file)
@@ -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;
     }
index 4588bde2fa4f694424aa6b76b945d6e055bf621c..1c7cd2d892833436e9ee9ea9a2784007f50c2fe9 100644 (file)
@@ -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);
 }
 
 
index d27c8b2c252bafac0a7ab8a6a7fcd41e032969db..bb895dd2f1972e6024bba8be72c3104d1663815e 100644 (file)
 #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
index d53b99392fa53b47ba9e9494809c64f3648bc6a3..d5602ea5d182e57dfcfca7659d8deb60e68e8c27 100644 (file)
@@ -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,
index 6ccf9a4e810ece5c16e7071f7e06ec16a9e36048..070da79b6802a88caf759f6cd2381d35c78439cd 100644 (file)
@@ -93,46 +93,5 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_proxy_enable, (conn_rec *));
 
 APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *));
 
-/** 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__ */
 /** @} */
index 8e84fa4131d4f9b3bedac0949dcaf54fd7edcaca..af79ad5179b55334a9c01618ada5348555ab2d49 100644 (file)
@@ -161,9 +161,6 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p)
     SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_CERTIFICATE);
     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,
index 9a549eff292aadf37b86327040677bd60ed004df..65a254397a7332cbea3499ee8bbab5f6d8e4e403 100644 (file)
@@ -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;
index 265a28097315e7160af5459e87b829cefefaa2ab..bd1b7cbb00fcc9d32c7e1ccafbd8f587160702ed 100644 (file)
@@ -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)
index c4085f52c17ade73cf1c1b1c58572d96b588e9c2..48ad0e72068eb27a44c8e40aa97262bd0a4a2c7a 100644 (file)
@@ -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);
index 3ad1931a891076150e46befbf4edb39afe06484a..bc964c40b471913f3d1a7f74fac0692fad41cf8f 100644 (file)
@@ -478,6 +478,8 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
 
     conf->trace_enable = AP_TRACE_UNSET;
 
+    conf->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
index 1e7a6268c8319d9dfdd5e99b93602309124c6073..bf1dc518cfa1c26ac27e88c00e1f975c008be9c4 100644 (file)
@@ -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)