http2 alpn npn pollout
authorAndy Green <andy.green@linaro.org>
Wed, 22 Oct 2014 07:37:28 +0000 (15:37 +0800)
committerAndy Green <andy.green@linaro.org>
Wed, 22 Oct 2014 07:38:12 +0000 (15:38 +0800)
This adds npn / alpn support if your openssl can handle it.
Then, browsers that understand alpn will by default
negotiate http/1.1 and work as normal.

Clients that understand http2.0 can negotiate h2-14 and
use the basic but working http2.0 support automatically

Signed-off-by: Andy Green <andy.green@linaro.org>
CMakeLists.txt
README.build
lib/http2.c
lib/libwebsockets.c
lib/pollfd.c
lib/private-libwebsockets.h
lib/service.c
lib/ssl-http2.c
lib/ssl.c
test-server/test-server.c

index f7fc82b..875da83 100644 (file)
@@ -493,8 +493,14 @@ if (LWS_WITH_SSL)
 
                list(APPEND LIB_LIST "${LWS_CYASSL_LIB}")
        else()
+               if (OPENSSL_ROOT_DIR)
+                       set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/include")
+                       set(OPENSSL_LIBRARIES "${OPENSSL_ROOT_DIR}/lib/libssl.so" "${OPENSSL_ROOT_DIR}/lib/libcrypto.so")
+               else(OPENSSL_ROOT_DIR)
+               
                # TODO: Add support for STATIC also.
                find_package(OpenSSL REQUIRED)
+               endif(OPENSSL_ROOT_DIR)
 
                message("OpenSSL include dir: ${OPENSSL_INCLUDE_DIR}")
                message("OpenSSL libraries: ${OPENSSL_LIBRARIES}")
index dab5ac2..5521e06 100644 (file)
@@ -66,7 +66,14 @@ Building on Unix:
        access to ALPN support only in newer OpenSSL versions) the nice way to
        express that in one cmake command is eg,
        
-               -DOPENSSL_ROOT_DIR=/usr/local/ssl
+       cmake .. -DOPENSSL_ROOT_DIR=/usr/local/ssl \
+                -DCMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE=/usr/local/ssl \
+                -DLWS_WITH_HTTP2=1
+       
+       When you run the test apps using non-distro SSL, you have to force them
+       to use your libs, not the distro ones
+       
+       LD_LIBRARY_PATH=/usr/local/ssl/lib libwebsockets-test-server --ssl
 
 4. Finally you can build using the generated Makefile:
 
@@ -150,6 +157,29 @@ cmake .. -DLWS_USE_CYASSL=1 \
 
 NOTE: On windows use the .lib file extension for LWS_CYASSL_LIB instead.
 
+
+Reproducing HTTP2.0 tests
+-------------------------
+
+You must have built and be running lws against a version of openssl that has
+ALPN / NPN.  Most distros still have older versions.  You'll know it's right by
+seeing
+
+lwsts[4752]:  Compiled with OpenSSL support
+lwsts[4752]:  Using SSL mode
+lwsts[4752]:  HTTP2 / ALPN enabled
+
+at lws startup.
+
+For non-SSL HTTP2.0 upgrade
+
+nghttp -nvasu http://localhost:7681/test.htm
+
+For SSL / ALPN HTTP2.0 upgrade
+
+nghttp -nvas https://localhost:7681/test.html
+
+
 Cross compiling
 ---------------
 To enable cross compiling libwebsockets using CMake you need to create
index 15e0793..7279f6b 100644 (file)
@@ -77,8 +77,11 @@ lws_create_server_child_wsi(struct libwebsocket_context *context, struct libwebs
        
        wsi->state = WSI_STATE_HTTP2_ESTABLISHED;
        wsi->mode = parent_wsi->mode;
+       
+       wsi->protocol = &context->protocols[0];
+       libwebsocket_ensure_user_space(wsi);
 
-       lwsl_info("%s: %p new child %p, sid %d\n", __func__, parent_wsi, wsi, sid);
+       lwsl_info("%s: %p new child %p, sid %d, user_space=%p\n", __func__, parent_wsi, wsi, sid, wsi->user_space);
        
        return wsi;
 }
@@ -189,7 +192,7 @@ lws_http2_parser(struct libwebsocket_context *context,
                        return 1;
 
                if (!https_client_preface[wsi->u.http2.count]) {
-                       lwsl_err("http2: %p: established\n", wsi);
+                       lwsl_info("http2: %p: established\n", wsi);
                        wsi->state = WSI_STATE_HTTP2_ESTABLISHED_PRE_SETTINGS;
                        wsi->u.http2.count = 0;
                        
@@ -224,6 +227,16 @@ lws_http2_parser(struct libwebsocket_context *context,
                                if (lws_hpack_interpret(context, wsi->u.http2.stream_wsi, c))
                                        return 1;
                                break;
+                       case LWS_HTTP2_FRAME_TYPE_GOAWAY:
+                               if (wsi->u.http2.count >= 5 && wsi->u.http2.count <= 8) {
+                                       wsi->u.http2.hpack_e_dep <<= 8;
+                                       wsi->u.http2.hpack_e_dep |= c;
+                                       if (wsi->u.http2.count == 8) {
+                                               lwsl_info("goaway err 0x%x\n", wsi->u.http2.hpack_e_dep);
+                                       }
+                               }
+                               wsi->u.http2.GOING_AWAY = 1;
+                               break;
                        }
                        if (wsi->u.http2.count != wsi->u.http2.length)
                                break;
@@ -241,7 +254,7 @@ lws_http2_parser(struct libwebsocket_context *context,
                        switch (wsi->u.http2.type) {
                        case LWS_HTTP2_FRAME_TYPE_HEADERS:
                                /* service the http request itself */
-                               lwsl_info("servicing initial http request\n");
+                               lwsl_info("servicing initial http request, wsi=%p, stream wsi=%p\n", wsi, wsi->u.http2.stream_wsi);
                                n = lws_http_action(context, wsi->u.http2.stream_wsi);
                                lwsl_info("  action result %d\n", n);
                                break;
@@ -378,6 +391,11 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
                        
                        wsi->u.http.fd = LWS_INVALID_FILE;
                        
+                       if (lws_is_ssl(lws_http2_get_network_wsi(wsi))) {
+                               lwsl_info("skipping nonexistant ssl upgrade headers\n");
+                               break;
+                       }
+                       
                        /* 
                         * we need to treat the headers from this upgrade
                         * as the first job.  These need to get
@@ -405,4 +423,28 @@ int lws_http2_do_pps_send(struct libwebsocket_context *context, struct libwebsoc
        }
        
        return 0;
-}
\ No newline at end of file
+}
+
+struct libwebsocket * lws_http2_get_nth_child(struct libwebsocket *wsi, int n)
+{
+       do {
+               wsi = wsi->u.http2.next_child_wsi;
+               if (!wsi)
+                       return NULL;
+       } while (n--);
+
+       return wsi;
+}
+#if 0
+struct libwebsocket * lws_http2_get_next_waiting_child(struct libwebsocket *wsi)
+{
+       struct libwebsocket *wsi_child = wsi, *wsi2;
+
+       do {
+               wsi2 = lws_http2_get_nth_child(wsi_child, wsi_child->round_robin_POLLOUT);
+               if (wsi2 == NULL) {
+                       wsi_child->round_robin = 0;
+               }
+       }
+}
+#endif
\ No newline at end of file
index 2387e7c..bb1a08d 100644 (file)
@@ -702,6 +702,7 @@ libwebsocket_get_reserved_bits(struct libwebsocket *wsi)
 int
 libwebsocket_ensure_user_space(struct libwebsocket *wsi)
 {
+       lwsl_info("%s: %p protocol %p\n", __func__, wsi, wsi->protocol);
        if (!wsi->protocol)
                return 1;
 
@@ -716,7 +717,8 @@ libwebsocket_ensure_user_space(struct libwebsocket *wsi)
                }
                memset(wsi->user_space, 0,
                                         wsi->protocol->per_session_data_size);
-       }
+       } else
+               lwsl_info("%s: %p protocol pss %u, user_space=%d\n", __func__, wsi, wsi->protocol->per_session_data_size, wsi->user_space);
        return 0;
 }
 
index 3995a37..63cdfc3 100644 (file)
@@ -193,6 +193,40 @@ LWS_VISIBLE int
 libwebsocket_callback_on_writable(struct libwebsocket_context *context,
                                                      struct libwebsocket *wsi)
 {
+#ifdef LWS_USE_HTTP2
+       struct libwebsocket *network_wsi, *wsi2;
+       int already;
+
+       lwsl_info("%s: %p\n", __func__, wsi);
+       
+       if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING)
+               goto network_sock;
+       
+       if (wsi->u.http2.requested_POLLOUT) {
+               lwsl_info("already pending writable\n");
+               return 1;
+       }
+       
+       network_wsi = lws_http2_get_network_wsi(wsi);
+       already = network_wsi->u.http2.requested_POLLOUT;
+       
+       /* mark everybody above him as requesting pollout */
+       
+       wsi2 = wsi;
+       while (wsi2) {
+               wsi2->u.http2.requested_POLLOUT = 1;
+               lwsl_info("mark %p pending writable\n", wsi2);
+               wsi2 = wsi2->u.http2.parent_wsi;
+       }
+       
+       /* for network action, act only on the network wsi */
+       
+       wsi = network_wsi;
+       if (already)
+               return 1;
+network_sock:
+#endif
+
        if (lws_ext_callback_for_each_active(wsi,
                                LWS_EXT_CALLBACK_REQUEST_ON_WRITEABLE, NULL, 0))
                return 1;
index 4f171de..9ff23c1 100755 (executable)
@@ -697,9 +697,14 @@ struct _lws_http2_related {
        unsigned char frame_state;
        unsigned char padding;
        
+       unsigned short round_robin_POLLOUT;
+       unsigned short count_POLLOUT_children;
+
        unsigned int END_STREAM:1;
        unsigned int END_HEADERS:1;
        unsigned int send_END_STREAM:1;
+       unsigned int GOING_AWAY;
+       unsigned int requested_POLLOUT:1;
 
        /* hpack */
        enum http2_hpack_state hpack;
@@ -720,7 +725,7 @@ struct _lws_http2_related {
        unsigned char one_setting[LWS_HTTP2_SETTINGS_LENGTH];
 };
 
-#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->parent_wsi)
+#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.http2.parent_wsi)
 
 #endif
 
@@ -824,6 +829,7 @@ struct libwebsocket {
        BIO *client_bio;
        unsigned int use_ssl:2;
        unsigned int buffered_reads_pending:1;
+       unsigned int upgraded:1;
 #endif
 
 #ifdef _WIN32
@@ -954,6 +960,7 @@ user_callback_handle_rxflow(callback_function,
                                                          void *in, size_t len);
 #ifdef LWS_USE_HTTP2
 LWS_EXTERN struct libwebsocket *lws_http2_get_network_wsi(struct libwebsocket *wsi);
+struct libwebsocket * lws_http2_get_nth_child(struct libwebsocket *wsi, int n);
 LWS_EXTERN int
 lws_http2_interpret_settings_payload(struct http2_settings *settings, unsigned char *buf, int len);
 LWS_EXTERN void lws_http2_init(struct http2_settings *settings);
@@ -989,6 +996,10 @@ lws_add_http2_header_status(struct libwebsocket_context *context,
                            unsigned int code,
                            unsigned char **p,
                            unsigned char *end);
+LWS_EXTERN
+void lws_http2_configure_if_upgraded(struct libwebsocket *wsi);
+#else
+#define lws_http2_configure_if_upgraded(x)
 #endif
 
 LWS_EXTERN int
index f3c0138..1ac4173 100644 (file)
 
 #include "private-libwebsockets.h"
 
+static int
+lws_calllback_as_writeable(struct libwebsocket_context *context,
+                  struct libwebsocket *wsi)
+{
+       int n;
+
+       switch (wsi->mode) {
+       case LWS_CONNMODE_WS_CLIENT:
+               n = LWS_CALLBACK_CLIENT_WRITEABLE;
+               break;
+       case LWS_CONNMODE_WS_SERVING:
+               n = LWS_CALLBACK_SERVER_WRITEABLE;
+               break;
+       default:
+               n = LWS_CALLBACK_HTTP_WRITEABLE;
+               break;
+       }
+       lwsl_info("%s: %p (user=%p)\n", __func__, wsi, wsi->user_space);
+       return user_callback_handle_rxflow(wsi->protocol->callback, context,
+                       wsi, (enum libwebsocket_callback_reasons) n,
+                                                     wsi->user_space, NULL, 0);
+}
+
 int
 lws_handle_POLLOUT_event(struct libwebsocket_context *context,
                   struct libwebsocket *wsi, struct libwebsocket_pollfd *pollfd)
 {
        int n;
        struct lws_tokens eff_buf;
+#ifdef LWS_USE_HTTP2
+       struct libwebsocket *wsi2;
+#endif
        int ret;
        int m;
        int handled = 0;
@@ -186,14 +212,56 @@ user_service:
        }
 
 notify_action:
-       if (wsi->mode == LWS_CONNMODE_WS_CLIENT)
-               n = LWS_CALLBACK_CLIENT_WRITEABLE;
-       else
-               n = LWS_CALLBACK_SERVER_WRITEABLE;
+#ifdef LWS_USE_HTTP2
+       /* 
+        * we are the 'network wsi' for potentially many muxed child wsi with
+        * no network connection of their own, who have to use us for all their
+        * network actions.  So we use a round-robin scheme to share out the
+        * POLLOUT notifications to our children.
+        * 
+        * But because any child could exhaust the socket's ability to take
+        * writes, we can only let one child get notified each time.
+        * 
+        * In addition children may be closed / deleted / added between POLLOUT
+        * notifications, so we can't hold pointers
+        */
+       
+       if (wsi->mode != LWS_CONNMODE_HTTP2_SERVING) {
+               lwsl_info("%s: non http2\n", __func__);
+               goto notify;
+       }
 
-       return user_callback_handle_rxflow(wsi->protocol->callback, context,
-                       wsi, (enum libwebsocket_callback_reasons) n,
-                                                     wsi->user_space, NULL, 0);
+       wsi->u.http2.requested_POLLOUT = 0;
+       if (!wsi->u.http2.initialized) {
+               lwsl_info("pollout on uninitialized http2 conn\n");
+               return 0;
+       }
+       
+       lwsl_info("%s: doing children\n", __func__);
+
+       wsi2 = wsi;
+       do {
+               wsi2 = wsi2->u.http2.next_child_wsi;
+               lwsl_info("%s: child %p\n", __func__, wsi2);
+               if (!wsi2)
+                       continue;
+               if (!wsi2->u.http2.requested_POLLOUT)
+                       continue;
+               wsi2->u.http2.requested_POLLOUT = 0;
+               if (lws_calllback_as_writeable(context, wsi2)) {
+                       lwsl_debug("Closing POLLOUT child\n");
+                       libwebsocket_close_and_free_session(context, wsi2,
+                                               LWS_CLOSE_STATUS_NOSTATUS);
+               }
+               wsi2 = wsi;
+       } while (wsi2 != NULL && !lws_send_pipe_choked(wsi));
+       
+       lwsl_info("%s: completed\n", __func__);
+       
+       return 0;
+notify:
+#endif
+       return lws_calllback_as_writeable(context, wsi);
 }
 
 
index a0ab277..445e87d 100644 (file)
 #ifdef LWS_OPENSSL_SUPPORT
 
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
-static int alpn_select_proto_cb(SSL* ssl,
-                         const unsigned char **out,
-                         unsigned char *outlen,
-                         const unsigned char *in, unsigned int inlen,
-                         void *arg)
+
+struct alpn_ctx {
+       unsigned char *data;
+       unsigned short len;
+};
+
+static int npn_cb(SSL *s, const unsigned char **data, unsigned int *len, void *arg)
+{
+       struct alpn_ctx *alpn_ctx = arg;
+
+       lwsl_info("%s\n", __func__);
+       *data = alpn_ctx->data;
+       *len = alpn_ctx->len;
+
+       return SSL_TLSEXT_ERR_OK;
+}
+
+static int alpn_cb(SSL *s, const unsigned char **out,
+               unsigned char *outlen, const unsigned char *in,
+               unsigned int inlen, void *arg)
 {
-       lwsl_err((char *)in);
-       return SSL_TLSEXT_ERR_OK; /* SSL_TLSEXT_ERR_NOACK */
+       struct alpn_ctx *alpn_ctx = arg;
+
+       if (SSL_select_next_proto((unsigned char **)out, outlen,
+                                 alpn_ctx->data, alpn_ctx->len, in, inlen) !=
+                                                       OPENSSL_NPN_NEGOTIATED)
+               return SSL_TLSEXT_ERR_NOACK;
+
+       return SSL_TLSEXT_ERR_OK;
 }
 #endif
 
@@ -68,13 +89,68 @@ LWS_VISIBLE void
 lws_context_init_http2_ssl(struct libwebsocket_context *context)
 {
 #if OPENSSL_VERSION_NUMBER >= 0x10002000L
+       static struct alpn_ctx protos = { (unsigned char *)
+                                               "\x05h2-14"
+                                               "\x08http/1.1",
+                                               6 + 9 };
+
+       SSL_CTX_set_next_protos_advertised_cb(context->ssl_ctx, npn_cb, &protos);
+       
        // ALPN selection callback
-       SSL_CTX_set_alpn_select_cb(context->ssl_ctx, alpn_select_proto_cb, NULL);
+       SSL_CTX_set_alpn_select_cb(context->ssl_ctx, alpn_cb, &protos);
        lwsl_notice(" HTTP2 / ALPN enabled\n");
 #else
        lwsl_notice(" HTTP2 / ALPN configured but not supported by OpenSSL version 0x%x\n", OPENSSL_VERSION_NUMBER);
 #endif // OPENSSL_VERSION_NUMBER >= 0x10002000L
 }
 
+void lws_http2_configure_if_upgraded(struct libwebsocket *wsi)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+       struct allocated_headers *ah;
+       const unsigned char *name;
+       unsigned len;
+       const char *method = "alpn";
+
+       SSL_get0_alpn_selected(wsi->ssl, &name, &len);
+       
+       if (!len) {
+               SSL_get0_next_proto_negotiated(wsi->ssl, &name, &len);
+               method = "npn";
+       }
+       
+       if (len) {
+               lwsl_info("negotiated %s using %s\n", name, method);
+               wsi->use_ssl = 1;
+               if (strncmp((char *)name, "http/1.1", 8) == 0)
+                       return;
+               
+               /* http2 */
+               
+               wsi->mode = LWS_CONNMODE_HTTP2_SERVING;
+               wsi->state = WSI_STATE_HTTP2_AWAIT_CLIENT_PREFACE;
+               
+               /* adopt the header info */
+
+               ah = wsi->u.hdr.ah;
+
+               wsi->mode = LWS_CONNMODE_HTTP2_SERVING;
+
+               /* union transition */
+               memset(&wsi->u, 0, sizeof(wsi->u));
+               
+               /* http2 union member has http union struct at start */
+               wsi->u.http.ah = ah;
+               
+               lws_http2_init(&wsi->u.http2.peer_settings);
+               lws_http2_init(&wsi->u.http2.my_settings);
+               
+               /* HTTP2 union */
+               
+       } else
+               lwsl_info("no npn/alpn upgrade\n");
+#endif
+}
+
 #endif
 #endif
\ No newline at end of file
index 7bec931..b37f1bb 100644 (file)
--- a/lib/ssl.c
+++ b/lib/ssl.c
@@ -593,6 +593,8 @@ accepted:
 
                wsi->mode = LWS_CONNMODE_HTTP_SERVING;
 
+               lws_http2_configure_if_upgraded(wsi);
+
                lwsl_debug("accepted new SSL conn\n");
                break;
        }
index 04f0b34..2ab28f7 100644 (file)
@@ -345,8 +345,9 @@ static int callback_http(struct libwebsocket_context *context,
                /*
                 * we can send more of whatever it is we were sending
                 */
-
+lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n");
                do {
+                       lwsl_info("a\n");
                        n = read(pss->fd, buffer + LWS_SEND_BUFFER_PRE_PADDING,
                                 sizeof (buffer) - LWS_SEND_BUFFER_PRE_PADDING);
                        /* problem reading, close conn */
@@ -355,7 +356,7 @@ static int callback_http(struct libwebsocket_context *context,
                        /* sent it all, close conn */
                        if (n == 0)
                                goto flush_bail;
-
+                       lwsl_info("b\n");
                        /*
                         * To support HTTP2, must take care about preamble space
                         * and identify when we send the last frame
@@ -366,13 +367,14 @@ static int callback_http(struct libwebsocket_context *context,
                        if (m < 0)
                                /* write failed, close conn */
                                goto bail;
+                                               lwsl_info("c\n");
                        /*
                         * http2 won't do this
                         */
                        if (m != n)
                                /* partial write, adjust */
                                lseek(pss->fd, m - n, SEEK_CUR);
-
+                       lwsl_info("d\n");
                        if (m) /* while still active, extend timeout */
                                libwebsocket_set_timeout(wsi,
                                        PENDING_TIMEOUT_HTTP_CONTENT, 5);
@@ -382,8 +384,9 @@ static int callback_http(struct libwebsocket_context *context,
                                break;
 
                } while (!lws_send_pipe_choked(wsi));
-               
+                                       lwsl_info("e\n");
                libwebsocket_callback_on_writable(context, wsi);
+                                       lwsl_info("f\n");
                break;
 flush_bail:
                /* true if still partial pending */