diff --git a/include/libwebsockets/lws-misc.h b/include/libwebsockets/lws-misc.h index 8dcf1bd33..9b8b7a943 100644 --- a/include/libwebsockets/lws-misc.h +++ b/include/libwebsockets/lws-misc.h @@ -318,10 +318,7 @@ lws_hex_to_byte_array(const char *h, uint8_t *dest, int max); * \param len: the number of bytes the buffer dest points to can hold * * This creates random ascii-hex strings up to a given length, with a - * terminating NUL. Hex characters are produced in pairs, if the length of - * the destination buffer is even, after accounting for the NUL there will be - * an unused byte at the end after the NUL. So lengths should be odd to get - * length - 1 characters exactly followed by the NUL. + * terminating NUL. * * There will not be any characters produced that are not 0-9, a-f, so it's * safe to go straight into, eg, JSON. diff --git a/lib/core/libwebsockets.c b/lib/core/libwebsockets.c index aabf35e7e..fef95983a 100644 --- a/lib/core/libwebsockets.c +++ b/lib/core/libwebsockets.c @@ -158,18 +158,22 @@ static char *hexch = "0123456789abcdef"; int lws_hex_random(struct lws_context *context, char *dest, size_t len) { - size_t n = (len - 1) / 2; + size_t n = ((len - 1) / 2) + 1; uint8_t b, *r = (uint8_t *)dest + len - n; if (lws_get_random(context, r, n) != n) return 1; - while (n--) { + while (len >= 3) { b = *r++; *dest++ = hexch[b >> 4]; *dest++ = hexch[b & 0xf]; + len -= 2; } + if (len == 2) + *dest++ = hexch[(*r) >> 4]; + *dest = '\0'; return 0; diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c index 6c1c4d84b..07d0ef6fe 100644 --- a/lib/roles/h2/http2.c +++ b/lib/roles/h2/http2.c @@ -2123,9 +2123,14 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t _inlen, WSI_TOKEN_HTTP_CONTENT_LENGTH) && h2n->swsi->http.rx_content_length && h2n->swsi->http.rx_content_remain < - lws_ptr_diff_size_t(iend, in) + 1 && /* last */ + lws_ptr_diff_size_t(iend, in) + 1 - 9 && /* last */ h2n->inside < h2n->length) { + lwsl_warn("%s: %lu %lu %lu %lu\n", __func__, + (unsigned long)h2n->swsi->http.rx_content_remain, + (unsigned long)(lws_ptr_diff_size_t(iend, in) + 1 - 9), + (unsigned long)h2n->inside, (unsigned long)h2n->length); + /* unread data in frame */ lws_h2_goaway(wsi, H2_ERR_PROTOCOL_ERROR, diff --git a/lib/roles/http/client/client-http.c b/lib/roles/http/client/client-http.c index e600bd651..dfb2105f1 100644 --- a/lib/roles/http/client/client-http.c +++ b/lib/roles/http/client/client-http.c @@ -1070,7 +1070,7 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) { const char *meth, *pp = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); - char *p = pkt, *p1; + char *p = pkt, *p1, *end = p + wsi->a.context->pt_serv_buf_size; meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (!meth) { @@ -1122,24 +1122,29 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) * Sec-WebSocket-Version: 4 */ - p += lws_snprintf(p, 2048, "%s %s HTTP/1.1\x0d\x0a", meth, - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "%s %s HTTP/1.1\x0d\x0a", meth, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); - p += lws_snprintf(p, 64, "Pragma: no-cache\x0d\x0a" - "Cache-Control: no-cache\x0d\x0a"); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); - p += lws_snprintf(p, 128, "Host: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Host: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { if (lws_check_opt(wsi->a.context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) - p += lws_snprintf(p, 128, "Origin: %s\x0d\x0a", - lws_hdr_simple_ptr(wsi, + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Origin: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); else - p += lws_snprintf(p, 128, "Origin: http://%s\x0d\x0a", - lws_hdr_simple_ptr(wsi, + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Origin: http://%s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); } @@ -1153,19 +1158,22 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) #if defined(LWS_WITH_HTTP_PROXY) if (wsi->parent && lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { - p += lws_snprintf(p, 128, "Content-Length: %s\x0d\x0a", + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Content-Length: %s\x0d\x0a", lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)); if (atoi(lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH))) wsi->client_http_body_pending = 1; } if (wsi->parent && lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION)) { - p += lws_snprintf(p, 128, "Authorization: %s\x0d\x0a", + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Authorization: %s\x0d\x0a", lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION)); } if (wsi->parent && lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE)) { - p += lws_snprintf(p, 128, "Content-Type: %s\x0d\x0a", + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), + "Content-Type: %s\x0d\x0a", lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE)); } #endif @@ -1192,12 +1200,12 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt) return NULL; if (wsi->flags & LCCSCF_HTTP_X_WWW_FORM_URLENCODED) { - p += lws_snprintf(p, 128, "Content-Type: application/x-www-form-urlencoded\x0d\x0a"); - p += lws_snprintf(p, 128, "Content-Length: %lu\x0d\x0a", wsi->http.writeable_len); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "Content-Type: application/x-www-form-urlencoded\x0d\x0a"); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "Content-Length: %lu\x0d\x0a", wsi->http.writeable_len); lws_client_http_body_pending(wsi, 1); } - p += lws_snprintf(p, 4, "\x0d\x0a"); + p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\x0d\x0a"); if (wsi->client_http_body_pending) lws_callback_on_writable(wsi); diff --git a/lib/secure-streams/secure-streams-client.c b/lib/secure-streams/secure-streams-client.c index 91d9bd0c5..e0477553e 100644 --- a/lib/secure-streams/secure-streams-client.c +++ b/lib/secure-streams/secure-streams-client.c @@ -130,10 +130,11 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { lws_sspc_handle_t *h = (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi); - uint8_t s[32], pkt[LWS_PRE + 2048], *p = pkt + LWS_PRE, - *end = p + sizeof(pkt) - LWS_PRE; + size_t pktsize = wsi->a.context->max_http_header_data; void *m = (void *)((uint8_t *)&h[1]); + uint8_t *pkt = NULL, *p = NULL, *end = NULL; const uint8_t *cp; + uint8_t s[32]; lws_usec_t us; int flags, n; @@ -313,7 +314,12 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, lws_dll2_get_tail(&h->metadata_owner), lws_sspc_metadata_t, list); - cp = p; + pkt = lws_malloc(pktsize + LWS_PRE, __func__); + if (!pkt) + goto hangup; + cp = p = pkt + LWS_PRE; + end = p + pktsize; + n = lws_sspc_serialize_metadata(md, p, end); if (n < 0) goto metadata_hangup; @@ -361,12 +367,17 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, * length? */ + pkt = lws_malloc(pktsize + LWS_PRE, __func__); + if (!pkt) + goto hangup; + cp = p = pkt + LWS_PRE; + end = p + pktsize; + if (h->metadata_owner.count) { lws_sspc_metadata_t *md = lws_container_of( lws_dll2_get_tail(&h->metadata_owner), lws_sspc_metadata_t, list); - cp = p; n = lws_sspc_serialize_metadata(md, p, end); if (n < 0) goto metadata_hangup; @@ -393,7 +404,7 @@ callback_sspc_client(struct lws *wsi, enum lws_callback_reasons reason, // break; } - len = sizeof(pkt) - LWS_PRE - 19; + len = pktsize - LWS_PRE - 19; flags = 0; if (!h->ssi.tx) { n = 0; @@ -457,12 +468,15 @@ do_write: break; } + lws_free(pkt); + return lws_callback_http_dummy(wsi, reason, user, in, len); metadata_hangup: lwsl_err("%s: metadata too large\n", __func__); hangup: + lws_free(pkt); lwsl_warn("hangup\n"); /* hang up on him */ return -1; diff --git a/lib/secure-streams/secure-streams-process.c b/lib/secure-streams/secure-streams-process.c index 63ac7f9a2..306d11d39 100644 --- a/lib/secure-streams/secure-streams-process.c +++ b/lib/secure-streams/secure-streams-process.c @@ -385,7 +385,10 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, /* * The current wsi is decoupled from the pss / conn and - * the conn no longer has a pointer on it + * the conn no longer has a pointer on it. + * + * If there's an outgoing, proxied SS conn on our behalf, we + * have to destroy those */ if (conn->ss) { @@ -397,8 +400,9 @@ callback_ss_proxy(struct lws *wsi, enum lws_callback_reasons reason, lwsl_info("%s: destroying %s, wsi %s\n", __func__, lws_ss_tag(conn->ss), lws_wsi_tag(conn->ss->wsi)); - /* sever relationship with ss about to be deleted */ - lws_set_opaque_user_data(wsi, NULL); + + /* sever conn relationship with ss about to be deleted */ + conn->ss->wsi = NULL; if (cw && wsi != cw) { diff --git a/lib/secure-streams/secure-streams.c b/lib/secure-streams/secure-streams.c index a5bd6c9e1..1cc7e6343 100644 --- a/lib/secure-streams/secure-streams.c +++ b/lib/secure-streams/secure-streams.c @@ -482,10 +482,11 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry, void *conn_if_sspc_onw) const struct ss_pcols *ssp; size_t used_in, used_out; union lws_ss_contemp ct; - char path[1024], ep[96]; lws_ss_state_return_t r; int port, _port, tls; + char *path, ep[96]; lws_strexp_t exp; + struct lws *wsi; if (!h->policy) { lwsl_err("%s: ss with no policy\n", __func__); @@ -651,14 +652,20 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry, void *conn_if_sspc_onw) i.protocol = ssp->protocol->name; /* lws protocol name */ i.local_protocol_name = i.protocol; + path = lws_malloc(h->context->max_http_header_data, __func__); + if (!path) { + lwsl_warn("%s: OOM on path prealloc\n", __func__); + return LWSSSSRET_TX_DONT_SEND; + } + if (ssp->munge) /* eg, raw doesn't use; endpoint strexp already done */ - ssp->munge(h, path, sizeof(path), &i, &ct); + ssp->munge(h, path, h->context->max_http_header_data, &i, &ct); i.pwsi = &h->wsi; #if defined(LWS_WITH_SSPLUGINS) if (h->policy->plugins[0] && h->policy->plugins[0]->munge) - h->policy->plugins[0]->munge(h, path, sizeof(path)); + h->policy->plugins[0]->munge(h, path, h->context->max_http_header_data); #endif lwsl_info("%s: connecting %s, '%s' '%s' %s\n", __func__, i.method, @@ -666,10 +673,14 @@ _lws_ss_client_connect(lws_ss_handle_t *h, int is_retry, void *conn_if_sspc_onw) h->txn_ok = 0; r = lws_ss_event_helper(h, LWSSSCS_CONNECTING); - if (r) + if (r) { + lws_free(path); return r; + } - if (!lws_client_connect_via_info(&i)) { + wsi = lws_client_connect_via_info(&i); + lws_free(path); + if (!wsi) { /* * We already found that we could not connect, without even * having to go around the event loop diff --git a/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/CMakeLists.txt b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/CMakeLists.txt new file mode 100644 index 000000000..52dc21623 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/CMakeLists.txt @@ -0,0 +1,133 @@ +project(lws-minimal-secure-streams-hugeurl C) +cmake_minimum_required(VERSION 2.8.12) +find_package(libwebsockets CONFIG REQUIRED) +list(APPEND CMAKE_MODULE_PATH ${LWS_CMAKE_DIR}) +include(CheckCSourceCompiles) +include(LwsCheckRequirements) + +set(SAMP lws-minimal-secure-streams-hugeurl) + +set(requirements 1) +require_lws_config(LWS_ROLE_H1 1 requirements) +require_lws_config(LWS_WITHOUT_CLIENT 0 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS 1 requirements) +require_lws_config(LWS_WITH_SECURE_STREAMS_STATIC_POLICY_ONLY 0 requirements) +require_lws_config(LWS_WITH_SYS_STATE 1 requirements) + +if (requirements) + add_executable(${SAMP} minimal-secure-streams.c) + + find_program(VALGRIND "valgrind") + + if (LWS_CTEST_INTERNET_AVAILABLE AND NOT WIN32) + + # + # When running in CI, wait for a lease on the resources + # before starting this test, so the server does not get + # thousands of simultaneous tls connection attempts + # + # sai-resource holds the lease on the resources until + # the time given in seconds or the sai-resource instance + # exits, whichever happens first + # + # If running under Sai, creates a lock test called "res_sspcmin_hurl" + # + + sai_resource(warmcat_conns 1 40 sspcmin_hurl) + + # + # simple test not via proxy + # + + if (VALGRIND) + message("testing via valgrind") + add_test(NAME ss-warmcat-hurl COMMAND + ${VALGRIND} --tool=memcheck --leak-check=yes --num-callers=20 + $) + else() + add_test(NAME ss-warmcat-hurl COMMAND lws-minimal-secure-streams) + endif() + + set_tests_properties(ss-warmcat-hurl + PROPERTIES + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/secure-streams/minimal-secure-streams + TIMEOUT 20) + if (DEFINED ENV{SAI_OVN}) + set_tests_properties(ss-warmcat-hurl PROPERTIES FIXTURES_REQUIRED "res_sspcmin_hurl") + endif() + + if (HAS_LWS_WITH_SECURE_STREAMS_PROXY_API OR LWS_WITH_SECURE_STREAMS_PROXY_API) + + # + # Define test dep to bring up and take down the test + # proxy + # + + if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + # uds abstract namespace for linux + set(CTEST_SOCKET_PATH "@ctest-ssp-hurl-$ENV{SAI_PROJECT}-$ENV{SAI_OVN}") + else() + # filesystem socket for others + set(CTEST_SOCKET_PATH "/tmp/ctest-ssp-hurl-$ENV{SAI_PROJECT}-$ENV{SAI_OVN}") + endif() + add_test(NAME st_ssproxy-hurl COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background.sh + ssproxy-hurl $ + -i ${CTEST_SOCKET_PATH} ) + set_tests_properties(st_ssproxy-hurl PROPERTIES WORKING_DIRECTORY . FIXTURES_SETUP ssproxy-hurl TIMEOUT 800) + + add_test(NAME ki_ssproxy-hurl COMMAND + ${CMAKE_SOURCE_DIR}/scripts/ctest-background-kill.sh + ssproxy-hurl $ + -i ${CTEST_SOCKET_PATH}) + set_tests_properties(ki_ssproxy-hurl PROPERTIES FIXTURES_CLEANUP ssproxy-hurl) + + # + # the client part that will connect to the proxy + # + + if (VALGRIND) + message("testing via valgrind") + add_test(NAME sspc-minimal-hurl COMMAND + ${VALGRIND} --tool=memcheck --leak-check=yes --num-callers=20 + $ -i +${CTEST_SOCKET_PATH}) + else() + add_test(NAME sspc-minimal-hurl COMMAND lws-minimal-secure-streams-client -i +${CTEST_SOCKET_PATH}) + endif() + + set(fixlist "ssproxy-hurl") + if (DEFINED ENV{SAI_OVN}) + list(APPEND fixlist "res_ssproxy-hurl") + endif() + + set_tests_properties(sspc-minimal-hurl PROPERTIES + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/minimal-examples/secure-streams/minimal-secure-streams + FIXTURES_REQUIRED "${fixlist}" + TIMEOUT 40) + + endif() + + endif() + + if (websockets_shared) + target_link_libraries(${SAMP} websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP} websockets_shared) + else() + target_link_libraries(${SAMP} websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() + + CHECK_C_SOURCE_COMPILES("#include \nint main(void) {\ni#if defined(LWS_WITH_SECURE_STREAMS_PROXY_API)\n return 0;\n #else\n fail\n #endif\n return 0;\n}\n" HAS_LWS_WITH_SECURE_STREAMS_PROXY_API) + + if (HAS_LWS_WITH_SECURE_STREAMS_PROXY_API OR LWS_WITH_SECURE_STREAMS_PROXY_API) + add_compile_options(-DLWS_SS_USE_SSPC) + + add_executable(${SAMP}-client minimal-secure-streams.c) + if (websockets_shared) + target_link_libraries(${SAMP}-client websockets_shared ${LIBWEBSOCKETS_DEP_LIBS}) + add_dependencies(${SAMP}-client websockets_shared) + else() + target_link_libraries(${SAMP}-client websockets ${LIBWEBSOCKETS_DEP_LIBS}) + endif() + endif() + +endif() diff --git a/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/README.md b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/README.md new file mode 100644 index 000000000..41c99b272 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/README.md @@ -0,0 +1,72 @@ +# lws minimal secure streams hugeurl + +This application sends a huge url to httpbin.org, by default 4000 bytes in +a urlarg ?x=xxxxxx..., where the argument is a random string in hex. + +Notice that httpbin.org has its own limit for urlsize, of 4094 bytes for +the entire URL. + +## build + +``` + $ cmake . && make +``` + +## usage + +Commandline option|Meaning +---|--- +-d |Debug verbosity in decimal, eg, -d15 +-h |Default 4000 +--h1|Force http/1.1 instead of default h2 + +``` +[2021/03/02 16:38:00:2662] U: LWS secure streams hugeurl test client [-d][-h ] +[2021/03/02 16:38:00:2662] U: main: huge argument size: 4000 bytes +[2021/03/02 16:38:00:2662] N: LWS: 4.1.99-v4.1.0-294-g85c1fe07a7, loglevel 1031 +[2021/03/02 16:38:00:2662] N: NET CLI SRV H1 H2 WS SS-JSON-POL SSPROX IPV6-on +[2021/03/02 16:38:00:2663] N: ++ [1903157|wsi|0|pipe] (1) +[2021/03/02 16:38:00:2663] N: ++ [1903157|vh|0|netlink] (1) +[2021/03/02 16:38:00:2677] N: ++ [1903157|vh|1|_ss_default||-1] (2) +[2021/03/02 16:38:00:2736] N: ++ [1903157|vh|2|arca1||-1] (3) +[2021/03/02 16:38:00:2798] N: ++ [1903157|wsiSScli|0|captive_portal_detect] (1) +[2021/03/02 16:38:00:2798] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect]: (unset) -> LWSSSCS_CREATING +[2021/03/02 16:38:00:2798] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect]: LWSSSCS_CREATING -> LWSSSCS_POLL +[2021/03/02 16:38:00:2800] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect]: LWSSSCS_POLL -> LWSSSCS_CONNECTING +[2021/03/02 16:38:00:2801] N: ++ [1903157|wsicli|0|GET/h1/connectivitycheck.android.com/([1903157|wsiSScli|0|captive_portal_det] (1) +[2021/03/02 16:38:00:3227] W: lws_metrics_hist_bump_priv_tagged: 'ss="captive_portal_detect",http_resp="204"' +[2021/03/02 16:38:00:3227] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect|204]: LWSSSCS_CONNECTING -> LWSSSCS_CONNECTED +[2021/03/02 16:38:00:3227] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect|204]: LWSSSCS_CONNECTED -> LWSSSCS_QOS_ACK_REMOTE +[2021/03/02 16:38:00:3227] N: lws_system_cpd_set: setting CPD result OK +[2021/03/02 16:38:00:3227] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect|204]: LWSSSCS_QOS_ACK_REMOTE -> LWSSSCS_DISCONNECTED +[2021/03/02 16:38:00:3228] N: lws_ss_check_next_state: [1903157|wsiSScli|0|captive_portal_detect|204]: LWSSSCS_DISCONNECTED -> LWSSSCS_DESTROYING +[2021/03/02 16:38:00:3228] N: -- [1903157|wsiSScli|0|captive_portal_detect|204] (0) 42.928ms +[2021/03/02 16:38:00:3231] N: -- [1903157|wsicli|0|GET/h1/connectivitycheck.android.com/([1903157|wsiSScli|0|captive_portal_det] (0) 42.994ms +[2021/03/02 16:38:00:3853] N: ++ [1903157|wsiSScli|1|httpbin_anything] (1) +[2021/03/02 16:38:00:3854] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything]: (unset) -> LWSSSCS_CREATING +[2021/03/02 16:38:00:3854] U: myss_state: LWSSSCS_CREATING (1), ord 0x0 +[2021/03/02 16:38:00:3855] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything]: LWSSSCS_CREATING -> LWSSSCS_CONNECTING +[2021/03/02 16:38:00:3855] U: myss_state: LWSSSCS_CONNECTING (6), ord 0x0 +[2021/03/02 16:38:00:3855] N: ++ [1903157|wsicli|1|GET/h1/httpbin.org/([1903157|wsiSScli|1|httpbin_anything])] (1) +[2021/03/02 16:38:00:6855] N: ++ [1903157|mux|0|h2_sid1_(1903157|wsicli|1)] (1) +[2021/03/02 16:38:00:6857] N: secstream_h1: [1903157|wsiSScli|1|httpbin_anything] no handle / tx +[2021/03/02 16:38:00:7904] W: lws_metrics_hist_bump_priv_tagged: 'ss="httpbin_anything",http_resp="200"' +[2021/03/02 16:38:00:7904] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything|200]: LWSSSCS_CONNECTING -> LWSSSCS_CONNECTED +[2021/03/02 16:38:00:7904] U: myss_state: LWSSSCS_CONNECTED (5), ord 0x0 +[2021/03/02 16:38:00:7907] U: myss_rx: return hugeurl len 4000 matches OK +[2021/03/02 16:38:00:7907] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything|200]: LWSSSCS_CONNECTED -> LWSSSCS_QOS_ACK_REMOTE +[2021/03/02 16:38:00:7907] U: myss_state: LWSSSCS_QOS_ACK_REMOTE (10), ord 0x0 +[2021/03/02 16:38:00:7908] N: myss_state: LWSSSCS_QOS_ACK_REMOTE +[2021/03/02 16:38:00:7908] N: -- [1903157|wsi|0|pipe] (0) 524.500ms +[2021/03/02 16:38:00:7908] N: -- [1903157|mux|0|h2_sid1_(1903157|wsicli|1)] (0) 105.284ms +[2021/03/02 16:38:00:7912] N: -- [1903157|vh|2|arca1||-1] (2) 517.621ms +[2021/03/02 16:38:00:7912] N: -- [1903157|wsicli|1|GET/h1/httpbin.org/([1903157|wsiSScli|1|httpbin_anything|arca1|h2|h2])] (0) 405.690ms +[2021/03/02 16:38:00:7912] N: -- [1903157|vh|0|netlink] (1) 524.918ms +[2021/03/02 16:38:00:7913] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything|200]: LWSSSCS_QOS_ACK_REMOTE -> LWSSSCS_DISCONNECTED +[2021/03/02 16:38:00:7913] U: myss_state: LWSSSCS_DISCONNECTED (2), ord 0x0 +[2021/03/02 16:38:00:7913] N: lws_ss_check_next_state: [1903157|wsiSScli|1|httpbin_anything|200]: LWSSSCS_DISCONNECTED -> LWSSSCS_DESTROYING +[2021/03/02 16:38:00:7913] U: myss_state: LWSSSCS_DESTROYING (7), ord 0x0 +[2021/03/02 16:38:00:7913] N: -- [1903157|wsiSScli|1|httpbin_anything|200] (0) 405.986ms +[2021/03/02 16:38:00:7925] N: -- [1903157|vh|1|_ss_default||-1] (0) 524.844ms +[2021/03/02 16:38:00:7926] U: Completed: OK +``` diff --git a/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c new file mode 100644 index 000000000..16e9e56d7 --- /dev/null +++ b/minimal-examples/secure-streams/minimal-secure-streams-hugeurl/minimal-secure-streams.c @@ -0,0 +1,444 @@ +/* + * lws-minimal-secure-streams-hugeurl + * + * Written in 2010-2021 by Andy Green + * + * This file is made available under the Creative Commons CC0 1.0 + * Universal Public Domain Dedication. + * + * + * This checks huge url operations via httpbin.org + */ + +#include +#include +#include + +static unsigned int timeout_ms = 3000; +static int interrupted, bad = 1, h1; +static lws_state_notify_link_t nl; +static size_t hugeurl_size = 4000; +static char *hugeurl, *check; + +#if !defined(LWS_SS_USE_SSPC) +static const char * const default_ss_policy = + "{" + "\"release\":" "\"01234567\"," + "\"product\":" "\"myproduct\"," + "\"schema-version\":" "1," +#if defined(VIA_LOCALHOST_SOCKS) + "\"via-socks5\":" "\"127.0.0.1:1080\"," +#endif + + "\"retry\": [" /* named backoff / retry strategies */ + "{\"default\": {" + "\"backoff\": [" "1000," + "2000," + "3000," + "5000," + "10000" + "]," + "\"conceal\":" "5," + "\"jitterpc\":" "20," + "\"svalidping\":" "30," + "\"svalidhup\":" "35" + "}}" + "]," + "\"certs\": [" /* named individual certificates in BASE64 DER */ + /* + * Let's Encrypt certs for warmcat.com / libwebsockets.org + * + * We fetch the real policy from there using SS and switch to + * using that. + */ + "{\"amazon_root_ca_1\": \"" + "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0" + "BAQsFADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQ" + "QDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExN" + "zAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcG" + "A1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggE" + "PADCCAQoCggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrA" + "IthtOgQ3pOsqTQNroBvo3bSMgHFzZM9O6II8c+6zf1tRn4SWiw3te5djgdY" + "Z6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQgLKm+a/sRxmPUDgH" + "3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0" + "tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyz" + "iKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIq" + "g0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw" + "HQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwU" + "AA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9r" + "bxenDIU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/m" + "sv0tadQ1wUsN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96L" + "XFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bld" + "ZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8o" + "b2xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5" + "\"}" + "]," + "\"trust_stores\": [" /* named cert chains */ + "{" + "\"name\": \"arca1\"," + "\"stack\": [" + "\"amazon_root_ca_1\"" + "]" + "}" + "]," + "\"s\": [{" + + "\"httpbin_anything_h1\": {" + "\"endpoint\":" "\"httpbin.org\"," + "\"port\":" "443," + "\"protocol\":" "\"h1\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"anything?x=${hugearg}\"," + "\"nghttp2_quirk_end_stream\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"metadata\": [{" + "\"hugearg\":" "\"\"" + "}]," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"arca1\"" + "}},{" + "\"httpbin_anything_h2\": {" + "\"endpoint\":" "\"httpbin.org\"," + "\"port\":" "443," + "\"protocol\":" "\"h2\"," + "\"http_method\":" "\"GET\"," + "\"http_url\":" "\"anything?x=${hugearg}\"," + "\"nghttp2_quirk_end_stream\":" "true," + "\"h2q_oflow_txcr\":" "true," + "\"metadata\": [{" + "\"hugearg\":" "\"\"" + "}]," + "\"tls\":" "true," + "\"opportunistic\":" "true," + "\"retry\":" "\"default\"," + "\"tls_trust_store\":" "\"arca1\"" + "}},{" + /* + * "captive_portal_detect" describes + * what to do in order to check if the path to + * the Internet is being interrupted by a + * captive portal. If there's a larger policy + * fetched from elsewhere, it should also include + * this since it needs to be done at least after + * every DHCP acquisition + */ + "\"captive_portal_detect\": {" + "\"endpoint\": \"connectivitycheck.android.com\"," + "\"http_url\": \"generate_204\"," + "\"port\": 80," + "\"protocol\": \"h1\"," + "\"http_method\": \"GET\"," + "\"opportunistic\": true," + "\"http_expect\": 204," + "\"http_fail_redirect\": true" + "}}" + "]}" +; + +#endif + +typedef struct myss { + struct lws_ss_handle *ss; + void *opaque_data; + /* ... application specific state ... */ + lws_sorted_usec_list_t sul; + struct lejp_ctx ctx; + size_t comp; + + char started; +} myss_t; + + +static const char * const lejp_tokens[] = { + "url" +}; + +/* + * Parse the "url" member of the JSON, and collect the part after the first '=' + * into the prepared buffer "check". + */ + +static signed char +lws_httpbin_json_cb(struct lejp_ctx *ctx, char reason) +{ + myss_t *m = (myss_t *)ctx->user; + const char *p = ctx->buf; + size_t l = ctx->npos; + + if (!(reason & LEJP_FLAG_CB_IS_VALUE)) + return 0; + + if (ctx->path_match - 1) + return 0; + + if (!m->started) + while (l--) + if (*p++ == '=') { + m->started = 1; + break; + } + + if (!m->started) + return 0; + + if (m->comp + l > hugeurl_size) { + lwsl_err("%s: returned url string too large %u, %u\n", + __func__, (unsigned int)m->comp, (unsigned int)l); + + return -1; + } + + memcpy(check + m->comp, p, l); + m->comp += l; + + return 0; +} + +/* secure streams payload interface */ + +static lws_ss_state_return_t +myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) +{ + myss_t *m = (myss_t *)userobj; + + if (flags & LWSSS_FLAG_SOM) + lejp_construct(&m->ctx, lws_httpbin_json_cb, m, + lejp_tokens, LWS_ARRAY_SIZE(lejp_tokens)); + + if (len) { + int pr = lejp_parse(&m->ctx, buf, (int)len); + + if (pr != LEJP_CONTINUE && pr < 0) { + lwsl_err("%s: parse failed line %u: %d: %s\n", __func__, + (unsigned int)m->ctx.line, pr, + lejp_error_to_string(pr)); + + return LWSSSSRET_DESTROY_ME; + } + } + + if (flags & LWSSS_FLAG_EOM) { + + interrupted = 1; + + /* confirm that what we collected is the expected size */ + + if (m->comp != hugeurl_size) { + lwsl_err("%s: wrong urlarg size recovered %d %d\n", + __func__, (int)m->comp, (int)hugeurl_size); + return LWSSSSRET_OK; + } + + /* confirm what we sent is the same as what we collected */ + + if (memcmp(hugeurl, check, hugeurl_size)) { + lwsl_err("%s: huge url content mismatch\n", __func__); + + return LWSSSSRET_OK; + } + + lwsl_user("%s: return hugeurl len %u matches OK\n", __func__, + (unsigned int)hugeurl_size); + + bad = 0; + } + + return LWSSSSRET_OK; +} + +static lws_ss_state_return_t +myss_state(void *userobj, void *sh, lws_ss_constate_t state, + lws_ss_tx_ordinal_t ack) +{ + myss_t *m = (myss_t *)userobj; + + lwsl_user("%s: %s (%d), ord 0x%x\n", __func__, + lws_ss_state_name((int)state), state, (unsigned int)ack); + + switch (state) { + case LWSSSCS_CREATING: + lws_ss_start_timeout(m->ss, timeout_ms); + + /* let's make the hugeurl part */ + + hugeurl = malloc(hugeurl_size + 1); + if (!hugeurl) { + lwsl_err("OOM\n"); + return LWSSSSRET_DESTROY_ME; + } + + check = malloc(hugeurl_size + 1); + if (!check) { + lwsl_err("OOM\n"); + free(hugeurl); + hugeurl = NULL; + return LWSSSSRET_DESTROY_ME; + } + + /* Create the big, random, urlarg */ + + lws_hex_random(lws_ss_get_context(m->ss), hugeurl, + hugeurl_size + 1); + lws_ss_set_metadata(m->ss, "hugearg", hugeurl, hugeurl_size); + + return lws_ss_client_connect(m->ss); + + case LWSSSCS_ALL_RETRIES_FAILED: + /* if we're out of retries, we want to close the app and FAIL */ + interrupted = 1; + break; + case LWSSSCS_QOS_ACK_REMOTE: + lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); + break; + + case LWSSSCS_TIMEOUT: + lwsl_notice("%s: LWSSSCS_TIMEOUT\n", __func__); + break; + + case LWSSSCS_USER_BASE: + lwsl_notice("%s: LWSSSCS_USER_BASE\n", __func__); + break; + + default: + break; + } + + return LWSSSSRET_OK; +} + +static lws_ss_info_t ssi = { + .handle_offset = offsetof(myss_t, ss), + .opaque_user_data_offset = offsetof(myss_t, opaque_data), + .rx = myss_rx, + .state = myss_state, + .user_alloc = sizeof(myss_t), + .streamtype = "httpbin_anything_h2" +}; + +static int +app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, + int current, int target) +{ + struct lws_context *context = lws_system_context_from_system_mgr(mgr); + + /* + * For the things we care about, let's notice if we are trying to get + * past them when we haven't solved them yet, and make the system + * state wait while we trigger the dependent action. + */ + if (target != LWS_SYSTATE_OPERATIONAL) + return 0; + + if (current != LWS_SYSTATE_OPERATIONAL) + return 0; + + if (h1) + ssi.streamtype = "httpbin_anything_h1"; + + if (!lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) + return 0; + + lwsl_err("%s: failed to create secure stream\n", __func__); + + return -1; +} + +static lws_state_notify_link_t * const app_notifier_list[] = { + &nl, NULL +}; + +static void +sigint_handler(int sig) +{ + interrupted = 1; +} + +int main(int argc, const char **argv) +{ + struct lws_context_creation_info info; + struct lws_context *context; + const char *p; + int n = 0; + + signal(SIGINT, sigint_handler); + + memset(&info, 0, sizeof info); + lws_cmdline_option_handle_builtin(argc, argv, &info); + + lwsl_user("LWS secure streams hugeurl test client [-d][-h ]\n"); + + info.fd_limit_per_thread = 1 + 6 + 1; + info.port = CONTEXT_PORT_NO_LISTEN; +#if defined(LWS_SS_USE_SSPC) + info.protocols = lws_sspc_protocols; + + /* connect to ssproxy via UDS by default, else via + * tcp connection to this port */ + if ((p = lws_cmdline_option(argc, argv, "-p"))) + info.ss_proxy_port = (uint16_t)atoi(p); + + /* UDS "proxy.ss.lws" in abstract namespace, else this socket + * path; when -p given this can specify the network interface + * to bind to */ + if ((p = lws_cmdline_option(argc, argv, "-i"))) + info.ss_proxy_bind = p; + + /* if -p given, -a specifies the proxy address to connect to */ + if ((p = lws_cmdline_option(argc, argv, "-a"))) + info.ss_proxy_address = p; +#else + info.pss_policies_json = default_ss_policy; + info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW | + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; +#endif + + if (lws_cmdline_option(argc, argv, "--h1")) + h1 = 1; + + if ((p = lws_cmdline_option(argc, argv, "-h"))) + hugeurl_size = (size_t)atol(p); + + if (hugeurl_size < 1 || hugeurl_size > 16384) { + lwsl_err("%s: -h should be between 1 and 16384\n", __func__); + return 1; + } + + lwsl_user("%s: huge argument size: %u bytes\n", __func__, + (unsigned int)hugeurl_size); + + info.pt_serv_buf_size = (unsigned int)((hugeurl_size * 2) + 2048); + info.max_http_header_data = (unsigned short)(hugeurl_size + 2048); + + /* integrate us with lws system state management when context created */ + + nl.name = "app"; + nl.notify_cb = app_system_state_nf; + info.register_notifier_list = app_notifier_list; + + /* create the context */ + + context = lws_create_context(&info); + if (!context) { + lwsl_err("lws init failed\n"); + return 1; + } + + /* the event loop */ + + while (n >= 0 && !interrupted) + n = lws_service(context, 0); + + lws_context_destroy(context); + + if (hugeurl) + free(hugeurl); + if (check) + free(check); + + lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); + + return bad; +} diff --git a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c index 522b6728b..b8f428f70 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c +++ b/minimal-examples/secure-streams/minimal-secure-streams-proxy/main.c @@ -275,6 +275,9 @@ int main(int argc, const char **argv) nl.notify_cb = app_system_state_nf; info.register_notifier_list = app_notifier_list; + info.pt_serv_buf_size = (unsigned int)((6144 * 2) + 2048); + info.max_http_header_data = (unsigned short)(6144 + 2048); + context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); diff --git a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c index a40c0ee1c..cd3f73adc 100644 --- a/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c +++ b/minimal-examples/secure-streams/minimal-secure-streams/minimal-secure-streams.c @@ -422,6 +422,7 @@ int main(int argc, const char **argv) #else info.pss_policies_json = default_ss_policy; info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | + LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; #endif #if defined(LWS_WITH_DETAILED_LATENCY)