1/* 2 * lws-minimal-secure-streams-hugeurl 3 * 4 * Written in 2010-2021 by Andy Green <andy@warmcat.com> 5 * 6 * This file is made available under the Creative Commons CC0 1.0 7 * Universal Public Domain Dedication. 8 * 9 * 10 * This checks huge url operations via httpbin.org 11 */ 12 13#include <libwebsockets.h> 14#include <string.h> 15#include <signal.h> 16 17static unsigned int timeout_ms = 3000; 18static int interrupted, bad = 1, h1; 19static lws_state_notify_link_t nl; 20static size_t hugeurl_size = 4000; 21static char *hugeurl, *check; 22 23#if !defined(LWS_SS_USE_SSPC) 24static const char * const default_ss_policy = 25 "{" 26 "\"release\":" "\"01234567\"," 27 "\"product\":" "\"myproduct\"," 28 "\"schema-version\":" "1," 29#if defined(VIA_LOCALHOST_SOCKS) 30 "\"via-socks5\":" "\"127.0.0.1:1080\"," 31#endif 32 33 "\"retry\": [" /* named backoff / retry strategies */ 34 "{\"default\": {" 35 "\"backoff\": [" "1000," 36 "2000," 37 "3000," 38 "5000," 39 "10000" 40 "]," 41 "\"conceal\":" "5," 42 "\"jitterpc\":" "20," 43 "\"svalidping\":" "30," 44 "\"svalidhup\":" "35" 45 "}}" 46 "]," 47 "\"certs\": [" /* named individual certificates in BASE64 DER */ 48 /* 49 * Let's Encrypt certs for warmcat.com / libwebsockets.org 50 * 51 * We fetch the real policy from there using SS and switch to 52 * using that. 53 */ 54 "{\"amazon_root_ca_1\": \"" 55 "MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0" 56 "BAQsFADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQ" 57 "QDExBBbWF6b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExN" 58 "zAwMDAwMFowOTELMAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcG" 59 "A1UEAxMQQW1hem9uIFJvb3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggE" 60 "PADCCAQoCggEBALJ4gHHKeNXjca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrA" 61 "IthtOgQ3pOsqTQNroBvo3bSMgHFzZM9O6II8c+6zf1tRn4SWiw3te5djgdY" 62 "Z6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qwIFAGbHrQgLKm+a/sRxmPUDgH" 63 "3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6VOujw5H5SNz/0egwLX0" 64 "tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L93FcXmn/6pUCyz" 65 "iKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQmjgSubJrIq" 66 "g0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw" 67 "HQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwU" 68 "AA4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9r" 69 "bxenDIU5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/m" 70 "sv0tadQ1wUsN+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96L" 71 "XFvKWlJbYK8U90vvo/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bld" 72 "ZwgJcJmApzyMZFo6IQ6XU5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8o" 73 "b2xJNDd2ZhwLnoQdeXeGADbkpyrqXRfboQnoZsG4q5WTP468SQvvG5" 74 "\"}" 75 "]," 76 "\"trust_stores\": [" /* named cert chains */ 77 "{" 78 "\"name\": \"arca1\"," 79 "\"stack\": [" 80 "\"amazon_root_ca_1\"" 81 "]" 82 "}" 83 "]," 84 "\"s\": [{" 85 86 "\"httpbin_anything_h1\": {" 87 "\"endpoint\":" "\"httpbin.org\"," 88 "\"port\":" "443," 89 "\"protocol\":" "\"h1\"," 90 "\"http_method\":" "\"GET\"," 91 "\"http_url\":" "\"anything?x=${hugearg}\"," 92 "\"nghttp2_quirk_end_stream\":" "true," 93 "\"h2q_oflow_txcr\":" "true," 94 "\"metadata\": [{" 95 "\"hugearg\":" "\"\"" 96 "}]," 97 "\"tls\":" "true," 98 "\"opportunistic\":" "true," 99 "\"retry\":" "\"default\"," 100 "\"tls_trust_store\":" "\"arca1\"" 101 "}},{" 102 "\"httpbin_anything_h2\": {" 103 "\"endpoint\":" "\"httpbin.org\"," 104 "\"port\":" "443," 105 "\"protocol\":" "\"h2\"," 106 "\"http_method\":" "\"GET\"," 107 "\"http_url\":" "\"anything?x=${hugearg}\"," 108 "\"nghttp2_quirk_end_stream\":" "true," 109 "\"h2q_oflow_txcr\":" "true," 110 "\"metadata\": [{" 111 "\"hugearg\":" "\"\"" 112 "}]," 113 "\"tls\":" "true," 114 "\"opportunistic\":" "true," 115 "\"retry\":" "\"default\"," 116 "\"tls_trust_store\":" "\"arca1\"" 117 "}},{" 118 /* 119 * "captive_portal_detect" describes 120 * what to do in order to check if the path to 121 * the Internet is being interrupted by a 122 * captive portal. If there's a larger policy 123 * fetched from elsewhere, it should also include 124 * this since it needs to be done at least after 125 * every DHCP acquisition 126 */ 127 "\"captive_portal_detect\": {" 128 "\"endpoint\": \"connectivitycheck.android.com\"," 129 "\"http_url\": \"generate_204\"," 130 "\"port\": 80," 131 "\"protocol\": \"h1\"," 132 "\"http_method\": \"GET\"," 133 "\"opportunistic\": true," 134 "\"http_expect\": 204," 135 "\"http_fail_redirect\": true" 136 "}}" 137 "]}" 138; 139 140#endif 141 142typedef struct myss { 143 struct lws_ss_handle *ss; 144 void *opaque_data; 145 /* ... application specific state ... */ 146 lws_sorted_usec_list_t sul; 147 struct lejp_ctx ctx; 148 size_t comp; 149 150 char started; 151} myss_t; 152 153 154static const char * const lejp_tokens[] = { 155 "url" 156}; 157 158/* 159 * Parse the "url" member of the JSON, and collect the part after the first '=' 160 * into the prepared buffer "check". 161 */ 162 163static signed char 164lws_httpbin_json_cb(struct lejp_ctx *ctx, char reason) 165{ 166 myss_t *m = (myss_t *)ctx->user; 167 const char *p = ctx->buf; 168 size_t l = ctx->npos; 169 170 if (!(reason & LEJP_FLAG_CB_IS_VALUE)) 171 return 0; 172 173 if (ctx->path_match - 1) 174 return 0; 175 176 if (!m->started) 177 while (l--) 178 if (*p++ == '=') { 179 m->started = 1; 180 break; 181 } 182 183 if (!m->started) 184 return 0; 185 186 if (m->comp + l > hugeurl_size) { 187 lwsl_err("%s: returned url string too large %u, %u\n", 188 __func__, (unsigned int)m->comp, (unsigned int)l); 189 190 return -1; 191 } 192 193 memcpy(check + m->comp, p, l); 194 m->comp += l; 195 196 return 0; 197} 198 199/* secure streams payload interface */ 200 201static lws_ss_state_return_t 202myss_rx(void *userobj, const uint8_t *buf, size_t len, int flags) 203{ 204 myss_t *m = (myss_t *)userobj; 205 206 if (flags & LWSSS_FLAG_SOM) 207 lejp_construct(&m->ctx, lws_httpbin_json_cb, m, 208 lejp_tokens, LWS_ARRAY_SIZE(lejp_tokens)); 209 210 if (len) { 211 int pr = lejp_parse(&m->ctx, buf, (int)len); 212 213 if (pr != LEJP_CONTINUE && pr < 0) { 214 lwsl_err("%s: parse failed line %u: %d: %s\n", __func__, 215 (unsigned int)m->ctx.line, pr, 216 lejp_error_to_string(pr)); 217 218 return LWSSSSRET_DESTROY_ME; 219 } 220 } 221 222 if (flags & LWSSS_FLAG_EOM) { 223 224 interrupted = 1; 225 226 /* confirm that what we collected is the expected size */ 227 228 if (m->comp != hugeurl_size) { 229 lwsl_err("%s: wrong urlarg size recovered %d %d\n", 230 __func__, (int)m->comp, (int)hugeurl_size); 231 return LWSSSSRET_OK; 232 } 233 234 /* confirm what we sent is the same as what we collected */ 235 236 if (memcmp(hugeurl, check, hugeurl_size)) { 237 lwsl_err("%s: huge url content mismatch\n", __func__); 238 239 return LWSSSSRET_OK; 240 } 241 242 lwsl_user("%s: return hugeurl len %u matches OK\n", __func__, 243 (unsigned int)hugeurl_size); 244 245 bad = 0; 246 } 247 248 return LWSSSSRET_OK; 249} 250 251static lws_ss_state_return_t 252myss_state(void *userobj, void *sh, lws_ss_constate_t state, 253 lws_ss_tx_ordinal_t ack) 254{ 255 myss_t *m = (myss_t *)userobj; 256 257 lwsl_user("%s: %s (%d), ord 0x%x\n", __func__, 258 lws_ss_state_name((int)state), state, (unsigned int)ack); 259 260 switch (state) { 261 case LWSSSCS_CREATING: 262 lws_ss_start_timeout(m->ss, timeout_ms); 263 264 /* let's make the hugeurl part */ 265 266 hugeurl = malloc(hugeurl_size + 1); 267 if (!hugeurl) { 268 lwsl_err("OOM\n"); 269 return LWSSSSRET_DESTROY_ME; 270 } 271 272 check = malloc(hugeurl_size + 1); 273 if (!check) { 274 lwsl_err("OOM\n"); 275 free(hugeurl); 276 hugeurl = NULL; 277 return LWSSSSRET_DESTROY_ME; 278 } 279 280 /* Create the big, random, urlarg */ 281 282 lws_hex_random(lws_ss_get_context(m->ss), hugeurl, 283 hugeurl_size + 1); 284 if (lws_ss_set_metadata(m->ss, "hugearg", hugeurl, hugeurl_size)) 285 return LWSSSSRET_DISCONNECT_ME; 286 287 return lws_ss_client_connect(m->ss); 288 289 case LWSSSCS_ALL_RETRIES_FAILED: 290 /* if we're out of retries, we want to close the app and FAIL */ 291 interrupted = 1; 292 break; 293 case LWSSSCS_QOS_ACK_REMOTE: 294 lwsl_notice("%s: LWSSSCS_QOS_ACK_REMOTE\n", __func__); 295 break; 296 297 case LWSSSCS_TIMEOUT: 298 lwsl_notice("%s: LWSSSCS_TIMEOUT\n", __func__); 299 break; 300 301 case LWSSSCS_USER_BASE: 302 lwsl_notice("%s: LWSSSCS_USER_BASE\n", __func__); 303 break; 304 305 default: 306 break; 307 } 308 309 return LWSSSSRET_OK; 310} 311 312static lws_ss_info_t ssi = { 313 .handle_offset = offsetof(myss_t, ss), 314 .opaque_user_data_offset = offsetof(myss_t, opaque_data), 315 .rx = myss_rx, 316 .state = myss_state, 317 .user_alloc = sizeof(myss_t), 318 .streamtype = "httpbin_anything_h2" 319}; 320 321static int 322app_system_state_nf(lws_state_manager_t *mgr, lws_state_notify_link_t *link, 323 int current, int target) 324{ 325 struct lws_context *context = lws_system_context_from_system_mgr(mgr); 326 327 /* 328 * For the things we care about, let's notice if we are trying to get 329 * past them when we haven't solved them yet, and make the system 330 * state wait while we trigger the dependent action. 331 */ 332 if (target != LWS_SYSTATE_OPERATIONAL) 333 return 0; 334 335 if (current != LWS_SYSTATE_OPERATIONAL) 336 return 0; 337 338 if (h1) 339 ssi.streamtype = "httpbin_anything_h1"; 340 341 if (!lws_ss_create(context, 0, &ssi, NULL, NULL, NULL, NULL)) 342 return 0; 343 344 lwsl_err("%s: failed to create secure stream\n", __func__); 345 346 return -1; 347} 348 349static lws_state_notify_link_t * const app_notifier_list[] = { 350 &nl, NULL 351}; 352 353static void 354sigint_handler(int sig) 355{ 356 interrupted = 1; 357} 358 359int main(int argc, const char **argv) 360{ 361 struct lws_context_creation_info info; 362 struct lws_context *context; 363 const char *p; 364 int n = 0; 365 366 signal(SIGINT, sigint_handler); 367 368 memset(&info, 0, sizeof info); 369 lws_cmdline_option_handle_builtin(argc, argv, &info); 370 371 lwsl_user("LWS secure streams hugeurl test client [-d<verb>][-h <urlarg len>]\n"); 372 373 info.fd_limit_per_thread = 1 + 6 + 1; 374 info.port = CONTEXT_PORT_NO_LISTEN; 375#if defined(LWS_SS_USE_SSPC) 376 info.protocols = lws_sspc_protocols; 377 378 /* connect to ssproxy via UDS by default, else via 379 * tcp connection to this port */ 380 if ((p = lws_cmdline_option(argc, argv, "-p"))) 381 info.ss_proxy_port = (uint16_t)atoi(p); 382 383 /* UDS "proxy.ss.lws" in abstract namespace, else this socket 384 * path; when -p given this can specify the network interface 385 * to bind to */ 386 if ((p = lws_cmdline_option(argc, argv, "-i"))) 387 info.ss_proxy_bind = p; 388 389 /* if -p given, -a specifies the proxy address to connect to */ 390 if ((p = lws_cmdline_option(argc, argv, "-a"))) 391 info.ss_proxy_address = p; 392#else 393 info.pss_policies_json = default_ss_policy; 394 info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS | 395 LWS_SERVER_OPTION_H2_JUST_FIX_WINDOW_UPDATE_OVERFLOW | 396 LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; 397#endif 398 399 if (lws_cmdline_option(argc, argv, "--h1")) 400 h1 = 1; 401 402 if ((p = lws_cmdline_option(argc, argv, "-h"))) 403 hugeurl_size = (size_t)atol(p); 404 405 if (hugeurl_size < 1 || hugeurl_size > 16384) { 406 lwsl_err("%s: -h should be between 1 and 16384\n", __func__); 407 return 1; 408 } 409 410 lwsl_user("%s: huge argument size: %u bytes\n", __func__, 411 (unsigned int)hugeurl_size); 412 413 info.pt_serv_buf_size = (unsigned int)((hugeurl_size * 2) + 2048); 414 info.max_http_header_data = (unsigned short)(hugeurl_size + 2048); 415 416 /* integrate us with lws system state management when context created */ 417 418 nl.name = "app"; 419 nl.notify_cb = app_system_state_nf; 420 info.register_notifier_list = app_notifier_list; 421 422 /* create the context */ 423 424 context = lws_create_context(&info); 425 if (!context) { 426 lwsl_err("lws init failed\n"); 427 return 1; 428 } 429 430 /* the event loop */ 431 432 while (n >= 0 && !interrupted) 433 n = lws_service(context, 0); 434 435 lws_context_destroy(context); 436 437 if (hugeurl) 438 free(hugeurl); 439 if (check) 440 free(check); 441 442 lwsl_user("Completed: %s\n", bad ? "failed" : "OK"); 443 444 return bad; 445} 446