diff --git a/CMakeLists.txt b/CMakeLists.txt index f7fc82bf..875da83b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}") diff --git a/README.build b/README.build index dab5ac2e..5521e060 100644 --- a/README.build +++ b/README.build @@ -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 diff --git a/lib/http2.c b/lib/http2.c index 15e07931..7279f6b8 100644 --- a/lib/http2.c +++ b/lib/http2.c @@ -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 diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 2387e7cc..bb1a08d6 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -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; } diff --git a/lib/pollfd.c b/lib/pollfd.c index 3995a378..63cdfc36 100644 --- a/lib/pollfd.c +++ b/lib/pollfd.c @@ -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; diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 4f171de2..9ff23c1e 100755 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -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 diff --git a/lib/service.c b/lib/service.c index f3c01388..1ac4173b 100644 --- a/lib/service.c +++ b/lib/service.c @@ -21,12 +21,38 @@ #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); } diff --git a/lib/ssl-http2.c b/lib/ssl-http2.c index a0ab2771..445e87d9 100644 --- a/lib/ssl-http2.c +++ b/lib/ssl-http2.c @@ -53,14 +53,35 @@ #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) { - lwsl_err((char *)in); - return SSL_TLSEXT_ERR_OK; /* SSL_TLSEXT_ERR_NOACK */ + 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) +{ + 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 diff --git a/lib/ssl.c b/lib/ssl.c index 7bec931c..b37f1bb8 100644 --- 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; } diff --git a/test-server/test-server.c b/test-server/test-server.c index 04f0b34e..2ab28f78 100644 --- a/test-server/test-server.c +++ b/test-server/test-server.c @@ -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 */