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>
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}")
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:
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
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;
}
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;
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;
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;
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
}
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
int
libwebsocket_ensure_user_space(struct libwebsocket *wsi)
{
+ lwsl_info("%s: %p protocol %p\n", __func__, wsi, wsi->protocol);
if (!wsi->protocol)
return 1;
}
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;
}
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;
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;
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
BIO *client_bio;
unsigned int use_ssl:2;
unsigned int buffered_reads_pending:1;
+ unsigned int upgraded:1;
#endif
#ifdef _WIN32
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);
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
#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;
}
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);
}
#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
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
wsi->mode = LWS_CONNMODE_HTTP_SERVING;
+ lws_http2_configure_if_upgraded(wsi);
+
lwsl_debug("accepted new SSL conn\n");
break;
}
/*
* 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 */
/* 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
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);
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 */