diff --git a/CMakeLists.txt b/CMakeLists.txt index 260979cb..3424c225 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -864,7 +864,6 @@ if (LWS_WITH_HTTP2 AND NOT LWS_WITHOUT_SERVER) list(APPEND SOURCES lib/roles/h2/http2.c lib/roles/h2/hpack.c - lib/roles/h2/ssl-http2.c lib/roles/h2/ops-h2.c) endif() # select the active platform files diff --git a/lib/context.c b/lib/context.c index ed8ff38c..8ef057da 100644 --- a/lib/context.c +++ b/lib/context.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010-2017 Andy Green + * Copyright (C) 2010-2018 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,22 @@ #define LWS_BUILD_HASH "unknown-build-hash" #endif +#if defined(LWS_WITH_TLS) +static const struct lws_role_ops * available_roles[] = { +#if defined(LWS_ROLE_H2) + &role_ops_h2, +#endif +#if defined(LWS_ROLE_H1) + &role_ops_h1, +#endif +#if defined(LWS_ROLE_WS) + &role_ops_ws, +#endif +}; + +static char alpn_discovered[32]; +#endif + static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH; /** @@ -40,6 +56,25 @@ lws_get_library_version(void) return library_version; } +int +lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn) +{ +#if defined(LWS_WITH_TLS) + int n; + + if (!alpn) + return 0; + + lwsl_info("%s: '%s'\n", __func__, alpn); + + for (n = 0; n < (int)LWS_ARRAY_SIZE(available_roles); n++) + if (!strcmp(available_roles[n]->alpn, alpn) && + available_roles[n]->alpn_negotiated) + return available_roles[n]->alpn_negotiated(wsi, alpn); +#endif + return 0; +} + static const char * const mount_protocols[] = { "http://", "https://", @@ -553,6 +588,7 @@ lws_create_vhost(struct lws_context *context, vh->pvo = info->pvo; vh->headers = info->headers; vh->user = info->user; + vh->alpn = info->alpn; #if defined(LWS_ROLE_H2) role_ops_h2.init_vhost(vh, info); @@ -1081,6 +1117,28 @@ lws_create_context(struct lws_context_creation_info *info) context->options = info->options; +#if defined(LWS_WITH_TLS) + if (info->alpn) + context->alpn_default = info->alpn; + else { + char *p = alpn_discovered, first = 1; + + for (n = 0; n < (int)LWS_ARRAY_SIZE(available_roles); n++) { + if (available_roles[n]->alpn) { + if (!first) + *p++ = ','; + p += lws_snprintf(p, alpn_discovered + + sizeof(alpn_discovered) - 2 - p, + "%s", available_roles[n]->alpn); + first = 0; + } + } + context->alpn_default = alpn_discovered; + } + + lwsl_info("Default ALPN advertisment: %s\n", context->alpn_default); +#endif + if (info->timeout_secs) context->timeout_secs = info->timeout_secs; else diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6c430b4d..af8ad172 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -2294,7 +2294,10 @@ lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, #ifndef LWS_PLAT_OPTEE socklen_t len = sizeof(struct sockaddr_storage); #endif - int n, m; + int n; +#if !defined(LWS_WITH_ESP32) + int m; +#endif struct sockaddr_storage sin; struct sockaddr *v; @@ -2822,6 +2825,22 @@ lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) } } +const char * +lws_cmdline_option(int argc, const char **argv, const char *val) +{ + int n = strlen(val), c = argc; + + while (--c > 0) + if (!strncmp(argv[c], val, n) && !*(argv[c] + n)) { + if (c != argc - 1) + return argv[c + 1]; + + return argv[c] + n; + } + + return NULL; +} + #ifdef LWS_WITH_SERVER_STATUS LWS_EXTERN int diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index d2c254a7..13b0984f 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -2998,6 +2998,13 @@ struct lws_context_creation_info { /**< VHOST: If non-NULL, when asked to serve a non-existent file, * lws attempts to server this url path instead. Eg, * "/404.html" */ + const char *alpn; + /**< CONTEXT: If non-NULL, default list of advertised alpn, comma- + * separated + * + * VHOST: If non-NULL, per-vhost list of advertised alpn, comma- + * separated + */ unsigned int h2_rx_scratch_size; /**< VHOST: size of the rx scratch buffer for each stream. 0 = * default (512 bytes). This affects the RX chunk size @@ -3404,7 +3411,6 @@ enum lws_client_connect_ssl_connection_flags { LCCSCF_ALLOW_SELFSIGNED = (1 << 1), LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK = (1 << 2), LCCSCF_ALLOW_EXPIRED = (1 << 3), - LCCSCF_NOT_H2 = (1 << 4), LCCSCF_PIPELINE = (1 << 16), /**< Serialize / pipeline multiple client connections @@ -3487,6 +3493,12 @@ struct lws_client_connect_info { * members added above will see 0 (default) even if the app * was not built against the newer headers. */ + const char *alpn; + /* NULL: allow lws default ALPN list, from vhost if present or from + * list of roles built into lws + * non-NULL: require one from provided comma-separated list of alpn + * tokens + */ void *_unused[4]; /**< dummy */ }; @@ -4088,6 +4100,7 @@ enum lws_token_indexes { _WSI_TOKEN_CLIENT_ORIGIN, _WSI_TOKEN_CLIENT_METHOD, _WSI_TOKEN_CLIENT_IFACE, + _WSI_TOKEN_CLIENT_ALPN, /* always last real token index*/ WSI_TOKEN_COUNT, @@ -4670,6 +4683,11 @@ LWS_VISIBLE LWS_EXTERN void lws_libuv_static_refcount_del(uv_handle_t *); #endif /* LWS_WITH_LIBUV */ + +#if defined(LWS_WITH_ESP32) +#define lws_libuv_static_refcount_add(_a, _b) +#define lws_libuv_static_refcount_del NULL +#endif ///@} /*! \defgroup event libevent helpers @@ -5365,6 +5383,7 @@ typedef union { lws_filefd_type filefd; } lws_sock_file_fd_type; +#if !defined(LWS_WITH_ESP32) struct lws_udp { struct sockaddr sa; socklen_t salen; @@ -5372,6 +5391,7 @@ struct lws_udp { struct sockaddr sa_pending; socklen_t salen_pending; }; +#endif /* * lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor @@ -5518,12 +5538,12 @@ lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, */ LWS_VISIBLE LWS_EXTERN const char * lws_get_peer_simple(struct lws *wsi, char *name, int namelen); -#if !defined(LWS_WITH_ESP32) + #define LWS_ITOSA_NOT_EXIST -1 #define LWS_ITOSA_NOT_USABLE -2 #define LWS_ITOSA_USABLE 0 - +#if !defined(LWS_WITH_ESP32) /** * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct * @@ -5809,6 +5829,25 @@ lws_set_wsi_user(struct lws *wsi, void *user); LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT lws_parse_uri(char *p, const char **prot, const char **ads, int *port, const char **path); +/** + * lws_cmdline_option(): simple commandline parser + * + * \param argc: count of argument strings + * \param argv: argument strings + * \param val: string to find + * + * Returns NULL if the string \p val is not found in the arguments. + * + * If it is found, then it returns a pointer to the next character after \p val. + * So if \p val is "-d", then for the commandlines "myapp -d15" and + * "myapp -d 15", in both cases the return will point to the "15". + * + * In the case there is no argument, like "myapp -d", the return will + * either point to the '\\0' at the end of -d, or to the start of the + * next argument, ie, will be non-NULL. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_cmdline_option(int argc, const char **argv, const char *val); /** * lws_now_secs(): return seconds since 1970-1-1 diff --git a/lib/output.c b/lib/output.c index c51e7c5a..96c1ae4f 100644 --- a/lib/output.c +++ b/lib/output.c @@ -181,11 +181,13 @@ handle_truncated_send: wsi->trunc_len = (unsigned int)(real_len - n); memcpy(wsi->trunc_alloc, buf + n, real_len - n); +#if !defined(LWS_WITH_ESP32) if (lws_wsi_is_udp(wsi)) { /* stash original destination for fulfilling UDP partials */ wsi->udp->sa_pending = wsi->udp->sa; wsi->udp->salen_pending = wsi->udp->salen; } +#endif /* since something buffered, force it to get another chance to send */ lws_callback_on_writable(wsi); @@ -488,14 +490,16 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; - int n; + int n = 0; lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); if (lws_wsi_is_udp(wsi)) { +#if !defined(LWS_WITH_ESP32) wsi->udp->salen = sizeof(wsi->udp->sa); n = recvfrom(wsi->desc.sockfd, (char *)buf, len, 0, &wsi->udp->sa, &wsi->udp->salen); +#endif } else n = recv(wsi->desc.sockfd, (char *)buf, len, 0); @@ -523,10 +527,12 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) int n = 0; if (lws_wsi_is_udp(wsi)) { +#if !defined(LWS_WITH_ESP32) if (wsi->trunc_len) n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa_pending, wsi->udp->salen_pending); else n = sendto(wsi->desc.sockfd, buf, len, 0, &wsi->udp->sa, wsi->udp->salen); +#endif } else n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); // lwsl_info("%s: sent len %d result %d", __func__, len, n); diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 2edcf9d6..c1a2c302 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -614,6 +614,7 @@ void lwsi_set_state(struct lws *wsi, lws_wsi_state_t lrs); struct lws_context_per_thread; struct lws_role_ops { const char *name; + const char *alpn; /* * After http headers have parsed, this is the last chance for a role * to upgrade the connection to something else using the headers. @@ -652,6 +653,9 @@ struct lws_role_ops { /* get encapsulation parent */ struct lws * (*encapsulation_parent)(struct lws *wsi); + /* role-specific destructor */ + int (*alpn_negotiated)(struct lws *wsi, const char *alpn); + /* chance for the role to handle close in the protocol */ int (*close_via_role_protocol)(struct lws *wsi, enum lws_close_status reason); @@ -693,9 +697,9 @@ enum { LWS_HP_RET_BAIL_DIE, LWS_HP_RET_USER_SERVICE, - LWS_HPI_RET_DIE, - LWS_HPI_RET_HANDLED, - LWS_HPI_RET_CLOSE_HANDLED, + LWS_HPI_RET_DIE, /* we closed it */ + LWS_HPI_RET_HANDLED, /* no probs */ + LWS_HPI_RET_CLOSE_HANDLED, /* close it for us */ LWS_UPG_RET_DONE, LWS_UPG_RET_CONTINUE, @@ -1071,6 +1075,11 @@ struct lws_timed_vh_protocol { struct lws_tls_ss_pieces; +struct alpn_ctx { + uint8_t data[23]; + uint8_t len; +}; + struct lws_vhost { char http_proxy_address[128]; char proxy_basic_auth_token[128]; @@ -1110,11 +1119,13 @@ struct lws_vhost { struct lws_dll_lws dll_active_client_conns; #endif const char *error_document_404; + const char *alpn; #if defined(LWS_WITH_TLS) lws_tls_ctx *ssl_ctx; lws_tls_ctx *ssl_client_ctx; struct lws_tls_ss_pieces *ss; /* for acme tls certs */ char ecdh_curve[16]; + struct alpn_ctx alpn_ctx; #endif #if defined(LWS_WITH_MBEDTLS) lws_tls_x509 *x509_client_CA; @@ -1262,6 +1273,7 @@ struct lws_context { void *user_space; const char *server_string; const struct lws_protocol_vhost_options *reject_service_keywords; + const char *alpn_default; lws_reload_func deprecation_cb; #if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) @@ -1527,6 +1539,7 @@ struct client_info_stash { char *protocol; char *method; char *iface; + char *alpn; }; #endif @@ -2406,8 +2419,6 @@ LWS_EXTERN int lws_add_http2_header_status(struct lws *wsi, unsigned int code, unsigned char **p, unsigned char *end); -LWS_EXTERN int -lws_h2_configure_if_upgraded(struct lws *wsi); LWS_EXTERN void lws_hpack_destroy_dynamic_header(struct lws *wsi); LWS_EXTERN int @@ -2435,8 +2446,6 @@ int lws_handle_POLLOUT_event_h2(struct lws *wsi); int lws_read_h2(struct lws *wsi, unsigned char *buf, lws_filepos_t len); -#else -#define lws_h2_configure_if_upgraded(x) #endif LWS_EXTERN int @@ -2511,7 +2520,7 @@ LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); #define LWS_SSL_ENABLED(context) (0) #define lws_context_init_server_ssl(_a, _b) (0) #define lws_ssl_destroy(_a) -#define lws_context_init_http2_ssl(_a) +#define lws_context_init_alpn(_a) #define lws_ssl_capable_read lws_ssl_capable_read_no_ssl #define lws_ssl_capable_write lws_ssl_capable_write_no_ssl #define lws_ssl_pending lws_ssl_pending_no_ssl @@ -2533,6 +2542,8 @@ enum lws_tls_extant { LWS_TLS_EXTANT_YES, LWS_TLS_EXTANT_ALTERNATIVE }; +LWS_EXTERN void +lws_context_init_alpn(struct lws_vhost *vhost); LWS_EXTERN enum lws_tls_extant lws_tls_use_any_upgrade_check_extant(const char *name); LWS_EXTERN int openssl_websocket_private_data_index; @@ -2641,17 +2652,9 @@ LWS_EXTERN lws_tls_ctx * lws_tls_ctx_from_wsi(struct lws *wsi); LWS_EXTERN int lws_ssl_get_error(struct lws *wsi, int n); - +#endif /* HTTP2-related */ -#ifdef LWS_WITH_HTTP2 -LWS_EXTERN void -lws_context_init_http2_ssl(struct lws_vhost *vhost); -#else -#define lws_context_init_http2_ssl(_a) -#endif -#endif - #if LWS_MAX_SMP > 1 static LWS_INLINE void @@ -3002,6 +3005,13 @@ int lws_client_ws_upgrade(struct lws *wsi, const char **cce); int lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi); +int +lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len); +int +lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn); +int +lws_tls_server_conn_alpn(struct lws *wsi); + #ifdef __cplusplus }; #endif diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c index 88210160..5e883aec 100644 --- a/lib/roles/cgi/ops-cgi.c +++ b/lib/roles/cgi/ops-cgi.c @@ -76,7 +76,8 @@ rops_periodic_checks_cgi(struct lws_context *context, int tsi, time_t now) } struct lws_role_ops role_ops_cgi = { - "cgi", + /* role name */ "cgi", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -90,6 +91,7 @@ struct lws_role_ops role_ops_cgi = { /* write_role_protocol */ NULL, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c index 6e6d6142..fd9c0382 100644 --- a/lib/roles/h1/ops-h1.c +++ b/lib/roles/h1/ops-h1.c @@ -572,7 +572,7 @@ rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi, return n; if (lwsi_state(wsi) != LRS_SSL_INIT) if (lws_server_socket_service_ssl(wsi, LWS_SOCK_INVALID)) - return LWS_HPI_RET_DIE; + return LWS_HPI_RET_CLOSE_HANDLED; return LWS_HPI_RET_HANDLED; } @@ -694,8 +694,29 @@ rops_write_role_protocol_h1(struct lws *wsi, unsigned char *buf, size_t len, return lws_issue_raw(wsi, (unsigned char *)buf, len); } +static int +rops_alpn_negotiated_h1(struct lws *wsi, const char *alpn) +{ + lwsl_debug("%s: client %d\n", __func__, lwsi_role_client(wsi)); +#if !defined(LWS_NO_CLIENT) + if (lwsi_role_client(wsi)) { + /* + * If alpn asserts it is http/1.1, server support for KA is + * mandatory. + * + * Knowing this lets us proceed with sending pipelined headers + * before we received the first response headers. + */ + wsi->keepalive_active = 1; + } +#endif + + return 0; +} + struct lws_role_ops role_ops_h1 = { - "h1", + /* role name */ "h1", + /* alpn id */ "http/1.1", /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -709,6 +730,7 @@ struct lws_role_ops role_ops_h1 = { /* write_role_protocol */ rops_write_role_protocol_h1, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ rops_alpn_negotiated_h1, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c index ded3ba24..148c6cb3 100644 --- a/lib/roles/h2/ops-h2.c +++ b/lib/roles/h2/ops-h2.c @@ -1056,8 +1056,54 @@ rops_encapsulation_parent_h2(struct lws *wsi) return NULL; } +static int +rops_alpn_negotiated_h2(struct lws *wsi, const char *alpn) +{ + struct allocated_headers *ah; + + lwsl_debug("%s: client %d\n", __func__, lwsi_role_client(wsi)); +#if !defined(LWS_NO_CLIENT) + if (lwsi_role_client(wsi)) { + lwsl_info("%s: upgraded to H2\n", __func__); + wsi->client_h2_alpn = 1; + } +#endif + + wsi->upgraded_to_http2 = 1; + wsi->vhost->conn_stats.h2_alpn++; + + /* adopt the header info */ + + ah = wsi->ah; + + lws_role_transition(wsi, LWSIFR_SERVER, LRS_H2_AWAIT_PREFACE, + &role_ops_h2); + + /* http2 union member has http union struct at start */ + wsi->ah = ah; + + if (!wsi->h2.h2n) + wsi->h2.h2n = lws_zalloc(sizeof(*wsi->h2.h2n), "h2n"); + if (!wsi->h2.h2n) + return 1; + + lws_h2_init(wsi); + + /* HTTP2 union */ + + lws_hpack_dynamic_size(wsi, + wsi->h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); + wsi->h2.tx_cr = 65535; + + lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi); + + + return 0; +} + struct lws_role_ops role_ops_h2 = { - "h2", + /* role name */ "h2", + /* alpn id */ "h2", /* check_upgrades */ rops_check_upgrades_h2, /* init_context */ rops_init_context_h2, /* init_vhost */ rops_init_vhost_h2, @@ -1071,6 +1117,7 @@ struct lws_role_ops role_ops_h2 = { /* write_role_protocol */ rops_write_role_protocol_h2, /* rxflow_cache */ rops_rxflow_cache_h2, /* encapsulation_parent */ rops_encapsulation_parent_h2, + /* alpn_negotiated */ rops_alpn_negotiated_h2, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ rops_close_kill_connection_h2, diff --git a/lib/roles/h2/ssl-http2.c b/lib/roles/h2/ssl-http2.c deleted file mode 100644 index 5174b6f7..00000000 --- a/lib/roles/h2/ssl-http2.c +++ /dev/null @@ -1,158 +0,0 @@ -/* - * libwebsockets - small server side websockets and web server implementation - * - * Copyright (C) 2010-2017 Andy Green - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation: - * version 2.1 of the License. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301 USA - * - * Some or all of this file is based on code from nghttp2, which has the - * following license. Since it's more liberal than lws license, you're also - * at liberty to get the original code from - * https://github.com/tatsuhiro-t/nghttp2 under his liberal terms alone. - * - * nghttp2 - HTTP/2.0 C Library - * - * Copyright (c) 2012 Tatsuhiro Tsujikawa - * - * 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-libwebsockets.h" - -#if !defined(LWS_NO_SERVER) -#if defined(LWS_WITH_TLS) - -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ - OPENSSL_VERSION_NUMBER >= 0x10002000L) - -struct alpn_ctx { - unsigned char *data; - unsigned short len; -}; - - -static int -alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, - const unsigned char *in, unsigned int inlen, void *arg) -{ -#if !defined(LWS_WITH_MBEDTLS) - 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; -#endif - return SSL_TLSEXT_ERR_OK; -} -#endif - -LWS_VISIBLE void -lws_context_init_http2_ssl(struct lws_vhost *vhost) -{ -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ - OPENSSL_VERSION_NUMBER >= 0x10002000L) - static struct alpn_ctx protos = { (unsigned char *)"\x02h2" - "\x08http/1.1", 6 + 9 }; - - SSL_CTX_set_alpn_select_cb(vhost->ssl_ctx, alpn_cb, &protos); - lwsl_notice(" HTTP2 / ALPN enabled\n"); -#else - lwsl_notice( - " HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n", - OPENSSL_VERSION_NUMBER); -#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L -} - -int lws_h2_configure_if_upgraded(struct lws *wsi) -{ -#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ - OPENSSL_VERSION_NUMBER >= 0x10002000L) - struct allocated_headers *ah; - const unsigned char *name = NULL; - char cstr[10]; - unsigned len; - - if (!wsi->ssl) - return 0; - - SSL_get0_alpn_selected(wsi->ssl, &name, &len); - if (!len) { - lwsl_info("no ALPN upgrade\n"); - return 0; - } - - if (len > sizeof(cstr) - 1) - len = sizeof(cstr) - 1; - - memcpy(cstr, name, len); - cstr[len] = '\0'; - - lwsl_info("negotiated '%s' using ALPN\n", cstr); - wsi->use_ssl |= LCCSCF_USE_SSL; - if (strncmp((char *)name, "http/1.1", 8) == 0) - return 0; - - /* http2 */ - - wsi->upgraded_to_http2 = 1; - wsi->vhost->conn_stats.h2_alpn++; - - /* adopt the header info */ - - ah = wsi->ah; - - lws_role_transition(wsi, LWSIFR_SERVER, LRS_H2_AWAIT_PREFACE, - &role_ops_h2); - - /* http2 union member has http union struct at start */ - wsi->ah = ah; - - wsi->h2.h2n = lws_zalloc(sizeof(*wsi->h2.h2n), "h2n"); - if (!wsi->h2.h2n) - return 1; - - lws_h2_init(wsi); - - /* HTTP2 union */ - - lws_hpack_dynamic_size(wsi, - wsi->h2.h2n->set.s[H2SET_HEADER_TABLE_SIZE]); - wsi->h2.tx_cr = 65535; - - lwsl_info("%s: wsi %p: configured for h2\n", __func__, wsi); -#endif - return 0; -} -#endif -#endif diff --git a/lib/roles/http/client/client-handshake.c b/lib/roles/http/client/client-handshake.c index cbd8e740..bf24bc26 100644 --- a/lib/roles/http/client/client-handshake.c +++ b/lib/roles/http/client/client-handshake.c @@ -84,8 +84,8 @@ lws_client_connect_2(struct lws *wsi) if (w != wsi && w->client_hostname_copy && !strcmp(adsin, w->client_hostname_copy) && #if defined(LWS_WITH_TLS) - (wsi->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) == - (w->use_ssl & (LCCSCF_NOT_H2 | LCCSCF_USE_SSL)) && + (wsi->use_ssl & LCCSCF_USE_SSL) == + (w->use_ssl & LCCSCF_USE_SSL) && #endif wsi->c_port == w->c_port) { @@ -403,7 +403,7 @@ create_new_conn: sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); } -lwsl_notice("%s: CONNECT\n", __func__); + if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || @@ -595,7 +595,7 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, const char *path, const char *host) { char origin[300] = "", protocol[300] = "", method[32] = "", - iface[16] = "", *p; + iface[16] = "", alpn[32], *p; struct lws *wsi = *pwsi; if (wsi->redirects == 3) { @@ -618,7 +618,11 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (p) - lws_strncpy(method, p, sizeof(iface)); + lws_strncpy(iface, p, sizeof(iface)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ALPN); + if (p) + lws_strncpy(alpn, p, sizeof(alpn)); lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n", address, port, path, ssl); @@ -687,6 +691,10 @@ lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, iface)) return NULL; + if (alpn[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + alpn)) + return NULL; origin[0] = '/'; strncpy(&origin[1], path, sizeof(origin) - 2); @@ -850,6 +858,7 @@ lws_client_stash_destroy(struct lws *wsi) lws_free_set_NULL(wsi->stash->protocol); lws_free_set_NULL(wsi->stash->method); lws_free_set_NULL(wsi->stash->iface); + lws_free_set_NULL(wsi->stash->alpn); lws_free_set_NULL(wsi->stash); } @@ -946,9 +955,6 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) #if defined(LWS_WITH_TLS) wsi->use_ssl = i->ssl_connection; - - if (!i->method) /* !!! disallow ws for h2 right now */ - wsi->use_ssl |= LCCSCF_NOT_H2; #else if (i->ssl_connection & LCCSCF_USE_SSL) { lwsl_err("libwebsockets not configured for ssl\n"); @@ -995,6 +1001,26 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) if (!wsi->stash->iface) goto bail1; } + /* + * For ws, default to http/1.1 only. If i->alpn is set, defer to + * whatever he has set in there (eg, "h2"). + * + * The problem is he has to commit to h2 before he can find out if the + * server has the SETTINGS for ws-over-h2 enabled; if not then ws is + * not possible on that connection. So we only try it if he + * assertively said to use h2 alpn. + */ + if (!i->method && !i->alpn) { + wsi->stash->alpn = lws_strdup("http/1.1"); + if (!wsi->stash->alpn) + goto bail1; + } else + if (i->alpn) { + wsi->stash->alpn = lws_strdup(i->alpn); + if (!wsi->stash->alpn) + goto bail1; + } + if (i->pwsi) *i->pwsi = wsi; @@ -1049,14 +1075,13 @@ lws_client_connect_via_info2(struct lws *wsi) /* * we're not necessarily in a position to action these right away, - * stash them... we only need during connect phase so u.hdr is fine + * stash them... we only need during connect phase so into a temp + * allocated stash */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, stash->address)) goto bail1; - /* these only need u.hdr lifetime as well */ - if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path)) goto bail1; @@ -1083,6 +1108,10 @@ lws_client_connect_via_info2(struct lws *wsi) if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, stash->iface)) goto bail1; + if (stash->alpn) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + stash->alpn)) + goto bail1; #if defined(LWS_WITH_SOCKS5) if (!wsi->vhost->socks_proxy_port) diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c index 5ffaf894..4b60bf8a 100644 --- a/lib/roles/http/client/client.c +++ b/lib/roles/http/client/client.c @@ -342,12 +342,7 @@ start_ws_handshake: * So this is it, we are an h2 master client connection * now, not an h1 client connection. */ - lwsl_info("client connection upgraded to h2\n"); - lws_h2_configure_if_upgraded(wsi); - - lws_role_transition(wsi, LWSIFR_CLIENT, - LRS_H2_CLIENT_SEND_SETTINGS, - &role_ops_h2); + lws_tls_server_conn_alpn(wsi); /* send the H2 preface to legitimize the connection */ if (lws_h2_issue_preface(wsi)) { diff --git a/lib/roles/http/server/lejp-conf.c b/lib/roles/http/server/lejp-conf.c index 7a5799d2..b41779d2 100644 --- a/lib/roles/http/server/lejp-conf.c +++ b/lib/roles/http/server/lejp-conf.c @@ -39,6 +39,7 @@ static const char * const paths_global[] = { "global.timeout-secs", "global.reject-service-keywords[].*", "global.reject-service-keywords[]", + "global.default-alpn", }; enum lejp_global_paths { @@ -51,7 +52,8 @@ enum lejp_global_paths { LWJPGP_PINGPONG_SECS, LWJPGP_TIMEOUT_SECS, LWJPGP_REJECT_SERVICE_KEYWORDS_NAME, - LWJPGP_REJECT_SERVICE_KEYWORDS + LWJPGP_REJECT_SERVICE_KEYWORDS, + LWJPGP_DEFAULT_ALPN, }; static const char * const paths_vhosts[] = { @@ -102,6 +104,7 @@ static const char * const paths_vhosts[] = { "vhosts[].client-cert-required", "vhosts[].ignore-missing-cert", "vhosts[].error-document-404", + "vhosts[].alpn", }; enum lejp_vhost_paths { @@ -152,6 +155,7 @@ enum lejp_vhost_paths { LEJPVP_FLAG_CLIENT_CERT_REQUIRED, LEJPVP_IGNORE_MISSING_CERT, LEJPVP_ERROR_DOCUMENT_404, + LEJPVP_ALPN, }; static const char * const parser_errs[] = { @@ -290,6 +294,10 @@ lejp_globals_cb(struct lejp_ctx *ctx, char reason) a->info->timeout_secs = atoi(ctx->buf); return 0; + case LWJPGP_DEFAULT_ALPN: + a->info->alpn = a->p; + break; + default: return 0; } @@ -732,6 +740,10 @@ lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) a->info->ssl_options_clear |= atol(ctx->buf); return 0; + case LEJPVP_ALPN: + a->info->alpn = a->p; + break; + default: return 0; } diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c index 42398222..ef0af5d7 100644 --- a/lib/roles/http/server/server.c +++ b/lib/roles/http/server/server.c @@ -729,6 +729,7 @@ lws_find_string_in_file(const char *filename, const char *string, int stringlen) return hit; } +#endif static int lws_unauthorised_basic_auth(struct lws *wsi) @@ -762,8 +763,6 @@ lws_unauthorised_basic_auth(struct lws *wsi) } -#endif - int lws_clean_url(char *p) { if (p[0] == 'h' && p[1] == 't' && p[2] == 't' && p[3] == 'p') { @@ -1887,13 +1886,14 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, if (type & LWS_ADOPT_SOCKET) { /* socket desc */ lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, (int)(lws_intptr_t)fd.sockfd); - +#if !defined(LWS_WITH_ESP32) if (type & LWS_ADOPT_FLAG_UDP) /* * these can be >128 bytes, so just alloc for UDP */ new_wsi->udp = lws_malloc(sizeof(*new_wsi->udp), "udp struct"); +#endif if (type & LWS_ADOPT_HTTP) /* the transport is accepted... diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c index e9d4d147..b61c615a 100644 --- a/lib/roles/listen/ops-listen.c +++ b/lib/roles/listen/ops-listen.c @@ -129,9 +129,11 @@ rops_handle_POLLIN_listen(struct lws_context_per_thread *pt, struct lws *wsi, /* already closed cleanly as necessary */ return LWS_HPI_RET_DIE; - if (lws_server_socket_service_ssl(cwsi, accept_fd)) + if (lws_server_socket_service_ssl(cwsi, accept_fd)) { lws_close_free_wsi(cwsi, LWS_CLOSE_STATUS_NOSTATUS, "listen svc fail"); + return LWS_HPI_RET_DIE; + } lwsl_info("%s: new wsi %p: wsistate 0x%x, role_ops %s\n", __func__, cwsi, cwsi->wsistate, cwsi->role_ops->name); @@ -148,7 +150,8 @@ int rops_handle_POLLOUT_listen(struct lws *wsi) } struct lws_role_ops role_ops_listen = { - "listen", + /* role name */ "listen", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -162,6 +165,7 @@ struct lws_role_ops role_ops_listen = { /* write_role_protocol */ NULL, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c index 8a573e70..aaccb5b5 100644 --- a/lib/roles/pipe/ops-pipe.c +++ b/lib/roles/pipe/ops-pipe.c @@ -55,7 +55,8 @@ rops_handle_POLLIN_pipe(struct lws_context_per_thread *pt, struct lws *wsi, } struct lws_role_ops role_ops_pipe = { - "pipe", + /* role name */ "pipe", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -69,6 +70,7 @@ struct lws_role_ops role_ops_pipe = { /* write_role_protocol */ NULL, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, diff --git a/lib/roles/raw/ops-raw.c b/lib/roles/raw/ops-raw.c index 1afb4b9f..7921fd9d 100644 --- a/lib/roles/raw/ops-raw.c +++ b/lib/roles/raw/ops-raw.c @@ -151,7 +151,8 @@ rops_handle_POLLIN_raw_file(struct lws_context_per_thread *pt, struct lws *wsi, struct lws_role_ops role_ops_raw_skt = { - "raw-skt", + /* role name */ "raw-skt", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -165,6 +166,7 @@ struct lws_role_ops role_ops_raw_skt = { /* write_role_protocol */ NULL, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, @@ -176,7 +178,8 @@ struct lws_role_ops role_ops_raw_skt = { struct lws_role_ops role_ops_raw_file = { - "raw-file", + /* role name */ "raw-file", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -190,6 +193,7 @@ struct lws_role_ops role_ops_raw_file = { /* write_role_protocol */ NULL, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ NULL, /* close_role */ NULL, /* close_kill_connection */ NULL, diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c index 914b348f..7e997d77 100644 --- a/lib/roles/ws/ops-ws.c +++ b/lib/roles/ws/ops-ws.c @@ -1847,7 +1847,8 @@ rops_callback_on_writable_ws(struct lws *wsi) } struct lws_role_ops role_ops_ws = { - "ws", + /* role name */ "ws", + /* alpn id */ NULL, /* check_upgrades */ NULL, /* init_context */ NULL, /* init_vhost */ NULL, @@ -1861,6 +1862,7 @@ struct lws_role_ops role_ops_ws = { /* write_role_protocol */ rops_write_role_protocol_ws, /* rxflow_cache */ NULL, /* encapsulation_parent */ NULL, + /* alpn_negotiated */ NULL, /* close_via_role_protocol */ rops_close_via_role_protocol_ws, /* close_role */ rops_close_role_ws, /* close_kill_connection */ rops_close_kill_connection_ws, diff --git a/lib/tls/mbedtls/mbedtls-client.c b/lib/tls/mbedtls/mbedtls-client.c index 7f674736..522332f0 100644 --- a/lib/tls/mbedtls/mbedtls-client.c +++ b/lib/tls/mbedtls/mbedtls-client.c @@ -27,29 +27,13 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) return 0; } -struct alpn_ctx { - unsigned char *data; - unsigned short len; -}; - -static struct alpn_ctx protos = { (unsigned char *) -#if defined(LWS_WITH_HTTP2) - "\x02h2" -#endif - "\x08http/1.1", 3 + -#if defined(LWS_WITH_HTTP2) - 3 + -#endif - 9 }; - -static struct alpn_ctx protos_h1 = { (unsigned char *)"\x08http/1.1", 3 + 9 }; - int lws_ssl_client_bio_create(struct lws *wsi) { - struct alpn_ctx *apro = &protos; X509_VERIFY_PARAM *param; char hostname[128], *p; + const char *alpn_comma = wsi->context->alpn_default; + struct alpn_ctx protos; if (lws_hdr_copy(wsi, hostname, sizeof(hostname), _WSI_TOKEN_CLIENT_HOST) <= 0) { @@ -75,11 +59,6 @@ lws_ssl_client_bio_create(struct lws *wsi) if (!wsi->ssl) return -1; - if (wsi->use_ssl & LCCSCF_NOT_H2) - apro = &protos_h1; - - SSL_set_alpn_select_cb(wsi->ssl, apro); - if (wsi->vhost->ssl_info_event_mask) SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); @@ -91,6 +70,22 @@ lws_ssl_client_bio_create(struct lws *wsi) X509_VERIFY_PARAM_set1_host(param, hostname, 0); } + if (wsi->vhost->alpn) + alpn_comma = wsi->vhost->alpn; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_ALPN) > 0) + alpn_comma = hostname; + + lwsl_info("%s: %p: client conn sending ALPN list '%s'\n", + __func__, wsi, alpn_comma); + + protos.len = lws_alpn_comma_to_openssl(alpn_comma, protos.data, + sizeof(protos.data) - 1); + + /* with mbedtls, protos is not pointed to after exit from this call */ + SSL_set_alpn_select_cb(wsi->ssl, &protos); + /* * use server name indication (SNI), if supported, * when establishing connection @@ -117,20 +112,8 @@ lws_tls_client_connect(struct lws *wsi) if (n == 1) { SSL_get0_alpn_selected(wsi->ssl, &prot, &len); -#if !defined(LWS_NO_CLIENT) - if (prot && !strcmp((char *)prot, "h2")) - wsi->client_h2_alpn = 1; -#endif - if (prot && !strcmp((char *)prot, "http/1.1")) - /* - * If alpn asserts it is http/1.1, KA is mandatory. - * - * Knowing this lets us proceed with sending - * pipelined headers before we received the first - * response headers. - */ - wsi->keepalive_active = 1; - + lws_role_call_alpn_negotiated(wsi, (const char *)prot); + lwsl_info("client connect OK\n"); return LWS_SSL_CAPABLE_DONE; } diff --git a/lib/tls/mbedtls/wrapper/library/ssl_lib.c b/lib/tls/mbedtls/wrapper/library/ssl_lib.c index 0b13cca1..2f688ca9 100644 --- a/lib/tls/mbedtls/wrapper/library/ssl_lib.c +++ b/lib/tls/mbedtls/wrapper/library/ssl_lib.c @@ -1648,8 +1648,8 @@ void *SSL_CTX_get_ex_data(const SSL_CTX *ctx, int idx) */ struct alpn_ctx { - unsigned char *data; - unsigned short len; + unsigned char data[23]; + unsigned char len; }; static void @@ -1674,6 +1674,9 @@ _openssl_alpn_to_mbedtls(struct alpn_ctx *ac, char ***palpn_protos) break; } + if (!len) + count++; + if (!count) return; @@ -1705,6 +1708,12 @@ _openssl_alpn_to_mbedtls(struct alpn_ctx *ac, char ***palpn_protos) if (!len) break; } + if (!len) { + *q++ = '\0'; + count++; + len = *p++; + alpn_protos[count] = (char *)q; + } alpn_protos[count] = NULL; /* last pointer ends list with NULL */ } diff --git a/lib/tls/openssl/openssl-client.c b/lib/tls/openssl/openssl-client.c index 7317b437..5c5bb489 100644 --- a/lib/tls/openssl/openssl-client.c +++ b/lib/tls/openssl/openssl-client.c @@ -86,14 +86,6 @@ OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) } #endif -#if defined(LWS_HAVE_SSL_set_alpn_protos) && defined(LWS_HAVE_SSL_get0_alpn_selected) -static const unsigned char client_alpn_protocols[] = { -#if defined(LWS_WITH_HTTP2) - 2, 'h', '2', -#endif - 8, 'h', 't', 't', 'p', '/', '1', '.', '1' -}; -#endif int lws_ssl_client_bio_create(struct lws *wsi) @@ -102,9 +94,11 @@ lws_ssl_client_bio_create(struct lws *wsi) X509_VERIFY_PARAM *param; #endif char hostname[128], *p; -#if defined(LWS_HAVE_SSL_set_alpn_protos) && defined(LWS_HAVE_SSL_get0_alpn_selected) - const unsigned char *plist = client_alpn_protocols; - int n = sizeof(client_alpn_protocols); +#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ + defined(LWS_HAVE_SSL_get0_alpn_selected) + uint8_t openssl_alpn[40]; + const char *alpn_comma = wsi->context->alpn_default; + int n; #endif if (lws_hdr_copy(wsi, hostname, sizeof(hostname), @@ -135,17 +129,6 @@ lws_ssl_client_bio_create(struct lws *wsi) return -1; } -#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ - defined(LWS_HAVE_SSL_get0_alpn_selected) -#if defined(LWS_WITH_HTTP2) - if (wsi->use_ssl & LCCSCF_NOT_H2) { - plist += 3; - n -= 3; - } -#endif - SSL_set_alpn_protos(wsi->ssl, plist, n); -#endif - #if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) if (wsi->vhost->ssl_info_event_mask) SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); @@ -221,6 +204,23 @@ lws_ssl_client_bio_create(struct lws *wsi) BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ #endif +#if defined(LWS_HAVE_SSL_set_alpn_protos) && \ + defined(LWS_HAVE_SSL_get0_alpn_selected) + if (wsi->vhost->alpn) + alpn_comma = wsi->vhost->alpn; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_ALPN) > 0) + alpn_comma = hostname; + + lwsl_info("client conn using alpn list '%s'\n", alpn_comma); + + n = lws_alpn_comma_to_openssl(alpn_comma, openssl_alpn, + sizeof(openssl_alpn) - 1); + + SSL_set_alpn_protos(wsi->ssl, openssl_alpn, n); +#endif + SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, wsi); return 0; @@ -244,24 +244,10 @@ lws_tls_client_connect(struct lws *wsi) len = sizeof(a) - 1; memcpy(a, (const char *)prot, len); a[len] = '\0'; -#if !defined(LWS_NO_CLIENT) - if (prot && !strcmp(a, "h2")) { - lwsl_info("%s: upgraded to H2\n", __func__); - wsi->client_h2_alpn = 1; - } -#endif - if (prot && !strcmp(a, "http/1.1")) - /* - * If alpn asserts it is http/1.1, KA is mandatory. - * - * Knowing this lets us proceed with sending - * pipelined headers before we received the first - * response headers. - */ - wsi->keepalive_active = 1; - lwsl_info("client connect OK\n"); + lws_role_call_alpn_negotiated(wsi, (const char *)a); #endif + lwsl_info("client connect OK\n"); return LWS_SSL_CAPABLE_DONE; } diff --git a/lib/tls/tls-server.c b/lib/tls/tls-server.c index c2a16f7d..1a0f3edc 100644 --- a/lib/tls/tls-server.c +++ b/lib/tls/tls-server.c @@ -21,6 +21,79 @@ #include "private-libwebsockets.h" +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) +static int +alpn_cb(SSL *s, const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ +#if !defined(LWS_WITH_MBEDTLS) + struct alpn_ctx *alpn_ctx = (struct 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; +#endif + + return SSL_TLSEXT_ERR_OK; +} +#endif + +void +lws_context_init_alpn(struct lws_vhost *vhost) +{ +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) + const char *alpn_comma = vhost->context->alpn_default; + + if (vhost->alpn) + alpn_comma = vhost->alpn; + + lwsl_info(" Server '%s' advertising ALPN: %s\n", + vhost->name, alpn_comma); + vhost->alpn_ctx.len = lws_alpn_comma_to_openssl(alpn_comma, + vhost->alpn_ctx.data, + sizeof(vhost->alpn_ctx.data) - 1); + + SSL_CTX_set_alpn_select_cb(vhost->ssl_ctx, alpn_cb, &vhost->alpn_ctx); +#else + lwsl_err( + " HTTP2 / ALPN configured but not supported by OpenSSL 0x%lx\n", + OPENSSL_VERSION_NUMBER); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L +} + +int +lws_tls_server_conn_alpn(struct lws *wsi) +{ +#if defined(LWS_WITH_MBEDTLS) || (defined(OPENSSL_VERSION_NUMBER) && \ + OPENSSL_VERSION_NUMBER >= 0x10002000L) + const unsigned char *name = NULL; + char cstr[10]; + unsigned len; + + SSL_get0_alpn_selected(wsi->ssl, &name, &len); + if (!len) { + lwsl_info("no ALPN upgrade\n"); + return 0; + } + + if (len > sizeof(cstr) - 1) + len = sizeof(cstr) - 1; + + memcpy(cstr, name, len); + cstr[len] = '\0'; + + lwsl_info("negotiated '%s' using ALPN\n", cstr); + wsi->use_ssl |= LCCSCF_USE_SSL; + + return lws_role_call_alpn_negotiated(wsi, (const char *)cstr); +#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L + + return 0; +} + LWS_VISIBLE int lws_context_init_server_ssl(struct lws_context_creation_info *info, struct lws_vhost *vhost) @@ -95,11 +168,7 @@ lws_context_init_server_ssl(struct lws_context_creation_info *info, } if (vhost->use_ssl) - /* - * SSL is happy and has a cert it's content with - * If we're supporting HTTP2, initialize that - */ - lws_context_init_http2_ssl(vhost); + lws_context_init_alpn(vhost); return 0; } @@ -296,11 +365,8 @@ accepted: context->timeout_secs); lwsi_set_state(wsi, LRS_ESTABLISHED); - -#if defined(LWS_WITH_HTTP2) - if (lws_h2_configure_if_upgraded(wsi)) + if (lws_tls_server_conn_alpn(wsi)) goto fail; -#endif lwsl_debug("accepted new SSL conn\n"); break; diff --git a/lib/tls/tls.c b/lib/tls/tls.c index 9333e399..38394642 100644 --- a/lib/tls/tls.c +++ b/lib/tls/tls.c @@ -478,3 +478,37 @@ lws_gate_accepts(struct lws_context *context, int on) return 0; } + +/* comma-separated alpn list, like "h2,http/1.1" to openssl alpn format */ + +int +lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len) +{ + uint8_t *oos = os, *plen = NULL; + + while (*comma && len > 1) { + if (!plen && *comma == ' ') { + comma++; + continue; + } + if (!plen) { + plen = os++; + len--; + } + + if (*comma == ',') { + *plen = lws_ptr_diff(os, plen + 1); + plen = NULL; + comma++; + } else { + *os++ = *comma++; + len--; + } + } + + if (plen) + *plen = lws_ptr_diff(os, plen + 1); + + return lws_ptr_diff(os, oos); +} + 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 6e271036..7e87780d 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 @@ -153,24 +153,13 @@ lws_try_client_connection(struct lws_client_connect_info *i, int m) lwsl_user("started connection %d\n", m); } -static int commandline_option(int argc, char **argv, const char *val) -{ - int n = strlen(val); - - while (--argc > 0) { - if (!strncmp(argv[argc], val, n)) - return argc; - } - - return 0; -} - -int main(int argc, char **argv) +int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_client_connect_info i; struct lws_context *context; unsigned long long start, next; + const char *p; int n = 0, m, staggered = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE /* for LLL_ verbosity above NOTICE to be built into lws, @@ -184,14 +173,13 @@ int main(int argc, char **argv) memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ - staggered = !!commandline_option(argc, argv, "-s"); - m = commandline_option(argc, argv, "-d"); - if (m && m + 1 < argc) - logs = atoi(argv[m + 1]); + staggered = !!lws_cmdline_option(argc, argv, "-s"); + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); lws_set_log_level(logs, NULL); lwsl_user("LWS minimal http client [-s (staggered)] [-p (pipeline)]\n"); - lwsl_user(" [--h1 (http/1 only)] [-l (localhost)]\n"); + lwsl_user(" [--h1 (http/1 only)] [-l (localhost)] [-d ]\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; @@ -218,14 +206,14 @@ int main(int argc, char **argv) i.ssl_connection = LCCSCF_USE_SSL; /* enables h1 or h2 connection sharing */ - if (commandline_option(argc, argv, "-p")) + if (lws_cmdline_option(argc, argv, "-p")) i.ssl_connection |= LCCSCF_PIPELINE; /* force h1 even if h2 available */ - if (commandline_option(argc, argv, "--h1")) - i.ssl_connection |= LCCSCF_NOT_H2; + if (lws_cmdline_option(argc, argv, "--h1")) + i.alpn = "http/1.1"; - if (commandline_option(argc, argv, "-l")) { + if (lws_cmdline_option(argc, argv, "-l")) { i.port = 7681; i.address = "localhost"; i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; diff --git a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c index a7ff86ae..a367e191 100644 --- a/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c +++ b/minimal-examples/ws-client/minimal-ws-client-rx/minimal-ws-client.c @@ -69,24 +69,26 @@ sigint_handler(int sig) interrupted = 1; } -int main(int argc, char **argv) +int main(int argc, const char **argv) { struct lws_context_creation_info info; struct lws_client_connect_info i; struct lws_context *context; - int n = 0; + const char *p; + int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE + /* for LLL_ verbosity above NOTICE to be built into lws, lws + * must have been configured with -DCMAKE_BUILD_TYPE=DEBUG + * instead of =RELEASE */ + /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ + /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ + /* | LLL_DEBUG */; signal(SIGINT, sigint_handler); + if ((p = lws_cmdline_option(argc, argv, "-d"))) + logs = atoi(p); - lws_set_log_level(LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE - /* for LLL_ verbosity above NOTICE to be built into lws, - * lws must have been configured and built with - * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ - /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ - /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ - /* | LLL_DEBUG */, NULL); - - lwsl_user("LWS minimal ws client rx\n"); + lws_set_log_level(logs, NULL); + lwsl_user("LWS minimal ws client rx [-d ] [--h2]\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; @@ -108,22 +110,25 @@ int main(int argc, char **argv) memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */ i.context = context; - i.port = 443; i.address = "libwebsockets.org"; i.path = "/"; i.host = i.address; i.origin = i.address; i.ssl_connection = LCCSCF_USE_SSL; - i.protocol = protocols[0].name; /* "dumb-increment-protocol" */ i.pwsi = &client_wsi; + + if (lws_cmdline_option(argc, argv, "--h2")) + i.alpn = "h2"; + lws_client_connect_via_info(&i); while (n >= 0 && client_wsi && !interrupted) n = lws_service(context, 1000); lws_context_destroy(context); + lwsl_user("Completed\n"); return 0;