http2 alpn npn pollout

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>
This commit is contained in:
Andy Green 2014-10-22 15:37:28 +08:00
parent b21122994c
commit 7df53c5550
10 changed files with 300 additions and 26 deletions

View 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}")

View 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

View 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;
}
}
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

View 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;
}

View 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;

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View 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 */