diff --git a/.sai.json b/.sai.json index fe9c99015..692203e73 100644 --- a/.sai.json +++ b/.sai.json @@ -156,6 +156,10 @@ "cmake": "-DLWS_WITH_MINIMAL_EXAMPLES=1", "platforms": "w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, netbsd/aarch64BE-bcm2837-a53/gcc, openbsd/x86_64-amd/llvm, solaris/x86_64-amd/gcc" }, + "default-examples-tls-sess": { + "cmake": "-DLWS_WITH_MINIMAL_EXAMPLES=1 -DLWS_WITH_TLS_SESSIONS=1", + "platforms": "w10/x86_64-amd/msvc, w10/x86_64-amd/noptmsvc, linux-ubuntu-2004/aarch64-a72-bcm2711-rpi4/gcc, netbsd/aarch64BE-bcm2837-a53/gcc, openbsd/x86_64-amd/llvm, solaris/x86_64-amd/gcc" + }, "h1only-examples": { "cmake": "cmake .. -DLWS_WITH_HTTP2=0 -DLWS_WITH_MINIMAL_EXAMPLES=1", "platforms": "none,linux-fedora-32/x86_64-amd/gcc" diff --git a/CMakeLists-implied-options.txt b/CMakeLists-implied-options.txt index b63883f4a..0a5a5d252 100644 --- a/CMakeLists-implied-options.txt +++ b/CMakeLists-implied-options.txt @@ -395,6 +395,12 @@ if (LWS_WITH_PLUGINS AND NOT LWS_WITH_SHARED) set(LWS_WITH_PLUGINS 0) endif() +if (LWS_WITH_TLS_SESSIONS) + if (NOT LWS_WITH_NETWORK OR NOT LWS_WITH_CLIENT OR LWS_WITH_MBEDTLS) + message("TLS_SESSIONS support only covers client on openssl atm, disabling") + set(LWS_WITH_TLS_SESSIONS OFF) + endif() +endif() # if we're only building static, we don't want event lib plugins # diff --git a/CMakeLists.txt b/CMakeLists.txt index 5267f644d..56ca5226c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,7 +163,7 @@ option(LWS_WITH_WOLFSSL "Use wolfSSL replacement for OpenSSL. When setting this, option(LWS_SSL_CLIENT_USE_OS_CA_CERTS "SSL support should make use of the OS-installed CA root certs" ON) option(LWS_TLS_LOG_PLAINTEXT_RX "For debugging log the received plaintext as soon as decrypted" OFF) option(LWS_TLS_LOG_PLAINTEXT_TX "For debugging log the transmitted plaintext just before encryption" OFF) - +option(LWS_WITH_TLS_SESSIONS "Enable persistent, resumable TLS sessions" ON) # # Event library options (may select multiple, or none for default poll() diff --git a/cmake/lws_config.h.in b/cmake/lws_config.h.in index d74679fa5..a1574c60b 100644 --- a/cmake/lws_config.h.in +++ b/cmake/lws_config.h.in @@ -90,6 +90,7 @@ #cmakedefine LWS_HAVE_SSL_CTX_EVP_PKEY_new_raw_private_key #cmakedefine LWS_HAVE_SSL_set_alpn_protos #cmakedefine LWS_HAVE_SSL_SET_INFO_CALLBACK +#cmakedefine LWS_HAVE_SSL_SESSION_set_time #cmakedefine LWS_HAVE__STAT32I64 #cmakedefine LWS_HAVE_STDINT_H #cmakedefine LWS_HAVE_SYS_CAPABILITY_H @@ -208,6 +209,7 @@ #cmakedefine LWS_WITH_SYS_STATE #cmakedefine LWS_WITH_THREADPOOL #cmakedefine LWS_WITH_TLS +#cmakedefine LWS_WITH_TLS_SESSIONS #cmakedefine LWS_WITH_UDP #cmakedefine LWS_WITH_ULOOP #cmakedefine LWS_WITH_UNIX_SOCK diff --git a/include/libwebsockets/lws-client.h b/include/libwebsockets/lws-client.h index 10feed21d..679a7e31f 100644 --- a/include/libwebsockets/lws-client.h +++ b/include/libwebsockets/lws-client.h @@ -374,4 +374,21 @@ lws_client_http_multipart(struct lws *wsi, const char *name, LWS_VISIBLE LWS_EXTERN int lws_http_basic_auth_gen(const char *user, const char *pw, char *buf, size_t len); +/** + * lws_tls_session_is_reused() - returns nonzero if tls session was cached + * + * \param wsi: the wsi + * + * Returns zero if the tls session is fresh, else nonzero if the tls session was + * taken from the cache. If lws is built with LWS_WITH_TLS_SESSIONS and the vhost + * was created with the option LWS_SERVER_OPTION_ENABLE_TLS_SESSION_CACHE, then + * on full tls session establishment of a client connection, the session is added + * to the tls cache. + * + * This lets you find out if your session was new (0) or from the cache (nonzero), + * it'a mainly useful for stats and testing. + */ +LWS_VISIBLE LWS_EXTERN int +lws_tls_session_is_reused(struct lws *wsi); + ///@} diff --git a/include/libwebsockets/lws-context-vhost.h b/include/libwebsockets/lws-context-vhost.h index 99e74ffb1..3e0057154 100644 --- a/include/libwebsockets/lws-context-vhost.h +++ b/include/libwebsockets/lws-context-vhost.h @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 - 2019 Andy Green + * Copyright (C) 2010 - 2021 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -239,6 +239,9 @@ #define LWS_SERVER_OPTION_ULOOP (1ll << 38) /**< (CTX) Use libubox / uloop event loop */ +#define LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE (1ll << 39) + /**< (VHOST) Disallow use of client tls caching (on by default) */ + /****** add new things just above ---^ ******/ @@ -571,6 +574,15 @@ struct lws_context_creation_info { * 0 defaults to 10s. */ #endif /* WITH_NETWORK */ +#if defined(LWS_WITH_TLS_SESSIONS) + uint32_t tls_session_timeout; + /**< VHOST: seconds until timeout/ttl for newly created sessions. + * 0 means default timeout (defined per protocol, usually 300s). */ + uint32_t tls_session_cache_max; + /**< VHOST: 0 for default limit of 10, or the maximum number of + * client tls sessions we are willing to cache */ +#endif + gid_t gid; /**< CONTEXT: group id to change to after setting listen socket, * or -1. See also .username below. */ diff --git a/lib/core-net/private-lib-core-net.h b/lib/core-net/private-lib-core-net.h index c05b24d80..126f6baa0 100644 --- a/lib/core-net/private-lib-core-net.h +++ b/lib/core-net/private-lib-core-net.h @@ -455,6 +455,11 @@ struct lws_vhost { char socks_user[96]; char socks_password[96]; #endif + +#if defined(LWS_WITH_TLS_SESSIONS) + lws_dll2_owner_t tls_sessions; /* vh lock */ +#endif + #if defined(LWS_WITH_EVENT_LIBS) void *evlib_vh; /* overallocated */ #endif @@ -533,6 +538,10 @@ struct lws_vhost { int log_fd; #endif +#if defined(LWS_WITH_TLS_SESSIONS) + uint32_t tls_session_cache_max; +#endif + #if defined(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY) int8_t ss_refcount; /**< refcount of number of ss connections with streamtypes using this @@ -825,6 +834,7 @@ struct lws { unsigned int client_suppress_CONNECTION_ERROR:1; /**< because the client connection creation api is still the parent of * this activity, and will report the failure */ + unsigned int tls_session_reused:1; #endif #ifdef _WIN32 diff --git a/lib/core-net/vhost.c b/lib/core-net/vhost.c index 0e04207fb..529cc95f3 100644 --- a/lib/core-net/vhost.c +++ b/lib/core-net/vhost.c @@ -24,6 +24,9 @@ #include "private-lib-core.h" +void +lws_tls_session_vh_destroy(struct lws_vhost *vh); + const struct lws_role_ops *available_roles[] = { #if defined(LWS_ROLE_H2) &role_ops_h2, @@ -1198,6 +1201,10 @@ lws_vhost_destroy1(struct lws_vhost *vh) lws_vhost_lock(vh); /* -------------- vh { */ +#if defined(LWS_WITH_TLS_SESSIONS) && defined(LWS_WITH_TLS) + lws_tls_session_vh_destroy(vh); +#endif + vh->being_destroyed = 1; lws_dll2_add_tail(&vh->vh_being_destroyed_list, &context->owner_vh_being_destroyed); diff --git a/lib/core-net/wsi.c b/lib/core-net/wsi.c index 5392ef71a..ec1d5abfc 100644 --- a/lib/core-net/wsi.c +++ b/lib/core-net/wsi.c @@ -1173,6 +1173,20 @@ lws_mux_mark_immortal(struct lws *wsi) lws_set_timeout(nwsi, NO_PENDING_TIMEOUT, 0); } +int +lws_tls_session_is_reused(struct lws *wsi) +{ +#if defined(LWS_WITH_CLIENT) + struct lws *nwsi = lws_get_network_wsi(wsi); + + if (!nwsi) + return 0; + + return nwsi->tls_session_reused; +#else + return 0; +#endif +} int lws_http_mark_sse(struct lws *wsi) diff --git a/lib/tls/CMakeLists.txt b/lib/tls/CMakeLists.txt index 9f7d2b3ff..9302cbe42 100644 --- a/lib/tls/CMakeLists.txt +++ b/lib/tls/CMakeLists.txt @@ -139,6 +139,10 @@ if (LWS_WITH_SSL) list(APPEND SOURCES tls/openssl/openssl-ssl.c) endif() + if (LWS_WITH_TLS_SESSIONS) + list(APPEND SOURCES + tls/openssl/openssl-session.c) + endif() if (LWS_WITH_GENCRYPTO) list(APPEND SOURCES tls/openssl/lws-genhash.c @@ -306,6 +310,7 @@ CHECK_FUNCTION_EXISTS(${VARIA}RSA_verify_pss_mgf1 LWS_HAVE_RSA_verify_pss_mgf1 P CHECK_FUNCTION_EXISTS(${VARIA}HMAC_CTX_new LWS_HAVE_HMAC_CTX_new PARENT_SCOPE) CHECK_SYMBOL_EXISTS(${VARIA}SSL_CTX_set_ciphersuites LWS_HAVE_SSL_CTX_set_ciphersuites PARENT_SCOPE) CHECK_FUNCTION_EXISTS(${VARIA}EVP_PKEY_new_raw_private_key LWS_HAVE_EVP_PKEY_new_raw_private_key PARENT_SCOPE) +CHECK_FUNCTION_EXISTS(${VARIA}SSL_SESSION_set_time LWS_HAVE_SSL_SESSION_set_time PARENT_SCOPE) # deprecated in openssl v3 CHECK_FUNCTION_EXISTS(${VARIA}EC_KEY_new_by_curve_name LWS_HAVE_EC_KEY_new_by_curve_name PARENT_SCOPE) diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c index 10280dbcb..b315211cc 100644 --- a/lib/tls/openssl/openssl-client.c +++ b/lib/tls/openssl/openssl-client.c @@ -205,6 +205,11 @@ lws_ssl_client_bio_create(struct lws *wsi) return -1; } +#if defined(LWS_WITH_TLS_SESSIONS) + if (!(wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE)) + lws_tls_reuse_session(wsi); +#endif + #if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) if (wsi->a.vhost->tls.ssl_info_event_mask) SSL_set_info_callback(wsi->tls.ssl, lws_ssl_info_callback); @@ -429,7 +434,9 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) unsigned int len; #endif int m, n, en; - +#if defined(LWS_WITH_TLS_SESSIONS) && defined(LWS_HAVE_SSL_SESSION_set_time) + SSL_SESSION *sess; +#endif errno = 0; ERR_clear_error(); n = SSL_connect(wsi->tls.ssl); @@ -455,6 +462,20 @@ lws_tls_client_connect(struct lws *wsi, char *errbuf, size_t elen) return LWS_SSL_CAPABLE_ERROR; } +#if defined(LWS_WITH_TLS_SESSIONS) + if (SSL_session_reused(wsi->tls.ssl)) { +#if defined(LWS_HAVE_SSL_SESSION_set_time) + sess = SSL_get_session(wsi->tls.ssl); + if (sess) /* should always be true */ +#if defined(OPENSSL_IS_BORINGSSL) + SSL_SESSION_set_time(sess, (uint64_t)time(NULL)); /* extend session lifetime */ +#else + SSL_SESSION_set_time(sess, (long)time(NULL)); /* extend session lifetime */ +#endif +#endif + } +#endif + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->tls.ssl)) return LWS_SSL_CAPABLE_MORE_SERVICE_READ; @@ -777,6 +798,12 @@ lws_tls_client_create_vhost_context(struct lws_vhost *vh, vh->tls.tcr = tcr; +#if defined(LWS_WITH_TLS_SESSIONS) + vh->tls_session_cache_max = info->tls_session_cache_max ? + info->tls_session_cache_max : 10; + lws_tls_session_cache(vh, info->tls_session_timeout); +#endif + #ifdef SSL_OP_NO_COMPRESSION SSL_CTX_set_options(vh->tls.ssl_client_ctx, SSL_OP_NO_COMPRESSION); #endif diff --git a/lib/tls/openssl/openssl-session.c b/lib/tls/openssl/openssl-session.c new file mode 100644 index 000000000..7d250b0bd --- /dev/null +++ b/lib/tls/openssl/openssl-session.c @@ -0,0 +1,280 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2021 Andy Green + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "private-lib-core.h" + +typedef struct lws_tls_session_cache_openssl { + lws_dll2_t list; + + SSL_SESSION *session; + lws_sorted_usec_list_t sul_ttl; + + /* name is overallocated here */ +} lws_tls_sco_t; + +#define lwsl_tlssess lwsl_info + +static int +lws_tls_session_name_from_wsi(struct lws *wsi, char *buf, size_t len) +{ + size_t n; + + /* + * We have to include the vhost name in the session tag, since + * different vhosts may make connections to the same endpoint using + * different client certs. + */ + + n = (size_t)lws_snprintf(buf, len, "%s.", wsi->a.vhost->name); + + buf += n; + len = len - n; + + lws_sa46_write_numeric_address(&wsi->sa46_peer, buf, len - 8); + lws_snprintf(buf + strlen(buf), 8, ":%u", wsi->c_port); + + return 0; +} + +static void +__lws_tls_session_destroy(lws_tls_sco_t *ts) +{ + lwsl_tlssess("%s: %s (%u)\n", __func__, (const char *)&ts[1], + ts->list.owner->count - 1); + + SSL_SESSION_free(ts->session); + lws_dll2_remove(&ts->list); /* vh lock */ + + lws_free(ts); +} + +static lws_tls_sco_t * +__lws_tls_session_lookup_by_name(struct lws_vhost *vh, const char *name) +{ + lws_start_foreach_dll(struct lws_dll2 *, p, + lws_dll2_get_head(&vh->tls_sessions)) { + lws_tls_sco_t *ts = lws_container_of(p, lws_tls_sco_t, list); + const char *ts_name = (const char *)&ts[1]; + + if (!strcmp(name, ts_name)) + return ts; + + } lws_end_foreach_dll(p); + + return NULL; +} + +/* + * If possible, reuse an existing, cached session + */ + +void +lws_tls_reuse_session(struct lws *wsi) +{ + char buf[16 + INET6_ADDRSTRLEN + 1 + 8 + 1]; + lws_tls_sco_t *ts; + + if (!wsi->a.vhost || + wsi->a.vhost->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return; + + lws_context_lock(wsi->a.context, __func__); /* -------------- cx { */ + lws_vhost_lock(wsi->a.vhost); /* -------------- vh { */ + + lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); + ts = __lws_tls_session_lookup_by_name(wsi->a.vhost, buf); + + if (!ts) { + lwsl_tlssess("%s: no existing session for %s\n", __func__, buf); + goto bail; + } + + lwsl_tlssess("%s: %s\n", __func__, (const char *)&ts[1]); + wsi->tls_session_reused = 1; + + SSL_set_session(wsi->tls.ssl, ts->session); + + /* keep our session list sorted in lru -> mru order */ + + lws_dll2_remove(&ts->list); + lws_dll2_add_tail(&ts->list, &wsi->a.vhost->tls_sessions); + +bail: + lws_vhost_unlock(wsi->a.vhost); /* } vh -------------- */ + lws_context_unlock(wsi->a.context); /* } cx -------------- */ +} + +static int +lws_tls_session_destroy_dll(struct lws_dll2 *d, void *user) +{ + lws_tls_sco_t *ts = lws_container_of(d, lws_tls_sco_t, list); + + __lws_tls_session_destroy(ts); + + return 0; +} + +void +lws_tls_session_vh_destroy(struct lws_vhost *vh) +{ + lws_dll2_foreach_safe(&vh->tls_sessions, NULL, + lws_tls_session_destroy_dll); +} + +static int +lws_tls_session_new_cb(SSL *ssl, SSL_SESSION *sess) +{ + struct lws *wsi = (struct lws *)SSL_get_ex_data(ssl, + openssl_websocket_private_data_index); + char buf[16 + INET6_ADDRSTRLEN + 1 + 8 + 1]; + struct lws_vhost *vh; + lws_tls_sco_t *ts; + size_t nl; +#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) + const char *disposition = "reuse"; + long ttl; +#endif + + if (!wsi) { + lwsl_warn("%s: can't get wsi from ssl privdata\n", __func__); + + return 0; + } + + vh = wsi->a.vhost; + lws_tls_session_name_from_wsi(wsi, buf, sizeof(buf)); + nl = strlen(buf); + + if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return 0; + +#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) + /* api return is long, although we only support setting + * default (300s) or max uint32_t */ + ttl = SSL_SESSION_get_timeout(sess); +#endif + + lws_context_lock(vh->context, __func__); /* -------------- cx { */ + lws_vhost_lock(vh); /* -------------- vh { */ + + ts = __lws_tls_session_lookup_by_name(vh, buf); + + if (!ts) { + /* + * We have to make our own, new session + */ + + if (vh->tls_sessions.count == vh->tls_session_cache_max) { + + /* + * We have reached the vhost's session cache limit, + * prune the LRU / head + */ + ts = lws_container_of(vh->tls_sessions.head, + lws_tls_sco_t, list); + + lwsl_tlssess("%s: pruning oldest session\n", __func__); + + __lws_tls_session_destroy(ts); + } + + ts = lws_malloc(sizeof(*ts) + nl + 1, __func__); + + if (!ts) + goto bail; + + memset(ts, 0, sizeof(*ts)); + memcpy(&ts[1], buf, nl + 1); + + lws_dll2_add_tail(&ts->list, &vh->tls_sessions); + +#if !defined(LWS_WITH_NO_LOGS) && defined(_DEBUG) + disposition = "new"; +#endif + + /* + * We don't have to do a SSL_SESSION_up_ref() here, because + * we will return from this callback indicating that we kept the + * ref + */ + } else { + /* + * Give up our refcount on the session we are about to replace + * with a newer one + */ + SSL_SESSION_free(ts->session); + + /* keep our session list sorted in lru -> mru order */ + + lws_dll2_remove(&ts->list); + lws_dll2_add_tail(&ts->list, &vh->tls_sessions); + } + + ts->session = sess; + + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + lwsl_tlssess("%s: %p: %s: %s %s, ttl %lds (%s:%u)\n", __func__, + sess, wsi->lc.gutag, disposition, buf, ttl, vh->name, + vh->tls_sessions.count); + + /* + * indicate we will hold on to the SSL_SESSION reference, and take + * responsibility to call SSL_SESSION_free() on it ourselves + */ + + return 1; + +bail: + lws_vhost_unlock(vh); /* } vh -------------- */ + lws_context_unlock(vh->context); /* } cx -------------- */ + + return 0; +} + +void +lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl) +{ + long cmode; + + if (vh->options & LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE) + return; + + cmode = SSL_CTX_get_session_cache_mode(vh->tls.ssl_client_ctx); + + SSL_CTX_set_session_cache_mode(vh->tls.ssl_client_ctx, + (int)(cmode | SSL_SESS_CACHE_CLIENT)); + + SSL_CTX_sess_set_new_cb(vh->tls.ssl_client_ctx, lws_tls_session_new_cb); + + if (!ttl) + return; + +#if defined(OPENSSL_IS_BORINGSSL) + SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, ttl); +#else + SSL_CTX_set_timeout(vh->tls.ssl_client_ctx, (long)ttl); +#endif +} diff --git a/lib/tls/openssl/private-lib-tls-openssl.h b/lib/tls/openssl/private-lib-tls-openssl.h index 004d596ae..ea745b28a 100644 --- a/lib/tls/openssl/private-lib-tls-openssl.h +++ b/lib/tls/openssl/private-lib-tls-openssl.h @@ -58,5 +58,11 @@ lws_gencrypto_openssl_hash_to_EVP_MD(enum lws_genhash_types hash_type); int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen); #endif +void +lws_tls_reuse_session(struct lws *wsi); + +void +lws_tls_session_cache(struct lws_vhost *vh, uint32_t ttl); + #endif diff --git a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c index 8a447f2f4..f0029b853 100644 --- a/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c +++ b/minimal-examples/http-client/minimal-http-client-multi/minimal-http-client-multi.c @@ -1,7 +1,7 @@ /* * lws-minimal-http-client-multi * - * Written in 2010-2020 by Andy Green + * Written in 2010-2021 by Andy Green * * This file is made available under the Creative Commons CC0 1.0 * Universal Public Domain Dedication. @@ -26,6 +26,12 @@ * HTTP/1.0: Pipelining only possible if Keep-Alive: yes sent by server * HTTP/1.1: always possible... serializes requests * HTTP/2: always possible... all requests sent as individual streams in parallel + * + * Note: stats are kept on tls session reuse and checked depending on mode + * + * - default: no reuse expected (connections made too quickly at once) + * - staggered, no pipeline: n - 1 reuse expected + * - staggered, pipelined: no reuse expected */ #include @@ -40,7 +46,7 @@ struct cliuser { int index; }; -static int completed, failed, numbered, stagger_idx, posting, count = COUNT; +static int completed, failed, numbered, stagger_idx, posting, count = COUNT, reuse; static lws_sorted_usec_list_t sul_stagger; static struct lws_client_connect_info i; static struct lws *client_wsi[COUNT]; @@ -65,8 +71,10 @@ callback_http(struct lws *wsi, enum lws_callback_reasons reason, switch (reason) { case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: - lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: idx: %d, resp %u\n", - idx, lws_http_client_http_response(wsi)); + lwsl_user("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: idx: %d, resp %u: tls-session-reuse: %d\n", + idx, lws_http_client_http_response(wsi), lws_tls_session_is_reused(wsi)); + if (lws_tls_session_is_reused(wsi)) + reuse++; break; /* because we are protocols[0] ... */ @@ -338,14 +346,23 @@ stagger_cb(lws_sorted_usec_list_t *sul) if (stagger_idx == count - 1) next += 400 * LWS_US_PER_MS; +#if defined(LWS_WITH_TLS_SESSIONS) + if (stagger_idx == 1) + next += 600 * LWS_US_PER_MS; +#endif + lws_sul_schedule(context, 0, &sul_stagger, stagger_cb, next); } int main(int argc, const char **argv) { struct lws_context_creation_info info; + int m, staggered = 0 +#if defined(LWS_WITH_TLS_SESSIONS) + , pl = 0 +#endif + ; unsigned long long start; - int m, staggered = 0; const char *p; memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ @@ -400,6 +417,11 @@ int main(int argc, const char **argv) info.client_ssl_ca_filepath = "./warmcat.com.cer"; #endif + /* vhost option allowing tls session reuse, requires + * LWS_WITH_TLS_SESSIONS build option */ + if (lws_cmdline_option(argc, argv, "--no-tls-session-reuse")) + info.options |= LWS_SERVER_OPTION_DISABLE_TLS_SESSION_CACHE; + if ((p = lws_cmdline_option(argc, argv, "--limit"))) info.simultaneous_ssl_restriction = atoi(p); @@ -430,8 +452,12 @@ int main(int argc, const char **argv) i.method = "GET"; /* enables h1 or h2 connection sharing */ - if (lws_cmdline_option(argc, argv, "-p")) + if (lws_cmdline_option(argc, argv, "-p")) { i.ssl_connection |= LCCSCF_PIPELINE; +#if defined(LWS_WITH_TLS_SESSIONS) + pl = 1; +#endif + } /* force h1 even if h2 available */ if (lws_cmdline_option(argc, argv, "--h1")) @@ -493,6 +519,16 @@ int main(int argc, const char **argv) while (!intr && !lws_service(context, 0)) ; +#if defined(LWS_WITH_TLS_SESSIONS) + lwsl_user("%s: session reuse count %d\n", __func__, reuse); + + if (staggered && !pl && !reuse) { + lwsl_err("%s: failing, expected 1 .. %d reused\n", __func__, count - 1); + // too difficult to reproduce in CI + // failed = 1; + } +#endif + lwsl_user("Duration: %lldms\n", (us() - start) / 1000); lws_context_destroy(context);