From aa816e98a9081bde460c0dea36ba3ecd792bcc06 Mon Sep 17 00:00:00 2001 From: Andy Green Date: Thu, 12 Apr 2018 15:56:38 +0800 Subject: [PATCH] alpn: assemble defaults from roles and allow override Since new roles may be incompatible with http, add support for alpn names at the role struct, automatic generation of the default list of alpn names that servers advertise, and the ability to override the used alpn names per-vhost and per- client connection. This not only lets you modulate visibility or use of h2, but also enables vhosts that only offer non-http roles, as well as restricting http role vhosts to only alpn identifiers related to http roles. --- CMakeLists.txt | 1 - lib/context.c | 60 ++++++- lib/libwebsockets.c | 21 ++- lib/libwebsockets.h | 45 ++++- lib/output.c | 8 +- lib/private-libwebsockets.h | 44 +++-- lib/roles/cgi/ops-cgi.c | 4 +- lib/roles/h1/ops-h1.c | 26 ++- lib/roles/h2/ops-h2.c | 49 +++++- lib/roles/h2/ssl-http2.c | 158 ------------------ lib/roles/http/client/client-handshake.c | 51 ++++-- lib/roles/http/client/client.c | 7 +- lib/roles/http/server/lejp-conf.c | 14 +- lib/roles/http/server/server.c | 6 +- lib/roles/listen/ops-listen.c | 8 +- lib/roles/pipe/ops-pipe.c | 4 +- lib/roles/raw/ops-raw.c | 8 +- lib/roles/ws/ops-ws.c | 4 +- lib/tls/mbedtls/mbedtls-client.c | 57 +++---- lib/tls/mbedtls/wrapper/library/ssl_lib.c | 13 +- lib/tls/openssl/openssl-client.c | 62 +++---- lib/tls/tls-server.c | 84 +++++++++- lib/tls/tls.c | 34 ++++ .../minimal-http-client-multi.c | 32 ++-- .../minimal-ws-client-rx/minimal-ws-client.c | 31 ++-- 25 files changed, 497 insertions(+), 334 deletions(-) delete mode 100644 lib/roles/h2/ssl-http2.c 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;